/********************************************************************* * * Filename: irport.c * Version: 1.0 * Description: Half duplex serial port SIR driver for IrDA. * Status: Experimental. * Author: Dag Brattli * Created at: Sun Aug 3 13:49:59 1997 * Modified at: Fri Jan 28 20:22:38 2000 * Modified by: Dag Brattli * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 MODULE static 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/20; 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++; /* * Reset Rx FIFO to make sure that all reflected transmit data * is discarded. This is needed for half duplex operation */ fcr = UART_FCR_ENABLE_FIFO | UART_FCR_CLEAR_RCVR; if (self->io.speed < 38400) fcr |= UART_FCR_TRIGGER_1; else fcr |= UART_FCR_TRIGGER_14; outb(fcr, iobase+UART_FCR); /* Turn on receive interrupts */ outb(UART_IER_RDI, iobase+UART_IER); } } /* * Function irport_write (driver) * * Fill Tx FIFO with transmit data * */ static int irport_write(int iobase, int fifo_size, __u8 *buf, int len) { int actual = 0; /* Tx FIFO should be empty! */ if (!(inb(iobase+UART_LSR) & UART_LSR_THRE)) { IRDA_DEBUG(0, __FUNCTION__ "(), failed, fifo not empty!\n"); return 0; } /* Fill FIFO with current frame */ while ((fifo_size-- > 0) && (actual < len)) { /* Transmit next byte */ outb(buf[actual], iobase+UART_TX); actual++; } return actual; } /* * Function irport_change_speed_complete (task) * * Called when the change speed operation completes * */ static int irport_change_speed_complete(struct irda_task *task) { struct irport_cb *self; IRDA_DEBUG(0, __FUNCTION__ "()\n"); self = (struct irport_cb *) task->instance; ASSERT(self != NULL, return -1;); ASSERT(self->netdev != NULL, return -1;); /* Finished changing speed, so we are not busy any longer */ /* Signal network layer so it can try to send the frame */ netif_wake_queue(self->netdev); return 0; } /* * Function irport_timeout (struct net_device *dev) * * The networking layer thinks we timed out. * */ static void irport_timeout(struct net_device *dev) { struct irport_cb *self; int iobase; self = (struct irport_cb *) dev->priv; iobase = self->io.sir_base; WARNING("%s: transmit timed out\n", dev->name); irport_start(self); self->change_speed(self->priv, self->io.speed); dev->trans_start = jiffies; netif_wake_queue(dev); } /* * Function irport_hard_start_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 transmited. */ int irport_hard_xmit(struct sk_buff *skb, struct net_device *dev) { struct irport_cb *self; unsigned long flags; int iobase; __u32 speed; ASSERT(dev != NULL, return 0;); self = (struct irport_cb *) dev->priv; ASSERT(self != NULL, return 0;); iobase = self->io.sir_base; netif_stop_queue(dev); /* Check if we need to change the speed */ if ((speed = irda_get_speed(skb)) != self->io.speed) self->new_speed = speed; spin_lock_irqsave(&self->lock, flags); /* Init tx buffer */ self->tx_buff.data = self->tx_buff.head; /* Copy skb to tx_buff while wrapping, stuffing and making CRC */ self->tx_buff.len = async_wrap_skb(skb, self->tx_buff.data, self->tx_buff.truesize); self->stats.tx_bytes += self->tx_buff.len; /* Turn on transmit finished interrupt. Will fire immediately! */ outb(UART_IER_THRI, iobase+UART_IER); spin_unlock_irqrestore(&self->lock, flags); dev_kfree_skb(skb); return 0; } /* * Function irport_receive (self) * * Receive one frame from the infrared port * */ static void irport_receive(struct irport_cb *self) { int boguscount = 0; int iobase; ASSERT(self != NULL, return;); iobase = self->io.sir_base; /* * Receive all characters in Rx FIFO, unwrap and unstuff them. * async_unwrap_char will deliver all found frames */ do { async_unwrap_char(self->netdev, &self->stats, &self->rx_buff, inb(iobase+UART_RX)); /* Make sure we don't stay here to long */ if (boguscount++ > 32) { IRDA_DEBUG(2,__FUNCTION__ "(), breaking!\n"); break; } } while (inb(iobase+UART_LSR) & UART_LSR_DR); } /* * Function irport_interrupt (irq, dev_id, regs) * * Interrupt handler */ void irport_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct net_device *dev = (struct net_device *) dev_id; struct irport_cb *self; int boguscount = 0; int iobase; int iir, lsr; if (!dev) { WARNING(__FUNCTION__ "() irq %d for unknown device.\n", irq); return; } self = (struct irport_cb *) dev->priv; spin_lock(&self->lock); iobase = self->io.sir_base; iir = inb(iobase+UART_IIR) & UART_IIR_ID; while (iir) { /* Clear interrupt */ lsr = inb(iobase+UART_LSR); IRDA_DEBUG(4, __FUNCTION__ "(), iir=%02x, lsr=%02x, iobase=%#x\n", iir, lsr, iobase); switch (iir) { case UART_IIR_RLSI: IRDA_DEBUG(2, __FUNCTION__ "(), RLSI\n"); break; case UART_IIR_RDI: /* Receive interrupt */ irport_receive(self); break; case UART_IIR_THRI: if (lsr & UART_LSR_THRE) /* Transmitter ready for data */ irport_write_wakeup(self); break; default: IRDA_DEBUG(0, __FUNCTION__ "(), unhandled IIR=%#x\n", iir); break; } /* Make sure we don't stay here to long */ if (boguscount++ > 100) break; iir = inb(iobase + UART_IIR) & UART_IIR_ID; } spin_unlock(&self->lock); } static int irport_net_init(struct net_device *dev) { /* Set up to be a normal IrDA network device driver */ irda_device_setup(dev); /* Insert overrides below this line! */ return 0; } /* * Function irport_net_open (dev) * * Network device is taken up. Usually this is done by "ifconfig irda0 up" * */ int irport_net_open(struct net_device *dev) { struct irport_cb *self; int iobase; ASSERT(dev != NULL, return -1;); self = (struct irport_cb *) dev->priv; iobase = self->io.sir_base; if (request_irq(self->io.irq, self->interrupt, 0, dev->name, (void *) dev)) return -EAGAIN; irport_start(self); /* * Open new IrLAP layer instance, now that everything should be * initialized properly */ self->irlap = irlap_open(dev, &self->qos); /* FIXME: change speed of dongle */ /* Ready to play! */ netif_start_queue(dev); MOD_INC_USE_COUNT; return 0; } /* * Function irport_net_close (self) * * Network device is taken down. Usually this is done by * "ifconfig irda0 down" */ int irport_net_close(struct net_device *dev) { struct irport_cb *self; int iobase; IRDA_DEBUG(4, __FUNCTION__ "()\n"); ASSERT(dev != NULL, return -1;); self = (struct irport_cb *) dev->priv; ASSERT(self != NULL, return -1;); iobase = self->io.sir_base; /* Stop device */ netif_stop_queue(dev); /* Stop and remove instance of IrLAP */ if (self->irlap) irlap_close(self->irlap); self->irlap = NULL; irport_stop(self); free_irq(self->io.irq, dev); MOD_DEC_USE_COUNT; return 0; } /* * Function irport_wait_until_sent (self) * * Delay exectution until finished transmitting * */ #if 0 void irport_wait_until_sent(struct irport_cb *self) { int iobase; iobase = self->io.sir_base; /* Wait until Tx FIFO is empty */ while (!(inb(iobase+UART_LSR) & UART_LSR_THRE)) { IRDA_DEBUG(2, __FUNCTION__ "(), waiting!\n"); current->state = TASK_INTERRUPTIBLE; schedule_timeout(MSECS_TO_JIFFIES(60)); } } #endif /* * Function irport_is_receiving (self) * * Returns true is we are currently receiving data * */ static int irport_is_receiving(struct irport_cb *self) { return (self->rx_buff.state != OUTSIDE_FRAME); } /* * Function irport_set_dtr_rts (tty, dtr, rts) * * This function can be used by dongles etc. to set or reset the status * of the dtr and rts lines */ static int irport_set_dtr_rts(struct net_device *dev, int dtr, int rts) { struct irport_cb *self = dev->priv; int iobase; ASSERT(self != NULL, return -1;); iobase = self->io.sir_base; if (dtr) dtr = UART_MCR_DTR; if (rts) rts = UART_MCR_RTS; outb(dtr|rts|UART_MCR_OUT2, iobase+UART_MCR); return 0; } static int irport_raw_write(struct net_device *dev, __u8 *buf, int len) { struct irport_cb *self = (struct irport_cb *) dev->priv; int actual = 0; int iobase; ASSERT(self != NULL, return -1;); iobase = self->io.sir_base; /* Tx FIFO should be empty! */ if (!(inb(iobase+UART_LSR) & UART_LSR_THRE)) { IRDA_DEBUG( 0, __FUNCTION__ "(), failed, fifo not empty!\n"); return -1; } /* Fill FIFO with current frame */ while (actual < len) { /* Transmit next byte */ outb(buf[actual], iobase+UART_TX); actual++; } return actual; } /* * Function irport_net_ioctl (dev, rq, cmd) * * Process IOCTL commands for this device * */ static int irport_net_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) { struct if_irda_req *irq = (struct if_irda_req *) rq; struct irport_cb *self; dongle_t *dongle; unsigned long flags; int ret = 0; ASSERT(dev != NULL, return -1;); self = dev->priv; ASSERT(self != NULL, return -1;); IRDA_DEBUG(2, __FUNCTION__ "(), %s, (cmd=0x%X)\n", dev->name, cmd); /* Disable interrupts & save flags */ save_flags(flags); cli(); switch (cmd) { case SIOCSBANDWIDTH: /* Set bandwidth */ if (!capable(CAP_NET_ADMIN)) return -EPERM; irda_task_execute(self, __irport_change_speed, NULL, NULL, (void *) irq->ifr_baudrate); break; case SIOCSDONGLE: /* Set dongle */ if (!capable(CAP_NET_ADMIN)) return -EPERM; /* Initialize dongle */ dongle = irda_device_dongle_init(dev, irq->ifr_dongle); if (!dongle) break; dongle->set_mode = NULL; dongle->read = NULL; dongle->write = irport_raw_write; dongle->set_dtr_rts = irport_set_dtr_rts; self->dongle = dongle; /* Now initialize the dongle! */ dongle->issue->open(dongle, &self->qos); /* Reset dongle */ irda_task_execute(dongle, dongle->issue->reset, NULL, NULL, NULL); break; case SIOCSMEDIABUSY: /* Set media busy */ if (!capable(CAP_NET_ADMIN)) return -EPERM; irda_device_set_media_busy(self->netdev, TRUE); break; case SIOCGRECEIVING: /* Check if we are receiving right now */ irq->ifr_receiving = irport_is_receiving(self); break; case SIOCSDTRRTS: if (!capable(CAP_NET_ADMIN)) return -EPERM; irport_set_dtr_rts(dev, irq->ifr_dtr, irq->ifr_rts); break; default: ret = -EOPNOTSUPP; } restore_flags(flags); return ret; } static struct net_device_stats *irport_net_get_stats(struct net_device *dev) { struct irport_cb *self = (struct irport_cb *) dev->priv; return &self->stats; } #ifdef MODULE MODULE_PARM(io, "1-4i"); MODULE_PARM(irq, "1-4i"); MODULE_AUTHOR("Dag Brattli "); MODULE_DESCRIPTION("Half duplex serial driver for IrDA SIR mode"); void cleanup_module(void) { irport_cleanup(); } int init_module(void) { return irport_init(); } #endif /* MODULE */