/*
  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/smp_lock.h>
#include <linux/delay.h>
#include <linux/usb.h>

#include "islsm_uart.h"
#include "islsm_log.h"
#include "islsm_rom.h"
#include "isl_38xx.h" /* just for ISL38XX_DEV_FIRMWARE_ADDRES */
#include "islusb_dev.h"
#include "islusb_3887.h"

/*
 * UART-over-USB routines
 */
static void             uart_cts_cb(struct urb *urb, struct pt_regs *regs);
static void             uart_dr_cb(struct urb *urb, struct pt_regs *regs);

static int
alloc_uart_urb(struct urb **urb_ref)
{
	void                   *buffer;
	*urb_ref = usb_alloc_urb(0, GFP_KERNEL);
	if (!*urb_ref)
		return -ENOMEM;

	buffer = kmalloc(P54U_MAX_FRAME_SIZE, GFP_KERNEL);
	if (!buffer) {
		usb_free_urb(*urb_ref);
		return -ENOMEM;
	}
	(*urb_ref)->transfer_buffer = buffer;
	(*urb_ref)->transfer_buffer_length = P54U_MAX_FRAME_SIZE;
	return 0;
}

static void
free_uart_urb(struct urb **urb_ref)
{
	kfree((*urb_ref)->transfer_buffer);
	usb_free_urb(*urb_ref);
	*urb_ref = 0;
}

static void
uartusb_prot_init(struct islsm *islsm)
{
	struct p54u            *p54u = P54U_OF_ISLSM(islsm);
	static char            *interrupt_string = "~";
	void                   *init_buf;
	int                     err;

	FN_ENTER;

	/* FIXME: Empty the standard queues for sending / receiving data
	 * (drain_queue of some sort)
	 */

	init_buf = kmalloc(strlen(interrupt_string), GFP_KERNEL);

	if (!init_buf)
		return;

	/* allocate the urb and buffer structures needed */
	err = alloc_uart_urb(&p54u->cts);
	if (err) {
		FN_EXIT0;
		return;
	}

	err = alloc_uart_urb(&p54u->dr);
	if (err) {
		free_uart_urb(&p54u->cts);
		FN_EXIT0;
		return;
	}

	/* fill the urbs */
	usb_fill_bulk_urb(p54u->cts, p54u->usbdev, p54u->data_tx.endp,
			  p54u->cts->transfer_buffer,
			  p54u->cts->transfer_buffer_length,
			  uart_cts_cb, islsm);

	usb_fill_bulk_urb(p54u->dr, p54u->usbdev, p54u->data_rx.endp,
			  p54u->dr->transfer_buffer,
			  p54u->dr->transfer_buffer_length, uart_dr_cb, islsm);

	/* really simple : start the urbs on data pipe,
	   send reinit sequence, and you're all set */
	/* submitting the DR urb *after* yields a series of ZZZZ;
	   probably to indicate that the endpoint is sleeping ? */

	/* give cts to device by submitting the DR urb */
	usb_submit_urb(p54u->dr, GFP_KERNEL);
	memcpy(init_buf, interrupt_string, strlen(interrupt_string));
	p54u_bulk_msg(p54u, P54U_PIPE_DATA, init_buf, strlen(interrupt_string));
	msleep(100);

	/* The device should reply to this. */
	kfree(init_buf);

	/* give ourselves CTS */
	islsm->uart.cts = 1;

	FN_EXIT0;
	return;
}

static void
uartusb_prot_exit(struct islsm *device)
{
	struct p54u            *p54u = P54U_OF_ISLSM(device);
	/* remove the submitted urbs from the endpoints (CTS and DR) */
	usb_kill_urb(p54u->cts);
	usb_kill_urb(p54u->dr);
}

static void
uart_cts_cb(struct urb *urb, struct pt_regs *regs)
{
	struct islsm           *device = (struct islsm *) urb->context;
	uart_instance_t        *uart = &device->uart;
	unsigned long           flags;
	char                    data;

	islog(L_DEBUG, "CTS urb returning\n");

	/* check the urb status */
	if (urb->status) {
		/* bad status */
		islog(L_DEBUG, "Bad CTS urb status\n");
		return;
	}

	if (!uart) {
		/* stray CTS signals can happen when we're opening then
		 * closing the device very fast. We should wait for this
		 * first CTS before returning from uart_prot_init */
		islog(L_DEBUG, "stray CTS signal\n");
		return;
	}

	spin_lock_irqsave(&uart->lock, flags);
	/* copy data to the urb */
	if (uart_inq(uart, &uart->txq) > 0) {
		data = uart_qconsume(uart, &uart->txq);
		spin_unlock_irqrestore(&uart->lock, flags);

		/* escape some special protocol characters */
		/* there may be more, but these seem sufficient
		   per se */

		switch (data) {
		case '~':
			memcpy(urb->transfer_buffer, "}^", 2);
			urb->transfer_buffer_length = 2;
			break;
		case '}':
			memcpy(urb->transfer_buffer, "}]", 2);
			urb->transfer_buffer_length = 2;
			break;
		default:
			memcpy(urb->transfer_buffer, &data, 1);
			urb->transfer_buffer_length = 1;
		}

		/* Give DR <=> resubmit an urb */
		/* what if this fails ? */
		if (usb_submit_urb(urb, GFP_ATOMIC))
			printk(KERN_ERR "islsm: sending CTS urb failed\n");
	} else {
		spin_unlock_irqrestore(&uart->lock, flags);
		/* Device gives us CTS without any data to transmit */
		uart->cts = 1;
	}
}

static void
uartusb_cts(struct islsm *device)
{
	struct p54u            *p54u = P54U_OF_ISLSM(device);
	uart_cts_cb(p54u->cts, 0);
}

static void
uart_dr_cb(struct urb *urb, struct pt_regs *regs)
{
	struct islsm           *device = (struct islsm *) urb->context;
	uart_instance_t        *uart = &device->uart;
	unsigned long           flags;
	uint8_t                *data;
	int                     i;

	islog(L_DEBUG, "DR urb returning\n");

	/* check the urb status */
	if (urb->status) {
		/* bad status. Should only happend on protocol exit */
		islog(L_DEBUG, "Bad DR urb status\n");
		return;
	}

	if (!uart) {
		islog(L_DEBUG, "stray DR signal\n");
		return;
	}

	data = (uint8_t *) urb->transfer_buffer;

	spin_lock_irqsave(&uart->lock, flags);
	/* copy the data from the urb */
	islog(L_DATA, "received %i bytes\n", urb->actual_length);
	for (i = 0; i < urb->actual_length; i++) {
		if (uart_inq(uart, &uart->rxq) < UARTPCI_RXQSIZE)
			uart_qproduce(uart, &uart->rxq, data[i]);
		else {		/* ENOMEM, too bad, we drop data */
			islog(L_DATA, "Dropping data in DR\n");
			break;
		}
	}
	spin_unlock_irqrestore(&uart->lock, flags);

	/* Give CTS == resubmit urb */
	if (usb_submit_urb(urb, GFP_ATOMIC))
		printk(KERN_ERR "islsm: sending DR urb failed\n");

	/* Activate the bottom halve */
	wake_up_interruptible(&uart->wait);
}

/*
 * Firmware-loading routines
 */

static int
p54u_send_abort_sequence(struct islsm *islsm) {
	struct p54u *p54u = P54U_OF_ISLSM(islsm);
	static char *init_string = "~~~~";
	void *init_buf;
	int err;

	init_buf = kmalloc(strlen(init_string), GFP_KERNEL);
	if (!init_buf)
		return -ENOMEM;

	memcpy(init_buf, init_string, strlen(init_string));
	err =
	    p54u_bulk_msg(p54u, P54U_PIPE_DATA, init_buf, strlen(init_string));
	kfree(init_buf);
	return err;
}

static int
p54u_reset_dev2(struct islsm *islsm)
{
	int err;
	struct p54u *p54u = P54U_OF_ISLSM(islsm);

	FN_ENTER;

	/* really simple : start the urbs on data pipe,
	   send reinit sequence, and you're all set */
	err = rx_queue_refill(p54u, &p54u->data_rx);
	if (err)
		goto out;

	err = p54u_send_abort_sequence(islsm);
	if (err)
		goto out;

	msleep(100);

out:
	FN_EXIT1(err);
	return err;
}

/* the data needs some formatting :
 * escape 0x7e to 0x7d 0x5e
 * escape 0x7d to 0x7d 0x5d
 */
static unsigned
firmware_escape_write_buffer(char *buf, char *data, unsigned write)
{
	static char carry = 0;
	unsigned int i = 0, j = 0;
	char val;

	if (carry) {
		buf[j++] = carry;
		carry = 0;
	}

	while (j < write) {
		switch (val = data[i++]) {
		case '~':
			buf[j++] = '}';
			if (j == write)
				carry =  '^';
			else
				buf[j++] = '^';
			break;
		case '}':
			buf[j++] = '}';
			if (j == write)
				carry = ']';
			else
				buf[j++] = ']';
			break;
		default:
			buf[j++] = val;
		}
	}
	return i;
}

static int
p54u_boot_fw2(struct islsm *islsm)
{
	struct p54u *p54u = P54U_OF_ISLSM(islsm);
	static char *go_string = "g\r";
	int err;
	void *fw_buf;
	/* The bootrom has control */

	FN_ENTER;

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

	/* send GO */
	strcpy(fw_buf, go_string);

	err = p54u_bulk_msg(p54u, P54U_PIPE_DATA,
			    fw_buf, strlen(go_string));

	/* The firmware should respond with g. Then we're on our own. */
	err = islsm_wait_timeout(islsm, HZ);

	kfree(fw_buf);
	/* The lm87 firmware need 350ms to boot */
	msleep(350);

	FN_EXIT1(err);
	return err;
}

static int
islrom_build_x2(struct x2_header *header,
		uint32_t fw_load_addr, uint32_t fw_length)
{
	uint32_t crc;
	memcpy(header->signature, X2_SIGNATURE, X2_SIGNATURE_SIZE);
	header->cmd.fw_load_addr = cpu_to_le32(fw_load_addr);
	header->cmd.fw_length = cpu_to_le32(fw_length);
	crc = islrom_crc32(&header->cmd, sizeof (header->cmd));
	header->crc = cpu_to_le32(crc);
	return 0;
}

static int
p54u_load_firmware2(struct islsm *islsm, const struct firmware *fw_entry)
{
	struct p54u *p54u = P54U_OF_ISLSM(islsm);
	struct usb_device *usbdev = p54u->usbdev;
	static char *start_string = "~~~~<\r";
	void *data, *fw_buf;
	unsigned int length, read, written, remains;
	uint32_t crc;
	int alen;
	int err = 0;

	FN_ENTER;

	fw_buf = kmalloc(P54U_FW_BLOCK, GFP_KERNEL);
	if (!fw_buf) {
		FN_EXIT1(-ENOMEM);
		return -ENOMEM;
	}
	/* this is awfully simple */
	data = fw_entry->data;
	remains = fw_entry->size;

	/* copy the command */
	strcpy(fw_buf, start_string);
	length = strlen(start_string);

	/* build the X2 header */
	islrom_build_x2(fw_buf + length,
			ISL38XX_DEV_FIRMWARE_ADDRES, fw_entry->size);
	length += sizeof(struct x2_header);

	while (remains > 0 && !err) {
		int pipe;
		written =
		    remains >
		    (P54U_FW_BLOCK - length) ? (P54U_FW_BLOCK -
						length) : remains;
		read =
		    firmware_escape_write_buffer(fw_buf + length, data,
						 written);

		/* directly transfert the data -- no debug, it causes
		 * kmalloc errors */
		pipe = usb_sndbulkpipe(usbdev, P54U_PIPE_DATA);
		err = usb_bulk_msg(usbdev, pipe, fw_buf,
				   length + written, &alen, 2*HZ);

		if (err) {
			printk(KERN_ERR
			       "%s: error writing firmware block:"
			       "remains %i of %zd.\n",
			       DRV_NAME, remains, fw_entry->size);
		}

		data += read;
		remains -= read;
		length = 0;
	}

	/* now send the checksum of the firmware */
	crc = islrom_crc32(fw_entry->data, fw_entry->size);
	*((uint32_t *)fw_buf) = cpu_to_le32(crc);
	err = p54u_bulk_msg(p54u, P54U_PIPE_DATA, fw_buf, sizeof(uint32_t));

	kfree(fw_buf);

	/* At this point the firmware sends back an "OK" string */
	err = islsm_wait_timeout(islsm,HZ);
	islog(L_DEBUG, "%s: send firmware done\n", DRV_NAME);

	/* Now we send the go command */

	FN_EXIT1(err);
	return err;
}

static int
isl_load_fw2(struct islsm *device, const struct firmware *fw)
{
	int err;
	err = p54u_reset_dev2(device);
	if (err)
		goto exit;
	err = p54u_load_firmware2(device, fw);
	if (err)
		goto exit;
	err = p54u_boot_fw2(device);
 exit:
	return err;
}

/*
 * Packet transport functions
 */
static int
tx_submit_ver2(struct sk_buff *skb)
{
	struct p54u *p54u = P54U_OF_NETDEV(skb->dev);
	int err;
	err = tx_queue_submit(&p54u->data_tx, skb);
	return err;
}

void
islusb_fill_3887(struct islsm *islsm) {
	/* some if not all of these should be filled
	   automatically when parsing the firmware */
	islsm->isl_tx = tx_submit_ver2;
	islsm->device_tx_header_space = 0;
	islsm->device_version = ISLSM_DEVICE_USB_VER2;
	/* possibly maybe */
	islsm->isl_romboot = p54u_send_abort_sequence;
	islsm->isl_load_fw = isl_load_fw2;

	/* unknown and probably unavailable */
	islsm->isl_read_pcireg = 0;
	islsm->isl_write_pcireg = 0;
	islsm->isl_write_devmem = 0;
	islsm->isl_read_devmem = 0;

	/* uart procotol initialization */
	islsm->uart_prot_init = uartusb_prot_init;
	islsm->uart_prot_exit = uartusb_prot_exit;
	islsm->uart_cts = uartusb_cts;
}
