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*47654a16SStephen Boyd #include <linux/extcon.h> 18*47654a16SStephen Boyd #include <linux/of.h> 198e22978cSAlexander Shishkin 208e22978cSAlexander Shishkin #include "ci.h" 218e22978cSAlexander Shishkin 22ee33f6e7SStephen Boyd #define HS_PHY_AHB_MODE 0x0098 238e22978cSAlexander Shishkin 24*47654a16SStephen Boyd #define HS_PHY_GENCONFIG 0x009c 25*47654a16SStephen Boyd #define HS_PHY_TXFIFO_IDLE_FORCE_DIS BIT(4) 26*47654a16SStephen Boyd 27*47654a16SStephen Boyd #define HS_PHY_GENCONFIG_2 0x00a0 28*47654a16SStephen Boyd #define HS_PHY_SESS_VLD_CTRL_EN BIT(7) 29*47654a16SStephen Boyd #define HS_PHY_ULPI_TX_PKT_EN_CLR_FIX BIT(19) 30*47654a16SStephen Boyd 31*47654a16SStephen Boyd #define HSPHY_SESS_VLD_CTRL BIT(25) 32*47654a16SStephen Boyd 332fc305beSStephen Boyd /* Vendor base starts at 0x200 beyond CI base */ 342fc305beSStephen Boyd #define HS_PHY_SEC_CTRL 0x0078 352fc305beSStephen Boyd #define HS_PHY_DIG_CLAMP_N BIT(16) 362fc305beSStephen Boyd 37e9f15a71SStephen Boyd struct ci_hdrc_msm { 38e9f15a71SStephen Boyd struct platform_device *ci; 39e9f15a71SStephen Boyd struct clk *core_clk; 40e9f15a71SStephen Boyd struct clk *iface_clk; 41e9f15a71SStephen Boyd struct clk *fs_clk; 422fc305beSStephen Boyd bool secondary_phy; 43*47654a16SStephen Boyd bool hsic; 442fc305beSStephen Boyd void __iomem *base; 45e9f15a71SStephen Boyd }; 46e9f15a71SStephen Boyd 478e22978cSAlexander Shishkin static void ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event) 488e22978cSAlexander Shishkin { 492fc305beSStephen Boyd struct device *dev = ci->dev->parent; 502fc305beSStephen Boyd struct ci_hdrc_msm *msm_ci = dev_get_drvdata(dev); 518e22978cSAlexander Shishkin 528e22978cSAlexander Shishkin switch (event) { 538e22978cSAlexander Shishkin case CI_HDRC_CONTROLLER_RESET_EVENT: 548e22978cSAlexander Shishkin dev_dbg(dev, "CI_HDRC_CONTROLLER_RESET_EVENT received\n"); 552fc305beSStephen Boyd if (msm_ci->secondary_phy) { 562fc305beSStephen Boyd u32 val = readl_relaxed(msm_ci->base + HS_PHY_SEC_CTRL); 572fc305beSStephen Boyd val |= HS_PHY_DIG_CLAMP_N; 582fc305beSStephen Boyd writel_relaxed(val, msm_ci->base + HS_PHY_SEC_CTRL); 592fc305beSStephen Boyd } 602fc305beSStephen Boyd 615ce7d27dSAndy Gross /* use AHB transactor, allow posted data writes */ 62ee33f6e7SStephen Boyd hw_write_id_reg(ci, HS_PHY_AHB_MODE, 0xffffffff, 0x8); 63*47654a16SStephen Boyd 64*47654a16SStephen Boyd /* workaround for rx buffer collision issue */ 65*47654a16SStephen Boyd hw_write_id_reg(ci, HS_PHY_GENCONFIG, 66*47654a16SStephen Boyd HS_PHY_TXFIFO_IDLE_FORCE_DIS, 0); 67*47654a16SStephen Boyd 68*47654a16SStephen Boyd if (!msm_ci->hsic) 69*47654a16SStephen Boyd hw_write_id_reg(ci, HS_PHY_GENCONFIG_2, 70*47654a16SStephen Boyd HS_PHY_ULPI_TX_PKT_EN_CLR_FIX, 0); 71*47654a16SStephen Boyd 72*47654a16SStephen Boyd if (!IS_ERR(ci->platdata->vbus_extcon.edev)) { 73*47654a16SStephen Boyd hw_write_id_reg(ci, HS_PHY_GENCONFIG_2, 74*47654a16SStephen Boyd HS_PHY_SESS_VLD_CTRL_EN, 75*47654a16SStephen Boyd HS_PHY_SESS_VLD_CTRL_EN); 76*47654a16SStephen Boyd hw_write(ci, OP_USBCMD, HSPHY_SESS_VLD_CTRL, 77*47654a16SStephen Boyd HSPHY_SESS_VLD_CTRL); 78*47654a16SStephen Boyd 79*47654a16SStephen Boyd } 80*47654a16SStephen Boyd 81ef44cb42SAntoine Tenart usb_phy_init(ci->usb_phy); 828e22978cSAlexander Shishkin break; 838e22978cSAlexander Shishkin case CI_HDRC_CONTROLLER_STOPPED_EVENT: 848e22978cSAlexander Shishkin dev_dbg(dev, "CI_HDRC_CONTROLLER_STOPPED_EVENT received\n"); 858e22978cSAlexander Shishkin /* 86ef44cb42SAntoine Tenart * Put the phy in non-driving mode. Otherwise host 878e22978cSAlexander Shishkin * may not detect soft-disconnection. 888e22978cSAlexander Shishkin */ 89ef44cb42SAntoine Tenart usb_phy_notify_disconnect(ci->usb_phy, USB_SPEED_UNKNOWN); 908e22978cSAlexander Shishkin break; 918e22978cSAlexander Shishkin default: 928e22978cSAlexander Shishkin dev_dbg(dev, "unknown ci_hdrc event\n"); 938e22978cSAlexander Shishkin break; 948e22978cSAlexander Shishkin } 958e22978cSAlexander Shishkin } 968e22978cSAlexander Shishkin 978e22978cSAlexander Shishkin static struct ci_hdrc_platform_data ci_hdrc_msm_platdata = { 988e22978cSAlexander Shishkin .name = "ci_hdrc_msm", 993c6d9826SIvan T. Ivanov .capoffset = DEF_CAPOFFSET, 1008e22978cSAlexander Shishkin .flags = CI_HDRC_REGS_SHARED | 101dd374909SStephen Boyd CI_HDRC_DISABLE_STREAMING | 102dd374909SStephen Boyd CI_HDRC_OVERRIDE_AHB_BURST, 1038e22978cSAlexander Shishkin 1048e22978cSAlexander Shishkin .notify_event = ci_hdrc_msm_notify_event, 1058e22978cSAlexander Shishkin }; 1068e22978cSAlexander Shishkin 1072fc305beSStephen Boyd static int ci_hdrc_msm_mux_phy(struct ci_hdrc_msm *ci, 1082fc305beSStephen Boyd struct platform_device *pdev) 1092fc305beSStephen Boyd { 1102fc305beSStephen Boyd struct regmap *regmap; 1112fc305beSStephen Boyd struct device *dev = &pdev->dev; 1122fc305beSStephen Boyd struct of_phandle_args args; 1132fc305beSStephen Boyd u32 val; 1142fc305beSStephen Boyd int ret; 1152fc305beSStephen Boyd 1162fc305beSStephen Boyd ret = of_parse_phandle_with_fixed_args(dev->of_node, "phy-select", 2, 0, 1172fc305beSStephen Boyd &args); 1182fc305beSStephen Boyd if (ret) 1192fc305beSStephen Boyd return 0; 1202fc305beSStephen Boyd 1212fc305beSStephen Boyd regmap = syscon_node_to_regmap(args.np); 1222fc305beSStephen Boyd of_node_put(args.np); 1232fc305beSStephen Boyd if (IS_ERR(regmap)) 1242fc305beSStephen Boyd return PTR_ERR(regmap); 1252fc305beSStephen Boyd 1262fc305beSStephen Boyd ret = regmap_write(regmap, args.args[0], args.args[1]); 1272fc305beSStephen Boyd if (ret) 1282fc305beSStephen Boyd return ret; 1292fc305beSStephen Boyd 1302fc305beSStephen Boyd ci->secondary_phy = !!args.args[1]; 1312fc305beSStephen Boyd if (ci->secondary_phy) { 1322fc305beSStephen Boyd val = readl_relaxed(ci->base + HS_PHY_SEC_CTRL); 1332fc305beSStephen Boyd val |= HS_PHY_DIG_CLAMP_N; 1342fc305beSStephen Boyd writel_relaxed(val, ci->base + HS_PHY_SEC_CTRL); 1352fc305beSStephen Boyd } 1362fc305beSStephen Boyd 1372fc305beSStephen Boyd return 0; 1382fc305beSStephen Boyd } 1392fc305beSStephen Boyd 1408e22978cSAlexander Shishkin static int ci_hdrc_msm_probe(struct platform_device *pdev) 1418e22978cSAlexander Shishkin { 142e9f15a71SStephen Boyd struct ci_hdrc_msm *ci; 1438e22978cSAlexander Shishkin struct platform_device *plat_ci; 1442629b101SIvan T. Ivanov struct usb_phy *phy; 145e9f15a71SStephen Boyd struct clk *clk; 146e9f15a71SStephen Boyd struct reset_control *reset; 1472fc305beSStephen Boyd struct resource *res; 148e9f15a71SStephen Boyd int ret; 149*47654a16SStephen Boyd struct device_node *ulpi_node, *phy_node; 1508e22978cSAlexander Shishkin 1518e22978cSAlexander Shishkin dev_dbg(&pdev->dev, "ci_hdrc_msm_probe\n"); 1528e22978cSAlexander Shishkin 153e9f15a71SStephen Boyd ci = devm_kzalloc(&pdev->dev, sizeof(*ci), GFP_KERNEL); 154e9f15a71SStephen Boyd if (!ci) 155e9f15a71SStephen Boyd return -ENOMEM; 156e9f15a71SStephen Boyd platform_set_drvdata(pdev, ci); 157e9f15a71SStephen Boyd 1582629b101SIvan T. Ivanov /* 1592629b101SIvan T. Ivanov * OTG(PHY) driver takes care of PHY initialization, clock management, 1602629b101SIvan T. Ivanov * powering up VBUS, mapping of registers address space and power 1612629b101SIvan T. Ivanov * management. 1622629b101SIvan T. Ivanov */ 1632629b101SIvan T. Ivanov phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0); 1642629b101SIvan T. Ivanov if (IS_ERR(phy)) 1652629b101SIvan T. Ivanov return PTR_ERR(phy); 1662629b101SIvan T. Ivanov 167ef44cb42SAntoine Tenart ci_hdrc_msm_platdata.usb_phy = phy; 1682629b101SIvan T. Ivanov 169e9f15a71SStephen Boyd reset = devm_reset_control_get(&pdev->dev, "core"); 170e9f15a71SStephen Boyd if (IS_ERR(reset)) 171e9f15a71SStephen Boyd return PTR_ERR(reset); 172e9f15a71SStephen Boyd 173e9f15a71SStephen Boyd ci->core_clk = clk = devm_clk_get(&pdev->dev, "core"); 174e9f15a71SStephen Boyd if (IS_ERR(clk)) 175e9f15a71SStephen Boyd return PTR_ERR(clk); 176e9f15a71SStephen Boyd 177e9f15a71SStephen Boyd ci->iface_clk = clk = devm_clk_get(&pdev->dev, "iface"); 178e9f15a71SStephen Boyd if (IS_ERR(clk)) 179e9f15a71SStephen Boyd return PTR_ERR(clk); 180e9f15a71SStephen Boyd 181e9f15a71SStephen Boyd ci->fs_clk = clk = devm_clk_get(&pdev->dev, "fs"); 182e9f15a71SStephen Boyd if (IS_ERR(clk)) { 183e9f15a71SStephen Boyd if (PTR_ERR(clk) == -EPROBE_DEFER) 184e9f15a71SStephen Boyd return -EPROBE_DEFER; 185e9f15a71SStephen Boyd ci->fs_clk = NULL; 186e9f15a71SStephen Boyd } 187e9f15a71SStephen Boyd 1882fc305beSStephen Boyd res = platform_get_resource(pdev, IORESOURCE_MEM, 1); 1892fc305beSStephen Boyd ci->base = devm_ioremap_resource(&pdev->dev, res); 1902fc305beSStephen Boyd if (!ci->base) 1912fc305beSStephen Boyd return -ENOMEM; 1922fc305beSStephen Boyd 193e9f15a71SStephen Boyd ret = clk_prepare_enable(ci->fs_clk); 194e9f15a71SStephen Boyd if (ret) 195e9f15a71SStephen Boyd return ret; 196e9f15a71SStephen Boyd 197e9f15a71SStephen Boyd reset_control_assert(reset); 198e9f15a71SStephen Boyd usleep_range(10000, 12000); 199e9f15a71SStephen Boyd reset_control_deassert(reset); 200e9f15a71SStephen Boyd 201e9f15a71SStephen Boyd clk_disable_unprepare(ci->fs_clk); 202e9f15a71SStephen Boyd 203e9f15a71SStephen Boyd ret = clk_prepare_enable(ci->core_clk); 204e9f15a71SStephen Boyd if (ret) 205e9f15a71SStephen Boyd return ret; 206e9f15a71SStephen Boyd 207e9f15a71SStephen Boyd ret = clk_prepare_enable(ci->iface_clk); 208e9f15a71SStephen Boyd if (ret) 209e9f15a71SStephen Boyd goto err_iface; 210e9f15a71SStephen Boyd 2112fc305beSStephen Boyd ret = ci_hdrc_msm_mux_phy(ci, pdev); 2122fc305beSStephen Boyd if (ret) 2132fc305beSStephen Boyd goto err_mux; 2142fc305beSStephen Boyd 215*47654a16SStephen Boyd ulpi_node = of_find_node_by_name(pdev->dev.of_node, "ulpi"); 216*47654a16SStephen Boyd if (ulpi_node) { 217*47654a16SStephen Boyd phy_node = of_get_next_available_child(ulpi_node, NULL); 218*47654a16SStephen Boyd ci->hsic = of_device_is_compatible(phy_node, "qcom,usb-hsic-phy"); 219*47654a16SStephen Boyd of_node_put(phy_node); 220*47654a16SStephen Boyd } 221*47654a16SStephen Boyd of_node_put(ulpi_node); 222*47654a16SStephen Boyd 2238e22978cSAlexander Shishkin plat_ci = ci_hdrc_add_device(&pdev->dev, 2248e22978cSAlexander Shishkin pdev->resource, pdev->num_resources, 2258e22978cSAlexander Shishkin &ci_hdrc_msm_platdata); 2268e22978cSAlexander Shishkin if (IS_ERR(plat_ci)) { 2278e22978cSAlexander Shishkin dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n"); 228e9f15a71SStephen Boyd ret = PTR_ERR(plat_ci); 229e9f15a71SStephen Boyd goto err_mux; 2308e22978cSAlexander Shishkin } 2318e22978cSAlexander Shishkin 232e9f15a71SStephen Boyd ci->ci = plat_ci; 2338e22978cSAlexander Shishkin 2342c8ea46dSStephen Boyd pm_runtime_set_active(&pdev->dev); 2358e22978cSAlexander Shishkin pm_runtime_no_callbacks(&pdev->dev); 2368e22978cSAlexander Shishkin pm_runtime_enable(&pdev->dev); 2378e22978cSAlexander Shishkin 2388e22978cSAlexander Shishkin return 0; 239e9f15a71SStephen Boyd 240e9f15a71SStephen Boyd err_mux: 241e9f15a71SStephen Boyd clk_disable_unprepare(ci->iface_clk); 242e9f15a71SStephen Boyd err_iface: 243e9f15a71SStephen Boyd clk_disable_unprepare(ci->core_clk); 244e9f15a71SStephen Boyd return ret; 2458e22978cSAlexander Shishkin } 2468e22978cSAlexander Shishkin 2478e22978cSAlexander Shishkin static int ci_hdrc_msm_remove(struct platform_device *pdev) 2488e22978cSAlexander Shishkin { 249e9f15a71SStephen Boyd struct ci_hdrc_msm *ci = platform_get_drvdata(pdev); 2508e22978cSAlexander Shishkin 2518e22978cSAlexander Shishkin pm_runtime_disable(&pdev->dev); 252e9f15a71SStephen Boyd ci_hdrc_remove_device(ci->ci); 253e9f15a71SStephen Boyd clk_disable_unprepare(ci->iface_clk); 254e9f15a71SStephen Boyd clk_disable_unprepare(ci->core_clk); 2558e22978cSAlexander Shishkin 2568e22978cSAlexander Shishkin return 0; 2578e22978cSAlexander Shishkin } 2588e22978cSAlexander Shishkin 2592629b101SIvan T. Ivanov static const struct of_device_id msm_ci_dt_match[] = { 2602629b101SIvan T. Ivanov { .compatible = "qcom,ci-hdrc", }, 2612629b101SIvan T. Ivanov { } 2622629b101SIvan T. Ivanov }; 2632629b101SIvan T. Ivanov MODULE_DEVICE_TABLE(of, msm_ci_dt_match); 2642629b101SIvan T. Ivanov 2658e22978cSAlexander Shishkin static struct platform_driver ci_hdrc_msm_driver = { 2668e22978cSAlexander Shishkin .probe = ci_hdrc_msm_probe, 2678e22978cSAlexander Shishkin .remove = ci_hdrc_msm_remove, 2682629b101SIvan T. Ivanov .driver = { 2692629b101SIvan T. Ivanov .name = "msm_hsusb", 2702629b101SIvan T. Ivanov .of_match_table = msm_ci_dt_match, 2712629b101SIvan T. Ivanov }, 2728e22978cSAlexander Shishkin }; 2738e22978cSAlexander Shishkin 2748e22978cSAlexander Shishkin module_platform_driver(ci_hdrc_msm_driver); 2758e22978cSAlexander Shishkin 2768e22978cSAlexander Shishkin MODULE_ALIAS("platform:msm_hsusb"); 2778e22978cSAlexander Shishkin MODULE_ALIAS("platform:ci13xxx_msm"); 2788e22978cSAlexander Shishkin MODULE_LICENSE("GPL v2"); 279