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

浙公网安备 33010602011771号