From: Alexander Shishkin Date: Fri, 11 May 2012 14:25:46 +0000 (+0300) Subject: usb: chipidea: split the driver code into units X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=e443b333629f82ca0da91a05ca638050943bbedd;p=GitHub%2Fmt8127%2Fandroid_kernel_alcatel_ttab.git usb: chipidea: split the driver code into units Split the driver into the following parts: * core -- resources, register access, capabilities, etc; * udc -- device controller functionality; * debug -- logging events. Signed-off-by: Alexander Shishkin Signed-off-by: Greg Kroah-Hartman --- diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index 71725ddc8f25..553c1976a66e 100644 --- a/drivers/usb/chipidea/Kconfig +++ b/drivers/usb/chipidea/Kconfig @@ -1,10 +1,26 @@ config USB_CHIPIDEA tristate "ChipIdea Highspeed Dual Role Controller" - depends on USB && USB_GADGET - select USB_GADGET_DUALSPEED + depends on USB help Say Y here if your system has a dual role high speed USB controller based on ChipIdea silicon IP. Currently, only the peripheral mode is supported. When compiled dynamically, the module will be called ci-hdrc.ko. + +if USB_CHIPIDEA + +config USB_CHIPIDEA_UDC + bool "ChipIdea device controller" + depends on USB_GADGET + select USB_GADGET_DUALSPEED + help + Say Y here to enable device controller functionality of the + ChipIdea driver. + +config USB_CHIPIDEA_DEBUG + bool "ChipIdea driver debug" + help + Say Y here to enable debugging output of the ChipIdea driver. + +endif diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile index e56bedbf9da2..a8279aac6a4a 100644 --- a/drivers/usb/chipidea/Makefile +++ b/drivers/usb/chipidea/Makefile @@ -1,6 +1,8 @@ obj-$(CONFIG_USB_CHIPIDEA) += ci_hdrc.o -ci_hdrc-y := ci13xxx_udc.o +ci_hdrc-y := core.o +ci_hdrc-$(CONFIG_USB_CHIPIDEA_UDC) += udc.o +ci_hdrc-$(CONFIG_USB_CHIPIDEA_DEBUG) += debug.o ifneq ($(CONFIG_PCI),) obj-$(CONFIG_USB_CHIPIDEA) += ci13xxx_pci.o diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h new file mode 100644 index 000000000000..5fbff11cf220 --- /dev/null +++ b/drivers/usb/chipidea/bits.h @@ -0,0 +1,71 @@ +/* + * bits.h - register bits of the ChipIdea USB IP core + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __DRIVERS_USB_CHIPIDEA_BITS_H +#define __DRIVERS_USB_CHIPIDEA_BITS_H + +/* HCCPARAMS */ +#define HCCPARAMS_LEN BIT(17) + +/* DCCPARAMS */ +#define DCCPARAMS_DEN (0x1F << 0) +#define DCCPARAMS_DC BIT(7) + +/* TESTMODE */ +#define TESTMODE_FORCE BIT(0) + +/* USBCMD */ +#define USBCMD_RS BIT(0) +#define USBCMD_RST BIT(1) +#define USBCMD_SUTW BIT(13) +#define USBCMD_ATDTW BIT(14) + +/* USBSTS & USBINTR */ +#define USBi_UI BIT(0) +#define USBi_UEI BIT(1) +#define USBi_PCI BIT(2) +#define USBi_URI BIT(6) +#define USBi_SLI BIT(8) + +/* DEVICEADDR */ +#define DEVICEADDR_USBADRA BIT(24) +#define DEVICEADDR_USBADR (0x7FUL << 25) + +/* PORTSC */ +#define PORTSC_FPR BIT(6) +#define PORTSC_SUSP BIT(7) +#define PORTSC_HSP BIT(9) +#define PORTSC_PTC (0x0FUL << 16) + +/* DEVLC */ +#define DEVLC_PSPD (0x03UL << 25) +#define DEVLC_PSPD_HS (0x02UL << 25) + +/* USBMODE */ +#define USBMODE_CM (0x03UL << 0) +#define USBMODE_CM_IDLE (0x00UL << 0) +#define USBMODE_CM_DEVICE (0x02UL << 0) +#define USBMODE_CM_HOST (0x03UL << 0) +#define USBMODE_SLOM BIT(3) +#define USBMODE_SDIS BIT(4) + +/* ENDPTCTRL */ +#define ENDPTCTRL_RXS BIT(0) +#define ENDPTCTRL_RXT (0x03UL << 2) +#define ENDPTCTRL_RXR BIT(6) /* reserved for port 0 */ +#define ENDPTCTRL_RXE BIT(7) +#define ENDPTCTRL_TXS BIT(16) +#define ENDPTCTRL_TXT (0x03UL << 18) +#define ENDPTCTRL_TXR BIT(22) /* reserved for port 0 */ +#define ENDPTCTRL_TXE BIT(23) + +#endif /* __DRIVERS_USB_CHIPIDEA_BITS_H */ diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h new file mode 100644 index 000000000000..f5b3b8538a3b --- /dev/null +++ b/drivers/usb/chipidea/ci.h @@ -0,0 +1,204 @@ +/* + * ci.h - common structures, functions, and macros of the ChipIdea driver + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __DRIVERS_USB_CHIPIDEA_CI_H +#define __DRIVERS_USB_CHIPIDEA_CI_H + +#include +#include + +/****************************************************************************** + * DEFINE + *****************************************************************************/ +#define DMA_ADDR_INVALID (~(dma_addr_t)0) +#define CI13XXX_PAGE_SIZE 4096ul /* page size for TD's */ +#define ENDPT_MAX 32 + +/****************************************************************************** + * STRUCTURES + *****************************************************************************/ +/* Extension of usb_ep */ +struct ci13xxx_ep { + struct usb_ep ep; + u8 dir; + u8 num; + u8 type; + char name[16]; + struct { + struct list_head queue; + struct ci13xxx_qh *ptr; + dma_addr_t dma; + } qh; + int wedge; + + /* global resources */ + struct ci13xxx *udc; + spinlock_t *lock; + struct device *device; + struct dma_pool *td_pool; +}; + +struct hw_bank { + unsigned lpm; /* is LPM? */ + void __iomem *abs; /* bus map offset */ + void __iomem *cap; /* bus map offset + CAP offset */ + void __iomem *op; /* bus map offset + OP offset */ + size_t size; /* bank size */ + void __iomem **regmap; +}; + +/* CI13XXX UDC descriptor & global resources */ +struct ci13xxx { + spinlock_t lock; /* ctrl register bank access */ + void __iomem *regs; /* registers address space */ + + struct dma_pool *qh_pool; /* DMA pool for queue heads */ + struct dma_pool *td_pool; /* DMA pool for transfer descs */ + struct usb_request *status; /* ep0 status request */ + + struct device *dev; + struct usb_gadget gadget; /* USB slave device */ + struct ci13xxx_ep ci13xxx_ep[ENDPT_MAX]; /* extended endpts */ + u32 ep0_dir; /* ep0 direction */ + struct ci13xxx_ep *ep0out, *ep0in; + unsigned hw_ep_max; /* number of hw endpoints */ + + bool setaddr; + u8 address; + u8 remote_wakeup; /* Is remote wakeup feature + enabled by the host? */ + u8 suspended; /* suspended by the host */ + u8 test_mode; /* the selected test mode */ + + struct hw_bank hw_bank; + int irq; + struct usb_gadget_driver *driver; /* 3rd party gadget driver */ + struct ci13xxx_udc_driver *udc_driver; /* device controller driver */ + int vbus_active; /* is VBUS active */ + struct usb_phy *transceiver; /* Transceiver struct */ +}; + +/****************************************************************************** + * REGISTERS + *****************************************************************************/ +/* register size */ +#define REG_BITS (32) + +/* register indices */ +enum ci13xxx_regs { + CAP_CAPLENGTH, + CAP_HCCPARAMS, + CAP_DCCPARAMS, + CAP_TESTMODE, + CAP_LAST = CAP_TESTMODE, + OP_USBCMD, + OP_USBSTS, + OP_USBINTR, + OP_DEVICEADDR, + OP_ENDPTLISTADDR, + OP_PORTSC, + OP_DEVLC, + OP_USBMODE, + OP_ENDPTSETUPSTAT, + OP_ENDPTPRIME, + OP_ENDPTFLUSH, + OP_ENDPTSTAT, + OP_ENDPTCOMPLETE, + OP_ENDPTCTRL, + /* endptctrl1..15 follow */ + OP_LAST = OP_ENDPTCTRL + ENDPT_MAX / 2, +}; + + +/** + * ffs_nr: find first (least significant) bit set + * @x: the word to search + * + * This function returns bit number (instead of position) + */ +static inline int ffs_nr(u32 x) +{ + int n = ffs(x); + + return n ? n-1 : 32; +} + +/** + * hw_read: reads from a hw register + * @reg: register index + * @mask: bitfield mask + * + * This function returns register contents + */ +static inline u32 hw_read(struct ci13xxx *udc, enum ci13xxx_regs reg, u32 mask) +{ + return ioread32(udc->hw_bank.regmap[reg]) & mask; +} + +/** + * hw_write: writes to a hw register + * @reg: register index + * @mask: bitfield mask + * @data: new value + */ +static inline void hw_write(struct ci13xxx *udc, enum ci13xxx_regs reg, + u32 mask, u32 data) +{ + if (~mask) + data = (ioread32(udc->hw_bank.regmap[reg]) & ~mask) + | (data & mask); + + iowrite32(data, udc->hw_bank.regmap[reg]); +} + +/** + * hw_test_and_clear: tests & clears a hw register + * @reg: register index + * @mask: bitfield mask + * + * This function returns register contents + */ +static inline u32 hw_test_and_clear(struct ci13xxx *udc, enum ci13xxx_regs reg, + u32 mask) +{ + u32 val = ioread32(udc->hw_bank.regmap[reg]) & mask; + + iowrite32(val, udc->hw_bank.regmap[reg]); + return val; +} + +/** + * hw_test_and_write: tests & writes a hw register + * @reg: register index + * @mask: bitfield mask + * @data: new value + * + * This function returns register contents + */ +static inline u32 hw_test_and_write(struct ci13xxx *udc, enum ci13xxx_regs reg, + u32 mask, u32 data) +{ + u32 val = hw_read(udc, reg, ~0); + + hw_write(udc, reg, mask, data); + return (val & mask) >> ffs_nr(mask); +} + +int hw_device_init(struct ci13xxx *udc, void __iomem *base, + uintptr_t cap_offset); +int hw_device_reset(struct ci13xxx *ci); + +int hw_port_test_set(struct ci13xxx *ci, u8 mode); + +u8 hw_port_test_get(struct ci13xxx *ci); + +#endif /* __DRIVERS_USB_CHIPIDEA_CI_H */ diff --git a/drivers/usb/chipidea/ci13xxx_msm.c b/drivers/usb/chipidea/ci13xxx_msm.c index 418de0e61c5a..27427931b681 100644 --- a/drivers/usb/chipidea/ci13xxx_msm.c +++ b/drivers/usb/chipidea/ci13xxx_msm.c @@ -11,8 +11,9 @@ #include #include #include +#include -#include "ci13xxx_udc.h" +#include "ci.h" #define MSM_USB_BASE (udc->regs) diff --git a/drivers/usb/chipidea/ci13xxx_pci.c b/drivers/usb/chipidea/ci13xxx_pci.c index f075ef33834f..84e8ab8d4f47 100644 --- a/drivers/usb/chipidea/ci13xxx_pci.c +++ b/drivers/usb/chipidea/ci13xxx_pci.c @@ -15,8 +15,7 @@ #include #include #include - -#include "ci13xxx_udc.h" +#include /* driver name */ #define UDC_DRIVER_NAME "ci13xxx_pci" diff --git a/drivers/usb/chipidea/ci13xxx_udc.c b/drivers/usb/chipidea/ci13xxx_udc.c deleted file mode 100644 index 819636a19186..000000000000 --- a/drivers/usb/chipidea/ci13xxx_udc.c +++ /dev/null @@ -1,2983 +0,0 @@ -/* - * ci13xxx_udc.c - MIPS USB IP core family device controller - * - * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. - * - * Author: David Lopo - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -/* - * Description: MIPS USB IP core family device controller - * Currently it only supports IP part number CI13412 - * - * This driver is composed of several blocks: - * - HW: hardware interface - * - DBG: debug facilities (optional) - * - UTIL: utilities - * - ISR: interrupts handling - * - ENDPT: endpoint operations (Gadget API) - * - GADGET: gadget operations (Gadget API) - * - BUS: bus glue code, bus abstraction layer - * - * Compile Options - * - CONFIG_USB_GADGET_DEBUG_FILES: enable debug facilities - * - STALL_IN: non-empty bulk-in pipes cannot be halted - * if defined mass storage compliance succeeds but with warnings - * => case 4: Hi > Dn - * => case 5: Hi > Di - * => case 8: Hi <> Do - * if undefined usbtest 13 fails - * - TRACE: enable function tracing (depends on DEBUG) - * - * Main Features - * - Chapter 9 & Mass Storage Compliance with Gadget File Storage - * - Chapter 9 Compliance with Gadget Zero (STALL_IN undefined) - * - Normal & LPM support - * - * USBTEST Report - * - OK: 0-12, 13 (STALL_IN defined) & 14 - * - Not Supported: 15 & 16 (ISO) - * - * TODO List - * - OTG - * - Isochronous & Interrupt Traffic - * - Handle requests which spawns into several TDs - * - GET_STATUS(device) - always reports 0 - * - Gadget API (majority of optional features) - * - Suspend & Remote Wakeup - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ci13xxx_udc.h" - -/****************************************************************************** - * DEFINE - *****************************************************************************/ - -#define DMA_ADDR_INVALID (~(dma_addr_t)0) - -/* control endpoint description */ -static const struct usb_endpoint_descriptor -ctrl_endpt_out_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - - .bEndpointAddress = USB_DIR_OUT, - .bmAttributes = USB_ENDPOINT_XFER_CONTROL, - .wMaxPacketSize = cpu_to_le16(CTRL_PAYLOAD_MAX), -}; - -static const struct usb_endpoint_descriptor -ctrl_endpt_in_desc = { - .bLength = USB_DT_ENDPOINT_SIZE, - .bDescriptorType = USB_DT_ENDPOINT, - - .bEndpointAddress = USB_DIR_IN, - .bmAttributes = USB_ENDPOINT_XFER_CONTROL, - .wMaxPacketSize = cpu_to_le16(CTRL_PAYLOAD_MAX), -}; - -/* Interrupt statistics */ -#define ISR_MASK 0x1F -static struct { - u32 test; - u32 ui; - u32 uei; - u32 pci; - u32 uri; - u32 sli; - u32 none; - struct { - u32 cnt; - u32 buf[ISR_MASK+1]; - u32 idx; - } hndl; -} isr_statistics; - -/** - * ffs_nr: find first (least significant) bit set - * @x: the word to search - * - * This function returns bit number (instead of position) - */ -static int ffs_nr(u32 x) -{ - int n = ffs(x); - - return n ? n-1 : 32; -} - -/****************************************************************************** - * HW block - *****************************************************************************/ - -/* MSM specific */ -#define ABS_AHBBURST (0x0090UL) -#define ABS_AHBMODE (0x0098UL) -/* UDC register map */ -static uintptr_t ci_regs_nolpm[] = { - [CAP_CAPLENGTH] = 0x000UL, - [CAP_HCCPARAMS] = 0x008UL, - [CAP_DCCPARAMS] = 0x024UL, - [CAP_TESTMODE] = 0x038UL, - [OP_USBCMD] = 0x000UL, - [OP_USBSTS] = 0x004UL, - [OP_USBINTR] = 0x008UL, - [OP_DEVICEADDR] = 0x014UL, - [OP_ENDPTLISTADDR] = 0x018UL, - [OP_PORTSC] = 0x044UL, - [OP_DEVLC] = 0x084UL, - [OP_USBMODE] = 0x068UL, - [OP_ENDPTSETUPSTAT] = 0x06CUL, - [OP_ENDPTPRIME] = 0x070UL, - [OP_ENDPTFLUSH] = 0x074UL, - [OP_ENDPTSTAT] = 0x078UL, - [OP_ENDPTCOMPLETE] = 0x07CUL, - [OP_ENDPTCTRL] = 0x080UL, -}; - -static uintptr_t ci_regs_lpm[] = { - [CAP_CAPLENGTH] = 0x000UL, - [CAP_HCCPARAMS] = 0x008UL, - [CAP_DCCPARAMS] = 0x024UL, - [CAP_TESTMODE] = 0x0FCUL, - [OP_USBCMD] = 0x000UL, - [OP_USBSTS] = 0x004UL, - [OP_USBINTR] = 0x008UL, - [OP_DEVICEADDR] = 0x014UL, - [OP_ENDPTLISTADDR] = 0x018UL, - [OP_PORTSC] = 0x044UL, - [OP_DEVLC] = 0x084UL, - [OP_USBMODE] = 0x0C8UL, - [OP_ENDPTSETUPSTAT] = 0x0D8UL, - [OP_ENDPTPRIME] = 0x0DCUL, - [OP_ENDPTFLUSH] = 0x0E0UL, - [OP_ENDPTSTAT] = 0x0E4UL, - [OP_ENDPTCOMPLETE] = 0x0E8UL, - [OP_ENDPTCTRL] = 0x0ECUL, -}; - -static int hw_alloc_regmap(struct ci13xxx *udc, bool is_lpm) -{ - int i; - - kfree(udc->hw_bank.regmap); - - udc->hw_bank.regmap = kzalloc((OP_LAST + 1) * sizeof(void *), - GFP_KERNEL); - if (!udc->hw_bank.regmap) - return -ENOMEM; - - for (i = 0; i < OP_ENDPTCTRL; i++) - udc->hw_bank.regmap[i] = - (i <= CAP_LAST ? udc->hw_bank.cap : udc->hw_bank.op) + - (is_lpm ? ci_regs_lpm[i] : ci_regs_nolpm[i]); - - for (; i <= OP_LAST; i++) - udc->hw_bank.regmap[i] = udc->hw_bank.op + - 4 * (i - OP_ENDPTCTRL) + - (is_lpm - ? ci_regs_lpm[OP_ENDPTCTRL] - : ci_regs_nolpm[OP_ENDPTCTRL]); - - return 0; -} - -/** - * hw_ep_bit: calculates the bit number - * @num: endpoint number - * @dir: endpoint direction - * - * This function returns bit number - */ -static inline int hw_ep_bit(int num, int dir) -{ - return num + (dir ? 16 : 0); -} - -static int ep_to_bit(struct ci13xxx *udc, int n) -{ - int fill = 16 - udc->hw_ep_max / 2; - - if (n >= udc->hw_ep_max / 2) - n += fill; - - return n; -} - -/** - * hw_read: reads from a hw register - * @reg: register index - * @mask: bitfield mask - * - * This function returns register contents - */ -static u32 hw_read(struct ci13xxx *udc, enum ci13xxx_regs reg, u32 mask) -{ - return ioread32(udc->hw_bank.regmap[reg]) & mask; -} - -/** - * hw_write: writes to a hw register - * @reg: register index - * @mask: bitfield mask - * @data: new value - */ -static void hw_write(struct ci13xxx *udc, enum ci13xxx_regs reg, u32 mask, - u32 data) -{ - if (~mask) - data = (ioread32(udc->hw_bank.regmap[reg]) & ~mask) - | (data & mask); - - iowrite32(data, udc->hw_bank.regmap[reg]); -} - -/** - * hw_test_and_clear: tests & clears a hw register - * @reg: register index - * @mask: bitfield mask - * - * This function returns register contents - */ -static u32 hw_test_and_clear(struct ci13xxx *udc, enum ci13xxx_regs reg, - u32 mask) -{ - u32 val = ioread32(udc->hw_bank.regmap[reg]) & mask; - - iowrite32(val, udc->hw_bank.regmap[reg]); - return val; -} - -/** - * hw_test_and_write: tests & writes a hw register - * @reg: register index - * @mask: bitfield mask - * @data: new value - * - * This function returns register contents - */ -static u32 hw_test_and_write(struct ci13xxx *udc, enum ci13xxx_regs reg, - u32 mask, u32 data) -{ - u32 val = hw_read(udc, reg, ~0); - - hw_write(udc, reg, mask, data); - return (val & mask) >> ffs_nr(mask); -} - -static int hw_device_init(struct ci13xxx *udc, void __iomem *base, - uintptr_t cap_offset) -{ - u32 reg; - - /* bank is a module variable */ - udc->hw_bank.abs = base; - - udc->hw_bank.cap = udc->hw_bank.abs; - udc->hw_bank.cap += cap_offset; - udc->hw_bank.op = udc->hw_bank.cap + ioread8(udc->hw_bank.cap); - - hw_alloc_regmap(udc, false); - reg = hw_read(udc, CAP_HCCPARAMS, HCCPARAMS_LEN) >> - ffs_nr(HCCPARAMS_LEN); - udc->hw_bank.lpm = reg; - hw_alloc_regmap(udc, !!reg); - udc->hw_bank.size = udc->hw_bank.op - udc->hw_bank.abs; - udc->hw_bank.size += OP_LAST; - udc->hw_bank.size /= sizeof(u32); - - reg = hw_read(udc, CAP_DCCPARAMS, DCCPARAMS_DEN) >> - ffs_nr(DCCPARAMS_DEN); - udc->hw_ep_max = reg * 2; /* cache hw ENDPT_MAX */ - - if (udc->hw_ep_max == 0 || udc->hw_ep_max > ENDPT_MAX) - return -ENODEV; - - dev_dbg(udc->dev, "ChipIdea UDC found, lpm: %d; cap: %p op: %p\n", - udc->hw_bank.lpm, udc->hw_bank.cap, udc->hw_bank.op); - - /* setup lock mode ? */ - - /* ENDPTSETUPSTAT is '0' by default */ - - /* HCSPARAMS.bf.ppc SHOULD BE zero for device */ - - return 0; -} -/** - * hw_device_reset: resets chip (execute without interruption) - * @base: register base address - * - * This function returns an error code - */ -static int hw_device_reset(struct ci13xxx *udc) -{ - /* should flush & stop before reset */ - hw_write(udc, OP_ENDPTFLUSH, ~0, ~0); - hw_write(udc, OP_USBCMD, USBCMD_RS, 0); - - hw_write(udc, OP_USBCMD, USBCMD_RST, USBCMD_RST); - while (hw_read(udc, OP_USBCMD, USBCMD_RST)) - udelay(10); /* not RTOS friendly */ - - - if (udc->udc_driver->notify_event) - udc->udc_driver->notify_event(udc, - CI13XXX_CONTROLLER_RESET_EVENT); - - if (udc->udc_driver->flags & CI13XXX_DISABLE_STREAMING) - hw_write(udc, OP_USBMODE, USBMODE_SDIS, USBMODE_SDIS); - - /* USBMODE should be configured step by step */ - hw_write(udc, OP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE); - hw_write(udc, OP_USBMODE, USBMODE_CM, USBMODE_CM_DEVICE); - /* HW >= 2.3 */ - hw_write(udc, OP_USBMODE, USBMODE_SLOM, USBMODE_SLOM); - - if (hw_read(udc, OP_USBMODE, USBMODE_CM) != USBMODE_CM_DEVICE) { - pr_err("cannot enter in device mode"); - pr_err("lpm = %i", udc->hw_bank.lpm); - return -ENODEV; - } - - return 0; -} - -/** - * hw_device_state: enables/disables interrupts & starts/stops device (execute - * without interruption) - * @dma: 0 => disable, !0 => enable and set dma engine - * - * This function returns an error code - */ -static int hw_device_state(struct ci13xxx *udc, u32 dma) -{ - if (dma) { - hw_write(udc, OP_ENDPTLISTADDR, ~0, dma); - /* interrupt, error, port change, reset, sleep/suspend */ - hw_write(udc, OP_USBINTR, ~0, - USBi_UI|USBi_UEI|USBi_PCI|USBi_URI|USBi_SLI); - hw_write(udc, OP_USBCMD, USBCMD_RS, USBCMD_RS); - } else { - hw_write(udc, OP_USBCMD, USBCMD_RS, 0); - hw_write(udc, OP_USBINTR, ~0, 0); - } - return 0; -} - -/** - * hw_ep_flush: flush endpoint fifo (execute without interruption) - * @num: endpoint number - * @dir: endpoint direction - * - * This function returns an error code - */ -static int hw_ep_flush(struct ci13xxx *udc, int num, int dir) -{ - int n = hw_ep_bit(num, dir); - - do { - /* flush any pending transfer */ - hw_write(udc, OP_ENDPTFLUSH, BIT(n), BIT(n)); - while (hw_read(udc, OP_ENDPTFLUSH, BIT(n))) - cpu_relax(); - } while (hw_read(udc, OP_ENDPTSTAT, BIT(n))); - - return 0; -} - -/** - * hw_ep_disable: disables endpoint (execute without interruption) - * @num: endpoint number - * @dir: endpoint direction - * - * This function returns an error code - */ -static int hw_ep_disable(struct ci13xxx *udc, int num, int dir) -{ - hw_ep_flush(udc, num, dir); - hw_write(udc, OP_ENDPTCTRL + num, - dir ? ENDPTCTRL_TXE : ENDPTCTRL_RXE, 0); - return 0; -} - -/** - * hw_ep_enable: enables endpoint (execute without interruption) - * @num: endpoint number - * @dir: endpoint direction - * @type: endpoint type - * - * This function returns an error code - */ -static int hw_ep_enable(struct ci13xxx *udc, int num, int dir, int type) -{ - u32 mask, data; - - if (dir) { - mask = ENDPTCTRL_TXT; /* type */ - data = type << ffs_nr(mask); - - mask |= ENDPTCTRL_TXS; /* unstall */ - mask |= ENDPTCTRL_TXR; /* reset data toggle */ - data |= ENDPTCTRL_TXR; - mask |= ENDPTCTRL_TXE; /* enable */ - data |= ENDPTCTRL_TXE; - } else { - mask = ENDPTCTRL_RXT; /* type */ - data = type << ffs_nr(mask); - - mask |= ENDPTCTRL_RXS; /* unstall */ - mask |= ENDPTCTRL_RXR; /* reset data toggle */ - data |= ENDPTCTRL_RXR; - mask |= ENDPTCTRL_RXE; /* enable */ - data |= ENDPTCTRL_RXE; - } - hw_write(udc, OP_ENDPTCTRL + num, mask, data); - return 0; -} - -/** - * hw_ep_get_halt: return endpoint halt status - * @num: endpoint number - * @dir: endpoint direction - * - * This function returns 1 if endpoint halted - */ -static int hw_ep_get_halt(struct ci13xxx *udc, int num, int dir) -{ - u32 mask = dir ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; - - return hw_read(udc, OP_ENDPTCTRL + num, mask) ? 1 : 0; -} - -/** - * hw_test_and_clear_setup_status: test & clear setup status (execute without - * interruption) - * @n: endpoint number - * - * This function returns setup status - */ -static int hw_test_and_clear_setup_status(struct ci13xxx *udc, int n) -{ - n = ep_to_bit(udc, n); - return hw_test_and_clear(udc, OP_ENDPTSETUPSTAT, BIT(n)); -} - -/** - * hw_ep_prime: primes endpoint (execute without interruption) - * @num: endpoint number - * @dir: endpoint direction - * @is_ctrl: true if control endpoint - * - * This function returns an error code - */ -static int hw_ep_prime(struct ci13xxx *udc, int num, int dir, int is_ctrl) -{ - int n = hw_ep_bit(num, dir); - - if (is_ctrl && dir == RX && hw_read(udc, OP_ENDPTSETUPSTAT, BIT(num))) - return -EAGAIN; - - hw_write(udc, OP_ENDPTPRIME, BIT(n), BIT(n)); - - while (hw_read(udc, OP_ENDPTPRIME, BIT(n))) - cpu_relax(); - if (is_ctrl && dir == RX && hw_read(udc, OP_ENDPTSETUPSTAT, BIT(num))) - return -EAGAIN; - - /* status shoult be tested according with manual but it doesn't work */ - return 0; -} - -/** - * hw_ep_set_halt: configures ep halt & resets data toggle after clear (execute - * without interruption) - * @num: endpoint number - * @dir: endpoint direction - * @value: true => stall, false => unstall - * - * This function returns an error code - */ -static int hw_ep_set_halt(struct ci13xxx *udc, int num, int dir, int value) -{ - if (value != 0 && value != 1) - return -EINVAL; - - do { - enum ci13xxx_regs reg = OP_ENDPTCTRL + num; - u32 mask_xs = dir ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; - u32 mask_xr = dir ? ENDPTCTRL_TXR : ENDPTCTRL_RXR; - - /* data toggle - reserved for EP0 but it's in ESS */ - hw_write(udc, reg, mask_xs|mask_xr, - value ? mask_xs : mask_xr); - } while (value != hw_ep_get_halt(udc, num, dir)); - - return 0; -} - -/** - * hw_intr_clear: disables interrupt & clears interrupt status (execute without - * interruption) - * @n: interrupt bit - * - * This function returns an error code - */ -static int hw_intr_clear(struct ci13xxx *udc, int n) -{ - if (n >= REG_BITS) - return -EINVAL; - - hw_write(udc, OP_USBINTR, BIT(n), 0); - hw_write(udc, OP_USBSTS, BIT(n), BIT(n)); - return 0; -} - -/** - * hw_intr_force: enables interrupt & forces interrupt status (execute without - * interruption) - * @n: interrupt bit - * - * This function returns an error code - */ -static int hw_intr_force(struct ci13xxx *udc, int n) -{ - if (n >= REG_BITS) - return -EINVAL; - - hw_write(udc, CAP_TESTMODE, TESTMODE_FORCE, TESTMODE_FORCE); - hw_write(udc, OP_USBINTR, BIT(n), BIT(n)); - hw_write(udc, OP_USBSTS, BIT(n), BIT(n)); - hw_write(udc, CAP_TESTMODE, TESTMODE_FORCE, 0); - return 0; -} - -/** - * hw_is_port_high_speed: test if port is high speed - * - * This function returns true if high speed port - */ -static int hw_port_is_high_speed(struct ci13xxx *udc) -{ - return udc->hw_bank.lpm ? hw_read(udc, OP_DEVLC, DEVLC_PSPD) : - hw_read(udc, OP_PORTSC, PORTSC_HSP); -} - -/** - * hw_port_test_get: reads port test mode value - * - * This function returns port test mode value - */ -static u8 hw_port_test_get(struct ci13xxx *udc) -{ - return hw_read(udc, OP_PORTSC, PORTSC_PTC) >> ffs_nr(PORTSC_PTC); -} - -/** - * hw_port_test_set: writes port test mode (execute without interruption) - * @mode: new value - * - * This function returns an error code - */ -static int hw_port_test_set(struct ci13xxx *udc, u8 mode) -{ - const u8 TEST_MODE_MAX = 7; - - if (mode > TEST_MODE_MAX) - return -EINVAL; - - hw_write(udc, OP_PORTSC, PORTSC_PTC, mode << ffs_nr(PORTSC_PTC)); - return 0; -} - -/** - * hw_read_intr_enable: returns interrupt enable register - * - * This function returns register data - */ -static u32 hw_read_intr_enable(struct ci13xxx *udc) -{ - return hw_read(udc, OP_USBINTR, ~0); -} - -/** - * hw_read_intr_status: returns interrupt status register - * - * This function returns register data - */ -static u32 hw_read_intr_status(struct ci13xxx *udc) -{ - return hw_read(udc, OP_USBSTS, ~0); -} - -/** - * hw_register_read: reads all device registers (execute without interruption) - * @buf: destination buffer - * @size: buffer size - * - * This function returns number of registers read - */ -static size_t hw_register_read(struct ci13xxx *udc, u32 *buf, size_t size) -{ - unsigned i; - - if (size > udc->hw_bank.size) - size = udc->hw_bank.size; - - for (i = 0; i < size; i++) - buf[i] = hw_read(udc, i * sizeof(u32), ~0); - - return size; -} - -/** - * hw_register_write: writes to register - * @addr: register address - * @data: register value - * - * This function returns an error code - */ -static int hw_register_write(struct ci13xxx *udc, u16 addr, u32 data) -{ - /* align */ - addr /= sizeof(u32); - - if (addr >= udc->hw_bank.size) - return -EINVAL; - - /* align */ - addr *= sizeof(u32); - - hw_write(udc, addr, ~0, data); - return 0; -} - -/** - * hw_test_and_clear_complete: test & clear complete status (execute without - * interruption) - * @n: endpoint number - * - * This function returns complete status - */ -static int hw_test_and_clear_complete(struct ci13xxx *udc, int n) -{ - n = ep_to_bit(udc, n); - return hw_test_and_clear(udc, OP_ENDPTCOMPLETE, BIT(n)); -} - -/** - * hw_test_and_clear_intr_active: test & clear active interrupts (execute - * without interruption) - * - * This function returns active interrutps - */ -static u32 hw_test_and_clear_intr_active(struct ci13xxx *udc) -{ - u32 reg = hw_read_intr_status(udc) & hw_read_intr_enable(udc); - - hw_write(udc, OP_USBSTS, ~0, reg); - return reg; -} - -/** - * hw_test_and_clear_setup_guard: test & clear setup guard (execute without - * interruption) - * - * This function returns guard value - */ -static int hw_test_and_clear_setup_guard(struct ci13xxx *udc) -{ - return hw_test_and_write(udc, OP_USBCMD, USBCMD_SUTW, 0); -} - -/** - * hw_test_and_set_setup_guard: test & set setup guard (execute without - * interruption) - * - * This function returns guard value - */ -static int hw_test_and_set_setup_guard(struct ci13xxx *udc) -{ - return hw_test_and_write(udc, OP_USBCMD, USBCMD_SUTW, USBCMD_SUTW); -} - -/** - * hw_usb_set_address: configures USB address (execute without interruption) - * @value: new USB address - * - * This function explicitly sets the address, without the "USBADRA" (advance) - * feature, which is not supported by older versions of the controller. - */ -static void hw_usb_set_address(struct ci13xxx *udc, u8 value) -{ - hw_write(udc, OP_DEVICEADDR, DEVICEADDR_USBADR, - value << ffs_nr(DEVICEADDR_USBADR)); -} - -/** - * hw_usb_reset: restart device after a bus reset (execute without - * interruption) - * - * This function returns an error code - */ -static int hw_usb_reset(struct ci13xxx *udc) -{ - hw_usb_set_address(udc, 0); - - /* ESS flushes only at end?!? */ - hw_write(udc, OP_ENDPTFLUSH, ~0, ~0); - - /* clear setup token semaphores */ - hw_write(udc, OP_ENDPTSETUPSTAT, 0, 0); - - /* clear complete status */ - hw_write(udc, OP_ENDPTCOMPLETE, 0, 0); - - /* wait until all bits cleared */ - while (hw_read(udc, OP_ENDPTPRIME, ~0)) - udelay(10); /* not RTOS friendly */ - - /* reset all endpoints ? */ - - /* reset internal status and wait for further instructions - no need to verify the port reset status (ESS does it) */ - - return 0; -} - -/****************************************************************************** - * DBG block - *****************************************************************************/ -/** - * show_device: prints information about device capabilities and status - * - * Check "device.h" for details - */ -static ssize_t show_device(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); - struct usb_gadget *gadget = &udc->gadget; - int n = 0; - - if (attr == NULL || buf == NULL) { - dev_err(udc->dev, "[%s] EINVAL\n", __func__); - return 0; - } - - n += scnprintf(buf + n, PAGE_SIZE - n, "speed = %d\n", - gadget->speed); - n += scnprintf(buf + n, PAGE_SIZE - n, "max_speed = %d\n", - gadget->max_speed); - /* TODO: Scheduled for removal in 3.8. */ - n += scnprintf(buf + n, PAGE_SIZE - n, "is_dualspeed = %d\n", - gadget_is_dualspeed(gadget)); - n += scnprintf(buf + n, PAGE_SIZE - n, "is_otg = %d\n", - gadget->is_otg); - n += scnprintf(buf + n, PAGE_SIZE - n, "is_a_peripheral = %d\n", - gadget->is_a_peripheral); - n += scnprintf(buf + n, PAGE_SIZE - n, "b_hnp_enable = %d\n", - gadget->b_hnp_enable); - n += scnprintf(buf + n, PAGE_SIZE - n, "a_hnp_support = %d\n", - gadget->a_hnp_support); - n += scnprintf(buf + n, PAGE_SIZE - n, "a_alt_hnp_support = %d\n", - gadget->a_alt_hnp_support); - n += scnprintf(buf + n, PAGE_SIZE - n, "name = %s\n", - (gadget->name ? gadget->name : "")); - - return n; -} -static DEVICE_ATTR(device, S_IRUSR, show_device, NULL); - -/** - * show_driver: prints information about attached gadget (if any) - * - * Check "device.h" for details - */ -static ssize_t show_driver(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); - struct usb_gadget_driver *driver = udc->driver; - int n = 0; - - if (attr == NULL || buf == NULL) { - dev_err(dev, "[%s] EINVAL\n", __func__); - return 0; - } - - if (driver == NULL) - return scnprintf(buf, PAGE_SIZE, - "There is no gadget attached!\n"); - - n += scnprintf(buf + n, PAGE_SIZE - n, "function = %s\n", - (driver->function ? driver->function : "")); - n += scnprintf(buf + n, PAGE_SIZE - n, "max speed = %d\n", - driver->max_speed); - - return n; -} -static DEVICE_ATTR(driver, S_IRUSR, show_driver, NULL); - -/* Maximum event message length */ -#define DBG_DATA_MSG 64UL - -/* Maximum event messages */ -#define DBG_DATA_MAX 128UL - -/* Event buffer descriptor */ -static struct { - char (buf[DBG_DATA_MAX])[DBG_DATA_MSG]; /* buffer */ - unsigned idx; /* index */ - unsigned tty; /* print to console? */ - rwlock_t lck; /* lock */ -} dbg_data = { - .idx = 0, - .tty = 0, - .lck = __RW_LOCK_UNLOCKED(lck) -}; - -/** - * dbg_dec: decrements debug event index - * @idx: buffer index - */ -static void dbg_dec(unsigned *idx) -{ - *idx = (*idx - 1) & (DBG_DATA_MAX-1); -} - -/** - * dbg_inc: increments debug event index - * @idx: buffer index - */ -static void dbg_inc(unsigned *idx) -{ - *idx = (*idx + 1) & (DBG_DATA_MAX-1); -} - -/** - * dbg_print: prints the common part of the event - * @addr: endpoint address - * @name: event name - * @status: status - * @extra: extra information - */ -static void dbg_print(u8 addr, const char *name, int status, const char *extra) -{ - struct timeval tval; - unsigned int stamp; - unsigned long flags; - - write_lock_irqsave(&dbg_data.lck, flags); - - do_gettimeofday(&tval); - stamp = tval.tv_sec & 0xFFFF; /* 2^32 = 4294967296. Limit to 4096s */ - stamp = stamp * 1000000 + tval.tv_usec; - - scnprintf(dbg_data.buf[dbg_data.idx], DBG_DATA_MSG, - "%04X\t? %02X %-7.7s %4i ?\t%s\n", - stamp, addr, name, status, extra); - - dbg_inc(&dbg_data.idx); - - write_unlock_irqrestore(&dbg_data.lck, flags); - - if (dbg_data.tty != 0) - pr_notice("%04X\t? %02X %-7.7s %4i ?\t%s\n", - stamp, addr, name, status, extra); -} - -/** - * dbg_done: prints a DONE event - * @addr: endpoint address - * @td: transfer descriptor - * @status: status - */ -static void dbg_done(u8 addr, const u32 token, int status) -{ - char msg[DBG_DATA_MSG]; - - scnprintf(msg, sizeof(msg), "%d %02X", - (int)(token & TD_TOTAL_BYTES) >> ffs_nr(TD_TOTAL_BYTES), - (int)(token & TD_STATUS) >> ffs_nr(TD_STATUS)); - dbg_print(addr, "DONE", status, msg); -} - -/** - * dbg_event: prints a generic event - * @addr: endpoint address - * @name: event name - * @status: status - */ -static void dbg_event(u8 addr, const char *name, int status) -{ - if (name != NULL) - dbg_print(addr, name, status, ""); -} - -/* - * dbg_queue: prints a QUEUE event - * @addr: endpoint address - * @req: USB request - * @status: status - */ -static void dbg_queue(u8 addr, const struct usb_request *req, int status) -{ - char msg[DBG_DATA_MSG]; - - if (req != NULL) { - scnprintf(msg, sizeof(msg), - "%d %d", !req->no_interrupt, req->length); - dbg_print(addr, "QUEUE", status, msg); - } -} - -/** - * dbg_setup: prints a SETUP event - * @addr: endpoint address - * @req: setup request - */ -static void dbg_setup(u8 addr, const struct usb_ctrlrequest *req) -{ - char msg[DBG_DATA_MSG]; - - if (req != NULL) { - scnprintf(msg, sizeof(msg), - "%02X %02X %04X %04X %d", req->bRequestType, - req->bRequest, le16_to_cpu(req->wValue), - le16_to_cpu(req->wIndex), le16_to_cpu(req->wLength)); - dbg_print(addr, "SETUP", 0, msg); - } -} - -/** - * show_events: displays the event buffer - * - * Check "device.h" for details - */ -static ssize_t show_events(struct device *dev, struct device_attribute *attr, - char *buf) -{ - unsigned long flags; - unsigned i, j, n = 0; - - if (attr == NULL || buf == NULL) { - dev_err(dev->parent, "[%s] EINVAL\n", __func__); - return 0; - } - - read_lock_irqsave(&dbg_data.lck, flags); - - i = dbg_data.idx; - for (dbg_dec(&i); i != dbg_data.idx; dbg_dec(&i)) { - n += strlen(dbg_data.buf[i]); - if (n >= PAGE_SIZE) { - n -= strlen(dbg_data.buf[i]); - break; - } - } - for (j = 0, dbg_inc(&i); j < n; dbg_inc(&i)) - j += scnprintf(buf + j, PAGE_SIZE - j, - "%s", dbg_data.buf[i]); - - read_unlock_irqrestore(&dbg_data.lck, flags); - - return n; -} - -/** - * store_events: configure if events are going to be also printed to console - * - * Check "device.h" for details - */ -static ssize_t store_events(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - unsigned tty; - - if (attr == NULL || buf == NULL) { - dev_err(dev, "[%s] EINVAL\n", __func__); - goto done; - } - - if (sscanf(buf, "%u", &tty) != 1 || tty > 1) { - dev_err(dev, "<1|0>: enable|disable console log\n"); - goto done; - } - - dbg_data.tty = tty; - dev_info(dev, "tty = %u", dbg_data.tty); - - done: - return count; -} -static DEVICE_ATTR(events, S_IRUSR | S_IWUSR, show_events, store_events); - -/** - * show_inters: interrupt status, enable status and historic - * - * Check "device.h" for details - */ -static ssize_t show_inters(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); - unsigned long flags; - u32 intr; - unsigned i, j, n = 0; - - if (attr == NULL || buf == NULL) { - dev_err(udc->dev, "[%s] EINVAL\n", __func__); - return 0; - } - - spin_lock_irqsave(&udc->lock, flags); - - n += scnprintf(buf + n, PAGE_SIZE - n, - "status = %08x\n", hw_read_intr_status(udc)); - n += scnprintf(buf + n, PAGE_SIZE - n, - "enable = %08x\n", hw_read_intr_enable(udc)); - - n += scnprintf(buf + n, PAGE_SIZE - n, "*test = %d\n", - isr_statistics.test); - n += scnprintf(buf + n, PAGE_SIZE - n, "? ui = %d\n", - isr_statistics.ui); - n += scnprintf(buf + n, PAGE_SIZE - n, "? uei = %d\n", - isr_statistics.uei); - n += scnprintf(buf + n, PAGE_SIZE - n, "? pci = %d\n", - isr_statistics.pci); - n += scnprintf(buf + n, PAGE_SIZE - n, "? uri = %d\n", - isr_statistics.uri); - n += scnprintf(buf + n, PAGE_SIZE - n, "? sli = %d\n", - isr_statistics.sli); - n += scnprintf(buf + n, PAGE_SIZE - n, "*none = %d\n", - isr_statistics.none); - n += scnprintf(buf + n, PAGE_SIZE - n, "*hndl = %d\n", - isr_statistics.hndl.cnt); - - for (i = isr_statistics.hndl.idx, j = 0; j <= ISR_MASK; j++, i++) { - i &= ISR_MASK; - intr = isr_statistics.hndl.buf[i]; - - if (USBi_UI & intr) - n += scnprintf(buf + n, PAGE_SIZE - n, "ui "); - intr &= ~USBi_UI; - if (USBi_UEI & intr) - n += scnprintf(buf + n, PAGE_SIZE - n, "uei "); - intr &= ~USBi_UEI; - if (USBi_PCI & intr) - n += scnprintf(buf + n, PAGE_SIZE - n, "pci "); - intr &= ~USBi_PCI; - if (USBi_URI & intr) - n += scnprintf(buf + n, PAGE_SIZE - n, "uri "); - intr &= ~USBi_URI; - if (USBi_SLI & intr) - n += scnprintf(buf + n, PAGE_SIZE - n, "sli "); - intr &= ~USBi_SLI; - if (intr) - n += scnprintf(buf + n, PAGE_SIZE - n, "??? "); - if (isr_statistics.hndl.buf[i]) - n += scnprintf(buf + n, PAGE_SIZE - n, "\n"); - } - - spin_unlock_irqrestore(&udc->lock, flags); - - return n; -} - -/** - * store_inters: enable & force or disable an individual interrutps - * (to be used for test purposes only) - * - * Check "device.h" for details - */ -static ssize_t store_inters(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); - unsigned long flags; - unsigned en, bit; - - if (attr == NULL || buf == NULL) { - dev_err(udc->dev, "EINVAL\n"); - goto done; - } - - if (sscanf(buf, "%u %u", &en, &bit) != 2 || en > 1) { - dev_err(udc->dev, "<1|0> : enable|disable interrupt\n"); - goto done; - } - - spin_lock_irqsave(&udc->lock, flags); - if (en) { - if (hw_intr_force(udc, bit)) - dev_err(dev, "invalid bit number\n"); - else - isr_statistics.test++; - } else { - if (hw_intr_clear(udc, bit)) - dev_err(dev, "invalid bit number\n"); - } - spin_unlock_irqrestore(&udc->lock, flags); - - done: - return count; -} -static DEVICE_ATTR(inters, S_IRUSR | S_IWUSR, show_inters, store_inters); - -/** - * show_port_test: reads port test mode - * - * Check "device.h" for details - */ -static ssize_t show_port_test(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); - unsigned long flags; - unsigned mode; - - if (attr == NULL || buf == NULL) { - dev_err(udc->dev, "EINVAL\n"); - return 0; - } - - spin_lock_irqsave(&udc->lock, flags); - mode = hw_port_test_get(udc); - spin_unlock_irqrestore(&udc->lock, flags); - - return scnprintf(buf, PAGE_SIZE, "mode = %u\n", mode); -} - -/** - * store_port_test: writes port test mode - * - * Check "device.h" for details - */ -static ssize_t store_port_test(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); - unsigned long flags; - unsigned mode; - - if (attr == NULL || buf == NULL) { - dev_err(udc->dev, "[%s] EINVAL\n", __func__); - goto done; - } - - if (sscanf(buf, "%u", &mode) != 1) { - dev_err(udc->dev, ": set port test mode"); - goto done; - } - - spin_lock_irqsave(&udc->lock, flags); - if (hw_port_test_set(udc, mode)) - dev_err(udc->dev, "invalid mode\n"); - spin_unlock_irqrestore(&udc->lock, flags); - - done: - return count; -} -static DEVICE_ATTR(port_test, S_IRUSR | S_IWUSR, - show_port_test, store_port_test); - -/** - * show_qheads: DMA contents of all queue heads - * - * Check "device.h" for details - */ -static ssize_t show_qheads(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); - unsigned long flags; - unsigned i, j, n = 0; - - if (attr == NULL || buf == NULL) { - dev_err(udc->dev, "[%s] EINVAL\n", __func__); - return 0; - } - - spin_lock_irqsave(&udc->lock, flags); - for (i = 0; i < udc->hw_ep_max/2; i++) { - struct ci13xxx_ep *mEpRx = &udc->ci13xxx_ep[i]; - struct ci13xxx_ep *mEpTx = - &udc->ci13xxx_ep[i + udc->hw_ep_max/2]; - n += scnprintf(buf + n, PAGE_SIZE - n, - "EP=%02i: RX=%08X TX=%08X\n", - i, (u32)mEpRx->qh.dma, (u32)mEpTx->qh.dma); - for (j = 0; j < (sizeof(struct ci13xxx_qh)/sizeof(u32)); j++) { - n += scnprintf(buf + n, PAGE_SIZE - n, - " %04X: %08X %08X\n", j, - *((u32 *)mEpRx->qh.ptr + j), - *((u32 *)mEpTx->qh.ptr + j)); - } - } - spin_unlock_irqrestore(&udc->lock, flags); - - return n; -} -static DEVICE_ATTR(qheads, S_IRUSR, show_qheads, NULL); - -/** - * show_registers: dumps all registers - * - * Check "device.h" for details - */ -#define DUMP_ENTRIES 512 -static ssize_t show_registers(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); - unsigned long flags; - u32 *dump; - unsigned i, k, n = 0; - - if (attr == NULL || buf == NULL) { - dev_err(udc->dev, "[%s] EINVAL\n", __func__); - return 0; - } - - dump = kmalloc(sizeof(u32) * DUMP_ENTRIES, GFP_KERNEL); - if (!dump) { - dev_err(udc->dev, "%s: out of memory\n", __func__); - return 0; - } - - spin_lock_irqsave(&udc->lock, flags); - k = hw_register_read(udc, dump, DUMP_ENTRIES); - spin_unlock_irqrestore(&udc->lock, flags); - - for (i = 0; i < k; i++) { - n += scnprintf(buf + n, PAGE_SIZE - n, - "reg[0x%04X] = 0x%08X\n", - i * (unsigned)sizeof(u32), dump[i]); - } - kfree(dump); - - return n; -} - -/** - * store_registers: writes value to register address - * - * Check "device.h" for details - */ -static ssize_t store_registers(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); - unsigned long addr, data, flags; - - if (attr == NULL || buf == NULL) { - dev_err(udc->dev, "[%s] EINVAL\n", __func__); - goto done; - } - - if (sscanf(buf, "%li %li", &addr, &data) != 2) { - dev_err(udc->dev, - " : write data to register address\n"); - goto done; - } - - spin_lock_irqsave(&udc->lock, flags); - if (hw_register_write(udc, addr, data)) - dev_err(udc->dev, "invalid address range\n"); - spin_unlock_irqrestore(&udc->lock, flags); - - done: - return count; -} -static DEVICE_ATTR(registers, S_IRUSR | S_IWUSR, - show_registers, store_registers); - -/** - * show_requests: DMA contents of all requests currently queued (all endpts) - * - * Check "device.h" for details - */ -static ssize_t show_requests(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); - unsigned long flags; - struct list_head *ptr = NULL; - struct ci13xxx_req *req = NULL; - unsigned i, j, n = 0, qSize = sizeof(struct ci13xxx_td)/sizeof(u32); - - if (attr == NULL || buf == NULL) { - dev_err(udc->dev, "[%s] EINVAL\n", __func__); - return 0; - } - - spin_lock_irqsave(&udc->lock, flags); - for (i = 0; i < udc->hw_ep_max; i++) - list_for_each(ptr, &udc->ci13xxx_ep[i].qh.queue) - { - req = list_entry(ptr, struct ci13xxx_req, queue); - - n += scnprintf(buf + n, PAGE_SIZE - n, - "EP=%02i: TD=%08X %s\n", - i % udc->hw_ep_max/2, (u32)req->dma, - ((i < udc->hw_ep_max/2) ? "RX" : "TX")); - - for (j = 0; j < qSize; j++) - n += scnprintf(buf + n, PAGE_SIZE - n, - " %04X: %08X\n", j, - *((u32 *)req->ptr + j)); - } - spin_unlock_irqrestore(&udc->lock, flags); - - return n; -} -static DEVICE_ATTR(requests, S_IRUSR, show_requests, NULL); - -/** - * dbg_create_files: initializes the attribute interface - * @dev: device - * - * This function returns an error code - */ -__maybe_unused static int dbg_create_files(struct device *dev) -{ - int retval = 0; - - if (dev == NULL) - return -EINVAL; - retval = device_create_file(dev, &dev_attr_device); - if (retval) - goto done; - retval = device_create_file(dev, &dev_attr_driver); - if (retval) - goto rm_device; - retval = device_create_file(dev, &dev_attr_events); - if (retval) - goto rm_driver; - retval = device_create_file(dev, &dev_attr_inters); - if (retval) - goto rm_events; - retval = device_create_file(dev, &dev_attr_port_test); - if (retval) - goto rm_inters; - retval = device_create_file(dev, &dev_attr_qheads); - if (retval) - goto rm_port_test; - retval = device_create_file(dev, &dev_attr_registers); - if (retval) - goto rm_qheads; - retval = device_create_file(dev, &dev_attr_requests); - if (retval) - goto rm_registers; - return 0; - - rm_registers: - device_remove_file(dev, &dev_attr_registers); - rm_qheads: - device_remove_file(dev, &dev_attr_qheads); - rm_port_test: - device_remove_file(dev, &dev_attr_port_test); - rm_inters: - device_remove_file(dev, &dev_attr_inters); - rm_events: - device_remove_file(dev, &dev_attr_events); - rm_driver: - device_remove_file(dev, &dev_attr_driver); - rm_device: - device_remove_file(dev, &dev_attr_device); - done: - return retval; -} - -/** - * dbg_remove_files: destroys the attribute interface - * @dev: device - * - * This function returns an error code - */ -__maybe_unused static int dbg_remove_files(struct device *dev) -{ - if (dev == NULL) - return -EINVAL; - device_remove_file(dev, &dev_attr_requests); - device_remove_file(dev, &dev_attr_registers); - device_remove_file(dev, &dev_attr_qheads); - device_remove_file(dev, &dev_attr_port_test); - device_remove_file(dev, &dev_attr_inters); - device_remove_file(dev, &dev_attr_events); - device_remove_file(dev, &dev_attr_driver); - device_remove_file(dev, &dev_attr_device); - return 0; -} - -/****************************************************************************** - * UTIL block - *****************************************************************************/ -/** - * _usb_addr: calculates endpoint address from direction & number - * @ep: endpoint - */ -static inline u8 _usb_addr(struct ci13xxx_ep *ep) -{ - return ((ep->dir == TX) ? USB_ENDPOINT_DIR_MASK : 0) | ep->num; -} - -/** - * _hardware_queue: configures a request at hardware level - * @gadget: gadget - * @mEp: endpoint - * - * This function returns an error code - */ -static int _hardware_enqueue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq) -{ - struct ci13xxx *udc = mEp->udc; - unsigned i; - int ret = 0; - unsigned length = mReq->req.length; - - /* don't queue twice */ - if (mReq->req.status == -EALREADY) - return -EALREADY; - - mReq->req.status = -EALREADY; - if (length && mReq->req.dma == DMA_ADDR_INVALID) { - mReq->req.dma = \ - dma_map_single(mEp->device, mReq->req.buf, - length, mEp->dir ? DMA_TO_DEVICE : - DMA_FROM_DEVICE); - if (mReq->req.dma == 0) - return -ENOMEM; - - mReq->map = 1; - } - - if (mReq->req.zero && length && (length % mEp->ep.maxpacket == 0)) { - mReq->zptr = dma_pool_alloc(mEp->td_pool, GFP_ATOMIC, - &mReq->zdma); - if (mReq->zptr == NULL) { - if (mReq->map) { - dma_unmap_single(mEp->device, mReq->req.dma, - length, mEp->dir ? DMA_TO_DEVICE : - DMA_FROM_DEVICE); - mReq->req.dma = DMA_ADDR_INVALID; - mReq->map = 0; - } - return -ENOMEM; - } - memset(mReq->zptr, 0, sizeof(*mReq->zptr)); - mReq->zptr->next = TD_TERMINATE; - mReq->zptr->token = TD_STATUS_ACTIVE; - if (!mReq->req.no_interrupt) - mReq->zptr->token |= TD_IOC; - } - /* - * TD configuration - * TODO - handle requests which spawns into several TDs - */ - memset(mReq->ptr, 0, sizeof(*mReq->ptr)); - mReq->ptr->token = length << ffs_nr(TD_TOTAL_BYTES); - mReq->ptr->token &= TD_TOTAL_BYTES; - mReq->ptr->token |= TD_STATUS_ACTIVE; - if (mReq->zptr) { - mReq->ptr->next = mReq->zdma; - } else { - mReq->ptr->next = TD_TERMINATE; - if (!mReq->req.no_interrupt) - mReq->ptr->token |= TD_IOC; - } - mReq->ptr->page[0] = mReq->req.dma; - for (i = 1; i < 5; i++) - mReq->ptr->page[i] = - (mReq->req.dma + i * CI13XXX_PAGE_SIZE) & ~TD_RESERVED_MASK; - - if (!list_empty(&mEp->qh.queue)) { - struct ci13xxx_req *mReqPrev; - int n = hw_ep_bit(mEp->num, mEp->dir); - int tmp_stat; - - mReqPrev = list_entry(mEp->qh.queue.prev, - struct ci13xxx_req, queue); - if (mReqPrev->zptr) - mReqPrev->zptr->next = mReq->dma & TD_ADDR_MASK; - else - mReqPrev->ptr->next = mReq->dma & TD_ADDR_MASK; - wmb(); - if (hw_read(udc, OP_ENDPTPRIME, BIT(n))) - goto done; - do { - hw_write(udc, OP_USBCMD, USBCMD_ATDTW, USBCMD_ATDTW); - tmp_stat = hw_read(udc, OP_ENDPTSTAT, BIT(n)); - } while (!hw_read(udc, OP_USBCMD, USBCMD_ATDTW)); - hw_write(udc, OP_USBCMD, USBCMD_ATDTW, 0); - if (tmp_stat) - goto done; - } - - /* QH configuration */ - mEp->qh.ptr->td.next = mReq->dma; /* TERMINATE = 0 */ - mEp->qh.ptr->td.token &= ~TD_STATUS; /* clear status */ - mEp->qh.ptr->cap |= QH_ZLT; - - wmb(); /* synchronize before ep prime */ - - ret = hw_ep_prime(udc, mEp->num, mEp->dir, - mEp->type == USB_ENDPOINT_XFER_CONTROL); -done: - return ret; -} - -/** - * _hardware_dequeue: handles a request at hardware level - * @gadget: gadget - * @mEp: endpoint - * - * This function returns an error code - */ -static int _hardware_dequeue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq) -{ - if (mReq->req.status != -EALREADY) - return -EINVAL; - - if ((TD_STATUS_ACTIVE & mReq->ptr->token) != 0) - return -EBUSY; - - if (mReq->zptr) { - if ((TD_STATUS_ACTIVE & mReq->zptr->token) != 0) - return -EBUSY; - dma_pool_free(mEp->td_pool, mReq->zptr, mReq->zdma); - mReq->zptr = NULL; - } - - mReq->req.status = 0; - - if (mReq->map) { - dma_unmap_single(mEp->device, mReq->req.dma, mReq->req.length, - mEp->dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE); - mReq->req.dma = DMA_ADDR_INVALID; - mReq->map = 0; - } - - mReq->req.status = mReq->ptr->token & TD_STATUS; - if ((TD_STATUS_HALTED & mReq->req.status) != 0) - mReq->req.status = -1; - else if ((TD_STATUS_DT_ERR & mReq->req.status) != 0) - mReq->req.status = -1; - else if ((TD_STATUS_TR_ERR & mReq->req.status) != 0) - mReq->req.status = -1; - - mReq->req.actual = mReq->ptr->token & TD_TOTAL_BYTES; - mReq->req.actual >>= ffs_nr(TD_TOTAL_BYTES); - mReq->req.actual = mReq->req.length - mReq->req.actual; - mReq->req.actual = mReq->req.status ? 0 : mReq->req.actual; - - return mReq->req.actual; -} - -/** - * _ep_nuke: dequeues all endpoint requests - * @mEp: endpoint - * - * This function returns an error code - * Caller must hold lock - */ -static int _ep_nuke(struct ci13xxx_ep *mEp) -__releases(mEp->lock) -__acquires(mEp->lock) -{ - if (mEp == NULL) - return -EINVAL; - - hw_ep_flush(mEp->udc, mEp->num, mEp->dir); - - while (!list_empty(&mEp->qh.queue)) { - - /* pop oldest request */ - struct ci13xxx_req *mReq = \ - list_entry(mEp->qh.queue.next, - struct ci13xxx_req, queue); - list_del_init(&mReq->queue); - mReq->req.status = -ESHUTDOWN; - - if (mReq->req.complete != NULL) { - spin_unlock(mEp->lock); - mReq->req.complete(&mEp->ep, &mReq->req); - spin_lock(mEp->lock); - } - } - return 0; -} - -/** - * _gadget_stop_activity: stops all USB activity, flushes & disables all endpts - * @gadget: gadget - * - * This function returns an error code - */ -static int _gadget_stop_activity(struct usb_gadget *gadget) -{ - struct usb_ep *ep; - struct ci13xxx *udc = container_of(gadget, struct ci13xxx, gadget); - unsigned long flags; - - if (gadget == NULL) - return -EINVAL; - - spin_lock_irqsave(&udc->lock, flags); - udc->gadget.speed = USB_SPEED_UNKNOWN; - udc->remote_wakeup = 0; - udc->suspended = 0; - spin_unlock_irqrestore(&udc->lock, flags); - - /* flush all endpoints */ - gadget_for_each_ep(ep, gadget) { - usb_ep_fifo_flush(ep); - } - usb_ep_fifo_flush(&udc->ep0out->ep); - usb_ep_fifo_flush(&udc->ep0in->ep); - - if (udc->driver) - udc->driver->disconnect(gadget); - - /* make sure to disable all endpoints */ - gadget_for_each_ep(ep, gadget) { - usb_ep_disable(ep); - } - - if (udc->status != NULL) { - usb_ep_free_request(&udc->ep0in->ep, udc->status); - udc->status = NULL; - } - - return 0; -} - -/****************************************************************************** - * ISR block - *****************************************************************************/ -/** - * isr_reset_handler: USB reset interrupt handler - * @udc: UDC device - * - * This function resets USB engine after a bus reset occurred - */ -static void isr_reset_handler(struct ci13xxx *udc) -__releases(udc->lock) -__acquires(udc->lock) -{ - int retval; - - dbg_event(0xFF, "BUS RST", 0); - - spin_unlock(&udc->lock); - retval = _gadget_stop_activity(&udc->gadget); - if (retval) - goto done; - - retval = hw_usb_reset(udc); - if (retval) - goto done; - - udc->status = usb_ep_alloc_request(&udc->ep0in->ep, GFP_ATOMIC); - if (udc->status == NULL) - retval = -ENOMEM; - - spin_lock(&udc->lock); - - done: - if (retval) - dev_err(udc->dev, "error: %i\n", retval); -} - -/** - * isr_get_status_complete: get_status request complete function - * @ep: endpoint - * @req: request handled - * - * Caller must release lock - */ -static void isr_get_status_complete(struct usb_ep *ep, struct usb_request *req) -{ - if (ep == NULL || req == NULL) - return; - - kfree(req->buf); - usb_ep_free_request(ep, req); -} - -/** - * isr_get_status_response: get_status request response - * @udc: udc struct - * @setup: setup request packet - * - * This function returns an error code - */ -static int isr_get_status_response(struct ci13xxx *udc, - struct usb_ctrlrequest *setup) -__releases(mEp->lock) -__acquires(mEp->lock) -{ - struct ci13xxx_ep *mEp = udc->ep0in; - struct usb_request *req = NULL; - gfp_t gfp_flags = GFP_ATOMIC; - int dir, num, retval; - - if (mEp == NULL || setup == NULL) - return -EINVAL; - - spin_unlock(mEp->lock); - req = usb_ep_alloc_request(&mEp->ep, gfp_flags); - spin_lock(mEp->lock); - if (req == NULL) - return -ENOMEM; - - req->complete = isr_get_status_complete; - req->length = 2; - req->buf = kzalloc(req->length, gfp_flags); - if (req->buf == NULL) { - retval = -ENOMEM; - goto err_free_req; - } - - if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) { - /* Assume that device is bus powered for now. */ - *(u16 *)req->buf = udc->remote_wakeup << 1; - retval = 0; - } else if ((setup->bRequestType & USB_RECIP_MASK) \ - == USB_RECIP_ENDPOINT) { - dir = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK) ? - TX : RX; - num = le16_to_cpu(setup->wIndex) & USB_ENDPOINT_NUMBER_MASK; - *(u16 *)req->buf = hw_ep_get_halt(udc, num, dir); - } - /* else do nothing; reserved for future use */ - - spin_unlock(mEp->lock); - retval = usb_ep_queue(&mEp->ep, req, gfp_flags); - spin_lock(mEp->lock); - if (retval) - goto err_free_buf; - - return 0; - - err_free_buf: - kfree(req->buf); - err_free_req: - spin_unlock(mEp->lock); - usb_ep_free_request(&mEp->ep, req); - spin_lock(mEp->lock); - return retval; -} - -/** - * isr_setup_status_complete: setup_status request complete function - * @ep: endpoint - * @req: request handled - * - * Caller must release lock. Put the port in test mode if test mode - * feature is selected. - */ -static void -isr_setup_status_complete(struct usb_ep *ep, struct usb_request *req) -{ - struct ci13xxx *udc = req->context; - unsigned long flags; - - if (udc->setaddr) { - hw_usb_set_address(udc, udc->address); - udc->setaddr = false; - } - - spin_lock_irqsave(&udc->lock, flags); - if (udc->test_mode) - hw_port_test_set(udc, udc->test_mode); - spin_unlock_irqrestore(&udc->lock, flags); -} - -/** - * isr_setup_status_phase: queues the status phase of a setup transation - * @udc: udc struct - * - * This function returns an error code - */ -static int isr_setup_status_phase(struct ci13xxx *udc) -__releases(mEp->lock) -__acquires(mEp->lock) -{ - int retval; - struct ci13xxx_ep *mEp; - - mEp = (udc->ep0_dir == TX) ? udc->ep0out : udc->ep0in; - udc->status->context = udc; - udc->status->complete = isr_setup_status_complete; - - spin_unlock(mEp->lock); - retval = usb_ep_queue(&mEp->ep, udc->status, GFP_ATOMIC); - spin_lock(mEp->lock); - - return retval; -} - -/** - * isr_tr_complete_low: transaction complete low level handler - * @mEp: endpoint - * - * This function returns an error code - * Caller must hold lock - */ -static int isr_tr_complete_low(struct ci13xxx_ep *mEp) -__releases(mEp->lock) -__acquires(mEp->lock) -{ - struct ci13xxx_req *mReq, *mReqTemp; - struct ci13xxx_ep *mEpTemp = mEp; - int uninitialized_var(retval); - - if (list_empty(&mEp->qh.queue)) - return -EINVAL; - - list_for_each_entry_safe(mReq, mReqTemp, &mEp->qh.queue, - queue) { - retval = _hardware_dequeue(mEp, mReq); - if (retval < 0) - break; - list_del_init(&mReq->queue); - dbg_done(_usb_addr(mEp), mReq->ptr->token, retval); - if (mReq->req.complete != NULL) { - spin_unlock(mEp->lock); - if ((mEp->type == USB_ENDPOINT_XFER_CONTROL) && - mReq->req.length) - mEpTemp = mEp->udc->ep0in; - mReq->req.complete(&mEpTemp->ep, &mReq->req); - spin_lock(mEp->lock); - } - } - - if (retval == -EBUSY) - retval = 0; - if (retval < 0) - dbg_event(_usb_addr(mEp), "DONE", retval); - - return retval; -} - -/** - * isr_tr_complete_handler: transaction complete interrupt handler - * @udc: UDC descriptor - * - * This function handles traffic events - */ -static void isr_tr_complete_handler(struct ci13xxx *udc) -__releases(udc->lock) -__acquires(udc->lock) -{ - unsigned i; - u8 tmode = 0; - - for (i = 0; i < udc->hw_ep_max; i++) { - struct ci13xxx_ep *mEp = &udc->ci13xxx_ep[i]; - int type, num, dir, err = -EINVAL; - struct usb_ctrlrequest req; - - if (mEp->ep.desc == NULL) - continue; /* not configured */ - - if (hw_test_and_clear_complete(udc, i)) { - err = isr_tr_complete_low(mEp); - if (mEp->type == USB_ENDPOINT_XFER_CONTROL) { - if (err > 0) /* needs status phase */ - err = isr_setup_status_phase(udc); - if (err < 0) { - dbg_event(_usb_addr(mEp), - "ERROR", err); - spin_unlock(&udc->lock); - if (usb_ep_set_halt(&mEp->ep)) - dev_err(udc->dev, - "error: ep_set_halt\n"); - spin_lock(&udc->lock); - } - } - } - - if (mEp->type != USB_ENDPOINT_XFER_CONTROL || - !hw_test_and_clear_setup_status(udc, i)) - continue; - - if (i != 0) { - dev_warn(udc->dev, "ctrl traffic at endpoint %d\n", i); - continue; - } - - /* - * Flush data and handshake transactions of previous - * setup packet. - */ - _ep_nuke(udc->ep0out); - _ep_nuke(udc->ep0in); - - /* read_setup_packet */ - do { - hw_test_and_set_setup_guard(udc); - memcpy(&req, &mEp->qh.ptr->setup, sizeof(req)); - } while (!hw_test_and_clear_setup_guard(udc)); - - type = req.bRequestType; - - udc->ep0_dir = (type & USB_DIR_IN) ? TX : RX; - - dbg_setup(_usb_addr(mEp), &req); - - switch (req.bRequest) { - case USB_REQ_CLEAR_FEATURE: - if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && - le16_to_cpu(req.wValue) == - USB_ENDPOINT_HALT) { - if (req.wLength != 0) - break; - num = le16_to_cpu(req.wIndex); - dir = num & USB_ENDPOINT_DIR_MASK; - num &= USB_ENDPOINT_NUMBER_MASK; - if (dir) /* TX */ - num += udc->hw_ep_max/2; - if (!udc->ci13xxx_ep[num].wedge) { - spin_unlock(&udc->lock); - err = usb_ep_clear_halt( - &udc->ci13xxx_ep[num].ep); - spin_lock(&udc->lock); - if (err) - break; - } - err = isr_setup_status_phase(udc); - } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE) && - le16_to_cpu(req.wValue) == - USB_DEVICE_REMOTE_WAKEUP) { - if (req.wLength != 0) - break; - udc->remote_wakeup = 0; - err = isr_setup_status_phase(udc); - } else { - goto delegate; - } - break; - case USB_REQ_GET_STATUS: - if (type != (USB_DIR_IN|USB_RECIP_DEVICE) && - type != (USB_DIR_IN|USB_RECIP_ENDPOINT) && - type != (USB_DIR_IN|USB_RECIP_INTERFACE)) - goto delegate; - if (le16_to_cpu(req.wLength) != 2 || - le16_to_cpu(req.wValue) != 0) - break; - err = isr_get_status_response(udc, &req); - break; - case USB_REQ_SET_ADDRESS: - if (type != (USB_DIR_OUT|USB_RECIP_DEVICE)) - goto delegate; - if (le16_to_cpu(req.wLength) != 0 || - le16_to_cpu(req.wIndex) != 0) - break; - udc->address = (u8)le16_to_cpu(req.wValue); - udc->setaddr = true; - err = isr_setup_status_phase(udc); - break; - case USB_REQ_SET_FEATURE: - if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && - le16_to_cpu(req.wValue) == - USB_ENDPOINT_HALT) { - if (req.wLength != 0) - break; - num = le16_to_cpu(req.wIndex); - dir = num & USB_ENDPOINT_DIR_MASK; - num &= USB_ENDPOINT_NUMBER_MASK; - if (dir) /* TX */ - num += udc->hw_ep_max/2; - - spin_unlock(&udc->lock); - err = usb_ep_set_halt(&udc->ci13xxx_ep[num].ep); - spin_lock(&udc->lock); - if (!err) - isr_setup_status_phase(udc); - } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE)) { - if (req.wLength != 0) - break; - switch (le16_to_cpu(req.wValue)) { - case USB_DEVICE_REMOTE_WAKEUP: - udc->remote_wakeup = 1; - err = isr_setup_status_phase(udc); - break; - case USB_DEVICE_TEST_MODE: - tmode = le16_to_cpu(req.wIndex) >> 8; - switch (tmode) { - case TEST_J: - case TEST_K: - case TEST_SE0_NAK: - case TEST_PACKET: - case TEST_FORCE_EN: - udc->test_mode = tmode; - err = isr_setup_status_phase( - udc); - break; - default: - break; - } - default: - goto delegate; - } - } else { - goto delegate; - } - break; - default: -delegate: - if (req.wLength == 0) /* no data phase */ - udc->ep0_dir = TX; - - spin_unlock(&udc->lock); - err = udc->driver->setup(&udc->gadget, &req); - spin_lock(&udc->lock); - break; - } - - if (err < 0) { - dbg_event(_usb_addr(mEp), "ERROR", err); - - spin_unlock(&udc->lock); - if (usb_ep_set_halt(&mEp->ep)) - dev_err(udc->dev, "error: ep_set_halt\n"); - spin_lock(&udc->lock); - } - } -} - -/****************************************************************************** - * ENDPT block - *****************************************************************************/ -/** - * ep_enable: configure endpoint, making it usable - * - * Check usb_ep_enable() at "usb_gadget.h" for details - */ -static int ep_enable(struct usb_ep *ep, - const struct usb_endpoint_descriptor *desc) -{ - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); - int retval = 0; - unsigned long flags; - - if (ep == NULL || desc == NULL) - return -EINVAL; - - spin_lock_irqsave(mEp->lock, flags); - - /* only internal SW should enable ctrl endpts */ - - mEp->ep.desc = desc; - - if (!list_empty(&mEp->qh.queue)) - dev_warn(mEp->udc->dev, "enabling a non-empty endpoint!\n"); - - mEp->dir = usb_endpoint_dir_in(desc) ? TX : RX; - mEp->num = usb_endpoint_num(desc); - mEp->type = usb_endpoint_type(desc); - - mEp->ep.maxpacket = usb_endpoint_maxp(desc); - - dbg_event(_usb_addr(mEp), "ENABLE", 0); - - mEp->qh.ptr->cap = 0; - - if (mEp->type == USB_ENDPOINT_XFER_CONTROL) - mEp->qh.ptr->cap |= QH_IOS; - else if (mEp->type == USB_ENDPOINT_XFER_ISOC) - mEp->qh.ptr->cap &= ~QH_MULT; - else - mEp->qh.ptr->cap &= ~QH_ZLT; - - mEp->qh.ptr->cap |= - (mEp->ep.maxpacket << ffs_nr(QH_MAX_PKT)) & QH_MAX_PKT; - mEp->qh.ptr->td.next |= TD_TERMINATE; /* needed? */ - - /* - * Enable endpoints in the HW other than ep0 as ep0 - * is always enabled - */ - if (mEp->num) - retval |= hw_ep_enable(mEp->udc, mEp->num, mEp->dir, mEp->type); - - spin_unlock_irqrestore(mEp->lock, flags); - return retval; -} - -/** - * ep_disable: endpoint is no longer usable - * - * Check usb_ep_disable() at "usb_gadget.h" for details - */ -static int ep_disable(struct usb_ep *ep) -{ - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); - int direction, retval = 0; - unsigned long flags; - - if (ep == NULL) - return -EINVAL; - else if (mEp->ep.desc == NULL) - return -EBUSY; - - spin_lock_irqsave(mEp->lock, flags); - - /* only internal SW should disable ctrl endpts */ - - direction = mEp->dir; - do { - dbg_event(_usb_addr(mEp), "DISABLE", 0); - - retval |= _ep_nuke(mEp); - retval |= hw_ep_disable(mEp->udc, mEp->num, mEp->dir); - - if (mEp->type == USB_ENDPOINT_XFER_CONTROL) - mEp->dir = (mEp->dir == TX) ? RX : TX; - - } while (mEp->dir != direction); - - mEp->ep.desc = NULL; - - spin_unlock_irqrestore(mEp->lock, flags); - return retval; -} - -/** - * ep_alloc_request: allocate a request object to use with this endpoint - * - * Check usb_ep_alloc_request() at "usb_gadget.h" for details - */ -static struct usb_request *ep_alloc_request(struct usb_ep *ep, gfp_t gfp_flags) -{ - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); - struct ci13xxx_req *mReq = NULL; - - if (ep == NULL) - return NULL; - - mReq = kzalloc(sizeof(struct ci13xxx_req), gfp_flags); - if (mReq != NULL) { - INIT_LIST_HEAD(&mReq->queue); - mReq->req.dma = DMA_ADDR_INVALID; - - mReq->ptr = dma_pool_alloc(mEp->td_pool, gfp_flags, - &mReq->dma); - if (mReq->ptr == NULL) { - kfree(mReq); - mReq = NULL; - } - } - - dbg_event(_usb_addr(mEp), "ALLOC", mReq == NULL); - - return (mReq == NULL) ? NULL : &mReq->req; -} - -/** - * ep_free_request: frees a request object - * - * Check usb_ep_free_request() at "usb_gadget.h" for details - */ -static void ep_free_request(struct usb_ep *ep, struct usb_request *req) -{ - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); - struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); - unsigned long flags; - - if (ep == NULL || req == NULL) { - return; - } else if (!list_empty(&mReq->queue)) { - dev_err(mEp->udc->dev, "freeing queued request\n"); - return; - } - - spin_lock_irqsave(mEp->lock, flags); - - if (mReq->ptr) - dma_pool_free(mEp->td_pool, mReq->ptr, mReq->dma); - kfree(mReq); - - dbg_event(_usb_addr(mEp), "FREE", 0); - - spin_unlock_irqrestore(mEp->lock, flags); -} - -/** - * ep_queue: queues (submits) an I/O request to an endpoint - * - * Check usb_ep_queue()* at usb_gadget.h" for details - */ -static int ep_queue(struct usb_ep *ep, struct usb_request *req, - gfp_t __maybe_unused gfp_flags) -{ - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); - struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); - struct ci13xxx *udc = mEp->udc; - int retval = 0; - unsigned long flags; - - if (ep == NULL || req == NULL || mEp->ep.desc == NULL) - return -EINVAL; - - spin_lock_irqsave(mEp->lock, flags); - - if (mEp->type == USB_ENDPOINT_XFER_CONTROL) { - if (req->length) - mEp = (udc->ep0_dir == RX) ? - udc->ep0out : udc->ep0in; - if (!list_empty(&mEp->qh.queue)) { - _ep_nuke(mEp); - retval = -EOVERFLOW; - dev_warn(mEp->udc->dev, "endpoint ctrl %X nuked\n", - _usb_addr(mEp)); - } - } - - /* first nuke then test link, e.g. previous status has not sent */ - if (!list_empty(&mReq->queue)) { - retval = -EBUSY; - dev_err(mEp->udc->dev, "request already in queue\n"); - goto done; - } - - if (req->length > 4 * CI13XXX_PAGE_SIZE) { - req->length = 4 * CI13XXX_PAGE_SIZE; - retval = -EMSGSIZE; - dev_warn(mEp->udc->dev, "request length truncated\n"); - } - - dbg_queue(_usb_addr(mEp), req, retval); - - /* push request */ - mReq->req.status = -EINPROGRESS; - mReq->req.actual = 0; - - retval = _hardware_enqueue(mEp, mReq); - - if (retval == -EALREADY) { - dbg_event(_usb_addr(mEp), "QUEUE", retval); - retval = 0; - } - if (!retval) - list_add_tail(&mReq->queue, &mEp->qh.queue); - - done: - spin_unlock_irqrestore(mEp->lock, flags); - return retval; -} - -/** - * ep_dequeue: dequeues (cancels, unlinks) an I/O request from an endpoint - * - * Check usb_ep_dequeue() at "usb_gadget.h" for details - */ -static int ep_dequeue(struct usb_ep *ep, struct usb_request *req) -{ - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); - struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); - unsigned long flags; - - if (ep == NULL || req == NULL || mReq->req.status != -EALREADY || - mEp->ep.desc == NULL || list_empty(&mReq->queue) || - list_empty(&mEp->qh.queue)) - return -EINVAL; - - spin_lock_irqsave(mEp->lock, flags); - - dbg_event(_usb_addr(mEp), "DEQUEUE", 0); - - hw_ep_flush(mEp->udc, mEp->num, mEp->dir); - - /* pop request */ - list_del_init(&mReq->queue); - if (mReq->map) { - dma_unmap_single(mEp->device, mReq->req.dma, mReq->req.length, - mEp->dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE); - mReq->req.dma = DMA_ADDR_INVALID; - mReq->map = 0; - } - req->status = -ECONNRESET; - - if (mReq->req.complete != NULL) { - spin_unlock(mEp->lock); - mReq->req.complete(&mEp->ep, &mReq->req); - spin_lock(mEp->lock); - } - - spin_unlock_irqrestore(mEp->lock, flags); - return 0; -} - -/** - * ep_set_halt: sets the endpoint halt feature - * - * Check usb_ep_set_halt() at "usb_gadget.h" for details - */ -static int ep_set_halt(struct usb_ep *ep, int value) -{ - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); - int direction, retval = 0; - unsigned long flags; - - if (ep == NULL || mEp->ep.desc == NULL) - return -EINVAL; - - spin_lock_irqsave(mEp->lock, flags); - -#ifndef STALL_IN - /* g_file_storage MS compliant but g_zero fails chapter 9 compliance */ - if (value && mEp->type == USB_ENDPOINT_XFER_BULK && mEp->dir == TX && - !list_empty(&mEp->qh.queue)) { - spin_unlock_irqrestore(mEp->lock, flags); - return -EAGAIN; - } -#endif - - direction = mEp->dir; - do { - dbg_event(_usb_addr(mEp), "HALT", value); - retval |= hw_ep_set_halt(mEp->udc, mEp->num, mEp->dir, value); - - if (!value) - mEp->wedge = 0; - - if (mEp->type == USB_ENDPOINT_XFER_CONTROL) - mEp->dir = (mEp->dir == TX) ? RX : TX; - - } while (mEp->dir != direction); - - spin_unlock_irqrestore(mEp->lock, flags); - return retval; -} - -/** - * ep_set_wedge: sets the halt feature and ignores clear requests - * - * Check usb_ep_set_wedge() at "usb_gadget.h" for details - */ -static int ep_set_wedge(struct usb_ep *ep) -{ - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); - unsigned long flags; - - if (ep == NULL || mEp->ep.desc == NULL) - return -EINVAL; - - spin_lock_irqsave(mEp->lock, flags); - - dbg_event(_usb_addr(mEp), "WEDGE", 0); - mEp->wedge = 1; - - spin_unlock_irqrestore(mEp->lock, flags); - - return usb_ep_set_halt(ep); -} - -/** - * ep_fifo_flush: flushes contents of a fifo - * - * Check usb_ep_fifo_flush() at "usb_gadget.h" for details - */ -static void ep_fifo_flush(struct usb_ep *ep) -{ - struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); - unsigned long flags; - - if (ep == NULL) { - dev_err(mEp->udc->dev, "%02X: -EINVAL\n", _usb_addr(mEp)); - return; - } - - spin_lock_irqsave(mEp->lock, flags); - - dbg_event(_usb_addr(mEp), "FFLUSH", 0); - hw_ep_flush(mEp->udc, mEp->num, mEp->dir); - - spin_unlock_irqrestore(mEp->lock, flags); -} - -/** - * Endpoint-specific part of the API to the USB controller hardware - * Check "usb_gadget.h" for details - */ -static const struct usb_ep_ops usb_ep_ops = { - .enable = ep_enable, - .disable = ep_disable, - .alloc_request = ep_alloc_request, - .free_request = ep_free_request, - .queue = ep_queue, - .dequeue = ep_dequeue, - .set_halt = ep_set_halt, - .set_wedge = ep_set_wedge, - .fifo_flush = ep_fifo_flush, -}; - -/****************************************************************************** - * GADGET block - *****************************************************************************/ -static int ci13xxx_vbus_session(struct usb_gadget *_gadget, int is_active) -{ - struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget); - unsigned long flags; - int gadget_ready = 0; - - if (!(udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS)) - return -EOPNOTSUPP; - - spin_lock_irqsave(&udc->lock, flags); - udc->vbus_active = is_active; - if (udc->driver) - gadget_ready = 1; - spin_unlock_irqrestore(&udc->lock, flags); - - if (gadget_ready) { - if (is_active) { - pm_runtime_get_sync(&_gadget->dev); - hw_device_reset(udc); - hw_device_state(udc, udc->ep0out->qh.dma); - } else { - hw_device_state(udc, 0); - if (udc->udc_driver->notify_event) - udc->udc_driver->notify_event(udc, - CI13XXX_CONTROLLER_STOPPED_EVENT); - _gadget_stop_activity(&udc->gadget); - pm_runtime_put_sync(&_gadget->dev); - } - } - - return 0; -} - -static int ci13xxx_wakeup(struct usb_gadget *_gadget) -{ - struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget); - unsigned long flags; - int ret = 0; - - spin_lock_irqsave(&udc->lock, flags); - if (!udc->remote_wakeup) { - ret = -EOPNOTSUPP; - goto out; - } - if (!hw_read(udc, OP_PORTSC, PORTSC_SUSP)) { - ret = -EINVAL; - goto out; - } - hw_write(udc, OP_PORTSC, PORTSC_FPR, PORTSC_FPR); -out: - spin_unlock_irqrestore(&udc->lock, flags); - return ret; -} - -static int ci13xxx_vbus_draw(struct usb_gadget *_gadget, unsigned mA) -{ - struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget); - - if (udc->transceiver) - return usb_phy_set_power(udc->transceiver, mA); - return -ENOTSUPP; -} - -static int ci13xxx_start(struct usb_gadget *gadget, - struct usb_gadget_driver *driver); -static int ci13xxx_stop(struct usb_gadget *gadget, - struct usb_gadget_driver *driver); -/** - * Device operations part of the API to the USB controller hardware, - * which don't involve endpoints (or i/o) - * Check "usb_gadget.h" for details - */ -static const struct usb_gadget_ops usb_gadget_ops = { - .vbus_session = ci13xxx_vbus_session, - .wakeup = ci13xxx_wakeup, - .vbus_draw = ci13xxx_vbus_draw, - .udc_start = ci13xxx_start, - .udc_stop = ci13xxx_stop, -}; - -static int init_eps(struct ci13xxx *udc) -{ - int retval = 0, i, j; - - for (i = 0; i < udc->hw_ep_max/2; i++) - for (j = RX; j <= TX; j++) { - int k = i + j * udc->hw_ep_max/2; - struct ci13xxx_ep *mEp = &udc->ci13xxx_ep[k]; - - scnprintf(mEp->name, sizeof(mEp->name), "ep%i%s", i, - (j == TX) ? "in" : "out"); - - mEp->udc = udc; - mEp->lock = &udc->lock; - mEp->device = &udc->gadget.dev; - mEp->td_pool = udc->td_pool; - - mEp->ep.name = mEp->name; - mEp->ep.ops = &usb_ep_ops; - mEp->ep.maxpacket = CTRL_PAYLOAD_MAX; - - INIT_LIST_HEAD(&mEp->qh.queue); - mEp->qh.ptr = dma_pool_alloc(udc->qh_pool, GFP_KERNEL, - &mEp->qh.dma); - if (mEp->qh.ptr == NULL) - retval = -ENOMEM; - else - memset(mEp->qh.ptr, 0, sizeof(*mEp->qh.ptr)); - - /* - * set up shorthands for ep0 out and in endpoints, - * don't add to gadget's ep_list - */ - if (i == 0) { - if (j == RX) - udc->ep0out = mEp; - else - udc->ep0in = mEp; - - continue; - } - - list_add_tail(&mEp->ep.ep_list, &udc->gadget.ep_list); - } - - return retval; -} - -/** - * ci13xxx_start: register a gadget driver - * @gadget: our gadget - * @driver: the driver being registered - * - * Interrupts are enabled here. - */ -static int ci13xxx_start(struct usb_gadget *gadget, - struct usb_gadget_driver *driver) -{ - struct ci13xxx *udc = container_of(gadget, struct ci13xxx, gadget); - unsigned long flags; - int retval = -ENOMEM; - - if (driver->disconnect == NULL) - return -EINVAL; - - - udc->ep0out->ep.desc = &ctrl_endpt_out_desc; - retval = usb_ep_enable(&udc->ep0out->ep); - if (retval) - return retval; - - udc->ep0in->ep.desc = &ctrl_endpt_in_desc; - retval = usb_ep_enable(&udc->ep0in->ep); - if (retval) - return retval; - spin_lock_irqsave(&udc->lock, flags); - - udc->driver = driver; - pm_runtime_get_sync(&udc->gadget.dev); - if (udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS) { - if (udc->vbus_active) { - if (udc->udc_driver->flags & CI13XXX_REGS_SHARED) - hw_device_reset(udc); - } else { - pm_runtime_put_sync(&udc->gadget.dev); - goto done; - } - } - - retval = hw_device_state(udc, udc->ep0out->qh.dma); - if (retval) - pm_runtime_put_sync(&udc->gadget.dev); - - done: - spin_unlock_irqrestore(&udc->lock, flags); - return retval; -} - -/** - * ci13xxx_stop: unregister a gadget driver - */ -static int ci13xxx_stop(struct usb_gadget *gadget, - struct usb_gadget_driver *driver) -{ - struct ci13xxx *udc = container_of(gadget, struct ci13xxx, gadget); - unsigned long flags; - - spin_lock_irqsave(&udc->lock, flags); - - if (!(udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS) || - udc->vbus_active) { - hw_device_state(udc, 0); - if (udc->udc_driver->notify_event) - udc->udc_driver->notify_event(udc, - CI13XXX_CONTROLLER_STOPPED_EVENT); - udc->driver = NULL; - spin_unlock_irqrestore(&udc->lock, flags); - _gadget_stop_activity(&udc->gadget); - spin_lock_irqsave(&udc->lock, flags); - pm_runtime_put(&udc->gadget.dev); - } - - spin_unlock_irqrestore(&udc->lock, flags); - - return 0; -} - -/****************************************************************************** - * BUS block - *****************************************************************************/ -/** - * udc_irq: global interrupt handler - * - * This function returns IRQ_HANDLED if the IRQ has been handled - * It locks access to registers - */ -static irqreturn_t udc_irq(int irq, void *data) -{ - struct ci13xxx *udc = data; - irqreturn_t retval; - u32 intr; - - if (udc == NULL) - return IRQ_HANDLED; - - spin_lock(&udc->lock); - - if (udc->udc_driver->flags & CI13XXX_REGS_SHARED) { - if (hw_read(udc, OP_USBMODE, USBMODE_CM) != - USBMODE_CM_DEVICE) { - spin_unlock(&udc->lock); - return IRQ_NONE; - } - } - intr = hw_test_and_clear_intr_active(udc); - if (intr) { - isr_statistics.hndl.buf[isr_statistics.hndl.idx++] = intr; - isr_statistics.hndl.idx &= ISR_MASK; - isr_statistics.hndl.cnt++; - - /* order defines priority - do NOT change it */ - if (USBi_URI & intr) { - isr_statistics.uri++; - isr_reset_handler(udc); - } - if (USBi_PCI & intr) { - isr_statistics.pci++; - udc->gadget.speed = hw_port_is_high_speed(udc) ? - USB_SPEED_HIGH : USB_SPEED_FULL; - if (udc->suspended && udc->driver->resume) { - spin_unlock(&udc->lock); - udc->driver->resume(&udc->gadget); - spin_lock(&udc->lock); - udc->suspended = 0; - } - } - if (USBi_UEI & intr) - isr_statistics.uei++; - if (USBi_UI & intr) { - isr_statistics.ui++; - isr_tr_complete_handler(udc); - } - if (USBi_SLI & intr) { - if (udc->gadget.speed != USB_SPEED_UNKNOWN && - udc->driver->suspend) { - udc->suspended = 1; - spin_unlock(&udc->lock); - udc->driver->suspend(&udc->gadget); - spin_lock(&udc->lock); - } - isr_statistics.sli++; - } - retval = IRQ_HANDLED; - } else { - isr_statistics.none++; - retval = IRQ_NONE; - } - spin_unlock(&udc->lock); - - return retval; -} - -/** - * udc_release: driver release function - * @dev: device - * - * Currently does nothing - */ -static void udc_release(struct device *dev) -{ -} - -/** - * udc_probe: parent probe must call this to initialize UDC - * @dev: parent device - * @regs: registers base address - * @name: driver name - * - * This function returns an error code - * No interrupts active, the IRQ has not been requested yet - * Kernel assumes 32-bit DMA operations by default, no need to dma_set_mask - */ -static int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev, - void __iomem *regs, struct ci13xxx **_udc) -{ - struct ci13xxx *udc; - int retval = 0; - - if (dev == NULL || regs == NULL || driver == NULL || - driver->name == NULL) - return -EINVAL; - - udc = kzalloc(sizeof(struct ci13xxx), GFP_KERNEL); - if (udc == NULL) - return -ENOMEM; - - spin_lock_init(&udc->lock); - udc->regs = regs; - udc->udc_driver = driver; - - udc->gadget.ops = &usb_gadget_ops; - udc->gadget.speed = USB_SPEED_UNKNOWN; - udc->gadget.max_speed = USB_SPEED_HIGH; - udc->gadget.is_otg = 0; - udc->gadget.name = driver->name; - - INIT_LIST_HEAD(&udc->gadget.ep_list); - - dev_set_name(&udc->gadget.dev, "gadget"); - udc->gadget.dev.dma_mask = dev->dma_mask; - udc->gadget.dev.coherent_dma_mask = dev->coherent_dma_mask; - udc->gadget.dev.parent = dev; - udc->gadget.dev.release = udc_release; - - udc->dev = dev; - - /* alloc resources */ - udc->qh_pool = dma_pool_create("ci13xxx_qh", dev, - sizeof(struct ci13xxx_qh), - 64, CI13XXX_PAGE_SIZE); - if (udc->qh_pool == NULL) { - retval = -ENOMEM; - goto free_udc; - } - - udc->td_pool = dma_pool_create("ci13xxx_td", dev, - sizeof(struct ci13xxx_td), - 64, CI13XXX_PAGE_SIZE); - if (udc->td_pool == NULL) { - retval = -ENOMEM; - goto free_qh_pool; - } - - retval = hw_device_init(udc, regs, driver->capoffset); - if (retval < 0) - goto free_pools; - - retval = init_eps(udc); - if (retval) - goto free_pools; - - udc->gadget.ep0 = &udc->ep0in->ep; - - udc->transceiver = usb_get_transceiver(); - - if (udc->udc_driver->flags & CI13XXX_REQUIRE_TRANSCEIVER) { - if (udc->transceiver == NULL) { - retval = -ENODEV; - goto free_pools; - } - } - - if (!(udc->udc_driver->flags & CI13XXX_REGS_SHARED)) { - retval = hw_device_reset(udc); - if (retval) - goto put_transceiver; - } - - retval = device_register(&udc->gadget.dev); - if (retval) { - put_device(&udc->gadget.dev); - goto put_transceiver; - } - -#ifdef CONFIG_USB_GADGET_DEBUG_FILES - retval = dbg_create_files(&udc->gadget.dev); -#endif - if (retval) - goto unreg_device; - - if (udc->transceiver) { - retval = otg_set_peripheral(udc->transceiver->otg, - &udc->gadget); - if (retval) - goto remove_dbg; - } - - retval = usb_add_gadget_udc(dev, &udc->gadget); - if (retval) - goto remove_trans; - - pm_runtime_no_callbacks(&udc->gadget.dev); - pm_runtime_enable(&udc->gadget.dev); - - *_udc = udc; - return retval; - -remove_trans: - if (udc->transceiver) { - otg_set_peripheral(udc->transceiver->otg, &udc->gadget); - usb_put_transceiver(udc->transceiver); - } - - dev_err(dev, "error = %i\n", retval); -remove_dbg: -#ifdef CONFIG_USB_GADGET_DEBUG_FILES - dbg_remove_files(&udc->gadget.dev); -#endif -unreg_device: - device_unregister(&udc->gadget.dev); -put_transceiver: - if (udc->transceiver) - usb_put_transceiver(udc->transceiver); -free_pools: - dma_pool_destroy(udc->td_pool); -free_qh_pool: - dma_pool_destroy(udc->qh_pool); -free_udc: - kfree(udc); - *_udc = NULL; - return retval; -} - -/** - * udc_remove: parent remove must call this to remove UDC - * - * No interrupts active, the IRQ has been released - */ -static void udc_remove(struct ci13xxx *udc) -{ - int i; - - if (udc == NULL) - return; - - usb_del_gadget_udc(&udc->gadget); - - for (i = 0; i < udc->hw_ep_max; i++) { - struct ci13xxx_ep *mEp = &udc->ci13xxx_ep[i]; - - dma_pool_free(udc->qh_pool, mEp->qh.ptr, mEp->qh.dma); - } - - dma_pool_destroy(udc->td_pool); - dma_pool_destroy(udc->qh_pool); - - if (udc->transceiver) { - otg_set_peripheral(udc->transceiver->otg, &udc->gadget); - usb_put_transceiver(udc->transceiver); - } -#ifdef CONFIG_USB_GADGET_DEBUG_FILES - dbg_remove_files(&udc->gadget.dev); -#endif - device_unregister(&udc->gadget.dev); - - kfree(udc->hw_bank.regmap); - kfree(udc); -} - -static int __devinit ci_udc_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct ci13xxx_udc_driver *driver = dev->platform_data; - struct ci13xxx *udc; - struct resource *res; - void __iomem *base; - int ret; - - if (!driver) { - dev_err(dev, "platform data missing\n"); - return -ENODEV; - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(dev, "missing resource\n"); - return -ENODEV; - } - - base = devm_request_and_ioremap(dev, res); - if (!res) { - dev_err(dev, "can't request and ioremap resource\n"); - return -ENOMEM; - } - - ret = udc_probe(driver, dev, base, &udc); - if (ret) - return ret; - - udc->irq = platform_get_irq(pdev, 0); - if (udc->irq < 0) { - dev_err(dev, "missing IRQ\n"); - ret = -ENODEV; - goto out; - } - - platform_set_drvdata(pdev, udc); - ret = request_irq(udc->irq, udc_irq, IRQF_SHARED, driver->name, udc); - -out: - if (ret) - udc_remove(udc); - - return ret; -} - -static int __devexit ci_udc_remove(struct platform_device *pdev) -{ - struct ci13xxx *udc = platform_get_drvdata(pdev); - - free_irq(udc->irq, udc); - udc_remove(udc); - - return 0; -} - -static struct platform_driver ci_udc_driver = { - .probe = ci_udc_probe, - .remove = __devexit_p(ci_udc_remove), - .driver = { - .name = "ci_udc", - }, -}; - -module_platform_driver(ci_udc_driver); - -MODULE_ALIAS("platform:ci_udc"); -MODULE_ALIAS("platform:ci13xxx"); -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("David Lopo "); -MODULE_DESCRIPTION("ChipIdea UDC Driver"); diff --git a/drivers/usb/chipidea/ci13xxx_udc.h b/drivers/usb/chipidea/ci13xxx_udc.h deleted file mode 100644 index a8aa1a70dec4..000000000000 --- a/drivers/usb/chipidea/ci13xxx_udc.h +++ /dev/null @@ -1,248 +0,0 @@ -/* - * ci13xxx_udc.h - structures, registers, and macros MIPS USB IP core - * - * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. - * - * Author: David Lopo - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * Description: MIPS USB IP core family device controller - * Structures, registers and logging macros - */ - -#ifndef _CI13XXX_h_ -#define _CI13XXX_h_ - -/****************************************************************************** - * DEFINE - *****************************************************************************/ -#define CI13XXX_PAGE_SIZE 4096ul /* page size for TD's */ -#define ENDPT_MAX 32 -#define CTRL_PAYLOAD_MAX 64 -#define RX 0 /* similar to USB_DIR_OUT but can be used as an index */ -#define TX 1 /* similar to USB_DIR_IN but can be used as an index */ - -/****************************************************************************** - * STRUCTURES - *****************************************************************************/ -/* DMA layout of transfer descriptors */ -struct ci13xxx_td { - /* 0 */ - u32 next; -#define TD_TERMINATE BIT(0) -#define TD_ADDR_MASK (0xFFFFFFEUL << 5) - /* 1 */ - u32 token; -#define TD_STATUS (0x00FFUL << 0) -#define TD_STATUS_TR_ERR BIT(3) -#define TD_STATUS_DT_ERR BIT(5) -#define TD_STATUS_HALTED BIT(6) -#define TD_STATUS_ACTIVE BIT(7) -#define TD_MULTO (0x0003UL << 10) -#define TD_IOC BIT(15) -#define TD_TOTAL_BYTES (0x7FFFUL << 16) - /* 2 */ - u32 page[5]; -#define TD_CURR_OFFSET (0x0FFFUL << 0) -#define TD_FRAME_NUM (0x07FFUL << 0) -#define TD_RESERVED_MASK (0x0FFFUL << 0) -} __attribute__ ((packed)); - -/* DMA layout of queue heads */ -struct ci13xxx_qh { - /* 0 */ - u32 cap; -#define QH_IOS BIT(15) -#define QH_MAX_PKT (0x07FFUL << 16) -#define QH_ZLT BIT(29) -#define QH_MULT (0x0003UL << 30) - /* 1 */ - u32 curr; - /* 2 - 8 */ - struct ci13xxx_td td; - /* 9 */ - u32 RESERVED; - struct usb_ctrlrequest setup; -} __attribute__ ((packed)); - -/* Extension of usb_request */ -struct ci13xxx_req { - struct usb_request req; - unsigned map; - struct list_head queue; - struct ci13xxx_td *ptr; - dma_addr_t dma; - struct ci13xxx_td *zptr; - dma_addr_t zdma; -}; - -/* Extension of usb_ep */ -struct ci13xxx_ep { - struct usb_ep ep; - u8 dir; - u8 num; - u8 type; - char name[16]; - struct { - struct list_head queue; - struct ci13xxx_qh *ptr; - dma_addr_t dma; - } qh; - int wedge; - - /* global resources */ - struct ci13xxx *udc; - spinlock_t *lock; - struct device *device; - struct dma_pool *td_pool; -}; - -struct ci13xxx; -struct ci13xxx_udc_driver { - const char *name; - /* offset of the capability registers */ - uintptr_t capoffset; - unsigned long flags; -#define CI13XXX_REGS_SHARED BIT(0) -#define CI13XXX_REQUIRE_TRANSCEIVER BIT(1) -#define CI13XXX_PULLUP_ON_VBUS BIT(2) -#define CI13XXX_DISABLE_STREAMING BIT(3) - -#define CI13XXX_CONTROLLER_RESET_EVENT 0 -#define CI13XXX_CONTROLLER_STOPPED_EVENT 1 - void (*notify_event) (struct ci13xxx *udc, unsigned event); -}; - -struct hw_bank { - unsigned lpm; /* is LPM? */ - void __iomem *abs; /* bus map offset */ - void __iomem *cap; /* bus map offset + CAP offset */ - void __iomem *op; /* bus map offset + OP offset */ - size_t size; /* bank size */ - void __iomem **regmap; -}; - -/* CI13XXX UDC descriptor & global resources */ -struct ci13xxx { - spinlock_t lock; /* ctrl register bank access */ - void __iomem *regs; /* registers address space */ - - struct dma_pool *qh_pool; /* DMA pool for queue heads */ - struct dma_pool *td_pool; /* DMA pool for transfer descs */ - struct usb_request *status; /* ep0 status request */ - - struct device *dev; - struct usb_gadget gadget; /* USB slave device */ - struct ci13xxx_ep ci13xxx_ep[ENDPT_MAX]; /* extended endpts */ - u32 ep0_dir; /* ep0 direction */ - struct ci13xxx_ep *ep0out, *ep0in; - unsigned hw_ep_max; /* number of hw endpoints */ - - bool setaddr; - u8 address; - u8 remote_wakeup; /* Is remote wakeup feature - enabled by the host? */ - u8 suspended; /* suspended by the host */ - u8 test_mode; /* the selected test mode */ - - struct hw_bank hw_bank; - int irq; - struct usb_gadget_driver *driver; /* 3rd party gadget driver */ - struct ci13xxx_udc_driver *udc_driver; /* device controller driver */ - int vbus_active; /* is VBUS active */ - struct usb_phy *transceiver; /* Transceiver struct */ -}; - -/****************************************************************************** - * REGISTERS - *****************************************************************************/ -/* Default offset of capability registers */ -#define DEF_CAPOFFSET 0x100 - -/* register size */ -#define REG_BITS (32) - -/* register indices */ -enum ci13xxx_regs { - CAP_CAPLENGTH, - CAP_HCCPARAMS, - CAP_DCCPARAMS, - CAP_TESTMODE, - CAP_LAST = CAP_TESTMODE, - OP_USBCMD, - OP_USBSTS, - OP_USBINTR, - OP_DEVICEADDR, - OP_ENDPTLISTADDR, - OP_PORTSC, - OP_DEVLC, - OP_USBMODE, - OP_ENDPTSETUPSTAT, - OP_ENDPTPRIME, - OP_ENDPTFLUSH, - OP_ENDPTSTAT, - OP_ENDPTCOMPLETE, - OP_ENDPTCTRL, - /* endptctrl1..15 follow */ - OP_LAST = OP_ENDPTCTRL + ENDPT_MAX / 2, -}; - -/* HCCPARAMS */ -#define HCCPARAMS_LEN BIT(17) - -/* DCCPARAMS */ -#define DCCPARAMS_DEN (0x1F << 0) -#define DCCPARAMS_DC BIT(7) - -/* TESTMODE */ -#define TESTMODE_FORCE BIT(0) - -/* USBCMD */ -#define USBCMD_RS BIT(0) -#define USBCMD_RST BIT(1) -#define USBCMD_SUTW BIT(13) -#define USBCMD_ATDTW BIT(14) - -/* USBSTS & USBINTR */ -#define USBi_UI BIT(0) -#define USBi_UEI BIT(1) -#define USBi_PCI BIT(2) -#define USBi_URI BIT(6) -#define USBi_SLI BIT(8) - -/* DEVICEADDR */ -#define DEVICEADDR_USBADRA BIT(24) -#define DEVICEADDR_USBADR (0x7FUL << 25) - -/* PORTSC */ -#define PORTSC_FPR BIT(6) -#define PORTSC_SUSP BIT(7) -#define PORTSC_HSP BIT(9) -#define PORTSC_PTC (0x0FUL << 16) - -/* DEVLC */ -#define DEVLC_PSPD (0x03UL << 25) -#define DEVLC_PSPD_HS (0x02UL << 25) - -/* USBMODE */ -#define USBMODE_CM (0x03UL << 0) -#define USBMODE_CM_IDLE (0x00UL << 0) -#define USBMODE_CM_DEVICE (0x02UL << 0) -#define USBMODE_CM_HOST (0x03UL << 0) -#define USBMODE_SLOM BIT(3) -#define USBMODE_SDIS BIT(4) - -/* ENDPTCTRL */ -#define ENDPTCTRL_RXS BIT(0) -#define ENDPTCTRL_RXT (0x03UL << 2) -#define ENDPTCTRL_RXR BIT(6) /* reserved for port 0 */ -#define ENDPTCTRL_RXE BIT(7) -#define ENDPTCTRL_TXS BIT(16) -#define ENDPTCTRL_TXT (0x03UL << 18) -#define ENDPTCTRL_TXR BIT(22) /* reserved for port 0 */ -#define ENDPTCTRL_TXE BIT(23) - -#endif /* _CI13XXX_h_ */ diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c new file mode 100644 index 000000000000..f6eab327ffea --- /dev/null +++ b/drivers/usb/chipidea/core.c @@ -0,0 +1,324 @@ +/* + * core.c - ChipIdea USB IP core family device controller + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/* + * Description: ChipIdea USB IP core family device controller + * + * This driver is composed of several blocks: + * - HW: hardware interface + * - DBG: debug facilities (optional) + * - UTIL: utilities + * - ISR: interrupts handling + * - ENDPT: endpoint operations (Gadget API) + * - GADGET: gadget operations (Gadget API) + * - BUS: bus glue code, bus abstraction layer + * + * Compile Options + * - CONFIG_USB_GADGET_DEBUG_FILES: enable debug facilities + * - STALL_IN: non-empty bulk-in pipes cannot be halted + * if defined mass storage compliance succeeds but with warnings + * => case 4: Hi > Dn + * => case 5: Hi > Di + * => case 8: Hi <> Do + * if undefined usbtest 13 fails + * - TRACE: enable function tracing (depends on DEBUG) + * + * Main Features + * - Chapter 9 & Mass Storage Compliance with Gadget File Storage + * - Chapter 9 Compliance with Gadget Zero (STALL_IN undefined) + * - Normal & LPM support + * + * USBTEST Report + * - OK: 0-12, 13 (STALL_IN defined) & 14 + * - Not Supported: 15 & 16 (ISO) + * + * TODO List + * - OTG + * - Isochronous & Interrupt Traffic + * - Handle requests which spawns into several TDs + * - GET_STATUS(device) - always reports 0 + * - Gadget API (majority of optional features) + * - Suspend & Remote Wakeup + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ci.h" +#include "udc.h" +#include "bits.h" +#include "debug.h" + +/* MSM specific */ +#define ABS_AHBBURST (0x0090UL) +#define ABS_AHBMODE (0x0098UL) +/* UDC register map */ +static uintptr_t ci_regs_nolpm[] = { + [CAP_CAPLENGTH] = 0x000UL, + [CAP_HCCPARAMS] = 0x008UL, + [CAP_DCCPARAMS] = 0x024UL, + [CAP_TESTMODE] = 0x038UL, + [OP_USBCMD] = 0x000UL, + [OP_USBSTS] = 0x004UL, + [OP_USBINTR] = 0x008UL, + [OP_DEVICEADDR] = 0x014UL, + [OP_ENDPTLISTADDR] = 0x018UL, + [OP_PORTSC] = 0x044UL, + [OP_DEVLC] = 0x084UL, + [OP_USBMODE] = 0x068UL, + [OP_ENDPTSETUPSTAT] = 0x06CUL, + [OP_ENDPTPRIME] = 0x070UL, + [OP_ENDPTFLUSH] = 0x074UL, + [OP_ENDPTSTAT] = 0x078UL, + [OP_ENDPTCOMPLETE] = 0x07CUL, + [OP_ENDPTCTRL] = 0x080UL, +}; + +static uintptr_t ci_regs_lpm[] = { + [CAP_CAPLENGTH] = 0x000UL, + [CAP_HCCPARAMS] = 0x008UL, + [CAP_DCCPARAMS] = 0x024UL, + [CAP_TESTMODE] = 0x0FCUL, + [OP_USBCMD] = 0x000UL, + [OP_USBSTS] = 0x004UL, + [OP_USBINTR] = 0x008UL, + [OP_DEVICEADDR] = 0x014UL, + [OP_ENDPTLISTADDR] = 0x018UL, + [OP_PORTSC] = 0x044UL, + [OP_DEVLC] = 0x084UL, + [OP_USBMODE] = 0x0C8UL, + [OP_ENDPTSETUPSTAT] = 0x0D8UL, + [OP_ENDPTPRIME] = 0x0DCUL, + [OP_ENDPTFLUSH] = 0x0E0UL, + [OP_ENDPTSTAT] = 0x0E4UL, + [OP_ENDPTCOMPLETE] = 0x0E8UL, + [OP_ENDPTCTRL] = 0x0ECUL, +}; + +static int hw_alloc_regmap(struct ci13xxx *udc, bool is_lpm) +{ + int i; + + kfree(udc->hw_bank.regmap); + + udc->hw_bank.regmap = kzalloc((OP_LAST + 1) * sizeof(void *), + GFP_KERNEL); + if (!udc->hw_bank.regmap) + return -ENOMEM; + + for (i = 0; i < OP_ENDPTCTRL; i++) + udc->hw_bank.regmap[i] = + (i <= CAP_LAST ? udc->hw_bank.cap : udc->hw_bank.op) + + (is_lpm ? ci_regs_lpm[i] : ci_regs_nolpm[i]); + + for (; i <= OP_LAST; i++) + udc->hw_bank.regmap[i] = udc->hw_bank.op + + 4 * (i - OP_ENDPTCTRL) + + (is_lpm + ? ci_regs_lpm[OP_ENDPTCTRL] + : ci_regs_nolpm[OP_ENDPTCTRL]); + + return 0; +} + +/** + * hw_port_test_set: writes port test mode (execute without interruption) + * @mode: new value + * + * This function returns an error code + */ +int hw_port_test_set(struct ci13xxx *ci, u8 mode) +{ + const u8 TEST_MODE_MAX = 7; + + if (mode > TEST_MODE_MAX) + return -EINVAL; + + hw_write(ci, OP_PORTSC, PORTSC_PTC, mode << ffs_nr(PORTSC_PTC)); + return 0; +} + +/** + * hw_port_test_get: reads port test mode value + * + * This function returns port test mode value + */ +u8 hw_port_test_get(struct ci13xxx *ci) +{ + return hw_read(ci, OP_PORTSC, PORTSC_PTC) >> ffs_nr(PORTSC_PTC); +} + +int hw_device_init(struct ci13xxx *udc, void __iomem *base, + uintptr_t cap_offset) +{ + u32 reg; + + /* bank is a module variable */ + udc->hw_bank.abs = base; + + udc->hw_bank.cap = udc->hw_bank.abs; + udc->hw_bank.cap += cap_offset; + udc->hw_bank.op = udc->hw_bank.cap + ioread8(udc->hw_bank.cap); + + hw_alloc_regmap(udc, false); + reg = hw_read(udc, CAP_HCCPARAMS, HCCPARAMS_LEN) >> + ffs_nr(HCCPARAMS_LEN); + udc->hw_bank.lpm = reg; + hw_alloc_regmap(udc, !!reg); + udc->hw_bank.size = udc->hw_bank.op - udc->hw_bank.abs; + udc->hw_bank.size += OP_LAST; + udc->hw_bank.size /= sizeof(u32); + + reg = hw_read(udc, CAP_DCCPARAMS, DCCPARAMS_DEN) >> + ffs_nr(DCCPARAMS_DEN); + udc->hw_ep_max = reg * 2; /* cache hw ENDPT_MAX */ + + if (udc->hw_ep_max == 0 || udc->hw_ep_max > ENDPT_MAX) + return -ENODEV; + + dev_dbg(udc->dev, "ChipIdea UDC found, lpm: %d; cap: %p op: %p\n", + udc->hw_bank.lpm, udc->hw_bank.cap, udc->hw_bank.op); + + /* setup lock mode ? */ + + /* ENDPTSETUPSTAT is '0' by default */ + + /* HCSPARAMS.bf.ppc SHOULD BE zero for device */ + + return 0; +} + +/** + * hw_device_reset: resets chip (execute without interruption) + * @ci: the controller + * + * This function returns an error code + */ +int hw_device_reset(struct ci13xxx *ci) +{ + /* should flush & stop before reset */ + hw_write(ci, OP_ENDPTFLUSH, ~0, ~0); + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); + + hw_write(ci, OP_USBCMD, USBCMD_RST, USBCMD_RST); + while (hw_read(ci, OP_USBCMD, USBCMD_RST)) + udelay(10); /* not RTOS friendly */ + + + if (ci->udc_driver->notify_event) + ci->udc_driver->notify_event(ci, + CI13XXX_CONTROLLER_RESET_EVENT); + + if (ci->udc_driver->flags & CI13XXX_DISABLE_STREAMING) + hw_write(ci, OP_USBMODE, USBMODE_SDIS, USBMODE_SDIS); + + /* USBMODE should be configured step by step */ + hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE); + hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_DEVICE); + /* HW >= 2.3 */ + hw_write(ci, OP_USBMODE, USBMODE_SLOM, USBMODE_SLOM); + + if (hw_read(ci, OP_USBMODE, USBMODE_CM) != USBMODE_CM_DEVICE) { + pr_err("cannot enter in device mode"); + pr_err("lpm = %i", ci->hw_bank.lpm); + return -ENODEV; + } + + return 0; +} + +static int __devinit ci_udc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ci13xxx_udc_driver *driver = dev->platform_data; + struct ci13xxx *udc; + struct resource *res; + void __iomem *base; + int ret; + + if (!driver) { + dev_err(dev, "platform data missing\n"); + return -ENODEV; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "missing resource\n"); + return -ENODEV; + } + + base = devm_request_and_ioremap(dev, res); + if (!res) { + dev_err(dev, "can't request and ioremap resource\n"); + return -ENOMEM; + } + + ret = udc_probe(driver, dev, base, &udc); + if (ret) + return ret; + + udc->irq = platform_get_irq(pdev, 0); + if (udc->irq < 0) { + dev_err(dev, "missing IRQ\n"); + ret = -ENODEV; + goto out; + } + + platform_set_drvdata(pdev, udc); + ret = request_irq(udc->irq, udc_irq, IRQF_SHARED, driver->name, udc); + +out: + if (ret) + udc_remove(udc); + + return ret; +} + +static int __devexit ci_udc_remove(struct platform_device *pdev) +{ + struct ci13xxx *udc = platform_get_drvdata(pdev); + + free_irq(udc->irq, udc); + udc_remove(udc); + + return 0; +} + +static struct platform_driver ci_udc_driver = { + .probe = ci_udc_probe, + .remove = __devexit_p(ci_udc_remove), + .driver = { + .name = "ci_udc", + }, +}; + +module_platform_driver(ci_udc_driver); + +MODULE_ALIAS("platform:ci_udc"); +MODULE_ALIAS("platform:ci13xxx"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("David Lopo "); +MODULE_DESCRIPTION("ChipIdea UDC Driver"); diff --git a/drivers/usb/chipidea/debug.c b/drivers/usb/chipidea/debug.c new file mode 100644 index 000000000000..c4b3e15532db --- /dev/null +++ b/drivers/usb/chipidea/debug.c @@ -0,0 +1,804 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ci.h" +#include "udc.h" +#include "bits.h" +#include "debug.h" + +/* Interrupt statistics */ +#define ISR_MASK 0x1F +static struct isr_statistics { + u32 test; + u32 ui; + u32 uei; + u32 pci; + u32 uri; + u32 sli; + u32 none; + struct { + u32 cnt; + u32 buf[ISR_MASK+1]; + u32 idx; + } hndl; +} isr_statistics; + +void dbg_interrupt(u32 intmask) +{ + if (!intmask) { + isr_statistics.none++; + return; + } + + isr_statistics.hndl.buf[isr_statistics.hndl.idx++] = intmask; + isr_statistics.hndl.idx &= ISR_MASK; + isr_statistics.hndl.cnt++; + + if (USBi_URI & intmask) + isr_statistics.uri++; + if (USBi_PCI & intmask) + isr_statistics.pci++; + if (USBi_UEI & intmask) + isr_statistics.uei++; + if (USBi_UI & intmask) + isr_statistics.ui++; + if (USBi_SLI & intmask) + isr_statistics.sli++; +} + +/** + * hw_register_read: reads all device registers (execute without interruption) + * @buf: destination buffer + * @size: buffer size + * + * This function returns number of registers read + */ +static size_t hw_register_read(struct ci13xxx *udc, u32 *buf, size_t size) +{ + unsigned i; + + if (size > udc->hw_bank.size) + size = udc->hw_bank.size; + + for (i = 0; i < size; i++) + buf[i] = hw_read(udc, i * sizeof(u32), ~0); + + return size; +} + +/** + * hw_register_write: writes to register + * @addr: register address + * @data: register value + * + * This function returns an error code + */ +static int hw_register_write(struct ci13xxx *udc, u16 addr, u32 data) +{ + /* align */ + addr /= sizeof(u32); + + if (addr >= udc->hw_bank.size) + return -EINVAL; + + /* align */ + addr *= sizeof(u32); + + hw_write(udc, addr, ~0, data); + return 0; +} + +/** + * hw_intr_clear: disables interrupt & clears interrupt status (execute without + * interruption) + * @n: interrupt bit + * + * This function returns an error code + */ +static int hw_intr_clear(struct ci13xxx *udc, int n) +{ + if (n >= REG_BITS) + return -EINVAL; + + hw_write(udc, OP_USBINTR, BIT(n), 0); + hw_write(udc, OP_USBSTS, BIT(n), BIT(n)); + return 0; +} + +/** + * hw_intr_force: enables interrupt & forces interrupt status (execute without + * interruption) + * @n: interrupt bit + * + * This function returns an error code + */ +static int hw_intr_force(struct ci13xxx *udc, int n) +{ + if (n >= REG_BITS) + return -EINVAL; + + hw_write(udc, CAP_TESTMODE, TESTMODE_FORCE, TESTMODE_FORCE); + hw_write(udc, OP_USBINTR, BIT(n), BIT(n)); + hw_write(udc, OP_USBSTS, BIT(n), BIT(n)); + hw_write(udc, CAP_TESTMODE, TESTMODE_FORCE, 0); + return 0; +} + +/** + * show_device: prints information about device capabilities and status + * + * Check "device.h" for details + */ +static ssize_t show_device(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + struct usb_gadget *gadget = &udc->gadget; + int n = 0; + + if (attr == NULL || buf == NULL) { + dev_err(udc->dev, "[%s] EINVAL\n", __func__); + return 0; + } + + n += scnprintf(buf + n, PAGE_SIZE - n, "speed = %d\n", + gadget->speed); + n += scnprintf(buf + n, PAGE_SIZE - n, "max_speed = %d\n", + gadget->max_speed); + /* TODO: Scheduled for removal in 3.8. */ + n += scnprintf(buf + n, PAGE_SIZE - n, "is_dualspeed = %d\n", + gadget_is_dualspeed(gadget)); + n += scnprintf(buf + n, PAGE_SIZE - n, "is_otg = %d\n", + gadget->is_otg); + n += scnprintf(buf + n, PAGE_SIZE - n, "is_a_peripheral = %d\n", + gadget->is_a_peripheral); + n += scnprintf(buf + n, PAGE_SIZE - n, "b_hnp_enable = %d\n", + gadget->b_hnp_enable); + n += scnprintf(buf + n, PAGE_SIZE - n, "a_hnp_support = %d\n", + gadget->a_hnp_support); + n += scnprintf(buf + n, PAGE_SIZE - n, "a_alt_hnp_support = %d\n", + gadget->a_alt_hnp_support); + n += scnprintf(buf + n, PAGE_SIZE - n, "name = %s\n", + (gadget->name ? gadget->name : "")); + + return n; +} +static DEVICE_ATTR(device, S_IRUSR, show_device, NULL); + +/** + * show_driver: prints information about attached gadget (if any) + * + * Check "device.h" for details + */ +static ssize_t show_driver(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + struct usb_gadget_driver *driver = udc->driver; + int n = 0; + + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + return 0; + } + + if (driver == NULL) + return scnprintf(buf, PAGE_SIZE, + "There is no gadget attached!\n"); + + n += scnprintf(buf + n, PAGE_SIZE - n, "function = %s\n", + (driver->function ? driver->function : "")); + n += scnprintf(buf + n, PAGE_SIZE - n, "max speed = %d\n", + driver->max_speed); + + return n; +} +static DEVICE_ATTR(driver, S_IRUSR, show_driver, NULL); + +/* Maximum event message length */ +#define DBG_DATA_MSG 64UL + +/* Maximum event messages */ +#define DBG_DATA_MAX 128UL + +/* Event buffer descriptor */ +static struct { + char (buf[DBG_DATA_MAX])[DBG_DATA_MSG]; /* buffer */ + unsigned idx; /* index */ + unsigned tty; /* print to console? */ + rwlock_t lck; /* lock */ +} dbg_data = { + .idx = 0, + .tty = 0, + .lck = __RW_LOCK_UNLOCKED(lck) +}; + +/** + * dbg_dec: decrements debug event index + * @idx: buffer index + */ +static void dbg_dec(unsigned *idx) +{ + *idx = (*idx - 1) & (DBG_DATA_MAX-1); +} + +/** + * dbg_inc: increments debug event index + * @idx: buffer index + */ +static void dbg_inc(unsigned *idx) +{ + *idx = (*idx + 1) & (DBG_DATA_MAX-1); +} + +/** + * dbg_print: prints the common part of the event + * @addr: endpoint address + * @name: event name + * @status: status + * @extra: extra information + */ +static void dbg_print(u8 addr, const char *name, int status, const char *extra) +{ + struct timeval tval; + unsigned int stamp; + unsigned long flags; + + write_lock_irqsave(&dbg_data.lck, flags); + + do_gettimeofday(&tval); + stamp = tval.tv_sec & 0xFFFF; /* 2^32 = 4294967296. Limit to 4096s */ + stamp = stamp * 1000000 + tval.tv_usec; + + scnprintf(dbg_data.buf[dbg_data.idx], DBG_DATA_MSG, + "%04X\t? %02X %-7.7s %4i ?\t%s\n", + stamp, addr, name, status, extra); + + dbg_inc(&dbg_data.idx); + + write_unlock_irqrestore(&dbg_data.lck, flags); + + if (dbg_data.tty != 0) + pr_notice("%04X\t? %02X %-7.7s %4i ?\t%s\n", + stamp, addr, name, status, extra); +} + +/** + * dbg_done: prints a DONE event + * @addr: endpoint address + * @td: transfer descriptor + * @status: status + */ +void dbg_done(u8 addr, const u32 token, int status) +{ + char msg[DBG_DATA_MSG]; + + scnprintf(msg, sizeof(msg), "%d %02X", + (int)(token & TD_TOTAL_BYTES) >> ffs_nr(TD_TOTAL_BYTES), + (int)(token & TD_STATUS) >> ffs_nr(TD_STATUS)); + dbg_print(addr, "DONE", status, msg); +} + +/** + * dbg_event: prints a generic event + * @addr: endpoint address + * @name: event name + * @status: status + */ +void dbg_event(u8 addr, const char *name, int status) +{ + if (name != NULL) + dbg_print(addr, name, status, ""); +} + +/* + * dbg_queue: prints a QUEUE event + * @addr: endpoint address + * @req: USB request + * @status: status + */ +void dbg_queue(u8 addr, const struct usb_request *req, int status) +{ + char msg[DBG_DATA_MSG]; + + if (req != NULL) { + scnprintf(msg, sizeof(msg), + "%d %d", !req->no_interrupt, req->length); + dbg_print(addr, "QUEUE", status, msg); + } +} + +/** + * dbg_setup: prints a SETUP event + * @addr: endpoint address + * @req: setup request + */ +void dbg_setup(u8 addr, const struct usb_ctrlrequest *req) +{ + char msg[DBG_DATA_MSG]; + + if (req != NULL) { + scnprintf(msg, sizeof(msg), + "%02X %02X %04X %04X %d", req->bRequestType, + req->bRequest, le16_to_cpu(req->wValue), + le16_to_cpu(req->wIndex), le16_to_cpu(req->wLength)); + dbg_print(addr, "SETUP", 0, msg); + } +} + +/** + * show_events: displays the event buffer + * + * Check "device.h" for details + */ +static ssize_t show_events(struct device *dev, struct device_attribute *attr, + char *buf) +{ + unsigned long flags; + unsigned i, j, n = 0; + + if (attr == NULL || buf == NULL) { + dev_err(dev->parent, "[%s] EINVAL\n", __func__); + return 0; + } + + read_lock_irqsave(&dbg_data.lck, flags); + + i = dbg_data.idx; + for (dbg_dec(&i); i != dbg_data.idx; dbg_dec(&i)) { + n += strlen(dbg_data.buf[i]); + if (n >= PAGE_SIZE) { + n -= strlen(dbg_data.buf[i]); + break; + } + } + for (j = 0, dbg_inc(&i); j < n; dbg_inc(&i)) + j += scnprintf(buf + j, PAGE_SIZE - j, + "%s", dbg_data.buf[i]); + + read_unlock_irqrestore(&dbg_data.lck, flags); + + return n; +} + +/** + * store_events: configure if events are going to be also printed to console + * + * Check "device.h" for details + */ +static ssize_t store_events(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned tty; + + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + goto done; + } + + if (sscanf(buf, "%u", &tty) != 1 || tty > 1) { + dev_err(dev, "<1|0>: enable|disable console log\n"); + goto done; + } + + dbg_data.tty = tty; + dev_info(dev, "tty = %u", dbg_data.tty); + + done: + return count; +} +static DEVICE_ATTR(events, S_IRUSR | S_IWUSR, show_events, store_events); + +/** + * show_inters: interrupt status, enable status and historic + * + * Check "device.h" for details + */ +static ssize_t show_inters(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + u32 intr; + unsigned i, j, n = 0; + + if (attr == NULL || buf == NULL) { + dev_err(udc->dev, "[%s] EINVAL\n", __func__); + return 0; + } + + spin_lock_irqsave(&udc->lock, flags); + + /*n += scnprintf(buf + n, PAGE_SIZE - n, + "status = %08x\n", hw_read_intr_status(udc)); + n += scnprintf(buf + n, PAGE_SIZE - n, + "enable = %08x\n", hw_read_intr_enable(udc));*/ + + n += scnprintf(buf + n, PAGE_SIZE - n, "*test = %d\n", + isr_statistics.test); + n += scnprintf(buf + n, PAGE_SIZE - n, "? ui = %d\n", + isr_statistics.ui); + n += scnprintf(buf + n, PAGE_SIZE - n, "? uei = %d\n", + isr_statistics.uei); + n += scnprintf(buf + n, PAGE_SIZE - n, "? pci = %d\n", + isr_statistics.pci); + n += scnprintf(buf + n, PAGE_SIZE - n, "? uri = %d\n", + isr_statistics.uri); + n += scnprintf(buf + n, PAGE_SIZE - n, "? sli = %d\n", + isr_statistics.sli); + n += scnprintf(buf + n, PAGE_SIZE - n, "*none = %d\n", + isr_statistics.none); + n += scnprintf(buf + n, PAGE_SIZE - n, "*hndl = %d\n", + isr_statistics.hndl.cnt); + + for (i = isr_statistics.hndl.idx, j = 0; j <= ISR_MASK; j++, i++) { + i &= ISR_MASK; + intr = isr_statistics.hndl.buf[i]; + + if (USBi_UI & intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "ui "); + intr &= ~USBi_UI; + if (USBi_UEI & intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "uei "); + intr &= ~USBi_UEI; + if (USBi_PCI & intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "pci "); + intr &= ~USBi_PCI; + if (USBi_URI & intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "uri "); + intr &= ~USBi_URI; + if (USBi_SLI & intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "sli "); + intr &= ~USBi_SLI; + if (intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "??? "); + if (isr_statistics.hndl.buf[i]) + n += scnprintf(buf + n, PAGE_SIZE - n, "\n"); + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return n; +} + +/** + * store_inters: enable & force or disable an individual interrutps + * (to be used for test purposes only) + * + * Check "device.h" for details + */ +static ssize_t store_inters(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + unsigned en, bit; + + if (attr == NULL || buf == NULL) { + dev_err(udc->dev, "EINVAL\n"); + goto done; + } + + if (sscanf(buf, "%u %u", &en, &bit) != 2 || en > 1) { + dev_err(udc->dev, "<1|0> : enable|disable interrupt\n"); + goto done; + } + + spin_lock_irqsave(&udc->lock, flags); + if (en) { + if (hw_intr_force(udc, bit)) + dev_err(dev, "invalid bit number\n"); + else + isr_statistics.test++; + } else { + if (hw_intr_clear(udc, bit)) + dev_err(dev, "invalid bit number\n"); + } + spin_unlock_irqrestore(&udc->lock, flags); + + done: + return count; +} +static DEVICE_ATTR(inters, S_IRUSR | S_IWUSR, show_inters, store_inters); + +/** + * show_port_test: reads port test mode + * + * Check "device.h" for details + */ +static ssize_t show_port_test(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + unsigned mode; + + if (attr == NULL || buf == NULL) { + dev_err(udc->dev, "EINVAL\n"); + return 0; + } + + spin_lock_irqsave(&udc->lock, flags); + mode = hw_port_test_get(udc); + spin_unlock_irqrestore(&udc->lock, flags); + + return scnprintf(buf, PAGE_SIZE, "mode = %u\n", mode); +} + +/** + * store_port_test: writes port test mode + * + * Check "device.h" for details + */ +static ssize_t store_port_test(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + unsigned mode; + + if (attr == NULL || buf == NULL) { + dev_err(udc->dev, "[%s] EINVAL\n", __func__); + goto done; + } + + if (sscanf(buf, "%u", &mode) != 1) { + dev_err(udc->dev, ": set port test mode"); + goto done; + } + + spin_lock_irqsave(&udc->lock, flags); + if (hw_port_test_set(udc, mode)) + dev_err(udc->dev, "invalid mode\n"); + spin_unlock_irqrestore(&udc->lock, flags); + + done: + return count; +} +static DEVICE_ATTR(port_test, S_IRUSR | S_IWUSR, + show_port_test, store_port_test); + +/** + * show_qheads: DMA contents of all queue heads + * + * Check "device.h" for details + */ +static ssize_t show_qheads(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + unsigned i, j, n = 0; + + if (attr == NULL || buf == NULL) { + dev_err(udc->dev, "[%s] EINVAL\n", __func__); + return 0; + } + + spin_lock_irqsave(&udc->lock, flags); + for (i = 0; i < udc->hw_ep_max/2; i++) { + struct ci13xxx_ep *mEpRx = &udc->ci13xxx_ep[i]; + struct ci13xxx_ep *mEpTx = + &udc->ci13xxx_ep[i + udc->hw_ep_max/2]; + n += scnprintf(buf + n, PAGE_SIZE - n, + "EP=%02i: RX=%08X TX=%08X\n", + i, (u32)mEpRx->qh.dma, (u32)mEpTx->qh.dma); + for (j = 0; j < (sizeof(struct ci13xxx_qh)/sizeof(u32)); j++) { + n += scnprintf(buf + n, PAGE_SIZE - n, + " %04X: %08X %08X\n", j, + *((u32 *)mEpRx->qh.ptr + j), + *((u32 *)mEpTx->qh.ptr + j)); + } + } + spin_unlock_irqrestore(&udc->lock, flags); + + return n; +} +static DEVICE_ATTR(qheads, S_IRUSR, show_qheads, NULL); + +/** + * show_registers: dumps all registers + * + * Check "device.h" for details + */ +#define DUMP_ENTRIES 512 +static ssize_t show_registers(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + u32 *dump; + unsigned i, k, n = 0; + + if (attr == NULL || buf == NULL) { + dev_err(udc->dev, "[%s] EINVAL\n", __func__); + return 0; + } + + dump = kmalloc(sizeof(u32) * DUMP_ENTRIES, GFP_KERNEL); + if (!dump) { + dev_err(udc->dev, "%s: out of memory\n", __func__); + return 0; + } + + spin_lock_irqsave(&udc->lock, flags); + k = hw_register_read(udc, dump, DUMP_ENTRIES); + spin_unlock_irqrestore(&udc->lock, flags); + + for (i = 0; i < k; i++) { + n += scnprintf(buf + n, PAGE_SIZE - n, + "reg[0x%04X] = 0x%08X\n", + i * (unsigned)sizeof(u32), dump[i]); + } + kfree(dump); + + return n; +} + +/** + * store_registers: writes value to register address + * + * Check "device.h" for details + */ +static ssize_t store_registers(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long addr, data, flags; + + if (attr == NULL || buf == NULL) { + dev_err(udc->dev, "[%s] EINVAL\n", __func__); + goto done; + } + + if (sscanf(buf, "%li %li", &addr, &data) != 2) { + dev_err(udc->dev, + " : write data to register address\n"); + goto done; + } + + spin_lock_irqsave(&udc->lock, flags); + if (hw_register_write(udc, addr, data)) + dev_err(udc->dev, "invalid address range\n"); + spin_unlock_irqrestore(&udc->lock, flags); + + done: + return count; +} +static DEVICE_ATTR(registers, S_IRUSR | S_IWUSR, + show_registers, store_registers); + +/** + * show_requests: DMA contents of all requests currently queued (all endpts) + * + * Check "device.h" for details + */ +static ssize_t show_requests(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + struct list_head *ptr = NULL; + struct ci13xxx_req *req = NULL; + unsigned i, j, n = 0, qSize = sizeof(struct ci13xxx_td)/sizeof(u32); + + if (attr == NULL || buf == NULL) { + dev_err(udc->dev, "[%s] EINVAL\n", __func__); + return 0; + } + + spin_lock_irqsave(&udc->lock, flags); + for (i = 0; i < udc->hw_ep_max; i++) + list_for_each(ptr, &udc->ci13xxx_ep[i].qh.queue) + { + req = list_entry(ptr, struct ci13xxx_req, queue); + + n += scnprintf(buf + n, PAGE_SIZE - n, + "EP=%02i: TD=%08X %s\n", + i % udc->hw_ep_max/2, (u32)req->dma, + ((i < udc->hw_ep_max/2) ? "RX" : "TX")); + + for (j = 0; j < qSize; j++) + n += scnprintf(buf + n, PAGE_SIZE - n, + " %04X: %08X\n", j, + *((u32 *)req->ptr + j)); + } + spin_unlock_irqrestore(&udc->lock, flags); + + return n; +} +static DEVICE_ATTR(requests, S_IRUSR, show_requests, NULL); + +/** + * dbg_create_files: initializes the attribute interface + * @dev: device + * + * This function returns an error code + */ +int dbg_create_files(struct device *dev) +{ + int retval = 0; + + if (dev == NULL) + return -EINVAL; + retval = device_create_file(dev, &dev_attr_device); + if (retval) + goto done; + retval = device_create_file(dev, &dev_attr_driver); + if (retval) + goto rm_device; + retval = device_create_file(dev, &dev_attr_events); + if (retval) + goto rm_driver; + retval = device_create_file(dev, &dev_attr_inters); + if (retval) + goto rm_events; + retval = device_create_file(dev, &dev_attr_port_test); + if (retval) + goto rm_inters; + retval = device_create_file(dev, &dev_attr_qheads); + if (retval) + goto rm_port_test; + retval = device_create_file(dev, &dev_attr_registers); + if (retval) + goto rm_qheads; + retval = device_create_file(dev, &dev_attr_requests); + if (retval) + goto rm_registers; + return 0; + + rm_registers: + device_remove_file(dev, &dev_attr_registers); + rm_qheads: + device_remove_file(dev, &dev_attr_qheads); + rm_port_test: + device_remove_file(dev, &dev_attr_port_test); + rm_inters: + device_remove_file(dev, &dev_attr_inters); + rm_events: + device_remove_file(dev, &dev_attr_events); + rm_driver: + device_remove_file(dev, &dev_attr_driver); + rm_device: + device_remove_file(dev, &dev_attr_device); + done: + return retval; +} + +/** + * dbg_remove_files: destroys the attribute interface + * @dev: device + * + * This function returns an error code + */ +int dbg_remove_files(struct device *dev) +{ + if (dev == NULL) + return -EINVAL; + device_remove_file(dev, &dev_attr_requests); + device_remove_file(dev, &dev_attr_registers); + device_remove_file(dev, &dev_attr_qheads); + device_remove_file(dev, &dev_attr_port_test); + device_remove_file(dev, &dev_attr_inters); + device_remove_file(dev, &dev_attr_events); + device_remove_file(dev, &dev_attr_driver); + device_remove_file(dev, &dev_attr_device); + return 0; +} diff --git a/drivers/usb/chipidea/debug.h b/drivers/usb/chipidea/debug.h new file mode 100644 index 000000000000..80d96865775c --- /dev/null +++ b/drivers/usb/chipidea/debug.h @@ -0,0 +1,56 @@ +/* + * debug.h - ChipIdea USB driver debug interfaces + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __DRIVERS_USB_CHIPIDEA_DEBUG_H +#define __DRIVERS_USB_CHIPIDEA_DEBUG_H + +#ifdef CONFIG_USB_CHIPIDEA_DEBUG +void dbg_interrupt(u32 intmask); +void dbg_done(u8 addr, const u32 token, int status); +void dbg_event(u8 addr, const char *name, int status); +void dbg_queue(u8 addr, const struct usb_request *req, int status); +void dbg_setup(u8 addr, const struct usb_ctrlrequest *req); +int dbg_create_files(struct device *dev); +int dbg_remove_files(struct device *dev); +#else +static inline void dbg_interrupt(u32 intmask) +{ +} + +static inline void dbg_done(u8 addr, const u32 token, int status) +{ +} + +static inline void dbg_event(u8 addr, const char *name, int status) +{ +} + +static inline void dbg_queue(u8 addr, const struct usb_request *req, int status) +{ +} + +static inline void dbg_setup(u8 addr, const struct usb_ctrlrequest *req) +{ +} + +static inline int dbg_create_files(struct device *dev) +{ + return 0; +} + +static inline int dbg_remove_files(struct device *dev) +{ + return 0; +} +#endif + +#endif /* __DRIVERS_USB_CHIPIDEA_DEBUG_H */ diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c new file mode 100644 index 000000000000..6866ef085397 --- /dev/null +++ b/drivers/usb/chipidea/udc.c @@ -0,0 +1,1837 @@ +/* + * udc.h - ChipIdea UDC driver + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ci.h" +#include "udc.h" +#include "bits.h" +#include "debug.h" + +/* control endpoint description */ +static const struct usb_endpoint_descriptor +ctrl_endpt_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = cpu_to_le16(CTRL_PAYLOAD_MAX), +}; + +static const struct usb_endpoint_descriptor +ctrl_endpt_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = cpu_to_le16(CTRL_PAYLOAD_MAX), +}; + +/** + * hw_ep_bit: calculates the bit number + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns bit number + */ +static inline int hw_ep_bit(int num, int dir) +{ + return num + (dir ? 16 : 0); +} + +static inline int ep_to_bit(struct ci13xxx *udc, int n) +{ + int fill = 16 - udc->hw_ep_max / 2; + + if (n >= udc->hw_ep_max / 2) + n += fill; + + return n; +} + +/** + * hw_device_state: enables/disables interrupts & starts/stops device (execute + * without interruption) + * @dma: 0 => disable, !0 => enable and set dma engine + * + * This function returns an error code + */ +static int hw_device_state(struct ci13xxx *udc, u32 dma) +{ + if (dma) { + hw_write(udc, OP_ENDPTLISTADDR, ~0, dma); + /* interrupt, error, port change, reset, sleep/suspend */ + hw_write(udc, OP_USBINTR, ~0, + USBi_UI|USBi_UEI|USBi_PCI|USBi_URI|USBi_SLI); + hw_write(udc, OP_USBCMD, USBCMD_RS, USBCMD_RS); + } else { + hw_write(udc, OP_USBCMD, USBCMD_RS, 0); + hw_write(udc, OP_USBINTR, ~0, 0); + } + return 0; +} + +/** + * hw_ep_flush: flush endpoint fifo (execute without interruption) + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns an error code + */ +static int hw_ep_flush(struct ci13xxx *udc, int num, int dir) +{ + int n = hw_ep_bit(num, dir); + + do { + /* flush any pending transfer */ + hw_write(udc, OP_ENDPTFLUSH, BIT(n), BIT(n)); + while (hw_read(udc, OP_ENDPTFLUSH, BIT(n))) + cpu_relax(); + } while (hw_read(udc, OP_ENDPTSTAT, BIT(n))); + + return 0; +} + +/** + * hw_ep_disable: disables endpoint (execute without interruption) + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns an error code + */ +static int hw_ep_disable(struct ci13xxx *udc, int num, int dir) +{ + hw_ep_flush(udc, num, dir); + hw_write(udc, OP_ENDPTCTRL + num, + dir ? ENDPTCTRL_TXE : ENDPTCTRL_RXE, 0); + return 0; +} + +/** + * hw_ep_enable: enables endpoint (execute without interruption) + * @num: endpoint number + * @dir: endpoint direction + * @type: endpoint type + * + * This function returns an error code + */ +static int hw_ep_enable(struct ci13xxx *udc, int num, int dir, int type) +{ + u32 mask, data; + + if (dir) { + mask = ENDPTCTRL_TXT; /* type */ + data = type << ffs_nr(mask); + + mask |= ENDPTCTRL_TXS; /* unstall */ + mask |= ENDPTCTRL_TXR; /* reset data toggle */ + data |= ENDPTCTRL_TXR; + mask |= ENDPTCTRL_TXE; /* enable */ + data |= ENDPTCTRL_TXE; + } else { + mask = ENDPTCTRL_RXT; /* type */ + data = type << ffs_nr(mask); + + mask |= ENDPTCTRL_RXS; /* unstall */ + mask |= ENDPTCTRL_RXR; /* reset data toggle */ + data |= ENDPTCTRL_RXR; + mask |= ENDPTCTRL_RXE; /* enable */ + data |= ENDPTCTRL_RXE; + } + hw_write(udc, OP_ENDPTCTRL + num, mask, data); + return 0; +} + +/** + * hw_ep_get_halt: return endpoint halt status + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns 1 if endpoint halted + */ +static int hw_ep_get_halt(struct ci13xxx *udc, int num, int dir) +{ + u32 mask = dir ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; + + return hw_read(udc, OP_ENDPTCTRL + num, mask) ? 1 : 0; +} + +/** + * hw_test_and_clear_setup_status: test & clear setup status (execute without + * interruption) + * @n: endpoint number + * + * This function returns setup status + */ +static int hw_test_and_clear_setup_status(struct ci13xxx *udc, int n) +{ + n = ep_to_bit(udc, n); + return hw_test_and_clear(udc, OP_ENDPTSETUPSTAT, BIT(n)); +} + +/** + * hw_ep_prime: primes endpoint (execute without interruption) + * @num: endpoint number + * @dir: endpoint direction + * @is_ctrl: true if control endpoint + * + * This function returns an error code + */ +static int hw_ep_prime(struct ci13xxx *udc, int num, int dir, int is_ctrl) +{ + int n = hw_ep_bit(num, dir); + + if (is_ctrl && dir == RX && hw_read(udc, OP_ENDPTSETUPSTAT, BIT(num))) + return -EAGAIN; + + hw_write(udc, OP_ENDPTPRIME, BIT(n), BIT(n)); + + while (hw_read(udc, OP_ENDPTPRIME, BIT(n))) + cpu_relax(); + if (is_ctrl && dir == RX && hw_read(udc, OP_ENDPTSETUPSTAT, BIT(num))) + return -EAGAIN; + + /* status shoult be tested according with manual but it doesn't work */ + return 0; +} + +/** + * hw_ep_set_halt: configures ep halt & resets data toggle after clear (execute + * without interruption) + * @num: endpoint number + * @dir: endpoint direction + * @value: true => stall, false => unstall + * + * This function returns an error code + */ +static int hw_ep_set_halt(struct ci13xxx *udc, int num, int dir, int value) +{ + if (value != 0 && value != 1) + return -EINVAL; + + do { + enum ci13xxx_regs reg = OP_ENDPTCTRL + num; + u32 mask_xs = dir ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; + u32 mask_xr = dir ? ENDPTCTRL_TXR : ENDPTCTRL_RXR; + + /* data toggle - reserved for EP0 but it's in ESS */ + hw_write(udc, reg, mask_xs|mask_xr, + value ? mask_xs : mask_xr); + } while (value != hw_ep_get_halt(udc, num, dir)); + + return 0; +} + +/** + * hw_is_port_high_speed: test if port is high speed + * + * This function returns true if high speed port + */ +static int hw_port_is_high_speed(struct ci13xxx *udc) +{ + return udc->hw_bank.lpm ? hw_read(udc, OP_DEVLC, DEVLC_PSPD) : + hw_read(udc, OP_PORTSC, PORTSC_HSP); +} + +/** + * hw_read_intr_enable: returns interrupt enable register + * + * This function returns register data + */ +static u32 hw_read_intr_enable(struct ci13xxx *udc) +{ + return hw_read(udc, OP_USBINTR, ~0); +} + +/** + * hw_read_intr_status: returns interrupt status register + * + * This function returns register data + */ +static u32 hw_read_intr_status(struct ci13xxx *udc) +{ + return hw_read(udc, OP_USBSTS, ~0); +} + +/** + * hw_test_and_clear_complete: test & clear complete status (execute without + * interruption) + * @n: endpoint number + * + * This function returns complete status + */ +static int hw_test_and_clear_complete(struct ci13xxx *udc, int n) +{ + n = ep_to_bit(udc, n); + return hw_test_and_clear(udc, OP_ENDPTCOMPLETE, BIT(n)); +} + +/** + * hw_test_and_clear_intr_active: test & clear active interrupts (execute + * without interruption) + * + * This function returns active interrutps + */ +static u32 hw_test_and_clear_intr_active(struct ci13xxx *udc) +{ + u32 reg = hw_read_intr_status(udc) & hw_read_intr_enable(udc); + + hw_write(udc, OP_USBSTS, ~0, reg); + return reg; +} + +/** + * hw_test_and_clear_setup_guard: test & clear setup guard (execute without + * interruption) + * + * This function returns guard value + */ +static int hw_test_and_clear_setup_guard(struct ci13xxx *udc) +{ + return hw_test_and_write(udc, OP_USBCMD, USBCMD_SUTW, 0); +} + +/** + * hw_test_and_set_setup_guard: test & set setup guard (execute without + * interruption) + * + * This function returns guard value + */ +static int hw_test_and_set_setup_guard(struct ci13xxx *udc) +{ + return hw_test_and_write(udc, OP_USBCMD, USBCMD_SUTW, USBCMD_SUTW); +} + +/** + * hw_usb_set_address: configures USB address (execute without interruption) + * @value: new USB address + * + * This function explicitly sets the address, without the "USBADRA" (advance) + * feature, which is not supported by older versions of the controller. + */ +static void hw_usb_set_address(struct ci13xxx *udc, u8 value) +{ + hw_write(udc, OP_DEVICEADDR, DEVICEADDR_USBADR, + value << ffs_nr(DEVICEADDR_USBADR)); +} + +/** + * hw_usb_reset: restart device after a bus reset (execute without + * interruption) + * + * This function returns an error code + */ +static int hw_usb_reset(struct ci13xxx *udc) +{ + hw_usb_set_address(udc, 0); + + /* ESS flushes only at end?!? */ + hw_write(udc, OP_ENDPTFLUSH, ~0, ~0); + + /* clear setup token semaphores */ + hw_write(udc, OP_ENDPTSETUPSTAT, 0, 0); + + /* clear complete status */ + hw_write(udc, OP_ENDPTCOMPLETE, 0, 0); + + /* wait until all bits cleared */ + while (hw_read(udc, OP_ENDPTPRIME, ~0)) + udelay(10); /* not RTOS friendly */ + + /* reset all endpoints ? */ + + /* reset internal status and wait for further instructions + no need to verify the port reset status (ESS does it) */ + + return 0; +} + +/****************************************************************************** + * UTIL block + *****************************************************************************/ +/** + * _usb_addr: calculates endpoint address from direction & number + * @ep: endpoint + */ +static inline u8 _usb_addr(struct ci13xxx_ep *ep) +{ + return ((ep->dir == TX) ? USB_ENDPOINT_DIR_MASK : 0) | ep->num; +} + +/** + * _hardware_queue: configures a request at hardware level + * @gadget: gadget + * @mEp: endpoint + * + * This function returns an error code + */ +static int _hardware_enqueue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq) +{ + struct ci13xxx *udc = mEp->udc; + unsigned i; + int ret = 0; + unsigned length = mReq->req.length; + + /* don't queue twice */ + if (mReq->req.status == -EALREADY) + return -EALREADY; + + mReq->req.status = -EALREADY; + if (length && mReq->req.dma == DMA_ADDR_INVALID) { + mReq->req.dma = \ + dma_map_single(mEp->device, mReq->req.buf, + length, mEp->dir ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + if (mReq->req.dma == 0) + return -ENOMEM; + + mReq->map = 1; + } + + if (mReq->req.zero && length && (length % mEp->ep.maxpacket == 0)) { + mReq->zptr = dma_pool_alloc(mEp->td_pool, GFP_ATOMIC, + &mReq->zdma); + if (mReq->zptr == NULL) { + if (mReq->map) { + dma_unmap_single(mEp->device, mReq->req.dma, + length, mEp->dir ? DMA_TO_DEVICE : + DMA_FROM_DEVICE); + mReq->req.dma = DMA_ADDR_INVALID; + mReq->map = 0; + } + return -ENOMEM; + } + memset(mReq->zptr, 0, sizeof(*mReq->zptr)); + mReq->zptr->next = TD_TERMINATE; + mReq->zptr->token = TD_STATUS_ACTIVE; + if (!mReq->req.no_interrupt) + mReq->zptr->token |= TD_IOC; + } + /* + * TD configuration + * TODO - handle requests which spawns into several TDs + */ + memset(mReq->ptr, 0, sizeof(*mReq->ptr)); + mReq->ptr->token = length << ffs_nr(TD_TOTAL_BYTES); + mReq->ptr->token &= TD_TOTAL_BYTES; + mReq->ptr->token |= TD_STATUS_ACTIVE; + if (mReq->zptr) { + mReq->ptr->next = mReq->zdma; + } else { + mReq->ptr->next = TD_TERMINATE; + if (!mReq->req.no_interrupt) + mReq->ptr->token |= TD_IOC; + } + mReq->ptr->page[0] = mReq->req.dma; + for (i = 1; i < 5; i++) + mReq->ptr->page[i] = + (mReq->req.dma + i * CI13XXX_PAGE_SIZE) & ~TD_RESERVED_MASK; + + if (!list_empty(&mEp->qh.queue)) { + struct ci13xxx_req *mReqPrev; + int n = hw_ep_bit(mEp->num, mEp->dir); + int tmp_stat; + + mReqPrev = list_entry(mEp->qh.queue.prev, + struct ci13xxx_req, queue); + if (mReqPrev->zptr) + mReqPrev->zptr->next = mReq->dma & TD_ADDR_MASK; + else + mReqPrev->ptr->next = mReq->dma & TD_ADDR_MASK; + wmb(); + if (hw_read(udc, OP_ENDPTPRIME, BIT(n))) + goto done; + do { + hw_write(udc, OP_USBCMD, USBCMD_ATDTW, USBCMD_ATDTW); + tmp_stat = hw_read(udc, OP_ENDPTSTAT, BIT(n)); + } while (!hw_read(udc, OP_USBCMD, USBCMD_ATDTW)); + hw_write(udc, OP_USBCMD, USBCMD_ATDTW, 0); + if (tmp_stat) + goto done; + } + + /* QH configuration */ + mEp->qh.ptr->td.next = mReq->dma; /* TERMINATE = 0 */ + mEp->qh.ptr->td.token &= ~TD_STATUS; /* clear status */ + mEp->qh.ptr->cap |= QH_ZLT; + + wmb(); /* synchronize before ep prime */ + + ret = hw_ep_prime(udc, mEp->num, mEp->dir, + mEp->type == USB_ENDPOINT_XFER_CONTROL); +done: + return ret; +} + +/** + * _hardware_dequeue: handles a request at hardware level + * @gadget: gadget + * @mEp: endpoint + * + * This function returns an error code + */ +static int _hardware_dequeue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq) +{ + if (mReq->req.status != -EALREADY) + return -EINVAL; + + if ((TD_STATUS_ACTIVE & mReq->ptr->token) != 0) + return -EBUSY; + + if (mReq->zptr) { + if ((TD_STATUS_ACTIVE & mReq->zptr->token) != 0) + return -EBUSY; + dma_pool_free(mEp->td_pool, mReq->zptr, mReq->zdma); + mReq->zptr = NULL; + } + + mReq->req.status = 0; + + if (mReq->map) { + dma_unmap_single(mEp->device, mReq->req.dma, mReq->req.length, + mEp->dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + mReq->req.dma = DMA_ADDR_INVALID; + mReq->map = 0; + } + + mReq->req.status = mReq->ptr->token & TD_STATUS; + if ((TD_STATUS_HALTED & mReq->req.status) != 0) + mReq->req.status = -1; + else if ((TD_STATUS_DT_ERR & mReq->req.status) != 0) + mReq->req.status = -1; + else if ((TD_STATUS_TR_ERR & mReq->req.status) != 0) + mReq->req.status = -1; + + mReq->req.actual = mReq->ptr->token & TD_TOTAL_BYTES; + mReq->req.actual >>= ffs_nr(TD_TOTAL_BYTES); + mReq->req.actual = mReq->req.length - mReq->req.actual; + mReq->req.actual = mReq->req.status ? 0 : mReq->req.actual; + + return mReq->req.actual; +} + +/** + * _ep_nuke: dequeues all endpoint requests + * @mEp: endpoint + * + * This function returns an error code + * Caller must hold lock + */ +static int _ep_nuke(struct ci13xxx_ep *mEp) +__releases(mEp->lock) +__acquires(mEp->lock) +{ + if (mEp == NULL) + return -EINVAL; + + hw_ep_flush(mEp->udc, mEp->num, mEp->dir); + + while (!list_empty(&mEp->qh.queue)) { + + /* pop oldest request */ + struct ci13xxx_req *mReq = \ + list_entry(mEp->qh.queue.next, + struct ci13xxx_req, queue); + list_del_init(&mReq->queue); + mReq->req.status = -ESHUTDOWN; + + if (mReq->req.complete != NULL) { + spin_unlock(mEp->lock); + mReq->req.complete(&mEp->ep, &mReq->req); + spin_lock(mEp->lock); + } + } + return 0; +} + +/** + * _gadget_stop_activity: stops all USB activity, flushes & disables all endpts + * @gadget: gadget + * + * This function returns an error code + */ +static int _gadget_stop_activity(struct usb_gadget *gadget) +{ + struct usb_ep *ep; + struct ci13xxx *udc = container_of(gadget, struct ci13xxx, gadget); + unsigned long flags; + + if (gadget == NULL) + return -EINVAL; + + spin_lock_irqsave(&udc->lock, flags); + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->remote_wakeup = 0; + udc->suspended = 0; + spin_unlock_irqrestore(&udc->lock, flags); + + /* flush all endpoints */ + gadget_for_each_ep(ep, gadget) { + usb_ep_fifo_flush(ep); + } + usb_ep_fifo_flush(&udc->ep0out->ep); + usb_ep_fifo_flush(&udc->ep0in->ep); + + if (udc->driver) + udc->driver->disconnect(gadget); + + /* make sure to disable all endpoints */ + gadget_for_each_ep(ep, gadget) { + usb_ep_disable(ep); + } + + if (udc->status != NULL) { + usb_ep_free_request(&udc->ep0in->ep, udc->status); + udc->status = NULL; + } + + return 0; +} + +/****************************************************************************** + * ISR block + *****************************************************************************/ +/** + * isr_reset_handler: USB reset interrupt handler + * @udc: UDC device + * + * This function resets USB engine after a bus reset occurred + */ +static void isr_reset_handler(struct ci13xxx *udc) +__releases(udc->lock) +__acquires(udc->lock) +{ + int retval; + + dbg_event(0xFF, "BUS RST", 0); + + spin_unlock(&udc->lock); + retval = _gadget_stop_activity(&udc->gadget); + if (retval) + goto done; + + retval = hw_usb_reset(udc); + if (retval) + goto done; + + udc->status = usb_ep_alloc_request(&udc->ep0in->ep, GFP_ATOMIC); + if (udc->status == NULL) + retval = -ENOMEM; + + spin_lock(&udc->lock); + + done: + if (retval) + dev_err(udc->dev, "error: %i\n", retval); +} + +/** + * isr_get_status_complete: get_status request complete function + * @ep: endpoint + * @req: request handled + * + * Caller must release lock + */ +static void isr_get_status_complete(struct usb_ep *ep, struct usb_request *req) +{ + if (ep == NULL || req == NULL) + return; + + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +/** + * isr_get_status_response: get_status request response + * @udc: udc struct + * @setup: setup request packet + * + * This function returns an error code + */ +static int isr_get_status_response(struct ci13xxx *udc, + struct usb_ctrlrequest *setup) +__releases(mEp->lock) +__acquires(mEp->lock) +{ + struct ci13xxx_ep *mEp = udc->ep0in; + struct usb_request *req = NULL; + gfp_t gfp_flags = GFP_ATOMIC; + int dir, num, retval; + + if (mEp == NULL || setup == NULL) + return -EINVAL; + + spin_unlock(mEp->lock); + req = usb_ep_alloc_request(&mEp->ep, gfp_flags); + spin_lock(mEp->lock); + if (req == NULL) + return -ENOMEM; + + req->complete = isr_get_status_complete; + req->length = 2; + req->buf = kzalloc(req->length, gfp_flags); + if (req->buf == NULL) { + retval = -ENOMEM; + goto err_free_req; + } + + if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) { + /* Assume that device is bus powered for now. */ + *(u16 *)req->buf = udc->remote_wakeup << 1; + retval = 0; + } else if ((setup->bRequestType & USB_RECIP_MASK) \ + == USB_RECIP_ENDPOINT) { + dir = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK) ? + TX : RX; + num = le16_to_cpu(setup->wIndex) & USB_ENDPOINT_NUMBER_MASK; + *(u16 *)req->buf = hw_ep_get_halt(udc, num, dir); + } + /* else do nothing; reserved for future use */ + + spin_unlock(mEp->lock); + retval = usb_ep_queue(&mEp->ep, req, gfp_flags); + spin_lock(mEp->lock); + if (retval) + goto err_free_buf; + + return 0; + + err_free_buf: + kfree(req->buf); + err_free_req: + spin_unlock(mEp->lock); + usb_ep_free_request(&mEp->ep, req); + spin_lock(mEp->lock); + return retval; +} + +/** + * isr_setup_status_complete: setup_status request complete function + * @ep: endpoint + * @req: request handled + * + * Caller must release lock. Put the port in test mode if test mode + * feature is selected. + */ +static void +isr_setup_status_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct ci13xxx *udc = req->context; + unsigned long flags; + + if (udc->setaddr) { + hw_usb_set_address(udc, udc->address); + udc->setaddr = false; + } + + spin_lock_irqsave(&udc->lock, flags); + if (udc->test_mode) + hw_port_test_set(udc, udc->test_mode); + spin_unlock_irqrestore(&udc->lock, flags); +} + +/** + * isr_setup_status_phase: queues the status phase of a setup transation + * @udc: udc struct + * + * This function returns an error code + */ +static int isr_setup_status_phase(struct ci13xxx *udc) +__releases(mEp->lock) +__acquires(mEp->lock) +{ + int retval; + struct ci13xxx_ep *mEp; + + mEp = (udc->ep0_dir == TX) ? udc->ep0out : udc->ep0in; + udc->status->context = udc; + udc->status->complete = isr_setup_status_complete; + + spin_unlock(mEp->lock); + retval = usb_ep_queue(&mEp->ep, udc->status, GFP_ATOMIC); + spin_lock(mEp->lock); + + return retval; +} + +/** + * isr_tr_complete_low: transaction complete low level handler + * @mEp: endpoint + * + * This function returns an error code + * Caller must hold lock + */ +static int isr_tr_complete_low(struct ci13xxx_ep *mEp) +__releases(mEp->lock) +__acquires(mEp->lock) +{ + struct ci13xxx_req *mReq, *mReqTemp; + struct ci13xxx_ep *mEpTemp = mEp; + int uninitialized_var(retval); + + if (list_empty(&mEp->qh.queue)) + return -EINVAL; + + list_for_each_entry_safe(mReq, mReqTemp, &mEp->qh.queue, + queue) { + retval = _hardware_dequeue(mEp, mReq); + if (retval < 0) + break; + list_del_init(&mReq->queue); + dbg_done(_usb_addr(mEp), mReq->ptr->token, retval); + if (mReq->req.complete != NULL) { + spin_unlock(mEp->lock); + if ((mEp->type == USB_ENDPOINT_XFER_CONTROL) && + mReq->req.length) + mEpTemp = mEp->udc->ep0in; + mReq->req.complete(&mEpTemp->ep, &mReq->req); + spin_lock(mEp->lock); + } + } + + if (retval == -EBUSY) + retval = 0; + if (retval < 0) + dbg_event(_usb_addr(mEp), "DONE", retval); + + return retval; +} + +/** + * isr_tr_complete_handler: transaction complete interrupt handler + * @udc: UDC descriptor + * + * This function handles traffic events + */ +static void isr_tr_complete_handler(struct ci13xxx *udc) +__releases(udc->lock) +__acquires(udc->lock) +{ + unsigned i; + u8 tmode = 0; + + for (i = 0; i < udc->hw_ep_max; i++) { + struct ci13xxx_ep *mEp = &udc->ci13xxx_ep[i]; + int type, num, dir, err = -EINVAL; + struct usb_ctrlrequest req; + + if (mEp->ep.desc == NULL) + continue; /* not configured */ + + if (hw_test_and_clear_complete(udc, i)) { + err = isr_tr_complete_low(mEp); + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) { + if (err > 0) /* needs status phase */ + err = isr_setup_status_phase(udc); + if (err < 0) { + dbg_event(_usb_addr(mEp), + "ERROR", err); + spin_unlock(&udc->lock); + if (usb_ep_set_halt(&mEp->ep)) + dev_err(udc->dev, + "error: ep_set_halt\n"); + spin_lock(&udc->lock); + } + } + } + + if (mEp->type != USB_ENDPOINT_XFER_CONTROL || + !hw_test_and_clear_setup_status(udc, i)) + continue; + + if (i != 0) { + dev_warn(udc->dev, "ctrl traffic at endpoint %d\n", i); + continue; + } + + /* + * Flush data and handshake transactions of previous + * setup packet. + */ + _ep_nuke(udc->ep0out); + _ep_nuke(udc->ep0in); + + /* read_setup_packet */ + do { + hw_test_and_set_setup_guard(udc); + memcpy(&req, &mEp->qh.ptr->setup, sizeof(req)); + } while (!hw_test_and_clear_setup_guard(udc)); + + type = req.bRequestType; + + udc->ep0_dir = (type & USB_DIR_IN) ? TX : RX; + + dbg_setup(_usb_addr(mEp), &req); + + switch (req.bRequest) { + case USB_REQ_CLEAR_FEATURE: + if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && + le16_to_cpu(req.wValue) == + USB_ENDPOINT_HALT) { + if (req.wLength != 0) + break; + num = le16_to_cpu(req.wIndex); + dir = num & USB_ENDPOINT_DIR_MASK; + num &= USB_ENDPOINT_NUMBER_MASK; + if (dir) /* TX */ + num += udc->hw_ep_max/2; + if (!udc->ci13xxx_ep[num].wedge) { + spin_unlock(&udc->lock); + err = usb_ep_clear_halt( + &udc->ci13xxx_ep[num].ep); + spin_lock(&udc->lock); + if (err) + break; + } + err = isr_setup_status_phase(udc); + } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE) && + le16_to_cpu(req.wValue) == + USB_DEVICE_REMOTE_WAKEUP) { + if (req.wLength != 0) + break; + udc->remote_wakeup = 0; + err = isr_setup_status_phase(udc); + } else { + goto delegate; + } + break; + case USB_REQ_GET_STATUS: + if (type != (USB_DIR_IN|USB_RECIP_DEVICE) && + type != (USB_DIR_IN|USB_RECIP_ENDPOINT) && + type != (USB_DIR_IN|USB_RECIP_INTERFACE)) + goto delegate; + if (le16_to_cpu(req.wLength) != 2 || + le16_to_cpu(req.wValue) != 0) + break; + err = isr_get_status_response(udc, &req); + break; + case USB_REQ_SET_ADDRESS: + if (type != (USB_DIR_OUT|USB_RECIP_DEVICE)) + goto delegate; + if (le16_to_cpu(req.wLength) != 0 || + le16_to_cpu(req.wIndex) != 0) + break; + udc->address = (u8)le16_to_cpu(req.wValue); + udc->setaddr = true; + err = isr_setup_status_phase(udc); + break; + case USB_REQ_SET_FEATURE: + if (type == (USB_DIR_OUT|USB_RECIP_ENDPOINT) && + le16_to_cpu(req.wValue) == + USB_ENDPOINT_HALT) { + if (req.wLength != 0) + break; + num = le16_to_cpu(req.wIndex); + dir = num & USB_ENDPOINT_DIR_MASK; + num &= USB_ENDPOINT_NUMBER_MASK; + if (dir) /* TX */ + num += udc->hw_ep_max/2; + + spin_unlock(&udc->lock); + err = usb_ep_set_halt(&udc->ci13xxx_ep[num].ep); + spin_lock(&udc->lock); + if (!err) + isr_setup_status_phase(udc); + } else if (type == (USB_DIR_OUT|USB_RECIP_DEVICE)) { + if (req.wLength != 0) + break; + switch (le16_to_cpu(req.wValue)) { + case USB_DEVICE_REMOTE_WAKEUP: + udc->remote_wakeup = 1; + err = isr_setup_status_phase(udc); + break; + case USB_DEVICE_TEST_MODE: + tmode = le16_to_cpu(req.wIndex) >> 8; + switch (tmode) { + case TEST_J: + case TEST_K: + case TEST_SE0_NAK: + case TEST_PACKET: + case TEST_FORCE_EN: + udc->test_mode = tmode; + err = isr_setup_status_phase( + udc); + break; + default: + break; + } + default: + goto delegate; + } + } else { + goto delegate; + } + break; + default: +delegate: + if (req.wLength == 0) /* no data phase */ + udc->ep0_dir = TX; + + spin_unlock(&udc->lock); + err = udc->driver->setup(&udc->gadget, &req); + spin_lock(&udc->lock); + break; + } + + if (err < 0) { + dbg_event(_usb_addr(mEp), "ERROR", err); + + spin_unlock(&udc->lock); + if (usb_ep_set_halt(&mEp->ep)) + dev_err(udc->dev, "error: ep_set_halt\n"); + spin_lock(&udc->lock); + } + } +} + +/****************************************************************************** + * ENDPT block + *****************************************************************************/ +/** + * ep_enable: configure endpoint, making it usable + * + * Check usb_ep_enable() at "usb_gadget.h" for details + */ +static int ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + int retval = 0; + unsigned long flags; + + if (ep == NULL || desc == NULL) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + + /* only internal SW should enable ctrl endpts */ + + mEp->ep.desc = desc; + + if (!list_empty(&mEp->qh.queue)) + dev_warn(mEp->udc->dev, "enabling a non-empty endpoint!\n"); + + mEp->dir = usb_endpoint_dir_in(desc) ? TX : RX; + mEp->num = usb_endpoint_num(desc); + mEp->type = usb_endpoint_type(desc); + + mEp->ep.maxpacket = usb_endpoint_maxp(desc); + + dbg_event(_usb_addr(mEp), "ENABLE", 0); + + mEp->qh.ptr->cap = 0; + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) + mEp->qh.ptr->cap |= QH_IOS; + else if (mEp->type == USB_ENDPOINT_XFER_ISOC) + mEp->qh.ptr->cap &= ~QH_MULT; + else + mEp->qh.ptr->cap &= ~QH_ZLT; + + mEp->qh.ptr->cap |= + (mEp->ep.maxpacket << ffs_nr(QH_MAX_PKT)) & QH_MAX_PKT; + mEp->qh.ptr->td.next |= TD_TERMINATE; /* needed? */ + + /* + * Enable endpoints in the HW other than ep0 as ep0 + * is always enabled + */ + if (mEp->num) + retval |= hw_ep_enable(mEp->udc, mEp->num, mEp->dir, mEp->type); + + spin_unlock_irqrestore(mEp->lock, flags); + return retval; +} + +/** + * ep_disable: endpoint is no longer usable + * + * Check usb_ep_disable() at "usb_gadget.h" for details + */ +static int ep_disable(struct usb_ep *ep) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + int direction, retval = 0; + unsigned long flags; + + if (ep == NULL) + return -EINVAL; + else if (mEp->ep.desc == NULL) + return -EBUSY; + + spin_lock_irqsave(mEp->lock, flags); + + /* only internal SW should disable ctrl endpts */ + + direction = mEp->dir; + do { + dbg_event(_usb_addr(mEp), "DISABLE", 0); + + retval |= _ep_nuke(mEp); + retval |= hw_ep_disable(mEp->udc, mEp->num, mEp->dir); + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) + mEp->dir = (mEp->dir == TX) ? RX : TX; + + } while (mEp->dir != direction); + + mEp->ep.desc = NULL; + + spin_unlock_irqrestore(mEp->lock, flags); + return retval; +} + +/** + * ep_alloc_request: allocate a request object to use with this endpoint + * + * Check usb_ep_alloc_request() at "usb_gadget.h" for details + */ +static struct usb_request *ep_alloc_request(struct usb_ep *ep, gfp_t gfp_flags) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx_req *mReq = NULL; + + if (ep == NULL) + return NULL; + + mReq = kzalloc(sizeof(struct ci13xxx_req), gfp_flags); + if (mReq != NULL) { + INIT_LIST_HEAD(&mReq->queue); + mReq->req.dma = DMA_ADDR_INVALID; + + mReq->ptr = dma_pool_alloc(mEp->td_pool, gfp_flags, + &mReq->dma); + if (mReq->ptr == NULL) { + kfree(mReq); + mReq = NULL; + } + } + + dbg_event(_usb_addr(mEp), "ALLOC", mReq == NULL); + + return (mReq == NULL) ? NULL : &mReq->req; +} + +/** + * ep_free_request: frees a request object + * + * Check usb_ep_free_request() at "usb_gadget.h" for details + */ +static void ep_free_request(struct usb_ep *ep, struct usb_request *req) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); + unsigned long flags; + + if (ep == NULL || req == NULL) { + return; + } else if (!list_empty(&mReq->queue)) { + dev_err(mEp->udc->dev, "freeing queued request\n"); + return; + } + + spin_lock_irqsave(mEp->lock, flags); + + if (mReq->ptr) + dma_pool_free(mEp->td_pool, mReq->ptr, mReq->dma); + kfree(mReq); + + dbg_event(_usb_addr(mEp), "FREE", 0); + + spin_unlock_irqrestore(mEp->lock, flags); +} + +/** + * ep_queue: queues (submits) an I/O request to an endpoint + * + * Check usb_ep_queue()* at usb_gadget.h" for details + */ +static int ep_queue(struct usb_ep *ep, struct usb_request *req, + gfp_t __maybe_unused gfp_flags) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); + struct ci13xxx *udc = mEp->udc; + int retval = 0; + unsigned long flags; + + if (ep == NULL || req == NULL || mEp->ep.desc == NULL) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) { + if (req->length) + mEp = (udc->ep0_dir == RX) ? + udc->ep0out : udc->ep0in; + if (!list_empty(&mEp->qh.queue)) { + _ep_nuke(mEp); + retval = -EOVERFLOW; + dev_warn(mEp->udc->dev, "endpoint ctrl %X nuked\n", + _usb_addr(mEp)); + } + } + + /* first nuke then test link, e.g. previous status has not sent */ + if (!list_empty(&mReq->queue)) { + retval = -EBUSY; + dev_err(mEp->udc->dev, "request already in queue\n"); + goto done; + } + + if (req->length > 4 * CI13XXX_PAGE_SIZE) { + req->length = 4 * CI13XXX_PAGE_SIZE; + retval = -EMSGSIZE; + dev_warn(mEp->udc->dev, "request length truncated\n"); + } + + dbg_queue(_usb_addr(mEp), req, retval); + + /* push request */ + mReq->req.status = -EINPROGRESS; + mReq->req.actual = 0; + + retval = _hardware_enqueue(mEp, mReq); + + if (retval == -EALREADY) { + dbg_event(_usb_addr(mEp), "QUEUE", retval); + retval = 0; + } + if (!retval) + list_add_tail(&mReq->queue, &mEp->qh.queue); + + done: + spin_unlock_irqrestore(mEp->lock, flags); + return retval; +} + +/** + * ep_dequeue: dequeues (cancels, unlinks) an I/O request from an endpoint + * + * Check usb_ep_dequeue() at "usb_gadget.h" for details + */ +static int ep_dequeue(struct usb_ep *ep, struct usb_request *req) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); + unsigned long flags; + + if (ep == NULL || req == NULL || mReq->req.status != -EALREADY || + mEp->ep.desc == NULL || list_empty(&mReq->queue) || + list_empty(&mEp->qh.queue)) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + + dbg_event(_usb_addr(mEp), "DEQUEUE", 0); + + hw_ep_flush(mEp->udc, mEp->num, mEp->dir); + + /* pop request */ + list_del_init(&mReq->queue); + if (mReq->map) { + dma_unmap_single(mEp->device, mReq->req.dma, mReq->req.length, + mEp->dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + mReq->req.dma = DMA_ADDR_INVALID; + mReq->map = 0; + } + req->status = -ECONNRESET; + + if (mReq->req.complete != NULL) { + spin_unlock(mEp->lock); + mReq->req.complete(&mEp->ep, &mReq->req); + spin_lock(mEp->lock); + } + + spin_unlock_irqrestore(mEp->lock, flags); + return 0; +} + +/** + * ep_set_halt: sets the endpoint halt feature + * + * Check usb_ep_set_halt() at "usb_gadget.h" for details + */ +static int ep_set_halt(struct usb_ep *ep, int value) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + int direction, retval = 0; + unsigned long flags; + + if (ep == NULL || mEp->ep.desc == NULL) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + +#ifndef STALL_IN + /* g_file_storage MS compliant but g_zero fails chapter 9 compliance */ + if (value && mEp->type == USB_ENDPOINT_XFER_BULK && mEp->dir == TX && + !list_empty(&mEp->qh.queue)) { + spin_unlock_irqrestore(mEp->lock, flags); + return -EAGAIN; + } +#endif + + direction = mEp->dir; + do { + dbg_event(_usb_addr(mEp), "HALT", value); + retval |= hw_ep_set_halt(mEp->udc, mEp->num, mEp->dir, value); + + if (!value) + mEp->wedge = 0; + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) + mEp->dir = (mEp->dir == TX) ? RX : TX; + + } while (mEp->dir != direction); + + spin_unlock_irqrestore(mEp->lock, flags); + return retval; +} + +/** + * ep_set_wedge: sets the halt feature and ignores clear requests + * + * Check usb_ep_set_wedge() at "usb_gadget.h" for details + */ +static int ep_set_wedge(struct usb_ep *ep) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + unsigned long flags; + + if (ep == NULL || mEp->ep.desc == NULL) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + + dbg_event(_usb_addr(mEp), "WEDGE", 0); + mEp->wedge = 1; + + spin_unlock_irqrestore(mEp->lock, flags); + + return usb_ep_set_halt(ep); +} + +/** + * ep_fifo_flush: flushes contents of a fifo + * + * Check usb_ep_fifo_flush() at "usb_gadget.h" for details + */ +static void ep_fifo_flush(struct usb_ep *ep) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + unsigned long flags; + + if (ep == NULL) { + dev_err(mEp->udc->dev, "%02X: -EINVAL\n", _usb_addr(mEp)); + return; + } + + spin_lock_irqsave(mEp->lock, flags); + + dbg_event(_usb_addr(mEp), "FFLUSH", 0); + hw_ep_flush(mEp->udc, mEp->num, mEp->dir); + + spin_unlock_irqrestore(mEp->lock, flags); +} + +/** + * Endpoint-specific part of the API to the USB controller hardware + * Check "usb_gadget.h" for details + */ +static const struct usb_ep_ops usb_ep_ops = { + .enable = ep_enable, + .disable = ep_disable, + .alloc_request = ep_alloc_request, + .free_request = ep_free_request, + .queue = ep_queue, + .dequeue = ep_dequeue, + .set_halt = ep_set_halt, + .set_wedge = ep_set_wedge, + .fifo_flush = ep_fifo_flush, +}; + +/****************************************************************************** + * GADGET block + *****************************************************************************/ +static int ci13xxx_vbus_session(struct usb_gadget *_gadget, int is_active) +{ + struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget); + unsigned long flags; + int gadget_ready = 0; + + if (!(udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS)) + return -EOPNOTSUPP; + + spin_lock_irqsave(&udc->lock, flags); + udc->vbus_active = is_active; + if (udc->driver) + gadget_ready = 1; + spin_unlock_irqrestore(&udc->lock, flags); + + if (gadget_ready) { + if (is_active) { + pm_runtime_get_sync(&_gadget->dev); + hw_device_reset(udc); + hw_device_state(udc, udc->ep0out->qh.dma); + } else { + hw_device_state(udc, 0); + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_STOPPED_EVENT); + _gadget_stop_activity(&udc->gadget); + pm_runtime_put_sync(&_gadget->dev); + } + } + + return 0; +} + +static int ci13xxx_wakeup(struct usb_gadget *_gadget) +{ + struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&udc->lock, flags); + if (!udc->remote_wakeup) { + ret = -EOPNOTSUPP; + goto out; + } + if (!hw_read(udc, OP_PORTSC, PORTSC_SUSP)) { + ret = -EINVAL; + goto out; + } + hw_write(udc, OP_PORTSC, PORTSC_FPR, PORTSC_FPR); +out: + spin_unlock_irqrestore(&udc->lock, flags); + return ret; +} + +static int ci13xxx_vbus_draw(struct usb_gadget *_gadget, unsigned mA) +{ + struct ci13xxx *udc = container_of(_gadget, struct ci13xxx, gadget); + + if (udc->transceiver) + return usb_phy_set_power(udc->transceiver, mA); + return -ENOTSUPP; +} + +static int ci13xxx_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +static int ci13xxx_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +/** + * Device operations part of the API to the USB controller hardware, + * which don't involve endpoints (or i/o) + * Check "usb_gadget.h" for details + */ +static const struct usb_gadget_ops usb_gadget_ops = { + .vbus_session = ci13xxx_vbus_session, + .wakeup = ci13xxx_wakeup, + .vbus_draw = ci13xxx_vbus_draw, + .udc_start = ci13xxx_start, + .udc_stop = ci13xxx_stop, +}; + +static int init_eps(struct ci13xxx *udc) +{ + int retval = 0, i, j; + + for (i = 0; i < udc->hw_ep_max/2; i++) + for (j = RX; j <= TX; j++) { + int k = i + j * udc->hw_ep_max/2; + struct ci13xxx_ep *mEp = &udc->ci13xxx_ep[k]; + + scnprintf(mEp->name, sizeof(mEp->name), "ep%i%s", i, + (j == TX) ? "in" : "out"); + + mEp->udc = udc; + mEp->lock = &udc->lock; + mEp->device = &udc->gadget.dev; + mEp->td_pool = udc->td_pool; + + mEp->ep.name = mEp->name; + mEp->ep.ops = &usb_ep_ops; + mEp->ep.maxpacket = CTRL_PAYLOAD_MAX; + + INIT_LIST_HEAD(&mEp->qh.queue); + mEp->qh.ptr = dma_pool_alloc(udc->qh_pool, GFP_KERNEL, + &mEp->qh.dma); + if (mEp->qh.ptr == NULL) + retval = -ENOMEM; + else + memset(mEp->qh.ptr, 0, sizeof(*mEp->qh.ptr)); + + /* + * set up shorthands for ep0 out and in endpoints, + * don't add to gadget's ep_list + */ + if (i == 0) { + if (j == RX) + udc->ep0out = mEp; + else + udc->ep0in = mEp; + + continue; + } + + list_add_tail(&mEp->ep.ep_list, &udc->gadget.ep_list); + } + + return retval; +} + +/** + * ci13xxx_start: register a gadget driver + * @gadget: our gadget + * @driver: the driver being registered + * + * Interrupts are enabled here. + */ +static int ci13xxx_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct ci13xxx *udc = container_of(gadget, struct ci13xxx, gadget); + unsigned long flags; + int retval = -ENOMEM; + + if (driver->disconnect == NULL) + return -EINVAL; + + + udc->ep0out->ep.desc = &ctrl_endpt_out_desc; + retval = usb_ep_enable(&udc->ep0out->ep); + if (retval) + return retval; + + udc->ep0in->ep.desc = &ctrl_endpt_in_desc; + retval = usb_ep_enable(&udc->ep0in->ep); + if (retval) + return retval; + spin_lock_irqsave(&udc->lock, flags); + + udc->driver = driver; + pm_runtime_get_sync(&udc->gadget.dev); + if (udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS) { + if (udc->vbus_active) { + if (udc->udc_driver->flags & CI13XXX_REGS_SHARED) + hw_device_reset(udc); + } else { + pm_runtime_put_sync(&udc->gadget.dev); + goto done; + } + } + + retval = hw_device_state(udc, udc->ep0out->qh.dma); + if (retval) + pm_runtime_put_sync(&udc->gadget.dev); + + done: + spin_unlock_irqrestore(&udc->lock, flags); + return retval; +} + +/** + * ci13xxx_stop: unregister a gadget driver + */ +static int ci13xxx_stop(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct ci13xxx *udc = container_of(gadget, struct ci13xxx, gadget); + unsigned long flags; + + spin_lock_irqsave(&udc->lock, flags); + + if (!(udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS) || + udc->vbus_active) { + hw_device_state(udc, 0); + if (udc->udc_driver->notify_event) + udc->udc_driver->notify_event(udc, + CI13XXX_CONTROLLER_STOPPED_EVENT); + udc->driver = NULL; + spin_unlock_irqrestore(&udc->lock, flags); + _gadget_stop_activity(&udc->gadget); + spin_lock_irqsave(&udc->lock, flags); + pm_runtime_put(&udc->gadget.dev); + } + + spin_unlock_irqrestore(&udc->lock, flags); + + return 0; +} + +/****************************************************************************** + * BUS block + *****************************************************************************/ +/** + * udc_irq: global interrupt handler + * + * This function returns IRQ_HANDLED if the IRQ has been handled + * It locks access to registers + */ +irqreturn_t udc_irq(int irq, void *data) +{ + struct ci13xxx *udc = data; + irqreturn_t retval; + u32 intr; + + if (udc == NULL) + return IRQ_HANDLED; + + spin_lock(&udc->lock); + + if (udc->udc_driver->flags & CI13XXX_REGS_SHARED) { + if (hw_read(udc, OP_USBMODE, USBMODE_CM) != + USBMODE_CM_DEVICE) { + spin_unlock(&udc->lock); + return IRQ_NONE; + } + } + intr = hw_test_and_clear_intr_active(udc); + dbg_interrupt(intr); + + if (intr) { + /* order defines priority - do NOT change it */ + if (USBi_URI & intr) + isr_reset_handler(udc); + + if (USBi_PCI & intr) { + udc->gadget.speed = hw_port_is_high_speed(udc) ? + USB_SPEED_HIGH : USB_SPEED_FULL; + if (udc->suspended && udc->driver->resume) { + spin_unlock(&udc->lock); + udc->driver->resume(&udc->gadget); + spin_lock(&udc->lock); + udc->suspended = 0; + } + } + + if (USBi_UI & intr) + isr_tr_complete_handler(udc); + + if (USBi_SLI & intr) { + if (udc->gadget.speed != USB_SPEED_UNKNOWN && + udc->driver->suspend) { + udc->suspended = 1; + spin_unlock(&udc->lock); + udc->driver->suspend(&udc->gadget); + spin_lock(&udc->lock); + } + } + retval = IRQ_HANDLED; + } else { + retval = IRQ_NONE; + } + spin_unlock(&udc->lock); + + return retval; +} + +/** + * udc_release: driver release function + * @dev: device + * + * Currently does nothing + */ +static void udc_release(struct device *dev) +{ +} + +/** + * udc_probe: parent probe must call this to initialize UDC + * @dev: parent device + * @regs: registers base address + * @name: driver name + * + * This function returns an error code + * No interrupts active, the IRQ has not been requested yet + * Kernel assumes 32-bit DMA operations by default, no need to dma_set_mask + */ +int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev, + void __iomem *regs, struct ci13xxx **_udc) +{ + struct ci13xxx *udc; + int retval = 0; + + if (dev == NULL || regs == NULL || driver == NULL || + driver->name == NULL) + return -EINVAL; + + udc = kzalloc(sizeof(struct ci13xxx), GFP_KERNEL); + if (udc == NULL) + return -ENOMEM; + + spin_lock_init(&udc->lock); + udc->regs = regs; + udc->udc_driver = driver; + + udc->gadget.ops = &usb_gadget_ops; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->gadget.max_speed = USB_SPEED_HIGH; + udc->gadget.is_otg = 0; + udc->gadget.name = driver->name; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + + dev_set_name(&udc->gadget.dev, "gadget"); + udc->gadget.dev.dma_mask = dev->dma_mask; + udc->gadget.dev.coherent_dma_mask = dev->coherent_dma_mask; + udc->gadget.dev.parent = dev; + udc->gadget.dev.release = udc_release; + + udc->dev = dev; + + /* alloc resources */ + udc->qh_pool = dma_pool_create("ci13xxx_qh", dev, + sizeof(struct ci13xxx_qh), + 64, CI13XXX_PAGE_SIZE); + if (udc->qh_pool == NULL) { + retval = -ENOMEM; + goto free_udc; + } + + udc->td_pool = dma_pool_create("ci13xxx_td", dev, + sizeof(struct ci13xxx_td), + 64, CI13XXX_PAGE_SIZE); + if (udc->td_pool == NULL) { + retval = -ENOMEM; + goto free_qh_pool; + } + + retval = hw_device_init(udc, regs, driver->capoffset); + if (retval < 0) + goto free_pools; + + retval = init_eps(udc); + if (retval) + goto free_pools; + + udc->gadget.ep0 = &udc->ep0in->ep; + + udc->transceiver = usb_get_transceiver(); + + if (udc->udc_driver->flags & CI13XXX_REQUIRE_TRANSCEIVER) { + if (udc->transceiver == NULL) { + retval = -ENODEV; + goto free_pools; + } + } + + if (!(udc->udc_driver->flags & CI13XXX_REGS_SHARED)) { + retval = hw_device_reset(udc); + if (retval) + goto put_transceiver; + } + + retval = device_register(&udc->gadget.dev); + if (retval) { + put_device(&udc->gadget.dev); + goto put_transceiver; + } + + retval = dbg_create_files(&udc->gadget.dev); + if (retval) + goto unreg_device; + + if (udc->transceiver) { + retval = otg_set_peripheral(udc->transceiver->otg, + &udc->gadget); + if (retval) + goto remove_dbg; + } + + retval = usb_add_gadget_udc(dev, &udc->gadget); + if (retval) + goto remove_trans; + + pm_runtime_no_callbacks(&udc->gadget.dev); + pm_runtime_enable(&udc->gadget.dev); + + *_udc = udc; + return retval; + +remove_trans: + if (udc->transceiver) { + otg_set_peripheral(udc->transceiver->otg, &udc->gadget); + usb_put_transceiver(udc->transceiver); + } + + dev_err(dev, "error = %i\n", retval); +remove_dbg: + dbg_remove_files(&udc->gadget.dev); +unreg_device: + device_unregister(&udc->gadget.dev); +put_transceiver: + if (udc->transceiver) + usb_put_transceiver(udc->transceiver); +free_pools: + dma_pool_destroy(udc->td_pool); +free_qh_pool: + dma_pool_destroy(udc->qh_pool); +free_udc: + kfree(udc); + *_udc = NULL; + return retval; +} + +/** + * udc_remove: parent remove must call this to remove UDC + * + * No interrupts active, the IRQ has been released + */ +void udc_remove(struct ci13xxx *udc) +{ + int i; + + if (udc == NULL) + return; + + usb_del_gadget_udc(&udc->gadget); + + for (i = 0; i < udc->hw_ep_max; i++) { + struct ci13xxx_ep *mEp = &udc->ci13xxx_ep[i]; + + dma_pool_free(udc->qh_pool, mEp->qh.ptr, mEp->qh.dma); + } + + dma_pool_destroy(udc->td_pool); + dma_pool_destroy(udc->qh_pool); + + if (udc->transceiver) { + otg_set_peripheral(udc->transceiver->otg, &udc->gadget); + usb_put_transceiver(udc->transceiver); + } + dbg_remove_files(&udc->gadget.dev); + device_unregister(&udc->gadget.dev); + + kfree(udc->hw_bank.regmap); + kfree(udc); +} diff --git a/drivers/usb/chipidea/udc.h b/drivers/usb/chipidea/udc.h new file mode 100644 index 000000000000..82c9e3e772f7 --- /dev/null +++ b/drivers/usb/chipidea/udc.h @@ -0,0 +1,96 @@ +/* + * udc.h - ChipIdea UDC structures + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __DRIVERS_USB_CHIPIDEA_UDC_H +#define __DRIVERS_USB_CHIPIDEA_UDC_H + +#include + +#define CTRL_PAYLOAD_MAX 64 +#define RX 0 /* similar to USB_DIR_OUT but can be used as an index */ +#define TX 1 /* similar to USB_DIR_IN but can be used as an index */ + +/* DMA layout of transfer descriptors */ +struct ci13xxx_td { + /* 0 */ + u32 next; +#define TD_TERMINATE BIT(0) +#define TD_ADDR_MASK (0xFFFFFFEUL << 5) + /* 1 */ + u32 token; +#define TD_STATUS (0x00FFUL << 0) +#define TD_STATUS_TR_ERR BIT(3) +#define TD_STATUS_DT_ERR BIT(5) +#define TD_STATUS_HALTED BIT(6) +#define TD_STATUS_ACTIVE BIT(7) +#define TD_MULTO (0x0003UL << 10) +#define TD_IOC BIT(15) +#define TD_TOTAL_BYTES (0x7FFFUL << 16) + /* 2 */ + u32 page[5]; +#define TD_CURR_OFFSET (0x0FFFUL << 0) +#define TD_FRAME_NUM (0x07FFUL << 0) +#define TD_RESERVED_MASK (0x0FFFUL << 0) +} __attribute__ ((packed)); + +/* DMA layout of queue heads */ +struct ci13xxx_qh { + /* 0 */ + u32 cap; +#define QH_IOS BIT(15) +#define QH_MAX_PKT (0x07FFUL << 16) +#define QH_ZLT BIT(29) +#define QH_MULT (0x0003UL << 30) + /* 1 */ + u32 curr; + /* 2 - 8 */ + struct ci13xxx_td td; + /* 9 */ + u32 RESERVED; + struct usb_ctrlrequest setup; +} __attribute__ ((packed)); + +/* Extension of usb_request */ +struct ci13xxx_req { + struct usb_request req; + unsigned map; + struct list_head queue; + struct ci13xxx_td *ptr; + dma_addr_t dma; + struct ci13xxx_td *zptr; + dma_addr_t zdma; +}; + +#ifdef CONFIG_USB_CHIPIDEA_UDC +irqreturn_t udc_irq(int irq, void *data); +int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev, + void __iomem *regs, struct ci13xxx **_udc); +void udc_remove(struct ci13xxx *udc); +#else +static inline irqreturn_t udc_irq(int irq, void *data) +{ + return IRQ_NONE; +} + +static inline +int udc_probe(struct ci13xxx_udc_driver *driver, struct device *dev, + void __iomem *regs, struct ci13xxx **_udc) +{ + return -ENODEV; +} + +static inline void udc_remove(struct ci13xxx *udc) +{ +} +#endif + +#endif /* __DRIVERS_USB_CHIPIDEA_UDC_H */ diff --git a/include/linux/usb/chipidea.h b/include/linux/usb/chipidea.h new file mode 100644 index 000000000000..86273ce5f554 --- /dev/null +++ b/include/linux/usb/chipidea.h @@ -0,0 +1,27 @@ +/* + * Platform data for the chipidea USB dual role controller + */ + +#ifndef __LINUX_USB_CHIPIDEA_H +#define __LINUX_USB_CHIPIDEA_H + +struct ci13xxx; +struct ci13xxx_udc_driver { + const char *name; + /* offset of the capability registers */ + uintptr_t capoffset; + unsigned long flags; +#define CI13XXX_REGS_SHARED BIT(0) +#define CI13XXX_REQUIRE_TRANSCEIVER BIT(1) +#define CI13XXX_PULLUP_ON_VBUS BIT(2) +#define CI13XXX_DISABLE_STREAMING BIT(3) + +#define CI13XXX_CONTROLLER_RESET_EVENT 0 +#define CI13XXX_CONTROLLER_STOPPED_EVENT 1 + void (*notify_event) (struct ci13xxx *udc, unsigned event); +}; + +/* Default offset of capability registers */ +#define DEF_CAPOFFSET 0x100 + +#endif