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