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;
else
ctlr->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)——设备驱动层