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");
原文地址:https://www.cnblogs.com/caesura-k/p/12378408.html