1*5fd54aceSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0 28e22978cSAlexander Shishkin /* Copyright (c) 2010, Code Aurora Forum. All rights reserved. 38e22978cSAlexander Shishkin * 48e22978cSAlexander Shishkin * This program is free software; you can redistribute it and/or modify 58e22978cSAlexander Shishkin * it under the terms of the GNU General Public License version 2 and 68e22978cSAlexander Shishkin * only version 2 as published by the Free Software Foundation. 78e22978cSAlexander Shishkin */ 88e22978cSAlexander Shishkin 98e22978cSAlexander Shishkin #include <linux/module.h> 108e22978cSAlexander Shishkin #include <linux/platform_device.h> 118e22978cSAlexander Shishkin #include <linux/pm_runtime.h> 128e22978cSAlexander Shishkin #include <linux/usb/chipidea.h> 13e9f15a71SStephen Boyd #include <linux/clk.h> 14e9f15a71SStephen Boyd #include <linux/reset.h> 152fc305beSStephen Boyd #include <linux/mfd/syscon.h> 162fc305beSStephen Boyd #include <linux/regmap.h> 172fc305beSStephen Boyd #include <linux/io.h> 181b8fc5a5SStephen Boyd #include <linux/reset-controller.h> 1947654a16SStephen Boyd #include <linux/extcon.h> 2047654a16SStephen Boyd #include <linux/of.h> 218e22978cSAlexander Shishkin 228e22978cSAlexander Shishkin #include "ci.h" 238e22978cSAlexander Shishkin 24ee33f6e7SStephen Boyd #define HS_PHY_AHB_MODE 0x0098 258e22978cSAlexander Shishkin 2647654a16SStephen Boyd #define HS_PHY_GENCONFIG 0x009c 2747654a16SStephen Boyd #define HS_PHY_TXFIFO_IDLE_FORCE_DIS BIT(4) 2847654a16SStephen Boyd 2947654a16SStephen Boyd #define HS_PHY_GENCONFIG_2 0x00a0 3047654a16SStephen Boyd #define HS_PHY_SESS_VLD_CTRL_EN BIT(7) 3147654a16SStephen Boyd #define HS_PHY_ULPI_TX_PKT_EN_CLR_FIX BIT(19) 3247654a16SStephen Boyd 3347654a16SStephen Boyd #define HSPHY_SESS_VLD_CTRL BIT(25) 3447654a16SStephen Boyd 352fc305beSStephen Boyd /* Vendor base starts at 0x200 beyond CI base */ 361b8fc5a5SStephen Boyd #define HS_PHY_CTRL 0x0040 372fc305beSStephen Boyd #define HS_PHY_SEC_CTRL 0x0078 382fc305beSStephen Boyd #define HS_PHY_DIG_CLAMP_N BIT(16) 391b8fc5a5SStephen Boyd #define HS_PHY_POR_ASSERT BIT(0) 402fc305beSStephen Boyd 41e9f15a71SStephen Boyd struct ci_hdrc_msm { 42e9f15a71SStephen Boyd struct platform_device *ci; 43e9f15a71SStephen Boyd struct clk *core_clk; 44e9f15a71SStephen Boyd struct clk *iface_clk; 45e9f15a71SStephen Boyd struct clk *fs_clk; 4626f8e3a8SStephen Boyd struct ci_hdrc_platform_data pdata; 471b8fc5a5SStephen Boyd struct reset_controller_dev rcdev; 482fc305beSStephen Boyd bool secondary_phy; 4947654a16SStephen Boyd bool hsic; 502fc305beSStephen Boyd void __iomem *base; 51e9f15a71SStephen Boyd }; 52e9f15a71SStephen Boyd 531b8fc5a5SStephen Boyd static int 541b8fc5a5SStephen Boyd ci_hdrc_msm_por_reset(struct reset_controller_dev *r, unsigned long id) 551b8fc5a5SStephen Boyd { 561b8fc5a5SStephen Boyd struct ci_hdrc_msm *ci_msm = container_of(r, struct ci_hdrc_msm, rcdev); 571b8fc5a5SStephen Boyd void __iomem *addr = ci_msm->base; 581b8fc5a5SStephen Boyd u32 val; 591b8fc5a5SStephen Boyd 601b8fc5a5SStephen Boyd if (id) 611b8fc5a5SStephen Boyd addr += HS_PHY_SEC_CTRL; 621b8fc5a5SStephen Boyd else 631b8fc5a5SStephen Boyd addr += HS_PHY_CTRL; 641b8fc5a5SStephen Boyd 651b8fc5a5SStephen Boyd val = readl_relaxed(addr); 661b8fc5a5SStephen Boyd val |= HS_PHY_POR_ASSERT; 671b8fc5a5SStephen Boyd writel(val, addr); 681b8fc5a5SStephen Boyd /* 691b8fc5a5SStephen Boyd * wait for minimum 10 microseconds as suggested by manual. 701b8fc5a5SStephen Boyd * Use a slightly larger value since the exact value didn't 711b8fc5a5SStephen Boyd * work 100% of the time. 721b8fc5a5SStephen Boyd */ 731b8fc5a5SStephen Boyd udelay(12); 741b8fc5a5SStephen Boyd val &= ~HS_PHY_POR_ASSERT; 751b8fc5a5SStephen Boyd writel(val, addr); 761b8fc5a5SStephen Boyd 771b8fc5a5SStephen Boyd return 0; 781b8fc5a5SStephen Boyd } 791b8fc5a5SStephen Boyd 801b8fc5a5SStephen Boyd static const struct reset_control_ops ci_hdrc_msm_reset_ops = { 811b8fc5a5SStephen Boyd .reset = ci_hdrc_msm_por_reset, 821b8fc5a5SStephen Boyd }; 831b8fc5a5SStephen Boyd 8411893daeSStephen Boyd static int ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event) 858e22978cSAlexander Shishkin { 862fc305beSStephen Boyd struct device *dev = ci->dev->parent; 872fc305beSStephen Boyd struct ci_hdrc_msm *msm_ci = dev_get_drvdata(dev); 8811893daeSStephen Boyd int ret; 898e22978cSAlexander Shishkin 908e22978cSAlexander Shishkin switch (event) { 918e22978cSAlexander Shishkin case CI_HDRC_CONTROLLER_RESET_EVENT: 928e22978cSAlexander Shishkin dev_dbg(dev, "CI_HDRC_CONTROLLER_RESET_EVENT received\n"); 9311893daeSStephen Boyd 9411893daeSStephen Boyd hw_phymode_configure(ci); 952fc305beSStephen Boyd if (msm_ci->secondary_phy) { 962fc305beSStephen Boyd u32 val = readl_relaxed(msm_ci->base + HS_PHY_SEC_CTRL); 972fc305beSStephen Boyd val |= HS_PHY_DIG_CLAMP_N; 982fc305beSStephen Boyd writel_relaxed(val, msm_ci->base + HS_PHY_SEC_CTRL); 992fc305beSStephen Boyd } 1002fc305beSStephen Boyd 10111893daeSStephen Boyd ret = phy_init(ci->phy); 10211893daeSStephen Boyd if (ret) 10311893daeSStephen Boyd return ret; 10411893daeSStephen Boyd 10511893daeSStephen Boyd ret = phy_power_on(ci->phy); 10611893daeSStephen Boyd if (ret) { 10711893daeSStephen Boyd phy_exit(ci->phy); 10811893daeSStephen Boyd return ret; 10911893daeSStephen Boyd } 11011893daeSStephen Boyd 1115ce7d27dSAndy Gross /* use AHB transactor, allow posted data writes */ 112ee33f6e7SStephen Boyd hw_write_id_reg(ci, HS_PHY_AHB_MODE, 0xffffffff, 0x8); 11347654a16SStephen Boyd 11447654a16SStephen Boyd /* workaround for rx buffer collision issue */ 11547654a16SStephen Boyd hw_write_id_reg(ci, HS_PHY_GENCONFIG, 11647654a16SStephen Boyd HS_PHY_TXFIFO_IDLE_FORCE_DIS, 0); 11747654a16SStephen Boyd 11847654a16SStephen Boyd if (!msm_ci->hsic) 11947654a16SStephen Boyd hw_write_id_reg(ci, HS_PHY_GENCONFIG_2, 12047654a16SStephen Boyd HS_PHY_ULPI_TX_PKT_EN_CLR_FIX, 0); 12147654a16SStephen Boyd 12247654a16SStephen Boyd if (!IS_ERR(ci->platdata->vbus_extcon.edev)) { 12347654a16SStephen Boyd hw_write_id_reg(ci, HS_PHY_GENCONFIG_2, 12447654a16SStephen Boyd HS_PHY_SESS_VLD_CTRL_EN, 12547654a16SStephen Boyd HS_PHY_SESS_VLD_CTRL_EN); 12647654a16SStephen Boyd hw_write(ci, OP_USBCMD, HSPHY_SESS_VLD_CTRL, 12747654a16SStephen Boyd HSPHY_SESS_VLD_CTRL); 12847654a16SStephen Boyd 12947654a16SStephen Boyd } 1308e22978cSAlexander Shishkin break; 1318e22978cSAlexander Shishkin case CI_HDRC_CONTROLLER_STOPPED_EVENT: 1328e22978cSAlexander Shishkin dev_dbg(dev, "CI_HDRC_CONTROLLER_STOPPED_EVENT received\n"); 13311893daeSStephen Boyd phy_power_off(ci->phy); 13411893daeSStephen Boyd phy_exit(ci->phy); 1358e22978cSAlexander Shishkin break; 1368e22978cSAlexander Shishkin default: 1378e22978cSAlexander Shishkin dev_dbg(dev, "unknown ci_hdrc event\n"); 1388e22978cSAlexander Shishkin break; 1398e22978cSAlexander Shishkin } 14011893daeSStephen Boyd 14111893daeSStephen Boyd return 0; 1428e22978cSAlexander Shishkin } 1438e22978cSAlexander Shishkin 1442fc305beSStephen Boyd static int ci_hdrc_msm_mux_phy(struct ci_hdrc_msm *ci, 1452fc305beSStephen Boyd struct platform_device *pdev) 1462fc305beSStephen Boyd { 1472fc305beSStephen Boyd struct regmap *regmap; 1482fc305beSStephen Boyd struct device *dev = &pdev->dev; 1492fc305beSStephen Boyd struct of_phandle_args args; 1502fc305beSStephen Boyd u32 val; 1512fc305beSStephen Boyd int ret; 1522fc305beSStephen Boyd 1532fc305beSStephen Boyd ret = of_parse_phandle_with_fixed_args(dev->of_node, "phy-select", 2, 0, 1542fc305beSStephen Boyd &args); 1552fc305beSStephen Boyd if (ret) 1562fc305beSStephen Boyd return 0; 1572fc305beSStephen Boyd 1582fc305beSStephen Boyd regmap = syscon_node_to_regmap(args.np); 1592fc305beSStephen Boyd of_node_put(args.np); 1602fc305beSStephen Boyd if (IS_ERR(regmap)) 1612fc305beSStephen Boyd return PTR_ERR(regmap); 1622fc305beSStephen Boyd 1632fc305beSStephen Boyd ret = regmap_write(regmap, args.args[0], args.args[1]); 1642fc305beSStephen Boyd if (ret) 1652fc305beSStephen Boyd return ret; 1662fc305beSStephen Boyd 1672fc305beSStephen Boyd ci->secondary_phy = !!args.args[1]; 1682fc305beSStephen Boyd if (ci->secondary_phy) { 1692fc305beSStephen Boyd val = readl_relaxed(ci->base + HS_PHY_SEC_CTRL); 1702fc305beSStephen Boyd val |= HS_PHY_DIG_CLAMP_N; 1712fc305beSStephen Boyd writel_relaxed(val, ci->base + HS_PHY_SEC_CTRL); 1722fc305beSStephen Boyd } 1732fc305beSStephen Boyd 1742fc305beSStephen Boyd return 0; 1752fc305beSStephen Boyd } 1762fc305beSStephen Boyd 1778e22978cSAlexander Shishkin static int ci_hdrc_msm_probe(struct platform_device *pdev) 1788e22978cSAlexander Shishkin { 179e9f15a71SStephen Boyd struct ci_hdrc_msm *ci; 1808e22978cSAlexander Shishkin struct platform_device *plat_ci; 181e9f15a71SStephen Boyd struct clk *clk; 182e9f15a71SStephen Boyd struct reset_control *reset; 1832fc305beSStephen Boyd struct resource *res; 184e9f15a71SStephen Boyd int ret; 18547654a16SStephen Boyd struct device_node *ulpi_node, *phy_node; 1868e22978cSAlexander Shishkin 1878e22978cSAlexander Shishkin dev_dbg(&pdev->dev, "ci_hdrc_msm_probe\n"); 1888e22978cSAlexander Shishkin 189e9f15a71SStephen Boyd ci = devm_kzalloc(&pdev->dev, sizeof(*ci), GFP_KERNEL); 190e9f15a71SStephen Boyd if (!ci) 191e9f15a71SStephen Boyd return -ENOMEM; 192e9f15a71SStephen Boyd platform_set_drvdata(pdev, ci); 193e9f15a71SStephen Boyd 19426f8e3a8SStephen Boyd ci->pdata.name = "ci_hdrc_msm"; 19526f8e3a8SStephen Boyd ci->pdata.capoffset = DEF_CAPOFFSET; 19626f8e3a8SStephen Boyd ci->pdata.flags = CI_HDRC_REGS_SHARED | CI_HDRC_DISABLE_STREAMING | 19711893daeSStephen Boyd CI_HDRC_OVERRIDE_AHB_BURST | 19811893daeSStephen Boyd CI_HDRC_OVERRIDE_PHY_CONTROL; 19926f8e3a8SStephen Boyd ci->pdata.notify_event = ci_hdrc_msm_notify_event; 2002629b101SIvan T. Ivanov 201e9f15a71SStephen Boyd reset = devm_reset_control_get(&pdev->dev, "core"); 202e9f15a71SStephen Boyd if (IS_ERR(reset)) 203e9f15a71SStephen Boyd return PTR_ERR(reset); 204e9f15a71SStephen Boyd 205e9f15a71SStephen Boyd ci->core_clk = clk = devm_clk_get(&pdev->dev, "core"); 206e9f15a71SStephen Boyd if (IS_ERR(clk)) 207e9f15a71SStephen Boyd return PTR_ERR(clk); 208e9f15a71SStephen Boyd 209e9f15a71SStephen Boyd ci->iface_clk = clk = devm_clk_get(&pdev->dev, "iface"); 210e9f15a71SStephen Boyd if (IS_ERR(clk)) 211e9f15a71SStephen Boyd return PTR_ERR(clk); 212e9f15a71SStephen Boyd 213e9f15a71SStephen Boyd ci->fs_clk = clk = devm_clk_get(&pdev->dev, "fs"); 214e9f15a71SStephen Boyd if (IS_ERR(clk)) { 215e9f15a71SStephen Boyd if (PTR_ERR(clk) == -EPROBE_DEFER) 216e9f15a71SStephen Boyd return -EPROBE_DEFER; 217e9f15a71SStephen Boyd ci->fs_clk = NULL; 218e9f15a71SStephen Boyd } 219e9f15a71SStephen Boyd 2202fc305beSStephen Boyd res = platform_get_resource(pdev, IORESOURCE_MEM, 1); 2212fc305beSStephen Boyd ci->base = devm_ioremap_resource(&pdev->dev, res); 222753dfd23SWei Yongjun if (IS_ERR(ci->base)) 223753dfd23SWei Yongjun return PTR_ERR(ci->base); 2242fc305beSStephen Boyd 2251b8fc5a5SStephen Boyd ci->rcdev.owner = THIS_MODULE; 2261b8fc5a5SStephen Boyd ci->rcdev.ops = &ci_hdrc_msm_reset_ops; 2271b8fc5a5SStephen Boyd ci->rcdev.of_node = pdev->dev.of_node; 2281b8fc5a5SStephen Boyd ci->rcdev.nr_resets = 2; 2291b8fc5a5SStephen Boyd ret = reset_controller_register(&ci->rcdev); 230e9f15a71SStephen Boyd if (ret) 231e9f15a71SStephen Boyd return ret; 232e9f15a71SStephen Boyd 2331b8fc5a5SStephen Boyd ret = clk_prepare_enable(ci->fs_clk); 2341b8fc5a5SStephen Boyd if (ret) 2351b8fc5a5SStephen Boyd goto err_fs; 2361b8fc5a5SStephen Boyd 237e9f15a71SStephen Boyd reset_control_assert(reset); 238e9f15a71SStephen Boyd usleep_range(10000, 12000); 239e9f15a71SStephen Boyd reset_control_deassert(reset); 240e9f15a71SStephen Boyd 241e9f15a71SStephen Boyd clk_disable_unprepare(ci->fs_clk); 242e9f15a71SStephen Boyd 243e9f15a71SStephen Boyd ret = clk_prepare_enable(ci->core_clk); 244e9f15a71SStephen Boyd if (ret) 2451b8fc5a5SStephen Boyd goto err_fs; 246e9f15a71SStephen Boyd 247e9f15a71SStephen Boyd ret = clk_prepare_enable(ci->iface_clk); 248e9f15a71SStephen Boyd if (ret) 249e9f15a71SStephen Boyd goto err_iface; 250e9f15a71SStephen Boyd 2512fc305beSStephen Boyd ret = ci_hdrc_msm_mux_phy(ci, pdev); 2522fc305beSStephen Boyd if (ret) 2532fc305beSStephen Boyd goto err_mux; 2542fc305beSStephen Boyd 255b74c4315SFrank Rowand ulpi_node = of_find_node_by_name(of_node_get(pdev->dev.of_node), "ulpi"); 25647654a16SStephen Boyd if (ulpi_node) { 25747654a16SStephen Boyd phy_node = of_get_next_available_child(ulpi_node, NULL); 25847654a16SStephen Boyd ci->hsic = of_device_is_compatible(phy_node, "qcom,usb-hsic-phy"); 25947654a16SStephen Boyd of_node_put(phy_node); 26047654a16SStephen Boyd } 26147654a16SStephen Boyd of_node_put(ulpi_node); 26247654a16SStephen Boyd 26326f8e3a8SStephen Boyd plat_ci = ci_hdrc_add_device(&pdev->dev, pdev->resource, 26426f8e3a8SStephen Boyd pdev->num_resources, &ci->pdata); 2658e22978cSAlexander Shishkin if (IS_ERR(plat_ci)) { 266e9f15a71SStephen Boyd ret = PTR_ERR(plat_ci); 267ed04f19fSStephen Boyd if (ret != -EPROBE_DEFER) 268ed04f19fSStephen Boyd dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n"); 269e9f15a71SStephen Boyd goto err_mux; 2708e22978cSAlexander Shishkin } 2718e22978cSAlexander Shishkin 272e9f15a71SStephen Boyd ci->ci = plat_ci; 2738e22978cSAlexander Shishkin 2742c8ea46dSStephen Boyd pm_runtime_set_active(&pdev->dev); 2758e22978cSAlexander Shishkin pm_runtime_no_callbacks(&pdev->dev); 2768e22978cSAlexander Shishkin pm_runtime_enable(&pdev->dev); 2778e22978cSAlexander Shishkin 2788e22978cSAlexander Shishkin return 0; 279e9f15a71SStephen Boyd 280e9f15a71SStephen Boyd err_mux: 281e9f15a71SStephen Boyd clk_disable_unprepare(ci->iface_clk); 282e9f15a71SStephen Boyd err_iface: 283e9f15a71SStephen Boyd clk_disable_unprepare(ci->core_clk); 2841b8fc5a5SStephen Boyd err_fs: 2851b8fc5a5SStephen Boyd reset_controller_unregister(&ci->rcdev); 286e9f15a71SStephen Boyd return ret; 2878e22978cSAlexander Shishkin } 2888e22978cSAlexander Shishkin 2898e22978cSAlexander Shishkin static int ci_hdrc_msm_remove(struct platform_device *pdev) 2908e22978cSAlexander Shishkin { 291e9f15a71SStephen Boyd struct ci_hdrc_msm *ci = platform_get_drvdata(pdev); 2928e22978cSAlexander Shishkin 2938e22978cSAlexander Shishkin pm_runtime_disable(&pdev->dev); 294e9f15a71SStephen Boyd ci_hdrc_remove_device(ci->ci); 295e9f15a71SStephen Boyd clk_disable_unprepare(ci->iface_clk); 296e9f15a71SStephen Boyd clk_disable_unprepare(ci->core_clk); 2971b8fc5a5SStephen Boyd reset_controller_unregister(&ci->rcdev); 2988e22978cSAlexander Shishkin 2998e22978cSAlexander Shishkin return 0; 3008e22978cSAlexander Shishkin } 3018e22978cSAlexander Shishkin 3022629b101SIvan T. Ivanov static const struct of_device_id msm_ci_dt_match[] = { 3032629b101SIvan T. Ivanov { .compatible = "qcom,ci-hdrc", }, 3042629b101SIvan T. Ivanov { } 3052629b101SIvan T. Ivanov }; 3062629b101SIvan T. Ivanov MODULE_DEVICE_TABLE(of, msm_ci_dt_match); 3072629b101SIvan T. Ivanov 3088e22978cSAlexander Shishkin static struct platform_driver ci_hdrc_msm_driver = { 3098e22978cSAlexander Shishkin .probe = ci_hdrc_msm_probe, 3108e22978cSAlexander Shishkin .remove = ci_hdrc_msm_remove, 3112629b101SIvan T. Ivanov .driver = { 3122629b101SIvan T. Ivanov .name = "msm_hsusb", 3132629b101SIvan T. Ivanov .of_match_table = msm_ci_dt_match, 3142629b101SIvan T. Ivanov }, 3158e22978cSAlexander Shishkin }; 3168e22978cSAlexander Shishkin 3178e22978cSAlexander Shishkin module_platform_driver(ci_hdrc_msm_driver); 3188e22978cSAlexander Shishkin 3198e22978cSAlexander Shishkin MODULE_ALIAS("platform:msm_hsusb"); 3208e22978cSAlexander Shishkin MODULE_ALIAS("platform:ci13xxx_msm"); 3218e22978cSAlexander Shishkin MODULE_LICENSE("GPL v2"); 322