美文网首页
linux驱动之spi框架

linux驱动之spi框架

作者: wipping的技术小栈 | 来源:发表于2020-09-13 23:17 被阅读0次

一、前言

前面我们简单的介绍了 I2C总线 的基本框架,本文则将继续讲述另一种常用的 外设总线,即 SPI总线SPI总线 常用于 传感器或者flash等外设。本文将沿着 SPI总线驱动代码,从整体上梳理一下 SPI框架 的使用及原理。

注意:本文将假设读者已经熟悉SPI总线协议,如若有读者不熟悉请自行查阅资料

二、正文
本节将使用 m25p80 这个驱动文件,m25p80 是常用 flash 的驱动代码,其中使用的就是 SPI总线。我们将以该模块为例,取出其中的 SPI总线 部分进行代码说明,而说明顺序将按照使用的步骤来说明,分为:

  • SPI驱动注册
  • SPI驱动写
  • SPI驱动读

2.1 SPI控制器注册

每个 Soc 上都带有 SPI控制器 用以与外设进行同行,在了解 SPI驱动框架 之前,我们可以先大致地了解一下 SPI控制器 的注册过程,这个过程与 SPI设备的生成 有关系,有助于我们理解后面 SPI框架 的一些操作。代码示例如下:

/* SPI控制器注册调用图谱 */
spi_alloc_master
spi_register_controller
  ->spi_controller_initialize_queue
    ->spi_queued_transfer
    ->spi_transfer_one_message
    ->spi_init_queue
      ->spi_pump_messages
    ->spi_start_queue
  ->of_register_spi_devices
    ->of_register_spi_device
      ->spi_alloc_device
      ->of_modalias_node
      ->spi_add_device
        ->device_add
          ->bus_add_device

/***********SPI控制器probe函数***********/
static int sun6i_spi_probe(struct platform_device *pdev)
{
    struct spi_master *master;
    struct sun6i_spi *sspi;
    struct resource *res;
    int ret = 0, irq;

    /* 分配一个SPI控制器 */
    master = spi_alloc_master(&pdev->dev, sizeof(struct sun6i_spi));
    if (!master) {
        dev_err(&pdev->dev, "Unable to allocate SPI Master\n");
        return -ENOMEM;
    }

    ......
    sspi->master = master;
    sspi->fifo_depth = (unsigned long)of_device_get_match_data(&pdev->dev);

    /* 初始化并填充SPI控制器 */
    master->max_speed_hz = 100 * 1000 * 1000;
    master->min_speed_hz = 3 * 1000;
    master->set_cs = sun6i_spi_set_cs;
    master->transfer_one = sun6i_spi_transfer_one;
    master->num_chipselect = 4;
    master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST;
    master->bits_per_word_mask = SPI_BPW_MASK(8);
    master->dev.of_node = pdev->dev.of_node;
    master->auto_runtime_pm = true;
    master->max_transfer_size = sun6i_spi_max_transfer_size;
    ......
    /* 注册SPI控制器,该接口完成了大部分的工作 */
    ret = devm_spi_register_master(&pdev->dev, master);
    if (ret) {
        dev_err(&pdev->dev, "cannot register SPI master\n");
        goto err_pm_disable;
    }

    return 0;

err_pm_disable:
    pm_runtime_disable(&pdev->dev);
    sun6i_spi_runtime_suspend(&pdev->dev);
err_free_master:
    spi_master_put(master);
    return ret;
}

/* 注册SPI控制器 */
int spi_register_controller(struct spi_controller *ctlr)
{
    struct device       *dev = ctlr->dev.parent;
    struct boardinfo    *bi;
    int         status = -ENODEV;
    int         id, first_dynamic;

    if (!dev)
        return -ENODEV;

    if (!spi_controller_is_slave(ctlr)) {
        status = of_spi_register_master(ctlr);
        if (status)
            return status;
    }

    /* even if it's just one always-selected device, there must
     * be at least one chipselect
     */
    if (ctlr->num_chipselect == 0)
        return -EINVAL;
    ......
    /* 初始化和填充SPI控制器的相关成员 */
    INIT_LIST_HEAD(&ctlr->queue);
    spin_lock_init(&ctlr->queue_lock);
    spin_lock_init(&ctlr->bus_lock_spinlock);
    mutex_init(&ctlr->bus_lock_mutex);
    mutex_init(&ctlr->io_mutex);
    ctlr->bus_lock_flag = 0;
    init_completion(&ctlr->xfer_completion);
    if (!ctlr->max_dma_len)
        ctlr->max_dma_len = INT_MAX;

    ......

    if (ctlr->transfer)
        dev_info(dev, "controller is unqueued, this is deprecated\n");
    else {
        /* 
          初始化控制器的消息队列,该队列在SPI驱动读写时会用到,需要重点关注。
          所有SPI数据传输会都被以消息的方式加入消息对列,再有内核线程完成消息的传输,即数据的读写
        */
        status = spi_controller_initialize_queue(ctlr);
        if (status) {
            ......
            goto done;
        }
    }

    ......
    /* 
          使用设备树注册SPI设备。
          SPI设备也就是在这里生成 ,生成SPI设备会在匹配时使用到
    */
    of_register_spi_devices(ctlr);
    ......
done:
    return status;
}

/* 初始化SPI控制器的消息队列和数据传输相关的回调接口 */
static int spi_controller_initialize_queue(struct spi_controller *ctlr)
{
    int ret;
    /* 注册SPI控制器的transfer回调,该回调会在传输数据时使用到 */
    ctlr->transfer = spi_queued_transfer;

    /* 注册SPI控制器的transfer_one_message回调,该回调会在传输数据时使用到 */
    if (!ctlr->transfer_one_message)
        ctlr->transfer_one_message = spi_transfer_one_message;

    /* 初始化消息队列 */
    ret = spi_init_queue(ctlr);
    if (ret) {
      ......
    }
    ctlr->queued = true;
    /* 开始队列任务,每个SPI控制器对有开辟一个线程来维护SPI的消息队列 */
    ret = spi_start_queue(ctlr);
    if (ret) {
        ......
    }

    return 0;
    ......
}

/* 初始化SPI控制器队列 */
static int spi_init_queue(struct spi_controller *ctlr)
{
    /* 维护SPI消息队列的线程的相关参数,比如优先级 */
    struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 };

    ctlr->running = false;
    ctlr->busy = false;
    
    /* 
      初始化一个内核worker和线程来执行spi_pump_messages。
      关于worker和内核线程有兴趣的读者请自行查阅其机制
    */
    kthread_init_worker(&ctlr->kworker);
    ctlr->kworker_task = kthread_run(kthread_worker_fn, &ctlr->kworker,
                     "%s", dev_name(&ctlr->dev));
    if (IS_ERR(ctlr->kworker_task)) {
        dev_err(&ctlr->dev, "failed to create message pump task\n");
        return PTR_ERR(ctlr->kworker_task);
    }
    kthread_init_work(&ctlr->pump_messages, spi_pump_messages);    
    ......
    return 0;
}

 /* 开始维护消息队列 */
static int spi_start_queue(struct spi_controller *ctlr)
{
    unsigned long flags;

    spin_lock_irqsave(&ctlr->queue_lock, flags);

    if (ctlr->running || ctlr->busy) {
        spin_unlock_irqrestore(&ctlr->queue_lock, flags);
        return -EBUSY;
    }

    ctlr->running = true;
    ctlr->cur_msg = NULL;
    spin_unlock_irqrestore(&ctlr->queue_lock, flags);
    
/* 这里添加到队列里面的函数其实就是前面指定的>spi_pump_messages */
    kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages);

    return 0;
}

/* 从设备树中解析并生成SPI设备 */
static void of_register_spi_devices(struct spi_controller *ctlr)
{
    struct spi_device *spi;
    struct device_node *nc;

    if (!ctlr->dev.of_node)
        return;
  
    /* 遍历设备树中的子节点,并对有效的子节点调用of_register_spi_device */
    for_each_available_child_of_node(ctlr->dev.of_node, nc) {
        if (of_node_test_and_set_flag(nc, OF_POPULATED))
            continue;
        spi = of_register_spi_device(ctlr, nc);
        if (IS_ERR(spi)) {
            dev_warn(&ctlr->dev,
                 "Failed to create SPI device for %pOF\n", nc);
            of_node_clear_flag(nc, OF_POPULATED);
        }
    }
}

/* 从设备树节点中生成并注册SPI设备 */
static struct spi_device * of_register_spi_device(struct spi_controller *ctlr, struct device_node *nc)
{
    struct spi_device *spi;
    int rc;

    /* 分配一个PSI设备 */
    spi = spi_alloc_device(ctlr);
    ......

    /* 使用设备树中的节点属性名字为SPI的别名进行赋值 */
    rc = of_modalias_node(nc, spi->modalias,
                sizeof(spi->modalias));
    ......


    /* 向内核中添加SPI设备 */
    rc = spi_add_device(spi);
    if (rc) {
        dev_err(&ctlr->dev, "spi_device register error %pOF\n", nc);
        goto err_of_node_put;
    }

    return spi;

    ......
}

/* 分配并初始化一个SPI设备 */
struct spi_device *spi_alloc_device(struct spi_controller *ctlr)
{
    struct spi_device   *spi;

    ......

    spi = kzalloc(sizeof(*spi), GFP_KERNEL);
    ......

    spi->master = spi->controller = ctlr;
    spi->dev.parent = &ctlr->dev;
    spi->dev.bus = &spi_bus_type;
    spi->dev.release = spidev_release;
    spi->cs_gpio = -ENOENT;

    spin_lock_init(&spi->statistics.lock);

    device_initialize(&spi->dev);
    return spi;
}

/* 使用节点名设置别名 */
int of_modalias_node(struct device_node *node, char *modalias, int len)
{
    const char *compatible, *p;
    int cplen;

    /* 获取节点中的compatible属性值 */
    compatible = of_get_property(node, "compatible", &cplen);
    ......
    /* 从中以 ,号 进行截断 */
    p = strchr(compatible, ',');

    /* 将p指向的compatible值赋值给别名modalias */
    strlcpy(modalias, p ? p + 1 : compatible, len);
    return 0;
}

int spi_add_device(struct spi_device *spi)
{
    static DEFINE_MUTEX(spi_add_lock);
    struct spi_controller *ctlr = spi->controller;
    struct device *dev = ctlr->dev.parent;
    int status;

    ......
    /* 将SPI设备中的device成员注册到内核中 */
    status = device_add(&spi->dev);
    ......

}

2.2 SPI设备驱动注册

编写 SPI驱动 的第一步就是需要注册相关驱动,让内核可以操作相应的外设。而 SPI驱动结构体 及 驱动代码示例如下:

/* SPI驱动注册调用图谱 */
module_spi_driver
  ->__spi_register_driver
    ->driver_register
      ->bus_add_driver
        ->driver_attach
          ->__driver_attach
            ->driver_match_device
              ->spi_match_device
          ->driver_probe_device
            ->really_probe
              ->dev->bus->probe(spi_drv_probe)
                ->m25p_probe

/*************** SPI驱动结构体说明 ***************/
struct spi_driver {
    /* SPI设备ID表,根据笔者理解该表的作用不大。一般声明设备ID表的驱动都是为了支持热插拔 */
    const struct spi_device_id *id_table;
    /* SPI驱动匹配后的probe函数及卸载时的remove函数 */
    int         (*probe)(struct spi_device *spi);
    int         (*remove)(struct spi_device *spi);
    /* 设备驱动抽象结构体 */
    struct device_driver    driver;
};

/*************** SPI驱动注册代码示例 ***************/
/* 
    m25p_ids为SPI设备ID表
    按照笔者理解设备ID表一般用于热插拔,而该表在笔者阅读代码中似乎没起到作用 
    欢迎指导的读者与笔者进行交流
*/
static const struct spi_device_id m25p_ids[] = {
    {"spi-nor"},
    {"s25sl064a"},  {"w25x16"}, {"m25p10"}, {"m25px64"},
    {"at25df321a"}, {"at25df641"},  {"at26df081a"},
    {"mx25l4005a"}, {"mx25l1606e"}, {"mx25l6405d"}, {"mx25l12805d"},
    {"mx25l25635e"},{"mx66l51235l"},
    {"n25q064"},    {"n25q128a11"}, {"n25q128a13"}, {"n25q512a"},
    {"s25fl256s1"}, {"s25fl512s"},  {"s25sl12801"}, {"s25fl008k"},
    {"s25fl064k"},
    {"sst25vf040b"},{"sst25vf016b"},{"sst25vf032b"},{"sst25wf040"},
    {"m25p40"}, {"m25p80"}, {"m25p16"}, {"m25p32"},
    {"m25p64"}, {"m25p128"},
    {"w25x80"}, {"w25x32"}, {"w25q32"}, {"w25q32dw"},
    {"w25q80bl"},   {"w25q128"},    {"w25q256"},
    {"m25p05-nonjedec"},    {"m25p10-nonjedec"},    {"m25p20-nonjedec"},
    {"m25p40-nonjedec"},    {"m25p80-nonjedec"},    {"m25p16-nonjedec"},
    {"m25p32-nonjedec"},    {"m25p64-nonjedec"},    {"m25p128-nonjedec"},
    { "mr25h256" }, /* 256 Kib, 40 MHz */
    { "mr25h10" },  /*   1 Mib, 40 MHz */
    { "mr25h40" },  /*   4 Mib, 40 MHz */
    { },
};
/* 
    MODULE_DEVICE_TABLE一般用于声明对应的设备表,
    该表会在编译阶段被加入设备依赖文件中,在热插拔设备时,可以通过该表来判断并加载相应的驱动。
    其代码如下:
    可以看到只是使用了typeof关键字来声明相同类型的表格结构体
    #define MODULE_DEVICE_TABLE(type, name)                 \
    extern typeof(name) __mod_##type##__##name##_device_table       \
    __attribute__ ((unused, alias(__stringify(name))))

*/
MODULE_DEVICE_TABLE(spi, m25p_ids);

/* SPI驱动的设备树匹配ID表,每个驱动都需要的,这里不再赘述 */
static const struct of_device_id m25p_of_table[] = {
    /*
     * Generic compatibility for SPI NOR that can be identified by the
     * JEDEC READ ID opcode (0x9F). Use this, if possible.
     */
    { .compatible = "jedec,spi-nor" },
    {}
};
MODULE_DEVICE_TABLE(of, m25p_of_table);

/* SPI驱动结构体声明 */
static struct spi_driver m25p80_driver = {
    /* driver成员跟一般的驱动一样,声明驱动名称及匹配表 */
    .driver = {
        .name   = "m25p80",
        .of_match_table = m25p_of_table,
    },
    /* 热插拔时用到设备ID表 */
    .id_table   = m25p_ids,
    /* 驱动的probe函数 */
    .probe  = m25p_probe,
    /* 驱动的remove函数 */
    .remove = m25p_remove,
};

/* 注册驱动 */
module_spi_driver(m25p80_driver);

上面简单的说明了 SPI驱动 相关的一些初始化工作,那么完成结构体的填充及初始化后我们就需要进行驱动注册,其代码如下:

/* 使用 module_driver 宏进行设备注册函数的调用 */
#define module_spi_driver(__spi_driver) \
    module_driver(__spi_driver, spi_register_driver, \
            spi_unregister_driver)
#define module_driver(__driver, __register, __unregister, ...) \ //在__init__段声明一个__driver##_init函数 
static int __init __driver##_init(void) \
{ \
    return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \  //将__driver##_init函数使用module_init注册进内核
static void __exit __driver##_exit(void) \ //在__exit__ 段声明一个__driver##_exit函数
{ \
    __unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);  //将__driver##_exit函数使用module_exit注册进内核

/* __driver##_init函数调用的就是spi_register_driver函数 */
#define spi_register_driver(driver) \
    __spi_register_driver(THIS_MODULE, driver)

/* 经过宏的层层展开,最终来到了真正的注册函数 */
struct bus_type spi_bus_type = {
    .name       = "spi",
    .dev_groups = spi_dev_groups,
    .match      = spi_match_device,
    .uevent     = spi_uevent,
};
int __spi_register_driver(struct module *owner, struct spi_driver *sdrv)
{
    sdrv->driver.owner = owner;
    /* 指定驱动的总线类型为spi总线 */
    sdrv->driver.bus = &spi_bus_type;
    /* 如果spi驱动已经实现了probe函数,则将设备驱动抽象的probe函数设置spi_drv_probe */
    if (sdrv->probe)
        sdrv->driver.probe = spi_drv_probe;
    /* remove 和 shutdown函数同理 */
    if (sdrv->remove)
        sdrv->driver.remove = spi_drv_remove;
    if (sdrv->shutdown)
        sdrv->driver.shutdown = spi_drv_shutdown;
    /* 
      使用driver_register注册驱动。
      有兴趣的小伙伴可以看I2C那一节,那一节简单的说明了driver_register的过程 
    */
    return driver_register(&sdrv->driver);
}

/* 
    driver_register经过一系列操作后最终会调用到spi_match_device。
    至于该接口的设备如何而来,可以参考SPI控制器初始化的那个小节
    调用过程读者们可以参考调用图谱进行索引
*/
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);

    /* 使用设备树判断device和driver是否匹配 */
    if (of_driver_match_device(dev, drv))
        return 1;
  
    ......
    /* 
      这里可以看到spi驱动注册是所填充的设备id表
      可以看到,如果使用设备树的ID不匹配时也没关系,依旧可以使用该表进行匹配
      但一般情况下我们都直接使用设备树完成匹配了,不再需要该表。所以笔者才说作用不大
    */
    if (sdrv->id_table)
        return !!spi_match_id(sdrv->id_table, spi);
    /* 
      如果还不匹配哈可以使用spi设备的别名和驱动的名称进行匹配 
      spi设备的别名在设备创建时指定,可以参考SPI控制器初始化小节
    */
    return strcmp(spi->modalias, drv->name) == 0;
}

/* SPI设备匹配SPI驱动的ID表 */
static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,
                        const struct spi_device *sdev)
{
    /* 直接遍历整个ID表并使用strcmp进行判断 */
    while (id->name[0]) {
        if (!strcmp(sdev->modalias, id->name))
            return id;
        id++;
    }
    return NULL;
}

/* 
    I2C章节讲到如果完成匹配则会调用总线驱动的device_driver成员的probe函数。
    而SPI驱动对应的函数为spi_drv_probe,可以参考前面驱动注册时的函数
*/
static int spi_drv_probe(struct device *dev)
{
    const struct spi_driver     *sdrv = to_spi_driver(dev->driver);
    struct spi_device       *spi = to_spi_device(dev);
    int ret;
      
    ......

    if (ret != -EPROBE_DEFER) {
        /* 
          调用SPI驱动本身的probe函数 
          到了这里就完成了函数入口的调用
        */
        ret = sdrv->probe(spi);
        if (ret)
            dev_pm_domain_detach(dev, true);
    }

    return ret;
}

2.3 SPI设备读写

按照笔者的理解,SPI设备读写 使用的方法都是一样的,其步骤如下:

  • 初始化消息:该消息可以是读、写、读写
  • 添加消息:设置传输任务并添加传输任务到消息中
  • 执行读写

按照上面的步骤,那么其对应的接口一般如下:

  1. spi_message_init
  2. spi_message_add_tail
  3. spi_sync

我们可以看调用图谱,可以看到读写的执行在 spi_sync 中执行。代码示例如下,其中结构体说明仅说明部分常用成员:

/************SPI设备读写调用图谱************/
spi_message_init
  ->spi_message_init_no_memset
spi_message_add_tail
spi_sync
  ->__spi_sync
    ->__spi_validate
    ->__spi_queued_transfer
      ->list_add_tail
      ->kthread_queue_work
        ->spi_pump_messages
        ->__spi_pump_messages
          ->transfer_one_message
            ->spi_transfer_one_message
              ->ctlr->transfer_one
                ->sun6i_spi_transfer_one
                ->sun6i_spi_handler

/* 每个消息都有多个spi_transfer组成,1个spi_transfer代表一次传输任务 */
struct spi_transfer {
    /* 写缓冲 */
    const void  *tx_buf;
    /* 读缓冲 */
    void        *rx_buf;
    /* 缓存长度 */
    unsigned    len;
    /* 写缓冲的字节宽度 */
    unsigned    tx_nbits:3;
    /* 读缓冲的字节宽度 */
    unsigned    rx_nbits:3;
    /* 添加到消息中的链表节点 */
    struct list_head transfer_list;.
    ......
};

struct spi_message {
    /* 该消息的传输任务链表 */
    struct list_head    transfers;
    /* 发送该消息的SPI设备 */
    struct spi_device   *spi;
    /* 完成消息传输后的回调函数 */
    void            (*complete)(void *context);
    ......
};

/* 初始化SPI传输消息 */
static inline void spi_message_init(struct spi_message *m)
{
    memset(m, 0, sizeof *m);
    spi_message_init_no_memset(m);
}

/* 初始化SPI传输消息结构体 */
static inline void spi_message_init_no_memset(struct spi_message *m)
{
    /* 初始化传输任务队列 */
    INIT_LIST_HEAD(&m->transfers);
    ......
}
  
/* 添加传输任务到消息中 */
spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
{
    /* 
      将传输任务中的链表节点直接链入到消息的transfers队列中 
      一般在调用该接口前是要先完成spi_transfer结构体的初始化跟填充
    */
    list_add_tail(&t->transfer_list, &m->transfers);
}

/* 执行SPI传输任务 */
int spi_sync(struct spi_device *spi, struct spi_message *message)
{
    int ret;
    ......
        ret = __spi_sync(spi, message);
    ......
    return ret;
}
static int __spi_sync(struct spi_device *spi, struct spi_message *message)
{
    DECLARE_COMPLETION_ONSTACK(done);
    int status;
    struct spi_controller *ctlr = spi->controller;
    unsigned long flags;

    ......

    message->complete = spi_complete;
    message->context = &done;
    message->spi = spi;


    /* 从前面的初始化过程可以看到transfer成员就是spi_queued_transfer */
    if (ctlr->transfer == spi_queued_transfer) {
        spin_lock_irqsave(&ctlr->bus_lock_spinlock, flags);

        ......
        /* 将传输任务入列,注意这里的flags为false */
        status = __spi_queued_transfer(spi, message, false);

        spin_unlock_irqrestore(&ctlr->bus_lock_spinlock, flags);
    }
    ......

    if (status == 0) {

        if (ctlr->transfer == spi_queued_transfer) {
            ......
            /* 执行__spi_pump_messages函数 */
            __spi_pump_messages(ctlr, false);
        }
        /* 等待操作完成 */
        wait_for_completion(&done);
        status = message->status;
    }
    message->context = NULL;
    return status;
}

/* 将传输任务入列 */
static int __spi_queued_transfer(struct spi_device *spi,
                 struct spi_message *msg,
                 bool need_pump)
{
    struct spi_controller *ctlr = spi->controller;
    unsigned long flags;

    ......

    /* 将msg链入SPI控制器的消息队列中 */
    list_add_tail(&msg->queue, &ctlr->queue);

    /* 上一层传进来的need_pump为false,所以不执行 */
    if (!ctlr->busy && need_pump)
        kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages);

    spin_unlock_irqrestore(&ctlr->queue_lock, flags);
    return 0;
}

static void __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread)
{
    unsigned long flags;
    bool was_busy = false;
    int ret;

    /* 
      这里省略了大量的锁操作跟标志判断操作,有兴趣的读者可以去阅读。
      这里为了梳理框架就不具体展开 
    */
    ......
    /* 
      执行transfer_one_message回调。
      从前面的初始化步骤可以看出是执行函数spi_transfer_one_message 
    */
    ret = ctlr->transfer_one_message(ctlr, ctlr->cur_msg);
    if (ret) {
        dev_err(&ctlr->dev,
            "failed to transfer one message from queue\n");
        goto out;
    }

out:
    mutex_unlock(&ctlr->io_mutex);

    /* Prod the scheduler in case transfer_one() was busy waiting */
    if (!ret)
        cond_resched();
}

/* 执行传输任务 */
static int spi_transfer_one_message(struct spi_controller *ctlr, struct spi_message *msg)
{
    struct spi_transfer *xfer;
    bool keep_cs = false;
    int ret = 0;
    unsigned long long ms = 1;

    ......

    /* 遍历消息上的传输任务 */
    list_for_each_entry(xfer, &msg->transfers, transfer_list) {
        ......
        /* 判断读缓冲和写缓冲的有效性 */
        if (xfer->tx_buf || xfer->rx_buf) {
            reinit_completion(&ctlr->xfer_completion);
            /* 
              执行SPI控制器的transfer_one回调,即每个控制器驱动自己实现的传输函数 
              每个SPI控制器驱动的实现都有所不同,在这里面会实现读写操作
            */
            ret = ctlr->transfer_one(ctlr, msg->spi, xfer);
            ......
            /* 这里省略了一下延迟操作跟长度计算 */
            ......
    }
}

/* 这里以全志的SPI驱动为例简单说明一下读操作的完成步骤 */
static int sun6i_spi_transfer_one(struct spi_master *master,
                  struct spi_device *spi,
                  struct spi_transfer *tfr)
{
    /* 获取私有数据结构体sspi,该结构体会在接收中断中使用 */
    struct sun6i_spi *sspi = spi_master_get_devdata(master);
    unsigned int mclk_rate, div, timeout;
    unsigned int start, end, tx_time;
    unsigned int trig_level;
    unsigned int tx_len = 0;
    int ret = 0;
    u32 reg;

    if (tfr->len > SUN6I_MAX_XFER_SIZE)
        return -EINVAL;

    reinit_completion(&sspi->done);
    sspi->tx_buf = tfr->tx_buf;
    /* 设置接收缓冲 */
    sspi->rx_buf = tfr->rx_buf;
    sspi->len = tfr->len;
    ......
    /* 启动中断 */
    if (tx_len > sspi->fifo_depth)
        sun6i_spi_enable_interrupt(sspi, SUN6I_INT_CTL_TF_ERQ);
    ......
}

static irqreturn_t sun6i_spi_handler(int irq, void *dev_id)
{
    /* sspi为控制器的私有数据,其中的读缓冲已经在sun6i_spi_transfer_one进行设置 */
    struct sun6i_spi *sspi = dev_id;
    u32 status = sun6i_spi_read(sspi, SUN6I_INT_STA_REG);

    ......

    if (status & SUN6I_INT_CTL_RF_RDY) {
        /* 读取SPI总线上的数据 */
        sun6i_spi_drain_fifo(sspi, SUN6I_FIFO_DEPTH);
        ......
        return IRQ_HANDLED;
    }
    ......
    return IRQ_NONE;
}

static inline void sun6i_spi_drain_fifo(struct sun6i_spi *sspi, int len)
{
    u32 reg, cnt;
    u8 byte;
    ......
    /* 循环读取接收到的数据 */
    while (len--) {
        byte = readb(sspi->base_addr + SUN6I_RXDATA_REG);
        if (sspi->rx_buf)
            *sspi->rx_buf++ = byte;
    }
}

到了这里,SPI框架 的梳理基本就完成了。当然,以上为了使得思路更加清晰,精简了不少内容。所以本文只是 SPI框架 的冰山一角,其中的一些 共享资源处理、片选操作及时序延时等等没有进行讲述,有兴趣的读者可以自行查阅相关代码。

三、参考链接

为什么教程都不讲MODULE_DEVICE_TABLE的作用
MODULE_DEVICE_TABLE
Linux 内核 MODULEDEVICETABLE 宏
Linux SPI 驱动分析(1)— 结构框架
linux驱动基础系列--linux spi驱动框架分析
SPI 总线协议和驱动

相关文章

网友评论

      本文标题:linux驱动之spi框架

      本文链接:https://www.haomeiwen.com/subject/lpbudktx.html