summaryrefslogtreecommitdiffstats
path: root/drivers/ieee1394/pcilynx.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/ieee1394/pcilynx.c
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
Diffstat (limited to 'drivers/ieee1394/pcilynx.c')
-rw-r--r--drivers/ieee1394/pcilynx.c1982
1 files changed, 1982 insertions, 0 deletions
diff --git a/drivers/ieee1394/pcilynx.c b/drivers/ieee1394/pcilynx.c
new file mode 100644
index 000000000000..a261d2b0e5ac
--- /dev/null
+++ b/drivers/ieee1394/pcilynx.c
@@ -0,0 +1,1982 @@
+/*
+ * pcilynx.c - Texas Instruments PCILynx driver
+ * Copyright (C) 1999,2000 Andreas Bombe <andreas.bombe@munich.netsurf.de>,
+ * Stephan Linz <linz@mazet.de>
+ * Manfred Weihs <weihs@ict.tuwien.ac.at>
+ *
+ * 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.
+ */
+
+/*
+ * Contributions:
+ *
+ * Manfred Weihs <weihs@ict.tuwien.ac.at>
+ * reading bus info block (containing GUID) from serial
+ * eeprom via i2c and storing it in config ROM
+ * Reworked code for initiating bus resets
+ * (long, short, with or without hold-off)
+ * Enhancements in async and iso send code
+ */
+
+#include <linux/config.h>
+#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/moduleparam.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <linux/kdev_t.h>
+#include <asm/byteorder.h>
+#include <asm/atomic.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/irq.h>
+
+#include "csr1212.h"
+#include "ieee1394.h"
+#include "ieee1394_types.h"
+#include "hosts.h"
+#include "ieee1394_core.h"
+#include "highlevel.h"
+#include "pcilynx.h"
+
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+
+/* print general (card independent) information */
+#define PRINT_G(level, fmt, args...) printk(level "pcilynx: " fmt "\n" , ## args)
+/* print card specific information */
+#define PRINT(level, card, fmt, args...) printk(level "pcilynx%d: " fmt "\n" , card , ## args)
+
+#ifdef CONFIG_IEEE1394_VERBOSEDEBUG
+#define PRINT_GD(level, fmt, args...) printk(level "pcilynx: " fmt "\n" , ## args)
+#define PRINTD(level, card, fmt, args...) printk(level "pcilynx%d: " fmt "\n" , card , ## args)
+#else
+#define PRINT_GD(level, fmt, args...) do {} while (0)
+#define PRINTD(level, card, fmt, args...) do {} while (0)
+#endif
+
+
+/* Module Parameters */
+static int skip_eeprom = 0;
+module_param(skip_eeprom, int, 0444);
+MODULE_PARM_DESC(skip_eeprom, "Use generic bus info block instead of serial eeprom (default = 0).");
+
+
+static struct hpsb_host_driver lynx_driver;
+static unsigned int card_id;
+
+
+
+/*
+ * I2C stuff
+ */
+
+/* the i2c stuff was inspired by i2c-philips-par.c */
+
+static void bit_setscl(void *data, int state)
+{
+ if (state) {
+ ((struct ti_lynx *) data)->i2c_driven_state |= 0x00000040;
+ } else {
+ ((struct ti_lynx *) data)->i2c_driven_state &= ~0x00000040;
+ }
+ reg_write((struct ti_lynx *) data, SERIAL_EEPROM_CONTROL, ((struct ti_lynx *) data)->i2c_driven_state);
+}
+
+static void bit_setsda(void *data, int state)
+{
+ if (state) {
+ ((struct ti_lynx *) data)->i2c_driven_state |= 0x00000010;
+ } else {
+ ((struct ti_lynx *) data)->i2c_driven_state &= ~0x00000010;
+ }
+ reg_write((struct ti_lynx *) data, SERIAL_EEPROM_CONTROL, ((struct ti_lynx *) data)->i2c_driven_state);
+}
+
+static int bit_getscl(void *data)
+{
+ return reg_read((struct ti_lynx *) data, SERIAL_EEPROM_CONTROL) & 0x00000040;
+}
+
+static int bit_getsda(void *data)
+{
+ return reg_read((struct ti_lynx *) data, SERIAL_EEPROM_CONTROL) & 0x00000010;
+}
+
+static int bit_reg(struct i2c_client *client)
+{
+ return 0;
+}
+
+static int bit_unreg(struct i2c_client *client)
+{
+ return 0;
+}
+
+static struct i2c_algo_bit_data bit_data = {
+ .setsda = bit_setsda,
+ .setscl = bit_setscl,
+ .getsda = bit_getsda,
+ .getscl = bit_getscl,
+ .udelay = 5,
+ .mdelay = 5,
+ .timeout = 100,
+};
+
+static struct i2c_adapter bit_ops = {
+ .id = 0xAA, //FIXME: probably we should get an id in i2c-id.h
+ .client_register = bit_reg,
+ .client_unregister = bit_unreg,
+ .name = "PCILynx I2C",
+};
+
+
+
+/*
+ * PCL handling functions.
+ */
+
+static pcl_t alloc_pcl(struct ti_lynx *lynx)
+{
+ u8 m;
+ int i, j;
+
+ spin_lock(&lynx->lock);
+ /* FIXME - use ffz() to make this readable */
+ for (i = 0; i < (LOCALRAM_SIZE / 1024); i++) {
+ m = lynx->pcl_bmap[i];
+ for (j = 0; j < 8; j++) {
+ if (m & 1<<j) {
+ continue;
+ }
+ m |= 1<<j;
+ lynx->pcl_bmap[i] = m;
+ spin_unlock(&lynx->lock);
+ return 8 * i + j;
+ }
+ }
+ spin_unlock(&lynx->lock);
+
+ return -1;
+}
+
+
+#if 0
+static void free_pcl(struct ti_lynx *lynx, pcl_t pclid)
+{
+ int off, bit;
+
+ off = pclid / 8;
+ bit = pclid % 8;
+
+ if (pclid < 0) {
+ return;
+ }
+
+ spin_lock(&lynx->lock);
+ if (lynx->pcl_bmap[off] & 1<<bit) {
+ lynx->pcl_bmap[off] &= ~(1<<bit);
+ } else {
+ PRINT(KERN_ERR, lynx->id,
+ "attempted to free unallocated PCL %d", pclid);
+ }
+ spin_unlock(&lynx->lock);
+}
+
+/* functions useful for debugging */
+static void pretty_print_pcl(const struct ti_pcl *pcl)
+{
+ int i;
+
+ printk("PCL next %08x, userdata %08x, status %08x, remtrans %08x, nextbuf %08x\n",
+ pcl->next, pcl->user_data, pcl->pcl_status,
+ pcl->remaining_transfer_count, pcl->next_data_buffer);
+
+ printk("PCL");
+ for (i=0; i<13; i++) {
+ printk(" c%x:%08x d%x:%08x",
+ i, pcl->buffer[i].control, i, pcl->buffer[i].pointer);
+ if (!(i & 0x3) && (i != 12)) printk("\nPCL");
+ }
+ printk("\n");
+}
+
+static void print_pcl(const struct ti_lynx *lynx, pcl_t pclid)
+{
+ struct ti_pcl pcl;
+
+ get_pcl(lynx, pclid, &pcl);
+ pretty_print_pcl(&pcl);
+}
+#endif
+
+
+
+/***********************************
+ * IEEE-1394 functionality section *
+ ***********************************/
+
+
+static int get_phy_reg(struct ti_lynx *lynx, int addr)
+{
+ int retval;
+ int i = 0;
+
+ unsigned long flags;
+
+ if (addr > 15) {
+ PRINT(KERN_ERR, lynx->id,
+ "%s: PHY register address %d out of range",
+ __FUNCTION__, addr);
+ return -1;
+ }
+
+ spin_lock_irqsave(&lynx->phy_reg_lock, flags);
+
+ reg_write(lynx, LINK_PHY, LINK_PHY_READ | LINK_PHY_ADDR(addr));
+ do {
+ retval = reg_read(lynx, LINK_PHY);
+
+ if (i > 10000) {
+ PRINT(KERN_ERR, lynx->id, "%s: runaway loop, aborting",
+ __FUNCTION__);
+ retval = -1;
+ break;
+ }
+ i++;
+ } while ((retval & 0xf00) != LINK_PHY_RADDR(addr));
+
+ reg_write(lynx, LINK_INT_STATUS, LINK_INT_PHY_REG_RCVD);
+ spin_unlock_irqrestore(&lynx->phy_reg_lock, flags);
+
+ if (retval != -1) {
+ return retval & 0xff;
+ } else {
+ return -1;
+ }
+}
+
+static int set_phy_reg(struct ti_lynx *lynx, int addr, int val)
+{
+ unsigned long flags;
+
+ if (addr > 15) {
+ PRINT(KERN_ERR, lynx->id,
+ "%s: PHY register address %d out of range", __FUNCTION__, addr);
+ return -1;
+ }
+
+ if (val > 0xff) {
+ PRINT(KERN_ERR, lynx->id,
+ "%s: PHY register value %d out of range", __FUNCTION__, val);
+ return -1;
+ }
+
+ spin_lock_irqsave(&lynx->phy_reg_lock, flags);
+
+ reg_write(lynx, LINK_PHY, LINK_PHY_WRITE | LINK_PHY_ADDR(addr)
+ | LINK_PHY_WDATA(val));
+
+ spin_unlock_irqrestore(&lynx->phy_reg_lock, flags);
+
+ return 0;
+}
+
+static int sel_phy_reg_page(struct ti_lynx *lynx, int page)
+{
+ int reg;
+
+ if (page > 7) {
+ PRINT(KERN_ERR, lynx->id,
+ "%s: PHY page %d out of range", __FUNCTION__, page);
+ return -1;
+ }
+
+ reg = get_phy_reg(lynx, 7);
+ if (reg != -1) {
+ reg &= 0x1f;
+ reg |= (page << 5);
+ set_phy_reg(lynx, 7, reg);
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+#if 0 /* not needed at this time */
+static int sel_phy_reg_port(struct ti_lynx *lynx, int port)
+{
+ int reg;
+
+ if (port > 15) {
+ PRINT(KERN_ERR, lynx->id,
+ "%s: PHY port %d out of range", __FUNCTION__, port);
+ return -1;
+ }
+
+ reg = get_phy_reg(lynx, 7);
+ if (reg != -1) {
+ reg &= 0xf0;
+ reg |= port;
+ set_phy_reg(lynx, 7, reg);
+ return 0;
+ } else {
+ return -1;
+ }
+}
+#endif
+
+static u32 get_phy_vendorid(struct ti_lynx *lynx)
+{
+ u32 pvid = 0;
+ sel_phy_reg_page(lynx, 1);
+ pvid |= (get_phy_reg(lynx, 10) << 16);
+ pvid |= (get_phy_reg(lynx, 11) << 8);
+ pvid |= get_phy_reg(lynx, 12);
+ PRINT(KERN_INFO, lynx->id, "PHY vendor id 0x%06x", pvid);
+ return pvid;
+}
+
+static u32 get_phy_productid(struct ti_lynx *lynx)
+{
+ u32 id = 0;
+ sel_phy_reg_page(lynx, 1);
+ id |= (get_phy_reg(lynx, 13) << 16);
+ id |= (get_phy_reg(lynx, 14) << 8);
+ id |= get_phy_reg(lynx, 15);
+ PRINT(KERN_INFO, lynx->id, "PHY product id 0x%06x", id);
+ return id;
+}
+
+static quadlet_t generate_own_selfid(struct ti_lynx *lynx,
+ struct hpsb_host *host)
+{
+ quadlet_t lsid;
+ char phyreg[7];
+ int i;
+
+ phyreg[0] = lynx->phy_reg0;
+ for (i = 1; i < 7; i++) {
+ phyreg[i] = get_phy_reg(lynx, i);
+ }
+
+ /* FIXME? We assume a TSB21LV03A phy here. This code doesn't support
+ more than 3 ports on the PHY anyway. */
+
+ lsid = 0x80400000 | ((phyreg[0] & 0xfc) << 22);
+ lsid |= (phyreg[1] & 0x3f) << 16; /* gap count */
+ lsid |= (phyreg[2] & 0xc0) << 8; /* max speed */
+ if (!hpsb_disable_irm)
+ lsid |= (phyreg[6] & 0x01) << 11; /* contender (phy dependent) */
+ /* lsid |= 1 << 11; *//* set contender (hack) */
+ lsid |= (phyreg[6] & 0x10) >> 3; /* initiated reset */
+
+ for (i = 0; i < (phyreg[2] & 0xf); i++) { /* ports */
+ if (phyreg[3 + i] & 0x4) {
+ lsid |= (((phyreg[3 + i] & 0x8) | 0x10) >> 3)
+ << (6 - i*2);
+ } else {
+ lsid |= 1 << (6 - i*2);
+ }
+ }
+
+ cpu_to_be32s(&lsid);
+ PRINT(KERN_DEBUG, lynx->id, "generated own selfid 0x%x", lsid);
+ return lsid;
+}
+
+static void handle_selfid(struct ti_lynx *lynx, struct hpsb_host *host)
+{
+ quadlet_t *q = lynx->rcv_page;
+ int phyid, isroot, size;
+ quadlet_t lsid = 0;
+ int i;
+
+ if (lynx->phy_reg0 == -1 || lynx->selfid_size == -1) return;
+
+ size = lynx->selfid_size;
+ phyid = lynx->phy_reg0;
+
+ i = (size > 16 ? 16 : size) / 4 - 1;
+ while (i >= 0) {
+ cpu_to_be32s(&q[i]);
+ i--;
+ }
+
+ if (!lynx->phyic.reg_1394a) {
+ lsid = generate_own_selfid(lynx, host);
+ }
+
+ isroot = (phyid & 2) != 0;
+ phyid >>= 2;
+ PRINT(KERN_INFO, lynx->id, "SelfID process finished (phyid %d, %s)",
+ phyid, (isroot ? "root" : "not root"));
+ reg_write(lynx, LINK_ID, (0xffc0 | phyid) << 16);
+
+ if (!lynx->phyic.reg_1394a && !size) {
+ hpsb_selfid_received(host, lsid);
+ }
+
+ while (size > 0) {
+ struct selfid *sid = (struct selfid *)q;
+
+ if (!lynx->phyic.reg_1394a && !sid->extended
+ && (sid->phy_id == (phyid + 1))) {
+ hpsb_selfid_received(host, lsid);
+ }
+
+ if (q[0] == ~q[1]) {
+ PRINT(KERN_DEBUG, lynx->id, "SelfID packet 0x%x rcvd",
+ q[0]);
+ hpsb_selfid_received(host, q[0]);
+ } else {
+ PRINT(KERN_INFO, lynx->id,
+ "inconsistent selfid 0x%x/0x%x", q[0], q[1]);
+ }
+ q += 2;
+ size -= 8;
+ }
+
+ if (!lynx->phyic.reg_1394a && isroot && phyid != 0) {
+ hpsb_selfid_received(host, lsid);
+ }
+
+ hpsb_selfid_complete(host, phyid, isroot);
+
+ if (host->in_bus_reset) return; /* in bus reset again */
+
+ if (isroot) reg_set_bits(lynx, LINK_CONTROL, LINK_CONTROL_CYCMASTER); //FIXME: I do not think, we need this here
+ reg_set_bits(lynx, LINK_CONTROL,
+ LINK_CONTROL_RCV_CMP_VALID | LINK_CONTROL_TX_ASYNC_EN
+ | LINK_CONTROL_RX_ASYNC_EN | LINK_CONTROL_CYCTIMEREN);
+}
+
+
+
+/* This must be called with the respective queue_lock held. */
+static void send_next(struct ti_lynx *lynx, int what)
+{
+ struct ti_pcl pcl;
+ struct lynx_send_data *d;
+ struct hpsb_packet *packet;
+
+ d = (what == hpsb_iso ? &lynx->iso_send : &lynx->async);
+ if (!list_empty(&d->pcl_queue)) {
+ PRINT(KERN_ERR, lynx->id, "trying to queue a new packet in nonempty fifo");
+ BUG();
+ }
+
+ packet = driver_packet(d->queue.next);
+ list_move_tail(&packet->driver_list, &d->pcl_queue);
+
+ d->header_dma = pci_map_single(lynx->dev, packet->header,
+ packet->header_size, PCI_DMA_TODEVICE);
+ if (packet->data_size) {
+ d->data_dma = pci_map_single(lynx->dev, packet->data,
+ packet->data_size,
+ PCI_DMA_TODEVICE);
+ } else {
+ d->data_dma = 0;
+ }
+
+ pcl.next = PCL_NEXT_INVALID;
+ pcl.async_error_next = PCL_NEXT_INVALID;
+ pcl.pcl_status = 0;
+ pcl.buffer[0].control = packet->speed_code << 14 | packet->header_size;
+#ifndef __BIG_ENDIAN
+ pcl.buffer[0].control |= PCL_BIGENDIAN;
+#endif
+ pcl.buffer[0].pointer = d->header_dma;
+ pcl.buffer[1].control = PCL_LAST_BUFF | packet->data_size;
+ pcl.buffer[1].pointer = d->data_dma;
+
+ switch (packet->type) {
+ case hpsb_async:
+ pcl.buffer[0].control |= PCL_CMD_XMT;
+ break;
+ case hpsb_iso:
+ pcl.buffer[0].control |= PCL_CMD_XMT | PCL_ISOMODE;
+ break;
+ case hpsb_raw:
+ pcl.buffer[0].control |= PCL_CMD_UNFXMT;
+ break;
+ }
+
+ put_pcl(lynx, d->pcl, &pcl);
+ run_pcl(lynx, d->pcl_start, d->channel);
+}
+
+
+/* called from subsystem core */
+static int lynx_transmit(struct hpsb_host *host, struct hpsb_packet *packet)
+{
+ struct ti_lynx *lynx = host->hostdata;
+ struct lynx_send_data *d;
+ unsigned long flags;
+
+ if (packet->data_size >= 4096) {
+ PRINT(KERN_ERR, lynx->id, "transmit packet data too big (%Zd)",
+ packet->data_size);
+ return -EOVERFLOW;
+ }
+
+ switch (packet->type) {
+ case hpsb_async:
+ case hpsb_raw:
+ d = &lynx->async;
+ break;
+ case hpsb_iso:
+ d = &lynx->iso_send;
+ break;
+ default:
+ PRINT(KERN_ERR, lynx->id, "invalid packet type %d",
+ packet->type);
+ return -EINVAL;
+ }
+
+ if (packet->tcode == TCODE_WRITEQ
+ || packet->tcode == TCODE_READQ_RESPONSE) {
+ cpu_to_be32s(&packet->header[3]);
+ }
+
+ spin_lock_irqsave(&d->queue_lock, flags);
+
+ list_add_tail(&packet->driver_list, &d->queue);
+ if (list_empty(&d->pcl_queue))
+ send_next(lynx, packet->type);
+
+ spin_unlock_irqrestore(&d->queue_lock, flags);
+
+ return 0;
+}
+
+
+/* called from subsystem core */
+static int lynx_devctl(struct hpsb_host *host, enum devctl_cmd cmd, int arg)
+{
+ struct ti_lynx *lynx = host->hostdata;
+ int retval = 0;
+ struct hpsb_packet *packet;
+ LIST_HEAD(packet_list);
+ unsigned long flags;
+ int phy_reg;
+
+ switch (cmd) {
+ case RESET_BUS:
+ if (reg_read(lynx, LINK_INT_STATUS) & LINK_INT_PHY_BUSRESET) {
+ retval = 0;
+ break;
+ }
+
+ switch (arg) {
+ case SHORT_RESET:
+ if (lynx->phyic.reg_1394a) {
+ phy_reg = get_phy_reg(lynx, 5);
+ if (phy_reg == -1) {
+ PRINT(KERN_ERR, lynx->id, "cannot reset bus, because read phy reg failed");
+ retval = -1;
+ break;
+ }
+ phy_reg |= 0x40;
+
+ PRINT(KERN_INFO, lynx->id, "resetting bus (short bus reset) on request");
+
+ lynx->selfid_size = -1;
+ lynx->phy_reg0 = -1;
+ set_phy_reg(lynx, 5, phy_reg); /* set ISBR */
+ break;
+ } else {
+ PRINT(KERN_INFO, lynx->id, "cannot do short bus reset, because of old phy");
+ /* fall through to long bus reset */
+ }
+ case LONG_RESET:
+ phy_reg = get_phy_reg(lynx, 1);
+ if (phy_reg == -1) {
+ PRINT(KERN_ERR, lynx->id, "cannot reset bus, because read phy reg failed");
+ retval = -1;
+ break;
+ }
+ phy_reg |= 0x40;
+
+ PRINT(KERN_INFO, lynx->id, "resetting bus (long bus reset) on request");
+
+ lynx->selfid_size = -1;
+ lynx->phy_reg0 = -1;
+ set_phy_reg(lynx, 1, phy_reg); /* clear RHB, set IBR */
+ break;
+ case SHORT_RESET_NO_FORCE_ROOT:
+ if (lynx->phyic.reg_1394a) {
+ phy_reg = get_phy_reg(lynx, 1);
+ if (phy_reg == -1) {
+ PRINT(KERN_ERR, lynx->id, "cannot reset bus, because read phy reg failed");
+ retval = -1;
+ break;
+ }
+ if (phy_reg & 0x80) {
+ phy_reg &= ~0x80;
+ set_phy_reg(lynx, 1, phy_reg); /* clear RHB */
+ }
+
+ phy_reg = get_phy_reg(lynx, 5);
+ if (phy_reg == -1) {
+ PRINT(KERN_ERR, lynx->id, "cannot reset bus, because read phy reg failed");
+ retval = -1;
+ break;
+ }
+ phy_reg |= 0x40;
+
+ PRINT(KERN_INFO, lynx->id, "resetting bus (short bus reset, no force_root) on request");
+
+ lynx->selfid_size = -1;
+ lynx->phy_reg0 = -1;
+ set_phy_reg(lynx, 5, phy_reg); /* set ISBR */
+ break;
+ } else {
+ PRINT(KERN_INFO, lynx->id, "cannot do short bus reset, because of old phy");
+ /* fall through to long bus reset */
+ }
+ case LONG_RESET_NO_FORCE_ROOT:
+ phy_reg = get_phy_reg(lynx, 1);
+ if (phy_reg == -1) {
+ PRINT(KERN_ERR, lynx->id, "cannot reset bus, because read phy reg failed");
+ retval = -1;
+ break;
+ }
+ phy_reg &= ~0x80;
+ phy_reg |= 0x40;
+
+ PRINT(KERN_INFO, lynx->id, "resetting bus (long bus reset, no force_root) on request");
+
+ lynx->selfid_size = -1;
+ lynx->phy_reg0 = -1;
+ set_phy_reg(lynx, 1, phy_reg); /* clear RHB, set IBR */
+ break;
+ case SHORT_RESET_FORCE_ROOT:
+ if (lynx->phyic.reg_1394a) {
+ phy_reg = get_phy_reg(lynx, 1);
+ if (phy_reg == -1) {
+ PRINT(KERN_ERR, lynx->id, "cannot reset bus, because read phy reg failed");
+ retval = -1;
+ break;
+ }
+ if (!(phy_reg & 0x80)) {
+ phy_reg |= 0x80;
+ set_phy_reg(lynx, 1, phy_reg); /* set RHB */
+ }
+
+ phy_reg = get_phy_reg(lynx, 5);
+ if (phy_reg == -1) {
+ PRINT(KERN_ERR, lynx->id, "cannot reset bus, because read phy reg failed");
+ retval = -1;
+ break;
+ }
+ phy_reg |= 0x40;
+
+ PRINT(KERN_INFO, lynx->id, "resetting bus (short bus reset, force_root set) on request");
+
+ lynx->selfid_size = -1;
+ lynx->phy_reg0 = -1;
+ set_phy_reg(lynx, 5, phy_reg); /* set ISBR */
+ break;
+ } else {
+ PRINT(KERN_INFO, lynx->id, "cannot do short bus reset, because of old phy");
+ /* fall through to long bus reset */
+ }
+ case LONG_RESET_FORCE_ROOT:
+ phy_reg = get_phy_reg(lynx, 1);
+ if (phy_reg == -1) {
+ PRINT(KERN_ERR, lynx->id, "cannot reset bus, because read phy reg failed");
+ retval = -1;
+ break;
+ }
+ phy_reg |= 0xc0;
+
+ PRINT(KERN_INFO, lynx->id, "resetting bus (long bus reset, force_root set) on request");
+
+ lynx->selfid_size = -1;
+ lynx->phy_reg0 = -1;
+ set_phy_reg(lynx, 1, phy_reg); /* set IBR and RHB */
+ break;
+ default:
+ PRINT(KERN_ERR, lynx->id, "unknown argument for reset_bus command %d", arg);
+ retval = -1;
+ }
+
+ break;
+
+ case GET_CYCLE_COUNTER:
+ retval = reg_read(lynx, CYCLE_TIMER);
+ break;
+
+ case SET_CYCLE_COUNTER:
+ reg_write(lynx, CYCLE_TIMER, arg);
+ break;
+
+ case SET_BUS_ID:
+ reg_write(lynx, LINK_ID,
+ (arg << 22) | (reg_read(lynx, LINK_ID) & 0x003f0000));
+ break;
+
+ case ACT_CYCLE_MASTER:
+ if (arg) {
+ reg_set_bits(lynx, LINK_CONTROL,
+ LINK_CONTROL_CYCMASTER);
+ } else {
+ reg_clear_bits(lynx, LINK_CONTROL,
+ LINK_CONTROL_CYCMASTER);
+ }
+ break;
+
+ case CANCEL_REQUESTS:
+ spin_lock_irqsave(&lynx->async.queue_lock, flags);
+
+ reg_write(lynx, DMA_CHAN_CTRL(CHANNEL_ASYNC_SEND), 0);
+ list_splice(&lynx->async.queue, &packet_list);
+ INIT_LIST_HEAD(&lynx->async.queue);
+
+ if (list_empty(&lynx->async.pcl_queue)) {
+ spin_unlock_irqrestore(&lynx->async.queue_lock, flags);
+ PRINTD(KERN_DEBUG, lynx->id, "no async packet in PCL to cancel");
+ } else {
+ struct ti_pcl pcl;
+ u32 ack;
+ struct hpsb_packet *packet;
+
+ PRINT(KERN_INFO, lynx->id, "cancelling async packet, that was already in PCL");
+
+ get_pcl(lynx, lynx->async.pcl, &pcl);
+
+ packet = driver_packet(lynx->async.pcl_queue.next);
+ list_del_init(&packet->driver_list);
+
+ pci_unmap_single(lynx->dev, lynx->async.header_dma,
+ packet->header_size, PCI_DMA_TODEVICE);
+ if (packet->data_size) {
+ pci_unmap_single(lynx->dev, lynx->async.data_dma,
+ packet->data_size, PCI_DMA_TODEVICE);
+ }
+
+ spin_unlock_irqrestore(&lynx->async.queue_lock, flags);
+
+ if (pcl.pcl_status & DMA_CHAN_STAT_PKTCMPL) {
+ if (pcl.pcl_status & DMA_CHAN_STAT_SPECIALACK) {
+ ack = (pcl.pcl_status >> 15) & 0xf;
+ PRINTD(KERN_INFO, lynx->id, "special ack %d", ack);
+ ack = (ack == 1 ? ACKX_TIMEOUT : ACKX_SEND_ERROR);
+ } else {
+ ack = (pcl.pcl_status >> 15) & 0xf;
+ }
+ } else {
+ PRINT(KERN_INFO, lynx->id, "async packet was not completed");
+ ack = ACKX_ABORTED;
+ }
+ hpsb_packet_sent(host, packet, ack);
+ }
+
+ while (!list_empty(&packet_list)) {
+ packet = driver_packet(packet_list.next);
+ list_del_init(&packet->driver_list);
+ hpsb_packet_sent(host, packet, ACKX_ABORTED);
+ }
+
+ break;
+
+ case ISO_LISTEN_CHANNEL:
+ spin_lock_irqsave(&lynx->iso_rcv.lock, flags);
+
+ if (lynx->iso_rcv.chan_count++ == 0) {
+ reg_write(lynx, DMA_WORD1_CMP_ENABLE(CHANNEL_ISO_RCV),
+ DMA_WORD1_CMP_ENABLE_MASTER);
+ }
+
+ spin_unlock_irqrestore(&lynx->iso_rcv.lock, flags);
+ break;
+
+ case ISO_UNLISTEN_CHANNEL:
+ spin_lock_irqsave(&lynx->iso_rcv.lock, flags);
+
+ if (--lynx->iso_rcv.chan_count == 0) {
+ reg_write(lynx, DMA_WORD1_CMP_ENABLE(CHANNEL_ISO_RCV),
+ 0);
+ }
+
+ spin_unlock_irqrestore(&lynx->iso_rcv.lock, flags);
+ break;
+
+ default:
+ PRINT(KERN_ERR, lynx->id, "unknown devctl command %d", cmd);
+ retval = -1;
+ }
+
+ return retval;
+}
+
+
+/***************************************
+ * IEEE-1394 functionality section END *
+ ***************************************/
+
+#ifdef CONFIG_IEEE1394_PCILYNX_PORTS
+/* VFS functions for local bus / aux device access. Access to those
+ * is implemented as a character device instead of block devices
+ * because buffers are not wanted for this. Therefore llseek (from
+ * VFS) can be used for these char devices with obvious effects.
+ */
+static int mem_open(struct inode*, struct file*);
+static int mem_release(struct inode*, struct file*);
+static unsigned int aux_poll(struct file*, struct poll_table_struct*);
+static loff_t mem_llseek(struct file*, loff_t, int);
+static ssize_t mem_read (struct file*, char*, size_t, loff_t*);
+static ssize_t mem_write(struct file*, const char*, size_t, loff_t*);
+
+
+static struct file_operations aux_ops = {
+ .owner = THIS_MODULE,
+ .read = mem_read,
+ .write = mem_write,
+ .poll = aux_poll,
+ .llseek = mem_llseek,
+ .open = mem_open,
+ .release = mem_release,
+};
+
+
+static void aux_setup_pcls(struct ti_lynx *lynx)
+{
+ struct ti_pcl pcl;
+
+ pcl.next = PCL_NEXT_INVALID;
+ pcl.user_data = pcl_bus(lynx, lynx->dmem_pcl);
+ put_pcl(lynx, lynx->dmem_pcl, &pcl);
+}
+
+static int mem_open(struct inode *inode, struct file *file)
+{
+ int cid = iminor(inode);
+ enum { t_rom, t_aux, t_ram } type;
+ struct memdata *md;
+
+ if (cid < PCILYNX_MINOR_AUX_START) {
+ /* just for completeness */
+ return -ENXIO;
+ } else if (cid < PCILYNX_MINOR_ROM_START) {
+ cid -= PCILYNX_MINOR_AUX_START;
+ if (cid >= num_of_cards || !cards[cid].aux_port)
+ return -ENXIO;
+ type = t_aux;
+ } else if (cid < PCILYNX_MINOR_RAM_START) {
+ cid -= PCILYNX_MINOR_ROM_START;
+ if (cid >= num_of_cards || !cards[cid].local_rom)
+ return -ENXIO;
+ type = t_rom;
+ } else {
+ /* WARNING: Know what you are doing when opening RAM.
+ * It is currently used inside the driver! */
+ cid -= PCILYNX_MINOR_RAM_START;
+ if (cid >= num_of_cards || !cards[cid].local_ram)
+ return -ENXIO;
+ type = t_ram;
+ }
+
+ md = (struct memdata *)kmalloc(sizeof(struct memdata), SLAB_KERNEL);
+ if (md == NULL)
+ return -ENOMEM;
+
+ md->lynx = &cards[cid];
+ md->cid = cid;
+
+ switch (type) {
+ case t_rom:
+ md->type = rom;
+ break;
+ case t_ram:
+ md->type = ram;
+ break;
+ case t_aux:
+ atomic_set(&md->aux_intr_last_seen,
+ atomic_read(&cards[cid].aux_intr_seen));
+ md->type = aux;
+ break;
+ }
+
+ file->private_data = md;
+
+ return 0;
+}
+
+static int mem_release(struct inode *inode, struct file *file)
+{
+ kfree(file->private_data);
+ return 0;
+}
+
+static unsigned int aux_poll(struct file *file, poll_table *pt)
+{
+ struct memdata *md = (struct memdata *)file->private_data;
+ int cid = md->cid;
+ unsigned int mask;
+
+ /* reading and writing is always allowed */
+ mask = POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM;
+
+ if (md->type == aux) {
+ poll_wait(file, &cards[cid].aux_intr_wait, pt);
+
+ if (atomic_read(&md->aux_intr_last_seen)
+ != atomic_read(&cards[cid].aux_intr_seen)) {
+ mask |= POLLPRI;
+ atomic_inc(&md->aux_intr_last_seen);
+ }
+ }
+
+ return mask;
+}
+
+loff_t mem_llseek(struct file *file, loff_t offs, int orig)
+{
+ loff_t newoffs;
+
+ switch (orig) {
+ case 0:
+ newoffs = offs;
+ break;
+ case 1:
+ newoffs = offs + file->f_pos;
+ break;
+ case 2:
+ newoffs = PCILYNX_MAX_MEMORY + 1 + offs;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (newoffs < 0 || newoffs > PCILYNX_MAX_MEMORY + 1) return -EINVAL;
+
+ file->f_pos = newoffs;
+ return newoffs;
+}
+
+/*
+ * do not DMA if count is too small because this will have a serious impact
+ * on performance - the value 2400 was found by experiment and may not work
+ * everywhere as good as here - use mem_mindma option for modules to change
+ */
+static short mem_mindma = 2400;
+module_param(mem_mindma, short, 0444);
+MODULE_PARM_DESC(mem_mindma, "Minimum amount of data required to use DMA");
+
+static ssize_t mem_dmaread(struct memdata *md, u32 physbuf, ssize_t count,
+ int offset)
+{
+ pcltmp_t pcltmp;
+ struct ti_pcl *pcl;
+ size_t retval;
+ int i;
+ DECLARE_WAITQUEUE(wait, current);
+
+ count &= ~3;
+ count = min(count, 53196);
+ retval = count;
+
+ if (reg_read(md->lynx, DMA_CHAN_CTRL(CHANNEL_LOCALBUS))
+ & DMA_CHAN_CTRL_BUSY) {
+ PRINT(KERN_WARNING, md->lynx->id, "DMA ALREADY ACTIVE!");
+ }
+
+ reg_write(md->lynx, LBUS_ADDR, md->type | offset);
+
+ pcl = edit_pcl(md->lynx, md->lynx->dmem_pcl, &pcltmp);
+ pcl->buffer[0].control = PCL_CMD_LBUS_TO_PCI | min(count, 4092);
+ pcl->buffer[0].pointer = physbuf;
+ count -= 4092;
+
+ i = 0;
+ while (count > 0) {
+ i++;
+ pcl->buffer[i].control = min(count, 4092);
+ pcl->buffer[i].pointer = physbuf + i * 4092;
+ count -= 4092;
+ }
+ pcl->buffer[i].control |= PCL_LAST_BUFF;
+ commit_pcl(md->lynx, md->lynx->dmem_pcl, &pcltmp);
+
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&md->lynx->mem_dma_intr_wait, &wait);
+ run_sub_pcl(md->lynx, md->lynx->dmem_pcl, 2, CHANNEL_LOCALBUS);
+
+ schedule();
+ while (reg_read(md->lynx, DMA_CHAN_CTRL(CHANNEL_LOCALBUS))
+ & DMA_CHAN_CTRL_BUSY) {
+ if (signal_pending(current)) {
+ retval = -EINTR;
+ break;
+ }
+ schedule();
+ }
+
+ reg_write(md->lynx, DMA_CHAN_CTRL(CHANNEL_LOCALBUS), 0);
+ remove_wait_queue(&md->lynx->mem_dma_intr_wait, &wait);
+
+ if (reg_read(md->lynx, DMA_CHAN_CTRL(CHANNEL_LOCALBUS))
+ & DMA_CHAN_CTRL_BUSY) {
+ PRINT(KERN_ERR, md->lynx->id, "DMA STILL ACTIVE!");
+ }
+
+ return retval;
+}
+
+static ssize_t mem_read(struct file *file, char *buffer, size_t count,
+ loff_t *offset)
+{
+ struct memdata *md = (struct memdata *)file->private_data;
+ ssize_t bcount;
+ size_t alignfix;
+ loff_t off = *offset; /* avoid useless 64bit-arithmetic */
+ ssize_t retval;
+ void *membase;
+
+ if ((off + count) > PCILYNX_MAX_MEMORY+1) {
+ count = PCILYNX_MAX_MEMORY+1 - off;
+ }
+ if (count == 0 || off > PCILYNX_MAX_MEMORY) {
+ return -ENOSPC;
+ }
+
+ switch (md->type) {
+ case rom:
+ membase = md->lynx->local_rom;
+ break;
+ case ram:
+ membase = md->lynx->local_ram;
+ break;
+ case aux:
+ membase = md->lynx->aux_port;<