目录
设备平台 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);
}
参考文章链接: