brintos

brintos / linux-shallow public Read only

0
0
Text · 13.7 KiB · 5693874 Raw
496 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * ON Semiconductor LC824206XA Micro USB Switch driver4 *5 * Copyright (c) 2024 Hans de Goede <hansg@kernel.org>6 *7 * ON Semiconductor has an "Advance Information" datasheet available8 * (ENA2222-D.PDF), but no full datasheet. So there is no documentation9 * 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 driver14 * for the LC824206XA not the FSA9285. The Android sources can be downloaded15 * 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 register33 * info, so this may not be 100% accurate.34 */35#define REG00				0x0036#define REG00_INIT_VALUE		0x0137 38#define REG_STATUS			0x0139#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		0x8044#define STATUS_USB_ID_ACA		0xf045#define STATUS_USB_ID_FLOAT		0xf846 47/*48 * This controls the DP/DM muxes + other switches,49 * meaning of individual bits is unknown.50 */51#define REG_SWITCH_CONTROL		0x0252#define SWITCH_STEREO_MIC		0xc853#define SWITCH_USB_HOST			0xec54#define SWITCH_DISCONNECTED		0xf855#define SWITCH_USB_DEVICE		0xfc56 57/* 5 bits? ADC 0x10 GND, 0x1a-0x1f ACA, 0x1f float */58#define REG_ID_PIN_ADC_VALUE		0x0359 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			0x0479 80/*81 * Writing a 1 to a bit here clears it in INTR_STATUS. These bits do NOT82 * auto-reset to 0, so these must be set to 0 manually after clearing.83 */84#define REG_INTR_CLEAR			0x0585 86/* Interrupts which bit is set to 1 here will not raise the HW IRQ */87#define REG_INTR_MASK			0x0688 89/* ID pin ADC control, meaning of individual bits is unknown */90#define REG_ID_PIN_ADC_CTRL		0x0791#define ID_PIN_ADC_AUTO			0x4092#define ID_PIN_ADC_CONTINUOUS		0x4493 94#define REG_CHARGER_DET			0x0895#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		0x09100#define CHARGER_TYPE_UNKNOWN		0x00101#define CHARGER_TYPE_DCP		0x01102#define CHARGER_TYPE_SDP_OR_CDP		0x04103#define CHARGER_TYPE_QC			0x06104 105#define REG10				0x10106#define REG10_INIT_VALUE		0x00107 108struct 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 124static 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 */135static 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 146static 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 157static 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 172static 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	else182		ret = regulator_disable(data->vbus_boost);183 184	if (ret == 0)185		data->vbus_boost_enabled = enable;186	else187		dev_err(&data->client->dev, "Error updating Vbus boost regulator: %d\n", ret);188}189 190static 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		else232			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 245static 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 341static 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 be361 * provided by a parent power_supply device for the charger chip.362 */363static 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 396static 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 402static 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 415static 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 477static const struct i2c_device_id lc824206xa_i2c_ids[] = {478	{ "lc824206xa" },479	{ }480};481MODULE_DEVICE_TABLE(i2c, lc824206xa_i2c_ids);482 483static struct i2c_driver lc824206xa_driver = {484	.driver = {485		.name = KBUILD_MODNAME,486	},487	.probe = lc824206xa_probe,488	.id_table = lc824206xa_i2c_ids,489};490 491module_i2c_driver(lc824206xa_driver);492 493MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");494MODULE_DESCRIPTION("LC824206XA Micro USB Switch driver");495MODULE_LICENSE("GPL");496