162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Intel Cherry Trail ACPI INT33FE pseudo device driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com>
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Some Intel Cherry Trail based device which ship with Windows 10, have
862306a36Sopenharmony_ci * this weird INT33FE ACPI device with a CRS table with 4 I2cSerialBusV2
962306a36Sopenharmony_ci * resources, for 4 different chips attached to various I²C buses:
1062306a36Sopenharmony_ci * 1. The Whiskey Cove PMIC, which is also described by the INT34D3 ACPI device
1162306a36Sopenharmony_ci * 2. Maxim MAX17047 Fuel Gauge Controller
1262306a36Sopenharmony_ci * 3. FUSB302 USB Type-C Controller
1362306a36Sopenharmony_ci * 4. PI3USB30532 USB switch
1462306a36Sopenharmony_ci *
1562306a36Sopenharmony_ci * So this driver is a stub / pseudo driver whose only purpose is to
1662306a36Sopenharmony_ci * instantiate I²C clients for chips 2 - 4, so that standard I²C drivers
1762306a36Sopenharmony_ci * for these chips can bind to the them.
1862306a36Sopenharmony_ci */
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#include <linux/dmi.h>
2162306a36Sopenharmony_ci#include <linux/i2c.h>
2262306a36Sopenharmony_ci#include <linux/interrupt.h>
2362306a36Sopenharmony_ci#include <linux/pci.h>
2462306a36Sopenharmony_ci#include <linux/platform_device.h>
2562306a36Sopenharmony_ci#include <linux/property.h>
2662306a36Sopenharmony_ci#include <linux/regulator/consumer.h>
2762306a36Sopenharmony_ci#include <linux/slab.h>
2862306a36Sopenharmony_ci#include <linux/usb/pd.h>
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistruct cht_int33fe_data {
3162306a36Sopenharmony_ci	struct i2c_client *battery_fg;
3262306a36Sopenharmony_ci	struct i2c_client *fusb302;
3362306a36Sopenharmony_ci	struct i2c_client *pi3usb30532;
3462306a36Sopenharmony_ci	struct fwnode_handle *dp;
3562306a36Sopenharmony_ci};
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci/*
3862306a36Sopenharmony_ci * Grrr, I severely dislike buggy BIOS-es. At least one BIOS enumerates
3962306a36Sopenharmony_ci * the max17047 both through the INT33FE ACPI device (it is right there
4062306a36Sopenharmony_ci * in the resources table) as well as through a separate MAX17047 device.
4162306a36Sopenharmony_ci *
4262306a36Sopenharmony_ci * These helpers are used to work around this by checking if an I²C client
4362306a36Sopenharmony_ci * for the max17047 has already been registered.
4462306a36Sopenharmony_ci */
4562306a36Sopenharmony_cistatic int cht_int33fe_check_for_max17047(struct device *dev, void *data)
4662306a36Sopenharmony_ci{
4762306a36Sopenharmony_ci	struct i2c_client **max17047 = data;
4862306a36Sopenharmony_ci	struct acpi_device *adev;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	adev = ACPI_COMPANION(dev);
5162306a36Sopenharmony_ci	if (!adev)
5262306a36Sopenharmony_ci		return 0;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	/* The MAX17047 ACPI node doesn't have an UID, so we don't check that */
5562306a36Sopenharmony_ci	if (!acpi_dev_hid_uid_match(adev, "MAX17047", NULL))
5662306a36Sopenharmony_ci		return 0;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	*max17047 = to_i2c_client(dev);
5962306a36Sopenharmony_ci	return 1;
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic const char * const max17047_suppliers[] = { "bq24190-charger" };
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistatic const struct property_entry max17047_properties[] = {
6562306a36Sopenharmony_ci	PROPERTY_ENTRY_STRING_ARRAY("supplied-from", max17047_suppliers),
6662306a36Sopenharmony_ci	{ }
6762306a36Sopenharmony_ci};
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic const struct software_node max17047_node = {
7062306a36Sopenharmony_ci	.name = "max17047",
7162306a36Sopenharmony_ci	.properties = max17047_properties,
7262306a36Sopenharmony_ci};
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci/*
7562306a36Sopenharmony_ci * We are not using inline property here because those are constant,
7662306a36Sopenharmony_ci * and we need to adjust this one at runtime to point to real
7762306a36Sopenharmony_ci * software node.
7862306a36Sopenharmony_ci */
7962306a36Sopenharmony_cistatic struct software_node_ref_args fusb302_mux_refs[] = {
8062306a36Sopenharmony_ci	{ .node = NULL },
8162306a36Sopenharmony_ci};
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic const struct property_entry fusb302_properties[] = {
8462306a36Sopenharmony_ci	PROPERTY_ENTRY_STRING("linux,extcon-name", "cht_wcove_pwrsrc"),
8562306a36Sopenharmony_ci	PROPERTY_ENTRY_REF_ARRAY("usb-role-switch", fusb302_mux_refs),
8662306a36Sopenharmony_ci	{ }
8762306a36Sopenharmony_ci};
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_cistatic const struct software_node fusb302_node = {
9062306a36Sopenharmony_ci	.name = "fusb302",
9162306a36Sopenharmony_ci	.properties = fusb302_properties,
9262306a36Sopenharmony_ci};
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci#define PDO_FIXED_FLAGS \
9562306a36Sopenharmony_ci	(PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM)
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic const u32 src_pdo[] = {
9862306a36Sopenharmony_ci	PDO_FIXED(5000, 1500, PDO_FIXED_FLAGS),
9962306a36Sopenharmony_ci};
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic const u32 snk_pdo[] = {
10262306a36Sopenharmony_ci	PDO_FIXED(5000, 400, PDO_FIXED_FLAGS),
10362306a36Sopenharmony_ci	PDO_VAR(5000, 12000, 3000),
10462306a36Sopenharmony_ci};
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cistatic const struct software_node pi3usb30532_node = {
10762306a36Sopenharmony_ci	.name = "pi3usb30532",
10862306a36Sopenharmony_ci};
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic const struct software_node displayport_node = {
11162306a36Sopenharmony_ci	.name = "displayport",
11262306a36Sopenharmony_ci};
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_cistatic const struct property_entry usb_connector_properties[] = {
11562306a36Sopenharmony_ci	PROPERTY_ENTRY_STRING("data-role", "dual"),
11662306a36Sopenharmony_ci	PROPERTY_ENTRY_STRING("power-role", "dual"),
11762306a36Sopenharmony_ci	PROPERTY_ENTRY_STRING("try-power-role", "sink"),
11862306a36Sopenharmony_ci	PROPERTY_ENTRY_U32_ARRAY("source-pdos", src_pdo),
11962306a36Sopenharmony_ci	PROPERTY_ENTRY_U32_ARRAY("sink-pdos", snk_pdo),
12062306a36Sopenharmony_ci	PROPERTY_ENTRY_U32("op-sink-microwatt", 2500000),
12162306a36Sopenharmony_ci	PROPERTY_ENTRY_REF("orientation-switch", &pi3usb30532_node),
12262306a36Sopenharmony_ci	PROPERTY_ENTRY_REF("mode-switch", &pi3usb30532_node),
12362306a36Sopenharmony_ci	PROPERTY_ENTRY_REF("displayport", &displayport_node),
12462306a36Sopenharmony_ci	{ }
12562306a36Sopenharmony_ci};
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_cistatic const struct software_node usb_connector_node = {
12862306a36Sopenharmony_ci	.name = "connector",
12962306a36Sopenharmony_ci	.parent = &fusb302_node,
13062306a36Sopenharmony_ci	.properties = usb_connector_properties,
13162306a36Sopenharmony_ci};
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic const struct software_node altmodes_node = {
13462306a36Sopenharmony_ci	.name = "altmodes",
13562306a36Sopenharmony_ci	.parent = &usb_connector_node,
13662306a36Sopenharmony_ci};
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic const struct property_entry dp_altmode_properties[] = {
13962306a36Sopenharmony_ci	PROPERTY_ENTRY_U32("svid", 0xff01),
14062306a36Sopenharmony_ci	PROPERTY_ENTRY_U32("vdo", 0x0c0086),
14162306a36Sopenharmony_ci	{ }
14262306a36Sopenharmony_ci};
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic const struct software_node dp_altmode_node = {
14562306a36Sopenharmony_ci	.name = "displayport-altmode",
14662306a36Sopenharmony_ci	.parent = &altmodes_node,
14762306a36Sopenharmony_ci	.properties = dp_altmode_properties,
14862306a36Sopenharmony_ci};
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic const struct software_node *node_group[] = {
15162306a36Sopenharmony_ci	&fusb302_node,
15262306a36Sopenharmony_ci	&max17047_node,
15362306a36Sopenharmony_ci	&pi3usb30532_node,
15462306a36Sopenharmony_ci	&displayport_node,
15562306a36Sopenharmony_ci	&usb_connector_node,
15662306a36Sopenharmony_ci	&altmodes_node,
15762306a36Sopenharmony_ci	&dp_altmode_node,
15862306a36Sopenharmony_ci	NULL
15962306a36Sopenharmony_ci};
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cistatic int cht_int33fe_setup_dp(struct cht_int33fe_data *data)
16262306a36Sopenharmony_ci{
16362306a36Sopenharmony_ci	struct fwnode_handle *fwnode;
16462306a36Sopenharmony_ci	struct pci_dev *pdev;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	fwnode = software_node_fwnode(&displayport_node);
16762306a36Sopenharmony_ci	if (!fwnode)
16862306a36Sopenharmony_ci		return -ENODEV;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	/* First let's find the GPU PCI device */
17162306a36Sopenharmony_ci	pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, NULL);
17262306a36Sopenharmony_ci	if (!pdev || pdev->vendor != PCI_VENDOR_ID_INTEL) {
17362306a36Sopenharmony_ci		pci_dev_put(pdev);
17462306a36Sopenharmony_ci		return -ENODEV;
17562306a36Sopenharmony_ci	}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	/* Then the DP-2 child device node */
17862306a36Sopenharmony_ci	data->dp = device_get_named_child_node(&pdev->dev, "DD04");
17962306a36Sopenharmony_ci	pci_dev_put(pdev);
18062306a36Sopenharmony_ci	if (!data->dp)
18162306a36Sopenharmony_ci		return -ENODEV;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	fwnode->secondary = ERR_PTR(-ENODEV);
18462306a36Sopenharmony_ci	data->dp->secondary = fwnode;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	return 0;
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cistatic void cht_int33fe_remove_nodes(struct cht_int33fe_data *data)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	software_node_unregister_node_group(node_group);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	if (fusb302_mux_refs[0].node) {
19462306a36Sopenharmony_ci		fwnode_handle_put(software_node_fwnode(fusb302_mux_refs[0].node));
19562306a36Sopenharmony_ci		fusb302_mux_refs[0].node = NULL;
19662306a36Sopenharmony_ci	}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	if (data->dp) {
19962306a36Sopenharmony_ci		data->dp->secondary = NULL;
20062306a36Sopenharmony_ci		fwnode_handle_put(data->dp);
20162306a36Sopenharmony_ci		data->dp = NULL;
20262306a36Sopenharmony_ci	}
20362306a36Sopenharmony_ci}
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_cistatic int cht_int33fe_add_nodes(struct cht_int33fe_data *data)
20662306a36Sopenharmony_ci{
20762306a36Sopenharmony_ci	const struct software_node *mux_ref_node;
20862306a36Sopenharmony_ci	int ret;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	/*
21162306a36Sopenharmony_ci	 * There is no ACPI device node for the USB role mux, so we need to wait
21262306a36Sopenharmony_ci	 * until the mux driver has created software node for the mux device.
21362306a36Sopenharmony_ci	 * It means we depend on the mux driver. This function will return
21462306a36Sopenharmony_ci	 * -EPROBE_DEFER until the mux device is registered.
21562306a36Sopenharmony_ci	 */
21662306a36Sopenharmony_ci	mux_ref_node = software_node_find_by_name(NULL, "intel-xhci-usb-sw");
21762306a36Sopenharmony_ci	if (!mux_ref_node)
21862306a36Sopenharmony_ci		return -EPROBE_DEFER;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	/*
22162306a36Sopenharmony_ci	 * Update node used in "usb-role-switch" property. Note that we
22262306a36Sopenharmony_ci	 * rely on software_node_register_node_group() to use the original
22362306a36Sopenharmony_ci	 * instance of properties instead of copying them.
22462306a36Sopenharmony_ci	 */
22562306a36Sopenharmony_ci	fusb302_mux_refs[0].node = mux_ref_node;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	ret = software_node_register_node_group(node_group);
22862306a36Sopenharmony_ci	if (ret)
22962306a36Sopenharmony_ci		return ret;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	/* The devices that are not created in this driver need extra steps. */
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	/*
23462306a36Sopenharmony_ci	 * The DP connector does have ACPI device node. In this case we can just
23562306a36Sopenharmony_ci	 * find that ACPI node and assign our node as the secondary node to it.
23662306a36Sopenharmony_ci	 */
23762306a36Sopenharmony_ci	ret = cht_int33fe_setup_dp(data);
23862306a36Sopenharmony_ci	if (ret)
23962306a36Sopenharmony_ci		goto err_remove_nodes;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	return 0;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_cierr_remove_nodes:
24462306a36Sopenharmony_ci	cht_int33fe_remove_nodes(data);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	return ret;
24762306a36Sopenharmony_ci}
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_cistatic int
25062306a36Sopenharmony_cicht_int33fe_register_max17047(struct device *dev, struct cht_int33fe_data *data)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	struct i2c_client *max17047 = NULL;
25362306a36Sopenharmony_ci	struct i2c_board_info board_info;
25462306a36Sopenharmony_ci	struct fwnode_handle *fwnode;
25562306a36Sopenharmony_ci	int ret;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	fwnode = software_node_fwnode(&max17047_node);
25862306a36Sopenharmony_ci	if (!fwnode)
25962306a36Sopenharmony_ci		return -ENODEV;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	i2c_for_each_dev(&max17047, cht_int33fe_check_for_max17047);
26262306a36Sopenharmony_ci	if (max17047) {
26362306a36Sopenharmony_ci		/* Pre-existing I²C client for the max17047, add device properties */
26462306a36Sopenharmony_ci		set_secondary_fwnode(&max17047->dev, fwnode);
26562306a36Sopenharmony_ci		/* And re-probe to get the new device properties applied */
26662306a36Sopenharmony_ci		ret = device_reprobe(&max17047->dev);
26762306a36Sopenharmony_ci		if (ret)
26862306a36Sopenharmony_ci			dev_warn(dev, "Reprobing max17047 error: %d\n", ret);
26962306a36Sopenharmony_ci		return 0;
27062306a36Sopenharmony_ci	}
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	memset(&board_info, 0, sizeof(board_info));
27362306a36Sopenharmony_ci	strscpy(board_info.type, "max17047", I2C_NAME_SIZE);
27462306a36Sopenharmony_ci	board_info.dev_name = "max17047";
27562306a36Sopenharmony_ci	board_info.fwnode = fwnode;
27662306a36Sopenharmony_ci	data->battery_fg = i2c_acpi_new_device(dev, 1, &board_info);
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	return PTR_ERR_OR_ZERO(data->battery_fg);
27962306a36Sopenharmony_ci}
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_cistatic const struct dmi_system_id cht_int33fe_typec_ids[] = {
28262306a36Sopenharmony_ci	{
28362306a36Sopenharmony_ci		/*
28462306a36Sopenharmony_ci		 * GPD win / GPD pocket mini laptops
28562306a36Sopenharmony_ci		 *
28662306a36Sopenharmony_ci		 * This DMI match may not seem unique, but it is. In the 67000+
28762306a36Sopenharmony_ci		 * DMI decode dumps from linux-hardware.org only 116 have
28862306a36Sopenharmony_ci		 * board_vendor set to "AMI Corporation" and of those 116 only
28962306a36Sopenharmony_ci		 * the GPD win's and pocket's board_name is "Default string".
29062306a36Sopenharmony_ci		 */
29162306a36Sopenharmony_ci		.matches = {
29262306a36Sopenharmony_ci			DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
29362306a36Sopenharmony_ci			DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"),
29462306a36Sopenharmony_ci			DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"),
29562306a36Sopenharmony_ci			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"),
29662306a36Sopenharmony_ci		},
29762306a36Sopenharmony_ci	},
29862306a36Sopenharmony_ci	{ }
29962306a36Sopenharmony_ci};
30062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(dmi, cht_int33fe_typec_ids);
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_cistatic int cht_int33fe_typec_probe(struct platform_device *pdev)
30362306a36Sopenharmony_ci{
30462306a36Sopenharmony_ci	struct i2c_board_info board_info;
30562306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
30662306a36Sopenharmony_ci	struct cht_int33fe_data *data;
30762306a36Sopenharmony_ci	struct fwnode_handle *fwnode;
30862306a36Sopenharmony_ci	struct regulator *regulator;
30962306a36Sopenharmony_ci	int fusb302_irq;
31062306a36Sopenharmony_ci	int ret;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	if (!dmi_check_system(cht_int33fe_typec_ids))
31362306a36Sopenharmony_ci		return -ENODEV;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
31662306a36Sopenharmony_ci	if (!data)
31762306a36Sopenharmony_ci		return -ENOMEM;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	/*
32062306a36Sopenharmony_ci	 * We expect the WC PMIC to be paired with a TI bq24292i charger-IC.
32162306a36Sopenharmony_ci	 * We check for the bq24292i vbus regulator here, this has 2 purposes:
32262306a36Sopenharmony_ci	 * 1) The bq24292i allows charging with up to 12V, setting the fusb302's
32362306a36Sopenharmony_ci	 *    max-snk voltage to 12V with another charger-IC is not good.
32462306a36Sopenharmony_ci	 * 2) For the fusb302 driver to get the bq24292i vbus regulator, the
32562306a36Sopenharmony_ci	 *    regulator-map, which is part of the bq24292i regulator_init_data,
32662306a36Sopenharmony_ci	 *    must be registered before the fusb302 is instantiated, otherwise
32762306a36Sopenharmony_ci	 *    it will end up with a dummy-regulator.
32862306a36Sopenharmony_ci	 * Note "cht_wc_usb_typec_vbus" comes from the regulator_init_data
32962306a36Sopenharmony_ci	 * which is defined in i2c-cht-wc.c from where the bq24292i I²C client
33062306a36Sopenharmony_ci	 * gets instantiated. We use regulator_get_optional here so that we
33162306a36Sopenharmony_ci	 * don't end up getting a dummy-regulator ourselves.
33262306a36Sopenharmony_ci	 */
33362306a36Sopenharmony_ci	regulator = regulator_get_optional(dev, "cht_wc_usb_typec_vbus");
33462306a36Sopenharmony_ci	if (IS_ERR(regulator)) {
33562306a36Sopenharmony_ci		ret = PTR_ERR(regulator);
33662306a36Sopenharmony_ci		return (ret == -ENODEV) ? -EPROBE_DEFER : ret;
33762306a36Sopenharmony_ci	}
33862306a36Sopenharmony_ci	regulator_put(regulator);
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	/* The FUSB302 uses the IRQ at index 1 and is the only IRQ user */
34162306a36Sopenharmony_ci	fusb302_irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 1);
34262306a36Sopenharmony_ci	if (fusb302_irq < 0) {
34362306a36Sopenharmony_ci		if (fusb302_irq != -EPROBE_DEFER)
34462306a36Sopenharmony_ci			dev_err(dev, "Error getting FUSB302 irq\n");
34562306a36Sopenharmony_ci		return fusb302_irq;
34662306a36Sopenharmony_ci	}
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	ret = cht_int33fe_add_nodes(data);
34962306a36Sopenharmony_ci	if (ret)
35062306a36Sopenharmony_ci		return ret;
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	/* Work around BIOS bug, see comment on cht_int33fe_check_for_max17047() */
35362306a36Sopenharmony_ci	ret = cht_int33fe_register_max17047(dev, data);
35462306a36Sopenharmony_ci	if (ret)
35562306a36Sopenharmony_ci		goto out_remove_nodes;
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	fwnode = software_node_fwnode(&fusb302_node);
35862306a36Sopenharmony_ci	if (!fwnode) {
35962306a36Sopenharmony_ci		ret = -ENODEV;
36062306a36Sopenharmony_ci		goto out_unregister_max17047;
36162306a36Sopenharmony_ci	}
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	memset(&board_info, 0, sizeof(board_info));
36462306a36Sopenharmony_ci	strscpy(board_info.type, "typec_fusb302", I2C_NAME_SIZE);
36562306a36Sopenharmony_ci	board_info.dev_name = "fusb302";
36662306a36Sopenharmony_ci	board_info.fwnode = fwnode;
36762306a36Sopenharmony_ci	board_info.irq = fusb302_irq;
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci	data->fusb302 = i2c_acpi_new_device(dev, 2, &board_info);
37062306a36Sopenharmony_ci	if (IS_ERR(data->fusb302)) {
37162306a36Sopenharmony_ci		ret = PTR_ERR(data->fusb302);
37262306a36Sopenharmony_ci		goto out_unregister_max17047;
37362306a36Sopenharmony_ci	}
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	fwnode = software_node_fwnode(&pi3usb30532_node);
37662306a36Sopenharmony_ci	if (!fwnode) {
37762306a36Sopenharmony_ci		ret = -ENODEV;
37862306a36Sopenharmony_ci		goto out_unregister_fusb302;
37962306a36Sopenharmony_ci	}
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	memset(&board_info, 0, sizeof(board_info));
38262306a36Sopenharmony_ci	board_info.dev_name = "pi3usb30532";
38362306a36Sopenharmony_ci	board_info.fwnode = fwnode;
38462306a36Sopenharmony_ci	strscpy(board_info.type, "pi3usb30532", I2C_NAME_SIZE);
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	data->pi3usb30532 = i2c_acpi_new_device(dev, 3, &board_info);
38762306a36Sopenharmony_ci	if (IS_ERR(data->pi3usb30532)) {
38862306a36Sopenharmony_ci		ret = PTR_ERR(data->pi3usb30532);
38962306a36Sopenharmony_ci		goto out_unregister_fusb302;
39062306a36Sopenharmony_ci	}
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	platform_set_drvdata(pdev, data);
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_ci	return 0;
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ciout_unregister_fusb302:
39762306a36Sopenharmony_ci	i2c_unregister_device(data->fusb302);
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ciout_unregister_max17047:
40062306a36Sopenharmony_ci	i2c_unregister_device(data->battery_fg);
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ciout_remove_nodes:
40362306a36Sopenharmony_ci	cht_int33fe_remove_nodes(data);
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci	return ret;
40662306a36Sopenharmony_ci}
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_cistatic void cht_int33fe_typec_remove(struct platform_device *pdev)
40962306a36Sopenharmony_ci{
41062306a36Sopenharmony_ci	struct cht_int33fe_data *data = platform_get_drvdata(pdev);
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	i2c_unregister_device(data->pi3usb30532);
41362306a36Sopenharmony_ci	i2c_unregister_device(data->fusb302);
41462306a36Sopenharmony_ci	i2c_unregister_device(data->battery_fg);
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci	cht_int33fe_remove_nodes(data);
41762306a36Sopenharmony_ci}
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_cistatic const struct acpi_device_id cht_int33fe_acpi_ids[] = {
42062306a36Sopenharmony_ci	{ "INT33FE", },
42162306a36Sopenharmony_ci	{ }
42262306a36Sopenharmony_ci};
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_cistatic struct platform_driver cht_int33fe_typec_driver = {
42562306a36Sopenharmony_ci	.driver	= {
42662306a36Sopenharmony_ci		.name = "Intel Cherry Trail ACPI INT33FE Type-C driver",
42762306a36Sopenharmony_ci		.acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids),
42862306a36Sopenharmony_ci	},
42962306a36Sopenharmony_ci	.probe = cht_int33fe_typec_probe,
43062306a36Sopenharmony_ci	.remove_new = cht_int33fe_typec_remove,
43162306a36Sopenharmony_ci};
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_cimodule_platform_driver(cht_int33fe_typec_driver);
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ciMODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE Type-C pseudo device driver");
43662306a36Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
43762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
438