SPI控制器驱动层

  上节中,讲了SPI核心层的东西,这一部分,以全志平台SPI控制器驱动为例,对SPI控制器驱动进行说明。
SPI控制器驱动,即SPI硬件控制器对应的驱动,核心部分需要实现硬件SPI数据收发部分功能。这样SPI设备驱动,才能通过SPI读写数据。
  下面一起来看一下全志平台的SPI控制器驱动。

设备树

  SPI是一种平台特定的资源,所以它是以platform平台设备的方式注册进内核的,因此它的struct platform_device结构是已经静态定义好了的,现在只待它的struct platform_driver注册,然后和platform_device匹配。
  下面是全志h3芯片设备树节点代码:

spi0: spi@01c68000 {compatible = "allwinner,sun8i-h3-spi";               /* 描述,和驱动匹配 */reg = <0x01c68000 0x1000>;                         /* 控制器IO地址 */interrupts = <GIC_SPI 65 IRQ_TYPE_LEVEL_HIGH>;      /* 控制器中断 */clocks = <&ccu CLK_BUS_SPI0>, <&ccu CLK_SPI0>;      /* 控制器时钟 */clock-names = "ahb", "mod";                         /* 时钟名 */dmas = <&dma 23>, <&dma 23>;                      /* dma通道配置 */dma-names = "rx", "tx";                               /* dma名 */pinctrl-names = "default";pinctrl-0 = <&spi0_pins>;                         /* 引脚复用功能配置 */resets = <&ccu RST_BUS_SPI0>;                      /* 复位寄存器 */status = "disabled";                              /* 暂时disable */#address-cells = <1>;#size-cells = <0>;
};

  基本信息都已经在注释中说明了。都是一些比较基本的,用于匹配的描述,IO地址,中断、clk等。

驱动代码

  全志h3芯片对应的spi驱动代码文件为(drivers/spi/spi-sun6i.c)。

platform_driver

  首先来看一下platform_driver部分:

static const struct of_device_id sun6i_spi_match[] = {{ .compatible = "allwinner,sun6i-a31-spi", .data = (void *)SUN6I_FIFO_DEPTH },{ .compatible = "allwinner,sun8i-h3-spi",  .data = (void *)SUN8I_FIFO_DEPTH },          (1){}
};
MODULE_DEVICE_TABLE(of, sun6i_spi_match);static const struct dev_pm_ops sun6i_spi_pm_ops = {.runtime_resume        = sun6i_spi_runtime_resume,.runtime_suspend    = sun6i_spi_runtime_suspend,
};static struct platform_driver sun6i_spi_driver = {.probe = sun6i_spi_probe,                                                             (2).remove  = sun6i_spi_remove,.driver = {.name       = "sun6i-spi",.of_match_table    = sun6i_spi_match,.pm      = &sun6i_spi_pm_ops,},
};
module_platform_driver(sun6i_spi_driver);

(1)描述,与设备树相对应,会调用probe函数
(2)probe函数,匹配时调用

probe

  接着来看一下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;master = spi_alloc_master(&pdev->dev, sizeof(struct sun6i_spi));                 (1)if (!master) {dev_err(&pdev->dev, "Unable to allocate SPI Master\n");return -ENOMEM;}platform_set_drvdata(pdev, master);                                                    (2)sspi = spi_master_get_devdata(master);res = platform_get_resource(pdev, IORESOURCE_MEM, 0);                                (3)sspi->base_addr = devm_ioremap_resource(&pdev->dev, res);                         (4)if (IS_ERR(sspi->base_addr)) {ret = PTR_ERR(sspi->base_addr);goto err_free_master;}irq = platform_get_irq(pdev, 0);                                                  (5)if (irq < 0) {dev_err(&pdev->dev, "No spi IRQ specified\n");ret = -ENXIO;goto err_free_master;}ret = devm_request_irq(&pdev->dev, irq, sun6i_spi_handler,                           (6)0, "sun6i-spi", sspi);if (ret) {dev_err(&pdev->dev, "Cannot request IRQ\n");goto err_free_master;}sspi->master = master;sspi->fifo_depth = (unsigned long)of_device_get_match_data(&pdev->dev);master->max_speed_hz = 100 * 1000 * 1000;master->min_speed_hz = 3 * 1000;master->set_cs = sun6i_spi_set_cs;                                                 (7)master->transfer_one = sun6i_spi_transfer_one;                                       (7)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;sspi->hclk = devm_clk_get(&pdev->dev, "ahb");                                       (8)if (IS_ERR(sspi->hclk)) {dev_err(&pdev->dev, "Unable to acquire AHB clock\n");ret = PTR_ERR(sspi->hclk);goto err_free_master;}sspi->mclk = devm_clk_get(&pdev->dev, "mod");                                     (8)if (IS_ERR(sspi->mclk)) {dev_err(&pdev->dev, "Unable to acquire module clock\n");ret = PTR_ERR(sspi->mclk);goto err_free_master;}init_completion(&sspi->done);sspi->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL);                 (9)if (IS_ERR(sspi->rstc)) {dev_err(&pdev->dev, "Couldn't get reset controller\n");ret = PTR_ERR(sspi->rstc);goto err_free_master;}/** This wake-up/shutdown pattern is to be able to have the* device woken up, even if runtime_pm is disabled*/ret = sun6i_spi_runtime_resume(&pdev->dev);if (ret) {dev_err(&pdev->dev, "Couldn't resume the device\n");goto err_free_master;}pm_runtime_set_active(&pdev->dev);pm_runtime_enable(&pdev->dev);pm_runtime_idle(&pdev->dev);ret = devm_spi_register_master(&pdev->dev, master);                                 (10)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;
}

(1)申请struct spi_master内存以及私有数据内存(struct sun6i_spi)
(2)将struct spi_master设置为platform_device的private_data。
(3)从设备树获取IO地址,对应设备树reg节点
(4)将IO内存映射为虚拟地址
(5)从设备树获取中断,对应设备树interrupts节点。
(6)申请中断,设置中断服务函数
(7)设置核心层回调的片选使能函数,设置spi_master的transfer_one函数,核心层篇中已经介绍了,如果控制器驱动不实现transfer和transfer_one_message,内核会自动填充默认的,最终控制器驱动只需要实现transfer_one。
(8)根据时钟名,从设备树获取时钟。
(9)根据设备树的reset节点,操作寄存器spi控制器对应的BIT进行复位。
(10)调用devm_spi_register_master,把spi_master->device注册到设备模型中。核心层篇中以及详细介绍了devm_spi_register_master函数,这里不再展开。
  这里展开看一下比较有意思的spi_alloc_master函数(1):

static inline struct spi_controller *spi_alloc_master(struct device *host,unsigned int size)
{return __spi_alloc_controller(host, size, false);
}struct spi_controller *__spi_alloc_controller(struct device *dev,unsigned int size, bool slave)
{struct spi_controller  *ctlr;if (!dev)return NULL;ctlr = kzalloc(size + sizeof(*ctlr), GFP_KERNEL);if (!ctlr)return NULL;device_initialize(&ctlr->dev);ctlr->bus_num = -1;ctlr->num_chipselect = 1;ctlr->slave = slave;if (IS_ENABLED(CONFIG_SPI_SLAVE) && slave)ctlr->dev.class = &spi_slave_class;elsectlr->dev.class = &spi_master_class;ctlr->dev.parent = dev;pm_suspend_ignore_children(&ctlr->dev, true);spi_controller_set_devdata(ctlr, &ctlr[1]);return ctlr;
}

  这里贴的代码,和核心篇中讲的存在一些偏差了,之前是基于4.9版本内核写的,那些数据结构也是一直都在用的。最近更新用了4.14版本内核,发现SPI核心层的代码存在改动,struct spi_controller其实就是struct spi_master。代码中实现如下,这里我们还是以老的数据结构名称来说明。如struct spi_controller继续用struct spi_master来称呼。

#define spi_master           spi_controller

  回到spi_alloc_master,可以看到申请了内存,申请的大小为struct spi_master数据结构大小+自定义数据结构大小。然后对spi_master部分参数进行设置,并设置spi_master对应的class,一般来说设置为spi_master_class,soc的spi一般是做spi主设备的。然后将spi_master多申请的内存,设为private_data,也就是为struct sun6i_spi *sspi;准备的。也就是申请一片内存,前面是struct spi_master内存空间,后面是控制器自定义的数据。spi_master的private_data指针又指向自定义数据的内存位置。

数据收发部分

  数据收发部分主要是两个函数,一个是控制收发的函数,即spi_master的transfer_one函数,还有一个就是配合发送的中断服务函数。

sun6i_spi_transfer_one

static int sun6i_spi_transfer_one(struct spi_master *master,struct spi_device *spi,struct spi_transfer *tfr)
{struct sun6i_spi *sspi = spi_master_get_devdata(master);...reinit_completion(&sspi->done);.../* 硬件相关寄存器操作 */...tx_time = max(tfr->len * 8 * 2 / (tfr->speed_hz / 1000), 100U);start = jiffies;timeout = wait_for_completion_timeout(&sspi->done,msecs_to_jiffies(tx_time));end = jiffies;if (!timeout) {dev_warn(&master->dev,"%s: timeout transferring %u bytes@%iHz for %i(%i)ms",dev_name(&spi->dev), tfr->len, tfr->speed_hz,jiffies_to_msecs(end - start), tx_time);ret = -ETIMEDOUT;goto out;}out:sun6i_spi_write(sspi, SUN6I_INT_CTL_REG, 0);return ret;
}

  全志h3的sun6i_spi_transfer_one函数,上面的代码删掉了硬件相关的寄存器操作,有兴趣的同学可以对着芯片datasheet,以及逻辑代码食用。这里我就不展开了。(PS:之前在别的平台上跟过,由于工作原因不方便贴那边的代码哈哈哈)
  可以看到全志这里代码的逻辑,一个是初始化完成量。然后进行一系列硬件操作后,睡眠等待完成信号,同时设置超时时间,超时了没有得到信号量,证明硬件出问题了,不应该一直等待,应该返回错误。
  这个完成信号由中断来发送,硬件上会有发送完成中断。也有平台是通过轮训查看FIFO是否为空来判断的,相对来说,中断会更搞笑及时一点,全志这里用的就是中断的方式。

sun6i_spi_handler

  下面来看下中断服务函数:

static irqreturn_t sun6i_spi_handler(int irq, void *dev_id)
{struct sun6i_spi *sspi = dev_id;u32 status = sun6i_spi_read(sspi, SUN6I_INT_STA_REG);/* Transfer complete */if (status & SUN6I_INT_CTL_TC) {sun6i_spi_write(sspi, SUN6I_INT_STA_REG, SUN6I_INT_CTL_TC);sun6i_spi_drain_fifo(sspi, sspi->fifo_depth);complete(&sspi->done);return IRQ_HANDLED;}...
}

  取其精华,去其糟粕,省略的代码都是FIFO半满半空类似的优化硬件连续发送的操作,这里省略。核心的代码贴出来了。
  读取中断状态寄存器,判断发送完成的BIT是否置位,如果置位,则表示硬件已经发送完数据了,那么发送完成信号量,给正在睡眠等待的sun6i_spi_transfer_one函数。sun6i_spi_transfer_one函数接收到信号量后被唤醒,知道硬件以及发送完数据了,返回0(表示发送成功)
  注意sun6i_spi_drain_fifo函数和读取数据有关,代码如下:

static inline void sun6i_spi_drain_fifo(struct sun6i_spi *sspi, int len)
{u32 reg, cnt;u8 byte;/* See how much data is available */reg = sun6i_spi_read(sspi, SUN6I_FIFO_STA_REG);reg &= SUN6I_FIFO_STA_RF_CNT_MASK;cnt = reg >> SUN6I_FIFO_STA_RF_CNT_BITS;if (len > cnt)len = cnt;while (len--) {byte = readb(sspi->base_addr + SUN6I_RXDATA_REG);if (sspi->rx_buf)*sspi->rx_buf++ = byte;}
}

  这里先获取接受FIFO中的数据长度,然后进行接受。当rx_buff指针不为空时,把rx FIFO中的数据读取出来,放到spi_transfer的rx_buff中。当SPI发送,其实是不需要返回的数据的。所以一般不填rx_buff指针。

SPI驱动框架链接:
Linux SPI驱动框架(1)——核心层
Linux SPI驱动框架(3)——设备驱动层

Linux SPI驱动框架(2)——控制器驱动层相关推荐

  1. Linux SPI驱动框架(3)——设备驱动层

    SPI设备驱动层   Linux SPI驱动框架(1)和(2)中分别介绍了SPI框架中核心层,和控制器驱动层.其实实际开发过程中,不是IC原厂工程师比较少会接触控制器驱动层,设备驱动层才是接触比较多的 ...

  2. Linux内核USB总线--设备控制器驱动框架分析

    正文 1.概述 如下图所示,USB控制器可以呈现出两种不同的状态.USB控制器作为Host时,称为USB主机控制器,使用USB主机控制器驱动.USB控制器作为Device时,称为USB设备控制器,使用 ...

  3. linux用户空间flash驱动,全面掌握Linux驱动框架——字符设备驱动、I2C驱动、总线设备驱动、NAND FLASH驱动...

    原标题:全面掌握Linux驱动框架--字符设备驱动.I2C驱动.总线设备驱动.NAND FLASH驱动 字符设备驱动 哈~ 这几天都在发图,通过这种方式,我们希望能帮大家梳理学过的知识,全局的掌握Li ...

  4. Linux驱动框架之framebuffer驱动框架

    1.什么是framebuffer? (1)framebuffer帧缓冲(一屏幕数据)(简称fb)是linux内核中虚拟出的一个设备,framebuffer向应用层提供一个统一标准接口的显示设备.帧缓冲 ...

  5. 驱动框架5——基于驱动框架写led驱动

    以下内容源于朱有鹏<物联网大讲堂>课程的学习整理,如有侵权,请告知删除. 五.基于驱动框架写led驱动1 1.分析 (1)参考哪里?  drivers/leds/leds-s3c24xx. ...

  6. 驱动框架2——内核驱动框架中LED的基本情况、初步分析

    以下内容源于朱有鹏嵌入式课程的学习,如有侵权,请告知删除. 一.内核驱动框架中LED的基本情况 1.相关文件 (1)drivers/leds目录 驱动框架规定的LED这种硬件的驱动应该待的地方. (2 ...

  7. linux pcie驱动框架_Linux设备驱动框架设计

    引子 Linux操作系统的一大优势就是支持数以万计的芯片设备,大大小小的芯片厂商工程师都在积极地向Linux kernel提交设备驱动代码.能让这个目标得以实现,这背后隐藏着一个看不见的技术优势:Li ...

  8. linux iio 设备驱动,FS4412开发板使用Linux IIO驱动框架实现ADC驱动

    1.概述 FS4412开发板有一个4通道(0/1/2).10/12比特精度的 ADC ,其中: 1)ADCIN0: 在核心板中引出 2)ADCIN1: 在核心板中引出 3)ADCIN2: 在核心板中引 ...

  9. 驱动框架8——将驱动集成到内核中

    以下内容源于朱有鹏<物联网大讲堂>课程的学习整理,如有侵权,请告知删除. 十五.将驱动添加到内核中 1.驱动的存在形式 (1)野生,优势是方便调试开发,所以在开发阶段都是这种: (2)家养 ...

最新文章

  1. 微型计算机原理及应用李干林,微机原理及接口技术李干林习题参考解答-20210415154329.docx-原创力文档...
  2. OAuth的机制原理讲解及开发流程
  3. Object of type 'ListSerializer' is not JSON serializable “listserializer”类型的对象不可JSON序列化...
  4. python编译exe文件太大了_python编译windows下可执行的exe文件
  5. 谈谈产品开发团队的配置管理规则
  6. 使用请求头认证来测试需要授权的 API 接口
  7. 根据后序和中序求二叉树的层序
  8. OSTU大律法二值化原理
  9. defaultlib library conflicts with use of other libs; use /NODEFAULTLIB:library
  10. Mybatis: 接口编程的实现
  11. mysql读写分离6_6\MySQL 主从同步 、 MySQL 读写分离 、 MySQL 性能调优
  12. 股票数据下载-如何下载股票历史行情数据?
  13. 网易交互设计师微专业C5 交互设计测试与评估
  14. Unity LitJson的读写使用
  15. 织梦dedecms包装设计生产公司网站模板(中英文版)
  16. 将MATLAB中不显示个别图例,隐藏图中某些图形对象的MATLAB图例条目
  17. SecureCRT 设置会话永不过期
  18. godaddy服务器内网站转移,2021年Godaddy最新域名转出教程 | Godaddy美国主机中文指南...
  19. iOS文章 - 收藏集 - 掘金
  20. (linux)idr(integer ID management)机制

热门文章

  1. 什么是消息补偿机制?
  2. 贝叶斯统计——先验分布与后验分布
  3. Harris角点及Shi-Tomasi角点检测
  4. hadoop是什么语言
  5. Keras Tuner官方教程
  6. MySQL——创建存储过程和函数
  7. 投资怕选错房?跟着买房路线图走不亏!
  8. 微信开发者工具扩展自定义插件
  9. Java之图片裁剪工具类-yellowcong
  10. python 修改图片尺寸_用Python更改图片尺寸大小