Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/extcon/extcon-lc824206xa.c
26378 views
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
/*
3
* ON Semiconductor LC824206XA Micro USB Switch driver
4
*
5
* Copyright (c) 2024 Hans de Goede <[email protected]>
6
*
7
* ON Semiconductor has an "Advance Information" datasheet available
8
* (ENA2222-D.PDF), but no full datasheet. So there is no documentation
9
* available for the registers.
10
*
11
* This driver is based on the register info from the extcon-fsa9285.c driver,
12
* from the Lollipop Android sources for the Lenovo Yoga Tablet 2 (Pro)
13
* 830 / 1050 / 1380 models. Note despite the name this is actually a driver
14
* for the LC824206XA not the FSA9285. The Android sources can be downloaded
15
* from Lenovo's support page for these tablets, filename:
16
* yoga_tab_2_osc_android_to_lollipop_201505.rar.
17
*/
18
19
#include <linux/bits.h>
20
#include <linux/delay.h>
21
#include <linux/device.h>
22
#include <linux/extcon-provider.h>
23
#include <linux/i2c.h>
24
#include <linux/interrupt.h>
25
#include <linux/module.h>
26
#include <linux/power_supply.h>
27
#include <linux/property.h>
28
#include <linux/regulator/consumer.h>
29
#include <linux/workqueue.h>
30
31
/*
32
* Register defines as mentioned above there is no datasheet with register
33
* info, so this may not be 100% accurate.
34
*/
35
#define REG00 0x00
36
#define REG00_INIT_VALUE 0x01
37
38
#define REG_STATUS 0x01
39
#define STATUS_OVP BIT(0)
40
#define STATUS_DATA_SHORT BIT(1)
41
#define STATUS_VBUS_PRESENT BIT(2)
42
#define STATUS_USB_ID GENMASK(7, 3)
43
#define STATUS_USB_ID_GND 0x80
44
#define STATUS_USB_ID_ACA 0xf0
45
#define STATUS_USB_ID_FLOAT 0xf8
46
47
/*
48
* This controls the DP/DM muxes + other switches,
49
* meaning of individual bits is unknown.
50
*/
51
#define REG_SWITCH_CONTROL 0x02
52
#define SWITCH_STEREO_MIC 0xc8
53
#define SWITCH_USB_HOST 0xec
54
#define SWITCH_DISCONNECTED 0xf8
55
#define SWITCH_USB_DEVICE 0xfc
56
57
/* 5 bits? ADC 0x10 GND, 0x1a-0x1f ACA, 0x1f float */
58
#define REG_ID_PIN_ADC_VALUE 0x03
59
60
/* Masks for all 3 interrupt registers */
61
#define INTR_ID_PIN_CHANGE BIT(0)
62
#define INTR_VBUS_CHANGE BIT(1)
63
/* Both of these get set after a continuous mode ADC conversion */
64
#define INTR_ID_PIN_ADC_INT1 BIT(2)
65
#define INTR_ID_PIN_ADC_INT2 BIT(3)
66
/* Charger type available in reg 0x09 */
67
#define INTR_CHARGER_DET_DONE BIT(4)
68
#define INTR_OVP BIT(5)
69
70
/* There are 7 interrupt sources, bit 6 use is unknown (OCP?) */
71
#define INTR_ALL GENMASK(6, 0)
72
73
/* Unmask interrupts this driver cares about */
74
#define INTR_MASK \
75
(INTR_ALL & ~(INTR_ID_PIN_CHANGE | INTR_VBUS_CHANGE | INTR_CHARGER_DET_DONE))
76
77
/* Active (event happened and not cleared yet) interrupts */
78
#define REG_INTR_STATUS 0x04
79
80
/*
81
* Writing a 1 to a bit here clears it in INTR_STATUS. These bits do NOT
82
* auto-reset to 0, so these must be set to 0 manually after clearing.
83
*/
84
#define REG_INTR_CLEAR 0x05
85
86
/* Interrupts which bit is set to 1 here will not raise the HW IRQ */
87
#define REG_INTR_MASK 0x06
88
89
/* ID pin ADC control, meaning of individual bits is unknown */
90
#define REG_ID_PIN_ADC_CTRL 0x07
91
#define ID_PIN_ADC_AUTO 0x40
92
#define ID_PIN_ADC_CONTINUOUS 0x44
93
94
#define REG_CHARGER_DET 0x08
95
#define CHARGER_DET_ON BIT(0)
96
#define CHARGER_DET_CDP_ON BIT(1)
97
#define CHARGER_DET_CDP_VAL BIT(2)
98
99
#define REG_CHARGER_TYPE 0x09
100
#define CHARGER_TYPE_UNKNOWN 0x00
101
#define CHARGER_TYPE_DCP 0x01
102
#define CHARGER_TYPE_SDP_OR_CDP 0x04
103
#define CHARGER_TYPE_QC 0x06
104
105
#define REG10 0x10
106
#define REG10_INIT_VALUE 0x00
107
108
struct lc824206xa_data {
109
struct work_struct work;
110
struct i2c_client *client;
111
struct extcon_dev *edev;
112
struct power_supply *psy;
113
struct regulator *vbus_boost;
114
unsigned int usb_type;
115
unsigned int cable;
116
unsigned int previous_cable;
117
u8 switch_control;
118
u8 previous_switch_control;
119
bool vbus_ok;
120
bool vbus_boost_enabled;
121
bool fastcharge_over_miclr;
122
};
123
124
static const unsigned int lc824206xa_cables[] = {
125
EXTCON_USB_HOST,
126
EXTCON_CHG_USB_SDP,
127
EXTCON_CHG_USB_CDP,
128
EXTCON_CHG_USB_DCP,
129
EXTCON_CHG_USB_ACA,
130
EXTCON_CHG_USB_FAST,
131
EXTCON_NONE,
132
};
133
134
/* read/write reg helpers to add error logging to smbus byte functions */
135
static int lc824206xa_read_reg(struct lc824206xa_data *data, u8 reg)
136
{
137
int ret;
138
139
ret = i2c_smbus_read_byte_data(data->client, reg);
140
if (ret < 0)
141
dev_err(&data->client->dev, "Error %d reading reg 0x%02x\n", ret, reg);
142
143
return ret;
144
}
145
146
static int lc824206xa_write_reg(struct lc824206xa_data *data, u8 reg, u8 val)
147
{
148
int ret;
149
150
ret = i2c_smbus_write_byte_data(data->client, reg, val);
151
if (ret < 0)
152
dev_err(&data->client->dev, "Error %d writing reg 0x%02x\n", ret, reg);
153
154
return ret;
155
}
156
157
static int lc824206xa_get_id(struct lc824206xa_data *data)
158
{
159
int ret;
160
161
ret = lc824206xa_write_reg(data, REG_ID_PIN_ADC_CTRL, ID_PIN_ADC_CONTINUOUS);
162
if (ret)
163
return ret;
164
165
ret = lc824206xa_read_reg(data, REG_ID_PIN_ADC_VALUE);
166
167
lc824206xa_write_reg(data, REG_ID_PIN_ADC_CTRL, ID_PIN_ADC_AUTO);
168
169
return ret;
170
}
171
172
static void lc824206xa_set_vbus_boost(struct lc824206xa_data *data, bool enable)
173
{
174
int ret;
175
176
if (data->vbus_boost_enabled == enable)
177
return;
178
179
if (enable)
180
ret = regulator_enable(data->vbus_boost);
181
else
182
ret = regulator_disable(data->vbus_boost);
183
184
if (ret == 0)
185
data->vbus_boost_enabled = enable;
186
else
187
dev_err(&data->client->dev, "Error updating Vbus boost regulator: %d\n", ret);
188
}
189
190
static void lc824206xa_charger_detect(struct lc824206xa_data *data)
191
{
192
int charger_type, ret;
193
194
charger_type = lc824206xa_read_reg(data, REG_CHARGER_TYPE);
195
if (charger_type < 0)
196
return;
197
198
dev_dbg(&data->client->dev, "charger type 0x%02x\n", charger_type);
199
200
switch (charger_type) {
201
case CHARGER_TYPE_UNKNOWN:
202
data->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
203
/* Treat as SDP */
204
data->cable = EXTCON_CHG_USB_SDP;
205
data->switch_control = SWITCH_USB_DEVICE;
206
break;
207
case CHARGER_TYPE_SDP_OR_CDP:
208
data->usb_type = POWER_SUPPLY_USB_TYPE_SDP;
209
data->cable = EXTCON_CHG_USB_SDP;
210
data->switch_control = SWITCH_USB_DEVICE;
211
212
ret = lc824206xa_write_reg(data, REG_CHARGER_DET,
213
CHARGER_DET_CDP_ON | CHARGER_DET_ON);
214
if (ret < 0)
215
break;
216
217
msleep(100);
218
ret = lc824206xa_read_reg(data, REG_CHARGER_DET);
219
if (ret >= 0 && (ret & CHARGER_DET_CDP_VAL)) {
220
data->usb_type = POWER_SUPPLY_USB_TYPE_CDP;
221
data->cable = EXTCON_CHG_USB_CDP;
222
}
223
224
lc824206xa_write_reg(data, REG_CHARGER_DET, CHARGER_DET_ON);
225
break;
226
case CHARGER_TYPE_DCP:
227
data->usb_type = POWER_SUPPLY_USB_TYPE_DCP;
228
data->cable = EXTCON_CHG_USB_DCP;
229
if (data->fastcharge_over_miclr)
230
data->switch_control = SWITCH_STEREO_MIC;
231
else
232
data->switch_control = SWITCH_DISCONNECTED;
233
break;
234
case CHARGER_TYPE_QC:
235
data->usb_type = POWER_SUPPLY_USB_TYPE_DCP;
236
data->cable = EXTCON_CHG_USB_DCP;
237
data->switch_control = SWITCH_DISCONNECTED;
238
break;
239
default:
240
dev_warn(&data->client->dev, "Unknown charger type: 0x%02x\n", charger_type);
241
break;
242
}
243
}
244
245
static void lc824206xa_work(struct work_struct *work)
246
{
247
struct lc824206xa_data *data = container_of(work, struct lc824206xa_data, work);
248
bool vbus_boost_enable = false;
249
int status, id;
250
251
status = lc824206xa_read_reg(data, REG_STATUS);
252
if (status < 0)
253
return;
254
255
dev_dbg(&data->client->dev, "status 0x%02x\n", status);
256
257
data->vbus_ok = (status & (STATUS_VBUS_PRESENT | STATUS_OVP)) == STATUS_VBUS_PRESENT;
258
259
/* Read id pin ADC if necessary */
260
switch (status & STATUS_USB_ID) {
261
case STATUS_USB_ID_GND:
262
case STATUS_USB_ID_FLOAT:
263
break;
264
default:
265
/* Happens when the connector is inserted slowly, log at dbg level */
266
dev_dbg(&data->client->dev, "Unknown status 0x%02x\n", status);
267
fallthrough;
268
case STATUS_USB_ID_ACA:
269
id = lc824206xa_get_id(data);
270
dev_dbg(&data->client->dev, "RID 0x%02x\n", id);
271
switch (id) {
272
case 0x10:
273
status = STATUS_USB_ID_GND;
274
break;
275
case 0x18 ... 0x1e:
276
status = STATUS_USB_ID_ACA;
277
break;
278
case 0x1f:
279
status = STATUS_USB_ID_FLOAT;
280
break;
281
default:
282
dev_warn(&data->client->dev, "Unknown RID 0x%02x\n", id);
283
return;
284
}
285
}
286
287
/* Check for out of spec OTG charging hubs, treat as ACA */
288
if ((status & STATUS_USB_ID) == STATUS_USB_ID_GND &&
289
data->vbus_ok && !data->vbus_boost_enabled) {
290
dev_info(&data->client->dev, "Out of spec USB host adapter with Vbus present, not enabling 5V output\n");
291
status = STATUS_USB_ID_ACA;
292
}
293
294
switch (status & STATUS_USB_ID) {
295
case STATUS_USB_ID_ACA:
296
data->usb_type = POWER_SUPPLY_USB_TYPE_ACA;
297
data->cable = EXTCON_CHG_USB_ACA;
298
data->switch_control = SWITCH_USB_HOST;
299
break;
300
case STATUS_USB_ID_GND:
301
data->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
302
data->cable = EXTCON_USB_HOST;
303
data->switch_control = SWITCH_USB_HOST;
304
vbus_boost_enable = true;
305
break;
306
case STATUS_USB_ID_FLOAT:
307
/* When fast charging with Vbus > 5V, OVP will be set */
308
if (data->fastcharge_over_miclr &&
309
data->switch_control == SWITCH_STEREO_MIC &&
310
(status & STATUS_OVP)) {
311
data->cable = EXTCON_CHG_USB_FAST;
312
break;
313
}
314
315
if (data->vbus_ok) {
316
lc824206xa_charger_detect(data);
317
} else {
318
data->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
319
data->cable = EXTCON_NONE;
320
data->switch_control = SWITCH_DISCONNECTED;
321
}
322
break;
323
}
324
325
lc824206xa_set_vbus_boost(data, vbus_boost_enable);
326
327
if (data->switch_control != data->previous_switch_control) {
328
lc824206xa_write_reg(data, REG_SWITCH_CONTROL, data->switch_control);
329
data->previous_switch_control = data->switch_control;
330
}
331
332
if (data->cable != data->previous_cable) {
333
extcon_set_state_sync(data->edev, data->previous_cable, false);
334
extcon_set_state_sync(data->edev, data->cable, true);
335
data->previous_cable = data->cable;
336
}
337
338
power_supply_changed(data->psy);
339
}
340
341
static irqreturn_t lc824206xa_irq(int irq, void *_data)
342
{
343
struct lc824206xa_data *data = _data;
344
int intr_status;
345
346
intr_status = lc824206xa_read_reg(data, REG_INTR_STATUS);
347
if (intr_status < 0)
348
intr_status = INTR_ALL; /* Should never happen, clear all */
349
350
dev_dbg(&data->client->dev, "interrupt 0x%02x\n", intr_status);
351
352
lc824206xa_write_reg(data, REG_INTR_CLEAR, intr_status);
353
lc824206xa_write_reg(data, REG_INTR_CLEAR, 0);
354
355
schedule_work(&data->work);
356
return IRQ_HANDLED;
357
}
358
359
/*
360
* Newer charger (power_supply) drivers expect the max input current to be
361
* provided by a parent power_supply device for the charger chip.
362
*/
363
static int lc824206xa_psy_get_prop(struct power_supply *psy,
364
enum power_supply_property psp,
365
union power_supply_propval *val)
366
{
367
struct lc824206xa_data *data = power_supply_get_drvdata(psy);
368
369
switch (psp) {
370
case POWER_SUPPLY_PROP_ONLINE:
371
val->intval = data->vbus_ok && !data->vbus_boost_enabled;
372
break;
373
case POWER_SUPPLY_PROP_USB_TYPE:
374
val->intval = data->usb_type;
375
break;
376
case POWER_SUPPLY_PROP_CURRENT_MAX:
377
switch (data->usb_type) {
378
case POWER_SUPPLY_USB_TYPE_DCP:
379
case POWER_SUPPLY_USB_TYPE_ACA:
380
val->intval = 2000000;
381
break;
382
case POWER_SUPPLY_USB_TYPE_CDP:
383
val->intval = 1500000;
384
break;
385
default:
386
val->intval = 500000;
387
}
388
break;
389
default:
390
return -EINVAL;
391
}
392
393
return 0;
394
}
395
396
static const enum power_supply_property lc824206xa_psy_props[] = {
397
POWER_SUPPLY_PROP_ONLINE,
398
POWER_SUPPLY_PROP_USB_TYPE,
399
POWER_SUPPLY_PROP_CURRENT_MAX,
400
};
401
402
static const struct power_supply_desc lc824206xa_psy_desc = {
403
.name = "lc824206xa-charger-detect",
404
.type = POWER_SUPPLY_TYPE_USB,
405
.usb_types = BIT(POWER_SUPPLY_USB_TYPE_SDP) |
406
BIT(POWER_SUPPLY_USB_TYPE_CDP) |
407
BIT(POWER_SUPPLY_USB_TYPE_DCP) |
408
BIT(POWER_SUPPLY_USB_TYPE_ACA) |
409
BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN),
410
.properties = lc824206xa_psy_props,
411
.num_properties = ARRAY_SIZE(lc824206xa_psy_props),
412
.get_property = lc824206xa_psy_get_prop,
413
};
414
415
static int lc824206xa_probe(struct i2c_client *client)
416
{
417
struct power_supply_config psy_cfg = { };
418
struct device *dev = &client->dev;
419
struct lc824206xa_data *data;
420
int ret;
421
422
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
423
if (!data)
424
return -ENOMEM;
425
426
data->client = client;
427
INIT_WORK(&data->work, lc824206xa_work);
428
data->cable = EXTCON_NONE;
429
data->previous_cable = EXTCON_NONE;
430
data->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
431
/* Some designs use a custom fast-charge protocol over the mic L/R inputs */
432
data->fastcharge_over_miclr =
433
device_property_read_bool(dev, "onnn,enable-miclr-for-dcp");
434
435
data->vbus_boost = devm_regulator_get(dev, "vbus");
436
if (IS_ERR(data->vbus_boost))
437
return dev_err_probe(dev, PTR_ERR(data->vbus_boost),
438
"getting regulator\n");
439
440
/* Init */
441
ret = lc824206xa_write_reg(data, REG00, REG00_INIT_VALUE);
442
ret |= lc824206xa_write_reg(data, REG10, REG10_INIT_VALUE);
443
msleep(100);
444
ret |= lc824206xa_write_reg(data, REG_INTR_CLEAR, INTR_ALL);
445
ret |= lc824206xa_write_reg(data, REG_INTR_CLEAR, 0);
446
ret |= lc824206xa_write_reg(data, REG_INTR_MASK, INTR_MASK);
447
ret |= lc824206xa_write_reg(data, REG_ID_PIN_ADC_CTRL, ID_PIN_ADC_AUTO);
448
ret |= lc824206xa_write_reg(data, REG_CHARGER_DET, CHARGER_DET_ON);
449
if (ret)
450
return -EIO;
451
452
/* Initialize extcon device */
453
data->edev = devm_extcon_dev_allocate(dev, lc824206xa_cables);
454
if (IS_ERR(data->edev))
455
return PTR_ERR(data->edev);
456
457
ret = devm_extcon_dev_register(dev, data->edev);
458
if (ret)
459
return dev_err_probe(dev, ret, "registering extcon device\n");
460
461
psy_cfg.drv_data = data;
462
data->psy = devm_power_supply_register(dev, &lc824206xa_psy_desc, &psy_cfg);
463
if (IS_ERR(data->psy))
464
return dev_err_probe(dev, PTR_ERR(data->psy), "registering power supply\n");
465
466
ret = devm_request_threaded_irq(dev, client->irq, NULL, lc824206xa_irq,
467
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
468
KBUILD_MODNAME, data);
469
if (ret)
470
return dev_err_probe(dev, ret, "requesting IRQ\n");
471
472
/* Sync initial state */
473
schedule_work(&data->work);
474
return 0;
475
}
476
477
static const struct i2c_device_id lc824206xa_i2c_ids[] = {
478
{ "lc824206xa" },
479
{ }
480
};
481
MODULE_DEVICE_TABLE(i2c, lc824206xa_i2c_ids);
482
483
static struct i2c_driver lc824206xa_driver = {
484
.driver = {
485
.name = KBUILD_MODNAME,
486
},
487
.probe = lc824206xa_probe,
488
.id_table = lc824206xa_i2c_ids,
489
};
490
491
module_i2c_driver(lc824206xa_driver);
492
493
MODULE_AUTHOR("Hans de Goede <[email protected]>");
494
MODULE_DESCRIPTION("LC824206XA Micro USB Switch driver");
495
MODULE_LICENSE("GPL");
496
497