]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blobdiff - drivers/usb/chipidea/ci_hdrc_msm.c
usb: chipidea: msm: Handle phy power states
[mirror_ubuntu-artful-kernel.git] / drivers / usb / chipidea / ci_hdrc_msm.c
index 3889809fd0c49f61495857323ccb33ab9729ae0b..316044dba0ac16f951c7d7bf0bb5af7745147e3f 100644 (file)
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
-#include <linux/usb/msm_hsusb_hw.h>
-#include <linux/usb/ulpi.h>
-#include <linux/usb/gadget.h>
 #include <linux/usb/chipidea.h>
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/io.h>
+#include <linux/reset-controller.h>
+#include <linux/extcon.h>
+#include <linux/of.h>
 
 #include "ci.h"
 
-#define MSM_USB_BASE   (ci->hw_bank.abs)
+#define HS_PHY_AHB_MODE                        0x0098
 
-static void ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event)
+#define HS_PHY_GENCONFIG               0x009c
+#define HS_PHY_TXFIFO_IDLE_FORCE_DIS   BIT(4)
+
+#define HS_PHY_GENCONFIG_2             0x00a0
+#define HS_PHY_SESS_VLD_CTRL_EN                BIT(7)
+#define HS_PHY_ULPI_TX_PKT_EN_CLR_FIX  BIT(19)
+
+#define HSPHY_SESS_VLD_CTRL            BIT(25)
+
+/* Vendor base starts at 0x200 beyond CI base */
+#define HS_PHY_CTRL                    0x0040
+#define HS_PHY_SEC_CTRL                        0x0078
+#define HS_PHY_DIG_CLAMP_N             BIT(16)
+#define HS_PHY_POR_ASSERT              BIT(0)
+
+struct ci_hdrc_msm {
+       struct platform_device *ci;
+       struct clk *core_clk;
+       struct clk *iface_clk;
+       struct clk *fs_clk;
+       struct ci_hdrc_platform_data pdata;
+       struct reset_controller_dev rcdev;
+       bool secondary_phy;
+       bool hsic;
+       void __iomem *base;
+};
+
+static int
+ci_hdrc_msm_por_reset(struct reset_controller_dev *r, unsigned long id)
 {
-       struct device *dev = ci->gadget.dev.parent;
+       struct ci_hdrc_msm *ci_msm = container_of(r, struct ci_hdrc_msm, rcdev);
+       void __iomem *addr = ci_msm->base;
+       u32 val;
+
+       if (id)
+               addr += HS_PHY_SEC_CTRL;
+       else
+               addr += HS_PHY_CTRL;
+
+       val = readl_relaxed(addr);
+       val |= HS_PHY_POR_ASSERT;
+       writel(val, addr);
+       /*
+        * wait for minimum 10 microseconds as suggested by manual.
+        * Use a slightly larger value since the exact value didn't
+        * work 100% of the time.
+        */
+       udelay(12);
+       val &= ~HS_PHY_POR_ASSERT;
+       writel(val, addr);
+
+       return 0;
+}
+
+static const struct reset_control_ops ci_hdrc_msm_reset_ops = {
+       .reset = ci_hdrc_msm_por_reset,
+};
+
+static int ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event)
+{
+       struct device *dev = ci->dev->parent;
+       struct ci_hdrc_msm *msm_ci = dev_get_drvdata(dev);
+       int ret;
 
        switch (event) {
        case CI_HDRC_CONTROLLER_RESET_EVENT:
                dev_dbg(dev, "CI_HDRC_CONTROLLER_RESET_EVENT received\n");
-               writel(0, USB_AHBBURST);
+
+               hw_phymode_configure(ci);
+               if (msm_ci->secondary_phy) {
+                       u32 val = readl_relaxed(msm_ci->base + HS_PHY_SEC_CTRL);
+                       val |= HS_PHY_DIG_CLAMP_N;
+                       writel_relaxed(val, msm_ci->base + HS_PHY_SEC_CTRL);
+               }
+
+               ret = phy_init(ci->phy);
+               if (ret)
+                       return ret;
+
+               ret = phy_power_on(ci->phy);
+               if (ret) {
+                       phy_exit(ci->phy);
+                       return ret;
+               }
+
                /* use AHB transactor, allow posted data writes */
-               writel(0x8, USB_AHBMODE);
-               usb_phy_init(ci->usb_phy);
+               hw_write_id_reg(ci, HS_PHY_AHB_MODE, 0xffffffff, 0x8);
+
+               /* workaround for rx buffer collision issue */
+               hw_write_id_reg(ci, HS_PHY_GENCONFIG,
+                               HS_PHY_TXFIFO_IDLE_FORCE_DIS, 0);
+
+               if (!msm_ci->hsic)
+                       hw_write_id_reg(ci, HS_PHY_GENCONFIG_2,
+                                       HS_PHY_ULPI_TX_PKT_EN_CLR_FIX, 0);
+
+               if (!IS_ERR(ci->platdata->vbus_extcon.edev)) {
+                       hw_write_id_reg(ci, HS_PHY_GENCONFIG_2,
+                                       HS_PHY_SESS_VLD_CTRL_EN,
+                                       HS_PHY_SESS_VLD_CTRL_EN);
+                       hw_write(ci, OP_USBCMD, HSPHY_SESS_VLD_CTRL,
+                                HSPHY_SESS_VLD_CTRL);
+
+               }
                break;
        case CI_HDRC_CONTROLLER_STOPPED_EVENT:
                dev_dbg(dev, "CI_HDRC_CONTROLLER_STOPPED_EVENT received\n");
-               /*
-                * Put the phy in non-driving mode. Otherwise host
-                * may not detect soft-disconnection.
-                */
-               usb_phy_notify_disconnect(ci->usb_phy, USB_SPEED_UNKNOWN);
+               phy_power_off(ci->phy);
+               phy_exit(ci->phy);
                break;
        default:
                dev_dbg(dev, "unknown ci_hdrc event\n");
                break;
        }
+
+       return 0;
 }
 
-static struct ci_hdrc_platform_data ci_hdrc_msm_platdata = {
-       .name                   = "ci_hdrc_msm",
-       .capoffset              = DEF_CAPOFFSET,
-       .flags                  = CI_HDRC_REGS_SHARED |
-                                 CI_HDRC_DISABLE_STREAMING,
+static int ci_hdrc_msm_mux_phy(struct ci_hdrc_msm *ci,
+                              struct platform_device *pdev)
+{
+       struct regmap *regmap;
+       struct device *dev = &pdev->dev;
+       struct of_phandle_args args;
+       u32 val;
+       int ret;
 
-       .notify_event           = ci_hdrc_msm_notify_event,
-};
+       ret = of_parse_phandle_with_fixed_args(dev->of_node, "phy-select", 2, 0,
+                                              &args);
+       if (ret)
+               return 0;
+
+       regmap = syscon_node_to_regmap(args.np);
+       of_node_put(args.np);
+       if (IS_ERR(regmap))
+               return PTR_ERR(regmap);
+
+       ret = regmap_write(regmap, args.args[0], args.args[1]);
+       if (ret)
+               return ret;
+
+       ci->secondary_phy = !!args.args[1];
+       if (ci->secondary_phy) {
+               val = readl_relaxed(ci->base + HS_PHY_SEC_CTRL);
+               val |= HS_PHY_DIG_CLAMP_N;
+               writel_relaxed(val, ci->base + HS_PHY_SEC_CTRL);
+       }
+
+       return 0;
+}
 
 static int ci_hdrc_msm_probe(struct platform_device *pdev)
 {
+       struct ci_hdrc_msm *ci;
        struct platform_device *plat_ci;
-       struct usb_phy *phy;
+       struct clk *clk;
+       struct reset_control *reset;
+       struct resource *res;
+       int ret;
+       struct device_node *ulpi_node, *phy_node;
 
        dev_dbg(&pdev->dev, "ci_hdrc_msm_probe\n");
 
-       /*
-        * OTG(PHY) driver takes care of PHY initialization, clock management,
-        * powering up VBUS, mapping of registers address space and power
-        * management.
-        */
-       phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0);
-       if (IS_ERR(phy))
-               return PTR_ERR(phy);
+       ci = devm_kzalloc(&pdev->dev, sizeof(*ci), GFP_KERNEL);
+       if (!ci)
+               return -ENOMEM;
+       platform_set_drvdata(pdev, ci);
+
+       ci->pdata.name = "ci_hdrc_msm";
+       ci->pdata.capoffset = DEF_CAPOFFSET;
+       ci->pdata.flags = CI_HDRC_REGS_SHARED | CI_HDRC_DISABLE_STREAMING |
+                         CI_HDRC_OVERRIDE_AHB_BURST |
+                         CI_HDRC_OVERRIDE_PHY_CONTROL;
+       ci->pdata.notify_event = ci_hdrc_msm_notify_event;
+
+       reset = devm_reset_control_get(&pdev->dev, "core");
+       if (IS_ERR(reset))
+               return PTR_ERR(reset);
+
+       ci->core_clk = clk = devm_clk_get(&pdev->dev, "core");
+       if (IS_ERR(clk))
+               return PTR_ERR(clk);
 
-       ci_hdrc_msm_platdata.usb_phy = phy;
+       ci->iface_clk = clk = devm_clk_get(&pdev->dev, "iface");
+       if (IS_ERR(clk))
+               return PTR_ERR(clk);
 
-       plat_ci = ci_hdrc_add_device(&pdev->dev,
-                               pdev->resource, pdev->num_resources,
-                               &ci_hdrc_msm_platdata);
+       ci->fs_clk = clk = devm_clk_get(&pdev->dev, "fs");
+       if (IS_ERR(clk)) {
+               if (PTR_ERR(clk) == -EPROBE_DEFER)
+                       return -EPROBE_DEFER;
+               ci->fs_clk = NULL;
+       }
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+       ci->base = devm_ioremap_resource(&pdev->dev, res);
+       if (!ci->base)
+               return -ENOMEM;
+
+       ci->rcdev.owner = THIS_MODULE;
+       ci->rcdev.ops = &ci_hdrc_msm_reset_ops;
+       ci->rcdev.of_node = pdev->dev.of_node;
+       ci->rcdev.nr_resets = 2;
+       ret = reset_controller_register(&ci->rcdev);
+       if (ret)
+               return ret;
+
+       ret = clk_prepare_enable(ci->fs_clk);
+       if (ret)
+               goto err_fs;
+
+       reset_control_assert(reset);
+       usleep_range(10000, 12000);
+       reset_control_deassert(reset);
+
+       clk_disable_unprepare(ci->fs_clk);
+
+       ret = clk_prepare_enable(ci->core_clk);
+       if (ret)
+               goto err_fs;
+
+       ret = clk_prepare_enable(ci->iface_clk);
+       if (ret)
+               goto err_iface;
+
+       ret = ci_hdrc_msm_mux_phy(ci, pdev);
+       if (ret)
+               goto err_mux;
+
+       ulpi_node = of_find_node_by_name(pdev->dev.of_node, "ulpi");
+       if (ulpi_node) {
+               phy_node = of_get_next_available_child(ulpi_node, NULL);
+               ci->hsic = of_device_is_compatible(phy_node, "qcom,usb-hsic-phy");
+               of_node_put(phy_node);
+       }
+       of_node_put(ulpi_node);
+
+       plat_ci = ci_hdrc_add_device(&pdev->dev, pdev->resource,
+                                    pdev->num_resources, &ci->pdata);
        if (IS_ERR(plat_ci)) {
                dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n");
-               return PTR_ERR(plat_ci);
+               ret = PTR_ERR(plat_ci);
+               goto err_mux;
        }
 
-       platform_set_drvdata(pdev, plat_ci);
+       ci->ci = plat_ci;
 
+       pm_runtime_set_active(&pdev->dev);
        pm_runtime_no_callbacks(&pdev->dev);
        pm_runtime_enable(&pdev->dev);
 
        return 0;
+
+err_mux:
+       clk_disable_unprepare(ci->iface_clk);
+err_iface:
+       clk_disable_unprepare(ci->core_clk);
+err_fs:
+       reset_controller_unregister(&ci->rcdev);
+       return ret;
 }
 
 static int ci_hdrc_msm_remove(struct platform_device *pdev)
 {
-       struct platform_device *plat_ci = platform_get_drvdata(pdev);
+       struct ci_hdrc_msm *ci = platform_get_drvdata(pdev);
 
        pm_runtime_disable(&pdev->dev);
-       ci_hdrc_remove_device(plat_ci);
+       ci_hdrc_remove_device(ci->ci);
+       clk_disable_unprepare(ci->iface_clk);
+       clk_disable_unprepare(ci->core_clk);
+       reset_controller_unregister(&ci->rcdev);
 
        return 0;
 }