现在的位置: 首页 > 综合 > 正文

SPI设备的驱动-1[转]

2018年04月10日 ⁄ 综合 ⁄ 共 11959字 ⁄ 字号 评论关闭

今天折腾了一天的SPI设备的驱动加载,甚至动用了逻辑分析仪来查看spi总线的波形,主要包括两个SPI设备,at45db321d和mcp2515,一个是串行的dataflash,一个是can总线设备芯片。前者对于我们来说非常重要,我们可以借助该设备对uboot和kernel以及根文件系统进行更新。
    预备知识:设备和驱动是如何匹配的?系统的热插拔是如何实现的?
    首先一点,设备和驱动是严格区分的,设备是设备,驱动是驱动,设备通过struct device来定义,当然用户也可以将该结构体封装到自己定义的device结构体中,例如,struct platform_device,这是我们采用platform_bus_type总线的设备定义的结构体形式:

include/linux/platform_device.h文件中:
struct platform_device {
    const char    * name;
    u32        id;
    struct device    dev;
    u32        num_resources;
    struct resource    * resource;
};
只要是9260的外围模块,就像IIC硬件控制器,SPI硬件控制器,都被完全的定义成这种结构体的格式,这种结构体主要包含了硬件资源和名称,硬件资源分为寄存器和IRQ两种。platform_device通过向内核注册struct device dev这个结构体来告诉内核加载这个设备,
    方法就是 device_register(&platform_device->dev)  
内核不关心你使用的是platform_device还是spi_device,内核只关心你的struct device结构体,内核通过这个struct device结构体自然能够顺藤摸瓜找到你是platform_device还是spi_device,这就是linux最引以为傲的contian_of()大法。

驱动通过struct driver这个结构体来定义,与struct device一致,你也可以用自己的结构体去封装:例如,struct platform_driver。
include/linux/platform_device.h文件中:

struct platform_driver {
    int (*probe)(struct platform_device *);
    int (*remove)(struct platform_device *);
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*suspend_late)(struct platform_device *, pm_message_t state);
    int (*resume_early)(struct platform_device *);
    int (*resume)(struct platform_device *);
    struct device_driver driver;
};
与device一致,应用程序通过driver_register(&platform_driver->driver)向内核中注册你当前的驱动,而内核不关心你封装成的结构,而内核搜索的方法还是同样的contain_of大法。

 

系统如何将这两者匹配上的?而不会将iic的设备加载到spi的驱动上面?不会将这个iic设备的驱动加载到那个iic设备上,设备和驱动之间是如何联系的?总线,这就是总线的作用!
include/linux/device.h文件中有总线类型的定义。
struct bus_type {
    const char        * name;

    struct subsystem    subsys;
    struct kset        drivers;
    struct kset        devices;
    struct klist        klist_devices;
    struct klist        klist_drivers;

    struct blocking_notifier_head bus_notifier;

    struct bus_attribute    * bus_attrs;
    struct device_attribute    * dev_attrs;
    struct driver_attribute    * drv_attrs;

    int        (*match)(struct device * dev, struct device_driver * drv);
    int        (*uevent)(struct device *dev, char **envp,
                  int num_envp, char *buffer, int buffer_size);
    int        (*probe)(struct device * dev);
    int        (*remove)(struct device * dev);
    void        (*shutdown)(struct device * dev);

    int (*suspend)(struct device * dev, pm_message_t state);
    int (*suspend_late)(struct device * dev, pm_message_t state);
    int (*resume_early)(struct device * dev);
    int (*resume)(struct device * dev);
};
这个总线设备中最重要的可能是match成员,由于我们一般很少去建立一个新的总线,所以我们很少涉及总线的编程,我们就只关注我们所关注的。
总线如何将两者关联起来,热插拔大家知道吧,当一个设备被通过device_register注册到内核中时,会导致一个热插拔事件产生,系统会遍历该总线上的所有驱动程序,调用bus的match算法,来寻找与该设备相匹配的驱动程序,当一个驱动注册到内核的时候,处理过程与此相似(这是我理解的阿,大家批评指正),而一般的macth算法都比较简单,例如platform_bus的匹配算法就很简单,就是比较platform_device和platform_driver的name成员,如果匹配成功,就加载相应的设备或者驱动!这就完成了一个连接的过程。。。

 

那么这两种设备驱动中最重要的类型在linux中如何表现出来,那我们就有必要介绍一下从2.6开始实现的sys文件系统了,

/sys/bus $ cat /etc/fstab
proc            /proc   proc    defaults    0   0
devpts   /dev/pts   devpts defaults   0 0
tmpfs           /dev/shm         tmpfs defaults        0       0
sysfs           /sys             sysfs          defaults        0       0
/dev/mtdblock2 /mnt/flash2     yaffs   defaults        0       0

加载这个文件系统对于我们分析设备模型是非常有好处的。
sys文件夹下一般有如下的目录:
/sys $ ls -al
drwxr-xr-x   10 root     root            0 Jan 1 1970 .
drwxrwxrwx   11 1000     tao          4096 May 22 06:56 ..
drwxr-xr-x    7 root     root            0 Oct 27 14:09 block
drwxr-xr-x    8 root     root            0 Jan 1 1970 bus
drwxr-xr-x   21 root     root            0 Jan 1 1970 class
drwxr-xr-x    4 root     root            0 Jan 1 1970 devices
drwxr-xr-x    2 root     root            0 Jan 1 1970 firmware
drwxr-xr-x    2 root     root            0 Jan 1 1970 fs
drwxr-xr-x    2 root     root            0 Jan 1 1970 kernel
drwxr-xr-x   22 root     root            0 Oct 27 14:10 module
block是由于历史原因形成的block设备的文件夹。我们关心的是bus文件夹。
我们以spi设备为例:spi部分要包括两种设备,一种是platform_device,一种是spi_device。
在arch/arm/mach-at91/at91sam9260_device.c文件中,定义的SPI硬件控制模块设备,这我们不需要关心。
还有一种是spi_device,定义在arch/arm/mach-at91/board-sam9260ek.c文件中,这就是我们的dataflash和mcp2515设备,
所以如何设备加载成功的话,在bus下面的每个目录里面,都存在devices和drivers两个文件夹,分别对应设备和文件。
/sys/bus/platform/devices $ ls -al
drwxr-xr-x    2 root     root            0 Oct 27 16:01 .
drwxr-xr-x    4 root     root            0 Jan 1 1970 ..
lrwxrwxrwx    1 root     root            0 Oct 27 16:01 at91_i2c -> ../../../devices/platform/at91_i2c
lrwxrwxrwx    1 root     root            0 Oct 27 16:01 at91_nand -> ../../../devices/platform/at91_nand
lrwxrwxrwx    1 root     root            0 Oct 27 16:01 at91_ohci -> ../../../devices/platform/at91_ohci
lrwxrwxrwx    1 root     root            0 Oct 27 16:01 atmel_spi.0 -> ../../../devices/platform/atmel_spi.0
lrwxrwxrwx    1 root     root            0 Oct 27 16:01 atmel_spi.1 -> ../../../devices/platform/atmel_spi.1
lrwxrwxrwx    1 root     root            0 Oct 27 16:01 atmel_usart.0 -> ../../../devices/platform/atmel_usart.0
lrwxrwxrwx    1 root     root            0 Oct 27 16:01 atmel_usart.1 -> ../../../devices/platform/atmel_usart.1
lrwxrwxrwx    1 root     root            0 Oct 27 16:01 atmel_usart.2 -> ../../../devices/platform/atmel_usart.2
lrwxrwxrwx    1 root     root            0 Oct 27 16:01 macb -> ../../../devices/platform/macb
驱动
/sys/bus/platform/drivers/atmel_spi $ ls -al
drwxr-xr-x    2 root     root            0 Jan 1 1970 .
drwxr-xr-x    8 root     root            0 Jan 1 1970 ..
lrwxrwxrwx    1 root     root            0 Oct 27 16:10 atmel_spi.0 -> ../../../../devices/platform/atmel_spi.0
lrwxrwxrwx    1 root     root            0 Oct 27 16:10 atmel_spi.1 -> ../../../../devices/platform/atmel_spi.1
--w-------    1 root     root         4096 Oct 27 16:10 bind
--w-------    1 root     root         4096 Oct 27 16:10 unbind
如果出现上面的这个情况,说明你的设备(两路spi总线)和驱动都加载成功了,如果你的devices下面没有spi.0设备和spi.1设备的话,说明
board-sam9260ek.c文件中的这个函数出错:
static void __init ek_board_init(void)
{
    /* Serial */
    at91_add_device_serial();
    /* USB Host */
    at91_add_device_usbh(&ek_usbh_data);
    /* USB Device */
    at91_add_device_udc(&ek_udc_data);
    /* SPI */
    at91_add_device_spi(ek_spi_devices, ARRAY_SIZE(ek_spi_devices));
    /* NAND */
    at91_add_device_nand(&ek_nand_data);
    /* Ethernet */
    at91_add_device_eth(&ek_macb_data);
    /* MMC */
    at91_add_device_mmc(0, &ek_mmc_data);
    /* I2C */
    at91_add_device_i2c();
}
这里是设备注册的地方,我们还应该在下面这个目录下看到这两个文件。
/sys/bus/spi/devices $ ls -al
drwxr-xr-x    2 root     root            0 Oct 27 14:09 .
drwxr-xr-x    4 root     root            0 Jan 1 1970 ..
lrwxrwxrwx    1 root     root            0 Oct 27 14:09 spi0.1 -> ../../../devices/platform/atmel_spi.0/spi0.1
lrwxrwxrwx    1 root     root            0 Oct 27 14:09 spi1.0 -> ../../../devices/platform/atmel_spi.1/spi1.0
这两个链接说明我们的两个spi设备注册都被接受了,剩下来就是驱动的问题。有人看不懂这个sys文件系统的层次关系,其实这里比较好说明,就是spi0.1是atmel_spi.0设备的子设备嘛,很好理解的。

驱动:
platform_driver驱动:
/sys/bus/platform/drivers $ ls -al
drwxr-xr-x    8 root     root            0 Jan 1 1970 .
drwxr-xr-x    4 root     root            0 Jan 1 1970 ..
drwxr-xr-x    2 root     root            0 Jan 1 1970 at91_i2c
drwxr-xr-x    2 root     root            0 Jan 1 1970 at91_nand
drwxr-xr-x    2 root     root            0 Jan 1 1970 at91_ohci
drwxr-xr-x    2 root     root            0 Oct 27 16:10 atmel_spi
drwxr-xr-x    2 root     root            0 Jan 1 1970 atmel_usart
drwxr-xr-x    2 root     root            0 Jan 1 1970 macb
我们可以看到这个驱动只有一个atmel_spi,这个驱动是在哪加载的?
driver/spi/atmel_spi.c文件加载的。

spi_driver驱动:
/sys/bus/spi/drivers $ ls -al
drwxr-xr-x    4 root     root            0 Oct 27 14:10 .
drwxr-xr-x    4 root     root            0 Jan 1 1970 ..
drwxr-xr-x    2 root     root            0 Oct 27 14:10 mcp2515
drwxr-xr-x    2 root     root            0 Oct 27 14:09 mtd_dataflash
这是我们加载的两个驱动,说明驱动也加载正常了。

下面我们来说说我们遇到的问题吧。
在设备和驱动都加载正常之后,出现与dataflash设备通信不上的情况,驱动加载的时候,读取芯片的状态字读出是0xff,说明工作不正常,动用逻辑分析仪监控spi总线的通信,意外的发现,sck信号和cs信号正常,但是mosi无信号输出,开始觉得可能是spi总线适配器有问题,后来仔细观察原理图之后,发现dataflash和mmc/sd是使用同样的io口的,即pa0,pa1,pa2,而我的内核配置中打开了对mmc的支持,所以导致mosi不正常,所以可能9260的mmc与dataflash不能同时使用,但9263的可以。
解决办法:make menuconfig
Device Drivers--->MMC/SD card support,取消其支持,问题解决!
文章出处:http://www.diybl.com/course/6_system/linux/Linuxjs/200868/123621.html

昨天其实还有一个问题可能大家没有注意到,没有解释清楚,其实是有问题的,我们的at91_add_device_spi函数如下:

static struct spi_board_info ek_spi_devices[] = {
#if !defined(CONFIG_MMC_AT91)
    {    /* DataFlash chip */
        .modalias    = "mtd_dataflash",
        .chip_select    = 1,
        .max_speed_hz    = 15 * 1000 * 1000,
        .bus_num    = 0,
    },
#if defined(CONFIG_MTD_AT91_DATAFLASH_CARD)
    {    /* DataFlash card */
        .modalias    = "mtd_dataflash",
        .chip_select    = 0,
        .max_speed_hz    = 15 * 1000 * 1000,
        .bus_num    = 0,
    },
#endif
#endif
#if defined(CONFIG_SND_AT73C213) || defined(CONFIG_SND_AT73C213_MODULE)
    {    /* AT73C213 DAC */
        .modalias    = "at73c213",
        .chip_select    = 0,
        .max_speed_hz    = 10 * 1000 * 1000,
        .bus_num    = 1,
    },
#endif

/* spi can ,add by mrz */
#if defined(CONFIG_CAN_MCP2515_MODULE) ||defined(CONFIG_CAN_MCP2515)
//defined(CONFIG_CAN_MCP2515)
    {
        .modalias = "mcp2515",
        .chip_select = 0,
//        .controller_data = AT91_PIN_PB3,
        .irq = AT91_PIN_PC6, //AT91SAM9260_ID_IRQ0,
        .platform_data = &mcp251x_data,
        .max_speed_hz = 10 * 1000 * 1000,
        .bus_num = 1,
        .mode = 0,
    },
    /*
    {
        .modalias = "mcp2515",
        .chip_select = 1,
//        .controller_data = AT91_PIN_PC5,
        .irq = AT91_PIN_PC7, //AT91SAM9260_ID_IRQ1,
        .platform_data = &mcp251x_data,
        .max_speed_hz = 10 * 1000 * 1000,
        .bus_num = 1,
        .mode = 0,
    },
*/
#elif defined(CONFIG_CAN_MCP251X)
    {
        .modalias = "mcp251x",
        .chip_select = 0,
//        .controller_data = AT91_PIN_PB3,
        .irq = AT91_PIN_PC6, //AT91SAM9260_ID_IRQ0,
        .platform_data = &mcp251x_data,
        .max_speed_hz = 10 * 1000 * 1000,
        .bus_num = 1,
        .mode = 0,
    },
    {
        .modalias = "mcp251x",
        .chip_select = 1,
//        .controller_data = AT91_PIN_PC5,
        .irq = AT91_PIN_PC7, //AT91SAM9260_ID_IRQ1,
        .platform_data = &mcp251x_data,
        .max_speed_hz = 10 * 1000 * 1000,
        .bus_num = 1,
        .mode = 0,
    },
#endif
}
void __init at91_add_device_spi(struct spi_board_info *devices, int nr_devices)
{
    int i;
    unsigned long cs_pin;
    short enable_spi0 = 0;
    short enable_spi1 = 0;

    /* Choose SPI chip-selects */
    /*这里加载我们定义的spi_board_info结构体,也就是两个spi设备的信息,注意,他们这里没有使用spi_device结构体来做,而是使用一个板级信息体来完成。*/
    for (i = 0; i < nr_devices; i++) {
    /*该成员定义的就是cs引脚*/
        if (devices[i].controller_data)
            cs_pin = (unsigned long) devices[i].controller_data;
        else if (devices[i].bus_num == 0)
            cs_pin = spi0_standard_cs[devices[i].chip_select];
        else
            cs_pin = spi1_standard_cs[devices[i].chip_select];
    /*根据需要加载的设备,确定需要打开哪几个SPI控制器,我们系统中有两个控制器,所以我们在以模块的方式加载驱动的时候,我们的设备必须在刚开始就被初始化!*/
        if (devices[i].bus_num == 0)
            enable_spi0 = 1;
        else
            enable_spi1 = 1;

        /* enable chip-select pin */
      /*将片选引脚设置为输出*/
        at91_set_gpio_output(cs_pin, 1);

        /* pass chip-select pin to driver */
        devices[i].controller_data = (void *) cs_pin;
    }
    /*到此,循环执行完毕,向内核注册这些板级信息体*/
    spi_register_board_info(devices, nr_devices);

    /* Configure SPI bus(es) */
    /*如果发现spi0上有设备注册,则打开spi0*/
    if (enable_spi0) {
        at91_set_A_periph(AT91_PIN_PA0, 0);    /* SPI0_MISO */
        at91_set_A_periph(AT91_PIN_PA1, 0);    /* SPI0_MOSI */
        at91_set_A_periph(AT91_PIN_PA2, 0);    /* SPI1_SPCK */

        at91_clock_associate("spi0_clk", &at91sam9260_spi0_device.dev, "spi_clk");
        platform_device_register(&at91sam9260_spi0_device);
    }
    /*spi0设备也是如此*/
    if (enable_spi1) {
        at91_set_A_periph(AT91_PIN_PB0, 0);    /* SPI1_MISO */
        at91_set_A_periph(AT91_PIN_PB1, 0);    /* SPI1_MOSI */
        at91_set_A_periph(AT91_PIN_PB2, 0);    /* SPI1_SPCK */

        at91_clock_associate("spi1_clk", &at91sam9260_spi1_device.dev, "spi_clk");
        platform_device_register(&at91sam9260_spi1_device);
    }
}

从上面这个函数我们可以看出,这个函数就完成了两个功能:
1、向内核完成spi板级信息结构体的注册
2、注册了两个platform_device:spi0与spi1,这两个设备是spi总线控制器!

那么我们客户端spi_device设备的注册是如何完成的?不知道,呵呵
我今天仔细的看代码才发现玄机所在。
内核的注释很清晰的告诉我们,我们的spi设备是不允许热插拔!!这是由于spi设备驱动的框架不允许,我们的spi_device设备注册不是在板级初始化的时候完成的。
在spi控制器的驱动加载的时候,也就是platform_driver:atmel_spi驱动加载的时候,
driver/spi/atmel_spi.c文件中:
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;

    regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!regs)
        return -ENXIO;

    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;

    clk = clk_get(&pdev->dev, "spi_clk");
    if (IS_ERR(clk))
        return PTR_ERR(clk);

抱歉!评论已关闭.