资讯详情

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@        #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        153Jan  1000: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函数几次。

     

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

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_nodestaticconst 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/ -ldrwxr-xr-x    2 root     root             0 Jan  1 00:00 devicesdrwxr-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 ueventroot@OpenWrt:/# ls /sys/class/spidev/ -llrwxrwxrwx    1 root     root             0 Jan  1 00:00 spidev2.0 -> ../../devices/platform/5a020000.lpspi/spi_master/spi2/spi2.0/spidev/spidev2.0root@OpenWrt:/# ls /sys/class/spi_master/ -llrwxrwxrwx    1 root     root             0 Jan  1 00:00 spi2 -> ../../devices/platform/5a020000.lpspi/spi_master/spi2lrwxrwxrwx    1 root     root             0 Jan  1 00:00 spi3 -> ../../devices/platform/5a030000.lpspi/spi_master/spi3root@OpenWrt:/# ls /sys/class/spi_slave/ -l

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 longspidev_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_tspidev_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_tspidev_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

标签: uhz液位变送器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

 锐单商城 - 一站式电子元器件采购平台  

 深圳锐单电子有限公司