SPI子系统内核源码分析

  目录

spi设备层

spi驱动层

spi核心层

spi收发数据流程

ioctl

write


        设备平台 NXP IMX8QXP,内核版本:linux-4.14.78。

spi设备层

        drivers/spi/spidev.c

        使用module_init注册模块驱动,register_chrdev注册spi字符设备,主设备号153;spidev_fops实现spi字符设备的open,read,write和ioctrl函数;class_create创建spidev_class类,后面会使用spidev_class类来创建dev目录下的spi设备节点;spi_register_driver注册spi设备驱动。

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_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;
}
module_init(spidev_init);

         Linux内核中,设备和驱动是分开的,设备驱动与设备驱动信息,设备与设备信息也是分开的,设备信息在dts文件中描述,使用match进行匹配。

static struct spi_driver spidev_spi_driver = {
	.driver = {
		.name =		"spidev",
		.of_match_table = of_match_ptr(spidev_dt_ids),
		.acpi_match_table = ACPI_PTR(spidev_acpi_ids),
	},
	.probe =	spidev_probe,
	.remove =	spidev_remove,
};

        dts文件编译成dtb,dtb在内核启动时就加载到内存并解析到对应的结构体,设备驱动注册时会在dt中搜索compatible匹配的字符串。

static const struct of_device_id spidev_dt_ids[] = {
    { .compatible = "rohm,dh2228fv" },
    { .compatible = "lineartechnology,ltc2488" },
    { .compatible = "ge,achc" },
    { .compatible = "semtech,sx1301" },
    { .compatible = "spi3,genvict3"},
    { .compatible = "xdja,xdja_spi2_0"},
    { .compatible = "siliconlabs,si3210" },
    {},
};
MODULE_DEVICE_TABLE(of, spidev_dt_ids);

         arch/arm64/boot/dts/freescale/fsl-imx8x-mek.dtsi文件中配置了spi2的设备节点,且配置了设备驱动使用"xdja,xdja_spi2_0",所以这里就会匹配成功,然后开始执行探针函数spidev_probe。

&lpspi2 {
        #address-cells = <1>;
        #size-cells = <0>;
        fsl,spi-num-chipselects = <1>;
        cs-gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_lpspi2>;
        assigned-clock-rates = <128000000>;
        status = "okay";

    spidev2_0:xdja_spi2_0@0 {
        #address-cells = <1>;
        #size-cells = <0>;
        compatible = "xdja,xdja_spi2_0";
        spi-max-frequency = <50000000>;
        reg = <0>;
        status = "okay";
    };
};

        spidev_probe函数,of_match_device检查驱动是否匹配,然后初始化锁,获取次版本号后使用device_create创建设备节点:spidev2.0;传入的spi指针在spi.c文件中已经被函数of_register_spi_device解析设备节点信息。最后设置max_speed_hz和设置设备驱动数据,其实就是spi->dev->driver_data = spidev,创建了spidev结构体指针各种赋值后传给driver_data后续使用。

crw-------    1 root     root      153,   0 Jan  1 00:00 /dev/spidev2.0
static int spidev_probe(struct spi_device *spi)
{
	struct spidev_data	*spidev;
	int			status;
	unsigned long		minor;

	/*
	 * spidev should never be referenced in DT without a specific
	 * compatible string, it is a Linux implementation thing
	 * rather than a description of the hardware.
	 */
	if (spi->dev.of_node && !of_match_device(spidev_dt_ids, &spi->dev)) {
		dev_err(&spi->dev, "buggy DT: spidev listed directly in DT\n");
		WARN_ON(spi->dev.of_node &&
			!of_match_device(spidev_dt_ids, &spi->dev));
	}

	spidev_probe_acpi(spi);

	/* Allocate driver data */
	spidev = kzalloc(sizeof(*spidev), GFP_KERNEL);
	if (!spidev)
		return -ENOMEM;

	/* Initialize the driver data */
	spidev->spi = spi;
	spin_lock_init(&spidev->spi_lock);
	mutex_init(&spidev->buf_lock);

	INIT_LIST_HEAD(&spidev->device_entry);

	/* If we can allocate a minor number, hook up this device.
	 * Reusing minors is fine so long as udev or mdev is working.
	 */
	mutex_lock(&device_list_lock);
	minor = find_first_zero_bit(minors, N_SPI_MINORS);
	if (minor < N_SPI_MINORS) {
		struct device *dev;

		spidev->devt = MKDEV(SPIDEV_MAJOR, minor);
		dev = device_create(spidev_class, &spi->dev, spidev->devt,
				    spidev, "spidev%d.%d",
				    spi->master->bus_num, spi->chip_select);
		status = PTR_ERR_OR_ZERO(dev);
	} else {
		dev_dbg(&spi->dev, "no minor number available!\n");
		status = -ENODEV;
	}
	if (status == 0) {
		set_bit(minor, minors);
		list_add(&spidev->device_entry, &device_list);
	}
	mutex_unlock(&device_list_lock);

	spidev->speed_hz = spi->max_speed_hz;

	if (status == 0)
		spi_set_drvdata(spi, spidev);
	else
		kfree(spidev);

	return status;
}

        dts中配置了几个spi设备节点,那么在注册设备驱动时就会匹配几次,就会触发几次探针spidev_probe函数几次。

        总结:注册设备驱动,根据dts的匹配信息创建设备节点,设置spi设备的基本参数。

spi驱动层

        drivers/spi/spi-fsl-lpspi.c

        使用module_platform_driver在platform总线上注册,spi驱动是操作硬件寄存器的层面,所以匹配的驱动就是操作controller。

static struct platform_driver fsl_lpspi_driver = {
	.driver = {
		.name = DRIVER_NAME,
		.of_match_table = fsl_lpspi_dt_ids,
		.pm = &fsl_lpspi_pm_ops,
	},
	.probe = fsl_lpspi_probe,
	.remove = fsl_lpspi_remove,
};
module_platform_driver(fsl_lpspi_driver);

         match匹配.compatible = "fsl,imx7ulp-spi"字符,lpspi2描述了CPU spi控制器的基本信息,包括寄存器基地址和长度,中断号,时钟信息。

static const struct of_device_id fsl_lpspi_dt_ids[] = {
	{ .compatible = "fsl,imx7ulp-spi", },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fsl_lpspi_dt_ids);
    lpspi2: lpspi@5a020000 {
        compatible = "fsl,imx7ulp-spi";
        reg = <0x0 0x5a020000 0x0 0x10000>;
        interrupts = <GIC_SPI 218 IRQ_TYPE_LEVEL_HIGH>;
        interrupt-parent = <&gic>;
        clocks = <&clk IMX8QXP_SPI2_CLK>,
             <&clk IMX8QXP_SPI2_IPG_CLK>;
        clock-names = "per", "ipg";
        assigned-clocks = <&clk IMX8QXP_SPI2_CLK>;
        assigned-clock-rates = <20000000>;
        power-domains = <&pd_dma_lpspi2>;
        dma-names = "tx","rx";
        dmas = <&edma2 5 0 0>, <&edma2 4 0 1>;
        status = "disabled";
    };

&lpspi2 {
        #address-cells = <1>;
        #size-cells = <0>;
        fsl,spi-num-chipselects = <1>;
        cs-gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_lpspi2>;
        assigned-clock-rates = <128000000>;
        status = "okay";

        匹配"fsl,imx7ulp-spi"字符后执行fsl_lpspi_probe探针函数,首先申请spi_controller,同时在把申请的fsl_lpspi_data放在spi_controller后面,然后设置相应的参数;fsl_lpspi = controller->dev->driver_data,这样fsl_lpspi与controller关联起来,pdev->dev->driver_data = fsl_lpspi也关联起来。设置controller的参数和函数指针。

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;
}

        fsl_lpspi_data是NXP spi驱动自己实现的特有结构体,后续会用到。

        指定spi_controller的transfer_one_message发送数据函数,使用NXP的message方式。

	controller->transfer_one_message = fsl_lpspi_transfer_one_msg;

        platform_get_resource获取IO资源,得到操作寄存器的基地址,devm_request_irq申请中断函数,spi接收数据走的中断处理,有数据时执行中断函数fsl_lpspi_isr。

	ret = devm_request_irq(&pdev->dev, irq, fsl_lpspi_isr, 0,
			       dev_name(&pdev->dev), fsl_lpspi);

        获取时钟并使能时钟,获取tx/rxfifosize并设置。

static int fsl_lpspi_probe(struct platform_device *pdev)
{
	struct fsl_lpspi_data *fsl_lpspi;
	struct spi_controller *controller;
	struct resource *res;
	int ret, irq;
	u32 temp;

	if (of_property_read_bool((&pdev->dev)->of_node, "spi-slave"))
		controller = spi_alloc_slave(&pdev->dev,
					sizeof(struct fsl_lpspi_data));
	else
		controller = spi_alloc_master(&pdev->dev,
					sizeof(struct fsl_lpspi_data));

	if (!controller)
		return -ENOMEM;

	platform_set_drvdata(pdev, controller);

	controller->bits_per_word_mask = SPI_BPW_RANGE_MASK(8, 32);
	controller->bus_num = pdev->id;

	fsl_lpspi = spi_controller_get_devdata(controller);
	fsl_lpspi->dev = &pdev->dev;
	dev_set_drvdata(&pdev->dev, fsl_lpspi);
	fsl_lpspi->is_slave = of_property_read_bool((&pdev->dev)->of_node,
						    "spi-slave");

	controller->transfer_one_message = fsl_lpspi_transfer_one_msg;
	controller->prepare_transfer_hardware = lpspi_prepare_xfer_hardware;
	controller->unprepare_transfer_hardware = lpspi_unprepare_xfer_hardware;
	controller->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
	controller->flags = SPI_MASTER_MUST_RX | SPI_MASTER_MUST_TX;
	controller->dev.of_node = pdev->dev.of_node;
	controller->bus_num = pdev->id;
	controller->slave_abort = fsl_lpspi_slave_abort;

	init_completion(&fsl_lpspi->xfer_done);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	fsl_lpspi->base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(fsl_lpspi->base)) {
		ret = PTR_ERR(fsl_lpspi->base);
		goto out_controller_put;
	}

	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		ret = irq;
		goto out_controller_put;
	}

	ret = devm_request_irq(&pdev->dev, irq, fsl_lpspi_isr, 0,
			       dev_name(&pdev->dev), fsl_lpspi);
	if (ret) {
		dev_err(&pdev->dev, "can't get irq%d: %d\n", irq, ret);
		goto out_controller_put;
	}

	fsl_lpspi->clk_per = devm_clk_get(&pdev->dev, "per");
	if (IS_ERR(fsl_lpspi->clk_per)) {
		ret = PTR_ERR(fsl_lpspi->clk_per);
		goto out_controller_put;
	}

	fsl_lpspi->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
	if (IS_ERR(fsl_lpspi->clk_ipg)) {
		ret = PTR_ERR(fsl_lpspi->clk_ipg);
		goto out_controller_put;
	}

	/* enable the clock */
	ret = fsl_lpspi_init_rpm(fsl_lpspi);
	if (ret)
		goto out_controller_put;

	ret = pm_runtime_get_sync(fsl_lpspi->dev);
	if (ret < 0) {
		dev_err(fsl_lpspi->dev, "failed to enable clock\n");
		return ret;
	}

	temp = readl(fsl_lpspi->base + IMX7ULP_PARAM);
	fsl_lpspi->txfifosize = 1 << (temp & 0x0f);
	fsl_lpspi->rxfifosize = 1 << ((temp >> 8) & 0x0f);

	ret = devm_spi_register_controller(&pdev->dev, controller);
	if (ret < 0) {
		dev_err(&pdev->dev, "spi_register_controller error.\n");
		goto out_controller_put;
	}

	return 0;

out_controller_put:
	spi_controller_put(controller);

	return ret;
}

        根据IMX7ULP_IER寄存器的Transmit Data Interrupt Enable 传输数据中断使能标志位,和IMX7ULP_SR寄存器的Transmit Data Flag ,来判断是否需要发送数据。

static irqreturn_t fsl_lpspi_isr(int irq, void *dev_id)
{
	u32 temp_SR, temp_IER;
	struct fsl_lpspi_data *fsl_lpspi = dev_id;

	temp_IER = readl(fsl_lpspi->base + IMX7ULP_IER);
	fsl_lpspi_intctrl(fsl_lpspi, 0);
	temp_SR = readl(fsl_lpspi->base + IMX7ULP_SR);

	fsl_lpspi_read_rx_fifo(fsl_lpspi);

	if ((temp_SR & SR_TDF) && (temp_IER & IER_TDIE)) {
		fsl_lpspi_write_tx_fifo(fsl_lpspi);
		return IRQ_HANDLED;
	}

	if (temp_SR & SR_FCF && (temp_IER & IER_FCIE)) {
		writel(SR_FCF, fsl_lpspi->base + IMX7ULP_SR);
		complete(&fsl_lpspi->xfer_done);
		return IRQ_HANDLED;
	}

	return IRQ_NONE;
}

        中断处理函数中,判断IMX7ULP_RSR寄存器的bit[1] RX FIFO is empty,不为空就一直读取数据。

static void fsl_lpspi_read_rx_fifo(struct fsl_lpspi_data *fsl_lpspi)
{
	while (!(readl(fsl_lpspi->base + IMX7ULP_RSR) & RSR_RXEMPTY))
		fsl_lpspi->rx(fsl_lpspi);
}

        fsl_lpspi_transfer_one_msg函数中的fsl_lpspi_setup_transfer里面有设置fsl_lpspi->rx和fsl_lpspi->tx回调函数。在进行全双工收发数据时,通过使用msg加入到controller的queue中的方式,最终调用内核线程函数spi_pump_messages,最终调用到fsl_lpspi_transfer_one里面,这里最后调用fsl_lpspi->tx进行数据发送,接收数据其实是用的中断回调函数,不过fsl_lpspi->rx在数据收发时已经设置了,所以总结来说收发的时序不确定的。

static void fsl_lpspi_setup_transfer(struct spi_device *spi,
				     struct spi_transfer *t)
{
	struct fsl_lpspi_data *fsl_lpspi =
				spi_controller_get_devdata(spi->controller);

	fsl_lpspi->config.mode = spi->mode;
	fsl_lpspi->config.bpw = t ? t->bits_per_word : spi->bits_per_word;
	fsl_lpspi->config.speed_hz = t ? t->speed_hz : spi->max_speed_hz;
	fsl_lpspi->config.chip_select = spi->chip_select;

	if (!fsl_lpspi->config.speed_hz)
		fsl_lpspi->config.speed_hz = spi->max_speed_hz;
	if (!fsl_lpspi->config.bpw)
		fsl_lpspi->config.bpw = spi->bits_per_word;

	/* Initialize the functions for transfer */
	if (fsl_lpspi->config.bpw <= 8) {
		fsl_lpspi->rx = fsl_lpspi_buf_rx_u8;
		fsl_lpspi->tx = fsl_lpspi_buf_tx_u8;
	} else if (fsl_lpspi->config.bpw <= 16) {
		fsl_lpspi->rx = fsl_lpspi_buf_rx_u16;
		fsl_lpspi->tx = fsl_lpspi_buf_tx_u16;
	} else {
		fsl_lpspi->rx = fsl_lpspi_buf_rx_u32;
		fsl_lpspi->tx = fsl_lpspi_buf_tx_u32;
	}

        devm_spi_register_controller函数进入到spi.c文件中注册spi。参考链接:内核 kthread_worker 和 kthread_work 机制_爱洋葱的博客-CSDN博客_kthread_queue_work

         注册内核线程worker,初始化一个内核work并设置执行函数spi_pump_messages,最后把work挂到worker上面并设置执行函数。__spi_pump_messages会把queue队列里面的msg取出来进行数据发送。

devm_spi_register_controller(struct device *dev, struct spi_controller *ctlr)
	spi_register_controller(ctlr);
		spi_controller_initialize_queue(ctlr)
			ctlr->transfer = spi_queued_transfer;
			spi_init_queue(ctlr)
				1、注册kthread_worker
				kthread_init_worker(&ctlr->kworker);
				2、为kthread_worker创建一个内核线程来处理 work
				ctlr->kworker_task = kthread_run(kthread_worker_fn, &ctlr->kworker, "%s", dev_name(&ctlr->dev));
				3、初始化kthread_work,设置work执行函数spi_pump_messages
				kthread_init_work(&ctlr->pump_messages, spi_pump_messages);
					spi_pump_messages(struct kthread_work *work)
						__spi_pump_messages(ctlr, true);
							ctlr->transfer_one_message(ctlr, ctlr->cur_msg)				
			spi_start_queue(ctlr)
				4、把work挂到worker上
				kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages);

        把spi_controller的链表挂在全局的spi_controller_list链表上面,

static LIST_HEAD(board_list);
static LIST_HEAD(spi_controller_list);

	list_add_tail(&ctlr->list, &spi_controller_list);

        搜索board_list链表,查看是否有相同的bus_num号,然后spi_new_device添加新的设备;但是没有哪里在添加boardinfo这种老版本的文件设备描述方式,所以这里应该不会执行。

	list_for_each_entry(bi, &board_list, list)
		spi_match_controller_to_boardinfo(ctlr, &bi->board_info);

        of_register_spi_devices函数中会根据dts中的node节点信息,查看是否需要添加设备,里面的实现基本与spi_new_device一致。

static void of_register_spi_devices(struct spi_controller *ctlr)
{
	struct spi_device *spi;
	struct device_node *nc;

	if (!ctlr->dev.of_node)
		return;

	for_each_available_child_of_node(ctlr->dev.of_node, nc) {
		if (of_node_test_and_set_flag(nc, OF_POPULATED))
			continue;
		spi = of_register_spi_device(ctlr, nc);
		if (IS_ERR(spi)) {
			dev_warn(&ctlr->dev,
				 "Failed to create SPI device for %pOF\n", nc);
			of_node_clear_flag(nc, OF_POPULATED);
		}
	}
}

        总结:注册核心驱动,主要目的是搭建起controller收发数据的硬件通道,发送数据使用了FIFO queue队列与内核线程的方式,接收数据使用中断处理函数的方式。

spi核心层

        drivers/spi/spi.c

        postcore_initcall(spi_init);在module_init和module_platform_driver之前执行,bus_register

 和class_register分别注册。

struct bus_type spi_bus_type = {
	.name		= "spi",
	.dev_groups	= spi_dev_groups,
	.match		= spi_match_device,
	.uevent		= spi_uevent,
};
EXPORT_SYMBOL_GPL(spi_bus_type);

static struct class spi_master_class = {
	.name		= "spi_master",
	.owner		= THIS_MODULE,
	.dev_release	= spi_controller_release,
	.dev_groups	= spi_master_groups,
};

static struct class spi_slave_class = {
	.name		= "spi_slave",
	.owner		= THIS_MODULE,
	.dev_release	= spi_controller_release,
	.dev_groups	= spi_slave_groups,
};
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);
	if (status < 0)
		goto err1;

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

	if (IS_ENABLED(CONFIG_SPI_SLAVE)) {
		status = class_register(&spi_slave_class);
		if (status < 0)
			goto err3;
	}

	if (IS_ENABLED(CONFIG_OF_DYNAMIC))
		WARN_ON(of_reconfig_notifier_register(&spi_of_notifier));
	if (IS_ENABLED(CONFIG_ACPI))
		WARN_ON(acpi_reconfig_notifier_register(&spi_acpi_notifier));

	return 0;

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

        spi设备与驱动匹配方式,通过.match        = spi_match_device函数进行匹配,使用驱动中的.of_match_table = fsl_lpspi_dt_ids, 里面定义了compatible,{ .compatible = "fsl,imx7ulp-spi", },

        使用设备树中的compatible与驱动名进行匹配。

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);

	/* Attempt an OF style match */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI */
	if (acpi_driver_match_device(dev, drv))
		return 1;

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

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

of_driver_match_device
    of_match_device(drv->of_match_table, dev)
        of_match_node(const struct of_device_id *matches, dev->of_node);
            __of_match_node

static
const struct of_device_id *__of_match_node(const struct of_device_id *matches,
					   const struct device_node *node)
{
	const struct of_device_id *best_match = NULL;
	int score, best_score = 0;

	if (!matches)
		return NULL;

	for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
		score = __of_device_is_compatible(node, matches->compatible,
						  matches->type, matches->name);
		if (score > best_score) {
			best_match = matches;
			best_score = score;
		}
	}

	return best_match;
}

        name和type都为NULL,只有.compatible = "fsl,imx7ulp-spi",

static int __of_device_is_compatible(const struct device_node *device,
				     const char *compat, const char *type, const char *name)
{
	struct property *prop;
	const char *cp;
	int index = 0, score = 0;

	/* Compatible match has highest priority */
	if (compat && compat[0]) {
		prop = __of_find_property(device, "compatible", NULL);
		for (cp = of_prop_next_string(prop, NULL); cp;
		     cp = of_prop_next_string(prop, cp), index++) {
			if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
				score = INT_MAX/2 - (index << 2);
				break;
			}
		}
		if (!score)
			return 0;
	}

        系统中查看注册的设备信息。

root@OpenWrt:/# ls /sys/bus/spi/ -l
drwxr-xr-x    2 root     root             0 Jan  1 00:00 devices
drwxr-xr-x   10 root     root             0 Jan  1 00:08 drivers
-rw-r--r--    1 root     root          4096 Jan  1 00:08 drivers_autoprobe
--w-------    1 root     root          4096 Jan  1 00:08 drivers_probe
--w-------    1 root     root          4096 Jan  1 00:08 uevent
root@OpenWrt:/# ls /sys/class/spidev/ -l
lrwxrwxrwx    1 root     root             0 Jan  1 00:00 spidev2.0 -> ../../devices/platform/5a020000.lpspi/spi_master/spi2/spi2.0/spidev/spidev2.0
root@OpenWrt:/# ls /sys/class/spi_master/ -l
lrwxrwxrwx    1 root     root             0 Jan  1 00:00 spi2 -> ../../devices/platform/5a020000.lpspi/spi_master/spi2
lrwxrwxrwx    1 root     root             0 Jan  1 00:00 spi3 -> ../../devices/platform/5a030000.lpspi/spi_master/spi3
root@OpenWrt:/# ls /sys/class/spi_slave/ -l

        总结: 匹配设备与驱动执行prob探测函数,提供应用程序与内核之间的数据传递函数调用。

spi收发数据流程

ioctl

        spidev_fops提供给上层文件系统调用,主要查看open,ioctl,read,write。

static const struct file_operations spidev_fops = {
	.owner =	THIS_MODULE,
	/* REVISIT switch to aio primitives, so that userspace
	 * gets more complete API coverage.  It'll simplify things
	 * too, except for the locking.
	 */
	.write =	spidev_write,
	.read =		spidev_read,
	.unlocked_ioctl = spidev_ioctl,
	.compat_ioctl = spidev_compat_ioctl,
	.open =		spidev_open,
	.release =	spidev_release,
	.llseek =	no_llseek,
};

        read和write只支持半双工,ioctl支持全双工,应用程序收发数据一般使用ioctl系统调用,使用SPI_IOC_MESSAGE命令,下面应用程序进行spi环回测试代码。

static const char *device = "/dev/spidev2.0";
static uint8_t mode;
static uint8_t bits = 8;
static uint32_t speed = 500000;
static uint16_t delay;
 
static void transfer(int fd)
{
	int ret;
	uint8_t tx[] = {
		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
		0x40, 0x00, 0x00, 0x00, 0x00, 0x95,
		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
		0xDE, 0xAD, 0xBE, 0xEF, 0xBA, 0xAD,
		0xF0, 0x0D,
	};
	uint8_t rx[ARRAY_SIZE(tx)] = {0, };
	struct spi_ioc_transfer tr = {
		.tx_buf = (unsigned long)tx,
		.rx_buf = (unsigned long)rx,
		.len = ARRAY_SIZE(tx),
		.delay_usecs = delay,
		.speed_hz = speed,
		.bits_per_word = bits,
	};
 
	ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
	if (ret < 1)
		pabort("can't send spi message");
 
	for (ret = 0; ret < ARRAY_SIZE(tx); ret++) {
		if (!(ret % 6))
			puts("");
		printf("%.2X ", rx[ret]);
	}
	puts("");
}

        spidev_get_ioc_message函数检查参数,memdup_user(u_ioc, tmp)分配内存拷贝用户程序的spi_ioc_transfer结构体数据到ioc。

        spidev_message函数进行数据处理和收发。

static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	unsigned		n_ioc;
	struct spi_ioc_transfer	*ioc;

	default:
		/* segmented and/or full-duplex I/O request */
		/* Check message and copy into scratch area */
		ioc = spidev_get_ioc_message(cmd,
				(struct spi_ioc_transfer __user *)arg, &n_ioc);
		if (IS_ERR(ioc)) {
			retval = PTR_ERR(ioc);
			break;
		}
		if (!ioc)
			break;	/* n_ioc is also 0 */

		/* translate to spi_message, execute */
		retval = spidev_message(spidev, ioc, n_ioc);
		kfree(ioc);
		break;

        申请spi_transfer内存,spidev->tx_buffer和spidev->rx_buffer在spidev_open函数中申请内存,接收数据使用k_tmp->rx_buf = rx_buf = spidev->rx_buffer,收到数据拷贝时rx_buf = spidev->rx_buffer;,在函数最后使用copy_to_user拷贝rx_buf到u_tmp->rx_buf用户程序的buf中。

        发送数据k_tmp->tx_buf = tx_buf = spidev->tx_buffer,使用copy_from_user函数把用户数据拷贝到tx_buf中,最后把spi_ioc_transfer信息赋值给spi_transfer,然后spi_message_add_tail(k_tmp, &msg);把spi_transfer挂在spi_message的transfers链表中。       

        最后使用spidev_sync(spidev, &msg)把msg加入到spi_controller的queue队列中。

static int spidev_message(struct spidev_data *spidev,
		struct spi_ioc_transfer *u_xfers, unsigned n_xfers)
{
	struct spi_message	msg;
	struct spi_transfer	*k_xfers;
	struct spi_transfer	*k_tmp;
	struct spi_ioc_transfer *u_tmp;
	unsigned		n, total, tx_total, rx_total;
	u8			*tx_buf, *rx_buf;
	int			status = -EFAULT;

	spi_message_init(&msg);
	k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL);
	if (k_xfers == NULL)
		return -ENOMEM;

	/* Construct spi_message, copying any tx data to bounce buffer.
	 * We walk the array of user-provided transfers, using each one
	 * to initialize a kernel version of the same transfer.
	 */
	tx_buf = spidev->tx_buffer;
	rx_buf = spidev->rx_buffer;
	total = 0;
	tx_total = 0;
	rx_total = 0;
	for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
			n;
			n--, k_tmp++, u_tmp++) {
		k_tmp->len = u_tmp->len;

		total += k_tmp->len;
		/* Since the function returns the total length of transfers
		 * on success, restrict the total to positive int values to
		 * avoid the return value looking like an error.  Also check
		 * each transfer length to avoid arithmetic overflow.
		 */
		if (total > INT_MAX || k_tmp->len > INT_MAX) {
			status = -EMSGSIZE;
			goto done;
		}

		if (u_tmp->rx_buf) {
			/* this transfer needs space in RX bounce buffer */
			rx_total += k_tmp->len;
			if (rx_total > bufsiz) {
				status = -EMSGSIZE;
				goto done;
			}
			k_tmp->rx_buf = rx_buf;
			rx_buf += k_tmp->len;
		}
		if (u_tmp->tx_buf) {
			/* this transfer needs space in TX bounce buffer */
			tx_total += k_tmp->len;
			if (tx_total > bufsiz) {
				status = -EMSGSIZE;
				goto done;
			}
			k_tmp->tx_buf = tx_buf;
			if (copy_from_user(tx_buf, (const u8 __user *)
						(uintptr_t) u_tmp->tx_buf,
					u_tmp->len))
				goto done;
			tx_buf += k_tmp->len;
		}

		k_tmp->cs_change = !!u_tmp->cs_change;
		k_tmp->tx_nbits = u_tmp->tx_nbits;
		k_tmp->rx_nbits = u_tmp->rx_nbits;
		k_tmp->bits_per_word = u_tmp->bits_per_word;
		k_tmp->delay_usecs = u_tmp->delay_usecs;
		k_tmp->speed_hz = u_tmp->speed_hz;
		if (!k_tmp->speed_hz)
			k_tmp->speed_hz = spidev->speed_hz;
#ifdef VERBOSE
		dev_dbg(&spidev->spi->dev,
			"  xfer len %u %s%s%s%dbits %u usec %uHz\n",
			u_tmp->len,
			u_tmp->rx_buf ? "rx " : "",
			u_tmp->tx_buf ? "tx " : "",
			u_tmp->cs_change ? "cs " : "",
			u_tmp->bits_per_word ? : spidev->spi->bits_per_word,
			u_tmp->delay_usecs,
			u_tmp->speed_hz ? : spidev->spi->max_speed_hz);
#endif
		spi_message_add_tail(k_tmp, &msg);
	}

	status = spidev_sync(spidev, &msg);
	if (status < 0)
		goto done;

	/* copy any rx data out of bounce buffer */
	rx_buf = spidev->rx_buffer;
	for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) {
		if (u_tmp->rx_buf) {
			if (copy_to_user((u8 __user *)
					(uintptr_t) u_tmp->rx_buf, rx_buf,
					u_tmp->len)) {
				status = -EFAULT;
				goto done;
			}
			rx_buf += u_tmp->len;
		}
	}
	status = total;

done:
	kfree(k_xfers);
	return status;
}

        函数spidev_sync调用流程:

spidev_sync(spidev, &msg)
    spi = spidev->spi;
    spi_sync(spi, message)
        __spi_sync(spi, message)
            __spi_queued_transfer(spi, message, false)
                struct spi_controller *ctlr = spi->controller;
                list_add_tail(&msg->queue, &ctlr->queue);

write

        write函数,拷贝用户层数据spidev->tx_buffer,tx_buffer在open函数中申请内存4096字节,使用sync同步传输,

/* Write-only message with current device setup */
static ssize_t
spidev_write(struct file *filp, const char __user *buf,
		size_t count, loff_t *f_pos)
{
	struct spidev_data	*spidev;
	ssize_t			status = 0;
	unsigned long		missing;

	/* chipselect only toggles at start or end of operation */
	if (count > bufsiz)
		return -EMSGSIZE;

	spidev = filp->private_data;

	mutex_lock(&spidev->buf_lock);
	missing = copy_from_user(spidev->tx_buffer, buf, count);
	if (missing == 0)
		status = spidev_sync_write(spidev, count);
	else
		status = -EFAULT;
	mutex_unlock(&spidev->buf_lock);

	return status;
}

        最终加入到spi->controller->queue队列里面,然后被内核线程函数取出数据发送。

static inline ssize_t
spidev_sync_write(struct spidev_data *spidev, size_t len)
{
	struct spi_transfer	t = {
			.tx_buf		= spidev->tx_buffer,
			.len		= len,
			.speed_hz	= spidev->speed_hz,
		};
	struct spi_message	m;

	spi_message_init(&m);
	spi_message_add_tail(&t, &m);
	return spidev_sync(spidev, &m);
}

参考文章链接:

Linux SPI 驱动分析(1)— 结构框架_爱洋葱的博客-CSDN博客_linux spi驱动框架

Linux SPI 驱动分析(2)— 框架层源码分析_爱洋葱的博客-CSDN博客_spi_message_init

猜你喜欢

转载自blog.csdn.net/TSZ0000/article/details/124478693
今日推荐