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> 171b8fc5a5SStephen 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 */ 351b8fc5a5SStephen Boyd #define HS_PHY_CTRL 0x0040 362fc305beSStephen Boyd #define HS_PHY_SEC_CTRL 0x0078 372fc305beSStephen Boyd #define HS_PHY_DIG_CLAMP_N BIT(16) 381b8fc5a5SStephen 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; 461b8fc5a5SStephen Boyd struct reset_controller_dev rcdev; 472fc305beSStephen Boyd bool secondary_phy; 4847654a16SStephen Boyd bool hsic; 492fc305beSStephen Boyd void __iomem *base; 50e9f15a71SStephen Boyd }; 51e9f15a71SStephen Boyd 521b8fc5a5SStephen Boyd static int 531b8fc5a5SStephen Boyd ci_hdrc_msm_por_reset(struct reset_controller_dev *r, unsigned long id) 541b8fc5a5SStephen Boyd { 551b8fc5a5SStephen Boyd struct ci_hdrc_msm *ci_msm = container_of(r, struct ci_hdrc_msm, rcdev); 561b8fc5a5SStephen Boyd void __iomem *addr = ci_msm->base; 571b8fc5a5SStephen Boyd u32 val; 581b8fc5a5SStephen Boyd 591b8fc5a5SStephen Boyd if (id) 601b8fc5a5SStephen Boyd addr += HS_PHY_SEC_CTRL; 611b8fc5a5SStephen Boyd else 621b8fc5a5SStephen Boyd addr += HS_PHY_CTRL; 631b8fc5a5SStephen Boyd 641b8fc5a5SStephen Boyd val = readl_relaxed(addr); 651b8fc5a5SStephen Boyd val |= HS_PHY_POR_ASSERT; 661b8fc5a5SStephen Boyd writel(val, addr); 671b8fc5a5SStephen Boyd /* 681b8fc5a5SStephen Boyd * wait for minimum 10 microseconds as suggested by manual. 691b8fc5a5SStephen Boyd * Use a slightly larger value since the exact value didn't 701b8fc5a5SStephen Boyd * work 100% of the time. 711b8fc5a5SStephen Boyd */ 721b8fc5a5SStephen Boyd udelay(12); 731b8fc5a5SStephen Boyd val &= ~HS_PHY_POR_ASSERT; 741b8fc5a5SStephen Boyd writel(val, addr); 751b8fc5a5SStephen Boyd 761b8fc5a5SStephen Boyd return 0; 771b8fc5a5SStephen Boyd } 781b8fc5a5SStephen Boyd 791b8fc5a5SStephen Boyd static const struct reset_control_ops ci_hdrc_msm_reset_ops = { 801b8fc5a5SStephen Boyd .reset = ci_hdrc_msm_por_reset, 811b8fc5a5SStephen Boyd }; 821b8fc5a5SStephen Boyd 8311893daeSStephen Boyd static int 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); 8711893daeSStephen Boyd int ret; 888e22978cSAlexander Shishkin 898e22978cSAlexander Shishkin switch (event) { 908e22978cSAlexander Shishkin case CI_HDRC_CONTROLLER_RESET_EVENT: 918e22978cSAlexander Shishkin dev_dbg(dev, "CI_HDRC_CONTROLLER_RESET_EVENT received\n"); 9211893daeSStephen Boyd 9311893daeSStephen Boyd hw_phymode_configure(ci); 942fc305beSStephen Boyd if (msm_ci->secondary_phy) { 952fc305beSStephen Boyd u32 val = readl_relaxed(msm_ci->base + HS_PHY_SEC_CTRL); 962fc305beSStephen Boyd val |= HS_PHY_DIG_CLAMP_N; 972fc305beSStephen Boyd writel_relaxed(val, msm_ci->base + HS_PHY_SEC_CTRL); 982fc305beSStephen Boyd } 992fc305beSStephen Boyd 10011893daeSStephen Boyd ret = phy_init(ci->phy); 10111893daeSStephen Boyd if (ret) 10211893daeSStephen Boyd return ret; 10311893daeSStephen Boyd 10411893daeSStephen Boyd ret = phy_power_on(ci->phy); 10511893daeSStephen Boyd if (ret) { 10611893daeSStephen Boyd phy_exit(ci->phy); 10711893daeSStephen Boyd return ret; 10811893daeSStephen Boyd } 10911893daeSStephen Boyd 1105ce7d27dSAndy Gross /* use AHB transactor, allow posted data writes */ 111ee33f6e7SStephen Boyd hw_write_id_reg(ci, HS_PHY_AHB_MODE, 0xffffffff, 0x8); 11247654a16SStephen Boyd 11347654a16SStephen Boyd /* workaround for rx buffer collision issue */ 11447654a16SStephen Boyd hw_write_id_reg(ci, HS_PHY_GENCONFIG, 11547654a16SStephen Boyd HS_PHY_TXFIFO_IDLE_FORCE_DIS, 0); 11647654a16SStephen Boyd 11747654a16SStephen Boyd if (!msm_ci->hsic) 11847654a16SStephen Boyd hw_write_id_reg(ci, HS_PHY_GENCONFIG_2, 11947654a16SStephen Boyd HS_PHY_ULPI_TX_PKT_EN_CLR_FIX, 0); 12047654a16SStephen Boyd 12147654a16SStephen Boyd if (!IS_ERR(ci->platdata->vbus_extcon.edev)) { 12247654a16SStephen Boyd hw_write_id_reg(ci, HS_PHY_GENCONFIG_2, 12347654a16SStephen Boyd HS_PHY_SESS_VLD_CTRL_EN, 12447654a16SStephen Boyd HS_PHY_SESS_VLD_CTRL_EN); 12547654a16SStephen Boyd hw_write(ci, OP_USBCMD, HSPHY_SESS_VLD_CTRL, 12647654a16SStephen Boyd HSPHY_SESS_VLD_CTRL); 12747654a16SStephen Boyd 12847654a16SStephen Boyd } 1298e22978cSAlexander Shishkin break; 1308e22978cSAlexander Shishkin case CI_HDRC_CONTROLLER_STOPPED_EVENT: 1318e22978cSAlexander Shishkin dev_dbg(dev, "CI_HDRC_CONTROLLER_STOPPED_EVENT received\n"); 13211893daeSStephen Boyd phy_power_off(ci->phy); 13311893daeSStephen Boyd phy_exit(ci->phy); 1348e22978cSAlexander Shishkin break; 1358e22978cSAlexander Shishkin default: 1368e22978cSAlexander Shishkin dev_dbg(dev, "unknown ci_hdrc event\n"); 1378e22978cSAlexander Shishkin break; 1388e22978cSAlexander Shishkin } 13911893daeSStephen Boyd 14011893daeSStephen Boyd return 0; 1418e22978cSAlexander Shishkin } 1428e22978cSAlexander Shishkin 1432fc305beSStephen Boyd static int ci_hdrc_msm_mux_phy(struct ci_hdrc_msm *ci, 1442fc305beSStephen Boyd struct platform_device *pdev) 1452fc305beSStephen Boyd { 1462fc305beSStephen Boyd struct regmap *regmap; 1472fc305beSStephen Boyd struct device *dev = &pdev->dev; 1482fc305beSStephen Boyd struct of_phandle_args args; 1492fc305beSStephen Boyd u32 val; 1502fc305beSStephen Boyd int ret; 1512fc305beSStephen Boyd 1522fc305beSStephen Boyd ret = of_parse_phandle_with_fixed_args(dev->of_node, "phy-select", 2, 0, 1532fc305beSStephen Boyd &args); 1542fc305beSStephen Boyd if (ret) 1552fc305beSStephen Boyd return 0; 1562fc305beSStephen Boyd 1572fc305beSStephen Boyd regmap = syscon_node_to_regmap(args.np); 1582fc305beSStephen Boyd of_node_put(args.np); 1592fc305beSStephen Boyd if (IS_ERR(regmap)) 1602fc305beSStephen Boyd return PTR_ERR(regmap); 1612fc305beSStephen Boyd 1622fc305beSStephen Boyd ret = regmap_write(regmap, args.args[0], args.args[1]); 1632fc305beSStephen Boyd if (ret) 1642fc305beSStephen Boyd return ret; 1652fc305beSStephen Boyd 1662fc305beSStephen Boyd ci->secondary_phy = !!args.args[1]; 1672fc305beSStephen Boyd if (ci->secondary_phy) { 1682fc305beSStephen Boyd val = readl_relaxed(ci->base + HS_PHY_SEC_CTRL); 1692fc305beSStephen Boyd val |= HS_PHY_DIG_CLAMP_N; 1702fc305beSStephen Boyd writel_relaxed(val, ci->base + HS_PHY_SEC_CTRL); 1712fc305beSStephen Boyd } 1722fc305beSStephen Boyd 1732fc305beSStephen Boyd return 0; 1742fc305beSStephen Boyd } 1752fc305beSStephen Boyd 1768e22978cSAlexander Shishkin static int ci_hdrc_msm_probe(struct platform_device *pdev) 1778e22978cSAlexander Shishkin { 178e9f15a71SStephen Boyd struct ci_hdrc_msm *ci; 1798e22978cSAlexander Shishkin struct platform_device *plat_ci; 180e9f15a71SStephen Boyd struct clk *clk; 181e9f15a71SStephen Boyd struct reset_control *reset; 1822fc305beSStephen Boyd struct resource *res; 183e9f15a71SStephen Boyd int ret; 18447654a16SStephen Boyd struct device_node *ulpi_node, *phy_node; 1858e22978cSAlexander Shishkin 1868e22978cSAlexander Shishkin dev_dbg(&pdev->dev, "ci_hdrc_msm_probe\n"); 1878e22978cSAlexander Shishkin 188e9f15a71SStephen Boyd ci = devm_kzalloc(&pdev->dev, sizeof(*ci), GFP_KERNEL); 189e9f15a71SStephen Boyd if (!ci) 190e9f15a71SStephen Boyd return -ENOMEM; 191e9f15a71SStephen Boyd platform_set_drvdata(pdev, ci); 192e9f15a71SStephen Boyd 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 | 19611893daeSStephen Boyd CI_HDRC_OVERRIDE_AHB_BURST | 19711893daeSStephen Boyd CI_HDRC_OVERRIDE_PHY_CONTROL; 19826f8e3a8SStephen Boyd ci->pdata.notify_event = ci_hdrc_msm_notify_event; 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); 221*753dfd23SWei Yongjun if (IS_ERR(ci->base)) 222*753dfd23SWei Yongjun return PTR_ERR(ci->base); 2232fc305beSStephen Boyd 2241b8fc5a5SStephen Boyd ci->rcdev.owner = THIS_MODULE; 2251b8fc5a5SStephen Boyd ci->rcdev.ops = &ci_hdrc_msm_reset_ops; 2261b8fc5a5SStephen Boyd ci->rcdev.of_node = pdev->dev.of_node; 2271b8fc5a5SStephen Boyd ci->rcdev.nr_resets = 2; 2281b8fc5a5SStephen Boyd ret = reset_controller_register(&ci->rcdev); 229e9f15a71SStephen Boyd if (ret) 230e9f15a71SStephen Boyd return ret; 231e9f15a71SStephen Boyd 2321b8fc5a5SStephen Boyd ret = clk_prepare_enable(ci->fs_clk); 2331b8fc5a5SStephen Boyd if (ret) 2341b8fc5a5SStephen Boyd goto err_fs; 2351b8fc5a5SStephen 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) 2441b8fc5a5SStephen 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)) { 265e9f15a71SStephen Boyd ret = PTR_ERR(plat_ci); 266ed04f19fSStephen Boyd if (ret != -EPROBE_DEFER) 267ed04f19fSStephen Boyd dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n"); 268e9f15a71SStephen Boyd goto err_mux; 2698e22978cSAlexander Shishkin } 2708e22978cSAlexander Shishkin 271e9f15a71SStephen Boyd ci->ci = plat_ci; 2728e22978cSAlexander Shishkin 2732c8ea46dSStephen Boyd pm_runtime_set_active(&pdev->dev); 2748e22978cSAlexander Shishkin pm_runtime_no_callbacks(&pdev->dev); 2758e22978cSAlexander Shishkin pm_runtime_enable(&pdev->dev); 2768e22978cSAlexander Shishkin 2778e22978cSAlexander Shishkin return 0; 278e9f15a71SStephen Boyd 279e9f15a71SStephen Boyd err_mux: 280e9f15a71SStephen Boyd clk_disable_unprepare(ci->iface_clk); 281e9f15a71SStephen Boyd err_iface: 282e9f15a71SStephen Boyd clk_disable_unprepare(ci->core_clk); 2831b8fc5a5SStephen Boyd err_fs: 2841b8fc5a5SStephen Boyd reset_controller_unregister(&ci->rcdev); 285e9f15a71SStephen Boyd return ret; 2868e22978cSAlexander Shishkin } 2878e22978cSAlexander Shishkin 2888e22978cSAlexander Shishkin static int ci_hdrc_msm_remove(struct platform_device *pdev) 2898e22978cSAlexander Shishkin { 290e9f15a71SStephen Boyd struct ci_hdrc_msm *ci = platform_get_drvdata(pdev); 2918e22978cSAlexander Shishkin 2928e22978cSAlexander Shishkin pm_runtime_disable(&pdev->dev); 293e9f15a71SStephen Boyd ci_hdrc_remove_device(ci->ci); 294e9f15a71SStephen Boyd clk_disable_unprepare(ci->iface_clk); 295e9f15a71SStephen Boyd clk_disable_unprepare(ci->core_clk); 2961b8fc5a5SStephen Boyd reset_controller_unregister(&ci->rcdev); 2978e22978cSAlexander Shishkin 2988e22978cSAlexander Shishkin return 0; 2998e22978cSAlexander Shishkin } 3008e22978cSAlexander Shishkin 3012629b101SIvan T. Ivanov static const struct of_device_id msm_ci_dt_match[] = { 3022629b101SIvan T. Ivanov { .compatible = "qcom,ci-hdrc", }, 3032629b101SIvan T. Ivanov { } 3042629b101SIvan T. Ivanov }; 3052629b101SIvan T. Ivanov MODULE_DEVICE_TABLE(of, msm_ci_dt_match); 3062629b101SIvan T. Ivanov 3078e22978cSAlexander Shishkin static struct platform_driver ci_hdrc_msm_driver = { 3088e22978cSAlexander Shishkin .probe = ci_hdrc_msm_probe, 3098e22978cSAlexander Shishkin .remove = ci_hdrc_msm_remove, 3102629b101SIvan T. Ivanov .driver = { 3112629b101SIvan T. Ivanov .name = "msm_hsusb", 3122629b101SIvan T. Ivanov .of_match_table = msm_ci_dt_match, 3132629b101SIvan T. Ivanov }, 3148e22978cSAlexander Shishkin }; 3158e22978cSAlexander Shishkin 3168e22978cSAlexander Shishkin module_platform_driver(ci_hdrc_msm_driver); 3178e22978cSAlexander Shishkin 3188e22978cSAlexander Shishkin MODULE_ALIAS("platform:msm_hsusb"); 3198e22978cSAlexander Shishkin MODULE_ALIAS("platform:ci13xxx_msm"); 3208e22978cSAlexander Shishkin MODULE_LICENSE("GPL v2"); 321