61 OrangePi Linux内核里的spi控制器驱动

在全志H3里有2个spi控制器, 每个控制器可有4个片选信号:
这里写图片描述

//

script.bin里的spi控制器,spi设备相关设置:

[spi0]
spi_used = 1
spi_cs_bitmap = 1
spi_mosi = port:PC00<3><default><default><default>
spi_miso = port:PC01<3><default><default><default>
spi_sclk = port:PC02<3><default><default><default>
spi_cs0 = port:PC03<3><1><default><default>

[spi1]
spi_used = 0
spi_cs_bitmap = 1
spi_cs0 = port:PA13<2><1><default><default>
spi_sclk = port:PA14<2><default><default><default>
spi_mosi = port:PA15<2><default><default><default>
spi_miso = port:PA16<2><default><default><default>

[spi_devices]
spi_dev_num = 1

[spi_board0]
modalias = "spidev"
max_speed_hz = 33000000
bus_num = 0
chip_select = 0
mode = 0
full_duplex = 1
manual_cs = 0

/在linux内核源码里spi控制器驱动//

make menuconfig ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

Device Drivers  ---> 
    [*] SPI support  --->
        <*>   SUNXI SPI Controller

控制器驱动源码在”drivers/spi/spi-sunxi.c”
控制器的驱动都是平台驱动,由平台设备来描述具体控制器的硬件资源。这spi控件器驱动与i2c控制器驱动一样,平台设备与平台驱动都在spi-sunxi.c文件里了。用起来方便,但不是很规范.

spi-sunxi.c的主过程分析:

module_init(sunxi_spi_init);


static struct platform_driver sunxi_spi_driver = {
    .probe   = sunxi_spi_probe,
    .remove  = sunxi_spi_remove,
    .driver = {
        .name   = SUNXI_SPI_DEV_NAME, //名字为"spi"
        .owner  = THIS_MODULE,
        .pm     = SUNXI_SPI_DEV_PM_OPS,
    },
};


static int __init sunxi_spi_init(void)
{
    int i, ret = 0; 

    sunxi_spi_device_scan();  //初始化控制器的平台设备,并提供控制器的配置寄存器地址,中断号等硬件资源
    sunxi_spi_chan_cfg(sunxi_spi_pdata); //用全局设量spi_used_mask记录script.bin里"spi_used=1"的控制器

    ret = sunxi_spi_register_spidev(); //注册script.bin里描述的spi设备
    ...

#ifdef CONFIG_EVB_PLATFORM //此宏成立
    for (i=0; i<SUNXI_SPI_NUM; i++)  // SUNXI_SPI_NUM的值为2
#else
    i = CONFIG_SPI_CHAN_NUM; /* In FPGA, only one channel is available. */
#endif
    {    
        if (sunxi_spi_chan_is_enable(i)) {
            SPI_DBG("Sunxi SPI init channel %d \n", i);
            ret = platform_device_register(&sunxi_spi_device[i]); //注册script.bin里"spi_used=1"的控制器的平台设备
        ...
            sunxi_spi_sysfs(&sunxi_spi_device[i]); //并在"/sys"目录下的控制器的平台设备目录下创建出"info", "status"属性文件。
        }
    }

    if (spi_used_mask)
        return platform_driver_register(&sunxi_spi_driver); //如有控制器的平台设备注册,则也注册控制器的平台驱动
    ...
}

///细看初始化函数里调用到的函数

static int spi_used_mask = 0; //全局变量用于记录script.bin里哪个控制器是"spi_used=1"的

//下面三个全局数组表示每个spi控制器使用的平台设备对象,资源,平台数据等 
static struct resource sunxi_spi_resources[SUNXI_SPI_NUM * SUNXI_SPI_RES_NUM];
static struct sunxi_spi_platform_data sunxi_spi_pdata[SUNXI_SPI_NUM];
static struct platform_device sunxi_spi_device[SUNXI_SPI_NUM];


//初始化控制器的平台设备,并提供控制器的配置寄存器地址,中断号等硬件资源
static void __init sunxi_spi_device_scan(void)
{
    int i;

    memset(sunxi_spi_device, 0, sizeof(sunxi_spi_device));
    memset(sunxi_spi_pdata, 0, sizeof(sunxi_spi_pdata));
    memset(sunxi_spi_resources, 0, sizeof(sunxi_spi_resources));

    for (i=0; i<SUNXI_SPI_NUM; i++) {
        sunxi_spi_resources[i * SUNXI_SPI_RES_NUM].start = SUNXI_SPI_MEM_START(i);
    ...
        sunxi_spi_resources[i * SUNXI_SPI_RES_NUM + 1].start = SUNXI_SPI_IRQ(i);
    ...
        sunxi_spi_pdata[i].cs_bitmap = SUNXI_CS_BITMAP(i); // 值为1
        sunxi_spi_pdata[i].cs_num    = SUNXI_CS_NUM(i);    // 值为1

        sunxi_spi_device[i].name = SUNXI_SPI_DEV_NAME; //"spi"
        sunxi_spi_device[i].id   = i; //平台设备的ID,也就驱动好的控制器编号
    ... //下面准备控制器的平台设备的资源,平台数据
        sunxi_spi_device[i].resource = &sunxi_spi_resources[i * SUNXI_SPI_RES_NUM];
        sunxi_spi_device[i].num_resources = SUNXI_SPI_RES_NUM;
        sunxi_spi_device[i].dev.platform_data = &sunxi_spi_pdata[i];
    }
}

//用全局设量spi_used_mask记录script.bin里"spi_used=1"的控制器
static void sunxi_spi_chan_cfg(struct sunxi_spi_platform_data *pdata)
{
    int i;
    script_item_u item = {0};
    script_item_value_type_e type = 0;
    char spi_para[16] = {0};

    for (i=0; i<SUNXI_SPI_NUM; i++) {
        sprintf(spi_para, SUNXI_SPI_DEV_NAME"%d", i);
        type = script_get_item(spi_para, "spi_used", &item);
        ...
        if (item.val)
            spi_used_mask |= SUNXI_SPI_CHAN_MASK(i);    
        ...
    }
}

就像i2c设备驱动模型一样,spi设备驱动模型里用spi_board_info来描述spi设备,然后用spi_register_board_info函数来注册spi_board_info设备信息. 最后在控制器驱动对象注册时再创建出相应的spi_device设备对象

struct spi_board_info {
        char            modalias[SPI_NAME_SIZE]; //设备名字
        const void      *platform_data;          //spi_device.dev.platform_data
        void            *controller_data;        // spi_device.controller_data, 这里通常指定一个片选的操作函数及IO口. 在控制器驱动里会调用到
        int             irq;                     // spi_device.irq

        u32             max_speed_hz;            // spi_device.max_speed_hz;

    u16             bus_num;                 //标明此SPI设备是挂载到哪个SPI控制器上
        u16             chip_select;             //一个控制器可以挂载多个设备,这里指定此设备使用的是控制器上的第几个片选, 使片选数组里的第几个片选

        u8              mode;    //工作时序方式
 };


//根据script.bin里描述的spi设备,创建出对应的spi_board_info对象,并注册
static int __init sunxi_spi_register_spidev(void)
{
    script_item_u spi_dev_num, temp_info;
    script_item_value_type_e type;
    int i, spidev_num;
    char spi_board_name[32] = {0};
    struct spi_board_info* board;

    type = script_get_item("spi_devices", "spi_dev_num", &spi_dev_num); //获取script.bin里的"spi_devices"的键值
    ...
    spidev_num = spi_dev_num.val; //表示在script.bin里描述spi设备的个数, 在script.bin里每个设备的主键是"[spi_board%d]"
    ...
    spi_boards = (struct spi_board_info*)kzalloc(sizeof(struct spi_board_info) * spidev_num, GFP_KERNEL);  //创建出所需的spi_board_info对象空间.


    for (i=0; i<spidev_num; i++) {
        board = &spi_boards[i];
        sprintf(spi_board_name, "spi_board%d", i);

        type = script_get_item(spi_board_name, "modalias", &temp_info); //获取"spi_board0"主键下的"modalias"子键的值
    ...
        sprintf(board->modalias, "%s", temp_info.str); //把获取出的名字设为spi_board_info对象的名字

        type = script_get_item(spi_board_name, "max_speed_hz", &temp_info); //设备的最大工作时钟
    ...
        board->max_speed_hz = temp_info.val;

        type = script_get_item(spi_board_name, "bus_num", &temp_info); //表示此设备是连接在哪个编号的控制器
    ...
        board->bus_num = temp_info.val;

        type = script_get_item(spi_board_name, "chip_select", &temp_info);//获取设备的片选线
    ...
        board->chip_select = temp_info.val;

        type = script_get_item(spi_board_name, "mode", &temp_info); //获取设备的时序工作方式
    ...
        board->mode = temp_info.val;

        SPI_INF("%-16d %-16s %-16d %-8d %-4d %-4d\n", i, board->modalias, board->max_speed_hz,
                board->bus_num, board->chip_select, board->mode);
    }

    if (spi_register_board_info(spi_boards, spidev_num)) { //最后注册所有的spi_board_info信息
    ...
    }

    return 0;
}

///

/// 在linux内核里,spi控制器驱动好后用struct spi_master的一个对象来描述.
struct spi_master {
    struct device   dev; //基于device成员扩展
    ...
    s16         bus_num; //控制器对象的编号,由平台设备提供
    ...
    u16         num_chipselect;
    ...
    u16         mode_bits; //工作时序方式
    ...
};  


在spi-sunxi.c里,封装了结构体struct sunxi_spi用于记录每个控制器对象在驱动里的数据
struct sunxi_spi {
    struct platform_device *pdev; //指定控制器的平台设备对象地址
    struct spi_master *master;  //指定驱动好后创建出来的spi_master对象地址
    ...
    enum spi_mode_type mode_type; //工作时序方式

    unsigned int irq;  //中断号
    char dev_name[48];  //名字为"spi.%d"
    ...
};

控制器的平台驱动的probe函数
static int __devinit sunxi_spi_probe(struct platform_device *pdev)
{
    struct resource *mem_res;
    struct sunxi_spi *sspi;
    struct sunxi_spi_platform_data *pdata;
    struct spi_master *master;
    int ret = 0, err = 0, irq;
    int cs_bitmap = 0;

    ...
    pdata = pdev->dev.platform_data;

    //获取平台设备提供的资源
    mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    ... 
    irq = platform_get_irq(pdev, 0);

    //创建出spi_master对象的空间和struct sunxi_spi对象的空间, 并dev_set_drvdata(&spi_master.dev, sunxi_spi);
    master = spi_alloc_master(&pdev->dev, sizeof(struct sunxi_spi));
    ...

    platform_set_drvdata(pdev, master);
    sspi = spi_master_get_devdata(master); // dev_get_drvdata(...)
    memset(sspi, 0, sizeof(struct sunxi_spi));

    sspi->master        = master;
    sspi->irq           = irq;

    ..
    sspi->cs_control    = sunxi_spi_cs_control;
    sspi->cs_bitmap     = pdata->cs_bitmap; /* cs0-0x1; cs1-0x2; cs0&cs1-0x3. */
    sspi->busy          = SPI_FREE;
    sspi->mode_type     = MODE_TYPE_NULL;

    //初始化spi_master对象
    master->bus_num         = pdev->id;
    master->setup           = sunxi_spi_setup;
    master->cleanup         = sunxi_spi_cleanup;
    master->transfer        = sunxi_spi_transfer;
    master->num_chipselect  = pdata->cs_num;
    master->mode_bits       = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH| SPI_LSB_FIRST;
    ...
    cs_bitmap = sunxi_spi_get_cfg_csbitmap(pdev->id);

    ...
    snprintf(sspi->dev_name, sizeof(sspi->dev_name), SUNXI_SPI_DEV_NAME"%d", pdev->id);
    err = request_irq(sspi->irq, sunxi_spi_handler, IRQF_DISABLED, sspi->dev_name, sspi);
    ...

    sspi->base_addr = ioremap(mem_res->start, resource_size(mem_res));
    ..
    sspi->base_addr_phy = mem_res->start;

    sspi->workqueue = create_singlethread_workqueue(dev_name(master->dev.parent)); //创建一个基于线程的工作队列,用于spi控制器的数据传输工作任务
    ...

    sspi->pdev = pdev;
    pdev->dev.init_name = sspi->dev_name;
    ret = sunxi_spi_hw_init(sspi, pdata); //根据参数配置控制器的寄存器
    ...

    spin_lock_init(&sspi->lock);
    init_completion(&sspi->done);
    INIT_WORK(&sspi->work, sunxi_spi_work); //初始化工作任务,当工作任务得到调用时,sunxi_spi_work函数就会触发调用
    INIT_LIST_HEAD(&sspi->queue);

    if (spi_register_master(master)) { //注册spi_master对象
    ...
    }
    ...
}

///
spi控制器驱动好不会在/sys目录下创建spi_master对象的子目录,只可以通过”/sys/bus/platform/drivers”目录下查看平台驱动目录下有哪些匹配上的平台设备.

  • 1
    点赞
  • 1
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

打赏
文章很值,打赏犒劳作者一下
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页

打赏

jklinux

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者