diff options
Diffstat (limited to 'drivers/fims/usb/fim_usb.c')
-rw-r--r-- | drivers/fims/usb/fim_usb.c | 2030 |
1 files changed, 2030 insertions, 0 deletions
diff --git a/drivers/fims/usb/fim_usb.c b/drivers/fims/usb/fim_usb.c new file mode 100644 index 000000000000..2292c4c406a9 --- /dev/null +++ b/drivers/fims/usb/fim_usb.c @@ -0,0 +1,2030 @@ +/* -*- linux-c -*- + * + * driver/fims/usb/fim_usb.c + * + * Copyright (C) 2009 Digi International Inc. + * + * 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. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/tty.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <linux/reboot.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/completion.h> + +#include <mach/fim-ns921x.h> +#include <mach/hardware.h> +#include <mach/gpio.h> +#include <mach/regs-iohub-ns921x.h> + +/* #include "fim_usb.h" */ +/* + * If the driver is being compiled as a built-in driver, then include the header file + * which contains the firmware for this FIM-device + */ +#if !defined(MODULE) +#include "fim_usb.h" +extern const unsigned char fim_usb_firmware[]; +#define FIM_USB_FIRMWARE_FILE (NULL) +#define FIM_USB_FIRMWARE_CODE fim_usb_firmware +#else +const unsigned char *fim_usb_firmware = NULL; +#define FIM_USB_FIRMWARE_FILE "fim_usb.bin" +#define FIM_USB_FIRMWARE_CODE fim_usb_firmware +#endif + +/* Driver informations */ +#define DRIVER_VERSION "1.0" +#define FIM_DRIVER_AUTHOR "Hector Oron, Luis Galdos" +#define FIM_DRIVER_DESC "FIM USB serial device driver" +#define FIM_DRIVER_NAME "fim-usb" +#define FIM_USB_UART_DEV_NAME "ttyFUSB" + +/* Module parameters */ +NS921X_FIM_NUMBERS_PARAM(fims_number); + +/* Macros used for the USB descriptors and resources */ +#define FIM_USB_MANU "Digi" +#define FIM_USB_PROD_DESC "FIM-USBx" /* @FIXME: Cant change to an odd length */ +#define FIM_USB_SERIALNR "NS921X" +#define FIM_USB_IN_EPNR (USB_DIR_IN | FIM_USB_INT_EP_NR) +#define FIM_USB_OUT_EPNR (USB_DIR_OUT | FIM_USB_INT_EP_NR) + +/* MAX GPIO NUMBER */ +#define FIM_USB_MAX_GPIOS (6) + +#define FIM_USB_GPIO_DP (0) +#define FIM_USB_GPIO_DM (1) +#define FIM_USB_GPIO_RCV (2) +#define FIM_USB_GPIO_OE (3) +#define FIM_USB_GPIO_ENUM (4) +#define FIM_USB_GPIO_SPND (5) + +/* Macros for controlling the enumeration GPIO of the MAX */ +#define FIM_USB_GPIO_ENUM_ENABLE (1) +#define FIM_USB_GPIO_ENUM_DISABLE (0) + + +#define FIM_USB_MAX_PACK_SIZE (8) +#define FIM_USB_INT_EP_NR (1) + + +/* Firmware dependent status/error flags */ +#define FIM_USB_BIT_STUFF_ERROR (1 << 0) +#define FIM_USB_CRC_ERROR (1 << 1) +#define FIM_USB_RX_OVERFLOW (1 << 2) +#define FIM_USB_DEV_ADDR_NO_MATCH (1 << 3) +#define FIM_USB_SYNC_ERROR (1 << 4) +#define FIM_USB_IN_DATA_SENT_MARKER (1 << 5) +#define FIM_USB_BUS_RESET (1 << 6) +#define FIM_USB_KEEP_ALIVE (1 << 7) + +/* When a data packet is received it includes the sync, token, CRC, etc. */ +#define FIM_USB_DATA_OVERHEAD (5) +#define FIM_USB_DATA_HEAD (2) +#define FIM_USB_DATA_TAIL (3) + +/* + * Since the FIMs doesn't support the CRC calculation and other additional features + * (like the setup of the different tokens), we must create the complete USB frame + * before putting it into the DMA-buffer (sucks!). So, we need the below token values + * for each frame. + * + * (Luis Galdos) + */ +#define USB_TOKEN_IN (0x69) +#define USB_TOKEN_OUT (0xe1) +#define USB_TOKEN_DATA0 (0xc3) +#define USB_TOKEN_DATA1 (0x4b) +#define USB_TOKEN_SETUP (0x2d) +#define USB_TOKEN_SYNC (0x80) + +/* Configuration status registers of the FIM firmware */ +#define FIM_USB_ADDR_REG 2 +#define FIM_USB_CONTROL3 3 +#define FIM_USB_CONTROL4 4 +#define FIM_USB_CONTROL5 5 +#define FIM_USB_CONTROL6 6 + +#define FIM_USB_CTRL10_REG (10) +#define FIM_USB_STAT10_REG (10) +#define FIM_USB_CTRL3_REG (3) +#define FIM_USB_STAT3_REG (3) +#define FIM_USB_ALIVE_COUNT_REG FIM_USB_CTRL10_REG +#define FIM_USB_ALIVE_COUNT_STAT FIM_USB_STAT10_REG + +/* Main control register */ +#define FIM_USB_MAIN_REG (7) +#define FIM_USB_MAIN_START (1 << 0) +#define FIM_USB_MAIN_NOKEEPALIVE (1 << 1) +#define FIM_USB_MAIN_NOINIRQ (1 << 2) +#define FIM_USB_MAIN_ALIVE_COUNT (1 << 3) + +#define FIM_USB_REVISION_REG (0) + +/* Macros for the configuration of the DMA-channel */ +#define FIM_USB_DMA_BUFFER_SIZE (512) +#define FIM_USB_DMA_RX_BUFFERS (61) +#define FIM_USB_DMA_TX_BUFFERS (4) + +/* For printing messages (errors, warning, etc.) */ +#define pk_err(fmt, args...) printk(KERN_ERR "[ ERROR ] fim-usb: " fmt, ## args) +#define pk_info(fmt, args...) printk(KERN_INFO "fim-usb: " fmt, ## args) +#define pk_dbg(fmt, args...) printk(KERN_DEBUG "fim-usb: " fmt, ## args) +#define pk_dump_ctrl_packet(p) printk(KERN_DEBUG "bRequestType 0x%02x | bRequest 0x%02x | " \ + "wValue 0x%04x | wIndex 0x%04x | wLength %u\n", \ + p->bRequestType, p->bRequest, \ + p->wValue, p->wIndex, p->wLength); + +#if 0 +#define FIM_USB_DEBUG +#endif + +/* Macros for printing debug messages */ +#ifdef FIM_USB_DEBUG +#define dbg_func(fmt, args...) printk(KERN_DEBUG "fim-usb: %s() " fmt, __func__, ## args) +#define dbg_naked(fmt, args...) printk(KERN_DEBUG "fim-usb: " fmt, ## args) +#define dbg_pr(fmt, args...) printk(KERN_DEBUG "fim-usb: " fmt, ## args) +#else +#define dbg_pr(fmt, args...) do { } while (0) +#define dbg_naked(fmt, args...) do { } while (0) +#define dbg_func(fmt, args...) do { } while (0) +#endif + +/* Enable the debug messages for the UART operations/functions */ +#if 0 +#define FIM_USB_UART_DEBUG +#endif + +#ifdef FIM_USB_UART_DEBUG +#define dbg_uart(fmt, args...) printk(KERN_DEBUG "fim-usb UART: " fmt, ## args) +#else +#define dbg_uart(fmt, args...) do { } while (0) +#endif + +/* Enable the debug messages for the enumeration sequence */ +#if 0 +#define FIM_USB_ENUM_DEBUG +#endif + +#ifdef FIM_USB_ENUM_DEBUG +#define dbg_enum(fmt, args...) printk(KERN_DEBUG "fim-usb ENUM: " fmt, ## args) +#else +#define dbg_enum(fmt, args...) do { } while (0) +#endif + +/* Enable the debug messages for the received tokens */ +#if 0 +#define FIM_USB_TOKEN_DEBUG +#endif + +#ifdef FIM_USB_TOKEN_DEBUG +#define dbg_token(fmt, args...) printk(KERN_DEBUG "fim-usb TN: " fmt, ## args) +#else +#define dbg_token(fmt, args...) do { } while (0) +#endif + +/* Enable the debug messages for the received tokens */ +#if 0 +#define FIM_USB_WRITE_DEBUG +#endif + +#ifdef FIM_USB_WRITE_DEBUG +#define dbg_write(fmt, args...) printk(KERN_DEBUG "fim-usb WRITE: " fmt, ## args) +#else +#define dbg_write(fmt, args...) do { } while (0) +#endif + +#if 0 +#define FIM_USB_FORCE_ENUM_TEST +#endif + +/* Data structure used for passing a data buffer to the FIM core */ +struct fim_usb_in_frame { + u8 epnr; + u8 bytes; + u8 token; + u8 data[32]; + u16 *crc; +}__attribute__((__packed__)); + +/* + * Let's use this structure for handling the received FIM-buffers + * + */ +struct fim_usb_token { + unsigned long type:8; + unsigned long addr:7; + unsigned long ep:4; +} __attribute__((packed)); + +/* Structure for the data received after the OUT tokens */ +struct fim_usb_data { + u8 sync; /* Sync token 0x80 */ + u8 token; /* DATA0 or DATA1 */ + u8 data[32]; /* @XXX: Quick and dirty! Need a macro for this value */ +} __attribute__((packed)); + +struct fim_usb_ep { + u8 nr; + u8 addr; + + u16 bytes; + u16 requested; + u8 *data; + + u8 zlp; + u8 last_data_token; + + struct fim_usb_port *port; + + struct work_struct tx_work; /* Worker for the IN transfers */ + struct uart_port *tx_uart; + struct semaphore tx_sem; +}; + +struct fim_usb_port { + int index; + struct fim_driver fim; + struct device *dev; + struct fim_gpio_t gpios[FIM_USB_MAX_GPIOS]; + struct clk *clk; + int reg; + + /* We have three endpoints: control and two interrupts */ +#define FIM_USB_NR_EP_CTRL (0) +#define FIM_USB_NR_EP_OUT (1) +#define FIM_USB_NR_EP_IN (2) +#define FIM_USB_NR_EPS (3) + struct fim_usb_ep eps[FIM_USB_NR_EPS]; + int ep_next_data; + + /* Data used for the emulation of the serial port */ + struct uart_port uart; + struct work_struct uart_work; + int uart_created; + struct semaphore uart_sem; + + /* Used for restarting the FIM after heavy failures */ + struct work_struct restart_work; + struct completion restart_completed; + + /* Timer used for detecting the hotplug events */ + struct delayed_work hotplug_work; + int unplug_detected; +#define FIM_USB_HOTPLUG_ALIVE_TIMER (HZ / 5) +#define FIM_USB_HOTPLUG_ALIVE_COUNTER (150) +}; + +#define FIM_USB_STRING_MAX_LEN (32) + +struct fim_usb_string { + u8 id; + const char *s; + + /* This data will be send to the host */ + struct usb_descriptor_header desc; + u16 data[FIM_USB_STRING_MAX_LEN]; +} __attribute__((packed)); + +/* The default endpoint descriptors include the members for the audio extension (see include/linux/usb/ch9.h) */ +struct usb_endpoint_descriptor2 { + __u8 bLength; + __u8 bDescriptorType; + + __u8 bEndpointAddress; + __u8 bmAttributes; + __le16 wMaxPacketSize; + __u8 bInterval; +} __attribute__ ((packed)); + +/* Let's keep it simple by using a static configuration descriptor */ +struct fim_usb_config_descriptor { + struct usb_config_descriptor config; + struct usb_interface_descriptor interface; + struct usb_endpoint_descriptor2 ep_in; + struct usb_endpoint_descriptor2 ep_out; +} __attribute__((__packed__)); + + +/* Prototypes */ +static void fim_usb_regs_reset(struct fim_usb_port *port); +static void fim_usb_uart_tx_work_func(struct work_struct *work); +static int fim_usb_reboot_notifier_func(struct notifier_block *this, unsigned long code, + void *unused); +static int fim_usb_restart_fim(struct fim_usb_port *port); + +static struct uart_driver fim_usb_uart_driver; +static struct fim_usb_port **fim_usb_ports; +static int fim_usb_ports_max; +static struct notifier_block fim_usb_reboot_notifier = { + .notifier_call = fim_usb_reboot_notifier_func, + .next = NULL, + .priority = 0 +}; + +static inline struct fim_usb_port *port_from_fim(struct fim_driver *fim) +{ + return fim->driver_data; +} + +static inline struct tty_struct *tty_from_port(struct fim_usb_port *port) +{ + struct uart_info *info; + + info = port->uart.info; + return (info) ? info->port.tty : NULL; +} + +static inline struct fim_usb_port *port_from_uart(struct uart_port *uart) +{ + return dev_get_drvdata(uart->dev); +} + +static void fim_usb_enable_pullup(struct fim_usb_port *port) +{ + dbg_func("FIM %i\n", port->index); + + /* + * Enable the pulldown for the enumeration process and remove the suspend + * control for starting the externa PHY. The other GPIOs are controller by + * the FIM, specially the Output Enable. + */ + gpio_set_value(port->gpios[FIM_USB_GPIO_SPND].nr, 0); + gpio_set_value(port->gpios[FIM_USB_GPIO_ENUM].nr, 1); +} + +static void fim_usb_disable_pullup(struct fim_usb_port *port) +{ + dbg_func("FIM %i\n", port->index); + + /* Disable the pulldown and set the PHY in the suspend mode */ + gpio_set_value(port->gpios[FIM_USB_GPIO_ENUM].nr, 0); + gpio_set_value(port->gpios[FIM_USB_GPIO_SPND].nr, 1); +} + +static void fim_usb_regs_reset(struct fim_usb_port *port) +{ + /* FIM control registers configuration */ + fim_set_ctrl_reg(&port->fim, 0, 0); + fim_set_ctrl_reg(&port->fim, 1, 0); + fim_set_ctrl_reg(&port->fim, 2, 0); + fim_set_ctrl_reg(&port->fim, 3, 0); + fim_set_ctrl_reg(&port->fim, 4, 0); + fim_set_ctrl_reg(&port->fim, 5, 0); + fim_set_ctrl_reg(&port->fim, 6, 0); + fim_set_ctrl_reg(&port->fim, FIM_USB_MAIN_REG, 0); +} + +/* + * According to the function[1] of the NetOS driver, we must first configure the + * dedicated GPIOs + * + * [1] iop_config.c:naIopUsbInit() + */ +static int fim_usb_config(struct fim_usb_port *port) +{ + unsigned long clks_per_bit; + unsigned long timer0_init_value; + struct fim_driver *fim; + int offset; + struct fim_gpio_t *gpios; + + fim = &port->fim; + gpios = port->gpios; + + /* Depending on the processor we have different offset */ + if (processor_is_ns9215()) + offset = 68; + else if (processor_is_ns9210()) + offset = 0; + else + return -EINVAL; + + /* FIM control registers configuration */ + fim_set_ctrl_reg(fim, 3, 1 << (gpios[FIM_USB_GPIO_DP].nr - offset)); + fim_set_ctrl_reg(fim, 4, 1 << (gpios[FIM_USB_GPIO_DM].nr - offset)); + fim_set_ctrl_reg(fim, 5, 1 << (gpios[FIM_USB_GPIO_RCV].nr - offset)); + fim_set_ctrl_reg(fim, 6, 1 << (gpios[FIM_USB_GPIO_OE].nr - offset)); + + /* Calculate timer setting */ + /* FIM init Configuration. XXX port->clk must be MHz */ + clks_per_bit = clk_get_rate(port->clk) / 1000000; + clks_per_bit = (clks_per_bit * 2) / 3; + dbg_pr("Clocks Per Bit: 0x%lx [Sysclock %lu]\n", clks_per_bit, + clk_get_rate(port->clk)); + + /* Sanity check for clock */ + if ((clks_per_bit > 270) || (clks_per_bit < 0)) { + pk_err("Invalid calculated clocks per bit %lu\n", clks_per_bit); + return -EINVAL; + } + + /* Load values according to the NetOS driver */ + fim_set_ctrl_reg(&port->fim, 0, 231); + + timer0_init_value = 256 - (clks_per_bit - 11); + fim_set_ctrl_reg(&port->fim, 1, timer0_init_value); + + /* Set the initial device address to zero */ + fim_set_ctrl_reg(&port->fim, FIM_USB_ADDR_REG, 0); + + return 0; +} + +/* We only provide two interrupt endpoints and one control EP */ +static void fim_usb_init_eps(struct fim_usb_port *port) +{ + struct fim_usb_ep *ep; + + memset(&port->eps, 0, sizeof(port->eps)); + + /* Setup the control EP0 */ + ep = &port->eps[FIM_USB_NR_EP_CTRL]; + ep->addr = 0; + ep->port = port; + ep->nr = FIM_USB_NR_EP_CTRL; + + /* Setup the OUT EP1 */ + ep = &port->eps[FIM_USB_NR_EP_OUT]; + ep->addr = FIM_USB_INT_EP_NR; + ep->port = port; + ep->nr = FIM_USB_NR_EP_OUT; + + /* Setup the IN EP1. Init the worker for the IN transfers too! */ + ep = &port->eps[FIM_USB_NR_EP_IN]; + ep->addr = FIM_USB_INT_EP_NR; + INIT_WORK(&ep->tx_work, fim_usb_uart_tx_work_func); + ep->port = port; + ep->nr = FIM_USB_NR_EP_IN; + + /* @XXX: Define a correct reset value for this member */ + port->ep_next_data = 0; +} + +/* Be sure that you have disconnected the pull-up first */ +static void fim_usb_init_port(struct fim_usb_port *port) +{ + /* Reset the registers */ + fim_usb_regs_reset(port); + + /* Download config to enpoint buffer */ + fim_usb_config(port); + fim_usb_init_eps(port); +} + +static int fim_usb_reboot_notifier_func(struct notifier_block *this, unsigned long code, + void *unused) +{ + struct fim_usb_port *port; + int cnt; + + for (cnt = 0; cnt < fim_usb_ports_max; cnt++) { + port = fim_usb_ports[cnt]; + if (!port) + continue; + + fim_usb_disable_pullup(port); + } + + return NOTIFY_DONE; +} + +static void fim_usb_hotplug_work_func(struct work_struct *work) +{ + struct fim_driver *fim; + int reg; + int cnt_curr, cnt_init; + struct fim_usb_port *port; + + port = container_of(work, struct fim_usb_port, hotplug_work.work); + fim = &port->fim; + + /* Get the status of the main register and the current countdown value */ + fim_get_ctrl_reg(fim, FIM_USB_MAIN_REG, ®); + fim_get_stat_reg(fim, FIM_USB_ALIVE_COUNT_STAT, &cnt_init); + + /* Set the initial value for the countdown register */ + fim_set_ctrl_reg(fim, FIM_USB_ALIVE_COUNT_REG, FIM_USB_HOTPLUG_ALIVE_COUNTER); + + /* Inform the FIM about the new reloaded value */ + fim_set_ctrl_reg(fim, FIM_USB_MAIN_REG, reg | FIM_USB_MAIN_ALIVE_COUNT); + + /* We need to wait for the status register */ + msleep_interruptible(50); + fim_get_stat_reg(fim, FIM_USB_ALIVE_COUNT_STAT, &cnt_curr); + + /* Reset the flag of the control register */ + fim_set_ctrl_reg(fim, FIM_USB_MAIN_REG, reg); + + /* Re-trigger the timer function if all looks OK */ + if (cnt_init != cnt_curr) { + port->unplug_detected = 0; + schedule_delayed_work(&port->hotplug_work, FIM_USB_HOTPLUG_ALIVE_TIMER); + } else { + /* In the case that the timer has expired we need to wait for the RXNRIP */ + port->unplug_detected = 1; + + /* Remove the serial port for canceling the UART operations */ + if (port->uart_created) { + struct uart_port *uart; + + uart = &port->uart; + uart_remove_one_port(&fim_usb_uart_driver, uart); + port->uart_created = 0; + } + + fim_usb_restart_fim(port); + + pk_dbg("Keep alive counter expired (init 0x%x | curr 0x%x). Port %i removed\n", + cnt_init, cnt_curr, fim->picnr); + } +} + +/* Function used for creating or removing the UART port of this FIM */ +static struct uart_ops fim_usb_uart_ops; +static void fim_usb_uart_work_func(struct work_struct *work) +{ + struct fim_usb_port *port; + int ret; + struct uart_port *uart; + struct fim_driver *fim; + + port = container_of(work, struct fim_usb_port, uart_work); + uart = &port->uart; + fim = &port->fim; + + /* + * This can happen when the device entered the suspend mode and restarts the + * enumeration sequence + */ + if (port->uart_created) { + pk_dbg("UART port %i already created.\n", fim->picnr); + goto exit_sched; + } + + /* We really need to restart the structure at this place */ + uart->ops = &fim_usb_uart_ops; + uart->flags = UPF_BOOT_AUTOCONF; + uart->dev = port->dev; + uart->type = UPIO_MEM; + uart->line = fim->picnr; /* Use the FIM number as minor */ + uart->iotype = UPIO_PORT; + dev_set_drvdata(uart->dev, port); + + /* + * @FIXME: Lock this section, otherwise we will have some problems by the plug-unplug + * tests of the SALAB. + */ + pk_dbg("Adding the new port %i\n", fim->picnr); + ret = uart_add_one_port(&fim_usb_uart_driver, uart); + if (ret) { + dev_set_drvdata(uart->dev, NULL); + pk_err("Couldn't register the UART port %i\n", fim->picnr); + goto err_add; + } + + port->uart_created = 1; + + exit_sched: + /* @FIXME: Is this the correct place for our FIM timer loader? */ + schedule_delayed_work(&port->hotplug_work, 0); + + err_add: + return; +} + +/* + * Send a buffer over the FIM-API. We are using static buffers for sending the data + * to the FIMs, we don't need to have a new allocated buffer, since the FIM only accepts + * frames with a maximal sizo of 13 bytes (8 with the EP data) + * (Luis Galdos) + */ +static int __fim_usb_ep_send_buffer(struct fim_usb_port *port, void *buffer, int len, void *priv) +{ + struct fim_driver *fim; + int retval; + struct fim_buffer_t buf; + + if (!port || !len) + return -EINVAL; + + fim = &port->fim; + + buf.private = priv; + buf.data = buffer; + buf.length = len; + + retval = fim_send_buffer(fim, &buf); + if (retval) { + int level; + + level = fim_tx_buffers_level(&port->fim); + pk_dbg("FIM%i send %i bytes failed (err %i, level %i)\n", + fim->picnr, buf.length, retval, level); + schedule_work(&port->restart_work); + } + + return retval; +} + +/* Calculate the CRC16 for the passed buffer */ +static inline u16 fim_usb_calc_buffer_crc(const u8 *buffer, unsigned long length) +{ + u16 retval, bx; + unsigned char crc16; + int i; + ulong cnt; + + retval = 0xffff; + for (cnt = 0; cnt <length; cnt++) { + for (i=0; i < 8; i++) { + crc16 = retval >> (15 - i); + bx = crc16 ^ buffer[cnt]; + retval <<= 1; + + if (bx & (0x1 << i)) + retval ^= 0x8005; + } + } + + bx = ~retval; + retval = 0; + for (i=0; i < 8; i++) + retval |= ((bx & (0x1 << i)) << (15 - (i * 2)) | + (bx & (0x100 << i)) >> (1 + (i * 2))); + + return retval; +} + +#define fim_usb_ep_next_data_token(ep) ((ep->last_data_token == USB_TOKEN_DATA0) ? \ + USB_TOKEN_DATA1 : USB_TOKEN_DATA0) + +/* + * Call this function for sending a ZLP by the passed EP + * If force is true, the function will ignore the current TX fifo level and will queue + * the new IN data packet. + */ +static int fim_usb_write_zlp(struct fim_usb_port *port, struct fim_usb_ep *ep, int force) +{ + struct fim_usb_in_frame fin; + int level; + + dbg_write("EP%u: Sending ZLP\n", ep->nr); + + level = fim_tx_buffers_level(&port->fim); + if (level && !force) { + pk_dbg("ZLP: TX level > 0 (%i). FIM restart.\n", level); + fim_usb_restart_fim(port); + return -ERESTART; + } + + memset(&fin, 0, sizeof(fin)); + fin.epnr = ep->addr & ~USB_DIR_IN; + fin.bytes = FIM_USB_DATA_TAIL; + fin.token = fim_usb_ep_next_data_token(ep); + + /* @XXX: Reset this flag depending on the return value of the send buffer function */ + ep->zlp = 0; + + return __fim_usb_ep_send_buffer(port, &fin, FIM_USB_DATA_OVERHEAD, ep); +} + +/* IMPORTANT: This function returns the number of transferred bytes! */ +static int fim_usb_write_packet(struct fim_usb_port *port, struct fim_usb_ep *ep, u8 *buf, u16 len) +{ + u16 crc, val; + struct fim_usb_in_frame fin; + int ret, level; + + /* @FIXME: Use a correct sanity check! */ + level = fim_tx_buffers_level(&port->fim); + if (level) { + pk_dbg("write packet: TX level = %i. FIM restart.\n", level); + fim_usb_restart_fim(port); + return -EINVAL; + } + + val = min(len, (u16)FIM_USB_MAX_PACK_SIZE); + ep->bytes = len - val; + ep->data = buf + val; + ep->requested -= val; + dbg_enum("EP%u: Sending %u bytes [%p], pending %u [%p], requested %u\n", + ep->nr, val, buf, ep->bytes, ep->data, ep->requested); + + /* + * The FIM expects that the IN-data is placed in the TX-FIFO. + * + */ + crc = fim_usb_calc_buffer_crc(buf, val); +#if defined(FIM_USB_DEBUG_SEND_BUFFER) + dbg_func("Calculated CRC 0x%04x\n", crc); +#endif + + /* Format: Endpoint number | Number of bytes | Token | Data | CRC16 */ + fin.epnr = ep->addr & ~USB_DIR_IN; + fin.bytes = val + FIM_USB_DATA_TAIL; /* Token and CRC16 */ + + fin.token = fim_usb_ep_next_data_token(ep); + ep->last_data_token = fin.token; + + fin.crc = (u16 *)(fin.data + val); + memcpy(fin.data, buf, val); + + *fin.crc = crc; + ret = __fim_usb_ep_send_buffer(port, &fin, val + FIM_USB_DATA_OVERHEAD, ep); + return ret ? ret : val; +} + +#define FIM_USB_VENDOR_ID (0x0210) +#define FIM_USB_PRODUCT_ID (0xbe13) + +#define STRING_MANUFACTURER 1 +#define STRING_PRODUCT 2 +#define STRING_SERIALNUM 3 + +#define DEV_CONFIG_VALUE 1 +#define FIM_USB_INTERFACE 0 + +static struct usb_device_descriptor fim_usb_device_desc = { + .bLength = sizeof(fim_usb_device_desc), + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16(0x0110), + .bDeviceClass = 0, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + .bMaxPacketSize0 = 8, + .idVendor = __constant_cpu_to_le16(FIM_USB_VENDOR_ID), + .idProduct = __constant_cpu_to_le16(FIM_USB_PRODUCT_ID), + .iManufacturer = STRING_MANUFACTURER, + .iProduct = STRING_PRODUCT, + .iSerialNumber = STRING_SERIALNUM, + .bNumConfigurations = 1 +}; + +static struct fim_usb_config_descriptor fim_usb_config_desc2 = { + .config = { + .bLength = USB_DT_CONFIG_SIZE, + .bDescriptorType = USB_DT_CONFIG, + + /* compute wTotalLength on the fly */ + .bNumInterfaces = 1, + .bConfigurationValue = DEV_CONFIG_VALUE, + .iConfiguration = 0, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = 0 + }, + .interface = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = FIM_USB_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = 0, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = 0 + }, + .ep_out = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = FIM_USB_OUT_EPNR, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(FIM_USB_MAX_PACK_SIZE), + .bInterval = 1, + }, + .ep_in = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = FIM_USB_IN_EPNR, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(FIM_USB_MAX_PACK_SIZE), + .bInterval = 1, + } +}; + +static struct fim_usb_string fim_usb_strings [] = { + { + .id = STRING_MANUFACTURER, + .s = FIM_USB_MANU + }, { + .id = STRING_PRODUCT, + .s = FIM_USB_PROD_DESC + }, { + .id = STRING_SERIALNUM, + .s = FIM_USB_SERIALNR + } +}; + +/* Send the requested string descriptor to the host */ +static int fim_usb_send_string_descriptor(struct fim_usb_port *port, struct fim_usb_ep *ep, + struct usb_ctrlrequest *ctrl) +{ + int ret, cnt; + u8 value; + + ret = -EINVAL; + value = le16_to_cpu(ctrl->wValue); + + if (value == 0) { + struct usb_string_descriptor desc; + + desc.bLength = sizeof(desc); + desc.bDescriptorType = USB_DT_STRING; + desc.wData[0] = __constant_cpu_to_le16(0x0409); + ret = fim_usb_write_packet(port, ep, (u8 *)&desc, sizeof(desc)); + + } else { + struct fim_usb_string *pstr; + + pstr = NULL; + for (cnt = 0; cnt < ARRAY_SIZE(fim_usb_strings); cnt++) { + if (fim_usb_strings[cnt].id == value) { + pstr = &fim_usb_strings[cnt]; + break; + } + } + + if (!pstr) { + pk_dbg("Unavailable string descriptor 0x%04x request!\n", value); + ret = -EINVAL; + goto exit_str_desc; + } + + /* OK, we have a string descriptor, send it now! */ + ret = fim_usb_write_packet(port, ep, (u8 *)&pstr->desc, pstr->desc.bLength); + } + + exit_str_desc: + return ret; +} + +static int fim_usb_handle_ep0_get_descriptor(struct fim_usb_port *port, struct usb_ctrlrequest *ctrl) +{ + u8 desc; + u16 wvalue, len, total, val; + struct fim_usb_ep *ep; + int ret; + + len = le16_to_cpu(ctrl->wLength); + wvalue = le16_to_cpu(ctrl->wValue); + desc = wvalue >> 8; + ep = &port->eps[FIM_USB_NR_EP_CTRL]; + + ep->requested = len; + dbg_enum("Handling get descriptor 0x%02x (len %u)\n", desc, len); + + ret = 0; + switch (desc) { + + /* Descriptor type device */ + case USB_DT_DEVICE: + ep->last_data_token = USB_TOKEN_DATA0; + ret = fim_usb_write_packet(port, ep, (u8 *)&fim_usb_device_desc, sizeof(fim_usb_device_desc)); + break; + + /* Descriptor type configuration */ + case USB_DT_CONFIG: + ep->last_data_token = USB_TOKEN_DATA0; + + /* Setup the total length of the configuration descriptor */ + total = sizeof(fim_usb_config_desc2); + val = min(len, total); + fim_usb_config_desc2.config.wTotalLength = cpu_to_le16(total); + + ret = fim_usb_write_packet(port, ep, (u8 *)&fim_usb_config_desc2, val); + dbg_enum("Configuration desc. size = %u | sent %u | requested %u\n", + total, ret, ep->requested); + break; + + /* Descriptor type string */ + case USB_DT_STRING: + + ep->last_data_token = USB_TOKEN_DATA0; + + /* According to the spec: by index zero return the table with the supported languages */ + ret = fim_usb_send_string_descriptor(port, ep, ctrl); + break; + + default: + pk_err("Unhandled GET descriptor request 0x%02x\n", desc); + break; + } + + return ret; +} + +static int fim_usb_handle_ep0_standard(struct fim_usb_port *port, struct usb_ctrlrequest *ctrl) +{ + int ret = 0; + struct fim_usb_ep *ep; + u8 addr; + u16 value, idx, type; + int level; + + ep = &port->eps[FIM_USB_NR_EP_CTRL]; + + switch (ctrl->bRequest) { + case USB_REQ_GET_DESCRIPTOR: + if (ctrl->bRequestType != USB_DIR_IN) + break; + + dbg_enum("Get descriptor 0x%02x\n", le16_to_cpu(ctrl->wValue)); + ret = fim_usb_handle_ep0_get_descriptor(port, ctrl); + break; + + /* We must responde with a zero packet length */ + case USB_REQ_SET_ADDRESS: + addr = le16_to_cpu(ctrl->wValue); + ep->last_data_token = USB_TOKEN_DATA0; + fim_usb_write_zlp(port, ep, 0); + + /* @FIXME: Arrgghhh...! Give the FIM some time for sending the ZLP */ + udelay(1500); + + fim_set_ctrl_reg(&port->fim, FIM_USB_ADDR_REG, addr); + dbg_enum("Set address request (%d)\n", addr); + break; + + /* @FIXME: Check for the configuration number */ + case USB_REQ_SET_CONFIGURATION: + value = le16_to_cpu(ctrl->wValue); + fim_usb_write_zlp(port, ep, 0); + dbg_enum("Set configuration %u request\n", value); + + /* So, let's create the serial device port */ + schedule_work(&port->uart_work); + break; + + /* @FIXME: Check for the interface number */ + case USB_REQ_SET_INTERFACE: + + level = fim_tx_buffers_level(&port->fim); + if (level) { + + /* + * That's really bad, cause the host wants to start a new data transfer but + * the TX FIFO is not free! + */ + pk_dbg("TX FIFO restart for SET interface (level = %i)\n", level); + schedule_work(&port->restart_work); + return -ERESTART; + } + + value = le16_to_cpu(ctrl->wValue); + fim_usb_write_zlp(port, ep, 0); + dbg_enum("Set configuration %u request\n", value); + break; + + case USB_REQ_CLEAR_FEATURE: + idx = le16_to_cpu(ctrl->wIndex); + value = le16_to_cpu(ctrl->wValue); + type = le16_to_cpu(ctrl->bRequest); + dbg_enum("Clear feature type 0x%x | index 0x%x | value 0x%0x\n", + type, idx, value); + + /* Only EPs supported now */ + if (type != 2 && (!idx || idx == FIM_USB_IN_EPNR || idx == FIM_USB_OUT_EPNR)) + fim_usb_write_zlp(port, ep, 0); + else { + /* @FIXME: Send a STALL at this point */ + pk_err("Invalid clear feature request (idx 0x%x | type 0x%x)\n", idx, type); + ret = -EINVAL; + } + + break; + + default: + pk_err("Unhandled standard request 0x%02x\n", ctrl->bRequest); + pk_dump_ctrl_packet(ctrl); + /* @FIXME: Send a STALL to the host! */ + break; + } + + return ret; +} + +static int fim_usb_handle_ep1_out(struct fim_usb_port *port, u8 *data, int len) +{ + struct tty_struct *tty; + int count; + + dbg_uart("FIM%i: Writing %i bytes into UART fifo\n", port->index, len); + + tty = tty_from_port(port); + if (!port->uart_created || !tty) { + + /* This happens when the serial port was not opened yet */ + dbg_uart("Found an uninitialized port %p | tty %p\n", port, tty); + return -ENODEV; + } + + /* Don't copy more bytes than there is room for in the buffer */ + count = tty_buffer_request_room(tty, len); + if (count < len) { + pk_dbg("Dropping %i chars [ len %i > %i count ]\n", len - count, len, count); + len = count; + + if (len == 0xfffe) + return 0; + } + + tty_insert_flip_string(tty, data, len); + tty_flip_buffer_push(tty); + + return 0; +} + +/* Handle the requests arrived for the EP0 (control EP) */ +static int fim_usb_handle_ep0(struct fim_usb_port *port, + struct usb_ctrlrequest *pctrl) +{ + int ret = 0; + u16 windex; + + if (!pctrl || !port) + return -ENODEV; + + windex = le16_to_cpu(pctrl->wIndex); + + switch (pctrl->bRequestType & USB_TYPE_MASK) { + + case USB_TYPE_STANDARD: + ret = fim_usb_handle_ep0_standard(port, pctrl); + break; + + default: + pk_err("bRequestType 0x%02x | bRequest 0x%02x | " + "wValue 0x%04x | wIndex 0x%04x | wLength %u\n", + pctrl->bRequestType, pctrl->bRequest, + pctrl->wValue, pctrl->wIndex, pctrl->wLength); + break; + } + + return ret; +} + +static void fim_usb_error_isr(struct fim_driver *driver, ulong rx_err, ulong tx_err) +{ + struct fim_usb_port *port; + + port = driver->driver_data; + + if ((rx_err & IOHUB_IFS_RXNRIP) || port->unplug_detected) { + + if (!work_pending(&port->restart_work)) { + /* pk_dbg("Scheduling restart worker\n"); */ + schedule_work(&port->restart_work); + } + } else + pk_dbg("Unhandled FIM error (rx 0x%lx | tx 0x%lx)\n", rx_err, tx_err); +} + +/* + * Handler for the incoming FIM-interrupts. Available interrupts: + * - + */ +static void fim_usb_isr(struct fim_driver *driver, int irq, unsigned char code, + unsigned int rx_fifo) +{ + struct fim_usb_port *port; + + dbg_func("FIM IRQ %i\n", irq); + port = port_from_fim(driver); + + switch (code) { + default: + pk_err("Unknown IRQ %i | FIM %i | %x\n", + code, port->fim.picnr, rx_fifo); + break; + } +} + +/* + * This is the TX-callback that the API call after a DMA-package was closed + * The fim buffer structure contains our internal private data + * Free the allocated FIM-buffer that was used for sending the DMA-data + */ +static void fim_usb_tx_isr(struct fim_driver *fim, int irq, + struct fim_buffer_t *pdata) +{ + struct fim_usb_port *port; + struct fim_usb_ep *ep; + struct uart_port *uart; + struct circ_buf *xmit; + + dbg_write("TX IRQ\n"); + + /* Reset the internal buffer descriptor of the EP */ + port = fim->driver_data; + + /* Here we can start the next data transfer if there is something pending */ + ep = &port->eps[FIM_USB_NR_EP_CTRL]; + if (!ep->addr && ep->bytes) { + int sent; + + sent = fim_usb_write_packet(port, ep, ep->data, ep->bytes); + if (sent == FIM_USB_MAX_PACK_SIZE && !ep->bytes && ep->requested) { + fim_usb_write_zlp(port, ep, 1); + dbg_enum("ZLP for big packet (request pending %u)\n", ep->requested); + } + + /* @XXX: Should we return at this point? */ + /* return; */ + } + + /* Schedule the work queue at this point if there is some remaining data */ + ep = &port->eps[FIM_USB_NR_EP_IN]; + uart = ep->tx_uart; + if (uart) { + + /* The worker will wakeup the device if the circular buffer is empty */ + xmit = &uart->info->xmit; + if (xmit && !uart_circ_empty(xmit) && !uart_tx_stopped(uart)) { + dbg_uart("EP%02x Scheduling TX work\n", ep->addr); + schedule_work(&ep->tx_work); + } + } +} + +static int fim_usb_restart_fim(struct fim_usb_port *port) +{ + int ret; + struct fim_driver *fim; + + if (!port) + return -ENODEV; + + fim = &port->fim; + + fim_usb_disable_pullup(port); + + /* Reset the main register */ + fim_usb_regs_reset(port); + + /* First try to stop the FIM */ + fim_disable_irq(fim); + ret = fim_send_stop(fim); + if (ret) { + pk_err("Couldn't stop the FIM%i\n", fim->picnr); + return ret; + } + + fim_dma_stop(&port->fim); + fim_dma_start(&port->fim, NULL); + + ret = fim_send_start(fim); + if (ret) { + pk_err("Couldn't start the FIM%i\n", fim->picnr); + return -EAGAIN; + } + + /* Restart the DMA channel when we know that we are safe! */ + fim_enable_irq(fim); + + /* This function resets the address number to zero */ + fim_usb_init_port(port); + + /* This will enable the start of the FIM in the firmware */ + fim_set_ctrl_reg(&port->fim, FIM_USB_MAIN_REG, + FIM_USB_MAIN_START | FIM_USB_MAIN_NOKEEPALIVE | FIM_USB_MAIN_NOINIRQ); + + fim_usb_enable_pullup(port); + + return ret; +} + +static void fim_usb_restart_work_func(struct work_struct *work) +{ + struct fim_usb_port *port; + + port = container_of(work, struct fim_usb_port, restart_work); + fim_usb_restart_fim(port); + complete(&port->restart_completed); +} + +/* Handle the bus resets reported by the FIM */ +static void fim_usb_bus_reset(struct fim_usb_port *port) +{ + unsigned int addr; + int level; + + dbg_enum("Bus reset (addr = 0)\n"); + + /* We need to restore the TX buffers at this point */ + fim_get_ctrl_reg(&port->fim, FIM_USB_ADDR_REG, &addr); + level = fim_tx_buffers_level(&port->fim); + if (level && addr) { + dbg_enum("Need to restart the FIM (level %i, curr addr. %u)\n", + level, addr); + schedule_work(&port->restart_work); + } else { + addr = 0; + fim_set_ctrl_reg(&port->fim, FIM_USB_ADDR_REG, addr); + } +} + +/* + * Called when a receive DMA-buffer was closed. + * The first byte contains the status of the closed buffer. + */ +static void fim_usb_rx_isr(struct fim_driver *fim, int irq, + struct fim_buffer_t *pdata) +{ + u8 status, err; + struct usb_ctrlrequest *pctrl; + struct fim_usb_port *port; + struct fim_usb_ep *ep; + struct fim_usb_token *token; + + port = fim->driver_data; + status = *(pdata->data); + err = *(pdata->data + pdata->length); + token = (struct fim_usb_token *)(pdata->data + 1); + + if (pdata->length == 1) { + + switch (status) { + + case FIM_USB_BUS_RESET: + dbg_pr("Bus reset received!\n"); + fim_usb_bus_reset(port); + break; + + case FIM_USB_KEEP_ALIVE: + dbg_pr("Alive\n"); + break; + + case FIM_USB_IN_DATA_SENT_MARKER: + dbg_write("FIM sending IN frame\n"); + break; + + default: + dbg_pr("Unexpected status 0x%x\n", status); + break; + } + + } else { + + switch (token->type) { + + case USB_TOKEN_DATA0: + case USB_TOKEN_DATA1: + dbg_token("DATA%c %u bytes for EP%u\n", + (token->type == USB_TOKEN_DATA0) ? '0' : '1', + pdata->length, port->ep_next_data); + + /* The complete frame contains the sync byte, the CRC and the error */ + if (port->ep_next_data == 0) { + + /* @XXX: Stupid sanity check for let it be for now */ + if (pdata->length != FIM_USB_DATA_OVERHEAD && pdata->length != FIM_USB_DATA_TAIL) { + pctrl = (struct usb_ctrlrequest *)(pdata->data + 2); + fim_usb_handle_ep0(port, pctrl); + } + + } else { + struct fim_usb_data *fdata; + u16 bytes; + + /* Remove the overhead from the data frame */ + bytes = pdata->length - FIM_USB_DATA_OVERHEAD; + fdata = (struct fim_usb_data *)pdata->data; + + /* We have only one EP, so let us call it directly from here */ + fim_usb_handle_ep1_out(port, pdata->data + FIM_USB_DATA_HEAD, bytes); + } + break; + + /* Get the endpoint number for the incoming data */ + case USB_TOKEN_OUT: + dbg_token("OUT token for EP %x\n", token->ep); + port->ep_next_data = token->ep; + + /* Reset the data token of the IN EP81 */ + ep = &port->eps[FIM_USB_NR_EP_IN]; + ep->last_data_token = USB_TOKEN_DATA1; + break; + + /* DONT print the IN tokens cause they are generated continously */ + case USB_TOKEN_IN: + /* dbg_token("IN token for EP %x\n", token->ep); */ + port->ep_next_data = token->ep; + break; + + case USB_TOKEN_SETUP: + port->ep_next_data = 0; + /* dbg_token("SETUP token for EP %x\n", token->ep); */ + + /* Reset the data token of the IN EP81 */ + ep = &port->eps[FIM_USB_NR_EP_IN]; + ep->last_data_token = USB_TOKEN_DATA1; + break; + + /* Here the tokens that only generate noise */ + case USB_TOKEN_SYNC: + default: + break; + } + } +} + +static int fim_usb_unregister_port(struct fim_usb_port *port) +{ + int cnt, ret; + struct fim_driver *fim; + struct uart_port *uart; + + if (!port || !port->reg) + return -ENODEV; + + fim = &port->fim; + pk_dbg("Going to unregister the FIM USB port %i\n", fim->picnr); + + flush_work(&port->uart_work); + cancel_work_sync(&port->restart_work); + cancel_delayed_work_sync(&port->hotplug_work); + + /* Disable the FIM from the connected host */ + fim_usb_disable_pullup(port); + + ret = fim_unregister_driver(fim); + if (ret) + goto exit_unreg; + + /* Unregister the UART port */ + if (port->uart_created) { + dbg_uart("Removing the port %i\n", fim->picnr); + uart = &port->uart; + uart_remove_one_port(&fim_usb_uart_driver, uart); + port->uart_created = 0; + } + + /* Free the requested GPIO */ + for (cnt = 0; cnt < FIM_USB_MAX_GPIOS; cnt++) { + dbg_pr("Freeing GPIO %i\n", port->gpios[cnt].nr); + if (port->gpios[cnt].nr == FIM_LAST_GPIO) + break; + else if (port->gpios[cnt].nr == FIM_GPIO_DONT_USE) + continue; + else + gpio_free(port->gpios[cnt].nr); + } + + /* Free the requested clock */ + if (!IS_ERR(port->clk)) + clk_put(port->clk); + + fim_usb_ports[fim->picnr] = NULL; + kfree(port); + ret = 0; + + exit_unreg: + return ret; +} + +/* + * Called when the port is opened. By the boot-console it will be called before + * the port configuration using set_termios + */ +static int fim_usb_uart_startup(struct uart_port *uart) +{ + struct fim_usb_port *port; + int ret = 0; + + dbg_uart("Calling %s()\n", __func__); + port = port_from_uart(uart); + if (!port) + return -ENODEV; + +#if defined(FIM_USB_FORCE_ENUM_TEST) + /* This will restart the enumeration sequence */ + pk_info("Forcing the enumeration test!\n"); + schedule_work(&port->restart_work); + ret = -ENODEV; +#else + /* + * We must flush the TX FIFO when there is some data pending. + * @XXX: Use a waitqueue with timeout for returning a failure, + * otherwise the user must reopen the port (annoying) + */ + if (fim_tx_buffers_level(&port->fim)) { + pk_dbg("TX FIFO contains data, FIM restart required.\n"); + schedule_work(&port->restart_work); + wait_for_completion(&port->restart_completed); + + pk_dbg("Restart completed. Re-checking TX FIFO state.\n"); + if (fim_tx_buffers_level(&port->fim)) { + pk_dbg("TX FIFO invalid state. Retry required.\n"); + ret = -EAGAIN; + } + } +#endif /* FIM_USB_FORCE_ENUM_TEST */ + + return ret; +} + +/* Return a string describing the type of the port */ +static const char *fim_usb_uart_type(struct uart_port *port) +{ + dbg_uart("Calling %s\n", __func__); + return FIM_DRIVER_NAME; +} + +static void fim_usb_uart_release_port(struct uart_port *port) +{ + dbg_uart("Calling %s\n", __func__); +} + +static int fim_usb_uart_request_port(struct uart_port *port) +{ + dbg_uart("Calling %s\n", __func__); + return 0; +} + +/* @TODO: Get more infos about the UART configuration */ +static void fim_usb_uart_config_port(struct uart_port *port, int flags) +{ + dbg_uart("Calling %s\n", __func__); + port->type = UPIO_MEM; +} + +static int fim_usb_uart_verify_port(struct uart_port *port, struct serial_struct *ser) +{ + dbg_uart("Verify port called\n"); + return 0; +} + +/* Called for setting the termios config */ +static void fim_usb_uart_set_termios(struct uart_port *uart, struct ktermios *termios, + struct ktermios *old) +{ + dbg_uart("Calling %s\n", __func__); +} + +static unsigned int fim_usb_uart_tx_empty(struct uart_port *uart) +{ + return TIOCSER_TEMT; +} + +/* Handle the data transfer from the UART circular buffer to the USB host (IN transfers) */ +static void fim_usb_uart_tx_work_func(struct work_struct *work) +{ + struct fim_usb_ep *ep; + struct circ_buf *xmit; + u8 buf[FIM_USB_MAX_PACK_SIZE]; + struct uart_port *uart; + ulong pending, pos; + int ret, tail; + struct fim_usb_port *port; + __u32 tx; + + ep = container_of(work, struct fim_usb_ep, tx_work); + port = ep->port; + + if (down_interruptible(&port->uart_sem)) + return; + + uart = ep->tx_uart; + xmit = &uart->info->xmit; + tail = xmit->tail; + tx = uart->icount.tx; + pending = uart_circ_chars_pending(xmit); + pos = ret = 0; + + while (pending--) { + + buf[pos++] = xmit->buf[tail]; + + if (pos == FIM_USB_MAX_PACK_SIZE || pending == 0) { + + /* + * Check if there is enough space in the DMA buffers for the next + * transfer request. Otherwise we must wait for the completion of + * some TX transfer and continue sending the data + */ + while (fim_tx_buffers_level(&port->fim)) { + schedule_timeout_interruptible(HZ / 1000); + if (signal_pending(current)) + goto exit_tx_worker; + } + + /* Send the data to the FIM */ + dbg_uart("EP%02x Sending %lu bytes | remaining %lu\n", ep->addr, pos, pending); + ret = fim_usb_write_packet(port, ep, buf, pos); + if (ret <= 0) { + pr_err("Couldn't send the buffer (err %i)\n", ret); + goto exit_tx_worker; + } + } + + tail = (tail + 1) & (UART_XMIT_SIZE - 1); + tx++; + + /* Only one messages per call! */ + if (pos == FIM_USB_MAX_PACK_SIZE || pending == 0) + break; + } + + /* OK, it looks we have sent some data, so update the UART structure */ + xmit->tail = tail; + uart->icount.tx = tx; + + /* Check for the ZLP condition on this EP */ + if (uart_circ_empty(xmit) && pos == FIM_USB_MAX_PACK_SIZE) { + dbg_uart("Need to send a ZLP\n"); + + /* @XXX: Quick and dirty! */ + /* Wait for an empty TX FIFO cause we can't queue messages */ + while (fim_tx_buffers_level(&port->fim)) { + schedule_timeout_interruptible(HZ / 1000); + if (signal_pending(current)) + goto exit_tx_worker; + } + + fim_usb_write_zlp(port, ep, 0); + } + + /* + * Tell the higher layer that we can send more data + * IMPORTANT: We need to check if TX was stopped before calling the wakeup cause + * the bove ZLP write function seems to take a lot of time and we have probably + * received a signal during this time + */ + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS && !uart_tx_stopped(uart)) { + uart_write_wakeup(uart); + } + + exit_tx_worker: + up(&port->uart_sem); +} + +/* This function is called when the TTY layer has some data to be sent */ +static void fim_usb_uart_start_tx(struct uart_port *uart) +{ + ulong pending; + struct fim_usb_ep *ep; + struct fim_usb_port *port; + struct circ_buf *xmit; + + port = port_from_uart(uart); + + /* + * IMPORTANT: The data token is modified in the function that puts the data + * buffer to the FIM-core. + */ + ep = &port->eps[FIM_USB_NR_EP_IN]; + ep->tx_uart = uart; + + xmit = &uart->info->xmit; + pending = uart_circ_chars_pending(xmit); + + dbg_uart("Scheduling TX worker | %lu bytes\n", pending); + schedule_work(&ep->tx_work); +} + +static unsigned int fim_usb_uart_get_mctrl(struct uart_port *port) +{ + return TIOCM_CTS | TIOCM_DSR | TIOCM_CAR; +} + +static void fim_usb_uart_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + /* @TODO? */ + dbg_uart("Calling %s\n", __func__); +} + +static void fim_usb_uart_stop_tx(struct uart_port *port) +{ + /* flush_work(&port->uart_work); */ + dbg_uart("Calling %s\n", __func__); +} + +static void fim_usb_uart_stop_rx(struct uart_port *port) +{ + dbg_uart("Calling %s\n", __func__); +} + +static void fim_usb_uart_enable_ms(struct uart_port *port) +{ + /* @TODO? */ + dbg_uart("Calling %s\n", __func__); +} + +static void fim_usb_uart_break_ctl(struct uart_port *port, int ctl) +{ + /* @TODO? */ + dbg_uart("Calling %s\n", __func__); +} + +static void fim_usb_uart_shutdown(struct uart_port *uart) +{ + struct fim_usb_port *port; + struct fim_usb_ep *ep; + + port = port_from_uart(uart); + ep = &port->eps[FIM_USB_NR_EP_IN]; + + flush_work(&ep->tx_work); + dbg_uart("Calling %s\n", __func__); +} + +static struct uart_ops fim_usb_uart_ops = { + .tx_empty = fim_usb_uart_tx_empty, + .set_mctrl = fim_usb_uart_set_mctrl, + .get_mctrl = fim_usb_uart_get_mctrl, + .stop_tx = fim_usb_uart_stop_tx, + .start_tx = fim_usb_uart_start_tx, + .stop_rx = fim_usb_uart_stop_rx, + .enable_ms = fim_usb_uart_enable_ms, + .break_ctl = fim_usb_uart_break_ctl, + .startup = fim_usb_uart_startup, + .shutdown = fim_usb_uart_shutdown, + .set_termios = fim_usb_uart_set_termios, + .type = fim_usb_uart_type, + .release_port = fim_usb_uart_release_port, + .request_port = fim_usb_uart_request_port, + .config_port = fim_usb_uart_config_port, + .verify_port = fim_usb_uart_verify_port +}; + +static int fim_usb_register_port(struct device *dev, int picnr, struct fim_gpio_t gpios[]) +{ + int ret, cnt; + struct fim_dma_cfg_t dma_cfg; + struct fim_usb_port *port; + unsigned int fwver; + + port = kzalloc(sizeof(* port), GFP_KERNEL); + if (!port) + return -ENOMEM; + + /* Get a reference to the SYS clock for setting CLK rate */ + ret = IS_ERR(port->clk = clk_get(port->fim.dev, "systemclock")); + if (ret) { + pk_err("Couldn't get the SYS clock.\n"); + goto err_free_port; + } + + /* Request GPIO */ + for (cnt = 0; cnt < FIM_USB_MAX_GPIOS; cnt++) { + + if (gpios[cnt].nr == FIM_LAST_GPIO) + break; + if (gpios[cnt].nr == FIM_GPIO_DONT_USE) + continue; + + dbg_pr("Requesting the GPIO %i (Function %i)\n", + gpios[cnt].nr, gpios[cnt].func); + ret = gpio_request(gpios[cnt].nr, FIM_DRIVER_NAME); + if (!ret) { + int dir, pullup; + + /* + * According to the documentation, the pins ENUM and SUSPEND + * must be controlled by the driver (they are not by the + * FIM-firmware). + */ + if (cnt == FIM_USB_GPIO_ENUM || cnt == FIM_USB_GPIO_SPND) { + dir = NS921X_GPIO_OUTPUT; + pullup = NS921X_GPIO_DISABLE_PULLUP; + } else { + dir = NS921X_GPIO_INPUT; + pullup = NS921X_GPIO_ENABLE_PULLUP; + } + + gpio_configure_ns921x_unlocked(gpios[cnt].nr, + dir, + NS921X_GPIO_DONT_INVERT, + gpios[cnt].func, + pullup); + } else { + /* Free the already requested GPIOs */ + pk_err("Couldn't request the GPIO %i\n", gpios[cnt].nr); + while (cnt) + gpio_free(gpios[--cnt].nr); + goto err_free_clk; + } + } + + /* Try to register FIM driver */ + port->index = picnr; + port->fim.picnr = picnr; + port->fim.driver.name = FIM_DRIVER_NAME; + port->fim.driver_data = port; + port->fim.fim_isr = fim_usb_isr; + port->fim.dma_tx_isr = fim_usb_tx_isr; + port->fim.dma_rx_isr = fim_usb_rx_isr; + port->fim.dma_error_isr = fim_usb_error_isr; + port->fim.driver_data = port; + + /* Specific DMA config */ + dma_cfg.rxnr = FIM_USB_DMA_RX_BUFFERS; + dma_cfg.txnr = FIM_USB_DMA_TX_BUFFERS; + dma_cfg.rxsz = FIM_USB_DMA_BUFFER_SIZE; + dma_cfg.txsz = FIM_USB_DMA_BUFFER_SIZE; + port->fim.dma_cfg = &dma_cfg; + + /* Check if have a firmware code for using to */ + port->fim.fw_name = FIM_USB_FIRMWARE_FILE; + port->fim.fw_code = FIM_USB_FIRMWARE_CODE; + ret = fim_register_driver(&port->fim); + if (ret) { + pk_err("Couldn't register the FIM %i USB driver.\n", picnr); + goto err_free_gpios; + } + + port->dev = dev; + memcpy(port->gpios, gpios, sizeof(port->gpios)); + + port->reg = 1; + dev_set_drvdata(dev, port); + + INIT_WORK(&port->uart_work, fim_usb_uart_work_func); + INIT_WORK(&port->restart_work, fim_usb_restart_work_func); + init_completion(&port->restart_completed); + init_MUTEX(&port->uart_sem); + + /* This is the timer for checking the state of the card detect line */ + INIT_DELAYED_WORK(&port->hotplug_work, fim_usb_hotplug_work_func); + + /* And enable the FIM-interrupt */ + ret = fim_enable_irq(&port->fim); + if (ret) { + pk_err("Couldn't enable the FIM IRQ\n"); + goto err_unreg_fim; + } + + /* First do it at this point! */ + fim_usb_disable_pullup(port); + fim_usb_init_port(port); + + /* Print the firmware version */ + fim_get_stat_reg(&port->fim, FIM_USB_REVISION_REG, &fwver); + pk_dbg("FIM%d running [fw rev 0x%02x]\n", picnr, fwver); + + fim_usb_ports[picnr] = port; + + /* This will enable the start of the FIM in the firmware */ + fim_set_ctrl_reg(&port->fim, FIM_USB_MAIN_REG, + FIM_USB_MAIN_START | FIM_USB_MAIN_NOKEEPALIVE | FIM_USB_MAIN_NOINIRQ); + fim_usb_enable_pullup(port); + return 0; + + err_unreg_fim: + fim_unregister_driver(&port->fim); + + err_free_gpios: + for (cnt = 0; cnt < FIM_USB_MAX_GPIOS; cnt++) { + + if (gpios[cnt].nr == FIM_GPIO_DONT_USE) + continue; + + if (gpios[cnt].nr == FIM_LAST_GPIO) + break; + + gpio_free(gpios[cnt].nr); + } + + err_free_clk: + clk_put(port->clk); + + err_free_port: + kfree(port); + + return ret; +} + +static int __init fim_usb_probe(struct platform_device *pdev) +{ + int nrpics; + struct fim_usb_platform_data *pdata; + struct fim_gpio_t gpios[FIM_USB_MAX_GPIOS]; + + dbg_func("New device with ID %i\n", pdev->id); + + pdata = pdev->dev.platform_data; + if (!pdata) + return -ENXIO; + + /* Get the number of available PICs from the FIM-core */ + nrpics = fim_number_pics(); + + if (fim_check_device_id(fims_number, pdata->fim_nr)) { +#if defined(MODULE) + pk_dbg("Skipping FIM%i (not selected)\n", pdata->fim_nr); +#else + pk_err("Invalid FIM number '%i' in platform data\n", pdata->fim_nr); +#endif + + return -ENODEV; + } + + /* + * Start with the registration of the pdata ports + */ + gpios[FIM_USB_GPIO_DP].nr = pdata->vp_gpio_nr; + gpios[FIM_USB_GPIO_DP].func = pdata->vp_gpio_func; + gpios[FIM_USB_GPIO_DM].nr = pdata->vm_gpio_nr; + gpios[FIM_USB_GPIO_DM].func = pdata->vm_gpio_func; + gpios[FIM_USB_GPIO_RCV].nr = pdata->rcv_gpio_nr; + gpios[FIM_USB_GPIO_RCV].func = pdata->rcv_gpio_func; + gpios[FIM_USB_GPIO_OE].nr = pdata->oe_l_gpio_nr; + gpios[FIM_USB_GPIO_OE].func = pdata->oe_l_gpio_func; + gpios[FIM_USB_GPIO_ENUM].nr = pdata->enum_gpio_nr; + gpios[FIM_USB_GPIO_ENUM].func = pdata->enum_gpio_func; + gpios[FIM_USB_GPIO_SPND].nr = pdata->spnd_gpio_nr; + gpios[FIM_USB_GPIO_SPND].func = pdata->spnd_gpio_func; + + dbg_pr("Pins: DP %d | DM %d | RCV %d | OE %d | ENUM %d | SPND %d\n", + gpios[0].nr, gpios[1].nr, gpios[2].nr, + gpios[3].nr, gpios[4].nr, gpios[5].nr ); + + return fim_usb_register_port(&pdev->dev, pdata->fim_nr, gpios); +} + +static int __exit fim_usb_remove(struct platform_device *pdev) +{ + struct fim_usb_port *port; + struct fim_usb_platform_data *pdata; + int retval; + + pdata = pdev->dev.platform_data; + if (!pdata) + return -ENXIO; + + port = dev_get_drvdata(&pdev->dev); + if (!port) + return -ENXIO; + + retval = fim_usb_unregister_port(port); + if (retval) + goto exit_remove; + + platform_set_drvdata(pdev, NULL); + + exit_remove: + return retval; +} + +#ifdef CONFIG_PM +/* Here we need to desconnect from the host and stop the FIM */ +static int fim_usb_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct fim_usb_port *port; + int ret; + struct fim_driver *fim; + + port = dev_get_drvdata(&pdev->dev); + if (!port) + return -ENXIO; + + cancel_delayed_work_sync(&port->hotplug_work); + + /* Disable the device and stop the FIM */ + fim_usb_disable_pullup(port); + + /* Stop the FIM now */ + fim = &port->fim; + ret = fim_send_stop(fim); + if (ret) + pk_err("Couldn't stop the FIM %i\n", fim->picnr); + + return ret; +} + +static int fim_usb_resume(struct platform_device *pdev) +{ + struct fim_usb_port *port; + + port = dev_get_drvdata(&pdev->dev); + if (!port) + return -ENXIO; + + return fim_usb_restart_fim(port); +} +#else +#define fim_usb_suspend NULL +#define fim_usb_resume NULL +#endif + +static struct platform_driver fim_usb_driver = { + .driver = { + .name = FIM_DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = fim_usb_probe, + .remove = __exit_p(fim_usb_remove), + .suspend = fim_usb_suspend, + .resume = fim_usb_resume, +}; + +static int __init fim_usb_init(void) +{ + int cnt, pos, ret, pics; + struct fim_usb_string *pstr; + u16 *pdata; + + /* Check for the passed number parameter */ + if (fim_check_numbers_param(fims_number)) { + pk_err("Invalid number '%i' of FIMs to handle\n", fims_number); + return -EINVAL; + } + + pk_info("%s: version %s [%s | %s]\n", FIM_DRIVER_NAME, DRIVER_VERSION, + __DATE__, __TIME__); + + /* For having an easier descriptors handling we only want to use static data buffers */ + for (cnt = 0; cnt < ARRAY_SIZE(fim_usb_strings); cnt++) { + pstr = &fim_usb_strings[cnt]; + pdata = pstr->data; + + for (pos = 0; pos < strlen(pstr->s); pos++) + *(pdata + pos) = cpu_to_le16(*(pstr->s + pos)); + + pstr->desc.bLength = sizeof(pstr->desc) + (2 * pos); + } + + pics = fim_number_pics(); + fim_usb_ports_max = pics; + fim_usb_ports = kzalloc(pics, sizeof(*fim_usb_ports)); + if (!fim_usb_ports) + return -ENOMEM; + + /* We need a reboot notifier for disabling the pullup */ + ret = register_reboot_notifier(&fim_usb_reboot_notifier); + if (ret) { + pk_err("Reboot notifier register failed, %i\n", ret); + goto err_exit_init; + } + + /* Register the internal UART driver */ + /* Get the number of maximal available FIMs */ + fim_usb_uart_driver.owner = THIS_MODULE; + fim_usb_uart_driver.driver_name = FIM_DRIVER_NAME; + fim_usb_uart_driver.dev_name = FIM_USB_UART_DEV_NAME; + fim_usb_uart_driver.nr = pics; + ret = uart_register_driver(&fim_usb_uart_driver); + if (ret) + goto err_unreg_noti; + + ret = platform_driver_register(&fim_usb_driver); + if (ret) + goto err_unreg_uart; + + return 0; + + err_unreg_uart: + uart_unregister_driver(&fim_usb_uart_driver); + + err_unreg_noti: + unregister_reboot_notifier(&fim_usb_reboot_notifier); + + err_exit_init: + kfree(fim_usb_ports); + return ret; +} + +static void __exit fim_usb_exit(void) +{ + pk_info("Removing the FIM USB driver [%s | %s]\n", __DATE__, __TIME__); + platform_driver_unregister(&fim_usb_driver); + uart_unregister_driver(&fim_usb_uart_driver); + + /* Free the resources for the reboot notifier */ + unregister_reboot_notifier(&fim_usb_reboot_notifier); + kfree(fim_usb_ports); +} + +module_init(fim_usb_init); +module_exit(fim_usb_exit); + +MODULE_DESCRIPTION(FIM_DRIVER_DESC); +MODULE_AUTHOR(FIM_DRIVER_AUTHOR); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:fim_usb"); + |