mirror of
https://github.com/torvalds/linux.git
synced 2024-12-22 10:56:40 +00:00
USB: OTG: add Marvell usb OTG driver support
This driver is for ChipIdea USB OTG controller on Marvell Socs. PXA9xx/MMP2/MMP3/MGx all have this USB OTG controller. Signed-off-by: Neil Zhang <zhangwm@marvell.com> Signed-off-by: Felipe Balbi <balbi@ti.com>
This commit is contained in:
parent
5e6c86b017
commit
277164f03f
@ -130,4 +130,16 @@ config FSL_USB2_OTG
|
|||||||
help
|
help
|
||||||
Enable this to support Freescale USB OTG transceiver.
|
Enable this to support Freescale USB OTG transceiver.
|
||||||
|
|
||||||
|
config USB_MV_OTG
|
||||||
|
tristate "Marvell USB OTG support"
|
||||||
|
depends on USB_MV_UDC
|
||||||
|
select USB_OTG
|
||||||
|
select USB_OTG_UTILS
|
||||||
|
help
|
||||||
|
Say Y here if you want to build Marvell USB OTG transciever
|
||||||
|
driver in kernel (including PXA and MMP series). This driver
|
||||||
|
implements role switch between EHCI host driver and gadget driver.
|
||||||
|
|
||||||
|
To compile this driver as a module, choose M here.
|
||||||
|
|
||||||
endif # USB || OTG
|
endif # USB || OTG
|
||||||
|
@ -21,3 +21,4 @@ obj-$(CONFIG_USB_MSM_OTG) += msm_otg.o
|
|||||||
obj-$(CONFIG_AB8500_USB) += ab8500-usb.o
|
obj-$(CONFIG_AB8500_USB) += ab8500-usb.o
|
||||||
fsl_usb2_otg-objs := fsl_otg.o otg_fsm.o
|
fsl_usb2_otg-objs := fsl_otg.o otg_fsm.o
|
||||||
obj-$(CONFIG_FSL_USB2_OTG) += fsl_usb2_otg.o
|
obj-$(CONFIG_FSL_USB2_OTG) += fsl_usb2_otg.o
|
||||||
|
obj-$(CONFIG_USB_MV_OTG) += mv_otg.o
|
||||||
|
957
drivers/usb/otg/mv_otg.c
Normal file
957
drivers/usb/otg/mv_otg.c
Normal file
@ -0,0 +1,957 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2011 Marvell International Ltd. All rights reserved.
|
||||||
|
* Author: Chao Xie <chao.xie@marvell.com>
|
||||||
|
* Neil Zhang <zhangwm@marvell.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/uaccess.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/proc_fs.h>
|
||||||
|
#include <linux/clk.h>
|
||||||
|
#include <linux/workqueue.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
|
||||||
|
#include <linux/usb.h>
|
||||||
|
#include <linux/usb/ch9.h>
|
||||||
|
#include <linux/usb/otg.h>
|
||||||
|
#include <linux/usb/gadget.h>
|
||||||
|
#include <linux/usb/hcd.h>
|
||||||
|
#include <linux/platform_data/mv_usb.h>
|
||||||
|
|
||||||
|
#include "mv_otg.h"
|
||||||
|
|
||||||
|
#define DRIVER_DESC "Marvell USB OTG transceiver driver"
|
||||||
|
#define DRIVER_VERSION "Jan 20, 2010"
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION(DRIVER_DESC);
|
||||||
|
MODULE_VERSION(DRIVER_VERSION);
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
|
||||||
|
static const char driver_name[] = "mv-otg";
|
||||||
|
|
||||||
|
static char *state_string[] = {
|
||||||
|
"undefined",
|
||||||
|
"b_idle",
|
||||||
|
"b_srp_init",
|
||||||
|
"b_peripheral",
|
||||||
|
"b_wait_acon",
|
||||||
|
"b_host",
|
||||||
|
"a_idle",
|
||||||
|
"a_wait_vrise",
|
||||||
|
"a_wait_bcon",
|
||||||
|
"a_host",
|
||||||
|
"a_suspend",
|
||||||
|
"a_peripheral",
|
||||||
|
"a_wait_vfall",
|
||||||
|
"a_vbus_err"
|
||||||
|
};
|
||||||
|
|
||||||
|
static int mv_otg_set_vbus(struct otg_transceiver *otg, bool on)
|
||||||
|
{
|
||||||
|
struct mv_otg *mvotg = container_of(otg, struct mv_otg, otg);
|
||||||
|
if (mvotg->pdata->set_vbus == NULL)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
return mvotg->pdata->set_vbus(on);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mv_otg_set_host(struct otg_transceiver *otg,
|
||||||
|
struct usb_bus *host)
|
||||||
|
{
|
||||||
|
otg->host = host;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mv_otg_set_peripheral(struct otg_transceiver *otg,
|
||||||
|
struct usb_gadget *gadget)
|
||||||
|
{
|
||||||
|
otg->gadget = gadget;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mv_otg_run_state_machine(struct mv_otg *mvotg,
|
||||||
|
unsigned long delay)
|
||||||
|
{
|
||||||
|
dev_dbg(&mvotg->pdev->dev, "transceiver is updated\n");
|
||||||
|
if (!mvotg->qwork)
|
||||||
|
return;
|
||||||
|
|
||||||
|
queue_delayed_work(mvotg->qwork, &mvotg->work, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mv_otg_timer_await_bcon(unsigned long data)
|
||||||
|
{
|
||||||
|
struct mv_otg *mvotg = (struct mv_otg *) data;
|
||||||
|
|
||||||
|
mvotg->otg_ctrl.a_wait_bcon_timeout = 1;
|
||||||
|
|
||||||
|
dev_info(&mvotg->pdev->dev, "B Device No Response!\n");
|
||||||
|
|
||||||
|
if (spin_trylock(&mvotg->wq_lock)) {
|
||||||
|
mv_otg_run_state_machine(mvotg, 0);
|
||||||
|
spin_unlock(&mvotg->wq_lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mv_otg_cancel_timer(struct mv_otg *mvotg, unsigned int id)
|
||||||
|
{
|
||||||
|
struct timer_list *timer;
|
||||||
|
|
||||||
|
if (id >= OTG_TIMER_NUM)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
timer = &mvotg->otg_ctrl.timer[id];
|
||||||
|
|
||||||
|
if (timer_pending(timer))
|
||||||
|
del_timer(timer);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mv_otg_set_timer(struct mv_otg *mvotg, unsigned int id,
|
||||||
|
unsigned long interval,
|
||||||
|
void (*callback) (unsigned long))
|
||||||
|
{
|
||||||
|
struct timer_list *timer;
|
||||||
|
|
||||||
|
if (id >= OTG_TIMER_NUM)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
timer = &mvotg->otg_ctrl.timer[id];
|
||||||
|
if (timer_pending(timer)) {
|
||||||
|
dev_err(&mvotg->pdev->dev, "Timer%d is already running\n", id);
|
||||||
|
return -EBUSY;
|
||||||
|
}
|
||||||
|
|
||||||
|
init_timer(timer);
|
||||||
|
timer->data = (unsigned long) mvotg;
|
||||||
|
timer->function = callback;
|
||||||
|
timer->expires = jiffies + interval;
|
||||||
|
add_timer(timer);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mv_otg_reset(struct mv_otg *mvotg)
|
||||||
|
{
|
||||||
|
unsigned int loops;
|
||||||
|
u32 tmp;
|
||||||
|
|
||||||
|
/* Stop the controller */
|
||||||
|
tmp = readl(&mvotg->op_regs->usbcmd);
|
||||||
|
tmp &= ~USBCMD_RUN_STOP;
|
||||||
|
writel(tmp, &mvotg->op_regs->usbcmd);
|
||||||
|
|
||||||
|
/* Reset the controller to get default values */
|
||||||
|
writel(USBCMD_CTRL_RESET, &mvotg->op_regs->usbcmd);
|
||||||
|
|
||||||
|
loops = 500;
|
||||||
|
while (readl(&mvotg->op_regs->usbcmd) & USBCMD_CTRL_RESET) {
|
||||||
|
if (loops == 0) {
|
||||||
|
dev_err(&mvotg->pdev->dev,
|
||||||
|
"Wait for RESET completed TIMEOUT\n");
|
||||||
|
return -ETIMEDOUT;
|
||||||
|
}
|
||||||
|
loops--;
|
||||||
|
udelay(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
writel(0x0, &mvotg->op_regs->usbintr);
|
||||||
|
tmp = readl(&mvotg->op_regs->usbsts);
|
||||||
|
writel(tmp, &mvotg->op_regs->usbsts);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mv_otg_init_irq(struct mv_otg *mvotg)
|
||||||
|
{
|
||||||
|
u32 otgsc;
|
||||||
|
|
||||||
|
mvotg->irq_en = OTGSC_INTR_A_SESSION_VALID
|
||||||
|
| OTGSC_INTR_A_VBUS_VALID;
|
||||||
|
mvotg->irq_status = OTGSC_INTSTS_A_SESSION_VALID
|
||||||
|
| OTGSC_INTSTS_A_VBUS_VALID;
|
||||||
|
|
||||||
|
if (mvotg->pdata->vbus == NULL) {
|
||||||
|
mvotg->irq_en |= OTGSC_INTR_B_SESSION_VALID
|
||||||
|
| OTGSC_INTR_B_SESSION_END;
|
||||||
|
mvotg->irq_status |= OTGSC_INTSTS_B_SESSION_VALID
|
||||||
|
| OTGSC_INTSTS_B_SESSION_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mvotg->pdata->id == NULL) {
|
||||||
|
mvotg->irq_en |= OTGSC_INTR_USB_ID;
|
||||||
|
mvotg->irq_status |= OTGSC_INTSTS_USB_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
otgsc = readl(&mvotg->op_regs->otgsc);
|
||||||
|
otgsc |= mvotg->irq_en;
|
||||||
|
writel(otgsc, &mvotg->op_regs->otgsc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mv_otg_start_host(struct mv_otg *mvotg, int on)
|
||||||
|
{
|
||||||
|
struct otg_transceiver *otg = &mvotg->otg;
|
||||||
|
struct usb_hcd *hcd;
|
||||||
|
|
||||||
|
if (!otg->host)
|
||||||
|
return;
|
||||||
|
|
||||||
|
dev_info(&mvotg->pdev->dev, "%s host\n", on ? "start" : "stop");
|
||||||
|
|
||||||
|
hcd = bus_to_hcd(otg->host);
|
||||||
|
|
||||||
|
if (on)
|
||||||
|
usb_add_hcd(hcd, hcd->irq, IRQF_SHARED);
|
||||||
|
else
|
||||||
|
usb_remove_hcd(hcd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mv_otg_start_periphrals(struct mv_otg *mvotg, int on)
|
||||||
|
{
|
||||||
|
struct otg_transceiver *otg = &mvotg->otg;
|
||||||
|
|
||||||
|
if (!otg->gadget)
|
||||||
|
return;
|
||||||
|
|
||||||
|
dev_info(otg->dev, "gadget %s\n", on ? "on" : "off");
|
||||||
|
|
||||||
|
if (on)
|
||||||
|
usb_gadget_vbus_connect(otg->gadget);
|
||||||
|
else
|
||||||
|
usb_gadget_vbus_disconnect(otg->gadget);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void otg_clock_enable(struct mv_otg *mvotg)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
for (i = 0; i < mvotg->clknum; i++)
|
||||||
|
clk_enable(mvotg->clk[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void otg_clock_disable(struct mv_otg *mvotg)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
for (i = 0; i < mvotg->clknum; i++)
|
||||||
|
clk_disable(mvotg->clk[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mv_otg_enable_internal(struct mv_otg *mvotg)
|
||||||
|
{
|
||||||
|
int retval = 0;
|
||||||
|
|
||||||
|
if (mvotg->active)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
dev_dbg(&mvotg->pdev->dev, "otg enabled\n");
|
||||||
|
|
||||||
|
otg_clock_enable(mvotg);
|
||||||
|
if (mvotg->pdata->phy_init) {
|
||||||
|
retval = mvotg->pdata->phy_init(mvotg->phy_regs);
|
||||||
|
if (retval) {
|
||||||
|
dev_err(&mvotg->pdev->dev,
|
||||||
|
"init phy error %d\n", retval);
|
||||||
|
otg_clock_disable(mvotg);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mvotg->active = 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mv_otg_enable(struct mv_otg *mvotg)
|
||||||
|
{
|
||||||
|
if (mvotg->clock_gating)
|
||||||
|
return mv_otg_enable_internal(mvotg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mv_otg_disable_internal(struct mv_otg *mvotg)
|
||||||
|
{
|
||||||
|
if (mvotg->active) {
|
||||||
|
dev_dbg(&mvotg->pdev->dev, "otg disabled\n");
|
||||||
|
if (mvotg->pdata->phy_deinit)
|
||||||
|
mvotg->pdata->phy_deinit(mvotg->phy_regs);
|
||||||
|
otg_clock_disable(mvotg);
|
||||||
|
mvotg->active = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mv_otg_disable(struct mv_otg *mvotg)
|
||||||
|
{
|
||||||
|
if (mvotg->clock_gating)
|
||||||
|
mv_otg_disable_internal(mvotg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mv_otg_update_inputs(struct mv_otg *mvotg)
|
||||||
|
{
|
||||||
|
struct mv_otg_ctrl *otg_ctrl = &mvotg->otg_ctrl;
|
||||||
|
u32 otgsc;
|
||||||
|
|
||||||
|
otgsc = readl(&mvotg->op_regs->otgsc);
|
||||||
|
|
||||||
|
if (mvotg->pdata->vbus) {
|
||||||
|
if (mvotg->pdata->vbus->poll() == VBUS_HIGH) {
|
||||||
|
otg_ctrl->b_sess_vld = 1;
|
||||||
|
otg_ctrl->b_sess_end = 0;
|
||||||
|
} else {
|
||||||
|
otg_ctrl->b_sess_vld = 0;
|
||||||
|
otg_ctrl->b_sess_end = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
otg_ctrl->b_sess_vld = !!(otgsc & OTGSC_STS_B_SESSION_VALID);
|
||||||
|
otg_ctrl->b_sess_end = !!(otgsc & OTGSC_STS_B_SESSION_END);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mvotg->pdata->id)
|
||||||
|
otg_ctrl->id = !!mvotg->pdata->id->poll();
|
||||||
|
else
|
||||||
|
otg_ctrl->id = !!(otgsc & OTGSC_STS_USB_ID);
|
||||||
|
|
||||||
|
if (mvotg->pdata->otg_force_a_bus_req && !otg_ctrl->id)
|
||||||
|
otg_ctrl->a_bus_req = 1;
|
||||||
|
|
||||||
|
otg_ctrl->a_sess_vld = !!(otgsc & OTGSC_STS_A_SESSION_VALID);
|
||||||
|
otg_ctrl->a_vbus_vld = !!(otgsc & OTGSC_STS_A_VBUS_VALID);
|
||||||
|
|
||||||
|
dev_dbg(&mvotg->pdev->dev, "%s: ", __func__);
|
||||||
|
dev_dbg(&mvotg->pdev->dev, "id %d\n", otg_ctrl->id);
|
||||||
|
dev_dbg(&mvotg->pdev->dev, "b_sess_vld %d\n", otg_ctrl->b_sess_vld);
|
||||||
|
dev_dbg(&mvotg->pdev->dev, "b_sess_end %d\n", otg_ctrl->b_sess_end);
|
||||||
|
dev_dbg(&mvotg->pdev->dev, "a_vbus_vld %d\n", otg_ctrl->a_vbus_vld);
|
||||||
|
dev_dbg(&mvotg->pdev->dev, "a_sess_vld %d\n", otg_ctrl->a_sess_vld);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mv_otg_update_state(struct mv_otg *mvotg)
|
||||||
|
{
|
||||||
|
struct mv_otg_ctrl *otg_ctrl = &mvotg->otg_ctrl;
|
||||||
|
struct otg_transceiver *otg = &mvotg->otg;
|
||||||
|
int old_state = otg->state;
|
||||||
|
|
||||||
|
switch (old_state) {
|
||||||
|
case OTG_STATE_UNDEFINED:
|
||||||
|
otg->state = OTG_STATE_B_IDLE;
|
||||||
|
/* FALL THROUGH */
|
||||||
|
case OTG_STATE_B_IDLE:
|
||||||
|
if (otg_ctrl->id == 0)
|
||||||
|
otg->state = OTG_STATE_A_IDLE;
|
||||||
|
else if (otg_ctrl->b_sess_vld)
|
||||||
|
otg->state = OTG_STATE_B_PERIPHERAL;
|
||||||
|
break;
|
||||||
|
case OTG_STATE_B_PERIPHERAL:
|
||||||
|
if (!otg_ctrl->b_sess_vld || otg_ctrl->id == 0)
|
||||||
|
otg->state = OTG_STATE_B_IDLE;
|
||||||
|
break;
|
||||||
|
case OTG_STATE_A_IDLE:
|
||||||
|
if (otg_ctrl->id)
|
||||||
|
otg->state = OTG_STATE_B_IDLE;
|
||||||
|
else if (!(otg_ctrl->a_bus_drop) &&
|
||||||
|
(otg_ctrl->a_bus_req || otg_ctrl->a_srp_det))
|
||||||
|
otg->state = OTG_STATE_A_WAIT_VRISE;
|
||||||
|
break;
|
||||||
|
case OTG_STATE_A_WAIT_VRISE:
|
||||||
|
if (otg_ctrl->a_vbus_vld)
|
||||||
|
otg->state = OTG_STATE_A_WAIT_BCON;
|
||||||
|
break;
|
||||||
|
case OTG_STATE_A_WAIT_BCON:
|
||||||
|
if (otg_ctrl->id || otg_ctrl->a_bus_drop
|
||||||
|
|| otg_ctrl->a_wait_bcon_timeout) {
|
||||||
|
mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER);
|
||||||
|
mvotg->otg_ctrl.a_wait_bcon_timeout = 0;
|
||||||
|
otg->state = OTG_STATE_A_WAIT_VFALL;
|
||||||
|
otg_ctrl->a_bus_req = 0;
|
||||||
|
} else if (!otg_ctrl->a_vbus_vld) {
|
||||||
|
mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER);
|
||||||
|
mvotg->otg_ctrl.a_wait_bcon_timeout = 0;
|
||||||
|
otg->state = OTG_STATE_A_VBUS_ERR;
|
||||||
|
} else if (otg_ctrl->b_conn) {
|
||||||
|
mv_otg_cancel_timer(mvotg, A_WAIT_BCON_TIMER);
|
||||||
|
mvotg->otg_ctrl.a_wait_bcon_timeout = 0;
|
||||||
|
otg->state = OTG_STATE_A_HOST;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OTG_STATE_A_HOST:
|
||||||
|
if (otg_ctrl->id || !otg_ctrl->b_conn
|
||||||
|
|| otg_ctrl->a_bus_drop)
|
||||||
|
otg->state = OTG_STATE_A_WAIT_BCON;
|
||||||
|
else if (!otg_ctrl->a_vbus_vld)
|
||||||
|
otg->state = OTG_STATE_A_VBUS_ERR;
|
||||||
|
break;
|
||||||
|
case OTG_STATE_A_WAIT_VFALL:
|
||||||
|
if (otg_ctrl->id
|
||||||
|
|| (!otg_ctrl->b_conn && otg_ctrl->a_sess_vld)
|
||||||
|
|| otg_ctrl->a_bus_req)
|
||||||
|
otg->state = OTG_STATE_A_IDLE;
|
||||||
|
break;
|
||||||
|
case OTG_STATE_A_VBUS_ERR:
|
||||||
|
if (otg_ctrl->id || otg_ctrl->a_clr_err
|
||||||
|
|| otg_ctrl->a_bus_drop) {
|
||||||
|
otg_ctrl->a_clr_err = 0;
|
||||||
|
otg->state = OTG_STATE_A_WAIT_VFALL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mv_otg_work(struct work_struct *work)
|
||||||
|
{
|
||||||
|
struct mv_otg *mvotg;
|
||||||
|
struct otg_transceiver *otg;
|
||||||
|
int old_state;
|
||||||
|
|
||||||
|
mvotg = container_of((struct delayed_work *)work, struct mv_otg, work);
|
||||||
|
|
||||||
|
run:
|
||||||
|
/* work queue is single thread, or we need spin_lock to protect */
|
||||||
|
otg = &mvotg->otg;
|
||||||
|
old_state = otg->state;
|
||||||
|
|
||||||
|
if (!mvotg->active)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mv_otg_update_inputs(mvotg);
|
||||||
|
mv_otg_update_state(mvotg);
|
||||||
|
|
||||||
|
if (old_state != otg->state) {
|
||||||
|
dev_info(&mvotg->pdev->dev, "change from state %s to %s\n",
|
||||||
|
state_string[old_state],
|
||||||
|
state_string[otg->state]);
|
||||||
|
|
||||||
|
switch (otg->state) {
|
||||||
|
case OTG_STATE_B_IDLE:
|
||||||
|
mvotg->otg.default_a = 0;
|
||||||
|
if (old_state == OTG_STATE_B_PERIPHERAL)
|
||||||
|
mv_otg_start_periphrals(mvotg, 0);
|
||||||
|
mv_otg_reset(mvotg);
|
||||||
|
mv_otg_disable(mvotg);
|
||||||
|
break;
|
||||||
|
case OTG_STATE_B_PERIPHERAL:
|
||||||
|
mv_otg_enable(mvotg);
|
||||||
|
mv_otg_start_periphrals(mvotg, 1);
|
||||||
|
break;
|
||||||
|
case OTG_STATE_A_IDLE:
|
||||||
|
mvotg->otg.default_a = 1;
|
||||||
|
mv_otg_enable(mvotg);
|
||||||
|
if (old_state == OTG_STATE_A_WAIT_VFALL)
|
||||||
|
mv_otg_start_host(mvotg, 0);
|
||||||
|
mv_otg_reset(mvotg);
|
||||||
|
break;
|
||||||
|
case OTG_STATE_A_WAIT_VRISE:
|
||||||
|
mv_otg_set_vbus(&mvotg->otg, 1);
|
||||||
|
break;
|
||||||
|
case OTG_STATE_A_WAIT_BCON:
|
||||||
|
if (old_state != OTG_STATE_A_HOST)
|
||||||
|
mv_otg_start_host(mvotg, 1);
|
||||||
|
mv_otg_set_timer(mvotg, A_WAIT_BCON_TIMER,
|
||||||
|
T_A_WAIT_BCON,
|
||||||
|
mv_otg_timer_await_bcon);
|
||||||
|
/*
|
||||||
|
* Now, we directly enter A_HOST. So set b_conn = 1
|
||||||
|
* here. In fact, it need host driver to notify us.
|
||||||
|
*/
|
||||||
|
mvotg->otg_ctrl.b_conn = 1;
|
||||||
|
break;
|
||||||
|
case OTG_STATE_A_HOST:
|
||||||
|
break;
|
||||||
|
case OTG_STATE_A_WAIT_VFALL:
|
||||||
|
/*
|
||||||
|
* Now, we has exited A_HOST. So set b_conn = 0
|
||||||
|
* here. In fact, it need host driver to notify us.
|
||||||
|
*/
|
||||||
|
mvotg->otg_ctrl.b_conn = 0;
|
||||||
|
mv_otg_set_vbus(&mvotg->otg, 0);
|
||||||
|
break;
|
||||||
|
case OTG_STATE_A_VBUS_ERR:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
goto run;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static irqreturn_t mv_otg_irq(int irq, void *dev)
|
||||||
|
{
|
||||||
|
struct mv_otg *mvotg = dev;
|
||||||
|
u32 otgsc;
|
||||||
|
|
||||||
|
otgsc = readl(&mvotg->op_regs->otgsc);
|
||||||
|
writel(otgsc, &mvotg->op_regs->otgsc);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* if we have vbus, then the vbus detection for B-device
|
||||||
|
* will be done by mv_otg_inputs_irq().
|
||||||
|
*/
|
||||||
|
if (mvotg->pdata->vbus)
|
||||||
|
if ((otgsc & OTGSC_STS_USB_ID) &&
|
||||||
|
!(otgsc & OTGSC_INTSTS_USB_ID))
|
||||||
|
return IRQ_NONE;
|
||||||
|
|
||||||
|
if ((otgsc & mvotg->irq_status) == 0)
|
||||||
|
return IRQ_NONE;
|
||||||
|
|
||||||
|
mv_otg_run_state_machine(mvotg, 0);
|
||||||
|
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static irqreturn_t mv_otg_inputs_irq(int irq, void *dev)
|
||||||
|
{
|
||||||
|
struct mv_otg *mvotg = dev;
|
||||||
|
|
||||||
|
/* The clock may disabled at this time */
|
||||||
|
if (!mvotg->active) {
|
||||||
|
mv_otg_enable(mvotg);
|
||||||
|
mv_otg_init_irq(mvotg);
|
||||||
|
}
|
||||||
|
|
||||||
|
mv_otg_run_state_machine(mvotg, 0);
|
||||||
|
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
get_a_bus_req(struct device *dev, struct device_attribute *attr, char *buf)
|
||||||
|
{
|
||||||
|
struct mv_otg *mvotg = dev_get_drvdata(dev);
|
||||||
|
return scnprintf(buf, PAGE_SIZE, "%d\n",
|
||||||
|
mvotg->otg_ctrl.a_bus_req);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
set_a_bus_req(struct device *dev, struct device_attribute *attr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct mv_otg *mvotg = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
if (count > 2)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* We will use this interface to change to A device */
|
||||||
|
if (mvotg->otg.state != OTG_STATE_B_IDLE
|
||||||
|
&& mvotg->otg.state != OTG_STATE_A_IDLE)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* The clock may disabled and we need to set irq for ID detected */
|
||||||
|
mv_otg_enable(mvotg);
|
||||||
|
mv_otg_init_irq(mvotg);
|
||||||
|
|
||||||
|
if (buf[0] == '1') {
|
||||||
|
mvotg->otg_ctrl.a_bus_req = 1;
|
||||||
|
mvotg->otg_ctrl.a_bus_drop = 0;
|
||||||
|
dev_dbg(&mvotg->pdev->dev,
|
||||||
|
"User request: a_bus_req = 1\n");
|
||||||
|
|
||||||
|
if (spin_trylock(&mvotg->wq_lock)) {
|
||||||
|
mv_otg_run_state_machine(mvotg, 0);
|
||||||
|
spin_unlock(&mvotg->wq_lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEVICE_ATTR(a_bus_req, S_IRUGO | S_IWUSR, get_a_bus_req,
|
||||||
|
set_a_bus_req);
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
set_a_clr_err(struct device *dev, struct device_attribute *attr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct mv_otg *mvotg = dev_get_drvdata(dev);
|
||||||
|
if (!mvotg->otg.default_a)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (count > 2)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (buf[0] == '1') {
|
||||||
|
mvotg->otg_ctrl.a_clr_err = 1;
|
||||||
|
dev_dbg(&mvotg->pdev->dev,
|
||||||
|
"User request: a_clr_err = 1\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spin_trylock(&mvotg->wq_lock)) {
|
||||||
|
mv_otg_run_state_machine(mvotg, 0);
|
||||||
|
spin_unlock(&mvotg->wq_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEVICE_ATTR(a_clr_err, S_IWUSR, NULL, set_a_clr_err);
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
get_a_bus_drop(struct device *dev, struct device_attribute *attr,
|
||||||
|
char *buf)
|
||||||
|
{
|
||||||
|
struct mv_otg *mvotg = dev_get_drvdata(dev);
|
||||||
|
return scnprintf(buf, PAGE_SIZE, "%d\n",
|
||||||
|
mvotg->otg_ctrl.a_bus_drop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t
|
||||||
|
set_a_bus_drop(struct device *dev, struct device_attribute *attr,
|
||||||
|
const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
struct mv_otg *mvotg = dev_get_drvdata(dev);
|
||||||
|
if (!mvotg->otg.default_a)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (count > 2)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (buf[0] == '0') {
|
||||||
|
mvotg->otg_ctrl.a_bus_drop = 0;
|
||||||
|
dev_dbg(&mvotg->pdev->dev,
|
||||||
|
"User request: a_bus_drop = 0\n");
|
||||||
|
} else if (buf[0] == '1') {
|
||||||
|
mvotg->otg_ctrl.a_bus_drop = 1;
|
||||||
|
mvotg->otg_ctrl.a_bus_req = 0;
|
||||||
|
dev_dbg(&mvotg->pdev->dev,
|
||||||
|
"User request: a_bus_drop = 1\n");
|
||||||
|
dev_dbg(&mvotg->pdev->dev,
|
||||||
|
"User request: and a_bus_req = 0\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spin_trylock(&mvotg->wq_lock)) {
|
||||||
|
mv_otg_run_state_machine(mvotg, 0);
|
||||||
|
spin_unlock(&mvotg->wq_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEVICE_ATTR(a_bus_drop, S_IRUGO | S_IWUSR,
|
||||||
|
get_a_bus_drop, set_a_bus_drop);
|
||||||
|
|
||||||
|
static struct attribute *inputs_attrs[] = {
|
||||||
|
&dev_attr_a_bus_req.attr,
|
||||||
|
&dev_attr_a_clr_err.attr,
|
||||||
|
&dev_attr_a_bus_drop.attr,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct attribute_group inputs_attr_group = {
|
||||||
|
.name = "inputs",
|
||||||
|
.attrs = inputs_attrs,
|
||||||
|
};
|
||||||
|
|
||||||
|
int mv_otg_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct mv_otg *mvotg = platform_get_drvdata(pdev);
|
||||||
|
int clk_i;
|
||||||
|
|
||||||
|
sysfs_remove_group(&mvotg->pdev->dev.kobj, &inputs_attr_group);
|
||||||
|
|
||||||
|
if (mvotg->irq)
|
||||||
|
free_irq(mvotg->irq, mvotg);
|
||||||
|
|
||||||
|
if (mvotg->pdata->vbus)
|
||||||
|
free_irq(mvotg->pdata->vbus->irq, mvotg);
|
||||||
|
if (mvotg->pdata->id)
|
||||||
|
free_irq(mvotg->pdata->id->irq, mvotg);
|
||||||
|
|
||||||
|
if (mvotg->qwork) {
|
||||||
|
flush_workqueue(mvotg->qwork);
|
||||||
|
destroy_workqueue(mvotg->qwork);
|
||||||
|
}
|
||||||
|
|
||||||
|
mv_otg_disable(mvotg);
|
||||||
|
|
||||||
|
if (mvotg->cap_regs)
|
||||||
|
iounmap(mvotg->cap_regs);
|
||||||
|
|
||||||
|
if (mvotg->phy_regs)
|
||||||
|
iounmap(mvotg->phy_regs);
|
||||||
|
|
||||||
|
for (clk_i = 0; clk_i <= mvotg->clknum; clk_i++)
|
||||||
|
clk_put(mvotg->clk[clk_i]);
|
||||||
|
|
||||||
|
otg_set_transceiver(NULL);
|
||||||
|
platform_set_drvdata(pdev, NULL);
|
||||||
|
|
||||||
|
kfree(mvotg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mv_otg_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct mv_usb_platform_data *pdata = pdev->dev.platform_data;
|
||||||
|
struct mv_otg *mvotg;
|
||||||
|
struct resource *r;
|
||||||
|
int retval = 0, clk_i, i;
|
||||||
|
size_t size;
|
||||||
|
|
||||||
|
if (pdata == NULL) {
|
||||||
|
dev_err(&pdev->dev, "failed to get platform data\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
size = sizeof(*mvotg) + sizeof(struct clk *) * pdata->clknum;
|
||||||
|
mvotg = kzalloc(size, GFP_KERNEL);
|
||||||
|
if (!mvotg) {
|
||||||
|
dev_err(&pdev->dev, "failed to allocate memory!\n");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, mvotg);
|
||||||
|
|
||||||
|
mvotg->pdev = pdev;
|
||||||
|
mvotg->pdata = pdata;
|
||||||
|
|
||||||
|
mvotg->clknum = pdata->clknum;
|
||||||
|
for (clk_i = 0; clk_i < mvotg->clknum; clk_i++) {
|
||||||
|
mvotg->clk[clk_i] = clk_get(&pdev->dev, pdata->clkname[clk_i]);
|
||||||
|
if (IS_ERR(mvotg->clk[clk_i])) {
|
||||||
|
retval = PTR_ERR(mvotg->clk[clk_i]);
|
||||||
|
goto err_put_clk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mvotg->qwork = create_singlethread_workqueue("mv_otg_queue");
|
||||||
|
if (!mvotg->qwork) {
|
||||||
|
dev_dbg(&pdev->dev, "cannot create workqueue for OTG\n");
|
||||||
|
retval = -ENOMEM;
|
||||||
|
goto err_put_clk;
|
||||||
|
}
|
||||||
|
|
||||||
|
INIT_DELAYED_WORK(&mvotg->work, mv_otg_work);
|
||||||
|
|
||||||
|
/* OTG common part */
|
||||||
|
mvotg->pdev = pdev;
|
||||||
|
mvotg->otg.dev = &pdev->dev;
|
||||||
|
mvotg->otg.label = driver_name;
|
||||||
|
mvotg->otg.set_host = mv_otg_set_host;
|
||||||
|
mvotg->otg.set_peripheral = mv_otg_set_peripheral;
|
||||||
|
mvotg->otg.set_vbus = mv_otg_set_vbus;
|
||||||
|
mvotg->otg.state = OTG_STATE_UNDEFINED;
|
||||||
|
|
||||||
|
for (i = 0; i < OTG_TIMER_NUM; i++)
|
||||||
|
init_timer(&mvotg->otg_ctrl.timer[i]);
|
||||||
|
|
||||||
|
r = platform_get_resource_byname(mvotg->pdev,
|
||||||
|
IORESOURCE_MEM, "phyregs");
|
||||||
|
if (r == NULL) {
|
||||||
|
dev_err(&pdev->dev, "no phy I/O memory resource defined\n");
|
||||||
|
retval = -ENODEV;
|
||||||
|
goto err_destroy_workqueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mvotg->phy_regs = ioremap(r->start, resource_size(r));
|
||||||
|
if (mvotg->phy_regs == NULL) {
|
||||||
|
dev_err(&pdev->dev, "failed to map phy I/O memory\n");
|
||||||
|
retval = -EFAULT;
|
||||||
|
goto err_destroy_workqueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = platform_get_resource_byname(mvotg->pdev,
|
||||||
|
IORESOURCE_MEM, "capregs");
|
||||||
|
if (r == NULL) {
|
||||||
|
dev_err(&pdev->dev, "no I/O memory resource defined\n");
|
||||||
|
retval = -ENODEV;
|
||||||
|
goto err_unmap_phyreg;
|
||||||
|
}
|
||||||
|
|
||||||
|
mvotg->cap_regs = ioremap(r->start, resource_size(r));
|
||||||
|
if (mvotg->cap_regs == NULL) {
|
||||||
|
dev_err(&pdev->dev, "failed to map I/O memory\n");
|
||||||
|
retval = -EFAULT;
|
||||||
|
goto err_unmap_phyreg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* we will acces controller register, so enable the udc controller */
|
||||||
|
retval = mv_otg_enable_internal(mvotg);
|
||||||
|
if (retval) {
|
||||||
|
dev_err(&pdev->dev, "mv otg enable error %d\n", retval);
|
||||||
|
goto err_unmap_capreg;
|
||||||
|
}
|
||||||
|
|
||||||
|
mvotg->op_regs =
|
||||||
|
(struct mv_otg_regs __iomem *) ((unsigned long) mvotg->cap_regs
|
||||||
|
+ (readl(mvotg->cap_regs) & CAPLENGTH_MASK));
|
||||||
|
|
||||||
|
if (pdata->id) {
|
||||||
|
retval = request_threaded_irq(pdata->id->irq, NULL,
|
||||||
|
mv_otg_inputs_irq,
|
||||||
|
IRQF_ONESHOT, "id", mvotg);
|
||||||
|
if (retval) {
|
||||||
|
dev_info(&pdev->dev,
|
||||||
|
"Failed to request irq for ID\n");
|
||||||
|
pdata->id = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pdata->vbus) {
|
||||||
|
mvotg->clock_gating = 1;
|
||||||
|
retval = request_threaded_irq(pdata->vbus->irq, NULL,
|
||||||
|
mv_otg_inputs_irq,
|
||||||
|
IRQF_ONESHOT, "vbus", mvotg);
|
||||||
|
if (retval) {
|
||||||
|
dev_info(&pdev->dev,
|
||||||
|
"Failed to request irq for VBUS, "
|
||||||
|
"disable clock gating\n");
|
||||||
|
mvotg->clock_gating = 0;
|
||||||
|
pdata->vbus = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pdata->disable_otg_clock_gating)
|
||||||
|
mvotg->clock_gating = 0;
|
||||||
|
|
||||||
|
mv_otg_reset(mvotg);
|
||||||
|
mv_otg_init_irq(mvotg);
|
||||||
|
|
||||||
|
r = platform_get_resource(mvotg->pdev, IORESOURCE_IRQ, 0);
|
||||||
|
if (r == NULL) {
|
||||||
|
dev_err(&pdev->dev, "no IRQ resource defined\n");
|
||||||
|
retval = -ENODEV;
|
||||||
|
goto err_disable_clk;
|
||||||
|
}
|
||||||
|
|
||||||
|
mvotg->irq = r->start;
|
||||||
|
if (request_irq(mvotg->irq, mv_otg_irq, IRQF_SHARED,
|
||||||
|
driver_name, mvotg)) {
|
||||||
|
dev_err(&pdev->dev, "Request irq %d for OTG failed\n",
|
||||||
|
mvotg->irq);
|
||||||
|
mvotg->irq = 0;
|
||||||
|
retval = -ENODEV;
|
||||||
|
goto err_disable_clk;
|
||||||
|
}
|
||||||
|
|
||||||
|
retval = otg_set_transceiver(&mvotg->otg);
|
||||||
|
if (retval < 0) {
|
||||||
|
dev_err(&pdev->dev, "can't register transceiver, %d\n",
|
||||||
|
retval);
|
||||||
|
goto err_free_irq;
|
||||||
|
}
|
||||||
|
|
||||||
|
retval = sysfs_create_group(&pdev->dev.kobj, &inputs_attr_group);
|
||||||
|
if (retval < 0) {
|
||||||
|
dev_dbg(&pdev->dev,
|
||||||
|
"Can't register sysfs attr group: %d\n", retval);
|
||||||
|
goto err_set_transceiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_lock_init(&mvotg->wq_lock);
|
||||||
|
if (spin_trylock(&mvotg->wq_lock)) {
|
||||||
|
mv_otg_run_state_machine(mvotg, 2 * HZ);
|
||||||
|
spin_unlock(&mvotg->wq_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_info(&pdev->dev,
|
||||||
|
"successful probe OTG device %s clock gating.\n",
|
||||||
|
mvotg->clock_gating ? "with" : "without");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_set_transceiver:
|
||||||
|
otg_set_transceiver(NULL);
|
||||||
|
err_free_irq:
|
||||||
|
free_irq(mvotg->irq, mvotg);
|
||||||
|
err_disable_clk:
|
||||||
|
if (pdata->vbus)
|
||||||
|
free_irq(pdata->vbus->irq, mvotg);
|
||||||
|
if (pdata->id)
|
||||||
|
free_irq(pdata->id->irq, mvotg);
|
||||||
|
mv_otg_disable_internal(mvotg);
|
||||||
|
err_unmap_capreg:
|
||||||
|
iounmap(mvotg->cap_regs);
|
||||||
|
err_unmap_phyreg:
|
||||||
|
iounmap(mvotg->phy_regs);
|
||||||
|
err_destroy_workqueue:
|
||||||
|
flush_workqueue(mvotg->qwork);
|
||||||
|
destroy_workqueue(mvotg->qwork);
|
||||||
|
err_put_clk:
|
||||||
|
for (clk_i--; clk_i >= 0; clk_i--)
|
||||||
|
clk_put(mvotg->clk[clk_i]);
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, NULL);
|
||||||
|
kfree(mvotg);
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_PM
|
||||||
|
static int mv_otg_suspend(struct platform_device *pdev, pm_message_t state)
|
||||||
|
{
|
||||||
|
struct mv_otg *mvotg = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
if (mvotg->otg.state != OTG_STATE_B_IDLE) {
|
||||||
|
dev_info(&pdev->dev,
|
||||||
|
"OTG state is not B_IDLE, it is %d!\n",
|
||||||
|
mvotg->otg.state);
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mvotg->clock_gating)
|
||||||
|
mv_otg_disable_internal(mvotg);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mv_otg_resume(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct mv_otg *mvotg = platform_get_drvdata(pdev);
|
||||||
|
u32 otgsc;
|
||||||
|
|
||||||
|
if (!mvotg->clock_gating) {
|
||||||
|
mv_otg_enable_internal(mvotg);
|
||||||
|
|
||||||
|
otgsc = readl(&mvotg->op_regs->otgsc);
|
||||||
|
otgsc |= mvotg->irq_en;
|
||||||
|
writel(otgsc, &mvotg->op_regs->otgsc);
|
||||||
|
|
||||||
|
if (spin_trylock(&mvotg->wq_lock)) {
|
||||||
|
mv_otg_run_state_machine(mvotg, 0);
|
||||||
|
spin_unlock(&mvotg->wq_lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static struct platform_driver mv_otg_driver = {
|
||||||
|
.probe = mv_otg_probe,
|
||||||
|
.remove = __exit_p(mv_otg_remove),
|
||||||
|
.driver = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.name = driver_name,
|
||||||
|
},
|
||||||
|
#ifdef CONFIG_PM
|
||||||
|
.suspend = mv_otg_suspend,
|
||||||
|
.resume = mv_otg_resume,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init mv_otg_init(void)
|
||||||
|
{
|
||||||
|
return platform_driver_register(&mv_otg_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __exit mv_otg_exit(void)
|
||||||
|
{
|
||||||
|
platform_driver_unregister(&mv_otg_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(mv_otg_init);
|
||||||
|
module_exit(mv_otg_exit);
|
165
drivers/usb/otg/mv_otg.h
Normal file
165
drivers/usb/otg/mv_otg.h
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2011 Marvell International Ltd. All rights reserved.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __MV_USB_OTG_CONTROLLER__
|
||||||
|
#define __MV_USB_OTG_CONTROLLER__
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
|
||||||
|
/* Command Register Bit Masks */
|
||||||
|
#define USBCMD_RUN_STOP (0x00000001)
|
||||||
|
#define USBCMD_CTRL_RESET (0x00000002)
|
||||||
|
|
||||||
|
/* otgsc Register Bit Masks */
|
||||||
|
#define OTGSC_CTRL_VUSB_DISCHARGE 0x00000001
|
||||||
|
#define OTGSC_CTRL_VUSB_CHARGE 0x00000002
|
||||||
|
#define OTGSC_CTRL_OTG_TERM 0x00000008
|
||||||
|
#define OTGSC_CTRL_DATA_PULSING 0x00000010
|
||||||
|
#define OTGSC_STS_USB_ID 0x00000100
|
||||||
|
#define OTGSC_STS_A_VBUS_VALID 0x00000200
|
||||||
|
#define OTGSC_STS_A_SESSION_VALID 0x00000400
|
||||||
|
#define OTGSC_STS_B_SESSION_VALID 0x00000800
|
||||||
|
#define OTGSC_STS_B_SESSION_END 0x00001000
|
||||||
|
#define OTGSC_STS_1MS_TOGGLE 0x00002000
|
||||||
|
#define OTGSC_STS_DATA_PULSING 0x00004000
|
||||||
|
#define OTGSC_INTSTS_USB_ID 0x00010000
|
||||||
|
#define OTGSC_INTSTS_A_VBUS_VALID 0x00020000
|
||||||
|
#define OTGSC_INTSTS_A_SESSION_VALID 0x00040000
|
||||||
|
#define OTGSC_INTSTS_B_SESSION_VALID 0x00080000
|
||||||
|
#define OTGSC_INTSTS_B_SESSION_END 0x00100000
|
||||||
|
#define OTGSC_INTSTS_1MS 0x00200000
|
||||||
|
#define OTGSC_INTSTS_DATA_PULSING 0x00400000
|
||||||
|
#define OTGSC_INTR_USB_ID 0x01000000
|
||||||
|
#define OTGSC_INTR_A_VBUS_VALID 0x02000000
|
||||||
|
#define OTGSC_INTR_A_SESSION_VALID 0x04000000
|
||||||
|
#define OTGSC_INTR_B_SESSION_VALID 0x08000000
|
||||||
|
#define OTGSC_INTR_B_SESSION_END 0x10000000
|
||||||
|
#define OTGSC_INTR_1MS_TIMER 0x20000000
|
||||||
|
#define OTGSC_INTR_DATA_PULSING 0x40000000
|
||||||
|
|
||||||
|
#define CAPLENGTH_MASK (0xff)
|
||||||
|
|
||||||
|
/* Timer's interval, unit 10ms */
|
||||||
|
#define T_A_WAIT_VRISE 100
|
||||||
|
#define T_A_WAIT_BCON 2000
|
||||||
|
#define T_A_AIDL_BDIS 100
|
||||||
|
#define T_A_BIDL_ADIS 20
|
||||||
|
#define T_B_ASE0_BRST 400
|
||||||
|
#define T_B_SE0_SRP 300
|
||||||
|
#define T_B_SRP_FAIL 2000
|
||||||
|
#define T_B_DATA_PLS 10
|
||||||
|
#define T_B_SRP_INIT 100
|
||||||
|
#define T_A_SRP_RSPNS 10
|
||||||
|
#define T_A_DRV_RSM 5
|
||||||
|
|
||||||
|
enum otg_function {
|
||||||
|
OTG_B_DEVICE = 0,
|
||||||
|
OTG_A_DEVICE
|
||||||
|
};
|
||||||
|
|
||||||
|
enum mv_otg_timer {
|
||||||
|
A_WAIT_BCON_TIMER = 0,
|
||||||
|
OTG_TIMER_NUM
|
||||||
|
};
|
||||||
|
|
||||||
|
/* PXA OTG state machine */
|
||||||
|
struct mv_otg_ctrl {
|
||||||
|
/* internal variables */
|
||||||
|
u8 a_set_b_hnp_en; /* A-Device set b_hnp_en */
|
||||||
|
u8 b_srp_done;
|
||||||
|
u8 b_hnp_en;
|
||||||
|
|
||||||
|
/* OTG inputs */
|
||||||
|
u8 a_bus_drop;
|
||||||
|
u8 a_bus_req;
|
||||||
|
u8 a_clr_err;
|
||||||
|
u8 a_bus_resume;
|
||||||
|
u8 a_bus_suspend;
|
||||||
|
u8 a_conn;
|
||||||
|
u8 a_sess_vld;
|
||||||
|
u8 a_srp_det;
|
||||||
|
u8 a_vbus_vld;
|
||||||
|
u8 b_bus_req; /* B-Device Require Bus */
|
||||||
|
u8 b_bus_resume;
|
||||||
|
u8 b_bus_suspend;
|
||||||
|
u8 b_conn;
|
||||||
|
u8 b_se0_srp;
|
||||||
|
u8 b_sess_end;
|
||||||
|
u8 b_sess_vld;
|
||||||
|
u8 id;
|
||||||
|
u8 a_suspend_req;
|
||||||
|
|
||||||
|
/*Timer event */
|
||||||
|
u8 a_aidl_bdis_timeout;
|
||||||
|
u8 b_ase0_brst_timeout;
|
||||||
|
u8 a_bidl_adis_timeout;
|
||||||
|
u8 a_wait_bcon_timeout;
|
||||||
|
|
||||||
|
struct timer_list timer[OTG_TIMER_NUM];
|
||||||
|
};
|
||||||
|
|
||||||
|
#define VUSBHS_MAX_PORTS 8
|
||||||
|
|
||||||
|
struct mv_otg_regs {
|
||||||
|
u32 usbcmd; /* Command register */
|
||||||
|
u32 usbsts; /* Status register */
|
||||||
|
u32 usbintr; /* Interrupt enable */
|
||||||
|
u32 frindex; /* Frame index */
|
||||||
|
u32 reserved1[1];
|
||||||
|
u32 deviceaddr; /* Device Address */
|
||||||
|
u32 eplistaddr; /* Endpoint List Address */
|
||||||
|
u32 ttctrl; /* HOST TT status and control */
|
||||||
|
u32 burstsize; /* Programmable Burst Size */
|
||||||
|
u32 txfilltuning; /* Host Transmit Pre-Buffer Packet Tuning */
|
||||||
|
u32 reserved[4];
|
||||||
|
u32 epnak; /* Endpoint NAK */
|
||||||
|
u32 epnaken; /* Endpoint NAK Enable */
|
||||||
|
u32 configflag; /* Configured Flag register */
|
||||||
|
u32 portsc[VUSBHS_MAX_PORTS]; /* Port Status/Control x, x = 1..8 */
|
||||||
|
u32 otgsc;
|
||||||
|
u32 usbmode; /* USB Host/Device mode */
|
||||||
|
u32 epsetupstat; /* Endpoint Setup Status */
|
||||||
|
u32 epprime; /* Endpoint Initialize */
|
||||||
|
u32 epflush; /* Endpoint De-initialize */
|
||||||
|
u32 epstatus; /* Endpoint Status */
|
||||||
|
u32 epcomplete; /* Endpoint Interrupt On Complete */
|
||||||
|
u32 epctrlx[16]; /* Endpoint Control, where x = 0.. 15 */
|
||||||
|
u32 mcr; /* Mux Control */
|
||||||
|
u32 isr; /* Interrupt Status */
|
||||||
|
u32 ier; /* Interrupt Enable */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mv_otg {
|
||||||
|
struct otg_transceiver otg;
|
||||||
|
struct mv_otg_ctrl otg_ctrl;
|
||||||
|
|
||||||
|
/* base address */
|
||||||
|
void __iomem *phy_regs;
|
||||||
|
void __iomem *cap_regs;
|
||||||
|
struct mv_otg_regs __iomem *op_regs;
|
||||||
|
|
||||||
|
struct platform_device *pdev;
|
||||||
|
int irq;
|
||||||
|
u32 irq_status;
|
||||||
|
u32 irq_en;
|
||||||
|
|
||||||
|
struct delayed_work work;
|
||||||
|
struct workqueue_struct *qwork;
|
||||||
|
|
||||||
|
spinlock_t wq_lock;
|
||||||
|
|
||||||
|
struct mv_usb_platform_data *pdata;
|
||||||
|
|
||||||
|
unsigned int active;
|
||||||
|
unsigned int clock_gating;
|
||||||
|
unsigned int clknum;
|
||||||
|
struct clk *clk[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -42,6 +42,11 @@ struct mv_usb_platform_data {
|
|||||||
/* only valid for HCD. OTG or Host only*/
|
/* only valid for HCD. OTG or Host only*/
|
||||||
unsigned int mode;
|
unsigned int mode;
|
||||||
|
|
||||||
|
/* This flag is used for that needs id pin checked by otg */
|
||||||
|
unsigned int disable_otg_clock_gating:1;
|
||||||
|
/* Force a_bus_req to be asserted */
|
||||||
|
unsigned int otg_force_a_bus_req:1;
|
||||||
|
|
||||||
int (*phy_init)(void __iomem *regbase);
|
int (*phy_init)(void __iomem *regbase);
|
||||||
void (*phy_deinit)(void __iomem *regbase);
|
void (*phy_deinit)(void __iomem *regbase);
|
||||||
int (*set_vbus)(unsigned int vbus);
|
int (*set_vbus)(unsigned int vbus);
|
||||||
|
Loading…
Reference in New Issue
Block a user