diff options
Diffstat (limited to 'drivers/usb/renesas_usbhs')
| -rw-r--r-- | drivers/usb/renesas_usbhs/Kconfig | 15 | ||||
| -rw-r--r-- | drivers/usb/renesas_usbhs/Makefile | 15 | ||||
| -rw-r--r-- | drivers/usb/renesas_usbhs/common.c | 645 | ||||
| -rw-r--r-- | drivers/usb/renesas_usbhs/common.h | 325 | ||||
| -rw-r--r-- | drivers/usb/renesas_usbhs/fifo.c | 1187 | ||||
| -rw-r--r-- | drivers/usb/renesas_usbhs/fifo.h | 102 | ||||
| -rw-r--r-- | drivers/usb/renesas_usbhs/mod.c | 387 | ||||
| -rw-r--r-- | drivers/usb/renesas_usbhs/mod.h | 172 | ||||
| -rw-r--r-- | drivers/usb/renesas_usbhs/mod_gadget.c | 1025 | ||||
| -rw-r--r-- | drivers/usb/renesas_usbhs/mod_host.c | 1586 | ||||
| -rw-r--r-- | drivers/usb/renesas_usbhs/pipe.c | 826 | ||||
| -rw-r--r-- | drivers/usb/renesas_usbhs/pipe.h | 116 | 
12 files changed, 6401 insertions, 0 deletions
diff --git a/drivers/usb/renesas_usbhs/Kconfig b/drivers/usb/renesas_usbhs/Kconfig new file mode 100644 index 00000000000..1c4195abc10 --- /dev/null +++ b/drivers/usb/renesas_usbhs/Kconfig @@ -0,0 +1,15 @@ +# +# Renesas USBHS Controller Drivers +# + +config USB_RENESAS_USBHS +	tristate 'Renesas USBHS controller' +	depends on USB_GADGET +	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" diff --git a/drivers/usb/renesas_usbhs/Makefile b/drivers/usb/renesas_usbhs/Makefile new file mode 100644 index 00000000000..bc8aef4311a --- /dev/null +++ b/drivers/usb/renesas_usbhs/Makefile @@ -0,0 +1,15 @@ +# +# for Renesas USB +# + +obj-$(CONFIG_USB_RENESAS_USBHS)	+= renesas_usbhs.o + +renesas_usbhs-y			:= common.o mod.o pipe.o fifo.o + +ifneq ($(CONFIG_USB_RENESAS_USBHS_HCD),) +	renesas_usbhs-y		+= mod_host.o +endif + +ifneq ($(CONFIG_USB_RENESAS_USBHS_UDC),) +	renesas_usbhs-y		+= mod_gadget.o +endif diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c new file mode 100644 index 00000000000..17267b0a2e9 --- /dev/null +++ b/drivers/usb/renesas_usbhs/common.c @@ -0,0 +1,645 @@ +/* + * 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/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include "common.h" + +/* + *		image of renesas_usbhs + * + * ex) gadget case + + * mod.c + * mod_gadget.c + * mod_host.c		pipe.c		fifo.c + * + *			+-------+	+-----------+ + *			| pipe0 |------>| fifo pio  | + * +------------+	+-------+	+-----------+ + * | mod_gadget |=====> | pipe1 |--+ + * +------------+	+-------+  |	+-----------+ + *			| pipe2 |  |  +-| fifo dma0 | + * +------------+	+-------+  |  |	+-----------+ + * | mod_host   |	| pipe3 |<-|--+ + * +------------+	+-------+  |	+-----------+ + *			| ....  |  +--->| fifo dma1 | + *			| ....  |	+-----------+ + */ + + +#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 + */ +static void usbhs_sys_clock_ctrl(struct usbhs_priv *priv, int enable) +{ +	usbhs_bset(priv, SYSCFG, SCKE, enable ? SCKE : 0); +} + +void usbhs_sys_host_ctrl(struct usbhs_priv *priv, int enable) +{ +	u16 mask = DCFM | DRPD | DPRPU | HSE | USBE; +	u16 val  = DCFM | DRPD | HSE | USBE; +	int has_otg = usbhs_get_dparam(priv, has_otg); + +	if (has_otg) +		usbhs_bset(priv, DVSTCTR, (EXTLP | PWEN), (EXTLP | PWEN)); + +	/* +	 * 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 | HSE | USBE; +	u16 val  = DPRPU | HSE | USBE; + +	/* +	 * if enable +	 * +	 * - select Function mode +	 * - D+ Line Pull-up +	 */ +	usbhs_bset(priv, SYSCFG, mask, enable ? val : 0); +} + +void usbhs_sys_function_pullup(struct usbhs_priv *priv, int enable) +{ +	usbhs_bset(priv, SYSCFG, DPRPU, enable ? DPRPU : 0); +} + +void usbhs_sys_set_test_mode(struct usbhs_priv *priv, u16 mode) +{ +	usbhs_write(priv, TESTMODE, mode); +} + +/* + *		frame functions + */ +int usbhs_frame_get_num(struct usbhs_priv *priv) +{ +	return usbhs_read(priv, FRMNUM) & FRNM_MASK; +} + +/* + *		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); + +	usbhs_bset(priv, DCPCTR, SUREQ, SUREQ); +} + +/* + *		bus/vbus functions + */ +void usbhs_bus_send_sof_enable(struct usbhs_priv *priv) +{ +	u16 status = usbhs_read(priv, DVSTCTR) & (USBRST | UACT); + +	if (status != USBRST) { +		struct device *dev = usbhs_priv_to_dev(priv); +		dev_err(dev, "usbhs should be reset\n"); +	} + +	usbhs_bset(priv, DVSTCTR, (USBRST | UACT), UACT); +} + +void usbhs_bus_send_reset(struct usbhs_priv *priv) +{ +	usbhs_bset(priv, DVSTCTR, (USBRST | UACT), USBRST); +} + +int usbhs_bus_get_speed(struct usbhs_priv *priv) +{ +	u16 dvstctr = usbhs_read(priv, DVSTCTR); + +	switch (RHST & dvstctr) { +	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_vbus_ctrl(struct usbhs_priv *priv, int enable) +{ +	struct platform_device *pdev = usbhs_priv_to_pdev(priv); + +	return usbhs_platform_call(priv, set_vbus, pdev, enable); +} + +static void usbhsc_bus_init(struct usbhs_priv *priv) +{ +	usbhs_write(priv, DVSTCTR, 0); + +	usbhs_vbus_ctrl(priv, 0); +} + +/* + *		device configuration + */ +int usbhs_set_device_config(struct usbhs_priv *priv, int devnum, +			   u16 upphub, u16 hubport, u16 speed) +{ +	struct device *dev = usbhs_priv_to_dev(priv); +	u16 usbspd = 0; +	u32 reg = DEVADD0 + (2 * devnum); + +	if (devnum > 10) { +		dev_err(dev, "cannot set speed to unknown device %d\n", devnum); +		return -EIO; +	} + +	if (upphub > 0xA) { +		dev_err(dev, "unsupported hub number %d\n", upphub); +		return -EIO; +	} + +	switch (speed) { +	case USB_SPEED_LOW: +		usbspd = USBSPD_SPEED_LOW; +		break; +	case USB_SPEED_FULL: +		usbspd = USBSPD_SPEED_FULL; +		break; +	case USB_SPEED_HIGH: +		usbspd = USBSPD_SPEED_HIGH; +		break; +	default: +		dev_err(dev, "unsupported speed %d\n", speed); +		return -EIO; +	} + +	usbhs_write(priv, reg,	UPPHUB(upphub)	| +				HUBPORT(hubport)| +				USBSPD(usbspd)); + +	return 0; +} + +/* + *		local functions + */ +static void usbhsc_set_buswait(struct usbhs_priv *priv) +{ +	int wait = usbhs_get_dparam(priv, buswait_bwait); + +	/* set bus wait if platform have */ +	if (wait) +		usbhs_bset(priv, BUSWAIT, 0x000F, wait); +} + +/* + *		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 platform_device *pdev = usbhs_priv_to_pdev(priv); +	struct device *dev = usbhs_priv_to_dev(priv); + +	if (enable) { +		/* enable PM */ +		pm_runtime_get_sync(dev); + +		/* enable platform power */ +		usbhs_platform_call(priv, power_ctrl, pdev, priv->base, enable); + +		/* USB on */ +		usbhs_sys_clock_ctrl(priv, enable); +	} else { +		/* USB off */ +		usbhs_sys_clock_ctrl(priv, enable); + +		/* disable platform power */ +		usbhs_platform_call(priv, power_ctrl, pdev, priv->base, enable); + +		/* disable PM */ +		pm_runtime_put_sync(dev); +	} +} + +/* + *		hotplug + */ +static void usbhsc_hotplug(struct usbhs_priv *priv) +{ +	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); + +		/* bus init */ +		usbhsc_set_buswait(priv); +		usbhsc_bus_init(priv); + +		/* 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); + +		/* bus init */ +		usbhsc_bus_init(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); +	} +} + +/* + *		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); +	usbhsc_hotplug(priv); +} + +static 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, +			      msecs_to_jiffies(delay)); +	return 0; +} + +/* + *		platform functions + */ +static int usbhs_probe(struct platform_device *pdev) +{ +	struct renesas_usbhs_platform_info *info = dev_get_platdata(&pdev->dev); +	struct renesas_usbhs_driver_callback *dfunc; +	struct usbhs_priv *priv; +	struct resource *res, *irq_res; +	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_res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); +	if (!res || !irq_res) { +		dev_err(&pdev->dev, "Not enough Renesas USB platform resources.\n"); +		return -ENODEV; +	} + +	/* usb private data */ +	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); +	if (!priv) { +		dev_err(&pdev->dev, "Could not allocate priv\n"); +		return -ENOMEM; +	} + +	priv->base = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(priv->base)) +		return PTR_ERR(priv->base); + +	/* +	 * care platform info +	 */ +	memcpy(&priv->pfunc, +	       &info->platform_callback, +	       sizeof(struct renesas_usbhs_platform_callback)); +	memcpy(&priv->dparam, +	       &info->driver_param, +	       sizeof(struct renesas_usbhs_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); +	} +	if (!priv->dparam.pio_dma_border) +		priv->dparam.pio_dma_border = 64; /* 64byte */ + +	/* FIXME */ +	/* runtime power control ? */ +	if (priv->pfunc.get_vbus) +		usbhsc_flags_set(priv, USBHSF_RUNTIME_PWCTRL); + +	/* +	 * priv settings +	 */ +	priv->irq	= irq_res->start; +	if (irq_res->flags & IORESOURCE_IRQ_SHAREABLE) +		priv->irqflags = IRQF_SHARED; +	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) +		return ret; + +	ret = usbhs_fifo_probe(priv); +	if (ret < 0) +		goto probe_end_pipe_exit; + +	ret = usbhs_mod_probe(priv); +	if (ret < 0) +		goto probe_end_fifo_exit; + +	/* dev_set_drvdata should be called after usbhs_mod_init */ +	platform_set_drvdata(pdev, 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_fifo_exit: +	usbhs_fifo_remove(priv); +probe_end_pipe_exit: +	usbhs_pipe_remove(priv); + +	dev_info(&pdev->dev, "probe failed\n"); + +	return ret; +} + +static int usbhs_remove(struct platform_device *pdev) +{ +	struct usbhs_priv *priv = usbhs_pdev_to_priv(pdev); +	struct renesas_usbhs_platform_info *info = dev_get_platdata(&pdev->dev); +	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_fifo_remove(priv); +	usbhs_pipe_remove(priv); + +	return 0; +} + +static int usbhsc_suspend(struct device *dev) +{ +	struct usbhs_priv *priv = dev_get_drvdata(dev); +	struct usbhs_mod *mod = usbhs_mod_get_current(priv); + +	if (mod) { +		usbhs_mod_call(priv, stop, priv); +		usbhs_mod_change(priv, -1); +	} + +	if (mod || !usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL)) +		usbhsc_power_ctrl(priv, 0); + +	return 0; +} + +static int usbhsc_resume(struct device *dev) +{ +	struct usbhs_priv *priv = dev_get_drvdata(dev); +	struct platform_device *pdev = usbhs_priv_to_pdev(priv); + +	if (!usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL)) +		usbhsc_power_ctrl(priv, 1); + +	usbhs_platform_call(priv, phy_reset, pdev); + +	usbhsc_drvcllbck_notify_hotplug(pdev); + +	return 0; +} + +static int usbhsc_runtime_nop(struct device *dev) +{ +	/* Runtime PM callback shared between ->runtime_suspend() +	 * and ->runtime_resume(). Simply returns success. +	 * +	 * This driver re-initializes all registers after +	 * pm_runtime_get_sync() anyway so there is no need +	 * to save and restore registers here. +	 */ +	return 0; +} + +static const struct dev_pm_ops usbhsc_pm_ops = { +	.suspend		= usbhsc_suspend, +	.resume			= usbhsc_resume, +	.runtime_suspend	= usbhsc_runtime_nop, +	.runtime_resume		= usbhsc_runtime_nop, +}; + +static struct platform_driver renesas_usbhs_driver = { +	.driver		= { +		.name	= "renesas_usbhs", +		.pm	= &usbhsc_pm_ops, +	}, +	.probe		= usbhs_probe, +	.remove		= usbhs_remove, +}; + +module_platform_driver(renesas_usbhs_driver); + +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..c69dd2fba36 --- /dev/null +++ b/drivers/usb/renesas_usbhs/common.h @@ -0,0 +1,325 @@ +/* + * 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 TESTMODE	0x000C +#define CFIFO		0x0014 +#define CFIFOSEL	0x0020 +#define CFIFOCTR	0x0022 +#define D0FIFO		0x0100 +#define D0FIFOSEL	0x0028 +#define D0FIFOCTR	0x002A +#define D1FIFO		0x0120 +#define D1FIFOSEL	0x002C +#define D1FIFOCTR	0x002E +#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 +#define PIPE1TRE	0x0090 +#define PIPE1TRN	0x0092 +#define PIPE2TRE	0x0094 +#define PIPE2TRN	0x0096 +#define PIPE3TRE	0x0098 +#define PIPE3TRN	0x009A +#define PIPE4TRE	0x009C +#define PIPE4TRN	0x009E +#define PIPE5TRE	0x00A0 +#define PIPE5TRN	0x00A2 +#define PIPEBTRE	0x00A4 +#define PIPEBTRN	0x00A6 +#define PIPECTRE	0x00A8 +#define PIPECTRN	0x00AA +#define PIPEDTRE	0x00AC +#define PIPEDTRN	0x00AE +#define PIPEETRE	0x00B0 +#define PIPEETRN	0x00B2 +#define PIPEFTRE	0x00B4 +#define PIPEFTRN	0x00B6 +#define PIPE9TRE	0x00B8 +#define PIPE9TRN	0x00BA +#define PIPEATRE	0x00BC +#define PIPEATRN	0x00BE +#define DEVADD0		0x00D0 /* Device address n configuration */ +#define DEVADD1		0x00D2 +#define DEVADD2		0x00D4 +#define DEVADD3		0x00D6 +#define DEVADD4		0x00D8 +#define DEVADD5		0x00DA +#define DEVADD6		0x00DC +#define DEVADD7		0x00DE +#define DEVADD8		0x00E0 +#define DEVADD9		0x00E2 +#define DEVADDA		0x00E4 + +/* 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 USBRST	(1 << 6)	/* Bus Reset Output */ +#define UACT	(1 << 4)	/* USB Bus Enable */ +#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 DREQE	(1 << 12)	/* DMA Transfer Request Enable */ +#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 */ + +/* INTSTS1 */ +#define OVRCR	(1 << 15) /* OVRCR Interrupt Status */ +#define BCHG	(1 << 14) /* USB Bus Change Interrupt Status */ +#define DTCH	(1 << 12) /* USB Disconnection Detect Interrupt Status */ +#define ATTCH	(1 << 11) /* ATTCH Interrupt Status */ +#define EOFERR	(1 << 6)  /* EOF Error Detect Interrupt Status */ +#define SIGN	(1 << 5)  /* Setup Transaction Error Interrupt Status */ +#define SACK	(1 << 4)  /* Setup Transaction ACK Response Interrupt Status */ + +/* 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 SUREQ		(1 << 14)	/* Sending SETUP Token */ +#define CSSTS		(1 << 12)	/* CSSTS Status */ +#define	ACLRM		(1 << 9)	/* Buffer Auto-Clear Mode */ +#define SQCLR		(1 << 8)	/* Toggle Bit Clear */ +#define SQSET		(1 << 7)	/* Toggle Bit Set */ +#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 */ + +/* PIPEnTRE */ +#define TRENB		(1 << 9)	/* Transaction Counter Enable */ +#define TRCLR		(1 << 8)	/* Transaction Counter Clear */ + +/* FRMNUM */ +#define FRNM_MASK	(0x7FF) + +/* DEVADDn */ +#define UPPHUB(x)	(((x) & 0xF) << 11)	/* HUB Register */ +#define HUBPORT(x)	(((x) & 0x7) << 8)	/* HUB Port for Target Device */ +#define USBSPD(x)	(((x) & 0x3) << 6)	/* Device Transfer Rate */ +#define USBSPD_SPEED_LOW	0x1 +#define USBSPD_SPEED_FULL	0x2 +#define USBSPD_SPEED_HIGH	0x3 + +/* + *		struct + */ +struct usbhs_priv { + +	void __iomem *base; +	unsigned int irq; +	unsigned long irqflags; + +	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; + +	/* +	 * fifo control +	 */ +	struct usbhs_fifo_info fifo_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); + +#define usbhs_lock(p, f) spin_lock_irqsave(usbhs_priv_to_lock(p), f) +#define usbhs_unlock(p, f) spin_unlock_irqrestore(usbhs_priv_to_lock(p), f) + +/* + * sysconfig + */ +void usbhs_sys_host_ctrl(struct usbhs_priv *priv, int enable); +void usbhs_sys_function_ctrl(struct usbhs_priv *priv, int enable); +void usbhs_sys_function_pullup(struct usbhs_priv *priv, int enable); +void usbhs_sys_set_test_mode(struct usbhs_priv *priv, u16 mode); + +/* + * 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); + +/* + * bus + */ +void usbhs_bus_send_sof_enable(struct usbhs_priv *priv); +void usbhs_bus_send_reset(struct usbhs_priv *priv); +int usbhs_bus_get_speed(struct usbhs_priv *priv); +int usbhs_vbus_ctrl(struct usbhs_priv *priv, int enable); + +/* + * frame + */ +int usbhs_frame_get_num(struct usbhs_priv *priv); + +/* + * device config + */ +int usbhs_set_device_config(struct usbhs_priv *priv, int devnum, u16 upphub, +			   u16 hubport, u16 speed); + +/* + * 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/fifo.c b/drivers/usb/renesas_usbhs/fifo.c new file mode 100644 index 00000000000..4fd36530bfa --- /dev/null +++ b/drivers/usb/renesas_usbhs/fifo.c @@ -0,0 +1,1187 @@ +/* + * 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/scatterlist.h> +#include "common.h" +#include "pipe.h" + +#define usbhsf_get_cfifo(p)	(&((p)->fifo_info.cfifo)) +#define usbhsf_get_d0fifo(p)	(&((p)->fifo_info.d0fifo)) +#define usbhsf_get_d1fifo(p)	(&((p)->fifo_info.d1fifo)) +#define usbhsf_is_cfifo(p, f)	(usbhsf_get_cfifo(p) == f) + +#define usbhsf_fifo_is_busy(f)	((f)->pipe) /* see usbhs_pipe_select_fifo */ + +/* + *		packet initialize + */ +void usbhs_pkt_init(struct usbhs_pkt *pkt) +{ +	INIT_LIST_HEAD(&pkt->node); +} + +/* + *		packet control function + */ +static int usbhsf_null_handle(struct usbhs_pkt *pkt, int *is_done) +{ +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pkt->pipe); +	struct device *dev = usbhs_priv_to_dev(priv); + +	dev_err(dev, "null handler\n"); + +	return -EINVAL; +} + +static struct usbhs_pkt_handle usbhsf_null_handler = { +	.prepare = usbhsf_null_handle, +	.try_run = usbhsf_null_handle, +}; + +void usbhs_pkt_push(struct usbhs_pipe *pipe, struct usbhs_pkt *pkt, +		    void (*done)(struct usbhs_priv *priv, +				 struct usbhs_pkt *pkt), +		    void *buf, int len, int zero, int sequence) +{ +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +	struct device *dev = usbhs_priv_to_dev(priv); +	unsigned long flags; + +	if (!done) { +		dev_err(dev, "no done function\n"); +		return; +	} + +	/********************  spin lock ********************/ +	usbhs_lock(priv, flags); + +	if (!pipe->handler) { +		dev_err(dev, "no handler function\n"); +		pipe->handler = &usbhsf_null_handler; +	} + +	list_move_tail(&pkt->node, &pipe->list); + +	/* +	 * each pkt must hold own handler. +	 * because handler might be changed by its situation. +	 * dma handler -> pio handler. +	 */ +	pkt->pipe	= pipe; +	pkt->buf	= buf; +	pkt->handler	= pipe->handler; +	pkt->length	= len; +	pkt->zero	= zero; +	pkt->actual	= 0; +	pkt->done	= done; +	pkt->sequence	= sequence; + +	usbhs_unlock(priv, flags); +	/********************  spin unlock ******************/ +} + +static void __usbhsf_pkt_del(struct usbhs_pkt *pkt) +{ +	list_del_init(&pkt->node); +} + +static struct usbhs_pkt *__usbhsf_pkt_get(struct usbhs_pipe *pipe) +{ +	if (list_empty(&pipe->list)) +		return NULL; + +	return list_first_entry(&pipe->list, struct usbhs_pkt, node); +} + +struct usbhs_pkt *usbhs_pkt_pop(struct usbhs_pipe *pipe, struct usbhs_pkt *pkt) +{ +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +	unsigned long flags; + +	/********************  spin lock ********************/ +	usbhs_lock(priv, flags); + +	if (!pkt) +		pkt = __usbhsf_pkt_get(pipe); + +	if (pkt) +		__usbhsf_pkt_del(pkt); + +	usbhs_unlock(priv, flags); +	/********************  spin unlock ******************/ + +	return pkt; +} + +enum { +	USBHSF_PKT_PREPARE, +	USBHSF_PKT_TRY_RUN, +	USBHSF_PKT_DMA_DONE, +}; + +static int usbhsf_pkt_handler(struct usbhs_pipe *pipe, int type) +{ +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +	struct usbhs_pkt *pkt; +	struct device *dev = usbhs_priv_to_dev(priv); +	int (*func)(struct usbhs_pkt *pkt, int *is_done); +	unsigned long flags; +	int ret = 0; +	int is_done = 0; + +	/********************  spin lock ********************/ +	usbhs_lock(priv, flags); + +	pkt = __usbhsf_pkt_get(pipe); +	if (!pkt) +		goto __usbhs_pkt_handler_end; + +	switch (type) { +	case USBHSF_PKT_PREPARE: +		func = pkt->handler->prepare; +		break; +	case USBHSF_PKT_TRY_RUN: +		func = pkt->handler->try_run; +		break; +	case USBHSF_PKT_DMA_DONE: +		func = pkt->handler->dma_done; +		break; +	default: +		dev_err(dev, "unknown pkt handler\n"); +		goto __usbhs_pkt_handler_end; +	} + +	ret = func(pkt, &is_done); + +	if (is_done) +		__usbhsf_pkt_del(pkt); + +__usbhs_pkt_handler_end: +	usbhs_unlock(priv, flags); +	/********************  spin unlock ******************/ + +	if (is_done) { +		pkt->done(priv, pkt); +		usbhs_pkt_start(pipe); +	} + +	return ret; +} + +void usbhs_pkt_start(struct usbhs_pipe *pipe) +{ +	usbhsf_pkt_handler(pipe, USBHSF_PKT_PREPARE); +} + +/* + *		irq enable/disable function + */ +#define usbhsf_irq_empty_ctrl(p, e) usbhsf_irq_callback_ctrl(p, irq_bempsts, e) +#define usbhsf_irq_ready_ctrl(p, e) usbhsf_irq_callback_ctrl(p, irq_brdysts, e) +#define usbhsf_irq_callback_ctrl(pipe, status, enable)			\ +	({								\ +		struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe);	\ +		struct usbhs_mod *mod = usbhs_mod_get_current(priv);	\ +		u16 status = (1 << usbhs_pipe_number(pipe));		\ +		if (!mod)						\ +			return;						\ +		if (enable)						\ +			mod->status |= status;				\ +		else							\ +			mod->status &= ~status;				\ +		usbhs_irq_callback_update(priv, mod);			\ +	}) + +static void usbhsf_tx_irq_ctrl(struct usbhs_pipe *pipe, int enable) +{ +	/* +	 * And 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 +	 */ +	if (usbhs_pipe_is_dcp(pipe)) +		usbhsf_irq_empty_ctrl(pipe, enable); +	else +		usbhsf_irq_ready_ctrl(pipe, enable); +} + +static void usbhsf_rx_irq_ctrl(struct usbhs_pipe *pipe, int enable) +{ +	usbhsf_irq_ready_ctrl(pipe, enable); +} + +/* + *		FIFO ctrl + */ +static void usbhsf_send_terminator(struct usbhs_pipe *pipe, +				   struct usbhs_fifo *fifo) +{ +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + +	usbhs_bset(priv, fifo->ctr, BVAL, BVAL); +} + +static int usbhsf_fifo_barrier(struct usbhs_priv *priv, +			       struct usbhs_fifo *fifo) +{ +	int timeout = 1024; + +	do { +		/* The FIFO port is accessible */ +		if (usbhs_read(priv, fifo->ctr) & FRDY) +			return 0; + +		udelay(10); +	} while (timeout--); + +	return -EBUSY; +} + +static void usbhsf_fifo_clear(struct usbhs_pipe *pipe, +			      struct usbhs_fifo *fifo) +{ +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + +	if (!usbhs_pipe_is_dcp(pipe)) +		usbhsf_fifo_barrier(priv, fifo); + +	usbhs_write(priv, fifo->ctr, BCLR); +} + +static int usbhsf_fifo_rcv_len(struct usbhs_priv *priv, +			       struct usbhs_fifo *fifo) +{ +	return usbhs_read(priv, fifo->ctr) & DTLN_MASK; +} + +static void usbhsf_fifo_unselect(struct usbhs_pipe *pipe, +				 struct usbhs_fifo *fifo) +{ +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + +	usbhs_pipe_select_fifo(pipe, NULL); +	usbhs_write(priv, fifo->sel, 0); +} + +static int usbhsf_fifo_select(struct usbhs_pipe *pipe, +			      struct usbhs_fifo *fifo, +			      int write) +{ +	struct usbhs_priv *priv = usbhs_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 (usbhs_pipe_is_busy(pipe) || +	    usbhsf_fifo_is_busy(fifo)) +		return -EBUSY; + +	if (usbhs_pipe_is_dcp(pipe)) { +		base |= (1 == write) << 5;	/* ISEL */ + +		if (usbhs_mod_is_host(priv)) +			usbhs_dcp_dir_for_host(pipe, write); +	} + +	/* "base" will be used below  */ +	if (usbhs_get_dparam(priv, has_sudmac) && !usbhsf_is_cfifo(priv, fifo)) +		usbhs_write(priv, fifo->sel, base); +	else +		usbhs_write(priv, fifo->sel, base | MBW_32); + +	/* check ISEL and CURPIPE value */ +	while (timeout--) { +		if (base == (mask & usbhs_read(priv, fifo->sel))) { +			usbhs_pipe_select_fifo(pipe, fifo); +			return 0; +		} +		udelay(10); +	} + +	dev_err(dev, "fifo select error\n"); + +	return -EIO; +} + +/* + *		DCP status stage + */ +static int usbhs_dcp_dir_switch_to_write(struct usbhs_pkt *pkt, int *is_done) +{ +	struct usbhs_pipe *pipe = pkt->pipe; +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +	struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv); /* CFIFO */ +	struct device *dev = usbhs_priv_to_dev(priv); +	int ret; + +	usbhs_pipe_disable(pipe); + +	ret = usbhsf_fifo_select(pipe, fifo, 1); +	if (ret < 0) { +		dev_err(dev, "%s() faile\n", __func__); +		return ret; +	} + +	usbhs_pipe_sequence_data1(pipe); /* DATA1 */ + +	usbhsf_fifo_clear(pipe, fifo); +	usbhsf_send_terminator(pipe, fifo); + +	usbhsf_fifo_unselect(pipe, fifo); + +	usbhsf_tx_irq_ctrl(pipe, 1); +	usbhs_pipe_enable(pipe); + +	return ret; +} + +static int usbhs_dcp_dir_switch_to_read(struct usbhs_pkt *pkt, int *is_done) +{ +	struct usbhs_pipe *pipe = pkt->pipe; +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +	struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv); /* CFIFO */ +	struct device *dev = usbhs_priv_to_dev(priv); +	int ret; + +	usbhs_pipe_disable(pipe); + +	ret = usbhsf_fifo_select(pipe, fifo, 0); +	if (ret < 0) { +		dev_err(dev, "%s() fail\n", __func__); +		return ret; +	} + +	usbhs_pipe_sequence_data1(pipe); /* DATA1 */ +	usbhsf_fifo_clear(pipe, fifo); + +	usbhsf_fifo_unselect(pipe, fifo); + +	usbhsf_rx_irq_ctrl(pipe, 1); +	usbhs_pipe_enable(pipe); + +	return ret; + +} + +static int usbhs_dcp_dir_switch_done(struct usbhs_pkt *pkt, int *is_done) +{ +	struct usbhs_pipe *pipe = pkt->pipe; + +	if (pkt->handler == &usbhs_dcp_status_stage_in_handler) +		usbhsf_tx_irq_ctrl(pipe, 0); +	else +		usbhsf_rx_irq_ctrl(pipe, 0); + +	pkt->actual = pkt->length; +	*is_done = 1; + +	return 0; +} + +struct usbhs_pkt_handle usbhs_dcp_status_stage_in_handler = { +	.prepare = usbhs_dcp_dir_switch_to_write, +	.try_run = usbhs_dcp_dir_switch_done, +}; + +struct usbhs_pkt_handle usbhs_dcp_status_stage_out_handler = { +	.prepare = usbhs_dcp_dir_switch_to_read, +	.try_run = usbhs_dcp_dir_switch_done, +}; + +/* + *		DCP data stage (push) + */ +static int usbhsf_dcp_data_stage_try_push(struct usbhs_pkt *pkt, int *is_done) +{ +	struct usbhs_pipe *pipe = pkt->pipe; + +	usbhs_pipe_sequence_data1(pipe); /* DATA1 */ + +	/* +	 * change handler to PIO push +	 */ +	pkt->handler = &usbhs_fifo_pio_push_handler; + +	return pkt->handler->prepare(pkt, is_done); +} + +struct usbhs_pkt_handle usbhs_dcp_data_stage_out_handler = { +	.prepare = usbhsf_dcp_data_stage_try_push, +}; + +/* + *		DCP data stage (pop) + */ +static int usbhsf_dcp_data_stage_prepare_pop(struct usbhs_pkt *pkt, +					     int *is_done) +{ +	struct usbhs_pipe *pipe = pkt->pipe; +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +	struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv); + +	if (usbhs_pipe_is_busy(pipe)) +		return 0; + +	/* +	 * prepare pop for DCP should +	 *  - change DCP direction, +	 *  - clear fifo +	 *  - DATA1 +	 */ +	usbhs_pipe_disable(pipe); + +	usbhs_pipe_sequence_data1(pipe); /* DATA1 */ + +	usbhsf_fifo_select(pipe, fifo, 0); +	usbhsf_fifo_clear(pipe, fifo); +	usbhsf_fifo_unselect(pipe, fifo); + +	/* +	 * change handler to PIO pop +	 */ +	pkt->handler = &usbhs_fifo_pio_pop_handler; + +	return pkt->handler->prepare(pkt, is_done); +} + +struct usbhs_pkt_handle usbhs_dcp_data_stage_in_handler = { +	.prepare = usbhsf_dcp_data_stage_prepare_pop, +}; + +/* + *		PIO push handler + */ +static int usbhsf_pio_try_push(struct usbhs_pkt *pkt, int *is_done) +{ +	struct usbhs_pipe *pipe = pkt->pipe; +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +	struct device *dev = usbhs_priv_to_dev(priv); +	struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv); /* CFIFO */ +	void __iomem *addr = priv->base + fifo->port; +	u8 *buf; +	int maxp = usbhs_pipe_get_maxpacket(pipe); +	int total_len; +	int i, ret, len; +	int is_short; + +	usbhs_pipe_data_sequence(pipe, pkt->sequence); +	pkt->sequence = -1; /* -1 sequence will be ignored */ + +	usbhs_pipe_set_trans_count_if_bulk(pipe, pkt->length); + +	ret = usbhsf_fifo_select(pipe, fifo, 1); +	if (ret < 0) +		return 0; + +	ret = usbhs_pipe_is_accessible(pipe); +	if (ret < 0) { +		/* inaccessible pipe is not an error */ +		ret = 0; +		goto usbhs_fifo_write_busy; +	} + +	ret = usbhsf_fifo_barrier(priv, fifo); +	if (ret < 0) +		goto usbhs_fifo_write_busy; + +	buf		= pkt->buf    + pkt->actual; +	len		= pkt->length - pkt->actual; +	len		= min(len, maxp); +	total_len	= len; +	is_short	= total_len < maxp; + +	/* +	 * 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))); + +	/* +	 * variable update +	 */ +	pkt->actual += total_len; + +	if (pkt->actual < pkt->length) +		*is_done = 0;		/* there are remainder data */ +	else if (is_short) +		*is_done = 1;		/* short packet */ +	else +		*is_done = !pkt->zero;	/* send zero packet ? */ + +	/* +	 * pipe/irq handling +	 */ +	if (is_short) +		usbhsf_send_terminator(pipe, fifo); + +	usbhsf_tx_irq_ctrl(pipe, !*is_done); +	usbhs_pipe_enable(pipe); + +	dev_dbg(dev, "  send %d (%d/ %d/ %d/ %d)\n", +		usbhs_pipe_number(pipe), +		pkt->length, pkt->actual, *is_done, pkt->zero); + +	/* +	 * Transmission end +	 */ +	if (*is_done) { +		if (usbhs_pipe_is_dcp(pipe)) +			usbhs_dcp_control_transfer_done(pipe); +	} + +	usbhsf_fifo_unselect(pipe, fifo); + +	return 0; + +usbhs_fifo_write_busy: +	usbhsf_fifo_unselect(pipe, fifo); + +	/* +	 * pipe is busy. +	 * retry in interrupt +	 */ +	usbhsf_tx_irq_ctrl(pipe, 1); + +	return ret; +} + +struct usbhs_pkt_handle usbhs_fifo_pio_push_handler = { +	.prepare = usbhsf_pio_try_push, +	.try_run = usbhsf_pio_try_push, +}; + +/* + *		PIO pop handler + */ +static int usbhsf_prepare_pop(struct usbhs_pkt *pkt, int *is_done) +{ +	struct usbhs_pipe *pipe = pkt->pipe; + +	if (usbhs_pipe_is_busy(pipe)) +		return 0; + +	/* +	 * pipe enable to prepare packet receive +	 */ +	usbhs_pipe_data_sequence(pipe, pkt->sequence); +	pkt->sequence = -1; /* -1 sequence will be ignored */ + +	usbhs_pipe_set_trans_count_if_bulk(pipe, pkt->length); +	usbhs_pipe_enable(pipe); +	usbhsf_rx_irq_ctrl(pipe, 1); + +	return 0; +} + +static int usbhsf_pio_try_pop(struct usbhs_pkt *pkt, int *is_done) +{ +	struct usbhs_pipe *pipe = pkt->pipe; +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +	struct device *dev = usbhs_priv_to_dev(priv); +	struct usbhs_fifo *fifo = usbhsf_get_cfifo(priv); /* CFIFO */ +	void __iomem *addr = priv->base + fifo->port; +	u8 *buf; +	u32 data = 0; +	int maxp = usbhs_pipe_get_maxpacket(pipe); +	int rcv_len, len; +	int i, ret; +	int total_len = 0; + +	ret = usbhsf_fifo_select(pipe, fifo, 0); +	if (ret < 0) +		return 0; + +	ret = usbhsf_fifo_barrier(priv, fifo); +	if (ret < 0) +		goto usbhs_fifo_read_busy; + +	rcv_len = usbhsf_fifo_rcv_len(priv, fifo); + +	buf		= pkt->buf    + pkt->actual; +	len		= pkt->length - pkt->actual; +	len		= min(len, rcv_len); +	total_len	= len; + +	/* +	 * update actual length first here to decide disable pipe. +	 * if this pipe keeps BUF status and all data were popped, +	 * then, next interrupt/token will be issued again +	 */ +	pkt->actual += total_len; + +	if ((pkt->actual == pkt->length) ||	/* receive all data */ +	    (total_len < maxp)) {		/* short packet */ +		*is_done = 1; +		usbhsf_rx_irq_ctrl(pipe, 0); +		usbhs_pipe_disable(pipe);	/* disable pipe first */ +	} + +	/* +	 * Buffer clear if Zero-Length packet +	 * +	 * see +	 * "Operation" - "FIFO Buffer Memory" - "FIFO Port Function" +	 */ +	if (0 == rcv_len) { +		pkt->zero = 1; +		usbhsf_fifo_clear(pipe, fifo); +		goto usbhs_fifo_read_end; +	} + +	/* +	 * FIXME +	 * +	 * 32-bit access only +	 */ +	if (len >= 4 && !((unsigned long)buf & 0x03)) { +		ioread32_rep(addr, buf, len / 4); +		len %= 4; +		buf += total_len - len; +	} + +	/* the rest operation */ +	for (i = 0; i < len; i++) { +		if (!(i & 0x03)) +			data = ioread32(addr); + +		buf[i] = (data >> ((i & 0x03) * 8)) & 0xff; +	} + +usbhs_fifo_read_end: +	dev_dbg(dev, "  recv %d (%d/ %d/ %d/ %d)\n", +		usbhs_pipe_number(pipe), +		pkt->length, pkt->actual, *is_done, pkt->zero); + +	/* +	 * Transmission end +	 */ +	if (*is_done) { +		if (usbhs_pipe_is_dcp(pipe)) +			usbhs_dcp_control_transfer_done(pipe); +	} + +usbhs_fifo_read_busy: +	usbhsf_fifo_unselect(pipe, fifo); + +	return ret; +} + +struct usbhs_pkt_handle usbhs_fifo_pio_pop_handler = { +	.prepare = usbhsf_prepare_pop, +	.try_run = usbhsf_pio_try_pop, +}; + +/* + *		DCP ctrol statge handler + */ +static int usbhsf_ctrl_stage_end(struct usbhs_pkt *pkt, int *is_done) +{ +	usbhs_dcp_control_transfer_done(pkt->pipe); + +	*is_done = 1; + +	return 0; +} + +struct usbhs_pkt_handle usbhs_ctrl_stage_end_handler = { +	.prepare = usbhsf_ctrl_stage_end, +	.try_run = usbhsf_ctrl_stage_end, +}; + +/* + *		DMA fifo functions + */ +static struct dma_chan *usbhsf_dma_chan_get(struct usbhs_fifo *fifo, +					    struct usbhs_pkt *pkt) +{ +	if (&usbhs_fifo_dma_push_handler == pkt->handler) +		return fifo->tx_chan; + +	if (&usbhs_fifo_dma_pop_handler == pkt->handler) +		return fifo->rx_chan; + +	return NULL; +} + +static struct usbhs_fifo *usbhsf_get_dma_fifo(struct usbhs_priv *priv, +					      struct usbhs_pkt *pkt) +{ +	struct usbhs_fifo *fifo; + +	/* DMA :: D0FIFO */ +	fifo = usbhsf_get_d0fifo(priv); +	if (usbhsf_dma_chan_get(fifo, pkt) && +	    !usbhsf_fifo_is_busy(fifo)) +		return fifo; + +	/* DMA :: D1FIFO */ +	fifo = usbhsf_get_d1fifo(priv); +	if (usbhsf_dma_chan_get(fifo, pkt) && +	    !usbhsf_fifo_is_busy(fifo)) +		return fifo; + +	return NULL; +} + +#define usbhsf_dma_start(p, f)	__usbhsf_dma_ctrl(p, f, DREQE) +#define usbhsf_dma_stop(p, f)	__usbhsf_dma_ctrl(p, f, 0) +static void __usbhsf_dma_ctrl(struct usbhs_pipe *pipe, +			      struct usbhs_fifo *fifo, +			      u16 dreqe) +{ +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + +	usbhs_bset(priv, fifo->sel, DREQE, dreqe); +} + +#define usbhsf_dma_map(p)	__usbhsf_dma_map_ctrl(p, 1) +#define usbhsf_dma_unmap(p)	__usbhsf_dma_map_ctrl(p, 0) +static int __usbhsf_dma_map_ctrl(struct usbhs_pkt *pkt, int map) +{ +	struct usbhs_pipe *pipe = pkt->pipe; +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +	struct usbhs_pipe_info *info = usbhs_priv_to_pipeinfo(priv); + +	return info->dma_map_ctrl(pkt, map); +} + +static void usbhsf_dma_complete(void *arg); +static void xfer_work(struct work_struct *work) +{ +	struct usbhs_pkt *pkt = container_of(work, struct usbhs_pkt, work); +	struct usbhs_pipe *pipe = pkt->pipe; +	struct usbhs_fifo *fifo = usbhs_pipe_to_fifo(pipe); +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +	struct dma_async_tx_descriptor *desc; +	struct dma_chan *chan = usbhsf_dma_chan_get(fifo, pkt); +	struct device *dev = usbhs_priv_to_dev(priv); +	enum dma_transfer_direction dir; + +	dir = usbhs_pipe_is_dir_in(pipe) ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV; + +	desc = dmaengine_prep_slave_single(chan, pkt->dma + pkt->actual, +					pkt->trans, dir, +					DMA_PREP_INTERRUPT | DMA_CTRL_ACK); +	if (!desc) +		return; + +	desc->callback		= usbhsf_dma_complete; +	desc->callback_param	= pipe; + +	if (dmaengine_submit(desc) < 0) { +		dev_err(dev, "Failed to submit dma descriptor\n"); +		return; +	} + +	dev_dbg(dev, "  %s %d (%d/ %d)\n", +		fifo->name, usbhs_pipe_number(pipe), pkt->length, pkt->zero); + +	usbhs_pipe_set_trans_count_if_bulk(pipe, pkt->trans); +	usbhs_pipe_enable(pipe); +	usbhsf_dma_start(pipe, fifo); +	dma_async_issue_pending(chan); +} + +/* + *		DMA push handler + */ +static int usbhsf_dma_prepare_push(struct usbhs_pkt *pkt, int *is_done) +{ +	struct usbhs_pipe *pipe = pkt->pipe; +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +	struct usbhs_fifo *fifo; +	int len = pkt->length - pkt->actual; +	int ret; + +	if (usbhs_pipe_is_busy(pipe)) +		return 0; + +	/* use PIO if packet is less than pio_dma_border or pipe is DCP */ +	if ((len < usbhs_get_dparam(priv, pio_dma_border)) || +	    usbhs_pipe_is_dcp(pipe)) +		goto usbhsf_pio_prepare_push; + +	if (len & 0x7) /* 8byte alignment */ +		goto usbhsf_pio_prepare_push; + +	if ((uintptr_t)(pkt->buf + pkt->actual) & 0x7) /* 8byte alignment */ +		goto usbhsf_pio_prepare_push; + +	/* get enable DMA fifo */ +	fifo = usbhsf_get_dma_fifo(priv, pkt); +	if (!fifo) +		goto usbhsf_pio_prepare_push; + +	if (usbhsf_dma_map(pkt) < 0) +		goto usbhsf_pio_prepare_push; + +	ret = usbhsf_fifo_select(pipe, fifo, 0); +	if (ret < 0) +		goto usbhsf_pio_prepare_push_unmap; + +	pkt->trans = len; + +	INIT_WORK(&pkt->work, xfer_work); +	schedule_work(&pkt->work); + +	return 0; + +usbhsf_pio_prepare_push_unmap: +	usbhsf_dma_unmap(pkt); +usbhsf_pio_prepare_push: +	/* +	 * change handler to PIO +	 */ +	pkt->handler = &usbhs_fifo_pio_push_handler; + +	return pkt->handler->prepare(pkt, is_done); +} + +static int usbhsf_dma_push_done(struct usbhs_pkt *pkt, int *is_done) +{ +	struct usbhs_pipe *pipe = pkt->pipe; + +	pkt->actual = pkt->trans; + +	*is_done = !pkt->zero;	/* send zero packet ? */ + +	usbhsf_dma_stop(pipe, pipe->fifo); +	usbhsf_dma_unmap(pkt); +	usbhsf_fifo_unselect(pipe, pipe->fifo); + +	return 0; +} + +struct usbhs_pkt_handle usbhs_fifo_dma_push_handler = { +	.prepare	= usbhsf_dma_prepare_push, +	.dma_done	= usbhsf_dma_push_done, +}; + +/* + *		DMA pop handler + */ +static int usbhsf_dma_try_pop(struct usbhs_pkt *pkt, int *is_done) +{ +	struct usbhs_pipe *pipe = pkt->pipe; +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +	struct usbhs_fifo *fifo; +	int len, ret; + +	if (usbhs_pipe_is_busy(pipe)) +		return 0; + +	if (usbhs_pipe_is_dcp(pipe)) +		goto usbhsf_pio_prepare_pop; + +	/* get enable DMA fifo */ +	fifo = usbhsf_get_dma_fifo(priv, pkt); +	if (!fifo) +		goto usbhsf_pio_prepare_pop; + +	if ((uintptr_t)(pkt->buf + pkt->actual) & 0x7) /* 8byte alignment */ +		goto usbhsf_pio_prepare_pop; + +	ret = usbhsf_fifo_select(pipe, fifo, 0); +	if (ret < 0) +		goto usbhsf_pio_prepare_pop; + +	/* use PIO if packet is less than pio_dma_border */ +	len = usbhsf_fifo_rcv_len(priv, fifo); +	len = min(pkt->length - pkt->actual, len); +	if (len & 0x7) /* 8byte alignment */ +		goto usbhsf_pio_prepare_pop_unselect; + +	if (len < usbhs_get_dparam(priv, pio_dma_border)) +		goto usbhsf_pio_prepare_pop_unselect; + +	ret = usbhsf_fifo_barrier(priv, fifo); +	if (ret < 0) +		goto usbhsf_pio_prepare_pop_unselect; + +	if (usbhsf_dma_map(pkt) < 0) +		goto usbhsf_pio_prepare_pop_unselect; + +	/* DMA */ + +	/* +	 * usbhs_fifo_dma_pop_handler :: prepare +	 * enabled irq to come here. +	 * but it is no longer needed for DMA. disable it. +	 */ +	usbhsf_rx_irq_ctrl(pipe, 0); + +	pkt->trans = len; + +	INIT_WORK(&pkt->work, xfer_work); +	schedule_work(&pkt->work); + +	return 0; + +usbhsf_pio_prepare_pop_unselect: +	usbhsf_fifo_unselect(pipe, fifo); +usbhsf_pio_prepare_pop: + +	/* +	 * change handler to PIO +	 */ +	pkt->handler = &usbhs_fifo_pio_pop_handler; + +	return pkt->handler->try_run(pkt, is_done); +} + +static int usbhsf_dma_pop_done(struct usbhs_pkt *pkt, int *is_done) +{ +	struct usbhs_pipe *pipe = pkt->pipe; +	int maxp = usbhs_pipe_get_maxpacket(pipe); + +	usbhsf_dma_stop(pipe, pipe->fifo); +	usbhsf_dma_unmap(pkt); +	usbhsf_fifo_unselect(pipe, pipe->fifo); + +	pkt->actual += pkt->trans; + +	if ((pkt->actual == pkt->length) ||	/* receive all data */ +	    (pkt->trans < maxp)) {		/* short packet */ +		*is_done = 1; +	} else { +		/* re-enable */ +		usbhsf_prepare_pop(pkt, is_done); +	} + +	return 0; +} + +struct usbhs_pkt_handle usbhs_fifo_dma_pop_handler = { +	.prepare	= usbhsf_prepare_pop, +	.try_run	= usbhsf_dma_try_pop, +	.dma_done	= usbhsf_dma_pop_done +}; + +/* + *		DMA setting + */ +static bool usbhsf_dma_filter(struct dma_chan *chan, void *param) +{ +	struct sh_dmae_slave *slave = param; + +	/* +	 * FIXME +	 * +	 * usbhs doesn't recognize id = 0 as valid DMA +	 */ +	if (0 == slave->shdma_slave.slave_id) +		return false; + +	chan->private = slave; + +	return true; +} + +static void usbhsf_dma_quit(struct usbhs_priv *priv, struct usbhs_fifo *fifo) +{ +	if (fifo->tx_chan) +		dma_release_channel(fifo->tx_chan); +	if (fifo->rx_chan) +		dma_release_channel(fifo->rx_chan); + +	fifo->tx_chan = NULL; +	fifo->rx_chan = NULL; +} + +static void usbhsf_dma_init(struct usbhs_priv *priv, +			    struct usbhs_fifo *fifo) +{ +	struct device *dev = usbhs_priv_to_dev(priv); +	dma_cap_mask_t mask; + +	dma_cap_zero(mask); +	dma_cap_set(DMA_SLAVE, mask); +	fifo->tx_chan = dma_request_channel(mask, usbhsf_dma_filter, +					    &fifo->tx_slave); + +	dma_cap_zero(mask); +	dma_cap_set(DMA_SLAVE, mask); +	fifo->rx_chan = dma_request_channel(mask, usbhsf_dma_filter, +					    &fifo->rx_slave); + +	if (fifo->tx_chan || fifo->rx_chan) +		dev_dbg(dev, "enable DMAEngine (%s%s%s)\n", +			 fifo->name, +			 fifo->tx_chan ? "[TX]" : "    ", +			 fifo->rx_chan ? "[RX]" : "    "); +} + +/* + *		irq functions + */ +static int usbhsf_irq_empty(struct usbhs_priv *priv, +			    struct usbhs_irq_state *irq_state) +{ +	struct usbhs_pipe *pipe; +	struct device *dev = usbhs_priv_to_dev(priv); +	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; + +		ret = usbhsf_pkt_handler(pipe, USBHSF_PKT_TRY_RUN); +		if (ret < 0) +			dev_err(dev, "irq_empty run_error %d : %d\n", i, ret); +	} + +	return 0; +} + +static int usbhsf_irq_ready(struct usbhs_priv *priv, +			    struct usbhs_irq_state *irq_state) +{ +	struct usbhs_pipe *pipe; +	struct device *dev = usbhs_priv_to_dev(priv); +	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; + +		ret = usbhsf_pkt_handler(pipe, USBHSF_PKT_TRY_RUN); +		if (ret < 0) +			dev_err(dev, "irq_ready run_error %d : %d\n", i, ret); +	} + +	return 0; +} + +static void usbhsf_dma_complete(void *arg) +{ +	struct usbhs_pipe *pipe = arg; +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +	struct device *dev = usbhs_priv_to_dev(priv); +	int ret; + +	ret = usbhsf_pkt_handler(pipe, USBHSF_PKT_DMA_DONE); +	if (ret < 0) +		dev_err(dev, "dma_complete run_error %d : %d\n", +			usbhs_pipe_number(pipe), ret); +} + +/* + *		fifo init + */ +void usbhs_fifo_init(struct usbhs_priv *priv) +{ +	struct usbhs_mod *mod = usbhs_mod_get_current(priv); +	struct usbhs_fifo *cfifo = usbhsf_get_cfifo(priv); +	struct usbhs_fifo *d0fifo = usbhsf_get_d0fifo(priv); +	struct usbhs_fifo *d1fifo = usbhsf_get_d1fifo(priv); + +	mod->irq_empty		= usbhsf_irq_empty; +	mod->irq_ready		= usbhsf_irq_ready; +	mod->irq_bempsts	= 0; +	mod->irq_brdysts	= 0; + +	cfifo->pipe	= NULL; +	d0fifo->pipe	= NULL; +	d1fifo->pipe	= NULL; +} + +void usbhs_fifo_quit(struct usbhs_priv *priv) +{ +	struct usbhs_mod *mod = usbhs_mod_get_current(priv); + +	mod->irq_empty		= NULL; +	mod->irq_ready		= NULL; +	mod->irq_bempsts	= 0; +	mod->irq_brdysts	= 0; +} + +int usbhs_fifo_probe(struct usbhs_priv *priv) +{ +	struct usbhs_fifo *fifo; + +	/* CFIFO */ +	fifo = usbhsf_get_cfifo(priv); +	fifo->name	= "CFIFO"; +	fifo->port	= CFIFO; +	fifo->sel	= CFIFOSEL; +	fifo->ctr	= CFIFOCTR; + +	/* D0FIFO */ +	fifo = usbhsf_get_d0fifo(priv); +	fifo->name	= "D0FIFO"; +	fifo->port	= D0FIFO; +	fifo->sel	= D0FIFOSEL; +	fifo->ctr	= D0FIFOCTR; +	fifo->tx_slave.shdma_slave.slave_id	= usbhs_get_dparam(priv, d0_tx_id); +	fifo->rx_slave.shdma_slave.slave_id	= usbhs_get_dparam(priv, d0_rx_id); +	usbhsf_dma_init(priv, fifo); + +	/* D1FIFO */ +	fifo = usbhsf_get_d1fifo(priv); +	fifo->name	= "D1FIFO"; +	fifo->port	= D1FIFO; +	fifo->sel	= D1FIFOSEL; +	fifo->ctr	= D1FIFOCTR; +	fifo->tx_slave.shdma_slave.slave_id	= usbhs_get_dparam(priv, d1_tx_id); +	fifo->rx_slave.shdma_slave.slave_id	= usbhs_get_dparam(priv, d1_rx_id); +	usbhsf_dma_init(priv, fifo); + +	return 0; +} + +void usbhs_fifo_remove(struct usbhs_priv *priv) +{ +	usbhsf_dma_quit(priv, usbhsf_get_d0fifo(priv)); +	usbhsf_dma_quit(priv, usbhsf_get_d1fifo(priv)); +} diff --git a/drivers/usb/renesas_usbhs/fifo.h b/drivers/usb/renesas_usbhs/fifo.h new file mode 100644 index 00000000000..a168a1760fc --- /dev/null +++ b/drivers/usb/renesas_usbhs/fifo.h @@ -0,0 +1,102 @@ +/* + * 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_FIFO_H +#define RENESAS_USB_FIFO_H + +#include <linux/interrupt.h> +#include <linux/sh_dma.h> +#include <linux/workqueue.h> +#include <asm/dma.h> +#include "pipe.h" + +struct usbhs_fifo { +	char *name; +	u32 port;	/* xFIFO */ +	u32 sel;	/* xFIFOSEL */ +	u32 ctr;	/* xFIFOCTR */ + +	struct usbhs_pipe	*pipe; + +	struct dma_chan		*tx_chan; +	struct dma_chan		*rx_chan; + +	struct sh_dmae_slave	tx_slave; +	struct sh_dmae_slave	rx_slave; +}; + +struct usbhs_fifo_info { +	struct usbhs_fifo cfifo; +	struct usbhs_fifo d0fifo; +	struct usbhs_fifo d1fifo; +}; + +struct usbhs_pkt_handle; +struct usbhs_pkt { +	struct list_head node; +	struct usbhs_pipe *pipe; +	struct usbhs_pkt_handle *handler; +	void (*done)(struct usbhs_priv *priv, +		     struct usbhs_pkt *pkt); +	struct work_struct work; +	dma_addr_t dma; +	void *buf; +	int length; +	int trans; +	int actual; +	int zero; +	int sequence; +}; + +struct usbhs_pkt_handle { +	int (*prepare)(struct usbhs_pkt *pkt, int *is_done); +	int (*try_run)(struct usbhs_pkt *pkt, int *is_done); +	int (*dma_done)(struct usbhs_pkt *pkt, int *is_done); +}; + +/* + * fifo + */ +int usbhs_fifo_probe(struct usbhs_priv *priv); +void usbhs_fifo_remove(struct usbhs_priv *priv); +void usbhs_fifo_init(struct usbhs_priv *priv); +void usbhs_fifo_quit(struct usbhs_priv *priv); + +/* + * packet info + */ +extern struct usbhs_pkt_handle usbhs_fifo_pio_push_handler; +extern struct usbhs_pkt_handle usbhs_fifo_pio_pop_handler; +extern struct usbhs_pkt_handle usbhs_ctrl_stage_end_handler; + +extern struct usbhs_pkt_handle usbhs_fifo_dma_push_handler; +extern struct usbhs_pkt_handle usbhs_fifo_dma_pop_handler; + +extern struct usbhs_pkt_handle usbhs_dcp_status_stage_in_handler; +extern struct usbhs_pkt_handle usbhs_dcp_status_stage_out_handler; + +extern struct usbhs_pkt_handle usbhs_dcp_data_stage_in_handler; +extern struct usbhs_pkt_handle usbhs_dcp_data_stage_out_handler; + +void usbhs_pkt_init(struct usbhs_pkt *pkt); +void usbhs_pkt_push(struct usbhs_pipe *pipe, struct usbhs_pkt *pkt, +		    void (*done)(struct usbhs_priv *priv, +				 struct usbhs_pkt *pkt), +		    void *buf, int len, int zero, int sequence); +struct usbhs_pkt *usbhs_pkt_pop(struct usbhs_pipe *pipe, struct usbhs_pkt *pkt); +void usbhs_pkt_start(struct usbhs_pipe *pipe); + +#endif /* RENESAS_USB_FIFO_H */ diff --git a/drivers/usb/renesas_usbhs/mod.c b/drivers/usb/renesas_usbhs/mod.c new file mode 100644 index 00000000000..6a030b931a3 --- /dev/null +++ b/drivers/usb/renesas_usbhs/mod.c @@ -0,0 +1,387 @@ +/* + * 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); + +	renesas_usbhs_call_notify_hotplug(pdev); + +	return 0; +} + +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 = usbhs_mod_get_current(priv); +	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_host_probe(priv); +	if (ret < 0) +		return ret; + +	ret = usbhs_mod_gadget_probe(priv); +	if (ret < 0) +		goto mod_init_host_err; + +	/* irq settings */ +	ret = devm_request_irq(dev, priv->irq, usbhs_interrupt, +			  priv->irqflags, 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); +mod_init_host_err: +	usbhs_mod_host_remove(priv); + +	return ret; +} + +void usbhs_mod_remove(struct usbhs_priv *priv) +{ +	usbhs_mod_host_remove(priv); +	usbhs_mod_gadget_remove(priv); +} + +/* + *		status functions + */ +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 int usbhs_status_get_each_irq(struct usbhs_priv *priv, +				     struct usbhs_irq_state *state) +{ +	struct usbhs_mod *mod = usbhs_mod_get_current(priv); +	u16 intenb0, intenb1; + +	state->intsts0 = usbhs_read(priv, INTSTS0); +	state->intsts1 = usbhs_read(priv, INTSTS1); + +	intenb0 = usbhs_read(priv, INTENB0); +	intenb1 = usbhs_read(priv, INTENB1); + +	/* 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; +	} + +	/* +	 * Check whether the irq enable registers and the irq status are set +	 * when IRQF_SHARED is set. +	 */ +	if (priv->irqflags & IRQF_SHARED) { +		if (!(intenb0 & state->intsts0) && +		    !(intenb1 & state->intsts1) && +		    !(state->bempsts) && +		    !(state->brdysts)) +			return -EIO; +	} + +	return 0; +} + +/* + *		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; + +	if (usbhs_status_get_each_irq(priv, &irq_state) < 0) +		return IRQ_NONE; + +	/* +	 * 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, ~irq_state.brdysts); +	usbhs_write(priv, NRDYSTS, ~irq_state.nrdysts); +	usbhs_write(priv, BEMPSTS, ~irq_state.bempsts); + +	/* +	 * call irq callback functions +	 * see also +	 *	usbhs_irq_setting_update +	 */ + +	/* INTSTS0 */ +	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); + +	/* INTSTS1 */ +	if (irq_state.intsts1 & ATTCH) +		usbhs_mod_call(priv, irq_attch, priv, &irq_state); + +	if (irq_state.intsts1 & DTCH) +		usbhs_mod_call(priv, irq_dtch, priv, &irq_state); + +	if (irq_state.intsts1 & SIGN) +		usbhs_mod_call(priv, irq_sign, priv, &irq_state); + +	if (irq_state.intsts1 & SACK) +		usbhs_mod_call(priv, irq_sack, priv, &irq_state); + +	return IRQ_HANDLED; +} + +void usbhs_irq_callback_update(struct usbhs_priv *priv, struct usbhs_mod *mod) +{ +	u16 intenb0 = 0; +	u16 intenb1 = 0; +	struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv); + +	/* +	 * BEMPENB/BRDYENB are picky. +	 * below method is required +	 * +	 *  - clear  INTSTS0 +	 *  - update BEMPENB/BRDYENB +	 *  - update INTSTS0 +	 */ +	usbhs_write(priv, INTENB0, 0); +	usbhs_write(priv, INTENB1, 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) { +		/* +		 * INTSTS0 +		 */ +		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; +		} + +		/* +		 * INTSTS1 +		 */ +		if (mod->irq_attch) +			intenb1 |= ATTCHE; + +		if (mod->irq_dtch) +			intenb1 |= DTCHE; + +		if (mod->irq_sign) +			intenb1 |= SIGNE; + +		if (mod->irq_sack) +			intenb1 |= SACKE; +	} + +	if (intenb0) +		usbhs_write(priv, INTENB0, intenb0); + +	if (intenb1) +		usbhs_write(priv, INTENB1, intenb1); +} diff --git a/drivers/usb/renesas_usbhs/mod.h b/drivers/usb/renesas_usbhs/mod.h new file mode 100644 index 00000000000..1ef5bf60407 --- /dev/null +++ b/drivers/usb/renesas_usbhs/mod.h @@ -0,0 +1,172 @@ +/* + * 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; +}; + +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); + +	/* CTRT (CTSQ) */ +	int (*irq_ctrl_stage)(struct usbhs_priv *priv, +			      struct usbhs_irq_state *irq_state); + +	/* BEMP / BEMPSTS */ +	int (*irq_empty)(struct usbhs_priv *priv, +			 struct usbhs_irq_state *irq_state); +	u16 irq_bempsts; + +	/* BRDY / BRDYSTS */ +	int (*irq_ready)(struct usbhs_priv *priv, +			 struct usbhs_irq_state *irq_state); +	u16 irq_brdysts; + +	/* +	 * INTSTS1 +	 */ + +	/* ATTCHE */ +	int (*irq_attch)(struct usbhs_priv *priv, +			 struct usbhs_irq_state *irq_state); + +	/* DTCHE */ +	int (*irq_dtch)(struct usbhs_priv *priv, +			struct usbhs_irq_state *irq_state); + +	/* SIGN */ +	int (*irq_sign)(struct usbhs_priv *priv, +			struct usbhs_irq_state *irq_state); + +	/* SACK */ +	int (*irq_sack)(struct usbhs_priv *priv, +			struct usbhs_irq_state *irq_state); + +	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); +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_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);			\ +	}) + +/* + * host / gadget control + */ +#if	defined(CONFIG_USB_RENESAS_USBHS_HCD) || \ +	defined(CONFIG_USB_RENESAS_USBHS_HCD_MODULE) +extern int usbhs_mod_host_probe(struct usbhs_priv *priv); +extern int usbhs_mod_host_remove(struct usbhs_priv *priv); +#else +static inline int usbhs_mod_host_probe(struct usbhs_priv *priv) +{ +	return 0; +} +static inline void usbhs_mod_host_remove(struct usbhs_priv *priv) +{ +} +#endif + +#if	defined(CONFIG_USB_RENESAS_USBHS_UDC) || \ +	defined(CONFIG_USB_RENESAS_USBHS_UDC_MODULE) +extern int usbhs_mod_gadget_probe(struct usbhs_priv *priv); +extern void 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..458f3766bef --- /dev/null +++ b/drivers/usb/renesas_usbhs/mod_gadget.c @@ -0,0 +1,1025 @@ +/* + * 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/dma-mapping.h> +#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 usbhs_pkt	pkt; +}; + +#define EP_NAME_SIZE 8 +struct usbhsg_gpriv; +struct usbhsg_uep { +	struct usb_ep		 ep; +	struct usbhs_pipe	*pipe; + +	char ep_name[EP_NAME_SIZE]; + +	struct usbhsg_gpriv *gpriv; +}; + +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) +#define USBHSG_STATUS_SELF_POWERED	(1 << 3) +}; + +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;					\ +	     ((i) < (g)->uep_size) && ((pos) = (g)->uep + (i));	\ +	     (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_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_ureq_to_pkt(u)		(&(u)->pkt) +#define usbhsg_pkt_to_ureq(i)	\ +	container_of(i, struct usbhsg_request, pkt) + +#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) + +/* + *		queue push/pop + */ +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); + +	dev_dbg(dev, "pipe %d : queue pop\n", usbhs_pipe_number(pipe)); + +	ureq->req.status = status; +	ureq->req.complete(&uep->ep, &ureq->req); +} + +static void usbhsg_queue_done(struct usbhs_priv *priv, struct usbhs_pkt *pkt) +{ +	struct usbhs_pipe *pipe = pkt->pipe; +	struct usbhsg_uep *uep = usbhsg_pipe_to_uep(pipe); +	struct usbhsg_request *ureq = usbhsg_pkt_to_ureq(pkt); + +	ureq->req.actual = pkt->actual; + +	usbhsg_queue_pop(uep, ureq, 0); +} + +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); +	struct usbhs_pkt *pkt = usbhsg_ureq_to_pkt(ureq); +	struct usb_request *req = &ureq->req; + +	req->actual = 0; +	req->status = -EINPROGRESS; +	usbhs_pkt_push(pipe, pkt, usbhsg_queue_done, +		       req->buf, req->length, req->zero, -1); +	usbhs_pkt_start(pipe); + +	dev_dbg(dev, "pipe %d : queue push (%d)\n", +		usbhs_pipe_number(pipe), +		req->length); +} + +/* + *		dma map/unmap + */ +static int usbhsg_dma_map_ctrl(struct usbhs_pkt *pkt, int map) +{ +	struct usbhsg_request *ureq = usbhsg_pkt_to_ureq(pkt); +	struct usb_request *req = &ureq->req; +	struct usbhs_pipe *pipe = pkt->pipe; +	struct usbhsg_uep *uep = usbhsg_pipe_to_uep(pipe); +	struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); +	enum dma_data_direction dir; +	int ret = 0; + +	dir = usbhs_pipe_is_dir_host(pipe); + +	if (map) { +		/* it can not use scatter/gather */ +		WARN_ON(req->num_sgs); + +		ret = usb_gadget_map_request(&gpriv->gadget, req, dir); +		if (ret < 0) +			return ret; + +		pkt->dma = req->dma; +	} else { +		usb_gadget_unmap_request(&gpriv->gadget, req, dir); +	} + +	return ret; +} + +/* + *		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_pipe_disable(pipe); +		usbhs_pipe_sequence_data0(pipe); +		usbhs_pipe_enable(pipe); +	} + +	usbhsg_recip_handler_std_control_done(priv, uep, ctrl); + +	usbhs_pkt_start(pipe); + +	return 0; +} + +static 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_STANDARD / set feature functions + */ +static int usbhsg_recip_handler_std_set_device(struct usbhs_priv *priv, +						 struct usbhsg_uep *uep, +						 struct usb_ctrlrequest *ctrl) +{ +	switch (le16_to_cpu(ctrl->wValue)) { +	case USB_DEVICE_TEST_MODE: +		usbhsg_recip_handler_std_control_done(priv, uep, ctrl); +		udelay(100); +		usbhs_sys_set_test_mode(priv, le16_to_cpu(ctrl->wIndex >> 8)); +		break; +	default: +		usbhsg_recip_handler_std_control_done(priv, uep, ctrl); +		break; +	} + +	return 0; +} + +static int usbhsg_recip_handler_std_set_endpoint(struct usbhs_priv *priv, +						 struct usbhsg_uep *uep, +						 struct usb_ctrlrequest *ctrl) +{ +	struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + +	usbhs_pipe_stall(pipe); + +	usbhsg_recip_handler_std_control_done(priv, uep, ctrl); + +	return 0; +} + +static struct usbhsg_recip_handle req_set_feature = { +	.name		= "set feature", +	.device		= usbhsg_recip_handler_std_set_device, +	.interface	= usbhsg_recip_handler_std_control_done, +	.endpoint	= usbhsg_recip_handler_std_set_endpoint, +}; + +/* + *		USB_TYPE_STANDARD / get status functions + */ +static void __usbhsg_recip_send_complete(struct usb_ep *ep, +					 struct usb_request *req) +{ +	struct usbhsg_request *ureq = usbhsg_req_to_ureq(req); + +	/* free allocated recip-buffer/usb_request */ +	kfree(ureq->pkt.buf); +	usb_ep_free_request(ep, req); +} + +static void __usbhsg_recip_send_status(struct usbhsg_gpriv *gpriv, +				       unsigned short status) +{ +	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_request *req; +	unsigned short *buf; + +	/* alloc new usb_request for recip */ +	req = usb_ep_alloc_request(&dcp->ep, GFP_ATOMIC); +	if (!req) { +		dev_err(dev, "recip request allocation fail\n"); +		return; +	} + +	/* alloc recip data buffer */ +	buf = kmalloc(sizeof(*buf), GFP_ATOMIC); +	if (!buf) { +		usb_ep_free_request(&dcp->ep, req); +		dev_err(dev, "recip data allocation fail\n"); +		return; +	} + +	/* recip data is status */ +	*buf = cpu_to_le16(status); + +	/* allocated usb_request/buffer will be freed */ +	req->complete	= __usbhsg_recip_send_complete; +	req->buf	= buf; +	req->length	= sizeof(*buf); +	req->zero	= 0; + +	/* push packet */ +	pipe->handler = &usbhs_fifo_pio_push_handler; +	usbhsg_queue_push(dcp, usbhsg_req_to_ureq(req)); +} + +static int usbhsg_recip_handler_std_get_device(struct usbhs_priv *priv, +					       struct usbhsg_uep *uep, +					       struct usb_ctrlrequest *ctrl) +{ +	struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); +	unsigned short status = 0; + +	if (usbhsg_status_has(gpriv, USBHSG_STATUS_SELF_POWERED)) +		status = 1 << USB_DEVICE_SELF_POWERED; + +	__usbhsg_recip_send_status(gpriv, status); + +	return 0; +} + +static int usbhsg_recip_handler_std_get_interface(struct usbhs_priv *priv, +						  struct usbhsg_uep *uep, +						  struct usb_ctrlrequest *ctrl) +{ +	struct usbhsg_gpriv *gpriv = usbhsg_uep_to_gpriv(uep); +	unsigned short status = 0; + +	__usbhsg_recip_send_status(gpriv, status); + +	return 0; +} + +static int usbhsg_recip_handler_std_get_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); +	unsigned short status = 0; + +	if (usbhs_pipe_is_stall(pipe)) +		status = 1 << USB_ENDPOINT_HALT; + +	__usbhsg_recip_send_status(gpriv, status); + +	return 0; +} + +static struct usbhsg_recip_handle req_get_status = { +	.name		= "get status", +	.device		= usbhsg_recip_handler_std_get_device, +	.interface	= usbhsg_recip_handler_std_get_interface, +	.endpoint	= usbhsg_recip_handler_std_get_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; +	struct usbhs_pipe *pipe; +	int recip = ctrl->bRequestType & USB_RECIP_MASK; +	int nth = le16_to_cpu(ctrl->wIndex) & USB_ENDPOINT_NUMBER_MASK; +	int ret = 0; +	int (*func)(struct usbhs_priv *priv, struct usbhsg_uep *uep, +		    struct usb_ctrlrequest *ctrl); +	char *msg; + +	uep = usbhsg_gpriv_to_nth_uep(gpriv, nth); +	pipe = usbhsg_uep_to_pipe(uep); +	if (!pipe) { +		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_bus_get_speed(priv); + +	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: +		pipe->handler = &usbhs_fifo_pio_push_handler; +		break; +	case WRITE_DATA_STAGE: +		pipe->handler = &usbhs_fifo_pio_pop_handler; +		break; +	case NODATA_STATUS_STAGE: +		pipe->handler = &usbhs_ctrl_stage_end_handler; +		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; +		case USB_REQ_SET_FEATURE: +			recip_handler = &req_set_feature; +			break; +		case USB_REQ_GET_STATUS: +			recip_handler = &req_get_status; +			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_pipe_stall(pipe); + +	return ret; +} + +/* + * + *		usb_dcp_ops + * + */ +static int usbhsg_pipe_disable(struct usbhsg_uep *uep) +{ +	struct usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); +	struct usbhs_pkt *pkt; + +	while (1) { +		pkt = usbhs_pkt_pop(pipe, NULL); +		if (!pkt) +			break; + +		usbhsg_queue_pop(uep, usbhsg_pkt_to_ureq(pkt), -ECONNRESET); +	} + +	usbhs_pipe_disable(pipe); + +	return 0; +} + +/* + * + *		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; +	int ret = -EIO; + +	/* +	 * if it already have pipe, +	 * nothing to do +	 */ +	if (uep->pipe) { +		usbhs_pipe_clear(uep->pipe); +		usbhs_pipe_sequence_data0(uep->pipe); +		return 0; +	} + +	pipe = usbhs_pipe_malloc(priv, +				 usb_endpoint_type(desc), +				 usb_endpoint_dir_in(desc)); +	if (pipe) { +		uep->pipe		= pipe; +		pipe->mod_private	= uep; + +		/* set epnum / maxp */ +		usbhs_pipe_config_update(pipe, 0, +					 usb_endpoint_num(desc), +					 usb_endpoint_maxp(desc)); + +		/* +		 * usbhs_fifo_dma_push/pop_handler try to +		 * use dmaengine if possible. +		 * It will use pio handler if impossible. +		 */ +		if (usb_endpoint_dir_in(desc)) +			pipe->handler = &usbhs_fifo_dma_push_handler; +		else +			pipe->handler = &usbhs_fifo_dma_pop_handler; + +		ret = 0; +	} + +	return ret; +} + +static int usbhsg_ep_disable(struct usb_ep *ep) +{ +	struct usbhsg_uep *uep = usbhsg_ep_to_uep(ep); + +	usbhsg_pipe_disable(uep); + +	uep->pipe->mod_private	= NULL; +	uep->pipe		= NULL; + +	return 0; +} + +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; + +	usbhs_pkt_init(usbhsg_ureq_to_pkt(ureq)); + +	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->pkt.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); + +	/* param check */ +	if (usbhsg_is_not_connected(gpriv)	|| +	    unlikely(!gpriv->driver)		|| +	    unlikely(!pipe)) +		return -ESHUTDOWN; + +	usbhsg_queue_push(uep, ureq); + +	return 0; +} + +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 usbhs_pipe *pipe = usbhsg_uep_to_pipe(uep); + +	usbhs_pkt_pop(pipe, usbhsg_ureq_to_pkt(ureq)); +	usbhsg_queue_pop(uep, ureq, -ECONNRESET); + +	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 usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); +	struct device *dev = usbhsg_gpriv_to_dev(gpriv); +	unsigned long flags; + +	usbhsg_pipe_disable(uep); + +	dev_dbg(dev, "set halt %d (pipe %d)\n", +		halt, usbhs_pipe_number(pipe)); + +	/********************  spin lock ********************/ +	usbhs_lock(priv, flags); + +	if (halt) +		usbhs_pipe_stall(pipe); +	else +		usbhs_pipe_disable(pipe); + +	if (halt && wedge) +		usbhsg_status_set(gpriv, USBHSG_STATUS_WEDGE); +	else +		usbhsg_status_clr(gpriv, USBHSG_STATUS_WEDGE); + +	usbhs_unlock(priv, flags); +	/********************  spin unlock ******************/ + +	return 0; +} + +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); +	unsigned long flags; +	int ret = 0; + +	/********************  spin lock ********************/ +	usbhs_lock(priv, flags); + +	usbhsg_status_set(gpriv, status); +	if (!(usbhsg_status_has(gpriv, USBHSG_STATUS_STARTED) && +	      usbhsg_status_has(gpriv, USBHSG_STATUS_REGISTERD))) +		ret = -1; /* not ready */ + +	usbhs_unlock(priv, flags); +	/********************  spin unlock ********************/ + +	if (ret < 0) +		return 0; /* not ready is not error */ + +	/* +	 * enable interrupt and systems if ready +	 */ +	dev_dbg(dev, "start gadget\n"); + +	/* +	 * pipe initialize and enable DCP +	 */ +	usbhs_pipe_init(priv, +			usbhsg_dma_map_ctrl); +	usbhs_fifo_init(priv); + +	/* dcp init instead of usbhsg_ep_enable() */ +	dcp->pipe		= usbhs_dcp_malloc(priv); +	dcp->pipe->mod_private	= dcp; +	usbhs_pipe_config_update(dcp->pipe, 0, 0, 64); + +	/* +	 * system config enble +	 * - HI speed +	 * - function +	 * - usb module +	 */ +	usbhs_sys_function_ctrl(priv, 1); + +	/* +	 * enable irq callback +	 */ +	mod->irq_dev_state	= usbhsg_irq_dev_state; +	mod->irq_ctrl_stage	= usbhsg_irq_ctrl_stage; +	usbhs_irq_callback_update(priv, mod); + +	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); +	unsigned long flags; +	int ret = 0; + +	/********************  spin lock ********************/ +	usbhs_lock(priv, flags); + +	usbhsg_status_clr(gpriv, status); +	if (!usbhsg_status_has(gpriv, USBHSG_STATUS_STARTED) && +	    !usbhsg_status_has(gpriv, USBHSG_STATUS_REGISTERD)) +		ret = -1; /* already done */ + +	usbhs_unlock(priv, flags); +	/********************  spin unlock ********************/ + +	if (ret < 0) +		return 0; /* already done is not error */ + +	/* +	 * disable interrupt and systems if 1st try +	 */ +	usbhs_fifo_quit(priv); + +	/* disable all irq */ +	mod->irq_dev_state	= NULL; +	mod->irq_ctrl_stage	= NULL; +	usbhs_irq_callback_update(priv, mod); + +	gpriv->gadget.speed = USB_SPEED_UNKNOWN; + +	/* disable sys */ +	usbhs_sys_set_test_mode(priv, 0); +	usbhs_sys_function_ctrl(priv, 0); + +	usbhsg_ep_disable(&dcp->ep); + +	dev_dbg(dev, "stop gadget\n"); + +	return 0; +} + +/* + * + *		linux usb function + * + */ +static int usbhsg_gadget_start(struct usb_gadget *gadget, +		struct usb_gadget_driver *driver) +{ +	struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget); +	struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); + +	if (!driver		|| +	    !driver->setup	|| +	    driver->max_speed < USB_SPEED_FULL) +		return -EINVAL; + +	/* first hook up the driver ... */ +	gpriv->driver = driver; + +	return usbhsg_try_start(priv, USBHSG_STATUS_REGISTERD); +} + +static int usbhsg_gadget_stop(struct usb_gadget *gadget, +		struct usb_gadget_driver *driver) +{ +	struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget); +	struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); + +	usbhsg_try_stop(priv, USBHSG_STATUS_REGISTERD); +	gpriv->driver = NULL; + +	return 0; +} + +/* + *		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 int usbhsg_pullup(struct usb_gadget *gadget, int is_on) +{ +	struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget); +	struct usbhs_priv *priv = usbhsg_gpriv_to_priv(gpriv); + +	usbhs_sys_function_pullup(priv, is_on); + +	return 0; +} + +static int usbhsg_set_selfpowered(struct usb_gadget *gadget, int is_self) +{ +	struct usbhsg_gpriv *gpriv = usbhsg_gadget_to_gpriv(gadget); + +	if (is_self) +		usbhsg_status_set(gpriv, USBHSG_STATUS_SELF_POWERED); +	else +		usbhsg_status_clr(gpriv, USBHSG_STATUS_SELF_POWERED); + +	return 0; +} + +static const struct usb_gadget_ops usbhsg_gadget_ops = { +	.get_frame		= usbhsg_get_frame, +	.set_selfpowered	= usbhsg_set_selfpowered, +	.udc_start		= usbhsg_gadget_start, +	.udc_stop		= usbhsg_gadget_stop, +	.pullup			= usbhsg_pullup, +}; + +static int usbhsg_start(struct usbhs_priv *priv) +{ +	return usbhsg_try_start(priv, USBHSG_STATUS_STARTED); +} + +static int usbhsg_stop(struct usbhs_priv *priv) +{ +	struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + +	/* cable disconnect */ +	if (gpriv->driver && +	    gpriv->driver->disconnect) +		gpriv->driver->disconnect(&gpriv->gadget); + +	return usbhsg_try_stop(priv, USBHSG_STATUS_STARTED); +} + +int 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; +	int ret; + +	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"); +		ret = -ENOMEM; +		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 +	 */ +	gpriv->gadget.dev.parent	= dev; +	gpriv->gadget.name		= "renesas_usbhs_udc"; +	gpriv->gadget.ops		= &usbhsg_gadget_ops; +	gpriv->gadget.max_speed		= USB_SPEED_HIGH; + +	INIT_LIST_HEAD(&gpriv->gadget.ep_list); + +	/* +	 * init usb_ep +	 */ +	usbhsg_for_each_uep_with_dcp(uep, gpriv, i) { +		uep->gpriv	= gpriv; +		uep->pipe	= NULL; +		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 DCP */ +		if (usbhsg_is_dcp(uep)) { +			gpriv->gadget.ep0 = &uep->ep; +			usb_ep_set_maxpacket_limit(&uep->ep, 64); +		} +		/* init normal pipe */ +		else { +			usb_ep_set_maxpacket_limit(&uep->ep, 512); +			list_add_tail(&uep->ep.ep_list, &gpriv->gadget.ep_list); +		} +	} + +	ret = usb_add_gadget_udc(dev, &gpriv->gadget); +	if (ret) +		goto err_add_udc; + + +	dev_info(dev, "gadget probed\n"); + +	return 0; + +err_add_udc: +	kfree(gpriv->uep); + +usbhs_mod_gadget_probe_err_gpriv: +	kfree(gpriv); + +	return ret; +} + +void usbhs_mod_gadget_remove(struct usbhs_priv *priv) +{ +	struct usbhsg_gpriv *gpriv = usbhsg_priv_to_gpriv(priv); + +	usb_del_gadget_udc(&gpriv->gadget); + +	kfree(gpriv->uep); +	kfree(gpriv); +} diff --git a/drivers/usb/renesas_usbhs/mod_host.c b/drivers/usb/renesas_usbhs/mod_host.c new file mode 100644 index 00000000000..10e1ded9c9c --- /dev/null +++ b/drivers/usb/renesas_usbhs/mod_host.c @@ -0,0 +1,1586 @@ +/* + * 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/list.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include "common.h" + +/* + *** HARDWARE LIMITATION *** + * + * 1) renesas_usbhs has a limited number of controllable devices. + *    it can control only 9 devices in generally. + *	see DEVADDn / DCPMAXP / PIPEMAXP. + * + * 2) renesas_usbhs pipe number is limited. + *    the pipe will be re-used for each devices. + *    so, software should control DATA0/1 sequence of each devices. + */ + + +/* + *		image of mod_host + * + * +--------+ + * | udev 0 | --> it is used when set address + * +--------+ + * + * +--------+					pipes are reused for each uep. + * | udev 1 |-+- [uep 0 (dcp) ] --+		pipe will be switched when + * +--------+ |			  |		other device requested + *	      +- [uep 1 (bulk)]	--|---+		   +--------------+ + *	      |			  +--------------> | pipe0 (dcp)  | + *	      +- [uep 2 (bulk)]	-@    |		   +--------------+ + *				      |		   | pipe1 (isoc) | + * +--------+			      |		   +--------------+ + * | udev 2 |-+- [uep 0 (dcp) ]	-@    +----------> | pipe2 (bulk) | + * +--------+ |					   +--------------+ + *	      +- [uep 1 (int) ]	----+	  +------> | pipe3 (bulk) | + *				    |	  |	   +--------------+ + * +--------+			    +-----|------> | pipe4 (int)  | + * | udev 3 |-+- [uep 0 (dcp) ]	-@	  |	   +--------------+ + * +--------+ |				  |	   | ....	  | + *	      +- [uep 1 (bulk)]	-@	  |	   | ....	  | + *	      |				  | + *	      +- [uep 2 (bulk)]-----------+ + * + * @ :	uep requested free pipe, but all have been used. + *	now it is waiting for free pipe + */ + + +/* + *		struct + */ +struct usbhsh_request { +	struct urb		*urb; +	struct usbhs_pkt	pkt; +}; + +struct usbhsh_device { +	struct usb_device	*usbv; +	struct list_head	ep_list_head; /* list of usbhsh_ep */ +}; + +struct usbhsh_ep { +	struct usbhs_pipe	*pipe;   /* attached pipe */ +	struct usbhsh_device	*udev;   /* attached udev */ +	struct usb_host_endpoint *ep; +	struct list_head	ep_list; /* list to usbhsh_device */ +	unsigned int		counter; /* pipe attach counter */ +}; + +#define USBHSH_DEVICE_MAX	10 /* see DEVADDn / DCPMAXP / PIPEMAXP */ +#define USBHSH_PORT_MAX		 7 /* see DEVADDn :: HUBPORT */ +struct usbhsh_hpriv { +	struct usbhs_mod	mod; +	struct usbhs_pipe	*dcp; + +	struct usbhsh_device	udev[USBHSH_DEVICE_MAX]; + +	u32	port_stat;	/* USB_PORT_STAT_xxx */ + +	struct completion	setup_ack_done; +}; + + +static const char usbhsh_hcd_name[] = "renesas_usbhs host"; + +/* + *		macro + */ +#define usbhsh_priv_to_hpriv(priv) \ +	container_of(usbhs_mod_get(priv, USBHS_HOST), struct usbhsh_hpriv, mod) + +#define __usbhsh_for_each_udev(start, pos, h, i)	\ +	for ((i) = start;						\ +	     ((i) < USBHSH_DEVICE_MAX) && ((pos) = (h)->udev + (i));	\ +	     (i)++) + +#define usbhsh_for_each_udev(pos, hpriv, i)	\ +	__usbhsh_for_each_udev(1, pos, hpriv, i) + +#define usbhsh_for_each_udev_with_dev0(pos, hpriv, i)	\ +	__usbhsh_for_each_udev(0, pos, hpriv, i) + +#define usbhsh_hcd_to_hpriv(h)	(struct usbhsh_hpriv *)((h)->hcd_priv) +#define usbhsh_hcd_to_dev(h)	((h)->self.controller) + +#define usbhsh_hpriv_to_priv(h)	((h)->mod.priv) +#define usbhsh_hpriv_to_dcp(h)	((h)->dcp) +#define usbhsh_hpriv_to_hcd(h)	\ +	container_of((void *)h, struct usb_hcd, hcd_priv) + +#define usbhsh_ep_to_uep(u)	((u)->hcpriv) +#define usbhsh_uep_to_pipe(u)	((u)->pipe) +#define usbhsh_uep_to_udev(u)	((u)->udev) +#define usbhsh_uep_to_ep(u)	((u)->ep) + +#define usbhsh_urb_to_ureq(u)	((u)->hcpriv) +#define usbhsh_urb_to_usbv(u)	((u)->dev) + +#define usbhsh_usbv_to_udev(d)	dev_get_drvdata(&(d)->dev) + +#define usbhsh_udev_to_usbv(h)	((h)->usbv) +#define usbhsh_udev_is_used(h)	usbhsh_udev_to_usbv(h) + +#define usbhsh_pipe_to_uep(p)	((p)->mod_private) + +#define usbhsh_device_parent(d)		(usbhsh_usbv_to_udev((d)->usbv->parent)) +#define usbhsh_device_hubport(d)	((d)->usbv->portnum) +#define usbhsh_device_number(h, d)	((int)((d) - (h)->udev)) +#define usbhsh_device_nth(h, d)		((h)->udev + d) +#define usbhsh_device0(h)		usbhsh_device_nth(h, 0) + +#define usbhsh_port_stat_init(h)	((h)->port_stat = 0) +#define usbhsh_port_stat_set(h, s)	((h)->port_stat |= (s)) +#define usbhsh_port_stat_clear(h, s)	((h)->port_stat &= ~(s)) +#define usbhsh_port_stat_get(h)		((h)->port_stat) + +#define usbhsh_pkt_to_ureq(p)	\ +	container_of((void *)p, struct usbhsh_request, pkt) + +/* + *		req alloc/free + */ +static struct usbhsh_request *usbhsh_ureq_alloc(struct usbhsh_hpriv *hpriv, +					       struct urb *urb, +					       gfp_t mem_flags) +{ +	struct usbhsh_request *ureq; +	struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); +	struct device *dev = usbhs_priv_to_dev(priv); + +	ureq = kzalloc(sizeof(struct usbhsh_request), mem_flags); +	if (!ureq) { +		dev_err(dev, "ureq alloc fail\n"); +		return NULL; +	} + +	usbhs_pkt_init(&ureq->pkt); +	ureq->urb = urb; +	usbhsh_urb_to_ureq(urb) = ureq; + +	return ureq; +} + +static void usbhsh_ureq_free(struct usbhsh_hpriv *hpriv, +			    struct usbhsh_request *ureq) +{ +	usbhsh_urb_to_ureq(ureq->urb) = NULL; +	ureq->urb = NULL; + +	kfree(ureq); +} + +/* + *		status + */ +static int usbhsh_is_running(struct usbhsh_hpriv *hpriv) +{ +	/* +	 * we can decide some device is attached or not +	 * by checking mod.irq_attch +	 * see +	 *	usbhsh_irq_attch() +	 *	usbhsh_irq_dtch() +	 */ +	return (hpriv->mod.irq_attch == NULL); +} + +/* + *		pipe control + */ +static void usbhsh_endpoint_sequence_save(struct usbhsh_hpriv *hpriv, +					  struct urb *urb, +					  struct usbhs_pkt *pkt) +{ +	int len = urb->actual_length; +	int maxp = usb_endpoint_maxp(&urb->ep->desc); +	int t = 0; + +	/* DCP is out of sequence control */ +	if (usb_pipecontrol(urb->pipe)) +		return; + +	/* +	 * renesas_usbhs pipe has a limitation in a number. +	 * So, driver should re-use the limited pipe for each device/endpoint. +	 * DATA0/1 sequence should be saved for it. +	 * see [image of mod_host] +	 *     [HARDWARE LIMITATION] +	 */ + +	/* +	 * next sequence depends on actual_length +	 * +	 * ex) actual_length = 1147, maxp = 512 +	 * data0 : 512 +	 * data1 : 512 +	 * data0 : 123 +	 * data1 is the next sequence +	 */ +	t = len / maxp; +	if (len % maxp) +		t++; +	if (pkt->zero) +		t++; +	t %= 2; + +	if (t) +		usb_dotoggle(urb->dev, +			     usb_pipeendpoint(urb->pipe), +			     usb_pipeout(urb->pipe)); +} + +static struct usbhsh_device *usbhsh_device_get(struct usbhsh_hpriv *hpriv, +					       struct urb *urb); + +static int usbhsh_pipe_attach(struct usbhsh_hpriv *hpriv, +			      struct urb *urb) +{ +	struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); +	struct usbhsh_ep *uep = usbhsh_ep_to_uep(urb->ep); +	struct usbhsh_device *udev = usbhsh_device_get(hpriv, urb); +	struct usbhs_pipe *pipe; +	struct usb_endpoint_descriptor *desc = &urb->ep->desc; +	struct device *dev = usbhs_priv_to_dev(priv); +	unsigned long flags; +	int dir_in_req = !!usb_pipein(urb->pipe); +	int is_dcp = usb_endpoint_xfer_control(desc); +	int i, dir_in; +	int ret = -EBUSY; + +	/********************  spin lock ********************/ +	usbhs_lock(priv, flags); + +	/* +	 * if uep has been attached to pipe, +	 * reuse it +	 */ +	if (usbhsh_uep_to_pipe(uep)) { +		ret = 0; +		goto usbhsh_pipe_attach_done; +	} + +	usbhs_for_each_pipe_with_dcp(pipe, priv, i) { + +		/* check pipe type */ +		if (!usbhs_pipe_type_is(pipe, usb_endpoint_type(desc))) +			continue; + +		/* check pipe direction if normal pipe */ +		if (!is_dcp) { +			dir_in = !!usbhs_pipe_is_dir_in(pipe); +			if (0 != (dir_in - dir_in_req)) +				continue; +		} + +		/* check pipe is free */ +		if (usbhsh_pipe_to_uep(pipe)) +			continue; + +		/* +		 * attach pipe to uep +		 * +		 * usbhs_pipe_config_update() should be called after +		 * usbhs_set_device_config() +		 * see +		 *  DCPMAXP/PIPEMAXP +		 */ +		usbhsh_uep_to_pipe(uep)		= pipe; +		usbhsh_pipe_to_uep(pipe)	= uep; + +		usbhs_pipe_config_update(pipe, +					 usbhsh_device_number(hpriv, udev), +					 usb_endpoint_num(desc), +					 usb_endpoint_maxp(desc)); + +		dev_dbg(dev, "%s [%d-%d(%s:%s)]\n", __func__, +			usbhsh_device_number(hpriv, udev), +			usb_endpoint_num(desc), +			usbhs_pipe_name(pipe), +			dir_in_req ? "in" : "out"); + +		ret = 0; +		break; +	} + +usbhsh_pipe_attach_done: +	if (0 == ret) +		uep->counter++; + +	usbhs_unlock(priv, flags); +	/********************  spin unlock ******************/ + +	return ret; +} + +static void usbhsh_pipe_detach(struct usbhsh_hpriv *hpriv, +			       struct usbhsh_ep *uep) +{ +	struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); +	struct usbhs_pipe *pipe; +	struct device *dev = usbhs_priv_to_dev(priv); +	unsigned long flags; + +	if (unlikely(!uep)) { +		dev_err(dev, "no uep\n"); +		return; +	} + +	/********************  spin lock ********************/ +	usbhs_lock(priv, flags); + +	pipe = usbhsh_uep_to_pipe(uep); + +	if (unlikely(!pipe)) { +		dev_err(dev, "uep doens't have pipe\n"); +	} else if (1 == uep->counter--) { /* last user */ +		struct usb_host_endpoint *ep = usbhsh_uep_to_ep(uep); +		struct usbhsh_device *udev = usbhsh_uep_to_udev(uep); + +		/* detach pipe from uep */ +		usbhsh_uep_to_pipe(uep)		= NULL; +		usbhsh_pipe_to_uep(pipe)	= NULL; + +		dev_dbg(dev, "%s [%d-%d(%s)]\n", __func__, +			usbhsh_device_number(hpriv, udev), +			usb_endpoint_num(&ep->desc), +			usbhs_pipe_name(pipe)); +	} + +	usbhs_unlock(priv, flags); +	/********************  spin unlock ******************/ +} + +/* + *		endpoint control + */ +static int usbhsh_endpoint_attach(struct usbhsh_hpriv *hpriv, +				  struct urb *urb, +				  gfp_t mem_flags) +{ +	struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); +	struct usbhsh_device *udev = usbhsh_device_get(hpriv, urb); +	struct usb_host_endpoint *ep = urb->ep; +	struct usbhsh_ep *uep; +	struct device *dev = usbhs_priv_to_dev(priv); +	struct usb_endpoint_descriptor *desc = &ep->desc; +	unsigned long flags; + +	uep = kzalloc(sizeof(struct usbhsh_ep), mem_flags); +	if (!uep) { +		dev_err(dev, "usbhsh_ep alloc fail\n"); +		return -ENOMEM; +	} + +	/********************  spin lock ********************/ +	usbhs_lock(priv, flags); + +	/* +	 * init endpoint +	 */ +	uep->counter = 0; +	INIT_LIST_HEAD(&uep->ep_list); +	list_add_tail(&uep->ep_list, &udev->ep_list_head); + +	usbhsh_uep_to_udev(uep)	= udev; +	usbhsh_uep_to_ep(uep)	= ep; +	usbhsh_ep_to_uep(ep)	= uep; + +	usbhs_unlock(priv, flags); +	/********************  spin unlock ******************/ + +	dev_dbg(dev, "%s [%d-%d]\n", __func__, +		usbhsh_device_number(hpriv, udev), +		usb_endpoint_num(desc)); + +	return 0; +} + +static void usbhsh_endpoint_detach(struct usbhsh_hpriv *hpriv, +				   struct usb_host_endpoint *ep) +{ +	struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); +	struct device *dev = usbhs_priv_to_dev(priv); +	struct usbhsh_ep *uep = usbhsh_ep_to_uep(ep); +	unsigned long flags; + +	if (!uep) +		return; + +	dev_dbg(dev, "%s [%d-%d]\n", __func__, +		usbhsh_device_number(hpriv, usbhsh_uep_to_udev(uep)), +		usb_endpoint_num(&ep->desc)); + +	if (usbhsh_uep_to_pipe(uep)) +		usbhsh_pipe_detach(hpriv, uep); + +	/********************  spin lock ********************/ +	usbhs_lock(priv, flags); + +	/* remove this endpoint from udev */ +	list_del_init(&uep->ep_list); + +	usbhsh_uep_to_udev(uep)	= NULL; +	usbhsh_uep_to_ep(uep)	= NULL; +	usbhsh_ep_to_uep(ep)	= NULL; + +	usbhs_unlock(priv, flags); +	/********************  spin unlock ******************/ + +	kfree(uep); +} + +static void usbhsh_endpoint_detach_all(struct usbhsh_hpriv *hpriv, +				       struct usbhsh_device *udev) +{ +	struct usbhsh_ep *uep, *next; + +	list_for_each_entry_safe(uep, next, &udev->ep_list_head, ep_list) +		usbhsh_endpoint_detach(hpriv, usbhsh_uep_to_ep(uep)); +} + +/* + *		device control + */ +static int usbhsh_connected_to_rhdev(struct usb_hcd *hcd, +				     struct usbhsh_device *udev) +{ +	struct usb_device *usbv = usbhsh_udev_to_usbv(udev); + +	return hcd->self.root_hub == usbv->parent; +} + +static int usbhsh_device_has_endpoint(struct usbhsh_device *udev) +{ +	return !list_empty(&udev->ep_list_head); +} + +static struct usbhsh_device *usbhsh_device_get(struct usbhsh_hpriv *hpriv, +					       struct urb *urb) +{ +	struct usb_device *usbv = usbhsh_urb_to_usbv(urb); +	struct usbhsh_device *udev = usbhsh_usbv_to_udev(usbv); + +	/* usbhsh_device_attach() is still not called */ +	if (!udev) +		return NULL; + +	/* if it is device0, return it */ +	if (0 == usb_pipedevice(urb->pipe)) +		return usbhsh_device0(hpriv); + +	/* return attached device */ +	return udev; +} + +static struct usbhsh_device *usbhsh_device_attach(struct usbhsh_hpriv *hpriv, +						 struct urb *urb) +{ +	struct usbhsh_device *udev = NULL; +	struct usbhsh_device *udev0 = usbhsh_device0(hpriv); +	struct usbhsh_device *pos; +	struct usb_hcd *hcd = usbhsh_hpriv_to_hcd(hpriv); +	struct device *dev = usbhsh_hcd_to_dev(hcd); +	struct usb_device *usbv = usbhsh_urb_to_usbv(urb); +	struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); +	unsigned long flags; +	u16 upphub, hubport; +	int i; + +	/* +	 * This function should be called only while urb is pointing to device0. +	 * It will attach unused usbhsh_device to urb (usbv), +	 * and initialize device0. +	 * You can use usbhsh_device_get() to get "current" udev, +	 * and usbhsh_usbv_to_udev() is for "attached" udev. +	 */ +	if (0 != usb_pipedevice(urb->pipe)) { +		dev_err(dev, "%s fail: urb isn't pointing device0\n", __func__); +		return NULL; +	} + +	/********************  spin lock ********************/ +	usbhs_lock(priv, flags); + +	/* +	 * find unused device +	 */ +	usbhsh_for_each_udev(pos, hpriv, i) { +		if (usbhsh_udev_is_used(pos)) +			continue; +		udev = pos; +		break; +	} + +	if (udev) { +		/* +		 * usbhsh_usbv_to_udev() +		 * usbhsh_udev_to_usbv() +		 * will be enable +		 */ +		dev_set_drvdata(&usbv->dev, udev); +		udev->usbv = usbv; +	} + +	usbhs_unlock(priv, flags); +	/********************  spin unlock ******************/ + +	if (!udev) { +		dev_err(dev, "no free usbhsh_device\n"); +		return NULL; +	} + +	if (usbhsh_device_has_endpoint(udev)) { +		dev_warn(dev, "udev have old endpoint\n"); +		usbhsh_endpoint_detach_all(hpriv, udev); +	} + +	if (usbhsh_device_has_endpoint(udev0)) { +		dev_warn(dev, "udev0 have old endpoint\n"); +		usbhsh_endpoint_detach_all(hpriv, udev0); +	} + +	/* uep will be attached */ +	INIT_LIST_HEAD(&udev0->ep_list_head); +	INIT_LIST_HEAD(&udev->ep_list_head); + +	/* +	 * set device0 config +	 */ +	usbhs_set_device_config(priv, +				0, 0, 0, usbv->speed); + +	/* +	 * set new device config +	 */ +	upphub	= 0; +	hubport	= 0; +	if (!usbhsh_connected_to_rhdev(hcd, udev)) { +		/* if udev is not connected to rhdev, it means parent is Hub */ +		struct usbhsh_device *parent = usbhsh_device_parent(udev); + +		upphub	= usbhsh_device_number(hpriv, parent); +		hubport	= usbhsh_device_hubport(udev); + +		dev_dbg(dev, "%s connecte to Hub [%d:%d](%p)\n", __func__, +			upphub, hubport, parent); +	} + +	usbhs_set_device_config(priv, +			       usbhsh_device_number(hpriv, udev), +			       upphub, hubport, usbv->speed); + +	dev_dbg(dev, "%s [%d](%p)\n", __func__, +		usbhsh_device_number(hpriv, udev), udev); + +	return udev; +} + +static void usbhsh_device_detach(struct usbhsh_hpriv *hpriv, +			       struct usbhsh_device *udev) +{ +	struct usb_hcd *hcd = usbhsh_hpriv_to_hcd(hpriv); +	struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); +	struct device *dev = usbhsh_hcd_to_dev(hcd); +	struct usb_device *usbv = usbhsh_udev_to_usbv(udev); +	unsigned long flags; + +	dev_dbg(dev, "%s [%d](%p)\n", __func__, +		usbhsh_device_number(hpriv, udev), udev); + +	if (usbhsh_device_has_endpoint(udev)) { +		dev_warn(dev, "udev still have endpoint\n"); +		usbhsh_endpoint_detach_all(hpriv, udev); +	} + +	/* +	 * There is nothing to do if it is device0. +	 * see +	 *  usbhsh_device_attach() +	 *  usbhsh_device_get() +	 */ +	if (0 == usbhsh_device_number(hpriv, udev)) +		return; + +	/********************  spin lock ********************/ +	usbhs_lock(priv, flags); + +	/* +	 * usbhsh_usbv_to_udev() +	 * usbhsh_udev_to_usbv() +	 * will be disable +	 */ +	dev_set_drvdata(&usbv->dev, NULL); +	udev->usbv = NULL; + +	usbhs_unlock(priv, flags); +	/********************  spin unlock ******************/ +} + +/* + *		queue push/pop + */ +static void usbhsh_queue_done(struct usbhs_priv *priv, struct usbhs_pkt *pkt) +{ +	struct usbhsh_request *ureq = usbhsh_pkt_to_ureq(pkt); +	struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); +	struct usb_hcd *hcd = usbhsh_hpriv_to_hcd(hpriv); +	struct urb *urb = ureq->urb; +	struct device *dev = usbhs_priv_to_dev(priv); +	int status = 0; + +	dev_dbg(dev, "%s\n", __func__); + +	if (!urb) { +		dev_warn(dev, "pkt doesn't have urb\n"); +		return; +	} + +	if (!usbhsh_is_running(hpriv)) +		status = -ESHUTDOWN; + +	urb->actual_length = pkt->actual; + +	usbhsh_endpoint_sequence_save(hpriv, urb, pkt); +	usbhsh_ureq_free(hpriv, ureq); + +	usbhsh_pipe_detach(hpriv, usbhsh_ep_to_uep(urb->ep)); + +	usb_hcd_unlink_urb_from_ep(hcd, urb); +	usb_hcd_giveback_urb(hcd, urb, status); +} + +static int usbhsh_queue_push(struct usb_hcd *hcd, +			     struct urb *urb, +			     gfp_t mem_flags) +{ +	struct usbhsh_hpriv *hpriv = usbhsh_hcd_to_hpriv(hcd); +	struct usbhsh_ep *uep = usbhsh_ep_to_uep(urb->ep); +	struct usbhs_pipe *pipe = usbhsh_uep_to_pipe(uep); +	struct device *dev = usbhsh_hcd_to_dev(hcd); +	struct usbhsh_request *ureq; +	void *buf; +	int len, sequence; + +	if (usb_pipeisoc(urb->pipe)) { +		dev_err(dev, "pipe iso is not supported now\n"); +		return -EIO; +	} + +	/* this ureq will be freed on usbhsh_queue_done() */ +	ureq = usbhsh_ureq_alloc(hpriv, urb, mem_flags); +	if (unlikely(!ureq)) { +		dev_err(dev, "ureq alloc fail\n"); +		return -ENOMEM; +	} + +	if (usb_pipein(urb->pipe)) +		pipe->handler = &usbhs_fifo_dma_pop_handler; +	else +		pipe->handler = &usbhs_fifo_dma_push_handler; + +	buf = (void *)(urb->transfer_buffer + urb->actual_length); +	len = urb->transfer_buffer_length - urb->actual_length; + +	sequence = usb_gettoggle(urb->dev, +				 usb_pipeendpoint(urb->pipe), +				 usb_pipeout(urb->pipe)); + +	dev_dbg(dev, "%s\n", __func__); +	usbhs_pkt_push(pipe, &ureq->pkt, usbhsh_queue_done, +		       buf, len, (urb->transfer_flags & URB_ZERO_PACKET), +		       sequence); + +	usbhs_pkt_start(pipe); + +	return 0; +} + +static void usbhsh_queue_force_pop(struct usbhs_priv *priv, +				   struct usbhs_pipe *pipe) +{ +	struct usbhs_pkt *pkt; + +	while (1) { +		pkt = usbhs_pkt_pop(pipe, NULL); +		if (!pkt) +			break; + +		/* +		 * if all packet are gone, usbhsh_endpoint_disable() +		 * will be called. +		 * then, attached device/endpoint/pipe will be detached +		 */ +		usbhsh_queue_done(priv, pkt); +	} +} + +static void usbhsh_queue_force_pop_all(struct usbhs_priv *priv) +{ +	struct usbhs_pipe *pos; +	int i; + +	usbhs_for_each_pipe_with_dcp(pos, priv, i) +		usbhsh_queue_force_pop(priv, pos); +} + +/* + *		DCP setup stage + */ +static int usbhsh_is_request_address(struct urb *urb) +{ +	struct usb_ctrlrequest *req; + +	req = (struct usb_ctrlrequest *)urb->setup_packet; + +	if ((DeviceOutRequest    == req->bRequestType << 8) && +	    (USB_REQ_SET_ADDRESS == req->bRequest)) +		return 1; +	else +		return 0; +} + +static void usbhsh_setup_stage_packet_push(struct usbhsh_hpriv *hpriv, +					   struct urb *urb, +					   struct usbhs_pipe *pipe) +{ +	struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); +	struct usb_ctrlrequest req; +	struct device *dev = usbhs_priv_to_dev(priv); + +	/* +	 * wait setup packet ACK +	 * see +	 *	usbhsh_irq_setup_ack() +	 *	usbhsh_irq_setup_err() +	 */ +	init_completion(&hpriv->setup_ack_done); + +	/* copy original request */ +	memcpy(&req, urb->setup_packet, sizeof(struct usb_ctrlrequest)); + +	/* +	 * renesas_usbhs can not use original usb address. +	 * see HARDWARE LIMITATION. +	 * modify usb address here to use attached device. +	 * see usbhsh_device_attach() +	 */ +	if (usbhsh_is_request_address(urb)) { +		struct usb_device *usbv = usbhsh_urb_to_usbv(urb); +		struct usbhsh_device *udev = usbhsh_usbv_to_udev(usbv); + +		/* udev is a attached device */ +		req.wValue = usbhsh_device_number(hpriv, udev); +		dev_dbg(dev, "create new address - %d\n", req.wValue); +	} + +	/* set request */ +	usbhs_usbreq_set_val(priv, &req); + +	/* +	 * wait setup packet ACK +	 */ +	wait_for_completion(&hpriv->setup_ack_done); + +	dev_dbg(dev, "%s done\n", __func__); +} + +/* + *		DCP data stage + */ +static void usbhsh_data_stage_packet_done(struct usbhs_priv *priv, +					  struct usbhs_pkt *pkt) +{ +	struct usbhsh_request *ureq = usbhsh_pkt_to_ureq(pkt); +	struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); + +	/* this ureq was connected to urb when usbhsh_urb_enqueue()  */ + +	usbhsh_ureq_free(hpriv, ureq); +} + +static int usbhsh_data_stage_packet_push(struct usbhsh_hpriv *hpriv, +					 struct urb *urb, +					 struct usbhs_pipe *pipe, +					 gfp_t mem_flags) + +{ +	struct usbhsh_request *ureq; + +	/* this ureq will be freed on usbhsh_data_stage_packet_done() */ +	ureq = usbhsh_ureq_alloc(hpriv, urb, mem_flags); +	if (unlikely(!ureq)) +		return -ENOMEM; + +	if (usb_pipein(urb->pipe)) +		pipe->handler = &usbhs_dcp_data_stage_in_handler; +	else +		pipe->handler = &usbhs_dcp_data_stage_out_handler; + +	usbhs_pkt_push(pipe, &ureq->pkt, +		       usbhsh_data_stage_packet_done, +		       urb->transfer_buffer, +		       urb->transfer_buffer_length, +		       (urb->transfer_flags & URB_ZERO_PACKET), +		       -1); + +	return 0; +} + +/* + *		DCP status stage + */ +static int usbhsh_status_stage_packet_push(struct usbhsh_hpriv *hpriv, +					    struct urb *urb, +					    struct usbhs_pipe *pipe, +					    gfp_t mem_flags) +{ +	struct usbhsh_request *ureq; + +	/* This ureq will be freed on usbhsh_queue_done() */ +	ureq = usbhsh_ureq_alloc(hpriv, urb, mem_flags); +	if (unlikely(!ureq)) +		return -ENOMEM; + +	if (usb_pipein(urb->pipe)) +		pipe->handler = &usbhs_dcp_status_stage_in_handler; +	else +		pipe->handler = &usbhs_dcp_status_stage_out_handler; + +	usbhs_pkt_push(pipe, &ureq->pkt, +		       usbhsh_queue_done, +		       NULL, +		       urb->transfer_buffer_length, +		       0, -1); + +	return 0; +} + +static int usbhsh_dcp_queue_push(struct usb_hcd *hcd, +				 struct urb *urb, +				 gfp_t mflags) +{ +	struct usbhsh_hpriv *hpriv = usbhsh_hcd_to_hpriv(hcd); +	struct usbhsh_ep *uep = usbhsh_ep_to_uep(urb->ep); +	struct usbhs_pipe *pipe = usbhsh_uep_to_pipe(uep); +	struct device *dev = usbhsh_hcd_to_dev(hcd); +	int ret; + +	dev_dbg(dev, "%s\n", __func__); + +	/* +	 * setup stage +	 * +	 * usbhsh_send_setup_stage_packet() wait SACK/SIGN +	 */ +	usbhsh_setup_stage_packet_push(hpriv, urb, pipe); + +	/* +	 * data stage +	 * +	 * It is pushed only when urb has buffer. +	 */ +	if (urb->transfer_buffer_length) { +		ret = usbhsh_data_stage_packet_push(hpriv, urb, pipe, mflags); +		if (ret < 0) { +			dev_err(dev, "data stage failed\n"); +			return ret; +		} +	} + +	/* +	 * status stage +	 */ +	ret = usbhsh_status_stage_packet_push(hpriv, urb, pipe, mflags); +	if (ret < 0) { +		dev_err(dev, "status stage failed\n"); +		return ret; +	} + +	/* +	 * start pushed packets +	 */ +	usbhs_pkt_start(pipe); + +	return 0; +} + +/* + *		dma map functions + */ +static int usbhsh_dma_map_ctrl(struct usbhs_pkt *pkt, int map) +{ +	if (map) { +		struct usbhsh_request *ureq = usbhsh_pkt_to_ureq(pkt); +		struct urb *urb = ureq->urb; + +		/* it can not use scatter/gather */ +		if (urb->num_sgs) +			return -EINVAL; + +		pkt->dma = urb->transfer_dma; +		if (!pkt->dma) +			return -EINVAL; +	} + +	return 0; +} + +/* + *		for hc_driver + */ +static int usbhsh_host_start(struct usb_hcd *hcd) +{ +	return 0; +} + +static void usbhsh_host_stop(struct usb_hcd *hcd) +{ +} + +static int usbhsh_urb_enqueue(struct usb_hcd *hcd, +			      struct urb *urb, +			      gfp_t mem_flags) +{ +	struct usbhsh_hpriv *hpriv = usbhsh_hcd_to_hpriv(hcd); +	struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); +	struct device *dev = usbhs_priv_to_dev(priv); +	struct usb_host_endpoint *ep = urb->ep; +	struct usbhsh_device *new_udev = NULL; +	int is_dir_in = usb_pipein(urb->pipe); +	int ret; + +	dev_dbg(dev, "%s (%s)\n", __func__, is_dir_in ? "in" : "out"); + +	if (!usbhsh_is_running(hpriv)) { +		ret = -EIO; +		dev_err(dev, "host is not running\n"); +		goto usbhsh_urb_enqueue_error_not_linked; +	} + +	ret = usb_hcd_link_urb_to_ep(hcd, urb); +	if (ret) { +		dev_err(dev, "urb link failed\n"); +		goto usbhsh_urb_enqueue_error_not_linked; +	} + +	/* +	 * attach udev if needed +	 * see [image of mod_host] +	 */ +	if (!usbhsh_device_get(hpriv, urb)) { +		new_udev = usbhsh_device_attach(hpriv, urb); +		if (!new_udev) { +			ret = -EIO; +			dev_err(dev, "device attach failed\n"); +			goto usbhsh_urb_enqueue_error_not_linked; +		} +	} + +	/* +	 * attach endpoint if needed +	 * see [image of mod_host] +	 */ +	if (!usbhsh_ep_to_uep(ep)) { +		ret = usbhsh_endpoint_attach(hpriv, urb, mem_flags); +		if (ret < 0) { +			dev_err(dev, "endpoint attach failed\n"); +			goto usbhsh_urb_enqueue_error_free_device; +		} +	} + +	/* +	 * attach pipe to endpoint +	 * see [image of mod_host] +	 */ +	ret = usbhsh_pipe_attach(hpriv, urb); +	if (ret < 0) { +		dev_err(dev, "pipe attach failed\n"); +		goto usbhsh_urb_enqueue_error_free_endpoint; +	} + +	/* +	 * push packet +	 */ +	if (usb_pipecontrol(urb->pipe)) +		ret = usbhsh_dcp_queue_push(hcd, urb, mem_flags); +	else +		ret = usbhsh_queue_push(hcd, urb, mem_flags); + +	return ret; + +usbhsh_urb_enqueue_error_free_endpoint: +	usbhsh_endpoint_detach(hpriv, ep); +usbhsh_urb_enqueue_error_free_device: +	if (new_udev) +		usbhsh_device_detach(hpriv, new_udev); +usbhsh_urb_enqueue_error_not_linked: + +	dev_dbg(dev, "%s error\n", __func__); + +	return ret; +} + +static int usbhsh_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ +	struct usbhsh_hpriv *hpriv = usbhsh_hcd_to_hpriv(hcd); +	struct usbhsh_request *ureq = usbhsh_urb_to_ureq(urb); + +	if (ureq) { +		struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); +		struct usbhs_pkt *pkt = &ureq->pkt; + +		usbhs_pkt_pop(pkt->pipe, pkt); +		usbhsh_queue_done(priv, pkt); +	} + +	return 0; +} + +static void usbhsh_endpoint_disable(struct usb_hcd *hcd, +				    struct usb_host_endpoint *ep) +{ +	struct usbhsh_ep *uep = usbhsh_ep_to_uep(ep); +	struct usbhsh_device *udev; +	struct usbhsh_hpriv *hpriv; + +	/* +	 * this function might be called manytimes by same hcd/ep +	 * in-endpoint == out-endpoint if ep == dcp. +	 */ +	if (!uep) +		return; + +	udev	= usbhsh_uep_to_udev(uep); +	hpriv	= usbhsh_hcd_to_hpriv(hcd); + +	usbhsh_endpoint_detach(hpriv, ep); + +	/* +	 * if there is no endpoint, +	 * free device +	 */ +	if (!usbhsh_device_has_endpoint(udev)) +		usbhsh_device_detach(hpriv, udev); +} + +static int usbhsh_hub_status_data(struct usb_hcd *hcd, char *buf) +{ +	struct usbhsh_hpriv *hpriv = usbhsh_hcd_to_hpriv(hcd); +	int roothub_id = 1; /* only 1 root hub */ + +	/* +	 * does port stat was changed ? +	 * check USB_PORT_STAT_C_xxx << 16 +	 */ +	if (usbhsh_port_stat_get(hpriv) & 0xFFFF0000) +		*buf = (1 << roothub_id); +	else +		*buf = 0; + +	return !!(*buf); +} + +static int __usbhsh_hub_hub_feature(struct usbhsh_hpriv *hpriv, +				    u16 typeReq, u16 wValue, +				    u16 wIndex, char *buf, u16 wLength) +{ +	struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); +	struct device *dev = usbhs_priv_to_dev(priv); + +	switch (wValue) { +	case C_HUB_OVER_CURRENT: +	case C_HUB_LOCAL_POWER: +		dev_dbg(dev, "%s :: C_HUB_xx\n", __func__); +		return 0; +	} + +	return -EPIPE; +} + +static int __usbhsh_hub_port_feature(struct usbhsh_hpriv *hpriv, +				     u16 typeReq, u16 wValue, +				     u16 wIndex, char *buf, u16 wLength) +{ +	struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); +	struct device *dev = usbhs_priv_to_dev(priv); +	int enable = (typeReq == SetPortFeature); +	int speed, i, timeout = 128; +	int roothub_id = 1; /* only 1 root hub */ + +	/* common error */ +	if (wIndex > roothub_id || wLength != 0) +		return -EPIPE; + +	/* check wValue */ +	switch (wValue) { +	case USB_PORT_FEAT_POWER: +		usbhs_vbus_ctrl(priv, enable); +		dev_dbg(dev, "%s :: USB_PORT_FEAT_POWER\n", __func__); +		break; + +	case USB_PORT_FEAT_ENABLE: +	case USB_PORT_FEAT_SUSPEND: +	case USB_PORT_FEAT_C_ENABLE: +	case USB_PORT_FEAT_C_SUSPEND: +	case USB_PORT_FEAT_C_CONNECTION: +	case USB_PORT_FEAT_C_OVER_CURRENT: +	case USB_PORT_FEAT_C_RESET: +		dev_dbg(dev, "%s :: USB_PORT_FEAT_xxx\n", __func__); +		break; + +	case USB_PORT_FEAT_RESET: +		if (!enable) +			break; + +		usbhsh_port_stat_clear(hpriv, +				       USB_PORT_STAT_HIGH_SPEED | +				       USB_PORT_STAT_LOW_SPEED); + +		usbhsh_queue_force_pop_all(priv); + +		usbhs_bus_send_reset(priv); +		msleep(20); +		usbhs_bus_send_sof_enable(priv); + +		for (i = 0; i < timeout ; i++) { +			switch (usbhs_bus_get_speed(priv)) { +			case USB_SPEED_LOW: +				speed = USB_PORT_STAT_LOW_SPEED; +				goto got_usb_bus_speed; +			case USB_SPEED_HIGH: +				speed = USB_PORT_STAT_HIGH_SPEED; +				goto got_usb_bus_speed; +			case USB_SPEED_FULL: +				speed = 0; +				goto got_usb_bus_speed; +			} + +			msleep(20); +		} +		return -EPIPE; + +got_usb_bus_speed: +		usbhsh_port_stat_set(hpriv, speed); +		usbhsh_port_stat_set(hpriv, USB_PORT_STAT_ENABLE); + +		dev_dbg(dev, "%s :: USB_PORT_FEAT_RESET (speed = %d)\n", +			__func__, speed); + +		/* status change is not needed */ +		return 0; + +	default: +		return -EPIPE; +	} + +	/* set/clear status */ +	if (enable) +		usbhsh_port_stat_set(hpriv, (1 << wValue)); +	else +		usbhsh_port_stat_clear(hpriv, (1 << wValue)); + +	return 0; +} + +static int __usbhsh_hub_get_status(struct usbhsh_hpriv *hpriv, +				   u16 typeReq, u16 wValue, +				   u16 wIndex, char *buf, u16 wLength) +{ +	struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); +	struct usb_hub_descriptor *desc = (struct usb_hub_descriptor *)buf; +	struct device *dev = usbhs_priv_to_dev(priv); +	int roothub_id = 1; /* only 1 root hub */ + +	switch (typeReq) { +	case GetHubStatus: +		dev_dbg(dev, "%s :: GetHubStatus\n", __func__); + +		*buf = 0x00; +		break; + +	case GetPortStatus: +		if (wIndex != roothub_id) +			return -EPIPE; + +		dev_dbg(dev, "%s :: GetPortStatus\n", __func__); +		*(__le32 *)buf = cpu_to_le32(usbhsh_port_stat_get(hpriv)); +		break; + +	case GetHubDescriptor: +		desc->bDescriptorType		= 0x29; +		desc->bHubContrCurrent		= 0; +		desc->bNbrPorts			= roothub_id; +		desc->bDescLength		= 9; +		desc->bPwrOn2PwrGood		= 0; +		desc->wHubCharacteristics	= cpu_to_le16(0x0011); +		desc->u.hs.DeviceRemovable[0]	= (roothub_id << 1); +		desc->u.hs.DeviceRemovable[1]	= ~0; +		dev_dbg(dev, "%s :: GetHubDescriptor\n", __func__); +		break; +	} + +	return 0; +} + +static int usbhsh_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, +			      u16 wIndex, char *buf, u16 wLength) +{ +	struct usbhsh_hpriv *hpriv = usbhsh_hcd_to_hpriv(hcd); +	struct usbhs_priv *priv = usbhsh_hpriv_to_priv(hpriv); +	struct device *dev = usbhs_priv_to_dev(priv); +	int ret = -EPIPE; + +	switch (typeReq) { + +	/* Hub Feature */ +	case ClearHubFeature: +	case SetHubFeature: +		ret = __usbhsh_hub_hub_feature(hpriv, typeReq, +					       wValue, wIndex, buf, wLength); +		break; + +	/* Port Feature */ +	case SetPortFeature: +	case ClearPortFeature: +		ret = __usbhsh_hub_port_feature(hpriv, typeReq, +						wValue, wIndex, buf, wLength); +		break; + +	/* Get status */ +	case GetHubStatus: +	case GetPortStatus: +	case GetHubDescriptor: +		ret = __usbhsh_hub_get_status(hpriv, typeReq, +					      wValue, wIndex, buf, wLength); +		break; +	} + +	dev_dbg(dev, "typeReq = %x, ret = %d, port_stat = %x\n", +		typeReq, ret, usbhsh_port_stat_get(hpriv)); + +	return ret; +} + +static int usbhsh_bus_nop(struct usb_hcd *hcd) +{ +	/* nothing to do */ +	return 0; +} + +static struct hc_driver usbhsh_driver = { +	.description =		usbhsh_hcd_name, +	.hcd_priv_size =	sizeof(struct usbhsh_hpriv), + +	/* +	 * generic hardware linkage +	 */ +	.flags =		HCD_USB2, + +	.start =		usbhsh_host_start, +	.stop =			usbhsh_host_stop, + +	/* +	 * managing i/o requests and associated device resources +	 */ +	.urb_enqueue =		usbhsh_urb_enqueue, +	.urb_dequeue =		usbhsh_urb_dequeue, +	.endpoint_disable =	usbhsh_endpoint_disable, + +	/* +	 * root hub +	 */ +	.hub_status_data =	usbhsh_hub_status_data, +	.hub_control =		usbhsh_hub_control, +	.bus_suspend =		usbhsh_bus_nop, +	.bus_resume =		usbhsh_bus_nop, +}; + +/* + *		interrupt functions + */ +static int usbhsh_irq_attch(struct usbhs_priv *priv, +			    struct usbhs_irq_state *irq_state) +{ +	struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); +	struct device *dev = usbhs_priv_to_dev(priv); + +	dev_dbg(dev, "device attached\n"); + +	usbhsh_port_stat_set(hpriv, USB_PORT_STAT_CONNECTION); +	usbhsh_port_stat_set(hpriv, USB_PORT_STAT_C_CONNECTION << 16); + +	/* +	 * attch interrupt might happen infinitely on some device +	 * (on self power USB hub ?) +	 * disable it here. +	 * +	 * usbhsh_is_running() becomes effective +	 * according to this process. +	 * see +	 *	usbhsh_is_running() +	 *	usbhsh_urb_enqueue() +	 */ +	hpriv->mod.irq_attch = NULL; +	usbhs_irq_callback_update(priv, &hpriv->mod); + +	return 0; +} + +static int usbhsh_irq_dtch(struct usbhs_priv *priv, +			   struct usbhs_irq_state *irq_state) +{ +	struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); +	struct device *dev = usbhs_priv_to_dev(priv); + +	dev_dbg(dev, "device detached\n"); + +	usbhsh_port_stat_clear(hpriv, USB_PORT_STAT_CONNECTION); +	usbhsh_port_stat_set(hpriv, USB_PORT_STAT_C_CONNECTION << 16); + +	/* +	 * enable attch interrupt again +	 * +	 * usbhsh_is_running() becomes invalid +	 * according to this process. +	 * see +	 *	usbhsh_is_running() +	 *	usbhsh_urb_enqueue() +	 */ +	hpriv->mod.irq_attch = usbhsh_irq_attch; +	usbhs_irq_callback_update(priv, &hpriv->mod); + +	/* +	 * usbhsh_queue_force_pop_all() should be called +	 * after usbhsh_is_running() becomes invalid. +	 */ +	usbhsh_queue_force_pop_all(priv); + +	return 0; +} + +static int usbhsh_irq_setup_ack(struct usbhs_priv *priv, +				struct usbhs_irq_state *irq_state) +{ +	struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); +	struct device *dev = usbhs_priv_to_dev(priv); + +	dev_dbg(dev, "setup packet OK\n"); + +	complete(&hpriv->setup_ack_done); /* see usbhsh_urb_enqueue() */ + +	return 0; +} + +static int usbhsh_irq_setup_err(struct usbhs_priv *priv, +				struct usbhs_irq_state *irq_state) +{ +	struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); +	struct device *dev = usbhs_priv_to_dev(priv); + +	dev_dbg(dev, "setup packet Err\n"); + +	complete(&hpriv->setup_ack_done); /* see usbhsh_urb_enqueue() */ + +	return 0; +} + +/* + *		module start/stop + */ +static void usbhsh_pipe_init_for_host(struct usbhs_priv *priv) +{ +	struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); +	struct usbhs_pipe *pipe; +	u32 *pipe_type = usbhs_get_dparam(priv, pipe_type); +	int pipe_size = usbhs_get_dparam(priv, pipe_size); +	int old_type, dir_in, i; + +	/* init all pipe */ +	old_type = USB_ENDPOINT_XFER_CONTROL; +	for (i = 0; i < pipe_size; i++) { + +		/* +		 * data "output" will be finished as soon as possible, +		 * but there is no guaranty at data "input" case. +		 * +		 * "input" needs "standby" pipe. +		 * So, "input" direction pipe > "output" direction pipe +		 * is good idea. +		 * +		 * 1st USB_ENDPOINT_XFER_xxx will be output direction, +		 * and the other will be input direction here. +		 * +		 * ex) +		 * ... +		 * USB_ENDPOINT_XFER_ISOC -> dir out +		 * USB_ENDPOINT_XFER_ISOC -> dir in +		 * USB_ENDPOINT_XFER_BULK -> dir out +		 * USB_ENDPOINT_XFER_BULK -> dir in +		 * USB_ENDPOINT_XFER_BULK -> dir in +		 * ... +		 */ +		dir_in = (pipe_type[i] == old_type); +		old_type = pipe_type[i]; + +		if (USB_ENDPOINT_XFER_CONTROL == pipe_type[i]) { +			pipe = usbhs_dcp_malloc(priv); +			usbhsh_hpriv_to_dcp(hpriv) = pipe; +		} else { +			pipe = usbhs_pipe_malloc(priv, +						 pipe_type[i], +						 dir_in); +		} + +		pipe->mod_private = NULL; +	} +} + +static int usbhsh_start(struct usbhs_priv *priv) +{ +	struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); +	struct usb_hcd *hcd = usbhsh_hpriv_to_hcd(hpriv); +	struct usbhs_mod *mod = usbhs_mod_get_current(priv); +	struct device *dev = usbhs_priv_to_dev(priv); +	int ret; + +	/* add hcd */ +	ret = usb_add_hcd(hcd, 0, 0); +	if (ret < 0) +		return 0; +	device_wakeup_enable(hcd->self.controller); + +	/* +	 * pipe initialize and enable DCP +	 */ +	usbhs_pipe_init(priv, +			usbhsh_dma_map_ctrl); +	usbhs_fifo_init(priv); +	usbhsh_pipe_init_for_host(priv); + +	/* +	 * system config enble +	 * - HI speed +	 * - host +	 * - usb module +	 */ +	usbhs_sys_host_ctrl(priv, 1); + +	/* +	 * enable irq callback +	 */ +	mod->irq_attch		= usbhsh_irq_attch; +	mod->irq_dtch		= usbhsh_irq_dtch; +	mod->irq_sack		= usbhsh_irq_setup_ack; +	mod->irq_sign		= usbhsh_irq_setup_err; +	usbhs_irq_callback_update(priv, mod); + +	dev_dbg(dev, "start host\n"); + +	return ret; +} + +static int usbhsh_stop(struct usbhs_priv *priv) +{ +	struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); +	struct usb_hcd *hcd = usbhsh_hpriv_to_hcd(hpriv); +	struct usbhs_mod *mod = usbhs_mod_get_current(priv); +	struct device *dev = usbhs_priv_to_dev(priv); + +	/* +	 * disable irq callback +	 */ +	mod->irq_attch	= NULL; +	mod->irq_dtch	= NULL; +	mod->irq_sack	= NULL; +	mod->irq_sign	= NULL; +	usbhs_irq_callback_update(priv, mod); + +	usb_remove_hcd(hcd); + +	/* disable sys */ +	usbhs_sys_host_ctrl(priv, 0); + +	dev_dbg(dev, "quit host\n"); + +	return 0; +} + +int usbhs_mod_host_probe(struct usbhs_priv *priv) +{ +	struct usbhsh_hpriv *hpriv; +	struct usb_hcd *hcd; +	struct usbhsh_device *udev; +	struct device *dev = usbhs_priv_to_dev(priv); +	int i; + +	/* initialize hcd */ +	hcd = usb_create_hcd(&usbhsh_driver, dev, usbhsh_hcd_name); +	if (!hcd) { +		dev_err(dev, "Failed to create hcd\n"); +		return -ENOMEM; +	} +	hcd->has_tt = 1; /* for low/full speed */ + +	/* +	 * 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 "usbhsh_start" +	 */ + +	hpriv = usbhsh_hcd_to_hpriv(hcd); + +	/* +	 * register itself +	 */ +	usbhs_mod_register(priv, &hpriv->mod, USBHS_HOST); + +	/* init hpriv */ +	hpriv->mod.name		= "host"; +	hpriv->mod.start	= usbhsh_start; +	hpriv->mod.stop		= usbhsh_stop; +	usbhsh_port_stat_init(hpriv); + +	/* init all device */ +	usbhsh_for_each_udev_with_dev0(udev, hpriv, i) { +		udev->usbv	= NULL; +		INIT_LIST_HEAD(&udev->ep_list_head); +	} + +	dev_info(dev, "host probed\n"); + +	return 0; +} + +int usbhs_mod_host_remove(struct usbhs_priv *priv) +{ +	struct usbhsh_hpriv *hpriv = usbhsh_priv_to_hpriv(priv); +	struct usb_hcd *hcd = usbhsh_hpriv_to_hcd(hpriv); + +	usb_put_hcd(hcd); + +	return 0; +} diff --git a/drivers/usb/renesas_usbhs/pipe.c b/drivers/usb/renesas_usbhs/pipe.c new file mode 100644 index 00000000000..7926e1c700f --- /dev/null +++ b/drivers/usb/renesas_usbhs/pipe.c @@ -0,0 +1,826 @@ +/* + * 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/slab.h> +#include "common.h" +#include "pipe.h" + +/* + *		macros + */ +#define usbhsp_addr_offset(p)	((usbhs_pipe_number(p) - 1) * 2) + +#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) + +/* + * 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", +}; + +char *usbhs_pipe_name(struct usbhs_pipe *pipe) +{ +	return usbhsp_pipe_name[usbhs_pipe_type(pipe)]; +} + +/* + *		DCPCTR/PIPEnCTR functions + */ +static void usbhsp_pipectrl_set(struct usbhs_pipe *pipe, u16 mask, u16 val) +{ +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +	int offset = usbhsp_addr_offset(pipe); + +	if (usbhs_pipe_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 = usbhs_pipe_to_priv(pipe); +	int offset = usbhsp_addr_offset(pipe); + +	if (usbhs_pipe_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 = usbhs_pipe_to_priv(pipe); + +	if (usbhs_pipe_is_dcp(pipe)) +		usbhs_bset(priv, dcp_reg, mask, val); +	else +		usbhs_bset(priv, pipe_reg, mask, val); +} + +/* + *		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); +} + +/* + *		PIPEnTRN/PIPEnTRE functions + */ +static void usbhsp_pipe_trn_set(struct usbhs_pipe *pipe, u16 mask, u16 val) +{ +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +	struct device *dev = usbhs_priv_to_dev(priv); +	int num = usbhs_pipe_number(pipe); +	u16 reg; + +	/* +	 * It is impossible to calculate address, +	 * since PIPEnTRN addresses were mapped randomly. +	 */ +#define CASE_PIPExTRN(a)		\ +	case 0x ## a:			\ +		reg = PIPE ## a ## TRN;	\ +		break; + +	switch (num) { +	CASE_PIPExTRN(1); +	CASE_PIPExTRN(2); +	CASE_PIPExTRN(3); +	CASE_PIPExTRN(4); +	CASE_PIPExTRN(5); +	CASE_PIPExTRN(B); +	CASE_PIPExTRN(C); +	CASE_PIPExTRN(D); +	CASE_PIPExTRN(E); +	CASE_PIPExTRN(F); +	CASE_PIPExTRN(9); +	CASE_PIPExTRN(A); +	default: +		dev_err(dev, "unknown pipe (%d)\n", num); +		return; +	} +	__usbhsp_pipe_xxx_set(pipe, 0, reg, mask, val); +} + +static void usbhsp_pipe_tre_set(struct usbhs_pipe *pipe, u16 mask, u16 val) +{ +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +	struct device *dev = usbhs_priv_to_dev(priv); +	int num = usbhs_pipe_number(pipe); +	u16 reg; + +	/* +	 * It is impossible to calculate address, +	 * since PIPEnTRE addresses were mapped randomly. +	 */ +#define CASE_PIPExTRE(a)			\ +	case 0x ## a:				\ +		reg = PIPE ## a ## TRE;		\ +		break; + +	switch (num) { +	CASE_PIPExTRE(1); +	CASE_PIPExTRE(2); +	CASE_PIPExTRE(3); +	CASE_PIPExTRE(4); +	CASE_PIPExTRE(5); +	CASE_PIPExTRE(B); +	CASE_PIPExTRE(C); +	CASE_PIPExTRE(D); +	CASE_PIPExTRE(E); +	CASE_PIPExTRE(F); +	CASE_PIPExTRE(9); +	CASE_PIPExTRE(A); +	default: +		dev_err(dev, "unknown pipe (%d)\n", num); +		return; +	} + +	__usbhsp_pipe_xxx_set(pipe, 0, reg, mask, val); +} + +/* + *		PIPEBUF + */ +static void usbhsp_pipe_buf_set(struct usbhs_pipe *pipe, u16 mask, u16 val) +{ +	if (usbhs_pipe_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); +} + +/* + *		pipe control functions + */ +static void usbhsp_pipe_select(struct usbhs_pipe *pipe) +{ +	struct usbhs_priv *priv = usbhs_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 = usbhs_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_pipe_disable(pipe); + +	do { +		val  = usbhsp_pipectrl_get(pipe); +		val &= CSSTS | PID_MASK; +		if (!val) +			return 0; + +		udelay(10); + +	} while (timeout--); + +	return -EBUSY; +} + +int usbhs_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_pipe_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_pipe_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_pipe_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; +	} +} + +int usbhs_pipe_is_stall(struct usbhs_pipe *pipe) +{ +	u16 pid = usbhsp_pipectrl_get(pipe) & PID_MASK; + +	return (int)(pid == PID_STALL10 || pid == PID_STALL11); +} + +void usbhs_pipe_set_trans_count_if_bulk(struct usbhs_pipe *pipe, int len) +{ +	if (!usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK)) +		return; + +	/* +	 * clear and disable transfer counter for IN/OUT pipe +	 */ +	usbhsp_pipe_tre_set(pipe, TRCLR | TRENB, TRCLR); + +	/* +	 * Only IN direction bulk pipe can use transfer count. +	 * Without using this function, +	 * received data will break if it was large data size. +	 * see PIPEnTRN/PIPEnTRE for detail +	 */ +	if (usbhs_pipe_is_dir_in(pipe)) { +		int maxp = usbhs_pipe_get_maxpacket(pipe); + +		usbhsp_pipe_trn_set(pipe, 0xffff, DIV_ROUND_UP(len, maxp)); +		usbhsp_pipe_tre_set(pipe, TRENB, TRENB); /* enable */ +	} +} + + +/* + *		pipe setup + */ +static int usbhsp_possible_double_buffer(struct usbhs_pipe *pipe) +{ +	/* +	 * only ISO / BULK pipe can use double buffer +	 */ +	if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK) || +	    usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_ISOC)) +		return 1; + +	return 0; +} + +static u16 usbhsp_setup_pipecfg(struct usbhs_pipe *pipe, +				int is_host, +				int dir_in) +{ +	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 (usbhs_pipe_is_dcp(pipe)) +		return -EINVAL; + +	/* +	 * PIPECFG +	 * +	 * see +	 *  - "Register Descriptions" - "PIPECFG" register +	 *  - "Features"  - "Pipe configuration" +	 *  - "Operation" - "Pipe Control" +	 */ + +	/* TYPE */ +	type = type_array[usbhs_pipe_type(pipe)]; + +	/* BFRE */ +	if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_ISOC) || +	    usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK)) +		bfre = 0; /* FIXME */ + +	/* DBLB */ +	if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_ISOC) || +	    usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK)) +		dblb = (is_double) ? DBLB : 0; + +	/* CNTMD */ +	if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK)) +		cntmd = 0; /* FIXME */ + +	/* DIR */ +	if (dir_in) +		usbhsp_flags_set(pipe, IS_DIR_HOST); + +	if (!!is_host ^ !!dir_in) +		dir |= DIR_OUT; + +	if (!dir) +		usbhsp_flags_set(pipe, IS_DIR_IN); + +	/* SHTNAK */ +	if (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_BULK) && +	    !dir) +		shtnak = SHTNAK; + +	/* EPNUM */ +	epnum = 0; /* see usbhs_pipe_config_update() */ + +	return	type	| +		bfre	| +		dblb	| +		cntmd	| +		dir	| +		shtnak	| +		epnum; +} + +static u16 usbhsp_setup_pipebuff(struct usbhs_pipe *pipe) +{ +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +	struct usbhs_pipe_info *info = usbhs_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 (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_CONTROL)) +		buff_size = 256; +	else if (usbhs_pipe_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 (usbhs_pipe_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; +} + +void usbhs_pipe_config_update(struct usbhs_pipe *pipe, u16 devsel, +			      u16 epnum, u16 maxp) +{ +	if (devsel > 0xA) { +		struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); +		struct device *dev = usbhs_priv_to_dev(priv); + +		dev_err(dev, "devsel error %d\n", devsel); + +		devsel = 0; +	} + +	usbhsp_pipe_barrier(pipe); + +	pipe->maxp = maxp; + +	usbhsp_pipe_select(pipe); +	usbhsp_pipe_maxp_set(pipe, 0xFFFF, +			     (devsel << 12) | +			     maxp); + +	if (!usbhs_pipe_is_dcp(pipe)) +		usbhsp_pipe_cfg_set(pipe,  0x000F, epnum); +} + +/* + *		pipe control + */ +int usbhs_pipe_get_maxpacket(struct usbhs_pipe *pipe) +{ +	/* +	 * see +	 *	usbhs_pipe_config_update() +	 *	usbhs_dcp_malloc() +	 */ +	return pipe->maxp; +} + +int usbhs_pipe_is_dir_in(struct usbhs_pipe *pipe) +{ +	return usbhsp_flags_has(pipe, IS_DIR_IN); +} + +int usbhs_pipe_is_dir_host(struct usbhs_pipe *pipe) +{ +	return usbhsp_flags_has(pipe, IS_DIR_HOST); +} + +void usbhs_pipe_data_sequence(struct usbhs_pipe *pipe, int sequence) +{ +	u16 mask = (SQCLR | SQSET); +	u16 val; + +	/* +	 * sequence +	 *  0  : data0 +	 *  1  : data1 +	 *  -1 : no change +	 */ +	switch (sequence) { +	case 0: +		val = SQCLR; +		break; +	case 1: +		val = SQSET; +		break; +	default: +		return; +	} + +	usbhsp_pipectrl_set(pipe, mask, val); +} + +void usbhs_pipe_clear(struct usbhs_pipe *pipe) +{ +	usbhsp_pipectrl_set(pipe, ACLRM, ACLRM); +	usbhsp_pipectrl_set(pipe, ACLRM, 0); +} + +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 (!usbhs_pipe_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, +		     int (*dma_map_ctrl)(struct usbhs_pkt *pkt, int map)) +{ +	struct usbhs_pipe_info *info = usbhs_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 (usbhs_pipe_type_is(pipe, USB_ENDPOINT_XFER_INT)) +			info->bufnmb_last++; + +		usbhsp_flags_init(pipe); +		pipe->fifo = NULL; +		pipe->mod_private = NULL; +		INIT_LIST_HEAD(&pipe->list); + +		/* pipe force init */ +		usbhs_pipe_clear(pipe); +	} + +	info->dma_map_ctrl = dma_map_ctrl; +} + +struct usbhs_pipe *usbhs_pipe_malloc(struct usbhs_priv *priv, +				     int endpoint_type, +				     int dir_in) +{ +	struct device *dev = usbhs_priv_to_dev(priv); +	struct usbhs_pipe *pipe; +	int is_host = usbhs_mod_is_host(priv); +	int ret; +	u16 pipecfg, pipebuf; + +	pipe = usbhsp_get_pipe(priv, endpoint_type); +	if (!pipe) { +		dev_err(dev, "can't get pipe (%s)\n", +			usbhsp_pipe_name[endpoint_type]); +		return NULL; +	} + +	INIT_LIST_HEAD(&pipe->list); + +	usbhs_pipe_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, is_host, dir_in); +	pipebuf  = usbhsp_setup_pipebuff(pipe); + +	usbhsp_pipe_select(pipe); +	usbhsp_pipe_cfg_set(pipe, 0xFFFF, pipecfg); +	usbhsp_pipe_buf_set(pipe, 0xFFFF, pipebuf); + +	usbhs_pipe_sequence_data0(pipe); + +	dev_dbg(dev, "enable pipe %d : %s (%s)\n", +		usbhs_pipe_number(pipe), +		usbhs_pipe_name(pipe), +		usbhs_pipe_is_dir_in(pipe) ? "in" : "out"); + +	/* +	 * epnum / maxp are still not set to this pipe. +	 * call usbhs_pipe_config_update() after this function !! +	 */ + +	return pipe; +} + +void usbhs_pipe_select_fifo(struct usbhs_pipe *pipe, struct usbhs_fifo *fifo) +{ +	if (pipe->fifo) +		pipe->fifo->pipe = NULL; + +	pipe->fifo = fifo; + +	if (fifo) +		fifo->pipe = 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; + +	INIT_LIST_HEAD(&pipe->list); + +	/* +	 * call usbhs_pipe_config_update() after this function !! +	 */ + +	return pipe; +} + +void usbhs_dcp_control_transfer_done(struct usbhs_pipe *pipe) +{ +	struct usbhs_priv *priv = usbhs_pipe_to_priv(pipe); + +	WARN_ON(!usbhs_pipe_is_dcp(pipe)); + +	usbhs_pipe_enable(pipe); + +	if (!usbhs_mod_is_host(priv)) /* funconly */ +		usbhsp_pipectrl_set(pipe, CCPL, CCPL); +} + +void usbhs_dcp_dir_for_host(struct usbhs_pipe *pipe, int dir_out) +{ +	usbhsp_pipe_cfg_set(pipe, DIR_OUT, +			    dir_out ? DIR_OUT : 0); +} + +/* + *		pipe module function + */ +int usbhs_pipe_probe(struct usbhs_priv *priv) +{ +	struct usbhs_pipe_info *info = usbhs_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; + +		usbhs_pipe_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 = usbhs_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..3e534987983 --- /dev/null +++ b/drivers/usb/renesas_usbhs/pipe.h @@ -0,0 +1,116 @@ +/* + * 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" +#include "fifo.h" + +/* + *	struct + */ +struct usbhs_pipe { +	u32 pipe_type;	/* USB_ENDPOINT_XFER_xxx */ + +	struct usbhs_priv *priv; +	struct usbhs_fifo *fifo; +	struct list_head list; + +	int maxp; + +	u32 flags; +#define USBHS_PIPE_FLAGS_IS_USED		(1 << 0) +#define USBHS_PIPE_FLAGS_IS_DIR_IN		(1 << 1) +#define USBHS_PIPE_FLAGS_IS_DIR_HOST		(1 << 2) + +	struct usbhs_pkt_handle *handler; + +	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 */ + +	int (*dma_map_ctrl)(struct usbhs_pkt *pkt, int map); +}; + +/* + * pipe list + */ +#define __usbhs_for_each_pipe(start, pos, info, i)	\ +	for ((i) = start;						\ +	     ((i) < (info)->size) && ((pos) = (info)->pipe + (i));	\ +	     (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) + +/* + * data + */ +#define usbhs_priv_to_pipeinfo(pr)	(&(pr)->pipe_info) + +/* + * pipe control + */ +char *usbhs_pipe_name(struct usbhs_pipe *pipe); +struct usbhs_pipe +*usbhs_pipe_malloc(struct usbhs_priv *priv, int endpoint_type, int dir_in); +int usbhs_pipe_probe(struct usbhs_priv *priv); +void usbhs_pipe_remove(struct usbhs_priv *priv); +int usbhs_pipe_is_dir_in(struct usbhs_pipe *pipe); +int usbhs_pipe_is_dir_host(struct usbhs_pipe *pipe); +void usbhs_pipe_init(struct usbhs_priv *priv, +		     int (*dma_map_ctrl)(struct usbhs_pkt *pkt, int map)); +int usbhs_pipe_get_maxpacket(struct usbhs_pipe *pipe); +void usbhs_pipe_clear(struct usbhs_pipe *pipe); +int usbhs_pipe_is_accessible(struct usbhs_pipe *pipe); +void usbhs_pipe_enable(struct usbhs_pipe *pipe); +void usbhs_pipe_disable(struct usbhs_pipe *pipe); +void usbhs_pipe_stall(struct usbhs_pipe *pipe); +int usbhs_pipe_is_stall(struct usbhs_pipe *pipe); +void usbhs_pipe_set_trans_count_if_bulk(struct usbhs_pipe *pipe, int len); +void usbhs_pipe_select_fifo(struct usbhs_pipe *pipe, struct usbhs_fifo *fifo); +void usbhs_pipe_config_update(struct usbhs_pipe *pipe, u16 devsel, +			      u16 epnum, u16 maxp); + +#define usbhs_pipe_sequence_data0(pipe)	usbhs_pipe_data_sequence(pipe, 0) +#define usbhs_pipe_sequence_data1(pipe)	usbhs_pipe_data_sequence(pipe, 1) +void usbhs_pipe_data_sequence(struct usbhs_pipe *pipe, int data); + +#define usbhs_pipe_to_priv(p)	((p)->priv) +#define usbhs_pipe_number(p)	(int)((p) - (p)->priv->pipe_info.pipe) +#define usbhs_pipe_is_dcp(p)	((p)->priv->pipe_info.pipe == (p)) +#define usbhs_pipe_to_fifo(p)	((p)->fifo) +#define usbhs_pipe_is_busy(p)	usbhs_pipe_to_fifo(p) + +#define usbhs_pipe_type(p)		((p)->pipe_type) +#define usbhs_pipe_type_is(p, t)	((p)->pipe_type == t) + +/* + * dcp control + */ +struct usbhs_pipe *usbhs_dcp_malloc(struct usbhs_priv *priv); +void usbhs_dcp_control_transfer_done(struct usbhs_pipe *pipe); +void usbhs_dcp_dir_for_host(struct usbhs_pipe *pipe, int dir_out); + +#endif /* RENESAS_USB_PIPE_H */  | 
