Commit 28e54414 authored by Sebastian Reichel's avatar Sebastian Reichel
Browse files

Merge tag 'psy-extcon-i2c-mfd-for-v5.18-signed' into psy-next



Immutable branch between power-supply, mfd, i2c and extcon for for 5.18

This immutable branch fixes the charger setup on Xiaomi Mi Pad 2 and
Lenovo Yogabook, which requires updates to multiple drivers throughout
the tree.

Signed-off-by: default avatarSebastian Reichel <sre@kernel.org>
parents 210bc22c 21356ac1
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -61,6 +61,8 @@ config EXTCON_INTEL_INT3496
config EXTCON_INTEL_CHT_WC
	tristate "Intel Cherrytrail Whiskey Cove PMIC extcon driver"
	depends on INTEL_SOC_PMIC_CHTWC
	depends on USB_SUPPORT
	select USB_ROLE_SWITCH
	help
	  Say Y here to enable extcon support for charger detection / control
	  on the Intel Cherrytrail Whiskey Cove PMIC.
+214 −26
Original line number Diff line number Diff line
@@ -14,8 +14,12 @@
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/usb/role.h>

#include "extcon-intel.h"

@@ -101,8 +105,13 @@ struct cht_wc_extcon_data {
	struct device *dev;
	struct regmap *regmap;
	struct extcon_dev *edev;
	struct usb_role_switch *role_sw;
	struct regulator *vbus_boost;
	struct power_supply *psy;
	enum power_supply_usb_type usb_type;
	unsigned int previous_cable;
	bool usb_host;
	bool vbus_boost_enabled;
};

static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts)
@@ -112,13 +121,21 @@ static int cht_wc_extcon_get_id(struct cht_wc_extcon_data *ext, int pwrsrc_sts)
		return INTEL_USB_ID_GND;
	case CHT_WC_PWRSRC_RID_FLOAT:
		return INTEL_USB_ID_FLOAT;
	case CHT_WC_PWRSRC_RID_ACA:
	default:
	/*
		 * Once we have IIO support for the GPADC we should read
		 * the USBID GPADC channel here and determine ACA role
		 * based on that.
	 * According to the spec. we should read the USB-ID pin ADC value here
	 * to determine the resistance of the used pull-down resister and then
	 * return RID_A / RID_B / RID_C based on this. But all "Accessory
	 * Charger Adapter"s (ACAs) which users can actually buy always use
	 * a combination of a charging port with one or more USB-A ports, so
	 * they should always use a resistor indicating RID_A. But the spec
	 * is hard to read / badly-worded so some of them actually indicate
	 * they are a RID_B ACA evnen though they clearly are a RID_A ACA.
	 * To workaround this simply always return INTEL_USB_RID_A, which
	 * matches all the ACAs which users can actually buy.
	 */
	case CHT_WC_PWRSRC_RID_ACA:
		return INTEL_USB_RID_A;
	default:
		return INTEL_USB_ID_FLOAT;
	}
}
@@ -147,14 +164,15 @@ static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext,
	} while (time_before(jiffies, timeout));

	if (status != CHT_WC_USBSRC_STS_SUCCESS) {
		if (ignore_errors)
			return EXTCON_CHG_USB_SDP; /* Save fallback */

		if (!ignore_errors) {
			if (status == CHT_WC_USBSRC_STS_FAIL)
				dev_warn(ext->dev, "Could not detect charger type\n");
			else
				dev_warn(ext->dev, "Timeout detecting charger type\n");
		return EXTCON_CHG_USB_SDP; /* Save fallback */
		}

		/* Safe fallback */
		usbsrc = CHT_WC_USBSRC_TYPE_SDP << CHT_WC_USBSRC_TYPE_SHIFT;
	}

	usbsrc = (usbsrc & CHT_WC_USBSRC_TYPE_MASK) >> CHT_WC_USBSRC_TYPE_SHIFT;
@@ -163,18 +181,23 @@ static int cht_wc_extcon_get_charger(struct cht_wc_extcon_data *ext,
		dev_warn(ext->dev,
			"Unhandled charger type %d, defaulting to SDP\n",
			 ret);
		ext->usb_type = POWER_SUPPLY_USB_TYPE_SDP;
		return EXTCON_CHG_USB_SDP;
	case CHT_WC_USBSRC_TYPE_SDP:
	case CHT_WC_USBSRC_TYPE_FLOATING:
	case CHT_WC_USBSRC_TYPE_OTHER:
		ext->usb_type = POWER_SUPPLY_USB_TYPE_SDP;
		return EXTCON_CHG_USB_SDP;
	case CHT_WC_USBSRC_TYPE_CDP:
		ext->usb_type = POWER_SUPPLY_USB_TYPE_CDP;
		return EXTCON_CHG_USB_CDP;
	case CHT_WC_USBSRC_TYPE_DCP:
	case CHT_WC_USBSRC_TYPE_DCP_EXTPHY:
	case CHT_WC_USBSRC_TYPE_MHL: /* MHL2+ delivers upto 2A, treat as DCP */
		ext->usb_type = POWER_SUPPLY_USB_TYPE_DCP;
		return EXTCON_CHG_USB_DCP;
	case CHT_WC_USBSRC_TYPE_ACA:
		ext->usb_type = POWER_SUPPLY_USB_TYPE_ACA;
		return EXTCON_CHG_USB_ACA;
	}
}
@@ -216,6 +239,18 @@ static void cht_wc_extcon_set_otgmode(struct cht_wc_extcon_data *ext,
				 CHT_WC_CHGRCTRL1_OTGMODE, val);
	if (ret)
		dev_err(ext->dev, "Error updating CHGRCTRL1 reg: %d\n", ret);

	if (ext->vbus_boost && ext->vbus_boost_enabled != enable) {
		if (enable)
			ret = regulator_enable(ext->vbus_boost);
		else
			ret = regulator_disable(ext->vbus_boost);

		if (ret)
			dev_err(ext->dev, "Error updating Vbus boost regulator: %d\n", ret);
		else
			ext->vbus_boost_enabled = enable;
	}
}

static void cht_wc_extcon_enable_charging(struct cht_wc_extcon_data *ext,
@@ -245,6 +280,9 @@ static void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext)
	unsigned int cable = EXTCON_NONE;
	/* Ignore errors in host mode, as the 5v boost converter is on then */
	bool ignore_get_charger_errors = ext->usb_host;
	enum usb_role role;

	ext->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;

	ret = regmap_read(ext->regmap, CHT_WC_PWRSRC_STS, &pwrsrc_sts);
	if (ret) {
@@ -288,6 +326,21 @@ static void cht_wc_extcon_pwrsrc_event(struct cht_wc_extcon_data *ext)

	ext->usb_host = ((id == INTEL_USB_ID_GND) || (id == INTEL_USB_RID_A));
	extcon_set_state_sync(ext->edev, EXTCON_USB_HOST, ext->usb_host);

	if (ext->usb_host)
		role = USB_ROLE_HOST;
	else if (pwrsrc_sts & CHT_WC_PWRSRC_VBUS)
		role = USB_ROLE_DEVICE;
	else
		role = USB_ROLE_NONE;

	/* Note: this is a no-op when ext->role_sw is NULL */
	ret = usb_role_switch_set_role(ext->role_sw, role);
	if (ret)
		dev_err(ext->dev, "Error setting USB-role: %d\n", ret);

	if (ext->psy)
		power_supply_changed(ext->psy);
}

static irqreturn_t cht_wc_extcon_isr(int irq, void *data)
@@ -333,6 +386,114 @@ static int cht_wc_extcon_sw_control(struct cht_wc_extcon_data *ext, bool enable)
	return ret;
}

static int cht_wc_extcon_find_role_sw(struct cht_wc_extcon_data *ext)
{
	const struct software_node *swnode;
	struct fwnode_handle *fwnode;

	swnode = software_node_find_by_name(NULL, "intel-xhci-usb-sw");
	if (!swnode)
		return -EPROBE_DEFER;

	fwnode = software_node_fwnode(swnode);
	ext->role_sw = usb_role_switch_find_by_fwnode(fwnode);
	fwnode_handle_put(fwnode);

	return ext->role_sw ? 0 : -EPROBE_DEFER;
}

static void cht_wc_extcon_put_role_sw(void *data)
{
	struct cht_wc_extcon_data *ext = data;

	usb_role_switch_put(ext->role_sw);
}

/* Some boards require controlling the role-sw and Vbus based on the id-pin */
static int cht_wc_extcon_get_role_sw_and_regulator(struct cht_wc_extcon_data *ext)
{
	int ret;

	ret = cht_wc_extcon_find_role_sw(ext);
	if (ret)
		return ret;

	ret = devm_add_action_or_reset(ext->dev, cht_wc_extcon_put_role_sw, ext);
	if (ret)
		return ret;

	/*
	 * On x86/ACPI platforms the regulator <-> consumer link is provided
	 * by platform_data passed to the regulator driver. This means that
	 * this info is not available before the regulator driver has bound.
	 * Use devm_regulator_get_optional() to avoid getting a dummy
	 * regulator and wait for the regulator to show up if necessary.
	 */
	ext->vbus_boost = devm_regulator_get_optional(ext->dev, "vbus");
	if (IS_ERR(ext->vbus_boost)) {
		ret = PTR_ERR(ext->vbus_boost);
		if (ret == -ENODEV)
			ret = -EPROBE_DEFER;

		return dev_err_probe(ext->dev, ret, "getting Vbus regulator");
	}

	return 0;
}

static int cht_wc_extcon_psy_get_prop(struct power_supply *psy,
				      enum power_supply_property psp,
				      union power_supply_propval *val)
{
	struct cht_wc_extcon_data *ext = power_supply_get_drvdata(psy);

	switch (psp) {
	case POWER_SUPPLY_PROP_USB_TYPE:
		val->intval = ext->usb_type;
		break;
	case POWER_SUPPLY_PROP_ONLINE:
		val->intval = ext->usb_type ? 1 : 0;
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

static const enum power_supply_usb_type cht_wc_extcon_psy_usb_types[] = {
	POWER_SUPPLY_USB_TYPE_SDP,
	POWER_SUPPLY_USB_TYPE_CDP,
	POWER_SUPPLY_USB_TYPE_DCP,
	POWER_SUPPLY_USB_TYPE_ACA,
	POWER_SUPPLY_USB_TYPE_UNKNOWN,
};

static const enum power_supply_property cht_wc_extcon_psy_props[] = {
	POWER_SUPPLY_PROP_USB_TYPE,
	POWER_SUPPLY_PROP_ONLINE,
};

static const struct power_supply_desc cht_wc_extcon_psy_desc = {
	.name = "cht_wcove_pwrsrc",
	.type = POWER_SUPPLY_TYPE_USB,
	.usb_types = cht_wc_extcon_psy_usb_types,
	.num_usb_types = ARRAY_SIZE(cht_wc_extcon_psy_usb_types),
	.properties = cht_wc_extcon_psy_props,
	.num_properties = ARRAY_SIZE(cht_wc_extcon_psy_props),
	.get_property = cht_wc_extcon_psy_get_prop,
};

static int cht_wc_extcon_register_psy(struct cht_wc_extcon_data *ext)
{
	struct power_supply_config psy_cfg = { .drv_data = ext };

	ext->psy = devm_power_supply_register(ext->dev,
					      &cht_wc_extcon_psy_desc,
					      &psy_cfg);
	return PTR_ERR_OR_ZERO(ext->psy);
}

static int cht_wc_extcon_probe(struct platform_device *pdev)
{
	struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
@@ -358,6 +519,8 @@ static int cht_wc_extcon_probe(struct platform_device *pdev)
	if (IS_ERR(ext->edev))
		return PTR_ERR(ext->edev);

	switch (pmic->cht_wc_model) {
	case INTEL_CHT_WC_GPD_WIN_POCKET:
		/*
		 * When a host-cable is detected the BIOS enables an external 5v boost
		 * converter to power connected devices there are 2 problems with this:
@@ -372,6 +535,31 @@ static int cht_wc_extcon_probe(struct platform_device *pdev)
		 * external 5v boost converter off and leave it off entirely.
		 */
		cht_wc_extcon_set_5v_boost(ext, false);
		break;
	case INTEL_CHT_WC_LENOVO_YOGABOOK1:
		/* Do this first, as it may very well return -EPROBE_DEFER. */
		ret = cht_wc_extcon_get_role_sw_and_regulator(ext);
		if (ret)
			return ret;
		/*
		 * The bq25890 used here relies on this driver's BC-1.2 charger
		 * detection, and the bq25890 driver expect this info to be
		 * available through a parent power_supply class device which
		 * models the detected charger (idem to how the Type-C TCPM code
		 * registers a power_supply classdev for the connected charger).
		 */
		ret = cht_wc_extcon_register_psy(ext);
		if (ret)
			return ret;
		break;
	case INTEL_CHT_WC_XIAOMI_MIPAD2:
		ret = cht_wc_extcon_get_role_sw_and_regulator(ext);
		if (ret)
			return ret;
		break;
	default:
		break;
	}

	/* Enable sw control */
	ret = cht_wc_extcon_sw_control(ext, true);
+102 −18
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/power/bq24190_charger.h>
#include <linux/power/bq25890_charger.h>
#include <linux/slab.h>

#define CHT_WC_I2C_CTRL			0x5e24
@@ -270,6 +271,7 @@ static const struct irq_chip cht_wc_i2c_irq_chip = {
	.name			= "cht_wc_ext_chrg_irq_chip",
};

/********** GPD Win / Pocket charger IC settings **********/
static const char * const bq24190_suppliers[] = {
	"tcpm-source-psy-i2c-fusb302" };

@@ -304,17 +306,92 @@ static struct bq24190_platform_data bq24190_pdata = {
	.regulator_init_data = &bq24190_vbus_init_data,
};

static int cht_wc_i2c_adap_i2c_probe(struct platform_device *pdev)
{
	struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
	struct cht_wc_i2c_adap *adap;
	struct i2c_board_info board_info = {
static struct i2c_board_info gpd_win_board_info = {
	.type = "bq24190",
	.addr = 0x6b,
	.dev_name = "bq24190",
	.swnode = &bq24190_node,
	.platform_data = &bq24190_pdata,
};

/********** Xiaomi Mi Pad 2 charger IC settings  **********/
static struct regulator_consumer_supply bq2589x_vbus_consumer = {
	.supply = "vbus",
	.dev_name = "cht_wcove_pwrsrc",
};

static const struct regulator_init_data bq2589x_vbus_init_data = {
	.constraints = {
		.valid_ops_mask = REGULATOR_CHANGE_STATUS,
	},
	.consumer_supplies = &bq2589x_vbus_consumer,
	.num_consumer_supplies = 1,
};

static struct bq25890_platform_data bq2589x_pdata = {
	.regulator_init_data = &bq2589x_vbus_init_data,
};

static const struct property_entry xiaomi_mipad2_props[] = {
	PROPERTY_ENTRY_BOOL("linux,skip-reset"),
	PROPERTY_ENTRY_BOOL("linux,read-back-settings"),
	{ }
};

static const struct software_node xiaomi_mipad2_node = {
	.properties = xiaomi_mipad2_props,
};

static struct i2c_board_info xiaomi_mipad2_board_info = {
	.type = "bq25890",
	.addr = 0x6a,
	.dev_name = "bq25890",
	.swnode = &xiaomi_mipad2_node,
	.platform_data = &bq2589x_pdata,
};

/********** Lenovo Yogabook YB1-X90F/-X91F/-X91L charger settings **********/
static const char * const lenovo_yb1_bq25892_suppliers[] = { "cht_wcove_pwrsrc" };

static const struct property_entry lenovo_yb1_bq25892_props[] = {
	PROPERTY_ENTRY_STRING_ARRAY("supplied-from",
				    lenovo_yb1_bq25892_suppliers),
	PROPERTY_ENTRY_U32("linux,pump-express-vbus-max", 12000000),
	PROPERTY_ENTRY_BOOL("linux,skip-reset"),
	/*
	 * The firmware sets everything to the defaults, which leads to a
	 * somewhat low charge-current of 2048mA and worse to a battery-voltage
	 * of 4.2V instead of 4.35V (when booted without a charger connected).
	 * Use our own values instead of "linux,read-back-settings" to fix this.
	 */
	PROPERTY_ENTRY_U32("ti,charge-current", 4224000),
	PROPERTY_ENTRY_U32("ti,battery-regulation-voltage", 4352000),
	PROPERTY_ENTRY_U32("ti,termination-current", 256000),
	PROPERTY_ENTRY_U32("ti,precharge-current", 128000),
	PROPERTY_ENTRY_U32("ti,minimum-sys-voltage", 3500000),
	PROPERTY_ENTRY_U32("ti,boost-voltage", 4998000),
	PROPERTY_ENTRY_U32("ti,boost-max-current", 1400000),
	PROPERTY_ENTRY_BOOL("ti,use-ilim-pin"),
	{ }
};

static const struct software_node lenovo_yb1_bq25892_node = {
	.properties = lenovo_yb1_bq25892_props,
};

static struct i2c_board_info lenovo_yogabook1_board_info = {
	.type = "bq25892",
	.addr = 0x6b,
	.dev_name = "bq25892",
	.swnode = &lenovo_yb1_bq25892_node,
	.platform_data = &bq2589x_pdata,
};

static int cht_wc_i2c_adap_i2c_probe(struct platform_device *pdev)
{
	struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
	struct i2c_board_info *board_info = NULL;
	struct cht_wc_i2c_adap *adap;
	int ret, reg, irq;

	irq = platform_get_irq(pdev, 0);
@@ -379,17 +456,24 @@ static int cht_wc_i2c_adap_i2c_probe(struct platform_device *pdev)
	if (ret)
		goto remove_irq_domain;

	/*
	 * Normally the Whiskey Cove PMIC is paired with a TI bq24292i charger,
	 * connected to this i2c bus, and a max17047 fuel-gauge and a fusb302
	 * USB Type-C controller connected to another i2c bus. In this setup
	 * the max17047 and fusb302 devices are enumerated through an INT33FE
	 * ACPI device. If this device is present register an i2c-client for
	 * the TI bq24292i charger.
	 */
	if (acpi_dev_present("INT33FE", NULL, -1)) {
		board_info.irq = adap->client_irq;
		adap->client = i2c_new_client_device(&adap->adapter, &board_info);
	switch (pmic->cht_wc_model) {
	case INTEL_CHT_WC_GPD_WIN_POCKET:
		board_info = &gpd_win_board_info;
		break;
	case INTEL_CHT_WC_XIAOMI_MIPAD2:
		board_info = &xiaomi_mipad2_board_info;
		break;
	case INTEL_CHT_WC_LENOVO_YOGABOOK1:
		board_info = &lenovo_yogabook1_board_info;
		break;
	default:
		dev_warn(&pdev->dev, "Unknown model, not instantiating charger device\n");
		break;
	}

	if (board_info) {
		board_info->irq = adap->client_irq;
		adap->client = i2c_new_client_device(&adap->adapter, board_info);
		if (IS_ERR(adap->client)) {
			ret = PTR_ERR(adap->client);
			goto del_adapter;
+40 −0
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@

#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
@@ -134,9 +135,44 @@ static const struct regmap_irq_chip cht_wc_regmap_irq_chip = {
	.num_regs = 1,
};

static const struct dmi_system_id cht_wc_model_dmi_ids[] = {
	{
		/* GPD win / GPD pocket mini laptops */
		.driver_data = (void *)(long)INTEL_CHT_WC_GPD_WIN_POCKET,
		/*
		 * This DMI match may not seem unique, but it is. In the 67000+
		 * DMI decode dumps from linux-hardware.org only 116 have
		 * board_vendor set to "AMI Corporation" and of those 116 only
		 * the GPD win's and pocket's board_name is "Default string".
		 */
		.matches = {
			DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
			DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"),
			DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"),
			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"),
		},
	}, {
		/* Xiaomi Mi Pad 2 */
		.driver_data = (void *)(long)INTEL_CHT_WC_XIAOMI_MIPAD2,
		.matches = {
			DMI_MATCH(DMI_SYS_VENDOR, "Xiaomi Inc"),
			DMI_MATCH(DMI_PRODUCT_NAME, "Mipad2"),
		},
	}, {
		/* Lenovo Yoga Book X90F / X91F / X91L */
		.driver_data = (void *)(long)INTEL_CHT_WC_LENOVO_YOGABOOK1,
		.matches = {
			/* Non exact match to match all versions */
			DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X9"),
		},
	},
	{ }
};

static int cht_wc_probe(struct i2c_client *client)
{
	struct device *dev = &client->dev;
	const struct dmi_system_id *id;
	struct intel_soc_pmic *pmic;
	acpi_status status;
	unsigned long long hrv;
@@ -160,6 +196,10 @@ static int cht_wc_probe(struct i2c_client *client)
	if (!pmic)
		return -ENOMEM;

	id = dmi_first_match(cht_wc_model_dmi_ids);
	if (id)
		pmic->cht_wc_model = (long)id->driver_data;

	pmic->irq = client->irq;
	pmic->dev = dev;
	i2c_set_clientdata(client, pmic);
+11 −1
Original line number Diff line number Diff line
@@ -1206,8 +1206,18 @@ static void bq24190_input_current_limit_work(struct work_struct *work)
	struct bq24190_dev_info *bdi =
		container_of(work, struct bq24190_dev_info,
			     input_current_limit_work.work);
	union power_supply_propval val;
	int ret;

	power_supply_set_input_current_limit_from_supplier(bdi->charger);
	ret = power_supply_get_property_from_supplier(bdi->charger,
						      POWER_SUPPLY_PROP_CURRENT_MAX,
						      &val);
	if (ret)
		return;

	bq24190_charger_set_property(bdi->charger,
				     POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
				     &val);
}

/* Sync the input-current-limit with our parent supply (if we have one) */
Loading