📄 irport.c
字号:
/********************************************************************* * * Filename: irport.c * Version: 1.0 * Description: Half duplex serial port SIR driver for IrDA. * Status: Experimental. * Author: Dag Brattli <dagb@cs.uit.no> * Created at: Sun Aug 3 13:49:59 1997 * Modified at: Fri Jan 28 20:22:38 2000 * Modified by: Dag Brattli <dagb@cs.uit.no> * Sources: serial.c by Linus Torvalds * * Copyright (c) 1997, 1998, 1999-2000 Dag Brattli, All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * * This driver is ment to be a small half duplex serial driver to be * used for IR-chipsets that has a UART (16550) compatibility mode. * Eventually it will replace irtty, because of irtty has some * problems that is hard to get around when we don't have control * over the serial driver. This driver may also be used by FIR * drivers to handle SIR mode for them. * ********************************************************************/#include <linux/module.h>#include <linux/kernel.h>#include <linux/types.h>#include <linux/ioport.h>#include <linux/malloc.h>#include <linux/string.h>#include <linux/skbuff.h>#include <linux/serial_reg.h>#include <linux/errno.h>#include <linux/init.h>#include <linux/spinlock.h>#include <linux/rtnetlink.h>#include <asm/system.h>#include <asm/bitops.h>#include <asm/io.h>#include <net/irda/irda.h>#include <net/irda/irmod.h>#include <net/irda/wrapper.h>#include <net/irda/irport.h>#define IO_EXTENT 8/* * Currently you'll need to set these values using insmod like this: * insmod irport io=0x3e8 irq=11 */static unsigned int io[] = { ~0, ~0, ~0, ~0 };static unsigned int irq[] = { 0, 0, 0, 0 };static unsigned int qos_mtt_bits = 0x03;static struct irport_cb *dev_self[] = { NULL, NULL, NULL, NULL};static char *driver_name = "irport";static void irport_write_wakeup(struct irport_cb *self);static int irport_write(int iobase, int fifo_size, __u8 *buf, int len);static void irport_receive(struct irport_cb *self);static int irport_net_init(struct net_device *dev);static int irport_net_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);static int irport_is_receiving(struct irport_cb *self);static int irport_set_dtr_rts(struct net_device *dev, int dtr, int rts);static int irport_raw_write(struct net_device *dev, __u8 *buf, int len);static struct net_device_stats *irport_net_get_stats(struct net_device *dev);static int irport_change_speed_complete(struct irda_task *task);static void irport_timeout(struct net_device *dev);EXPORT_SYMBOL(irport_open);EXPORT_SYMBOL(irport_close);EXPORT_SYMBOL(irport_start);EXPORT_SYMBOL(irport_stop);EXPORT_SYMBOL(irport_interrupt);EXPORT_SYMBOL(irport_hard_xmit);EXPORT_SYMBOL(irport_timeout);EXPORT_SYMBOL(irport_change_speed);EXPORT_SYMBOL(irport_net_open);EXPORT_SYMBOL(irport_net_close);int __init irport_init(void){ int i; for (i=0; (io[i] < 2000) && (i < 4); i++) { int ioaddr = io[i]; if (check_region(ioaddr, IO_EXTENT)) continue; if (irport_open(i, io[i], irq[i]) != NULL) return 0; } /* * Maybe something failed, but we can still be usable for FIR drivers */ return 0;}/* * Function irport_cleanup () * * Close all configured ports * */#ifdef MODULEstatic void irport_cleanup(void){ int i; IRDA_DEBUG( 4, __FUNCTION__ "()\n"); for (i=0; i < 4; i++) { if (dev_self[i]) irport_close(dev_self[i]); }}#endif /* MODULE */struct irport_cb *irport_open(int i, unsigned int iobase, unsigned int irq){ struct net_device *dev; struct irport_cb *self; int ret; int err; IRDA_DEBUG(0, __FUNCTION__ "()\n"); /* * Allocate new instance of the driver */ self = kmalloc(sizeof(struct irport_cb), GFP_KERNEL); if (!self) { ERROR(__FUNCTION__ "(), can't allocate memory for " "control block!\n"); return NULL; } memset(self, 0, sizeof(struct irport_cb)); spin_lock_init(&self->lock); /* Need to store self somewhere */ dev_self[i] = self; self->priv = self; self->index = i; /* Initialize IO */ self->io.sir_base = iobase; self->io.sir_ext = IO_EXTENT; self->io.irq = irq; self->io.fifo_size = 16; /* Lock the port that we need */ ret = check_region(self->io.sir_base, self->io.sir_ext); if (ret < 0) { IRDA_DEBUG(0, __FUNCTION__ "(), can't get iobase of 0x%03x\n", self->io.sir_base); return NULL; } request_region(self->io.sir_base, self->io.sir_ext, driver_name); /* Initialize QoS for this device */ irda_init_max_qos_capabilies(&self->qos); self->qos.baud_rate.bits = IR_9600|IR_19200|IR_38400|IR_57600| IR_115200; self->qos.min_turn_time.bits = qos_mtt_bits; irda_qos_bits_to_value(&self->qos); self->flags = IFF_SIR|IFF_PIO; /* Specify how much memory we want */ self->rx_buff.truesize = 4000; self->tx_buff.truesize = 4000; /* Allocate memory if needed */ if (self->rx_buff.truesize > 0) { self->rx_buff.head = (__u8 *) kmalloc(self->rx_buff.truesize, GFP_KERNEL); if (self->rx_buff.head == NULL) return NULL; memset(self->rx_buff.head, 0, self->rx_buff.truesize); } if (self->tx_buff.truesize > 0) { self->tx_buff.head = (__u8 *) kmalloc(self->tx_buff.truesize, GFP_KERNEL); if (self->tx_buff.head == NULL) { kfree(self->rx_buff.head); return NULL; } memset(self->tx_buff.head, 0, self->tx_buff.truesize); } self->rx_buff.in_frame = FALSE; self->rx_buff.state = OUTSIDE_FRAME; self->tx_buff.data = self->tx_buff.head; self->rx_buff.data = self->rx_buff.head; self->mode = IRDA_IRLAP; if (!(dev = dev_alloc("irda%d", &err))) { ERROR(__FUNCTION__ "(), dev_alloc() failed!\n"); return NULL; } self->netdev = dev; /* May be overridden by piggyback drivers */ dev->priv = (void *) self; self->interrupt = irport_interrupt; self->change_speed = irport_change_speed; /* Override the network functions we need to use */ dev->init = irport_net_init; dev->hard_start_xmit = irport_hard_xmit; dev->tx_timeout = irport_timeout; dev->watchdog_timeo = HZ; /* Allow time enough for speed change */ dev->open = irport_net_open; dev->stop = irport_net_close; dev->get_stats = irport_net_get_stats; dev->do_ioctl = irport_net_ioctl; /* Make ifconfig display some details */ dev->base_addr = iobase; dev->irq = irq; rtnl_lock(); err = register_netdevice(dev); rtnl_unlock(); if (err) { ERROR(__FUNCTION__ "(), register_netdev() failed!\n"); return NULL; } MESSAGE("IrDA: Registered device %s\n", dev->name); return self;}int irport_close(struct irport_cb *self){ ASSERT(self != NULL, return -1;); /* We are not using any dongle anymore! */ if (self->dongle) irda_device_dongle_cleanup(self->dongle); self->dongle = NULL; /* Remove netdevice */ if (self->netdev) { rtnl_lock(); unregister_netdevice(self->netdev); rtnl_unlock(); } /* Release the IO-port that this driver is using */ IRDA_DEBUG(0 , __FUNCTION__ "(), Releasing Region %03x\n", self->io.sir_base); release_region(self->io.sir_base, self->io.sir_ext); if (self->tx_buff.head) kfree(self->tx_buff.head); if (self->rx_buff.head) kfree(self->rx_buff.head); /* Remove ourselves */ dev_self[self->index] = NULL; kfree(self); return 0;}void irport_start(struct irport_cb *self){ unsigned long flags; int iobase; iobase = self->io.sir_base; spin_lock_irqsave(&self->lock, flags); irport_stop(self); /* Initialize UART */ outb(UART_LCR_WLEN8, iobase+UART_LCR); /* Reset DLAB */ outb((UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2), iobase+UART_MCR); /* Turn on interrups */ outb(UART_IER_RLSI | UART_IER_RDI |UART_IER_THRI, iobase+UART_IER); spin_unlock_irqrestore(&self->lock, flags);}void irport_stop(struct irport_cb *self){ unsigned long flags; int iobase; iobase = self->io.sir_base; spin_lock_irqsave(&self->lock, flags); /* Reset UART */ outb(0, iobase+UART_MCR); /* Turn off interrupts */ outb(0, iobase+UART_IER); spin_unlock_irqrestore(&self->lock, flags);}/* * Function irport_probe (void) * * Start IO port * */int irport_probe(int iobase){ IRDA_DEBUG(4, __FUNCTION__ "(), iobase=%#x\n", iobase); return 0;}/* * Function irport_change_speed (self, speed) * * Set speed of IrDA port to specified baudrate * */void irport_change_speed(void *priv, __u32 speed){ struct irport_cb *self = (struct irport_cb *) priv; unsigned long flags; int iobase; int fcr; /* FIFO control reg */ int lcr; /* Line control reg */ int divisor; IRDA_DEBUG(2, __FUNCTION__ "(), Setting speed to: %d\n", speed); ASSERT(self != NULL, return;); iobase = self->io.sir_base; /* Update accounting for new speed */ self->io.speed = speed; spin_lock_irqsave(&self->lock, flags); /* Turn off interrupts */ outb(0, iobase+UART_IER); divisor = SPEED_MAX/speed; fcr = UART_FCR_ENABLE_FIFO; /* * Use trigger level 1 to avoid 3 ms. timeout delay at 9600 bps, and * almost 1,7 ms at 19200 bps. At speeds above that we can just forget * about this timeout since it will always be fast enough. */ if (self->io.speed < 38400) fcr |= UART_FCR_TRIGGER_1; else fcr |= UART_FCR_TRIGGER_14; /* IrDA ports use 8N1 */ lcr = UART_LCR_WLEN8; outb(UART_LCR_DLAB | lcr, iobase+UART_LCR); /* Set DLAB */ outb(divisor & 0xff, iobase+UART_DLL); /* Set speed */ outb(divisor >> 8, iobase+UART_DLM); outb(lcr, iobase+UART_LCR); /* Set 8N1 */ outb(fcr, iobase+UART_FCR); /* Enable FIFO's */ /* Turn on interrups */ outb(/*UART_IER_RLSI|*/UART_IER_RDI/*|UART_IER_THRI*/, iobase+UART_IER); spin_unlock_irqrestore(&self->lock, flags);}/* * Function __irport_change_speed (instance, state, param) * * State machine for changing speed of the device. We do it this way since * we cannot use schedule_timeout() when we are in interrupt context */int __irport_change_speed(struct irda_task *task){ struct irport_cb *self; __u32 speed = (__u32) task->param; int ret = 0; IRDA_DEBUG(2, __FUNCTION__ "(), <%ld>\n", jiffies); self = (struct irport_cb *) task->instance; ASSERT(self != NULL, return -1;); switch (task->state) { case IRDA_TASK_INIT: case IRDA_TASK_WAIT: /* Are we ready to change speed yet? */ if (self->tx_buff.len > 0) { task->state = IRDA_TASK_WAIT; /* Try again later */ ret = MSECS_TO_JIFFIES(20); break; } if (self->dongle) irda_task_next_state(task, IRDA_TASK_CHILD_INIT); else irda_task_next_state(task, IRDA_TASK_CHILD_DONE); break; case IRDA_TASK_CHILD_INIT: /* Go to default speed */ self->change_speed(self->priv, 9600); /* Change speed of dongle */ if (irda_task_execute(self->dongle, self->dongle->issue->change_speed, NULL, task, (void *) speed)) { /* Dongle need more time to change its speed */ irda_task_next_state(task, IRDA_TASK_CHILD_WAIT); /* Give dongle 1 sec to finish */ ret = MSECS_TO_JIFFIES(1000); } else /* Child finished immediately */ irda_task_next_state(task, IRDA_TASK_CHILD_DONE); break; case IRDA_TASK_CHILD_WAIT: WARNING(__FUNCTION__ "(), changing speed of dongle timed out!\n"); ret = -1; break; case IRDA_TASK_CHILD_DONE: /* Finally we are ready to change the speed */ self->change_speed(self->priv, speed); irda_task_next_state(task, IRDA_TASK_DONE); break; default: ERROR(__FUNCTION__ "(), unknown state %d\n", task->state); irda_task_next_state(task, IRDA_TASK_DONE); ret = -1; break; } return ret;}/* * Function irport_write_wakeup (tty) * * Called by the driver when there's room for more data. If we have * more packets to send, we send them here. * */static void irport_write_wakeup(struct irport_cb *self){ int actual = 0; int iobase; int fcr; ASSERT(self != NULL, return;); IRDA_DEBUG(4, __FUNCTION__ "()\n"); iobase = self->io.sir_base; /* Finished with frame? */ if (self->tx_buff.len > 0) { /* Write data left in transmit buffer */ actual = irport_write(iobase, self->io.fifo_size, self->tx_buff.data, self->tx_buff.len); self->tx_buff.data += actual; self->tx_buff.len -= actual; } 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 (self->new_speed) { IRDA_DEBUG(5, __FUNCTION__ "(), Changing speed!\n"); irda_task_execute(self, __irport_change_speed, irport_change_speed_complete, NULL, (void *) self->new_speed); self->new_speed = 0; } else { /* Tell network layer that we want more frames */ netif_wake_queue(self->netdev); } self->stats.tx_packets++;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -