From d740795d9273dafb02ed809f3b68d62388bc08d9 Mon Sep 17 00:00:00 2001 From: Yauhen Kharuzhy Date: Sat, 5 Oct 2019 23:42:55 +0300 Subject: platform/x86: intel_cht_int33fe: Split code to Micro-B and Type-C Existing intel_cht_int33fe ACPI pseudo-device driver assumes that hardware has Type-C connector and register related devices described as I2C connections in the _CRS resource. There is at least one hardware (Lenovo Yoga Book YB1-91L/F) with Micro-B USB connector exists. It has INT33FE device in the DSDT table but there are only two I2C connection described: PMIC and BQ27452 battery fuel gauge. Splitting existing INT33FE driver allow to maintain code for USB Micro-B (or AB) connector variant separately and make it simpler. Split driver to intel_cht_int33fe_common.c and intel_cht_int33fe_{microb,typec}.c. Compile all this sources to one .ko module to make user experience easier. Signed-off-by: Yauhen Kharuzhy Signed-off-by: Andy Shevchenko --- drivers/platform/x86/Kconfig | 10 +- drivers/platform/x86/Makefile | 4 + drivers/platform/x86/intel_cht_int33fe.c | 416 ------------------------ drivers/platform/x86/intel_cht_int33fe_common.c | 147 +++++++++ drivers/platform/x86/intel_cht_int33fe_common.h | 41 +++ drivers/platform/x86/intel_cht_int33fe_microb.c | 57 ++++ drivers/platform/x86/intel_cht_int33fe_typec.c | 354 ++++++++++++++++++++ 7 files changed, 611 insertions(+), 418 deletions(-) delete mode 100644 drivers/platform/x86/intel_cht_int33fe.c create mode 100644 drivers/platform/x86/intel_cht_int33fe_common.c create mode 100644 drivers/platform/x86/intel_cht_int33fe_common.h create mode 100644 drivers/platform/x86/intel_cht_int33fe_microb.c create mode 100644 drivers/platform/x86/intel_cht_int33fe_typec.c (limited to 'drivers/platform/x86') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 3c55f05f6ec3..da958684f767 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -927,14 +927,20 @@ config INTEL_CHT_INT33FE This driver add support for the INT33FE ACPI device found on some Intel Cherry Trail devices. + There are two kinds of INT33FE ACPI device possible: for hardware + with USB Type-C and Micro-B connectors. This driver supports both. + The INT33FE ACPI device has a CRS table with I2cSerialBusV2 - resources for 3 devices: Maxim MAX17047 Fuel Gauge Controller, + resources for Fuel Gauge Controller and (in the Type-C variant) FUSB302 USB Type-C Controller and PI3USB30532 USB switch. This driver instantiates i2c-clients for these, so that standard i2c drivers for these chips can bind to the them. If you enable this driver it is advised to also select - CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m. + CONFIG_BATTERY_BQ27XXX=m or CONFIG_BATTERY_BQ27XXX_I2C=m for Micro-B + device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m + for Type-C device. + config INTEL_INT0002_VGPIO tristate "Intel ACPI INT0002 Virtual GPIO driver" diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 415104033060..216d3b6fd6a7 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -61,6 +61,10 @@ obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o obj-$(CONFIG_TOSHIBA_HAPS) += toshiba_haps.o obj-$(CONFIG_TOSHIBA_WMI) += toshiba-wmi.o obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o +intel_cht_int33fe-objs := intel_cht_int33fe_common.o \ + intel_cht_int33fe_typec.o \ + intel_cht_int33fe_microb.o + obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o diff --git a/drivers/platform/x86/intel_cht_int33fe.c b/drivers/platform/x86/intel_cht_int33fe.c deleted file mode 100644 index 1d5d877b9582..000000000000 --- a/drivers/platform/x86/intel_cht_int33fe.c +++ /dev/null @@ -1,416 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Cherry Trail ACPI INT33FE pseudo device driver - * - * Copyright (C) 2017 Hans de Goede - * - * Some Intel Cherry Trail based device which ship with Windows 10, have - * this weird INT33FE ACPI device with a CRS table with 4 I2cSerialBusV2 - * resources, for 4 different chips attached to various i2c busses: - * 1. The Whiskey Cove pmic, which is also described by the INT34D3 ACPI device - * 2. Maxim MAX17047 Fuel Gauge Controller - * 3. FUSB302 USB Type-C Controller - * 4. PI3USB30532 USB switch - * - * So this driver is a stub / pseudo driver whose only purpose is to - * instantiate i2c-clients for chips 2 - 4, so that standard i2c drivers - * for these chips can bind to the them. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define EXPECTED_PTYPE 4 - -enum { - INT33FE_NODE_FUSB302, - INT33FE_NODE_MAX17047, - INT33FE_NODE_PI3USB30532, - INT33FE_NODE_DISPLAYPORT, - INT33FE_NODE_USB_CONNECTOR, - INT33FE_NODE_MAX, -}; - -struct cht_int33fe_data { - struct i2c_client *max17047; - struct i2c_client *fusb302; - struct i2c_client *pi3usb30532; - - struct fwnode_handle *dp; -}; - -static const struct software_node nodes[]; - -static const struct software_node_ref_args pi3usb30532_ref = { - &nodes[INT33FE_NODE_PI3USB30532] -}; - -static const struct software_node_ref_args dp_ref = { - &nodes[INT33FE_NODE_DISPLAYPORT] -}; - -static struct software_node_ref_args mux_ref; - -static const struct software_node_reference usb_connector_refs[] = { - { "orientation-switch", 1, &pi3usb30532_ref}, - { "mode-switch", 1, &pi3usb30532_ref}, - { "displayport", 1, &dp_ref}, - { } -}; - -static const struct software_node_reference fusb302_refs[] = { - { "usb-role-switch", 1, &mux_ref}, - { } -}; - -/* - * Grrr I severly dislike buggy BIOS-es. At least one BIOS enumerates - * the max17047 both through the INT33FE ACPI device (it is right there - * in the resources table) as well as through a separate MAX17047 device. - * - * These helpers are used to work around this by checking if an i2c-client - * for the max17047 has already been registered. - */ -static int cht_int33fe_check_for_max17047(struct device *dev, void *data) -{ - struct i2c_client **max17047 = data; - struct acpi_device *adev; - const char *hid; - - adev = ACPI_COMPANION(dev); - if (!adev) - return 0; - - hid = acpi_device_hid(adev); - - /* The MAX17047 ACPI node doesn't have an UID, so we don't check that */ - if (strcmp(hid, "MAX17047")) - return 0; - - *max17047 = to_i2c_client(dev); - return 1; -} - -static const char * const max17047_suppliers[] = { "bq24190-charger" }; - -static const struct property_entry max17047_props[] = { - PROPERTY_ENTRY_STRING_ARRAY("supplied-from", max17047_suppliers), - { } -}; - -static const struct property_entry fusb302_props[] = { - PROPERTY_ENTRY_STRING("linux,extcon-name", "cht_wcove_pwrsrc"), - { } -}; - -#define PDO_FIXED_FLAGS \ - (PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM) - -static const u32 src_pdo[] = { - PDO_FIXED(5000, 1500, PDO_FIXED_FLAGS), -}; - -static const u32 snk_pdo[] = { - PDO_FIXED(5000, 400, PDO_FIXED_FLAGS), - PDO_VAR(5000, 12000, 3000), -}; - -static const struct property_entry usb_connector_props[] = { - PROPERTY_ENTRY_STRING("data-role", "dual"), - PROPERTY_ENTRY_STRING("power-role", "dual"), - PROPERTY_ENTRY_STRING("try-power-role", "sink"), - PROPERTY_ENTRY_U32_ARRAY("source-pdos", src_pdo), - PROPERTY_ENTRY_U32_ARRAY("sink-pdos", snk_pdo), - PROPERTY_ENTRY_U32("op-sink-microwatt", 2500000), - { } -}; - -static const struct software_node nodes[] = { - { "fusb302", NULL, fusb302_props, fusb302_refs }, - { "max17047", NULL, max17047_props }, - { "pi3usb30532" }, - { "displayport" }, - { "connector", &nodes[0], usb_connector_props, usb_connector_refs }, - { } -}; - -static int cht_int33fe_setup_dp(struct cht_int33fe_data *data) -{ - struct fwnode_handle *fwnode; - struct pci_dev *pdev; - - fwnode = software_node_fwnode(&nodes[INT33FE_NODE_DISPLAYPORT]); - if (!fwnode) - return -ENODEV; - - /* First let's find the GPU PCI device */ - pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, NULL); - if (!pdev || pdev->vendor != PCI_VENDOR_ID_INTEL) { - pci_dev_put(pdev); - return -ENODEV; - } - - /* Then the DP child device node */ - data->dp = device_get_named_child_node(&pdev->dev, "DD02"); - pci_dev_put(pdev); - if (!data->dp) - return -ENODEV; - - fwnode->secondary = ERR_PTR(-ENODEV); - data->dp->secondary = fwnode; - - return 0; -} - -static void cht_int33fe_remove_nodes(struct cht_int33fe_data *data) -{ - software_node_unregister_nodes(nodes); - - if (mux_ref.node) { - fwnode_handle_put(software_node_fwnode(mux_ref.node)); - mux_ref.node = NULL; - } - - if (data->dp) { - data->dp->secondary = NULL; - fwnode_handle_put(data->dp); - data->dp = NULL; - } -} - -static int cht_int33fe_add_nodes(struct cht_int33fe_data *data) -{ - int ret; - - ret = software_node_register_nodes(nodes); - if (ret) - return ret; - - /* The devices that are not created in this driver need extra steps. */ - - /* - * There is no ACPI device node for the USB role mux, so we need to wait - * until the mux driver has created software node for the mux device. - * It means we depend on the mux driver. This function will return - * -EPROBE_DEFER until the mux device is registered. - */ - mux_ref.node = software_node_find_by_name(NULL, "intel-xhci-usb-sw"); - if (!mux_ref.node) { - ret = -EPROBE_DEFER; - goto err_remove_nodes; - } - - /* - * The DP connector does have ACPI device node. In this case we can just - * find that ACPI node and assign our node as the secondary node to it. - */ - ret = cht_int33fe_setup_dp(data); - if (ret) - goto err_remove_nodes; - - return 0; - -err_remove_nodes: - cht_int33fe_remove_nodes(data); - - return ret; -} - -static int -cht_int33fe_register_max17047(struct device *dev, struct cht_int33fe_data *data) -{ - struct i2c_client *max17047 = NULL; - struct i2c_board_info board_info; - struct fwnode_handle *fwnode; - int ret; - - fwnode = software_node_fwnode(&nodes[INT33FE_NODE_MAX17047]); - if (!fwnode) - return -ENODEV; - - i2c_for_each_dev(&max17047, cht_int33fe_check_for_max17047); - if (max17047) { - /* Pre-existing i2c-client for the max17047, add device-props */ - fwnode->secondary = ERR_PTR(-ENODEV); - max17047->dev.fwnode->secondary = fwnode; - /* And re-probe to get the new device-props applied. */ - ret = device_reprobe(&max17047->dev); - if (ret) - dev_warn(dev, "Reprobing max17047 error: %d\n", ret); - return 0; - } - - memset(&board_info, 0, sizeof(board_info)); - strlcpy(board_info.type, "max17047", I2C_NAME_SIZE); - board_info.dev_name = "max17047"; - board_info.fwnode = fwnode; - data->max17047 = i2c_acpi_new_device(dev, 1, &board_info); - - return PTR_ERR_OR_ZERO(data->max17047); -} - -static int cht_int33fe_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct i2c_board_info board_info; - struct cht_int33fe_data *data; - struct fwnode_handle *fwnode; - struct regulator *regulator; - unsigned long long ptyp; - acpi_status status; - int fusb302_irq; - int ret; - - status = acpi_evaluate_integer(ACPI_HANDLE(dev), "PTYP", NULL, &ptyp); - if (ACPI_FAILURE(status)) { - dev_err(dev, "Error getting PTYPE\n"); - return -ENODEV; - } - - /* - * The same ACPI HID is used for different configurations check PTYP - * to ensure that we are dealing with the expected config. - */ - if (ptyp != EXPECTED_PTYPE) - return -ENODEV; - - /* Check presence of INT34D3 (hardware-rev 3) expected for ptype == 4 */ - if (!acpi_dev_present("INT34D3", "1", 3)) { - dev_err(dev, "Error PTYPE == %d, but no INT34D3 device\n", - EXPECTED_PTYPE); - return -ENODEV; - } - - /* - * We expect the WC PMIC to be paired with a TI bq24292i charger-IC. - * We check for the bq24292i vbus regulator here, this has 2 purposes: - * 1) The bq24292i allows charging with up to 12V, setting the fusb302's - * max-snk voltage to 12V with another charger-IC is not good. - * 2) For the fusb302 driver to get the bq24292i vbus regulator, the - * regulator-map, which is part of the bq24292i regulator_init_data, - * must be registered before the fusb302 is instantiated, otherwise - * it will end up with a dummy-regulator. - * Note "cht_wc_usb_typec_vbus" comes from the regulator_init_data - * which is defined in i2c-cht-wc.c from where the bq24292i i2c-client - * gets instantiated. We use regulator_get_optional here so that we - * don't end up getting a dummy-regulator ourselves. - */ - regulator = regulator_get_optional(dev, "cht_wc_usb_typec_vbus"); - if (IS_ERR(regulator)) { - ret = PTR_ERR(regulator); - return (ret == -ENODEV) ? -EPROBE_DEFER : ret; - } - regulator_put(regulator); - - /* The FUSB302 uses the irq at index 1 and is the only irq user */ - fusb302_irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 1); - if (fusb302_irq < 0) { - if (fusb302_irq != -EPROBE_DEFER) - dev_err(dev, "Error getting FUSB302 irq\n"); - return fusb302_irq; - } - - data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - ret = cht_int33fe_add_nodes(data); - if (ret) - return ret; - - /* Work around BIOS bug, see comment on cht_int33fe_check_for_max17047 */ - ret = cht_int33fe_register_max17047(dev, data); - if (ret) - goto out_remove_nodes; - - fwnode = software_node_fwnode(&nodes[INT33FE_NODE_FUSB302]); - if (!fwnode) { - ret = -ENODEV; - goto out_unregister_max17047; - } - - memset(&board_info, 0, sizeof(board_info)); - strlcpy(board_info.type, "typec_fusb302", I2C_NAME_SIZE); - board_info.dev_name = "fusb302"; - board_info.fwnode = fwnode; - board_info.irq = fusb302_irq; - - data->fusb302 = i2c_acpi_new_device(dev, 2, &board_info); - if (IS_ERR(data->fusb302)) { - ret = PTR_ERR(data->fusb302); - goto out_unregister_max17047; - } - - fwnode = software_node_fwnode(&nodes[INT33FE_NODE_PI3USB30532]); - if (!fwnode) { - ret = -ENODEV; - goto out_unregister_fusb302; - } - - memset(&board_info, 0, sizeof(board_info)); - board_info.dev_name = "pi3usb30532"; - board_info.fwnode = fwnode; - strlcpy(board_info.type, "pi3usb30532", I2C_NAME_SIZE); - - data->pi3usb30532 = i2c_acpi_new_device(dev, 3, &board_info); - if (IS_ERR(data->pi3usb30532)) { - ret = PTR_ERR(data->pi3usb30532); - goto out_unregister_fusb302; - } - - platform_set_drvdata(pdev, data); - - return 0; - -out_unregister_fusb302: - i2c_unregister_device(data->fusb302); - -out_unregister_max17047: - i2c_unregister_device(data->max17047); - -out_remove_nodes: - cht_int33fe_remove_nodes(data); - - return ret; -} - -static int cht_int33fe_remove(struct platform_device *pdev) -{ - struct cht_int33fe_data *data = platform_get_drvdata(pdev); - - i2c_unregister_device(data->pi3usb30532); - i2c_unregister_device(data->fusb302); - i2c_unregister_device(data->max17047); - - cht_int33fe_remove_nodes(data); - - return 0; -} - -static const struct acpi_device_id cht_int33fe_acpi_ids[] = { - { "INT33FE", }, - { } -}; -MODULE_DEVICE_TABLE(acpi, cht_int33fe_acpi_ids); - -static struct platform_driver cht_int33fe_driver = { - .driver = { - .name = "Intel Cherry Trail ACPI INT33FE driver", - .acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids), - }, - .probe = cht_int33fe_probe, - .remove = cht_int33fe_remove, -}; - -module_platform_driver(cht_int33fe_driver); - -MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE pseudo device driver"); -MODULE_AUTHOR("Hans de Goede "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_cht_int33fe_common.c b/drivers/platform/x86/intel_cht_int33fe_common.c new file mode 100644 index 000000000000..42dd11623f56 --- /dev/null +++ b/drivers/platform/x86/intel_cht_int33fe_common.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers + * (USB Micro-B and Type-C connector variants). + * + * Copyright (c) 2019 Yauhen Kharuzhy + */ + +#include +#include +#include +#include +#include + +#include "intel_cht_int33fe_common.h" + +#define EXPECTED_PTYPE 4 + +static int cht_int33fe_i2c_res_filter(struct acpi_resource *ares, void *data) +{ + struct acpi_resource_i2c_serialbus *sb; + int *count = data; + + if (i2c_acpi_get_i2c_resource(ares, &sb)) + (*count)++; + + return 1; +} + +static int cht_int33fe_count_i2c_clients(struct device *dev) +{ + struct acpi_device *adev; + LIST_HEAD(resource_list); + int count = 0; + + adev = ACPI_COMPANION(dev); + if (!adev) + return -EINVAL; + + acpi_dev_get_resources(adev, &resource_list, + cht_int33fe_i2c_res_filter, &count); + + acpi_dev_free_resource_list(&resource_list); + + return count; +} + +static int cht_int33fe_check_hw_type(struct device *dev) +{ + unsigned long long ptyp; + acpi_status status; + int ret; + + status = acpi_evaluate_integer(ACPI_HANDLE(dev), "PTYP", NULL, &ptyp); + if (ACPI_FAILURE(status)) { + dev_err(dev, "Error getting PTYPE\n"); + return -ENODEV; + } + + /* + * The same ACPI HID is used for different configurations check PTYP + * to ensure that we are dealing with the expected config. + */ + if (ptyp != EXPECTED_PTYPE) + return -ENODEV; + + /* Check presence of INT34D3 (hardware-rev 3) expected for ptype == 4 */ + if (!acpi_dev_present("INT34D3", "1", 3)) { + dev_err(dev, "Error PTYPE == %d, but no INT34D3 device\n", + EXPECTED_PTYPE); + return -ENODEV; + } + + ret = cht_int33fe_count_i2c_clients(dev); + if (ret < 0) + return ret; + + switch (ret) { + case 2: + return INT33FE_HW_MICROB; + case 4: + return INT33FE_HW_TYPEC; + default: + return -ENODEV; + } +} + +static int cht_int33fe_probe(struct platform_device *pdev) +{ + struct cht_int33fe_data *data; + struct device *dev = &pdev->dev; + int ret; + + ret = cht_int33fe_check_hw_type(dev); + if (ret < 0) + return ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = dev; + + switch (ret) { + case INT33FE_HW_MICROB: + data->probe = cht_int33fe_microb_probe; + data->remove = cht_int33fe_microb_remove; + break; + + case INT33FE_HW_TYPEC: + data->probe = cht_int33fe_typec_probe; + data->remove = cht_int33fe_typec_remove; + break; + } + + platform_set_drvdata(pdev, data); + + return data->probe(data); +} + +static int cht_int33fe_remove(struct platform_device *pdev) +{ + struct cht_int33fe_data *data = platform_get_drvdata(pdev); + + return data->remove(data); +} + +static const struct acpi_device_id cht_int33fe_acpi_ids[] = { + { "INT33FE", }, + { } +}; +MODULE_DEVICE_TABLE(acpi, cht_int33fe_acpi_ids); + +static struct platform_driver cht_int33fe_driver = { + .driver = { + .name = "Intel Cherry Trail ACPI INT33FE driver", + .acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids), + }, + .probe = cht_int33fe_probe, + .remove = cht_int33fe_remove, +}; + +module_platform_driver(cht_int33fe_driver); + +MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE pseudo device driver"); +MODULE_AUTHOR("Yauhen Kharuzhy "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_cht_int33fe_common.h b/drivers/platform/x86/intel_cht_int33fe_common.h new file mode 100644 index 000000000000..03cd45f4e8cb --- /dev/null +++ b/drivers/platform/x86/intel_cht_int33fe_common.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers + * (USB Micro-B and Type-C connector variants), header file + * + * Copyright (c) 2019 Yauhen Kharuzhy + */ + +#ifndef _INTEL_CHT_INT33FE_COMMON_H +#define _INTEL_CHT_INT33FE_COMMON_H + +#include +#include +#include + +enum int33fe_hw_type { + INT33FE_HW_MICROB, + INT33FE_HW_TYPEC, +}; + +struct cht_int33fe_data { + struct device *dev; + + int (*probe)(struct cht_int33fe_data *data); + int (*remove)(struct cht_int33fe_data *data); + + struct i2c_client *battery_fg; + + /* Type-C only */ + struct i2c_client *fusb302; + struct i2c_client *pi3usb30532; + + struct fwnode_handle *dp; +}; + +int cht_int33fe_microb_probe(struct cht_int33fe_data *data); +int cht_int33fe_microb_remove(struct cht_int33fe_data *data); +int cht_int33fe_typec_probe(struct cht_int33fe_data *data); +int cht_int33fe_typec_remove(struct cht_int33fe_data *data); + +#endif /* _INTEL_CHT_INT33FE_COMMON_H */ diff --git a/drivers/platform/x86/intel_cht_int33fe_microb.c b/drivers/platform/x86/intel_cht_int33fe_microb.c new file mode 100644 index 000000000000..20b11e0d9a75 --- /dev/null +++ b/drivers/platform/x86/intel_cht_int33fe_microb.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Cherry Trail ACPI INT33FE pseudo device driver for devices with + * USB Micro-B connector (e.g. without of FUSB302 USB Type-C controller) + * + * Copyright (C) 2019 Yauhen Kharuzhy + * + * At least one Intel Cherry Trail based device which ship with Windows 10 + * (Lenovo YogaBook YB1-X91L/F tablet), have this weird INT33FE ACPI device + * with a CRS table with 2 I2cSerialBusV2 resources, for 2 different chips + * attached to various i2c busses: + * 1. The Whiskey Cove PMIC, which is also described by the INT34D3 ACPI device + * 2. TI BQ27542 Fuel Gauge Controller + * + * So this driver is a stub / pseudo driver whose only purpose is to + * instantiate i2c-client for battery fuel gauge, so that standard i2c driver + * for these chip can bind to the it. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intel_cht_int33fe_common.h" + +static const char * const bq27xxx_suppliers[] = { "bq25890-charger" }; + +static const struct property_entry bq27xxx_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq27xxx_suppliers), + { } +}; + +int cht_int33fe_microb_probe(struct cht_int33fe_data *data) +{ + struct device *dev = data->dev; + struct i2c_board_info board_info; + + memset(&board_info, 0, sizeof(board_info)); + strscpy(board_info.type, "bq27542", ARRAY_SIZE(board_info.type)); + board_info.dev_name = "bq27542"; + board_info.properties = bq27xxx_props; + data->battery_fg = i2c_acpi_new_device(dev, 1, &board_info); + + return PTR_ERR_OR_ZERO(data->battery_fg); +} + +int cht_int33fe_microb_remove(struct cht_int33fe_data *data) +{ + i2c_unregister_device(data->battery_fg); + + return 0; +} diff --git a/drivers/platform/x86/intel_cht_int33fe_typec.c b/drivers/platform/x86/intel_cht_int33fe_typec.c new file mode 100644 index 000000000000..2d097fc2dd46 --- /dev/null +++ b/drivers/platform/x86/intel_cht_int33fe_typec.c @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Cherry Trail ACPI INT33FE pseudo device driver + * + * Copyright (C) 2017 Hans de Goede + * + * Some Intel Cherry Trail based device which ship with Windows 10, have + * this weird INT33FE ACPI device with a CRS table with 4 I2cSerialBusV2 + * resources, for 4 different chips attached to various i2c busses: + * 1. The Whiskey Cove pmic, which is also described by the INT34D3 ACPI device + * 2. Maxim MAX17047 Fuel Gauge Controller + * 3. FUSB302 USB Type-C Controller + * 4. PI3USB30532 USB switch + * + * So this driver is a stub / pseudo driver whose only purpose is to + * instantiate i2c-clients for chips 2 - 4, so that standard i2c drivers + * for these chips can bind to the them. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "intel_cht_int33fe_common.h" + +enum { + INT33FE_NODE_FUSB302, + INT33FE_NODE_MAX17047, + INT33FE_NODE_PI3USB30532, + INT33FE_NODE_DISPLAYPORT, + INT33FE_NODE_USB_CONNECTOR, + INT33FE_NODE_MAX, +}; + +static const struct software_node nodes[]; + +static const struct software_node_ref_args pi3usb30532_ref = { + &nodes[INT33FE_NODE_PI3USB30532] +}; + +static const struct software_node_ref_args dp_ref = { + &nodes[INT33FE_NODE_DISPLAYPORT] +}; + +static struct software_node_ref_args mux_ref; + +static const struct software_node_reference usb_connector_refs[] = { + { "orientation-switch", 1, &pi3usb30532_ref}, + { "mode-switch", 1, &pi3usb30532_ref}, + { "displayport", 1, &dp_ref}, + { } +}; + +static const struct software_node_reference fusb302_refs[] = { + { "usb-role-switch", 1, &mux_ref}, + { } +}; + +/* + * Grrr I severly dislike buggy BIOS-es. At least one BIOS enumerates + * the max17047 both through the INT33FE ACPI device (it is right there + * in the resources table) as well as through a separate MAX17047 device. + * + * These helpers are used to work around this by checking if an i2c-client + * for the max17047 has already been registered. + */ +static int cht_int33fe_check_for_max17047(struct device *dev, void *data) +{ + struct i2c_client **max17047 = data; + struct acpi_device *adev; + const char *hid; + + adev = ACPI_COMPANION(dev); + if (!adev) + return 0; + + hid = acpi_device_hid(adev); + + /* The MAX17047 ACPI node doesn't have an UID, so we don't check that */ + if (strcmp(hid, "MAX17047")) + return 0; + + *max17047 = to_i2c_client(dev); + return 1; +} + +static const char * const max17047_suppliers[] = { "bq24190-charger" }; + +static const struct property_entry max17047_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", max17047_suppliers), + { } +}; + +static const struct property_entry fusb302_props[] = { + PROPERTY_ENTRY_STRING("linux,extcon-name", "cht_wcove_pwrsrc"), + { } +}; + +#define PDO_FIXED_FLAGS \ + (PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM) + +static const u32 src_pdo[] = { + PDO_FIXED(5000, 1500, PDO_FIXED_FLAGS), +}; + +static const u32 snk_pdo[] = { + PDO_FIXED(5000, 400, PDO_FIXED_FLAGS), + PDO_VAR(5000, 12000, 3000), +}; + +static const struct property_entry usb_connector_props[] = { + PROPERTY_ENTRY_STRING("data-role", "dual"), + PROPERTY_ENTRY_STRING("power-role", "dual"), + PROPERTY_ENTRY_STRING("try-power-role", "sink"), + PROPERTY_ENTRY_U32_ARRAY("source-pdos", src_pdo), + PROPERTY_ENTRY_U32_ARRAY("sink-pdos", snk_pdo), + PROPERTY_ENTRY_U32("op-sink-microwatt", 2500000), + { } +}; + +static const struct software_node nodes[] = { + { "fusb302", NULL, fusb302_props, fusb302_refs }, + { "max17047", NULL, max17047_props }, + { "pi3usb30532" }, + { "displayport" }, + { "connector", &nodes[0], usb_connector_props, usb_connector_refs }, + { } +}; + +static int cht_int33fe_setup_dp(struct cht_int33fe_data *data) +{ + struct fwnode_handle *fwnode; + struct pci_dev *pdev; + + fwnode = software_node_fwnode(&nodes[INT33FE_NODE_DISPLAYPORT]); + if (!fwnode) + return -ENODEV; + + /* First let's find the GPU PCI device */ + pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, NULL); + if (!pdev || pdev->vendor != PCI_VENDOR_ID_INTEL) { + pci_dev_put(pdev); + return -ENODEV; + } + + /* Then the DP child device node */ + data->dp = device_get_named_child_node(&pdev->dev, "DD02"); + pci_dev_put(pdev); + if (!data->dp) + return -ENODEV; + + fwnode->secondary = ERR_PTR(-ENODEV); + data->dp->secondary = fwnode; + + return 0; +} + +static void cht_int33fe_remove_nodes(struct cht_int33fe_data *data) +{ + software_node_unregister_nodes(nodes); + + if (mux_ref.node) { + fwnode_handle_put(software_node_fwnode(mux_ref.node)); + mux_ref.node = NULL; + } + + if (data->dp) { + data->dp->secondary = NULL; + fwnode_handle_put(data->dp); + data->dp = NULL; + } +} + +static int cht_int33fe_add_nodes(struct cht_int33fe_data *data) +{ + int ret; + + ret = software_node_register_nodes(nodes); + if (ret) + return ret; + + /* The devices that are not created in this driver need extra steps. */ + + /* + * There is no ACPI device node for the USB role mux, so we need to wait + * until the mux driver has created software node for the mux device. + * It means we depend on the mux driver. This function will return + * -EPROBE_DEFER until the mux device is registered. + */ + mux_ref.node = software_node_find_by_name(NULL, "intel-xhci-usb-sw"); + if (!mux_ref.node) { + ret = -EPROBE_DEFER; + goto err_remove_nodes; + } + + /* + * The DP connector does have ACPI device node. In this case we can just + * find that ACPI node and assign our node as the secondary node to it. + */ + ret = cht_int33fe_setup_dp(data); + if (ret) + goto err_remove_nodes; + + return 0; + +err_remove_nodes: + cht_int33fe_remove_nodes(data); + + return ret; +} + +static int +cht_int33fe_register_max17047(struct device *dev, struct cht_int33fe_data *data) +{ + struct i2c_client *max17047 = NULL; + struct i2c_board_info board_info; + struct fwnode_handle *fwnode; + int ret; + + fwnode = software_node_fwnode(&nodes[INT33FE_NODE_MAX17047]); + if (!fwnode) + return -ENODEV; + + i2c_for_each_dev(&max17047, cht_int33fe_check_for_max17047); + if (max17047) { + /* Pre-existing i2c-client for the max17047, add device-props */ + fwnode->secondary = ERR_PTR(-ENODEV); + max17047->dev.fwnode->secondary = fwnode; + /* And re-probe to get the new device-props applied. */ + ret = device_reprobe(&max17047->dev); + if (ret) + dev_warn(dev, "Reprobing max17047 error: %d\n", ret); + return 0; + } + + memset(&board_info, 0, sizeof(board_info)); + strlcpy(board_info.type, "max17047", I2C_NAME_SIZE); + board_info.dev_name = "max17047"; + board_info.fwnode = fwnode; + data->battery_fg = i2c_acpi_new_device(dev, 1, &board_info); + + return PTR_ERR_OR_ZERO(data->battery_fg); +} + +int cht_int33fe_typec_probe(struct cht_int33fe_data *data) +{ + struct device *dev = data->dev; + struct i2c_board_info board_info; + struct fwnode_handle *fwnode; + struct regulator *regulator; + int fusb302_irq; + int ret; + + /* + * We expect the WC PMIC to be paired with a TI bq24292i charger-IC. + * We check for the bq24292i vbus regulator here, this has 2 purposes: + * 1) The bq24292i allows charging with up to 12V, setting the fusb302's + * max-snk voltage to 12V with another charger-IC is not good. + * 2) For the fusb302 driver to get the bq24292i vbus regulator, the + * regulator-map, which is part of the bq24292i regulator_init_data, + * must be registered before the fusb302 is instantiated, otherwise + * it will end up with a dummy-regulator. + * Note "cht_wc_usb_typec_vbus" comes from the regulator_init_data + * which is defined in i2c-cht-wc.c from where the bq24292i i2c-client + * gets instantiated. We use regulator_get_optional here so that we + * don't end up getting a dummy-regulator ourselves. + */ + regulator = regulator_get_optional(dev, "cht_wc_usb_typec_vbus"); + if (IS_ERR(regulator)) { + ret = PTR_ERR(regulator); + return (ret == -ENODEV) ? -EPROBE_DEFER : ret; + } + regulator_put(regulator); + + /* The FUSB302 uses the irq at index 1 and is the only irq user */ + fusb302_irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 1); + if (fusb302_irq < 0) { + if (fusb302_irq != -EPROBE_DEFER) + dev_err(dev, "Error getting FUSB302 irq\n"); + return fusb302_irq; + } + + ret = cht_int33fe_add_nodes(data); + if (ret) + return ret; + + /* Work around BIOS bug, see comment on cht_int33fe_check_for_max17047 */ + ret = cht_int33fe_register_max17047(dev, data); + if (ret) + goto out_remove_nodes; + + fwnode = software_node_fwnode(&nodes[INT33FE_NODE_FUSB302]); + if (!fwnode) { + ret = -ENODEV; + goto out_unregister_max17047; + } + + memset(&board_info, 0, sizeof(board_info)); + strlcpy(board_info.type, "typec_fusb302", I2C_NAME_SIZE); + board_info.dev_name = "fusb302"; + board_info.fwnode = fwnode; + board_info.irq = fusb302_irq; + + data->fusb302 = i2c_acpi_new_device(dev, 2, &board_info); + if (IS_ERR(data->fusb302)) { + ret = PTR_ERR(data->fusb302); + goto out_unregister_max17047; + } + + fwnode = software_node_fwnode(&nodes[INT33FE_NODE_PI3USB30532]); + if (!fwnode) { + ret = -ENODEV; + goto out_unregister_fusb302; + } + + memset(&board_info, 0, sizeof(board_info)); + board_info.dev_name = "pi3usb30532"; + board_info.fwnode = fwnode; + strlcpy(board_info.type, "pi3usb30532", I2C_NAME_SIZE); + + data->pi3usb30532 = i2c_acpi_new_device(dev, 3, &board_info); + if (IS_ERR(data->pi3usb30532)) { + ret = PTR_ERR(data->pi3usb30532); + goto out_unregister_fusb302; + } + + return 0; + +out_unregister_fusb302: + i2c_unregister_device(data->fusb302); + +out_unregister_max17047: + i2c_unregister_device(data->battery_fg); + +out_remove_nodes: + cht_int33fe_remove_nodes(data); + + return ret; +} + +int cht_int33fe_typec_remove(struct cht_int33fe_data *data) +{ + i2c_unregister_device(data->pi3usb30532); + i2c_unregister_device(data->fusb302); + i2c_unregister_device(data->battery_fg); + + cht_int33fe_remove_nodes(data); + + return 0; +} -- cgit v1.2.3