SPI在linux3.14.78 FS_S5PC100(Cortex A8)和S3C2440上驱动移植(deep dive)

发布时间:2024-08-09  

由于工作的原因,对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,

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。

我们与500+贴片厂合作,完美满足客户的定制需求。为品牌提供定制化的推广方案、专属产品特色页,多渠道推广,SEM/SEO精准营销以及与公众号的联合推广...详细>>

利用葫芦芯平台的卓越技术服务和新产品推广能力,原厂代理能轻松打入消费物联网(IOT)、信息与通信(ICT)、汽车及新能源汽车、工业自动化及工业物联网、装备及功率电子...详细>>

充分利用其强大的电子元器件采购流量,创新性地为这些物料提供了一个全新的窗口。我们的高效数字营销技术,不仅可以助你轻松识别与连接到需求方,更能够极大地提高“闲置物料”的处理能力,通过葫芦芯平台...详细>>

我们的目标很明确:构建一个全方位的半导体产业生态系统。成为一家全球领先的半导体互联网生态公司。目前,我们已成功打造了智能汽车、智能家居、大健康医疗、机器人和材料等五大生态领域。更为重要的是...详细>>

我们深知加工与定制类服务商的价值和重要性,因此,我们倾力为您提供最顶尖的营销资源。在我们的平台上,您可以直接接触到100万的研发工程师和采购工程师,以及10万的活跃客户群体...详细>>

凭借我们强大的专业流量和尖端的互联网数字营销技术,我们承诺为原厂提供免费的产品资料推广服务。无论是最新的资讯、技术动态还是创新产品,都可以通过我们的平台迅速传达给目标客户...详细>>

我们不止于将线索转化为潜在客户。葫芦芯平台致力于形成业务闭环,从引流、宣传到最终销售,全程跟进,确保每一个potential lead都得到妥善处理,从而大幅提高转化率。不仅如此...详细>>