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