首页 » 互联网 » 【驱动】SPI驱动分析(六)-RK SPI驱动分析_函数_主装备

【驱动】SPI驱动分析(六)-RK SPI驱动分析_函数_主装备

南宫静远 2024-12-07 01:47:49 0

扫一扫用手机浏览

文章目录 [+]

先看Makefile,里面关键几行: obj-$(CONFIG_SPI_MASTER) += spi.o //这个是针对有spi掌握器的soc选项,一样平常的soc都有spi掌握器吧。
# SPI master controller drivers (bus) //下面的这些便是针对不同soc上的spi掌握器的驱动了,我们可以通过make menuconfig的时候选上自己对应平台的

# SPI master controller drivers (bus) obj-$(CONFIG_SPI_ALTERA) += spi-altera.o obj-$(CONFIG_SPI_ARMADA_3700) += spi-armada-3700.o ............ obj-$(CONFIG_SPI_AXI_SPI_ENGINE) += spi-axi-spi-engine.o

下面这些便是针对付主机作为spi从设备的时候用的,暂时貌似没支持,毕竟现实中险些没有用过,而是作为master端涌现

【驱动】SPI驱动分析(六)-RK SPI驱动分析_函数_主装备 【驱动】SPI驱动分析(六)-RK SPI驱动分析_函数_主装备 互联网

# SPI slave protocol handlers obj-$(CONFIG_SPI_SLAVE_TIME) += spi-slave-time.o obj-$(CONFIG_SPI_SLAVE_SYSTEM_CONTROL) += spi-slave-system-control.o

再看Kconfig,第一个SPI选项我以为有必要贴一下,首先只有选择它了才能进行后面的配置,其次它的help对spi的描述说的很清楚!

【驱动】SPI驱动分析(六)-RK SPI驱动分析_函数_主装备 【驱动】SPI驱动分析(六)-RK SPI驱动分析_函数_主装备 互联网
(图片来自网络侵删)

# # SPI driver configuration # menuconfig SPI bool "SPI support" depends on HAS_IOMEM help The "Serial Peripheral Interface" is a low level synchronous protocol. Chips that support SPI can have data transfer rates up to several tens of Mbit/sec. Chips are addressed with a controller and a chipselect. Most SPI slaves don't support dynamic device discovery; some are even write-only or read-only. SPI is widely used by microcontrollers to talk with sensors, eeprom and flash memory, codecs and various other controller chips, analog to digital (and d-to-a) converters, and more. MMC and SD cards can be accessed using SPI protocol; and for DataFlash cards used in MMC sockets, SPI must always be used. SPI is one of a family of similar protocols using a four wire interface (select, clock, data in, data out) including Microwire (half duplex), SSP, SSI, and PSP. This driver framework should work with most such devices and controllers.

我们其次须要配上的选项便是SPI_MASTER和CONFIG_SPI_ROCKCHIP(我手上的是RK的SDK)。

config SPI_MASTER # bool "SPI Master Support" bool default SPI help If your system has an master-capable SPI controller (which provides the clock and chipselect), you can enable that controller and the protocol drivers for the SPI slave chips that are connected. config SPI_ROCKCHIP tristate "Rockchip SPI controller driver" help This selects a driver for Rockchip SPI controller. If you say yes to this option, support will be included for RK3066, RK3188 and RK3288 families of SPI controller. Rockchip SPI controller support DMA transport and PIO mode. The main usecase of this controller is to use spi flash as boot device.

于是从Makefile里得到如下语句和我们干系:

obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockchip.o

于是我们要剖析的代码紧张有:spi.c spi-rockchip.c,瞬间没压力了,就两个文件,呵呵

下面紧张从三个方面来剖析spi框架

spi掌握器驱动的实现(毕竟spi掌握器的驱动还是有可能要打仗的)spi设备的驱动(我们更多的是编写设备的驱动,还是以eeprom为例吧,虽然我很想以spi接口的nor flash驱动为例,但是那又会牵扯出mtd子系统,这个留在mtd子系统剖析吧)spi核心层的实现(上面1、2都因此各自的驱动实现为目标,并不深入到spi核心层,也便是至于spi核心层怎么为我们供应的做事不去关心,只须要按spi核心层利用它供应的做事便是了。
以是现在统一剖析spi核心层,看它是怎么供应的做事)spi掌握器驱动的实现

以spi-rockchip.c为例,直接看module_platform_driver:

static struct platform_driver rockchip_spi_driver = { .driver = { .name = DRIVER_NAME, .pm = &rockchip_spi_pm, .of_match_table = of_match_ptr(rockchip_spi_dt_match), }, .probe = rockchip_spi_probe, .remove = rockchip_spi_remove, };

平台驱动的内部流程就不剖析了,直接看匹配成功后rockchip_spi_probe的调用,但这里还是插入平台spi掌握器设备审察干的代码:

利用spi_alloc_master函数为平台设备pdev分配一个SPI主设备构造体,并将其大小设置为sizeof(struct rockchip_spi)。
这个函数会分配内存并初始化主设备构造体的各个字段。
调用platform_set_drvdata函数将主设备构造体指针保存在平台设备的私有数据中,以便后续在驱动的其他函数中可以访问该指针。
利用spi_master_get_devdata函数获取之前保存在私有数据中的主设备构造体指针rs。

master = spi_alloc_master(&pdev->dev, sizeof(struct rockchip_spi)); if (!master) return -ENOMEM; platform_set_drvdata(pdev, master); rs = spi_master_get_devdata(master);利用platform_get_resource函数获取SPI掌握器的IO资源。
这些资源包括寄存器地址、中断号等信息,向操作系统要求资源空间并建立起映射为往后所用。

mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); rs->regs = devm_ioremap_resource(&pdev->dev, mem); if (IS_ERR(rs->regs)) { ret = PTR_ERR(rs->regs); goto err_ioremap_resource; }利用devm_clk_get函数获取SPI掌握器所需的时钟,包括"apb_pclk"和"spiclk"。
这些时钟用于掌握SPI掌握器的时序和传输速率。

rs->apb_pclk = devm_clk_get(&pdev->dev, "apb_pclk");if (IS_ERR(rs->apb_pclk)) {dev_err(&pdev->dev, "Failed to get apb_pclk\n");ret = PTR_ERR(rs->apb_pclk);goto err_ioremap_resource;}rs->spiclk = devm_clk_get(&pdev->dev, "spiclk");if (IS_ERR(rs->spiclk)) {dev_err(&pdev->dev, "Failed to get spi_pclk\n");ret = PTR_ERR(rs->spiclk);goto err_ioremap_resource;}利用clk_prepare_enable函数使获取到的时钟生效,确保SPI掌握器可以正常事情。

ret = clk_prepare_enable(rs->apb_pclk);if (ret) {dev_err(&pdev->dev, "Failed to enable apb_pclk\n");goto err_ioremap_resource;}ret = clk_prepare_enable(rs->spiclk);if (ret) {dev_err(&pdev->dev, "Failed to enable spi_clk\n");goto err_spiclk_enable;}调用spi_enable_chip函数使SPI芯片处于可用状态。
这个函数会实行一些特定的SPI掌握器寄存器的配置,以便使芯片可以正常通信。
设置SPI主设备的属性。
这些属性包括SPI总线类型、主设备指针、设备指针、最大频率等。

spi_enable_chip(rs, 0);rs->type = SSI_MOTO_SPI;rs->master = master;rs->dev = &pdev->dev;rs->max_freq = clk_get_rate(rs->spiclk);利用of_property_read_u32函数从设备节点中读取属性值。
在这个例子中,它读取了"rx-sample-delay-ns"属性,并将其存储在rsd_nsecs变量中。
调用get_fifo_len函数获取FIFO的长度。
FIFO用于在SPI传输过程中暂存数据。

if (!of_property_read_u32(pdev->dev.of_node, "rx-sample-delay-ns", &rsd_nsecs))rs->rsd_nsecs = rsd_nsecs;rs->fifo_len = get_fifo_len(rs);if (!rs->fifo_len) {dev_err(&pdev->dev, "Failed to get fifo length\n");ret = -EINVAL;goto err_get_fifo_len;}初始化自旋锁。
自旋锁用于保护共享资源,防止多个进程同时访问造成冲突。
设置设备的运行时PM状态为活动,并启用运行时PM。
许可系统在不须要SPI设备时将其置于低功耗状态。

spin_lock_init(&rs->lock);pm_runtime_set_active(&pdev->dev);pm_runtime_enable(&pdev->dev);设置主设备的一些属性,例如自动运行时PM、总线号、模式位、芯片选择数量、设备节点等。
同时设置主设备的回调函数。
这些回调函数将在SPI传输中的不同阶段被调用,以实行相应的操作

master->auto_runtime_pm = true;master->bus_num = pdev->id;master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LOOP | SPI_LSB_FIRST;master->num_chipselect = 2;master->dev.of_node = pdev->dev.of_node;master->bits_per_word_mask = SPI_BPW_MASK(16) | SPI_BPW_MASK(8);master->set_cs = rockchip_spi_set_cs;master->prepare_message = rockchip_spi_prepare_message;master->unprepare_message = rockchip_spi_unprepare_message;master->transfer_one = rockchip_spi_transfer_one;master->handle_err = rockchip_spi_handle_err;利用dma_request_slave_channel函数要求DMA通道,如果同时成功要求到了TX和RX的DMA通道,则设置DMA传输的地址和方向,并将相应的DMA通道设置为主设备的属性。

rs->dma_tx.ch = dma_request_slave_channel(rs->dev, "tx");if (IS_ERR_OR_NULL(rs->dma_tx.ch)) {/ Check tx to see if we need defer probing driver /if (PTR_ERR(rs->dma_tx.ch) == -EPROBE_DEFER) {ret = -EPROBE_DEFER;goto err_get_fifo_len;}dev_warn(rs->dev, "Failed to request TX DMA channel\n");}rs->dma_rx.ch = dma_request_slave_channel(rs->dev, "rx");if (!rs->dma_rx.ch) {if (rs->dma_tx.ch) {dma_release_channel(rs->dma_tx.ch);rs->dma_tx.ch = NULL;}dev_warn(rs->dev, "Failed to request RX DMA channel\n");}利用pinctrl_lookup_state函数查找高速模式的引脚掌握状态。
检讨查找高速模式的引脚掌握状态是否成功。

rs->high_speed_state = pinctrl_lookup_state(rs->dev->pins->p, "high_speed");if (IS_ERR_OR_NULL(rs->high_speed_state)) {dev_warn(&pdev->dev, "no high_speed pinctrl state\n");rs->high_speed_state = NULL;}利用devm_spi_register_master函数注册SPI主设备。

ret = devm_spi_register_master(&pdev->dev, master);if (ret) {dev_err(&pdev->dev, "Failed to register master\n");goto err_register_master;}

暂时不进入到spi核心层剖析,这里我们只须要知道调用核心层的注册函数后,核心层会遍历所有注册到核心层的设备(实际最开始是加入到一个全局链表里,和I2C核心层的实现类似),然后考试测验着添加每一个总线号为该掌握器衍生出的总线号的设备到该spi掌握器上,当然如果该spi掌握器不支持某一个设备,那就取消添加这个设备,如果添加成功,那么该设备将会添加到spi总线上去,这条总线是spi核心层注册的,用来管理spi接口的设备和spi接口的驱动。

总结下probe函数的紧张事情:

分配和初始化SPI主设备构造体。
获取并映射IO资源。
获取和使能时钟。
设置SPI主设备的属性和回调函数。
要求并设置DMA通道。
注册SPI主设备。
spi设备的驱动

以eeprom为例,我们剖析下文件at25.c:

同样的,driver的注册过程我们就不深入理解了,实在便是一个总线设备驱动模型。
我们直接看probe函数做了什么。

static const struct of_device_id at25_of_match[] = {{ .compatible = "atmel,at25", },{ }};MODULE_DEVICE_TABLE(of, at25_of_match);static struct spi_driver at25_driver = {.driver = {.name= "at25",.of_match_table = at25_of_match,},.probe= at25_probe,.remove= at25_remove,};检讨spi->dev.platform_data是否存在,如果不存在,则调用at25_fw_to_chip函数将固件信息转换为芯片描述,并将其存储在chip构造体中。
如果存在,则直接将spi->dev.platform_data逼迫类型转换为spi_eeprom构造体,并将其赋值给chip。

/ Chip description /if (!spi->dev.platform_data) {err = at25_fw_to_chip(&spi->dev, &chip);if (err)return err;} elsechip = (struct spi_eeprom )spi->dev.platform_data;根据chip构造体中的标志位判断EEPROM的地址长度是8位、16位还是24位,并将相应的值赋给addrlen变量。

/ For now we only support 8/16/24 bit addressing /if (chip.flags & EE_ADDR1)addrlen = 1;else if (chip.flags & EE_ADDR2)addrlen = 2;else if (chip.flags & EE_ADDR3)addrlen = 3;else {dev_dbg(&spi->dev, "unsupported address type\n");return -EINVAL;}通过发送AT25_RDSR指令读取EEPROM的状态寄存器。
如果读取失落败或状态寄存器中的AT25_SR_nRDY位为1,表示EEPROM不可用,返回缺点码-ENXIO。

/ Ping the chip ... the status register is pretty portable, unlike probing manufacturer IDs. We do expect that system firmware didn't write it in the past few milliseconds! /sr = spi_w8r8(spi, AT25_RDSR);if (sr < 0 || sr & AT25_SR_nRDY) {dev_dbg(&spi->dev, "rdsr --> %d (%02x)\n", sr, sr);return -ENXIO;}利用devm_kzalloc函数为at25_data构造体分配内存,并利用GFP_KERNEL标志指定内存分配的高下文。
初始化互斥锁at25->lock,用于保护共享资源的访问。
将chip构造体和spi设备保存在at25_data构造体中。
利用spi_set_drvdata函数将at25_data构造体指针存储在spi设备的私有数据中,以便在后续的函数中可以方便地访问。
将地址长度addrlen保存在at25_data构造体的addrlen字段中。

at25 = devm_kzalloc(&spi->dev, sizeof(struct at25_data), GFP_KERNEL);if (!at25)return -ENOMEM;mutex_init(&at25->lock);at25->chip = chip;at25->spi = spi_dev_get(spi);spi_set_drvdata(spi, at25);at25->addrlen = addrlen;创建运用层用来操作的文件,利用sysfs_bin_attr_init函数初始化at25->bin成员变量,个中at25->bin是struct bin_attribute类型的变量。
设置at25->bin的属性名称为"eeprom",访问权限为用户只读模式(S_IRUSR)。
设置at25->bin的读回调函数为at25_bin_read,写回调函数为at25_bin_write。

sysfs_bin_attr_init(&at25->bin);at25->bin.attr.name = "eeprom";at25->bin.attr.mode = S_IRUSR;at25->bin.read = at25_bin_read;at25->mem.read = at25_mem_read;根据chip的只读标志位(EE_READONLY),确定是否将写回调函数和写权限添加到at25->bin。

at25->bin.size = at25->chip.byte_len;if (!(chip.flags & EE_READONLY)) {at25->bin.write = at25_bin_write;at25->bin.attr.mode |= S_IWUSR;at25->mem.write = at25_mem_write;}利用sysfs_create_bin_file函数将at25->bin添加到SPI设备的内核对象(spi->dev.kobj)中,以便将EEPROM字节通过sysfs导出。

err = sysfs_create_bin_file(&spi->dev.kobj, &at25->bin);if (err)return err;如果chip的setup字段不为空,将调用chip.setup函数,并将at25->mem和chip.context作为参数通报。
利用dev_info函数打印一条设备信息,包括EEPROM的大小、名称、是否只读以及页面大小。

if (chip.setup)chip.setup(&at25->mem, chip.context);dev_info(&spi->dev, "%Zd %s %s eeprom%s, pagesize %u\n",(at25->bin.size < 1024)? at25->bin.size: (at25->bin.size / 1024),(at25->bin.size < 1024) ? "Byte" : "KByte",at25->chip.name,(chip.flags & EE_READONLY) ? " (readonly)" : "",at25->chip.page_size);

该代码的功能是在SPI设备上探测并初始化一个EEPROM芯片,然后将EEPROM的字节通过sysfs导出,以便其他内核代码或用户空间程序可以方便地访问和操作EEPROM数据。

spi核心层的实现

紧张看spi.c文件:

static int __init spi_init(void);postcore_initcall(spi_init);

从这里可以知道spi_init的调用(也便是spi核心层的初始化)是在驱动加载前的。

调用kmalloc函数为SPI子系统分配一个大小为SPI_BUFSIZ的内核内存缓冲区,并将返回的指针赋值给buf。

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

调用bus_register函数注册SPI总线类型,将其添加到系统总线列表中。
如果注册失落败,将status设置为返回的缺点码,并跳转到err1标签处进行缺点处理。

status = bus_register(&spi_bus_type);if (status < 0)goto err1;

调用class_register函数注册SPI主掌握器类,将其添加到系统设备类列表中。
如果注册失落败,将status设置为返回的缺点码,并跳转到err2标签处进行缺点处理。

status = class_register(&spi_master_class);if (status < 0)goto err2;

所有注册到核心层的spi掌握器都属于这个class。

标签:

相关文章