diff options
Diffstat (limited to 'drivers/ieee1394/ohci1394.c')
-rw-r--r-- | drivers/ieee1394/ohci1394.c | 1403 |
1 files changed, 1403 insertions, 0 deletions
diff --git a/drivers/ieee1394/ohci1394.c b/drivers/ieee1394/ohci1394.c new file mode 100644 index 000000000..f39a8de86 --- /dev/null +++ b/drivers/ieee1394/ohci1394.c @@ -0,0 +1,1403 @@ +/* + * ti_ohci1394.c - Texas Instruments Ohci1394 driver + * Copyright (C)1999,2000 Sebastien Rougeaux <sebastien.rougeaux@anu.edu.au> + * Gord Peters <GordPeters@smarttech.com> + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <asm/byteorder.h> +#include <asm/atomic.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <linux/proc_fs.h> +#include <linux/tqueue.h> + +#include "ieee1394.h" +#include "ieee1394_types.h" +#include "hosts.h" +#include "ieee1394_core.h" +#include "ohci1394.h" + +#undef CONFIG_PROC_FS + +/* print general (card independent) information */ +#define PRINT_G(level, fmt, args...) \ +printk(level "ohci1394: " fmt "\n" , ## args) + +/* print card specific information */ +#define PRINT(level, card, fmt, args...) \ +printk(level "ohci1394_%d: " fmt "\n" , card , ## args) + +int supported_chips[][2] = { + { PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_OHCI1394 }, + { PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_OHCI1394_2 }, + { PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_OHCI1394 }, + { -1, -1 } +}; + +static struct ti_ohci cards[MAX_OHCI1394_CARDS]; +static int num_of_cards = 0; + +static int add_card(struct pci_dev *dev); +static void remove_card(struct ti_ohci *ohci); +static int init_driver(void); + +/*********************************** + * IEEE-1394 functionality section * + ***********************************/ + + +#if 0 /* not needed at this time */ +static int get_phy_reg(struct ti_ohci *ohci, int addr) +{ + int timeout=10000; + static quadlet_t r; + + if ((addr < 1) || (addr > 15)) { + PRINT(KERN_ERR, ohci->id, __FUNCTION__ + ": PHY register address %d out of range", addr); + return -EFAULT; + } + + spin_lock(&ohci->phy_reg_lock); + + /* initiate read request */ + reg_write(ohci, OHCI1394_PhyControl, + ((addr<<8)&0x00000f00) | 0x00008000); + + /* wait */ + while (!(reg_read(ohci, OHCI1394_PhyControl)&0x80000000) && timeout) + timeout--; + + + if (!timeout) { + PRINT(KERN_ERR, ohci->id, "get_phy_reg timeout !!!\n"); + spin_unlock(&ohci->phy_reg_lock); + return -EFAULT; + } + r = reg_read(ohci, OHCI1394_PhyControl); + + spin_unlock(&ohci->phy_reg_lock); + + return (r&0x00ff0000)>>16; +} + +static int set_phy_reg(struct ti_ohci *ohci, int addr, unsigned char data) { + int timeout=10000; + u32 r; + + if ((addr < 1) || (addr > 15)) { + PRINT(KERN_ERR, ohci->id, __FUNCTION__ + ": PHY register address %d out of range", addr); + return -EFAULT; + } + + r = ((addr<<8)&0x00000f00) | 0x00004000 | ((u32)data & 0x000000ff); + + spin_lock(&ohci->phy_reg_lock); + + reg_write(ohci, OHCI1394_PhyControl, r); + + /* wait */ + while (!(reg_read(ohci, OHCI1394_PhyControl)&0x80000000) && timeout) + timeout--; + + spin_unlock(&ohci->phy_reg_lock); + + if (!timeout) { + PRINT(KERN_ERR, ohci->id, "set_phy_reg timeout !!!\n"); + return -EFAULT; + } + + return 0; +} +#endif /* unneeded functions */ + +inline static int handle_selfid(struct ti_ohci *ohci, struct hpsb_host *host, + int phyid, int isroot) +{ + quadlet_t *q = ohci->self_id_buffer; + quadlet_t self_id_count=reg_read(ohci, OHCI1394_SelfIDCount); + size_t size; + quadlet_t lsid; + + /* Self-id handling seems much easier than for the aic5800 chip. + All the self-id packets, including this device own self-id, + should be correctly arranged in the self_id_buffer at this + stage */ + + /* Check status of self-id reception */ + if ((self_id_count&0x80000000) || + ((self_id_count&0x00FF0000) != (q[0]&0x00FF0000))) { + PRINT(KERN_ERR, ohci->id, + "Error in reception of self-id packets"); + return -1; + } + + size = ((self_id_count&0x0000EFFC)>>2) - 1; + q++; + + while (size > 0) { + if (q[0] == ~q[1]) { + printk("-%d- selfid packet 0x%x rcvd\n", + ohci->id, q[0]); + hpsb_selfid_received(host, q[0]); + if (((q[0]&0x3f000000)>>24)==phyid) { + lsid=q[0]; + printk("This node self-id is 0x%08x\n",lsid); + } + } else { + printk("-%d- inconsistent selfid 0x%x/0x%x\n", ohci->id, + q[0], q[1]); + } + q += 2; + size -= 2; + } + + printk(" calling self-id complete\n"); + + hpsb_selfid_complete(host, phyid, isroot); + return 0; +} + +static int ohci_detect(struct hpsb_host_template *tmpl) +{ + struct hpsb_host *host; + int i; + + init_driver(); + + for (i = 0; i < num_of_cards; i++) { + host = hpsb_get_host(tmpl, 0); + if (host == NULL) { + /* simply don't init more after out of mem */ + return i; + } + host->hostdata = &cards[i]; + cards[i].host = host; + } + + return num_of_cards; +} + +static int ohci_soft_reset(struct ti_ohci *ohci) { + int timeout=10000; + + reg_write(ohci, OHCI1394_HCControlSet, 0x00010000); + + while ((reg_read(ohci, OHCI1394_HCControlSet)&0x00010000) && timeout) + timeout--; + if (!timeout) { + PRINT(KERN_ERR, ohci->id, "soft reset timeout !!!"); + return -EFAULT; + } + else PRINT(KERN_INFO, ohci->id, "soft reset finished"); + return 0; +} + +static int ohci_initialize(struct hpsb_host *host) +{ + struct ti_ohci *ohci=host->hostdata; + int retval, i; + + spin_lock_init(&ohci->phy_reg_lock); + + /* Soft reset */ + if ((retval=ohci_soft_reset(ohci))<0) return retval; + + /* Set the bus number */ + reg_write(ohci, OHCI1394_NodeID, 0x0000ffc0); + + /* Set Link Power Status (LPS) */ + reg_write(ohci, OHCI1394_HCControlSet, 0x00080000); + + /* Enable posted writes */ + reg_write(ohci, OHCI1394_HCControlSet, 0x00040000); + + /* Clear link control register */ + reg_write(ohci, OHCI1394_LinkControlClear, 0xffffffff); + + /* Enable cycle timer and cycle master */ + reg_write(ohci, OHCI1394_LinkControlSet, 0x00300000); + + /* Clear interrupt registers */ + reg_write(ohci, OHCI1394_IntEventClear, 0xffffffff); + reg_write(ohci, OHCI1394_IntMaskClear, 0xffffffff); + + /* Set up self-id dma buffer */ + reg_write(ohci, OHCI1394_SelfIDBuffer, + virt_to_bus(ohci->self_id_buffer)); + + /* enable self-id dma */ + reg_write(ohci, OHCI1394_LinkControlSet, 0x00000200); + + /* Set the configuration ROM mapping register */ + reg_write(ohci, OHCI1394_ConfigROMmap, + virt_to_bus(ohci->csr_config_rom)); + +#if 1 /* Why is this step necessary ? */ + /* Write the config ROM header */ + reg_write(ohci, OHCI1394_ConfigROMhdr,0x04040000); + + /* Set bus options */ + reg_write(ohci, OHCI1394_BusOptions, 0xf064A002); +#endif + +#if 1 + /* Accept phy packets into AR request context */ + reg_write(ohci, OHCI1394_LinkControlSet, 0x00000400); +#endif + + /* Enable link */ + reg_write(ohci, OHCI1394_HCControlSet, 0x00020000); + + /* Initialize IR dma */ + + /* make sure the context isn't running, dead, or active */ + if (!(reg_read(ohci, OHCI1394_IrRcvContextControlSet) & 0x00008F00)) { + + /* initialize IR program */ + for (i= 0; i < IR_NUM_DESC; i++) { + + /* end of descriptor list? */ + if ((i + 1) < IR_NUM_DESC) { + ohci->IR_recv_prg[i]->control= + (0x283C << 16) | IR_RECV_BUF_SIZE; + ohci->IR_recv_prg[i]->branchAddress= + (virt_to_bus(ohci->IR_recv_prg[i + 1]) + & 0xfffffff0) | 0x1; + } else { + ohci->IR_recv_prg[i]->control= + (0x283C << 16) | IR_RECV_BUF_SIZE; + ohci->IR_recv_prg[i]->branchAddress= + (virt_to_bus(ohci->IR_recv_prg[0]) + & 0xfffffff0) | 0x1; + } + + ohci->IR_recv_prg[i]->address= + virt_to_bus(ohci->IR_recv_buf[i]); + ohci->IR_recv_prg[i]->status= IR_RECV_BUF_SIZE; + } + + /* Tell the controller where the first IR program is */ + reg_write(ohci, OHCI1394_IrRcvCommandPtr, + virt_to_bus(ohci->IR_recv_prg[0]) | 0x1 ); + + /* Set bufferFill, isochHeader, multichannel for IR context */ + reg_write(ohci, OHCI1394_IrRcvContextControlSet, 0xd0000000); + + /* Set the context match register to match on all tags */ + reg_write(ohci, OHCI1394_IrRcvContextMatch, 0xf0000000); + + /* Clear the multi channel mask high and low registers */ + reg_write(ohci, OHCI1394_IRMultiChanMaskHiClear, 0xffffffff); + reg_write(ohci, OHCI1394_IRMultiChanMaskLoClear, 0xffffffff); + + /* Set up isoRecvIntMask to generate interrupts for context 0 + (thanks to Michael Greger for seeing that I forgot this) */ + reg_write(ohci, OHCI1394_IsoRecvIntMaskSet, 0x00000001); + + /* Run IR context */ + reg_write(ohci, OHCI1394_IrRcvContextControlSet, 0x00008000); + } + + /* Initialize AR dma */ + ohci->AR_resp_prg->control=0x283C << 16 | AR_RESP_BUF_SIZE; + ohci->AR_resp_prg->address=virt_to_bus(ohci->AR_resp_buf); + ohci->AR_resp_prg->status=AR_RESP_BUF_SIZE; + PRINT(KERN_INFO, ohci->id, "AR control: %x", + ohci->AR_resp_prg->control); + PRINT(KERN_INFO, ohci->id, "AR status: %x %d", + ohci->AR_resp_prg->status & 0xffff, + ohci->AR_resp_prg->status & 0xffff); + + /* Tell the controller where the AR program is */ + reg_write(ohci, OHCI1394_AsRspRcvCommandPtr, + virt_to_bus(ohci->AR_resp_prg)|0x00000001); + +#if 1 + /* Accept phy packets into AR request context */ + reg_write(ohci, OHCI1394_LinkControlSet, 0x00000400); +#endif + + /* Run AR context */ + reg_write(ohci, OHCI1394_AsRspRcvContextControlSet, 0x00008000); + +#ifndef __BIG_ENDIAN + reg_write(ohci, OHCI1394_HCControlSet, 0x40000000); +#else + reg_write(ohci, OHCI1394_HCControlClear, 0x40000000); +#endif + + /* Enable interrupts */ + reg_write(ohci, OHCI1394_IntMaskSet, + OHCI1394_masterIntEnable | + OHCI1394_phyRegRcvd | + OHCI1394_busReset | + OHCI1394_selfIDComplete | + OHCI1394_RSPkt | + OHCI1394_RQPkt | + OHCI1394_ARRS | + OHCI1394_ARRQ | + OHCI1394_respTxComplete | + OHCI1394_reqTxComplete | + OHCI1394_isochRx + ); + + return 1; +} + +static void ohci_remove(struct hpsb_host *host) +{ + struct ti_ohci *ohci; + + if (host != NULL) { + ohci = host->hostdata; + remove_card(ohci); + } +} + + +/* This must be called with the async_queue_lock held. */ +static void send_next_async(struct ti_ohci *ohci) +{ + int i=0; + struct hpsb_packet *packet = ohci->async_queue; + struct dma_cmd prg; + quadlet_t *ptr = (quadlet_t *)ohci->AT_req_prg; + + //HPSB_TRACE(); + + /* stop the channel program if it's still running */ + reg_write(ohci, OHCI1394_AsReqTrContextControlClear, 0x8000); + + /* Wait until it effectively stops */ + while (reg_read(ohci, OHCI1394_AsReqTrContextControlSet) + & 0x400) { + i++; + if (i>5000) { + PRINT(KERN_ERR, ohci->id, + "runaway loop in DmaAT. bailing out..."); + break; + } + }; + + if (packet->type == async) + { + + /* re-format packet header according to ohci specification */ + packet->header[1] = (packet->header[1] & 0xFFFF) | + (packet->header[0] & 0xFFFF0000); + packet->header[0] = DMA_SPEED_200 | + (packet->header[0] & 0xFFFF); + + if (packet->data_size) { /* block transmit */ + prg.control = OUTPUT_MORE_IMMEDIATE | 0x10; + prg.address = 0; + prg.branchAddress = 0; + prg.status = 0; + memcpy(ohci->AT_req_prg, &prg, 16); + memcpy(ohci->AT_req_prg + 1, packet->header, 16); + prg.control = OUTPUT_LAST | packet->data_size; + prg.address = virt_to_bus(packet->data); + memcpy(ohci->AT_req_prg + 2, &prg, 16); + + reg_write(ohci, OHCI1394_AsReqTrCommandPtr, + virt_to_bus(ohci->AT_req_prg)|0x3); + } + else { /* quadlet transmit */ + prg.control = OUTPUT_LAST_IMMEDIATE | + packet->header_size; + prg.address = 0; + prg.branchAddress = 0; + prg.status = 0; + memcpy(ohci->AT_req_prg, &prg, 16); + memcpy(ohci->AT_req_prg + 1, packet->header, 16); + + PRINT(KERN_INFO, ohci->id, + "dma_cmd: %08x %08x %08x %08x", + *ptr, *(ptr+1), *(ptr+2), *(ptr+3)); + PRINT(KERN_INFO, ohci->id, + "header: %08x %08x %08x %08x", + *(ptr+4), *(ptr+5), *(ptr+6), *(ptr+7)); + + reg_write(ohci, OHCI1394_AsReqTrCommandPtr, + virt_to_bus(ohci->AT_req_prg)|0x2); + } + + } + else if (packet->type == raw) + { + prg.control = OUTPUT_LAST | packet->data_size; + prg.address = virt_to_bus(packet->data); + prg.branchAddress = 0; + prg.status = 0; + memcpy(ohci->AT_req_prg, &prg, 16); + + PRINT(KERN_INFO, ohci->id, + "dma_cmd: %08x %08x %08x %08x", + *ptr, *(ptr+1), *(ptr+2), *(ptr+3)); + + reg_write(ohci, OHCI1394_AsReqTrCommandPtr, + virt_to_bus(ohci->AT_req_prg)|0x2); + } + + /* run program */ + reg_write(ohci, OHCI1394_AsReqTrContextControlSet, 0x00008000); +} + +static int ohci_transmit(struct hpsb_host *host, struct hpsb_packet *packet) +{ + struct ti_ohci *ohci = host->hostdata; + struct hpsb_packet *p; + unsigned long flags; + + if (packet->data_size >= 4096) { + PRINT(KERN_ERR, ohci->id, "transmit packet data too big (%d)", + packet->data_size); + return 0; + } + + //HPSB_TRACE(); + packet->xnext = NULL; + + spin_lock_irqsave(&ohci->async_queue_lock, flags); + + if (ohci->async_queue == NULL) { + ohci->async_queue = packet; + send_next_async(ohci); + } else { + p = ohci->async_queue; + while (p->xnext != NULL) { + p = p->xnext; + } + + p->xnext = packet; + } + + spin_unlock_irqrestore(&ohci->async_queue_lock, flags); + + return 1; +} + +static int ohci_devctl(struct hpsb_host *host, enum devctl_cmd cmd, int arg) +{ + struct ti_ohci *ohci = host->hostdata; + int retval = 0; + unsigned long flags; + struct hpsb_packet *packet, *lastpacket; + u32 r; + + switch (cmd) { + case RESET_BUS: + PRINT(KERN_INFO, ohci->id, "resetting bus on request%s", + (host->attempt_root ? " and attempting to become root" + : "")); + r = (host->attempt_root) ? 0x000041ff : 0x0000417f; + reg_write(ohci, OHCI1394_PhyControl, r); + break; + + case GET_CYCLE_COUNTER: + retval = reg_read(ohci, OHCI1394_IsochronousCycleTimer); + break; + + case SET_CYCLE_COUNTER: + reg_write(ohci, OHCI1394_IsochronousCycleTimer, arg); + break; + + case SET_BUS_ID: + PRINT(KERN_ERR, ohci->id, "devctl command SET_BUS_ID err"); + break; + + case ACT_CYCLE_MASTER: +#if 0 + if (arg) { + /* enable cycleTimer, cycleMaster, cycleSource */ + reg_write(ohci, OHCI1394_LinkControlSet, 0x00700000); + } else { + /* disable cycleTimer, cycleMaster, cycleSource */ + reg_write(ohci, OHCI1394_LinkControlClear, 0x00700000); + }; +#endif + break; + + case CANCEL_REQUESTS: + spin_lock_irqsave(&ohci->async_queue_lock, flags); + /* stop any chip activity */ + reg_write(ohci, OHCI1394_HCControlClear, 0x00020000); + packet = ohci->async_queue; + ohci->async_queue = NULL; + spin_unlock_irqrestore(&ohci->async_queue_lock, flags); + + while (packet != NULL) { + lastpacket = packet; + packet = packet->xnext; + hpsb_packet_sent(host, lastpacket, ACKX_ABORTED); + } + + break; + + case MODIFY_USAGE: + if (arg) { + MOD_INC_USE_COUNT; + } else { + MOD_DEC_USE_COUNT; + } + break; + + case ISO_LISTEN_CHANNEL: + + spin_lock_irqsave(&ohci->IR_channel_lock, flags); + + if (!test_and_set_bit(arg, &ohci->IR_channel_usage)) { + PRINT(KERN_INFO, ohci->id, + "listening enabled on channel %d", arg); + + if (arg > 31) { + u32 setMask= 0x00000001; + arg-= 32; + while(arg--) setMask= setMask << 1; + reg_write(ohci, OHCI1394_IRMultiChanMaskHiSet, + setMask); + } else { + u32 setMask= 0x00000001; + while(arg--) setMask= setMask << 1; + reg_write(ohci, OHCI1394_IRMultiChanMaskLoSet, + setMask); + } + + } + + spin_unlock_irqrestore(&ohci->IR_channel_lock, flags); + break; + + case ISO_UNLISTEN_CHANNEL: + + spin_lock_irqsave(&ohci->IR_channel_lock, flags); + + if (test_and_clear_bit(arg, &ohci->IR_channel_usage)) { + PRINT(KERN_INFO, ohci->id, + "listening disabled on iso channel %d", arg); + + if (arg > 31) { + u32 clearMask= 0x00000001; + arg-= 32; + while(arg--) clearMask= clearMask << 1; + reg_write(ohci, + OHCI1394_IRMultiChanMaskHiClear, + clearMask); + } else { + u32 clearMask= 0x00000001; + while(arg--) clearMask= clearMask << 1; + reg_write(ohci, + OHCI1394_IRMultiChanMaskLoClear, + clearMask); + } + + } + + spin_unlock_irqrestore(&ohci->IR_channel_lock, flags); + break; + + default: + PRINT_G(KERN_ERR, "ohci_devctl cmd %d not implemented yet\n", + cmd); + break; + } + return retval; +} + +/*************************************** + * IEEE-1394 functionality section END * + ***************************************/ + + +/******************************************************** + * Global stuff (interrupt handler, init/shutdown code) * + ********************************************************/ + +static void ohci_irq_handler(int irq, void *dev_id, + struct pt_regs *regs_are_unused) +{ + //int i; + static quadlet_t event,node_id; + struct ti_ohci *ohci = (struct ti_ohci *)dev_id; + struct hpsb_host *host = ohci->host; + int phyid = -1, isroot = 0; + + event=reg_read(ohci, OHCI1394_IntEventSet); + + /* Clear the interrupt register */ + reg_write(ohci, OHCI1394_IntEventClear, event); + + /* PRINT(KERN_INFO, ohci->id, "int event %08X mask %08X", + event,reg_read(ohci, OHCI1394_IntMaskSet)); */ + + if (event & OHCI1394_busReset) { + PRINT(KERN_INFO, ohci->id, "bus reset interrupt"); + if (!host->in_bus_reset) { + hpsb_bus_reset(host); + } + ohci->NumBusResets++; + } + if (event & OHCI1394_reqTxComplete) { + PRINT(KERN_INFO, ohci->id, "reqTxComplete int received"); + } + if (event & OHCI1394_RQPkt) { + PRINT(KERN_INFO, ohci->id, "RQPkt int received"); + } + if (event & OHCI1394_RQPkt) { + PRINT(KERN_INFO, ohci->id, "ControlContext: %08X", + reg_read(ohci, OHCI1394_AsReqRcvContextControlSet)); + } + if (event & OHCI1394_RSPkt) { + int rcv_bytes; + int i=0; + + /* we calculate the number of received bytes from the + residual count field */ + rcv_bytes = AR_RESP_BUF_SIZE - + (ohci->AR_resp_prg->status & 0xFFFF); + + PRINT(KERN_INFO, ohci->id, "AR_status 0x%x %d, %d bytes read", + ohci->AR_resp_prg->status, + ohci->AR_resp_prg->status & 0xffff, + rcv_bytes); + + ohci->AR_resp_active = 0; + + if ((ohci->AR_resp_prg->status & 0x84000000) + && (ohci->AR_resp_prg->status & 0xFFFF) >= 8 ) { + hpsb_packet_received(host, ohci->AR_resp_buf, + rcv_bytes); + } else { + //HPSB_TRACE(); + PRINT(KERN_ERR, ohci->id, + "AR resp DMA program status value 0x%x is incorrect!", + ohci->AR_resp_prg->status); + } + + + /* --------------- FIXME --------------------------------- + this is a complete hack... we stop the dma prg + and start it again so as to reset the dma buffer address + Very slow, very bad design... to change ASAP */ + + /* stop the channel program if it's still running */ + reg_write(ohci, OHCI1394_AsRspRcvContextControlClear, 0x8000); + + /* Wait until it effectively stops */ + while (reg_read(ohci, OHCI1394_AsRspRcvContextControlSet) + & 0x400) { + i++; + if (i>5000) { + PRINT(KERN_ERR, ohci->id, + "runaway loop in DmaAT. bailing out..."); + break; + } + } + + reg_write(ohci, OHCI1394_AsRspRcvCommandPtr, + virt_to_bus(ohci->AR_resp_prg)|0x00000001); + ohci->AR_resp_prg->status=AR_RESP_BUF_SIZE; + reg_write(ohci, OHCI1394_AsRspRcvContextControlSet, 0x8000); + + /* ---------------- end of FIXME --------------------------*/ + } + if (event & OHCI1394_isochRx) { + quadlet_t isoRecvIntEvent; + + /* ASSUMPTION: We assume there is only one context for now. */ + + spin_lock(&ohci->IR_recv_lock); + + /* Clear the isoRecvIntEvent register (very important!) */ + isoRecvIntEvent= reg_read(ohci, OHCI1394_IsoRecvIntEventSet); + reg_write(ohci, OHCI1394_IsoRecvIntEventClear, + isoRecvIntEvent); + + ohci->IR_buf_used++; + ohci->IR_buf_next_ind= + (ohci->IR_buf_next_ind + 1) % IR_NUM_DESC; + + /* is buffer processing too slow? (all buffers used) */ + if (ohci->IR_buf_next_ind == ohci->IR_buf_last_ind) { + int i= 0; + + /* stop the context */ + reg_write(ohci, + OHCI1394_IrRcvContextControlClear, 0x8000); + + while (reg_read(ohci, OHCI1394_IrRcvContextControlSet) + & 0x400) { + i++; + + if (i>5000) { + PRINT(KERN_ERR, ohci->id, "runaway loop in DmaIR. bailing out..."); + break; + } + + } + + spin_unlock(&ohci->IR_recv_lock); + PRINT(KERN_ERR, ohci->id, + "iso receive processing too slow... stopped"); + return; + } + + /* reset status field of next descriptor */ + ohci->IR_recv_prg[ohci->IR_buf_next_ind]->status= + IR_RECV_BUF_SIZE; + + spin_unlock(&ohci->IR_recv_lock); + + /* queue bottom half in immediate queue */ + queue_task(&ohci->IR_pdl_task, &tq_immediate); + mark_bh(IMMEDIATE_BH); + } + if (event & OHCI1394_selfIDComplete) { + if (host->in_bus_reset) { + node_id = reg_read(ohci, OHCI1394_NodeID); + if (node_id & 0x8000000) { /* NodeID valid */ + phyid = node_id & 0x0000003f; + isroot = (node_id & 0x40000000) != 0; + + PRINT(KERN_INFO, ohci->id, + "SelfID process finished (phyid %d, %s)", + phyid, (isroot ? "root" : "not root")); + + handle_selfid(ohci, host, phyid, isroot); + } + else + PRINT(KERN_ERR, ohci->id, + "SelfID process finished but NodeID" + " not valid: %08X",node_id); + } + else PRINT(KERN_INFO, ohci->id, + "phy reg received without reset\n"); + } + if (event & OHCI1394_phyRegRcvd) { +#if 0 + if (host->in_bus_reset) { + PRINT(KERN_INFO, ohci->id, "PhyControl: %08X", + reg_read(ohci, OHCI1394_PhyControl)); + } else printk("-%d- phy reg received without reset\n", + ohci->id); +#endif + } + if (event & OHCI1394_reqTxComplete) { + /* async packet sent - transmitter ready */ + u32 ack; + struct hpsb_packet *packet; + + if (ohci->async_queue) { + + spin_lock(&ohci->async_queue_lock); + + ack=reg_read(ohci, OHCI1394_AsReqTrContextControlSet) + & 0xF; + + packet = ohci->async_queue; + ohci->async_queue = packet->xnext; + + if (ohci->async_queue != NULL) { + send_next_async(ohci); + } + spin_unlock(&ohci->async_queue_lock); + PRINT(KERN_INFO,ohci->id, + "packet sent with ack code %d",ack); + hpsb_packet_sent(host, packet, ack); + } else + PRINT(KERN_INFO,ohci->id, + "packet sent without async_queue (self-id?)"); + + ohci->TxRdy++; + } + + ohci->NumInterrupts++; +} + +/* This is the bottom half that processes iso receive descriptor buffers. */ +static void ohci_ir_proc_desc(void *data) +{ + quadlet_t *buf_ptr; + struct ti_ohci *ohci= (struct ti_ohci*)data; + int bytes_left, data_length; + unsigned int idx; + unsigned long flags; + + spin_lock_irqsave(&ohci->IR_recv_lock, flags); + + while(ohci->IR_buf_used > 0) + { + idx= ohci->IR_buf_last_ind; + + /* check to see if a fatal error occurred */ + if ((ohci->IR_recv_prg[idx]->status >> 16) & 0x800) { + int i= 0; + + /* stop the context */ + reg_write(ohci, OHCI1394_IrRcvContextControlClear, + 0x8000); + + while (reg_read(ohci, OHCI1394_IrRcvContextControlSet) + & 0x400) { + i++; + + if (i > 5000) { + PRINT(KERN_ERR, ohci->id, "runaway loop in DmaIR. bailing out..."); + break; + } + + } + + spin_unlock_irqrestore(&ohci->IR_recv_lock, flags); + PRINT(KERN_ERR, ohci->id, + "fatal iso receive error -- status is %d", + ohci->IR_recv_prg[idx]->status & 0x1F); + return; + } + + spin_unlock_irqrestore(&ohci->IR_recv_lock, flags); + + buf_ptr= bus_to_virt(ohci->IR_recv_prg[idx]->address); + bytes_left= IR_RECV_BUF_SIZE; + + /* are we processing a split packet from last buffer */ + if (ohci->IR_sp_bytes_left) { + + if (!ohci->IR_spb_bytes_used) { + /* packet is in process of being dropped */ + if (ohci->IR_sp_bytes_left > bytes_left) { + ohci->IR_sp_bytes_left-= bytes_left; + bytes_left= 0; + } else { + buf_ptr= bus_to_virt((unsigned long) + &((quadlet_t*)ohci->IR_recv_prg + [idx]->address) + [ohci->IR_sp_bytes_left / 4]); + bytes_left-= ohci->IR_sp_bytes_left; + ohci->IR_sp_bytes_left= 0; + } + + } else { + /* packet is being assembled */ + if (ohci->IR_sp_bytes_left > bytes_left) { + memcpy(&ohci->IR_spb + [ohci->IR_spb_bytes_used / 4], + buf_ptr, bytes_left); + ohci->IR_spb_bytes_used+= bytes_left; + ohci->IR_sp_bytes_left-= bytes_left; + bytes_left= 0; + } else { + memcpy(&ohci->IR_spb + [ohci->IR_spb_bytes_used / 4], + buf_ptr, + ohci->IR_sp_bytes_left); + ohci->IR_spb_bytes_used+= + ohci->IR_sp_bytes_left; + hpsb_packet_received(ohci->host, + ohci->IR_spb, + ohci->IR_spb_bytes_used); + buf_ptr= + bus_to_virt((unsigned long) + &((quadlet_t*)ohci->IR_recv_prg + [idx]->address) + [ohci->IR_sp_bytes_left / 4]); + bytes_left-= ohci->IR_sp_bytes_left; + ohci->IR_sp_bytes_left= 0; + ohci->IR_spb_bytes_used= 0; + } + + } + + } + + while(bytes_left > 0) { + data_length= (int)((buf_ptr[0] >> 16) & 0xffff); + + if (data_length % 4) + data_length+= 4 - (data_length % 4); + + /* is this a split packet? */ + if ( (bytes_left - (data_length + 8)) < 0 ) { + + if ( (data_length + 8) <= + IR_SPLIT_PACKET_BUF_SIZE ) { + memcpy(ohci->IR_spb, buf_ptr, + bytes_left); + ohci->IR_spb_bytes_used= bytes_left; + } else { + PRINT(KERN_ERR, ohci->id, "Packet too large for split packet buffer... dropping it"); + PRINT(KERN_DEBUG, ohci->id, "Header: %8.8x\n", buf_ptr[0]); + ohci->IR_spb_bytes_used= 0; + } + + ohci->IR_sp_bytes_left= + (data_length + 8) - bytes_left; + } else { + hpsb_packet_received(ohci->host, buf_ptr, + (data_length + 8)); + buf_ptr= bus_to_virt((unsigned long) + &((quadlet_t*)ohci->IR_recv_prg + [idx]->address) + [(IR_RECV_BUF_SIZE - bytes_left + + data_length + 8) / 4]); + } + + bytes_left-= (data_length + 8); + } + + spin_lock_irqsave(&ohci->IR_recv_lock, flags); + ohci->IR_buf_last_ind= (idx + 1) % IR_NUM_DESC; + ohci->IR_buf_used--; + } + + spin_unlock_irqrestore(&ohci->IR_recv_lock, flags); +} + +static int add_card(struct pci_dev *dev) +{ +#define FAIL(fmt, args...) \ + PRINT_G(KERN_ERR, fmt , ## args); \ + num_of_cards--; \ + remove_card(ohci); \ + return 1; + + struct ti_ohci *ohci; /* shortcut to currently handled device */ + int i; + + if (num_of_cards == MAX_OHCI1394_CARDS) { + PRINT_G(KERN_WARNING, "cannot handle more than %d cards. " + "Adjust MAX_OHCI1394_CARDS in ti_ohci1394.h.", + MAX_OHCI1394_CARDS); + return 1; + } + + ohci = &cards[num_of_cards++]; + + ohci->id = num_of_cards-1; + ohci->dev = dev; + + ohci->state = 0; + + if (!request_irq(dev->irq, ohci_irq_handler, SA_SHIRQ, + OHCI1394_DRIVER_NAME, ohci)) { + PRINT(KERN_INFO, ohci->id, "allocated interrupt %d", dev->irq); + } else { + FAIL("failed to allocate shared interrupt %d", dev->irq); + } + + /* csr_config rom allocation */ + ohci->csr_config_rom = kmalloc(1024, GFP_KERNEL); + if (ohci->csr_config_rom == NULL) { + FAIL("failed to allocate buffer config rom"); + } + memcpy(ohci->csr_config_rom, ohci_csr_rom, sizeof(ohci_csr_rom)); + + /* self-id dma buffer allocation */ + ohci->self_id_buffer = kmalloc(2048, GFP_KERNEL); + if (ohci->self_id_buffer == NULL) { + FAIL("failed to allocate DMA buffer for self-id packets"); + } + + /* AR dma buffer allocation */ + ohci->AR_resp_buf = kmalloc(AR_RESP_BUF_SIZE, GFP_KERNEL); + if (ohci->AR_resp_buf != NULL) { + memset(ohci->AR_resp_buf, 0, AR_RESP_BUF_SIZE); + } else { + FAIL("failed to allocate AR response DMA buffer"); + } + + /* AR dma program allocation */ + ohci->AR_resp_prg = (struct dma_cmd *) kmalloc(AR_RESP_PRG_SIZE, + GFP_KERNEL); + if (ohci->AR_resp_prg != NULL) { + memset(ohci->AR_resp_prg, 0, AR_RESP_PRG_SIZE); + } else { + FAIL("failed to allocate AR response DMA program"); + } + + /* AT dma program allocation */ + ohci->AT_req_prg = (struct dma_cmd *) kmalloc(AT_REQ_PRG_SIZE, + GFP_KERNEL); + if (ohci->AT_req_prg != NULL) { + memset(ohci->AT_req_prg, 0, AT_REQ_PRG_SIZE); + } else { + FAIL("failed to allocate AT request DMA program"); + } + + /* IR dma buffer and program allocation */ + ohci->IR_recv_buf= + kmalloc(IR_NUM_DESC * sizeof(quadlet_t*), + GFP_KERNEL); + + if (ohci->IR_recv_buf == NULL) { + FAIL("failed to allocate IR receive DMA buffer"); + } + + ohci->IR_recv_prg= + kmalloc(IR_NUM_DESC * sizeof(struct dma_cmd*), + GFP_KERNEL); + + if (ohci->IR_recv_prg == NULL) { + FAIL("failed to allocate IR receive DMA program"); + } + + ohci->IR_spb= kmalloc(IR_SPLIT_PACKET_BUF_SIZE, GFP_KERNEL); + + if (ohci->IR_spb == NULL) { + FAIL("failed to allocate IR split packet buffer"); + } + + for (i= 0; i < IR_NUM_DESC; i++) { + ohci->IR_recv_buf[i]= kmalloc(IR_RECV_BUF_SIZE, GFP_KERNEL); + + if (ohci->IR_recv_buf[i] != NULL) { + memset(ohci->IR_recv_buf[i], 0, IR_RECV_BUF_SIZE); + } else { + FAIL("failed to allocate IR DMA buffer"); + } + + ohci->IR_recv_prg[i]= kmalloc(sizeof(struct dma_cmd), + GFP_KERNEL); + + if (ohci->IR_recv_prg[i] != NULL) { + memset(ohci->IR_recv_prg[i], 0, + sizeof(struct dma_cmd)); + } else { + FAIL("failed to allocate IR DMA buffer"); + } + + } + + ohci->IR_buf_used= 0; + ohci->IR_buf_last_ind= 0; + ohci->IR_buf_next_ind= 0; + spin_lock_init(&ohci->IR_recv_lock); + spin_lock_init(&ohci->IR_channel_lock); + ohci->IR_channel_usage= 0x0000000000000000; + + /* initialize iso receive task */ + ohci->IR_pdl_task.routine= ohci_ir_proc_desc; + ohci->IR_pdl_task.data= (void*)ohci; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,13) + ohci->registers = ioremap_nocache(dev->base_address[0], + OHCI1394_REGISTER_SIZE); +#else + ohci->registers = ioremap_nocache(dev->resource[0].start, + OHCI1394_REGISTER_SIZE); +#endif + + if (ohci->registers == NULL) { + FAIL("failed to remap registers - card not accessible"); + } + + PRINT(KERN_INFO, ohci->id, "remapped memory spaces reg 0x%p", + ohci->registers); + + return 0; +#undef FAIL +} + +#ifdef CONFIG_PROC_FS + +#define SR(fmt, reg0, reg1, reg2)\ +p += sprintf(p,fmt,reg_read(ohci, reg0),\ + reg_read(ohci, reg1),reg_read(ohci, reg2)); + +int ohci_get_info(char *buf, char **start, off_t fpos, + int length, int dummy) +{ + struct ti_ohci *ohci=&cards[0]; + struct hpsb_host *host=ohci->host; + char *p=buf; + //unsigned char phyreg; + //int i, nports; + int i; + + p += sprintf(p,"IEEE-1394 OHCI Driver status report:\n"); + p += sprintf(p," bus number: 0x%x Node ID: 0x%x\n", + (reg_read(ohci, OHCI1394_NodeID) & 0xFFC0) >> 6, + reg_read(ohci, OHCI1394_NodeID)&0x3f); +#if 0 + p += sprintf(p," hardware version %d.%d GUID_ROM is %s\n\n", + (reg_read(ohci, OHCI1394_Version) & 0xFF0000) >>16, + reg_read(ohci, OHCI1394_Version) & 0xFF, + (reg_read(ohci, OHCI1394_Version) & 0x01000000) + ? "set" : "clear"); +#endif + p += sprintf(p,"\n### Host data ###\n"); + p += sprintf(p,"node_count: %8d ",host->node_count); + p += sprintf(p,"node_id : %08X\n",host->node_id); + p += sprintf(p,"irm_id : %08X ",host->irm_id); + p += sprintf(p,"busmgr_id : %08X\n",host->busmgr_id); + p += sprintf(p,"%s %s %s\n", + host->initialized ? "initialized" : "", + host->in_bus_reset ? "in_bus_reset" : "", + host->attempt_root ? "attempt_root" : ""); + p += sprintf(p,"%s %s %s %s\n", + host->is_root ? "root" : "", + host->is_cycmst ? "cycle_master" : "", + host->is_irm ? "iso_res_mgr" : "", + host->is_busmgr ? "bus_mgr" : ""); + + p += sprintf(p,"\n### ohci data ###\n"); + p += sprintf(p,"AR_resp_buf : %p AR_resp_prg: %p\n", + ohci->AR_resp_buf, ohci->AR_resp_prg); + + for (i= 0; i < IR_NUM_DESC; i++) { + p += sprintf(p, "IR_recv_buf[%d] : %p IR_recv_prg[%d]: %p\n", + i, ohci->IR_recv_buf[i], i, ohci->IR_recv_prg[i]); + } + + /* ----- Register Dump ----- */ + p += sprintf(p,"\n### HC Register dump ###\n"); + SR("Version : %08x GUID_ROM : %08x ATRetries : %08x\n", + OHCI1394_Version, OHCI1394_GUID_ROM, OHCI1394_ATRetries); + SR("CSRReadData : %08x CSRCompData : %08x CSRControl : %08x\n", + OHCI1394_CSRReadData, OHCI1394_CSRCompareData, OHCI1394_CSRControl); + SR("ConfigROMhdr: %08x BusID : %08x BusOptions : %08x\n", + OHCI1394_ConfigROMhdr, OHCI1394_BusID, OHCI1394_BusOptions); + SR("GUIDHi : %08x GUIDLo : %08x ConfigROMmap: %08x\n", + OHCI1394_GUIDHi, OHCI1394_GUIDLo, OHCI1394_ConfigROMmap); + SR("PtdWrAddrLo : %08x PtdWrAddrHi : %08x VendorID : %08x\n", + OHCI1394_PostedWriteAddressLo, OHCI1394_PostedWriteAddressHi, + OHCI1394_VendorID); + SR("HCControl : %08x SelfIDBuffer: %08x SelfIDCount : %08x\n", + OHCI1394_HCControlSet, OHCI1394_SelfIDBuffer, OHCI1394_SelfIDCount); + SR("IRMuChMaskHi: %08x IRMuChMaskLo: %08x IntEvent : %08x\n", + OHCI1394_IRMultiChanMaskHiSet, OHCI1394_IRMultiChanMaskLoSet, + OHCI1394_IntEventSet); + SR("IntMask : %08x IsoXmIntEvnt: %08x IsoXmIntMask: %08x\n", + OHCI1394_IntMaskSet, OHCI1394_IsoXmitIntEventSet, + OHCI1394_IsoXmitIntMaskSet); + SR("IsoRcvIntEvt: %08x IsoRcvIntMsk: %08x FairnessCtrl: %08x\n", + OHCI1394_IsoRecvIntEventSet, OHCI1394_IsoRecvIntMaskSet, + OHCI1394_FairnessControl); + SR("LinkControl : %08x NodeID : %08x PhyControl : %08x\n", + OHCI1394_LinkControlSet, OHCI1394_NodeID, OHCI1394_PhyControl); + SR("IsoCyclTimer: %08x AsRqFilterHi: %08x AsRqFilterLo: %08x\n", + OHCI1394_IsochronousCycleTimer, + OHCI1394_AsReqFilterHiSet, OHCI1394_AsReqFilterLoSet); + SR("PhyReqFiltHi: %08x PhyReqFiltLo: %08x PhyUpperBnd : %08x\n", + OHCI1394_PhyReqFilterHiSet, OHCI1394_PhyReqFilterLoSet, + OHCI1394_PhyUpperBound); + SR("AsRqTrCxtCtl: %08x AsRqTrCmdPtr: %08x AsRsTrCtxCtl: %08x\n", + OHCI1394_AsReqTrContextControlSet, OHCI1394_AsReqTrCommandPtr, + OHCI1394_AsRspTrContextControlSet); + SR("AsRsTrCmdPtr: %08x AsRqRvCtxCtl: %08x AsRqRvCmdPtr: %08x\n", + OHCI1394_AsRspTrCommandPtr, OHCI1394_AsReqRcvContextControlSet, + OHCI1394_AsReqRcvCommandPtr); + SR("AsRsRvCtxCtl: %08x AsRsRvCmdPtr: %08x IntEvent : %08x\n", + OHCI1394_AsRspRcvContextControlSet, OHCI1394_AsRspRcvCommandPtr, + OHCI1394_IntEventSet); + +#if 0 + p += sprintf(p,"\n### Phy Register dump ###\n"); + phyreg=get_phy_reg(ohci,1); + p += sprintf(p,"offset: %d val: 0x%02x -> RHB: %d IBR: %d Gap_count: %d\n", + 1,phyreg,(phyreg&0x80) != 0, (phyreg&0x40) !=0, phyreg&0x3f); + phyreg=get_phy_reg(ohci,2); + nports=phyreg&0x1f; + p += sprintf(p,"offset: %d val: 0x%02x -> SPD: %d E : %d Ports : %2d\n", + 2,phyreg, + (phyreg&0xC0)>>6, (phyreg&0x20) !=0, nports); + for (i=0;i<nports;i++) { + phyreg=get_phy_reg(ohci,3+i); + p += sprintf(p,"offset: %d val: 0x%02x -> [port %d] TPA: %d TPB: %d | %s %s\n", + 3+i,phyreg, + i, (phyreg&0xC0)>>6, (phyreg&0x30)>>4, + (phyreg&0x08) ? "child" : "parent", + (phyreg&0x04) ? "connected" : "disconnected"); + } + phyreg=get_phy_reg(ohci,3+i); + p += sprintf(p,"offset: %d val: 0x%02x -> ENV: %s Reg_count: %d\n", + 3+i,phyreg, + (((phyreg&0xC0)>>6)==0) ? "backplane" : + (((phyreg&0xC0)>>6)==1) ? "cable" : "reserved", + phyreg&0x3f); +#endif + + p += sprintf(p,"AR_resp_prg ctrl: %08x\n",ohci->AR_resp_prg->control); + p += sprintf(p,"AR_resp_prg status: %08x\n",ohci->AR_resp_prg->status); + + return p - buf; +} + +struct proc_dir_entry ohci_proc_entry = +{ + 0, /* Inode number - dynamic */ + 8, /* Length of the file name */ + "ohci1394", /* The file name */ + S_IFREG | S_IRUGO, /* File mode */ + 1, /* Number of links */ + 0, 0, /* The uid and gid for the file */ + 0, /* The size of the file reported by ls. */ + NULL, /* functions which can be done on the inode */ + ohci_get_info, /* The read function for this file */ + NULL +}; +#endif + +static void remove_card(struct ti_ohci *ohci) +{ + if (ohci->registers) + iounmap(ohci->registers); + if (ohci->AR_resp_buf) + kfree(ohci->AR_resp_buf); + if (ohci->AR_resp_prg) + kfree(ohci->AR_resp_prg); + if (ohci->AT_req_prg) + kfree(ohci->AT_req_prg); + if (ohci->IR_recv_buf) { + int i; + for (i= 0; i < IR_NUM_DESC; i++) { + kfree(ohci->IR_recv_buf[i]); + } + kfree(ohci->IR_recv_buf); + } + if (ohci->IR_recv_prg) { + int i; + for (i= 0; i < IR_NUM_DESC; i++) { + kfree(ohci->IR_recv_prg[i]); + } + kfree(ohci->IR_recv_prg); + } + kfree(ohci->IR_spb); + if (ohci->self_id_buffer) + kfree(ohci->self_id_buffer); + if (ohci->csr_config_rom) + kfree(ohci->csr_config_rom); + + free_irq(ohci->dev->irq, ohci); + + ohci->state = 0; +} + +static int init_driver() +{ + struct pci_dev *dev = NULL; + int success = 0; + int i; + + if (num_of_cards) { + PRINT_G(KERN_DEBUG, __PRETTY_FUNCTION__ " called again"); + return 0; + } + + PRINT_G(KERN_INFO, "looking for Ohci1394 cards"); + + for (i = 0; supported_chips[i][0] != -1; i++) { + while ((dev = pci_find_device(supported_chips[i][0], + supported_chips[i][1], dev)) + != NULL) { + if (add_card(dev) == 0) { + success = 1; + } + } + } + + if (success == 0) { + PRINT_G(KERN_WARNING, "no operable Ohci1394 cards found"); + return -ENXIO; + } + +#ifdef CONFIG_PROC_FS + if (proc_register(&proc_root, &ohci_proc_entry)) { + PRINT_G(KERN_ERR, "unable to register proc file\n"); + return -EIO; + } +#endif + return 0; +} + +static size_t get_ohci_rom(struct hpsb_host *host, const quadlet_t **ptr) +{ + struct ti_ohci *ohci=host->hostdata; + +#if 0 + PRINT(KERN_INFO, ohci->id, "request csr_rom address: %08X", + (u32)ohci->csr_config_rom); +#endif + + *ptr = ohci->csr_config_rom; + return sizeof(ohci_csr_rom); +} + +struct hpsb_host_template *get_ohci_template(void) +{ + static struct hpsb_host_template tmpl; + static int initialized = 0; + + if (!initialized) { + /* Initialize by field names so that a template structure + * reorganization does not influence this code. */ + tmpl.name = "ohci1394"; + + tmpl.detect_hosts = ohci_detect; + tmpl.initialize_host = ohci_initialize; + tmpl.release_host = ohci_remove; + tmpl.get_rom = get_ohci_rom; + tmpl.transmit_packet = ohci_transmit; + tmpl.devctl = ohci_devctl; + + initialized = 1; + } + + return &tmpl; +} + + +#ifdef MODULE + +/* EXPORT_NO_SYMBOLS; */ + +MODULE_AUTHOR("Sebastien Rougeaux <sebastien.rougeaux@anu.edu.au>"); +MODULE_DESCRIPTION("driver for PCI Ohci IEEE-1394 controller"); +MODULE_SUPPORTED_DEVICE("ohci1394"); + +void cleanup_module(void) +{ + hpsb_unregister_lowlevel(get_ohci_template()); +#ifdef CONFIG_PROC_FS + proc_unregister(&proc_root, ohci_proc_entry.low_ino); +#endif + PRINT_G(KERN_INFO, "removed " OHCI1394_DRIVER_NAME " module\n"); +} + +int init_module(void) +{ + + if (hpsb_register_lowlevel(get_ohci_template())) { + PRINT_G(KERN_ERR, "registering failed\n"); + return -ENXIO; + } else { + return 0; + } +} + +#endif /* MODULE */ |