/*
  Copyright 2004 Feyd
  Copyright 2004, 2005, 2006 Jean-Baptiste Note

  This file is part of prism54usb.

  prism54usb 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.

  prism54usb 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 prism54usb; if not, write to the Free Software Foundation,
  Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

*/

#include <linux/version.h>
#include "islusb_net2280.h"
#include "islusb_dev.h"
#include "islsm_log.h"

/*
 * PCI registers access functions
 */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,12)
#define RW_TIMEOUT     1000
#else
#define RW_TIMEOUT     1 * HZ
#endif

static inline void
islusb_lock_reg(struct p54u *p54u)
{
	down(&p54u->reg_sem);
}

static inline void
islusb_unlock_reg(struct p54u *p54u)
{
	up(&p54u->reg_sem);
}

#define IS_U32_PORT(port) ((port & P54U_PORT_U32) == P54U_PORT_U32)

static int
_net2280_write_reg(struct usb_device *usbdev, int ep, int port,
			  u32 val, u32 addr) {
	/* structures & defs should be in net2280.h -- this is not at all islusb-specific */
	struct net2280_reg_write wreg;
	unsigned pipe, retlen;
	int err;

	wreg.port = cpu_to_le16(port);
	wreg.addr = cpu_to_le32(addr);
	if (!IS_U32_PORT(port))
		val &= 0xffff;
	wreg.val  = cpu_to_le32(val);

	pipe = usb_sndbulkpipe(usbdev, ep & USB_ENDPOINT_NUMBER_MASK);
	/* passing a stack'd address to bulk_msg is OKay for many
	   architecture.  We may have some problems on some, though */
	err = usb_bulk_msg(usbdev, pipe, &wreg, sizeof(wreg), &retlen, RW_TIMEOUT);

	if (err)
		usb_clear_halt(usbdev, pipe);

	if (!err)
		islsm_txdata_debug(ep, &wreg, retlen);

	return err;
}

static int
_net2280_send_read_reg(struct usb_device *usbdev, int ep, int port,
		       u32 addr) {
	struct net2280_reg_read rreg;
	unsigned pipe, retlen;
	int err;

	rreg.port = cpu_to_le16(port);
	rreg.addr = cpu_to_le32(addr);

	pipe = usb_sndbulkpipe(usbdev, ep & USB_ENDPOINT_NUMBER_MASK);
	/* passing a stack'd address to bulk_msg is OKay for many
	   architecture.  We may have some problems on some, though */
	err = usb_bulk_msg(usbdev, pipe, &rreg, sizeof(rreg), &retlen, RW_TIMEOUT);
	if (!err)
		islsm_txdata_debug(ep, &rreg, retlen);

	return err;
}

static int
_net2280_rx_read_reg(struct usb_device *usbdev, int ep, int port,
		     u32 *retval) {
	u32 reg;
	unsigned pipe, retlen;
	int err;

	pipe = usb_rcvbulkpipe(usbdev, ep & USB_ENDPOINT_NUMBER_MASK);

	/* passing a stack'd address to bulk_msg is OKay for many
	   architecture.  We may have some problems on some, though */
	err = usb_bulk_msg(usbdev, pipe, &reg, sizeof(reg), &retlen, RW_TIMEOUT);

	reg = le32_to_cpu(reg);

	if (!IS_U32_PORT(port))
		reg &= 0xffff;

	if (!err) {
		islsm_rxdata_debug(ep | USB_DIR_IN, &reg, retlen);
		*retval = reg;
	}

	return err;
}

#define NET2280_WRITE_RETRY 2

int islusb_net2280_write_reg(struct p54u *p54u, int ep, int port,
			     u32 val, u32 addr) {
	struct usb_device *usbdev = p54u->usbdev;
	int tries = 0, err;

	islusb_lock_reg(p54u);

	do {
		err = _net2280_write_reg(usbdev, ep, port, val, addr);
		tries++;
	} while (err && tries < NET2280_WRITE_RETRY);

	if (err) {
		printk(KERN_ERR
		       "%s: Register write %02x %04x %08x %08x failed: %i\n",
		       DRV_NAME, ep, port, addr, val, err);
		goto exit_unlock;
	}

 exit_unlock:
	islusb_unlock_reg(p54u);
	return err;
}

int islusb_net2280_read_reg(struct p54u *p54u, int ep, int port,
			    u32 *retval, u32 addr) {
	struct usb_device *usbdev = p54u->usbdev;
	int err;

	islusb_lock_reg(p54u);

	err = _net2280_send_read_reg(usbdev, ep, port, addr);
	if (err) {
		printk(KERN_ERR
		       "%s: Register read tx %02x %04x %08x failed: %i\n",
		       DRV_NAME, ep, port, addr, err);
		goto exit_unlock;
	}

	err = _net2280_rx_read_reg(usbdev, ep, port, retval);
	if (err) {
		printk(KERN_ERR
		       "%s: Register read rx %02x %04x %08x failed: %i\n",
		       DRV_NAME, ep, port, addr, err);
	}

 exit_unlock:
	islusb_unlock_reg(p54u);
	return err;
}

/*
 * UART-over-net2280 functions
 */
#define ISL_USBPCI 1
#include "islsm_uartpci.h"

/*
 * Interrup-handling function
 */

static inline void
p54u_free_int_urb(struct urb *urb) {
	usb_buffer_free(urb->dev, 4,
			urb->transfer_buffer,
			urb->transfer_dma);
	usb_free_urb(urb);
}

static void
p54u_int_urb_callback(struct urb *urb, struct pt_regs *regs)
{
	struct p54u *p54u = (struct p54u *) urb->context;
	int status = urb->status;
	unsigned urb_len = urb->actual_length;

	if (status || urb_len < 4) {
		p54u_free_int_urb(urb);
		p54u->int_urb = 0;
		/* this is the end */
	} else {
		/* the bh will resend the urb */
		printk(KERN_ERR "scheduling int bh\n");
		schedule_work(&p54u->int_bh);
	}

	return;
}

static struct urb*
p54u_alloc_int_urb(struct p54u *p54u,
		   const struct usb_endpoint_descriptor *desc) {
	struct usb_device *usbdev = p54u->usbdev;
	const unsigned size = sizeof(u32);
	unsigned endp;
	struct urb *urb;
	void *buf;

	urb = usb_alloc_urb(0, GFP_KERNEL);
	if (!urb)
		return 0;

	buf = usb_buffer_alloc(usbdev, size, GFP_KERNEL, &urb->transfer_dma);
	if (!buf)
		return 0;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14)
#define URB_ASYNC_UNLINK 0
#endif

	endp = usb_rcvintpipe(usbdev, desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
	urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP | URB_ASYNC_UNLINK;
	usb_fill_int_urb(urb, usbdev, endp, buf, size,
			 p54u_int_urb_callback, p54u, 6);

	return urb;
}

static int isl_process_interrupt(struct islsm *islsm);

static void
p54u_new_int_bh(void *data)
{
	struct islsm *islsm = data;
	struct p54u *p54u = P54U_OF_ISLSM(islsm);
	struct urb *urb = p54u->int_urb;
	int pending = *(u32*)urb->transfer_buffer;
	int err = 0;

	FN_ENTER;

	if (pending & NET2280_PCI_INTA_INTERRUPT)
		err = isl_process_interrupt(islsm);

	if (!err && p54u->state != P54U_SHUTDOWN)
		err = usb_submit_urb(urb, GFP_KERNEL);

	if (err)
		printk(KERN_ERR "%s: error processing interrupts (%i)",
		       DRV_NAME, err);

	FN_EXIT0;
	return;
}

int
p54u_int_queue_init(struct p54u * p54u,
		    const struct usb_endpoint_descriptor *desc) {
	/* FIXME: fix this mess, use the standard hierarchy */
	struct islsm *islsm = usb_get_intfdata(p54u->interface);
	struct urb *urb;
	urb = p54u_alloc_int_urb(p54u,desc);
	if (!urb)
		return -ENOMEM;

	p54u->int_urb = urb;
	INIT_WORK(&p54u->int_bh, p54u_new_int_bh, islsm);
	return 0;
}

void
p54u_int_queue_destroy(struct p54u *p54u) {
	FN_ENTER;
	if (p54u->int_urb) {
		//usb_kill_urb(p54u->int_urb);
		usb_unlink_urb(p54u->int_urb);
		printk(KERN_ERR "URB unlinked\n");
	}
	flush_scheduled_work();
	FN_EXIT0;
}

static int
isl_process_interrupt(struct islsm *islsm)
{
	struct p54u *p54u = P54U_OF_ISLSM(islsm);
	u32 reg;
	int err;

	p54u_dev_writel(p54u, 0, ISL38XX_INT_EN_REG);

	do {
		/* read the raised interrupts */
		err = p54u_dev_readl(p54u, &reg, ISL38XX_INT_IDENT_REG);
		if (err)
			break;
		islog(L_IRQ, "isl int vector: %08x\n", reg);

		/* mask interrupts that we are not concerned with */
#ifdef PCIUART
		reg &= (ISL38XX_INT_IDENT_INIT |
			ISL38XX_DEV_INT_PCIUART_CTS |
			ISL38XX_DEV_INT_PCIUART_DR);
#else
		reg &= ISL38XX_INT_IDENT_INIT;
#endif

		if (!reg)
			break;

		/* ack interrupts */
		err = p54u_dev_writel(p54u, reg, ISL38XX_INT_ACK_REG);
		if (err)
			break;

		if (reg & ISL38XX_INT_IDENT_INIT) {
			islog(L_DEBUG,
			      "isl init flag set, firmware has booted !\n");
			complete(&islsm->dev_init_comp);
		}
#ifdef PCIUART
		if (reg & ISL38XX_DEV_INT_PCIUART_CTS) {
			islog(L_IRQ, "IRQ: UARTPCI CTS\n");
			uart_cts(islsm);
		}

		if (reg & ISL38XX_DEV_INT_PCIUART_DR) {
			islog(L_IRQ, "IRQ: UARTPCI DR\n");
			uart_dr(islsm);
		}
#endif
		/* ack the interrupts */
	} while (reg != 0);

	/* clear the interrupt at net2280 level
	   the net2280 is in host mode */
	err = p54u_brg_writel(p54u, NET2280_PCI_INTA_INTERRUPT, NET2280_IRQSTAT1);

	/* reenable interrupts in ISL -- note that we should really have
	 * a cache of the enable mask */
#ifdef PCIUART
	err = p54u_dev_writel(p54u,
			      ISL38XX_INT_IDENT_PCIUART_CTS |
			      ISL38XX_INT_IDENT_PCIUART_DR,
			      ISL38XX_INT_EN_REG);
#endif
	return err;
}

/*
 * Device setup functions
 */

#define ISLUSB_REBOOT_DELAY 100
#define ISLUSB_LATCH_DELAY   20

static void
islusb_mdelay(int ms)
{
	msleep(ms);
}

static int
p54u_reset_pipe(struct usb_device *usbdev, int addr)
{
	int err = 0;

	FN_ENTER;
	islog(L_FUNC, "resetting pipe %02x\n", addr);

	switch (addr) {
	case P54U_PIPE_DATA:
	case P54U_PIPE_MGMT:
	case P54U_PIPE_BRG:
		err = usb_clear_halt(usbdev, usb_sndbulkpipe(usbdev, addr));
		break;
	case P54U_PIPE_BRG | USB_DIR_IN:
	case P54U_PIPE_DATA | USB_DIR_IN:
	case P54U_PIPE_MGMT | USB_DIR_IN:
		err = usb_clear_halt(usbdev, usb_rcvbulkpipe(usbdev, addr));
		break;
	case P54U_PIPE_INT | USB_DIR_IN:
		err = usb_clear_halt(usbdev, usb_rcvintpipe(usbdev, addr));
		break;
	default:
		break;
	}
	FN_EXIT1(err);
	return err;
}

static int __attribute__ ((unused))
p54u_reset_pipes(struct p54u *p54u)
{
	struct usb_device *usbdev = p54u->usbdev;

	(void) p54u_reset_pipe(usbdev, P54U_PIPE_DEV | USB_DIR_IN);
	(void) p54u_reset_pipe(usbdev, P54U_PIPE_DEV);
	(void) p54u_reset_pipe(usbdev, P54U_PIPE_BRG | USB_DIR_IN);
	(void) p54u_reset_pipe(usbdev, P54U_PIPE_BRG);

	(void) p54u_reset_pipe(usbdev, P54U_PIPE_INT | USB_DIR_IN);

	(void) p54u_reset_pipe(usbdev, P54U_PIPE_DATA | USB_DIR_IN);
	(void) p54u_reset_pipe(usbdev, P54U_PIPE_DATA);
	(void) p54u_reset_pipe(usbdev, P54U_PIPE_MGMT | USB_DIR_IN);
	(void) p54u_reset_pipe(usbdev, P54U_PIPE_MGMT);

	return 0;
}

/* Setup of the PCI interface of the isl chip  */
static int
p54u_islpci_setup(struct p54u *p54u)
{
	int err;

	FN_ENTER;

	/* Enable mmio and busmaster on the device */
	/* the | 0x10000 selects a bus number in type1 cfg
	 * transactions. Here we're selecting bus number one.
	 * However, we're NOT in the case of a type one transaction
	 * whose address's LSB must be one. Is the net2280 interering
	 * here ?
	 */
	err = p54u_pcicfg_dev_writew(p54u, PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER,
				     0x10000 | PCI_COMMAND);
	if (err)
		goto exit;

	/* to try */
//      p54u_pcicfg_dev_writew(netdev, PCI_COMMAND << 2, (PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER));
//      p54u_pcicfg_dev_writew(netdev, PCI_COMMAND, (PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER));

/*
  in original islsm code :
#define PCI_TRDY_TIMEOUT                        0x40
#define PCI_RETRY_TIMEOUT                       0x41
*/
	/* Set TRDY_TIMEOUT and RETRY_TIMEOUT to 0 */
	err = p54u_pcicfg_dev_writew(p54u, 0, P54U_TRDY_TIMEOUT | 0x10000);
	if (err)
		goto exit;
//      p54u_pcicfg_dev_writew(netdev, P54U_TRDY_TIMEOUT, 0);
//      p54u_pcicfg_dev_writew(netdev, P54U_TRDY_TIMEOUT << 2, 0);

	/* Set base address 0 */
	err = p54u_pcicfg_dev_writel(p54u, P54U_DEV_BASE,
				      PCI_BASE_ADDRESS_0 | 0x10000);
//      p54u_pcicfg_dev_writel(netdev, PCI_BASE_ADDRESS_0, P54U_DEV_BASE);
 exit:
	FN_EXIT1(err);
	return err;
}

/* this function brings down a possibly running lmac firmware. It also
   brings down a possibly running rom ; for rom communication we should
   take care not to call this. */
static void
p54u_islpci_assert(struct p54u *p54u)
{
	/* Assert wakeup */
	FN_ENTER;
	p54u_dev_writel(p54u, ISL38XX_DEV_INT_WAKEUP, ISL38XX_DEV_INT_REG);
	islusb_mdelay(20);
	/* (see old driver for INT_ABORT definition) */
	p54u_dev_writel(p54u, ISL38XX_DEV_INT_ABORT, ISL38XX_DEV_INT_REG);
	islusb_mdelay(20);
	FN_EXIT0;
}

static int
p54u_romboot(struct p54u *p54u)
{
	u32 reg;
	int err;

	FN_ENTER;

	/* Disable irqs in case they were not */
	/* not sure which way is okay : net2280_irqdisable or this */
	err = p54u_dev_writel(p54u, 0, ISL38XX_INT_EN_REG);
	if (err)
		goto exit;

	/* Reset the device */
	/* clear the RAMBoot, the CLKRUN and the Reset bit */
	/* we're booting into rom */
	err = p54u_dev_readl(p54u, &reg, ISL38XX_CTRL_STAT_REG);
	if (err)
		goto exit;

	reg &= ~(ISL38XX_CTRL_STAT_RESET |
		 ISL38XX_CTRL_STAT_RAMBOOT |
		 ISL38XX_CTRL_STAT_CLKRUN);

	err = p54u_dev_writel(p54u, reg, ISL38XX_CTRL_STAT_REG);
	if (err)
		goto exit;
	islusb_mdelay(ISLUSB_LATCH_DELAY);

	/* set the Reset bit without reading the register */
	err = p54u_dev_writel(p54u, reg | ISL38XX_CTRL_STAT_RESET,
			      ISL38XX_CTRL_STAT_REG);
	if (err)
		goto exit;
	islusb_mdelay(ISLUSB_LATCH_DELAY);

	/* clear the Reset bit */
	err = p54u_dev_writel(p54u, reg, ISL38XX_CTRL_STAT_REG);
	if (err)
		goto exit;

	islusb_mdelay(ISLUSB_REBOOT_DELAY);

 exit:
	FN_EXIT1(err);
	return err;
}

static int
isl_net2280_romboot(struct islsm *islsm) {
	return p54u_romboot(P54U_OF_ISLSM(islsm));
}

static int
p54u_ramboot(struct p54u *p54u)
{
	u32 reg;
	int err;

	FN_ENTER;

	/* Reset the device */
	/* clear the RAMBoot, the CLKRUN and the Reset bit */
	/* we're booting into rom */
	err = p54u_dev_readl(p54u, &reg, ISL38XX_CTRL_STAT_REG);
	if (err)
		goto exit;

	reg &= ~(ISL38XX_CTRL_STAT_RESET | ISL38XX_CTRL_STAT_CLKRUN);
	reg |= ISL38XX_CTRL_STAT_RAMBOOT;
	err = p54u_dev_writel(p54u, reg, ISL38XX_CTRL_STAT_REG);
	if (err)
		goto exit;
	islusb_mdelay(ISLUSB_LATCH_DELAY);

	/* set the Reset bit without reading the register */
	err = p54u_dev_writel(p54u, reg | ISL38XX_CTRL_STAT_RESET,
			      ISL38XX_CTRL_STAT_REG);
	if (err)
		goto exit;
	islusb_mdelay(ISLUSB_LATCH_DELAY);

	/* clear the Reset bit */
	err = p54u_dev_writel(p54u, reg, ISL38XX_CTRL_STAT_REG);
	if (err)
		goto exit;

	/* wait for the device to reboot */
	islusb_mdelay(ISLUSB_REBOOT_DELAY);

 exit:
	FN_EXIT1(err);
	return err;
}

/* enables routing of the interrupts to its dedicated endpoint */
static int
p54u_net2280_irqdisable(struct p54u *p54u)
{
	int err;

	FN_ENTER;

	/* See 7.6.5.5 and chapter 9 of net2280 doc */
	/* tell the net2280 that we don't want it to send us interrupts
	   via the interrupt endpoing at all */
	err = p54u_brg_writel(p54u, 0, NET2280_USBIRQENB1);

	/* clear the INTA# interrupt bit in the net2280 */
	err = p54u_brg_writel(p54u, NET2280_PCI_INTA_INTERRUPT, NET2280_IRQSTAT1);

	FN_EXIT1(err);

	return err;
}

static int
p54u_net2280_irqenable(struct p54u *p54u)
{
	int err;

	FN_ENTER;
	/* clear the INTA# interrupt bit in the net2280 */
	err = p54u_brg_writel(p54u, NET2280_PCI_INTA_INTERRUPT, NET2280_IRQSTAT1);
	/* activate routing of the interrupts to int_rx */
	err = p54u_brg_writel(p54u,
			      NET2280_PCI_INTA_INTERRUPT_ENABLE |
			      NET2280_USB_INTERRUPT_ENABLE,
			      NET2280_USBIRQENB1);
	FN_EXIT1(err);
	return err;
}

#if 0
/* XXX This used to be used ! why has it changed ? */
/* flush the (data) endpoints of the net2280 */
static void
p54u_net2280_flushep(struct p54u *p54u)
{
	FN_ENTER;
	/* Magic */
	p54u_brg_writel(p54u, NET2280_FIFO_FLUSH, NET2280_EPA_STAT);
	p54u_brg_writel(p54u, NET2280_FIFO_FLUSH, NET2280_EPB_STAT);
	p54u_brg_writel(p54u, NET2280_FIFO_FLUSH, NET2280_EPC_STAT);
	p54u_brg_writel(p54u, NET2280_FIFO_FLUSH, NET2280_EPD_STAT);
	/* why this again ? */
	p54u_brg_writel(p54u, NET2280_FIFO_FLUSH, NET2280_EPA_STAT);
	FN_EXIT0;
}
#endif

/* This function only does the initialization needed to communicate
   with the PCI device behind the bridge */
static int
p54u_net2280_init(struct p54u *p54u)
{
	u32 reg, revision;
	u16 reg16;
	int err;

	FN_ENTER;
	/* Bridge */
	/* Reset the usb<->pci bridge */
	err = p54u_brg_readl(p54u, &reg, NET2280_GPIOCTL);
	if (err)
		goto exit;

	if (reg & P54U_BRG_POWER_UP)
		printk(KERN_WARNING "%s: bridge already up, expect problems\n",
		       DRV_NAME);

	/* power down the bridge */
	reg |= P54U_BRG_POWER_DOWN;
	reg &= ~P54U_BRG_POWER_UP;
	err = p54u_brg_writel(p54u, reg, NET2280_GPIOCTL);
	if (err)
		goto exit;

	islusb_mdelay(100);

	/* power up the bridge */
	reg |= P54U_BRG_POWER_UP;
	reg &= ~P54U_BRG_POWER_DOWN;
	err = p54u_brg_writel(p54u, reg, NET2280_GPIOCTL);
	if (err)
		goto exit;

	islusb_mdelay(100);
	islog(L_DEBUG, "%s: Reset bridge done\n", DRV_NAME);

	/* See 11.5.1 on net2280 doc */
	islog(L_DEBUG, "%s: Magic 1\n", DRV_NAME);
	err = p54u_brg_writel(p54u,
			      NET2280_CLK_30Mhz |
			      NET2280_PCI_ENABLE |
			      NET2280_PCI_SOFT_RESET,
			      NET2280_DEVINIT);
	if (err)
		goto exit;
	islusb_mdelay(20);
	islog(L_DEBUG, "%s: Magic 1 done\n", DRV_NAME);

	/* Enable mmio and busmaster on the bridge */
	islog(L_DEBUG, "%s: setup bridge pci resources\n", DRV_NAME);
	err = p54u_pcicfg_brg_writew(p54u, PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER,
				     PCI_COMMAND);
	if (err)
		goto exit;

	/* Set base address 0 */
	err = p54u_pcicfg_brg_writel(p54u, NET2280_BASE, PCI_BASE_ADDRESS_0);
	if (err)
		goto exit;

	/* Set PCI_STATUS_REC_MASTER_ABORT (why?) */
	err = p54u_pcicfg_brg_readw(p54u, &reg16, PCI_STATUS);
	if (err)
		goto exit;

	err = p54u_pcicfg_brg_writew(p54u, reg16 | PCI_STATUS_REC_MASTER_ABORT,
				     PCI_STATUS);

	/* Read revision? */
	err = p54u_brg_readl(p54u, &revision, NET2280_RELNUM);
	if (err)
		goto exit;

	islog(L_DEBUG, "%s: Setup bridge pci resources done\n", DRV_NAME);

	/* Magic */
	islog(L_DEBUG, "%s: Magic 2\n", DRV_NAME);
	err = p54u_brg_writel(p54u, NET2280_CLEAR_NAK_OUT_PACKETS_MODE,
			      NET2280_EPA_RSP);
	if (err)
		goto exit;

	err = p54u_brg_writel(p54u, NET2280_CLEAR_NAK_OUT_PACKETS_MODE,
			      NET2280_EPC_RSP);
	if (err)
		goto exit;

	islog(L_DEBUG, "%s: Magic 2 done\n", DRV_NAME);

	/* Set base address 2 for bridge : where the endpoint fifos are */
	islog(L_DEBUG, "%s: Setup bridge base addr 2\n", DRV_NAME);
	err = p54u_pcicfg_brg_writel(p54u, NET2280_BASE2, PCI_BASE_ADDRESS_2);
	if (err)
		goto exit;
	islog(L_DEBUG, "%s: Setup bridge base addr 2 done\n", DRV_NAME);

 exit:
	FN_EXIT1(err);
	return err;
}

int
p54u_setup_rom(struct p54u *p54u)
{
	int err;
	FN_ENTER;

	/* setup pci communications */
	err = p54u_net2280_init(p54u);
	err = p54u_islpci_setup(p54u);
	err = p54u_net2280_irqdisable(p54u);

	/* maybe the ROM already is booted there */

	/* bring down a possibly running lmac firmware */
	p54u_islpci_assert(p54u);

	err = p54u_romboot(p54u);

	/* submit the urb for the interrupt endpoint */
	//rx_queue_refill(p54u, &p54u->int_rx);
	err = usb_submit_urb(p54u->int_urb, GFP_KERNEL);

	/* reenable irqs */
	err = p54u_net2280_irqenable(p54u);

	FN_EXIT1(err);
	return err;
}

static int
p54u_reset_dev(struct p54u *p54u)
{
	u32 reg;
	int err;

	FN_ENTER;

	err = p54u_net2280_irqdisable(p54u);
	if (err)
		goto do_err;

	/* resets the device. The rom should have control
	 * there. This will raise the CTS irq, which we will not receive
	 * due to the above irqdisable.
	 */
	err = p54u_romboot(p54u);
	if (err)
		goto do_err;

	/* at this point we could already do some things with the rom.
	 * I'd like to be able to launch the irq circus right there already.
	 */
	err = p54u_dev_writel(p54u, 0, ISL38XX_INT_EN_REG);
	if (err)
		goto do_err;

	/* Ack the irqs ourselves. When have they been generated ? --
	 * Could we have disabled them earlier ? *
	 * Basically the irq pending is the CTS. */
	islog(L_DEBUG, "%s: ack irq\n", DRV_NAME);
	err = p54u_dev_readl(p54u, &reg, ISL38XX_INT_IDENT_REG);
	if (err)
		goto do_err;

	islog(L_DEBUG, "irq register is %x\n", reg);
	err = p54u_dev_writel(p54u, reg, ISL38XX_INT_ACK_REG);
	if (err)
		goto do_err;

	FN_EXIT1(0);
	return 0;

      do_err:
	printk(KERN_ERR "%s: reset error: %i\n", DRV_NAME, err);
	FN_EXIT1(err);
	return err;
}

static int
p54u_load_firmware(struct p54u *p54u, const struct firmware *fw_entry)
{
	void *data, *fw_buf;
	int err, remains;
	unsigned offset = 0;

	FN_ENTER;

	fw_buf = kmalloc(P54U_FW_BLOCK, GFP_KERNEL);
	if (!fw_buf)
		return -ENOMEM;

	/* Firmware */
	data = fw_entry->data;
	remains = fw_entry->size;

	if ((remains & 3) != 0)
		printk(KERN_ERR "%s: error, firmware length must be 32-bits aligned",
		       DRV_NAME);

	/* past this point we guarantee that len will always be
	   a multiple of 4, which is necessary for the DMA engine
	   to work properly */
	while (remains > 0) {
		u32 reg;
		int len;
		len = min(remains, P54U_FW_BLOCK);

		memcpy(fw_buf, data, len);

		/* we upload a chunk of the firmware into the Net2280's
		   endpoint fifos */
		err = p54u_bulk_msg(p54u, P54U_PIPE_DATA, fw_buf, len);

		if (err) {
			printk(KERN_ERR "%s: error writing firmware block", DRV_NAME);
			goto do_release;
		}

		data += len;
		remains -= len;

		/* Now we control the ISL chip so that it reads back the
		 * chunk of firmware from the Net2280's fifos */

		/* Set the window to the base of the slave DMA RX engine
		 * of the chip */
		p54u_dev_writel(p54u, 0xc0000f00, ISL38XX_DIR_MEM_BASE_REG);
		/* Do a reset of the pci slave RX dma engine */
		p54u_dev_writel(p54u, 0, 0x0020 | ISL38XX_DIRECT_MEM_WIN);
		p54u_dev_writel(p54u, 1, 0x0020 | ISL38XX_DIRECT_MEM_WIN);
		/* Setup the slave RX engine */
		p54u_dev_writel(p54u, len, 0x0024 | ISL38XX_DIRECT_MEM_WIN);
		p54u_dev_writel(p54u, ISL38XX_DEV_FIRMWARE_ADDRES + offset,
				0x0028 | ISL38XX_DIRECT_MEM_WIN);
		/* Then setup the master dma register. This is accessed
		 * not through the memory window, but through the
		 * standard mapping. The in-arm address is around
		 * 0xc0000960 */
		/* Please have a look at net2280_dma.c in the freemac
		   project for more details */
		/* set the PCI address we DMA from */
		p54u_dev_writel(p54u, NET2280_EPA_FIFO_PCI_ADDR, 0x0060);
		/* length of the DMA, in 32-bits words */
		p54u_dev_writel(p54u, len >> 2, 0x0064);
		/* actually start the _read_ DMA */
		p54u_dev_writel(p54u, ISL38XX_DMA_MASTER_CONTROL_TRIGGER, 0x0068);

		/* readback the slave DMA RX engine state */
		/* wait for the dma to complete -- we could also
		 * busy-wait on the register */

		islusb_mdelay(10);
		err = p54u_dev_readl(p54u, &reg, 0x002c | ISL38XX_DIRECT_MEM_WIN);
		if (err)
			goto do_release;

		if (reg != (reg | ISL38XX_DMA_STATUS_DONE | ISL38XX_DMA_STATUS_READY)) {
			err = -EBUSY;
			printk(KERN_ERR DRV_NAME ": firmware dma engine error\n");
			goto do_release;
		}


		if (remains > 0)
			err = p54u_brg_writel(p54u, NET2280_FIFO_FLUSH,
					      NET2280_EPA_STAT);
		if (err) {
			printk(KERN_ERR DRV_NAME ": error flushing net2280 fifos\n");
			goto do_release;
		}

		offset += len;
	}

	err = p54u_ramboot(p54u);

      do_release:
	kfree(fw_buf);
	FN_EXIT1(err);
	return err;
}

static int
p54u_boot_fw(struct islsm *islsm)
{
	struct p54u *p54u = P54U_OF_ISLSM(islsm);
	int err;
	/* The bootrom has control */

	/* now use the new RX queue mechanism */
	rx_queue_refill(p54u, &p54u->data_rx);
	rx_queue_refill(p54u, &p54u->mgmt_rx);

	/* enable interrupts at the isl level */
#ifdef PCIUART
	p54u_dev_writel(p54u, ISL38XX_INT_IDENT_INIT |
			ISL38XX_INT_IDENT_PCIUART_CTS |
			ISL38XX_INT_IDENT_PCIUART_DR,
			ISL38XX_INT_EN_REG);
#else
	p54u_dev_writel(p54u, ISL38XX_INT_IDENT_INIT,
			ISL38XX_INT_EN_REG);
#endif

	err = p54u_net2280_irqenable(p54u);
	if (err)
		return err;

	/* The ramboot has been done just after firmware load */
	/* now we're ready. reset the firmware (which has already booted). */
	err = p54u_dev_writel(p54u, ISL38XX_DEV_INT_RESET, ISL38XX_DEV_INT_REG);
	if (err)
		return err;

	err = islsm_wait_timeout(islsm, HZ);
	return err;
}

static int
isl_load_fw(struct islsm *device, const struct firmware *fw)
{
	struct p54u *p54u = P54U_OF_ISLSM(device);
	int err;
	err = p54u_reset_dev(p54u);
	if (err)
		goto exit;
	err = p54u_load_firmware(p54u, fw);
	if (err)
		goto exit;
	err = p54u_boot_fw(device);
 exit:
	return err;
}

/*
 * Transport functions
 */
static int
tx_submit_ver1(struct sk_buff *skb)
{
	struct p54u *p54u = P54U_OF_NETDEV(skb->dev);
	u32 lmac_addr = LMAC_ADDR_OF_SKB(skb);
	int len = skb->len;
	int err = 0;
	struct islsm_tx *islsm_tx;

	islsm_tx = (void *) skb_push(skb, SIZE_NET2280_H);
	memset(skb->data, 0, SIZE_NET2280_H);
	islsm_tx->arm_addr = cpu_to_le32(lmac_addr);
	islsm_tx->length = cpu_to_le16(len);

	islsm_tx->follower = cpu_to_le16(0);
	err = tx_queue_submit(&p54u->data_tx, skb);

	/* FIXME -- we should do fragmentation of the frames sent on the
	 * control pipe for frames larget than 0x400 -- for now we don't
	 * even use it */
	/* this is the limit payload that will trigger a
	   split on the control pipe */
/* #define LIMIT 0x400 */
/* 	if (len + SIZE_NET2280_H < LIMIT) { */
/* 		/\* we overflow the max size, we must split the frame *\/ */
/* 		islsm_tx->follower = cpu_to_le16(ISLSM_MGMT_FOLLOWUP); */
/* 		// This one is not announced ! f* ! */
/* 		// Should take care of this in the lower */
/* 		// functions ? */
/* 		err = p54u_bulk_msg(p54u, P54U_PIPE_MGMT, */
/* 				    skb->data, SIZE_NET2280_H); */
/* 		//tx_queue_submit (netdev, &p54u->mgmt_tx, skb, queue); */
/* 		if (!err) { */
/* 			skb_pull(skb, SIZE_NET2280_H); */
/* 			err = tx_queue_submit(&p54u->mgmt_tx, skb); */
/* 		} */
/* 	} */
	return err;
}

void
islusb_fill_net2280(struct islsm *islsm) {
	islsm->isl_tx = tx_submit_ver1;
	islsm->device_tx_header_space = SIZE_NET2280_H;
	islsm->device_version = ISLSM_DEVICE_USB_VER1;

	/* (pci-specific) device callbacks */
	islsm->isl_read_pcireg = isl_read_pcireg;
	islsm->isl_write_pcireg = isl_write_pcireg;
	islsm->isl_write_devmem = isl_write_devmem;
	islsm->isl_read_devmem = isl_read_devmem;
	islsm->isl_romboot = isl_net2280_romboot;
	islsm->isl_load_fw = isl_load_fw;

	/* uart callbacks */
	islsm->uart_prot_init = uart_prot_init;
	islsm->uart_prot_exit = uart_prot_exit;
	islsm->uart_cts = uart_cts;
}
