root/drivers/platform/x86/x86-android-tablets/lenovo.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Board info for Lenovo X86 tablets which ship with Android as the factory image
 * and which have broken DSDT tables. The factory kernels shipped on these
 * devices typically have a bunch of things hardcoded, rather than specified
 * in their DSDT.
 *
 * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org>
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/efi.h>
#include <linux/gpio/machine.h>
#include <linux/gpio/property.h>
#include <linux/input-event-codes.h>
#include <linux/mfd/arizona/pdata.h>
#include <linux/mfd/arizona/registers.h>
#include <linux/mfd/intel_soc_pmic.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pinctrl/machine.h>
#include <linux/platform_data/lp855x.h>
#include <linux/platform_device.h>
#include <linux/power/bq24190_charger.h>
#include <linux/reboot.h>
#include <linux/rmi.h>
#include <linux/spi/spi.h>

#include "shared-psy-info.h"
#include "x86-android-tablets.h"

/*
 * Various Lenovo models use a TI LP8557 LED backlight controller with its PWM
 * input connected to a PWM output coming from the LCD panel's controller.
 * The Android kernels have a hack in the i915 driver to write a non-standard
 * panel specific DSI register to set the duty-cycle of the LCD's PWM output.
 *
 * To avoid having to have a similar hack in the mainline kernel program the
 * LP8557 to directly set the level and use the lp855x_bl driver for control.
 *
 * The LP8557 can either be configured to multiply its PWM input and
 * the I2C register set level (requiring both to be at 100% for 100% output);
 * or to only take the I2C register set level into account.
 *
 * Multiplying the 2 levels is useful because this will turn off the backlight
 * when the panel goes off and turns off its PWM output.
 *
 * But on some models the panel's PWM output defaults to a duty-cycle of
 * much less then 100%, severely limiting max brightness. In this case
 * the LP8557 should be configured to only take the I2C register into
 * account and the i915 driver must turn off the panel and the backlight
 * separately using e.g. VBT MIPI sequences to turn off the backlight.
 */
static struct lp855x_platform_data lenovo_lp8557_pwm_and_reg_pdata = {
        .device_control = 0x86,
        .initial_brightness = 128,
};

static struct lp855x_platform_data lenovo_lp8557_reg_only_pdata = {
        .device_control = 0x85,
        .initial_brightness = 128,
};

static const struct software_node arizona_gpiochip_node = {
        .name = "arizona",
};

static const struct software_node crystalcove_gpiochip_node = {
        .name = "gpio_crystalcove",
};

/* Lenovo Yoga Book X90F / X90L's Android factory image has everything hardcoded */

static const struct property_entry lenovo_yb1_x90_goodix_props[] = {
        PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[1], 53, GPIO_ACTIVE_HIGH),
        PROPERTY_ENTRY_GPIO("irq-gpios", &cherryview_gpiochip_nodes[1], 56, GPIO_ACTIVE_HIGH),
        { }
};

static const struct software_node lenovo_yb1_x90_goodix_node = {
        .properties = lenovo_yb1_x90_goodix_props,
};

static const struct property_entry lenovo_yb1_x90_wacom_props[] = {
        PROPERTY_ENTRY_U32("hid-descr-addr", 0x0001),
        PROPERTY_ENTRY_U32("post-reset-deassert-delay-ms", 150),
        PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 82, GPIO_ACTIVE_LOW),
        { }
};

static const struct software_node lenovo_yb1_x90_wacom_node = {
        .properties = lenovo_yb1_x90_wacom_props,
};

/*
 * The HiDeep IST940E touchscreen comes up in I2C-HID mode. The native protocol
 * reports ABS_MT_PRESSURE and ABS_MT_TOUCH_MAJOR which are not reported in HID
 * mode, so using native mode is preferred.
 * It could alternatively be used in HID mode by changing the properties to:
 *      PROPERTY_ENTRY_U32("hid-descr-addr", 0x0020),
 *      PROPERTY_ENTRY_U32("post-reset-deassert-delay-ms", 120),
 * and changing board_info.type to "hid-over-i2c".
 */
static const struct property_entry lenovo_yb1_x90_hideep_ts_props[] = {
        PROPERTY_ENTRY_U32("touchscreen-size-x", 1200),
        PROPERTY_ENTRY_U32("touchscreen-size-y", 1920),
        PROPERTY_ENTRY_U32("touchscreen-max-pressure", 16384),
        PROPERTY_ENTRY_BOOL("hideep,force-native-protocol"),
        PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 7, GPIO_ACTIVE_LOW),
        { }
};

static const struct software_node lenovo_yb1_x90_hideep_ts_node = {
        .properties = lenovo_yb1_x90_hideep_ts_props,
};

static const struct x86_i2c_client_info lenovo_yb1_x90_i2c_clients[] __initconst = {
        {
                /* BQ27542 fuel-gauge */
                .board_info = {
                        .type = "bq27542",
                        .addr = 0x55,
                        .dev_name = "bq27542",
                        .swnode = &fg_bq25890_supply_node,
                },
                .adapter_path = "\\_SB_.PCI0.I2C1",
        }, {
                /* Goodix Touchscreen in keyboard half */
                .board_info = {
                        .type = "GDIX1001:00",
                        .addr = 0x14,
                        .dev_name = "goodix_ts",
                        .swnode = &lenovo_yb1_x90_goodix_node,
                },
                .adapter_path = "\\_SB_.PCI0.I2C2",
                .irq_data = {
                        .type = X86_ACPI_IRQ_TYPE_GPIOINT,
                        .chip = "INT33FF:01",
                        .index = 56,
                        .trigger = ACPI_EDGE_SENSITIVE,
                        .polarity = ACPI_ACTIVE_LOW,
                        .con_id = "goodix_ts_irq",
                        .free_gpio = true,
                },
        }, {
                /* Wacom Digitizer in keyboard half */
                .board_info = {
                        .type = "hid-over-i2c",
                        .addr = 0x09,
                        .dev_name = "wacom",
                        .swnode = &lenovo_yb1_x90_wacom_node,
                },
                .adapter_path = "\\_SB_.PCI0.I2C4",
                .irq_data = {
                        .type = X86_ACPI_IRQ_TYPE_GPIOINT,
                        .chip = "INT33FF:01",
                        .index = 49,
                        .trigger = ACPI_LEVEL_SENSITIVE,
                        .polarity = ACPI_ACTIVE_LOW,
                        .con_id = "wacom_irq",
                },
        }, {
                /* LP8557 Backlight controller */
                .board_info = {
                        .type = "lp8557",
                        .addr = 0x2c,
                        .dev_name = "lp8557",
                        .platform_data = &lenovo_lp8557_pwm_and_reg_pdata,
                },
                .adapter_path = "\\_SB_.PCI0.I2C4",
        }, {
                /* HiDeep IST940E Touchscreen in display half */
                .board_info = {
                        .type = "hideep_ts",
                        .addr = 0x6c,
                        .dev_name = "hideep_ts",
                        .swnode = &lenovo_yb1_x90_hideep_ts_node,
                },
                .adapter_path = "\\_SB_.PCI0.I2C6",
                .irq_data = {
                        .type = X86_ACPI_IRQ_TYPE_GPIOINT,
                        .chip = "INT33FF:03",
                        .index = 77,
                        .trigger = ACPI_LEVEL_SENSITIVE,
                        .polarity = ACPI_ACTIVE_LOW,
                        .con_id = "hideep_ts_irq",
                },
        },
};

static const struct platform_device_info lenovo_yb1_x90_pdevs[] __initconst = {
        {
                .name = "yogabook-touch-kbd-digitizer-switch",
                .id = PLATFORM_DEVID_NONE,
        },
};

/*
 * DSDT says UART path is "\\_SB.PCIO.URT1" with a letter 'O' instead of
 * the number '0' add the link manually.
 */
static const struct x86_serdev_info lenovo_yb1_x90_serdevs[] __initconst = {
        {
                .ctrl.acpi.hid = "8086228A",
                .ctrl.acpi.uid = "1",
                .ctrl_devname = "serial0",
                .serdev_hid = "BCM2E1A",
        },
};

/*
 * Software node attached to gpio-keys device representing the LID and
 * serving as a parent to software nodes representing individual keys/buttons
 * as required by the device tree binding.
 */
static const struct software_node lenovo_lid_gpio_keys_node = {
        .name = "lid_sw",
};

static const struct property_entry lenovo_yb1_x90_lid_props[] = {
        PROPERTY_ENTRY_U32("linux,input-type", EV_SW),
        PROPERTY_ENTRY_U32("linux,code", SW_LID),
        PROPERTY_ENTRY_STRING("label", "lid_sw"),
        PROPERTY_ENTRY_GPIO("gpios", &cherryview_gpiochip_nodes[2], 19, GPIO_ACTIVE_LOW),
        PROPERTY_ENTRY_U32("debounce-interval", 50),
        PROPERTY_ENTRY_BOOL("wakeup-source"),
        { }
};

static const struct software_node lenovo_yb1_x90_lid_node = {
        .parent = &lenovo_lid_gpio_keys_node,
        .properties = lenovo_yb1_x90_lid_props,
};

static const struct software_node *lenovo_yb1_x90_lid_swnodes[] = {
        &lenovo_lid_gpio_keys_node,
        &lenovo_yb1_x90_lid_node,
        NULL
};

static int __init lenovo_yb1_x90_init(struct device *dev)
{
        /* Enable the regulators used by the touchscreens */

        /* Vprog3B 3.0V used by the goodix touchscreen in the keyboard half */
        intel_soc_pmic_exec_mipi_pmic_seq_element(0x6e, 0x9b, 0x02, 0xff);

        /* Vprog4D 3.0V used by the HiDeep touchscreen in the display half */
        intel_soc_pmic_exec_mipi_pmic_seq_element(0x6e, 0x9f, 0x02, 0xff);

        /* Vprog5A 1.8V used by the HiDeep touchscreen in the display half */
        intel_soc_pmic_exec_mipi_pmic_seq_element(0x6e, 0xa0, 0x02, 0xff);

        /* Vprog5B 1.8V used by the goodix touchscreen in the keyboard half */
        intel_soc_pmic_exec_mipi_pmic_seq_element(0x6e, 0xa1, 0x02, 0xff);

        return 0;
}

const struct x86_dev_info lenovo_yogabook_x90_info __initconst = {
        .i2c_client_info = lenovo_yb1_x90_i2c_clients,
        .i2c_client_count = ARRAY_SIZE(lenovo_yb1_x90_i2c_clients),
        .pdev_info = lenovo_yb1_x90_pdevs,
        .pdev_count = ARRAY_SIZE(lenovo_yb1_x90_pdevs),
        .serdev_info = lenovo_yb1_x90_serdevs,
        .serdev_count = ARRAY_SIZE(lenovo_yb1_x90_serdevs),
        .gpio_button_swnodes = lenovo_yb1_x90_lid_swnodes,
        .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW,
        .init = lenovo_yb1_x90_init,
};

/* Lenovo Yoga Book X91F/L Windows tablet needs manual instantiation of the fuel-gauge client */
static const struct x86_i2c_client_info lenovo_yogabook_x91_i2c_clients[] __initconst = {
        {
                /* BQ27542 fuel-gauge */
                .board_info = {
                        .type = "bq27542",
                        .addr = 0x55,
                        .dev_name = "bq27542",
                        .swnode = &fg_bq25890_supply_node,
                },
                .adapter_path = "\\_SB_.PCI0.I2C1",
        },
};

const struct x86_dev_info lenovo_yogabook_x91_info __initconst = {
        .i2c_client_info = lenovo_yogabook_x91_i2c_clients,
        .i2c_client_count = ARRAY_SIZE(lenovo_yogabook_x91_i2c_clients),
};

/* Lenovo Yoga Tablet 2 1050F/L's Android factory image has everything hardcoded */
static const struct property_entry lenovo_yoga_tab2_830_1050_bq24190_props[] = {
        PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", tusb1211_chg_det_psy, 1),
        PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node),
        PROPERTY_ENTRY_BOOL("omit-battery-class"),
        PROPERTY_ENTRY_BOOL("disable-reset"),
        { }
};

static const struct software_node lenovo_yoga_tab2_830_1050_bq24190_node = {
        .properties = lenovo_yoga_tab2_830_1050_bq24190_props,
};

static const struct property_entry lenovo_yoga_tab2_830_1050_lid_props[] = {
        PROPERTY_ENTRY_U32("linux,input-type", EV_SW),
        PROPERTY_ENTRY_U32("linux,code", SW_LID),
        PROPERTY_ENTRY_STRING("label", "lid_sw"),
        PROPERTY_ENTRY_GPIO("gpios", &baytrail_gpiochip_nodes[2], 26, GPIO_ACTIVE_LOW),
        PROPERTY_ENTRY_U32("debounce-interval", 50),
        PROPERTY_ENTRY_BOOL("wakeup-source"),
        { }
};

static const struct software_node lenovo_yoga_tab2_830_1050_lid_node = {
        .parent = &lenovo_lid_gpio_keys_node,
        .properties = lenovo_yoga_tab2_830_1050_lid_props,
};

static const struct software_node *lenovo_yoga_tab2_830_1050_lid_swnodes[] = {
        &lenovo_lid_gpio_keys_node,
        &lenovo_yoga_tab2_830_1050_lid_node,
        NULL
};

/* This gets filled by lenovo_yoga_tab2_830_1050_init() */
static struct rmi_device_platform_data lenovo_yoga_tab2_830_1050_rmi_pdata = { };

static struct x86_i2c_client_info lenovo_yoga_tab2_830_1050_i2c_clients[] __initdata = {
        {
                /*
                 * This must be the first entry because lenovo_yoga_tab2_830_1050_init()
                 * may update its swnode. LSM303DA accelerometer + magnetometer.
                 */
                .board_info = {
                        .type = "lsm303d",
                        .addr = 0x1d,
                        .dev_name = "lsm303d",
                },
                .adapter_path = "\\_SB_.I2C5",
        }, {
                /* AL3320A ambient light sensor */
                .board_info = {
                        .type = "al3320a",
                        .addr = 0x1c,
                        .dev_name = "al3320a",
                },
                .adapter_path = "\\_SB_.I2C5",
        }, {
                /* bq24292i battery charger */
                .board_info = {
                        .type = "bq24190",
                        .addr = 0x6b,
                        .dev_name = "bq24292i",
                        .swnode = &lenovo_yoga_tab2_830_1050_bq24190_node,
                        .platform_data = &bq24190_pdata,
                },
                .adapter_path = "\\_SB_.I2C1",
                .irq_data = {
                        .type = X86_ACPI_IRQ_TYPE_GPIOINT,
                        .chip = "INT33FC:02",
                        .index = 2,
                        .trigger = ACPI_EDGE_SENSITIVE,
                        .polarity = ACPI_ACTIVE_HIGH,
                        .con_id = "bq24292i_irq",
                },
        }, {
                /* BQ27541 fuel-gauge */
                .board_info = {
                        .type = "bq27541",
                        .addr = 0x55,
                        .dev_name = "bq27541",
                        .swnode = &fg_bq24190_supply_node,
                },
                .adapter_path = "\\_SB_.I2C1",
        }, {
                /* Synaptics RMI touchscreen */
                .board_info = {
                        .type = "rmi4_i2c",
                        .addr = 0x38,
                        .dev_name = "rmi4_i2c",
                        .platform_data = &lenovo_yoga_tab2_830_1050_rmi_pdata,
                },
                .adapter_path = "\\_SB_.I2C6",
                .irq_data = {
                        .type = X86_ACPI_IRQ_TYPE_APIC,
                        .index = 0x45,
                        .trigger = ACPI_EDGE_SENSITIVE,
                        .polarity = ACPI_ACTIVE_HIGH,
                },
        }, {
                /* LP8557 Backlight controller */
                .board_info = {
                        .type = "lp8557",
                        .addr = 0x2c,
                        .dev_name = "lp8557",
                        .platform_data = &lenovo_lp8557_pwm_and_reg_pdata,
                },
                .adapter_path = "\\_SB_.I2C3",
        },
};

static const struct property_entry lenovo_yoga_tab2_830_1050_int3496_props[] __initconst = {
        PROPERTY_ENTRY_GPIO("mux-gpios", &baytrail_gpiochip_nodes[2], 1, GPIO_ACTIVE_LOW),
        PROPERTY_ENTRY_GPIO("id-gpios", &baytrail_gpiochip_nodes[2], 24, GPIO_ACTIVE_HIGH),
        { }
};

static const struct platform_device_info lenovo_yoga_tab2_830_1050_pdevs[] __initconst = {
        {
                /* For micro USB ID pin handling */
                .name = "intel-int3496",
                .id = PLATFORM_DEVID_NONE,
                .properties = lenovo_yoga_tab2_830_1050_int3496_props,
        },
};

#define LENOVO_YOGA_TAB2_830_1050_CODEC_NAME "spi-10WM5102:00"

static const struct property_entry lenovo_yoga_tab2_830_1050_wm1502_props[] = {
        PROPERTY_ENTRY_GPIO("reset-gpios",
                            &crystalcove_gpiochip_node, 3, GPIO_ACTIVE_HIGH),
        PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios",
                            &baytrail_gpiochip_nodes[1], 23, GPIO_ACTIVE_HIGH),
        PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios",
                            &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH),
        PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios",
                            &arizona_gpiochip_node, 4, GPIO_ACTIVE_LOW),
        { }
};

static const struct software_node lenovo_yoga_tab2_830_1050_wm5102 = {
        .properties = lenovo_yoga_tab2_830_1050_wm1502_props,
};

static const struct software_node *lenovo_yoga_tab2_830_1050_swnodes[] = {
        &crystalcove_gpiochip_node,
        &arizona_gpiochip_node,
        &lenovo_yoga_tab2_830_1050_wm5102,
        &generic_lipo_hv_4v35_battery_node,
        NULL
};

static int __init lenovo_yoga_tab2_830_1050_init(struct device *dev);
static void lenovo_yoga_tab2_830_1050_exit(void);

static const char * const lenovo_yoga_tab2_modules[] __initconst = {
        "spi_pxa2xx_platform",  /* For the SPI codec device */
        "bq24190_charger",      /* For the Vbus regulator for int3496/lc824206xa */
        NULL
};

const struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initconst = {
        .i2c_client_info = lenovo_yoga_tab2_830_1050_i2c_clients,
        .i2c_client_count = ARRAY_SIZE(lenovo_yoga_tab2_830_1050_i2c_clients),
        .pdev_info = lenovo_yoga_tab2_830_1050_pdevs,
        .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_830_1050_pdevs),
        .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes,
        .swnode_group = lenovo_yoga_tab2_830_1050_swnodes,
        .modules = lenovo_yoga_tab2_modules,
        .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
        .init = lenovo_yoga_tab2_830_1050_init,
        .exit = lenovo_yoga_tab2_830_1050_exit,
};

/*
 * The Lenovo Yoga Tablet 2 830 and 1050 (8" vs 10") versions use the same
 * mainboard, but the 830 uses a portrait LCD panel with a landscape touchscreen,
 * requiring the touchscreen driver to adjust the touch-coords to match the LCD.
 * And requiring the accelerometer to have a mount-matrix set to correct for
 * the 90° rotation of the LCD vs the frame.
 */
static const char * const lenovo_yoga_tab2_830_lms303d_mount_matrix[] = {
        "0", "1", "0",
        "-1", "0", "0",
        "0", "0", "1"
};

static const struct property_entry lenovo_yoga_tab2_830_lms303d_props[] = {
        PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", lenovo_yoga_tab2_830_lms303d_mount_matrix),
        { }
};

static const struct software_node lenovo_yoga_tab2_830_lms303d_node = {
        .properties = lenovo_yoga_tab2_830_lms303d_props,
};

static int __init lenovo_yoga_tab2_830_1050_init_touchscreen(void)
{
        struct gpio_desc *gpiod;
        int ret;

        /* Use PMIC GPIO 10 bootstrap pin to differentiate 830 vs 1050 */
        ret = x86_android_tablet_get_gpiod("gpio_crystalcove", 10, "yoga_bootstrap",
                                           false, GPIOD_ASIS, &gpiod);
        if (ret)
                return ret;

        ret = gpiod_get_value_cansleep(gpiod);
        if (ret) {
                pr_info("detected Lenovo Yoga Tablet 2 1050F/L\n");
        } else {
                pr_info("detected Lenovo Yoga Tablet 2 830F/L\n");
                lenovo_yoga_tab2_830_1050_rmi_pdata.sensor_pdata.axis_align.swap_axes = true;
                lenovo_yoga_tab2_830_1050_rmi_pdata.sensor_pdata.axis_align.flip_y = true;
                lenovo_yoga_tab2_830_1050_i2c_clients[0].board_info.swnode =
                        &lenovo_yoga_tab2_830_lms303d_node;
        }

        return 0;
}

/* SUS (INT33FC:02) pin 6 needs to be configured as pmu_clk for the audio codec */
static const struct pinctrl_map lenovo_yoga_tab2_830_1050_codec_pinctrl_map =
        PIN_MAP_MUX_GROUP(LENOVO_YOGA_TAB2_830_1050_CODEC_NAME, "codec_32khz_clk",
                          "INT33FC:02", "pmu_clk2_grp", "pmu_clk");

static struct device *lenovo_yoga_tab2_830_1050_codec_dev;
static struct pinctrl *lenovo_yoga_tab2_830_1050_codec_pinctrl;
static struct sys_off_handler *lenovo_yoga_tab2_830_1050_sys_off_handler;

static int __init lenovo_yoga_tab2_830_1050_init_codec(void)
{
        struct device *codec_dev;
        struct pinctrl *pinctrl;
        int ret;

        codec_dev = bus_find_device_by_name(&spi_bus_type, NULL,
                                            LENOVO_YOGA_TAB2_830_1050_CODEC_NAME);
        if (!codec_dev) {
                pr_err("error cannot find %s device\n", LENOVO_YOGA_TAB2_830_1050_CODEC_NAME);
                return -ENODEV;
        }

        ret = pinctrl_register_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map, 1);
        if (ret)
                goto err_put_device;

        pinctrl = pinctrl_get_select(codec_dev, "codec_32khz_clk");
        if (IS_ERR(pinctrl)) {
                ret = dev_err_probe(codec_dev, PTR_ERR(pinctrl), "selecting codec_32khz_clk\n");
                goto err_unregister_mappings;
        }

        ret = device_add_software_node(codec_dev, &lenovo_yoga_tab2_830_1050_wm5102);
        if (ret) {
                dev_err_probe(codec_dev, ret, "adding software node\n");
                goto err_put_pinctrl;
        }

        lenovo_yoga_tab2_830_1050_codec_dev = codec_dev;
        lenovo_yoga_tab2_830_1050_codec_pinctrl = pinctrl;
        return 0;

err_put_pinctrl:
        pinctrl_put(lenovo_yoga_tab2_830_1050_codec_pinctrl);
err_unregister_mappings:
        pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map);
err_put_device:
        put_device(codec_dev);
        return ret;
}

/*
 * These tablet's DSDT does not set acpi_gbl_reduced_hardware, so acpi_power_off()
 * gets used as pm_power_off handler. This causes "poweroff" on these tablets
 * to hang hard. Requiring pressing the power button for 30 seconds *twice*
 * followed by a normal 3 second press to recover. Avoid this by doing an EFI
 * poweroff instead.
 */
static int lenovo_yoga_tab2_830_1050_power_off(struct sys_off_data *data)
{
        efi.reset_system(EFI_RESET_SHUTDOWN, EFI_SUCCESS, 0, NULL);

        return NOTIFY_DONE;
}

static int __init lenovo_yoga_tab2_830_1050_init(struct device *dev)
{
        int ret;

        ret = lenovo_yoga_tab2_830_1050_init_touchscreen();
        if (ret)
                return ret;

        ret = lenovo_yoga_tab2_830_1050_init_codec();
        if (ret)
                return ret;

        /* SYS_OFF_PRIO_FIRMWARE + 1 so that it runs before acpi_power_off() */
        lenovo_yoga_tab2_830_1050_sys_off_handler =
                register_sys_off_handler(SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_FIRMWARE + 1,
                                         lenovo_yoga_tab2_830_1050_power_off, NULL);
        if (IS_ERR(lenovo_yoga_tab2_830_1050_sys_off_handler))
                return PTR_ERR(lenovo_yoga_tab2_830_1050_sys_off_handler);

        return 0;
}

static void lenovo_yoga_tab2_830_1050_exit(void)
{
        unregister_sys_off_handler(lenovo_yoga_tab2_830_1050_sys_off_handler);

        device_remove_software_node(lenovo_yoga_tab2_830_1050_codec_dev);
        pinctrl_put(lenovo_yoga_tab2_830_1050_codec_pinctrl);
        pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map);
        put_device(lenovo_yoga_tab2_830_1050_codec_dev);
}

/*
 * Lenovo Yoga Tablet 2 Pro 1380F/L
 *
 * The Lenovo Yoga Tablet 2 Pro 1380F/L mostly has the same design as the 830F/L
 * and the 1050F/L so this re-uses some of the handling for that from above.
 */
static const char * const lc824206xa_chg_det_psy[] = { "lc824206xa-charger-detect" };

static const struct property_entry lenovo_yoga_tab2_1380_bq24190_props[] = {
        PROPERTY_ENTRY_STRING_ARRAY("supplied-from", lc824206xa_chg_det_psy),
        PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node),
        PROPERTY_ENTRY_BOOL("omit-battery-class"),
        PROPERTY_ENTRY_BOOL("disable-reset"),
        { }
};

static const struct software_node lenovo_yoga_tab2_1380_bq24190_node = {
        .properties = lenovo_yoga_tab2_1380_bq24190_props,
};

/* For enabling the bq24190 5V boost based on id-pin */
static struct regulator_consumer_supply lc824206xa_consumer = {
        .supply = "vbus",
        .dev_name = "i2c-lc824206xa",
};

static const struct regulator_init_data lenovo_yoga_tab2_1380_bq24190_vbus_init_data = {
        .constraints = {
                .name = "bq24190_vbus",
                .valid_ops_mask = REGULATOR_CHANGE_STATUS,
        },
        .consumer_supplies = &lc824206xa_consumer,
        .num_consumer_supplies = 1,
};

static struct bq24190_platform_data lenovo_yoga_tab2_1380_bq24190_pdata = {
        .regulator_init_data = &lenovo_yoga_tab2_1380_bq24190_vbus_init_data,
};

static const struct property_entry lenovo_yoga_tab2_1380_lc824206xa_props[] = {
        PROPERTY_ENTRY_BOOL("onnn,enable-miclr-for-dcp"),
        { }
};

static const struct software_node lenovo_yoga_tab2_1380_lc824206xa_node = {
        .properties = lenovo_yoga_tab2_1380_lc824206xa_props,
};

static const char * const lenovo_yoga_tab2_1380_lms303d_mount_matrix[] = {
        "0", "-1", "0",
        "-1", "0", "0",
        "0", "0", "1"
};

static const struct property_entry lenovo_yoga_tab2_1380_lms303d_props[] = {
        PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", lenovo_yoga_tab2_1380_lms303d_mount_matrix),
        { }
};

static const struct software_node lenovo_yoga_tab2_1380_lms303d_node = {
        .properties = lenovo_yoga_tab2_1380_lms303d_props,
};

static const struct x86_i2c_client_info lenovo_yoga_tab2_1380_i2c_clients[] __initconst = {
        {
                /* BQ27541 fuel-gauge */
                .board_info = {
                        .type = "bq27541",
                        .addr = 0x55,
                        .dev_name = "bq27541",
                        .swnode = &fg_bq24190_supply_node,
                },
                .adapter_path = "\\_SB_.I2C1",
        }, {
                /* bq24292i battery charger */
                .board_info = {
                        .type = "bq24190",
                        .addr = 0x6b,
                        .dev_name = "bq24292i",
                        .swnode = &lenovo_yoga_tab2_1380_bq24190_node,
                        .platform_data = &lenovo_yoga_tab2_1380_bq24190_pdata,
                },
                .adapter_path = "\\_SB_.I2C1",
                .irq_data = {
                        .type = X86_ACPI_IRQ_TYPE_GPIOINT,
                        .chip = "INT33FC:02",
                        .index = 2,
                        .trigger = ACPI_EDGE_SENSITIVE,
                        .polarity = ACPI_ACTIVE_HIGH,
                        .con_id = "bq24292i_irq",
                },
        }, {
                /* LP8557 Backlight controller */
                .board_info = {
                        .type = "lp8557",
                        .addr = 0x2c,
                        .dev_name = "lp8557",
                        .platform_data = &lenovo_lp8557_pwm_and_reg_pdata,
                },
                .adapter_path = "\\_SB_.I2C3",
        }, {
                /* LC824206XA Micro USB Switch */
                .board_info = {
                        .type = "lc824206xa",
                        .addr = 0x48,
                        .dev_name = "lc824206xa",
                        .swnode = &lenovo_yoga_tab2_1380_lc824206xa_node,
                },
                .adapter_path = "\\_SB_.I2C3",
                .irq_data = {
                        .type = X86_ACPI_IRQ_TYPE_GPIOINT,
                        .chip = "INT33FC:02",
                        .index = 1,
                        .trigger = ACPI_LEVEL_SENSITIVE,
                        .polarity = ACPI_ACTIVE_LOW,
                        .con_id = "lc824206xa_irq",
                },
        }, {
                /* AL3320A ambient light sensor */
                .board_info = {
                        .type = "al3320a",
                        .addr = 0x1c,
                        .dev_name = "al3320a",
                },
                .adapter_path = "\\_SB_.I2C5",
        }, {
                /* LSM303DA accelerometer + magnetometer */
                .board_info = {
                        .type = "lsm303d",
                        .addr = 0x1d,
                        .dev_name = "lsm303d",
                        .swnode = &lenovo_yoga_tab2_1380_lms303d_node,
                },
                .adapter_path = "\\_SB_.I2C5",
        }, {
                /* Synaptics RMI touchscreen */
                .board_info = {
                        .type = "rmi4_i2c",
                        .addr = 0x38,
                        .dev_name = "rmi4_i2c",
                        .platform_data = &lenovo_yoga_tab2_830_1050_rmi_pdata,
                },
                .adapter_path = "\\_SB_.I2C6",
                .irq_data = {
                        .type = X86_ACPI_IRQ_TYPE_APIC,
                        .index = 0x45,
                        .trigger = ACPI_EDGE_SENSITIVE,
                        .polarity = ACPI_ACTIVE_HIGH,
                },
        }
};

static const struct property_entry lenovo_yoga_tab2_1380_fc_props[] __initconst = {
        PROPERTY_ENTRY_GPIO("uart3_txd-gpios", &baytrail_gpiochip_nodes[0], 57, GPIO_ACTIVE_HIGH),
        PROPERTY_ENTRY_GPIO("uart3_rxd-gpios", &baytrail_gpiochip_nodes[0], 61, GPIO_ACTIVE_HIGH),
        { }
};

static const struct platform_device_info lenovo_yoga_tab2_1380_pdevs[] __initconst = {
        {
                /* For the Tablet 2 Pro 1380's custom fast charging driver */
                .name = "lenovo-yoga-tab2-pro-1380-fastcharger",
                .id = PLATFORM_DEVID_NONE,
                .properties = lenovo_yoga_tab2_1380_fc_props,
        },
};

static int __init lenovo_yoga_tab2_1380_init(struct device *dev)
{
        int ret;

        /* To verify that the DMI matching works vs the 830 / 1050 models */
        pr_info("detected Lenovo Yoga Tablet 2 Pro 1380F/L\n");

        ret = lenovo_yoga_tab2_830_1050_init_codec();
        if (ret)
                return ret;

        /* SYS_OFF_PRIO_FIRMWARE + 1 so that it runs before acpi_power_off() */
        lenovo_yoga_tab2_830_1050_sys_off_handler =
                register_sys_off_handler(SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_FIRMWARE + 1,
                                         lenovo_yoga_tab2_830_1050_power_off, NULL);
        if (IS_ERR(lenovo_yoga_tab2_830_1050_sys_off_handler))
                return PTR_ERR(lenovo_yoga_tab2_830_1050_sys_off_handler);

        return 0;
}

const struct x86_dev_info lenovo_yoga_tab2_1380_info __initconst = {
        .i2c_client_info = lenovo_yoga_tab2_1380_i2c_clients,
        .i2c_client_count = ARRAY_SIZE(lenovo_yoga_tab2_1380_i2c_clients),
        .pdev_info = lenovo_yoga_tab2_1380_pdevs,
        .pdev_count = ARRAY_SIZE(lenovo_yoga_tab2_1380_pdevs),
        .gpio_button_swnodes = lenovo_yoga_tab2_830_1050_lid_swnodes,
        .swnode_group = lenovo_yoga_tab2_830_1050_swnodes,
        .modules = lenovo_yoga_tab2_modules,
        .gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
        .init = lenovo_yoga_tab2_1380_init,
        .exit = lenovo_yoga_tab2_830_1050_exit,
};

/* Lenovo Yoga Tab 3 Pro YT3-X90F */

/*
 * There are 2 batteries, with 2 bq27500 fuel-gauges and 2 bq25892 chargers,
 * "bq25890-charger-1" is instantiated from: drivers/i2c/busses/i2c-cht-wc.c.
 */
static const char * const lenovo_yt3_bq25892_0_suppliers[] = { "cht_wcove_pwrsrc" };
static const char * const bq25890_1_psy[] = { "bq25890-charger-1" };

static const struct property_entry fg_bq25890_1_supply_props[] = {
        PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq25890_1_psy),
        { }
};

static const struct software_node fg_bq25890_1_supply_node = {
        .properties = fg_bq25890_1_supply_props,
};

/* bq25892 charger settings for the flat LiPo battery behind the screen */
static const struct property_entry lenovo_yt3_bq25892_0_props[] = {
        PROPERTY_ENTRY_STRING_ARRAY("supplied-from", lenovo_yt3_bq25892_0_suppliers),
        PROPERTY_ENTRY_U32("linux,iinlim-percentage", 40),
        PROPERTY_ENTRY_BOOL("linux,skip-reset"),
        /* Values taken from Android Factory Image */
        PROPERTY_ENTRY_U32("ti,charge-current", 2048000),
        PROPERTY_ENTRY_U32("ti,battery-regulation-voltage", 4352000),
        PROPERTY_ENTRY_U32("ti,termination-current", 128000),
        PROPERTY_ENTRY_U32("ti,precharge-current", 128000),
        PROPERTY_ENTRY_U32("ti,minimum-sys-voltage", 3700000),
        PROPERTY_ENTRY_U32("ti,boost-voltage", 4998000),
        PROPERTY_ENTRY_U32("ti,boost-max-current", 500000),
        PROPERTY_ENTRY_BOOL("ti,use-ilim-pin"),
        { }
};

static const struct software_node lenovo_yt3_bq25892_0_node = {
        .properties = lenovo_yt3_bq25892_0_props,
};

static const struct property_entry lenovo_yt3_hideep_ts_props[] = {
        PROPERTY_ENTRY_U32("touchscreen-size-x", 1600),
        PROPERTY_ENTRY_U32("touchscreen-size-y", 2560),
        PROPERTY_ENTRY_U32("touchscreen-max-pressure", 255),
        PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 7, GPIO_ACTIVE_LOW),
        { }
};

static const struct software_node lenovo_yt3_hideep_ts_node = {
        .properties = lenovo_yt3_hideep_ts_props,
};

static const struct x86_i2c_client_info lenovo_yt3_i2c_clients[] __initconst = {
        {
                /* bq27500 fuel-gauge for the flat LiPo battery behind the screen */
                .board_info = {
                        .type = "bq27500",
                        .addr = 0x55,
                        .dev_name = "bq27500_0",
                        .swnode = &fg_bq25890_supply_node,
                },
                .adapter_path = "\\_SB_.PCI0.I2C1",
        }, {
                /* bq25892 charger for the flat LiPo battery behind the screen */
                .board_info = {
                        .type = "bq25892",
                        .addr = 0x6b,
                        .dev_name = "bq25892_0",
                        .swnode = &lenovo_yt3_bq25892_0_node,
                },
                .adapter_path = "\\_SB_.PCI0.I2C1",
                .irq_data = {
                        .type = X86_ACPI_IRQ_TYPE_GPIOINT,
                        .chip = "INT33FF:01",
                        .index = 5,
                        .trigger = ACPI_EDGE_SENSITIVE,
                        .polarity = ACPI_ACTIVE_LOW,
                        .con_id = "bq25892_0_irq",
                },
        }, {
                /* bq27500 fuel-gauge for the round Li-ion cells in the hinge */
                .board_info = {
                        .type = "bq27500",
                        .addr = 0x55,
                        .dev_name = "bq27500_1",
                        .swnode = &fg_bq25890_1_supply_node,
                },
                .adapter_path = "\\_SB_.PCI0.I2C2",
        }, {
                /* HiDeep IST520E Touchscreen */
                .board_info = {
                        .type = "hideep_ts",
                        .addr = 0x6c,
                        .dev_name = "hideep_ts",
                        .swnode = &lenovo_yt3_hideep_ts_node,
                },
                .adapter_path = "\\_SB_.PCI0.I2C6",
                .irq_data = {
                        .type = X86_ACPI_IRQ_TYPE_GPIOINT,
                        .chip = "INT33FF:03",
                        .index = 77,
                        .trigger = ACPI_LEVEL_SENSITIVE,
                        .polarity = ACPI_ACTIVE_LOW,
                        .con_id = "hideep_ts_irq",
                },
        }, {
                /* LP8557 Backlight controller */
                .board_info = {
                        .type = "lp8557",
                        .addr = 0x2c,
                        .dev_name = "lp8557",
                        .platform_data = &lenovo_lp8557_reg_only_pdata,
                },
                .adapter_path = "\\_SB_.PCI0.I2C1",
        }
};

/*
 * The AOSP 3.5 mm Headset: Accessory Specification gives the following values:
 * Function A Play/Pause:           0 ohm
 * Function D Voice assistant:    135 ohm
 * Function B Volume Up           240 ohm
 * Function C Volume Down         470 ohm
 * Minimum Mic DC resistance     1000 ohm
 * Minimum Ear speaker impedance   16 ohm
 * Note the first max value below must be less then the min. speaker impedance,
 * to allow CTIA/OMTP detection to work. The other max values are the closest
 * value from extcon-arizona.c:arizona_micd_levels halfway 2 button resistances.
 */
static const struct arizona_micd_range arizona_micd_aosp_ranges[] = {
        { .max =  11, .key = KEY_PLAYPAUSE },
        { .max = 186, .key = KEY_VOICECOMMAND },
        { .max = 348, .key = KEY_VOLUMEUP },
        { .max = 752, .key = KEY_VOLUMEDOWN },
};

/* YT3 WM5102 arizona_micd_config comes from Android kernel sources */
static struct arizona_micd_config lenovo_yt3_wm5102_micd_config[] = {
        { 0, 1, 0 },
        { ARIZONA_ACCDET_SRC, 2, 1 },
};

static struct arizona_pdata lenovo_yt3_wm5102_pdata = {
        .irq_flags = IRQF_TRIGGER_LOW,
        .micd_detect_debounce = 200,
        .micd_ranges = arizona_micd_aosp_ranges,
        .num_micd_ranges = ARRAY_SIZE(arizona_micd_aosp_ranges),
        .hpdet_channel = ARIZONA_ACCDET_MODE_HPL,

        /* Below settings come from Android kernel sources */
        .micd_bias_start_time = 1,
        .micd_rate = 6,
        .micd_configs = lenovo_yt3_wm5102_micd_config,
        .num_micd_configs = ARRAY_SIZE(lenovo_yt3_wm5102_micd_config),
        .micbias = {
                [0] = { /* MICBIAS1 */
                        .mV = 2800,
                        .ext_cap = 1,
                        .discharge = 1,
                        .soft_start = 0,
                        .bypass = 0,
                },
                [1] = { /* MICBIAS2 */
                        .mV = 2800,
                        .ext_cap = 1,
                        .discharge = 1,
                        .soft_start = 0,
                        .bypass = 0,
                },
                [2] = { /* MICBIAS2 */
                        .mV = 2800,
                        .ext_cap = 1,
                        .discharge = 1,
                        .soft_start = 0,
                        .bypass = 0,
                },
        },
};

static const struct property_entry lenovo_yt3_wm1502_props[] = {
        PROPERTY_ENTRY_GPIO("wlf,spkvdd-ena-gpios",
                            &cherryview_gpiochip_nodes[0], 75, GPIO_ACTIVE_HIGH),
        PROPERTY_ENTRY_GPIO("wlf,ldoena-gpios",
                            &cherryview_gpiochip_nodes[0], 81, GPIO_ACTIVE_HIGH),
        PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[0], 82, GPIO_ACTIVE_HIGH),
        PROPERTY_ENTRY_GPIO("wlf,micd-pol-gpios", &arizona_gpiochip_node, 2, GPIO_ACTIVE_HIGH),
        { }
};

static const struct software_node lenovo_yt3_wm5102 = {
        .properties = lenovo_yt3_wm1502_props,
        .name = "wm5102",
};

static const struct software_node *lenovo_yt3_swnodes[] = {
        &arizona_gpiochip_node,
        &lenovo_yt3_wm5102,
        NULL
};

static const struct x86_spi_dev_info lenovo_yt3_spi_devs[] __initconst = {
        {
                /* WM5102 codec */
                .board_info = {
                        .modalias = "wm5102",
                        .platform_data = &lenovo_yt3_wm5102_pdata,
                        .swnode = &lenovo_yt3_wm5102,
                        .max_speed_hz = 5000000,
                },
                .ctrl_path = "\\_SB_.PCI0.SPI1",
                .irq_data = {
                        .type = X86_ACPI_IRQ_TYPE_GPIOINT,
                        .chip = "INT33FF:00",
                        .index = 91,
                        .trigger = ACPI_LEVEL_SENSITIVE,
                        .polarity = ACPI_ACTIVE_LOW,
                        .con_id = "wm5102_irq",
                },
        }
};

static int __init lenovo_yt3_init(struct device *dev)
{
        int ret;

        /*
         * The "bq25892_0" charger IC has its /CE (Charge-Enable) and OTG pins
         * connected to GPIOs, rather then having them hardwired to the correct
         * values as is normally done.
         *
         * The bq25890_charger driver controls these through I2C, but this only
         * works if not overridden by the pins. Set these pins here:
         * 1. Set /CE to 1 to allow charging.
         * 2. Set OTG to 0 disable V5 boost output since the 5V boost output of
         *    the main "bq25892_1" charger is used when necessary.
         */

        /* /CE pin */
        ret = x86_android_tablet_get_gpiod("INT33FF:02", 22, "bq25892_0_ce",
                                           true, GPIOD_OUT_HIGH, NULL);
        if (ret < 0)
                return ret;

        /* OTG pin */
        ret = x86_android_tablet_get_gpiod("INT33FF:03", 19, "bq25892_0_otg",
                                           false, GPIOD_OUT_LOW, NULL);
        if (ret < 0)
                return ret;

        /* Enable the regulators used by the touchscreen */
        intel_soc_pmic_exec_mipi_pmic_seq_element(0x6e, 0x9b, 0x02, 0xff);
        intel_soc_pmic_exec_mipi_pmic_seq_element(0x6e, 0xa0, 0x02, 0xff);

        return 0;
}

static const char * const lenovo_yt3_modules[] __initconst = {
        "spi_pxa2xx_platform",  /* For the SPI codec device */
        NULL
};

const struct x86_dev_info lenovo_yt3_info __initconst = {
        .i2c_client_info = lenovo_yt3_i2c_clients,
        .i2c_client_count = ARRAY_SIZE(lenovo_yt3_i2c_clients),
        .spi_dev_info = lenovo_yt3_spi_devs,
        .spi_dev_count = ARRAY_SIZE(lenovo_yt3_spi_devs),
        .swnode_group = lenovo_yt3_swnodes,
        .modules = lenovo_yt3_modules,
        .gpiochip_type = X86_GPIOCHIP_CHERRYVIEW,
        .init = lenovo_yt3_init,
};