1 | // SPDX-License-Identifier: GPL-2.0+ |
---|---|
2 | /* |
3 | * TI HD3SS3220 Type-C DRP Port Controller Driver |
4 | * |
5 | * Copyright (C) 2019 Renesas Electronics Corp. |
6 | */ |
7 | |
8 | #include <linux/module.h> |
9 | #include <linux/i2c.h> |
10 | #include <linux/usb/role.h> |
11 | #include <linux/irqreturn.h> |
12 | #include <linux/interrupt.h> |
13 | #include <linux/regmap.h> |
14 | #include <linux/slab.h> |
15 | #include <linux/usb/typec.h> |
16 | #include <linux/delay.h> |
17 | #include <linux/workqueue.h> |
18 | |
19 | #define HD3SS3220_REG_CN_STAT 0x08 |
20 | #define HD3SS3220_REG_CN_STAT_CTRL 0x09 |
21 | #define HD3SS3220_REG_GEN_CTRL 0x0A |
22 | #define HD3SS3220_REG_DEV_REV 0xA0 |
23 | |
24 | /* Register HD3SS3220_REG_CN_STAT */ |
25 | #define HD3SS3220_REG_CN_STAT_CURRENT_MODE_MASK (BIT(7) | BIT(6)) |
26 | #define HD3SS3220_REG_CN_STAT_CURRENT_MODE_DEFAULT 0x00 |
27 | #define HD3SS3220_REG_CN_STAT_CURRENT_MODE_MID BIT(6) |
28 | #define HD3SS3220_REG_CN_STAT_CURRENT_MODE_HIGH BIT(7) |
29 | |
30 | /* Register HD3SS3220_REG_CN_STAT_CTRL*/ |
31 | #define HD3SS3220_REG_CN_STAT_CTRL_ATTACHED_STATE_MASK (BIT(7) | BIT(6)) |
32 | #define HD3SS3220_REG_CN_STAT_CTRL_AS_DFP BIT(6) |
33 | #define HD3SS3220_REG_CN_STAT_CTRL_AS_UFP BIT(7) |
34 | #define HD3SS3220_REG_CN_STAT_CTRL_TO_ACCESSORY (BIT(7) | BIT(6)) |
35 | #define HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS BIT(4) |
36 | |
37 | /* Register HD3SS3220_REG_GEN_CTRL*/ |
38 | #define HD3SS3220_REG_GEN_CTRL_DISABLE_TERM BIT(0) |
39 | #define HD3SS3220_REG_GEN_CTRL_SRC_PREF_MASK (BIT(2) | BIT(1)) |
40 | #define HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_DEFAULT 0x00 |
41 | #define HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SNK BIT(1) |
42 | #define HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SRC (BIT(2) | BIT(1)) |
43 | #define HD3SS3220_REG_GEN_CTRL_MODE_SELECT_MASK (BIT(5) | BIT(4)) |
44 | #define HD3SS3220_REG_GEN_CTRL_MODE_SELECT_DEFAULT 0x00 |
45 | #define HD3SS3220_REG_GEN_CTRL_MODE_SELECT_DFP BIT(5) |
46 | #define HD3SS3220_REG_GEN_CTRL_MODE_SELECT_UFP BIT(4) |
47 | #define HD3SS3220_REG_GEN_CTRL_MODE_SELECT_DRP (BIT(5) | BIT(4)) |
48 | |
49 | struct hd3ss3220 { |
50 | struct device *dev; |
51 | struct regmap *regmap; |
52 | struct usb_role_switch *role_sw; |
53 | struct typec_port *port; |
54 | struct delayed_work output_poll_work; |
55 | enum usb_role role_state; |
56 | bool poll; |
57 | }; |
58 | |
59 | static int hd3ss3220_set_power_opmode(struct hd3ss3220 *hd3ss3220, int power_opmode) |
60 | { |
61 | int current_mode; |
62 | |
63 | switch (power_opmode) { |
64 | case TYPEC_PWR_MODE_USB: |
65 | current_mode = HD3SS3220_REG_CN_STAT_CURRENT_MODE_DEFAULT; |
66 | break; |
67 | case TYPEC_PWR_MODE_1_5A: |
68 | current_mode = HD3SS3220_REG_CN_STAT_CURRENT_MODE_MID; |
69 | break; |
70 | case TYPEC_PWR_MODE_3_0A: |
71 | current_mode = HD3SS3220_REG_CN_STAT_CURRENT_MODE_HIGH; |
72 | break; |
73 | case TYPEC_PWR_MODE_PD: /* Power delivery not supported */ |
74 | default: |
75 | dev_err(hd3ss3220->dev, "bad power operation mode: %d\n", power_opmode); |
76 | return -EINVAL; |
77 | } |
78 | |
79 | return regmap_update_bits(map: hd3ss3220->regmap, HD3SS3220_REG_CN_STAT, |
80 | HD3SS3220_REG_CN_STAT_CURRENT_MODE_MASK, |
81 | val: current_mode); |
82 | } |
83 | |
84 | static int hd3ss3220_set_port_type(struct hd3ss3220 *hd3ss3220, int type) |
85 | { |
86 | int mode_select, err; |
87 | |
88 | switch (type) { |
89 | case TYPEC_PORT_SRC: |
90 | mode_select = HD3SS3220_REG_GEN_CTRL_MODE_SELECT_DFP; |
91 | break; |
92 | case TYPEC_PORT_SNK: |
93 | mode_select = HD3SS3220_REG_GEN_CTRL_MODE_SELECT_UFP; |
94 | break; |
95 | case TYPEC_PORT_DRP: |
96 | mode_select = HD3SS3220_REG_GEN_CTRL_MODE_SELECT_DRP; |
97 | break; |
98 | default: |
99 | dev_err(hd3ss3220->dev, "bad port type: %d\n", type); |
100 | return -EINVAL; |
101 | } |
102 | |
103 | /* Disable termination before changing MODE_SELECT as required by datasheet */ |
104 | err = regmap_update_bits(map: hd3ss3220->regmap, HD3SS3220_REG_GEN_CTRL, |
105 | HD3SS3220_REG_GEN_CTRL_DISABLE_TERM, |
106 | HD3SS3220_REG_GEN_CTRL_DISABLE_TERM); |
107 | if (err < 0) { |
108 | dev_err(hd3ss3220->dev, "Failed to disable port for mode change: %d\n", err); |
109 | return err; |
110 | } |
111 | |
112 | err = regmap_update_bits(map: hd3ss3220->regmap, HD3SS3220_REG_GEN_CTRL, |
113 | HD3SS3220_REG_GEN_CTRL_MODE_SELECT_MASK, |
114 | val: mode_select); |
115 | if (err < 0) { |
116 | dev_err(hd3ss3220->dev, "Failed to change mode: %d\n", err); |
117 | regmap_update_bits(map: hd3ss3220->regmap, HD3SS3220_REG_GEN_CTRL, |
118 | HD3SS3220_REG_GEN_CTRL_DISABLE_TERM, val: 0); |
119 | return err; |
120 | } |
121 | |
122 | err = regmap_update_bits(map: hd3ss3220->regmap, HD3SS3220_REG_GEN_CTRL, |
123 | HD3SS3220_REG_GEN_CTRL_DISABLE_TERM, val: 0); |
124 | if (err < 0) |
125 | dev_err(hd3ss3220->dev, "Failed to re-enable port after mode change: %d\n", err); |
126 | |
127 | return err; |
128 | } |
129 | |
130 | static int hd3ss3220_set_source_pref(struct hd3ss3220 *hd3ss3220, int prefer_role) |
131 | { |
132 | int src_pref; |
133 | |
134 | switch (prefer_role) { |
135 | case TYPEC_NO_PREFERRED_ROLE: |
136 | src_pref = HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_DEFAULT; |
137 | break; |
138 | case TYPEC_SINK: |
139 | src_pref = HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SNK; |
140 | break; |
141 | case TYPEC_SOURCE: |
142 | src_pref = HD3SS3220_REG_GEN_CTRL_SRC_PREF_DRP_TRY_SRC; |
143 | break; |
144 | default: |
145 | dev_err(hd3ss3220->dev, "bad role preference: %d\n", prefer_role); |
146 | return -EINVAL; |
147 | } |
148 | |
149 | return regmap_update_bits(map: hd3ss3220->regmap, HD3SS3220_REG_GEN_CTRL, |
150 | HD3SS3220_REG_GEN_CTRL_SRC_PREF_MASK, |
151 | val: src_pref); |
152 | } |
153 | |
154 | static enum usb_role hd3ss3220_get_attached_state(struct hd3ss3220 *hd3ss3220) |
155 | { |
156 | unsigned int reg_val; |
157 | enum usb_role attached_state; |
158 | int ret; |
159 | |
160 | ret = regmap_read(map: hd3ss3220->regmap, HD3SS3220_REG_CN_STAT_CTRL, |
161 | val: ®_val); |
162 | if (ret < 0) |
163 | return ret; |
164 | |
165 | switch (reg_val & HD3SS3220_REG_CN_STAT_CTRL_ATTACHED_STATE_MASK) { |
166 | case HD3SS3220_REG_CN_STAT_CTRL_AS_DFP: |
167 | attached_state = USB_ROLE_HOST; |
168 | break; |
169 | case HD3SS3220_REG_CN_STAT_CTRL_AS_UFP: |
170 | attached_state = USB_ROLE_DEVICE; |
171 | break; |
172 | default: |
173 | attached_state = USB_ROLE_NONE; |
174 | break; |
175 | } |
176 | |
177 | return attached_state; |
178 | } |
179 | |
180 | static int hd3ss3220_try_role(struct typec_port *port, int role) |
181 | { |
182 | struct hd3ss3220 *hd3ss3220 = typec_get_drvdata(port); |
183 | |
184 | return hd3ss3220_set_source_pref(hd3ss3220, prefer_role: role); |
185 | } |
186 | |
187 | static int hd3ss3220_port_type_set(struct typec_port *port, enum typec_port_type type) |
188 | { |
189 | struct hd3ss3220 *hd3ss3220 = typec_get_drvdata(port); |
190 | |
191 | return hd3ss3220_set_port_type(hd3ss3220, type); |
192 | } |
193 | |
194 | static const struct typec_operations hd3ss3220_ops = { |
195 | .try_role = hd3ss3220_try_role, |
196 | .port_type_set = hd3ss3220_port_type_set, |
197 | }; |
198 | |
199 | static void hd3ss3220_set_role(struct hd3ss3220 *hd3ss3220) |
200 | { |
201 | enum usb_role role_state = hd3ss3220_get_attached_state(hd3ss3220); |
202 | |
203 | usb_role_switch_set_role(sw: hd3ss3220->role_sw, role: role_state); |
204 | |
205 | switch (role_state) { |
206 | case USB_ROLE_HOST: |
207 | typec_set_data_role(port: hd3ss3220->port, role: TYPEC_HOST); |
208 | break; |
209 | case USB_ROLE_DEVICE: |
210 | typec_set_data_role(port: hd3ss3220->port, role: TYPEC_DEVICE); |
211 | break; |
212 | default: |
213 | break; |
214 | } |
215 | |
216 | hd3ss3220->role_state = role_state; |
217 | } |
218 | |
219 | static void output_poll_execute(struct work_struct *work) |
220 | { |
221 | struct delayed_work *delayed_work = to_delayed_work(work); |
222 | struct hd3ss3220 *hd3ss3220 = container_of(delayed_work, |
223 | struct hd3ss3220, |
224 | output_poll_work); |
225 | enum usb_role role_state = hd3ss3220_get_attached_state(hd3ss3220); |
226 | |
227 | if (hd3ss3220->role_state != role_state) |
228 | hd3ss3220_set_role(hd3ss3220); |
229 | |
230 | schedule_delayed_work(dwork: &hd3ss3220->output_poll_work, HZ); |
231 | } |
232 | |
233 | static irqreturn_t hd3ss3220_irq(struct hd3ss3220 *hd3ss3220) |
234 | { |
235 | int err; |
236 | |
237 | hd3ss3220_set_role(hd3ss3220); |
238 | err = regmap_write_bits(map: hd3ss3220->regmap, HD3SS3220_REG_CN_STAT_CTRL, |
239 | HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS, |
240 | HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS); |
241 | if (err < 0) |
242 | return IRQ_NONE; |
243 | |
244 | return IRQ_HANDLED; |
245 | } |
246 | |
247 | static irqreturn_t hd3ss3220_irq_handler(int irq, void *data) |
248 | { |
249 | struct i2c_client *client = to_i2c_client(data); |
250 | struct hd3ss3220 *hd3ss3220 = i2c_get_clientdata(client); |
251 | |
252 | return hd3ss3220_irq(hd3ss3220); |
253 | } |
254 | |
255 | static int hd3ss3220_configure_power_opmode(struct hd3ss3220 *hd3ss3220, |
256 | struct fwnode_handle *connector) |
257 | { |
258 | /* |
259 | * Supported power operation mode can be configured through device tree |
260 | */ |
261 | const char *cap_str; |
262 | int ret, power_opmode; |
263 | |
264 | ret = fwnode_property_read_string(fwnode: connector, propname: "typec-power-opmode", val: &cap_str); |
265 | if (ret) |
266 | return 0; |
267 | |
268 | power_opmode = typec_find_pwr_opmode(name: cap_str); |
269 | return hd3ss3220_set_power_opmode(hd3ss3220, power_opmode); |
270 | } |
271 | |
272 | static int hd3ss3220_configure_port_type(struct hd3ss3220 *hd3ss3220, |
273 | struct fwnode_handle *connector, |
274 | struct typec_capability *cap) |
275 | { |
276 | /* |
277 | * Port type can be configured through device tree |
278 | */ |
279 | const char *cap_str; |
280 | int ret; |
281 | |
282 | ret = fwnode_property_read_string(fwnode: connector, propname: "power-role", val: &cap_str); |
283 | if (ret) |
284 | return 0; |
285 | |
286 | ret = typec_find_port_power_role(name: cap_str); |
287 | if (ret < 0) |
288 | return ret; |
289 | |
290 | cap->type = ret; |
291 | return hd3ss3220_set_port_type(hd3ss3220, type: cap->type); |
292 | } |
293 | |
294 | static int hd3ss3220_configure_source_pref(struct hd3ss3220 *hd3ss3220, |
295 | struct fwnode_handle *connector, |
296 | struct typec_capability *cap) |
297 | { |
298 | /* |
299 | * Preferred role can be configured through device tree |
300 | */ |
301 | const char *cap_str; |
302 | int ret; |
303 | |
304 | ret = fwnode_property_read_string(fwnode: connector, propname: "try-power-role", val: &cap_str); |
305 | if (ret) |
306 | return 0; |
307 | |
308 | ret = typec_find_power_role(name: cap_str); |
309 | if (ret < 0) |
310 | return ret; |
311 | |
312 | cap->prefer_role = ret; |
313 | return hd3ss3220_set_source_pref(hd3ss3220, prefer_role: cap->prefer_role); |
314 | } |
315 | |
316 | static const struct regmap_config config = { |
317 | .reg_bits = 8, |
318 | .val_bits = 8, |
319 | .max_register = 0x0A, |
320 | }; |
321 | |
322 | static int hd3ss3220_probe(struct i2c_client *client) |
323 | { |
324 | struct typec_capability typec_cap = { }; |
325 | struct hd3ss3220 *hd3ss3220; |
326 | struct fwnode_handle *connector, *ep; |
327 | int ret; |
328 | unsigned int data; |
329 | |
330 | hd3ss3220 = devm_kzalloc(dev: &client->dev, size: sizeof(struct hd3ss3220), |
331 | GFP_KERNEL); |
332 | if (!hd3ss3220) |
333 | return -ENOMEM; |
334 | |
335 | i2c_set_clientdata(client, data: hd3ss3220); |
336 | |
337 | hd3ss3220->dev = &client->dev; |
338 | hd3ss3220->regmap = devm_regmap_init_i2c(client, &config); |
339 | if (IS_ERR(ptr: hd3ss3220->regmap)) |
340 | return PTR_ERR(ptr: hd3ss3220->regmap); |
341 | |
342 | /* For backward compatibility check the connector child node first */ |
343 | connector = device_get_named_child_node(dev: hd3ss3220->dev, childname: "connector"); |
344 | if (connector) { |
345 | hd3ss3220->role_sw = fwnode_usb_role_switch_get(node: connector); |
346 | } else { |
347 | ep = fwnode_graph_get_next_endpoint(dev_fwnode(hd3ss3220->dev), NULL); |
348 | if (!ep) |
349 | return -ENODEV; |
350 | connector = fwnode_graph_get_remote_port_parent(fwnode: ep); |
351 | fwnode_handle_put(fwnode: ep); |
352 | if (!connector) |
353 | return -ENODEV; |
354 | hd3ss3220->role_sw = usb_role_switch_get(dev: hd3ss3220->dev); |
355 | } |
356 | |
357 | if (IS_ERR(ptr: hd3ss3220->role_sw)) { |
358 | ret = PTR_ERR(ptr: hd3ss3220->role_sw); |
359 | goto err_put_fwnode; |
360 | } |
361 | |
362 | typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE; |
363 | typec_cap.driver_data = hd3ss3220; |
364 | typec_cap.type = TYPEC_PORT_DRP; |
365 | typec_cap.data = TYPEC_PORT_DRD; |
366 | typec_cap.ops = &hd3ss3220_ops; |
367 | typec_cap.fwnode = connector; |
368 | |
369 | ret = hd3ss3220_configure_source_pref(hd3ss3220, connector, cap: &typec_cap); |
370 | if (ret < 0) |
371 | goto err_put_role; |
372 | |
373 | ret = hd3ss3220_configure_port_type(hd3ss3220, connector, cap: &typec_cap); |
374 | if (ret < 0) |
375 | goto err_put_role; |
376 | |
377 | hd3ss3220->port = typec_register_port(parent: &client->dev, cap: &typec_cap); |
378 | if (IS_ERR(ptr: hd3ss3220->port)) { |
379 | ret = PTR_ERR(ptr: hd3ss3220->port); |
380 | goto err_put_role; |
381 | } |
382 | |
383 | ret = hd3ss3220_configure_power_opmode(hd3ss3220, connector); |
384 | if (ret < 0) |
385 | goto err_unreg_port; |
386 | |
387 | hd3ss3220_set_role(hd3ss3220); |
388 | ret = regmap_read(map: hd3ss3220->regmap, HD3SS3220_REG_CN_STAT_CTRL, val: &data); |
389 | if (ret < 0) |
390 | goto err_unreg_port; |
391 | |
392 | if (data & HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS) { |
393 | ret = regmap_write(map: hd3ss3220->regmap, |
394 | HD3SS3220_REG_CN_STAT_CTRL, |
395 | val: data | HD3SS3220_REG_CN_STAT_CTRL_INT_STATUS); |
396 | if (ret < 0) |
397 | goto err_unreg_port; |
398 | } |
399 | |
400 | if (client->irq > 0) { |
401 | ret = devm_request_threaded_irq(dev: &client->dev, irq: client->irq, NULL, |
402 | thread_fn: hd3ss3220_irq_handler, |
403 | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, |
404 | devname: "hd3ss3220", dev_id: &client->dev); |
405 | if (ret) |
406 | goto err_unreg_port; |
407 | } else { |
408 | INIT_DELAYED_WORK(&hd3ss3220->output_poll_work, output_poll_execute); |
409 | hd3ss3220->poll = true; |
410 | } |
411 | |
412 | ret = i2c_smbus_read_byte_data(client, HD3SS3220_REG_DEV_REV); |
413 | if (ret < 0) |
414 | goto err_unreg_port; |
415 | |
416 | fwnode_handle_put(fwnode: connector); |
417 | |
418 | if (hd3ss3220->poll) |
419 | schedule_delayed_work(dwork: &hd3ss3220->output_poll_work, HZ); |
420 | |
421 | dev_info(&client->dev, "probed revision=0x%x\n", ret); |
422 | |
423 | return 0; |
424 | err_unreg_port: |
425 | typec_unregister_port(port: hd3ss3220->port); |
426 | err_put_role: |
427 | usb_role_switch_put(sw: hd3ss3220->role_sw); |
428 | err_put_fwnode: |
429 | fwnode_handle_put(fwnode: connector); |
430 | |
431 | return ret; |
432 | } |
433 | |
434 | static void hd3ss3220_remove(struct i2c_client *client) |
435 | { |
436 | struct hd3ss3220 *hd3ss3220 = i2c_get_clientdata(client); |
437 | |
438 | if (hd3ss3220->poll) |
439 | cancel_delayed_work_sync(dwork: &hd3ss3220->output_poll_work); |
440 | |
441 | typec_unregister_port(port: hd3ss3220->port); |
442 | usb_role_switch_put(sw: hd3ss3220->role_sw); |
443 | } |
444 | |
445 | static const struct of_device_id dev_ids[] = { |
446 | { .compatible = "ti,hd3ss3220"}, |
447 | {} |
448 | }; |
449 | MODULE_DEVICE_TABLE(of, dev_ids); |
450 | |
451 | static struct i2c_driver hd3ss3220_driver = { |
452 | .driver = { |
453 | .name = "hd3ss3220", |
454 | .of_match_table = dev_ids, |
455 | }, |
456 | .probe = hd3ss3220_probe, |
457 | .remove = hd3ss3220_remove, |
458 | }; |
459 | |
460 | module_i2c_driver(hd3ss3220_driver); |
461 | |
462 | MODULE_AUTHOR("Biju Das <biju.das@bp.renesas.com>"); |
463 | MODULE_DESCRIPTION("TI HD3SS3220 DRP Port Controller Driver"); |
464 | MODULE_LICENSE("GPL"); |
465 |
Definitions
- hd3ss3220
- hd3ss3220_set_power_opmode
- hd3ss3220_set_port_type
- hd3ss3220_set_source_pref
- hd3ss3220_get_attached_state
- hd3ss3220_try_role
- hd3ss3220_port_type_set
- hd3ss3220_ops
- hd3ss3220_set_role
- output_poll_execute
- hd3ss3220_irq
- hd3ss3220_irq_handler
- hd3ss3220_configure_power_opmode
- hd3ss3220_configure_port_type
- hd3ss3220_configure_source_pref
- config
- hd3ss3220_probe
- hd3ss3220_remove
- dev_ids
Improve your Profiling and Debugging skills
Find out more