linux下AT91RM9200红外驱动程序


linux下AT91RM9200红外驱动程序
2009-08-18 11:08
红外适配器型号为HSDL3602,可以实现通讯,但是通讯速度太慢.仅供参考

#include <linux/config.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <linux/slab.h>
#include <linux/rtnetlink.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/ioport.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/platform_device.h>

#include <net/irda/irda.h>
#include <net/irda/irmod.h>
#include <net/irda/wrapper.h>
#include <net/irda/irda_device.h>

#include <asm/irq.h>
#include <asm/io.h>
#include <asm/hardware.h>
#include <asm/serial.h>
#include <asm/mach-types.h>
#include <asm/dma.h>

#include <asm/arch-at91rm9200/board.h>

#include <asm/arch/at91rm9200_usart.h>
#include <asm/arch/at91rm9200_pdc.h>
#include <asm/arch/board.h>
#include <asm/arch/gpio.h>
#include <asm/arch/system.h>

#define SUPPORT_PDC
#define PDC_BUFFER_SIZE   (L1_CACHE_BYTES << 3)
#warning "Revisit"
#define PDC_RX_TIMEOUT   (3 * 10)   /* 3 bytes */

#define UART_PUT_CR(port,v) writel(v, (port)->membase + AT91_US_CR)
#define UART_GET_MR(port) readl((port)->membase + AT91_US_MR)
#define UART_PUT_MR(port,v) writel(v, (port)->membase + AT91_US_MR)
#define UART_PUT_IER(port,v) writel(v, (port)->membase + AT91_US_IER)
#define UART_PUT_IDR(port,v) writel(v, (port)->membase + AT91_US_IDR)
#define UART_GET_IMR(port) readl((port)->membase + AT91_US_IMR)
#define UART_GET_CSR(port) readl((port)->membase + AT91_US_CSR)
#define UART_GET_CHAR(port) readl((port)->membase + AT91_US_RHR)
#define UART_PUT_CHAR(port,v) writel(v, (port)->membase + AT91_US_THR)
#define UART_GET_BRGR(port) readl((port)->membase + AT91_US_BRGR)
#define UART_PUT_BRGR(port,v) writel(v, (port)->membase + AT91_US_BRGR)
#define UART_PUT_RTOR(port,v) writel(v, (port)->membase + AT91_US_RTOR)
#define UART_PUT_IF(port,v)     writel(v, (port)->membase + AT91_US_IF)

/* PDC registers */
#define UART_PUT_PTCR(port,v) writel(v, (port)->membase + AT91_PDC_PTCR)
#define UART_GET_PTSR(port) readl((port)->membase + AT91_PDC_PTSR)

#define UART_PUT_RPR(port,v) writel(v, (port)->membase + AT91_PDC_RPR)
#define UART_GET_RPR(port) readl((port)->membase + AT91_PDC_RPR)
#define UART_PUT_RCR(port,v) writel(v, (port)->membase + AT91_PDC_RCR)
#define UART_PUT_RNPR(port,v) writel(v, (port)->membase + AT91_PDC_RNPR)
#define UART_PUT_RNCR(port,v) writel(v, (port)->membase + AT91_PDC_RNCR)

#define UART_PUT_TPR(port,v) writel(v, (port)->membase + AT91_PDC_TPR)
#define UART_PUT_TCR(port,v) writel(v, (port)->membase + AT91_PDC_TCR)
//#define UART_PUT_TNPR(port,v) writel(v, (port)->membase + AT91_PDC_TNPR)
//#define UART_PUT_TNCR(port,v) writel(v, (port)->membase + AT91_PDC_TNCR)

struct at91_dma_buffer {
unsigned char *buf;
dma_addr_t dma_addr;
size_t   dma_size;
unsigned int ofs;
};

#define SIR_MAX_RXLEN   2047

struct at91_irda_port {
unsigned long mapbase;   /*physical mapping address*/
unsigned char __iomem *membase;   /*remapped address*/
struct clk *clk;        /*irda uart colock*/
unsigned char open;
int transmitting;
int speed;   /* Current IrDA speed */
int new_speed;

spinlock_t lock;

struct net_device_stats stats;
struct irlap_cb *irlap;
struct qos_info qos;
// struct sk_buff *rx_skb;
// struct sk_buff *tx_skb;
iobuff_t rx_buff;
iobuff_t tx_buff;


//short    use_dma_rx; /* enable PDC receiver */
//short    pdc_rx_idx; /* current PDC RX buffer */
//struct at91_dma_buffer pdc_rx[2]; /* PDC receier */

//short    use_dma_tx; /* enable PDC transmitter */
//struct at91_dma_buffer pdc_tx;   /* PDC transmitter */
struct at91_irda_port_data *pdata;
struct device *dev;
};

static void at91_irda_set_speed(struct at91_irda_port *si, int speed);

//Switch Periperial A <-> GPIO
static void at91_tx_io(int tx)
{
   if(tx)
at91_set_A_periph(AT91_PIN_PA23, 0);
   else
        at91_set_gpio_output(AT91_PIN_PA23, 0);
}
/*
* Make Sure Transmitter Shift register is empty.
*/
static u_int at91_irda_tx_empty(struct at91_irda_port *port)
{
int count = 1000; /* 1 ms */

/* Calibrated busy loop */
while((count-- > 0) && !(UART_GET_CSR(port) & AT91_US_TXEMPTY))
   udelay(1);

if(count == 0){
   IRDA_DEBUG(0, "%s(): stuck transmitter\n", __FUNCTION__);
   return -1;
}

return 0;
}

//serial transmit data
static int at91_irda_rx_wakeup(struct net_device *dev)
{
struct at91_irda_port *si = (struct at91_irda_port *)dev->priv;

//printk("rx_wakeup\n");

/* Finished with frame? */
if (si->tx_buff.len > 0) {
   /* Write data left in transmit buffer */
   UART_PUT_CHAR(si,*(si->tx_buff.data));
   si->tx_buff.data += 1;
   si->tx_buff.len -= 1;
} else {
   /*
   * Now serial buffer is almost free & we can start
   * transmission of another packet. But first we must check
   * if we need to change the speed of the hardware
   */
   if (si->new_speed) {
    at91_irda_tx_empty(si);
    at91_irda_set_speed(si,si->new_speed);
    si->new_speed = 0;
   } else {
    /* Tell network layer that we want more frames */
    at91_irda_tx_empty(si);
    netif_wake_queue(dev);
   }
   si->stats.tx_packets++;
   at91_irda_tx_empty(si);

   
   /* Finished transmitting */
   si->transmitting = 0;

   /* Turn on receive interrupts and disable transimitter*/
   UART_PUT_CR(si, AT91_US_RSTSTA);
   UART_PUT_CR(si,AT91_US_RSTTX | AT91_US_RSTRX);
   //UART_PUT_IDR(si, AT91_US_TXRDY);
   UART_PUT_CR(si,AT91_US_TXDIS);
       at91_tx_io(0);
      UART_PUT_CR(si,AT91_US_RXEN);
   UART_PUT_IER(si, AT91_US_RXRDY);

   IRDA_DEBUG(1, "%s() : finished Tx\n", __FUNCTION__);
}

}
/*
* Function at91_irda_rx (netdev)
*
*    Receive one frame from the infrared port
*
*    Called only from at91_irda_interrupt()
*/
static inline void at91_irda_rx(struct net_device *dev)
{
struct at91_irda_port *si = (struct at91_irda_port *)dev->priv;
int boguscount = 0;

IRDA_ASSERT(dev != NULL, return;);

// IRDA_DEBUG(1, "%s() : Rx\n", __FUNCTION__);

/*
* Receive all characters in Rx FIFO, unwrap and unstuff them.
         * async_unwrap_char will deliver all found frames
*/
do {
   async_unwrap_char(dev, &si->stats, &si->rx_buff,
      UART_GET_CHAR(si));
 
   /* Make sure we don't stay here too long */
   if (boguscount++ > 32) {
    IRDA_DEBUG(2,"%s(), breaking!\n", __FUNCTION__);
    break;
   }
} while (UART_GET_CSR(si) & AT91_US_RXRDY);
}

/* interrupt handler
* at91_irda_interrupt(irq,dev_id,regs)
*/

static irqreturn_t at91_irda_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
struct net_device *dev = (struct net_device *)dev_id;
struct at91_irda_port *si = (struct at91_irda_port *)dev->priv;
unsigned int status,pending;

/* Get interrupt status */

status = UART_GET_CSR(si);
pending = status & UART_GET_IMR(si);
while (pending){
 
   /* Are we receiving or transmitting ? */
   if(!si->transmitting) {
    /* Received something ? */
    if (pending & AT91_US_RXRDY)
     at91_irda_rx(dev);
   } else {
    /* Room in Tx fifo ? */
    if (pending & AT91_US_TXRDY)
     at91_irda_rx_wakeup(dev);
   }

   /* Read interrupt register */
          status = UART_GET_CSR(si);
   pending = status & UART_GET_IMR(si);
}

return IRQ_HANDLED;
}

/*
* Function at91_irda_hard_xmit (struct sk_buff *skb, struct net_device *dev)
*
*    Transmits the current frame until FIFO is full, then
*    waits until the next transmitt interrupt, and continues until the
*    frame is transmitted.
*/
static int at91_irda_hard_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct at91_irda_port *si;
unsigned long flags;
s32 speed;

IRDA_DEBUG(1, "%s()\n", __FUNCTION__);

si = (struct at91_irda_port *) dev->priv;

netif_stop_queue(dev);

/* Make sure tests & speed change are atomic */
spin_lock_irqsave(&si->lock, flags);
/* Check if we need to change the speed */
speed = irda_get_next_speed(skb);

/*
* Does this packet contain a request to change the interface
* speed? If so, remember it until we complete the transmission
* of this frame.
*/
if (speed != si->speed && speed != -1)
   si->new_speed = speed;

/*
* If this is an empty frame, we can bypass a lot.
*/
if (skb->len == 0) {
   if (si->new_speed) {
    si->new_speed = 0;
    at91_irda_set_speed(si, speed);
   }
   spin_unlock_irqrestore(&si->lock, flags);
   dev_kfree_skb(skb);
   return 0;
}

/* Init tx buffer */
si->tx_buff.data = si->tx_buff.head;

        /* Copy skb to tx_buff while wrapping, stuffing and making CRC */
si->tx_buff.len = async_wrap_skb(skb, si->tx_buff.data,
        si->tx_buff.truesize);

si->stats.tx_bytes += si->tx_buff.len;

/* We are transmitting */
si->transmitting = 1;

/* Turn on transmit finished interrupt. Will fire immediately! */
at91_tx_io(1);
UART_PUT_CR(si, AT91_US_RSTSTA);
UART_PUT_CR(si,AT91_US_RSTTX | AT91_US_RSTRX);
UART_PUT_CR(si,AT91_US_RXDIS);
UART_PUT_CR(si,AT91_US_TXEN);
       UART_PUT_IER(si, AT91_US_TXRDY);

dev->trans_start = jiffies;

spin_unlock_irqrestore(&si->lock, flags);

dev_kfree_skb(skb);

return 0;
}

static struct net_device_stats *at91_irda_stats(struct net_device *dev)
{
struct at91_irda_port *si = netdev_priv(dev);
return &si->stats;
}

static void at91_irda_shutdown(struct at91_irda_port *si)
{
UART_PUT_IDR(si, -1);
UART_PUT_CR(si, AT91_US_RSTSTA);
UART_PUT_CR(si, AT91_US_TXDIS | AT91_US_RXDIS);
si->transmitting = 0;
}
static void at91_irda_startup(struct at91_irda_port *si)
{
at91_irda_shutdown(si);
UART_PUT_MR(si,AT91_US_USMODE_IRDA | AT91_US_CHRL_8 | AT91_US_PAR_NONE | AT91_US_NBSTOP_1
    | AT91_US_CHMODE_NORMAL);
UART_PUT_IF(si,10);
UART_PUT_CR(si, AT91_US_TXEN | AT91_US_RXEN);

}
/*
* Change the port parameters
*/
static void at91_irda_set_speed(struct at91_irda_port *si, int speed)
{
unsigned long quot;
unsigned long imr,flags;
struct clk *main_clk;
unsigned int at91_master_clock;

main_clk=clk_get(NULL,"mck");
at91_master_clock=clk_get_rate(main_clk);

/* first, disable interrupts and drain transmitter */
local_irq_save(flags);
//imr = UART_GET_IMR(si); /* get interrupt mask */
//UART_PUT_IDR(si, -1);   /* disable all interrupts */
//while (!(UART_GET_CSR(si) & AT91_US_TXEMPTY)) { barrier(); }

/* disable receiver and transmitter */
UART_PUT_CR(si, AT91_US_TXDIS | AT91_US_RXDIS);

/* set the baud rate */
        if (speed == 0)
           quot = 0;
        else
           quot=(at91_master_clock + (8 * speed)) / (16 * speed);

si->speed = speed;
UART_PUT_BRGR(si, quot);
//UART_PUT_CR(si, AT91_US_TXEN | AT91_US_RXEN);//soft lockup

/* restore interrupts */
//UART_PUT_IER(si, imr);
local_irq_restore(flags);
}

static int at91_irda_ioctl(struct net_device *dev, struct ifreq *ifreq, int cmd)
{
struct if_irda_req *rq = (struct if_irda_req *)ifreq;
struct at91_irda_port *si = netdev_priv(dev);
int ret;

switch (cmd) {
case SIOCSBANDWIDTH:
   ret = -EPERM;
   if (capable(CAP_NET_ADMIN)) {
    /*
    * We are unable to set the speed if the
    * device is not running.
    */
    if (netif_running(dev)) {
     at91_irda_set_speed(si, rq->ifr_baudrate);
     ret = 0;
    } else {
     printk(KERN_INFO "at91_ir: SIOCSBANDWIDTH: !netif_running\n");
     ret = 0;
    }
   }
   break;

case SIOCSMEDIABUSY:
   ret = -EPERM;
   if (capable(CAP_NET_ADMIN)) {
    irda_device_set_media_busy(dev, TRUE);
    ret = 0;
   }
   break;

case SIOCGRECEIVING:
   ret = 0;
   rq->ifr_receiving = (si->rx_buff.state != OUTSIDE_FRAME);
   break;

default:
   ret = -EOPNOTSUPP;
   break;
}

return ret;
}

/*
* Function at91_irda_timeout (struct net_device *dev)
*
*    The networking layer thinks we timed out.
*
*/

static void at91_irda_timeout(struct net_device *dev)
{
struct at91_irda_port *si = dev->priv;
unsigned long flags;

IRDA_WARNING("%s: transmit timed out, jiffies = %ld, trans_start = %ld\n",
   dev->name, jiffies, dev->trans_start);
spin_lock_irqsave(&si->lock, flags);

/* Debug what's happening */
IRDA_DEBUG(0, "%s(), transmitting=%d, remain=%d, done=%d\n",
     __FUNCTION__, si->transmitting, si->tx_buff.len,
     si->tx_buff.data - si->tx_buff.head);

/* Now, restart the port */
at91_irda_startup(si);
//change speed

at91_irda_set_speed(si,si->speed);
/* This will re-enable irqs */

//UART_PUT_IER(si, AT91_US_RXRDY | AT91_US_TXRDY); //soft lockup
dev->trans_start = jiffies;
spin_unlock_irqrestore(&si->lock, flags);

netif_wake_queue(dev);
}

static int at91_irda_start(struct net_device *dev)
{
struct at91_irda_port *si = dev->priv;
int err;

si->speed = 9600;

err = request_irq(dev->irq, at91_irda_interrupt, 0, "at91_irda", dev);
if (err)
   goto err_irq;

/*
* The interrupt must remain disabled for now.
*/
//UART_PUT_IDR(si, -1);

/* Setup the serial port for the initial speed. */
at91_irda_startup(si);
at91_irda_set_speed(si,9600);
IRDA_DEBUG(0,"open irlap\n");
/*
* Open a new IrLAP layer instance.
*/
si->irlap = irlap_open(dev, &si->qos, "at91_ir");
err = -ENOMEM;
if (!si->irlap){
   printk("open return NULL\n");
   goto err_irlap;
}
/*
* Now enable the interrupt and start the queue
*/
UART_PUT_CR(si, AT91_US_TXDIS);
at91_tx_io(0);
UART_PUT_IER(si, /*AT91_US_TXRDY |*/ AT91_US_RXRDY);
si->open = 1;
netif_start_queue(dev);

printk("at91_ir: irda driver opened\n");

return 0;

err_irlap:
si->open = 0;
at91_irda_shutdown(si);
free_irq(dev->irq,dev);
err_irq:

return err;

}
/*
* Function at91_irda_close (dev)
*
*    Network device is taken down. Usually this is done by
*    "ifconfig irda0 down"
*/
static int at91_irda_stop(struct net_device *dev)
{
struct at91_irda_port *si = dev->priv;
unsigned long flags;

IRDA_DEBUG(4, "%s()\n", __FUNCTION__);

si->open = 0;

/* Stop device */
netif_stop_queue(dev);

/* Stop and remove instance of IrLAP */
if (si->irlap)
   irlap_close(si->irlap);
si->irlap = NULL;

spin_lock_irqsave(&si->lock, flags);
at91_irda_shutdown(si);
spin_unlock_irqrestore(&si->lock, flags);

free_irq(dev->irq, dev);

return 0;
}
#ifdef CONFIG_PM
static int at91_irda_suspend(struct platform_device *pdev, pm_message_t state)
{
struct net_device *dev = platform_get_drvdata(pdev);
struct at91_irda_port *si = dev->priv;

if (!dev)
   return 0;

if (si->open) {
   /*
   * Stop the transmit queue
   */
   netif_device_detach(dev);
   disable_irq(dev->irq);
   at91_irda_shutdown(si);
}

return 0;
}

static int at91_irda_resume(struct platform_device *pdev)
{
struct net_device *dev = platform_get_drvdata(pdev);
struct at91_irda_port *si = dev->priv;

if (!dev)
   return 0;

if (si->open) {
   /*
   * If we missed a speed change, initialise at the new speed
   * directly. It is debatable whether this is actually
   * required, but in the interests of continuing from where
   * we left off it is desireable. The converse argument is
   * that we should re-negotiate at 9600 baud again.
   */
   if (si->new_speed) {
    si->speed = si->new_speed;
    si->new_speed = 0;
   }

   at91_irda_startup(si);
   at91_irda_set_speed(si, si->speed);
   enable_irq(dev->irq);

   /*
   * This automatically wakes up the queue
   */
   netif_device_attach(dev);
}
 
return 0;
}
#else
#define at91_serial_suspend NULL
#define at91_serial_resume NULL
#endif

static int at91_irda_init_iobuf(iobuff_t *io, int size)
{
io->head = kmalloc(size, GFP_KERNEL | GFP_DMA);
if (io->head != NULL) {
   io->truesize = size;
   io->in_frame = FALSE;
   io->state    = OUTSIDE_FRAME;
   io->data     = io->head;
}
return io->head ? 0 : -ENOMEM;
}

static int at91_irda_probe(struct platform_device *pdev)
{
struct net_device *dev;
struct at91_irda_port *si;
struct at91_irda_port_data *pdata = pdev->dev.platform_data;
unsigned int baudrate_mask;
int err = 0;
int irq = NO_IRQ;
int size;

if (!pdata) {
   printk(KERN_ERR "IrDA Platform data not supplied\n");
   return -ENOENT;
}

dev = alloc_irdadev(sizeof(struct at91_irda_port));
if (!dev){
   err = -EBUSY;
   goto erro_1;
}

si = dev->priv;
si->dev = &pdev->dev;
si->pdata = pdata;

memset(si,0,sizeof(struct at91_irda_port));
si->mapbase = pdev->resource[0].start;
size = pdev->resource[0].end - pdev->resource[0].start + 1;

if (!request_mem_region(si->mapbase, size, "at91_irda")){
   err = -EBUSY;
   goto erro_2;
}

si->membase = ioremap(si->mapbase, size);
if (si->membase == NULL) {
   err = -EBUSY;
   goto erro_3;
}
 
irq = platform_get_irq(pdev, 0);
if (irq <= 0) {
   printk(KERN_WARNING "no irq for IrDA\n");
   err = -ENOENT;
   goto erro_4;
}

if(!si->clk){
   si->clk = clk_get(&pdev->dev,"usart");
   clk_enable(si->clk);
}
/*
* Initialise the SIR buffers
*/

si->rx_buff.truesize = IRDA_SKB_MAX_MTU;
si->rx_buff.skb = __dev_alloc_skb(si->rx_buff.truesize,
         GFP_KERNEL);

if (si->rx_buff.skb == NULL) {
   printk(KERN_ERR "at91_ir: out of memory for RX SKB\n");
   err = -ENOMEM;
   goto erro_4;
}
/*
* Align any IP headers that may be contained
* within the frame.
*/
skb_reserve(si->rx_buff.skb, 1);

si->rx_buff.head = si->rx_buff.skb->data;
/* No need to memset the buffer, unless you are really pedantic */

/* Finish setup the Rx buffer descriptor */
si->rx_buff.in_frame = FALSE;
si->rx_buff.state = OUTSIDE_FRAME;
si->rx_buff.data = si->rx_buff.head;

   err = at91_irda_init_iobuf(&si->tx_buff, 4000);
if (err)
   goto erro_5;
dev->hard_start_xmit = at91_irda_hard_xmit;
dev->open   = at91_irda_start;
dev->stop   = at91_irda_stop;
dev->do_ioctl   = at91_irda_ioctl;
dev->get_stats   = at91_irda_stats;
dev->watchdog_timeo = HZ; /* Allow time enough for speed change */
dev->tx_timeout         = at91_irda_timeout;
dev->irq   = irq;

irda_init_max_qos_capabilies(&si->qos);

baudrate_mask = 0;
baudrate_mask |= IR_9600|IR_19200|IR_38400|IR_57600;

si->qos.baud_rate.bits &= baudrate_mask;
si->qos.min_turn_time.bits = 7;

irda_qos_bits_to_value(&si->qos);

err = register_netdev(dev);
if (!err){
   IRDA_MESSAGE("IrDA: Registered device %s ",dev->name);
   platform_set_drvdata(pdev, dev);
}else{
   kfree(si->tx_buff.head);
erro_5:
   kfree_skb(si->rx_buff.skb);
erro_4:
   iounmap((void *)si->membase);
erro_3:
   release_mem_region(si->mapbase, size);
erro_2:
   free_netdev(dev);
}
erro_1:
return err;

}
static int at91_irda_remove(struct platform_device *pdev)
{
struct net_device *dev = platform_get_drvdata(pdev);
struct at91_irda_port *si = dev->priv;
int size;

size = pdev->resource[0].end - pdev->resource[0].start + 1;

unregister_netdev(dev);

if(si->rx_buff.skb)
   kfree_skb(si->rx_buff.skb);
kfree(si->tx_buff.head);

clk_disable(si->clk);
clk_put(si->clk);

iounmap((void *)si->membase);
release_mem_region(si->mapbase,size);

free_netdev(dev);
platform_set_drvdata(pdev, NULL);

return 0;
}

static struct platform_driver at91_irda_driver = {
.probe = at91_irda_probe,
.remove = at91_irda_remove,
.suspend = at91_irda_suspend,
.resume = at91_irda_resume,
.driver = {
   .name = "at91_irda",
},
};

static int __init at91_irda_init(void)
{
printk(KERN_INFO "at91rm9200 irda driver loaded");

return platform_driver_register(&at91_irda_driver);

}
static void __exit at91_irda_exit(void)
{
platform_driver_unregister(&at91_irda_driver);
}

module_init(at91_irda_init);
module_exit(at91_irda_exit);

MODULE_AUTHOR("stone");
MODULE_DESCRIPTION("at91rm9200 irda driver");
MODULE_LICENSE("GPL");

posted @ 2009-12-31 07:57  clara  阅读(1374)  评论(0编辑  收藏  举报