zoukankan      html  css  js  c++  java
  • linux:MTK_SPI模块

     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
    ",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
    ",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
    ",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
    ",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
    ", 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
    ", 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
    ", 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)
    ", 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
    ", 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
    ",TAG,(int)ret);
    
        if (ret == 0) {
            printk("%s wait_for_completion ... 
    ",TAG);
            wait_for_completion(&done);
            printk("%s wait_for_completion done
    ",TAG);
            ret = message->status;
            message->context = NULL;
            if (ret == 0)
                ret = message->actual_length;
        }
    
        printk("%s spidev_sync spin unlock,wait_for_completion_done.
    ",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 ", TAG, (int)ret); s_read_exit: printk("%s spi read exit ", 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 ",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 ",TAG,(int)ret); s_write_exit: printk("%s spi write exit ", 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 ", TAG); return 0; } //.release和.remove的区别在哪里呢?这个close感觉没什么用啊; static int mt_spi_close(struct inode *inode,struct file *file) { printk("%s spi close ", 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. ",TAG); return result; } TxRx_buf = kmalloc(TxRx_bufsize,GFP_KERNEL); if(!TxRx_buf){ printk("%s TxRx_buf null! ",TAG); return -ENOMEM; } spi_dev = spi;//这里把匹配成功的spi device的地址放入了spi_dev中; printk("%s spi probe done. ",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! ",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 ",TAG,ret); return ret; } static void __init mt_spi_exit ( void ) { printk("%s module spi exit ",TAG); spi_unregister_driver(&mt_spi_driver); } module_init(mt_spi_init); module_exit(mt_spi_exit); MODULE_LICENSE("GPL");
  • 相关阅读:
    C语言 · 阶乘计算 · 基础练习
    C语言 · 查找整数 · 基础练习
    UML课程复习重点
    运维参考
    mysql语法总结
    Python杂篇
    Python练习题
    Python参考
    k8s中ipvs和iptables选择
    安装cni网络插件-非必须
  • 原文地址:https://www.cnblogs.com/caesura-k/p/12378408.html
Copyright © 2011-2022 走看看