ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
# SPI 驱动编写 这里主要实现SPI的设备驱动。 编写一个spidbg驱动,用来实现对spi总线的debug。(其实就是从spidev精简而来) ## SPIDBG 驱动编写 ### 代码环境 在staging中添加spidbg目录,并修改上级的Makefile和Kconfig 添加Makefile: ~~~ obj-$(CONFIG_SPIDBG) := spidbg.o ccflags-y += -I$(srctree)/$(src)/include ~~~ 添加Kconfig: ~~~ config SPIDBG tristate "SPI DBG driver" ---help--- SPIDBG ~~~ 然后在menuconfig里选中驱动 ### 设备树编写 建立新dts:sun8i-v3s-licheepi-zero-driver.dts ~~~ /dts-v1/; #include "sun8i-v3s-licheepi-zero.dts" /{ chosen { /delete-node/ framebuffer@0; }; }; &spi0 { pinctrl-names = "default"; pinctrl-0 = <&spi0_pins>; status = "okay"; spidbg@0x00 { compatible = "spidbg"; spi-max-frequency = <100000000>; reg = <0>; }; }; ~~~ 插入驱动的时候通过compatible = "spidbg";就能匹配上驱动。 ### 模块入口 基础信息: ~~~ //记录主设备号 int major = 252; //字符驱动操作 static const struct file_operations spidbg_fops = { .owner = THIS_MODULE, .write = spidbg_write, .read = spidbg_read, .unlocked_ioctl = spidbg_ioctl, .open = spidbg_open, .release = spidbg_release, .llseek = no_llseek, }; //匹配的设备树信息 static const struct of_device_id spidbg_dt_ids[] = { { .compatible = "spidbg" }, {}, }; MODULE_DEVICE_TABLE(of, spidbg_dt_ids); //spi驱动信息 static struct spi_driver spidbg_spi_driver = { .driver = { .name = "spidbg", .of_match_table = of_match_ptr(spidbg_dt_ids), }, .probe = spidbg_probe, .remove = spidbg_remove, }; ~~~ 模块出入口 ~~~ //注册SPI设备 static int __init spidbg_driver_module_init(void) { int ret; int major; printk("spidbg_driver_module_init\r\n"); //注册字符驱动 major = register_chrdev(0, "spidbg", &spidbg_fops); if (major < 0) { printk(KERN_ERR "register_chrdev fail\n"); return -EINVAL; } printk("register_chrdev success... spidbg major = %d.\n", major); //注册类 spidbg_class = class_create(THIS_MODULE, "spidbg"); if (IS_ERR(spidbg_class)) { unregister_chrdev(major, spidbg_spi_driver.driver.name); return PTR_ERR(spidbg_class); } //注册驱动 ret = spi_register_driver(&spidbg_spi_driver); if (ret < 0) { class_destroy(spidbg_class); unregister_chrdev(major, spidbg_spi_driver.driver.name); } return ret; } static void __exit spidbg_driver_module_exit(void) { printk("spidbg_driver_module_exit\r\n"); spi_unregister_driver(&spidbg_spi_driver); class_destroy(spidbg_class); unregister_chrdev(major, spidbg_spi_driver.driver.name); } module_init(spidbg_driver_module_init); module_exit(spidbg_driver_module_exit); MODULE_AUTHOR("zepan <zepanwucai@gmail.com>"); MODULE_DESCRIPTION("SPIDBG driver"); MODULE_LICENSE("GPL"); ~~~ ### 驱动插入移除 ~~~ struct spidbg_data { dev_t devt; spinlock_t spi_lock; struct spi_device *spi; /* TX/RX buffers are NULL unless this device is open (users > 0) */ struct mutex buf_lock; unsigned users; u8 *tx_buffer; u8 *rx_buffer; u32 speed_hz; }; #define bufsiz 4096 unsigned char tx_buf[bufsiz]; unsigned char rx_buf[bufsiz]; struct spidbg_data spidbg_dat; struct class *spidbg_class; /* struct spi_device { struct device dev; struct spi_controller *controller; struct spi_controller *master; /* compatibility layer */ u32 max_speed_hz; u8 chip_select; u8 bits_per_word; u16 mode; int irq; void *controller_state; void *controller_data; char modalias[SPI_NAME_SIZE]; int cs_gpio; /* chip select gpio */ struct spi_statistics statistics; }; */ /* #define SPI_CPHA 0x01 /* clock phase */ #define SPI_CPOL 0x02 /* clock polarity */ #define SPI_MODE_0 (0|0) /* (original MicroWire) */ #define SPI_MODE_1 (0|SPI_CPHA) #define SPI_MODE_2 (SPI_CPOL|0) #define SPI_MODE_3 (SPI_CPOL|SPI_CPHA) #define SPI_CS_HIGH 0x04 /* chipselect active high? */ #define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire */ #define SPI_3WIRE 0x10 /* SI/SO signals shared */ #define SPI_LOOP 0x20 /* loopback mode */ #define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */ #define SPI_READY 0x80 /* slave pulls low to pause */ #define SPI_TX_DUAL 0x100 /* transmit with 2 wires */ #define SPI_TX_QUAD 0x200 /* transmit with 4 wires */ #define SPI_RX_DUAL 0x400 /* receive with 2 wires */ #define SPI_RX_QUAD 0x800 /* receive with 4 wires */ */ static int spidbg_probe(struct spi_device *spi) { struct spidbg_data *spidbg; int status; unsigned long minor=0; //申请驱动数据 spidbg = kzalloc(sizeof(*spidbg), GFP_KERNEL); if (!spidbg) return -ENOMEM; //初始化驱动数据 spidbg->spi = spi; spidbg->tx_buffer = tx_buf; spidbg->rx_buffer = rx_buf; spin_lock_init(&spidbg->spi_lock); mutex_init(&spidbg->buf_lock); { struct device *dev; spidbg->devt = MKDEV(major, minor); dev = device_create(spidbg_class, &spi->dev, spidbg->devt, spidbg, "spidbg%d.%d", spi->master->bus_num, spi->chip_select); status = PTR_ERR_OR_ZERO(dev); } spidbg->speed_hz = spi->max_speed_hz; if (status == 0) spi_set_drvdata(spi, spidbg); //将数据寄存于spi设备中 else kfree(spidbg); return status; } static int spidbg_remove(struct spi_device *spi) { struct spidbg_data *spidbg = spi_get_drvdata(spi); /* make sure ops on existing fds can abort cleanly */ spin_lock_irq(&spidbg->spi_lock); spidbg->spi = NULL; spin_unlock_irq(&spidbg->spi_lock); /* prevent new opens */ if (spidbg->users == 0) kfree(spidbg); return 0; } ~~~ ### 字符设备操作之打开,关闭 ~~~ static int spidbg_open(struct inode *inode, struct file *filp) { int status = -ENXIO; struct spidbg_data *spidbg = &spidbg_dat; if(spidbg->devt == inode->i_rdev) //判断设备号是否相符 status = 0; if (status) { pr_debug("spidbg: nothing for minor %d\n", iminor(inode)); goto err_find_dev; } spidbg->users++; filp->private_data = spidbg; //关联数据 nonseekable_open(inode, filp); //不支持seek return 0; err_find_dev: return status; } static int spidbg_release(struct inode *inode, struct file *filp) { struct spidbg_data *spidbg; spidbg = filp->private_data; filp->private_data = NULL; //如果没人在使用了,并且已经remove了,释放资源 spidbg->users--; if (!spidbg->users) { int dofree; spin_lock_irq(&spidbg->spi_lock); dofree = (spidbg->spi == NULL); spin_unlock_irq(&spidbg->spi_lock); if (dofree) kfree(spidbg); } return 0; } ~~~ ### 字符设备操作之读写 首先实现底层操作: ~~~ static ssize_t spidbg_sync(struct spidbg_data *spidbg, struct spi_message *message) { int status; struct spi_device *spi; spin_lock_irq(&spidbg->spi_lock); spi = spidbg->spi; spin_unlock_irq(&spidbg->spi_lock); if (spi == NULL) status = -ESHUTDOWN; else status = spi_sync(spi, message); //spi驱动核心接口 if (status == 0) status = message->actual_length; return status; } static inline ssize_t spidbg_sync_write(struct spidbg_data *spidbg, size_t len) { struct spi_transfer t = { .tx_buf = spidbg->tx_buffer, .len = len, .speed_hz = spidbg->speed_hz, }; struct spi_message m; spi_message_init(&m); spi_message_add_tail(&t, &m); return spidbg_sync(spidbg, &m); } static inline ssize_t spidbg_sync_read(struct spidbg_data *spidbg, size_t len) { struct spi_transfer t = { .rx_buf = spidbg->rx_buffer, .len = len, .speed_hz = spidbg->speed_hz, }; struct spi_message m; spi_message_init(&m); spi_message_add_tail(&t, &m); return spidbg_sync(spidbg, &m); } ~~~ 读写函数: ~~~ /* Read-only message with current device setup */ static ssize_t spidbg_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct spidbg_data *spidbg; ssize_t n = 0; /* chipselect only toggles at start or end of operation */ if (count > bufsiz) return -EMSGSIZE; spidbg = filp->private_data; mutex_lock(&spidbg->buf_lock); n = spidbg_sync_read(spidbg, count); if (n > 0) { unsigned long missing; missing = copy_to_user(buf, spidbg->rx_buffer, n); if (missing == n) n = -EFAULT; else n = n - missing; } mutex_unlock(&spidbg->buf_lock); return n; } /* 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 spidbg_data *spidbg; ssize_t n = 0; unsigned long missing; /* chipselect only toggles at start or end of operation */ if (count > bufsiz) return -EMSGSIZE; spidbg = filp->private_data; mutex_lock(&spidbg->buf_lock); missing = copy_from_user(spidbg->tx_buffer, buf, count); if (missing == 0) n = spidbg_sync_write(spidbg, count); else n = -EFAULT; mutex_unlock(&spidbg->buf_lock); return status; } ~~~ ### IOCTL ~~~ static long spidbg_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int retval = 0; struct spidbg_data *spidbg; struct spi_device *spi; u32 tmp; unsigned n_ioc; struct spi_ioc_transfer *ioc; //检查命令magic是否是spi命令 if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC) return -ENOTTY; /* guard against device removal before, or while, * we issue this ioctl. */ spidbg = filp->private_data; spin_lock_irq(&spidbg->spi_lock); spi = spi_dev_get(spidbg->spi); //获取spi设备 spin_unlock_irq(&spidbg->spi_lock); if (spi == NULL) return -ESHUTDOWN; /* use the buffer lock here for triple duty: * - prevent I/O (from us) so calling spi_setup() is safe; * - prevent concurrent SPI_IOC_WR_* from morphing * data fields while SPI_IOC_RD_* reads them; * - SPI_IOC_MESSAGE needs the buffer locked "normally". */ mutex_lock(&spidbg->buf_lock); switch (cmd) { //IOCTL读请求 case SPI_IOC_RD_MODE: retval = put_user(spi->mode & SPI_MODE_MASK, (__u8 __user *)arg); break; case SPI_IOC_RD_LSB_FIRST: retval = put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0, (__u8 __user *)arg); printk("%csb first\n", (spi->mode & SPI_LSB_FIRST) ? 'l' : 'm'); break; case SPI_IOC_RD_BITS_PER_WORD: retval = put_user(8, (__u8 __user *)arg); //v3s 固定只有8bit模式 break; case SPI_IOC_RD_MAX_SPEED_HZ: retval = put_user(spi->max_speed_hz, (__u32 __user *)arg); break; //IOCTL 写请求 case SPI_IOC_WR_MODE: case SPI_IOC_WR_LSB_FIRST: retval = get_user(tmp, (u8 __user *)arg); if (retval == 0) { u32 save = spi->mode; if(cmd == SPI_IOC_WR_MODE) { tmp = tmp & SPI_MODE_MASK; tmp |= spi->mode & ~SPI_MODE_MASK; } else tmp |= spi->mode & ~SPI_LSB_FIRST; spi->mode = (u16)tmp; retval = spi_setup(spi); if (retval < 0) spi->mode = save; else printk(&spi->dev, "spi mode %x\n", tmp); } break; case SPI_IOC_WR_BITS_PER_WORD: case SPI_IOC_WR_MAX_SPEED_HZ: printk("not implement\n"); break; default: //用户层的一次读写请求 //struct spi_ioc_transfer mesg[4]; //status = ioctl(fd, SPI_IOC_MESSAGE(4), mesg); //下面的ioc是拷贝到内核空间的ioc消息内容 ioc = spidbg_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 */ //翻译成spi_message,执行 */ retval = spidbg_message(spidbg, ioc, n_ioc); kfree(ioc); break; } mutex_unlock(&spidbg->buf_lock); spi_dev_put(spi); //释放spi设备 return retval; } ~~~ ioc message的处理 ~~~ /* struct spi_ioc_transfer { __u64 tx_buf; __u64 rx_buf; __u32 len; __u32 speed_hz; __u16 delay_usecs; __u8 bits_per_word; __u8 cs_change; __u8 tx_nbits; __u8 rx_nbits; __u16 pad; }; */ static struct spi_ioc_transfer * spidbg_get_ioc_message(unsigned int cmd, struct spi_ioc_transfer __user *u_ioc, unsigned *n_ioc) { u32 tmp; /* Check type, command number and direction */ if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC || _IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0)) || _IOC_DIR(cmd) != _IOC_WRITE) return ERR_PTR(-ENOTTY); tmp = _IOC_SIZE(cmd); //最大16KB if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) return ERR_PTR(-EINVAL); *n_ioc = tmp / sizeof(struct spi_ioc_transfer); if (*n_ioc == 0) return NULL; /* copy into scratch area */ return memdup_user(u_ioc, tmp); //这里动态申请了内存 } //执行ioc命令 static int spidbg_message(struct spidbg_data *spidbg, 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; //构建spi_message tx_buf = spidbg->tx_buffer; rx_buf = spidbg->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; 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 = spidbg->speed_hz; spi_message_add_tail(k_tmp, &msg); } status = spidbg_sync(spidbg, &msg); if (status < 0) goto done; /* copy any rx data out of bounce buffer */ rx_buf = spidbg->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; } ~~~ ### 需要的头文件 ~~~ #include <linux/init.h> #include <linux/module.h> #include <linux/ioctl.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/err.h> #include <linux/list.h> #include <linux/errno.h> #include <linux/mutex.h> #include <linux/slab.h> #include <linux/compat.h> #include <linux/of.h> #include <linux/of_device.h> #include <linux/acpi.h> #include <linux/spi/spi.h> #include <linux/spi/spidev.h> #include <linux/uaccess.h> ~~~ ### 单独编译模块 make M=drivers/staging/spidbg modules