spi驱动框架全面分析,从master驱动到设备驱动

发布时间:2024-07-16  

内核版本:linux2.6.32.2 

硬件资源:s3c2440

参考:   韦东山SPI视频教程



内容概括:

    1、I2C 驱动框架回顾

    2、SPI 框架简单介绍

    3、master 驱动框架

        3.1 驱动侧

        3.2 设备侧

    4、SPI 设备驱动框架



        4.1 设备册

        4.2 驱动侧


    5、设备驱动程序实例


1、I2C 驱动框架回顾

    在前面学习 I2C 驱动程序的时候我们知道,I2C 驱动框架分为两层,一层是控制器驱动程序 i2c_adapter,它一般是由芯片厂商写好的,主要提供一个 algorithm 底层的 i2c 协议的收发函数。i2c_adapter 驱动是基于 platform 模型,在driver侧的probe函数里,取出资源信息进行设置,最后将adapter注册到i2c_bus_type,注册时会调用 i2c_scan_static_board_info,扫描并使用 i2c_new_device 创建设备(设备层的设备)。我们还提到了4种创建device的方式。


    另一层是设备驱动层,基于 i2c_bus_type ,这个就很简单了,在设备驱动层 device 只需要提供一个从设备地址和名字,在 driver 里使用 i2c_smbus_read_byte_data 等类似的函数进行收发即可了,i2c_smbus_read_byte_data 等函数最终就会调用到 我们的 i2c_adapter->algorithm 里的收发函数进行收发。


2、SPI 框架简单介绍

    对于SPI的大框架,与I2C是完全一致的,也分为两层,控制器驱动程序层叫 spi_master ,主要提供transfer函数,进行spi协议的收发。spi_master 也是基于 Platform 模型的,注册 spi_master 时也会扫描一个链表进行注册设备,简直太相似了。


    另一层是设备驱动层,基于 spi_bus_type,相比 i2c 在device中需要提供的信息多一些,需要有名字、片选、最大速率、模式、中断号等等,在driver里则使用spi_read、spi_writer 等函数,最终也会调用到 master->transfer 函数进行发送接收。


    相比 I2C ,SPI驱动的框架是要简单的,因为它少了两种注册device的方式,另外它不需要像I2C一样去发送Start信号和设备地址去探测设备,SPI只需要片选选中就行了。但是它的底层收发的控制,相对I2C要复杂一点,毕竟4根线。


3、master 驱动框架

    之前,分析的驱动程序都是 S3C2410S3C2440 平台的,由于我的开发板 Mini2440 没有SPI设备,因此厂商带的内核里关于 SPI 驱动部分不完整,而且在s3c23xx_spi_probe函数里注册master的时候写的十分复杂,干扰信息太多,不适合分析学习,因此,我搜索了一下其他平台的代码,发现 atmel_spi.c (driversspi),里 atmel 实现的底层控制器驱动简单清晰多了,因此就拿它开刀,分析Master驱动框架。


  3.1 驱动侧

    前面简介里,我提到 master 驱动框架是基于 platform 平台的(我分析的这俩都是,其它的不清楚),那么肯定就要注册platform_driver了,下面我们就开看看。


分配一个platfrom_driver结构


static struct platform_driver atmel_spi_driver = {  

    .driver     = {  

        .name   = "atmel_spi",  

        .owner  = THIS_MODULE,  

    },  

    .suspend    = atmel_spi_suspend,  

    .resume     = atmel_spi_resume,  

    .remove     = __exit_p(atmel_spi_remove),  

};  

    将 atmel_spi_driver 注册到 platform_bus_type ,匹配设备 probe



static int __init atmel_spi_init(void)  

{  

    return platform_driver_probe(&atmel_spi_driver, atmel_spi_probe);  

}     

    我们之前都是将 probe 函数,直接放在driver结构体里,这里不是,而是调用了 platform_driver_probe ,就不贴代码了,还看段函数介绍,大致了解下什么意思。

/**

 * platform_driver_probe - register driver for non-hotpluggable device

 * @drv: platform driver structure

 * @probe: the driver probe routine, probably from an __init section

 *

 * Use this instead of platform_driver_register() when you know the device

 * is not hotpluggable and has already been registered, and you want to

 * remove its run-once probe() infrastructure from memory after the driver

 * has bound to the device.

 *

 * One typical use for this would be with drivers for controllers integrated

 * into system-on-chip processors, where the controller devices have been

 * configured as part of board setup.

 *

 * Returns zero if the driver registered and bound to a device, else returns

 * a negative error code and with the driver not registered.

 */

    1、适用于非热插拔设备

    2、通常Probe位于__init段3、当你知道device是非热拔插的,而且设备已经被注册了,而且你想在probe函数调用一次之后就销毁它节省空间,使用 platform_driver_probe 而非 platform_driver_register。

    4、一个典型的应用是,用在完整的控制器驱动,控制器设备被当作 board setup 的一部分(在板子初始化的时候,设备就已经被注册了,放在board_info里)

    5、返回0 ,如果driver注册成功且匹配到一个device ,以后再也无法被别的device probe了。

    6、否则,返回一个错误,且driver未注册。

    显然,我们写的正式一个控制器驱动程序,设备侧确实是早已注册(后边会讲)。

    疑问:有人说使用 platform_driver_probe 时 driver 只能被一个 device 匹配绑定,之后再也无法被别的device probe,难道说,我有俩spi控制器还需要写两个控制器驱动程序么?我认为这种说法是不对的,我猜大概是driver注册时,会匹配一遍device链表,把能支持的device都probe,之后再有deivce注册进来就不行了。这个有待验证。


    i2c驱动框架里,是在driver->probe 分配设置注册adapter,想必spi也是在driver->probe里分配设置注册master。

static int __init atmel_spi_probe(struct platform_device *pdev)  

{  

    struct resource     *regs;  

    int         irq;  

    struct clk      *clk;  

    int         ret;  

    struct spi_master   *master;  

    struct atmel_spi    *as;  

      

    /* 获取 device 侧提供的Io内存以及中断 */  

    regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);  

    irq = platform_get_irq(pdev, 0);  

      

    /* 获取 spi 时钟,一会好使能它 */  

    clk = clk_get(&pdev->dev, "spi_clk");  

  

    /* 分配一个spi_master结构 额外加上一个 atmel_spi 用来存放其它信息 */  

    master = spi_alloc_master(&pdev->dev, sizeof *as);  

  

    /* 设置 master  */  

    master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;   // 所支持的模式  

    master->bus_num = pdev->id;   // 控制器编号,用于分辨外围spi设备是连接在哪一个控制器上  

    master->num_chipselect = 4;  // 片选最大值+1,spi设备的片选值要小于它  

    master->setup = atmel_spi_setup; // 一个控制器上可能接有多个spi设备,它们的频率和模式是不一样的,用于设备之间切换时设置这些信息。  

    master->transfer = atmel_spi_transfer;   // 最重要的发送函数  

    master->cleanup = atmel_spi_cleanup;  

      

    /* 将 Master 放入 pdev->dev->p->driver_data 里*/  

    platform_set_drvdata(pdev, master);   

      

    /* as 指向 master->dev->p->driver_data ,填充多出来那个 atmel_spi 结构 */  

    as = spi_master_get_devdata(master);  

    as->buffer = dma_alloc_coherent(&pdev->dev, BUFFER_SIZE,  

                    &as->buffer_dma, GFP_KERNEL);  

    spin_lock_init(&as->lock);  

    INIT_LIST_HEAD(&as->queue);  

    as->pdev = pdev;  

    as->regs = ioremap(regs->start, (regs->end - regs->start) + 1);  

    as->irq = irq;  

    as->clk = clk;  

      

    /* 注册中断 使能时钟 */  

    ret = request_irq(irq, atmel_spi_interrupt, 0,  

            dev_name(&pdev->dev), master);  

    clk_enable(clk);  

      

    /* 设置硬件寄存器 */  

    spi_writel(as, CR, SPI_BIT(SWRST));  

    spi_writel(as, CR, SPI_BIT(SWRST)); /* AT91SAM9263 Rev B workaround */  

    spi_writel(as, MR, SPI_BIT(MSTR) | SPI_BIT(MODFDIS));  

    spi_writel(as, PTCR, SPI_BIT(RXTDIS) | SPI_BIT(TXTDIS));  

    spi_writel(as, CR, SPI_BIT(SPIEN));  

  

    /* 注册master */  

    ret = spi_register_master(master);  

  

    return 0;  

}  

    对于master的设置过程注释已经说的很明白了,我们还得看看分配和注册过程。



struct spi_master *spi_alloc_master(struct device *dev, unsigned size)  

{  

    struct spi_master   *master;  

  

    master = kzalloc(size + sizeof *master, GFP_KERNEL);  

  

    device_initialize(&master->dev); // 初始化设备  

    master->dev.class = &spi_master_class;     

    master->dev.parent = get_device(dev);    // 在 sysfs 平台设备xxx目录下创建目录  

    spi_master_set_devdata(master, &master[1]);  

  

    return master;  

}  

    1、spi_alloc_master 实际申请的内存大小为一个struct master + struct atmel_spi,并用master->dev->p->driver_data 指向这个多出来的 struct atmel_spi 空间,用来存放 master 的中断 、寄存器等东西。

    2、初始化 master->dev ,设置它的父设备等。


int spi_register_master(struct spi_master *master)  

{  

    /* 将master注册到内核中去 */  

    dev_set_name(&master->dev, "spi%u", master->bus_num);  

    status = device_add(&master->dev);  

      

    /* 扫描spi设备信息,创建设备 */  

    scan_boardinfo(master);  

}  

    1、设置 master->dev 的名字,例如 spi0、spi1 ...

    2、device_add 注册设备


    3、扫描spi设备信息:scan_boardinfo(master)



static void scan_boardinfo(struct spi_master *master)  

{  

    struct boardinfo    *bi;  

  

    mutex_lock(&board_lock);  

    list_for_each_entry(bi, &board_list, list) {  

        struct spi_board_info   *chip = bi->board_info;  

        unsigned        n;  

        /* 如果说  board_info 提供的bus_num 和 master—>bus_num 一致,则调用 spi_new_device */  

        for (n = bi->n_board_info; n > 0; n--, chip++) {  

            if (chip->bus_num != master->bus_num)  

                continue;  

  

            (void) spi_new_device(master, chip);    // 我们放到设备驱动层,在分析它  

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

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

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

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

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

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

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

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