1 SPI協議
SPI全稱為serial peripheral interface串行外圍接口協議,一般為四線,也可以省略為三線或兩線;
支持全雙工,在主設備發送數據的時候同時從從設備接收數據;此時的從設備接收到主設備的時鍾信號和數據的第一位,將准備好的數據發送給主設備;
支持半雙工,要么發送數據,要么接收數據;操作設備的read,wirte函數屬於半雙工通信;
SPI協議大部分用在EEPROM和FLASH存儲器上,通過單片機與其通信;不巧本文的SPI是GPIO復用的SPI;
SPI協議發送數據的時候通常選擇高位在前先發送;通信總是由主設備發起;
部分相關在STM32:SPI相關的筆記中補充,這里不重復了;
2 SPI驅動
概括來說,就是先用spi_board_info的device信息向內核框架注冊一個driver東東,
這個driver東東對於內核而言被稱之為從設備,由內核管理,感覺和SPI通信過程中的主從設備有點差別的;
然后在匹配好的.probe函數中將driver綁定到具體的spi設備(/dev/SPI0),並存儲下匹配上的硬件SPI的地址;
(問題:比如設備樹,通過dts管理,由廠家寫好配置,只要匹配上,配置后就由dts自己映射到寄存器地址了;
這里是怎么匹配上硬件SPI的地址有些不理解,對於misc框架不熟悉;資料都不知道怎么找;)
(補充:匹配上的mt_spi1_t是具體硬件地址映射的結構體,較為直觀,可以理解,
難道是因為這個芯片只有一個SPI外設,所以前面的板級信息spi_board_info就傳給了這個SPI,於是就對應上了;)
然后使用spi_master東東來處理傳輸數據到硬件相關的結構體中;
這些東東分別處理着自己的任務,然后通過接口函數將處理完的數據進行交換;在分析的時候應該考慮成多個獨立的結構;
2.1 首先需要編寫module_init();和module_exit();在module_init()中向board注冊board的device和driver的信息;
2.1.1 driver的配置信息為spi_driver 結構體,主要是用來匹配的.name,以及匹配成功執行的.probe和.remove;
2.1.2 device的配置信息為spi_board_info結構體,主要是用來匹配的.modalias,以及spi的配置信息;
提供的spi的配置信息可以讓內核單獨運行spi,是配置boadr info需要的信息;
問題:spi_board_info和spi_device的功能有些分不清,spi_device是硬件層的映射,其中成員spi_master就是所在總線;
至於spi_board_info應該在某些地方,最終把值傳入spi_device,或者在這里就自動傳入了;
2.2 接着是.probe和.remove函數
內核開始運行時,自動加載模塊執行module_init(),然后查看注冊的device和driver是否匹配,匹配的話則將其綁定,然后執行.porbe的函數;
2.2.1 .probe的函數spi_probe;
1)主要是使用misc_register函數注冊了一個misc設備,注冊的設備可以在/dev下查看;misc設備的.fops操作函數之后補充;
2)然后分配一個內核緩存buff,之后收發數據的時候會用到;
3)然后把匹配成功的(struct spi_device * spi)的地址放入了spi_dev變量中;
問題:這里的 (struct spi_device * spi)的spi是.probe的參數,應該是設備的device部分,此device目前應該只包含了前面spi_board_info的信息;
問題:這里注冊的misc設備,等會又是如何映射到硬件的device和驅動的driver的呢?
2.2.2 .remove的函數spi_remove;
1)使用misc_deregister函數注銷了前面注冊的misc設備,釋放前面分配的內核緩存;
2)這里傳入的參數也是struct spi_device * spi,但是沒有用上;.remove應該是關機的時候調用的;
2.3 接下來是misc設備的.fops函數的編寫了;
2.3.1 .open的函數mt_spi_open;
1)將匹配成功的spi_device的地址內的spi_master地址取出,然后將spi_master內專門給driver使用的數據地址傳給mt1_spi_t結構體;
2)然后初始化了兩個鎖,還有mt1_spi_t結構體的一些參數;
2.3.2 .write的函數mt_spi_write;
初始化spi_transfer和spi_message,然后將其綁定,然后調用spidev_sync異步傳輸,使用的是前面匹配上的&spidev;
write函數都是半雙工通信,配置spi_transfer的時候,需要把發送數據放入.tx中;然后.rx為NULL不用配置,表示半雙工通信;
2.3.3 .read的函數mt_spi_read;
同上的write函數的代碼框架一樣;
2.3.4 .unlock_ioctl的函數mt_spi_ioctl;
2.3.5 .close的函數mt_spi_close;
什么也沒執行;值返回了個0;
3 設備樹:用樹形結構描述硬件設備信息;方便內核管理和解析硬件設備
之前內核中描述硬件信息的代碼主要是通過arch下arm文件夾內C文件,來直接配置各種開發板的硬件信息,就和單片機開發一樣;
使得linux決定將描述板間硬件信息的內容從linux內核中分離出來,用稱之為硬件樹的專屬文件格式.dts來描述;
這些硬件信息包括開發板上的IIC設備,SPI設備等。另外包括SOC級硬件信息如CPU個數、頻率、外設控制器等信息。
用.dts文件將設備描述信息從Linux內核中分離出來,.dts文件位於arch/arm/boot/dts文件夾下;一個平台或機器對應一個.dts文件;
.dts文件需要使用dtc工具編譯成.dtb文件才可以使用;
查找了一下arch/arm64/boot/dts下的mt6735.dtsi文件,可以得到spi設備的信息如下:
4 ioctl操作函數
將不容易通過框架實現的字符設備的操作函數放在ioctl函數中,然后通過應用層傳入的cmd,在ioctl里去直接控制寄存器,
在有些時候像這樣不使用linux的框架,有時候比較便利。
使用ioctl來注冊一個misc類型的spi設備,雖然這樣又麻煩,又不方便,但是這樣就不用了解框架了;
5 代碼部分
此代碼目前只測試了發送數據是可行的,接收數據會停在wait_for_completion_done(&done)函數部分;
該函數根據代碼是在等待接收完成,然后自動調用complete(arg)來喚醒釋放,那么接收完成對於內核而言接收完成的標志又是什么呢?
個人覺得,接收完成的標志是不是和底層寄存器相關,由於沒有外設,於是一直沒有被設置;導致程序一直卡在這里;
后面證實和底層寄存器沒有關系,主要是接收數據也需要先發送數據,所以加了發送buf之后就可以了;
#include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> #include <linux/ioctl.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/miscdevice.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/spi/spi.h> #include <linux/spi/spidev.h> #include <linux/wakelock.h> #include <mach/mt_spi.h> #include <mach/mt_gpio.h> #include <asm/uaccess.h> #define SPI_MODE_MASK (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH \ | SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP \ | SPI_NO_CS | SPI_READY) static struct mutex buf_lock; static spinlock_t spi_lock; static unsigned char * TxRx_buf = NULL; static unsigned int TxRx_bufsize = 256; static struct spi_device * spi_dev; #define TAG "kernel_spi3" //spi_message是將一系列協議操作集中在一起作為原子操作的; //使用完transfer_one_message()之后,必須調用spi_finalize_current_message()初始化message給下次調用; static int spidev_message(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; unsigned char *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. */ buf = TxRx_buf; 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 > TxRx_buf) { status = -EMSGSIZE; goto done; } if (u_tmp->rx_buf) { k_tmp->rx_buf = buf; if (!access_ok(VERIFY_WRITE, (unsigned char __user *)(size_t)u_tmp->rx_buf, u_tmp->len)) goto done; } if (u_tmp->tx_buf) { k_tmp->tx_buf = buf; if (copy_from_user(buf, (const unsigned char __user *)(size_t)u_tmp->tx_buf,u_tmp->len)) goto done; } buf += k_tmp->len; k_tmp->cs_change = !!u_tmp->cs_change; 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; spi_message_add_tail(k_tmp, &msg); } status = spidev_sync(&msg); if (status < 0) goto done; /* copy any rx data out of bounce buffer */ buf = TxRx_buf; for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) { if (u_tmp->rx_buf) { if (__copy_to_user((unsigned char __user *)(size_t)u_tmp->rx_buf, buf, u_tmp->len)) { status = -EFAULT; goto done; } } buf += u_tmp->len; } status = total; done: kfree(k_xfers); return status; } //主要是通過spi_setup()來重新配置spi的模式, static long mt_spi_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int err = 0; int retval = 0; struct spi_ioc_transfer *ioc; struct spi_device *spi = spi_dev; unsigned int n_ioc, tmp; /* Check type and command number */ if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC) return -ENOTTY; if (_IOC_DIR(cmd) & _IOC_READ) err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); if (err) return -EFAULT; if (spi_dev == NULL) return -ESHUTDOWN; mutex_lock(&buf_lock); switch (cmd) { /* read requests */ case SPI_IOC_RD_MODE: retval = __put_user(spi->mode & SPI_MODE_MASK, (unsigned char __user *)arg); printk("%s SPI_IOC_RD_MODE retval:%#x\n",TAG,retval); break; case SPI_IOC_RD_LSB_FIRST: retval = __put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0, (unsigned char __user *)arg); printk("%s SPI_IOC_RD_LSB_FIRST retval:%#x\n",TAG,retval); break; case SPI_IOC_RD_BITS_PER_WORD: retval = __put_user(spi->bits_per_word, (unsigned char __user *)arg); printk("%s SPI_IOC_RD_BITS_PER_WORD retval:%#x\n",TAG,retval); break; case SPI_IOC_RD_MAX_SPEED_HZ: retval = __put_user(spi->max_speed_hz, (unsigned int __user *)arg); printk("%s SPI_IOC_RD_MAX_SPEED_HZ retval:%#x\n",TAG,retval); break; /* write requests */ case SPI_IOC_WR_MODE: retval = __get_user(tmp, (unsigned char __user *)arg); if (retval == 0) { unsigned char save = spi->mode; if (tmp & ~SPI_MODE_MASK) { retval = -EINVAL; break; } tmp |= spi->mode & ~SPI_MODE_MASK; spi->mode = (unsigned char)tmp; retval = spi_setup(spi); if (retval < 0) spi->mode = save; else dev_dbg(&spi->dev, "spi mode %02x\n", tmp); } break; case SPI_IOC_WR_LSB_FIRST: retval = __get_user(tmp, (unsigned char __user *)arg); if (retval == 0) { unsigned char save = spi->mode; if (tmp) spi->mode |= SPI_LSB_FIRST; else spi->mode &= ~SPI_LSB_FIRST; retval = spi_setup(spi); if (retval < 0) spi->mode = save; else dev_dbg(&spi->dev, "%csb first\n", tmp ? 'l' : 'm'); } break; case SPI_IOC_WR_BITS_PER_WORD: retval = __get_user(tmp, (unsigned char __user *)arg); if (retval == 0) { unsigned char save = spi->bits_per_word; spi->bits_per_word = tmp; retval = spi_setup(spi); if (retval < 0) spi->bits_per_word = save; else dev_dbg(&spi->dev, "%d bits per word\n", tmp); } break; case SPI_IOC_WR_MAX_SPEED_HZ: retval = __get_user(tmp, (unsigned int __user *)arg); if (retval == 0) { unsigned int save = spi->max_speed_hz; spi->max_speed_hz = tmp; retval = spi_setup(spi); if (retval < 0) spi->max_speed_hz = save; else dev_dbg(&spi->dev, "%d Hz (max)\n", tmp); } break; default: /* segmented and/or full-duplex I/O request */ if (_IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0)) || _IOC_DIR(cmd) != _IOC_WRITE) { retval = -ENOTTY; break; } tmp = _IOC_SIZE(cmd); if ((tmp % sizeof(struct spi_ioc_transfer)) != 0) { retval = -EINVAL; break; } n_ioc = tmp / sizeof(struct spi_ioc_transfer); if (n_ioc == 0) break; /* copy into scratch area */ ioc = kmalloc(tmp, GFP_KERNEL); if (!ioc) { retval = -ENOMEM; break; } if (__copy_from_user(ioc, (void __user *)arg, tmp)) { kfree(ioc); retval = -EFAULT; break; } /* translate to spi_message, execute */ retval = spidev_message(ioc, n_ioc); kfree(ioc); break; } mutex_unlock(&buf_lock); spi_dev_put(spi); printk("%s spi ioctl\n", TAG); return retval; } //spi_async() 來打包的時候需要context和completion一起使用; //對於像spi_sync(),spi_write()來打包發送的時候一般只需要context; static void spidev_complete(void *arg) { complete(arg); } static ssize_t spidev_sync(struct spi_message *message) { DECLARE_COMPLETION_ONSTACK(done); int ret; message->complete = spidev_complete; message->context = &done; if (spi_dev == NULL) return -ESHUTDOWN; spin_lock_irq(&spi_lock);//自旋鎖,要是解不開就一直在這里等的那種; ret = spi_async(spi_dev, message); spin_unlock_irq(&spi_lock); printk("%s spi_async ret:%d\n",TAG,(int)ret); if (ret == 0) { printk("%s wait_for_completion ... \n",TAG); wait_for_completion(&done); printk("%s wait_for_completion done\n",TAG); ret = message->status; message->context = NULL; if (ret == 0) ret = message->actual_length; } printk("%s spidev_sync spin unlock,wait_for_completion_done.\n",TAG); return ret; } static ssize_t mt_spi_read(struct file *file, char *buf, size_t count, loff_t *offset) { ssize_t ret = -EMSGSIZE; if (count > TxRx_buf) goto s_read_exit; //mutex_lock(&buf_lock); struct spi_transfer t = {
.tx_buf = TxRx_buf, //接收不行,主要是因為接收前也需要發送buf來提供時鍾,這里添加了buf之后就可以接收了; .rx_buf = TxRx_buf, .len = count, }; struct spi_message m; spi_message_init(&m); spi_message_add_tail(&t, &m); ret = spidev_sync(&m); if (ret > 0) { unsigned long missing; missing = copy_to_user(buf, TxRx_buf, ret); if (missing == ret) ret = -EFAULT; else ret = ret - missing; } //mutex_unlock(&buf_lock); printk("%s spi sync read done,ret :%d\n", TAG, (int)ret); s_read_exit: printk("%s spi read exit \n", TAG); return ret; } static ssize_t mt_spi_write(struct file *file, const char *buf, size_t count, loff_t *offset) { ssize_t ret = -EMSGSIZE; if (count > TxRx_buf) goto s_write_exit; mutex_lock(&buf_lock); ret = copy_from_user(TxRx_buf, buf, count); if(ret){ printk("%s copy from user space fail \n",TAG); ret = -EFAULT; } mutex_unlock(&buf_lock); //每個spi_transfer都會包含.tx_buf發送緩存和.rx_buf接收緩存; //當兩個buf相同的時候,一般是全雙工;其中一個buf為null時,為半雙工; //可以通過.delay_usecs來定義transfer之后的延時,延時單位是us; struct spi_transfer t = { .tx_buf = TxRx_buf, .len = count, }; struct spi_message m; spi_message_init(&m);//初始化message spi_message_add_tail(&t, &m);//將spi_transfer添加到spi_message上; ret = spidev_sync(&m); //異步傳輸,用的是device的地址; printk("%s spi write done, ret:%d\n",TAG,(int)ret); s_write_exit: printk("%s spi write exit \n", TAG); return ret; } //配置信息2:platform_device包括了寄存器的物理地址等; //如何編寫spi master 呢? //通過spi_alloc_master()來分配一個master, //然后通過spi_master_get_devdata()來獲取該master結構與device相關的所有數據如mt1_spi_t struct mt1_spi_t { struct platform_device *pdev; void __iomem *regs; int irq; int running; struct wake_lock wk_lock; struct mt_chip_conf *config; struct spi_master *master; struct spi_transfer *cur_transfer; struct spi_transfer *next_transfer; spinlock_t lock; //spi_message中也有lock和queue,使用完后要初始化,不然下次用不了transfer_one_message; struct list_head queue; }; //將&master->dev的數據傳遞給mt1_spi_t,然后初始化spi_message; static int mt_spi_open(struct inode *inode,struct file *file) { struct spi_message *msg; struct spi_master *master = spi_dev->master; struct mt1_spi_t *ms = spi_master_get_devdata(master); //將綁定的spi device的master取出,&master->dev的數據傳遞給mt1_spi_t; mutex_init(&buf_lock);//互斥鎖初始化;如果互斥鎖不能加鎖,就阻塞睡眠線程,直到Unlock喚醒線程; spin_lock_init(&spi_lock);//自旋鎖初始化;如果自旋鎖不能加鎖,CPU始終處於忙等待不能做其他任務; list_for_each_entry(msg, &ms->queue, queue) { msg->status = -ESHUTDOWN; msg->complete(msg->context); } ms->cur_transfer = NULL; ms->next_transfer = NULL; ms->running = 0; INIT_LIST_HEAD(&ms->queue); //初始化的是上面的mt1_spi_t,這是綁定的spi_device的一部分;看不太懂; printk("%s spi opened\n", TAG); return 0; } //.release和.remove的區別在哪里呢?這個close感覺沒什么用啊; static int mt_spi_close(struct inode *inode,struct file *file) { printk("%s spi close\n", TAG); return 0; } static struct file_operations spi_fops = { .owner = THIS_MODULE, .open = mt_spi_open, .release = mt_spi_close, .read = mt_spi_read, .write = mt_spi_write, .unlocked_ioctl = mt_spi_ioctl, }; //misc device的設備信息,文件操作 static struct miscdevice misc_dev = { .minor = MISC_DYNAMIC_MINOR, .name = "SPI0", .fops = &spi_fops, }; //注冊 misc device,分配device buff的內存,存儲device的地址到指針中; static int spi_probe(struct spi_device * spi) { int result; result = misc_register(&misc_dev); if(result < 0){ printk("%s misc register fail. \n",TAG); return result; } TxRx_buf = kmalloc(TxRx_bufsize,GFP_KERNEL); if(!TxRx_buf){ printk("%s TxRx_buf null!\n",TAG); return -ENOMEM; } spi_dev = spi;//這里把匹配成功的spi device的地址放入了spi_dev中; printk("%s spi probe done.\n",TAG); return 0; } //注銷misc device,注銷device使用的buffer內存; static int spi_remove(struct spi_device * spi) { int result; result = misc_deregister(&misc_dev); if(result < 0){ printk("%s misc de register fail!\n",TAG); return result; } if(TxRx_buf) kfree(TxRx_buf); TxRx_buf = NULL; return 0; } //配置信息1:(slave devices的配置信息)board info的信息;提供的這些信息需要足夠內核單獨運行spi static struct spi_board_info spi_board_devs[] __initdata = { [0] = { .modalias = "mt_spi3", .max_speed_hz = 25 * 1000 *1000, // 25MHz .bus_num = 0, .chip_select = 0, .mode = SPI_MODE_0, }, }; //spi protocol driver:當spi_driver注冊之后, //會自動查找注冊的device中board_info的.modalias與.name匹配的device,然后綁定; static struct spi_driver mt_spi_driver = { .driver = { .name = "mt_spi3", .owner = THIS_MODULE, }, .probe = spi_probe, .remove = spi_remove, }; static int __init mt_spi_init ( void ) { int ret; //向當前board注冊一個SPI device spi_register_board_info(spi_board_devs, ARRAY_SIZE(spi_board_devs)); //向當前board注冊一個SPI driver ret = spi_register_driver(&mt_spi_driver); printk("%s module spi init ... ret :%#x\n",TAG,ret); return ret; } static void __init mt_spi_exit ( void ) { printk("%s module spi exit \n",TAG); spi_unregister_driver(&mt_spi_driver); } module_init(mt_spi_init); module_exit(mt_spi_exit); MODULE_LICENSE("GPL");