zoukankan      html  css  js  c++  java
  • SPI masterslave驱动框架分析

    SPI主要分主控制器及SPI设备两端,两者之间靠spi.h这个公共接口来作为抽象层。
    首先来分析SPI总线:【本篇着重分析注册及匹配流程,下篇将会详细分析SPI master驱动的时序及实现方式】
    ===================================================================================================
    spi bus
    ===================================================================================================
    总线类型:
    struct bus_type spi_bus_type = {
        .name        = "spi",
        .dev_attrs    = spi_dev_attrs,
        .match        = spi_match_device,
        .uevent        = spi_uevent,
        .pm        = &spi_pm,
    };
    注:这里的spi_bus_type没有实现probe函数,对下面的drv.probe有重要影响。

    总线注册:【对应与/sys/bus/目录】
    static int __init spi_init(void)
    {
        int    status;
        buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
        status = bus_register(&spi_bus_type);            //注册总线类型
        status = class_register(&spi_master_class);        //注册spi主控器类型
        return 0;
    }

    spi主控器为一个device,为此类型
    static struct class spi_master_class = {
        .name        = "spi_master",
        .owner        = THIS_MODULE,
        .dev_release    = spi_master_release,
    };

    整个spi master侧的设备及驱动都是依赖与平台驱动的,下面详细跟踪spi的整个注册流程:
    以展讯平台的8810 spi master为例:
    static struct platform_device sprd_spi_controller_device[] =
    {
           {
                   .name = "sprd_spi",
                   .id = 0,
                   .dev = {
                           .dma_mask = &spi_dmamask,
                           .coherent_dma_mask = DMA_BIT_MASK(32),
                           },
                   .resource = spi_resources[0],
                   .num_resources = ARRAY_SIZE(spi_resources[0]),
           },
           {
                   .name = "sprd_spi",
                   .id = 1,
                   .dev = {
                           .dma_mask = &spi_dmamask,
                           .coherent_dma_mask = DMA_BIT_MASK(32),
                           },
                   .resource = spi_resources[1],
                   .num_resources = ARRAY_SIZE(spi_resources[1]),
           },
    };
    在8810中一共有两个spi master,由id来区别,下面是对应的MEM/IRQ资源:
    static struct resource spi_resources[][2] = {
                   {
                           {
                                   .start = SPRD_SPI0_PHYS,
                                   .end = SPRD_SPI0_PHYS + SZ_4K - 1,
                                   .flags = IORESOURCE_MEM,
                           },
                           {
                                   .start = IRQ_SPI0_INT,
                                   .end = IRQ_SPI0_INT,
                                   .flags = IORESOURCE_IRQ,
                           },
                   },
                   {
                           {
                                   .start = SPRD_SPI1_PHYS,
                                   .end = SPRD_SPI1_PHYS + SZ_4K - 1,
                                   .flags = IORESOURCE_MEM,
                           },

                           {
                                   .start = IRQ_SPI1_INT,
                                   .end = IRQ_SPI1_INT,
                                   .flags = IORESOURCE_IRQ,
                           },
                   },
    };
    首先需要注册平台设备:
    platform_device_register(&sprd_spi_controller_device[0]);
    platform_device_register(&sprd_spi_controller_device[1]);
    这一步主要的工作有:
    1. 初始化platform_device的dev结构
    2. 设置dev的dma相关
    3. 挂接设备所属总线到platform_bus_type
    4. 解析资源RES
    5. 挂接dev到驱动模型

    然后需要注册平台驱动:【在master的驱动实现中】
    static struct platform_driver sprd_spi_driver = {
        .driver        = {
            .name    = "sprd_spi",
            .owner    = THIS_MODULE,
        },
        .suspend    = sprd_spi_suspend,
        .resume        = sprd_spi_resume,
        .remove        = __exit_p(sprd_spi_remove),
    };
    初始化:
    static int __init sprd_spi_init(void)
    {
        return platform_driver_probe(&sprd_spi_driver, sprd_spi_probe);
    }
    这一步主要的工作有:
    1. 填入驱动真正的probe,并注册平台驱动【platform_driver_register】
    2. 挂上平台总线platform_bus_type,检测驱动名称是否重复,加入bus【bus_add_driver】
    3. 下面的事情是设备驱动模型里面的任务了,为驱动建立节点,建立私有driver_private,初始化设备链等
    probe的详细过程见上一篇【设备驱动模型】。
    4. 调用probe函数,进行匹配
           4.1 int __init sprd_spi_probe(struct platform_device *pdev),这里的参数来自于哪里呢?从上面的分析来看,很明显这个时候device/driver
    都已经明确挂到了平台总线下,那么这里的平台设备为此驱动所属的bus下的所有设备。
           4.2 在probe函数内部,可以区分也可以不区分具体的platform device, 这里分成两种模式:
            a. 不同的spi master使用自己独立的master driver
            b. 使用相同的spi master driver,但是需要生成两个master是必须的,并且由不同的bus_num来提供给device挂接
        这里涉及到两个匹配的过程:
            master device <-----> master driver
            master driver <-----> spi slave
            第一个过程可以由device的name + id来判断,并由id来转化成master的bus_num,第二个过程则直接由device的bus_num项与master匹配
        4.3 生成具体的master后,需要挂接标准接口实现到spi master,如:setup/transfer/cleanup等
        4.4 这里可以有两个数据位置值得注意,其一是master内部的private data,第二个是dev下的master
            master = spi_alloc_master(&pdev->dev, sizeof *sprd_data);/spi_master_get_devdata(master);
            platform_set_drvdata(pdev, master);/platform_get_drvdata(pdev);
        4.5 还有就是一些资源的解析到private data中,设置dma 参数,中断及相关的变量/队列等
        4.6 最重要的一个步骤,注册master到整个SPI系统中【注,不是bus!,master作为一个抽象,代表了spi的master端的所有功能及控制】
            ret = spi_register_master(master);
            a.初始化一些标准接口,如用于传输spi_message的消息队列
            b.加入到全局链尾,spi_master_list
            c.匹配已经存在的board_list链,并进行匹配,spi_match_master_to_boardinfo,这里主要进行两步操作:第一是匹配bus_num,第二步是
        进行实际的slave与master的匹配工作,
        struct spi_device *spi_new_device(struct spi_master *master, struct spi_board_info *chip)【分析在slave后面】
    注:这里的触发spi_match_master_to_boardinfo流程的源头可能有两个:
    1. 注册master:spi_register_master
    2. 注册spi board:spi_register_board_info


    系统为了SPI slave及master的匹配,声明了两个静态链表
    static LIST_HEAD(board_list);                    宿主为board_info->{list + spi_board_info}
    static LIST_HEAD(spi_master_list);                宿主为spi_master【已包含list_head】
    一个用于存储spi_board_info【slave端会详细描述】,一个用于存储master链。

    slave端的device声明:
    static struct spi_board_info openhone_spi_devices_wifi[] = {
        {
         .modalias = "spi_slot0",    // "spidev" --> spidev_spi
         .chip_select = SPRD_3RDPARTY_SPI_WIFI_CS,
         //.max_speed_hz = 24 * 1000 * 1000,    wch
          .max_speed_hz = 8 * 1000 * 1000,   
         .mode = SPI_CPOL | SPI_CPHA,
         },
    };
    slave端的注册流程:
    1.标准做法
    spi_register_board_info

    2.展讯做法
    gps_spi_dev = sprd_spi_wifi_device_register(1, NULL);
    struct spi_device *sprd_spi_wifi_device_register(int master_bus_num, struct spi_board_info *chip)
    {
        return sprd_spi_device_register(master_bus_num, chip, SPRD_3RDPARTY_SPI_WIFI_CS);
    }
    struct spi_device *sprd_spi_device_register(int master_bus_num,struct spi_board_info *chip,int type)
    展讯的实现流程较为混乱,梳理后的方式如下:【没有采用标准的做法】
    一共需要两个参数:1. 与master相对应的bus_num; 2. 与spi_board_info spi slave device相对应的CS【每个设备需要一个独立的CS】
    找到这两个设备后:
    使用1获取到master结构体,使用2获取到spi_board_info结构体,直接调用 spi_new_device
    标准的做法为:
    只需要注册spi_register_board_info即可,相关的匹配流程交给kernel去匹配。【boardinfo中含有bus_num/cs等,不需要单独拿出来自己匹配】

    至此:master与slave端都同时汇聚到了一起:spi_new_device,下面是这个函数的详细描述,
    在spi_new_device中并没有判断是否已经生成spi proxy的代码,那么是否会同一个master/slave生成了两次proxy?
    展讯的方法中,spi_board_info并没有注册到board_list中,故master不会匹配到,只有等到slave主动调用spi_new_device的时候才会成功匹配,
    标准的方法中,总是后加入链的进行匹配的工作。

    spi_new_device
    这个函数调用的条件是:master与slave bus_num一致,进行两者之间的匹配
    具体的工作如下:
    1.生成一个父节点为master->dev的spi_device对象,并且master指向匹配的master结构【注意:这里将设备的bus指向 &spi_bus_type,后续对设备驱动有大的作用!,
    而master指针则会负责后续的传输责任转发】
    2.获取spi_board_info上的所有信息到spi_device对象中去
    3.添加此spi_device设备到系统中去:spi_add_device
        检查片选小于master支持的片选总数
        设置名称为master->dev.name + cs_num
        确认没有使用总线上其它设备已占用的cs号(从名称判断)【不管什么的master上配对的device都隶属于这条系统仅有的spi总线】
        【这里有个问题:不同master上的cs号的范围不能重复?一般cs号即为gpio号,不会重复】
        启动master接口,setup
        添加device->dev到sys系统中去


    以上都是在平台总线上的master device与driver的配对,master与slave的配对,这些对于一般的spi设备驱动开发人员都是透明的,下面来说
    spi driver与spi device的配对过程:

    static struct spi_driver gpsspidev_spi = {
        .driver = {
            .name =        "spi_slot0",
            .owner =    THIS_MODULE,
        },
        .probe =    gpsspidev_probe,
        .remove =    __devexit_p(mxdspidev_remove),
    };
    注册到系统中去:
    major= spi_register_driver(&gpsspidev_spi);
    主要完成如下工作:
    首先设置其bus指向spi_bus_type,然后调用driver_register【加入到bus的driver链表中】
    一般在加入device或者driver的时候都会调用:driver_attach/device_attach
    Probe的详细过程见【设备驱动模型】
    在匹配过程中__driver_attach,首先会调用spi bus type的match函数【最高匹配否认优先级】
    其次是bus的probe【spi未实现,则调用driver本身的probe函数】,最后执行设备与驱动的绑定
    probe成功后需要对驱动本生的一些成员进行初始化,代表我probe到设备后就要开始准备工作了。
    SPI框架的match实现:
    static int spi_match_device(struct device *dev, struct device_driver *drv)
    {
        const struct spi_device    *spi = to_spi_device(dev);
        const struct spi_driver    *sdrv = to_spi_driver(drv);

        /* Attempt an OF style match */
        if (of_driver_match_device(dev, drv))
            return 1;

        if (sdrv->id_table)
            return !!spi_match_id(sdrv->id_table, spi);

        return strcmp(spi->modalias, drv->name) == 0;
    }
    先匹配driver的id table中的每一项中的名字是否与device匹配
    其次匹配driver的name与device是否相配
    一般的SPI slave驱动,直接名称匹配即可,不需要自己进行probe

    在分析的过程中,想到这么几个问题:
    总线类型与实际总线【master】的关系?
    系统中不区分master的不同,而只有一条类型为spi_bus_type的spi总线,所有注册上来的设备(spi_device)及设备驱动(spi_driver)都隶属于
    这条总线,这样可以对整个spi体系下的所有slave设备及驱动进行管理。

    spi device是什么时候挂到spi_bus_type设备链中去的?
    spi device指向总线spi_bus_type是在spi device初始化的时候做的
    反向挂是在:初始化完成之后,device_add函数中通过 bus_add_device(dev);添加到spi总线中去的

    如何访问spi设备以及访问过程中如何分发到master?
    spi slave driver会自行封装spi message,并请求同步到slave,具体由slave挂接的master实现具体的分发功能。
    这部分内容放在下一篇【SPI master驱动的时序及实现方式】详细讲解


  • 相关阅读:
    深入理解加密、解密、数字签名和数字证书
    支付网关的设计
    spring boot Rabbitmq集成,延时消息队列实现
    五一之起一台服务器玩玩-u盘安装centos
    shell初识
    用户身份切换之初窥企业远程用户没root还有root权限
    man帮助文档打印
    开源镜像软件下载网址{转载}
    bash shell第一课
    jQuery常用ajax操作
  • 原文地址:https://www.cnblogs.com/wenhuisun/p/3050024.html
Copyright © 2011-2022 走看看