summaryrefslogtreecommitdiff
path: root/drivers/usb/otg/fsl_otg.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/otg/fsl_otg.c')
-rw-r--r--drivers/usb/otg/fsl_otg.c233
1 files changed, 158 insertions, 75 deletions
diff --git a/drivers/usb/otg/fsl_otg.c b/drivers/usb/otg/fsl_otg.c
index b1454886fd7a..81a2985632b6 100644
--- a/drivers/usb/otg/fsl_otg.c
+++ b/drivers/usb/otg/fsl_otg.c
@@ -34,6 +34,7 @@
#include <linux/init.h>
#include <linux/reboot.h>
#include <linux/timer.h>
+#include <linux/jiffies.h>
#include <linux/list.h>
#include <linux/usb.h>
#include <linux/device.h>
@@ -41,6 +42,7 @@
#include <linux/usb/gadget.h>
#include <linux/workqueue.h>
#include <linux/time.h>
+#include <linux/usb/fsl_xcvr.h>
#include <linux/fsl_devices.h>
#include <linux/platform_device.h>
#include <linux/irq.h>
@@ -60,6 +62,8 @@
#define DRIVER_DESC "Freescale USB OTG Driver"
#define DRIVER_INFO DRIVER_VERSION " " DRIVER_DESC
+#define TIMER_FREQ 1000 /* 100 ms*/
+#define IDLE_TIME 5000 /* 1000 ms */
MODULE_DESCRIPTION("Freescale USB OTG Transceiver Driver");
@@ -89,6 +93,13 @@ static struct fsl_otg_config fsl_otg_initdata = {
.otg_port = 1,
};
+/* the timer is used to monitor the otg loading, if idle for some times
+ * we will close the otg clk
+ */
+static unsigned long last_busy;
+static bool clk_stopped;
+static struct timer_list monitor_timer;
+
int write_ulpi(u8 addr, u8 data)
{
u32 temp;
@@ -136,17 +147,10 @@ void fsl_otg_dischrg_vbus(int on)
}
/* A-device driver vbus, controlled through PP bit in PORTSC */
-void fsl_otg_drv_vbus(int on)
+void fsl_otg_drv_vbus(struct fsl_usb2_platform_data *pdata, int on)
{
-/* if (on)
- usb_dr_regs->portsc =
- cpu_to_le32((le32_to_cpu(usb_dr_regs->portsc) &
- ~PORTSC_W1C_BITS) | PORTSC_PORT_POWER);
- else
- usb_dr_regs->portsc =
- cpu_to_le32(le32_to_cpu(usb_dr_regs->portsc) &
- ~PORTSC_W1C_BITS & ~PORTSC_PORT_POWER);
-*/
+ if (pdata->xcvr_ops && pdata->xcvr_ops->set_vbus_power)
+ pdata->xcvr_ops->set_vbus_power(pdata->xcvr_ops, pdata, on);
}
/*
@@ -395,6 +399,61 @@ int fsl_otg_tick_timer(void)
return expired;
}
+static void fsl_otg_clk_gate(bool on)
+{
+ struct device *dev = fsl_otg_dev->otg.dev;
+ struct fsl_usb2_platform_data *pdata;
+
+ if (dev) {
+ pdata = dev->platform_data;
+ if (pdata && pdata->usb_clock_for_pm)
+ pdata->usb_clock_for_pm(on);
+ }
+}
+
+static void fsl_otg_clk_ctl(void)
+{
+ if (clk_stopped){
+ fsl_otg_clk_gate(true);
+ clk_stopped = false;
+ }
+ last_busy = jiffies;
+}
+
+static void fsl_otg_loading_monitor(unsigned long data)
+{
+ unsigned long now = jiffies;
+ if (!clk_stopped){
+ if (time_after(now, last_busy + msecs_to_jiffies(IDLE_TIME))){
+ printk("otg is idle for some times,so we close the clock %x\n", le32_to_cpu(usb_dr_regs->otgsc));
+ clk_stopped = true;
+ fsl_otg_clk_gate(false);
+ printk("close otg clk ok\n");
+ }
+ }
+ mod_timer(&monitor_timer, jiffies + msecs_to_jiffies(TIMER_FREQ));
+}
+
+/**
+ * Enable vbus interrupt
+ * The otg cares USB_ID interrupt
+ * The device cares B Sesstion Valid
+ */
+static void b_session_irq_enable(bool enable)
+{
+ int osc = le32_to_cpu(usb_dr_regs->otgsc);
+ /* The other interrupts' status should not be cleared */
+ osc &= ~(OTGSC_INTSTS_USB_ID | OTGSC_INTSTS_A_VBUS_VALID
+ | OTGSC_INTSTS_A_SESSION_VALID | OTGSC_INTSTS_B_SESSION_VALID);
+ osc |= OTGSC_INTSTS_B_SESSION_VALID;
+
+ if (enable)
+ osc |= OTGSC_INTR_B_SESSION_VALID_EN;
+ else
+ osc &= ~OTGSC_INTR_B_SESSION_VALID_EN;
+ usb_dr_regs->otgsc = cpu_to_le32(osc);
+}
+
/* Reset controller, not reset the bus */
void otg_reset_controller(void)
{
@@ -438,7 +497,7 @@ int fsl_otg_start_host(struct otg_fsm *fsm, int on)
retval = host_pdrv->resume(host_pdev);
if (fsm->id) {
/* default-b */
- fsl_otg_drv_vbus(1);
+ fsl_otg_drv_vbus(dev->platform_data, 1);
/* Workaround: b_host can't driver
* vbus, but PP in PORTSC needs to
* be 1 for host to work.
@@ -463,7 +522,7 @@ int fsl_otg_start_host(struct otg_fsm *fsm, int on)
otg_suspend_state);
if (fsm->id)
/* default-b */
- fsl_otg_drv_vbus(0);
+ fsl_otg_drv_vbus(dev->platform_data, 0);
}
otg_dev->host_working = 0;
}
@@ -481,7 +540,6 @@ int fsl_otg_start_gadget(struct otg_fsm *fsm, int on)
struct device *dev;
struct platform_driver *gadget_pdrv;
struct platform_device *gadget_pdev;
-
if (!xceiv->gadget || !xceiv->gadget->dev.parent)
return -ENODEV;
@@ -517,7 +575,6 @@ static int fsl_otg_set_host(struct otg_transceiver *otg_p, struct usb_bus *host)
if (host) {
VDBG("host off......\n");
-
otg_p->host->otg_port = fsl_otg_initdata.otg_port;
otg_p->host->is_b_host = otg_dev->fsm.id;
/* must leave time for khubd to finish its thing
@@ -634,11 +691,29 @@ static void fsl_otg_event(struct work_struct *work)
{
struct fsl_otg *og = container_of(work, struct fsl_otg, otg_event.work);
struct otg_fsm *fsm = &og->fsm;
+ struct otg_transceiver *otg = &og->otg;
+
+ otg->default_a = (fsm->id == 0);
+ /* clear conn information */
+ if (fsm->id)
+ fsm->b_conn = 0;
+ else
+ fsm->a_conn = 0;
+
+ if (otg->host)
+ otg->host->is_b_host = fsm->id;
+ if (otg->gadget)
+ otg->gadget->is_a_peripheral = !fsm->id;
if (fsm->id) { /* switch to gadget */
+ b_session_irq_enable(true);
fsl_otg_start_host(fsm, 0);
otg_drv_vbus(fsm, 0);
fsl_otg_start_gadget(fsm, 1);
+ }else { /* switch to host */
+ fsl_otg_start_gadget(fsm, 0);
+ otg_drv_vbus(fsm, 1);
+ fsl_otg_start_host(fsm, 1);
}
}
@@ -678,12 +753,13 @@ irqreturn_t fsl_otg_isr_gpio(int irq, void *dev_id)
struct otg_fsm *fsm;
struct fsl_usb2_platform_data *pdata =
(struct fsl_usb2_platform_data *)dev_id;
- struct fsl_otg *p_otg;
+ struct fsl_otg *f_otg;
struct otg_transceiver *otg_trans = otg_get_transceiver();
- p_otg = container_of(otg_trans, struct fsl_otg, otg);
- fsm = &p_otg->fsm;
int value;
+ f_otg = container_of(otg_trans, struct fsl_otg, otg);
+ fsm = &f_otg->fsm;
+ fsl_otg_clk_ctl();
if (pdata->id_gpio == 0)
return IRQ_NONE;
@@ -695,35 +771,25 @@ irqreturn_t fsl_otg_isr_gpio(int irq, void *dev_id)
set_irq_type(gpio_to_irq(pdata->id_gpio), IRQ_TYPE_LEVEL_HIGH);
- if (value == p_otg->fsm.id)
+ if (value == f_otg->fsm.id)
return IRQ_HANDLED;
- p_otg->fsm.id = value;
-
- otg_trans->default_a = (fsm->id == 0);
- /* clear conn information */
- if (fsm->id)
- fsm->b_conn = 0;
- else
- fsm->a_conn = 0;
-
- if (otg_trans->host)
- otg_trans->host->is_b_host = fsm->id;
- if (otg_trans->gadget)
- otg_trans->gadget->is_a_peripheral = !fsm->id;
-
- VDBG("ID int (ID is %d)\n", fsm->id);
- if (fsm->id) { /* switch to gadget */
- schedule_delayed_work(&p_otg->otg_event, 100);
+ f_otg->fsm.id = value;
- } else { /* switch to host */
- cancel_delayed_work(&p_otg->otg_event);
- fsl_otg_start_gadget(fsm, 0);
- otg_drv_vbus(fsm, 1);
- fsl_otg_start_host(fsm, 1);
+ cancel_delayed_work(&f_otg->otg_event);
+ schedule_delayed_work(&f_otg->otg_event, msecs_to_jiffies(10));
+ /* if host mode, we should clear B_SESSION_VLD event and disable
+ * B_SESSION_VLD irq
+ */
+ if (!f_otg->fsm.id) {
+ b_session_irq_enable(false);
+ }else {
+ //b_session_irq_enable(true);
}
+
return IRQ_HANDLED;
}
+
/* Interrupt handler. OTG/host/peripheral share the same int line.
* OTG driver clears OTGSC interrupts and leaves USB interrupts
* intact. It needs to have knowledge of some USB interrupts
@@ -731,60 +797,70 @@ irqreturn_t fsl_otg_isr_gpio(int irq, void *dev_id)
*/
irqreturn_t fsl_otg_isr(int irq, void *dev_id)
{
- struct otg_fsm *fsm = &((struct fsl_otg *)dev_id)->fsm;
- struct otg_transceiver *otg = &((struct fsl_otg *)dev_id)->otg;
+ struct fsl_otg *fotg = (struct fsl_otg *)dev_id;
+ struct otg_transceiver *otg = &fotg->otg;
u32 otg_int_src, otg_sc;
+ irqreturn_t ret = IRQ_NONE;
+ fsl_otg_clk_ctl();
otg_sc = le32_to_cpu(usb_dr_regs->otgsc);
otg_int_src = otg_sc & OTGSC_INTSTS_MASK & (otg_sc >> 8);
- /* Only clear otg interrupts */
- usb_dr_regs->otgsc |= cpu_to_le32(otg_sc & OTGSC_INTSTS_MASK);
+ /* Only clear otg interrupts, expect B_SESSION_VALID,
+ * Leave it to be handled by arcotg_udc */
+ usb_dr_regs->otgsc = ((usb_dr_regs->otgsc | cpu_to_le32(otg_sc & OTGSC_INTSTS_MASK))&
+ (~OTGSC_INTSTS_B_SESSION_VALID));
/*FIXME: ID change not generate when init to 0 */
- fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0;
- otg->default_a = (fsm->id == 0);
+ fotg->fsm.id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0;
+ otg->default_a = (fotg->fsm.id == 0);
/* process OTG interrupts */
if (otg_int_src) {
if (otg_int_src & OTGSC_INTSTS_USB_ID) {
- fsm->id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0;
- otg->default_a = (fsm->id == 0);
- /* clear conn information */
- if (fsm->id)
- fsm->b_conn = 0;
- else
- fsm->a_conn = 0;
-
- if (otg->host)
- otg->host->is_b_host = fsm->id;
- if (otg->gadget)
- otg->gadget->is_a_peripheral = !fsm->id;
- VDBG("ID int (ID is %d)\n", fsm->id);
-
- if (fsm->id) { /* switch to gadget */
- schedule_delayed_work(&((struct fsl_otg *)
- dev_id)->otg_event,
- 100);
- } else { /* switch to host */
- cancel_delayed_work(&
- ((struct fsl_otg *)dev_id)->
- otg_event);
- fsl_otg_start_gadget(fsm, 0);
- otg_drv_vbus(fsm, 1);
- fsl_otg_start_host(fsm, 1);
+ fotg->fsm.id = (otg_sc & OTGSC_STS_USB_ID) ? 1 : 0;
+
+ printk("ID int (ID is %d)\n", fotg->fsm.id);
+
+ cancel_delayed_work(&fotg->otg_event);
+ schedule_delayed_work(&fotg->otg_event, msecs_to_jiffies(10));
+ /* if host mode, we should clear B_SESSION_VLD event and disable
+ * B_SESSION_VLD irq
+ */
+ if (!fotg->fsm.id) {
+ b_session_irq_enable(false);
+ }else {
+ //b_session_irq_enable(true);
}
-
- return IRQ_HANDLED;
+ ret = IRQ_HANDLED;
}
}
- return IRQ_NONE;
+ return ret;
+}
+
+static void fsl_otg_fsm_drv_vbus(int on)
+{
+ struct otg_fsm *fsm = &(fsl_otg_dev->fsm);
+ struct otg_transceiver *xceiv = fsm->transceiver;
+
+ struct device *dev;
+ /*
+ * The host is assigned at otg_set_host
+ */
+ if (!xceiv->host)
+ return;
+ /*
+ * The dev is assigned at usb_create_hcd which is called earlier
+ * than otg_set_host at host driver's probe
+ */
+ dev = xceiv->host->controller;
+ fsl_otg_drv_vbus(dev->platform_data, on);
}
static struct otg_fsm_ops fsl_otg_ops = {
.chrg_vbus = fsl_otg_chrg_vbus,
- .drv_vbus = fsl_otg_drv_vbus,
+ .drv_vbus = fsl_otg_fsm_drv_vbus,
.loc_conn = fsl_otg_loc_conn,
.loc_sof = fsl_otg_loc_sof,
.start_pulse = fsl_otg_start_pulse,
@@ -837,6 +913,7 @@ static int fsl_otg_conf(struct platform_device *pdev)
fsl_otg_tc->otg.set_power = fsl_otg_set_power;
fsl_otg_tc->otg.start_hnp = fsl_otg_start_hnp;
fsl_otg_tc->otg.start_srp = fsl_otg_start_srp;
+ fsl_otg_tc->otg.dev = &pdev->dev;
fsl_otg_dev = fsl_otg_tc;
@@ -903,6 +980,8 @@ int usb_otg_start(struct platform_device *pdev)
if (pdata->platform_init && pdata->platform_init(pdev) != 0)
return -EINVAL;
+ clk_stopped = false; /* platform_init will open the otg clk */
+
/* stop the controller */
temp = readl(&p_otg->dr_mem_map->usbcmd);
temp &= ~USB_CMD_RUN_STOP;
@@ -1229,6 +1308,10 @@ static int __init fsl_otg_probe(struct platform_device *pdev)
return -EIO;
}
+ last_busy = jiffies;
+ setup_timer(&monitor_timer, fsl_otg_loading_monitor, (unsigned long)pdev);
+ mod_timer(&monitor_timer, jiffies + msecs_to_jiffies(TIMER_FREQ));
+
create_proc_file();
return status;
}