diff options
Diffstat (limited to 'drivers/usb/renesas_usbhs')
-rw-r--r-- | drivers/usb/renesas_usbhs/Kconfig | 16 | ||||
-rw-r--r-- | drivers/usb/renesas_usbhs/Makefile | 9 | ||||
-rw-r--r-- | drivers/usb/renesas_usbhs/common.c | 437 | ||||
-rw-r--r-- | drivers/usb/renesas_usbhs/common.h | 230 | ||||
-rw-r--r-- | drivers/usb/renesas_usbhs/mod.c | 328 | ||||
-rw-r--r-- | drivers/usb/renesas_usbhs/mod.h | 137 | ||||
-rw-r--r-- | drivers/usb/renesas_usbhs/mod_gadget.c | 1384 | ||||
-rw-r--r-- | drivers/usb/renesas_usbhs/pipe.c | 874 | ||||
-rw-r--r-- | drivers/usb/renesas_usbhs/pipe.h | 104 |
9 files changed, 3519 insertions, 0 deletions
diff --git a/drivers/usb/renesas_usbhs/Kconfig b/drivers/usb/renesas_usbhs/Kconfig new file mode 100644 index 00000000000..b2e64918884 --- /dev/null +++ b/drivers/usb/renesas_usbhs/Kconfig @@ -0,0 +1,16 @@ +# +# Renesas USB Controller Drivers +# + +config USB_RENESAS_USBHS + tristate 'Renesas USBHS controller' + depends on SUPERH || ARCH_SHMOBILE + default n + help + Renesas USBHS is a discrete USB host and peripheral controller chip + that supports both full and high speed USB 2.0 data transfers. + It has nine or more configurable endpoints, and endpoint zero. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "renesas_usbhs" and force all + gadget drivers to also be dynamically linked. diff --git a/drivers/usb/renesas_usbhs/Makefile b/drivers/usb/renesas_usbhs/Makefile new file mode 100644 index 00000000000..b8798ad1627 --- /dev/null +++ b/drivers/usb/renesas_usbhs/Makefile @@ -0,0 +1,9 @@ +# +# for Renesas USB +# + +obj-$(CONFIG_USB_RENESAS_USBHS) += renesas_usbhs.o + +renesas_usbhs-y := common.o mod.o pipe.o + +renesas_usbhs-$(CONFIG_USB_RENESAS_USBHS_UDC) += mod_gadget.o diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c new file mode 100644 index 00000000000..f3664d6af66 --- /dev/null +++ b/drivers/usb/renesas_usbhs/common.c @@ -0,0 +1,437 @@ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <linux/io.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include "./common.h" + +#define USBHSF_RUNTIME_PWCTRL (1 << 0) + +/* status */ +#define usbhsc_flags_init(p) do {(p)->flags = 0; } while (0) +#define usbhsc_flags_set(p, b) ((p)->flags |= (b)) +#define usbhsc_flags_clr(p, b) ((p)->flags &= ~(b)) +#define usbhsc_flags_has(p, b) ((p)->flags & (b)) + +/* + * platform call back + * + * renesas usb support platform callback function. + * Below macro call it. + * if platform doesn't have callback, it return 0 (no error) + */ +#define usbhs_platform_call(priv, func, args...)\ + (!(priv) ? -ENODEV : \ + !((priv)->pfunc->func) ? 0 : \ + (priv)->pfunc->func(args)) + +/* + * common functions + */ +u16 usbhs_read(struct usbhs_priv *priv, u32 reg) +{ + return ioread16(priv->base + reg); +} + +void usbhs_write(struct usbhs_priv *priv, u32 reg, u16 data) +{ + iowrite16(data, priv->base + reg); +} + +void usbhs_bset(struct usbhs_priv *priv, u32 reg, u16 mask, u16 data) +{ + u16 val = usbhs_read(priv, reg); + + val &= ~mask; + val |= data & mask; + + usbhs_write(priv, reg, val); +} + +struct usbhs_priv *usbhs_pdev_to_priv(struct platform_device *pdev) +{ + return dev_get_drvdata(&pdev->dev); +} + +/* + * syscfg functions + */ +void usbhs_sys_clock_ctrl(struct usbhs_priv *priv, int enable) +{ + usbhs_bset(priv, SYSCFG, SCKE, enable ? SCKE : 0); +} + +void usbhs_sys_hispeed_ctrl(struct usbhs_priv *priv, int enable) +{ + usbhs_bset(priv, SYSCFG, HSE, enable ? HSE : 0); +} + +void usbhs_sys_usb_ctrl(struct usbhs_priv *priv, int enable) +{ + usbhs_bset(priv, SYSCFG, USBE, enable ? USBE : 0); +} + +void usbhs_sys_host_ctrl(struct usbhs_priv *priv, int enable) +{ + u16 mask = DCFM | DRPD | DPRPU; + u16 val = DCFM | DRPD; + + /* + * if enable + * + * - select Host mode + * - D+ Line/D- Line Pull-down + */ + usbhs_bset(priv, SYSCFG, mask, enable ? val : 0); +} + +void usbhs_sys_function_ctrl(struct usbhs_priv *priv, int enable) +{ + u16 mask = DCFM | DRPD | DPRPU; + u16 val = DPRPU; + + /* + * if enable + * + * - select Function mode + * - D+ Line Pull-up + */ + usbhs_bset(priv, SYSCFG, mask, enable ? val : 0); +} + +/* + * frame functions + */ +int usbhs_frame_get_num(struct usbhs_priv *priv) +{ + return usbhs_read(priv, FRMNUM) & FRNM_MASK; +} + +/* + * local functions + */ +static void usbhsc_bus_ctrl(struct usbhs_priv *priv, int enable) +{ + int wait = usbhs_get_dparam(priv, buswait_bwait); + u16 data = 0; + + if (enable) { + /* set bus wait if platform have */ + if (wait) + usbhs_bset(priv, BUSWAIT, 0x000F, wait); + } + usbhs_write(priv, DVSTCTR, data); +} + +/* + * platform default param + */ +static u32 usbhsc_default_pipe_type[] = { + USB_ENDPOINT_XFER_CONTROL, + USB_ENDPOINT_XFER_ISOC, + USB_ENDPOINT_XFER_ISOC, + USB_ENDPOINT_XFER_BULK, + USB_ENDPOINT_XFER_BULK, + USB_ENDPOINT_XFER_BULK, + USB_ENDPOINT_XFER_INT, + USB_ENDPOINT_XFER_INT, + USB_ENDPOINT_XFER_INT, + USB_ENDPOINT_XFER_INT, +}; + +/* + * power control + */ +static void usbhsc_power_ctrl(struct usbhs_priv *priv, int enable) +{ + struct device *dev = usbhs_priv_to_dev(priv); + + if (enable) { + /* enable PM */ + pm_runtime_get_sync(dev); + + /* USB on */ + usbhs_sys_clock_ctrl(priv, enable); + usbhsc_bus_ctrl(priv, enable); + } else { + /* USB off */ + usbhsc_bus_ctrl(priv, enable); + usbhs_sys_clock_ctrl(priv, enable); + + /* disable PM */ + pm_runtime_put_sync(dev); + } +} + +/* + * notify hotplug + */ +static void usbhsc_notify_hotplug(struct work_struct *work) +{ + struct usbhs_priv *priv = container_of(work, + struct usbhs_priv, + notify_hotplug_work.work); + struct platform_device *pdev = usbhs_priv_to_pdev(priv); + struct usbhs_mod *mod = usbhs_mod_get_current(priv); + int id; + int enable; + int ret; + + /* + * get vbus status from platform + */ + enable = usbhs_platform_call(priv, get_vbus, pdev); + + /* + * get id from platform + */ + id = usbhs_platform_call(priv, get_id, pdev); + + if (enable && !mod) { + ret = usbhs_mod_change(priv, id); + if (ret < 0) + return; + + dev_dbg(&pdev->dev, "%s enable\n", __func__); + + /* power on */ + if (usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL)) + usbhsc_power_ctrl(priv, enable); + + /* module start */ + usbhs_mod_call(priv, start, priv); + + } else if (!enable && mod) { + dev_dbg(&pdev->dev, "%s disable\n", __func__); + + /* module stop */ + usbhs_mod_call(priv, stop, priv); + + /* power off */ + if (usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL)) + usbhsc_power_ctrl(priv, enable); + + usbhs_mod_change(priv, -1); + + /* reset phy for next connection */ + usbhs_platform_call(priv, phy_reset, pdev); + } +} + +int usbhsc_drvcllbck_notify_hotplug(struct platform_device *pdev) +{ + struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); + int delay = usbhs_get_dparam(priv, detection_delay); + + /* + * This functions will be called in interrupt. + * To make sure safety context, + * use workqueue for usbhs_notify_hotplug + */ + schedule_delayed_work(&priv->notify_hotplug_work, delay); + return 0; +} + +/* + * platform functions + */ +static int __devinit usbhs_probe(struct platform_device *pdev) +{ + struct renesas_usbhs_platform_info *info = pdev->dev.platform_data; + struct renesas_usbhs_driver_callback *dfunc; + struct usbhs_priv *priv; + struct resource *res; + unsigned int irq; + int ret; + + /* check platform information */ + if (!info || + !info->platform_callback.get_id) { + dev_err(&pdev->dev, "no platform information\n"); + return -EINVAL; + } + + /* platform data */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (!res || (int)irq <= 0) { + dev_err(&pdev->dev, "Not enough Renesas USB platform resources.\n"); + return -ENODEV; + } + + /* usb private data */ + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&pdev->dev, "Could not allocate priv\n"); + return -ENOMEM; + } + + priv->base = ioremap_nocache(res->start, resource_size(res)); + if (!priv->base) { + dev_err(&pdev->dev, "ioremap error.\n"); + ret = -ENOMEM; + goto probe_end_kfree; + } + + /* + * care platform info + */ + priv->pfunc = &info->platform_callback; + priv->dparam = &info->driver_param; + + /* set driver callback functions for platform */ + dfunc = &info->driver_callback; + dfunc->notify_hotplug = usbhsc_drvcllbck_notify_hotplug; + + /* set default param if platform doesn't have */ + if (!priv->dparam->pipe_type) { + priv->dparam->pipe_type = usbhsc_default_pipe_type; + priv->dparam->pipe_size = ARRAY_SIZE(usbhsc_default_pipe_type); + } + + /* FIXME */ + /* runtime power control ? */ + if (priv->pfunc->get_vbus) + usbhsc_flags_set(priv, USBHSF_RUNTIME_PWCTRL); + + /* + * priv settings + */ + priv->irq = irq; + priv->pdev = pdev; + INIT_DELAYED_WORK(&priv->notify_hotplug_work, usbhsc_notify_hotplug); + spin_lock_init(usbhs_priv_to_lock(priv)); + + /* call pipe and module init */ + ret = usbhs_pipe_probe(priv); + if (ret < 0) + goto probe_end_iounmap; + + ret = usbhs_mod_probe(priv); + if (ret < 0) + goto probe_end_pipe_exit; + + /* dev_set_drvdata should be called after usbhs_mod_init */ + dev_set_drvdata(&pdev->dev, priv); + + /* + * deviece reset here because + * USB device might be used in boot loader. + */ + usbhs_sys_clock_ctrl(priv, 0); + + /* + * platform call + * + * USB phy setup might depend on CPU/Board. + * If platform has its callback functions, + * call it here. + */ + ret = usbhs_platform_call(priv, hardware_init, pdev); + if (ret < 0) { + dev_err(&pdev->dev, "platform prove failed.\n"); + goto probe_end_mod_exit; + } + + /* reset phy for connection */ + usbhs_platform_call(priv, phy_reset, pdev); + + /* power control */ + pm_runtime_enable(&pdev->dev); + if (!usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL)) { + usbhsc_power_ctrl(priv, 1); + usbhs_mod_autonomy_mode(priv); + } + + /* + * manual call notify_hotplug for cold plug + */ + ret = usbhsc_drvcllbck_notify_hotplug(pdev); + if (ret < 0) + goto probe_end_call_remove; + + dev_info(&pdev->dev, "probed\n"); + + return ret; + +probe_end_call_remove: + usbhs_platform_call(priv, hardware_exit, pdev); +probe_end_mod_exit: + usbhs_mod_remove(priv); +probe_end_pipe_exit: + usbhs_pipe_remove(priv); +probe_end_iounmap: + iounmap(priv->base); +probe_end_kfree: + kfree(priv); + + dev_info(&pdev->dev, "probe failed\n"); + + return ret; +} + +static int __devexit usbhs_remove(struct platform_device *pdev) +{ + struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); + struct renesas_usbhs_platform_info *info = pdev->dev.platform_data; + struct renesas_usbhs_driver_callback *dfunc = &info->driver_callback; + + dev_dbg(&pdev->dev, "usb remove\n"); + + dfunc->notify_hotplug = NULL; + + /* power off */ + if (!usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL)) + usbhsc_power_ctrl(priv, 0); + + pm_runtime_disable(&pdev->dev); + + usbhs_platform_call(priv, hardware_exit, pdev); + usbhs_mod_remove(priv); + usbhs_pipe_remove(priv); + iounmap(priv->base); + kfree(priv); + + return 0; +} + +static struct platform_driver renesas_usbhs_driver = { + .driver = { + .name = "renesas_usbhs", + }, + .probe = usbhs_probe, + .remove = __devexit_p(usbhs_remove), +}; + +static int __init usbhs_init(void) +{ + return platform_driver_register(&renesas_usbhs_driver); +} + +static void __exit usbhs_exit(void) +{ + platform_driver_unregister(&renesas_usbhs_driver); +} + +module_init(usbhs_init); +module_exit(usbhs_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Renesas USB driver"); +MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); diff --git a/drivers/usb/renesas_usbhs/common.h b/drivers/usb/renesas_usbhs/common.h new file mode 100644 index 00000000000..0aadcb40276 --- /dev/null +++ b/drivers/usb/renesas_usbhs/common.h @@ -0,0 +1,230 @@ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef RENESAS_USB_DRIVER_H +#define RENESAS_USB_DRIVER_H + +#include <linux/platform_device.h> +#include <linux/usb/renesas_usbhs.h> + +struct usbhs_priv; + +#include "./mod.h" +#include "./pipe.h" + +/* + * + * register define + * + */ +#define SYSCFG 0x0000 +#define BUSWAIT 0x0002 +#define DVSTCTR 0x0008 +#define CFIFO 0x0014 +#define CFIFOSEL 0x0020 +#define CFIFOCTR 0x0022 +#define INTENB0 0x0030 +#define INTENB1 0x0032 +#define BRDYENB 0x0036 +#define NRDYENB 0x0038 +#define BEMPENB 0x003A +#define INTSTS0 0x0040 +#define INTSTS1 0x0042 +#define BRDYSTS 0x0046 +#define NRDYSTS 0x0048 +#define BEMPSTS 0x004A +#define FRMNUM 0x004C +#define USBREQ 0x0054 /* USB request type register */ +#define USBVAL 0x0056 /* USB request value register */ +#define USBINDX 0x0058 /* USB request index register */ +#define USBLENG 0x005A /* USB request length register */ +#define DCPCFG 0x005C +#define DCPMAXP 0x005E +#define DCPCTR 0x0060 +#define PIPESEL 0x0064 +#define PIPECFG 0x0068 +#define PIPEBUF 0x006A +#define PIPEMAXP 0x006C +#define PIPEPERI 0x006E +#define PIPEnCTR 0x0070 + +/* SYSCFG */ +#define SCKE (1 << 10) /* USB Module Clock Enable */ +#define HSE (1 << 7) /* High-Speed Operation Enable */ +#define DCFM (1 << 6) /* Controller Function Select */ +#define DRPD (1 << 5) /* D+ Line/D- Line Resistance Control */ +#define DPRPU (1 << 4) /* D+ Line Resistance Control */ +#define USBE (1 << 0) /* USB Module Operation Enable */ + +/* DVSTCTR */ +#define EXTLP (1 << 10) /* Controls the EXTLP pin output state */ +#define PWEN (1 << 9) /* Controls the PWEN pin output state */ +#define RHST (0x7) /* Reset Handshake */ +#define RHST_LOW_SPEED 1 /* Low-speed connection */ +#define RHST_FULL_SPEED 2 /* Full-speed connection */ +#define RHST_HIGH_SPEED 3 /* High-speed connection */ + +/* CFIFOSEL */ +#define MBW_32 (0x2 << 10) /* CFIFO Port Access Bit Width */ + +/* CFIFOCTR */ +#define BVAL (1 << 15) /* Buffer Memory Enable Flag */ +#define BCLR (1 << 14) /* CPU buffer clear */ +#define FRDY (1 << 13) /* FIFO Port Ready */ +#define DTLN_MASK (0x0FFF) /* Receive Data Length */ + +/* INTENB0 */ +#define VBSE (1 << 15) /* Enable IRQ VBUS_0 and VBUSIN_0 */ +#define RSME (1 << 14) /* Enable IRQ Resume */ +#define SOFE (1 << 13) /* Enable IRQ Frame Number Update */ +#define DVSE (1 << 12) /* Enable IRQ Device State Transition */ +#define CTRE (1 << 11) /* Enable IRQ Control Stage Transition */ +#define BEMPE (1 << 10) /* Enable IRQ Buffer Empty */ +#define NRDYE (1 << 9) /* Enable IRQ Buffer Not Ready Response */ +#define BRDYE (1 << 8) /* Enable IRQ Buffer Ready */ + +/* INTENB1 */ +#define BCHGE (1 << 14) /* USB Bus Change Interrupt Enable */ +#define DTCHE (1 << 12) /* Disconnection Detect Interrupt Enable */ +#define ATTCHE (1 << 11) /* Connection Detect Interrupt Enable */ +#define EOFERRE (1 << 6) /* EOF Error Detect Interrupt Enable */ +#define SIGNE (1 << 5) /* Setup Transaction Error Interrupt Enable */ +#define SACKE (1 << 4) /* Setup Transaction ACK Interrupt Enable */ + +/* INTSTS0 */ +#define VBINT (1 << 15) /* VBUS0_0 and VBUS1_0 Interrupt Status */ +#define DVST (1 << 12) /* Device State Transition Interrupt Status */ +#define CTRT (1 << 11) /* Control Stage Interrupt Status */ +#define BEMP (1 << 10) /* Buffer Empty Interrupt Status */ +#define BRDY (1 << 8) /* Buffer Ready Interrupt Status */ +#define VBSTS (1 << 7) /* VBUS_0 and VBUSIN_0 Input Status */ +#define VALID (1 << 3) /* USB Request Receive */ + +#define DVSQ_MASK (0x3 << 4) /* Device State */ +#define POWER_STATE (0 << 4) +#define DEFAULT_STATE (1 << 4) +#define ADDRESS_STATE (2 << 4) +#define CONFIGURATION_STATE (3 << 4) + +#define CTSQ_MASK (0x7) /* Control Transfer Stage */ +#define IDLE_SETUP_STAGE 0 /* Idle stage or setup stage */ +#define READ_DATA_STAGE 1 /* Control read data stage */ +#define READ_STATUS_STAGE 2 /* Control read status stage */ +#define WRITE_DATA_STAGE 3 /* Control write data stage */ +#define WRITE_STATUS_STAGE 4 /* Control write status stage */ +#define NODATA_STATUS_STAGE 5 /* Control write NoData status stage */ +#define SEQUENCE_ERROR 6 /* Control transfer sequence error */ + +/* PIPECFG */ +/* DCPCFG */ +#define TYPE_NONE (0 << 14) /* Transfer Type */ +#define TYPE_BULK (1 << 14) +#define TYPE_INT (2 << 14) +#define TYPE_ISO (3 << 14) +#define DBLB (1 << 9) /* Double Buffer Mode */ +#define SHTNAK (1 << 7) /* Pipe Disable in Transfer End */ +#define DIR_OUT (1 << 4) /* Transfer Direction */ + +/* PIPEMAXP */ +/* DCPMAXP */ +#define DEVSEL_MASK (0xF << 12) /* Device Select */ +#define DCP_MAXP_MASK (0x7F) +#define PIPE_MAXP_MASK (0x7FF) + +/* PIPEBUF */ +#define BUFSIZE_SHIFT 10 +#define BUFSIZE_MASK (0x1F << BUFSIZE_SHIFT) +#define BUFNMB_MASK (0xFF) + +/* PIPEnCTR */ +/* DCPCTR */ +#define BSTS (1 << 15) /* Buffer Status */ +#define CSSTS (1 << 12) /* CSSTS Status */ +#define SQCLR (1 << 8) /* Toggle Bit Clear */ +#define ACLRM (1 << 9) /* Buffer Auto-Clear Mode */ +#define PBUSY (1 << 5) /* Pipe Busy */ +#define PID_MASK (0x3) /* Response PID */ +#define PID_NAK 0 +#define PID_BUF 1 +#define PID_STALL10 2 +#define PID_STALL11 3 + +#define CCPL (1 << 2) /* Control Transfer End Enable */ + +/* FRMNUM */ +#define FRNM_MASK (0x7FF) + +/* + * struct + */ +struct usbhs_priv { + + void __iomem *base; + unsigned int irq; + + struct renesas_usbhs_platform_callback *pfunc; + struct renesas_usbhs_driver_param *dparam; + + struct delayed_work notify_hotplug_work; + struct platform_device *pdev; + + spinlock_t lock; + + u32 flags; + + /* + * module control + */ + struct usbhs_mod_info mod_info; + + /* + * pipe control + */ + struct usbhs_pipe_info pipe_info; +}; + +/* + * common + */ +u16 usbhs_read(struct usbhs_priv *priv, u32 reg); +void usbhs_write(struct usbhs_priv *priv, u32 reg, u16 data); +void usbhs_bset(struct usbhs_priv *priv, u32 reg, u16 mask, u16 data); + +int usbhsc_drvcllbck_notify_hotplug(struct platform_device *pdev); +/* + * sysconfig + */ +void usbhs_sys_clock_ctrl(struct usbhs_priv *priv, int enable); +void usbhs_sys_hispeed_ctrl(struct usbhs_priv *priv, int enable); +void usbhs_sys_usb_ctrl(struct usbhs_priv *priv, int enable); +void usbhs_sys_host_ctrl(struct usbhs_priv *priv, int enable); +void usbhs_sys_function_ctrl(struct usbhs_priv *priv, int enable); + +/* + * frame + */ +int usbhs_frame_get_num(struct usbhs_priv *priv); + +/* + * data + */ +struct usbhs_priv *usbhs_pdev_to_priv(struct platform_device *pdev); +#define usbhs_get_dparam(priv, param) (priv->dparam->param) +#define usbhs_priv_to_pdev(priv) (priv->pdev) +#define usbhs_priv_to_dev(priv) (&priv->pdev->dev) +#define usbhs_priv_to_lock(priv) (&priv->lock) + +#endif /* RENESAS_USB_DRIVER_H */ diff --git a/drivers/usb/renesas_usbhs/mod.c b/drivers/usb/renesas_usbhs/mod.c new file mode 100644 index 00000000000..a577f8f4064 --- /dev/null +++ b/drivers/usb/renesas_usbhs/mod.c @@ -0,0 +1,328 @@ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <linux/interrupt.h> + +#include "./common.h" +#include "./mod.h" + +#define usbhs_priv_to_modinfo(priv) (&priv->mod_info) +#define usbhs_mod_info_call(priv, func, param...) \ +({ \ + struct usbhs_mod_info *info; \ + info = usbhs_priv_to_modinfo(priv); \ + !info->func ? 0 : \ + info->func(param); \ +}) + +/* + * autonomy + * + * these functions are used if platform doesn't have external phy. + * -> there is no "notify_hotplug" callback from platform + * -> call "notify_hotplug" by itself + * -> use own interrupt to connect/disconnect + * -> it mean module clock is always ON + * ~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +static int usbhsm_autonomy_get_vbus(struct platform_device *pdev) +{ + struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); + + return VBSTS & usbhs_read(priv, INTSTS0); +} + +static int usbhsm_autonomy_irq_vbus(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state) +{ + struct platform_device *pdev = usbhs_priv_to_pdev(priv); + + return usbhsc_drvcllbck_notify_hotplug(pdev); +} + +void usbhs_mod_autonomy_mode(struct usbhs_priv *priv) +{ + struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv); + + info->irq_vbus = usbhsm_autonomy_irq_vbus; + priv->pfunc->get_vbus = usbhsm_autonomy_get_vbus; + + usbhs_irq_callback_update(priv, NULL); +} + +/* + * host / gadget functions + * + * renesas_usbhs host/gadget can register itself by below functions. + * these functions are called when probe + * + */ +void usbhs_mod_register(struct usbhs_priv *priv, struct usbhs_mod *mod, int id) +{ + struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv); + + info->mod[id] = mod; + mod->priv = priv; +} + +struct usbhs_mod *usbhs_mod_get(struct usbhs_priv *priv, int id) +{ + struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv); + struct usbhs_mod *ret = NULL; + + switch (id) { + case USBHS_HOST: + case USBHS_GADGET: + ret = info->mod[id]; + break; + } + + return ret; +} + +int usbhs_mod_is_host(struct usbhs_priv *priv, struct usbhs_mod *mod) +{ + struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv); + + if (!mod) + return -EINVAL; + + return info->mod[USBHS_HOST] == mod; +} + +struct usbhs_mod *usbhs_mod_get_current(struct usbhs_priv *priv) +{ + struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv); + + return info->curt; +} + +int usbhs_mod_change(struct usbhs_priv *priv, int id) +{ + struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv); + struct usbhs_mod *mod = NULL; + int ret = 0; + + /* id < 0 mean no current */ + switch (id) { + case USBHS_HOST: + case USBHS_GADGET: + mod = info->mod[id]; + break; + default: + ret = -EINVAL; + } + info->curt = mod; + + return ret; +} + +static irqreturn_t usbhs_interrupt(int irq, void *data); +int usbhs_mod_probe(struct usbhs_priv *priv) +{ + struct device *dev = usbhs_priv_to_dev(priv); + int ret; + + /* + * install host/gadget driver + */ + ret = usbhs_mod_gadget_probe(priv); + if (ret < 0) + return ret; + + /* irq settings */ + ret = request_irq(priv->irq, usbhs_interrupt, + IRQF_DISABLED, dev_name(dev), priv); + if (ret) { + dev_err(dev, "irq request err\n"); + goto mod_init_gadget_err; + } + + return ret; + +mod_init_gadget_err: + usbhs_mod_gadget_remove(priv); + + return ret; +} + +void usbhs_mod_remove(struct usbhs_priv *priv) +{ + usbhs_mod_gadget_remove(priv); + free_irq(priv->irq, priv); +} + +/* + * status functions + */ +int usbhs_status_get_usb_speed(struct usbhs_irq_state *irq_state) +{ + switch (irq_state->dvstctr & RHST) { + case RHST_LOW_SPEED: + return USB_SPEED_LOW; + case RHST_FULL_SPEED: + return USB_SPEED_FULL; + case RHST_HIGH_SPEED: + return USB_SPEED_HIGH; + } + + return USB_SPEED_UNKNOWN; +} + +int usbhs_status_get_device_state(struct usbhs_irq_state *irq_state) +{ + int state = irq_state->intsts0 & DVSQ_MASK; + + switch (state) { + case POWER_STATE: + case DEFAULT_STATE: + case ADDRESS_STATE: + case CONFIGURATION_STATE: + return state; + } + + return -EIO; +} + +int usbhs_status_get_ctrl_stage(struct usbhs_irq_state *irq_state) +{ + /* + * return value + * + * IDLE_SETUP_STAGE + * READ_DATA_STAGE + * READ_STATUS_STAGE + * WRITE_DATA_STAGE + * WRITE_STATUS_STAGE + * NODATA_STATUS_STAGE + * SEQUENCE_ERROR + */ + return (int)irq_state->intsts0 & CTSQ_MASK; +} + +static void usbhs_status_get_each_irq(struct usbhs_priv *priv, + struct usbhs_irq_state *state) +{ + struct usbhs_mod *mod = usbhs_mod_get_current(priv); + + state->intsts0 = usbhs_read(priv, INTSTS0); + state->intsts1 = usbhs_read(priv, INTSTS1); + + state->dvstctr = usbhs_read(priv, DVSTCTR); + + /* mask */ + if (mod) { + state->brdysts = usbhs_read(priv, BRDYSTS); + state->nrdysts = usbhs_read(priv, NRDYSTS); + state->bempsts = usbhs_read(priv, BEMPSTS); + + state->bempsts &= mod->irq_bempsts; + state->brdysts &= mod->irq_brdysts; + } +} + +/* + * interrupt + */ +#define INTSTS0_MAGIC 0xF800 /* acknowledge magical interrupt sources */ +#define INTSTS1_MAGIC 0xA870 /* acknowledge magical interrupt sources */ +static irqreturn_t usbhs_interrupt(int irq, void *data) +{ + struct usbhs_priv *priv = data; + struct usbhs_irq_state irq_state; + + usbhs_status_get_each_irq(priv, &irq_state); + + /* + * clear interrupt + * + * The hardware is _very_ picky to clear interrupt bit. + * Especially INTSTS0_MAGIC, INTSTS1_MAGIC value. + * + * see + * "Operation" + * - "Control Transfer (DCP)" + * - Function :: VALID bit should 0 + */ + usbhs_write(priv, INTSTS0, ~irq_state.intsts0 & INTSTS0_MAGIC); + usbhs_write(priv, INTSTS1, ~irq_state.intsts1 & INTSTS1_MAGIC); + + usbhs_write(priv, BRDYSTS, 0); + usbhs_write(priv, NRDYSTS, 0); + usbhs_write(priv, BEMPSTS, 0); + + /* + * call irq callback functions + * see also + * usbhs_irq_setting_update + */ + if (irq_state.intsts0 & VBINT) + usbhs_mod_info_call(priv, irq_vbus, priv, &irq_state); + + if (irq_state.intsts0 & DVST) + usbhs_mod_call(priv, irq_dev_state, priv, &irq_state); + + if (irq_state.intsts0 & CTRT) + usbhs_mod_call(priv, irq_ctrl_stage, priv, &irq_state); + + if (irq_state.intsts0 & BEMP) + usbhs_mod_call(priv, irq_empty, priv, &irq_state); + + if (irq_state.intsts0 & BRDY) + usbhs_mod_call(priv, irq_ready, priv, &irq_state); + + return IRQ_HANDLED; +} + +void usbhs_irq_callback_update(struct usbhs_priv *priv, struct usbhs_mod *mod) +{ + u16 intenb0 = 0; + struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv); + + usbhs_write(priv, INTENB0, 0); + + usbhs_write(priv, BEMPENB, 0); + usbhs_write(priv, BRDYENB, 0); + + /* + * see also + * usbhs_interrupt + */ + + /* + * it don't enable DVSE (intenb0) here + * but "mod->irq_dev_state" will be called. + */ + if (info->irq_vbus) + intenb0 |= VBSE; + + if (mod) { + if (mod->irq_ctrl_stage) + intenb0 |= CTRE; + + if (mod->irq_empty && mod->irq_bempsts) { + usbhs_write(priv, BEMPENB, mod->irq_bempsts); + intenb0 |= BEMPE; + } + + if (mod->irq_ready && mod->irq_brdysts) { + usbhs_write(priv, BRDYENB, mod->irq_brdysts); + intenb0 |= BRDYE; + } + } + + usbhs_write(priv, INTENB0, intenb0); +} diff --git a/drivers/usb/renesas_usbhs/mod.h b/drivers/usb/renesas_usbhs/mod.h new file mode 100644 index 00000000000..5c845a28a21 --- /dev/null +++ b/drivers/usb/renesas_usbhs/mod.h @@ -0,0 +1,137 @@ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef RENESAS_USB_MOD_H +#define RENESAS_USB_MOD_H + +#include <linux/spinlock.h> +#include <linux/usb/renesas_usbhs.h> +#include "./common.h" + +/* + * struct + */ +struct usbhs_irq_state { + u16 intsts0; + u16 intsts1; + u16 brdysts; + u16 nrdysts; + u16 bempsts; + u16 dvstctr; +}; + +struct usbhs_mod { + char *name; + + /* + * entry point from common.c + */ + int (*start)(struct usbhs_priv *priv); + int (*stop)(struct usbhs_priv *priv); + + /* INTSTS0 :: DVST (DVSQ) */ + int (*irq_dev_state)(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state); + + /* INTSTS0 :: CTRT (CTSQ) */ + int (*irq_ctrl_stage)(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state); + + /* INTSTS0 :: BEMP */ + /* BEMPSTS */ + int (*irq_empty)(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state); + u16 irq_bempsts; + + /* INTSTS0 :: BRDY */ + /* BRDYSTS */ + int (*irq_ready)(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state); + u16 irq_brdysts; + + struct usbhs_priv *priv; +}; + +struct usbhs_mod_info { + struct usbhs_mod *mod[USBHS_MAX]; + struct usbhs_mod *curt; /* current mod */ + + /* + * INTSTS0 :: VBINT + * + * This function will be used as autonomy mode + * when platform cannot call notify_hotplug. + * + * This callback cannot be member of "struct usbhs_mod" + * because it will be used even though + * host/gadget has not been selected. + */ + int (*irq_vbus)(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state); +}; + +/* + * for host/gadget module + */ +struct usbhs_mod *usbhs_mod_get(struct usbhs_priv *priv, int id); +struct usbhs_mod *usbhs_mod_get_current(struct usbhs_priv *priv); +void usbhs_mod_register(struct usbhs_priv *priv, struct usbhs_mod *usb, int id); +int usbhs_mod_is_host(struct usbhs_priv *priv, struct usbhs_mod *mod); +int usbhs_mod_change(struct usbhs_priv *priv, int id); +int usbhs_mod_probe(struct usbhs_priv *priv); +void usbhs_mod_remove(struct usbhs_priv *priv); + +void usbhs_mod_autonomy_mode(struct usbhs_priv *priv); + +/* + * status functions + */ +int usbhs_status_get_usb_speed(struct usbhs_irq_state *irq_state); +int usbhs_status_get_device_state(struct usbhs_irq_state *irq_state); +int usbhs_status_get_ctrl_stage(struct usbhs_irq_state *irq_state); + +/* + * callback functions + */ +void usbhs_irq_callback_update(struct usbhs_priv *priv, struct usbhs_mod *mod); + + +#define usbhs_mod_call(priv, func, param...) \ + ({ \ + struct usbhs_mod *mod; \ + mod = usbhs_mod_get_current(priv); \ + !mod ? -ENODEV : \ + !mod->func ? 0 : \ + mod->func(param); \ + }) + +/* + * gadget control + */ +#ifdef CONFIG_USB_RENESAS_USBHS_UDC +extern int __devinit usbhs_mod_gadget_probe(struct usbhs_priv *priv); +extern void __devexit usbhs_mod_gadget_remove(struct usbhs_priv *priv); +#else +static inline int usbhs_mod_gadget_probe(struct usbhs_priv *priv) +{ + return 0; +} +static inline void usbhs_mod_gadget_remove(struct usbhs_priv *priv) +{ +} +#endif + +#endif /* RENESAS_USB_MOD_H */ diff --git a/drivers/usb/renesas_usbhs/mod_gadget.c b/drivers/usb/renesas_usbhs/mod_gadget.c new file mode 100644 index 00000000000..206cfabc928 --- /dev/null +++ b/drivers/usb/renesas_usbhs/mod_gadget.c @@ -0,0 +1,1384 @@ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include "common.h" + +/* + * struct + */ +struct usbhsg_request { + struct usb_request req; + struct list_head node; +}; + +#define EP_NAME_SIZE 8 +struct usbhsg_gpriv; +struct usbhsg_pipe_handle; +struct usbhsg_uep { + struct usb_ep ep; + struct usbhs_pipe *pipe; + struct list_head list; + + char ep_name[EP_NAME_SIZE]; + + struct usbhsg_gpriv *gpriv; + struct usbhsg_pipe_handle *handler; +}; + +struct usbhsg_gpriv { + struct usb_gadget gadget; + struct usbhs_mod mod; + + struct usbhsg_uep *uep; + int uep_size; + + struct usb_gadget_driver *driver; + + u32 status; +#define USBHSG_STATUS_STARTED (1 << 0) +#define USBHSG_STATUS_REGISTERD (1 << 1) +#define USBHSG_STATUS_WEDGE (1 << 2) +}; + +struct usbhsg_pipe_handle { + int (*prepare)(struct usbhsg_uep *uep, struct usbhsg_request *ureq); + int (*try_run)(struct usbhsg_uep *uep, struct usbhsg_request *ureq); + void (*irq_mask)(struct usbhsg_uep *uep, int enable); +}; + +struct usbhsg_recip_handle { + char *name; + int (*device)(struct usbhs_priv *priv, struct usbhsg_uep *uep, + struct usb_ctrlrequest *ctrl); + int (*interface)(struct usbhs_priv *priv, struct usbhsg_uep *uep, + struct usb_ctrlrequest *ctrl); + int (*endpoint)(struct usbhs_priv *priv, struct usbhsg_uep *uep, + struct usb_ctrlrequest *ctrl); +}; + +/* + * macro + */ +#define usbhsg_priv_to_gpriv(priv) \ + container_of( \ + usbhs_mod_get(priv, USBHS_GADGET), \ + struct usbhsg_gpriv, mod) + +#define __usbhsg_for_each_uep(start, pos, g, i) \ + for (i = start, pos = (g)->uep; \ + i < (g)->uep_size; \ + i++, pos = (g)->uep + i) + +#define usbhsg_for_each_uep(pos, gpriv, i) \ + __usbhsg_for_each_uep(1, pos, gpriv, i) + +#define usbhsg_for_each_uep_with_dcp(pos, gpriv, i) \ + __usbhsg_for_each_uep(0, pos, gpriv, i) + +#define usbhsg_gadget_to_gpriv(g)\ + container_of(g, struct usbhsg_gpriv, gadget) + +#define usbhsg_req_to_ureq(r)\ + container_of(r, struct usbhsg_request, req) + +#define usbhsg_ep_to_uep(e) container_of(e, struct usbhsg_uep, ep) +#define usbhsg_gpriv_to_lock(gp) usbhs_priv_to_lock((gp)->mod.priv) +#define usbhsg_gpriv_to_dev(gp) usbhs_priv_to_dev((gp)->mod.priv) +#define usbhsg_gpriv_to_priv(gp) ((gp)->mod.priv) +#define usbhsg_gpriv_to_dcp(gp) ((gp)->uep) +#define usbhsg_gpriv_to_nth_uep(gp, i) ((gp)->uep + i) +#define usbhsg_uep_to_gpriv(u) ((u)->gpriv) +#define usbhsg_uep_to_pipe(u) ((u)->pipe) +#define usbhsg_pipe_to_uep(p) ((p)->mod_private) +#define usbhsg_is_dcp(u) ((u) == usbhsg_gpriv_to_dcp((u)->gpriv)) + +#define usbhsg_is_not_connected(gp) ((gp)->gadget.speed == USB_SPEED_UNKNOWN) + +/* status */ +#define usbhsg_status_init(gp) do {(gp)->status = 0; } while (0) +#define usbhsg_status_set(gp, b) (gp->status |= b) +#define usbhsg_status_clr(gp, b) (gp->status &= ~b) +#define usbhsg_status_has(gp, b) (gp->status & b) + +/* + * usbhsg_trylock + * + * This driver don't use spin_try_lock + * to avoid warning of CONFIG_DEBUG_SPINLOCK + */ +static spinlock_t *usbhsg_trylock(struct usbhsg_gpriv *gpriv, + unsigned long *flags) +{ + spinlock_t *lock = usbhsg_gpriv_to_lock(gpriv); + + /* check spin lock status + * to avoid deadlock/nest */ + if (spin_is_locked(lock)) + return NULL; + + spin_lock_irqsave(lock, *flags); + + return lock; +} + +static void usbhsg_unlock(spinlock_t *lock, unsigned long *flags) +{ + if (!lock) + return; + + spin_unlock_irqrestore(lock, *flags); +} + +/* + * list push/pop + */ +static void usbhsg_queue_push(struct usbhsg_uep *uep, + struct usbhsg_request *ureq) +{ + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + + /* + ********* assume under spin lock ********* + */ + list_del_init(&ureq->node); + list_add_tail(&ureq->node, &uep->list); + ureq->req.actual = 0; + ureq->req.status = -EINPROGRESS; + + dev_dbg(dev, "pipe %d : queue push (%d)\n", + usbhs_pipe_number(pipe), + ureq->req.length); +} + +static struct usbhsg_request *usbhsg_queue_get(struct usbhsg_uep *uep) +{ + /* + ********* assume under spin lock ********* + */ + if (list_empty(&uep->list)) + return NULL; + + return list_entry(uep->list.next, struct usbhsg_request, node); +} + +#define usbhsg_queue_prepare(uep) __usbhsg_queue_handler(uep, 1); +#define usbhsg_queue_handle(uep) __usbhsg_queue_handler(uep, 0); +static int __usbhsg_queue_handler(struct usbhsg_uep *uep, int prepare) +{ + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + struct usbhsg_request *ureq; + spinlock_t *lock; + unsigned long flags; + int ret = 0; + + if (!uep->handler) { + dev_err(dev, "no handler function\n"); + return -EIO; + } + + /* + * CAUTION [*queue handler*] + * + * This function will be called for start/restart queue operation. + * OTOH the most much worry for USB driver is spinlock nest. + * Specially it are + * - usb_ep_ops :: queue + * - usb_request :: complete + * + * But the caller of this function need not care about spinlock. + * This function is using usbhsg_trylock for it. + * if "is_locked" is 1, this mean this function lock it. + * but if it is 0, this mean it is already under spin lock. + * see also + * CAUTION [*endpoint queue*] + * CAUTION [*request complete*] + */ + + /****************** spin try lock *******************/ + lock = usbhsg_trylock(gpriv, &flags); + + ureq = usbhsg_queue_get(uep); + if (ureq) { + if (prepare) + ret = uep->handler->prepare(uep, ureq); + else + ret = uep->handler->try_run(uep, ureq); + } + usbhsg_unlock(lock, &flags); + /******************** spin unlock ******************/ + + return ret; +} + +static void usbhsg_queue_pop(struct usbhsg_uep *uep, + struct usbhsg_request *ureq, + int status) +{ + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + + /* + ********* assume under spin lock ********* + */ + + /* + * CAUTION [*request complete*] + * + * There is a possibility not to be called in correct order + * if "complete" is called without spinlock. + * + * So, this function assume it is under spinlock, + * and call usb_request :: complete. + * + * But this "complete" will push next usb_request. + * It mean "usb_ep_ops :: queue" which is using spinlock is called + * under spinlock. + * + * To avoid dead-lock, this driver is using usbhsg_trylock. + * CAUTION [*endpoint queue*] + * CAUTION [*queue handler*] + */ + + dev_dbg(dev, "pipe %d : queue pop\n", usbhs_pipe_number(pipe)); + + list_del_init(&ureq->node); + + ureq->req.status = status; + ureq->req.complete(&uep->ep, &ureq->req); + + /* more request ? */ + if (0 == status) + usbhsg_queue_prepare(uep); +} + +/* + * irq enable/disable function + */ +#define usbhsg_irq_callback_ctrl(uep, status, enable) \ + ({ \ + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); \ + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); \ + struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); \ + struct usbhs_mod *mod = usbhs_mod_get_current(priv); \ + if (!mod) \ + return; \ + if (enable) \ + mod->irq_##status |= (1 << usbhs_pipe_number(pipe)); \ + else \ + mod->irq_##status &= ~(1 << usbhs_pipe_number(pipe)); \ + usbhs_irq_callback_update(priv, mod); \ + }) + +static void usbhsg_irq_empty_ctrl(struct usbhsg_uep *uep, int enable) +{ + usbhsg_irq_callback_ctrl(uep, bempsts, enable); +} + +static void usbhsg_irq_ready_ctrl(struct usbhsg_uep *uep, int enable) +{ + usbhsg_irq_callback_ctrl(uep, brdysts, enable); +} + +/* + * handler function + */ +static int usbhsg_try_run_ctrl_stage_end(struct usbhsg_uep *uep, + struct usbhsg_request *ureq) +{ + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + + /* + ********* assume under spin lock ********* + */ + + usbhs_dcp_control_transfer_done(pipe); + usbhsg_queue_pop(uep, ureq, 0); + + return 0; +} + +static int usbhsg_try_run_send_packet(struct usbhsg_uep *uep, + struct usbhsg_request *ureq) +{ + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + struct usb_request *req = &ureq->req; + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + void *buf; + int remainder, send; + int is_done = 0; + int enable; + int maxp; + + /* + ********* assume under spin lock ********* + */ + + maxp = usbhs_pipe_get_maxpacket(pipe); + buf = req->buf + req->actual; + remainder = req->length - req->actual; + + send = usbhs_fifo_write(pipe, buf, remainder); + + /* + * send < 0 : pipe busy + * send = 0 : send zero packet + * send > 0 : send data + * + * send <= max_packet + */ + if (send > 0) + req->actual += send; + + /* send all packet ? */ + if (send < remainder) + is_done = 0; /* there are remainder data */ + else if (send < maxp) + is_done = 1; /* short packet */ + else + is_done = !req->zero; /* send zero packet ? */ + + dev_dbg(dev, " send %d (%d/ %d/ %d/ %d)\n", + usbhs_pipe_number(pipe), + remainder, send, is_done, req->zero); + + /* + * enable interrupt and send again in irq handler + * if it still have remainder data which should be sent. + */ + enable = !is_done; + uep->handler->irq_mask(uep, enable); + + /* + * usbhs_fifo_enable execute + * - after callback_update, + * - before queue_pop / stage_end + */ + usbhs_fifo_enable(pipe); + + /* + * all data were sent ? + */ + if (is_done) { + /* it care below call in + "function mode" */ + if (usbhsg_is_dcp(uep)) + usbhs_dcp_control_transfer_done(pipe); + + usbhsg_queue_pop(uep, ureq, 0); + } + + return 0; +} + +static int usbhsg_prepare_send_packet(struct usbhsg_uep *uep, + struct usbhsg_request *ureq) +{ + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + + /* + ********* assume under spin lock ********* + */ + + usbhs_fifo_prepare_write(pipe); + usbhsg_try_run_send_packet(uep, ureq); + + return 0; +} + +static int usbhsg_try_run_receive_packet(struct usbhsg_uep *uep, + struct usbhsg_request *ureq) +{ + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + struct usb_request *req = &ureq->req; + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + void *buf; + int maxp; + int remainder, recv; + int is_done = 0; + + /* + ********* assume under spin lock ********* + */ + + maxp = usbhs_pipe_get_maxpacket(pipe); + buf = req->buf + req->actual; + remainder = req->length - req->actual; + + recv = usbhs_fifo_read(pipe, buf, remainder); + /* + * recv < 0 : pipe busy + * recv >= 0 : receive data + * + * recv <= max_packet + */ + if (recv < 0) + return -EBUSY; + + /* update parameters */ + req->actual += recv; + + if ((recv == remainder) || /* receive all data */ + (recv < maxp)) /* short packet */ + is_done = 1; + + dev_dbg(dev, " recv %d (%d/ %d/ %d/ %d)\n", + usbhs_pipe_number(pipe), + remainder, recv, is_done, req->zero); + + /* read all data ? */ + if (is_done) { + int disable = 0; + + uep->handler->irq_mask(uep, disable); + usbhs_fifo_disable(pipe); + usbhsg_queue_pop(uep, ureq, 0); + } + + return 0; +} + +static int usbhsg_prepare_receive_packet(struct usbhsg_uep *uep, + struct usbhsg_request *ureq) +{ + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + int enable = 1; + int ret; + + /* + ********* assume under spin lock ********* + */ + + ret = usbhs_fifo_prepare_read(pipe); + if (ret < 0) + return ret; + + /* + * data will be read in interrupt handler + */ + uep->handler->irq_mask(uep, enable); + + return ret; +} + +static struct usbhsg_pipe_handle usbhsg_handler_send_by_empty = { + .prepare = usbhsg_prepare_send_packet, + .try_run = usbhsg_try_run_send_packet, + .irq_mask = usbhsg_irq_empty_ctrl, +}; + +static struct usbhsg_pipe_handle usbhsg_handler_send_by_ready = { + .prepare = usbhsg_prepare_send_packet, + .try_run = usbhsg_try_run_send_packet, + .irq_mask = usbhsg_irq_ready_ctrl, +}; + +static struct usbhsg_pipe_handle usbhsg_handler_recv_by_ready = { + .prepare = usbhsg_prepare_receive_packet, + .try_run = usbhsg_try_run_receive_packet, + .irq_mask = usbhsg_irq_ready_ctrl, +}; + +static struct usbhsg_pipe_handle usbhsg_handler_ctrl_stage_end = { + .prepare = usbhsg_try_run_ctrl_stage_end, + .try_run = usbhsg_try_run_ctrl_stage_end, +}; + +/* + * DCP pipe can NOT use "ready interrupt" for "send" + * it should use "empty" interrupt. + * see + * "Operation" - "Interrupt Function" - "BRDY Interrupt" + * + * on the other hand, normal pipe can use "ready interrupt" for "send" + * even though it is single/double buffer + */ +#define usbhsg_handler_send_ctrl usbhsg_handler_send_by_empty +#define usbhsg_handler_recv_ctrl usbhsg_handler_recv_by_ready + +#define usbhsg_handler_send_packet usbhsg_handler_send_by_ready +#define usbhsg_handler_recv_packet usbhsg_handler_recv_by_ready + +/* + * USB_TYPE_STANDARD / clear feature functions + */ +static int usbhsg_recip_handler_std_control_done(struct usbhs_priv *priv, + struct usbhsg_uep *uep, + struct usb_ctrlrequest *ctrl) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + struct usbhsg_uep *dcp = usbhsg_gpriv_to_dcp(gpriv); + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(dcp); + + usbhs_dcp_control_transfer_done(pipe); + + return 0; +} + +static int usbhsg_recip_handler_std_clear_endpoint(struct usbhs_priv *priv, + struct usbhsg_uep *uep, + struct usb_ctrlrequest *ctrl) +{ + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + + if (!usbhsg_status_has(gpriv, USBHSG_STATUS_WEDGE)) { + usbhs_fifo_disable(pipe); + usbhs_pipe_clear_sequence(pipe); + usbhs_fifo_enable(pipe); + } + + usbhsg_recip_handler_std_control_done(priv, uep, ctrl); + + usbhsg_queue_prepare(uep); + + return 0; +} + +struct usbhsg_recip_handle req_clear_feature = { + .name = "clear feature", + .device = usbhsg_recip_handler_std_control_done, + .interface = usbhsg_recip_handler_std_control_done, + .endpoint = usbhsg_recip_handler_std_clear_endpoint, +}; + +/* + * USB_TYPE handler + */ +static int usbhsg_recip_run_handle(struct usbhs_priv *priv, + struct usbhsg_recip_handle *handler, + struct usb_ctrlrequest *ctrl) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + struct usbhsg_uep *uep; + int recip = ctrl->bRequestType & USB_RECIP_MASK; + int nth = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK; + int ret; + int (*func)(struct usbhs_priv *priv, struct usbhsg_uep *uep, + struct usb_ctrlrequest *ctrl); + char *msg; + + uep = usbhsg_gpriv_to_nth_uep(gpriv, nth); + if (!usbhsg_uep_to_pipe(uep)) { + dev_err(dev, "wrong recip request\n"); + return -EINVAL; + } + + switch (recip) { + case USB_RECIP_DEVICE: + msg = "DEVICE"; + func = handler->device; + break; + case USB_RECIP_INTERFACE: + msg = "INTERFACE"; + func = handler->interface; + break; + case USB_RECIP_ENDPOINT: + msg = "ENDPOINT"; + func = handler->endpoint; + break; + default: + dev_warn(dev, "unsupported RECIP(%d)\n", recip); + func = NULL; + ret = -EINVAL; + } + + if (func) { + dev_dbg(dev, "%s (pipe %d :%s)\n", handler->name, nth, msg); + ret = func(priv, uep, ctrl); + } + + return ret; +} + +/* + * irq functions + * + * it will be called from usbhs_interrupt + */ +static int usbhsg_irq_dev_state(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + + gpriv->gadget.speed = usbhs_status_get_usb_speed(irq_state); + + dev_dbg(dev, "state = %x : speed : %d\n", + usbhs_status_get_device_state(irq_state), + gpriv->gadget.speed); + + return 0; +} + +static int usbhsg_irq_ctrl_stage(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + struct usbhsg_uep *dcp = usbhsg_gpriv_to_dcp(gpriv); + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(dcp); + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + struct usb_ctrlrequest ctrl; + struct usbhsg_recip_handle *recip_handler = NULL; + int stage = usbhs_status_get_ctrl_stage(irq_state); + int ret = 0; + + dev_dbg(dev, "stage = %d\n", stage); + + /* + * see Manual + * + * "Operation" + * - "Interrupt Function" + * - "Control Transfer Stage Transition Interrupt" + * - Fig. "Control Transfer Stage Transitions" + */ + + switch (stage) { + case READ_DATA_STAGE: + dcp->handler = &usbhsg_handler_send_ctrl; + break; + case WRITE_DATA_STAGE: + dcp->handler = &usbhsg_handler_recv_ctrl; + break; + case NODATA_STATUS_STAGE: + dcp->handler = &usbhsg_handler_ctrl_stage_end; + break; + default: + return ret; + } + + /* + * get usb request + */ + usbhs_usbreq_get_val(priv, &ctrl); + + switch (ctrl.bRequestType & USB_TYPE_MASK) { + case USB_TYPE_STANDARD: + switch (ctrl.bRequest) { + case USB_REQ_CLEAR_FEATURE: + recip_handler = &req_clear_feature; + break; + } + } + + /* + * setup stage / run recip + */ + if (recip_handler) + ret = usbhsg_recip_run_handle(priv, recip_handler, &ctrl); + else + ret = gpriv->driver->setup(&gpriv->gadget, &ctrl); + + if (ret < 0) + usbhs_fifo_stall(pipe); + + return ret; +} + +static int usbhsg_irq_empty(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + struct usbhsg_uep *uep; + struct usbhs_pipe *pipe; + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + int i, ret; + + if (!irq_state->bempsts) { + dev_err(dev, "debug %s !!\n", __func__); + return -EIO; + } + + dev_dbg(dev, "irq empty [0x%04x]\n", irq_state->bempsts); + + /* + * search interrupted "pipe" + * not "uep". + */ + usbhs_for_each_pipe_with_dcp(pipe, priv, i) { + if (!(irq_state->bempsts & (1 << i))) + continue; + + uep = usbhsg_pipe_to_uep(pipe); + ret = usbhsg_queue_handle(uep); + if (ret < 0) + dev_err(dev, "send error %d : %d\n", i, ret); + } + + return 0; +} + +static int usbhsg_irq_ready(struct usbhs_priv *priv, + struct usbhs_irq_state *irq_state) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + struct usbhsg_uep *uep; + struct usbhs_pipe *pipe; + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + int i, ret; + + if (!irq_state->brdysts) { + dev_err(dev, "debug %s !!\n", __func__); + return -EIO; + } + + dev_dbg(dev, "irq ready [0x%04x]\n", irq_state->brdysts); + + /* + * search interrupted "pipe" + * not "uep". + */ + usbhs_for_each_pipe_with_dcp(pipe, priv, i) { + if (!(irq_state->brdysts & (1 << i))) + continue; + + uep = usbhsg_pipe_to_uep(pipe); + ret = usbhsg_queue_handle(uep); + if (ret < 0) + dev_err(dev, "receive error %d : %d\n", i, ret); + } + + return 0; +} + +/* + * + * usb_dcp_ops + * + */ +static int usbhsg_dcp_enable(struct usbhsg_uep *uep) +{ + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); + struct usbhs_pipe *pipe; + + /* + ********* assume under spin lock ********* + */ + + pipe = usbhs_dcp_malloc(priv); + if (!pipe) + return -EIO; + + uep->pipe = pipe; + uep->pipe->mod_private = uep; + INIT_LIST_HEAD(&uep->list); + + return 0; +} + +#define usbhsg_dcp_disable usbhsg_pipe_disable +static int usbhsg_pipe_disable(struct usbhsg_uep *uep) +{ + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + struct usbhsg_request *ureq; + int disable = 0; + + /* + ********* assume under spin lock ********* + */ + + usbhs_fifo_disable(pipe); + + /* + * disable pipe irq + */ + usbhsg_irq_empty_ctrl(uep, disable); + usbhsg_irq_ready_ctrl(uep, disable); + + while (1) { + ureq = usbhsg_queue_get(uep); + if (!ureq) + break; + + usbhsg_queue_pop(uep, ureq, -ECONNRESET); + } + + return 0; +} + +static void usbhsg_uep_init(struct usbhsg_gpriv *gpriv) +{ + int i; + struct usbhsg_uep *uep; + + usbhsg_for_each_uep_with_dcp(uep, gpriv, i) + uep->pipe = NULL; +} + +/* + * + * usb_ep_ops + * + */ +static int usbhsg_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct usbhsg_uep *uep = usbhsg_ep_to_uep(ep); + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); + struct usbhs_pipe *pipe; + spinlock_t *lock; + unsigned long flags; + int ret = -EIO; + + /* + * if it already have pipe, + * nothing to do + */ + if (uep->pipe) + return 0; + + /******************** spin lock ********************/ + lock = usbhsg_trylock(gpriv, &flags); + + pipe = usbhs_pipe_malloc(priv, desc); + if (pipe) { + uep->pipe = pipe; + pipe->mod_private = uep; + INIT_LIST_HEAD(&uep->list); + + if (usb_endpoint_dir_in(desc)) + uep->handler = &usbhsg_handler_send_packet; + else + uep->handler = &usbhsg_handler_recv_packet; + + ret = 0; + } + + usbhsg_unlock(lock, &flags); + /******************** spin unlock ******************/ + + return ret; +} + +static int usbhsg_ep_disable(struct usb_ep *ep) +{ + struct usbhsg_uep *uep = usbhsg_ep_to_uep(ep); + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + spinlock_t *lock; + unsigned long flags; + int ret; + + /******************** spin lock ********************/ + lock = usbhsg_trylock(gpriv, &flags); + + ret = usbhsg_pipe_disable(uep); + + usbhsg_unlock(lock, &flags); + /******************** spin unlock ******************/ + + return ret; +} + +static struct usb_request *usbhsg_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct usbhsg_request *ureq; + + ureq = kzalloc(sizeof *ureq, gfp_flags); + if (!ureq) + return NULL; + + INIT_LIST_HEAD(&ureq->node); + return &ureq->req; +} + +static void usbhsg_ep_free_request(struct usb_ep *ep, + struct usb_request *req) +{ + struct usbhsg_request *ureq = usbhsg_req_to_ureq(req); + + WARN_ON(!list_empty(&ureq->node)); + kfree(ureq); +} + +static int usbhsg_ep_queue(struct usb_ep *ep, struct usb_request *req, + gfp_t gfp_flags) +{ + struct usbhsg_uep *uep = usbhsg_ep_to_uep(ep); + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + struct usbhsg_request *ureq = usbhsg_req_to_ureq(req); + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + spinlock_t *lock; + unsigned long flags; + int ret = 0; + + /* + * CAUTION [*endpoint queue*] + * + * This function will be called from usb_request :: complete + * or usb driver timing. + * If this function is called from usb_request :: complete, + * it is already under spinlock on this driver. + * but it is called frm usb driver, this function should call spinlock. + * + * This function is using usbshg_trylock to solve this issue. + * if "is_locked" is 1, this mean this function lock it. + * but if it is 0, this mean it is already under spin lock. + * see also + * CAUTION [*queue handler*] + * CAUTION [*request complete*] + */ + + /******************** spin lock ********************/ + lock = usbhsg_trylock(gpriv, &flags); + + /* param check */ + if (usbhsg_is_not_connected(gpriv) || + unlikely(!gpriv->driver) || + unlikely(!pipe)) + ret = -ESHUTDOWN; + else + usbhsg_queue_push(uep, ureq); + + usbhsg_unlock(lock, &flags); + /******************** spin unlock ******************/ + + usbhsg_queue_prepare(uep); + + return ret; +} + +static int usbhsg_ep_dequeue(struct usb_ep *ep, struct usb_request *req) +{ + struct usbhsg_uep *uep = usbhsg_ep_to_uep(ep); + struct usbhsg_request *ureq = usbhsg_req_to_ureq(req); + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + spinlock_t *lock; + unsigned long flags; + + /* + * see + * CAUTION [*queue handler*] + * CAUTION [*endpoint queue*] + * CAUTION [*request complete*] + */ + + /******************** spin lock ********************/ + lock = usbhsg_trylock(gpriv, &flags); + + usbhsg_queue_pop(uep, ureq, -ECONNRESET); + + usbhsg_unlock(lock, &flags); + /******************** spin unlock ******************/ + + return 0; +} + +static int __usbhsg_ep_set_halt_wedge(struct usb_ep *ep, int halt, int wedge) +{ + struct usbhsg_uep *uep = usbhsg_ep_to_uep(ep); + struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + spinlock_t *lock; + unsigned long flags; + int ret = -EAGAIN; + + /* + * see + * CAUTION [*queue handler*] + * CAUTION [*endpoint queue*] + * CAUTION [*request complete*] + */ + + /******************** spin lock ********************/ + lock = usbhsg_trylock(gpriv, &flags); + if (!usbhsg_queue_get(uep)) { + + dev_dbg(dev, "set halt %d (pipe %d)\n", + halt, usbhs_pipe_number(pipe)); + + if (halt) + usbhs_fifo_stall(pipe); + else + usbhs_fifo_disable(pipe); + + if (halt && wedge) + usbhsg_status_set(gpriv, USBHSG_STATUS_WEDGE); + else + usbhsg_status_clr(gpriv, USBHSG_STATUS_WEDGE); + + ret = 0; + } + + usbhsg_unlock(lock, &flags); + /******************** spin unlock ******************/ + + return ret; +} + +static int usbhsg_ep_set_halt(struct usb_ep *ep, int value) +{ + return __usbhsg_ep_set_halt_wedge(ep, value, 0); +} + +static int usbhsg_ep_set_wedge(struct usb_ep *ep) +{ + return __usbhsg_ep_set_halt_wedge(ep, 1, 1); +} + +static struct usb_ep_ops usbhsg_ep_ops = { + .enable = usbhsg_ep_enable, + .disable = usbhsg_ep_disable, + + .alloc_request = usbhsg_ep_alloc_request, + .free_request = usbhsg_ep_free_request, + + .queue = usbhsg_ep_queue, + .dequeue = usbhsg_ep_dequeue, + + .set_halt = usbhsg_ep_set_halt, + .set_wedge = usbhsg_ep_set_wedge, +}; + +/* + * usb module start/end + */ +static int usbhsg_try_start(struct usbhs_priv *priv, u32 status) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + struct usbhsg_uep *dcp = usbhsg_gpriv_to_dcp(gpriv); + struct usbhs_mod *mod = usbhs_mod_get_current(priv); + struct device *dev = usbhs_priv_to_dev(priv); + spinlock_t *lock; + unsigned long flags; + + /******************** spin lock ********************/ + lock = usbhsg_trylock(gpriv, &flags); + + /* + * enable interrupt and systems if ready + */ + usbhsg_status_set(gpriv, status); + if (!(usbhsg_status_has(gpriv, USBHSG_STATUS_STARTED) && + usbhsg_status_has(gpriv, USBHSG_STATUS_REGISTERD))) + goto usbhsg_try_start_unlock; + + dev_dbg(dev, "start gadget\n"); + + /* + * pipe initialize and enable DCP + */ + usbhs_pipe_init(priv); + usbhsg_uep_init(gpriv); + usbhsg_dcp_enable(dcp); + + /* + * system config enble + * - HI speed + * - function + * - usb module + */ + usbhs_sys_hispeed_ctrl(priv, 1); + usbhs_sys_function_ctrl(priv, 1); + usbhs_sys_usb_ctrl(priv, 1); + + /* + * enable irq callback + */ + mod->irq_dev_state = usbhsg_irq_dev_state; + mod->irq_ctrl_stage = usbhsg_irq_ctrl_stage; + mod->irq_empty = usbhsg_irq_empty; + mod->irq_ready = usbhsg_irq_ready; + mod->irq_bempsts = 0; + mod->irq_brdysts = 0; + usbhs_irq_callback_update(priv, mod); + +usbhsg_try_start_unlock: + usbhsg_unlock(lock, &flags); + /******************** spin unlock ********************/ + + return 0; +} + +static int usbhsg_try_stop(struct usbhs_priv *priv, u32 status) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + struct usbhs_mod *mod = usbhs_mod_get_current(priv); + struct usbhsg_uep *dcp = usbhsg_gpriv_to_dcp(gpriv); + struct device *dev = usbhs_priv_to_dev(priv); + spinlock_t *lock; + unsigned long flags; + + /******************** spin lock ********************/ + lock = usbhsg_trylock(gpriv, &flags); + + /* + * disable interrupt and systems if 1st try + */ + usbhsg_status_clr(gpriv, status); + if (!usbhsg_status_has(gpriv, USBHSG_STATUS_STARTED) && + !usbhsg_status_has(gpriv, USBHSG_STATUS_REGISTERD)) + goto usbhsg_try_stop_unlock; + + /* disable all irq */ + mod->irq_dev_state = NULL; + mod->irq_ctrl_stage = NULL; + mod->irq_empty = NULL; + mod->irq_ready = NULL; + mod->irq_bempsts = 0; + mod->irq_brdysts = 0; + usbhs_irq_callback_update(priv, mod); + + usbhsg_dcp_disable(dcp); + + gpriv->gadget.speed = USB_SPEED_UNKNOWN; + + /* disable sys */ + usbhs_sys_hispeed_ctrl(priv, 0); + usbhs_sys_function_ctrl(priv, 0); + usbhs_sys_usb_ctrl(priv, 0); + + usbhsg_unlock(lock, &flags); + /******************** spin unlock ********************/ + + if (gpriv->driver && + gpriv->driver->disconnect) + gpriv->driver->disconnect(&gpriv->gadget); + + dev_dbg(dev, "stop gadget\n"); + + return 0; + +usbhsg_try_stop_unlock: + usbhsg_unlock(lock, &flags); + + return 0; +} + +/* + * + * linux usb function + * + */ +struct usbhsg_gpriv *the_controller; +int usb_gadget_probe_driver(struct usb_gadget_driver *driver, + int (*bind)(struct usb_gadget *)) +{ + struct usbhsg_gpriv *gpriv = the_controller; + struct usbhs_priv *priv; + struct device *dev; + int ret; + + if (!bind || + !driver || + !driver->setup || + driver->speed != USB_SPEED_HIGH) + return -EINVAL; + if (!gpriv) + return -ENODEV; + if (gpriv->driver) + return -EBUSY; + + dev = usbhsg_gpriv_to_dev(gpriv); + priv = usbhsg_gpriv_to_priv(gpriv); + + /* first hook up the driver ... */ + gpriv->driver = driver; + gpriv->gadget.dev.driver = &driver->driver; + + ret = device_add(&gpriv->gadget.dev); + if (ret) { + dev_err(dev, "device_add error %d\n", ret); + goto add_fail; + } + + ret = bind(&gpriv->gadget); + if (ret) { + dev_err(dev, "bind to driver %s error %d\n", + driver->driver.name, ret); + goto bind_fail; + } + + dev_dbg(dev, "bind %s\n", driver->driver.name); + + return usbhsg_try_start(priv, USBHSG_STATUS_REGISTERD); + +bind_fail: + device_del(&gpriv->gadget.dev); +add_fail: + gpriv->driver = NULL; + gpriv->gadget.dev.driver = NULL; + + return ret; +} +EXPORT_SYMBOL(usb_gadget_probe_driver); + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct usbhsg_gpriv *gpriv = the_controller; + struct usbhs_priv *priv; + struct device *dev = usbhsg_gpriv_to_dev(gpriv); + + if (!gpriv) + return -ENODEV; + + if (!driver || + !driver->unbind || + driver != gpriv->driver) + return -EINVAL; + + dev = usbhsg_gpriv_to_dev(gpriv); + priv = usbhsg_gpriv_to_priv(gpriv); + + usbhsg_try_stop(priv, USBHSG_STATUS_REGISTERD); + device_del(&gpriv->gadget.dev); + gpriv->driver = NULL; + + if (driver->disconnect) + driver->disconnect(&gpriv->gadget); + + driver->unbind(&gpriv->gadget); + dev_dbg(dev, "unbind %s\n", driver->driver.name); + + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +/* + * usb gadget ops + */ +static int usbhsg_get_frame(struct usb_gadget *gadget) +{ + struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget); + struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); + + return usbhs_frame_get_num(priv); +} + +static struct usb_gadget_ops usbhsg_gadget_ops = { + .get_frame = usbhsg_get_frame, +}; + +static int usbhsg_start(struct usbhs_priv *priv) +{ + return usbhsg_try_start(priv, USBHSG_STATUS_STARTED); +} + +static int usbhsg_stop(struct usbhs_priv *priv) +{ + return usbhsg_try_stop(priv, USBHSG_STATUS_STARTED); +} + +int __devinit usbhs_mod_gadget_probe(struct usbhs_priv *priv) +{ + struct usbhsg_gpriv *gpriv; + struct usbhsg_uep *uep; + struct device *dev = usbhs_priv_to_dev(priv); + int pipe_size = usbhs_get_dparam(priv, pipe_size); + int i; + + gpriv = kzalloc(sizeof(struct usbhsg_gpriv), GFP_KERNEL); + if (!gpriv) { + dev_err(dev, "Could not allocate gadget priv\n"); + return -ENOMEM; + } + + uep = kzalloc(sizeof(struct usbhsg_uep) * pipe_size, GFP_KERNEL); + if (!uep) { + dev_err(dev, "Could not allocate ep\n"); + goto usbhs_mod_gadget_probe_err_gpriv; + } + + /* + * CAUTION + * + * There is no guarantee that it is possible to access usb module here. + * Don't accesses to it. + * The accesse will be enable after "usbhsg_start" + */ + + /* + * register itself + */ + usbhs_mod_register(priv, &gpriv->mod, USBHS_GADGET); + + /* init gpriv */ + gpriv->mod.name = "gadget"; + gpriv->mod.start = usbhsg_start; + gpriv->mod.stop = usbhsg_stop; + gpriv->uep = uep; + gpriv->uep_size = pipe_size; + usbhsg_status_init(gpriv); + + /* + * init gadget + */ + device_initialize(&gpriv->gadget.dev); + dev_set_name(&gpriv->gadget.dev, "gadget"); + gpriv->gadget.dev.parent = dev; + gpriv->gadget.name = "renesas_usbhs_udc"; + gpriv->gadget.ops = &usbhsg_gadget_ops; + gpriv->gadget.is_dualspeed = 1; + + INIT_LIST_HEAD(&gpriv->gadget.ep_list); + + /* + * init usb_ep + */ + usbhsg_for_each_uep_with_dcp(uep, gpriv, i) { + uep->gpriv = gpriv; + snprintf(uep->ep_name, EP_NAME_SIZE, "ep%d", i); + + uep->ep.name = uep->ep_name; + uep->ep.ops = &usbhsg_ep_ops; + INIT_LIST_HEAD(&uep->ep.ep_list); + INIT_LIST_HEAD(&uep->list); + + /* init DCP */ + if (usbhsg_is_dcp(uep)) { + gpriv->gadget.ep0 = &uep->ep; + uep->ep.maxpacket = 64; + } + /* init normal pipe */ + else { + uep->ep.maxpacket = 512; + list_add_tail(&uep->ep.ep_list, &gpriv->gadget.ep_list); + } + } + + the_controller = gpriv; + + dev_info(dev, "gadget probed\n"); + + return 0; + +usbhs_mod_gadget_probe_err_gpriv: + kfree(gpriv); + + return -ENOMEM; +} + +void __devexit usbhs_mod_gadget_remove(struct usbhs_priv *priv) +{ + struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + + kfree(gpriv); +} diff --git a/drivers/usb/renesas_usbhs/pipe.c b/drivers/usb/renesas_usbhs/pipe.c new file mode 100644 index 00000000000..bc4521c5426 --- /dev/null +++ b/drivers/usb/renesas_usbhs/pipe.c @@ -0,0 +1,874 @@ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/slab.h> +#include "./common.h" +#include "./pipe.h" + +/* + * macros + */ +#define usbhsp_priv_to_pipeinfo(pr) (&(pr)->pipe_info) +#define usbhsp_pipe_to_priv(p) ((p)->priv) + +#define usbhsp_addr_offset(p) ((usbhs_pipe_number(p) - 1) * 2) + +#define usbhsp_is_dcp(p) ((p)->priv->pipe_info.pipe == (p)) + +#define usbhsp_flags_set(p, f) ((p)->flags |= USBHS_PIPE_FLAGS_##f) +#define usbhsp_flags_clr(p, f) ((p)->flags &= ~USBHS_PIPE_FLAGS_##f) +#define usbhsp_flags_has(p, f) ((p)->flags & USBHS_PIPE_FLAGS_##f) +#define usbhsp_flags_init(p) do {(p)->flags = 0; } while (0) + +#define usbhsp_type(p) ((p)->pipe_type) +#define usbhsp_type_is(p, t) ((p)->pipe_type == t) + +/* + * for debug + */ +static char *usbhsp_pipe_name[] = { + [USB_ENDPOINT_XFER_CONTROL] = "DCP", + [USB_ENDPOINT_XFER_BULK] = "BULK", + [USB_ENDPOINT_XFER_INT] = "INT", + [USB_ENDPOINT_XFER_ISOC] = "ISO", +}; + +/* + * usb request functions + */ +void usbhs_usbreq_get_val(struct usbhs_priv *priv, struct usb_ctrlrequest *req) +{ + u16 val; + + val = usbhs_read(priv, USBREQ); + req->bRequest = (val >> 8) & 0xFF; + req->bRequestType = (val >> 0) & 0xFF; + + req->wValue = usbhs_read(priv, USBVAL); + req->wIndex = usbhs_read(priv, USBINDX); + req->wLength = usbhs_read(priv, USBLENG); +} + +void usbhs_usbreq_set_val(struct usbhs_priv *priv, struct usb_ctrlrequest *req) +{ + usbhs_write(priv, USBREQ, (req->bRequest << 8) | req->bRequestType); + usbhs_write(priv, USBVAL, req->wValue); + usbhs_write(priv, USBINDX, req->wIndex); + usbhs_write(priv, USBLENG, req->wLength); +} + +/* + * DCPCTR/PIPEnCTR functions + */ +static void usbhsp_pipectrl_set(struct usbhs_pipe *pipe, u16 mask, u16 val) +{ + struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe); + int offset = usbhsp_addr_offset(pipe); + + if (usbhsp_is_dcp(pipe)) + usbhs_bset(priv, DCPCTR, mask, val); + else + usbhs_bset(priv, PIPEnCTR + offset, mask, val); +} + +static u16 usbhsp_pipectrl_get(struct usbhs_pipe *pipe) +{ + struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe); + int offset = usbhsp_addr_offset(pipe); + + if (usbhsp_is_dcp(pipe)) + return usbhs_read(priv, DCPCTR); + else + return usbhs_read(priv, PIPEnCTR + offset); +} + +/* + * DCP/PIPE functions + */ +static void __usbhsp_pipe_xxx_set(struct usbhs_pipe *pipe, + u16 dcp_reg, u16 pipe_reg, + u16 mask, u16 val) +{ + struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe); + + if (usbhsp_is_dcp(pipe)) + usbhs_bset(priv, dcp_reg, mask, val); + else + usbhs_bset(priv, pipe_reg, mask, val); +} + +static u16 __usbhsp_pipe_xxx_get(struct usbhs_pipe *pipe, + u16 dcp_reg, u16 pipe_reg) +{ + struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe); + + if (usbhsp_is_dcp(pipe)) + return usbhs_read(priv, dcp_reg); + else + return usbhs_read(priv, pipe_reg); +} + +/* + * DCPCFG/PIPECFG functions + */ +static void usbhsp_pipe_cfg_set(struct usbhs_pipe *pipe, u16 mask, u16 val) +{ + __usbhsp_pipe_xxx_set(pipe, DCPCFG, PIPECFG, mask, val); +} + +/* + * PIPEBUF + */ +static void usbhsp_pipe_buf_set(struct usbhs_pipe *pipe, u16 mask, u16 val) +{ + if (usbhsp_is_dcp(pipe)) + return; + + __usbhsp_pipe_xxx_set(pipe, 0, PIPEBUF, mask, val); +} + +/* + * DCPMAXP/PIPEMAXP + */ +static void usbhsp_pipe_maxp_set(struct usbhs_pipe *pipe, u16 mask, u16 val) +{ + __usbhsp_pipe_xxx_set(pipe, DCPMAXP, PIPEMAXP, mask, val); +} + +static u16 usbhsp_pipe_maxp_get(struct usbhs_pipe *pipe) +{ + return __usbhsp_pipe_xxx_get(pipe, DCPMAXP, PIPEMAXP); +} + +/* + * pipe control functions + */ +static void usbhsp_pipe_select(struct usbhs_pipe *pipe) +{ + struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe); + + /* + * On pipe, this is necessary before + * accesses to below registers. + * + * PIPESEL : usbhsp_pipe_select + * PIPECFG : usbhsp_pipe_cfg_xxx + * PIPEBUF : usbhsp_pipe_buf_xxx + * PIPEMAXP : usbhsp_pipe_maxp_xxx + * PIPEPERI + */ + + /* + * if pipe is dcp, no pipe is selected. + * it is no problem, because dcp have its register + */ + usbhs_write(priv, PIPESEL, 0xF & usbhs_pipe_number(pipe)); +} + +static int usbhsp_pipe_barrier(struct usbhs_pipe *pipe) +{ + struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe); + int timeout = 1024; + u16 val; + + /* + * make sure.... + * + * Modify these bits when CSSTS = 0, PID = NAK, and no pipe number is + * specified by the CURPIPE bits. + * When changing the setting of this bit after changing + * the PID bits for the selected pipe from BUF to NAK, + * check that CSSTS = 0 and PBUSY = 0. + */ + + /* + * CURPIPE bit = 0 + * + * see also + * "Operation" + * - "Pipe Control" + * - "Pipe Control Registers Switching Procedure" + */ + usbhs_write(priv, CFIFOSEL, 0); + usbhs_fifo_disable(pipe); + + do { + val = usbhsp_pipectrl_get(pipe); + val &= CSSTS | PID_MASK; + if (!val) + return 0; + + udelay(10); + + } while (timeout--); + + return -EBUSY; +} + +static int usbhsp_pipe_is_accessible(struct usbhs_pipe *pipe) +{ + u16 val; + + val = usbhsp_pipectrl_get(pipe); + if (val & BSTS) + return 0; + + return -EBUSY; +} + +/* + * PID ctrl + */ +static void __usbhsp_pid_try_nak_if_stall(struct usbhs_pipe *pipe) +{ + u16 pid = usbhsp_pipectrl_get(pipe); + + pid &= PID_MASK; + + /* + * see + * "Pipe n Control Register" - "PID" + */ + switch (pid) { + case PID_STALL11: + usbhsp_pipectrl_set(pipe, PID_MASK, PID_STALL10); + /* fall-through */ + case PID_STALL10: + usbhsp_pipectrl_set(pipe, PID_MASK, PID_NAK); + } +} + +void usbhs_fifo_disable(struct usbhs_pipe *pipe) +{ + int timeout = 1024; + u16 val; + + /* see "Pipe n Control Register" - "PID" */ + __usbhsp_pid_try_nak_if_stall(pipe); + + usbhsp_pipectrl_set(pipe, PID_MASK, PID_NAK); + + do { + val = usbhsp_pipectrl_get(pipe); + val &= PBUSY; + if (!val) + break; + + udelay(10); + } while (timeout--); +} + +void usbhs_fifo_enable(struct usbhs_pipe *pipe) +{ + /* see "Pipe n Control Register" - "PID" */ + __usbhsp_pid_try_nak_if_stall(pipe); + + usbhsp_pipectrl_set(pipe, PID_MASK, PID_BUF); +} + +void usbhs_fifo_stall(struct usbhs_pipe *pipe) +{ + u16 pid = usbhsp_pipectrl_get(pipe); + + pid &= PID_MASK; + + /* + * see + * "Pipe n Control Register" - "PID" + */ + switch (pid) { + case PID_NAK: + usbhsp_pipectrl_set(pipe, PID_MASK, PID_STALL10); + break; + case PID_BUF: + usbhsp_pipectrl_set(pipe, PID_MASK, PID_STALL11); + break; + } +} + +/* + * CFIFO ctrl + */ +void usbhs_fifo_send_terminator(struct usbhs_pipe *pipe) +{ + struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe); + + usbhs_bset(priv, CFIFOCTR, BVAL, BVAL); +} + +static void usbhsp_fifo_clear(struct usbhs_pipe *pipe) +{ + struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe); + + usbhs_write(priv, CFIFOCTR, BCLR); +} + +static int usbhsp_fifo_barrier(struct usbhs_priv *priv) +{ + int timeout = 1024; + + do { + /* The FIFO port is accessible */ + if (usbhs_read(priv, CFIFOCTR) & FRDY) + return 0; + + udelay(10); + } while (timeout--); + + return -EBUSY; +} + +static int usbhsp_fifo_rcv_len(struct usbhs_priv *priv) +{ + return usbhs_read(priv, CFIFOCTR) & DTLN_MASK; +} + +static int usbhsp_fifo_select(struct usbhs_pipe *pipe, int write) +{ + struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe); + struct device *dev = usbhs_priv_to_dev(priv); + int timeout = 1024; + u16 mask = ((1 << 5) | 0xF); /* mask of ISEL | CURPIPE */ + u16 base = usbhs_pipe_number(pipe); /* CURPIPE */ + + if (usbhsp_is_dcp(pipe)) + base |= (1 == write) << 5; /* ISEL */ + + /* "base" will be used below */ + usbhs_write(priv, CFIFOSEL, base | MBW_32); + + /* check ISEL and CURPIPE value */ + while (timeout--) { + if (base == (mask & usbhs_read(priv, CFIFOSEL))) + return 0; + udelay(10); + } + + dev_err(dev, "fifo select error\n"); + + return -EIO; +} + +int usbhs_fifo_prepare_write(struct usbhs_pipe *pipe) +{ + return usbhsp_fifo_select(pipe, 1); +} + +int usbhs_fifo_write(struct usbhs_pipe *pipe, u8 *buf, int len) +{ + struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe); + void __iomem *addr = priv->base + CFIFO; + int maxp = usbhs_pipe_get_maxpacket(pipe); + int total_len; + int i, ret; + + ret = usbhsp_pipe_is_accessible(pipe); + if (ret < 0) + return ret; + + ret = usbhsp_fifo_select(pipe, 1); + if (ret < 0) + return ret; + + ret = usbhsp_fifo_barrier(priv); + if (ret < 0) + return ret; + + len = min(len, maxp); + total_len = len; + + /* + * FIXME + * + * 32-bit access only + */ + if (len >= 4 && + !((unsigned long)buf & 0x03)) { + iowrite32_rep(addr, buf, len / 4); + len %= 4; + buf += total_len - len; + } + + /* the rest operation */ + for (i = 0; i < len; i++) + iowrite8(buf[i], addr + (0x03 - (i & 0x03))); + + if (total_len < maxp) + usbhs_fifo_send_terminator(pipe); + + return total_len; +} + +int usbhs_fifo_prepare_read(struct usbhs_pipe *pipe) +{ + int ret; + + /* + * select pipe and enable it to prepare packet receive + */ + ret = usbhsp_fifo_select(pipe, 0); + if (ret < 0) + return ret; + + usbhs_fifo_enable(pipe); + + return ret; +} + +int usbhs_fifo_read(struct usbhs_pipe *pipe, u8 *buf, int len) +{ + struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe); + void __iomem *addr = priv->base + CFIFO; + int rcv_len; + int i, ret; + int total_len; + u32 data = 0; + + ret = usbhsp_fifo_select(pipe, 0); + if (ret < 0) + return ret; + + ret = usbhsp_fifo_barrier(priv); + if (ret < 0) + return ret; + + rcv_len = usbhsp_fifo_rcv_len(priv); + + /* + * Buffer clear if Zero-Length packet + * + * see + * "Operation" - "FIFO Buffer Memory" - "FIFO Port Function" + */ + if (0 == rcv_len) { + usbhsp_fifo_clear(pipe); + return 0; + } + + len = min(rcv_len, len); + total_len = len; + + /* + * FIXME + * + * 32-bit access only + */ + if (len >= 4 && + !((unsigned long)buf & 0x03)) { + ioread32_rep(addr, buf, len / 4); + len %= 4; + buf += rcv_len - len; + } + + /* the rest operation */ + for (i = 0; i < len; i++) { + if (!(i & 0x03)) + data = ioread32(addr); + + buf[i] = (data >> ((i & 0x03) * 8)) & 0xff; + } + + return total_len; +} + +/* + * pipe setup + */ +static int usbhsp_possible_double_buffer(struct usbhs_pipe *pipe) +{ + /* + * only ISO / BULK pipe can use double buffer + */ + if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_BULK) || + usbhsp_type_is(pipe, USB_ENDPOINT_XFER_ISOC)) + return 1; + + return 0; +} + +static u16 usbhsp_setup_pipecfg(struct usbhs_pipe *pipe, + const struct usb_endpoint_descriptor *desc, + int is_host) +{ + u16 type = 0; + u16 bfre = 0; + u16 dblb = 0; + u16 cntmd = 0; + u16 dir = 0; + u16 epnum = 0; + u16 shtnak = 0; + u16 type_array[] = { + [USB_ENDPOINT_XFER_BULK] = TYPE_BULK, + [USB_ENDPOINT_XFER_INT] = TYPE_INT, + [USB_ENDPOINT_XFER_ISOC] = TYPE_ISO, + }; + int is_double = usbhsp_possible_double_buffer(pipe); + + if (usbhsp_is_dcp(pipe)) + return -EINVAL; + + /* + * PIPECFG + * + * see + * - "Register Descriptions" - "PIPECFG" register + * - "Features" - "Pipe configuration" + * - "Operation" - "Pipe Control" + */ + + /* TYPE */ + type = type_array[usbhsp_type(pipe)]; + + /* BFRE */ + if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_ISOC) || + usbhsp_type_is(pipe, USB_ENDPOINT_XFER_BULK)) + bfre = 0; /* FIXME */ + + /* DBLB */ + if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_ISOC) || + usbhsp_type_is(pipe, USB_ENDPOINT_XFER_BULK)) + dblb = (is_double) ? DBLB : 0; + + /* CNTMD */ + if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_BULK)) + cntmd = 0; /* FIXME */ + + /* DIR */ + if (usb_endpoint_dir_in(desc)) + usbhsp_flags_set(pipe, IS_DIR_IN); + + if ((is_host && usb_endpoint_dir_out(desc)) || + (!is_host && usb_endpoint_dir_in(desc))) + dir |= DIR_OUT; + + /* SHTNAK */ + if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_BULK) && + !dir) + shtnak = SHTNAK; + + /* EPNUM */ + epnum = 0xF & usb_endpoint_num(desc); + + return type | + bfre | + dblb | + cntmd | + dir | + shtnak | + epnum; +} + +static u16 usbhsp_setup_pipemaxp(struct usbhs_pipe *pipe, + const struct usb_endpoint_descriptor *desc, + int is_host) +{ + /* host should set DEVSEL */ + + /* reutn MXPS */ + return PIPE_MAXP_MASK & le16_to_cpu(desc->wMaxPacketSize); +} + +static u16 usbhsp_setup_pipebuff(struct usbhs_pipe *pipe, + const struct usb_endpoint_descriptor *desc, + int is_host) +{ + struct usbhs_priv *priv = usbhsp_pipe_to_priv(pipe); + struct usbhs_pipe_info *info = usbhsp_priv_to_pipeinfo(priv); + struct device *dev = usbhs_priv_to_dev(priv); + int pipe_num = usbhs_pipe_number(pipe); + int is_double = usbhsp_possible_double_buffer(pipe); + u16 buff_size; + u16 bufnmb; + u16 bufnmb_cnt; + + /* + * PIPEBUF + * + * see + * - "Register Descriptions" - "PIPEBUF" register + * - "Features" - "Pipe configuration" + * - "Operation" - "FIFO Buffer Memory" + * - "Operation" - "Pipe Control" + * + * ex) if pipe6 - pipe9 are USB_ENDPOINT_XFER_INT (SH7724) + * + * BUFNMB: PIPE + * 0: pipe0 (DCP 256byte) + * 1: - + * 2: - + * 3: - + * 4: pipe6 (INT 64byte) + * 5: pipe7 (INT 64byte) + * 6: pipe8 (INT 64byte) + * 7: pipe9 (INT 64byte) + * 8 - xx: free (for BULK, ISOC) + */ + + /* + * FIXME + * + * it doesn't have good buffer allocator + * + * DCP : 256 byte + * BULK: 512 byte + * INT : 64 byte + * ISOC: 512 byte + */ + if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_CONTROL)) + buff_size = 256; + else if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_INT)) + buff_size = 64; + else + buff_size = 512; + + /* change buff_size to register value */ + bufnmb_cnt = (buff_size / 64) - 1; + + /* BUFNMB has been reserved for INT pipe + * see above */ + if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_INT)) { + bufnmb = pipe_num - 2; + } else { + bufnmb = info->bufnmb_last; + info->bufnmb_last += bufnmb_cnt + 1; + + /* + * double buffer + */ + if (is_double) + info->bufnmb_last += bufnmb_cnt + 1; + } + + dev_dbg(dev, "pipe : %d : buff_size 0x%x: bufnmb 0x%x\n", + pipe_num, buff_size, bufnmb); + + return (0x1f & bufnmb_cnt) << 10 | + (0xff & bufnmb) << 0; +} + +/* + * pipe control + */ +int usbhs_pipe_get_maxpacket(struct usbhs_pipe *pipe) +{ + u16 mask = usbhsp_is_dcp(pipe) ? DCP_MAXP_MASK : PIPE_MAXP_MASK; + + usbhsp_pipe_select(pipe); + + return (int)(usbhsp_pipe_maxp_get(pipe) & mask); +} + +int usbhs_pipe_is_dir_in(struct usbhs_pipe *pipe) +{ + return usbhsp_flags_has(pipe, IS_DIR_IN); +} + +void usbhs_pipe_clear_sequence(struct usbhs_pipe *pipe) +{ + usbhsp_pipectrl_set(pipe, SQCLR, SQCLR); +} + +static struct usbhs_pipe *usbhsp_get_pipe(struct usbhs_priv *priv, u32 type) +{ + struct usbhs_pipe *pos, *pipe; + int i; + + /* + * find target pipe + */ + pipe = NULL; + usbhs_for_each_pipe_with_dcp(pos, priv, i) { + if (!usbhsp_type_is(pos, type)) + continue; + if (usbhsp_flags_has(pos, IS_USED)) + continue; + + pipe = pos; + break; + } + + if (!pipe) + return NULL; + + /* + * initialize pipe flags + */ + usbhsp_flags_init(pipe); + usbhsp_flags_set(pipe, IS_USED); + + return pipe; +} + +void usbhs_pipe_init(struct usbhs_priv *priv) +{ + struct usbhs_pipe_info *info = usbhsp_priv_to_pipeinfo(priv); + struct usbhs_pipe *pipe; + int i; + + /* + * FIXME + * + * driver needs good allocator. + * + * find first free buffer area (BULK, ISOC) + * (DCP, INT area is fixed) + * + * buffer number 0 - 3 have been reserved for DCP + * see + * usbhsp_to_bufnmb + */ + info->bufnmb_last = 4; + usbhs_for_each_pipe_with_dcp(pipe, priv, i) { + if (usbhsp_type_is(pipe, USB_ENDPOINT_XFER_INT)) + info->bufnmb_last++; + + usbhsp_flags_init(pipe); + pipe->mod_private = NULL; + + usbhsp_fifo_clear(pipe); + } +} + +struct usbhs_pipe *usbhs_pipe_malloc(struct usbhs_priv *priv, + const struct usb_endpoint_descriptor *desc) +{ + struct device *dev = usbhs_priv_to_dev(priv); + struct usbhs_mod *mod = usbhs_mod_get_current(priv); + struct usbhs_pipe *pipe; + int is_host = usbhs_mod_is_host(priv, mod); + int ret; + u16 pipecfg, pipebuf, pipemaxp; + + pipe = usbhsp_get_pipe(priv, usb_endpoint_type(desc)); + if (!pipe) { + dev_err(dev, "can't get pipe (%s)\n", + usbhsp_pipe_name[usb_endpoint_type(desc)]); + return NULL; + } + + usbhs_fifo_disable(pipe); + + /* make sure pipe is not busy */ + ret = usbhsp_pipe_barrier(pipe); + if (ret < 0) { + dev_err(dev, "pipe setup failed %d\n", usbhs_pipe_number(pipe)); + return NULL; + } + + pipecfg = usbhsp_setup_pipecfg(pipe, desc, is_host); + pipebuf = usbhsp_setup_pipebuff(pipe, desc, is_host); + pipemaxp = usbhsp_setup_pipemaxp(pipe, desc, is_host); + + /* buffer clear + * see PIPECFG :: BFRE */ + usbhsp_pipectrl_set(pipe, ACLRM, ACLRM); + usbhsp_pipectrl_set(pipe, ACLRM, 0); + + usbhsp_pipe_select(pipe); + usbhsp_pipe_cfg_set(pipe, 0xFFFF, pipecfg); + usbhsp_pipe_buf_set(pipe, 0xFFFF, pipebuf); + usbhsp_pipe_maxp_set(pipe, 0xFFFF, pipemaxp); + + usbhs_pipe_clear_sequence(pipe); + + dev_dbg(dev, "enable pipe %d : %s (%s)\n", + usbhs_pipe_number(pipe), + usbhsp_pipe_name[usb_endpoint_type(desc)], + usbhs_pipe_is_dir_in(pipe) ? "in" : "out"); + + return pipe; +} + +/* + * dcp control + */ +struct usbhs_pipe *usbhs_dcp_malloc(struct usbhs_priv *priv) +{ + struct usbhs_pipe *pipe; + + pipe = usbhsp_get_pipe(priv, USB_ENDPOINT_XFER_CONTROL); + if (!pipe) + return NULL; + + /* + * dcpcfg : default + * dcpmaxp : default + * pipebuf : nothing to do + */ + + usbhsp_pipe_select(pipe); + usbhs_pipe_clear_sequence(pipe); + + return pipe; +} + +void usbhs_dcp_control_transfer_done(struct usbhs_pipe *pipe) +{ + WARN_ON(!usbhsp_is_dcp(pipe)); + + usbhs_fifo_enable(pipe); + usbhsp_pipectrl_set(pipe, CCPL, CCPL); +} + + +/* + * pipe module function + */ +int usbhs_pipe_probe(struct usbhs_priv *priv) +{ + struct usbhs_pipe_info *info = usbhsp_priv_to_pipeinfo(priv); + struct usbhs_pipe *pipe; + struct device *dev = usbhs_priv_to_dev(priv); + u32 *pipe_type = usbhs_get_dparam(priv, pipe_type); + int pipe_size = usbhs_get_dparam(priv, pipe_size); + int i; + + /* This driver expects 1st pipe is DCP */ + if (pipe_type[0] != USB_ENDPOINT_XFER_CONTROL) { + dev_err(dev, "1st PIPE is not DCP\n"); + return -EINVAL; + } + + info->pipe = kzalloc(sizeof(struct usbhs_pipe) * pipe_size, GFP_KERNEL); + if (!info->pipe) { + dev_err(dev, "Could not allocate pipe\n"); + return -ENOMEM; + } + + info->size = pipe_size; + + /* + * init pipe + */ + usbhs_for_each_pipe_with_dcp(pipe, priv, i) { + pipe->priv = priv; + usbhsp_type(pipe) = pipe_type[i] & USB_ENDPOINT_XFERTYPE_MASK; + + dev_dbg(dev, "pipe %x\t: %s\n", + i, usbhsp_pipe_name[pipe_type[i]]); + } + + return 0; +} + +void usbhs_pipe_remove(struct usbhs_priv *priv) +{ + struct usbhs_pipe_info *info = usbhsp_priv_to_pipeinfo(priv); + + kfree(info->pipe); +} diff --git a/drivers/usb/renesas_usbhs/pipe.h b/drivers/usb/renesas_usbhs/pipe.h new file mode 100644 index 00000000000..1cca9b7fb26 --- /dev/null +++ b/drivers/usb/renesas_usbhs/pipe.h @@ -0,0 +1,104 @@ +/* + * Renesas USB driver + * + * Copyright (C) 2011 Renesas Solutions Corp. + * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ +#ifndef RENESAS_USB_PIPE_H +#define RENESAS_USB_PIPE_H + +#include "./common.h" + +/* + * struct + */ +struct usbhs_pipe { + u32 pipe_type; /* USB_ENDPOINT_XFER_xxx */ + + struct usbhs_priv *priv; + + u32 flags; +#define USBHS_PIPE_FLAGS_IS_USED (1 << 0) +#define USBHS_PIPE_FLAGS_IS_DIR_IN (1 << 1) + + void *mod_private; +}; + +struct usbhs_pipe_info { + struct usbhs_pipe *pipe; + int size; /* array size of "pipe" */ + int bufnmb_last; /* FIXME : driver needs good allocator */ +}; + +/* + * pipe list + */ +#define __usbhs_for_each_pipe(start, pos, info, i) \ + for (i = start, pos = (info)->pipe; \ + i < (info)->size; \ + i++, pos = (info)->pipe + i) + +#define usbhs_for_each_pipe(pos, priv, i) \ + __usbhs_for_each_pipe(1, pos, &((priv)->pipe_info), i) + +#define usbhs_for_each_pipe_with_dcp(pos, priv, i) \ + __usbhs_for_each_pipe(0, pos, &((priv)->pipe_info), i) + +/* + * pipe module probe / remove + */ +int usbhs_pipe_probe(struct usbhs_priv *priv); +void usbhs_pipe_remove(struct usbhs_priv *priv); + +/* + * cfifo + */ +int usbhs_fifo_write(struct usbhs_pipe *pipe, u8 *buf, int len); +int usbhs_fifo_read(struct usbhs_pipe *pipe, u8 *buf, int len); +int usbhs_fifo_prepare_write(struct usbhs_pipe *pipe); +int usbhs_fifo_prepare_read(struct usbhs_pipe *pipe); + +void usbhs_fifo_enable(struct usbhs_pipe *pipe); +void usbhs_fifo_disable(struct usbhs_pipe *pipe); +void usbhs_fifo_stall(struct usbhs_pipe *pipe); + +void usbhs_fifo_send_terminator(struct usbhs_pipe *pipe); + + +/* + * usb request + */ +void usbhs_usbreq_get_val(struct usbhs_priv *priv, struct usb_ctrlrequest *req); +void usbhs_usbreq_set_val(struct usbhs_priv *priv, struct usb_ctrlrequest *req); + +/* + * pipe control + */ +struct usbhs_pipe +*usbhs_pipe_malloc(struct usbhs_priv *priv, + const struct usb_endpoint_descriptor *desc); + +int usbhs_pipe_is_dir_in(struct usbhs_pipe *pipe); +void usbhs_pipe_init(struct usbhs_priv *priv); +int usbhs_pipe_get_maxpacket(struct usbhs_pipe *pipe); +void usbhs_pipe_clear_sequence(struct usbhs_pipe *pipe); + +#define usbhs_pipe_number(p) (int)((p) - (p)->priv->pipe_info.pipe) + +/* + * dcp control + */ +struct usbhs_pipe *usbhs_dcp_malloc(struct usbhs_priv *priv); +void usbhs_dcp_control_transfer_done(struct usbhs_pipe *pipe); + +#endif /* RENESAS_USB_PIPE_H */ |