0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

linux内核中的SPI框架及SPI核心的初始化简析

嵌入式小生 来源:嵌入式小生 2023-12-11 09:10 次阅读

一、linux内核中的SPI框架

嵌入式linux开发中,SPI是一种常见的通信方式,如下图所示:

58d7b654-97ba-11ee-8b88-92fbcf53809c.png

常见的属于SPI设备包括:RF芯片智能卡、EEPROM、RTC、触摸传感器等等。

在内核中,与I2C一样,也同样提供了一个SPI框架,本文围绕这个框架展开,来分析内核提供的SPI框架是如何运作的。内核中与SPI相关的代码规范放置在/drivers/spi路径下,

58f01604-97ba-11ee-8b88-92fbcf53809c.png

从Makefile可知,内核提供的SPI框架主要实现在spi.c、spidev.c文件中。

spi.c文件实现了spi核心的初始化,以及实现spi框架的相关API接口。(如果想让系统支持spi,此文件必须被编译)

spidev.c文件用于实现SPI设备同步用户空间接口。(该文件为可选特性)

存在/drivers/spi路径下其他洋洋洒洒的文件则是不同厂家提供的SPI控制器的驱动程序,这些文件往往由芯片厂家开发,然后合并到linux内核源码中,以适配自家芯片。

二、SPI核心的初始化

SPI核心的初始化实现在/drivers/spi/spi.c文件中,如下代码:

staticint__initspi_init(void)
{
intstatus;

buf=kmalloc(SPI_BUFSIZ,GFP_KERNEL);
if(!buf){
status=-ENOMEM;
gotoerr0;
}

status=bus_register(&spi_bus_type);
if(status< 0)
  goto err1;

 status = class_register(&spi_master_class);
 if (status < 0)
  goto err2;

 if (IS_ENABLED(CONFIG_OF_DYNAMIC))
  WARN_ON(of_reconfig_notifier_register(&spi_of_notifier));

 return 0;

err2:
 bus_unregister(&spi_bus_type);
err1:
 kfree(buf);
 buf = NULL;
err0:
 return status;
}

/* board_info is normally registered in arch_initcall(),
 * but even essential drivers wait till later
 *
 * REVISIT only boardinfo really needs static linking. the rest (device and
 * driver registration) _could_ be dynamically linked (modular) ... costs
 * include needing to have boardinfo data structures be much more public.
 */
postcore_initcall(spi_init);

在postcore_initcall()导出的spi核心的初始化过程中,主要做了以下几件事情:

1、分配一个用于SPI的buffer。

2、注册spi总线。

3、注册spi_master主机类。

以上操作是内核中面向对象的基础构件过程,spi框架也不例外,也必须这样实现,以获得内核设备驱动模型的管理。

关于spi bus总线对设备和驱动的匹配过程:在spi/spic.c文件中定义了用于描述spi的bus总线,命名为spi,该总线在spi_init()函数中注册,匹配过程由spi_match_device()描述,该函数实现如下:

staticintspi_match_device(structdevice*dev,structdevice_driver*drv)
{
conststructspi_device*spi=to_spi_device(dev);
conststructspi_driver*sdrv=to_spi_driver(drv);

/*Checkoverridefirst,andifset,onlyusethenameddriver*/
if(spi->driver_override)
returnstrcmp(spi->driver_override,drv->name)==0;

/*AttemptanOFstylematch*/
if(of_driver_match_device(dev,drv))
return1;

/*ThentryACPI*/
if(acpi_driver_match_device(dev,drv))
return1;

if(sdrv->id_table)
return!!spi_match_id(sdrv->id_table,spi->modalias);

returnstrcmp(spi->modalias,drv->name)==0;
}

在上述代码中,描述了对spi设备和驱动匹配的四种方式。

三、SPI核心的数据结构

注意:几乎芯片原厂都要提供一个主机侧的SPI驱动,以支持自家的芯片。

编程接口是围绕两种驱动程序和两种设备构建。SPI控制器驱动程序抽象了控制器硬件,它可以像一组GPIO引脚一样简单,也可以像fifo一样复杂,也有可能支持DMA引擎(实现数据的最大化吞吐量)。这样的驱动程序在它们所在的总线(通常是平台总线)和SPI之间架桥,并将其设备的SPI端作为struct spi_controller公开。

SPI设备是主设备的子设备,由struct spi_device表示,并由struct spi_board_info描述符进行描述,这些描述符通常由特定板卡的初始化代码提供。

struct spi_driver称为协议驱动程序,并通过正常的驱动程序模型绑定到spi_device。

SPI的I/O模型是一组排队的消息,在协议驱动程序中可提交一个或多个struct spi_message对象,这些对象被异步处理和完成(包含同步包装器)。消息是从一个或多个struct spi_transfer对象构建,每个对象都封装了一个全双工SPI传输,在开发中需要对各种协议进行配置,因为不同的芯片采用不同的策略来使用SPI传输的数据。

1、struct spi_statistics

struct spi_statistics描述spi传输的统计信息

该结构中放置了几个u64_stats_t类型的数据,描述统计了spi传输的统计信息,该结构实现如下:

structspi_statistics{
structu64_stats_syncsyncp;//该参数用于保护这个结构体中的成员,在32位系统上实现per-cpu更新。

u64_stats_tmessages;//处理的spi消息数。
u64_stats_ttransfers;//处理的spi_transfer的数量。
u64_stats_terrors;//在spi_transfer过程中的错误数。
u64_stats_ttimedout;//spi_transfer期间的timeout。

u64_stats_tspi_sync;//使用spi_sync的次数。
u64_stats_tspi_sync_immediate;//立即执行spi_sync的次数(在调用上下文时不需要排队和调度)。
u64_stats_tspi_async;//使用spi_async的次数。

u64_stats_tbytes;//传输到/从设备接收的字节数。
u64_stats_tbytes_rx;//从设备接收的字节数。
u64_stats_tbytes_tx;//发送到设备的字节数。

#defineSPI_STATISTICS_HISTO_SIZE17
u64_stats_ttransfer_bytes_histo[SPI_STATISTICS_HISTO_SIZE];//用于描述直方图的数据数组。

u64_stats_ttransfers_split_maxsize;//传输数最大尺寸限制。
};

2、struct spi_delay

struct spi_delay用于描述SPI延时信息。

在linux内核中有特定的延时方法,但是spi框架基于udelay()实现了自己的延时,这个延时用于spi的数据传输,struct spi_delay实现如下:

structspi_delay{
#defineSPI_DELAY_UNIT_USECS0
#defineSPI_DELAY_UNIT_NSECS1
#defineSPI_DELAY_UNIT_SCK2
u16value;//延时的值。
u8unit;//延时的单位。
};

3、struct spi_device

struct spi_device用于描述控制器端SPI从设备。

该数据结构为linux内核spi子系统的内部结构。

struct spi_device定义如下:

structspi_device{
structdevicedev;//设备的驱动模型表示。
structspi_controller*controller;//与设备配套使用的SPI控制器。
structspi_controller*master;//控制器的副本(用于为了实现向后兼容)。
u32max_speed_hz;//此芯片(在此板上)使用的最大时钟速率;可能由设备的驱动程序更改。```spi_transfer.speed_hz```可以在每次传输时覆盖此设置。
u8chip_select;//芯片选择,用于区分由控制器处理的芯片。
u8bits_per_word;//表示数据传输涉及的字长,例如8位或12位这样的字长很常见。可以在每次传输spi_transfer.bits_per_word时重写此设置。
boolrt;//该参数用于开始是否开启实时线程优先特性。
#defineSPI_NO_TXBIT(31);
#defineSPI_NO_RXBIT(30);
#defineSPI_TPM_HW_FLOWBIT(29);
#defineSPI_MODE_KERNEL_MASK(~(BIT(29)-1));
u32mode;
intirq;//该参数可能为负值,或者传递给request_irq()以接收来自该设备的中断的数字。
void*controller_state;//控制器的运行状态。
void*controller_data;//特定于主板的控制器定义,例如FIFO初始化参数;来自于board_info.controller_data。
charmodalias[SPI_NAME_SIZE];//要与此设备一起使用的驱动程序名称,或该名称的别名。这出现在用于驱动冷插拔的sysfs的“modalias”属性中,以及用于热插拔的事件中。
constchar*driver_override;//如果将驱动程序的名称写入此属性,则设备将绑定到命名的驱动程序,并且仅绑定到命名的驱动程序。
structgpio_desc*cs_gpiod;//芯片选择线(CS)的GPIO描述符(该参数可选,如果不使用GPIO line该参数为NULL)。
structspi_delayword_delay;//表示在传送的连续字(Word)之间插入的延迟。
structspi_delaycs_setup;//表示在CS被断言后由控制器引入的延迟。
structspi_delaycs_hold;//表示控制器在CS解除断言之前引入的延迟。
structspi_delaycs_inactive;//CS解除断言后控制器引入的延时。如果在spi_transfer中使用cs_change_delay,则两个延迟将相加。
structspi_statistics__percpu*pcpu_statistics;//表示spi_device的统计信息。
};

4、struct spi_driver

struct spi_driver用于描述主机端“协议”驱动程序。

为什么叫“协议”驱动程序,实属不易理解,这个词我是从官方文档中的描述(“protocol”)直接音译过来,因为这个结构主要用于基于spi总线协议通信的从设备。

struct spi_driver结构实现如下:

structspi_driver{
conststructspi_device_id*id_table;//描述这个驱动程序支持的SPI设备列表。
int(*probe)(structspi_device*spi);//用于将此驱动程序绑定到SPI设备。驱动程序可以验证设备是否实际存在,可能需要配置不需要的特征(例如bits_per_word)。用于系统启动过程中完成初始配置。
void(*remove)(structspi_device*spi);//从SPI设备解除与这个驱动程序的绑定。
void(*shutdown)(structspi_device*spi);//在系统状态转换期间使用的标准关机回调,如powerdown/halt和kexec。
structdevice_driverdriver;//SPI设备驱动程序应该初始化此结构的name和owner字段。
};

5、struct spi_controller

struct spi_controller描述到SPI主或从控制器的接口。

每个SPI控制器可以与一个或多个spi_device代表的子设备通信。这些设备通常使用4线spi总线:共享MOSI, MISO和SCK信号,但不共享芯片选择信号。每个设备可以配置为使用不同的时钟速率。

SPI控制器的驱动程序通过spi_message事务队列管理对这些设备的访问,在CPU内存和SPI从设备之间复制数据。对于它所排队的每条这样的消息,在事务完成时将调用spi_message的*complete回调函数。

struct spi_controller实现如下:

structspi_controller{
structdevicedev;//此驱动程序的设备接口。
structlist_headlist;//链接到全局spi_controller列表。
s16bus_num;//给定SPI控制器的特定板级标识符。
u16num_chipselect;//chipselects用于区分各个SPI从机,编号从0到num_chipselects。每个从机都有一个芯片选择信号。
u16dma_alignment;//SPI控制器对DMA缓冲区对齐的约束。
u32mode_bits;//由控制器驱动程序解析的标志。
u32buswidth_override_bits;//要覆盖此控制器驱动程序的标志
u32bits_per_word_mask;//一个掩码参数,指示驱动程序支持bits_per_word的哪些值,第n位表示支持的bits_per_word为n+1
#defineSPI_BPW_MASK(bits)BIT((bits)-1);
#defineSPI_BPW_RANGE_MASK(min,max)GENMASK((max)-1,(min)-1);
u32min_speed_hz;//最低支持的传输速度。
u32max_speed_hz;//最高支持的传输速度
u16flags;//与此驱动程序相关的其他约束标志
#defineSPI_CONTROLLER_HALF_DUPLEXBIT(0);
#defineSPI_CONTROLLER_NO_RXBIT(1);
#defineSPI_CONTROLLER_NO_TXBIT(2);
#defineSPI_CONTROLLER_MUST_RXBIT(3);
#defineSPI_CONTROLLER_MUST_TXBIT(4);
#defineSPI_CONTROLLER_GPIO_SSBIT(5);
booldevm_allocated;//该结构体的分配是否为线程管理
union{
boolslave;//表示这是一个SPI从控制器
booltarget;//表示这是一个SPI目标控制器
};
size_t(*max_transfer_size)(structspi_device*spi);//返回spi_device的最大传输大小的回调函数;该回调函数指针可能为NULL,这时候将使用默认的SIZE_MAX。
size_t(*max_message_size)(structspi_device*spi);//返回spi_device的最大消息大小的回函数调;该回调函数可能为NULL,这时候将使用默认的SIZE_MAX。
structmutexio_mutex;//用于物理总线访问的互斥锁。
structmutexadd_lock;//该互斥锁用于避免将设备添加到相同的芯片选择。
spinlock_tbus_lock_spinlock;//用于SPI总线锁定的自旋锁。
structmutexbus_lock_mutex;//该互斥锁用于排除多个调用者。
boolbus_lock_flag;//表示SPI总线为独占使用而被锁定。
int(*setup)(structspi_device*spi);//更新设备的SPI控制器使用的设备模式和时钟记录;协议代码可以调用这个。如果请求无法识别或不支持的模式,则此操作必须失败。
int(*set_cs_timing)(structspi_device*spi);//SPI设备请求SPI主控制器配置特定的CS设置时间,保持时间和非活动延迟的时钟计数的回调函数。该函数可选。
int(*transfer)(structspi_device*spi,structspi_message*mesg);//将消息添加到控制器的传输队列。
void(*cleanup)(structspi_device*spi);//释放特定于控制器的状态
bool(*can_dma)(structspi_controller*ctlr,structspi_device*spi,structspi_transfer*xfer);//判断该控制器是否支持DMA
structdevice*dma_map_dev;//可以用于DMA映射的设备。
structdevice*cur_rx_dma_dev;//当前用于RX DMA映射的设备。
structdevice*cur_tx_dma_dev;//当前用于TX DMA映射的设备。
boolqueued;//此控制器是否提供内部消息队列。
structkthread_worker*kworker;//指向消息pump的线程结构的指针。
structkthread_workpump_messages;//将工作安排到消息pump的工作结构。
spinlock_tqueue_lock;//该自旋锁用于同步对消息队列的访问。
structlist_headqueue;//消息队列。
structspi_message*cur_msg;//当前正在传输的消息。
structcompletioncur_msg_completion;//完成当前正在运行的消息。
boolcur_msg_incomplete;//内部使用标志,用于跳过cur_msg_completion。此标志用于检查驱动程序是否已经调用了spi_finalize_current_message()。
boolcur_msg_need_completion;//内部使用标志,用于跳过cur_msg_completion。此标志用于通知正在运行spi_finalize_current_message()的上下文,它需要complete()
boolbusy;//用于描述消息pump是否busy。
boolrunning;//用于描述消息pump是否running。
boolrt;//是否将此队列设置为作为实时任务运行。
boolauto_runtime_pm;//该标志用于描述是否内核应该确保在硬件准备好时保持运行时PM的引用,使用父设备进行扩展。
boolcur_msg_mapped;//用于描述消息是否已映射为DMA。
charlast_cs;//表示set_cs记录的最后一个chip_select,在非芯片选择时值为-1。
boollast_cs_mode_high;
boolfallback;
structcompletionxfer_completion;//该参数由transfer_one_message()使用
size_tmax_dma_len;//设备DMA传输的最大长度。
int(*prepare_transfer_hardware)(structspi_controller*ctlr);//spi子系统请求驱动程序通过发出此调用来准备传输硬件
int(*transfer_one_message)(structspi_controller*ctlr,structspi_message*mesg);//spi子系统调用驱动程序来传输单个消息,同时对到达的传输进行排队。
当驱动程序处理完这个消息后,必须调用spi_finalize_current_message(),这样spi子系统才能发出下一个消息。
int(*unprepare_transfer_hardware)(structspi_controller*ctlr);//当前队列上没有更多的消息时,spi子系统将通知驱动程序,spi子系统通过调用该回调来释放硬件。
int(*prepare_message)(structspi_controller*ctlr,structspi_message*message);//该回调用于设置控制器以传输单个消息,例如:进行DMA映射,该回调在线程上下文中调用。
int(*unprepare_message)(structspi_controller*ctlr,structspi_message*message);//撤销prepare_message()所做的所有操作。
union{
int(*slave_abort)(structspi_controller*ctlr);//该回调用于中止SPI从控制器上正在进行的传输请求。
int(*target_abort)(structspi_controller*ctlr);//该回调用于中止在SPI目标控制器上正在进行的传输请求
};
void(*set_cs)(structspi_device*spi,boolenable);//设置芯片选择线(CS)的逻辑电平。可以从中断上下文调用。

//传输单个spi_transfer。如果传输完成,返回0;如果传输仍在进行中,返回1。当驱动程序完成此传输时,它必须调用spi_finalize_current_transfer(),以便子系统可以发出下一次传输。
int(*transfer_one)(structspi_controller*ctlr,structspi_device*spi,structspi_transfer*transfer);

//spi子系统调用驱动程序中的该函数来处理在transfer_one_message()中发生的错误。
void(*handle_err)(structspi_controller*ctlr,structspi_message*message);
conststructspi_controller_mem_ops*mem_ops;//与SPI内存交互的优化/专用操作。该字段可选,只有在控制器具有内存类操作的原生支持时才应该实现。
conststructspi_controller_mem_caps*mem_caps;//处理内存操作的控制器能力。
structgpio_desc**cs_gpiods;//用作芯片选择线的GPIO描述符数组;每个CS号码一个。对于非gpio(由SPI控制器本身驱动)的CS线,任何单个值都可能为NULL。
booluse_gpio_descriptors;//是否打开SPI核心中的代码来解析和获取GPIO描述符,此后将填充cs_gpiods,如果为芯片选择找到了GPIO线,SPI设备将分配cs_gpiods。
s8unused_native_cs;//当使用cs_gpiods时,spi_register_controller()将用第一个未使用的原生CS填充此字段,供在使用GPIO CS时需要驱动原生CS的SPI控制器驱动程序使用。
s8max_native_cs;//当使用cs_gpiods并填充此字段时,spi_register_controller()将根据此值验证所有原生CS(包括未使用的原生CS)。
structspi_statistics__percpu*pcpu_statistics;//为spi_controller的统计。
structdma_chan*dma_tx;//DMA传输通道。
structdma_chan*dma_rx;//DMA接收通道。
void*dummy_rx;//全双工设备的虚拟接收缓冲区
void*dummy_tx;//全双工设备的虚拟传输缓冲器
int(*fw_translate_cs)(structspi_controller*ctlr,unsignedcs);//如果引导固件使用与Linux期望编号方案不同,则可以使用这个可选回调在二者之间进行转换。
boolptp_sts_supported;//如果驱动程序将其设置为true,则驱动程序必须在spi_transfer->ptp_sts中提供一个时间快照,尽可能接近spi_transfer->ptp_sts_word_pre和spi_transfer->ptp_sts_word_post传输的时刻。如果驱动程序没有设置这个参数,SPI核心将尽可能接近驱动程序移交的快照。
unsignedlongirq_flags;//表示PTP系统时间戳期间中断使能状态。
boolqueue_empty;//该参数用于表示消息队列是否为空。
boolmust_async;//该参数用于表示是否关闭spi框架的所有快速路径操作。
};

struct spi_controller中的组成元素算比较多的了。

备注:在旧版的linux内核中使用struct spi_master描述SPI控制器。在较新的linux内核版本中,使用struct spi_controller替换了struct spi_master。

6、struct spi_res

struct spi_res用于描述资源管理结构,侧重于在spi_message处理期间的生命周期管理。该结构定义如下:

structspi_res{
structlist_headentry;//资源链表项
spi_res_release_trelease;//释放此资源之前调用的释放代码
unsignedlonglongdata[];//为特定用例分配的额外数据
};

7、struct spi_transfer

struct spi_transfer用于描述读/写缓冲对,该结构定义定义如下:

structspi_transfer{
constvoid*tx_buf;//要写入的数据(DMA安全内存),该值可能为NULL。
void*rx_buf;//要读取的数据(DMA安全内存),该值可能为NULL
unsignedlen;//rx和tx缓冲区的大小(字节为单位)
#defineSPI_TRANS_FAIL_NO_STARTBIT(0);
u16error;//SPI控制器驱动程序记录的错误状态。
dma_addr_ttx_dma;//tx_buf的DMA地址,如spi_message.is_dma_mapped。
dma_addr_trx_dma;//rx_buf的DMA地址,如spi_message.is_dma_mapped。
structsg_tabletx_sg;//用于传输的散列表。
structsg_tablerx_sg;//用于接收的散列表;
unsigneddummy_data:1;
unsignedcs_off:1;
unsignedcs_change:1;
unsignedtx_nbits:3;//用于写入的位数。如果为0,则使用默认值(SPI_NBITS_SINGLE)。
unsignedrx_nbits:3;//用于读取的位数。如果为0,则使用默认值(SPI_NBITS_SINGLE)。
unsignedtimestamped:1;//如果传输有时间戳,则为True
#defineSPI_NBITS_SINGLE0x01;
#defineSPI_NBITS_DUAL0x02;
#defineSPI_NBITS_QUAD0x04;
u8bits_per_word;//为此传输选择一个bits_per_word,而不是设备默认值。如果为0,则使用默认值(来自spi_device)。
structspi_delaydelay;//在此传输之后(可选地)更改chipselect状态之前引入的延迟,然后开始下一次传输或完成此spi_message。
structspi_delaycs_change_delay;//当设置了cs_change并且spi_transfer不是spi_message中的最后一个时,在cs deassert和assert之间的延迟。
structspi_delayword_delay;//每个字长(由bits_per_word设置)传输后引入的字间延迟。
u32speed_hz;//为此传输选择设备默认速度以外的速度。如果为0,则使用默认值(来自spi_device)。
u32effective_speed_hz;//用于传输此传输的有效sck速度。如果SPI总线驱动不支持,设置为0。
unsignedintptp_sts_word_pre;//在tx_buf中的字(受bits_per_word语义约束)偏移量,SPI设备为此请求开始此传输的时间快照。在完成SPI传输后,该值可能与请求的值相比发生了变化,这取决于可用的快照分辨率(DMA传输、ptp_sts_supported为false等)。
unsignedintptp_sts_word_post;
structptp_system_timestamp*ptp_sts;
structlist_headtransfer_list;//通过spi_message.transfers进行排序的transfer。
};

SPI传输总是需要写入与读取相同数量的字节。struct spi_driverSPI设备驱动程序应该提供rx_buf或tx_buf。在某些情况下,可能还需要为正在传输的数据提供DMA地址,当底层驱动程序使用DMA时,这样会减少CPU开销。

如果传输缓冲区为NULL,则在填充rx_buf时将移出零。如果接收缓冲区为NULL,则移入的数据将被丢弃。只有len字节移出(或移进)。

内存中的数据总是按照本地CPU字节顺序进行排列。例如:当bits_per_word为16时,缓冲区是2N字节长(len = 2N),并按CPU字节顺序保存N个16位字。

当SPI传输的字长不是8的2次幂倍数时,内存中的字包含额外的位。内存中的字总是被协议驱动程序视为右对齐,因此未定义(rx)或未使用(tx)位始终是最重要的位。

所有SPI传输从芯片选择(CS)被激活开始。通常,直到消息中的最后一次传输之后,它才会被选中。驱动程序可以使用cs_change影响芯片选择(CS)信号:

(1)如果传输不是消息中的最后一个,则此标志用于使CS在消息中间短暂地处于非活动状态。以这种方式切换CS可能需要终止一个芯片命令,让单个spi_message一起执行所有的芯片事务组。

(2)当传输是消息中的最后一个传输时,芯片可以保持选中状态直到下一次传输。在多设备SPI总线上,没有阻止消息到其他设备,这只是一个性能提示;向另一个设备发送消息将取消选中该设备。但在其他情况下,这可以用来确保正确性,一些SPI设备需要通过一系列spi_message提交来构建协议事务,其中一条消息的内容由之前消息的结果决定,当CS处于非活动状态时,整个事务结束。

当SPI可以在1x,2x或4x传输时。它可以通过tx_nbits和rx_nbits从设备获取传输信息。在双向传输中,tx_nbits和rx_nbits都应该被设置。用户可以设置传输模式SPI_NBITS_SINGLE(1x)、SPI_NBITS_DUAL(2x)和SPI_NBITS_QUAD(4x)来支持这三种传输方式。

将spi_message(及其spi_transfers)提交给较低层的代码负责管理其内存。因此零初始化没有显式设置的字段,可以防止未来API更新。在提交消息及其传输之后,忽略它们,直到它完成回调。

8、struct spi_message

spi_message用于执行数据传输的原子序列,每个序列由struct spi_transfer表示。该结构定义如下:

structspi_message{
structlist_headtransfers;//该传输中传输段链表
structspi_device*spi;//表示该传输的SPI设备
unsignedis_dma_mapped:1;//如果为true,则调用者为每个传输缓冲区提供DMA和CPU虚拟地址。
boolprepared;//是否为此消息调用spi_prepare_message()
intstatus;//表示传输状态,0表示成功,否则为负errno
void(*complete)(void*context);//调用该回调以报告事务的完成情况。
void*context;//调用complete()时的参数。
unsignedframe_length;//message中的总字节数。
unsignedactual_length;//传输成功的字节总数。
structlist_headqueue;//该参数供当前拥有该消息的驱动程序使用。
void*state;//该参数供当前拥有该消息的驱动程序使用。
structlist_headresources;//用于处理SPI消息时的资源管理。
structspi_transfert[];//该组成元素用于spi_message_alloc()。(当消息和传输已经一起分配时)
};

一个spi_message用于执行数据传输的原子序列,每个序列由struct spi_transfer结构表示。该序列是“原子的”,因为在该序列完成之前,没有其他spi_message可以使用该SPI总线。在一些系统中,许多这样的序列可以作为单个编程的DMA传输来执行。在所有系统上,这些消息都是以队列方式组织的,并且可能在发送到其他设备的事务之后完成,发送到给定spi_device的消息总是按照FIFO顺序执行。

将spi_message(及其spi_transfers)提交给较底层的代码负责管理其内存。使用零初始化没有显式设置的每个字段,以隔离后续可能发生的API更新带来的影响。

9、struct spi_board_info

struct spi_board_info用于SPI设备的特定板卡模板。该结构定义如下:

structspi_board_info{
charmodalias[SPI_NAME_SIZE];//用于初始化spi_device.modalias,用于识别驱动程序。
constvoid*platform_data;//用于初始化spi_device.platform_data,用于存储特定数据。
conststructsoftware_node*swnode;//用于描述设备的软件节点
void*controller_data;//用于初始化spi_device.controller_data;一些控制器需要提示硬件设置,例如DMA。
intirq;//用于初始化spi_device.irq;取决于板卡的连接。
u32max_speed_hz;//用于初始化spi_device.max_speed_hz;基于芯片数据表和主板特定信号质量问题的限制。
u16bus_num;//识别哪些spi_controller作为spi_device的父设备;在spi_new_device()中未使用,取决于板卡接线。
u16chip_select;//用于初始化spi_device.chip_select;取决于板卡连接。
u32mode;//用于初始化spi_device.mode;根据芯片数据表,电路板布线。
};

当向设备树中添加新的SPI设备时,该结构可用作设备模板,该结构在两个地方使用,第一个作用是可存储在板卡特定设备描述符的表中,这些描述符在板卡初始化的早期声明,然后在控制器的驱动程序初始化之后使用。第二个作用是作为spi_new_device()调用的参数。

四、SPI框架的常用API总结

linux内核不同版本的SPI框架开放的API可能不同,以具体源码为主!

//1、初始化spi_message并附加到transfer
voidspi_message_init_with_transfers(structspi_message*m,structspi_transfer*xfers,unsignedintnum_xfers)

//2、检查是否支持每字位
boolspi_is_bpw_supported(structspi_device*spi,u32bpw)

//3、计算一个合适的超时值
unsignedintspi_controller_xfer_timeout(structspi_controller*ctlr,structspi_transfer*xfer)

//4、同步SPI数据传输
intspi_sync_transfer(structspi_device*spi,structspi_transfer*xfers,unsignedintnum_xfers)

//5、SPI同步写操作
intspi_write(structspi_device*spi,constvoid*buf,size_tlen)

//6、SPI同步读操作
intspi_read(structspi_device*spi,void*buf,size_tlen)

//7、SPI同步8位写然后8位读
ssize_tspi_w8r8(structspi_device*spi,u8cmd)

//8、SPI同步8位写然后16位读
ssize_tspi_w8r16(structspi_device*spi,u8cmd)

//9、SPI同步8位写入,然后16位大端读
ssize_tspi_w8r16be(structspi_device*spi,u8cmd)

//10、为给定的board注册SPI设备
intspi_register_board_info(structspi_board_infoconst*info,unsignedn)

//11、注册一个SPI驱动
int__spi_register_driver(structmodule*owner,structspi_driver*sdrv)

//12、分配新的SPI设备
structspi_device*spi_alloc_device(structspi_controller*ctlr)

//13、向SPI核心添加使用spi_alloc_device分配的spi_device
intspi_add_device(structspi_device*spi)

//14、实例化一个新的SPI设备
structspi_device*spi_new_device(structspi_controller*ctlr,structspi_board_info*chip)

//15、注销单个SPI设备
voidspi_unregister_device(structspi_device*spi)

//16、报告transfer的完成情况
voidspi_finalize_current_transfer(structspi_controller*ctlr)

//17、获取TX开始时间戳的助手函数
voidspi_take_timestamp_pre(structspi_controller*ctlr,structspi_transfer*xfer,size_tprogress,boolirqs_off)

//18、获取TX结束时间戳的助手函数
voidspi_take_timestamp_post(structspi_controller*ctlr,structspi_transfer*xfer,size_tprogress,boolirqs_off)

//19、获取下一个排队的消息。(由驱动程序调用用于检查排队的消息)
structspi_message*spi_get_next_queued_message(structspi_controller*ctlr)

//20、由驱动程序调用,通知内核队列前面的消息已经完成,可以从队列中删除。
voidspi_finalize_current_message(structspi_controller*ctlr)

//21、注册辅助SPI设备(该函数只能从主SPI设备的probe函数中调用)
structspi_device*spi_new_ancillary_device(structspi_device*spi,u8chip_select)

//22、统计SpiSerialBus资源的个数
intacpi_spi_count_resources(structacpi_device*adev)

//23、中止SPI从控制器上正在进行的传输请求
intspi_slave_abort(structspi_device*spi)

//24、分配一个SPI主控制器或者从控制器
structspi_controller*__spi_alloc_controller(structdevice*dev,unsignedintsize,boolslave)

//25、带资源管理的__spi_alloc_controller()
structspi_controller*__devm_spi_alloc_controller(structdevice*dev,unsignedintsize,boolslave)

//26、注册SPI主控制器或者从控制器
intspi_register_controller(structspi_controller*ctlr)

//27、带资源管理的spi_register_controller()
intdevm_spi_register_controller(structdevice*dev,structspi_controller*ctlr)

//28、注销SPI主控制器或从控制器
voidspi_unregister_controller(structspi_controller*ctlr)

//29、当单个传输超过一定大小时,将spi传输拆分为多个传输
intspi_split_transfers_maxsize(structspi_controller*ctlr,structspi_message*msg,size_tmaxsize,gfp_tgfp)

//30、当单个传输超过一定数量的SPI字时,将SPI传输拆分为多个传输
intspi_split_transfers_maxwords(structspi_controller*ctlr,structspi_message*msg,size_tmaxwords,gfp_tgfp)

//31、设置SPI模式和时钟速率
intspi_setup(structspi_device*spi)

//32、异步SPI传输
intspi_async(structspi_device*spi,structspi_message*message)

//33、阻塞/同步SPI数据传输
intspi_sync(structspi_device*spi,structspi_message*message)

//34、具有独占总线使用的spi_sync()版本
intspi_sync_locked(structspi_device*spi,structspi_message*message)

//35、获得独占SPI总线使用的锁
intspi_bus_lock(structspi_controller*ctlr)

//36、释放独占SPI总线使用的锁
intspi_bus_unlock(structspi_controller*ctlr)

//37、SPI同步写然后读。
intspi_write_then_read(structspi_device*spi,constvoid*txbuf,unsignedn_tx,void*rxbuf,unsignedn_rx)

五、SPI驱动实例分析

SPI驱动分为两个部分:主机侧驱动设备侧驱动

(5-1)SPI主机侧驱动

(1)SPI主机侧驱动设计思路

一般情况下,SPI主机侧的驱动程序芯片原厂,会去实现,并会合并到自己厂家维护的linux内核版本中发布给其他基于该芯片设计的厂商。在实现SPi主机侧驱动的时候,可以基于平台设备驱动框架实现,然后使用module_platform_driver()或者其他模块函数导出,例如:module_init()。在平台驱动的.probe指向的函数中实现spi驱动:

1、区分spi驱动是slave还是master,并创建对应的struct spi_controller,如果是slave,则使用spi_alloc_slave()创建,如果是master,则使用spi_allov_master()创建。

2、实现spi寄存器相关的映射。

3、设置spi时钟。

4、创建spi中断服务函数(以中断线程化方式实现)。

5、初始化spi_controller相关组成元素的信息。

6、指定struct spi_controller操作的callback。

7、spi控制器相关的状态获取和保存。

8、注册spi控制器。可使用devm_spi_register_controller()或者相关接口实现。

(2)、SPI主机侧驱动案例分析

本小节,以Rockchip的rk3568的SPI主机侧驱动为例。分析SPI主机侧驱动的实现步骤,rk3568的spi驱动位于/drivers/spi/spi-rockchip.c(以具体linux内核源码为准)文件中。该驱动以platform驱动框架为基础实现,对应的struct platform_driver实现如下:

staticstructplatform_driverrockchip_spi_driver={
.driver={
.name=DRIVER_NAME,
.pm=&rockchip_spi_pm,
.of_match_table=of_match_ptr(rockchip_spi_dt_match),
},
.probe=rockchip_spi_probe,
.remove=rockchip_spi_remove,
};

module_platform_driver(rockchip_spi_driver);

在源码的最后使用module_platform_driver()导出spi驱动。

接着看看rockchip_spi_dt_match设备匹配表,定义如下:

5908c992-97ba-11ee-8b88-92fbcf53809c.png

可见该spi驱动支持的芯片类型比较多。

再看看.probe对应的rockchip_spi_probe(),该函数实现如下(函数中内容较多):

staticintrockchip_spi_probe(structplatform_device*pdev)
{
intret;
structrockchip_spi*rs;
structspi_controller*ctlr;
structresource*mem;
structdevice_node*np=pdev->dev.of_node;
u32rsd_nsecs;
boolslave_mode;
structpinctrl*pinctrl=NULL;

slave_mode=of_property_read_bool(np,"spi-slave");

if(slave_mode)
ctlr=spi_alloc_slave(&pdev->dev,
sizeof(structrockchip_spi));
else
ctlr=spi_alloc_master(&pdev->dev,
sizeof(structrockchip_spi));

if(!ctlr)
return-ENOMEM;

platform_set_drvdata(pdev,ctlr);

rs=spi_controller_get_devdata(ctlr);
ctlr->slave=slave_mode;

/*Getbasicioresourceandmapit*/
mem=platform_get_resource(pdev,IORESOURCE_MEM,0);
rs->regs=devm_ioremap_resource(&pdev->dev,mem);
if(IS_ERR(rs->regs)){
ret=PTR_ERR(rs->regs);
gotoerr_put_ctlr;
}

rs->apb_pclk=devm_clk_get(&pdev->dev,"apb_pclk");
if(IS_ERR(rs->apb_pclk)){
dev_err(&pdev->dev,"Failedtogetapb_pclk
");
ret=PTR_ERR(rs->apb_pclk);
gotoerr_put_ctlr;
}

rs->spiclk=devm_clk_get(&pdev->dev,"spiclk");
if(IS_ERR(rs->spiclk)){
dev_err(&pdev->dev,"Failedtogetspi_pclk
");
ret=PTR_ERR(rs->spiclk);
gotoerr_put_ctlr;
}

ret=clk_prepare_enable(rs->apb_pclk);
if(ret< 0) {
  dev_err(&pdev->dev,"Failedtoenableapb_pclk
");
gotoerr_put_ctlr;
}

ret=clk_prepare_enable(rs->spiclk);
if(ret< 0) {
  dev_err(&pdev->dev,"Failedtoenablespi_clk
");
gotoerr_disable_apbclk;
}

spi_enable_chip(rs,false);

ret=platform_get_irq(pdev,0);
if(ret< 0)
  goto err_disable_spiclk;

 ret = devm_request_threaded_irq(&pdev->dev,ret,rockchip_spi_isr,NULL,
IRQF_ONESHOT,dev_name(&pdev->dev),ctlr);
if(ret)
gotoerr_disable_spiclk;

rs->dev=&pdev->dev;
rs->freq=clk_get_rate(rs->spiclk);
rs->gpio_requested=false;

if(!of_property_read_u32(pdev->dev.of_node,"rx-sample-delay-ns",
&rsd_nsecs)){
/*rxsampledelayisexpressedinparentclockcycles(max3)*/
u32rsd=DIV_ROUND_CLOSEST(rsd_nsecs*(rs->freq>>8),
1000000000>>8);
if(!rsd){
dev_warn(rs->dev,"%uHzaretooslowtoexpress%unsdelay
",
rs->freq,rsd_nsecs);
}elseif(rsd>CR0_RSD_MAX){
rsd=CR0_RSD_MAX;
dev_warn(rs->dev,"%uHzaretoofasttoexpress%unsdelay,clampingat%uns
",
rs->freq,rsd_nsecs,
CR0_RSD_MAX*1000000000U/rs->freq);
}
rs->rsd=rsd;
}

rs->fifo_len=get_fifo_len(rs);
if(!rs->fifo_len){
dev_err(&pdev->dev,"Failedtogetfifolength
");
ret=-EINVAL;
gotoerr_disable_spiclk;
}

pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);

ctlr->auto_runtime_pm=true;
ctlr->bus_num=pdev->id;
ctlr->mode_bits=SPI_CPOL|SPI_CPHA|SPI_LOOP|SPI_LSB_FIRST|SPI_CS_HIGH;
if(slave_mode){
ctlr->mode_bits|=SPI_NO_CS;
ctlr->slave_abort=rockchip_spi_slave_abort;
}else{
ctlr->flags=SPI_MASTER_GPIO_SS;
}
ctlr->num_chipselect=ROCKCHIP_SPI_MAX_CS_NUM;
ctlr->dev.of_node=pdev->dev.of_node;
ctlr->bits_per_word_mask=SPI_BPW_MASK(16)|SPI_BPW_MASK(8)|SPI_BPW_MASK(4);
ctlr->min_speed_hz=rs->freq/BAUDR_SCKDV_MAX;
ctlr->max_speed_hz=min(rs->freq/BAUDR_SCKDV_MIN,MAX_SCLK_OUT);

ctlr->set_cs=rockchip_spi_set_cs;
ctlr->setup=rockchip_spi_setup;
ctlr->cleanup=rockchip_spi_cleanup;
ctlr->transfer_one=rockchip_spi_transfer_one;
ctlr->max_transfer_size=rockchip_spi_max_transfer_size;
ctlr->handle_err=rockchip_spi_handle_err;

ctlr->dma_tx=dma_request_chan(rs->dev,"tx");
if(IS_ERR(ctlr->dma_tx)){
/*Checktxtoseeifweneeddeferprobingdriver*/
if(PTR_ERR(ctlr->dma_tx)==-EPROBE_DEFER){
ret=-EPROBE_DEFER;
gotoerr_disable_pm_runtime;
}
dev_warn(rs->dev,"FailedtorequestTXDMAchannel
");
ctlr->dma_tx=NULL;
}

ctlr->dma_rx=dma_request_chan(rs->dev,"rx");
if(IS_ERR(ctlr->dma_rx)){
if(PTR_ERR(ctlr->dma_rx)==-EPROBE_DEFER){
ret=-EPROBE_DEFER;
gotoerr_free_dma_tx;
}
dev_warn(rs->dev,"FailedtorequestRXDMAchannel
");
ctlr->dma_rx=NULL;
}

if(ctlr->dma_tx&&ctlr->dma_rx){
rs->dma_addr_tx=mem->start+ROCKCHIP_SPI_TXDR;
rs->dma_addr_rx=mem->start+ROCKCHIP_SPI_RXDR;
ctlr->can_dma=rockchip_spi_can_dma;
}

switch(readl_relaxed(rs->regs+ROCKCHIP_SPI_VERSION)){
caseROCKCHIP_SPI_VER2_TYPE1:
caseROCKCHIP_SPI_VER2_TYPE2:
if(ctlr->can_dma&&slave_mode)
rs->cs_inactive=true;
else
rs->cs_inactive=false;
break;
default:
rs->cs_inactive=false;
}

pinctrl=devm_pinctrl_get(&pdev->dev);
if(!IS_ERR(pinctrl)){
rs->high_speed_state=pinctrl_lookup_state(pinctrl,"high_speed");
if(IS_ERR_OR_NULL(rs->high_speed_state)){
dev_warn(&pdev->dev,"nohigh_speedpinctrlstate
");
rs->high_speed_state=NULL;
}
}

ret=devm_spi_register_controller(&pdev->dev,ctlr);
if(ret< 0) {
  dev_err(&pdev->dev,"Failedtoregistercontroller
");
gotoerr_free_dma_rx;
}

return0;

err_free_dma_rx:
if(ctlr->dma_rx)
dma_release_channel(ctlr->dma_rx);
err_free_dma_tx:
if(ctlr->dma_tx)
dma_release_channel(ctlr->dma_tx);
err_disable_pm_runtime:
pm_runtime_disable(&pdev->dev);
err_disable_spiclk:
clk_disable_unprepare(rs->spiclk);
err_disable_apbclk:
clk_disable_unprepare(rs->apb_pclk);
err_put_ctlr:
spi_controller_put(ctlr);

returnret;
}

上述.probe实现的主要步骤如下:

读取spi-slave属性获取模式,如果是slave模式,则调用spi_alloc_slave()分配struct spi_contoller内存,否则为master模式,则调用spi_alloc_master()同样分配一个struct spi_contoller内存。

获取基本的IO资源并对其进行映射。

获取时钟并enable时钟。

调用platform_get_irq()获取中断号,接着调用devm_request_threaded_irq()创建中断处理函数,其中中断处理函数为rockchip_spi_isr()。

设置struct rockchip_spi结构中的组成元素。struct rockchip_spi表示具体的spi控制器。

设置struct spi_controller 结构中的组成元素。

最后调用devm_spi_register_controller()注册spi控制器。

(5-2)SPI设备侧驱动

(1)SPI设备侧驱动设计思路

对于SPI设备的驱动,主要围绕如何与该SPI设备进行数据通信或者实现控制。在SPI控制器驱动实现的情况下,SPI设备侧的实现思路:

1、对SPI设备进行描述。

可以通过修改设备树的方式对SPI设备进行描述。

2、创建struct spi_driver的具体实例作为设备侧驱动。

3、SPI设备数据收发处理流程

SPI设备数据的收发主要涉及到两个数据结构:struct spi_message、struct spi_transfer,还需要几个用于传输的API:

//在使用spi_message之前需要对其进行初始化:
voidspi_message_init(structspi_message*m)

//spi_message初始化完成以后可使用spi_message_add_tail将spi_transfer添加到spi_message队列中:
voidspi_message_add_tail(structspi_transfer*t,structspi_message*m)

//spi_message准备好以后既可以进行数据传输了,数据传输分为同步传输和异步传输,同步
//传输会阻塞的等待SPI数据传输完成,同步传输函数为spi_sync():
intspi_sync(structspi_device*spi,structspi_message*message)

//异步传输不会阻塞的等到SPI数据传输完成,异步传输需要设置spi_message中的complete成员变量,complete是一个回调函数,当SPI异步传输完成以后此函数就会被调用。SPI异步传
//输函数为spi_async():
intspi_async(structspi_device*spi,structspi_message*message)

4、根据具体驱动需求设计struct file_operations对应的ops的callback。

5、在.probe中使用spi_register_driver()向SPI核心注册SPI驱动,以字符设备类方式导出用户空间SPI设备文件,分配中断等

6、以驱动框架方式导出。

(2)、SPI设备侧驱动案例分析

当主机侧的SPI实现后,我们可以快速使用SPI控制器与SPI设备进行通信了。本小节以icm20608这款常见的SPI接口的六轴传感器为例分析SPI设备侧驱动的具体实现步骤。

首先使用struct spi_driver创建spi驱动实例icm20608_driver,指定其中的.probe、.remove、driver参数,如有必要可实现.id_table:

staticstructspi_drivericm20608_driver={
.probe=icm20608_probe,
.remove=icm20608_remove,
.driver={
.owner=THIS_MODULE,
.name="ICM20608",
.of_match_table=icm20608_of_match,
},
};

接着使用module_init()初始化模块,在模块初始化函数中调用spi_register_driver()将icm20608_driver注册到linux内核。然后实现模块退出接口函数,在该函数中实现必要的退出清理操作。

接着实现icm20608_probe()、icm20608_remove和设备树匹配表icm20608_of_match。

在icm20608_probe()中以字符设备方式向用户暴露出设备文件。并实现对icm20608设备文件的struct file_operations中的callback。例如:.open、.read、release。因icm20608为六轴传感器,主要操作是读取数据,所以.write可以不用实现。

在实现.read操作时,主要内容是使用SPI控制器发送相应控制数据到SPI设备。使用SPI控制器发送数据的代码如下:

inticm20608_readRegs(structicm20608_dev*dev,u8reg,void*buf,intlen)
{
intret;
unsignedchartxdata[len];
structspi_messagemessage;
structspi_transfer*transfer;
structspi_device*spi=(structspi_device*)dev->private_data;

/*1、申请内存*/
transfer=kzalloc(sizeof(structspi_transfer),GFP_KERNEL);

/*2、发送要读取的寄存地址*/
txdata[0]=reg|0x80;/*写数据的时候寄存器地址bit8要置1*/
transfer->tx_buf=txdata;/*要发送的数据*/
transfer->len=1;/*1个字节*/

/*3、初始化spi_message*/
spi_message_init(&message);

/*4、将spi_transfer添加到spi_message队列*/
spi_message_add_tail(transfer,&message);

/*5、同步发送*/
ret=spi_sync(spi,&message);

/*6、释放内存*/
kfree(transfer);

returnret;
}

六、SPI驱动调试总结

在SPI驱动调试过程中,还是需要注意以下几点:

(1)确保自己系统的SPI成功运行。这是SPI设备能正常通信工作的前提条件。

(2)在进行SPI数据传输时,确认时序是否正确:发送引脚有正常的波形,CLK引脚有正常的时钟信号,CS引脚有拉低。

(3)确保SPI的4个引脚的引脚复用配置正确。







审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    5068

    文章

    19008

    浏览量

    302951
  • 触摸传感器
    +关注

    关注

    0

    文章

    121

    浏览量

    23048
  • EEPROM
    +关注

    关注

    9

    文章

    1010

    浏览量

    81395
  • SPI通信
    +关注

    关注

    0

    文章

    35

    浏览量

    11345
  • LINUX内核
    +关注

    关注

    1

    文章

    316

    浏览量

    21614

原文标题:linux内核中的spi“简单”吗?

文章出处:【微信号:嵌入式小生,微信公众号:嵌入式小生】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    SPI接口初始化函数spi_master_init

    有谁知道SPI接口初始化函数spi_master_init 和 SPI接口数据传送函数SPI_Transmit ?哪里能找到资料?搜索百度没
    发表于 04-03 09:16

    SPI模块的初始化代码

    第一次写博客有点紧张哈哈哈所以话不多说先上代码压压惊//以下是SPI模块的初始化代码,配置成主机模式//SPI初始化//这里针是对SPI2
    发表于 08-04 07:17

    STM32的SPI1、SPI2、SPI3初始化及RF1101的应用

    STM32的SPI1、SPI2、SPI3初始化及RF1101的应用(标准库与HAL库)——基于STM32F103RCT6---- 标准库:说明:相关文件共有两个:bsp_
    发表于 08-05 07:53

    SPI主机驱动Linux

    1. LinuxSPI驱动框架1.1 SPI主机驱动Linux使用spi_master结构体表
    发表于 11-02 07:50

    LINUX系统引导和初始化-LINUX内核解读

    Linux 的系统引导和初始化 ----------Linux2.4.22内核解读之一 一、 系统引导和初始化概述 相关代码(引导扇区的程序
    发表于 11-03 22:31 53次下载

    Linux内存初始化

    之前有几篇博客详细介绍了Xen的内存初始化,确实感觉这部分内容蛮复杂的。这两天在看Linux内核启动内存的初始化,也是看的云里雾里的,想尝
    发表于 10-12 11:16 0次下载

    解析内核初始化时根内存盘的加载过程

    2006-12-12 13:54:41 来源:Linux 宝库 分享到:标签:loadlin gzip 作者:opera 概述 ==== 1)当内核配置了内存盘时, 内核初始化时可以
    发表于 11-08 10:40 0次下载

    uboot和内核里phy的初始化_内核里的双网络配置及phy的初始化

    uboot 和内核里 phy 的初始化,以及内核里的双网络配置及 phy 的初始化。 本文以盈鹏飞嵌入式的CoM-335x(基于AM335x)核心
    的头像 发表于 05-17 08:19 1.2w次阅读

    SD卡的SPI模式的初始化顺序

    SD卡的SPI模式的初始化顺序 这些天没有出门,一直在家研究SD卡的SPI模式的初始化顺序,这里为大家总结了一下编写该程序所需要的知识: SD卡的官方资料(我承认这个资料很垃圾,比起民
    发表于 09-21 14:34 1075次阅读

    单片机SPI初始化设计

    6 //这里只针对SPI初始化 7 GPIOA-》CRL&=0X000FFFFF; 8 GPIOA-》CRL|=0XBBB00000;//PA5.6.7复用推挽输出 9
    发表于 12-06 15:55 2352次阅读

    Linux内核初始化过程的调用顺序

    所有的__init函数在区段.initcall.init还保存了一份函数指针,在初始化内核会通过这些函数指针调用这些__init函数指针,并在整个初始化完成后,释放整个init区段
    发表于 05-12 08:40 1604次阅读

    嵌入式Linux SPI驱动

    1. LinuxSPI驱动框架1.1 SPI主机驱动Linux使用spi_master结构体表
    发表于 11-01 17:05 14次下载
    嵌入式<b class='flag-5'>Linux</b> <b class='flag-5'>SPI</b>驱动

    stm32单片机用spi初始化sd卡的一些问题及反思

    初次接触使用spi接口读写sd卡,在初始化阶段一直过不去,但随着这几天的学习也有一些心得,分享出来,供大家参考使用,新手摸石头过河,多有不足,还请斧正!!
    发表于 12-22 19:19 3次下载
    stm32单片机用<b class='flag-5'>spi</b><b class='flag-5'>初始化</b>sd卡<b class='flag-5'>中</b>的一些问题及反思

    STM32F103学习笔记——SPI读写Flash(二)

      此系列文章是小白学习STM32的一些学习笔记。小白第一次写笔记文章,有不足或是错误之处,请多体谅和交流!目录1.软件设计流程2.SPI初始化3.SPI发送接收一字节函数编写4.FLASH控制指令
    发表于 12-22 19:30 10次下载
    STM32F103学习笔记——<b class='flag-5'>SPI</b>读写Flash(二)

    SPI复用的初始化覆盖问题-数据不对,而且写的数据不一样,位数还在变化。

    器件写的数据不但不对,而且写使能函数写入不用的的数据时,数据的位数还在变化。最后看到前面一个器件初始化配置后,后面紧跟着针对另一个器件进行SPI初始化,想到可能是
    发表于 12-22 19:35 3次下载
    <b class='flag-5'>SPI</b>复用的<b class='flag-5'>初始化</b>覆盖问题-数据不对,而且写的数据不一样,位数还在变化。