Initial commit
This commit is contained in:
commit
169c65d57e
51358 changed files with 23120455 additions and 0 deletions
15
drivers/usb/renesas_usbhs/Kconfig
Normal file
15
drivers/usb/renesas_usbhs/Kconfig
Normal file
|
@ -0,0 +1,15 @@
|
|||
#
|
||||
# Renesas USBHS Controller Drivers
|
||||
#
|
||||
|
||||
config USB_RENESAS_USBHS
|
||||
tristate 'Renesas USBHS controller'
|
||||
depends on USB_GADGET && GENERIC_HARDIRQS
|
||||
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"
|
15
drivers/usb/renesas_usbhs/Makefile
Normal file
15
drivers/usb/renesas_usbhs/Makefile
Normal file
|
@ -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
|
645
drivers/usb/renesas_usbhs/common.c
Normal file
645
drivers/usb/renesas_usbhs/common.c
Normal file
|
@ -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 = pdev->dev.platform_data;
|
||||
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 */
|
||||
dev_set_drvdata(&pdev->dev, priv);
|
||||
|
||||
/*
|
||||
* deviece reset here because
|
||||
* USB device might be used in boot loader.
|
||||
*/
|
||||
usbhs_sys_clock_ctrl(priv, 0);
|
||||
|
||||
/*
|
||||
* platform call
|
||||
*
|
||||
* USB phy setup might depend on CPU/Board.
|
||||
* If platform has its callback functions,
|
||||
* call it here.
|
||||
*/
|
||||
ret = usbhs_platform_call(priv, hardware_init, pdev);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "platform prove failed.\n");
|
||||
goto probe_end_mod_exit;
|
||||
}
|
||||
|
||||
/* reset phy for connection */
|
||||
usbhs_platform_call(priv, phy_reset, pdev);
|
||||
|
||||
/* power control */
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
if (!usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL)) {
|
||||
usbhsc_power_ctrl(priv, 1);
|
||||
usbhs_mod_autonomy_mode(priv);
|
||||
}
|
||||
|
||||
/*
|
||||
* manual call notify_hotplug for cold plug
|
||||
*/
|
||||
ret = usbhsc_drvcllbck_notify_hotplug(pdev);
|
||||
if (ret < 0)
|
||||
goto probe_end_call_remove;
|
||||
|
||||
dev_info(&pdev->dev, "probed\n");
|
||||
|
||||
return ret;
|
||||
|
||||
probe_end_call_remove:
|
||||
usbhs_platform_call(priv, hardware_exit, pdev);
|
||||
probe_end_mod_exit:
|
||||
usbhs_mod_remove(priv);
|
||||
probe_end_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 = pdev->dev.platform_data;
|
||||
struct renesas_usbhs_driver_callback *dfunc = &info->driver_callback;
|
||||
|
||||
dev_dbg(&pdev->dev, "usb remove\n");
|
||||
|
||||
dfunc->notify_hotplug = NULL;
|
||||
|
||||
/* power off */
|
||||
if (!usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL))
|
||||
usbhsc_power_ctrl(priv, 0);
|
||||
|
||||
pm_runtime_disable(&pdev->dev);
|
||||
|
||||
usbhs_platform_call(priv, hardware_exit, pdev);
|
||||
usbhs_mod_remove(priv);
|
||||
usbhs_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>");
|
325
drivers/usb/renesas_usbhs/common.h
Normal file
325
drivers/usb/renesas_usbhs/common.h
Normal file
|
@ -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 */
|
1189
drivers/usb/renesas_usbhs/fifo.c
Normal file
1189
drivers/usb/renesas_usbhs/fifo.c
Normal file
File diff suppressed because it is too large
Load diff
102
drivers/usb/renesas_usbhs/fifo.h
Normal file
102
drivers/usb/renesas_usbhs/fifo.h
Normal file
|
@ -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 */
|
387
drivers/usb/renesas_usbhs/mod.c
Normal file
387
drivers/usb/renesas_usbhs/mod.c
Normal file
|
@ -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);
|
||||
}
|
172
drivers/usb/renesas_usbhs/mod.h
Normal file
172
drivers/usb/renesas_usbhs/mod.h
Normal file
|
@ -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 */
|
1029
drivers/usb/renesas_usbhs/mod_gadget.c
Normal file
1029
drivers/usb/renesas_usbhs/mod_gadget.c
Normal file
File diff suppressed because it is too large
Load diff
1585
drivers/usb/renesas_usbhs/mod_host.c
Normal file
1585
drivers/usb/renesas_usbhs/mod_host.c
Normal file
File diff suppressed because it is too large
Load diff
826
drivers/usb/renesas_usbhs/pipe.c
Normal file
826
drivers/usb/renesas_usbhs/pipe.c
Normal file
|
@ -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);
|
||||
}
|
116
drivers/usb/renesas_usbhs/pipe.h
Normal file
116
drivers/usb/renesas_usbhs/pipe.h
Normal file
|
@ -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, pos = (info)->pipe + i; \
|
||||
i < (info)->size; \
|
||||
i++, pos = (info)->pipe + i)
|
||||
|
||||
#define usbhs_for_each_pipe(pos, priv, i) \
|
||||
__usbhs_for_each_pipe(1, pos, &((priv)->pipe_info), i)
|
||||
|
||||
#define usbhs_for_each_pipe_with_dcp(pos, priv, i) \
|
||||
__usbhs_for_each_pipe(0, pos, &((priv)->pipe_info), i)
|
||||
|
||||
/*
|
||||
* 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 */
|
Loading…
Add table
Add a link
Reference in a new issue