由于工作的原因,对SPI的理解最为深刻,也和SPI最有感情了,之前工作都是基于OSEK操作系统上进行实现,也在US/OS3上实现过SPI驱动的实现和测试,但是都是基于基本的寄存器操作,没有一个系统软件架构的思想,感觉linux SPI驱动很强大,水很深,废话少说,SPI总线上有两类设备:一类是主机端,通常作为SOC系统的一个子模块出现,比如很多嵌入式MPU中都常常包含SPI模块。一类是从机被控端,例如一些SPI接口的Flash、传感器等等。主机端是SPI总线的控制者,通过使用SPI协议主动发起SPI总线上的会话。而受控端则被动接受SPI主控端的指令,并作出响应的响应,本文读者前提是必须熟练掌握linux Platform总线驱动模型 和基本字符设备驱动的实现。
SPI总线由MISO(串行数据输入)、MOSI(串行数据输出)、SCK(串行移位时钟)、CS(使能信号)4个信号线组成。SPI常用四种数据传输模式,主要差别在于:输出串行同步时钟极性(CPOL)和相位(CPHA)可以进行配置。如果CPOL= 0,串行同步时钟的空闲状态为低电平;如果CPOL= 1,串行同步时钟的空闲状态为高电平。如果CPHA= 0,在串行同步时钟的前沿(上升或下降)数据被采样;如果CPHA = 1,在串行同步时钟的后沿(上升或下降)数据被采样。同I2C子系统类似,SPI子系统分为3个部分,分别是SPI核心层、主控制器驱动和协议驱动,通俗一点就是SPI核心层主要完成1.定义并注册SPI总线spi_bus_type和控制器类spi_master_class;2.提供spi_driver,spi_device和spi_master的分配,创建,注册和注销;3.实现SPI通信方法的上层代码。主控制器驱动对应I2C的适配器驱动,SPI用spi_master来描述相应的控制器,通常用spi_bitbang来控制实际的数据传输,功能非常类似与i2c_algorithm。协议驱动类比I2C设备驱动,可以理解成客户端驱动,下面详细分析和实现基于linux3.14.78内核版本 SPI在S3C2440/6410上驱动移植.
Step1,实现SPI控制器的设备接口(相对应有两种方式,一种实现方式是S3c2440纯粹通过配置来实现,另一种实现方式针对m25p10,独立编写单独的spi驱动模块)
首先针对S3C2440,SPI控制器的设备接口在drivers/spi/spidev.c中实现,下图为相应的软件流程图,spidev.c中的spidev_init()作为模块初始化函数,在系统启动或者模块加载是被调用,主要完成以下三种操作:
1、调用register_chrdev()为SPI控制器注册主设备号为153,次设备号范围为0~255,文件操作集合为spidev_fops的字符设备;
2、调用class_create()注册一个名为“spidev”的设备类;
3、调用spi_register_driver()向系统添加SPI控制器的设备驱动spidev_spi;
spi_register_driver()将驱动spidev_spi添加到SPI核心层注册的spi_bus_type总线上,注意,该总线属于spi总线,Spi总线对应的总线类型为spi_bus_type,在内核的drivers/spi/spi.c中定义,对应的匹配规则是(高版本中的匹配规则会稍有变化,引入了id_table,可以匹配多个spi设备名称)详见以下代码。由于spi总线的匹配方式是检查spi_device.modalias与spi_driver.driver.name是否相同,而spidev_spi的driver.name是“spidev”,so 只有spi_bus_type总线上的modalias为“spidev”的设备,才可以与spidev_spi驱动匹配。由于S3C2440拥有两个SPI控制器,对应的平台信息是s3c_device_spi0和s3c_device_spi1,可以将其添加到机器配置文件的My2440_devices数组中,驱动中probe方法中用到的总线编号、片选总数等信息来源于平台数据,这些数据需要用户添加,添加方式有两种,平台设备的平台数据类型是S3C2410_SPI_info,在板级初始化文件中可以为两个SPI平台设备分别定义和添加这些平台数据,此外为了支持SPI控制器设备接口功能,还需要在机器配置文件为SPI控制器设备添加并注册spi_board_info对象,详见下面代码。
备与驱动匹配后,通过调用spidev_spi的probe方法来绑定工作见代码。SPI控制器设备操作集合是spidev_fops,主要包括spidev_write,spidev_read,spidev_ioctl,spidev_open和spidev_release。下面的代码注释中详尽分析了spidev_read()函数的实现过程,spidev_write()的分析和实现方法与read类似,spidev_open()会遍历device_list链表,找出其中设备号与打开设备文件的inode.i_rdev相等的spidev_data对象,并将该对象记录在文件的私有数据filp->private_data中,以供read和write函数获取,此外要实现全双工的传输需要借助控制器设备的ioctl方法,对应实现函数是spidev_ioctl(),全双工传输也是通过spidev_sync()完成的,与半双工传输唯一不同的是在消息的传输段中,tx_buffer和rx_buffer同时被设置,它还提供获取和修改时钟模式、子宽、最大时钟频率属性的命令。
1 static struct s3c2410_spi_info s3c2410_spi0_platdata = {
2 .pin_cs = S3C2410_GPG2,
3 .num_cs = 2,
4 .bus_num = 0,
5 .gpio_setup = s3c24xx_spi_gpiocfg_bus0_gpe11_12_13,
6 };
7 static struct spi_board_info s3c2410_spi0_board[] =
8 {
9 [0] = {
10 .modalias = "spidev",
11 .bus_num = 0,
12 .chip_select = 0,
13 .max_speed_hz = 500 * 1000,
14 },
15 [1] = {
16 .modalias = "at25",
17 .platform_data = &at25_eeprom_data,
18 .bus_num = 0,
19 .chip_select = 1,
20 .max_speed_hz = 500 * 1000,
21 }
22 };
23 static struct s3c2410_spi_info s3c2410_spi1_platdata = {
24 .pin_cs = S3C2410_GPG3,
25 .num_cs = 1,
26 .bus_num = 1,
27 .gpio_setup = s3c24xx_spi_gpiocfg_bus1_gpg5_6_7,
28 };
29 static struct spi_board_info s3c2410_spi1_board[] =
30 {
31 [0] = {
32 .modalias = "spidev",
33 .bus_num = 1,
34 .chip_select = 0,
35 .max_speed_hz = 500 * 1000,
36 }
37 };
38 static void __init My2440_machine_init(void)
39 {
40 s3c24xx_fb_set_platdata(&My2440_fb_info);
41 s3c_i2c0_set_platdata(NULL);
42 i2c_register_board_info(0, i2c_devices, ARRAY_SIZE(i2c_devices));
43 s3c_device_spi0.dev.platform_data= &s3c2410_spi0_platdata;
44 spi_register_board_info(s3c2410_spi0_board, ARRAY_SIZE(s3c2410_spi0_board));
45 s3c_device_spi1.dev.platform_data= &s3c2410_spi1_platdata;
46 spi_register_board_info(s3c2410_spi1_board, ARRAY_SIZE(s3c2410_spi1_board));
47 s3c_device_nand.dev.platform_data = &My2440_nand_info;
48 s3c_device_sdi.dev.platform_data = &My2440_mmc_cfg;
49 platform_add_devices(My2440_devices, ARRAY_SIZE(My2440_devices));
50 }
1 struct bus_type spi_bus_type = {
2 .name = "spi",
3 .dev_attrs = spi_dev_attrs,
4 .match = spi_match_device,
5 .uevent = spi_uevent,
6 .suspend = spi_suspend,
7 .resume = spi_resume,
8 };
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
const struct spi_device *spi = to_spi_device(dev);
return strcmp(spi->modalias, drv->name) == 0;
}
static int spidev_probe(struct spi_device *spi)
{
struct spidev_data *spidev;
int status;
unsigned long minor;
/* Allocate driver data, 分配并初始化一个spidev_data对象,用来描述SPI控制器设备驱动操作的控制器设备,包含它的设备号,传输缓存和内嵌spi_device*/
spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
if (!spidev)
return -ENOMEM;
/* Initialize the driver data ,将与spidev_spi驱动匹配的spi_device赋值给spidev_data.spi,通过find_first_zero_bit()从位图minors中分配到次设备号,
与主设备号SPIDEV_MAJOR构成设备号并赋值给spidev_data.devt,然后调用device_create()在/dev下创建名称格式为“spidev%d%d”的字符设备文件,第一个%d表示控制器编号,
即spi_device.master.bus_num,第二个对应片选号,即spi_device.chip_select*/
spidev->spi = spi;
spin_lock_init(&spidev->spi_lock);
mutex_init(&spidev->buf_lock);
INIT_LIST_HEAD(&spidev->device_entry);
/* If we can allocate a minor number, hook up this device.
* Reusing minors is fine so long as udev or mdev is working.
*/
mutex_lock(&device_list_lock);
minor = find_first_zero_bit(minors, N_SPI_MINORS);
if (minor < N_SPI_MINORS) {
struct device *dev;
spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
dev = device_create(spidev_class, &spi->dev, spidev->devt,
spidev, "spidev%d.%d",
spi->master->bus_num, spi->chip_select);
status = PTR_ERR_OR_ZERO(dev);
} else {
dev_dbg(&spi->dev, "no minor number available!n");
status = -ENODEV;
}
if (status == 0) {
set_bit(minor, minors);
/*将spidev_data对象添加到一个名为device_list的设备列表中,最后将spidev_data对象设置为对应spi_device的驱动数据,将其值赋值给dpi_device.dev.driver_data*/
list_add(&spidev->device_entry, &device_list);
}
mutex_unlock(&device_list_lock);
if (status == 0)
spi_set_drvdata(spi, spidev);
else
kfree(spidev);
return status;
}
/*spidev_read(),spidev_write()实现的读写操作都是半双工的传输,要实现全双工传输需要调用spidev_ioctl()操作,在spidev_read()中,通过调用spidev_sync_read()
同步读取指定长度数据到spidev_data_buffer中,然后调用copy_to_user()将读取的数据复制到用户空间缓冲*/
static ssize_t spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct spidev_data *spidev;
ssize_t status = 0;
/* chipselect only toggles at start or end of operation */
if (count > bufsiz)
return -EMSGSIZE;
spidev = filp->private_data;
mutex_lock(&spidev->buf_lock);
status = spidev_sync_read(spidev, count);
if (status > 0) {
unsigned long missing;
missing = copy_to_user(buf, spidev->buffer, status);
if (missing == status)
status = -EFAULT;
else
status = status - missing;
}
mutex_unlock(&spidev->buf_lock);
return status;
}
/*spidev_sync_read()中定义一个spi_transfer和spi_message对象,使用spidev_data.buffer初始化spi_transfer.rx_buf,
然后将spi_transfer对象添加到spi_message中,调用spidev_sync()传输信息;*/
spidev_sync_read(struct spidev_data *spidev, size_t len)
{
struct spi_transfer t = {
.rx_buf = spidev->buffer,