前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux驱动之SPI子系统剖析

Linux驱动之SPI子系统剖析

作者头像
菜菜cc
发布2022-11-15 21:31:50
4.3K0
发布2022-11-15 21:31:50
举报

SPI硬件基本概念

SPI总线由四根通信线组成,全双工、主从方式串行同步通信,一次传输8bit,高位在前,低位在后。

  • MOSI(Master Out Slave In): 主设备输出从设备输入
  • MISO(Master In Slave out): 从设备输入主设备输出
  • SCLK:同步信号的时钟线
  • CS: 片选线,通过片选来选择与哪一个从设备通信 注: 与I2C对比,由于SPI采用的是两根单向的数据线,而不是I2C采用的双向数据线,所以SPI为全双工通信,而I2C半双工。 ​ I2C选择总线上挂接的一个从设备是使用从地址来区分的,而SPI采用的是CS片选线

SPI子系统框架

  • SPI核心层: drivers/spi/spi.c
  • SPI总线驱动层(主机控制器驱动层):drivers/spi/spi_s3c24xx. c
  • SPI设备驱动层:drivers/spi/spidev.c (内核提供的SPI通用设备驱动)

Linux中的主从模式的总线子系统采用的是同一种分离思想,其分离的具体策略大同小异,同样分为设备驱动层核心层总线驱动层。具体的分离策略详细分析可参考Linux驱动之I2C子系统剖析中内核对I2C子系统框架的阐述。笔者在这与I2C子系统类比,列出数据结构名。

I2C

SPI

主机适配器(控制器)

struct i2c_adapter

struct spi_master

机控制器的操作方法

struct i2c_algorithm

struct spi_bitbang

从机设备

struct i2c_client

struct spi_device

从机设备板卡信息

struct i2c_board_info

struct spi_board_info

从机设备驱动

struct i2c_driver

struct spi_driver

一次完整的数据包

struct i2c_msg

struct spi_transfer

多个完整数据包的封装

struct spi_message

源码分析

由于子系统架构与I2C等总线类似,所以不会在一些重复部分展开,具体分析可以参考的Linux驱动之I2C子系统剖析中的分析方法。

SPI核心层

SPI核心层代码位于drivers/spi/spi.c 中, 从init函数开始分析

代码语言:javascript
复制
static int __init spi_init(void)
{
    int status;

    buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
    if (!buf) {
        status = -ENOMEM;
        goto err0;
    }

    status = bus_register(&spi_bus_type);     /* 注册SPI总线 */
    if (status < 0)
        goto err1;

    status = class_register(&spi_master_class);     /* 注册SPI类 */
    if (status < 0)
        goto err2;
    return 0;

err2:
    bus_unregister(&spi_bus_type);
err1:
    kfree(buf);
    buf = NULL;
err0:
    return status;
}

spi_init函数如同I2C核心层中的init函数一样做了两件事,注册SPI总线和创建SPI类,这是内核驱动模型的基本套路,就不比多说了。接下来看下match函数

代码语言:javascript
复制
static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,
                        const struct spi_device *sdev)
{
    while (id->name[0]) {
        if (!strcmp(sdev->modalias, id->name))
            return id;
        id++;
    }
    return NULL;
}

/* SPI总线的match方法 */
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);

    if (sdrv->id_table)
        return !!spi_match_id(sdrv->id_table, spi);

    return strcmp(spi->modalias, drv->name) == 0;
}

可以看到,SPI设备和驱动的匹配是先匹配id_table中的name和设备的modalias,然后匹配驱动的name和设备的modalias。

SPI总线驱动层

SPI的控制器驱动,即总线驱动层位于drivers/spi/spi_s3c24xx. c中,从init函数开始分析。

代码语言:javascript
复制
static int __init s3c24xx_spi_init(void)
{   
    return platform_driver_probe(&s3c24xx_spi_driver, s3c24xx_spi_probe);
}

会发现SPI控制器驱动并不是用的是platform_driver_register接口来注册的,而是使用了另一个接口platform_driver_probe, 其实这是内核提供的不支持热插拔方式的专用平台总线驱动的注册接口,该接口接受两个参数,第一个就是熟知的struct platform_driver,第二个则是probe函数,当驱动和设备匹配上后就会调用这个probe函数。进入到 s3c24xx_spi_probe函数进行分析,probe函数的代码比较多,分段进行分析。

代码语言:javascript
复制
struct s3c2410_spi_info *pdata;
struct s3c24xx_spi *hw;
struct spi_master *master;
struct resource *res;
int err = 0;

/* 实例化spi控制器 */
master = spi_alloc_master(&pdev->dev, squdongqudongizeof(struct s3c24xx_spi));
if (master == NULL) {
    dev_err(&pdev->dev, "No memory for spi_master\n");
    err = -ENOMEM;
    goto err_nomem;
}

/* 获取spi的私有数据结构体并初始化为空 */
hw = spi_master_get_devdata(master);
memset(hw, 0, sizeof(struct s3c24xx_spi));

/* 设置spi的私有数据*/
hw->master = spi_master_get(master);
hw->pdata = pdata = pdev->dev.platform_data;
hw->dev = &pdev->dev;

if (pdata == NULL) {
    dev_err(&pdev->dev, "No platform data supplied\n");
    err = -ENOENT;
    goto err_no_pdata;
}

platform_set_drvdata(pdev, hw);
init_completion(&hw->done);     /* 初始化completion,  用于IO的同步*/

实例化SPI控制器后设置SPI的私有数据,然后初始化completion。

代码语言:javascript
复制
/* initialise fiq handler */

s3c24xx_spi_initfiq(hw);       /* 初始化s3c24xx_spi结构体中的handler,为其绑定中断处理函数 */

/* setup the master state. */

/* the spi->mode bits understood by this driver: */
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;

master->num_chipselect = hw->pdata->num_cs;
master->bus_num = pdata->bus_num;

这一段初始化s3c24xx_spi结构体中的handler,为其绑定中断处理函数,然后设置了主机控制器支持的SPI模式,设置master的片选线编号和总线编号。

代码语言:javascript
复制
hw->bitbang.master         = hw->master;
hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer;
hw->bitbang.chipselect     = s3c24xx_spi_chipsel;
hw->bitbang.txrx_bufs      = s3c24xx_spi_txrx;

hw->master->setup  = s3c24xx_spi_setup;
hw->master->cleanup = s3c24xx_spi_cleanup;

bitbang表示的是SPI的操作方法,这一段关键是填充了setup_transfer,即传输方法。

代码语言:javascript
复制
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
    dev_err(&pdev->dev, "Cannot get IORESOURCE_MEM\n");
    err = -ENOENT;
    goto err_no_iores;
}

hw->ioarea = request_mem_region(res->start, resource_size(res),
                pdev->name);

if (hw->ioarea == NULL) {
    dev_err(&pdev->dev, "Cannot reserve region\n");
    err = -ENXIO;
    goto err_no_iores;
}

hw->regs = ioremap(res->start, resource_size(res));
if (hw->regs == NULL) {
    dev_err(&pdev->dev, "Cannot map IO\n");
    err = -ENXIO;
    goto err_no_iomap;
}

hw->irq = platform_get_irq(pdev, 0);
if (hw->irq < 0) {
    dev_err(&pdev->dev, "No IRQ specified\n");
    err = -ENOENT;
    goto err_no_irq;
}

err = request_irq(hw->irq, s3c24xx_spi_irq, 0, pdev->name, hw);
if (err) {
    dev_err(&pdev->dev, "Cannot claim IRQ\n");
    goto err_no_irq;
}

hw->clk = clk_get(&pdev->dev, "spi");
if (IS_ERR(hw->clk)) {
    dev_err(&pdev->dev, "No clock for device\n");
    err = PTR_ERR(hw->clk);
    goto err_no_clk;
}

/* setup any gpio we can */

if (!pdata->set_cs) {
    if (pdata->pin_cs < 0) {
        dev_err(&pdev->dev, "No chipselect pin\n");
        goto err_register;
    }

    err = gpio_request(pdata->pin_cs, dev_name(&pdev->dev));
    if (err) {
        dev_err(&pdev->dev, "Failed to get gpio for cs\n");
        goto err_register;
    }

    hw->set_cs = s3c24xx_spi_gpiocs;
    gpio_direction_output(pdata->pin_cs, 1);
} else
    hw->set_cs = pdata->set_cs;

s3c24xx_spi_initialsetup(hw);  
    s3c24xx_spi_initialsetup(hw);  /* spi 控制器初始化 */

/* register our spi controller */

/* 内部最后spi_register_master来注册SPI控制器 */
err = spi_bitbang_start(&hw->bitbang);
if (err) {
    dev_err(&pdev->dev, "Failed to register SPI master\n");
    goto err_register;weiyu
}

这一段是跟具体硬件息息相关的,从获取平台资源开始,然后分别做了IO的映射、中断的申请与中断处理函数的绑定、时钟的初始化和片选的GPIO的申请和拉高电平。最后关键是调用了s3c24xx_spi_initialsetup函数,该函数内部最后调用了spi_register_master方法来注册SPI控制器。类比I2C在probe函数中调用的i2c_add_numbered_adapter函数,其内部会扫描SPI的板卡信息,然后利用板卡信息生成SPI设备,并将控制器spi_master挂接到spi_device上,随后在SPI设备驱动层中注册设备驱动后调用probe函数会获取到该spi_device,然后即可通过spi_device 中挂接的spi_master来调用控制器的操作方法spi_bitbang_transfer来传输数据。要注意的是SPI与I2C提供的通用设备驱动不同,其设备节点的生成并不是在注册主机控制器中完成的,而是在通用设备中完成的,这一段从之后设备驱动层的分析可以看出。这一段逻辑类似于I2C,就不参考源码分析了。(好吧,一如既往的懒QAQ)

SPI设备驱动层

SPI通用设备驱动位于drivers/spi/spidev.c中,从init函数开始。

代码语言:javascript
复制
static struct spi_driver spidev_spi_driver = {
    .driver = {
        .name =     "spidev",
        .owner =    THIS_MODULE,
    },
    .probe =    spidev_probe,
    .remove =   __devexit_p(spidev_remove),
};

static int __init spidev_init(void)
{
    int status;

    /* Claim our 256 reserved device numbers.  Then register a class
     * that will key udev/mdev to add/remove /dev nodes.  Last, register
     * the driver which manages those device numbers.
     */
    BUILD_BUG_ON(N_SPI_MINORS > 256);
    /* 注册为字符设备驱动,为应用层提供调用接口 */
    status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
    if (status < 0)
        return status;

    /* 创建spidev类 */
    spidev_class = class_create(THIS_MODULE, "spidev");
    if (IS_ERR(spidev_class)) {
        unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
        return PTR_ERR(spidev_class);
    }

    /* 调用核心层提供的接口来注册设备驱动 */
    status = spi_register_driver(&spidev_spi_driver);
    if (status < 0) {
        class_destroy(spidev_class);
        unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
    }
    return status;
}

有空再写了,先休息啦

本文作者: Ifan Tsai  (菜菜)

本文链接: https://cloud.tencent.com/developer/article/2164594

版权声明: 本文采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-07-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • SPI硬件基本概念
  • SPI子系统框架
  • 源码分析
    • SPI核心层
      • SPI总线驱动层
        • SPI设备驱动层
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档