/********************************************************************* * * Filename: toshoboe.c * Version: 0.1 * Description: Driver for the Toshiba OBOE (or type-O or 700 or 701) * FIR Chipset. * Status: Experimental. * Author: James McKenzie * Created at: Sat May 8 12:35:27 1999 * Modified: Paul Bristow * Modified: Mon Nov 11 19:10:05 1999 * * Copyright (c) 1999-2000 James McKenzie, 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. * * Neither James McKenzie nor Cambridge University admit liability nor * provide warranty for any of this software. This material is * provided "AS-IS" and at no charge. * * Applicable Models : Libretto 100CT. and many more * Toshiba refers to this chip as the type-O IR port. * ********************************************************************/ /* This driver is experimental, I have only three ir devices */ /* an olivetti notebook which doesn't have FIR, a toshiba libretto, and */ /* an hp printer, this works fine at 4MBPS with my HP printer */ static char *rcsid = "$Id: toshoboe.c,v 1.91 1999/06/29 14:21:06 root Exp $"; /* Define this to have only one frame in the XMIT or RECV queue */ /* Toshiba's drivers do this, but it disables back to back tansfers */ /* I think that the chip may have some problems certainly, I have */ /* seen it jump over tasks in the taskfile->xmit with this turned on */ #define ONETASK /* To adjust the number of tasks in use edit toshoboe.h */ /* Define this to enable FIR and MIR support */ #define ENABLE_FAST /* Size of IO window */ #define CHIP_IO_EXTENT 0x1f /* Transmit and receive buffer sizes, adjust at your peril */ #define RX_BUF_SZ 4196 #define TX_BUF_SZ 4196 /* No user servicable parts below here */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PCI_DEVICE_ID_FIR701b 0x0d01 static struct pci_device_id toshoboe_pci_tbl[] __initdata = { { PCI_VENDOR_ID_TOSHIBA, PCI_DEVICE_ID_FIR701, PCI_ANY_ID, PCI_ANY_ID, }, { PCI_VENDOR_ID_TOSHIBA, PCI_DEVICE_ID_FIR701b, PCI_ANY_ID, PCI_ANY_ID, }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE(pci, toshoboe_pci_tbl); static const char *driver_name = "toshoboe"; static int max_baud = 4000000; /* Shutdown the chip and point the taskfile reg somewhere else */ static void toshoboe_stopchip (struct toshoboe_cb *self) { IRDA_DEBUG (4, "%s()\n", __FUNCTION__); outb_p (0x0e, OBOE_REG_11); outb_p (0x00, OBOE_RST); outb_p (0x3f, OBOE_TFP2); /*Write the taskfile address */ outb_p (0xff, OBOE_TFP1); outb_p (0xff, OBOE_TFP0); outb_p (0x0f, OBOE_REG_1B); outb_p (0xff, OBOE_REG_1A); outb_p (0x00, OBOE_ISR); /*FIXME: should i do this to disbale ints */ outb_p (0x80, OBOE_RST); outb_p (0xe, OBOE_LOCK); } /*Set the baud rate */ static void toshoboe_setbaud (struct toshoboe_cb *self, int baud) { unsigned long flags; IRDA_DEBUG (4, "%s()\n", __FUNCTION__); printk (KERN_WARNING "ToshOboe: setting baud to %d\n", baud); save_flags (flags); cli (); switch (baud) { case 2400: outb_p (OBOE_PMDL_SIR, OBOE_PMDL); outb_p (OBOE_SMDL_SIR, OBOE_SMDL); outb_p (0xbf, OBOE_UDIV); break; case 4800: outb_p (OBOE_PMDL_SIR, OBOE_PMDL); outb_p (OBOE_SMDL_SIR, OBOE_SMDL); outb_p (0x5f, OBOE_UDIV); break; case 9600: outb_p (OBOE_PMDL_SIR, OBOE_PMDL); outb_p (OBOE_SMDL_SIR, OBOE_SMDL); outb_p (0x2f, OBOE_UDIV); break; case 19200: outb_p (OBOE_PMDL_SIR, OBOE_PMDL); outb_p (OBOE_SMDL_SIR, OBOE_SMDL); outb_p (0x17, OBOE_UDIV); break; case 38400: outb_p (OBOE_PMDL_SIR, OBOE_PMDL); outb_p (OBOE_SMDL_SIR, OBOE_SMDL); outb_p (0xb, OBOE_UDIV); break; case 57600: outb_p (OBOE_PMDL_SIR, OBOE_PMDL); outb_p (OBOE_SMDL_SIR, OBOE_SMDL); outb_p (0x7, OBOE_UDIV); break; case 115200: outb_p (OBOE_PMDL_SIR, OBOE_PMDL); outb_p (OBOE_SMDL_SIR, OBOE_SMDL); outb_p (0x3, OBOE_UDIV); break; case 1152000: outb_p (OBOE_PMDL_MIR, OBOE_PMDL); outb_p (OBOE_SMDL_MIR, OBOE_SMDL); outb_p (0x1, OBOE_UDIV); break; case 4000000: outb_p (OBOE_PMDL_FIR, OBOE_PMDL); outb_p (OBOE_SMDL_FIR, OBOE_SMDL); outb_p (0x0, OBOE_UDIV); break; } restore_flags (flags); outb_p (0x00, OBOE_RST); outb_p (0x80, OBOE_RST); outb_p (0x01, OBOE_REG_9); self->io.speed = baud; } /* Wake the chip up and get it looking at the taskfile */ static void toshoboe_startchip (struct toshoboe_cb *self) { __u32 physaddr; IRDA_DEBUG (4, "%s()\n", __FUNCTION__); outb_p (0, OBOE_LOCK); outb_p (0, OBOE_RST); outb_p (OBOE_NTR_VAL, OBOE_NTR); outb_p (0xf0, OBOE_REG_D); outb_p (0xff, OBOE_ISR); outb_p (0x0f, OBOE_REG_1B); outb_p (0xff, OBOE_REG_1A); physaddr = virt_to_bus (self->taskfile); outb_p ((physaddr >> 0x0a) & 0xff, OBOE_TFP0); outb_p ((physaddr >> 0x12) & 0xff, OBOE_TFP1); outb_p ((physaddr >> 0x1a) & 0x3f, OBOE_TFP2); outb_p (0x0e, OBOE_REG_11); outb_p (0x80, OBOE_RST); toshoboe_setbaud (self, 9600); } /*Let the chip look at memory */ static void toshoboe_enablebm (struct toshoboe_cb *self) { IRDA_DEBUG (4, "%s()\n", __FUNCTION__); pci_set_master (self->pdev); } /*Don't let the chip look at memory */ static void toshoboe_disablebm (struct toshoboe_cb *self) { __u8 command; IRDA_DEBUG (4, "%s()\n", __FUNCTION__); pci_read_config_byte (self->pdev, PCI_COMMAND, &command); command &= ~PCI_COMMAND_MASTER; pci_write_config_byte (self->pdev, PCI_COMMAND, command); } /*setup the taskfile */ static void toshoboe_initbuffs (struct toshoboe_cb *self) { int i; unsigned long flags; IRDA_DEBUG (4, "%s()\n", __FUNCTION__); save_flags (flags); cli (); for (i = 0; i < TX_SLOTS; ++i) { self->taskfile->xmit[i].len = 0; self->taskfile->xmit[i].control = 0x00; self->taskfile->xmit[i].buffer = virt_to_bus (self->xmit_bufs[i]); } for (i = 0; i < RX_SLOTS; ++i) { self->taskfile->recv[i].len = 0; self->taskfile->recv[i].control = 0x83; self->taskfile->recv[i].buffer = virt_to_bus (self->recv_bufs[i]); } restore_flags (flags); } /*Transmit something */ static int toshoboe_hard_xmit (struct sk_buff *skb, struct net_device *dev) { struct toshoboe_cb *self; __s32 speed; int mtt, len; self = (struct toshoboe_cb *) dev->priv; ASSERT (self != NULL, return 0; ); /* Check if we need to change the speed */ speed = irda_get_next_speed(skb); if ((speed != self->io.speed) && (speed != -1)) { /* Check for empty frame */ if (!skb->len) { toshoboe_setbaud(self, speed); dev_kfree_skb(skb); return 0; } else self->new_speed = speed; } netif_stop_queue(dev); if (self->stopped) { dev_kfree_skb(skb); return 0; } #ifdef ONETASK if (self->txpending) return -EBUSY; self->txs = inb_p (OBOE_XMTT) - OBOE_XMTT_OFFSET; self->txs &= 0x3f; #endif if (self->taskfile->xmit[self->txs].control) return -EBUSY; if (inb_p (OBOE_RST) & OBOE_RST_WRAP) { len = async_wrap_skb (skb, self->xmit_bufs[self->txs], TX_BUF_SZ); } else { len = skb->len; memcpy (self->xmit_bufs[self->txs], skb->data, len); } self->taskfile->xmit[self->txs].len = len & 0x0fff; outb_p (0, OBOE_RST); outb_p (0x1e, OBOE_REG_11); self->taskfile->xmit[self->txs].control = 0x84; mtt = irda_get_mtt (skb); if (mtt) udelay (mtt); self->txpending++; /*FIXME: ask about busy,media_busy stuff, for the moment */ /*busy means can't queue any more */ #ifndef ONETASK if (self->txpending != TX_SLOTS) { netif_wake_queue(dev); } #endif outb_p (0x80, OBOE_RST); outb_p (1, OBOE_REG_9); self->txs++; self->txs %= TX_SLOTS; dev_kfree_skb (skb); return 0; } /*interrupt handler */ static void toshoboe_interrupt (int irq, void *dev_id, struct pt_regs *regs) { struct toshoboe_cb *self = (struct toshoboe_cb *) dev_id; __u8 irqstat; struct sk_buff *skb; if (self == NULL) { printk (KERN_WARNING "%s: irq %d for unknown device.\n", driver_name, irq); return; } IRDA_DEBUG (4, "%s()\n", __FUNCTION__); irqstat = inb_p (OBOE_ISR); /* woz it us */ if (!(irqstat & 0xf8)) return; outb_p (irqstat, OBOE_ISR); /*Acknologede it */ /* Txdone */ if (irqstat & OBOE_ISR_TXDONE) { self->txpending--; self->stats.tx_packets++; if (self->new_speed) { toshoboe_setbaud(self, self->new_speed); self->new_speed = 0; } /* Tell network layer that we want more frames */ netif_wake_queue(self->netdev); } if (irqstat & OBOE_ISR_RXDONE) { #ifdef ONETASK self->rxs = inb_p (OBOE_RCVT); self->rxs += (RX_SLOTS - 1); self->rxs %= RX_SLOTS; #else while (self->taskfile->recv[self->rxs].control == 0) #endif { int len = self->taskfile->recv[self->rxs].len; if (len > 2) len -= 2; skb = dev_alloc_skb (len + 1); if (skb) { skb_reserve (skb, 1); skb_put (skb, len); memcpy (skb->data, self->recv_bufs[self->rxs], len); self->stats.rx_packets++; skb->dev = self->netdev; skb->mac.raw = skb->data; skb->protocol = htons (ETH_P_IRDA); } else { printk (KERN_INFO "%s(), memory squeeze, dropping frame.\n", __FUNCTION__); } self->taskfile->recv[self->rxs].control = 0x83; self->taskfile->recv[self->rxs].len = 0x0; self->rxs++; self->rxs %= RX_SLOTS; if (skb) netif_rx (skb); } } if (irqstat & OBOE_ISR_20) { printk (KERN_WARNING "Oboe_irq: 20\n"); } if (irqstat & OBOE_ISR_10) { printk (KERN_WARNING "Oboe_irq: 10\n"); } if (irqstat & 0x8) { /*FIXME: I think this is a TX or RX error of some sort */ self->stats.tx_errors++; self->stats.rx_errors++; } } static int toshoboe_net_init (struct net_device *dev) { IRDA_DEBUG (4, "%s()\n", __FUNCTION__); /* Setup to be a normal IrDA network device driver */ irda_device_setup (dev); /* Insert overrides below this line! */ return 0; } static void toshoboe_initptrs (struct toshoboe_cb *self) { unsigned long flags; save_flags (flags); cli (); /*FIXME: need to test this carefully to check which one */ /*of the two possible startup logics the chip uses */ /*although it won't make any difference if no-one xmits durining init */ /*and none what soever if using ONETASK */ self->rxs = inb_p (OBOE_RCVT); self->txs = inb_p (OBOE_XMTT) - OBOE_XMTT_OFFSET; #if 0 self->rxs = 0; self->txs = 0; #endif #if 0 self->rxs = RX_SLOTS - 1; self->txs = 0; #endif self->txpending = 0; restore_flags (flags); } static int toshoboe_net_open (struct net_device *dev) { struct toshoboe_cb *self; char hwname[32]; IRDA_DEBUG (4, "%s()\n", __FUNCTION__); ASSERT (dev != NULL, return -1; ); self = (struct toshoboe_cb *) dev->priv; ASSERT (self != NULL, return 0; ); if (self->stopped) return 0; if (request_irq (self->io.irq, toshoboe_interrupt, SA_SHIRQ | SA_INTERRUPT, dev->name, (void *) self)) { return -EAGAIN; } toshoboe_initbuffs (self); toshoboe_enablebm (self); toshoboe_startchip (self); toshoboe_initptrs (self); /* Ready to play! */ netif_start_queue(dev); /* Give self a hardware name */ sprintf(hwname, "Toshiba-FIR @ 0x%03x", self->base); /* * Open new IrLAP layer instance, now that everything should be * initialized properly */ self->irlap = irlap_open(dev, &self->qos, hwname); self->open = 1; MOD_INC_USE_COUNT; return 0; } static int toshoboe_net_close (struct net_device *dev) { struct toshoboe_cb *self; IRDA_DEBUG (4, "%s()\n", __FUNCTION__); ASSERT (dev != NULL, return -1; ); self = (struct toshoboe_cb *) dev->priv; /* Stop device */ netif_stop_queue(dev); /* Stop and remove instance of IrLAP */ if (self->irlap) irlap_close(self->irlap); self->irlap = NULL; self->open = 0; free_irq (self->io.irq, (void *) self); if (!self->stopped) { toshoboe_stopchip (self); toshoboe_disablebm (self); } MOD_DEC_USE_COUNT; return 0; } /* * Function toshoboe_net_ioctl (dev, rq, cmd) * * Process IOCTL commands for this device * */ static int toshoboe_net_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) { struct if_irda_req *irq = (struct if_irda_req *) rq; struct toshoboe_cb *self; unsigned long flags; int ret = 0; ASSERT(dev != NULL, return -1;); self = dev->priv; ASSERT(self != NULL, return -1;); IRDA_DEBUG(2, "%s(), %s, (cmd=0x%X)\n", __FUNCTION__, dev->name, cmd); /* Disable interrupts & save flags */ save_flags(flags); cli(); switch (cmd) { case SIOCSBANDWIDTH: /* Set bandwidth */ if (!capable(CAP_NET_ADMIN)) { ret = -EPERM; goto out; } /* toshoboe_setbaud(self, irq->ifr_baudrate); */ /* Just change speed once - inserted by Paul Bristow */ self->new_speed = irq->ifr_baudrate; break; case SIOCSMEDIABUSY: /* Set media busy */ if (!capable(CAP_NET_ADMIN)) { ret = -EPERM; goto out; } irda_device_set_media_busy(self->netdev, TRUE); break; case SIOCGRECEIVING: /* Check if we are receiving right now */ irq->ifr_receiving = 0; /* Can't tell */ break; default: ret = -EOPNOTSUPP; } out: restore_flags(flags); return ret; } MODULE_DESCRIPTION("Toshiba OBOE IrDA Device Driver"); MODULE_AUTHOR("James McKenzie "); MODULE_LICENSE("GPL"); MODULE_PARM (max_baud, "i"); MODULE_PARM_DESC(max_baus, "Maximum baud rate"); static void toshoboe_remove (struct pci_dev *pci_dev) { int i; struct toshoboe_cb *self = (struct toshoboe_cb*)pci_get_drvdata(pci_dev); IRDA_DEBUG (4, "%s()\n", __FUNCTION__); ASSERT (self != NULL, return; ); if (!self->stopped) { toshoboe_stopchip (self); toshoboe_disablebm (self); } release_region (self->io.sir_base, self->io.sir_ext); for (i = 0; i < TX_SLOTS; ++i) { kfree (self->xmit_bufs[i]); self->xmit_bufs[i] = NULL; } for (i = 0; i < RX_SLOTS; ++i) { kfree (self->recv_bufs[i]); self->recv_bufs[i] = NULL; } if (self->netdev) { /* Remove netdevice */ rtnl_lock(); unregister_netdevice(self->netdev); rtnl_unlock(); } kfree (self->taskfilebuf); self->taskfilebuf = NULL; self->taskfile = NULL; return; } static int toshoboe_probe (struct pci_dev *pci_dev, const struct pci_device_id *pdid) { struct toshoboe_cb *self; struct net_device *dev; int i = 0; int ok = 0; int err; IRDA_DEBUG (4, "%s()\n", __FUNCTION__); if ((err=pci_enable_device(pci_dev))) return err; self = kmalloc (sizeof (struct toshoboe_cb), GFP_KERNEL); if (self == NULL) { printk (KERN_ERR "IrDA: Can't allocate memory for " "IrDA control block!\n"); return -ENOMEM; } memset (self, 0, sizeof (struct toshoboe_cb)); self->open = 0; self->stopped = 0; self->pdev = pci_dev; self->base = pci_resource_start(pci_dev,0); self->io.sir_base = self->base; self->io.irq = pci_dev->irq; self->io.sir_ext = CHIP_IO_EXTENT; self->io.speed = 9600; /* Lock the port that we need */ if (NULL==request_region (self->io.sir_base, self->io.sir_ext, driver_name)) { IRDA_DEBUG (0, "%s(), can't get iobase of 0x%03x\n", __FUNCTION__, self->io.sir_base); err = -EBUSY; goto freeself; } irda_init_max_qos_capabilies (&self->qos); self->qos.baud_rate.bits = 0; if (max_baud >= 2400) self->qos.baud_rate.bits |= IR_2400; /*if (max_baud>=4800) idev->qos.baud_rate.bits|=IR_4800; */ if (max_baud >= 9600) self->qos.baud_rate.bits |= IR_9600; if (max_baud >= 19200) self->qos.baud_rate.bits |= IR_19200; if (max_baud >= 115200) self->qos.baud_rate.bits |= IR_115200; #ifdef ENABLE_FAST if (max_baud >= 576000) self->qos.baud_rate.bits |= IR_576000; if (max_baud >= 1152000) self->qos.baud_rate.bits |= IR_1152000; if (max_baud >= 4000000) self->qos.baud_rate.bits |= (IR_4000000 << 8); #endif self->qos.min_turn_time.bits = 0xff; /*FIXME: what does this do? */ irda_qos_bits_to_value (&self->qos); self->flags = IFF_SIR | IFF_DMA | IFF_PIO; #ifdef ENABLE_FAST if (max_baud >= 576000) self->flags |= IFF_FIR; #endif /* Now setup the endless buffers we need */ self->txs = 0; self->rxs = 0; self->taskfilebuf = kmalloc (OBOE_TASK_BUF_LEN, GFP_KERNEL); if (!self->taskfilebuf) { printk (KERN_ERR "toshoboe: kmalloc for DMA failed()\n"); err = -ENOMEM; goto freeregion; } memset (self->taskfilebuf, 0, OBOE_TASK_BUF_LEN); /*We need to align the taskfile on a taskfile size boundary */ { __u32 addr; addr = (__u32) self->taskfilebuf; addr &= ~(sizeof (struct OboeTaskFile) - 1); addr += sizeof (struct OboeTaskFile); self->taskfile = (struct OboeTaskFile *) addr; } for (i = 0; i < TX_SLOTS; ++i) { self->xmit_bufs[i] = kmalloc (TX_BUF_SZ, GFP_KERNEL | GFP_DMA); if (self->xmit_bufs[i]) ok++; } for (i = 0; i < RX_SLOTS; ++i) { self->recv_bufs[i] = kmalloc (TX_BUF_SZ, GFP_KERNEL | GFP_DMA); if (self->recv_bufs[i]) ok++; } if (ok != RX_SLOTS + TX_SLOTS) { printk (KERN_ERR "toshoboe: kmalloc for buffers failed()\n"); err = -ENOMEM; goto freebufs; } if (!(dev = dev_alloc("irda%d", &err))) { ERROR("%s(), dev_alloc() failed!\n", __FUNCTION__); err = -ENOMEM; goto freebufs; } dev->priv = (void *) self; self->netdev = dev; MESSAGE("IrDA: Registered device %s\n", dev->name); dev->init = toshoboe_net_init; dev->hard_start_xmit = toshoboe_hard_xmit; dev->open = toshoboe_net_open; dev->stop = toshoboe_net_close; dev->do_ioctl = toshoboe_net_ioctl; rtnl_lock(); err = register_netdevice(dev); rtnl_unlock(); if (err) { ERROR("%s(), register_netdev() failed!\n", __FUNCTION__); /* XXX there is not freeing for dev? */ goto freebufs; } pci_set_drvdata(pci_dev,self); printk (KERN_WARNING "ToshOboe: Using "); #ifdef ONETASK printk ("single"); #else printk ("multiple"); #endif printk (" tasks, version %s\n", rcsid); return (0); freebufs: for (i = 0; i < TX_SLOTS; ++i) if (self->xmit_bufs[i]) kfree (self->xmit_bufs[i]); for (i = 0; i < RX_SLOTS; ++i) if (self->recv_bufs[i]) kfree (self->recv_bufs[i]); kfree(self->taskfilebuf); freeregion: release_region (self->io.sir_base, self->io.sir_ext); freeself: kfree (self); return err; } static int toshoboe_suspend (struct pci_dev *pci_dev, u32 crap) { int i = 10; struct toshoboe_cb *self = (struct toshoboe_cb*)pci_get_drvdata(pci_dev); printk (KERN_WARNING "ToshOboe: suspending\n"); if (!self || self->stopped) return 0; self->stopped = 1; if (!self->open) return 0; /*FIXME: can't sleep here wait one second */ while ((i--) && (self->txpending)) udelay (100); toshoboe_stopchip (self); toshoboe_disablebm (self); self->txpending = 0; return 0; } static int toshoboe_resume (struct pci_dev *pci_dev) { struct toshoboe_cb *self = (struct toshoboe_cb*)pci_get_drvdata(pci_dev); unsigned long flags; if (!self) return 0; if (!self->stopped) return 0; if (!self->open) { self->stopped = 0; return 0; } save_flags (flags); cli (); toshoboe_initbuffs (self); toshoboe_enablebm (self); toshoboe_startchip (self); toshoboe_setbaud (self, self->io.speed); toshoboe_initptrs (self); netif_wake_queue(self->netdev); restore_flags (flags); printk (KERN_WARNING "ToshOboe: waking up\n"); return 0; } static struct pci_driver toshoboe_pci_driver = { name : "toshoboe", id_table : toshoboe_pci_tbl, probe : toshoboe_probe, remove : toshoboe_remove, suspend : toshoboe_suspend, resume : toshoboe_resume }; int __init toshoboe_init (void) { return pci_module_init(&toshoboe_pci_driver); } void toshoboe_cleanup (void) { pci_unregister_driver(&toshoboe_pci_driver); } module_init(toshoboe_init); module_exit(toshoboe_cleanup);