1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Extcon charger detection driver for Intel Cherrytrail Whiskey Cove PMIC |
4 | * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com> |
5 | * |
6 | * Based on various non upstream patches to support the CHT Whiskey Cove PMIC: |
7 | * Copyright (C) 2013-2015 Intel Corporation. All rights reserved. |
8 | */ |
9 | |
10 | #include <linux/extcon-provider.h> |
11 | #include <linux/interrupt.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/mfd/intel_soc_pmic.h> |
14 | #include <linux/module.h> |
15 | #include <linux/mod_devicetable.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/power_supply.h> |
18 | #include <linux/property.h> |
19 | #include <linux/regmap.h> |
20 | #include <linux/regulator/consumer.h> |
21 | #include <linux/slab.h> |
22 | #include <linux/usb/role.h> |
23 | |
24 | #include "extcon-intel.h" |
25 | |
26 | #define CHT_WC_PHYCTRL 0x5e07 |
27 | |
28 | #define CHT_WC_CHGRCTRL0 0x5e16 |
29 | #define CHT_WC_CHGRCTRL0_CHGRRESET BIT(0) |
30 | #define CHT_WC_CHGRCTRL0_EMRGCHREN BIT(1) |
31 | #define CHT_WC_CHGRCTRL0_EXTCHRDIS BIT(2) |
32 | #define CHT_WC_CHGRCTRL0_SWCONTROL BIT(3) |
33 | #define CHT_WC_CHGRCTRL0_TTLCK BIT(4) |
34 | #define CHT_WC_CHGRCTRL0_CCSM_OFF BIT(5) |
35 | #define CHT_WC_CHGRCTRL0_DBPOFF BIT(6) |
36 | #define CHT_WC_CHGRCTRL0_CHR_WDT_NOKICK BIT(7) |
37 | |
38 | #define CHT_WC_CHGRCTRL1 0x5e17 |
39 | #define CHT_WC_CHGRCTRL1_FUSB_INLMT_100 BIT(0) |
40 | #define CHT_WC_CHGRCTRL1_FUSB_INLMT_150 BIT(1) |
41 | #define CHT_WC_CHGRCTRL1_FUSB_INLMT_500 BIT(2) |
42 | #define CHT_WC_CHGRCTRL1_FUSB_INLMT_900 BIT(3) |
43 | #define CHT_WC_CHGRCTRL1_FUSB_INLMT_1500 BIT(4) |
44 | #define CHT_WC_CHGRCTRL1_FTEMP_EVENT BIT(5) |
45 | #define CHT_WC_CHGRCTRL1_OTGMODE BIT(6) |
46 | #define CHT_WC_CHGRCTRL1_DBPEN BIT(7) |
47 | |
48 | #define CHT_WC_USBSRC 0x5e29 |
49 | #define CHT_WC_USBSRC_STS_MASK GENMASK(1, 0) |
50 | #define CHT_WC_USBSRC_STS_SUCCESS 2 |
51 | #define CHT_WC_USBSRC_STS_FAIL 3 |
52 | #define CHT_WC_USBSRC_TYPE_SHIFT 2 |
53 | #define CHT_WC_USBSRC_TYPE_MASK GENMASK(5, 2) |
54 | #define CHT_WC_USBSRC_TYPE_NONE 0 |
55 | #define CHT_WC_USBSRC_TYPE_SDP 1 |
56 | #define CHT_WC_USBSRC_TYPE_DCP 2 |
57 | #define CHT_WC_USBSRC_TYPE_CDP 3 |
58 | #define CHT_WC_USBSRC_TYPE_ACA 4 |
59 | #define CHT_WC_USBSRC_TYPE_SE1 5 |
60 | #define CHT_WC_USBSRC_TYPE_MHL 6 |
61 | #define CHT_WC_USBSRC_TYPE_FLOATING 7 |
62 | #define CHT_WC_USBSRC_TYPE_OTHER 8 |
63 | #define CHT_WC_USBSRC_TYPE_DCP_EXTPHY 9 |
64 | |
65 | #define CHT_WC_CHGDISCTRL 0x5e2f |
66 | #define CHT_WC_CHGDISCTRL_OUT BIT(0) |
67 | /* 0 - open drain, 1 - regular push-pull output */ |
68 | #define CHT_WC_CHGDISCTRL_DRV BIT(4) |
69 | /* 0 - pin is controlled by SW, 1 - by HW */ |
70 | #define CHT_WC_CHGDISCTRL_FN BIT(6) |
71 | |
72 | #define CHT_WC_PWRSRC_IRQ 0x6e03 |
73 | #define CHT_WC_PWRSRC_IRQ_MASK 0x6e0f |
74 | #define CHT_WC_PWRSRC_STS 0x6e1e |
75 | #define CHT_WC_PWRSRC_VBUS BIT(0) |
76 | #define CHT_WC_PWRSRC_DC BIT(1) |
77 | #define CHT_WC_PWRSRC_BATT BIT(2) |
78 | #define CHT_WC_PWRSRC_USBID_MASK GENMASK(4, 3) |
79 | #define CHT_WC_PWRSRC_USBID_SHIFT 3 |
80 | #define CHT_WC_PWRSRC_RID_ACA 0 |
81 | #define CHT_WC_PWRSRC_RID_GND 1 |
82 | #define CHT_WC_PWRSRC_RID_FLOAT 2 |
83 | |
84 | #define CHT_WC_VBUS_GPIO_CTLO 0x6e2d |
85 | #define CHT_WC_VBUS_GPIO_CTLO_OUTPUT BIT(0) |
86 | #define CHT_WC_VBUS_GPIO_CTLO_DRV_OD BIT(4) |
87 | #define CHT_WC_VBUS_GPIO_CTLO_DIR_OUT BIT(5) |
88 | |
89 | enum cht_wc_mux_select { |
90 | MUX_SEL_PMIC = 0, |
91 | MUX_SEL_SOC, |
92 | }; |
93 | |
94 | static const unsigned int cht_wc_extcon_cables[] = { |
95 | EXTCON_USB, |
96 | EXTCON_USB_HOST, |
97 | EXTCON_CHG_USB_SDP, |
98 | EXTCON_CHG_USB_CDP, |
99 | EXTCON_CHG_USB_DCP, |
100 | EXTCON_CHG_USB_ACA, |
101 | EXTCON_NONE, |
102 | }; |
103 | |
104 | struct cht_wc_extcon_data { |
105 | struct device *dev; |
106 | struct regmap *regmap; |
107 | struct extcon_dev *edev; |
108 | struct usb_role_switch *role_sw; |
109 | struct regulator *vbus_boost; |
110 | struct power_supply *psy; |
111 | enum power_supply_usb_type usb_type; |
112 | unsigned int previous_cable; |
113 | bool usb_host; |
114 | bool vbus_boost_enabled; |
115 | }; |
116 | |
117 | static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts) |
118 | { |
119 | switch ((pwrsrc_sts & CHT_WC_PWRSRC_USBID_MASK) >> CHT_WC_PWRSRC_USBID_SHIFT) { |
120 | case CHT_WC_PWRSRC_RID_GND: |
121 | return INTEL_USB_ID_GND; |
122 | case CHT_WC_PWRSRC_RID_FLOAT: |
123 | return INTEL_USB_ID_FLOAT; |
124 | /* |
125 | * According to the spec. we should read the USB-ID pin ADC value here |
126 | * to determine the resistance of the used pull-down resister and then |
127 | * return RID_A / RID_B / RID_C based on this. But all "Accessory |
128 | * Charger Adapter"s (ACAs) which users can actually buy always use |
129 | * a combination of a charging port with one or more USB-A ports, so |
130 | * they should always use a resistor indicating RID_A. But the spec |
131 | * is hard to read / badly-worded so some of them actually indicate |
132 | * they are a RID_B ACA evnen though they clearly are a RID_A ACA. |
133 | * To workaround this simply always return INTEL_USB_RID_A, which |
134 | * matches all the ACAs which users can actually buy. |
135 | */ |
136 | case CHT_WC_PWRSRC_RID_ACA: |
137 | return INTEL_USB_RID_A; |
138 | default: |
139 | return INTEL_USB_ID_FLOAT; |
140 | } |
141 | } |
142 | |
143 | static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext, |
144 | bool ignore_errors) |
145 | { |
146 | int ret, usbsrc, status; |
147 | unsigned long timeout; |
148 | |
149 | /* Charger detection can take upto 600ms, wait 800ms max. */ |
150 | timeout = jiffies + msecs_to_jiffies(m: 800); |
151 | do { |
152 | ret = regmap_read(map: ext->regmap, CHT_WC_USBSRC, val: &usbsrc); |
153 | if (ret) { |
154 | dev_err(ext->dev, "Error reading usbsrc: %d\n" , ret); |
155 | return ret; |
156 | } |
157 | |
158 | status = usbsrc & CHT_WC_USBSRC_STS_MASK; |
159 | if (status == CHT_WC_USBSRC_STS_SUCCESS || |
160 | status == CHT_WC_USBSRC_STS_FAIL) |
161 | break; |
162 | |
163 | msleep(msecs: 50); /* Wait a bit before retrying */ |
164 | } while (time_before(jiffies, timeout)); |
165 | |
166 | if (status != CHT_WC_USBSRC_STS_SUCCESS) { |
167 | if (!ignore_errors) { |
168 | if (status == CHT_WC_USBSRC_STS_FAIL) |
169 | dev_warn(ext->dev, "Could not detect charger type\n" ); |
170 | else |
171 | dev_warn(ext->dev, "Timeout detecting charger type\n" ); |
172 | } |
173 | |
174 | /* Safe fallback */ |
175 | usbsrc = CHT_WC_USBSRC_TYPE_SDP << CHT_WC_USBSRC_TYPE_SHIFT; |
176 | } |
177 | |
178 | usbsrc = (usbsrc & CHT_WC_USBSRC_TYPE_MASK) >> CHT_WC_USBSRC_TYPE_SHIFT; |
179 | switch (usbsrc) { |
180 | default: |
181 | dev_warn(ext->dev, |
182 | "Unhandled charger type %d, defaulting to SDP\n" , |
183 | ret); |
184 | ext->usb_type = POWER_SUPPLY_USB_TYPE_SDP; |
185 | return EXTCON_CHG_USB_SDP; |
186 | case CHT_WC_USBSRC_TYPE_SDP: |
187 | case CHT_WC_USBSRC_TYPE_FLOATING: |
188 | case CHT_WC_USBSRC_TYPE_OTHER: |
189 | ext->usb_type = POWER_SUPPLY_USB_TYPE_SDP; |
190 | return EXTCON_CHG_USB_SDP; |
191 | case CHT_WC_USBSRC_TYPE_CDP: |
192 | ext->usb_type = POWER_SUPPLY_USB_TYPE_CDP; |
193 | return EXTCON_CHG_USB_CDP; |
194 | case CHT_WC_USBSRC_TYPE_DCP: |
195 | case CHT_WC_USBSRC_TYPE_DCP_EXTPHY: |
196 | case CHT_WC_USBSRC_TYPE_MHL: /* MHL2+ delivers upto 2A, treat as DCP */ |
197 | ext->usb_type = POWER_SUPPLY_USB_TYPE_DCP; |
198 | return EXTCON_CHG_USB_DCP; |
199 | case CHT_WC_USBSRC_TYPE_ACA: |
200 | ext->usb_type = POWER_SUPPLY_USB_TYPE_ACA; |
201 | return EXTCON_CHG_USB_ACA; |
202 | } |
203 | } |
204 | |
205 | static void cht_wc_extcon_set_phymux(struct cht_wc_extcon_data *ext, u8 state) |
206 | { |
207 | int ret; |
208 | |
209 | ret = regmap_write(map: ext->regmap, CHT_WC_PHYCTRL, val: state); |
210 | if (ret) |
211 | dev_err(ext->dev, "Error writing phyctrl: %d\n" , ret); |
212 | } |
213 | |
214 | static void cht_wc_extcon_set_5v_boost(struct cht_wc_extcon_data *ext, |
215 | bool enable) |
216 | { |
217 | int ret, val; |
218 | |
219 | /* |
220 | * The 5V boost converter is enabled through a gpio on the PMIC, since |
221 | * there currently is no gpio driver we access the gpio reg directly. |
222 | */ |
223 | val = CHT_WC_VBUS_GPIO_CTLO_DRV_OD | CHT_WC_VBUS_GPIO_CTLO_DIR_OUT; |
224 | if (enable) |
225 | val |= CHT_WC_VBUS_GPIO_CTLO_OUTPUT; |
226 | |
227 | ret = regmap_write(map: ext->regmap, CHT_WC_VBUS_GPIO_CTLO, val); |
228 | if (ret) |
229 | dev_err(ext->dev, "Error writing Vbus GPIO CTLO: %d\n" , ret); |
230 | } |
231 | |
232 | static void cht_wc_extcon_set_otgmode(struct cht_wc_extcon_data *ext, |
233 | bool enable) |
234 | { |
235 | unsigned int val = enable ? CHT_WC_CHGRCTRL1_OTGMODE : 0; |
236 | int ret; |
237 | |
238 | ret = regmap_update_bits(map: ext->regmap, CHT_WC_CHGRCTRL1, |
239 | CHT_WC_CHGRCTRL1_OTGMODE, val); |
240 | if (ret) |
241 | dev_err(ext->dev, "Error updating CHGRCTRL1 reg: %d\n" , ret); |
242 | |
243 | if (ext->vbus_boost && ext->vbus_boost_enabled != enable) { |
244 | if (enable) |
245 | ret = regulator_enable(regulator: ext->vbus_boost); |
246 | else |
247 | ret = regulator_disable(regulator: ext->vbus_boost); |
248 | |
249 | if (ret) |
250 | dev_err(ext->dev, "Error updating Vbus boost regulator: %d\n" , ret); |
251 | else |
252 | ext->vbus_boost_enabled = enable; |
253 | } |
254 | } |
255 | |
256 | static void cht_wc_extcon_enable_charging(struct cht_wc_extcon_data *ext, |
257 | bool enable) |
258 | { |
259 | unsigned int val = enable ? 0 : CHT_WC_CHGDISCTRL_OUT; |
260 | int ret; |
261 | |
262 | ret = regmap_update_bits(map: ext->regmap, CHT_WC_CHGDISCTRL, |
263 | CHT_WC_CHGDISCTRL_OUT, val); |
264 | if (ret) |
265 | dev_err(ext->dev, "Error updating CHGDISCTRL reg: %d\n" , ret); |
266 | } |
267 | |
268 | /* Small helper to sync EXTCON_CHG_USB_SDP and EXTCON_USB state */ |
269 | static void cht_wc_extcon_set_state(struct cht_wc_extcon_data *ext, |
270 | unsigned int cable, bool state) |
271 | { |
272 | extcon_set_state_sync(edev: ext->edev, id: cable, state); |
273 | if (cable == EXTCON_CHG_USB_SDP) |
274 | extcon_set_state_sync(edev: ext->edev, EXTCON_USB, state); |
275 | } |
276 | |
277 | static void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext) |
278 | { |
279 | int ret, pwrsrc_sts, id; |
280 | unsigned int cable = EXTCON_NONE; |
281 | /* Ignore errors in host mode, as the 5v boost converter is on then */ |
282 | bool ignore_get_charger_errors = ext->usb_host; |
283 | enum usb_role role; |
284 | |
285 | ext->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN; |
286 | |
287 | ret = regmap_read(map: ext->regmap, CHT_WC_PWRSRC_STS, val: &pwrsrc_sts); |
288 | if (ret) { |
289 | dev_err(ext->dev, "Error reading pwrsrc status: %d\n" , ret); |
290 | return; |
291 | } |
292 | |
293 | id = cht_wc_extcon_get_id(ext, pwrsrc_sts); |
294 | if (id == INTEL_USB_ID_GND) { |
295 | cht_wc_extcon_enable_charging(ext, enable: false); |
296 | cht_wc_extcon_set_otgmode(ext, enable: true); |
297 | |
298 | /* The 5v boost causes a false VBUS / SDP detect, skip */ |
299 | goto charger_det_done; |
300 | } |
301 | |
302 | cht_wc_extcon_set_otgmode(ext, enable: false); |
303 | cht_wc_extcon_enable_charging(ext, enable: true); |
304 | |
305 | /* Plugged into a host/charger or not connected? */ |
306 | if (!(pwrsrc_sts & CHT_WC_PWRSRC_VBUS)) { |
307 | /* Route D+ and D- to PMIC for future charger detection */ |
308 | cht_wc_extcon_set_phymux(ext, state: MUX_SEL_PMIC); |
309 | goto set_state; |
310 | } |
311 | |
312 | ret = cht_wc_extcon_get_charger(ext, ignore_errors: ignore_get_charger_errors); |
313 | if (ret >= 0) |
314 | cable = ret; |
315 | |
316 | charger_det_done: |
317 | /* Route D+ and D- to SoC for the host or gadget controller */ |
318 | cht_wc_extcon_set_phymux(ext, state: MUX_SEL_SOC); |
319 | |
320 | set_state: |
321 | if (cable != ext->previous_cable) { |
322 | cht_wc_extcon_set_state(ext, cable, state: true); |
323 | cht_wc_extcon_set_state(ext, cable: ext->previous_cable, state: false); |
324 | ext->previous_cable = cable; |
325 | } |
326 | |
327 | ext->usb_host = ((id == INTEL_USB_ID_GND) || (id == INTEL_USB_RID_A)); |
328 | extcon_set_state_sync(edev: ext->edev, EXTCON_USB_HOST, state: ext->usb_host); |
329 | |
330 | if (ext->usb_host) |
331 | role = USB_ROLE_HOST; |
332 | else if (pwrsrc_sts & CHT_WC_PWRSRC_VBUS) |
333 | role = USB_ROLE_DEVICE; |
334 | else |
335 | role = USB_ROLE_NONE; |
336 | |
337 | /* Note: this is a no-op when ext->role_sw is NULL */ |
338 | ret = usb_role_switch_set_role(sw: ext->role_sw, role); |
339 | if (ret) |
340 | dev_err(ext->dev, "Error setting USB-role: %d\n" , ret); |
341 | |
342 | if (ext->psy) |
343 | power_supply_changed(psy: ext->psy); |
344 | } |
345 | |
346 | static irqreturn_t cht_wc_extcon_isr(int irq, void *data) |
347 | { |
348 | struct cht_wc_extcon_data *ext = data; |
349 | int ret, irqs; |
350 | |
351 | ret = regmap_read(map: ext->regmap, CHT_WC_PWRSRC_IRQ, val: &irqs); |
352 | if (ret) { |
353 | dev_err(ext->dev, "Error reading irqs: %d\n" , ret); |
354 | return IRQ_NONE; |
355 | } |
356 | |
357 | cht_wc_extcon_pwrsrc_event(ext); |
358 | |
359 | ret = regmap_write(map: ext->regmap, CHT_WC_PWRSRC_IRQ, val: irqs); |
360 | if (ret) { |
361 | dev_err(ext->dev, "Error writing irqs: %d\n" , ret); |
362 | return IRQ_NONE; |
363 | } |
364 | |
365 | return IRQ_HANDLED; |
366 | } |
367 | |
368 | static int cht_wc_extcon_sw_control(struct cht_wc_extcon_data *ext, bool enable) |
369 | { |
370 | int ret, mask, val; |
371 | |
372 | val = enable ? 0 : CHT_WC_CHGDISCTRL_FN; |
373 | ret = regmap_update_bits(map: ext->regmap, CHT_WC_CHGDISCTRL, |
374 | CHT_WC_CHGDISCTRL_FN, val); |
375 | if (ret) |
376 | dev_err(ext->dev, |
377 | "Error setting sw control for CHGDIS pin: %d\n" , |
378 | ret); |
379 | |
380 | mask = CHT_WC_CHGRCTRL0_SWCONTROL | CHT_WC_CHGRCTRL0_CCSM_OFF; |
381 | val = enable ? mask : 0; |
382 | ret = regmap_update_bits(map: ext->regmap, CHT_WC_CHGRCTRL0, mask, val); |
383 | if (ret) |
384 | dev_err(ext->dev, "Error setting sw control: %d\n" , ret); |
385 | |
386 | return ret; |
387 | } |
388 | |
389 | static int cht_wc_extcon_find_role_sw(struct cht_wc_extcon_data *ext) |
390 | { |
391 | const struct software_node *swnode; |
392 | struct fwnode_handle *fwnode; |
393 | |
394 | swnode = software_node_find_by_name(NULL, name: "intel-xhci-usb-sw" ); |
395 | if (!swnode) |
396 | return -EPROBE_DEFER; |
397 | |
398 | fwnode = software_node_fwnode(node: swnode); |
399 | ext->role_sw = usb_role_switch_find_by_fwnode(fwnode); |
400 | fwnode_handle_put(fwnode); |
401 | |
402 | return ext->role_sw ? 0 : -EPROBE_DEFER; |
403 | } |
404 | |
405 | static void cht_wc_extcon_put_role_sw(void *data) |
406 | { |
407 | struct cht_wc_extcon_data *ext = data; |
408 | |
409 | usb_role_switch_put(sw: ext->role_sw); |
410 | } |
411 | |
412 | /* Some boards require controlling the role-sw and Vbus based on the id-pin */ |
413 | static int cht_wc_extcon_get_role_sw_and_regulator(struct cht_wc_extcon_data *ext) |
414 | { |
415 | int ret; |
416 | |
417 | ret = cht_wc_extcon_find_role_sw(ext); |
418 | if (ret) |
419 | return ret; |
420 | |
421 | ret = devm_add_action_or_reset(ext->dev, cht_wc_extcon_put_role_sw, ext); |
422 | if (ret) |
423 | return ret; |
424 | |
425 | /* |
426 | * On x86/ACPI platforms the regulator <-> consumer link is provided |
427 | * by platform_data passed to the regulator driver. This means that |
428 | * this info is not available before the regulator driver has bound. |
429 | * Use devm_regulator_get_optional() to avoid getting a dummy |
430 | * regulator and wait for the regulator to show up if necessary. |
431 | */ |
432 | ext->vbus_boost = devm_regulator_get_optional(dev: ext->dev, id: "vbus" ); |
433 | if (IS_ERR(ptr: ext->vbus_boost)) { |
434 | ret = PTR_ERR(ptr: ext->vbus_boost); |
435 | if (ret == -ENODEV) |
436 | ret = -EPROBE_DEFER; |
437 | |
438 | return dev_err_probe(dev: ext->dev, err: ret, fmt: "getting Vbus regulator" ); |
439 | } |
440 | |
441 | return 0; |
442 | } |
443 | |
444 | static int cht_wc_extcon_psy_get_prop(struct power_supply *psy, |
445 | enum power_supply_property psp, |
446 | union power_supply_propval *val) |
447 | { |
448 | struct cht_wc_extcon_data *ext = power_supply_get_drvdata(psy); |
449 | |
450 | switch (psp) { |
451 | case POWER_SUPPLY_PROP_USB_TYPE: |
452 | val->intval = ext->usb_type; |
453 | break; |
454 | case POWER_SUPPLY_PROP_ONLINE: |
455 | val->intval = ext->usb_type ? 1 : 0; |
456 | break; |
457 | default: |
458 | return -EINVAL; |
459 | } |
460 | |
461 | return 0; |
462 | } |
463 | |
464 | static const enum power_supply_usb_type cht_wc_extcon_psy_usb_types[] = { |
465 | POWER_SUPPLY_USB_TYPE_SDP, |
466 | POWER_SUPPLY_USB_TYPE_CDP, |
467 | POWER_SUPPLY_USB_TYPE_DCP, |
468 | POWER_SUPPLY_USB_TYPE_ACA, |
469 | POWER_SUPPLY_USB_TYPE_UNKNOWN, |
470 | }; |
471 | |
472 | static const enum power_supply_property cht_wc_extcon_psy_props[] = { |
473 | POWER_SUPPLY_PROP_USB_TYPE, |
474 | POWER_SUPPLY_PROP_ONLINE, |
475 | }; |
476 | |
477 | static const struct power_supply_desc cht_wc_extcon_psy_desc = { |
478 | .name = "cht_wcove_pwrsrc" , |
479 | .type = POWER_SUPPLY_TYPE_USB, |
480 | .usb_types = cht_wc_extcon_psy_usb_types, |
481 | .num_usb_types = ARRAY_SIZE(cht_wc_extcon_psy_usb_types), |
482 | .properties = cht_wc_extcon_psy_props, |
483 | .num_properties = ARRAY_SIZE(cht_wc_extcon_psy_props), |
484 | .get_property = cht_wc_extcon_psy_get_prop, |
485 | }; |
486 | |
487 | static int cht_wc_extcon_register_psy(struct cht_wc_extcon_data *ext) |
488 | { |
489 | struct power_supply_config psy_cfg = { .drv_data = ext }; |
490 | |
491 | ext->psy = devm_power_supply_register(parent: ext->dev, |
492 | desc: &cht_wc_extcon_psy_desc, |
493 | cfg: &psy_cfg); |
494 | return PTR_ERR_OR_ZERO(ptr: ext->psy); |
495 | } |
496 | |
497 | static int cht_wc_extcon_probe(struct platform_device *pdev) |
498 | { |
499 | struct intel_soc_pmic *pmic = dev_get_drvdata(dev: pdev->dev.parent); |
500 | struct cht_wc_extcon_data *ext; |
501 | unsigned long mask = ~(CHT_WC_PWRSRC_VBUS | CHT_WC_PWRSRC_USBID_MASK); |
502 | int pwrsrc_sts, id; |
503 | int irq, ret; |
504 | |
505 | irq = platform_get_irq(pdev, 0); |
506 | if (irq < 0) |
507 | return irq; |
508 | |
509 | ext = devm_kzalloc(dev: &pdev->dev, size: sizeof(*ext), GFP_KERNEL); |
510 | if (!ext) |
511 | return -ENOMEM; |
512 | |
513 | ext->dev = &pdev->dev; |
514 | ext->regmap = pmic->regmap; |
515 | ext->previous_cable = EXTCON_NONE; |
516 | |
517 | /* Initialize extcon device */ |
518 | ext->edev = devm_extcon_dev_allocate(dev: ext->dev, cable: cht_wc_extcon_cables); |
519 | if (IS_ERR(ptr: ext->edev)) |
520 | return PTR_ERR(ptr: ext->edev); |
521 | |
522 | switch (pmic->cht_wc_model) { |
523 | case INTEL_CHT_WC_GPD_WIN_POCKET: |
524 | /* |
525 | * When a host-cable is detected the BIOS enables an external 5v boost |
526 | * converter to power connected devices there are 2 problems with this: |
527 | * 1) This gets seen by the external battery charger as a valid Vbus |
528 | * supply and it then tries to feed Vsys from this creating a |
529 | * feedback loop which causes aprox. 300 mA extra battery drain |
530 | * (and unless we drive the external-charger-disable pin high it |
531 | * also tries to charge the battery causing even more feedback). |
532 | * 2) This gets seen by the pwrsrc block as a SDP USB Vbus supply |
533 | * Since the external battery charger has its own 5v boost converter |
534 | * which does not have these issues, we simply turn the separate |
535 | * external 5v boost converter off and leave it off entirely. |
536 | */ |
537 | cht_wc_extcon_set_5v_boost(ext, enable: false); |
538 | break; |
539 | case INTEL_CHT_WC_LENOVO_YOGABOOK1: |
540 | case INTEL_CHT_WC_LENOVO_YT3_X90: |
541 | /* Do this first, as it may very well return -EPROBE_DEFER. */ |
542 | ret = cht_wc_extcon_get_role_sw_and_regulator(ext); |
543 | if (ret) |
544 | return ret; |
545 | /* |
546 | * The bq25890 used here relies on this driver's BC-1.2 charger |
547 | * detection, and the bq25890 driver expect this info to be |
548 | * available through a parent power_supply class device which |
549 | * models the detected charger (idem to how the Type-C TCPM code |
550 | * registers a power_supply classdev for the connected charger). |
551 | */ |
552 | ret = cht_wc_extcon_register_psy(ext); |
553 | if (ret) |
554 | return ret; |
555 | break; |
556 | case INTEL_CHT_WC_XIAOMI_MIPAD2: |
557 | ret = cht_wc_extcon_get_role_sw_and_regulator(ext); |
558 | if (ret) |
559 | return ret; |
560 | break; |
561 | default: |
562 | break; |
563 | } |
564 | |
565 | /* Enable sw control */ |
566 | ret = cht_wc_extcon_sw_control(ext, enable: true); |
567 | if (ret) |
568 | goto disable_sw_control; |
569 | |
570 | /* Disable charging by external battery charger */ |
571 | cht_wc_extcon_enable_charging(ext, enable: false); |
572 | |
573 | /* Register extcon device */ |
574 | ret = devm_extcon_dev_register(dev: ext->dev, edev: ext->edev); |
575 | if (ret) { |
576 | dev_err(ext->dev, "Error registering extcon device: %d\n" , ret); |
577 | goto disable_sw_control; |
578 | } |
579 | |
580 | ret = regmap_read(map: ext->regmap, CHT_WC_PWRSRC_STS, val: &pwrsrc_sts); |
581 | if (ret) { |
582 | dev_err(ext->dev, "Error reading pwrsrc status: %d\n" , ret); |
583 | goto disable_sw_control; |
584 | } |
585 | |
586 | /* |
587 | * If no USB host or device connected, route D+ and D- to PMIC for |
588 | * initial charger detection |
589 | */ |
590 | id = cht_wc_extcon_get_id(ext, pwrsrc_sts); |
591 | if (id != INTEL_USB_ID_GND) |
592 | cht_wc_extcon_set_phymux(ext, state: MUX_SEL_PMIC); |
593 | |
594 | /* Get initial state */ |
595 | cht_wc_extcon_pwrsrc_event(ext); |
596 | |
597 | ret = devm_request_threaded_irq(dev: ext->dev, irq, NULL, thread_fn: cht_wc_extcon_isr, |
598 | IRQF_ONESHOT, devname: pdev->name, dev_id: ext); |
599 | if (ret) { |
600 | dev_err(ext->dev, "Error requesting interrupt: %d\n" , ret); |
601 | goto disable_sw_control; |
602 | } |
603 | |
604 | /* Unmask irqs */ |
605 | ret = regmap_write(map: ext->regmap, CHT_WC_PWRSRC_IRQ_MASK, val: mask); |
606 | if (ret) { |
607 | dev_err(ext->dev, "Error writing irq-mask: %d\n" , ret); |
608 | goto disable_sw_control; |
609 | } |
610 | |
611 | platform_set_drvdata(pdev, data: ext); |
612 | |
613 | return 0; |
614 | |
615 | disable_sw_control: |
616 | cht_wc_extcon_sw_control(ext, enable: false); |
617 | return ret; |
618 | } |
619 | |
620 | static int cht_wc_extcon_remove(struct platform_device *pdev) |
621 | { |
622 | struct cht_wc_extcon_data *ext = platform_get_drvdata(pdev); |
623 | |
624 | cht_wc_extcon_sw_control(ext, enable: false); |
625 | |
626 | return 0; |
627 | } |
628 | |
629 | static const struct platform_device_id cht_wc_extcon_table[] = { |
630 | { .name = "cht_wcove_pwrsrc" }, |
631 | {}, |
632 | }; |
633 | MODULE_DEVICE_TABLE(platform, cht_wc_extcon_table); |
634 | |
635 | static struct platform_driver cht_wc_extcon_driver = { |
636 | .probe = cht_wc_extcon_probe, |
637 | .remove = cht_wc_extcon_remove, |
638 | .id_table = cht_wc_extcon_table, |
639 | .driver = { |
640 | .name = "cht_wcove_pwrsrc" , |
641 | }, |
642 | }; |
643 | module_platform_driver(cht_wc_extcon_driver); |
644 | |
645 | MODULE_DESCRIPTION("Intel Cherrytrail Whiskey Cove PMIC extcon driver" ); |
646 | MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>" ); |
647 | MODULE_LICENSE("GPL v2" ); |
648 | |