1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright (c) 2010, Code Aurora Forum. All rights reserved. */ |
3 | |
4 | #include <linux/module.h> |
5 | #include <linux/platform_device.h> |
6 | #include <linux/pm_runtime.h> |
7 | #include <linux/usb/chipidea.h> |
8 | #include <linux/clk.h> |
9 | #include <linux/reset.h> |
10 | #include <linux/mfd/syscon.h> |
11 | #include <linux/regmap.h> |
12 | #include <linux/io.h> |
13 | #include <linux/reset-controller.h> |
14 | #include <linux/extcon.h> |
15 | #include <linux/of.h> |
16 | |
17 | #include "ci.h" |
18 | |
19 | #define HS_PHY_AHB_MODE 0x0098 |
20 | |
21 | #define HS_PHY_GENCONFIG 0x009c |
22 | #define HS_PHY_TXFIFO_IDLE_FORCE_DIS BIT(4) |
23 | |
24 | #define HS_PHY_GENCONFIG_2 0x00a0 |
25 | #define HS_PHY_SESS_VLD_CTRL_EN BIT(7) |
26 | #define HS_PHY_ULPI_TX_PKT_EN_CLR_FIX BIT(19) |
27 | |
28 | #define HSPHY_SESS_VLD_CTRL BIT(25) |
29 | |
30 | /* Vendor base starts at 0x200 beyond CI base */ |
31 | #define HS_PHY_CTRL 0x0040 |
32 | #define HS_PHY_SEC_CTRL 0x0078 |
33 | #define HS_PHY_DIG_CLAMP_N BIT(16) |
34 | #define HS_PHY_POR_ASSERT BIT(0) |
35 | |
36 | struct ci_hdrc_msm { |
37 | struct platform_device *ci; |
38 | struct clk *core_clk; |
39 | struct clk *iface_clk; |
40 | struct clk *fs_clk; |
41 | struct ci_hdrc_platform_data pdata; |
42 | struct reset_controller_dev rcdev; |
43 | bool secondary_phy; |
44 | bool hsic; |
45 | void __iomem *base; |
46 | }; |
47 | |
48 | static int |
49 | ci_hdrc_msm_por_reset(struct reset_controller_dev *r, unsigned long id) |
50 | { |
51 | struct ci_hdrc_msm *ci_msm = container_of(r, struct ci_hdrc_msm, rcdev); |
52 | void __iomem *addr = ci_msm->base; |
53 | u32 val; |
54 | |
55 | if (id) |
56 | addr += HS_PHY_SEC_CTRL; |
57 | else |
58 | addr += HS_PHY_CTRL; |
59 | |
60 | val = readl_relaxed(addr); |
61 | val |= HS_PHY_POR_ASSERT; |
62 | writel(val, addr); |
63 | /* |
64 | * wait for minimum 10 microseconds as suggested by manual. |
65 | * Use a slightly larger value since the exact value didn't |
66 | * work 100% of the time. |
67 | */ |
68 | udelay(12); |
69 | val &= ~HS_PHY_POR_ASSERT; |
70 | writel(val, addr); |
71 | |
72 | return 0; |
73 | } |
74 | |
75 | static const struct reset_control_ops ci_hdrc_msm_reset_ops = { |
76 | .reset = ci_hdrc_msm_por_reset, |
77 | }; |
78 | |
79 | static int ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event) |
80 | { |
81 | struct device *dev = ci->dev->parent; |
82 | struct ci_hdrc_msm *msm_ci = dev_get_drvdata(dev); |
83 | int ret; |
84 | |
85 | switch (event) { |
86 | case CI_HDRC_CONTROLLER_RESET_EVENT: |
87 | dev_dbg(dev, "CI_HDRC_CONTROLLER_RESET_EVENT received\n" ); |
88 | |
89 | hw_phymode_configure(ci); |
90 | if (msm_ci->secondary_phy) { |
91 | u32 val = readl_relaxed(msm_ci->base + HS_PHY_SEC_CTRL); |
92 | val |= HS_PHY_DIG_CLAMP_N; |
93 | writel_relaxed(val, msm_ci->base + HS_PHY_SEC_CTRL); |
94 | } |
95 | |
96 | ret = phy_init(phy: ci->phy); |
97 | if (ret) |
98 | return ret; |
99 | |
100 | ret = phy_power_on(phy: ci->phy); |
101 | if (ret) { |
102 | phy_exit(phy: ci->phy); |
103 | return ret; |
104 | } |
105 | |
106 | /* use AHB transactor, allow posted data writes */ |
107 | hw_write_id_reg(ci, HS_PHY_AHB_MODE, mask: 0xffffffff, data: 0x8); |
108 | |
109 | /* workaround for rx buffer collision issue */ |
110 | hw_write_id_reg(ci, HS_PHY_GENCONFIG, |
111 | HS_PHY_TXFIFO_IDLE_FORCE_DIS, data: 0); |
112 | |
113 | if (!msm_ci->hsic) |
114 | hw_write_id_reg(ci, HS_PHY_GENCONFIG_2, |
115 | HS_PHY_ULPI_TX_PKT_EN_CLR_FIX, data: 0); |
116 | |
117 | if (!IS_ERR(ptr: ci->platdata->vbus_extcon.edev) || ci->role_switch) { |
118 | hw_write_id_reg(ci, HS_PHY_GENCONFIG_2, |
119 | HS_PHY_SESS_VLD_CTRL_EN, |
120 | HS_PHY_SESS_VLD_CTRL_EN); |
121 | hw_write(ci, reg: OP_USBCMD, HSPHY_SESS_VLD_CTRL, |
122 | HSPHY_SESS_VLD_CTRL); |
123 | |
124 | } |
125 | break; |
126 | case CI_HDRC_CONTROLLER_STOPPED_EVENT: |
127 | dev_dbg(dev, "CI_HDRC_CONTROLLER_STOPPED_EVENT received\n" ); |
128 | phy_power_off(phy: ci->phy); |
129 | phy_exit(phy: ci->phy); |
130 | break; |
131 | default: |
132 | dev_dbg(dev, "unknown ci_hdrc event\n" ); |
133 | break; |
134 | } |
135 | |
136 | return 0; |
137 | } |
138 | |
139 | static int ci_hdrc_msm_mux_phy(struct ci_hdrc_msm *ci, |
140 | struct platform_device *pdev) |
141 | { |
142 | struct regmap *regmap; |
143 | struct device *dev = &pdev->dev; |
144 | struct of_phandle_args args; |
145 | u32 val; |
146 | int ret; |
147 | |
148 | ret = of_parse_phandle_with_fixed_args(np: dev->of_node, list_name: "phy-select" , cell_count: 2, index: 0, |
149 | out_args: &args); |
150 | if (ret) |
151 | return 0; |
152 | |
153 | regmap = syscon_node_to_regmap(np: args.np); |
154 | of_node_put(node: args.np); |
155 | if (IS_ERR(ptr: regmap)) |
156 | return PTR_ERR(ptr: regmap); |
157 | |
158 | ret = regmap_write(map: regmap, reg: args.args[0], val: args.args[1]); |
159 | if (ret) |
160 | return ret; |
161 | |
162 | ci->secondary_phy = !!args.args[1]; |
163 | if (ci->secondary_phy) { |
164 | val = readl_relaxed(ci->base + HS_PHY_SEC_CTRL); |
165 | val |= HS_PHY_DIG_CLAMP_N; |
166 | writel_relaxed(val, ci->base + HS_PHY_SEC_CTRL); |
167 | } |
168 | |
169 | return 0; |
170 | } |
171 | |
172 | static int ci_hdrc_msm_probe(struct platform_device *pdev) |
173 | { |
174 | struct ci_hdrc_msm *ci; |
175 | struct platform_device *plat_ci; |
176 | struct clk *clk; |
177 | struct reset_control *reset; |
178 | int ret; |
179 | struct device_node *ulpi_node, *phy_node; |
180 | |
181 | dev_dbg(&pdev->dev, "ci_hdrc_msm_probe\n" ); |
182 | |
183 | ci = devm_kzalloc(dev: &pdev->dev, size: sizeof(*ci), GFP_KERNEL); |
184 | if (!ci) |
185 | return -ENOMEM; |
186 | platform_set_drvdata(pdev, data: ci); |
187 | |
188 | ci->pdata.name = "ci_hdrc_msm" ; |
189 | ci->pdata.capoffset = DEF_CAPOFFSET; |
190 | ci->pdata.flags = CI_HDRC_REGS_SHARED | CI_HDRC_DISABLE_STREAMING | |
191 | CI_HDRC_OVERRIDE_AHB_BURST | |
192 | CI_HDRC_OVERRIDE_PHY_CONTROL; |
193 | ci->pdata.notify_event = ci_hdrc_msm_notify_event; |
194 | |
195 | reset = devm_reset_control_get(dev: &pdev->dev, id: "core" ); |
196 | if (IS_ERR(ptr: reset)) |
197 | return PTR_ERR(ptr: reset); |
198 | |
199 | ci->core_clk = clk = devm_clk_get(dev: &pdev->dev, id: "core" ); |
200 | if (IS_ERR(ptr: clk)) |
201 | return PTR_ERR(ptr: clk); |
202 | |
203 | ci->iface_clk = clk = devm_clk_get(dev: &pdev->dev, id: "iface" ); |
204 | if (IS_ERR(ptr: clk)) |
205 | return PTR_ERR(ptr: clk); |
206 | |
207 | ci->fs_clk = clk = devm_clk_get_optional(dev: &pdev->dev, id: "fs" ); |
208 | if (IS_ERR(ptr: clk)) |
209 | return PTR_ERR(ptr: clk); |
210 | |
211 | ci->base = devm_platform_ioremap_resource(pdev, index: 1); |
212 | if (IS_ERR(ptr: ci->base)) |
213 | return PTR_ERR(ptr: ci->base); |
214 | |
215 | ci->rcdev.owner = THIS_MODULE; |
216 | ci->rcdev.ops = &ci_hdrc_msm_reset_ops; |
217 | ci->rcdev.of_node = pdev->dev.of_node; |
218 | ci->rcdev.nr_resets = 2; |
219 | ret = devm_reset_controller_register(dev: &pdev->dev, rcdev: &ci->rcdev); |
220 | if (ret) |
221 | return ret; |
222 | |
223 | ret = clk_prepare_enable(clk: ci->fs_clk); |
224 | if (ret) |
225 | return ret; |
226 | |
227 | reset_control_assert(rstc: reset); |
228 | usleep_range(min: 10000, max: 12000); |
229 | reset_control_deassert(rstc: reset); |
230 | |
231 | clk_disable_unprepare(clk: ci->fs_clk); |
232 | |
233 | ret = clk_prepare_enable(clk: ci->core_clk); |
234 | if (ret) |
235 | return ret; |
236 | |
237 | ret = clk_prepare_enable(clk: ci->iface_clk); |
238 | if (ret) |
239 | goto err_iface; |
240 | |
241 | ret = ci_hdrc_msm_mux_phy(ci, pdev); |
242 | if (ret) |
243 | goto err_mux; |
244 | |
245 | ulpi_node = of_get_child_by_name(node: pdev->dev.of_node, name: "ulpi" ); |
246 | if (ulpi_node) { |
247 | phy_node = of_get_next_available_child(node: ulpi_node, NULL); |
248 | ci->hsic = of_device_is_compatible(device: phy_node, "qcom,usb-hsic-phy" ); |
249 | of_node_put(node: phy_node); |
250 | } |
251 | of_node_put(node: ulpi_node); |
252 | |
253 | plat_ci = ci_hdrc_add_device(dev: &pdev->dev, res: pdev->resource, |
254 | nres: pdev->num_resources, platdata: &ci->pdata); |
255 | if (IS_ERR(ptr: plat_ci)) { |
256 | ret = PTR_ERR(ptr: plat_ci); |
257 | if (ret != -EPROBE_DEFER) |
258 | dev_err(&pdev->dev, "ci_hdrc_add_device failed!\n" ); |
259 | goto err_mux; |
260 | } |
261 | |
262 | ci->ci = plat_ci; |
263 | |
264 | pm_runtime_set_active(dev: &pdev->dev); |
265 | pm_runtime_no_callbacks(dev: &pdev->dev); |
266 | pm_runtime_enable(dev: &pdev->dev); |
267 | |
268 | return 0; |
269 | |
270 | err_mux: |
271 | clk_disable_unprepare(clk: ci->iface_clk); |
272 | err_iface: |
273 | clk_disable_unprepare(clk: ci->core_clk); |
274 | return ret; |
275 | } |
276 | |
277 | static void ci_hdrc_msm_remove(struct platform_device *pdev) |
278 | { |
279 | struct ci_hdrc_msm *ci = platform_get_drvdata(pdev); |
280 | |
281 | pm_runtime_disable(dev: &pdev->dev); |
282 | ci_hdrc_remove_device(pdev: ci->ci); |
283 | clk_disable_unprepare(clk: ci->iface_clk); |
284 | clk_disable_unprepare(clk: ci->core_clk); |
285 | } |
286 | |
287 | static const struct of_device_id msm_ci_dt_match[] = { |
288 | { .compatible = "qcom,ci-hdrc" , }, |
289 | { } |
290 | }; |
291 | MODULE_DEVICE_TABLE(of, msm_ci_dt_match); |
292 | |
293 | static struct platform_driver ci_hdrc_msm_driver = { |
294 | .probe = ci_hdrc_msm_probe, |
295 | .remove_new = ci_hdrc_msm_remove, |
296 | .driver = { |
297 | .name = "msm_hsusb" , |
298 | .of_match_table = msm_ci_dt_match, |
299 | }, |
300 | }; |
301 | |
302 | module_platform_driver(ci_hdrc_msm_driver); |
303 | |
304 | MODULE_ALIAS("platform:msm_hsusb" ); |
305 | MODULE_ALIAS("platform:ci13xxx_msm" ); |
306 | MODULE_LICENSE("GPL v2" ); |
307 | |