显示标签为“Uart3”的博文。显示所有博文
显示标签为“Uart3”的博文。显示所有博文

星期一, 十二月 03, 2007

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

Read More...