usb: renesas_usbhs: add autonomy mode
authorKuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Thu, 28 Apr 2011 07:41:20 +0000 (16:41 +0900)
committerGreg Kroah-Hartman <gregkh@suse.de>
Sat, 30 Apr 2011 00:24:35 +0000 (17:24 -0700)
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 <kuninori.morimoto.gx@renesas.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/renesas_usbhs/common.c
drivers/usb/renesas_usbhs/common.h
drivers/usb/renesas_usbhs/mod.c
drivers/usb/renesas_usbhs/mod.h

index 9a75a45687bbf2b5a00daf7564bf6ef744fe6b3b..34e68e0205c525a50f4205136e3845a88f0d72de 100644 (file)
 #include <linux/sysfs.h>
 #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);
index 0157eb805cf683a2a19549122dc5a91e60b85b3f..0aadcb4027648a45455b633e21c9de9b78203fe4 100644 (file)
@@ -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
         */
index d0f5f67e07499ab7e2b82aa1da3fe0535069d2d2..a577f8f4064ca00a92a5dc43a931e097a496e9dc 100644 (file)
 #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)
index 8644191e164f2ff3304c3054967c8848cf92ebcd..5c845a28a21ca951be378b4f04425fc3e5ce36c0 100644 (file)
@@ -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
  */