Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1145473
  • 博文数量: 146
  • 博客积分: 190
  • 博客等级: 入伍新兵
  • 技术积分: 5225
  • 用 户 组: 普通用户
  • 注册时间: 2012-06-06 08:24
个人简介

慢行者

文章分类

全部博文(146)

文章存档

2013年(145)

2012年(1)

分类: LINUX

2013-04-16 17:27:42

转载至http://blog.csdn.net/armeasy/article/details/6022056
目录

MX25SPI驱动,和大多数平台的SPI驱动都有相似之处。这里有三个非常关键的文件:mxc_spi.cspi.cspidev.c,路径都在kernel/drivers/spi下。其中spi.c为驱动与驱动之间的调用接口,spidev.c为应用与驱动之间的调用接口,mxc_spi.c为驱动的最底层代码,直接操作SPI的相关寄存器。下面分别从驱动间调用SPI驱动和应用层调用SPI驱动两条路分析SPI驱动的实现过程。

1.      驱动中调用SPI驱动

kernel/arch/arm/mach-mx25目录下,有一个mx25_3stack_cpld.c文件,为飞思卡尔开发板上的CPLD驱动。由于没有开发板的debug板的原理图,只能从程序上分析,debug板上有一个Lattice公司的CPLD,如果没有猜错,该CPLD留有SPI接口,可以通过该SPI接口读写CPLD里面的寄存器。altera公司的CPLD是没有寄存器的,具体Lattice公司的有没寄存器,我们暂且不用管它,只需要分析mx25_3stack_cpld.c里面的代码,了解调用SPI驱动的流程。

该程序通过宏device_initcall调用spi_register_driver函数将mx25_3ds_cpld_driver注册为SPI驱动,探测函数mx25_3ds_cpld_probe随即运行,探测函数第一句便将SPI结构体的bits_per_word设置为46,这是SPI的相当关键的一个参数,表示每一次SPI的传输,都传46位。如果我们一次传输只传一个字节,要将46修改为8。这个设置相当的重要,如果设置不当,会引起kernel的崩溃!这个我们后面再分析。

同时,我们还可以在这里添加SPI结构体的其它一些属性。默认这里没有设置SPI的波特率,按照默认设置,SPI的传输速度为18M,如果我们要将波特率设置为4M,我们可以添加如下代码:

spi->bits_per_word = 46;

spi->max_speed_hz = 4000000;//lqm added for change baudrate.

红色部分为添加的代码,后面的程序会通过SPI结构体的参数设置SPI的寄存器。程序紧接着调用spi_setup函数设置SPI结构体的一些参数。这个名字起得很动听,让人觉得这个函数非常的重要,但是事实上,它做的事情相当的少。它先对bits_per_word做了判断,如果为0,则将bits_per_word设置为8,即bits_per_word最少也要为8,然后再调用mxc_spi.c中的mxc_spi_setup函数,这个函数中同样对bits_per_word再做一次判断,为0时设置为8,然后就是判断SPI的波特率是否小于0,若小于0则返回错误。可见整个spi_setup函数做的事情相当的少,如果我们参数设置正确,完全可以不使用该函数。

接下来调用spi_cpld_writespi_cpld_read函数对CPLD进行读写操作,这两个函数都调用了mx25_3ds_cpld_rw函数完成寄存器的读写。这个函数很关键,代码如下:

static inline int mx25_3ds_cpld_rw(u8 *buf, size_t len)

{

         struct spi_transfer t = {

                   .tx_buf = (const void *)buf,   // frame[2]数组的值存放到tx_buf缓冲区

                   .rx_buf = buf,                                           // 这里是通过SPI读取寄存器的值???

                   .len = len,                                                  // len=2

                   .cs_change = 0,                                         // SPICS位设置?

                   .delay_usecs = 0,                             // SPI的延时时间设置?

         };

         if (!cpld_spi)

                   return -1;

        

         mxc_spi_poll_transfer(cpld_spi, &t);//调用SPI传输函数,发送SPI数据

         return 0;

}

函数中定义了一个结构体,t.tx_buf用于保存要写入的数据,t.rx_buf存放SPI读取的寄存器的值,t.len保存一次写入的SPI数据的个数。然后调用mxc_spi_poll_transfer函数开始寄存器的读写操作。其中cpld_spi为传入的SPI结构体,前面设置的bits_per_wordmax_speed_hz都是从这里传入的。t为结构体数据,包含了传入传出的SPI数据。这个函数在mxc_spi.c中,也就是上面我们提到的,充当SPI最底层的寄存器操作的文件。

函数首先通过mxc_spi_chipselect函数设置SPI的波特率,相位,极性等,然后使能SPI的时钟,将传入的结构体t赋给master_drv_data->transfer,再调用spi_put_tx_data函数开始SPI的数据交互,通过读取SPI的状态寄存器等待SPI数据传送完毕,再通过for循环读取MXC_CSPIRXDATA寄存器,将SPI读到的数据保存到t->rx_buf,最后关闭SPI时钟。可见,每一个SPI的数据交互,都会先打开SPI的时钟,再进行数据交互,最后再关闭时钟。通过示波器可以很明显的看到,有数据交互时就会有时钟信号,没有数据交互时时钟信号就消失了。而且每一次数据交互,都始终会存在SPI的读写操作,也就是说mxc_spi_poll_transfer函数是读写操作都会执行的,这也就是为什么在CPLD程序中读函数和写函数都调用这个函数的原因。

spi_put_tx_data函数是SPI操作最底层的函数,代码如下:

static void spi_put_tx_data(void *base, unsigned int count,struct mxc_spi *master_drv_data)

{

         unsigned int ctrl_reg;

         unsigned int data;

         int i = 0;

         /* Perform Tx transaction */

         //将数据写入TXDATA寄存器,一次性写入count次。由于TXDATA8FIFO,每一级占32位,

         //故这里count绝对不允许大于8.在调用本函数之前有对count的判断。

         for (i = 0; i < count; i++)

         {

                   data = master_drv_data->transfer.tx_get(master_drv_data);

                   printk("[mxc_spi.c]data=0x%x/n",data);//lqm added.

                   __raw_writel(data, base + MXC_CSPITXDATA);//将数据写入TXDATA寄存器

         }

         ctrl_reg = __raw_readl(base + MXC_CSPICTRL);

         ctrl_reg |= master_drv_data->spi_ver_def->xch;

         __raw_writel(ctrl_reg, base + MXC_CSPICTRL);//写入XCH

         return;

}

这里传入参数countmx25_3ds_cpld_rw函数的t结构体的len,可以看到程序中它作为了for循环的次数,换句话说,len为多少,就会往TXDATA中写几次。MX25TXDATA8FIFO,每一级占32位。从这里可以看出我们每次做一次SPI传输时,长度不能超过8,否则程序会只传8次,在mxc_spi_poll_transfer函数中有下面的程序用于校正传输次数:

count = (t->len > fifo_size) ? fifo_size : t->len;

    执行完这句话,马上就会执行spi_put_tx_data函数。将数据写入TXDATA后,再写入XCH位,接着就是等待SPI传输完成。

整个驱动调用SPI驱动的流程到此就结束了,当然这里我们使用的是轮询的方式,SPI还支持中断,DMA等方式,方法类似,所不同的是我们可能需要调用mxc_spi_transfer函数。

G860上没有使用CPLD,我们完全可以将mx25_3stack_cpld.c修改为其它需要调用SPI驱动的程序,如打印机驱动。打印机一次只传输一个字节,速度也不能过高,默认18M大大超过了打印机的响应速度,因此在mx25_3ds_cpld_probe函数中,我们作了上面的修改。另外程序中凡是关于CPLD寄存器的操作程序全部都删掉,然后添加打印机的SPI写函数,并声明为外部函数:

void spi_printer_write(unsigned char *buf, size_t len)

{

         mx25_3ds_cpld_rw(buf,len);

}

EXPORT_SYMBOL(spi_printer_write);

这样,我们就可以在打印机驱动里面直接调用spi_printer_write函数进行SPI操作了。在当前文件下我们可以调试该函数是否能够正常执行,在mx25_3ds_cpld_probe中添加如下代码:

unsigned char/*int*/ temp[6]={0xaa,0xbb,0xcc,0xdd,0xee,0xff};

……

spi_printer_write((unsigned char *)temp,6);

由于在spi_put_tx_data函数中我们添加了SPI数据流的打印信息,在kernel启动时会打印中这里SPI写入的6个数据。

需要注意的是,前面的bits_per_word的定义一定要修改为8,否则添加上面的temp字符型数组后,kernel都会崩溃。也就是说,bits_per_word一定要和temp的类型对应起来。

2.      应用层调用SPI驱动

spidev.c文件定义了应用层和SPI驱动层之间的一些接口,应用层通过open,ioctl等标准函数调用spidev驱动,spidev.c下的函数又会调用mxc_spi.c下的函数实现最底层的寄存器操作。

spidev.c文件的spidev_init函数通过register_chrdev函数注册SPI驱动,通过class_create函数创建设备文件,再调用spi_register_driver函数将spidev注册为SPI驱动。在file_operations结构体中,定义了ioctl函数spidev_ioctl,它是应用层与驱动层数据交互的桥梁。

spidev_ioctl函数中,下面的程序用于检测命令幻数是否为'k'_IOC_TYPE(cmd)获取命令的幻数:

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

switchcase语句中,使用__put_user函数将SPI的数据传输顺序,每次读取的位数,SPI的传输速度返回给应用,使用__get_user函数从应用程序读取SPI数据传输的模式,数据传输顺序,传输的位数以及传输的速度,并调用spi_setup函数填充相关结构体。

当进行SPI的数据交互时,应用程序调用spidev_ioctl函数里面default下面的程序,通过__copy_from_user将应用层要传输的SPI数据拷备到kernel层,再通过spidev_message函数执行数据传输。spidev_message函数会调用spidev_sync函数,spidev_sync又会调用spi_async函数,spi_async再调用mxc_spi.c文件下的mxc_spi_transfer函数执行最底层的SPI数据交互。mxc_spi_transfer函数代码如下:

int mxc_spi_transfer(struct spi_device *spi, struct spi_transfer *t)

{

         struct mxc_spi *master_drv_data = NULL;

         int count;

         int chipselect_status;

         u32 fifo_size;

         /* Get the master controller driver data from spi device's master */

         master_drv_data = spi_master_get_devdata(spi->master);

         chipselect_status = __raw_readl(MXC_CSPICONFIG + master_drv_data->ctrl_addr);

         chipselect_status >>= master_drv_data->spi_ver_def->ss_pol_shift &

             master_drv_data->spi_ver_def->mode_mask;

         if (master_drv_data->chipselect_active)

                   master_drv_data->chipselect_active(spi->master->bus_num,

                                                           chipselect_status,

                                                           (spi->chip_select &

                                                            MXC_CSPICTRL_CSMASK) + 1);

         clk_enable(master_drv_data->clk);

         /* Modify the Tx, Rx, Count */

         master_drv_data->transfer.tx_buf = t->tx_buf;

         master_drv_data->transfer.rx_buf = t->rx_buf;

         master_drv_data->transfer.count = t->len;

         fifo_size = master_drv_data->spi_ver_def->fifo_size;

         INIT_COMPLETION(master_drv_data->xfer_done);

         /* Enable the Rx Interrupts */

         spi_enable_interrupt(master_drv_data,1 << (MXC_CSPIINT_RREN_SHIFT +

                                                        master_drv_data->spi_ver_def->rx_inten_dif));

         count = (t->len > fifo_size) ? fifo_size : t->len;

         /* Perform Tx transaction */

         master_drv_data->transfer.rx_count = count;

         spi_put_tx_data(master_drv_data->base, count, master_drv_data);

         /* Wait for transfer completion */

         wait_for_completion(&master_drv_data->xfer_done);

         /* Disable the Rx Interrupts */

         spi_disable_interrupt(master_drv_data,1 << (MXC_CSPIINT_RREN_SHIFT +

                                         master_drv_data->spi_ver_def->rx_inten_dif));

         clk_disable(master_drv_data->clk);

         if (master_drv_data->chipselect_inactive)

                   master_drv_data->chipselect_inactive(spi->master->bus_num,

                                                             chipselect_status,

                                                             (spi->chip_select & MXC_CSPICTRL_CSMASK) + 1);

         return (t->len - master_drv_data->transfer.count);

}

函数首先读取SPI的状态,再使能SPI的时钟,将传进的结构体数据赋给master_drv_data->transfer,使能接收中断,然后调用spi_put_tx_data函数开始SPI的数据传输。这个函数和上面的驱动间SPI调用的函数是一样的。接着通过wait_for_completion函数等待传输的完成,传输结束后,禁能SPI中断和SPI时钟,mxc_spi_transfer函数到此完成自身任务。

再回到spidev.c下的spidev_message函数中,执行完spidev_sync,即上面的传输函数mxc_spi_transfer执行完后,再通过__copy_to_user函数将SPI读取的数据回传给应用。到此,整个应用与驱动的数据交互也就结束了。

飞思卡尔官方提供了SPI测试的应用程序,该程序通过main函数传入SPI的硬件属性,进而调用驱动设置相关寄存器。如通过下面的命令执行应用程序:

./ mxc_spi_test1 –D 0 –s 4000000 –b 8 abcdefg

表示执行mxc_spi_test1应用程序,使用SPI1,速度为4M,每次传输8位,传输内容为abcdefg7个数。

阅读(2728) | 评论(0) | 转发(2) |
0

上一篇:中断和DMA的区别

下一篇:start_kernel 分析

给主人留下些什么吧!~~