18e22978cSAlexander Shishkin /* Copyright (c) 2010, Code Aurora Forum. All rights reserved. 28e22978cSAlexander Shishkin * 38e22978cSAlexander Shishkin * This program is free software; you can redistribute it and/or modify 48e22978cSAlexander Shishkin * it under the terms of the GNU General Public License version 2 and 58e22978cSAlexander Shishkin * only version 2 as published by the Free Software Foundation. 68e22978cSAlexander Shishkin */ 78e22978cSAlexander Shishkin 88e22978cSAlexander Shishkin #include <linux/module.h> 98e22978cSAlexander Shishkin #include <linux/platform_device.h> 108e22978cSAlexander Shishkin #include <linux/pm_runtime.h> 118e22978cSAlexander Shishkin #include <linux/usb/chipidea.h> 12e9f15a71SStephen Boyd #include <linux/clk.h> 13e9f15a71SStephen Boyd #include <linux/reset.h> 142fc305beSStephen Boyd #include <linux/mfd/syscon.h> 152fc305beSStephen Boyd #include <linux/regmap.h> 162fc305beSStephen Boyd #include <linux/io.h> 17*1b8fc5a5SStephen Boyd #include <linux/reset-controller.h> 1847654a16SStephen Boyd #include <linux/extcon.h> 1947654a16SStephen Boyd #include <linux/of.h> 208e22978cSAlexander Shishkin 218e22978cSAlexander Shishkin #include "ci.h" 228e22978cSAlexander Shishkin 23ee33f6e7SStephen Boyd #define HS_PHY_AHB_MODE 0x0098 248e22978cSAlexander Shishkin 2547654a16SStephen Boyd #define HS_PHY_GENCONFIG 0x009c 2647654a16SStephen Boyd #define HS_PHY_TXFIFO_IDLE_FORCE_DIS BIT(4) 2747654a16SStephen Boyd 2847654a16SStephen Boyd #define HS_PHY_GENCONFIG_2 0x00a0 2947654a16SStephen Boyd #define HS_PHY_SESS_VLD_CTRL_EN BIT(7) 3047654a16SStephen Boyd #define HS_PHY_ULPI_TX_PKT_EN_CLR_FIX BIT(19) 3147654a16SStephen Boyd 3247654a16SStephen Boyd #define HSPHY_SESS_VLD_CTRL BIT(25) 3347654a16SStephen Boyd 342fc305beSStephen Boyd /* Vendor base starts at 0x200 beyond CI base */ 35*1b8fc5a5SStephen Boyd #define HS_PHY_CTRL 0x0040 362fc305beSStephen Boyd #define HS_PHY_SEC_CTRL 0x0078 372fc305beSStephen Boyd #define HS_PHY_DIG_CLAMP_N BIT(16) 38*1b8fc5a5SStephen Boyd #define HS_PHY_POR_ASSERT BIT(0) 392fc305beSStephen Boyd 40e9f15a71SStephen Boyd struct ci_hdrc_msm { 41e9f15a71SStephen Boyd struct platform_device *ci; 42e9f15a71SStephen Boyd struct clk *core_clk; 43e9f15a71SStephen Boyd struct clk *iface_clk; 44e9f15a71SStephen Boyd struct clk *fs_clk; 4526f8e3a8SStephen Boyd struct ci_hdrc_platform_data pdata; 46*1b8fc5a5SStephen Boyd struct reset_controller_dev rcdev; 472fc305beSStephen Boyd bool secondary_phy; 4847654a16SStephen Boyd bool hsic; 492fc305beSStephen Boyd void __iomem *base; 50e9f15a71SStephen Boyd }; 51e9f15a71SStephen Boyd 52*1b8fc5a5SStephen Boyd static int 53*1b8fc5a5SStephen Boyd ci_hdrc_msm_por_reset(struct reset_controller_dev *r, unsigned long id) 54*1b8fc5a5SStephen Boyd { 55*1b8fc5a5SStephen Boyd struct ci_hdrc_msm *ci_msm = container_of(r, struct ci_hdrc_msm, rcdev); 56*1b8fc5a5SStephen Boyd void __iomem *addr = ci_msm->base; 57*1b8fc5a5SStephen Boyd u32 val; 58*1b8fc5a5SStephen Boyd 59*1b8fc5a5SStephen Boyd if (id) 60*1b8fc5a5SStephen Boyd addr += HS_PHY_SEC_CTRL; 61*1b8fc5a5SStephen Boyd else 62*1b8fc5a5SStephen Boyd addr += HS_PHY_CTRL; 63*1b8fc5a5SStephen Boyd 64*1b8fc5a5SStephen Boyd val = readl_relaxed(addr); 65*1b8fc5a5SStephen Boyd val |= HS_PHY_POR_ASSERT; 66*1b8fc5a5SStephen Boyd writel(val, addr); 67*1b8fc5a5SStephen Boyd /* 68*1b8fc5a5SStephen Boyd * wait for minimum 10 microseconds as suggested by manual. 69*1b8fc5a5SStephen Boyd * Use a slightly larger value since the exact value didn't 70*1b8fc5a5SStephen Boyd * work 100% of the time. 71*1b8fc5a5SStephen Boyd */ 72*1b8fc5a5SStephen Boyd udelay(12); 73*1b8fc5a5SStephen Boyd val &= ~HS_PHY_POR_ASSERT; 74*1b8fc5a5SStephen Boyd writel(val, addr); 75*1b8fc5a5SStephen Boyd 76*1b8fc5a5SStephen Boyd return 0; 77*1b8fc5a5SStephen Boyd } 78*1b8fc5a5SStephen Boyd 79*1b8fc5a5SStephen Boyd static const struct reset_control_ops ci_hdrc_msm_reset_ops = { 80*1b8fc5a5SStephen Boyd .reset = ci_hdrc_msm_por_reset, 81*1b8fc5a5SStephen Boyd }; 82*1b8fc5a5SStephen Boyd 838e22978cSAlexander Shishkin static void ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event) 848e22978cSAlexander Shishkin { 852fc305beSStephen Boyd struct device *dev = ci->dev->parent; 862fc305beSStephen Boyd struct ci_hdrc_msm *msm_ci = dev_get_drvdata(dev); 878e22978cSAlexander Shishkin 888e22978cSAlexander Shishkin switch (event) { 898e22978cSAlexander Shishkin case CI_HDRC_CONTROLLER_RESET_EVENT: 908e22978cSAlexander Shishkin dev_dbg(dev, "CI_HDRC_CONTROLLER_RESET_EVENT received\n"); 912fc305beSStephen Boyd if (msm_ci->secondary_phy) { 922fc305beSStephen Boyd u32 val = readl_relaxed(msm_ci->base + HS_PHY_SEC_CTRL); 932fc305beSStephen Boyd val |= HS_PHY_DIG_CLAMP_N; 942fc305beSStephen Boyd writel_relaxed(val, msm_ci->base + HS_PHY_SEC_CTRL); 952fc305beSStephen Boyd } 962fc305beSStephen Boyd 975ce7d27dSAndy Gross /* use AHB transactor, allow posted data writes */ 98ee33f6e7SStephen Boyd hw_write_id_reg(ci, HS_PHY_AHB_MODE, 0xffffffff, 0x8); 9947654a16SStephen Boyd 10047654a16SStephen Boyd /* workaround for rx buffer collision issue */ 10147654a16SStephen Boyd hw_write_id_reg(ci, HS_PHY_GENCONFIG, 10247654a16SStephen Boyd HS_PHY_TXFIFO_IDLE_FORCE_DIS, 0); 10347654a16SStephen Boyd 10447654a16SStephen Boyd if (!msm_ci->hsic) 10547654a16SStephen Boyd hw_write_id_reg(ci, HS_PHY_GENCONFIG_2, 10647654a16SStephen Boyd HS_PHY_ULPI_TX_PKT_EN_CLR_FIX, 0); 10747654a16SStephen Boyd 10847654a16SStephen Boyd if (!IS_ERR(ci->platdata->vbus_extcon.edev)) { 10947654a16SStephen Boyd hw_write_id_reg(ci, HS_PHY_GENCONFIG_2, 11047654a16SStephen Boyd HS_PHY_SESS_VLD_CTRL_EN, 11147654a16SStephen Boyd HS_PHY_SESS_VLD_CTRL_EN); 11247654a16SStephen Boyd hw_write(ci, OP_USBCMD, HSPHY_SESS_VLD_CTRL, 11347654a16SStephen Boyd HSPHY_SESS_VLD_CTRL); 11447654a16SStephen Boyd 11547654a16SStephen Boyd } 11647654a16SStephen Boyd 117ef44cb42SAntoine Tenart usb_phy_init(ci->usb_phy); 1188e22978cSAlexander Shishkin break; 1198e22978cSAlexander Shishkin case CI_HDRC_CONTROLLER_STOPPED_EVENT: 1208e22978cSAlexander Shishkin dev_dbg(dev, "CI_HDRC_CONTROLLER_STOPPED_EVENT received\n"); 1218e22978cSAlexander Shishkin /* 122ef44cb42SAntoine Tenart * Put the phy in non-driving mode. Otherwise host 1238e22978cSAlexander Shishkin * may not detect soft-disconnection. 1248e22978cSAlexander Shishkin */ 125ef44cb42SAntoine Tenart usb_phy_notify_disconnect(ci->usb_phy, USB_SPEED_UNKNOWN); 1268e22978cSAlexander Shishkin break; 1278e22978cSAlexander Shishkin default: 1288e22978cSAlexander Shishkin dev_dbg(dev, "unknown ci_hdrc event\n"); 1298e22978cSAlexander Shishkin break; 1308e22978cSAlexander Shishkin } 1318e22978cSAlexander Shishkin } 1328e22978cSAlexander Shishkin 1332fc305beSStephen Boyd static int ci_hdrc_msm_mux_phy(struct ci_hdrc_msm *ci, 1342fc305beSStephen Boyd struct platform_device *pdev) 1352fc305beSStephen Boyd { 1362fc305beSStephen Boyd struct regmap *regmap; 1372fc305beSStephen Boyd struct device *dev = &pdev->dev; 1382fc305beSStephen Boyd struct of_phandle_args args; 1392fc305beSStephen Boyd u32 val; 1402fc305beSStephen Boyd int ret; 1412fc305beSStephen Boyd 1422fc305beSStephen Boyd ret = of_parse_phandle_with_fixed_args(dev->of_node, "phy-select", 2, 0, 1432fc305beSStephen Boyd &args); 1442fc305beSStephen Boyd if (ret) 1452fc305beSStephen Boyd return 0; 1462fc305beSStephen Boyd 1472fc305beSStephen Boyd regmap = syscon_node_to_regmap(args.np); 1482fc305beSStephen Boyd of_node_put(args.np); 1492fc305beSStephen Boyd if (IS_ERR(regmap)) 1502fc305beSStephen Boyd return PTR_ERR(regmap); 1512fc305beSStephen Boyd 1522fc305beSStephen Boyd ret = regmap_write(regmap, args.args[0], args.args[1]); 1532fc305beSStephen Boyd if (ret) 1542fc305beSStephen Boyd return ret; 1552fc305beSStephen Boyd 1562fc305beSStephen Boyd ci->secondary_phy = !!args.args[1]; 1572fc305beSStephen Boyd if (ci->secondary_phy) { 1582fc305beSStephen Boyd val = readl_relaxed(ci->base + HS_PHY_SEC_CTRL); 1592fc305beSStephen Boyd val |= HS_PHY_DIG_CLAMP_N; 1602fc305beSStephen Boyd writel_relaxed(val, ci->base + HS_PHY_SEC_CTRL); 1612fc305beSStephen Boyd } 1622fc305beSStephen Boyd 1632fc305beSStephen Boyd return 0; 1642fc305beSStephen Boyd } 1652fc305beSStephen Boyd 1668e22978cSAlexander Shishkin static int ci_hdrc_msm_probe(struct platform_device *pdev) 1678e22978cSAlexander Shishkin { 168e9f15a71SStephen Boyd struct ci_hdrc_msm *ci; 1698e22978cSAlexander Shishkin struct platform_device *plat_ci; 1702629b101SIvan T. Ivanov struct usb_phy *phy; 171e9f15a71SStephen Boyd struct clk *clk; 172e9f15a71SStephen Boyd struct reset_control *reset; 1732fc305beSStephen Boyd struct resource *res; 174e9f15a71SStephen Boyd int ret; 17547654a16SStephen Boyd struct device_node *ulpi_node, *phy_node; 1768e22978cSAlexander Shishkin 1778e22978cSAlexander Shishkin dev_dbg(&pdev->dev, "ci_hdrc_msm_probe\n"); 1788e22978cSAlexander Shishkin 179e9f15a71SStephen Boyd ci = devm_kzalloc(&pdev->dev, sizeof(*ci), GFP_KERNEL); 180e9f15a71SStephen Boyd if (!ci) 181e9f15a71SStephen Boyd return -ENOMEM; 182e9f15a71SStephen Boyd platform_set_drvdata(pdev, ci); 183e9f15a71SStephen Boyd 1842629b101SIvan T. Ivanov /* 1852629b101SIvan T. Ivanov * OTG(PHY) driver takes care of PHY initialization, clock management, 1862629b101SIvan T. Ivanov * powering up VBUS, mapping of registers address space and power 1872629b101SIvan T. Ivanov * management. 1882629b101SIvan T. Ivanov */ 1892629b101SIvan T. Ivanov phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0); 1902629b101SIvan T. Ivanov if (IS_ERR(phy)) 1912629b101SIvan T. Ivanov return PTR_ERR(phy); 1922629b101SIvan T. Ivanov 19326f8e3a8SStephen Boyd ci->pdata.name = "ci_hdrc_msm"; 19426f8e3a8SStephen Boyd ci->pdata.capoffset = DEF_CAPOFFSET; 19526f8e3a8SStephen Boyd ci->pdata.flags = CI_HDRC_REGS_SHARED | CI_HDRC_DISABLE_STREAMING | 19626f8e3a8SStephen Boyd CI_HDRC_OVERRIDE_AHB_BURST; 19726f8e3a8SStephen Boyd ci->pdata.notify_event = ci_hdrc_msm_notify_event; 19826f8e3a8SStephen Boyd ci->pdata.usb_phy = phy; 1992629b101SIvan T. Ivanov 200e9f15a71SStephen Boyd reset = devm_reset_control_get(&pdev->dev, "core"); 201e9f15a71SStephen Boyd if (IS_ERR(reset)) 202e9f15a71SStephen Boyd return PTR_ERR(reset); 203e9f15a71SStephen Boyd 204e9f15a71SStephen Boyd ci->core_clk = clk = devm_clk_get(&pdev->dev, "core"); 205e9f15a71SStephen Boyd if (IS_ERR(clk)) 206e9f15a71SStephen Boyd return PTR_ERR(clk); 207e9f15a71SStephen Boyd 208e9f15a71SStephen Boyd ci->iface_clk = clk = devm_clk_get(&pdev->dev, "iface"); 209e9f15a71SStephen Boyd if (IS_ERR(clk)) 210e9f15a71SStephen Boyd return PTR_ERR(clk); 211e9f15a71SStephen Boyd 212e9f15a71SStephen Boyd ci->fs_clk = clk = devm_clk_get(&pdev->dev, "fs"); 213e9f15a71SStephen Boyd if (IS_ERR(clk)) { 214e9f15a71SStephen Boyd if (PTR_ERR(clk) == -EPROBE_DEFER) 215e9f15a71SStephen Boyd return -EPROBE_DEFER; 216e9f15a71SStephen Boyd ci->fs_clk = NULL; 217e9f15a71SStephen Boyd } 218e9f15a71SStephen Boyd 2192fc305beSStephen Boyd res = platform_get_resource(pdev, IORESOURCE_MEM, 1); 2202fc305beSStephen Boyd ci->base = devm_ioremap_resource(&pdev->dev, res); 2212fc305beSStephen Boyd if (!ci->base) 2222fc305beSStephen Boyd return -ENOMEM; 2232fc305beSStephen Boyd 224*1b8fc5a5SStephen Boyd ci->rcdev.owner = THIS_MODULE; 225*1b8fc5a5SStephen Boyd ci->rcdev.ops = &ci_hdrc_msm_reset_ops; 226*1b8fc5a5SStephen Boyd ci->rcdev.of_node = pdev->dev.of_node; 227*1b8fc5a5SStephen Boyd ci->rcdev.nr_resets = 2; 228*1b8fc5a5SStephen Boyd ret = reset_controller_register(&ci->rcdev); 229e9f15a71SStephen Boyd if (ret) 230e9f15a71SStephen Boyd return ret; 231e9f15a71SStephen Boyd 232*1b8fc5a5SStephen Boyd ret = clk_prepare_enable(ci->fs_clk); 233*1b8fc5a5SStephen Boyd if (ret) 234*1b8fc5a5SStephen Boyd goto err_fs; 235*1b8fc5a5SStephen Boyd 236e9f15a71SStephen Boyd reset_control_assert(reset); 237e9f15a71SStephen Boyd usleep_range(10000, 12000); 238e9f15a71SStephen Boyd reset_control_deassert(reset); 239e9f15a71SStephen Boyd 240e9f15a71SStephen Boyd clk_disable_unprepare(ci->fs_clk); 241e9f15a71SStephen Boyd 242e9f15a71SStephen Boyd ret = clk_prepare_enable(ci->core_clk); 243e9f15a71SStephen Boyd if (ret) 244*1b8fc5a5SStephen Boyd goto err_fs; 245e9f15a71SStephen Boyd 246e9f15a71SStephen Boyd ret = clk_prepare_enable(ci->iface_clk); 247e9f15a71SStephen Boyd if (ret) 248e9f15a71SStephen Boyd goto err_iface; 249e9f15a71SStephen Boyd 2502fc305beSStephen Boyd ret = ci_hdrc_msm_mux_phy(ci, pdev); 2512fc305beSStephen Boyd if (ret) 2522fc305beSStephen Boyd goto err_mux; 2532fc305beSStephen Boyd 25447654a16SStephen Boyd ulpi_node = of_find_node_by_name(pdev->dev.of_node, "ulpi"); 25547654a16SStephen Boyd if (ulpi_node) { 25647654a16SStephen Boyd phy_node = of_get_next_available_child(ulpi_node, NULL); 25747654a16SStephen Boyd ci->hsic = of_device_is_compatible(phy_node, "qcom,usb-hsic-phy"); 25847654a16SStephen Boyd of_node_put(phy_node); 25947654a16SStephen Boyd } 26047654a16SStephen Boyd of_node_put(ulpi_node); 26147654a16SStephen Boyd 26226f8e3a8SStephen Boyd plat_ci = ci_hdrc_add_device(&pdev->dev, pdev->resource, 26326f8e3a8SStephen Boyd pdev->num_resources, &ci->pdata); 2648e22978cSAlexander Shishkin if (IS_ERR(plat_ci)) { 2658e22978cSAlexander Shishkin dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n"); 266e9f15a71SStephen Boyd ret = PTR_ERR(plat_ci); 267e9f15a71SStephen Boyd goto err_mux; 2688e22978cSAlexander Shishkin } 2698e22978cSAlexander Shishkin 270e9f15a71SStephen Boyd ci->ci = plat_ci; 2718e22978cSAlexander Shishkin 2722c8ea46dSStephen Boyd pm_runtime_set_active(&pdev->dev); 2738e22978cSAlexander Shishkin pm_runtime_no_callbacks(&pdev->dev); 2748e22978cSAlexander Shishkin pm_runtime_enable(&pdev->dev); 2758e22978cSAlexander Shishkin 2768e22978cSAlexander Shishkin return 0; 277e9f15a71SStephen Boyd 278e9f15a71SStephen Boyd err_mux: 279e9f15a71SStephen Boyd clk_disable_unprepare(ci->iface_clk); 280e9f15a71SStephen Boyd err_iface: 281e9f15a71SStephen Boyd clk_disable_unprepare(ci->core_clk); 282*1b8fc5a5SStephen Boyd err_fs: 283*1b8fc5a5SStephen Boyd reset_controller_unregister(&ci->rcdev); 284e9f15a71SStephen Boyd return ret; 2858e22978cSAlexander Shishkin } 2868e22978cSAlexander Shishkin 2878e22978cSAlexander Shishkin static int ci_hdrc_msm_remove(struct platform_device *pdev) 2888e22978cSAlexander Shishkin { 289e9f15a71SStephen Boyd struct ci_hdrc_msm *ci = platform_get_drvdata(pdev); 2908e22978cSAlexander Shishkin 2918e22978cSAlexander Shishkin pm_runtime_disable(&pdev->dev); 292e9f15a71SStephen Boyd ci_hdrc_remove_device(ci->ci); 293e9f15a71SStephen Boyd clk_disable_unprepare(ci->iface_clk); 294e9f15a71SStephen Boyd clk_disable_unprepare(ci->core_clk); 295*1b8fc5a5SStephen Boyd reset_controller_unregister(&ci->rcdev); 2968e22978cSAlexander Shishkin 2978e22978cSAlexander Shishkin return 0; 2988e22978cSAlexander Shishkin } 2998e22978cSAlexander Shishkin 3002629b101SIvan T. Ivanov static const struct of_device_id msm_ci_dt_match[] = { 3012629b101SIvan T. Ivanov { .compatible = "qcom,ci-hdrc", }, 3022629b101SIvan T. Ivanov { } 3032629b101SIvan T. Ivanov }; 3042629b101SIvan T. Ivanov MODULE_DEVICE_TABLE(of, msm_ci_dt_match); 3052629b101SIvan T. Ivanov 3068e22978cSAlexander Shishkin static struct platform_driver ci_hdrc_msm_driver = { 3078e22978cSAlexander Shishkin .probe = ci_hdrc_msm_probe, 3088e22978cSAlexander Shishkin .remove = ci_hdrc_msm_remove, 3092629b101SIvan T. Ivanov .driver = { 3102629b101SIvan T. Ivanov .name = "msm_hsusb", 3112629b101SIvan T. Ivanov .of_match_table = msm_ci_dt_match, 3122629b101SIvan T. Ivanov }, 3138e22978cSAlexander Shishkin }; 3148e22978cSAlexander Shishkin 3158e22978cSAlexander Shishkin module_platform_driver(ci_hdrc_msm_driver); 3168e22978cSAlexander Shishkin 3178e22978cSAlexander Shishkin MODULE_ALIAS("platform:msm_hsusb"); 3188e22978cSAlexander Shishkin MODULE_ALIAS("platform:ci13xxx_msm"); 3198e22978cSAlexander Shishkin MODULE_LICENSE("GPL v2"); 320