Commit | Line | Data |
---|---|---|
56d77c9a AV |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (c) 2023, Linaro Limited | |
4 | */ | |
5 | ||
6 | #include <linux/module.h> | |
7 | #include <linux/platform_device.h> | |
8 | #include <linux/regulator/consumer.h> | |
9 | #include <linux/regmap.h> | |
10 | #include <linux/of.h> | |
56d77c9a AV |
11 | #include <linux/phy/phy.h> |
12 | ||
13 | /* eUSB2 status registers */ | |
14 | #define EUSB2_RPTR_STATUS 0x08 | |
15 | #define RPTR_OK BIT(7) | |
16 | ||
17 | /* eUSB2 control registers */ | |
18 | #define EUSB2_EN_CTL1 0x46 | |
19 | #define EUSB2_RPTR_EN BIT(7) | |
20 | ||
21 | #define EUSB2_FORCE_EN_5 0xe8 | |
22 | #define F_CLK_19P2M_EN BIT(6) | |
23 | ||
24 | #define EUSB2_FORCE_VAL_5 0xeD | |
25 | #define V_CLK_19P2M_EN BIT(6) | |
26 | ||
99a517a5 | 27 | #define EUSB2_TUNE_USB2_CROSSOVER 0x50 |
56d77c9a | 28 | #define EUSB2_TUNE_IUSB2 0x51 |
99a517a5 KD |
29 | #define EUSB2_TUNE_RES_FSDIF 0x52 |
30 | #define EUSB2_TUNE_HSDISC 0x53 | |
56d77c9a | 31 | #define EUSB2_TUNE_SQUELCH_U 0x54 |
99a517a5 KD |
32 | #define EUSB2_TUNE_USB2_SLEW 0x55 |
33 | #define EUSB2_TUNE_USB2_EQU 0x56 | |
56d77c9a | 34 | #define EUSB2_TUNE_USB2_PREEM 0x57 |
99a517a5 KD |
35 | #define EUSB2_TUNE_USB2_HS_COMP_CUR 0x58 |
36 | #define EUSB2_TUNE_EUSB_SLEW 0x59 | |
37 | #define EUSB2_TUNE_EUSB_EQU 0x5A | |
38 | #define EUSB2_TUNE_EUSB_HS_COMP_CUR 0x5B | |
56d77c9a | 39 | |
734550d6 AV |
40 | enum eusb2_reg_layout { |
41 | TUNE_EUSB_HS_COMP_CUR, | |
42 | TUNE_EUSB_EQU, | |
43 | TUNE_EUSB_SLEW, | |
44 | TUNE_USB2_HS_COMP_CUR, | |
45 | TUNE_USB2_PREEM, | |
46 | TUNE_USB2_EQU, | |
47 | TUNE_USB2_SLEW, | |
48 | TUNE_SQUELCH_U, | |
49 | TUNE_HSDISC, | |
50 | TUNE_RES_FSDIF, | |
51 | TUNE_IUSB2, | |
52 | TUNE_USB2_CROSSOVER, | |
53 | NUM_TUNE_FIELDS, | |
54 | ||
55 | FORCE_VAL_5 = NUM_TUNE_FIELDS, | |
56 | FORCE_EN_5, | |
57 | ||
58 | EN_CTL1, | |
59 | ||
60 | RPTR_STATUS, | |
61 | LAYOUT_SIZE, | |
4ba2e527 KD |
62 | }; |
63 | ||
56d77c9a | 64 | struct eusb2_repeater_cfg { |
99a517a5 | 65 | const u32 *init_tbl; |
56d77c9a AV |
66 | int init_tbl_num; |
67 | const char * const *vreg_list; | |
68 | int num_vregs; | |
69 | }; | |
70 | ||
71 | struct eusb2_repeater { | |
72 | struct device *dev; | |
734550d6 | 73 | struct regmap *regmap; |
56d77c9a AV |
74 | struct phy *phy; |
75 | struct regulator_bulk_data *vregs; | |
76 | const struct eusb2_repeater_cfg *cfg; | |
734550d6 | 77 | u32 base; |
56d77c9a AV |
78 | enum phy_mode mode; |
79 | }; | |
80 | ||
81 | static const char * const pm8550b_vreg_l[] = { | |
82 | "vdd18", "vdd3", | |
83 | }; | |
84 | ||
734550d6 AV |
85 | static const u32 pm8550b_init_tbl[NUM_TUNE_FIELDS] = { |
86 | [TUNE_IUSB2] = 0x8, | |
87 | [TUNE_SQUELCH_U] = 0x3, | |
88 | [TUNE_USB2_PREEM] = 0x5, | |
56d77c9a AV |
89 | }; |
90 | ||
67076749 AV |
91 | static const u32 smb2360_init_tbl[NUM_TUNE_FIELDS] = { |
92 | [TUNE_IUSB2] = 0x5, | |
93 | [TUNE_SQUELCH_U] = 0x3, | |
94 | [TUNE_USB2_PREEM] = 0x2, | |
95 | }; | |
96 | ||
56d77c9a AV |
97 | static const struct eusb2_repeater_cfg pm8550b_eusb2_cfg = { |
98 | .init_tbl = pm8550b_init_tbl, | |
99 | .init_tbl_num = ARRAY_SIZE(pm8550b_init_tbl), | |
100 | .vreg_list = pm8550b_vreg_l, | |
101 | .num_vregs = ARRAY_SIZE(pm8550b_vreg_l), | |
102 | }; | |
103 | ||
67076749 AV |
104 | static const struct eusb2_repeater_cfg smb2360_eusb2_cfg = { |
105 | .init_tbl = smb2360_init_tbl, | |
106 | .init_tbl_num = ARRAY_SIZE(smb2360_init_tbl), | |
107 | .vreg_list = pm8550b_vreg_l, | |
108 | .num_vregs = ARRAY_SIZE(pm8550b_vreg_l), | |
109 | }; | |
110 | ||
56d77c9a AV |
111 | static int eusb2_repeater_init_vregs(struct eusb2_repeater *rptr) |
112 | { | |
113 | int num = rptr->cfg->num_vregs; | |
114 | struct device *dev = rptr->dev; | |
115 | int i; | |
116 | ||
117 | rptr->vregs = devm_kcalloc(dev, num, sizeof(*rptr->vregs), GFP_KERNEL); | |
118 | if (!rptr->vregs) | |
119 | return -ENOMEM; | |
120 | ||
121 | for (i = 0; i < num; i++) | |
122 | rptr->vregs[i].supply = rptr->cfg->vreg_list[i]; | |
123 | ||
124 | return devm_regulator_bulk_get(dev, num, rptr->vregs); | |
125 | } | |
126 | ||
127 | static int eusb2_repeater_init(struct phy *phy) | |
128 | { | |
129 | struct eusb2_repeater *rptr = phy_get_drvdata(phy); | |
56156a76 | 130 | struct device_node *np = rptr->dev->of_node; |
734550d6 AV |
131 | struct regmap *regmap = rptr->regmap; |
132 | const u32 *init_tbl = rptr->cfg->init_tbl; | |
133 | u8 tune_usb2_preem = init_tbl[TUNE_USB2_PREEM]; | |
134 | u8 tune_hsdisc = init_tbl[TUNE_HSDISC]; | |
135 | u8 tune_iusb2 = init_tbl[TUNE_IUSB2]; | |
136 | u32 base = rptr->base; | |
56d77c9a AV |
137 | u32 val; |
138 | int ret; | |
734550d6 AV |
139 | |
140 | of_property_read_u8(np, "qcom,tune-usb2-amplitude", &tune_iusb2); | |
141 | of_property_read_u8(np, "qcom,tune-usb2-disc-thres", &tune_hsdisc); | |
142 | of_property_read_u8(np, "qcom,tune-usb2-preem", &tune_usb2_preem); | |
56d77c9a AV |
143 | |
144 | ret = regulator_bulk_enable(rptr->cfg->num_vregs, rptr->vregs); | |
145 | if (ret) | |
146 | return ret; | |
147 | ||
734550d6 | 148 | regmap_write(regmap, base + EUSB2_EN_CTL1, EUSB2_RPTR_EN); |
56d77c9a | 149 | |
734550d6 AV |
150 | regmap_write(regmap, base + EUSB2_TUNE_EUSB_HS_COMP_CUR, init_tbl[TUNE_EUSB_HS_COMP_CUR]); |
151 | regmap_write(regmap, base + EUSB2_TUNE_EUSB_EQU, init_tbl[TUNE_EUSB_EQU]); | |
152 | regmap_write(regmap, base + EUSB2_TUNE_EUSB_SLEW, init_tbl[TUNE_EUSB_SLEW]); | |
153 | regmap_write(regmap, base + EUSB2_TUNE_USB2_HS_COMP_CUR, init_tbl[TUNE_USB2_HS_COMP_CUR]); | |
154 | regmap_write(regmap, base + EUSB2_TUNE_USB2_EQU, init_tbl[TUNE_USB2_EQU]); | |
155 | regmap_write(regmap, base + EUSB2_TUNE_USB2_SLEW, init_tbl[TUNE_USB2_SLEW]); | |
156 | regmap_write(regmap, base + EUSB2_TUNE_SQUELCH_U, init_tbl[TUNE_SQUELCH_U]); | |
157 | regmap_write(regmap, base + EUSB2_TUNE_RES_FSDIF, init_tbl[TUNE_RES_FSDIF]); | |
158 | regmap_write(regmap, base + EUSB2_TUNE_USB2_CROSSOVER, init_tbl[TUNE_USB2_CROSSOVER]); | |
56156a76 | 159 | |
734550d6 AV |
160 | regmap_write(regmap, base + EUSB2_TUNE_USB2_PREEM, tune_usb2_preem); |
161 | regmap_write(regmap, base + EUSB2_TUNE_HSDISC, tune_hsdisc); | |
162 | regmap_write(regmap, base + EUSB2_TUNE_IUSB2, tune_iusb2); | |
56156a76 | 163 | |
734550d6 | 164 | ret = regmap_read_poll_timeout(regmap, base + EUSB2_RPTR_STATUS, val, val & RPTR_OK, 10, 5); |
56d77c9a AV |
165 | if (ret) |
166 | dev_err(rptr->dev, "initialization timed-out\n"); | |
167 | ||
168 | return ret; | |
169 | } | |
170 | ||
171 | static int eusb2_repeater_set_mode(struct phy *phy, | |
172 | enum phy_mode mode, int submode) | |
173 | { | |
174 | struct eusb2_repeater *rptr = phy_get_drvdata(phy); | |
734550d6 AV |
175 | struct regmap *regmap = rptr->regmap; |
176 | u32 base = rptr->base; | |
56d77c9a AV |
177 | |
178 | switch (mode) { | |
179 | case PHY_MODE_USB_HOST: | |
180 | /* | |
181 | * CM.Lx is prohibited when repeater is already into Lx state as | |
182 | * per eUSB 1.2 Spec. Below implement software workaround until | |
183 | * PHY and controller is fixing seen observation. | |
184 | */ | |
734550d6 AV |
185 | regmap_write(regmap, base + EUSB2_FORCE_EN_5, F_CLK_19P2M_EN); |
186 | regmap_write(regmap, base + EUSB2_FORCE_VAL_5, V_CLK_19P2M_EN); | |
56d77c9a AV |
187 | break; |
188 | case PHY_MODE_USB_DEVICE: | |
189 | /* | |
190 | * In device mode clear host mode related workaround as there | |
191 | * is no repeater reset available, and enable/disable of | |
192 | * repeater doesn't clear previous value due to shared | |
193 | * regulators (say host <-> device mode switch). | |
194 | */ | |
734550d6 AV |
195 | regmap_write(regmap, base + EUSB2_FORCE_EN_5, 0); |
196 | regmap_write(regmap, base + EUSB2_FORCE_VAL_5, 0); | |
56d77c9a AV |
197 | break; |
198 | default: | |
199 | return -EINVAL; | |
200 | } | |
201 | ||
202 | return 0; | |
203 | } | |
204 | ||
205 | static int eusb2_repeater_exit(struct phy *phy) | |
206 | { | |
207 | struct eusb2_repeater *rptr = phy_get_drvdata(phy); | |
208 | ||
209 | return regulator_bulk_disable(rptr->cfg->num_vregs, rptr->vregs); | |
210 | } | |
211 | ||
212 | static const struct phy_ops eusb2_repeater_ops = { | |
213 | .init = eusb2_repeater_init, | |
214 | .exit = eusb2_repeater_exit, | |
215 | .set_mode = eusb2_repeater_set_mode, | |
216 | .owner = THIS_MODULE, | |
217 | }; | |
218 | ||
219 | static int eusb2_repeater_probe(struct platform_device *pdev) | |
220 | { | |
221 | struct eusb2_repeater *rptr; | |
222 | struct device *dev = &pdev->dev; | |
223 | struct phy_provider *phy_provider; | |
224 | struct device_node *np = dev->of_node; | |
225 | u32 res; | |
734550d6 | 226 | int ret; |
56d77c9a AV |
227 | |
228 | rptr = devm_kzalloc(dev, sizeof(*rptr), GFP_KERNEL); | |
229 | if (!rptr) | |
230 | return -ENOMEM; | |
231 | ||
232 | rptr->dev = dev; | |
233 | dev_set_drvdata(dev, rptr); | |
234 | ||
235 | rptr->cfg = of_device_get_match_data(dev); | |
236 | if (!rptr->cfg) | |
237 | return -EINVAL; | |
238 | ||
734550d6 AV |
239 | rptr->regmap = dev_get_regmap(dev->parent, NULL); |
240 | if (!rptr->regmap) | |
56d77c9a AV |
241 | return -ENODEV; |
242 | ||
243 | ret = of_property_read_u32(np, "reg", &res); | |
244 | if (ret < 0) | |
245 | return ret; | |
246 | ||
734550d6 | 247 | rptr->base = res; |
56d77c9a AV |
248 | |
249 | ret = eusb2_repeater_init_vregs(rptr); | |
250 | if (ret < 0) { | |
251 | dev_err(dev, "unable to get supplies\n"); | |
252 | return ret; | |
253 | } | |
254 | ||
255 | rptr->phy = devm_phy_create(dev, np, &eusb2_repeater_ops); | |
256 | if (IS_ERR(rptr->phy)) { | |
257 | dev_err(dev, "failed to create PHY: %d\n", ret); | |
258 | return PTR_ERR(rptr->phy); | |
259 | } | |
260 | ||
261 | phy_set_drvdata(rptr->phy, rptr); | |
262 | ||
263 | phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); | |
264 | if (IS_ERR(phy_provider)) | |
265 | return PTR_ERR(phy_provider); | |
266 | ||
267 | dev_info(dev, "Registered Qcom-eUSB2 repeater\n"); | |
268 | ||
269 | return 0; | |
270 | } | |
271 | ||
e5ce6d9d | 272 | static void eusb2_repeater_remove(struct platform_device *pdev) |
56d77c9a AV |
273 | { |
274 | struct eusb2_repeater *rptr = platform_get_drvdata(pdev); | |
275 | ||
276 | if (!rptr) | |
e5ce6d9d | 277 | return; |
56d77c9a AV |
278 | |
279 | eusb2_repeater_exit(rptr->phy); | |
56d77c9a AV |
280 | } |
281 | ||
282 | static const struct of_device_id eusb2_repeater_of_match_table[] = { | |
283 | { | |
284 | .compatible = "qcom,pm8550b-eusb2-repeater", | |
285 | .data = &pm8550b_eusb2_cfg, | |
286 | }, | |
67076749 AV |
287 | { |
288 | .compatible = "qcom,smb2360-eusb2-repeater", | |
289 | .data = &smb2360_eusb2_cfg, | |
290 | }, | |
56d77c9a AV |
291 | { }, |
292 | }; | |
293 | MODULE_DEVICE_TABLE(of, eusb2_repeater_of_match_table); | |
294 | ||
295 | static struct platform_driver eusb2_repeater_driver = { | |
296 | .probe = eusb2_repeater_probe, | |
e5ce6d9d | 297 | .remove_new = eusb2_repeater_remove, |
56d77c9a AV |
298 | .driver = { |
299 | .name = "qcom-eusb2-repeater", | |
300 | .of_match_table = eusb2_repeater_of_match_table, | |
301 | }, | |
302 | }; | |
303 | ||
304 | module_platform_driver(eusb2_repeater_driver); | |
305 | ||
306 | MODULE_DESCRIPTION("Qualcomm PMIC eUSB2 Repeater driver"); | |
307 | MODULE_LICENSE("GPL"); |