Commit 277164f0 authored by Neil Zhang's avatar Neil Zhang Committed by Felipe Balbi

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: default avatarNeil Zhang <zhangwm@marvell.com>
Signed-off-by: default avatarFelipe Balbi <balbi@ti.com>
parent 5e6c86b0
......@@ -130,4 +130,16 @@ config FSL_USB2_OTG
help
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
......@@ -21,3 +21,4 @@ obj-$(CONFIG_USB_MSM_OTG) += msm_otg.o
obj-$(CONFIG_AB8500_USB) += ab8500-usb.o
fsl_usb2_otg-objs := fsl_otg.o otg_fsm.o
obj-$(CONFIG_FSL_USB2_OTG) += fsl_usb2_otg.o
obj-$(CONFIG_USB_MV_OTG) += mv_otg.o
/*
* 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);
/*
* 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*/
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);
void (*phy_deinit)(void __iomem *regbase);
int (*set_vbus)(unsigned int vbus);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment