extcon: axp288: Add missed error check
[linux-block.git] / drivers / extcon / extcon-axp288.c
CommitLineData
1802d0be 1// SPDX-License-Identifier: GPL-2.0-only
f0312378
RP
2/*
3 * extcon-axp288.c - X-Power AXP288 PMIC extcon cable detection driver
4 *
d54f063c 5 * Copyright (c) 2017-2018 Hans de Goede <hdegoede@redhat.com>
f0312378
RP
6 * Copyright (C) 2015 Intel Corporation
7 * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
f0312378
RP
8 */
9
d54f063c 10#include <linux/acpi.h>
f0312378
RP
11#include <linux/module.h>
12#include <linux/kernel.h>
13#include <linux/io.h>
14#include <linux/slab.h>
15#include <linux/interrupt.h>
16#include <linux/platform_device.h>
17#include <linux/property.h>
f0312378 18#include <linux/notifier.h>
176aa360 19#include <linux/extcon-provider.h>
f0312378 20#include <linux/regmap.h>
f0312378 21#include <linux/mfd/axp20x.h>
d54f063c
HG
22#include <linux/usb/role.h>
23#include <linux/workqueue.h>
24
25#include <asm/cpu_device_id.h>
26#include <asm/intel-family.h>
f0312378
RP
27
28/* Power source status register */
29#define PS_STAT_VBUS_TRIGGER BIT(0)
30#define PS_STAT_BAT_CHRG_DIR BIT(2)
31#define PS_STAT_VBUS_ABOVE_VHOLD BIT(3)
32#define PS_STAT_VBUS_VALID BIT(4)
33#define PS_STAT_VBUS_PRESENT BIT(5)
34
35/* BC module global register */
36#define BC_GLOBAL_RUN BIT(0)
37#define BC_GLOBAL_DET_STAT BIT(2)
38#define BC_GLOBAL_DBP_TOUT BIT(3)
39#define BC_GLOBAL_VLGC_COM_SEL BIT(4)
40#define BC_GLOBAL_DCD_TOUT_MASK (BIT(6)|BIT(5))
41#define BC_GLOBAL_DCD_TOUT_300MS 0
42#define BC_GLOBAL_DCD_TOUT_100MS 1
43#define BC_GLOBAL_DCD_TOUT_500MS 2
44#define BC_GLOBAL_DCD_TOUT_900MS 3
45#define BC_GLOBAL_DCD_DET_SEL BIT(7)
46
47/* BC module vbus control and status register */
48#define VBUS_CNTL_DPDM_PD_EN BIT(4)
49#define VBUS_CNTL_DPDM_FD_EN BIT(5)
50#define VBUS_CNTL_FIRST_PO_STAT BIT(6)
51
52/* BC USB status register */
53#define USB_STAT_BUS_STAT_MASK (BIT(3)|BIT(2)|BIT(1)|BIT(0))
54#define USB_STAT_BUS_STAT_SHIFT 0
55#define USB_STAT_BUS_STAT_ATHD 0
56#define USB_STAT_BUS_STAT_CONN 1
57#define USB_STAT_BUS_STAT_SUSP 2
58#define USB_STAT_BUS_STAT_CONF 3
59#define USB_STAT_USB_SS_MODE BIT(4)
60#define USB_STAT_DEAD_BAT_DET BIT(6)
61#define USB_STAT_DBP_UNCFG BIT(7)
62
63/* BC detect status register */
64#define DET_STAT_MASK (BIT(7)|BIT(6)|BIT(5))
65#define DET_STAT_SHIFT 5
66#define DET_STAT_SDP 1
67#define DET_STAT_CDP 2
68#define DET_STAT_DCP 3
69
f0312378
RP
70enum axp288_extcon_reg {
71 AXP288_PS_STAT_REG = 0x00,
72 AXP288_PS_BOOT_REASON_REG = 0x02,
73 AXP288_BC_GLOBAL_REG = 0x2c,
74 AXP288_BC_VBUS_CNTL_REG = 0x2d,
75 AXP288_BC_USB_STAT_REG = 0x2e,
76 AXP288_BC_DET_STAT_REG = 0x2f,
f0312378
RP
77};
78
f0312378
RP
79enum axp288_extcon_irq {
80 VBUS_FALLING_IRQ = 0,
81 VBUS_RISING_IRQ,
82 MV_CHNG_IRQ,
83 BC_USB_CHNG_IRQ,
84 EXTCON_IRQ_END,
85};
86
73b6ecdb 87static const unsigned int axp288_extcon_cables[] = {
11eecf91
CC
88 EXTCON_CHG_USB_SDP,
89 EXTCON_CHG_USB_CDP,
90 EXTCON_CHG_USB_DCP,
5298b836 91 EXTCON_USB,
2a9de9c0 92 EXTCON_NONE,
f0312378
RP
93};
94
95struct axp288_extcon_info {
96 struct device *dev;
97 struct regmap *regmap;
98 struct regmap_irq_chip_data *regmap_irqc;
d54f063c
HG
99 struct usb_role_switch *role_sw;
100 struct work_struct role_work;
f0312378
RP
101 int irq[EXTCON_IRQ_END];
102 struct extcon_dev *edev;
d54f063c
HG
103 struct extcon_dev *id_extcon;
104 struct notifier_block id_nb;
5d2199ea 105 unsigned int previous_cable;
d54f063c
HG
106 bool vbus_attach;
107};
108
109static const struct x86_cpu_id cherry_trail_cpu_ids[] = {
110 { X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_AIRMONT, X86_FEATURE_ANY },
111 {}
f0312378
RP
112};
113
114/* Power up/down reason string array */
5b4e64be 115static const char * const axp288_pwr_up_down_info[] = {
f0312378
RP
116 "Last wake caused by user pressing the power button",
117 "Last wake caused by a charger insertion",
118 "Last wake caused by a battery insertion",
119 "Last wake caused by SOC initiated global reset",
120 "Last wake caused by cold reset",
121 "Last shutdown caused by PMIC UVLO threshold",
122 "Last shutdown caused by SOC initiated cold off",
123 "Last shutdown caused by user pressing the power button",
124 NULL,
125};
126
127/*
128 * Decode and log the given "reset source indicator" (rsi)
129 * register and then clear it.
130 */
131static void axp288_extcon_log_rsi(struct axp288_extcon_info *info)
132{
5b4e64be 133 const char * const *rsi;
f0312378
RP
134 unsigned int val, i, clear_mask = 0;
135 int ret;
136
137 ret = regmap_read(info->regmap, AXP288_PS_BOOT_REASON_REG, &val);
d72e3dc7
AS
138 if (ret < 0) {
139 dev_err(info->dev, "failed to read reset source indicator\n");
140 return;
141 }
142
f0312378
RP
143 for (i = 0, rsi = axp288_pwr_up_down_info; *rsi; rsi++, i++) {
144 if (val & BIT(i)) {
145 dev_dbg(info->dev, "%s\n", *rsi);
146 clear_mask |= BIT(i);
147 }
148 }
149
150 /* Clear the register value for next reboot (write 1 to clear bit) */
151 regmap_write(info->regmap, AXP288_PS_BOOT_REASON_REG, clear_mask);
152}
153
d54f063c
HG
154/*
155 * The below code to control the USB role-switch on devices with an AXP288
156 * may seem out of place, but there are 2 reasons why this is the best place
157 * to control the USB role-switch on such devices:
158 * 1) On many devices the USB role is controlled by AML code, but the AML code
159 * only switches between the host and none roles, because of Windows not
160 * really using device mode. To make device mode work we need to toggle
161 * between the none/device roles based on Vbus presence, and this driver
162 * gets interrupts on Vbus insertion / removal.
163 * 2) In order for our BC1.2 charger detection to work properly the role
164 * mux must be properly set to device mode before we do the detection.
165 */
166
167/* Returns the id-pin value, note pulled low / false == host-mode */
168static bool axp288_get_id_pin(struct axp288_extcon_info *info)
f0312378 169{
d54f063c
HG
170 enum usb_role role;
171
172 if (info->id_extcon)
173 return extcon_get_state(info->id_extcon, EXTCON_USB_HOST) <= 0;
174
175 /* We cannot access the id-pin, see what mode the AML code has set */
176 role = usb_role_switch_get_role(info->role_sw);
177 return role != USB_ROLE_HOST;
178}
179
180static void axp288_usb_role_work(struct work_struct *work)
181{
182 struct axp288_extcon_info *info =
183 container_of(work, struct axp288_extcon_info, role_work);
184 enum usb_role role;
185 bool id_pin;
186 int ret;
187
188 id_pin = axp288_get_id_pin(info);
189 if (!id_pin)
190 role = USB_ROLE_HOST;
191 else if (info->vbus_attach)
192 role = USB_ROLE_DEVICE;
193 else
194 role = USB_ROLE_NONE;
195
196 ret = usb_role_switch_set_role(info->role_sw, role);
197 if (ret)
198 dev_err(info->dev, "failed to set role: %d\n", ret);
199}
200
201static bool axp288_get_vbus_attach(struct axp288_extcon_info *info)
202{
203 int ret, pwr_stat;
f0312378
RP
204
205 ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
206 if (ret < 0) {
207 dev_err(info->dev, "failed to read vbus status\n");
d54f063c 208 return false;
f0312378
RP
209 }
210
d54f063c
HG
211 return !!(pwr_stat & PS_STAT_VBUS_VALID);
212}
213
214static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
215{
216 int ret, stat, cfg;
217 u8 chrg_type;
218 unsigned int cable = info->previous_cable;
219 bool vbus_attach = false;
220
221 vbus_attach = axp288_get_vbus_attach(info);
f0312378 222 if (!vbus_attach)
3fe1e0e2 223 goto no_vbus;
f0312378
RP
224
225 /* Check charger detection completion status */
226 ret = regmap_read(info->regmap, AXP288_BC_GLOBAL_REG, &cfg);
227 if (ret < 0)
228 goto dev_det_ret;
229 if (cfg & BC_GLOBAL_DET_STAT) {
230 dev_dbg(info->dev, "can't complete the charger detection\n");
231 goto dev_det_ret;
232 }
233
234 ret = regmap_read(info->regmap, AXP288_BC_DET_STAT_REG, &stat);
235 if (ret < 0)
236 goto dev_det_ret;
237
238 chrg_type = (stat & DET_STAT_MASK) >> DET_STAT_SHIFT;
239
240 switch (chrg_type) {
241 case DET_STAT_SDP:
525867db 242 dev_dbg(info->dev, "sdp cable is connected\n");
11eecf91 243 cable = EXTCON_CHG_USB_SDP;
f0312378
RP
244 break;
245 case DET_STAT_CDP:
525867db 246 dev_dbg(info->dev, "cdp cable is connected\n");
11eecf91 247 cable = EXTCON_CHG_USB_CDP;
f0312378
RP
248 break;
249 case DET_STAT_DCP:
525867db 250 dev_dbg(info->dev, "dcp cable is connected\n");
11eecf91 251 cable = EXTCON_CHG_USB_DCP;
f0312378
RP
252 break;
253 default:
ca90a64d
HG
254 dev_warn(info->dev, "unknown (reserved) bc detect result\n");
255 cable = EXTCON_CHG_USB_SDP;
f0312378
RP
256 }
257
3fe1e0e2 258no_vbus:
5d2199ea 259 extcon_set_state_sync(info->edev, info->previous_cable, false);
5298b836
BW
260 if (info->previous_cable == EXTCON_CHG_USB_SDP)
261 extcon_set_state_sync(info->edev, EXTCON_USB, false);
262
5d2199ea
HG
263 if (vbus_attach) {
264 extcon_set_state_sync(info->edev, cable, vbus_attach);
5298b836
BW
265 if (cable == EXTCON_CHG_USB_SDP)
266 extcon_set_state_sync(info->edev, EXTCON_USB,
267 vbus_attach);
268
5d2199ea
HG
269 info->previous_cable = cable;
270 }
f0312378 271
d54f063c
HG
272 if (info->role_sw && info->vbus_attach != vbus_attach) {
273 info->vbus_attach = vbus_attach;
274 /* Setting the role can take a while */
275 queue_work(system_long_wq, &info->role_work);
276 }
277
f0312378
RP
278 return 0;
279
280dev_det_ret:
281 if (ret < 0)
282 dev_err(info->dev, "failed to detect BC Mod\n");
283
284 return ret;
285}
286
d54f063c
HG
287static int axp288_extcon_id_evt(struct notifier_block *nb,
288 unsigned long event, void *param)
289{
290 struct axp288_extcon_info *info =
291 container_of(nb, struct axp288_extcon_info, id_nb);
292
293 /* We may not sleep and setting the role can take a while */
294 queue_work(system_long_wq, &info->role_work);
295
296 return NOTIFY_OK;
297}
298
f0312378
RP
299static irqreturn_t axp288_extcon_isr(int irq, void *data)
300{
301 struct axp288_extcon_info *info = data;
302 int ret;
303
304 ret = axp288_handle_chrg_det_event(info);
305 if (ret < 0)
306 dev_err(info->dev, "failed to handle the interrupt\n");
307
308 return IRQ_HANDLED;
309}
310
d82e233c 311static void axp288_extcon_enable(struct axp288_extcon_info *info)
f0312378 312{
f0312378
RP
313 regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
314 BC_GLOBAL_RUN, 0);
f0312378
RP
315 /* Enable the charger detection logic */
316 regmap_update_bits(info->regmap, AXP288_BC_GLOBAL_REG,
317 BC_GLOBAL_RUN, BC_GLOBAL_RUN);
318}
319
d54f063c
HG
320static void axp288_put_role_sw(void *data)
321{
322 struct axp288_extcon_info *info = data;
323
324 cancel_work_sync(&info->role_work);
325 usb_role_switch_put(info->role_sw);
326}
327
f0312378
RP
328static int axp288_extcon_probe(struct platform_device *pdev)
329{
330 struct axp288_extcon_info *info;
331 struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
d54f063c 332 struct device *dev = &pdev->dev;
0cf064db 333 struct acpi_device *adev;
9bf317e9 334 int ret, i, pirq;
f0312378
RP
335
336 info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
337 if (!info)
338 return -ENOMEM;
339
340 info->dev = &pdev->dev;
341 info->regmap = axp20x->regmap;
342 info->regmap_irqc = axp20x->regmap_irqc;
5d2199ea 343 info->previous_cable = EXTCON_NONE;
d54f063c
HG
344 INIT_WORK(&info->role_work, axp288_usb_role_work);
345 info->id_nb.notifier_call = axp288_extcon_id_evt;
76884241 346
f0312378
RP
347 platform_set_drvdata(pdev, info);
348
d54f063c
HG
349 info->role_sw = usb_role_switch_get(dev);
350 if (IS_ERR(info->role_sw))
351 return PTR_ERR(info->role_sw);
352 if (info->role_sw) {
353 ret = devm_add_action_or_reset(dev, axp288_put_role_sw, info);
354 if (ret)
355 return ret;
356
0cf064db
AS
357 adev = acpi_dev_get_first_match_dev("INT3496", NULL, -1);
358 if (adev) {
359 info->id_extcon = extcon_get_extcon_dev(acpi_dev_name(adev));
360 put_device(&adev->dev);
d54f063c
HG
361 if (!info->id_extcon)
362 return -EPROBE_DEFER;
363
364 dev_info(dev, "controlling USB role\n");
365 } else {
366 dev_info(dev, "controlling USB role based on Vbus presence\n");
367 }
368 }
369
370 info->vbus_attach = axp288_get_vbus_attach(info);
371
f0312378
RP
372 axp288_extcon_log_rsi(info);
373
374 /* Initialize extcon device */
375 info->edev = devm_extcon_dev_allocate(&pdev->dev,
376 axp288_extcon_cables);
377 if (IS_ERR(info->edev)) {
378 dev_err(&pdev->dev, "failed to allocate memory for extcon\n");
379 return PTR_ERR(info->edev);
380 }
381
382 /* Register extcon device */
383 ret = devm_extcon_dev_register(&pdev->dev, info->edev);
384 if (ret) {
385 dev_err(&pdev->dev, "failed to register extcon device\n");
386 return ret;
387 }
388
f0312378
RP
389 for (i = 0; i < EXTCON_IRQ_END; i++) {
390 pirq = platform_get_irq(pdev, i);
01e1429b
AY
391 if (pirq < 0)
392 return pirq;
393
f0312378
RP
394 info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
395 if (info->irq[i] < 0) {
396 dev_err(&pdev->dev,
397 "failed to get virtual interrupt=%d\n", pirq);
398 ret = info->irq[i];
4bf27b70 399 return ret;
f0312378
RP
400 }
401
402 ret = devm_request_threaded_irq(&pdev->dev, info->irq[i],
403 NULL, axp288_extcon_isr,
404 IRQF_ONESHOT | IRQF_NO_SUSPEND,
405 pdev->name, info);
406 if (ret) {
407 dev_err(&pdev->dev, "failed to request interrupt=%d\n",
408 info->irq[i]);
4bf27b70 409 return ret;
f0312378
RP
410 }
411 }
412
d54f063c
HG
413 if (info->id_extcon) {
414 ret = devm_extcon_register_notifier_all(dev, info->id_extcon,
415 &info->id_nb);
416 if (ret)
417 return ret;
418 }
419
420 /* Make sure the role-sw is set correctly before doing BC detection */
421 if (info->role_sw) {
422 queue_work(system_long_wq, &info->role_work);
423 flush_work(&info->role_work);
424 }
425
be174952 426 /* Start charger cable type detection */
d82e233c 427 axp288_extcon_enable(info);
f0312378
RP
428
429 return 0;
f0312378
RP
430}
431
dd3a55fc
HG
432static const struct platform_device_id axp288_extcon_table[] = {
433 { .name = "axp288_extcon" },
434 {},
435};
436MODULE_DEVICE_TABLE(platform, axp288_extcon_table);
437
f0312378
RP
438static struct platform_driver axp288_extcon_driver = {
439 .probe = axp288_extcon_probe,
dd3a55fc 440 .id_table = axp288_extcon_table,
f0312378
RP
441 .driver = {
442 .name = "axp288_extcon",
443 },
444};
d54f063c
HG
445
446static struct device_connection axp288_extcon_role_sw_conn = {
447 .endpoint[0] = "axp288_extcon",
448 .endpoint[1] = "intel_xhci_usb_sw-role-switch",
449 .id = "usb-role-switch",
450};
451
452static int __init axp288_extcon_init(void)
453{
454 if (x86_match_cpu(cherry_trail_cpu_ids))
455 device_connection_add(&axp288_extcon_role_sw_conn);
456
457 return platform_driver_register(&axp288_extcon_driver);
458}
459module_init(axp288_extcon_init);
460
461static void __exit axp288_extcon_exit(void)
462{
463 if (x86_match_cpu(cherry_trail_cpu_ids))
464 device_connection_remove(&axp288_extcon_role_sw_conn);
465
466 platform_driver_unregister(&axp288_extcon_driver);
467}
468module_exit(axp288_extcon_exit);
f0312378
RP
469
470MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
d54f063c 471MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
f0312378
RP
472MODULE_DESCRIPTION("X-Powers AXP288 extcon driver");
473MODULE_LICENSE("GPL v2");