努力成为大神~
分类: LINUX
2016-05-23 21:24:39
原文地址:MX25的SPI驱动 作者:formycuteboy
MX25的SPI驱动,和大多数平台的SPI驱动都有相似之处。这里有三个非常关键的文件:mxc_spi.c,spi.c,spidev.c,路径都在kernel/drivers/spi下。其中spi.c为驱动与驱动之间的调用接口,spidev.c为应用与驱动之间的调用接口,mxc_spi.c为驱动的最底层代码,直接操作SPI的相关寄存器。下面分别从驱动间调用SPI驱动和应用层调用SPI驱动两条路分析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_write和spi_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, // SPI的CS位设置?
.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_word和max_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次。由于TXDATA有8级FIFO,每一级占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;
}
这里传入参数count为mx25_3ds_cpld_rw函数的t结构体的len,可以看到程序中它作为了for循环的次数,换句话说,len为多少,就会往TXDATA中写几次。MX25的TXDATA有8级FIFO,每一级占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的类型对应起来。
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));
在switch的case语句中,使用__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位,传输内容为abcdefg共7个数。