SPI是同步外设接口,由摩托罗拉公司开发的全双工同步串行总线,接口有MISO、MOSI、SCK和SS四线组成。在这里不具体介绍SPI的工作原理了,相信学SPI驱动的同学已经在单片机上实现过了SPI的通讯。
学习SPI驱动首先必须要建立一个分层的设计思想,分层设计不光在SPI中体现,在linux内核中都是分层的思想,使得linux具有强大的适应性。分层设计思想在linux的Input、RTC、MTD、IIC、TTY、USB、SPI等很多设备驱动中得到体现。那么SPI驱动分了哪几个层呢?
SPI在linux中分为3层分别为主机控制器驱动、核心层驱动和外设驱动:
在OK6410中 主机控制器驱动:spi_s3c64xx.c--------------这个文件将s3c6410的硬件spi控制器驱动。
核心层驱动 :spi.c---------------------------这个文件是实现了spi的注册和注销的函数等,连接了主机控制器和外设驱动,起到了桥梁的作用。
外设驱动 :spidev.c----------------------内核中的一个通用的外设驱动。
学习底层驱动免不了大量的结构体,下面就来看一些重要的结构体:
为了看起来可以有调理性,首先从主机控制器的结构体开始:
在include\linux\spi下有spi.h
struct spi_master {
struct device dev; //内嵌标准dev结构
struct list_head list;
s16 bus_num; //总线编号
u16 num_chipselect; //芯片支持的片选数量
u16 dma_alignment; //采用dma时的对齐要求
/* spi_device.mode flags understood by this controller driver */
u16 mode_bits;
/* other constraints relevant to this driver */
u16 flags;
#define SPI_MASTER_HALF_DUPLEX BIT(0) /* can't do full duplex */
#define SPI_MASTER_NO_RX BIT(1) /* can't do buffer read */
#define SPI_MASTER_NO_TX BIT(2) /* can't do buffer write */
/* lock and mutex for SPI bus locking */
spinlock_t bus_lock_spinlock; //自旋锁定义
struct mutex bus_lock_mutex; //互斥信号量定义
/* flag indicating that the SPI bus is locked for exclusive use */
bool bus_lock_flag;
//改变spi_device的特性如:传输模式,字长,时钟频率
int (*setup)(struct spi_device *spi);
/*添加消息到队列的方法,这个函数不可睡眠,他的任务是安排发生的传送并且调用注册的回调函数complete()*/
int (*transfer)(struct spi_device *spi,
struct spi_message *mesg);
/* called on release() to free memory provided by spi_master */
void (*cleanup)(struct spi_device *spi); //释放内存
};
在linux中,使用
spi_master结构体来描述一个SPI的主机控制器驱动,结构体主要完成主机控制器的序列号(s3c6410中有两个spi主机控制器)、片选数量、SPI模式和时钟以及一些传输函数的实现。
上面这个结构体通过一下3个函数来注册和注销:
struct spi_master *spi_alloc_master(struct device *host, unsigned size); //自动分配内存
int spi_register_master(struct spi_master *master); //注册spi控制器
void spi_unregister_master(struct spi_master *master); //注销spi控制器
以上的结构体和函数最终在spi_s3c64xx.c中得到实现,有兴趣的可以看一下里面的代码很不错,但是也不好理解,因为6410的spi驱动采用了DMA传输。
SPI的核心层,作为桥梁的功能,里面主要注册了一个spi_bus_type的spi总线和spi_master的class类。通过注册在我们的系统中sys/bus中产生了一个spi的目录和sys/class下产生spi_master目录。
SPI外设驱动层,该层就像我们平常写的驱动,里面有几个重要的结构体分别是:
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
int (*suspend)(struct spi_device *spi, pm_message_t mesg);
int (*resume)(struct spi_device *spi);
struct device_driver driver;
};
spi_driver这个结构体和platform_driver结构体非常的相似。
在驱动中传输使用的结构体:
struct spi_transfer {
const void *tx_buf; //要写入设备的数据(必须是dma_safe),或者为NULL
void *rx_buf; //要读取的数据缓冲(必须是dma_safe),或者为NULL
unsigned len; //tx和rx的大小(字节数),这里不是指它的和,而是各自的长度,他们总是相等的
dma_addr_t tx_dma; //如果spi_message.is_dma_mapped是真,这个是tx的dma地址
dma_addr_t rx_dma; //如果spi_message.is_dma_mapped是真,这个是rx的dma地址
unsigned cs_change:1; //影响此次传输之后的片选,指示本次tranfer结束之后是否要重新片选并调用setup改变设置,这个标志可以较少系统开销u8
u8 bits_per_word; //每个字长的比特数,如果是0,使用默认值
u16 delay_usecs; //此次传输结束和片选改变之间的延时,之后就会启动另一个传输或者结束整个消息
u32 speed_hz; //通信时钟。如果是0,使用默认值
struct list_head transfer_list; //用来连接的双向链表节点
};
struct spi_message {
struct list_head transfers; //此次消息的传输队列,一个消息可以包含多个传输段
struct spi_device *spi; //传输的目的设备
unsigned is_dma_mapped:1; //如果为真,此次调用提供dma和cpu虚拟地址
/* completion is reported through a callback */
void (*complete)(void *context); //异步调用完成后的回调函数
void *context; //回调函数的参数
unsigned actual_length; //此次传输的实际长度
int status; //执行的结果,成功被置0,否则是一个负的错误码
struct list_head queue;
void *state;
};
通过
static inline void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
初始化spi_message,进而将spi_transfer添加到spi_message中,发起传输有两种方式分别是同步传输和异步传输。
extern int spi_sync(struct spi_device *spi, struct spi_message *message); -------------同步传输
extern int spi_async(struct spi_device *spi, struct spi_message *message); -------------异步传输
下面介绍两个通用的SPI传输函数:
static inline ssize_t spidev_sync_write(struct spidev_data *spidev, size_t len)
{
struct spi_transfer t = {
.tx_buf = spidev->buffer,
.len = len,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spidev_sync(spidev, &m);
}
static inline ssize_t spidev_sync_read(struct spidev_data *spidev, size_t len)
{
struct spi_transfer t = {
.rx_buf = spidev->buffer,
.len = len,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spidev_sync(spidev, &m);
}