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