折腾了几天,终于把spi扩展串口给搞定了,这个芯片有两个通道、可配置波特率、支持IIC和spi通信,支持中断等诸多优良特性。
主cpu是采用AT91SAM9X35平台,linux内核采用的是2.6.39,文件系统是用buildroot生成的。
由于之前用通用驱动来操作752芯片,所以,我们需要先关闭内核通用驱动的配置(不关闭也是可以的,modalias一改,通用驱动就匹配不上了)。好了,先上原理图:
通过查看原理图,可以知道采用的是SPI模式,并且与cpu的spi0相连,IRQ中断和cpu的PB18引脚。
打开board-sam9x5ek.c文件,先把原来通用驱动的板级配置注释掉:
.modalias是设置spi设备名字的,我们设置为“sc16is752”.max_speed_hz是设置spi时钟频率,752最大支持4Mhz。.bus_num是表示采用哪一个控制器的,假如我们采用spi1,则bus_num设置为1,chip_select表示的是高电平还是低电平选择752。Mode表示是使用哪种模式,spi有四种模式,关于四种模式,百度有详细介绍,通过查看752芯片手册,我们知道752工作在模式0。Irq是表示中断号,752irq是和PB18相连接的,所以设置为AT91_PIN_PB18,AT91_PIN_PB18是一个宏,展开后是82,有兴趣的自己去了解smx35的硬件中断。需要注意的是,这不是spi的中断,别搞错了,是752外设FIFO缓冲区有内容的中断。
板级配置好了之后,会通过ek_board_init函数,然后调用at91_add_device_spi(at91sam9x5_spi_devices, ARRAY_SIZE(at91sam9x5_spi_devices)); 将板级文件信息注册到linux内核。
接下来编写驱动,新建文件:sc16is752.c:
当应用程序去read sc16is752时,会判断buf中是否接收到了数据,如果没有数据,则让进程休眠,如果有数据,则通过spi接口读取752 fifo中的数据。
752 fifo中一旦接收到了数据,就会产生中断,在中断处理函数中,提交了一个工作,然后便返回,将复杂的中断处理过程留给工作队列去处理。sc752_work_func将处理下半部分工作,在sc752_work_func中,我们先去读取752的IIR寄存器,判断是不是接收数据FIFO中断,然后把唤醒条件置1,并且唤醒工作队列。唤醒进程之后,便继续执行read函数。
下面分析一些比较重要的代码。
为了方便操作该设备,我们定义一个结构体来描述这个设备的一些基本特性,然后声明一个struct sc16is752_dev类型全局指针。Probe函数中,将对这个全局指针进行初始化和赋值。
Probe函数中,首先使用kzalloc,分配一个struct sc16is752_dev空间,kzallc与kmallc的区别在于kzalloc会对这个空间进行初始化为0的操作,其它特性一模一样。
sc16is752->irq = spi->irq;获取中断号,中断号在板级文件中定义了,.irq = AT91_PIN_PB18,
sc16is752->spi = spi;将spi的结构赋值给sc16is752全局指针,后面我们就可以利用sc16is752->spi方便的调用spi core层提供的函数了。
ret = misc_register(&sc16is752_misc);用于注册一个混杂设备,这个混杂设备采用自动分配次设备号。
ret = request_threaded_irq(sc16is752->irq, NULL, sc16is752_handler,
IRQF_TRIGGER_FALLING, "sc16is752", NULL);申请中断,因为752 FIFO中有数据是低电平的,所以采用下降沿触发中断的方式。
sc16is752->sc752_irq_work = kzalloc(sizeof(struct work_struct),GFP_KERNEL);分配一个工作结构体空间。
INIT_WORK(sc16is752->sc752_irq_work, sc752_work_func);初始化工作。
init_waitqueue_head(&sc16is752->sc16is752_q);初始化工作队列。
sc16is752->condition = 0;初始化等待条件为0
spi_set_drvdata(spi, sc16is752); 用来存储驱动中要用到的私有数据
sc16is752_misc结构:
混杂设备操作函数集:
sc16is752_open函数主要是进行752的初始化、将全局指针sc16is752复制给设备的私有数据filp->private_data = sc16is752,然后给spi申请一个64byte的缓存空间sc16is752->buf = kmalloc(64,GFP_KERNEL);最后将此设备文件设置为不可随机读取nonseekable_open(inode, filp)。
sc16is752_release函数就是释放了申请的64byte的buf缓冲区。
sc16is752_ioctl实现了通道的设置、波特率设置、停止位设置。需要注意波特率设置那一块,通过实践发现,不能够直接写DLL和DLH去设置波特率,会不成功。:
需要做一些初始化的步骤:
接下来分析write函数。
copy_from_user(sc16is752->buf, buf, size);将应用层的buf指针当中size空间的数据复制到sc16is752设备中的buf空间中。注意size是应用空间write传下来的。如:write(fd,”hello”, 6);把hello 6字节数据传到sc16is752->buf中,hello后面还一个‘\0’字符哦。
write_channel(sc16is752->buf, size);将hello字符,写到752中,打开write_channel函数:
以hello字符串分析,参数 buf指向sc16is752->buf空间,也就是存放hello字符串的内核空间,参数len就是应用空间传过来的6。函数开始读取752的LSR寄存器,判断发送FIFO是否为空,如果为空,就开始构造spi_message,根据752 spi时序图可以知道,需要先写一个字节,告诉从设备我主设备是想读还是想写,想操作那个寄存器,想操作哪个通道。
cmd = 0x00 | (THR << 3) | (sc16is752->channel_num << 1);表示写,操作THR寄存器,操作的通道由ioctl设置,未设置的情况下是channelA通道。
t[0].tx_buf存放的是操作码,操作码长度为1个字节,t[1].tx_buf存放的是sc16is752->buf空间“hello”字符串,长度就是6。构造好了spi_message之后,就通过spicore层函数spi_sync(sc16is752->spi, &m);将数据发送出去。
Spi_message中有一个actual_length成员变量,actual_length成员变量表示spi实际发送的字节数,也就是t[0].len+t[1].len = len的值,也就是7.
接下来分析sc16is752_read函数:
开始时调用wait_event_interruptible,当spi接收的数据位空时,wait_event_interruptible让应用程序进入等待队列中休眠,最好不要用wait_event,因为wait_event是不可中断的休眠。
length = read_channel(size);将读取的数据放到sc16is752->buf空间,然后通过copy_to_user,将数据传给应用空间。注意这个当中的length变量,它是实际读取到spi字节数的长度,是需要非常精确的返回给read的,应用空间read成功之后返回的是读取到的字节数!
读取完毕之后,就将等待条件sc16is752->condition设置为0,这样下次读取数据的时候,sc16is752->buf空间为空时,再次让进程进入休眠状态。
多了很多乱码的,这就是因为一次性把数据读取出来,无法判断串口接收到数据的自己长度造成的。
#if 0 注释掉的就是一次性读取数据的代码。
后面的代码是一个一个字节读取,然后查询LSR寄存器的bit1位,直到FIFO全部读完,每读取一个自己数据,length就加一,这样就能知道串口接收到的数据长度了,并且返回给应用空间。虽然效率低一点,但是保证了驱动的正确性。
正确读取的效果:
补充:
出现如上的错误是write_channel函数中t[1].len没有初始化造成的,因为没有设置为应用空间传进来的长度,这里面是个垃圾值,spi_sync独占了资源导致内核崩溃。
通过串口更新内核方法,uboot下操作:
loadb 0x22000000
主cpu是采用AT91SAM9X35平台,linux内核采用的是2.6.39,文件系统是用buildroot生成的。
由于之前用通用驱动来操作752芯片,所以,我们需要先关闭内核通用驱动的配置(不关闭也是可以的,modalias一改,通用驱动就匹配不上了)。好了,先上原理图:
通过查看原理图,可以知道采用的是SPI模式,并且与cpu的spi0相连,IRQ中断和cpu的PB18引脚。
打开board-sam9x5ek.c文件,先把原来通用驱动的板级配置注释掉:
/*
static struct spi_board_info at91sam9x5_spi_devices[] = {
{
.modalias= "spidev",
.max_speed_hz= 1000000,
.bus_num= 0,
.chip_select= 0,
.mode = SPI_MODE_0,
},
};
*/
然后加上如下的代码:
static struct spi_board_info at91sam9x5_spi_devices[] = {
{
.modalias= "sc16is752",
.max_speed_hz= 1000000,
.bus_num= 0,
.chip_select= 0,
.mode = SPI_MODE_0,
.irq = AT91_PIN_PB18,
},
};
.modalias是设置spi设备名字的,我们设置为“sc16is752”.max_speed_hz是设置spi时钟频率,752最大支持4Mhz。.bus_num是表示采用哪一个控制器的,假如我们采用spi1,则bus_num设置为1,chip_select表示的是高电平还是低电平选择752。Mode表示是使用哪种模式,spi有四种模式,关于四种模式,百度有详细介绍,通过查看752芯片手册,我们知道752工作在模式0。Irq是表示中断号,752irq是和PB18相连接的,所以设置为AT91_PIN_PB18,AT91_PIN_PB18是一个宏,展开后是82,有兴趣的自己去了解smx35的硬件中断。需要注意的是,这不是spi的中断,别搞错了,是752外设FIFO缓冲区有内容的中断。
板级配置好了之后,会通过ek_board_init函数,然后调用at91_add_device_spi(at91sam9x5_spi_devices, ARRAY_SIZE(at91sam9x5_spi_devices)); 将板级文件信息注册到linux内核。
接下来编写驱动,新建文件:sc16is752.c:
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#define RHR 0x00 //R
#define THR 0x00 //W
#define IER 0x01
#define FCR 0x02 //R
#define IIR 0x02 //W
#define LCR 0x03
#define MCR 0x04
#define LSR 0x05
#define MSR_1 0x06 //本来是直接定义为MSR的,但是这样定义与汇编指令MSR有冲突
#define SPR 0x07
#define TCR 0x06 //These registers are accessible only when EFR[4] = 1, and MCR[2] = 1
#define TLR 0x07
#define TXLVL 0x08
#define RXLVL 0x09
#define IODIR_752 0x0a
#define IOSTATE 0x0b
#define IOINTENA 0x0c
#define RESERVED 0x0d
#define IOCONTROL 0x0e
#define EFCR 0x0f
//special register The Special Register set is accessible only when LCR[7] = 1 and not 0xBF
#define DLL 0x00
#define DLH 0x01
//enhanced register Enhanced Features Registers are only accessible when LCR = 0xBF
#define EFR 0x02
#define XON1 0x04
#define XON2 0x05
#define XOFF1 0x06
#define XOFF2 0x07
//定义端口
#define channelA 0x0
#define channelB 0x1
//定义spi uart操作幻数
#define SET_channleA _IOW('k', 0, int)
#define SET_channleB _IOW('k', 1, int)
#define SET_STOP_BIT _IOW('k', 2, int)
#define SET_BPS _IOW('k', 3, int)
struct sc16is752_dev {
struct spi_device *spi;
spinlock_t spi_lock;
struct mutex lock;
unsigned char *buf;
unsigned int channel_num;
int irq;
struct work_struct *sc752_irq_work;
wait_queue_head_t sc16is752_q;
unsigned int condition;
};
static struct sc16is752_dev *sc16is752 = NULL;
static char read_752_reg(unsigned int reg, unsigned int channel)
{
struct spi_transfer t[2];
struct spi_message m;
char val = 0;
unsigned char cmd = 0x80 | (reg << 3) | (channel << 1);
spi_message_init(&m);
memset(t, 0, (sizeof t));
t[0].tx_buf = &cmd;
t[0].len = 1;
spi_message_add_tail(&t[0], &m);
t[1].rx_buf = &val;
t[1].len = 1;
spi_message_add_tail(&t[1], &m);
spin_lock_irq(&sc16is752->spi_lock);
spi_sync(sc16is752->spi, &m);
spin_unlock_irq(&sc16is752->spi_lock);
return val;
}
static ssize_t read_channel(int len)
{
#if 0
struct spi_transfer t[2];
struct spi_message m;
unsigned char cmd;
cmd = 0x80 | (THR << 3) | (sc16is752->channel_num<< 1);
printk(KERN_INFO "enter read_channel\n");
spi_message_init(&m);
memset(t, 0, (sizeof t));
t[0].tx_buf = &cmd;
t[0].len = 1;
spi_message_add_tail(&t[0], &m);
t[1].rx_buf = sc16is752->buf;
t[1].len = len;
spi_message_add_tail(&t[1], &m);
spin_lock_irq(&sc16is752->spi_lock);
spi_sync(sc16is752->spi, &m);
spin_unlock_irq(&sc16is752->spi_lock);
printk(KERN_INFO "m.actual_length = %d\n",m.actual_length);
return m.actual_length;
#endif
unsigned int length = 0;
unsigned char status = 0;
do{
status = read_752_reg(LSR, sc16is752->channel_num);
if(status & 0x01)
{
sc16is752->buf[length] = read_752_reg(RHR, sc16is752->channel_num);
length ++;
}
}while(status & 0x01);
return length;
}
static void write_752_reg(unsigned int reg, unsigned int channel, unsigned char data)
{
struct spi_transfer t[2];
struct spi_message m;
char val = data;
unsigned char cmd;
cmd = 0x00 | (reg << 3) | (channel << 1);
spi_message_init(&m);
memset(t, 0, (sizeof t));
t[0].tx_buf = &cmd;
t[0].len = 1;
spi_message_add_tail(&t[0], &m);
t[1].tx_buf = &val;
t[1].len = 1;
spi_message_add_tail(&t[1], &m);
spin_lock_irq(&sc16is752->spi_lock);
spi_sync(sc16is752->spi, &m);
spin_unlock_irq(&sc16is752->spi_lock);
return;
}
static void write_channel(unsigned char *buf, int len)
{
struct spi_transfer t[2];
struct spi_message m;
unsigned char status;
unsigned char cmd;
cmd = 0x00 | (THR << 3) | (sc16is752->channel_num << 1);
status = read_752_reg(LSR, sc16is752->channel_num);
if (status & 0x20)
{
spi_message_init(&m);
memset(t, 0, (sizeof t));
t[0].tx_buf = &cmd;
t[0].len = 1;
spi_message_add_tail(&t[0], &m);
t[1].tx_buf = buf;
t[1].len = len;
spi_message_add_tail(&t[1], &m);
spin_lock_irq(&sc16is752->spi_lock);
spi_sync(sc16is752->spi, &m);
spin_unlock_irq(&sc16is752->spi_lock);
}
return;
}
static int sc16is752_open(struct inode *inode, struct file *filp)
{
filp->private_data = sc16is752;
/*initlizate channel A*/
write_752_reg(LCR, channelA, 0x80);
write_752_reg(DLL, channelA, 0x14);
write_752_reg(DLH, channelA, 0x00);
write_752_reg(LCR, channelA, 0xbf);
write_752_reg(EFR, channelA, 0x10);
write_752_reg(LCR, channelA, 0x03);
write_752_reg(IER, channelA, 0x01);
write_752_reg(FCR, channelA, 0xf1);
//write_752_reg(FCR, channelA, 0x51);
write_752_reg(SPR, channelA, 0x41);
write_752_reg(IODIR_752, channelA, 0xff);
write_752_reg(IOSTATE, channelA, 0x00);
/*initlizate channel B*/
write_752_reg(LCR, channelB, 0x80);
write_752_reg(DLL, channelB, 0x14);
write_752_reg(DLH, channelB, 0x00);
write_752_reg(LCR, channelB, 0xbf);
write_752_reg(EFR, channelB, 0x10);
write_752_reg(LCR, channelB, 0x03);
write_752_reg(IER, channelB, 0x01);
write_752_reg(FCR, channelB, 0xf1);
//write_752_reg(FCR, channelB, 0x51);
write_752_reg(SPR, channelB, 0x41);
write_752_reg(IODIR_752, channelB, 0xff);
write_752_reg(IOSTATE, channelB, 0x00);
//request spi buffer
sc16is752->buf = kmalloc(64,GFP_KERNEL);
if(!sc16is752->buf)
{
printk(KERN_INFO "kzallo buf error\n");
return -ENOMEM;
}
nonseekable_open(inode, filp);//设置为不可随机读取。
printk(KERN_INFO "open and initlizate channel A/B succeed\n ");
return 0;
}
static int sc16is752_release(struct inode *inode, struct file *filp)
{
if (NULL != sc16is752->buf)
kfree(sc16is752->buf);
return 0;
}
static long sc16is752_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{
unsigned char tmp = 0;
switch (cmd) {
case SET_channleA:
{
sc16is752->channel_num = channelA;
break;
}
case SET_channleB:
{
sc16is752->channel_num = channelB;
break;
}
case SET_STOP_BIT:
{
tmp = read_752_reg(LCR, sc16is752->channel_num);
if(1 == args)
{
tmp &= (~(0x01<<2));//设置1位停止位
write_752_reg(LCR, sc16is752->channel_num, tmp);
}
else if (2 == args)
{
tmp |= (0x01<<2);//设置1.5/2位停止位
write_752_reg(LCR, sc16is752->channel_num, tmp);
}
break;
}
case SET_BPS:
{
tmp = read_752_reg(IER, sc16is752->channel_num);
tmp &= (~(0x01<<4));
//禁止睡眠模式才可以设置波特率。
write_752_reg(IER, sc16is752->channel_num, tmp);
if(9600 == args)
{
write_752_reg(LCR, sc16is752->channel_num, 0x80);
write_752_reg(DLL, sc16is752->channel_num, 0x14);
write_752_reg(DLH, sc16is752->channel_num, 0x00);
write_752_reg(LCR, sc16is752->channel_num, 0xbf);
write_752_reg(EFR, sc16is752->channel_num, 0x10);
write_752_reg(LCR, sc16is752->channel_num, 0x03);
write_752_reg(IER, sc16is752->channel_num, 0x01);
write_752_reg(FCR, sc16is752->channel_num, 0xf1);
write_752_reg(SPR, sc16is752->channel_num, 0x41);
write_752_reg(IODIR_752, sc16is752->channel_num, 0xff);
write_752_reg(IOSTATE, sc16is752->channel_num, 0x00);
}
else if(19200 == args)
{
write_752_reg(LCR, sc16is752->channel_num, 0x80);
write_752_reg(DLL, sc16is752->channel_num, 0x0a);
write_752_reg(DLH, sc16is752->channel_num, 0x00);
write_752_reg(LCR, sc16is752->channel_num, 0xbf);
write_752_reg(EFR, sc16is752->channel_num, 0x10);
write_752_reg(LCR, sc16is752->channel_num, 0x03);
write_752_reg(IER, sc16is752->channel_num, 0x01);
write_752_reg(FCR, sc16is752->channel_num, 0xf1);
write_752_reg(SPR, sc16is752->channel_num, 0x41);
write_752_reg(IODIR_752, sc16is752->channel_num, 0xff);
write_752_reg(IOSTATE, sc16is752->channel_num, 0x00);
}
else if(38400 == args)
{
write_752_reg(LCR, sc16is752->channel_num, 0x80);
write_752_reg(DLL, sc16is752->channel_num, 0x05);
write_752_reg(DLH, sc16is752->channel_num, 0x00);
write_752_reg(LCR, sc16is752->channel_num, 0xbf);
write_752_reg(EFR, sc16is752->channel_num, 0x10);
write_752_reg(LCR, sc16is752->channel_num, 0x03);
write_752_reg(IER, sc16is752->channel_num, 0x01);
write_752_reg(FCR, sc16is752->channel_num, 0xf1);
write_752_reg(SPR, sc16is752->channel_num, 0x41);
write_752_reg(IODIR_752, sc16is752->channel_num, 0xff);
write_752_reg(IOSTATE, sc16is752->channel_num, 0x00);
}
break;
}
default:
break;
}
return 0;
}
static ssize_t sc16is752_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
struct sc16is752_dev *sc16is752 = filp->private_data;
int length = 0;
int missing;
//wait_event(sc16is752->sc16is752_q,sc16is752->condition);//用此函数会导致进程杀不死。
wait_event_interruptible(sc16is752->sc16is752_q,sc16is752->condition);
mutex_lock(&sc16is752->lock);
length = read_channel(size);
missing = copy_to_user(buf, sc16is752->buf, length);
if (missing == length)
length = -EFAULT;
else
length = length - missing;
mutex_unlock(&sc16is752->lock);
sc16is752->condition = 0;
return length;
}
static ssize_t sc16is752_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
int missing ;
struct sc16is752_dev *sc16is752 = filp->private_data;
if(size > 64)
return -EMSGSIZE;
mutex_lock(&sc16is752->lock);
missing = copy_from_user(sc16is752->buf, buf, size);
write_channel(sc16is752->buf, size);
mutex_unlock(&sc16is752->lock);
return 0;
}
struct file_operations sc16is752_fops = {
.owner = THIS_MODULE,
.open = sc16is752_open,
.write = sc16is752_write,
.read = sc16is752_read,
.unlocked_ioctl = sc16is752_ioctl,
.release = sc16is752_release,
};
struct miscdevice sc16is752_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "sc16is752",
.fops = &sc16is752_fops,
};
void sc752_work_func(struct work_struct *work)
{
unsigned char tmp = 0;
tmp = read_752_reg(IIR, sc16is752->channel_num);
printk(KERN_INFO "enter sc752_work_func\n");
if(tmp & 0x04)
{
sc16is752->condition = 1;
wake_up(&sc16is752->sc16is752_q);
}
}
irqreturn_t sc16is752_handler(int irq, void *dev_id)
{
schedule_work(sc16is752->sc752_irq_work);
printk(KERN_INFO "enter sc16is752_handler\n");
return IRQ_HANDLED;
}
static int sc16is752_probe(struct spi_device *spi)
{
int ret = 0;
//printk(KERN_INFO "Spi device sc16is752 is probed!\n");
sc16is752 = kzalloc(sizeof(*sc16is752), GFP_KERNEL);
if (!sc16is752)
return -ENOMEM;
sc16is752->channel_num = channelA;//默认使用channelA
sc16is752->buf = NULL;
sc16is752->irq = spi->irq; //中断
sc16is752->spi = spi;
spin_lock_init(&sc16is752->spi_lock);
mutex_init(&sc16is752->lock);
ret = misc_register(&sc16is752_misc);
if (ret != 0) {
printk(KERN_INFO "cannot register miscdev err = %d\n", ret);
}
//printk(KERN_INFO "sc16is752->irq %d\n",sc16is752->irq);
ret = request_threaded_irq(sc16is752->irq, NULL, sc16is752_handler,
IRQF_TRIGGER_FALLING, "sc16is752", NULL);
if (ret < 0 )
{
printk(KERN_INFO "Failed to request IRQ!\n");
}
sc16is752->sc752_irq_work = kzalloc(sizeof(struct work_struct),GFP_KERNEL);
INIT_WORK(sc16is752->sc752_irq_work, sc752_work_func);
init_waitqueue_head(&sc16is752->sc16is752_q);
sc16is752->condition = 0;//初始化等待条件为0
spi_set_drvdata(spi, sc16is752);
return 0;
}
static int __devexit sc16is752_remove(struct spi_device *spi)
{
misc_deregister(&sc16is752_misc);
free_irq(sc16is752->irq,NULL);
kfree(sc16is752);
return 0;
}
static struct spi_driver sc16is752_driver = {
.driver = {
.name = "sc16is752",
.owner = THIS_MODULE,
},
.probe = sc16is752_probe,
.remove = __devexit_p(sc16is752_remove),
};
static int __init sc16is752_init(void)
{
return spi_register_driver(&sc16is752_driver);
}
static void __exit sc16is752_exit(void)
{
spi_unregister_driver(&sc16is752_driver);
}
module_init(sc16is752_init);
module_exit(sc16is752_exit);
MODULE_DESCRIPTION("Driver for most SPI SC16IS752");
MODULE_AUTHOR("tianyu");
MODULE_LICENSE("GPL");
MODULE_ALIAS("sct");
上面代码注册了一个spi驱动,然后注册一个混杂设备驱动,实现了常用的系统调用。代码运行成功后,会在板子的/dev/目录下,生成一个sc16is752设备文件。打开该设备,是进行设备的初始化,将752设置为中断模式、1位停止位、波特率9600、无奇偶校验、8位数据位。
当应用程序去read sc16is752时,会判断buf中是否接收到了数据,如果没有数据,则让进程休眠,如果有数据,则通过spi接口读取752 fifo中的数据。
752 fifo中一旦接收到了数据,就会产生中断,在中断处理函数中,提交了一个工作,然后便返回,将复杂的中断处理过程留给工作队列去处理。sc752_work_func将处理下半部分工作,在sc752_work_func中,我们先去读取752的IIR寄存器,判断是不是接收数据FIFO中断,然后把唤醒条件置1,并且唤醒工作队列。唤醒进程之后,便继续执行read函数。
下面分析一些比较重要的代码。
struct sc16is752_dev {
struct spi_device *spi;//用于存放spi设备结构
spinlock_t spi_lock;//定义自旋锁
struct mutex lock;//互斥锁
unsigned char *buf;//spi设备缓冲区,之后会申请为64字节
unsigned int channel_num;//设备的通道号,表示我要操作那个通道
int irq;//中断号
struct work_struct *sc752_irq_work;//工作队列
wait_queue_head_t sc16is752_q;//进程等待队列
unsigned int condition;//唤醒条件
};
为了方便操作该设备,我们定义一个结构体来描述这个设备的一些基本特性,然后声明一个struct sc16is752_dev类型全局指针。Probe函数中,将对这个全局指针进行初始化和赋值。
static int sc16is752_probe(struct spi_device *spi)
{
int ret = 0;
//printk(KERN_INFO "Spi device sc16is752 is probed!\n");
sc16is752 = kzalloc(sizeof(*sc16is752), GFP_KERNEL);
if (!sc16is752)
return -ENOMEM;
sc16is752->channel_num = channelA;//默认使用channelA
sc16is752->buf = NULL;
sc16is752->irq = spi->irq; //中断
sc16is752->spi = spi;
spin_lock_init(&sc16is752->spi_lock);
mutex_init(&sc16is752->lock);
ret = misc_register(&sc16is752_misc);
if (ret != 0) {
printk(KERN_INFO "cannot register miscdev err = %d\n", ret);
}
//printk(KERN_INFO "sc16is752->irq %d\n",sc16is752->irq);
ret = request_threaded_irq(sc16is752->irq, NULL, sc16is752_handler,
IRQF_TRIGGER_FALLING, "sc16is752", NULL);
if (ret < 0 )
{
printk(KERN_INFO "Failed to request IRQ!\n");
}
sc16is752->sc752_irq_work = kzalloc(sizeof(struct work_struct),GFP_KERNEL);
INIT_WORK(sc16is752->sc752_irq_work, sc752_work_func);
init_waitqueue_head(&sc16is752->sc16is752_q);
sc16is752->condition = 0;//初始化等待条件为0
spi_set_drvdata(spi, sc16is752);
return 0;
}
Probe函数中,首先使用kzalloc,分配一个struct sc16is752_dev空间,kzallc与kmallc的区别在于kzalloc会对这个空间进行初始化为0的操作,其它特性一模一样。
sc16is752->irq = spi->irq;获取中断号,中断号在板级文件中定义了,.irq = AT91_PIN_PB18,
sc16is752->spi = spi;将spi的结构赋值给sc16is752全局指针,后面我们就可以利用sc16is752->spi方便的调用spi core层提供的函数了。
ret = misc_register(&sc16is752_misc);用于注册一个混杂设备,这个混杂设备采用自动分配次设备号。
ret = request_threaded_irq(sc16is752->irq, NULL, sc16is752_handler,
IRQF_TRIGGER_FALLING, "sc16is752", NULL);申请中断,因为752 FIFO中有数据是低电平的,所以采用下降沿触发中断的方式。
sc16is752->sc752_irq_work = kzalloc(sizeof(struct work_struct),GFP_KERNEL);分配一个工作结构体空间。
INIT_WORK(sc16is752->sc752_irq_work, sc752_work_func);初始化工作。
init_waitqueue_head(&sc16is752->sc16is752_q);初始化工作队列。
sc16is752->condition = 0;初始化等待条件为0
spi_set_drvdata(spi, sc16is752); 用来存储驱动中要用到的私有数据
sc16is752_misc结构:
struct miscdevice sc16is752_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "sc16is752",
.fops = &sc16is752_fops,
};
混杂设备操作函数集:
struct file_operations sc16is752_fops = {
.owner = THIS_MODULE,
.open = sc16is752_open,
.write = sc16is752_write,
.read = sc16is752_read,
.unlocked_ioctl = sc16is752_ioctl,
.release = sc16is752_release,
};
sc16is752_open函数主要是进行752的初始化、将全局指针sc16is752复制给设备的私有数据filp->private_data = sc16is752,然后给spi申请一个64byte的缓存空间sc16is752->buf = kmalloc(64,GFP_KERNEL);最后将此设备文件设置为不可随机读取nonseekable_open(inode, filp)。
sc16is752_release函数就是释放了申请的64byte的buf缓冲区。
sc16is752_ioctl实现了通道的设置、波特率设置、停止位设置。需要注意波特率设置那一块,通过实践发现,不能够直接写DLL和DLH去设置波特率,会不成功。:
write_752_reg(DLL, sc16is752->channel_num, 0x14);
write_752_reg(DLH, sc16is752->channel_num, 0x00);
需要做一些初始化的步骤:
write_752_reg(LCR, sc16is752->channel_num, 0x80);
write_752_reg(DLL, sc16is752->channel_num, 0x14);//设置波特率。
write_752_reg(DLH, sc16is752->channel_num, 0x00);
write_752_reg(LCR, sc16is752->channel_num, 0xbf);
write_752_reg(EFR, sc16is752->channel_num, 0x10);
write_752_reg(LCR, sc16is752->channel_num, 0x03);
write_752_reg(IER, sc16is752->channel_num, 0x01);
write_752_reg(FCR, sc16is752->channel_num, 0xf1);
write_752_reg(SPR, sc16is752->channel_num, 0x41);
write_752_reg(IODIR_752, sc16is752->channel_num, 0xff);
write_752_reg(IOSTATE, sc16is752->channel_num, 0x00);
接下来分析write函数。
static ssize_t sc16is752_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
int missing ;
struct sc16is752_dev *sc16is752 = filp->private_data;
if(size > 64)
return -EMSGSIZE;
mutex_lock(&sc16is752->lock);
missing = copy_from_user(sc16is752->buf, buf, size);
write_channel(sc16is752->buf, size);
mutex_unlock(&sc16is752->lock);
return 0;
}
copy_from_user(sc16is752->buf, buf, size);将应用层的buf指针当中size空间的数据复制到sc16is752设备中的buf空间中。注意size是应用空间write传下来的。如:write(fd,”hello”, 6);把hello 6字节数据传到sc16is752->buf中,hello后面还一个‘\0’字符哦。
write_channel(sc16is752->buf, size);将hello字符,写到752中,打开write_channel函数:
static void write_channel(unsigned char *buf, int len)
{
struct spi_transfer t[2];
struct spi_message m;
unsigned char status;
unsigned char cmd;
cmd = 0x00 | (THR << 3) | (sc16is752->channel_num << 1);
status = read_752_reg(LSR, sc16is752->channel_num);
if (status & 0x20)
{
spi_message_init(&m);
memset(t, 0, (sizeof t));
t[0].tx_buf = &cmd;
t[0].len = 1;
spi_message_add_tail(&t[0], &m);
t[1].tx_buf = buf;
t[1].len = len;
spi_message_add_tail(&t[1], &m);
spin_lock_irq(&sc16is752->spi_lock);
spi_sync(sc16is752->spi, &m);
spin_unlock_irq(&sc16is752->spi_lock);
}
return;
}
以hello字符串分析,参数 buf指向sc16is752->buf空间,也就是存放hello字符串的内核空间,参数len就是应用空间传过来的6。函数开始读取752的LSR寄存器,判断发送FIFO是否为空,如果为空,就开始构造spi_message,根据752 spi时序图可以知道,需要先写一个字节,告诉从设备我主设备是想读还是想写,想操作那个寄存器,想操作哪个通道。
cmd = 0x00 | (THR << 3) | (sc16is752->channel_num << 1);表示写,操作THR寄存器,操作的通道由ioctl设置,未设置的情况下是channelA通道。
t[0].tx_buf存放的是操作码,操作码长度为1个字节,t[1].tx_buf存放的是sc16is752->buf空间“hello”字符串,长度就是6。构造好了spi_message之后,就通过spicore层函数spi_sync(sc16is752->spi, &m);将数据发送出去。
Spi_message中有一个actual_length成员变量,actual_length成员变量表示spi实际发送的字节数,也就是t[0].len+t[1].len = len的值,也就是7.
接下来分析sc16is752_read函数:
static ssize_t sc16is752_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
struct sc16is752_dev *sc16is752 = filp->private_data;
int length = 0;
int missing;
//wait_event(sc16is752->sc16is752_q,sc16is752->condition);//用此函数会导致进程杀不死。
wait_event_interruptible(sc16is752->sc16is752_q,sc16is752->condition);
mutex_lock(&sc16is752->lock);
length = read_channel(size);
missing = copy_to_user(buf, sc16is752->buf, length);
if (missing == length)
length = -EFAULT;
else
length = length - missing;
mutex_unlock(&sc16is752->lock);
sc16is752->condition = 0;
return length;
}
开始时调用wait_event_interruptible,当spi接收的数据位空时,wait_event_interruptible让应用程序进入等待队列中休眠,最好不要用wait_event,因为wait_event是不可中断的休眠。
length = read_channel(size);将读取的数据放到sc16is752->buf空间,然后通过copy_to_user,将数据传给应用空间。注意这个当中的length变量,它是实际读取到spi字节数的长度,是需要非常精确的返回给read的,应用空间read成功之后返回的是读取到的字节数!
读取完毕之后,就将等待条件sc16is752->condition设置为0,这样下次读取数据的时候,sc16is752->buf空间为空时,再次让进程进入休眠状态。
Read_channel函数,非常纠结的函数!!
static ssize_t read_channel(int len)
{
#if 0
struct spi_transfer t[2];
struct spi_message m;
unsigned char cmd;
cmd = 0x80 | (THR << 3) | (sc16is752->channel_num<< 1);
printk(KERN_INFO "enter read_channel\n");
spi_message_init(&m);
memset(t, 0, (sizeof t));
t[0].tx_buf = &cmd;
t[0].len = 1;
spi_message_add_tail(&t[0], &m);
t[1].rx_buf = sc16is752->buf;
t[1].len = len;
spi_message_add_tail(&t[1], &m);
spin_lock_irq(&sc16is752->spi_lock);
spi_sync(sc16is752->spi, &m);
spin_unlock_irq(&sc16is752->spi_lock);
printk(KERN_INFO "m.actual_length = %d\n",m.actual_length);
return m.actual_length;
#endif
unsigned int length = 0;
unsigned char status = 0;
do{
status = read_752_reg(LSR, sc16is752->channel_num);
if(status & 0x01)
{
sc16is752->buf[length] = read_752_reg(RHR, sc16is752->channel_num);
length ++;
}
}while(status & 0x01);
return length;
}
当时想一次性把数据读取出来,这样效率快啊,可是,并不知道串口发了多少字节的数据来,所以也无法正确的把接收到的字节长度传送到应用空间。应用空间读取的数据就会是这样:
多了很多乱码的,这就是因为一次性把数据读取出来,无法判断串口接收到数据的自己长度造成的。
#if 0 注释掉的就是一次性读取数据的代码。
后面的代码是一个一个字节读取,然后查询LSR寄存器的bit1位,直到FIFO全部读完,每读取一个自己数据,length就加一,这样就能知道串口接收到的数据长度了,并且返回给应用空间。虽然效率低一点,但是保证了驱动的正确性。
正确读取的效果:
补充:
出现如上的错误是write_channel函数中t[1].len没有初始化造成的,因为没有设置为应用空间传进来的长度,这里面是个垃圾值,spi_sync独占了资源导致内核崩溃。
通过串口更新内核方法,uboot下操作:
loadb 0x22000000
nand erase 0x200000 0x300000
nand write 0x22000000 0x200000 0x300000
附测试代码:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#define SC752 "/dev/sc16is752"
#define SET_channleA _IOW('k', 0, int)
#define SET_channleB _IOW('k', 1, int)
#define SET_STOP_BIT _IOW('k', 2, int)
#define SET_BPS _IOW('k', 3, int)
#define STOP_BIT_1 0x01
#define STOP_BIT_2 0x02
/**************************************************/
/* 说明:752可以设置1 1.5 2 位停止位,默认设置为1个停止位,字长度默认为8,且不可修改 */
/* 默认波特率设置为9600 */
/* 0-1个停止位(字长度=5,6,7,8) */
/* 1.5个停止位(字长度=5)*/
/* 2个停止位 */
/**************************************************/
int main(void)
{
char buf[64] = {0};
int length = 0;
int i = 0;
int fd = open(SC752, O_RDWR);
if (fd < 0) {
printf("Open %s failure.\n", SC752);
return -1;
}
int rate;
char c;
while(1)
{
/*默认是通道A,波特率9600,停止位1位*/
printf("this is default test,stop bit is 1,Baud rate is 9600\n");
write(fd, "hello", 6);
sleep(1);
length = read(fd, buf, 30);
printf("read length %d. read spi_uart is :%s\n",length, buf);
memset(buf, 0, 64);
sleep(1);
}
#if 0
/*设置通道测试*/
printf("then test set channel,please input a or b\n");
scanf("%c", &c);
if (c == 'a')
{
ioctl(fd, SET_channleA);
}
else if(c == 'b')
{
ioctl(fd, SET_channleB);
}
write(fd, "hello", 6);
sleep(1);
length = read(fd, buf, 30);
printf("read length %d. read spi_uart is :%s\n",length, buf);
memset(buf, 0, 64);
sleep(1);
/*设置停止位测试*/
printf("then test set stop bits,please input 1 or 2");
scanf("%d", &c);
if(c == 1)
{
ioctl(fd, SET_STOP_BIT, STOP_BIT_1);
}
else if(c == 2)
{
ioctl(fd, SET_STOP_BIT, STOP_BIT_2);
}
write(fd, "hello", 6);
sleep(1);
length = read(fd, buf, 30);
printf("read length %d. read spi_uart is :%s\n",length, buf);
memset(buf, 0, 64);
sleep(1);
while(1)
{
/*设置波特率测试,设置波特率之后,停止位变为默认的1位,*/
printf("then test set baud rate,please input 9600 19200 or 38400\n");
scanf("%d", &rate);
ioctl(fd, SET_BPS, rate);
write(fd, "hello", 6);
sleep(1);
length = read(fd, buf, 30);
printf("read length %d\n",length);
for(i = 0;i<length; i++)
{
printf("%d\t", buf[i]);
}
printf("\n");
memset(buf, 0, 64);
sleep(1);
}
#endif
close(fd);
return 0;
}