一个Linux下的char型设备的驱动
这个驱动程序是典型char型设备驱动,它涉及到了如何使用共享中断、相同功能的设备共用驱动程序等一些技巧。个人的愚见,欢迎来讨论。
/****************************************************************************
直放站 UART3 driver
shanghai xinmin telecom std.
diming.feng 2007-05
*****************************************************************************/
#include <linux/init.h>
#include <linux/slab.h>
#include <asm/errno.h>
#include <asm/delay.h>
//#include <asm/fcntl.h>
#include <asm/arch/irqs.h>
#include <linux/mm.h>
#include <linux/poll.h>
#include <linux/module.h>
#include <linux/serial_reg.h>
#include <asm/hardware.h>
#include <asm/io.h>
#include "expuart.h"
#define GPIO_CFG0 VPint(0xFFF83000)
#define GPIO_DIR0 VPint(0xFFF83004)
#define GPIO_DATAOUT0 VPint(0xFFF83008)
#define GPIO_CFG5 VPint(0xFFF83050)
#define GPIO_DIR5 VPint(0xFFF83054)
#define GPIO_DATAOUT5 VPint(0xFFF83058)
#define GPIO_XICFG VPint(0xFFF83074)
#define GPIO_XISTATUS VPint(0xFFF83078)
#define UART_TX VPchar(COM_TX_3)
#define UART_RX VPchar(COM_RX_3)
#define UART_DLL VPchar(COM_DLL_3)
#define UART_DLM VPchar(COM_DLM_3)
#define UART_IER VPchar(COM_IER_3)
#define UART_IIR VPchar(COM_IIR_3)
#define UART_FCR VPchar(COM_FCR_3)
#define UART_LCR VPchar(COM_LCR_3)
#define UART_MCR VPchar(COM_MCR_3)
#define UART_LSR VPchar(COM_LSR_3)
#define UART_MSR VPchar(COM_MSR_3)
#define UART_TOR VPchar(COM_TOR_3)
#define LSR_TE 0x40
#define LSR_THRE 0x20
#define LSR_RFDR 0x01
#define nIRQ2 31
#define uart3_IRQ 12
#define NOIRQ 0x01
#define RSTIRQ 0x06
#define RFVIRQ 0x04
#define RFTIRQ 0x0C
#define THRIRQ 0x02
#define MSTIRQ 0x00
#define RX_FIFO_LEVEL 14
#define UART_FCR_LEVEL 0xc0
#define CMD_BANDRATE 0 // set bandrate
#define CMD_UARTLCR 1 // set data bit
#define CMD_RXTIMEOUT 2
#define CMD_MODE 3 // set RS-485/RS-232 mode
#define CMD_MODEM_SET 4 // set internal modem
#define MAJOR_NUM 248
#define YES 1
#define NO 0
#define TIME_OUT 20000
#define uart3_int(band) do{ \
UART_LCR = 0; \
UART_IER = 0; \
UART_LCR = DLAB;\
UART_DLL = band;\
UART_DLM = 0; \
UART_LCR = 0x03;\
UART_FCR = 0x07;\
UART_TOR = 0x00;\
}while(0)
#define write_reg(reg,dat) do{ \
UART_TX = 0x80+reg; \
while((UART_LSR&LSR_TE)!=LSR_TE); \
UART_TX = dat; \
while((UART_LSR&LSR_TE)!=LSR_TE); \
}while(0)
#define read_reg(reg,dat) do{ \
UART_TX = reg; \
while((UART_LSR & LSR_RFDR)!=LSR_RFDR); \
dat = UART_RX; \
}while(0)
static int uart3tx_busy;
static uart3_dev exp_uart[EXPUART_NUM]={
{248, "uart3", 0},
{247, "uart4", 1},
{246, "uart5", 2}, // internal mode
{245, "uart6", 3},
};
DECLARE_WAIT_QUEUE_HEAD(expuart1_queue_Rd);
DECLARE_WAIT_QUEUE_HEAD(expuart1_queue_Wr);
DECLARE_WAIT_QUEUE_HEAD(expuart2_queue_Rd);
DECLARE_WAIT_QUEUE_HEAD(expuart2_queue_Wr);
DECLARE_WAIT_QUEUE_HEAD(expuart3_queue_Rd);
DECLARE_WAIT_QUEUE_HEAD(expuart3_queue_Wr);
DECLARE_WAIT_QUEUE_HEAD(expuart4_queue_Rd);
DECLARE_WAIT_QUEUE_HEAD(expuart4_queue_Wr);
static void ms_delay(int ms)
{
while(ms--){
udelay(1000);
}
}
/*
static void write_reg(char reg_addr, char reg_dat)
{
char temp;
UART_TX = 0x80+reg_addr;
do{
temp = UART_LSR;
}while((temp&LSR_TE)!=LSR_TE);
UART_TX = reg_dat;
do{
temp = UART_LSR;
}while((temp&LSR_TE)!=LSR_TE);
}
*/
static int uart3_chkbuf(int write, int read, int reqnum)
{
int temp = write-read;
if(temp >= 0){
if(temp >= reqnum){
return 1;
}
}
else{
if((temp + RX_BUFF_SIZE)>= reqnum){
return 1;
}
}
return 0;
}
static void expuart_handler(int irq, void *dev_id, struct pt_regs * regs)
{
char sub_sifr, int_no, sub_ssr;
int expnum;
int ctn;
GPIO_XISTATUS = 0;
uart3_dev *dev = (uart3_dev *)dev_id;
expnum = dev->no;
/* disable VK3214 all interrupt */
write_reg(gIR,0x00);
read_reg(gIR,int_no); // read current interrupt uart
int_no = int_no & 0x0f;
if(1<<expnum != int_no){
goto out;
}
/* read sub uart of VK3214 interrupt flags */
read_reg(SSR(expnum),sub_ssr);
// send data to uart
if((dev->usr_wr_num>0)&&((sub_ssr&4)==4)){
ctn = dev->usr_wr_num - dev->tx_ctn;
if(ctn >15){
ctn = 15;
}
UART_TX = 0xc0+(expnum<<4)+(ctn-1);
while(ctn--){
UART_TX = dev->txbuf[dev->tx_ctn++];
}
while((UART_LSR & LSR_TE)!=LSR_TE);
do{
UART_TX = SSR(expnum);
while((UART_LSR & LSR_RFDR)!=LSR_RFDR);
}while((UART_RX&4)!=4);
if(dev->tx_ctn == dev->usr_wr_num){
write_reg(SIER(expnum),0x01); // tx interrupt disable, rx interrupt enable
dev->usr_wr_num = 0;
dev->tran_flag = 1;
uart3tx_busy = NO;
if(exp_uart[0].wait_flag == 1){
// printk("wake 0\n");
exp_uart[0].wait_flag = 0;
wake_up_interruptible(&expuart1_queue_Wr); //
}
if(exp_uart[1].wait_flag == 1){
// printk("wake 1\n");
exp_uart[1].wait_flag = 0;
wake_up_interruptible(&expuart2_queue_Wr); //
}
if(exp_uart[2].wait_flag == 1){
// printk("wake 2\n");
exp_uart[2].wait_flag = 0;
wake_up_interruptible(&expuart3_queue_Wr); //
}
if(exp_uart[3].wait_flag == 1){
// printk("wake 3\n");
exp_uart[3].wait_flag = 0;
wake_up_interruptible(&expuart4_queue_Wr); //
}
}
goto out;
}
// receive data from uart
if((sub_ssr&1) == 1){
goto out;
}
read_reg(SFSR(expnum), ctn);
ctn = UART_RX & 0x0f;
while(ctn--){
read_reg(SFDR(expnum), dev->rxbuf[dev->Recv_wr_index++]);
if(dev->Recv_wr_index == RX_BUFF_SIZE){
dev->Recv_wr_index = 0;
}
}
dev->recv_flag = uart3_chkbuf(dev->Recv_wr_index,dev->Recv_rd_index,dev->usr_rd_num);
if(dev->recv_flag){
switch(expnum){
case 0:
wake_up_interruptible(&expuart1_queue_Rd); //
break;
case 1:
wake_up_interruptible(&expuart2_queue_Rd); //
break;
case 2:
wake_up_interruptible(&expuart3_queue_Rd); //
break;
case 3:
wake_up_interruptible(&expuart4_queue_Rd); //
break;
default:
break;
}
}
out:
write_reg(gIR,0xf0);
}
ssize_t uart3_read(struct file *flip, char *buf, size_t count, loff_t *offset)
{
int i;
int expnum;
wait_queue_t wait;
uart3_dev *dev = (uart3_dev *)flip->private_data;
expnum = dev->no;
dev->usr_rd_num = count;
dev->recv_flag = uart3_chkbuf(dev->Recv_wr_index,dev->Recv_rd_index,dev->usr_rd_num);
if(dev->recv_flag==0){
init_waitqueue_entry(&wait, current); //
switch(expnum){
case 0:
add_wait_queue(&expuart1_queue_Rd, &wait); //
break;
case 1:
add_wait_queue(&expuart2_queue_Rd, &wait); //
break;
case 2:
add_wait_queue(&expuart3_queue_Rd, &wait); //
break;
case 3:
add_wait_queue(&expuart4_queue_Rd, &wait); //
break;
default:
break;
}
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(dev->time_out*HZ);
set_current_state(TASK_RUNNING);
switch(expnum){
case 0:
remove_wait_queue(&expuart1_queue_Rd, &wait); //
break;
case 1:
remove_wait_queue(&expuart2_queue_Rd, &wait); //
break;
case 2:
remove_wait_queue(&expuart3_queue_Rd, &wait); //
break;
case 3:
remove_wait_queue(&expuart4_queue_Rd, &wait); //
break;
default:
break;
}
}
count = dev->Recv_wr_index - dev->Recv_rd_index;
if(count<0){
count += RX_BUFF_SIZE;
}
if(count > dev->usr_rd_num){
count = dev->usr_rd_num;
}
dev->usr_rd_num = 0;
for(i=0; i<count; i++){
buf[i] = dev->rxbuf[dev->Recv_rd_index];
dev->Recv_rd_index += 1;
if(dev->Recv_rd_index == RX_BUFF_SIZE){
dev->Recv_rd_index = 0;
}
}
return count;
}
static int uart3_write(struct file *flip, int *buf, size_t count, loff_t *offset)
{
int expnum;
wait_queue_t wait;
uart3_dev *dev = (uart3_dev *)flip->private_data;
if(count==0){
return 0;
}
dev->tx_ctn = 0;
expnum = dev->no;
if((dev->tran_flag==1)&&(uart3tx_busy==NO)){
goto copy_data;
}
// printk("wait %d\n",expnum);
dev->wait_flag = 1;
init_waitqueue_entry(&wait, current); //
switch(expnum){
case 0:
add_wait_queue(&expuart1_queue_Wr, &wait); //
break;
case 1:
add_wait_queue(&expuart2_queue_Wr, &wait); //
break;
case 2:
add_wait_queue(&expuart3_queue_Wr, &wait); //
break;
case 3:
add_wait_queue(&expuart4_queue_Wr, &wait); //
break;
default:
break;
}
set_current_state(TASK_INTERRUPTIBLE);
schedule();
set_current_state(TASK_RUNNING);
switch(expnum){
case 0:
remove_wait_queue(&expuart1_queue_Wr, &wait); //
break;
case 1:
remove_wait_queue(&expuart2_queue_Wr, &wait); //
break;
case 2:
remove_wait_queue(&expuart3_queue_Wr, &wait); //
break;
case 3:
remove_wait_queue(&expuart4_queue_Wr, &wait); //
break;
default:
break;
}
copy_data:
if(count > TX_BUFF_SIZE){
count = TX_BUFF_SIZE;
}
copy_to_user(dev->txbuf, buf, count);
dev->tran_flag = 0;
dev->usr_wr_num = count;
uart3tx_busy = YES;
write_reg(SIER(expnum),0x03); // rx and tx interrupt enable
return count;
}
static int uart3_open(struct inode *inode,struct file* flip)
{
int result;
int expnum;
uart3_dev *dev;
expnum = 248 - MAJOR(inode->i_rdev);
dev =(uart3_dev *)(&exp_uart[expnum]);
result = request_irq(nIRQ2, expuart_handler, SA_SHIRQ, dev->dev_name, dev);
if(result == -1){
printk("register the expand uart irq failed!\n");
return -EIO;
}
dev->no = expnum;
dev->Recv_wr_index = 0;
dev->Recv_rd_index = 0;
dev->usr_rd_num = 0;
dev->recv_flag = 0;
dev->time_out = 10;
dev->tx_ctn = 0;
dev->usr_wr_num = 0;
dev->tran_flag = 1;
MOD_INC_USE_COUNT;
flip->private_data = dev;
// default
write_reg(SCTLR(expnum),B1200+0x08); // enable uart
write_reg(SFOCR(expnum),0xff);
write_reg(SFOCR(expnum),0x0c);
write_reg(SIER(expnum),0x01); // rx interrupt enable, tx interrupt disable
return 0;
}
static int uart3_close(struct inode* i,struct file* flip)
{
uart3_dev *dev = (uart3_dev *)flip->private_data;
free_irq(nIRQ2, dev);
MOD_DEC_USE_COUNT;
return 0;
}
static int uart3_ioctl(struct inode *inode, struct file *flip, unsigned int cmd, unsigned long arg)
{
char temp;
int expnum;
uart3_dev *dev = (uart3_dev *)flip->private_data;
expnum = dev->no;
switch(cmd)
{
case CMD_BANDRATE: // set bandrate
if(arg==115200)
temp = B115200;
else if(arg==57600)
temp = B57600;
else if(arg==38400)
temp = B38400;
else if(arg==19200)
temp = B19200;
else if(arg==9600)
temp = B9600;
else if(arg==4800)
temp = B4800;
else if(arg==2400)
temp = B2400;
else if(arg==1200)
temp = B1200;
else
temp = B9600;
// printk("set expuart %d bandrate:%d\n",expnum,arg);
write_reg(SCTLR(expnum),temp+0x08); // enable uart
break;
case CMD_UARTLCR: // set uart
write_reg(SCONR(expnum),(char)arg);
break;
case CMD_RXTIMEOUT:
dev->time_out = arg;
break;
case CMD_MODEM_SET:
if((arg == 1)&&(expnum==2)){
GPIO_DATAOUT5 |= 0x200;
printk("high\n");
}
if((arg == 0)&&(expnum==2)){
GPIO_DATAOUT5 &= 0xFFFFFDFF;
printk("low\n");
}
break;
default:
break;
}
return 0;
}
struct file_operations uart3_fops =
{
owner: THIS_MODULE,
open: uart3_open,
write: uart3_write,
read: uart3_read,
ioctl: uart3_ioctl,
release: uart3_close,
};
static int __init uart3_init(void)
{
int i;
char temp;
int result;
int cfg;
int time,time1;
time1 = 1;
cfg = GPIO_CFG0;
cfg &= 0xFFFFFC00;
cfg |= 0x3C2; // GPIO.0=nIRQ2, GPIO.1=output, GPIO.3=TXD3,GPIO.4=RXD3
GPIO_CFG0 = cfg;
GPIO_DIR0 |= 0x60006;
GPIO_DATAOUT0 |= 0x4;
cfg = GPIO_XICFG;
cfg &= 0xf0;
// cfg |= 0x8; // low level sensitive
cfg |= 0xa; // negative edge triggered
GPIO_XICFG = cfg;
cfg = GPIO_CFG5; // GPIO.14=output
cfg &= 0x3F3FFFF;
GPIO_CFG5 = cfg;
GPIO_DIR5 = 0x2000200;
GPIO_DATAOUT5 |= 0x200;
uart3_int(96); // default 9600
GPIO_DATAOUT0 |= 0x02; // gpio.1=1
ms_delay(10);
GPIO_DATAOUT0 &= ~(0x02); // gpio.1=0
ms_delay(20);
GPIO_DATAOUT0 |= 0x02; // gpio.1=1
write_reg(gIR,0xf0); // enable all uart port interrupt
loop:
uart3_int(96); // default 9600
write_reg(gMUCR,B115200); // main uart bandrate 115200
uart3_int(6); // 230400bps@15MHz
for(i=0;i<time1;i++){
ms_delay(10);
}
time = TIME_OUT;
UART_TX = gMUCR;
do{
temp = UART_LSR;
time--;
if(time == 0){
time1++;
goto loop;
}
}while((temp & LSR_RFDR)!=LSR_RFDR);
temp = UART_RX;
if(temp != B115200){
time1++;
// printk("temp = %02x\n",temp);
goto loop;
}
for(i=0;i<EXPUART_NUM;i++){
result = register_chrdev(exp_uart[i].major_num,exp_uart[i].dev_name,&uart3_fops);
if(result == 0)
printk("expand uart %d at 0xfff80300 (irq = 31) is a W90N745!\n",i+1);
}
uart3tx_busy = NO;
return 0;
}
static void uart3_release(void)
{
int i;
for(i=0;i<EXPUART_NUM;i++){
unregister_chrdev(exp_uart[i].major_num,exp_uart[i].dev_name);
}
printk("cancel the device expand uart1,2,3,4!\n");
return;
}
module_init(uart3_init);
module_exit(uart3_release);

