1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Willsemi WUSB3801 Type-C port controller driver |
4 | * |
5 | * Copyright (C) 2022 Samuel Holland <samuel@sholland.org> |
6 | */ |
7 | |
8 | #include <linux/i2c.h> |
9 | #include <linux/module.h> |
10 | #include <linux/regmap.h> |
11 | #include <linux/regulator/consumer.h> |
12 | #include <linux/usb/typec.h> |
13 | |
14 | #define WUSB3801_REG_DEVICE_ID 0x01 |
15 | #define WUSB3801_REG_CTRL0 0x02 |
16 | #define WUSB3801_REG_INT 0x03 |
17 | #define WUSB3801_REG_STAT 0x04 |
18 | #define WUSB3801_REG_CTRL1 0x05 |
19 | #define WUSB3801_REG_TEST00 0x06 |
20 | #define WUSB3801_REG_TEST01 0x07 |
21 | #define WUSB3801_REG_TEST02 0x08 |
22 | #define WUSB3801_REG_TEST03 0x09 |
23 | #define WUSB3801_REG_TEST04 0x0a |
24 | #define WUSB3801_REG_TEST05 0x0b |
25 | #define WUSB3801_REG_TEST06 0x0c |
26 | #define WUSB3801_REG_TEST07 0x0d |
27 | #define WUSB3801_REG_TEST08 0x0e |
28 | #define WUSB3801_REG_TEST09 0x0f |
29 | #define WUSB3801_REG_TEST0A 0x10 |
30 | #define WUSB3801_REG_TEST0B 0x11 |
31 | #define WUSB3801_REG_TEST0C 0x12 |
32 | #define WUSB3801_REG_TEST0D 0x13 |
33 | #define WUSB3801_REG_TEST0E 0x14 |
34 | #define WUSB3801_REG_TEST0F 0x15 |
35 | #define WUSB3801_REG_TEST10 0x16 |
36 | #define WUSB3801_REG_TEST11 0x17 |
37 | #define WUSB3801_REG_TEST12 0x18 |
38 | |
39 | #define WUSB3801_DEVICE_ID_VERSION_ID GENMASK(7, 3) |
40 | #define WUSB3801_DEVICE_ID_VENDOR_ID GENMASK(2, 0) |
41 | |
42 | #define WUSB3801_CTRL0_DIS_ACC_SUPPORT BIT(7) |
43 | #define WUSB3801_CTRL0_TRY GENMASK(6, 5) |
44 | #define WUSB3801_CTRL0_TRY_NONE (0x0 << 5) |
45 | #define WUSB3801_CTRL0_TRY_SNK (0x1 << 5) |
46 | #define WUSB3801_CTRL0_TRY_SRC (0x2 << 5) |
47 | #define WUSB3801_CTRL0_CURRENT GENMASK(4, 3) /* SRC */ |
48 | #define WUSB3801_CTRL0_CURRENT_DEFAULT (0x0 << 3) |
49 | #define WUSB3801_CTRL0_CURRENT_1_5A (0x1 << 3) |
50 | #define WUSB3801_CTRL0_CURRENT_3_0A (0x2 << 3) |
51 | #define WUSB3801_CTRL0_ROLE GENMASK(2, 1) |
52 | #define WUSB3801_CTRL0_ROLE_SNK (0x0 << 1) |
53 | #define WUSB3801_CTRL0_ROLE_SRC (0x1 << 1) |
54 | #define WUSB3801_CTRL0_ROLE_DRP (0x2 << 1) |
55 | #define WUSB3801_CTRL0_INT_MASK BIT(0) |
56 | |
57 | #define WUSB3801_INT_ATTACHED BIT(0) |
58 | #define WUSB3801_INT_DETACHED BIT(1) |
59 | |
60 | #define WUSB3801_STAT_VBUS_DETECTED BIT(7) |
61 | #define WUSB3801_STAT_CURRENT GENMASK(6, 5) /* SNK */ |
62 | #define WUSB3801_STAT_CURRENT_STANDBY (0x0 << 5) |
63 | #define WUSB3801_STAT_CURRENT_DEFAULT (0x1 << 5) |
64 | #define WUSB3801_STAT_CURRENT_1_5A (0x2 << 5) |
65 | #define WUSB3801_STAT_CURRENT_3_0A (0x3 << 5) |
66 | #define WUSB3801_STAT_PARTNER GENMASK(4, 2) |
67 | #define WUSB3801_STAT_PARTNER_STANDBY (0x0 << 2) |
68 | #define WUSB3801_STAT_PARTNER_SNK (0x1 << 2) |
69 | #define WUSB3801_STAT_PARTNER_SRC (0x2 << 2) |
70 | #define WUSB3801_STAT_PARTNER_AUDIO (0x3 << 2) |
71 | #define WUSB3801_STAT_PARTNER_DEBUG (0x4 << 2) |
72 | #define WUSB3801_STAT_ORIENTATION GENMASK(1, 0) |
73 | #define WUSB3801_STAT_ORIENTATION_NONE (0x0 << 0) |
74 | #define WUSB3801_STAT_ORIENTATION_CC1 (0x1 << 0) |
75 | #define WUSB3801_STAT_ORIENTATION_CC2 (0x2 << 0) |
76 | #define WUSB3801_STAT_ORIENTATION_BOTH (0x3 << 0) |
77 | |
78 | #define WUSB3801_CTRL1_SM_RESET BIT(0) |
79 | |
80 | #define WUSB3801_TEST01_VENDOR_SUB_ID (BIT(8) | BIT(6)) |
81 | |
82 | #define WUSB3801_TEST02_FORCE_ERR_RCY BIT(8) |
83 | |
84 | #define WUSB3801_TEST0A_WAIT_VBUS BIT(5) |
85 | |
86 | struct wusb3801 { |
87 | struct typec_capability cap; |
88 | struct device *dev; |
89 | struct typec_partner *partner; |
90 | struct typec_port *port; |
91 | struct regmap *regmap; |
92 | struct regulator *vbus_supply; |
93 | unsigned int partner_type; |
94 | enum typec_port_type port_type; |
95 | enum typec_pwr_opmode pwr_opmode; |
96 | bool vbus_on; |
97 | }; |
98 | |
99 | static enum typec_role wusb3801_get_default_role(struct wusb3801 *wusb3801) |
100 | { |
101 | switch (wusb3801->port_type) { |
102 | case TYPEC_PORT_SRC: |
103 | return TYPEC_SOURCE; |
104 | case TYPEC_PORT_SNK: |
105 | return TYPEC_SINK; |
106 | case TYPEC_PORT_DRP: |
107 | default: |
108 | if (wusb3801->cap.prefer_role == TYPEC_SOURCE) |
109 | return TYPEC_SOURCE; |
110 | return TYPEC_SINK; |
111 | } |
112 | } |
113 | |
114 | static int wusb3801_map_port_type(enum typec_port_type type) |
115 | { |
116 | switch (type) { |
117 | case TYPEC_PORT_SRC: |
118 | return WUSB3801_CTRL0_ROLE_SRC; |
119 | case TYPEC_PORT_SNK: |
120 | return WUSB3801_CTRL0_ROLE_SNK; |
121 | case TYPEC_PORT_DRP: |
122 | default: |
123 | return WUSB3801_CTRL0_ROLE_DRP; |
124 | } |
125 | } |
126 | |
127 | static int wusb3801_map_pwr_opmode(enum typec_pwr_opmode mode) |
128 | { |
129 | switch (mode) { |
130 | case TYPEC_PWR_MODE_USB: |
131 | default: |
132 | return WUSB3801_CTRL0_CURRENT_DEFAULT; |
133 | case TYPEC_PWR_MODE_1_5A: |
134 | return WUSB3801_CTRL0_CURRENT_1_5A; |
135 | case TYPEC_PWR_MODE_3_0A: |
136 | return WUSB3801_CTRL0_CURRENT_3_0A; |
137 | } |
138 | } |
139 | |
140 | static unsigned int wusb3801_map_try_role(int role) |
141 | { |
142 | switch (role) { |
143 | case TYPEC_NO_PREFERRED_ROLE: |
144 | default: |
145 | return WUSB3801_CTRL0_TRY_NONE; |
146 | case TYPEC_SINK: |
147 | return WUSB3801_CTRL0_TRY_SNK; |
148 | case TYPEC_SOURCE: |
149 | return WUSB3801_CTRL0_TRY_SRC; |
150 | } |
151 | } |
152 | |
153 | static enum typec_orientation wusb3801_unmap_orientation(unsigned int status) |
154 | { |
155 | switch (status & WUSB3801_STAT_ORIENTATION) { |
156 | case WUSB3801_STAT_ORIENTATION_NONE: |
157 | case WUSB3801_STAT_ORIENTATION_BOTH: |
158 | default: |
159 | return TYPEC_ORIENTATION_NONE; |
160 | case WUSB3801_STAT_ORIENTATION_CC1: |
161 | return TYPEC_ORIENTATION_NORMAL; |
162 | case WUSB3801_STAT_ORIENTATION_CC2: |
163 | return TYPEC_ORIENTATION_REVERSE; |
164 | } |
165 | } |
166 | |
167 | static enum typec_pwr_opmode wusb3801_unmap_pwr_opmode(unsigned int status) |
168 | { |
169 | switch (status & WUSB3801_STAT_CURRENT) { |
170 | case WUSB3801_STAT_CURRENT_STANDBY: |
171 | case WUSB3801_STAT_CURRENT_DEFAULT: |
172 | default: |
173 | return TYPEC_PWR_MODE_USB; |
174 | case WUSB3801_STAT_CURRENT_1_5A: |
175 | return TYPEC_PWR_MODE_1_5A; |
176 | case WUSB3801_STAT_CURRENT_3_0A: |
177 | return TYPEC_PWR_MODE_3_0A; |
178 | } |
179 | } |
180 | |
181 | static int wusb3801_try_role(struct typec_port *port, int role) |
182 | { |
183 | struct wusb3801 *wusb3801 = typec_get_drvdata(port); |
184 | |
185 | return regmap_update_bits(map: wusb3801->regmap, WUSB3801_REG_CTRL0, |
186 | WUSB3801_CTRL0_TRY, |
187 | val: wusb3801_map_try_role(role)); |
188 | } |
189 | |
190 | static int wusb3801_port_type_set(struct typec_port *port, |
191 | enum typec_port_type type) |
192 | { |
193 | struct wusb3801 *wusb3801 = typec_get_drvdata(port); |
194 | int ret; |
195 | |
196 | ret = regmap_update_bits(map: wusb3801->regmap, WUSB3801_REG_CTRL0, |
197 | WUSB3801_CTRL0_ROLE, |
198 | val: wusb3801_map_port_type(type)); |
199 | if (ret) |
200 | return ret; |
201 | |
202 | wusb3801->port_type = type; |
203 | |
204 | return 0; |
205 | } |
206 | |
207 | static const struct typec_operations wusb3801_typec_ops = { |
208 | .try_role = wusb3801_try_role, |
209 | .port_type_set = wusb3801_port_type_set, |
210 | }; |
211 | |
212 | static int wusb3801_hw_init(struct wusb3801 *wusb3801) |
213 | { |
214 | return regmap_write(map: wusb3801->regmap, WUSB3801_REG_CTRL0, |
215 | val: wusb3801_map_try_role(role: wusb3801->cap.prefer_role) | |
216 | wusb3801_map_pwr_opmode(mode: wusb3801->pwr_opmode) | |
217 | wusb3801_map_port_type(type: wusb3801->port_type)); |
218 | } |
219 | |
220 | static void wusb3801_hw_update(struct wusb3801 *wusb3801) |
221 | { |
222 | struct typec_port *port = wusb3801->port; |
223 | struct device *dev = wusb3801->dev; |
224 | unsigned int partner_type, status; |
225 | int ret; |
226 | |
227 | ret = regmap_read(map: wusb3801->regmap, WUSB3801_REG_STAT, val: &status); |
228 | if (ret) { |
229 | dev_warn(dev, "Failed to read port status: %d\n" , ret); |
230 | status = 0; |
231 | } |
232 | dev_dbg(dev, "status = 0x%02x\n" , status); |
233 | |
234 | partner_type = status & WUSB3801_STAT_PARTNER; |
235 | |
236 | if (partner_type == WUSB3801_STAT_PARTNER_SNK) { |
237 | if (!wusb3801->vbus_on) { |
238 | ret = regulator_enable(regulator: wusb3801->vbus_supply); |
239 | if (ret) |
240 | dev_warn(dev, "Failed to enable VBUS: %d\n" , ret); |
241 | wusb3801->vbus_on = true; |
242 | } |
243 | } else { |
244 | if (wusb3801->vbus_on) { |
245 | regulator_disable(regulator: wusb3801->vbus_supply); |
246 | wusb3801->vbus_on = false; |
247 | } |
248 | } |
249 | |
250 | if (partner_type != wusb3801->partner_type) { |
251 | struct typec_partner_desc desc = {}; |
252 | enum typec_data_role data_role; |
253 | enum typec_role pwr_role = wusb3801_get_default_role(wusb3801); |
254 | |
255 | switch (partner_type) { |
256 | case WUSB3801_STAT_PARTNER_STANDBY: |
257 | break; |
258 | case WUSB3801_STAT_PARTNER_SNK: |
259 | pwr_role = TYPEC_SOURCE; |
260 | break; |
261 | case WUSB3801_STAT_PARTNER_SRC: |
262 | pwr_role = TYPEC_SINK; |
263 | break; |
264 | case WUSB3801_STAT_PARTNER_AUDIO: |
265 | desc.accessory = TYPEC_ACCESSORY_AUDIO; |
266 | break; |
267 | case WUSB3801_STAT_PARTNER_DEBUG: |
268 | desc.accessory = TYPEC_ACCESSORY_DEBUG; |
269 | break; |
270 | } |
271 | |
272 | if (wusb3801->partner) { |
273 | typec_unregister_partner(partner: wusb3801->partner); |
274 | wusb3801->partner = NULL; |
275 | } |
276 | |
277 | if (partner_type != WUSB3801_STAT_PARTNER_STANDBY) { |
278 | wusb3801->partner = typec_register_partner(port, desc: &desc); |
279 | if (IS_ERR(ptr: wusb3801->partner)) |
280 | dev_err(dev, "Failed to register partner: %ld\n" , |
281 | PTR_ERR(wusb3801->partner)); |
282 | } |
283 | |
284 | data_role = pwr_role == TYPEC_SOURCE ? TYPEC_HOST : TYPEC_DEVICE; |
285 | typec_set_data_role(port, role: data_role); |
286 | typec_set_pwr_role(port, role: pwr_role); |
287 | typec_set_vconn_role(port, role: pwr_role); |
288 | } |
289 | |
290 | typec_set_pwr_opmode(port: wusb3801->port, |
291 | mode: partner_type == WUSB3801_STAT_PARTNER_SRC |
292 | ? wusb3801_unmap_pwr_opmode(status) |
293 | : wusb3801->pwr_opmode); |
294 | typec_set_orientation(port: wusb3801->port, |
295 | orientation: wusb3801_unmap_orientation(status)); |
296 | |
297 | wusb3801->partner_type = partner_type; |
298 | } |
299 | |
300 | static irqreturn_t wusb3801_irq(int irq, void *data) |
301 | { |
302 | struct wusb3801 *wusb3801 = data; |
303 | unsigned int dummy; |
304 | |
305 | /* |
306 | * The interrupt register must be read in order to clear the IRQ, |
307 | * but all of the useful information is in the status register. |
308 | */ |
309 | regmap_read(map: wusb3801->regmap, WUSB3801_REG_INT, val: &dummy); |
310 | |
311 | wusb3801_hw_update(wusb3801); |
312 | |
313 | return IRQ_HANDLED; |
314 | } |
315 | |
316 | static const struct regmap_config config = { |
317 | .reg_bits = 8, |
318 | .val_bits = 8, |
319 | .max_register = WUSB3801_REG_TEST12, |
320 | }; |
321 | |
322 | static int wusb3801_probe(struct i2c_client *client) |
323 | { |
324 | struct device *dev = &client->dev; |
325 | struct fwnode_handle *connector; |
326 | struct wusb3801 *wusb3801; |
327 | const char *cap_str; |
328 | int ret; |
329 | |
330 | wusb3801 = devm_kzalloc(dev, size: sizeof(*wusb3801), GFP_KERNEL); |
331 | if (!wusb3801) |
332 | return -ENOMEM; |
333 | |
334 | i2c_set_clientdata(client, data: wusb3801); |
335 | |
336 | wusb3801->dev = dev; |
337 | |
338 | wusb3801->regmap = devm_regmap_init_i2c(client, &config); |
339 | if (IS_ERR(ptr: wusb3801->regmap)) |
340 | return PTR_ERR(ptr: wusb3801->regmap); |
341 | |
342 | wusb3801->vbus_supply = devm_regulator_get(dev, id: "vbus" ); |
343 | if (IS_ERR(ptr: wusb3801->vbus_supply)) |
344 | return PTR_ERR(ptr: wusb3801->vbus_supply); |
345 | |
346 | connector = device_get_named_child_node(dev, childname: "connector" ); |
347 | if (!connector) |
348 | return -ENODEV; |
349 | |
350 | ret = typec_get_fw_cap(cap: &wusb3801->cap, fwnode: connector); |
351 | if (ret) |
352 | goto err_put_connector; |
353 | wusb3801->port_type = wusb3801->cap.type; |
354 | |
355 | ret = fwnode_property_read_string(fwnode: connector, propname: "typec-power-opmode" , val: &cap_str); |
356 | if (ret) |
357 | goto err_put_connector; |
358 | |
359 | ret = typec_find_pwr_opmode(name: cap_str); |
360 | if (ret < 0 || ret == TYPEC_PWR_MODE_PD) |
361 | goto err_put_connector; |
362 | wusb3801->pwr_opmode = ret; |
363 | |
364 | /* Initialize the hardware with the devicetree settings. */ |
365 | ret = wusb3801_hw_init(wusb3801); |
366 | if (ret) |
367 | goto err_put_connector; |
368 | |
369 | wusb3801->cap.revision = USB_TYPEC_REV_1_2; |
370 | wusb3801->cap.accessory[0] = TYPEC_ACCESSORY_AUDIO; |
371 | wusb3801->cap.accessory[1] = TYPEC_ACCESSORY_DEBUG; |
372 | wusb3801->cap.orientation_aware = true; |
373 | wusb3801->cap.driver_data = wusb3801; |
374 | wusb3801->cap.ops = &wusb3801_typec_ops; |
375 | |
376 | wusb3801->port = typec_register_port(parent: dev, cap: &wusb3801->cap); |
377 | if (IS_ERR(ptr: wusb3801->port)) { |
378 | ret = PTR_ERR(ptr: wusb3801->port); |
379 | goto err_put_connector; |
380 | } |
381 | |
382 | /* Initialize the port attributes from the hardware state. */ |
383 | wusb3801_hw_update(wusb3801); |
384 | |
385 | ret = request_threaded_irq(irq: client->irq, NULL, thread_fn: wusb3801_irq, |
386 | IRQF_ONESHOT, name: dev_name(dev), dev: wusb3801); |
387 | if (ret) |
388 | goto err_unregister_port; |
389 | |
390 | fwnode_handle_put(fwnode: connector); |
391 | |
392 | return 0; |
393 | |
394 | err_unregister_port: |
395 | typec_unregister_port(port: wusb3801->port); |
396 | err_put_connector: |
397 | fwnode_handle_put(fwnode: connector); |
398 | |
399 | return ret; |
400 | } |
401 | |
402 | static void wusb3801_remove(struct i2c_client *client) |
403 | { |
404 | struct wusb3801 *wusb3801 = i2c_get_clientdata(client); |
405 | |
406 | free_irq(client->irq, wusb3801); |
407 | |
408 | if (wusb3801->partner) |
409 | typec_unregister_partner(partner: wusb3801->partner); |
410 | typec_unregister_port(port: wusb3801->port); |
411 | |
412 | if (wusb3801->vbus_on) |
413 | regulator_disable(regulator: wusb3801->vbus_supply); |
414 | } |
415 | |
416 | static const struct of_device_id wusb3801_of_match[] = { |
417 | { .compatible = "willsemi,wusb3801" }, |
418 | {} |
419 | }; |
420 | MODULE_DEVICE_TABLE(of, wusb3801_of_match); |
421 | |
422 | static struct i2c_driver wusb3801_driver = { |
423 | .probe = wusb3801_probe, |
424 | .remove = wusb3801_remove, |
425 | .driver = { |
426 | .name = "wusb3801" , |
427 | .of_match_table = wusb3801_of_match, |
428 | }, |
429 | }; |
430 | |
431 | module_i2c_driver(wusb3801_driver); |
432 | |
433 | MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>" ); |
434 | MODULE_DESCRIPTION("Willsemi WUSB3801 Type-C port controller driver" ); |
435 | MODULE_LICENSE("GPL" ); |
436 | |