xref: /openbmc/linux/drivers/usb/chipidea/ci_hdrc_msm.c (revision 91edf63d5022bd0464788ffb4acc3d5febbaf81d)
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