SPI内核驱动模型

3.1.    SPI内核驱动模型
在2.6的linux内核中,SPI的驱动架构可以分为如下三个层次:SPI 核心层、SPI控制器驱动层和SPI设备驱动层。Linux 中SPI驱动代码位于drivers/spi目录。

3.1.1.    SPI核心层
SPI核心层是Linux的SPI核心部分,提供了核心数据结构的定义、SPI控制器驱动和设备驱动的注册、注销管理等API。其为硬件平台无关层,向下屏蔽了物理总线控制器的差异,定义了统一的访问策略和接口;其向上提供了统一的接口,以便SPI设备驱动通过总线控制器进行数据收发。Linux中,SPI核心层的代码位于driver/spi/ spi.c。

3.1.2.    SPI控制器驱动层
SPI控制器驱动层,每种处理器平台都有自己的控制器驱动,属于平台移植相关层。它的职责是为系统中每条SPI总线实现相应的读写方法。在物理上,每个SPI控制器可以连接若干个SPI从设备。
在系统开机时,SPI控制器驱动被首先装载。一个控制器驱动用于支持一条特定的SPI总线的读写。一个控制器驱动可以用数据结构struct spi_master来描述,定义在include/liunx/spi/spi.h文件中。
针对DM8127,控制器驱动程序为omap2_mcspi.c,SPI 控制器驱动的注册采用Platform device和Platform driver机制。

3.1.2.1.    SPI控制器的Platform device
Platform device的定义和注册代码位于arch/arm/mach-omap2/devices.c中。
static struct platform_device omap2_mcspi1 = {
    .name        = "omap2_mcspi",
    .id        = 1,
    .num_resources    = ARRAY_SIZE(omap2_mcspi1_resources),
    .resource    = omap2_mcspi1_resources,
    .dev        = {
        .platform_data = &omap2_mcspi1_config,
    },
};

static struct platform_device omap2_mcspi2 = {
    .name        = "omap2_mcspi",
    .id        = 2,
    .num_resources    = ARRAY_SIZE(omap2_mcspi2_resources),
    .resource    = omap2_mcspi2_resources,
    .dev        = {
        .platform_data = &omap2_mcspi2_config,
    },
};

static struct platform_device omap2_mcspi3 = {
    .name        = "omap2_mcspi",
    .id        = 3,
    .num_resources    = ARRAY_SIZE(omap2_mcspi3_resources),
    .resource    = omap2_mcspi3_resources,
    .dev        = {
        .platform_data = &omap2_mcspi3_config,
    },
};

static struct platform_device omap2_mcspi4 = {
    .name        = "omap2_mcspi",
    .id        = 4,
    .num_resources    = ARRAY_SIZE(omap2_mcspi4_resources),
    .resource    = omap2_mcspi4_resources,
    .dev        = {
        .platform_data = &omap2_mcspi4_config,
    },
};
可以看到,这边定义了4个SPI控制器的Platform device,id分别为“1,2,3, 4”,name都为“omap2_mcspi”,变量resource中定义了适配器的寄存器基地址。

3.1.2.2.    Platform device的注册如下
platform_device_register(&omap2_mcspi1);
platform_device_register(&omap2_mcspi2);
platform_device_register(&omap2_mcspi3);
platform_device_register(&omap2_mcspi4);
以上四个函数在内核启动时完成。将Platform device注册到Platform总线上,完成注册后,寄存器的基地址等信息会在设备树中描述,此后只需利用platform_get_resource等标准接口自动获取即可,实现了驱动和资源的分离。

3.1.2.3.    SPI控制器的Platform driver
Platform driver的注册代码位于内核的drivers/spi/omap2_mcspi.c中,该驱动的注册目的是初始化DM8127的SPI 控制器,提供SPI总线数据传输的具体实现,并且向PSI核心层注册SPI 控制器。

3.1.2.4.    Platform driver的定义如下
static struct platform_driver omap2_mcspi_driver = {
    .driver = {
        .name =        "omap2_mcspi",
        .owner =    THIS_MODULE,
        .pm =        &omap2_mcspi_pm_ops
    },
    .remove =    __exit_p(omap2_mcspi_remove),
};
内核启动时会调用函数platform_driver_probe()来注册Platform driver,该函数是带有probe函数的平台驱动注册函数,注册时,会扫描platform bus上的所有设备,由于匹配因子name为" omap2_mcspi ",与之前注册的Platform device匹配成功,于是函数omap2_mcspi_probe()将被调用,控制器device和driver将被绑定起来。
在文件drivers/spi/omap2_mcspi.c中会涉及到一个数据结构omap2_mcspi,这个结构专门针对DM8127的SPI控制器,代码如下:
struct omap2_mcspi {
    struct work_struct    work;
    
    spinlock_t        lock;
    struct list_head    msg_queue;
    struct spi_master    *master;
    struct clk        *ick;
    struct clk        *fck;
    
    void __iomem        *base;
    unsigned long        phys;
    
    struct omap2_mcspi_dma    *dma_channels;
};
msg_queue对应消息队列。
master对应通用的spi控制器结构。
ick和fck分别对应接口时钟和功能时钟。
base对应SPI控制器寄存器的虚拟地址。
phys对应SPI控制器寄存器的物理地址。
dma_channels对应SPI控制器的DMA通道。  
函数omap2_mcspi_probe()的执行流程如下图:
 
3.1.3.    SPI设备驱动层
SPI设备驱动层为用户接口层,其为用户提供了通过SPI总线访问具体设备的接口。
SPI设备驱动层可以用两个模块来描述,struct spi_driver和struct spi_device。
针对DM8127上的设备,我们利用内核提供的SPI用户驱动程序spidev.c

3.1.2.1.    DM8127上的设备注册
设备的创建和注册分为两步。
第一步:将设备信息加入到设备链表
在板级初始化时将SPI设备的名称,地址和相关的信息加入到链表board_list中,该链表定义在driver/spi/spi.c中,记录了具体开发板上的SPI设备信息。
在board-ti8148ipnc.c中,设备信息定义如下:(这里是我们主要修改的代码,添加spi设备所对应的信息,包括spi总信号,也就是具体对应dm8127的哪个spi控制器;spi时钟平率;片选,也就是对应dm8127的哪个片选信号;工作模式即极性和相位)
struct spi_board_info __initdata ti8148_spi_slave_info[] = {
    {
        .modalias    = "dm8127_spidev",
        .irq        = -1,
        .max_speed_hz    = 100000,
        .bus_num    = 1,//AD9970挂在SPI0上,片选为cs0
        .chip_select    = 0,
        .mode = SPI_MODE_3,
    },
    {
        .modalias    = "dm8127_spidev",
        .irq        = -1,
        .max_speed_hz    = 100000,
        .bus_num    = 1, //AD9970挂在SPI0上,片选为cs1
        .chip_select    = 1,
        .mode = SPI_MODE_3,
    },
    {
        .modalias    = "dm8127_spidev",
        .irq        = -1,
        .max_speed_hz    = 100000,
        .bus_num    = 1, //eeprom挂在SPI0上,片选为cs2
        .chip_select    = 2,
        .mode = SPI_MODE_0,
    },
    {
        .modalias    = "dm8127_spidev",
        .irq        = -1,
        .max_speed_hz    = 100000,
        .bus_num    = 2, //fpga挂在SPI1上,片选为cs0
        .chip_select    = 0,
        .mode = SPI_MODE_0,
    },
}
设备信息通过函数spi_register_board_info()加入到链表board_list中,代码如下:
void __init ti8148_spi_init(void)
{
    spi_register_board_info(ti8148_spi_slave_info,
                ARRAY_SIZE(ti8148_spi_slave_info));
}
设备加入到设备链表board_list的流程图如下:
 
第二步:设备spi_device的创建并添加
spi_device的创建并添加是在SPI控制器驱动注册过程中完成的,spi_register_master()函数在注册SPI控制器驱动的过程会扫描SPI设备链表board_list,如果该总线上有对应的SPI设备,则创建相应的spi_device,并将其添加到SPI的设备链表中。流程图如下所示:
 
3.1.2.2.    DM8127上的设备驱动注册
在drivers/spi/spidev.c中,定义了设备驱动,代码如下:
static struct spi_driver spidev_spi_driver = {
    .driver = {
        .name =        "dm8127_spidev",
        .owner =    THIS_MODULE,
    },
    .probe =    spidev_probe,
    .remove =    __devexit_p(spidev_remove),
};

static int __init spidev_init(void)
{
status = spi_register_driver(&spidev_spi_driver);    
}
注册的简要示意图如下:
 
在模块加载的时候首先调用spidev_init(),然后spidev_init()调用函数spi_register_driver()注册spider_spi_driver结构体。
函数spi_register_driver()初始化该驱动的总线为spi_bus_type,然后使用函数driver_register(&sdrv->driver)注册该驱动,因此内核会在SPI总线上遍历所有SPI设备,由于该设备驱动的匹配因子name变量为“dm8127_spidev”,因此正好和之前创建的chip->modalias为“dm8127_spidev”的SPI 设备匹配。因此SPI设备驱动的probe函数spidev_probe()将会被调用,完成一些具体设备相关的初始化等操作,同时我们要针对不同spi设备去配置一下spi传输的bits_per_word,代码如下:
if(spi->master->bus_num == 2)//spi1
{
    spi->bits_per_word = 32;
}
else if(spi->master->bus_num == 1)//spi0
{
    spi->bits_per_word = 8;
}
spi_setup(spi);

发布了13 篇原创文章 · 获赞 1 · 访问量 864

猜你喜欢

转载自blog.csdn.net/qq_36191395/article/details/90751543