15fd54aceSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0 251b751f1SGreg Kroah-Hartman /* Copyright (c) 2010, Code Aurora Forum. All rights reserved. */ 38e22978cSAlexander Shishkin 48e22978cSAlexander Shishkin #include <linux/module.h> 58e22978cSAlexander Shishkin #include <linux/platform_device.h> 68e22978cSAlexander Shishkin #include <linux/pm_runtime.h> 78e22978cSAlexander Shishkin #include <linux/usb/chipidea.h> 8e9f15a71SStephen Boyd #include <linux/clk.h> 9e9f15a71SStephen Boyd #include <linux/reset.h> 102fc305beSStephen Boyd #include <linux/mfd/syscon.h> 112fc305beSStephen Boyd #include <linux/regmap.h> 122fc305beSStephen Boyd #include <linux/io.h> 131b8fc5a5SStephen Boyd #include <linux/reset-controller.h> 1447654a16SStephen Boyd #include <linux/extcon.h> 1547654a16SStephen Boyd #include <linux/of.h> 168e22978cSAlexander Shishkin 178e22978cSAlexander Shishkin #include "ci.h" 188e22978cSAlexander Shishkin 19ee33f6e7SStephen Boyd #define HS_PHY_AHB_MODE 0x0098 208e22978cSAlexander Shishkin 2147654a16SStephen Boyd #define HS_PHY_GENCONFIG 0x009c 2247654a16SStephen Boyd #define HS_PHY_TXFIFO_IDLE_FORCE_DIS BIT(4) 2347654a16SStephen Boyd 2447654a16SStephen Boyd #define HS_PHY_GENCONFIG_2 0x00a0 2547654a16SStephen Boyd #define HS_PHY_SESS_VLD_CTRL_EN BIT(7) 2647654a16SStephen Boyd #define HS_PHY_ULPI_TX_PKT_EN_CLR_FIX BIT(19) 2747654a16SStephen Boyd 2847654a16SStephen Boyd #define HSPHY_SESS_VLD_CTRL BIT(25) 2947654a16SStephen Boyd 302fc305beSStephen Boyd /* Vendor base starts at 0x200 beyond CI base */ 311b8fc5a5SStephen Boyd #define HS_PHY_CTRL 0x0040 322fc305beSStephen Boyd #define HS_PHY_SEC_CTRL 0x0078 332fc305beSStephen Boyd #define HS_PHY_DIG_CLAMP_N BIT(16) 341b8fc5a5SStephen Boyd #define HS_PHY_POR_ASSERT BIT(0) 352fc305beSStephen Boyd 36e9f15a71SStephen Boyd struct ci_hdrc_msm { 37e9f15a71SStephen Boyd struct platform_device *ci; 38e9f15a71SStephen Boyd struct clk *core_clk; 39e9f15a71SStephen Boyd struct clk *iface_clk; 40e9f15a71SStephen Boyd struct clk *fs_clk; 4126f8e3a8SStephen Boyd struct ci_hdrc_platform_data pdata; 421b8fc5a5SStephen Boyd struct reset_controller_dev rcdev; 432fc305beSStephen Boyd bool secondary_phy; 4447654a16SStephen Boyd bool hsic; 452fc305beSStephen Boyd void __iomem *base; 46e9f15a71SStephen Boyd }; 47e9f15a71SStephen Boyd 481b8fc5a5SStephen Boyd static int 491b8fc5a5SStephen Boyd ci_hdrc_msm_por_reset(struct reset_controller_dev *r, unsigned long id) 501b8fc5a5SStephen Boyd { 511b8fc5a5SStephen Boyd struct ci_hdrc_msm *ci_msm = container_of(r, struct ci_hdrc_msm, rcdev); 521b8fc5a5SStephen Boyd void __iomem *addr = ci_msm->base; 531b8fc5a5SStephen Boyd u32 val; 541b8fc5a5SStephen Boyd 551b8fc5a5SStephen Boyd if (id) 561b8fc5a5SStephen Boyd addr += HS_PHY_SEC_CTRL; 571b8fc5a5SStephen Boyd else 581b8fc5a5SStephen Boyd addr += HS_PHY_CTRL; 591b8fc5a5SStephen Boyd 601b8fc5a5SStephen Boyd val = readl_relaxed(addr); 611b8fc5a5SStephen Boyd val |= HS_PHY_POR_ASSERT; 621b8fc5a5SStephen Boyd writel(val, addr); 631b8fc5a5SStephen Boyd /* 641b8fc5a5SStephen Boyd * wait for minimum 10 microseconds as suggested by manual. 651b8fc5a5SStephen Boyd * Use a slightly larger value since the exact value didn't 661b8fc5a5SStephen Boyd * work 100% of the time. 671b8fc5a5SStephen Boyd */ 681b8fc5a5SStephen Boyd udelay(12); 691b8fc5a5SStephen Boyd val &= ~HS_PHY_POR_ASSERT; 701b8fc5a5SStephen Boyd writel(val, addr); 711b8fc5a5SStephen Boyd 721b8fc5a5SStephen Boyd return 0; 731b8fc5a5SStephen Boyd } 741b8fc5a5SStephen Boyd 751b8fc5a5SStephen Boyd static const struct reset_control_ops ci_hdrc_msm_reset_ops = { 761b8fc5a5SStephen Boyd .reset = ci_hdrc_msm_por_reset, 771b8fc5a5SStephen Boyd }; 781b8fc5a5SStephen Boyd 7911893daeSStephen Boyd static int ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event) 808e22978cSAlexander Shishkin { 812fc305beSStephen Boyd struct device *dev = ci->dev->parent; 822fc305beSStephen Boyd struct ci_hdrc_msm *msm_ci = dev_get_drvdata(dev); 8311893daeSStephen Boyd int ret; 848e22978cSAlexander Shishkin 858e22978cSAlexander Shishkin switch (event) { 868e22978cSAlexander Shishkin case CI_HDRC_CONTROLLER_RESET_EVENT: 878e22978cSAlexander Shishkin dev_dbg(dev, "CI_HDRC_CONTROLLER_RESET_EVENT received\n"); 8811893daeSStephen Boyd 8911893daeSStephen Boyd hw_phymode_configure(ci); 902fc305beSStephen Boyd if (msm_ci->secondary_phy) { 912fc305beSStephen Boyd u32 val = readl_relaxed(msm_ci->base + HS_PHY_SEC_CTRL); 922fc305beSStephen Boyd val |= HS_PHY_DIG_CLAMP_N; 932fc305beSStephen Boyd writel_relaxed(val, msm_ci->base + HS_PHY_SEC_CTRL); 942fc305beSStephen Boyd } 952fc305beSStephen Boyd 9611893daeSStephen Boyd ret = phy_init(ci->phy); 9711893daeSStephen Boyd if (ret) 9811893daeSStephen Boyd return ret; 9911893daeSStephen Boyd 10011893daeSStephen Boyd ret = phy_power_on(ci->phy); 10111893daeSStephen Boyd if (ret) { 10211893daeSStephen Boyd phy_exit(ci->phy); 10311893daeSStephen Boyd return ret; 10411893daeSStephen Boyd } 10511893daeSStephen Boyd 1065ce7d27dSAndy Gross /* use AHB transactor, allow posted data writes */ 107ee33f6e7SStephen Boyd hw_write_id_reg(ci, HS_PHY_AHB_MODE, 0xffffffff, 0x8); 10847654a16SStephen Boyd 10947654a16SStephen Boyd /* workaround for rx buffer collision issue */ 11047654a16SStephen Boyd hw_write_id_reg(ci, HS_PHY_GENCONFIG, 11147654a16SStephen Boyd HS_PHY_TXFIFO_IDLE_FORCE_DIS, 0); 11247654a16SStephen Boyd 11347654a16SStephen Boyd if (!msm_ci->hsic) 11447654a16SStephen Boyd hw_write_id_reg(ci, HS_PHY_GENCONFIG_2, 11547654a16SStephen Boyd HS_PHY_ULPI_TX_PKT_EN_CLR_FIX, 0); 11647654a16SStephen Boyd 117*91edf63dSBryan O'Donoghue if (!IS_ERR(ci->platdata->vbus_extcon.edev) || ci->role_switch) { 11847654a16SStephen Boyd hw_write_id_reg(ci, HS_PHY_GENCONFIG_2, 11947654a16SStephen Boyd HS_PHY_SESS_VLD_CTRL_EN, 12047654a16SStephen Boyd HS_PHY_SESS_VLD_CTRL_EN); 12147654a16SStephen Boyd hw_write(ci, OP_USBCMD, HSPHY_SESS_VLD_CTRL, 12247654a16SStephen Boyd HSPHY_SESS_VLD_CTRL); 12347654a16SStephen Boyd 12447654a16SStephen Boyd } 1258e22978cSAlexander Shishkin break; 1268e22978cSAlexander Shishkin case CI_HDRC_CONTROLLER_STOPPED_EVENT: 1278e22978cSAlexander Shishkin dev_dbg(dev, "CI_HDRC_CONTROLLER_STOPPED_EVENT received\n"); 12811893daeSStephen Boyd phy_power_off(ci->phy); 12911893daeSStephen Boyd phy_exit(ci->phy); 1308e22978cSAlexander Shishkin break; 1318e22978cSAlexander Shishkin default: 1328e22978cSAlexander Shishkin dev_dbg(dev, "unknown ci_hdrc event\n"); 1338e22978cSAlexander Shishkin break; 1348e22978cSAlexander Shishkin } 13511893daeSStephen Boyd 13611893daeSStephen Boyd return 0; 1378e22978cSAlexander Shishkin } 1388e22978cSAlexander Shishkin 1392fc305beSStephen Boyd static int ci_hdrc_msm_mux_phy(struct ci_hdrc_msm *ci, 1402fc305beSStephen Boyd struct platform_device *pdev) 1412fc305beSStephen Boyd { 1422fc305beSStephen Boyd struct regmap *regmap; 1432fc305beSStephen Boyd struct device *dev = &pdev->dev; 1442fc305beSStephen Boyd struct of_phandle_args args; 1452fc305beSStephen Boyd u32 val; 1462fc305beSStephen Boyd int ret; 1472fc305beSStephen Boyd 1482fc305beSStephen Boyd ret = of_parse_phandle_with_fixed_args(dev->of_node, "phy-select", 2, 0, 1492fc305beSStephen Boyd &args); 1502fc305beSStephen Boyd if (ret) 1512fc305beSStephen Boyd return 0; 1522fc305beSStephen Boyd 1532fc305beSStephen Boyd regmap = syscon_node_to_regmap(args.np); 1542fc305beSStephen Boyd of_node_put(args.np); 1552fc305beSStephen Boyd if (IS_ERR(regmap)) 1562fc305beSStephen Boyd return PTR_ERR(regmap); 1572fc305beSStephen Boyd 1582fc305beSStephen Boyd ret = regmap_write(regmap, args.args[0], args.args[1]); 1592fc305beSStephen Boyd if (ret) 1602fc305beSStephen Boyd return ret; 1612fc305beSStephen Boyd 1622fc305beSStephen Boyd ci->secondary_phy = !!args.args[1]; 1632fc305beSStephen Boyd if (ci->secondary_phy) { 1642fc305beSStephen Boyd val = readl_relaxed(ci->base + HS_PHY_SEC_CTRL); 1652fc305beSStephen Boyd val |= HS_PHY_DIG_CLAMP_N; 1662fc305beSStephen Boyd writel_relaxed(val, ci->base + HS_PHY_SEC_CTRL); 1672fc305beSStephen Boyd } 1682fc305beSStephen Boyd 1692fc305beSStephen Boyd return 0; 1702fc305beSStephen Boyd } 1712fc305beSStephen Boyd 1728e22978cSAlexander Shishkin static int ci_hdrc_msm_probe(struct platform_device *pdev) 1738e22978cSAlexander Shishkin { 174e9f15a71SStephen Boyd struct ci_hdrc_msm *ci; 1758e22978cSAlexander Shishkin struct platform_device *plat_ci; 176e9f15a71SStephen Boyd struct clk *clk; 177e9f15a71SStephen Boyd struct reset_control *reset; 178e9f15a71SStephen Boyd int ret; 17947654a16SStephen Boyd struct device_node *ulpi_node, *phy_node; 1808e22978cSAlexander Shishkin 1818e22978cSAlexander Shishkin dev_dbg(&pdev->dev, "ci_hdrc_msm_probe\n"); 1828e22978cSAlexander Shishkin 183e9f15a71SStephen Boyd ci = devm_kzalloc(&pdev->dev, sizeof(*ci), GFP_KERNEL); 184e9f15a71SStephen Boyd if (!ci) 185e9f15a71SStephen Boyd return -ENOMEM; 186e9f15a71SStephen Boyd platform_set_drvdata(pdev, ci); 187e9f15a71SStephen Boyd 18826f8e3a8SStephen Boyd ci->pdata.name = "ci_hdrc_msm"; 18926f8e3a8SStephen Boyd ci->pdata.capoffset = DEF_CAPOFFSET; 19026f8e3a8SStephen Boyd ci->pdata.flags = CI_HDRC_REGS_SHARED | CI_HDRC_DISABLE_STREAMING | 19111893daeSStephen Boyd CI_HDRC_OVERRIDE_AHB_BURST | 19211893daeSStephen Boyd CI_HDRC_OVERRIDE_PHY_CONTROL; 19326f8e3a8SStephen Boyd ci->pdata.notify_event = ci_hdrc_msm_notify_event; 1942629b101SIvan T. Ivanov 195e9f15a71SStephen Boyd reset = devm_reset_control_get(&pdev->dev, "core"); 196e9f15a71SStephen Boyd if (IS_ERR(reset)) 197e9f15a71SStephen Boyd return PTR_ERR(reset); 198e9f15a71SStephen Boyd 199e9f15a71SStephen Boyd ci->core_clk = clk = devm_clk_get(&pdev->dev, "core"); 200e9f15a71SStephen Boyd if (IS_ERR(clk)) 201e9f15a71SStephen Boyd return PTR_ERR(clk); 202e9f15a71SStephen Boyd 203e9f15a71SStephen Boyd ci->iface_clk = clk = devm_clk_get(&pdev->dev, "iface"); 204e9f15a71SStephen Boyd if (IS_ERR(clk)) 205e9f15a71SStephen Boyd return PTR_ERR(clk); 206e9f15a71SStephen Boyd 207fcafadf7SChunfeng Yun ci->fs_clk = clk = devm_clk_get_optional(&pdev->dev, "fs"); 208fcafadf7SChunfeng Yun if (IS_ERR(clk)) 209fcafadf7SChunfeng Yun return PTR_ERR(clk); 210e9f15a71SStephen Boyd 211034252e3SFabio Estevam ci->base = devm_platform_ioremap_resource(pdev, 1); 212753dfd23SWei Yongjun if (IS_ERR(ci->base)) 213753dfd23SWei Yongjun return PTR_ERR(ci->base); 2142fc305beSStephen Boyd 2151b8fc5a5SStephen Boyd ci->rcdev.owner = THIS_MODULE; 2161b8fc5a5SStephen Boyd ci->rcdev.ops = &ci_hdrc_msm_reset_ops; 2171b8fc5a5SStephen Boyd ci->rcdev.of_node = pdev->dev.of_node; 2181b8fc5a5SStephen Boyd ci->rcdev.nr_resets = 2; 219d7290cd0SChuhong Yuan ret = devm_reset_controller_register(&pdev->dev, &ci->rcdev); 220e9f15a71SStephen Boyd if (ret) 221e9f15a71SStephen Boyd return ret; 222e9f15a71SStephen Boyd 2231b8fc5a5SStephen Boyd ret = clk_prepare_enable(ci->fs_clk); 2241b8fc5a5SStephen Boyd if (ret) 225d7290cd0SChuhong Yuan return ret; 2261b8fc5a5SStephen Boyd 227e9f15a71SStephen Boyd reset_control_assert(reset); 228e9f15a71SStephen Boyd usleep_range(10000, 12000); 229e9f15a71SStephen Boyd reset_control_deassert(reset); 230e9f15a71SStephen Boyd 231e9f15a71SStephen Boyd clk_disable_unprepare(ci->fs_clk); 232e9f15a71SStephen Boyd 233e9f15a71SStephen Boyd ret = clk_prepare_enable(ci->core_clk); 234e9f15a71SStephen Boyd if (ret) 235d7290cd0SChuhong Yuan return ret; 236e9f15a71SStephen Boyd 237e9f15a71SStephen Boyd ret = clk_prepare_enable(ci->iface_clk); 238e9f15a71SStephen Boyd if (ret) 239e9f15a71SStephen Boyd goto err_iface; 240e9f15a71SStephen Boyd 2412fc305beSStephen Boyd ret = ci_hdrc_msm_mux_phy(ci, pdev); 2422fc305beSStephen Boyd if (ret) 2432fc305beSStephen Boyd goto err_mux; 2442fc305beSStephen Boyd 245964728f9SJohan Hovold ulpi_node = of_get_child_by_name(pdev->dev.of_node, "ulpi"); 24647654a16SStephen Boyd if (ulpi_node) { 24747654a16SStephen Boyd phy_node = of_get_next_available_child(ulpi_node, NULL); 24847654a16SStephen Boyd ci->hsic = of_device_is_compatible(phy_node, "qcom,usb-hsic-phy"); 24947654a16SStephen Boyd of_node_put(phy_node); 25047654a16SStephen Boyd } 25147654a16SStephen Boyd of_node_put(ulpi_node); 25247654a16SStephen Boyd 25326f8e3a8SStephen Boyd plat_ci = ci_hdrc_add_device(&pdev->dev, pdev->resource, 25426f8e3a8SStephen Boyd pdev->num_resources, &ci->pdata); 2558e22978cSAlexander Shishkin if (IS_ERR(plat_ci)) { 256e9f15a71SStephen Boyd ret = PTR_ERR(plat_ci); 257ed04f19fSStephen Boyd if (ret != -EPROBE_DEFER) 258ed04f19fSStephen Boyd dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n"); 259e9f15a71SStephen Boyd goto err_mux; 2608e22978cSAlexander Shishkin } 2618e22978cSAlexander Shishkin 262e9f15a71SStephen Boyd ci->ci = plat_ci; 2638e22978cSAlexander Shishkin 2642c8ea46dSStephen Boyd pm_runtime_set_active(&pdev->dev); 2658e22978cSAlexander Shishkin pm_runtime_no_callbacks(&pdev->dev); 2668e22978cSAlexander Shishkin pm_runtime_enable(&pdev->dev); 2678e22978cSAlexander Shishkin 2688e22978cSAlexander Shishkin return 0; 269e9f15a71SStephen Boyd 270e9f15a71SStephen Boyd err_mux: 271e9f15a71SStephen Boyd clk_disable_unprepare(ci->iface_clk); 272e9f15a71SStephen Boyd err_iface: 273e9f15a71SStephen Boyd clk_disable_unprepare(ci->core_clk); 274e9f15a71SStephen Boyd return ret; 2758e22978cSAlexander Shishkin } 2768e22978cSAlexander Shishkin 2778e22978cSAlexander Shishkin static int ci_hdrc_msm_remove(struct platform_device *pdev) 2788e22978cSAlexander Shishkin { 279e9f15a71SStephen Boyd struct ci_hdrc_msm *ci = platform_get_drvdata(pdev); 2808e22978cSAlexander Shishkin 2818e22978cSAlexander Shishkin pm_runtime_disable(&pdev->dev); 282e9f15a71SStephen Boyd ci_hdrc_remove_device(ci->ci); 283e9f15a71SStephen Boyd clk_disable_unprepare(ci->iface_clk); 284e9f15a71SStephen Boyd clk_disable_unprepare(ci->core_clk); 2858e22978cSAlexander Shishkin 2868e22978cSAlexander Shishkin return 0; 2878e22978cSAlexander Shishkin } 2888e22978cSAlexander Shishkin 2892629b101SIvan T. Ivanov static const struct of_device_id msm_ci_dt_match[] = { 2902629b101SIvan T. Ivanov { .compatible = "qcom,ci-hdrc", }, 2912629b101SIvan T. Ivanov { } 2922629b101SIvan T. Ivanov }; 2932629b101SIvan T. Ivanov MODULE_DEVICE_TABLE(of, msm_ci_dt_match); 2942629b101SIvan T. Ivanov 2958e22978cSAlexander Shishkin static struct platform_driver ci_hdrc_msm_driver = { 2968e22978cSAlexander Shishkin .probe = ci_hdrc_msm_probe, 2978e22978cSAlexander Shishkin .remove = ci_hdrc_msm_remove, 2982629b101SIvan T. Ivanov .driver = { 2992629b101SIvan T. Ivanov .name = "msm_hsusb", 3002629b101SIvan T. Ivanov .of_match_table = msm_ci_dt_match, 3012629b101SIvan T. Ivanov }, 3028e22978cSAlexander Shishkin }; 3038e22978cSAlexander Shishkin 3048e22978cSAlexander Shishkin module_platform_driver(ci_hdrc_msm_driver); 3058e22978cSAlexander Shishkin 3068e22978cSAlexander Shishkin MODULE_ALIAS("platform:msm_hsusb"); 3078e22978cSAlexander Shishkin MODULE_ALIAS("platform:ci13xxx_msm"); 3088e22978cSAlexander Shishkin MODULE_LICENSE("GPL v2"); 309