xref: /linux/drivers/extcon/extcon-lc824206xa.c (revision 3a39d672e7f48b8d6b91a09afa4b55352773b4b5)
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 <hansg@kernel.org>
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 */
lc824206xa_read_reg(struct lc824206xa_data * data,u8 reg)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 
lc824206xa_write_reg(struct lc824206xa_data * data,u8 reg,u8 val)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 
lc824206xa_get_id(struct lc824206xa_data * data)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 
lc824206xa_set_vbus_boost(struct lc824206xa_data * data,bool enable)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 
lc824206xa_charger_detect(struct lc824206xa_data * data)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 
lc824206xa_work(struct work_struct * work)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 
lc824206xa_irq(int irq,void * _data)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  */
lc824206xa_psy_get_prop(struct power_supply * psy,enum power_supply_property psp,union power_supply_propval * val)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 
lc824206xa_probe(struct i2c_client * client)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 <hansg@kernel.org>");
494 MODULE_DESCRIPTION("LC824206XA Micro USB Switch driver");
495 MODULE_LICENSE("GPL");
496