Bootstrap

Linux SPI驱动框架(2)——控制器驱动层

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

;