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\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");

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM