SPI驱动的框架
SPI总线的核心文件:\drivers\spi\spi.c
厂家提供
linux标准 用户针对不同的设备自己实现
SPI总线设备层的注册一般由厂家完成,用户只需要编写SPI的驱动层即可。
在spi.c中调用了总线注册函数,注册了SPI总线。
status = bus_register(&spi_bus_type); |
总线类型定义:
struct bus_type spi_bus_type = { .name= "spi", .dev_attrs= spi_dev_attrs, .match= spi_match_device, /*匹配设备端与驱动端处理函数*/ .uevent= spi_uevent, .pm= &spi_pm, }; |
spi的函数接口
1.2.1 spi 驱动注册与注销
int spi_register_driver(struct spi_driver *sdrv) //注册SPI的驱动端 static inline void spi_unregister_driver(struct spi_driver *sdrv) //注销SPI驱动端 |
以上两个函数在SPI驱动端使用。注册SPI的驱动。
SPI驱动的注册参考:\drivers\spi\spidev.c
1.2.2 分配SPI主控制器
struct spi_master *spi_alloc_master(struct device *dev, unsigned size); |
在该函数里创建了新的SPI设备,最终注册了SPI设备。
创建SPI设备调用流程:spi_match_master_to_boardinfo() --> spi_new_device
最终在of_register_spi_devices函数里注册添加了SPI设备端。
1.2.3 获取SPI设备数据
static inline void spi_master_set_devdata(struct spi_master *master, void *data) //设备数据设置 static inline void *spi_master_get_devdata(struct spi_master *master) //设备数据获取 |
在分配SPI主控制器的时候已经调用了SPI设备数据设置函数进行了设置,所以获取数据的函数就将设置的数据取出来。
1.2.4. 注册SPI主控制器
int spi_register_master(struct spi_master *master) //注册SPI控制器 void spi_unregister_master(struct spi_master *master) //注销SPI控制器 |
SPI的主控器一般由厂家提供注册。
exynos4412的SPI主控制器注册参考:\drivers\spi\spi-s3c64xx.c
一个SPI主控制器结构就表示一路CPU的SPI总线接口。
- struct spi_master结构原型如下:
struct spi_master { struct devicedev; //驱动设备接口 struct list_head list; //链表头 s16bus_num; //总线编号 u16num_chipselect; //SPI片选编号 u16dma_alignment; //DMA模式 u16mode_bits; u16flags; #define SPI_MASTER_HALF_DUPLEXBIT(0)/* can't do full duplex */ #define SPI_MASTER_NO_RXBIT(1)/* can't do buffer read */ #define SPI_MASTER_NO_TXBIT(2)/* can't do buffer write */ spinlock_tbus_lock_spinlock; struct mutexbus_lock_mutex; boolbus_lock_flag; int(*setup)(struct spi_device *spi); 更新SPI采样时钟 int(*transfer)(struct spi_device *spi,struct spi_message *mesg); 添加消息到SPI控制器的队列 void(*cleanup)(struct spi_device *spi); boolqueued; struct kthread_workerkworker; struct task_struct*kworker_task; struct kthread_workpump_messages; spinlock_tqueue_lock; struct list_headqueue; struct spi_message*cur_msg; boolbusy; boolrunning; boolrt; int (*prepare_transfer_hardware)(struct spi_master *master); int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg); int (*unprepare_transfer_hardware)(struct spi_master *master); }; |
1.2.5 SPI控制器的使用计数
static inline void spi_master_put(struct spi_master *master) //取消SPI控制器的使用计数 |
1.2.6 注册SPI板级设备信息
int __devinitspi_register_board_info(struct spi_board_info const *info, unsigned n) |
该函数一般在板级初始化代码里调用。比如:exynos4412开发板的SPI板级信息设置在mach-tiny4412.c文件的底层初始化函数里调用。struct spi_board_info结构体里保存了与SPI驱动端匹配的名称。
该函数里最终将传入的struct spi_board_info结构加入到struct boardinfo 结构的链表中。
- struct spi_board_info 结构体原型:
struct spi_board_info { charmodalias[SPI_NAME_SIZE];设备名称,与驱动端做匹配使用*/ const void*platform_data;/*平台数据*/ void*controller_data; intirq; u32max_speed_hz; //最大时钟频率 u16bus_num; //总线编号 u16chip_select; u8mode; //数据传输模式 }; |
- struct spi_board_info 结构体填充示例:
static struct spi_board_info spi1_board_info[] __initdata = { { .modalias = "spidev", .platform_data = NULL, .max_speed_hz = 10*1000*1000, .bus_num = 1, .chip_select = 0, .mode = SPI_MODE_0, .controller_data = &spi1_csi[0], } |
1.2.7 设置SPI驱动私有数据
static inline void spi_set_drvdata(struct spi_device *spi, void *data) //设置私有数据 static inline void *spi_get_drvdata(struct spi_device *spi) //获取设置的私有数据 |
spi_set_drvdata功能:
将data指针赋值给spi的dev->p->driver_data成员。
spi_get_drvdata功能:
获取之前设置的数据指针。
1.2.8 初始化SPI信息结构
static inline void spi_message_init(struct spi_message *m) |
- struct spi_message结构:
struct spi_message { struct list_headtransfers; struct spi_device*spi;加到传输队列中的spi设备 unsignedis_dma_mapped:1; void(*complete)(void *context);传输完成回调函数 void*context; //complete函数的参数 unsignedactual_length;//所有成功传输字段的总长度 intstatus;//传输成功返回0,否则返回错误 struct list_headqueue; void*state; }; |
1.2.9 添加信息结构
static inline voidspi_message_add_tail(struct spi_transfer *t, struct spi_message *m) |
功能:将struct spi_message m结构添加到struct spi_transfer 结构的transfer_list成员中。
- struct spi_transfer结构原型:
struct spi_transfer { const void*tx_buf; //写数据的缓冲区 void*rx_buf; //读数据的缓冲区 unsignedlen; //数据长度 dma_addr_ttx_dma; //DMA地址 dma_addr_trx_dma; unsignedcs_change:1; u8bits_per_word; //传输的字节数,不设置使用默认的 u16delay_usecs;//微秒延时,用以传输数据后,改变片选信号前 u32speed_hz;//传输速率,不选就用默认的 struct list_head transfer_list; } |
1.2.10 SPI异步传输数据
int spi_async(struct spi_device *spi, struct spi_message *message) |
参数:
struct spi_device *spi :交换数据的SPI设备
struct spi_message *message :传输的信息结构
1.2.11 等待传输完成
void __sched wait_for_completion(struct completion *x) |
该函数在SPI发送的时候有调用到,等待SPi的数据发送完成。
1.2.12 设置SPI的时钟与速率
int spi_setup(struct spi_device *spi) |
1.2.13 SPI同步传输
int spi_sync(struct spi_device *spi, struct spi_message *message) |
该函数传输数据的过程中会进行阻塞。
1.2.14 SPI同步写函数
static inline int spi_write(struct spi_device *spi, const void *buf, size_t len); |
参数:
@spi: 要写的SPI设备
@buf: 写入的数据缓冲区
@len: 写入的数据缓冲区大小
1.2.15 SPI同步读
static inline int spi_read(struct spi_device *spi, void *buf, size_t len) |
参数:
将被读的设备
读出存放的数据缓冲区
读出的数据缓冲区大小
1.2.16 SPI同步8位写紧随其后的8位读--(不是同步,写和读是分开两次进行的)
static inline ssize_t spi_w8r8(struct spi_device *spi, u8 cmd) |
参数:
数据交换的SPI设备
寄存器命令
1.2.17 SPI同步8位写紧随其后的16位读-(不是同步,写和读是分开两次进行的)
static inline ssize_t spi_w8r16(struct spi_device *spi, u8 cmd) |
参数:
数据交换的SPI设备
寄存器命令
1.2.18 先写后读同步
int spi_write_then_read(struct spi_device *spi,const void *txbuf, unsigned n_tx,void *rxbuf, unsigned n_rx) |
参数:
数据交换的设备
写数据(不需要dma-safe)
大小,以字节为单位
将读数据的缓冲区(不需要dma-safe)
大小,以字节为单位
1.3 linxu内核的SPI测试代码
Linxu内核自带的SPI驱动注册示例代码:\drivers\spi\spidev.c
Linxu内核自带的SPI APP注册示例代码:\Documentation\spi
配置exynos 4412内核支持SPI总线
Exynos4412设备的板级资源在mach-tiny4412.c中定义,如果要保证SPI驱动端的资源探测函数能够正常调用,需要保证mach-tiny4412.中的SPI设备名称与SPI驱动端的一样。
1.4 SPI子系统使用测试
/* 函数功能:读取设备ID和制造商ID W25Q64: EF16 W25QQ128:EF17 参数:0x90表示读取ID号的指令 */ unsigned short W25Q64_ReadDeviceID(void) { /*使用硬件SPI同步读写时序*/ char tx_buf[6]={0x90,0x0,0x0,0x0,0xFF,0xFF}; char rx_buf[6]; struct spi_message m; struct spi_transfer t= { .tx_buf=tx_buf, .rx_buf=rx_buf, .len=6, .delay_usecs=0, .speed_hz=1000000, .bits_per_word=8 }; spi_message_init(&m); spi_message_add_tail(&t,&m); spi_sync(w25q64_spi_Device,&m); return rx_buf[4]<<8|rx_buf[5]; /*得到ID值*/ } |