Linux spi system

《Linux spi system》 题图:gratisography

Linux spi system

SPI是由Motorola提出的一种全双工同步串行通信接口,通信波特率可以高达5Mbps,但具体速度大小取决于SPI硬件,SPI接口具有全双工操作,操作简单,数据传输速率较高的优点,但也存在没有指定的流控制,没有应答机制确认是否接收到数据的缺点。

1.Linux下SPI的驱动架构

如下:

《Linux spi system》 SPI框架

从图中可以观察到SPI系统的整个框架,发现跟I2C的框架很十分相似;

硬件和用户空间就不多说了,直接看内核空间部分。

内核空间主要分为三个模块,控制器驱动,设备器驱动,和连接两个驱动的SPI核心;

spi与i2c内核空间的对比

  • master就相当于i2c中的apapter
  • spi_device即i2c中的i2c_client
  • spi core 即i2c core

不同于i2c的目录框架比较忙明确,i2c的控制器驱动在/drivers/i2c/busses/下面,设备器驱动在/drivers/i2c/chips/下面。而spi的控制器就直接放在spi/目录下,设备器驱动根据功能分开在各个地方,如spi flash的一般就在/drivers/mtd/device/下,siwtch的spi接口就放在/drivers/net/ethernet/下。

2.SPI master驱动

SPI控制器的驱动分为两部分,device和driver,使用platform总线进行连接。

platform_device的注册一般在arch下面实现,使用platform_add_devices(arch_devices, ARRAY_SIZE(arch_devices));进行添加,由于内核中有很多使用platform注册的总线,所以一般会统一使用一次platform_add_devices函数进行添加,如下:

static struct platform_device *arch_devices[] __initdata = {
    &arch_nor,
    &arch_i2c,
    &arch_wdt,
    &arch_spi,
    &arch_tdm_device,
    &arch_pfe_device,
}

一个arch_devices里面包含很多platform_device,每个device各自实现后,再一块儿添加。

static struct platform_device comcerto_spi = {
    .name = "comcerto_spi",
    .id = 0,
    .num_resources = ARRAY_SIZE(comcerto_spi_resource),
    .resource = comcerto_spi_resource,
    .dev = {
        .platform_data = &ls_spi_pdata,
    },
};

platform_driver的实现在/driver/spi/中实现,使用platform_driver_registerplatform_driver_unregister进行加载下载。

static struct platform_driver designware_spi_driver = {
    .probe  = designware_spi_probe,
    .remove = __devexit_p(designware_spi_remove),
#if CONFIG_PM
        .suspend        = designware_spi_suspend,
        .resume         = designware_spi_resume,
#endif
    .driver = {
        .name = DESIGNWARE_SPI_NAME,
        .owner = THIS_MODULE,
    },
};

static int __init designware_spi_init(void)
{
    return platform_driver_register(&designware_spi_driver);
}

static void __exit designware_spi_exit(void)
{
    platform_driver_unregister(&designware_spi_driver);
}

module_init(designware_spi_init);
module_exit(designware_spi_exit);

designware_spi_probe函数里面就是master的实现部分,里面会对spi进行很多处理,如中断/dma/等,最后会使用spi_register_master函数进行注册spi_master,该函数就是spi的核心层了,位于/driver/spi/spi.c中,这一部分的内容还不知道如何描述,后面有思路时在更新。

3.SPI device驱动

SPI设备的驱动也是分为两部分,device和driver,使用spi总线进行连接。

spi_device的注册一般在arch下面实现,使用spi_register_board_info(arch_spi_board_info, ARRAY_SIZE(arch_spi_board_info))函数进行添加,函数里面的arch_spi_board_info
spi_board_info结构体,需要我们对里面的参数进行配置,如下例子:

static struct spi_board_info comcerto_spi_board_info[] = {
    {
        /* FIXME: for chipselect-0 */
        .modalias = "s25fl256s0",
        .chip_select = 0,
        .max_speed_hz = 4*1000*1000,
        .bus_num = 1,
        .irq = -1,
        .mode = SPI_MODE_3,
        .platform_data = &spi_pdata,
        .controller_data = &spi_ctrl_data,
    },
}

spi_driver的实现也比较简单,一般就是通过spi_register_driver来进行添加,spi_unregister_driver进行卸载。

如下例子:

static struct spi_driver m25p80_driver = {
    .driver = {
        .name   = "m25p80",
        .bus    = &spi_bus_type,
        .owner  = THIS_MODULE,
    },
    .id_table   = m25p_ids,
    .probe  = m25p_probe,
    .remove = __devexit_p(m25p_remove),
};

static int __init m25p80_init(void)
{
    return spi_register_driver(&m25p80_driver);
}

static void __exit m25p80_exit(void)
{
    spi_unregister_driver(&m25p80_driver);
}

module_init(m25p80_init);
module_exit(m25p80_exit);

spi_driver.id_table信息与spi_board_info所指向的设备(或者设备树中的节点)匹配成功,则执行spi_driver.probe(),对应probe函数里面实现的内容就要根据spi所使用的场景进行对于的实现了。

如spi flash一般是作为存储介质位于mtd下面的,所以probe函数里面就要使用mtd_device_parse_register来实现mtd的添加;switch的spi接口是用来跟switch进行数据通信,probe函数里面就可以使用register_chrdev_region来添加个字符设备进行操作。

调试驱动的时候最常用的方法就是使用printk来进行交互,进行定位、验证,但是要在哪边进行printk呢,个人觉得调试spi驱动有一个地方一定要进行printk,那就是位于spi.c下的spi_match_id()函数,如下:

static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,
                                                const struct spi_device *sdev)
{
        while (id->name[0]) {
                if (!strcmp(sdev->modalias, id->name))
                        return id;
                id++;
        }
        return NULL;
}

总结:分析调试I2C/SPI的驱动后,有以下几点感悟:

  • 不管什么驱动,大致都可以分为两个动作响应,或者说两个probe接口,一个是控制器的probe(根据cpu的不同进行改变),一个是设备的probe(根据设备的不同进行改变)。
  • 每一个probe接口有与之对于的device和driver,并且需要一种总线相连。
  • 控制器的probe一般使用platform总线,对应platform_deviceplatform_driver;设备的probe使用相关的总线,如i2c总线,则对应i2c_devicei2c_driver,i2c总线,则对应spi_devicespi_driver
  • probe函数的触发都是对应的match_id函数,如platform_match_idi2c_match_idspi_match_id
  • platform的device的注册在arch下面,使用platform_add_devices添加,platform对应的driver,位于各目录下,如i2c位于i2c/busses/里面,使用i2c_register_adapter来添加。
  • 驱动如i2c的device的注册位于arch下,使用i2c_register_board_info或dts添加,对应i2c的driver位于i2c/chips/下面,使用i2c_add_driver来添加。

Linux spi system的分析就到这边,有感悟时会持续会更新。

注:以上内容都是本人在学习过程积累的一些心得,难免会有参考到其他文章的一些知识,如有侵权,请及时通知我,我将及时删除或标注内容出处,如有错误之处也请指出,进行探讨学习。文章只是起一个引导作用,详细的数据解析内容还请查看Linux相关教程,感谢您的查阅。

    原文作者:Creator_Ly
    原文地址: https://www.jianshu.com/p/60d2d5849db9
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞