From b002ff6e268b6024d6927a1ce330a14ca162b6ab Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Thu, 28 Apr 2011 16:41:20 +0900 Subject: [PATCH] usb: renesas_usbhs: add autonomy mode Current renesas_usbhs was designed to save power when USB is not connected. And it assumed platform uses callback to notify connection/disconnection by external interrupt. But some SuperH / platform board doesn't have such feature. This patch adds autonomy mode which detect USB connection/disconnection by internal interrupt. But power will be always ON when autonomy mode is selected. Signed-off-by: Kuninori Morimoto Signed-off-by: Greg Kroah-Hartman --- drivers/usb/renesas_usbhs/common.c | 36 +++++++++++++++++----- drivers/usb/renesas_usbhs/common.h | 3 ++ drivers/usb/renesas_usbhs/mod.c | 48 ++++++++++++++++++++++++++++++ drivers/usb/renesas_usbhs/mod.h | 15 ++++++++++ 4 files changed, 95 insertions(+), 7 deletions(-) diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c index 9a75a45687bb..34e68e0205c5 100644 --- a/drivers/usb/renesas_usbhs/common.c +++ b/drivers/usb/renesas_usbhs/common.c @@ -21,6 +21,14 @@ #include #include "./common.h" +#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 * @@ -203,7 +211,8 @@ static void usbhsc_notify_hotplug(struct work_struct *work) dev_dbg(&pdev->dev, "%s enable\n", __func__); /* power on */ - usbhsc_power_ctrl(priv, enable); + if (usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL)) + usbhsc_power_ctrl(priv, enable); /* module start */ usbhs_mod_call(priv, start, priv); @@ -215,7 +224,8 @@ static void usbhsc_notify_hotplug(struct work_struct *work) usbhs_mod_call(priv, stop, priv); /* power off */ - usbhsc_power_ctrl(priv, enable); + if (usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL)) + usbhsc_power_ctrl(priv, enable); usbhs_mod_change(priv, -1); @@ -252,8 +262,7 @@ static int __devinit usbhs_probe(struct platform_device *pdev) /* check platform information */ if (!info || - !info->platform_callback.get_id || - !info->platform_callback.get_vbus) { + !info->platform_callback.get_id) { dev_err(&pdev->dev, "no platform information\n"); return -EINVAL; } @@ -296,6 +305,11 @@ static int __devinit usbhs_probe(struct platform_device *pdev) priv->dparam->pipe_size = ARRAY_SIZE(usbhsc_default_pipe_type); } + /* FIXME */ + /* runtime power control ? */ + if (priv->pfunc->get_vbus) + usbhsc_flags_set(priv, USBHSF_RUNTIME_PWCTRL); + /* * priv settings */ @@ -338,10 +352,16 @@ static int __devinit usbhs_probe(struct platform_device *pdev) /* 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 */ - pm_runtime_enable(&pdev->dev); ret = usbhsc_drvcllbck_notify_hotplug(pdev); if (ret < 0) goto probe_end_call_remove; @@ -376,9 +396,11 @@ static int __devexit usbhs_remove(struct platform_device *pdev) dfunc->notify_hotplug = NULL; - pm_runtime_disable(&pdev->dev); + /* power off */ + if (!usbhsc_flags_has(priv, USBHSF_RUNTIME_PWCTRL)) + usbhsc_power_ctrl(priv, 0); - usbhsc_bus_ctrl(priv, 0); + pm_runtime_disable(&pdev->dev); usbhs_platform_call(priv, hardware_exit, pdev); usbhs_pipe_remove(priv); diff --git a/drivers/usb/renesas_usbhs/common.h b/drivers/usb/renesas_usbhs/common.h index 0157eb805cf6..0aadcb402764 100644 --- a/drivers/usb/renesas_usbhs/common.h +++ b/drivers/usb/renesas_usbhs/common.h @@ -105,6 +105,7 @@ struct usbhs_priv; #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 */ @@ -182,6 +183,8 @@ struct usbhs_priv { spinlock_t lock; + u32 flags; + /* * module control */ diff --git a/drivers/usb/renesas_usbhs/mod.c b/drivers/usb/renesas_usbhs/mod.c index d0f5f67e0749..a577f8f4064c 100644 --- a/drivers/usb/renesas_usbhs/mod.c +++ b/drivers/usb/renesas_usbhs/mod.c @@ -20,6 +20,48 @@ #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); + + return usbhsc_drvcllbck_notify_hotplug(pdev); +} + +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 @@ -227,6 +269,9 @@ static irqreturn_t usbhs_interrupt(int irq, void *data) * see also * usbhs_irq_setting_update */ + 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); @@ -245,6 +290,7 @@ static irqreturn_t usbhs_interrupt(int irq, void *data) void usbhs_irq_callback_update(struct usbhs_priv *priv, struct usbhs_mod *mod) { u16 intenb0 = 0; + struct usbhs_mod_info *info = usbhs_priv_to_modinfo(priv); usbhs_write(priv, INTENB0, 0); @@ -260,6 +306,8 @@ void usbhs_irq_callback_update(struct usbhs_priv *priv, struct usbhs_mod *mod) * it don't enable DVSE (intenb0) here * but "mod->irq_dev_state" will be called. */ + if (info->irq_vbus) + intenb0 |= VBSE; if (mod) { if (mod->irq_ctrl_stage) diff --git a/drivers/usb/renesas_usbhs/mod.h b/drivers/usb/renesas_usbhs/mod.h index 8644191e164f..5c845a28a21c 100644 --- a/drivers/usb/renesas_usbhs/mod.h +++ b/drivers/usb/renesas_usbhs/mod.h @@ -68,6 +68,19 @@ struct usbhs_mod { 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); }; /* @@ -81,6 +94,8 @@ 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 */ -- 2.20.1