Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,26 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# X86 Android tablet support Kconfig
#
config X86_ANDROID_TABLETS
tristate "X86 Android tablet support"
depends on I2C && SPI && SERIAL_DEV_BUS
depends on GPIOLIB && PMIC_OPREGION
depends on ACPI && EFI && PCI
select NEW_LEDS
select LEDS_CLASS
select POWER_SUPPLY
help
X86 tablets which ship with Android as (part of) the factory image
typically have various problems with their DSDTs. The factory kernels
shipped on these devices typically have device addresses and GPIOs
hardcoded in the kernel, rather than specified in their DSDT.
With the DSDT containing a random collection of devices which may or
may not actually be present. This driver contains various fixes for
such tablets, including instantiating kernel devices for devices which
are missing from the DSDT.
If you have a x86 Android tablet say Y or M here, for a generic x86
distro configuration say M here.
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# X86 Android tablet support Makefile
#
obj-$(CONFIG_X86_ANDROID_TABLETS) += vexia_atla10_ec.o
obj-$(CONFIG_X86_ANDROID_TABLETS) += x86-android-tablets.o
x86-android-tablets-y := core.o dmi.o shared-psy-info.o \
acer.o asus.o lenovo.o other.o
@@ -0,0 +1,247 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Board info for Acer 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-2025 Hans de Goede <hansg@kernel.org>
*/
#include <linux/gpio/machine.h>
#include <linux/gpio/property.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include "shared-psy-info.h"
#include "x86-android-tablets.h"
/* Acer Iconia One 8 A1-840 (non FHD version) */
static const struct property_entry acer_a1_840_bq24190_props[] = {
PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_4v2_battery_node),
PROPERTY_ENTRY_BOOL("omit-battery-class"),
PROPERTY_ENTRY_BOOL("disable-reset"),
{ }
};
static const struct software_node acer_a1_840_bq24190_node = {
.properties = acer_a1_840_bq24190_props,
};
static const struct property_entry acer_a1_840_touchscreen_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 800),
PROPERTY_ENTRY_U32("touchscreen-size-y", 1280),
PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_LOW),
{ }
};
static const struct software_node acer_a1_840_touchscreen_node = {
.properties = acer_a1_840_touchscreen_props,
};
static const struct x86_i2c_client_info acer_a1_840_i2c_clients[] __initconst = {
{
/* BQ24297 charger IC */
.board_info = {
.type = "bq24297",
.addr = 0x6b,
.dev_name = "bq24297",
.swnode = &acer_a1_840_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_LOW,
.con_id = "bq24297_irq",
},
}, {
/* MPU6515 sensors */
.board_info = {
.type = "mpu6515",
.addr = 0x69,
.dev_name = "mpu6515",
},
.adapter_path = "\\_SB_.I2C3",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x47,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
},
}, {
/* FT5416 touchscreen controller */
.board_info = {
.type = "edt-ft5x06",
.addr = 0x38,
.dev_name = "ft5416",
.swnode = &acer_a1_840_touchscreen_node,
},
.adapter_path = "\\_SB_.I2C4",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x45,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
},
}
};
static const struct property_entry acer_a1_840_int3496_props[] __initconst = {
PROPERTY_ENTRY_GPIO("mux-gpios", &baytrail_gpiochip_nodes[2], 1, GPIO_ACTIVE_HIGH),
PROPERTY_ENTRY_GPIO("id-gpios", &baytrail_gpiochip_nodes[2], 18, GPIO_ACTIVE_HIGH),
{ }
};
static const struct platform_device_info acer_a1_840_pdevs[] __initconst = {
{
/* For micro USB ID pin handling */
.name = "intel-int3496",
.id = PLATFORM_DEVID_NONE,
.properties = acer_a1_840_int3496_props,
},
};
/* Properties for the Dollar Cove TI PMIC battery MFD child used as fuel-gauge */
static const struct property_entry acer_a1_840_fg_props[] = {
PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_4v2_battery_node),
PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", bq24190_psy, 1),
PROPERTY_ENTRY_GPIO("charged-gpios", &baytrail_gpiochip_nodes[2], 10, GPIO_ACTIVE_HIGH),
{ }
};
static struct device *acer_a1_840_fg_dev;
static struct fwnode_handle *acer_a1_840_fg_node;
static int __init acer_a1_840_init(struct device *dev)
{
int ret;
acer_a1_840_fg_dev = bus_find_device_by_name(&platform_bus_type, NULL, "chtdc_ti_battery");
if (!acer_a1_840_fg_dev)
return dev_err_probe(dev, -EPROBE_DEFER, "getting chtdc_ti_battery dev\n");
acer_a1_840_fg_node = fwnode_create_software_node(acer_a1_840_fg_props, NULL);
if (IS_ERR(acer_a1_840_fg_node)) {
ret = PTR_ERR(acer_a1_840_fg_node);
goto err_put;
}
ret = device_add_software_node(acer_a1_840_fg_dev,
to_software_node(acer_a1_840_fg_node));
if (ret)
goto err_put;
return 0;
err_put:
fwnode_handle_put(acer_a1_840_fg_node);
acer_a1_840_fg_node = NULL;
put_device(acer_a1_840_fg_dev);
acer_a1_840_fg_dev = NULL;
return ret;
}
static void acer_a1_840_exit(void)
{
device_remove_software_node(acer_a1_840_fg_dev);
/*
* Skip fwnode_handle_put(acer_a1_840_fg_node), instead leak the node.
* The intel_dc_ti_battery driver may still reference the strdup-ed
* "supplied-from" string. This string will be free-ed if the node
* is released.
*/
acer_a1_840_fg_node = NULL;
put_device(acer_a1_840_fg_dev);
acer_a1_840_fg_dev = NULL;
}
static const char * const acer_a1_840_modules[] __initconst = {
"bq24190_charger", /* For the Vbus regulator for intel-int3496 */
NULL
};
const struct x86_dev_info acer_a1_840_info __initconst = {
.i2c_client_info = acer_a1_840_i2c_clients,
.i2c_client_count = ARRAY_SIZE(acer_a1_840_i2c_clients),
.pdev_info = acer_a1_840_pdevs,
.pdev_count = ARRAY_SIZE(acer_a1_840_pdevs),
.gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
.swnode_group = generic_lipo_4v2_battery_swnodes,
.modules = acer_a1_840_modules,
.init = acer_a1_840_init,
.exit = acer_a1_840_exit,
};
/* Acer Iconia One 7 B1-750 has an Android factory image with everything hardcoded */
static const char * const acer_b1_750_mount_matrix[] = {
"-1", "0", "0",
"0", "1", "0",
"0", "0", "1"
};
static const struct property_entry acer_b1_750_bma250e_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", acer_b1_750_mount_matrix),
{ }
};
static const struct software_node acer_b1_750_bma250e_node = {
.properties = acer_b1_750_bma250e_props,
};
static const struct property_entry acer_b1_750_novatek_props[] = {
PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_LOW),
{ }
};
static const struct software_node acer_b1_750_novatek_node = {
.properties = acer_b1_750_novatek_props,
};
static const struct x86_i2c_client_info acer_b1_750_i2c_clients[] __initconst = {
{
/* Novatek NVT-ts touchscreen */
.board_info = {
.type = "nt11205-ts",
.addr = 0x34,
.dev_name = "NVT-ts",
.swnode = &acer_b1_750_novatek_node,
},
.adapter_path = "\\_SB_.I2C4",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FC:02",
.index = 3,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
.con_id = "NVT-ts_irq",
},
}, {
/* BMA250E accelerometer */
.board_info = {
.type = "bma250e",
.addr = 0x18,
.swnode = &acer_b1_750_bma250e_node,
},
.adapter_path = "\\_SB_.I2C3",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FC:02",
.index = 25,
.trigger = ACPI_LEVEL_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
.con_id = "bma250e_irq",
},
},
};
const struct x86_dev_info acer_b1_750_info __initconst = {
.i2c_client_info = acer_b1_750_i2c_clients,
.i2c_client_count = ARRAY_SIZE(acer_b1_750_i2c_clients),
.pdev_info = int3496_pdevs,
.pdev_count = 1,
.gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
@@ -0,0 +1,319 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Board info for Asus 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>
*/
#include <linux/gpio/machine.h>
#include <linux/gpio/property.h>
#include <linux/input-event-codes.h>
#include <linux/platform_device.h>
#include "shared-psy-info.h"
#include "x86-android-tablets.h"
/* Asus ME176C and TF103C tablets shared data */
static const struct property_entry asus_me176c_tf103c_int3496_props[] __initconst = {
PROPERTY_ENTRY_GPIO("id-gpios", &baytrail_gpiochip_nodes[2], 22, GPIO_ACTIVE_HIGH),
{ }
};
static const struct platform_device_info asus_me176c_tf103c_pdevs[] __initconst = {
{
/* For micro USB ID pin handling */
.name = "intel-int3496",
.id = PLATFORM_DEVID_NONE,
.properties = asus_me176c_tf103c_int3496_props,
},
};
static const struct software_node asus_me176c_tf103c_gpio_keys_node = {
.name = "lid_sw",
};
static const struct property_entry asus_me176c_tf103c_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], 12, GPIO_ACTIVE_LOW),
PROPERTY_ENTRY_U32("debounce-interval", 50),
PROPERTY_ENTRY_BOOL("wakeup-source"),
{ }
};
static const struct software_node asus_me176c_tf103c_lid_node = {
.parent = &asus_me176c_tf103c_gpio_keys_node,
.properties = asus_me176c_tf103c_lid_props,
};
static const struct software_node *asus_me176c_tf103c_lid_swnodes[] = {
&asus_me176c_tf103c_gpio_keys_node,
&asus_me176c_tf103c_lid_node,
NULL
};
/* Asus ME176C tablets have an Android factory image with everything hardcoded */
static const char * const asus_me176c_accel_mount_matrix[] = {
"-1", "0", "0",
"0", "1", "0",
"0", "0", "1"
};
static const struct property_entry asus_me176c_accel_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", asus_me176c_accel_mount_matrix),
{ }
};
static const struct software_node asus_me176c_accel_node = {
.properties = asus_me176c_accel_props,
};
static const struct property_entry asus_me176c_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_U32("ti,system-minimum-microvolt", 3600000),
PROPERTY_ENTRY_BOOL("omit-battery-class"),
PROPERTY_ENTRY_BOOL("disable-reset"),
{ }
};
static const struct software_node asus_me176c_bq24190_node = {
.properties = asus_me176c_bq24190_props,
};
static const struct property_entry asus_me176c_ug3105_props[] = {
PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", bq24190_psy, 1),
PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node),
PROPERTY_ENTRY_U32("upisemi,rsns-microohm", 10000),
{ }
};
static const struct software_node asus_me176c_ug3105_node = {
.properties = asus_me176c_ug3105_props,
};
static const struct property_entry asus_me176c_touchscreen_props[] = {
PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[0], 60, GPIO_ACTIVE_HIGH),
PROPERTY_ENTRY_GPIO("irq-gpios", &baytrail_gpiochip_nodes[2], 28, GPIO_ACTIVE_HIGH),
{ }
};
static const struct software_node asus_me176c_touchscreen_node = {
.properties = asus_me176c_touchscreen_props,
};
static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = {
{
/* bq24297 battery charger */
.board_info = {
.type = "bq24190",
.addr = 0x6b,
.dev_name = "bq24297",
.swnode = &asus_me176c_bq24190_node,
.platform_data = &bq24190_pdata,
},
.adapter_path = "\\_SB_.I2C1",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_PMIC,
.chip = "\\_SB_.I2C7.PMIC",
.domain = DOMAIN_BUS_WAKEUP,
.index = 0,
},
}, {
/* ug3105 battery monitor */
.board_info = {
.type = "ug3105",
.addr = 0x70,
.dev_name = "ug3105",
.swnode = &asus_me176c_ug3105_node,
},
.adapter_path = "\\_SB_.I2C1",
}, {
/* ak09911 compass */
.board_info = {
.type = "ak09911",
.addr = 0x0c,
.dev_name = "ak09911",
},
.adapter_path = "\\_SB_.I2C5",
}, {
/* kxtj21009 accelerometer */
.board_info = {
.type = "kxtj21009",
.addr = 0x0f,
.dev_name = "kxtj21009",
.swnode = &asus_me176c_accel_node,
},
.adapter_path = "\\_SB_.I2C5",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x44,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
},
}, {
/* goodix touchscreen */
.board_info = {
.type = "GDIX1001:00",
.addr = 0x14,
.dev_name = "goodix_ts",
.swnode = &asus_me176c_touchscreen_node,
},
.adapter_path = "\\_SB_.I2C6",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x45,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
},
},
};
static const struct x86_serdev_info asus_me176c_serdevs[] __initconst = {
{
.ctrl.acpi.hid = "80860F0A",
.ctrl.acpi.uid = "2",
.ctrl_devname = "serial0",
.serdev_hid = "BCM2E3A",
},
};
const struct x86_dev_info asus_me176c_info __initconst = {
.i2c_client_info = asus_me176c_i2c_clients,
.i2c_client_count = ARRAY_SIZE(asus_me176c_i2c_clients),
.pdev_info = asus_me176c_tf103c_pdevs,
.pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs),
.serdev_info = asus_me176c_serdevs,
.serdev_count = ARRAY_SIZE(asus_me176c_serdevs),
.gpio_button_swnodes = asus_me176c_tf103c_lid_swnodes,
.swnode_group = generic_lipo_hv_4v35_battery_swnodes,
.modules = bq24190_modules,
.gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/* Asus TF103C tablets have an Android factory image with everything hardcoded */
static const char * const asus_tf103c_accel_mount_matrix[] = {
"0", "-1", "0",
"-1", "0", "0",
"0", "0", "1"
};
static const struct property_entry asus_tf103c_accel_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", asus_tf103c_accel_mount_matrix),
{ }
};
static const struct software_node asus_tf103c_accel_node = {
.properties = asus_tf103c_accel_props,
};
static const struct property_entry asus_tf103c_touchscreen_props[] = {
PROPERTY_ENTRY_STRING("compatible", "atmel,atmel_mxt_ts"),
{ }
};
static const struct software_node asus_tf103c_touchscreen_node = {
.properties = asus_tf103c_touchscreen_props,
};
static const struct property_entry asus_tf103c_bq24190_props[] = {
PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", tusb1211_chg_det_psy, 1),
PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_4v2_battery_node),
PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000),
PROPERTY_ENTRY_BOOL("omit-battery-class"),
PROPERTY_ENTRY_BOOL("disable-reset"),
{ }
};
static const struct software_node asus_tf103c_bq24190_node = {
.properties = asus_tf103c_bq24190_props,
};
static const struct property_entry asus_tf103c_ug3105_props[] = {
PROPERTY_ENTRY_STRING_ARRAY_LEN("supplied-from", bq24190_psy, 1),
PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_4v2_battery_node),
PROPERTY_ENTRY_U32("upisemi,rsns-microohm", 5000),
{ }
};
static const struct software_node asus_tf103c_ug3105_node = {
.properties = asus_tf103c_ug3105_props,
};
static const struct x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst = {
{
/* bq24297 battery charger */
.board_info = {
.type = "bq24190",
.addr = 0x6b,
.dev_name = "bq24297",
.swnode = &asus_tf103c_bq24190_node,
.platform_data = &bq24190_pdata,
},
.adapter_path = "\\_SB_.I2C1",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_PMIC,
.chip = "\\_SB_.I2C7.PMIC",
.domain = DOMAIN_BUS_WAKEUP,
.index = 0,
},
}, {
/* ug3105 battery monitor */
.board_info = {
.type = "ug3105",
.addr = 0x70,
.dev_name = "ug3105",
.swnode = &asus_tf103c_ug3105_node,
},
.adapter_path = "\\_SB_.I2C1",
}, {
/* ak09911 compass */
.board_info = {
.type = "ak09911",
.addr = 0x0c,
.dev_name = "ak09911",
},
.adapter_path = "\\_SB_.I2C5",
}, {
/* kxtj21009 accelerometer */
.board_info = {
.type = "kxtj21009",
.addr = 0x0f,
.dev_name = "kxtj21009",
.swnode = &asus_tf103c_accel_node,
},
.adapter_path = "\\_SB_.I2C5",
}, {
/* atmel touchscreen */
.board_info = {
.type = "atmel_mxt_ts",
.addr = 0x4a,
.dev_name = "atmel_mxt_ts",
.swnode = &asus_tf103c_touchscreen_node,
},
.adapter_path = "\\_SB_.I2C6",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FC:02",
.index = 28,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
.con_id = "atmel_mxt_ts_irq",
},
},
};
const struct x86_dev_info asus_tf103c_info __initconst = {
.i2c_client_info = asus_tf103c_i2c_clients,
.i2c_client_count = ARRAY_SIZE(asus_tf103c_i2c_clients),
.pdev_info = asus_me176c_tf103c_pdevs,
.pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs),
.gpio_button_swnodes = asus_me176c_tf103c_lid_swnodes,
.swnode_group = generic_lipo_4v2_battery_swnodes,
.modules = bq24190_modules,
.gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
@@ -0,0 +1,623 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* DMI based code to deal with broken DSDTs on X86 tablets which ship with
* Android as (part of) the factory image. 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/acpi.h>
#include <linux/bug.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/fwnode.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/machine.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/serdev.h>
#include <linux/string.h>
#include "x86-android-tablets.h"
#include "../serdev_helpers.h"
static struct platform_device *x86_android_tablet_device;
/*
* This helper allows getting a GPIO descriptor *before* the actual device
* consuming it has been instantiated. This function MUST only be used to
* handle this special case such as, e.g.:
*
* 1. Getting an IRQ from a GPIO for i2c_board_info.irq which is passed to
* i2c_client_new() to instantiate i2c_client-s; or
* 2. Calling desc_to_gpio() to get an old style GPIO number for gpio-keys
* platform_data which still uses old style GPIO numbers.
*
* Since the consuming device has not been instantiated yet a dynamic lookup
* is generated using the special x86_android_tablet device for dev_id.
*
* For normal GPIO lookups a standard static struct gpiod_lookup_table MUST be used.
*/
int x86_android_tablet_get_gpiod(const char *chip, int pin, const char *con_id,
bool active_low, enum gpiod_flags dflags,
struct gpio_desc **desc)
{
struct gpiod_lookup_table *lookup;
struct gpio_desc *gpiod;
lookup = kzalloc_flex(*lookup, table, 2);
if (!lookup)
return -ENOMEM;
lookup->dev_id = KBUILD_MODNAME;
lookup->table[0] =
GPIO_LOOKUP(chip, pin, con_id, active_low ? GPIO_ACTIVE_LOW : GPIO_ACTIVE_HIGH);
gpiod_add_lookup_table(lookup);
gpiod = devm_gpiod_get(&x86_android_tablet_device->dev, con_id, dflags);
gpiod_remove_lookup_table(lookup);
kfree(lookup);
if (IS_ERR(gpiod)) {
pr_err("error %ld getting GPIO %s %d\n", PTR_ERR(gpiod), chip, pin);
return PTR_ERR(gpiod);
}
if (desc)
*desc = gpiod;
return 0;
}
int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data)
{
struct irq_fwspec fwspec = { };
struct irq_domain *domain;
struct acpi_device *adev;
struct gpio_desc *gpiod;
unsigned int irq_type;
acpi_handle handle;
acpi_status status;
int irq, ret;
switch (data->type) {
case X86_ACPI_IRQ_TYPE_APIC:
/*
* The DSDT may already reference the GSI in a device skipped by
* acpi_quirk_skip_i2c_client_enumeration(). Unregister the GSI
* to avoid -EBUSY errors in this case.
*/
acpi_unregister_gsi(data->index);
irq = acpi_register_gsi(NULL, data->index, data->trigger, data->polarity);
if (irq < 0)
pr_err("error %d getting APIC IRQ %d\n", irq, data->index);
return irq;
case X86_ACPI_IRQ_TYPE_GPIOINT:
/* Like acpi_dev_gpio_irq_get(), but without parsing ACPI resources */
ret = x86_android_tablet_get_gpiod(data->chip, data->index, data->con_id,
false, GPIOD_ASIS, &gpiod);
if (ret)
return ret;
irq = gpiod_to_irq(gpiod);
if (irq < 0) {
pr_err("error %d getting IRQ %s %d\n", irq, data->chip, data->index);
return irq;
}
irq_type = acpi_dev_get_irq_type(data->trigger, data->polarity);
if (irq_type != IRQ_TYPE_NONE && irq_type != irq_get_trigger_type(irq))
irq_set_irq_type(irq, irq_type);
if (data->free_gpio)
devm_gpiod_put(&x86_android_tablet_device->dev, gpiod);
return irq;
case X86_ACPI_IRQ_TYPE_PMIC:
status = acpi_get_handle(NULL, data->chip, &handle);
if (ACPI_FAILURE(status)) {
pr_err("error could not get %s handle\n", data->chip);
return -ENODEV;
}
adev = acpi_fetch_acpi_dev(handle);
if (!adev) {
pr_err("error could not get %s adev\n", data->chip);
return -ENODEV;
}
fwspec.fwnode = acpi_fwnode_handle(adev);
domain = irq_find_matching_fwspec(&fwspec, data->domain);
if (!domain) {
pr_err("error could not find IRQ domain for %s\n", data->chip);
return -ENODEV;
}
return irq_create_mapping(domain, data->index);
default:
return 0;
}
}
static int i2c_client_count;
static int spi_dev_count;
static int pdev_count;
static int serdev_count;
static struct i2c_client **i2c_clients;
static struct spi_device **spi_devs;
static struct platform_device **pdevs;
static struct serdev_device **serdevs;
static const struct software_node **gpio_button_swnodes;
static const struct software_node **swnode_group;
static const struct software_node **gpiochip_node_group;
static void (*exit_handler)(void);
static __init struct i2c_adapter *
get_i2c_adap_by_handle(const struct x86_i2c_client_info *client_info)
{
acpi_handle handle;
acpi_status status;
status = acpi_get_handle(NULL, client_info->adapter_path, &handle);
if (ACPI_FAILURE(status)) {
pr_err("Error could not get %s handle\n", client_info->adapter_path);
return NULL;
}
return i2c_acpi_find_adapter_by_handle(handle);
}
static __init int match_parent(struct device *dev, const void *data)
{
return dev->parent == data;
}
static __init struct i2c_adapter *
get_i2c_adap_by_pci_parent(const struct x86_i2c_client_info *client_info)
{
struct i2c_adapter *adap = NULL;
struct device *pdev, *adap_dev;
pdev = bus_find_device_by_name(&pci_bus_type, NULL, client_info->adapter_path);
if (!pdev) {
pr_err("Error could not find %s PCI device\n", client_info->adapter_path);
return NULL;
}
adap_dev = bus_find_device(&i2c_bus_type, NULL, pdev, match_parent);
if (adap_dev) {
adap = i2c_verify_adapter(adap_dev);
if (!adap)
put_device(adap_dev);
}
put_device(pdev);
return adap;
}
static __init int x86_instantiate_i2c_client(const struct x86_dev_info *dev_info,
int idx)
{
const struct x86_i2c_client_info *client_info = &dev_info->i2c_client_info[idx];
struct i2c_board_info board_info = client_info->board_info;
struct i2c_adapter *adap;
board_info.irq = x86_acpi_irq_helper_get(&client_info->irq_data);
if (board_info.irq < 0)
return board_info.irq;
if (dev_info->use_pci)
adap = get_i2c_adap_by_pci_parent(client_info);
else
adap = get_i2c_adap_by_handle(client_info);
if (!adap) {
pr_err("error could not get %s adapter\n", client_info->adapter_path);
return -ENODEV;
}
i2c_clients[idx] = i2c_new_client_device(adap, &board_info);
put_device(&adap->dev);
if (IS_ERR(i2c_clients[idx]))
return dev_err_probe(&adap->dev, PTR_ERR(i2c_clients[idx]),
"creating I2C-client %d\n", idx);
return 0;
}
static __init int x86_instantiate_spi_dev(const struct x86_dev_info *dev_info, int idx)
{
const struct x86_spi_dev_info *spi_dev_info = &dev_info->spi_dev_info[idx];
struct spi_board_info board_info = spi_dev_info->board_info;
struct spi_controller *controller;
struct acpi_device *adev;
acpi_handle handle;
acpi_status status;
board_info.irq = x86_acpi_irq_helper_get(&spi_dev_info->irq_data);
if (board_info.irq < 0)
return board_info.irq;
status = acpi_get_handle(NULL, spi_dev_info->ctrl_path, &handle);
if (ACPI_FAILURE(status)) {
pr_err("Error could not get %s handle\n", spi_dev_info->ctrl_path);
return -ENODEV;
}
adev = acpi_fetch_acpi_dev(handle);
if (!adev) {
pr_err("Error could not get adev for %s\n", spi_dev_info->ctrl_path);
return -ENODEV;
}
controller = acpi_spi_find_controller_by_adev(adev);
if (!controller) {
pr_err("Error could not get SPI controller for %s\n", spi_dev_info->ctrl_path);
return -ENODEV;
}
spi_devs[idx] = spi_new_device(controller, &board_info);
put_device(&controller->dev);
if (!spi_devs[idx])
return -ENOMEM;
return 0;
}
static __init struct device *
get_serdev_controller_by_pci_parent(const struct x86_serdev_info *info)
{
struct pci_dev *pdev;
pdev = pci_get_domain_bus_and_slot(0, 0, info->ctrl.pci.devfn);
if (!pdev) {
pr_err("error could not get PCI serdev at devfn 0x%02x\n", info->ctrl.pci.devfn);
return ERR_PTR(-ENODEV);
}
/* This puts our reference on pdev and returns a ref on the ctrl */
return get_serdev_controller_from_parent(&pdev->dev, 0, info->ctrl_devname);
}
static __init int x86_instantiate_serdev(const struct x86_dev_info *dev_info, int idx)
{
const struct x86_serdev_info *info = &dev_info->serdev_info[idx];
struct acpi_device *serdev_adev;
struct serdev_device *serdev;
struct device *ctrl_dev;
int ret = -ENODEV;
if (dev_info->use_pci)
ctrl_dev = get_serdev_controller_by_pci_parent(info);
else
ctrl_dev = get_serdev_controller(info->ctrl.acpi.hid, info->ctrl.acpi.uid,
0, info->ctrl_devname);
if (IS_ERR(ctrl_dev))
return PTR_ERR(ctrl_dev);
serdev_adev = acpi_dev_get_first_match_dev(info->serdev_hid, NULL, -1);
if (!serdev_adev) {
pr_err("error could not get %s serdev adev\n", info->serdev_hid);
goto put_ctrl_dev;
}
serdev = serdev_device_alloc(to_serdev_controller(ctrl_dev));
if (!serdev) {
ret = -ENOMEM;
goto put_serdev_adev;
}
ACPI_COMPANION_SET(&serdev->dev, serdev_adev);
acpi_device_set_enumerated(serdev_adev);
ret = serdev_device_add(serdev);
if (ret) {
dev_err(&serdev->dev, "error %d adding serdev\n", ret);
serdev_device_put(serdev);
goto put_serdev_adev;
}
serdevs[idx] = serdev;
put_serdev_adev:
acpi_dev_put(serdev_adev);
put_ctrl_dev:
put_device(ctrl_dev);
return ret;
}
const struct software_node baytrail_gpiochip_nodes[] = {
{ .name = "INT33FC:00" },
{ .name = "INT33FC:01" },
{ .name = "INT33FC:02" },
};
static const struct software_node *baytrail_gpiochip_node_group[] = {
&baytrail_gpiochip_nodes[0],
&baytrail_gpiochip_nodes[1],
&baytrail_gpiochip_nodes[2],
NULL
};
const struct software_node cherryview_gpiochip_nodes[] = {
{ .name = "INT33FF:00" },
{ .name = "INT33FF:01" },
{ .name = "INT33FF:02" },
{ .name = "INT33FF:03" },
};
static const struct software_node *cherryview_gpiochip_node_group[] = {
&cherryview_gpiochip_nodes[0],
&cherryview_gpiochip_nodes[1],
&cherryview_gpiochip_nodes[2],
&cherryview_gpiochip_nodes[3],
NULL
};
static void gpio_secondary_unset(void *data)
{
struct device *dev = data;
set_secondary_fwnode(dev, NULL);
}
static void gpio_secondary_unregister_node_group(void *data)
{
const struct software_node **nodes = data;
software_node_unregister_node_group(nodes);
}
static int gpio_secondary_fwnode_init(struct device *parent)
{
const struct software_node *const *swnode;
struct fwnode_handle *fwnode;
int ret;
if (!gpiochip_node_group)
return 0;
ret = software_node_register_node_group(gpiochip_node_group);
if (ret)
return ret;
ret = devm_add_action_or_reset(parent,
gpio_secondary_unregister_node_group,
gpiochip_node_group);
if (ret)
return ret;
for (swnode = gpiochip_node_group; *swnode; swnode++) {
struct device *dev __free(put_device) =
acpi_bus_find_device_by_name((*swnode)->name);
if (!dev)
return dev_err_probe(parent,
-ENODEV, "Failed to find the required GPIO controller: %s\n",
(*swnode)->name);
fwnode = software_node_fwnode(*swnode);
if (WARN_ON(!fwnode))
return -ENOENT;
set_secondary_fwnode(dev, fwnode);
ret = devm_add_action_or_reset(parent, gpio_secondary_unset, dev);
if (ret)
return ret;
}
return 0;
}
static void x86_android_tablet_remove(struct platform_device *pdev)
{
int i;
for (i = serdev_count - 1; i >= 0; i--) {
if (serdevs[i])
serdev_device_remove(serdevs[i]);
}
kfree(serdevs);
for (i = pdev_count - 1; i >= 0; i--)
platform_device_unregister(pdevs[i]);
kfree(pdevs);
for (i = spi_dev_count - 1; i >= 0; i--)
spi_unregister_device(spi_devs[i]);
kfree(spi_devs);
for (i = i2c_client_count - 1; i >= 0; i--)
i2c_unregister_device(i2c_clients[i]);
kfree(i2c_clients);
if (exit_handler)
exit_handler();
software_node_unregister_node_group(gpio_button_swnodes);
software_node_unregister_node_group(swnode_group);
}
static __init int x86_android_tablet_probe(struct platform_device *pdev)
{
const struct x86_dev_info *dev_info;
const struct dmi_system_id *id;
int i, ret = 0;
id = dmi_first_match(x86_android_tablet_ids);
if (!id)
return -ENODEV;
dev_info = id->driver_data;
/* Allow x86_android_tablet_device use before probe() exits */
x86_android_tablet_device = pdev;
/*
* Since this runs from module_init() it cannot use -EPROBE_DEFER,
* instead pre-load any modules which are listed as requirements.
*/
for (i = 0; dev_info->modules && dev_info->modules[i]; i++)
request_module(dev_info->modules[i]);
switch (dev_info->gpiochip_type) {
case X86_GPIOCHIP_BAYTRAIL:
gpiochip_node_group = baytrail_gpiochip_node_group;
break;
case X86_GPIOCHIP_CHERRYVIEW:
gpiochip_node_group = cherryview_gpiochip_node_group;
break;
case X86_GPIOCHIP_UNSPECIFIED:
gpiochip_node_group = NULL;
break;
}
ret = gpio_secondary_fwnode_init(&pdev->dev);
if (ret) {
x86_android_tablet_remove(pdev);
return ret;
}
ret = software_node_register_node_group(dev_info->swnode_group);
if (ret) {
x86_android_tablet_remove(pdev);
return ret;
}
swnode_group = dev_info->swnode_group;
if (dev_info->init) {
ret = dev_info->init(&pdev->dev);
if (ret < 0) {
x86_android_tablet_remove(pdev);
return ret;
}
exit_handler = dev_info->exit;
}
i2c_clients = kzalloc_objs(*i2c_clients, dev_info->i2c_client_count);
if (!i2c_clients) {
x86_android_tablet_remove(pdev);
return -ENOMEM;
}
i2c_client_count = dev_info->i2c_client_count;
for (i = 0; i < i2c_client_count; i++) {
ret = x86_instantiate_i2c_client(dev_info, i);
if (ret < 0) {
x86_android_tablet_remove(pdev);
return ret;
}
}
spi_devs = kzalloc_objs(*spi_devs, dev_info->spi_dev_count);
if (!spi_devs) {
x86_android_tablet_remove(pdev);
return -ENOMEM;
}
spi_dev_count = dev_info->spi_dev_count;
for (i = 0; i < spi_dev_count; i++) {
ret = x86_instantiate_spi_dev(dev_info, i);
if (ret < 0) {
x86_android_tablet_remove(pdev);
return ret;
}
}
/* + 1 to make space for the (optional) gpio_keys_button platform device */
pdevs = kzalloc_objs(*pdevs, dev_info->pdev_count + 1);
if (!pdevs) {
x86_android_tablet_remove(pdev);
return -ENOMEM;
}
pdev_count = dev_info->pdev_count;
for (i = 0; i < pdev_count; i++) {
pdevs[i] = platform_device_register_full(&dev_info->pdev_info[i]);
if (IS_ERR(pdevs[i])) {
ret = PTR_ERR(pdevs[i]);
x86_android_tablet_remove(pdev);
return ret;
}
}
serdevs = kzalloc_objs(*serdevs, dev_info->serdev_count);
if (!serdevs) {
x86_android_tablet_remove(pdev);
return -ENOMEM;
}
serdev_count = dev_info->serdev_count;
for (i = 0; i < serdev_count; i++) {
ret = x86_instantiate_serdev(dev_info, i);
if (ret < 0) {
x86_android_tablet_remove(pdev);
return ret;
}
}
if (dev_info->gpio_button_swnodes) {
struct platform_device_info button_info = {
.name = "gpio-keys",
.id = PLATFORM_DEVID_AUTO,
};
ret = software_node_register_node_group(dev_info->gpio_button_swnodes);
if (ret < 0) {
x86_android_tablet_remove(pdev);
return ret;
}
gpio_button_swnodes = dev_info->gpio_button_swnodes;
button_info.fwnode = software_node_fwnode(dev_info->gpio_button_swnodes[0]);
pdevs[pdev_count] = platform_device_register_full(&button_info);
if (IS_ERR(pdevs[pdev_count])) {
ret = PTR_ERR(pdevs[pdev_count]);
x86_android_tablet_remove(pdev);
return ret;
}
pdev_count++;
}
return 0;
}
static struct platform_driver x86_android_tablet_driver = {
.driver = {
.name = KBUILD_MODNAME,
},
.remove = x86_android_tablet_remove,
};
static int __init x86_android_tablet_init(void)
{
x86_android_tablet_device = platform_create_bundle(&x86_android_tablet_driver,
x86_android_tablet_probe,
NULL, 0, NULL, 0);
return PTR_ERR_OR_ZERO(x86_android_tablet_device);
}
module_init(x86_android_tablet_init);
static void __exit x86_android_tablet_exit(void)
{
platform_device_unregister(x86_android_tablet_device);
platform_driver_unregister(&x86_android_tablet_driver);
}
module_exit(x86_android_tablet_exit);
MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
MODULE_DESCRIPTION("X86 Android tablets DSDT fixups driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,233 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* DMI based code to deal with broken DSDTs on X86 tablets which ship with
* Android as (part of) the factory image. 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>
*/
#include <linux/dmi.h>
#include <linux/init.h>
#include <linux/module.h>
#include "x86-android-tablets.h"
const struct dmi_system_id x86_android_tablet_ids[] __initconst = {
{
/* Acer Iconia One 8 A1-840 (non FHD version) */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Insyde"),
DMI_MATCH(DMI_PRODUCT_NAME, "BayTrail"),
/* Above strings are too generic also match BIOS date */
DMI_MATCH(DMI_BIOS_DATE, "04/01/2014"),
},
.driver_data = (void *)&acer_a1_840_info,
},
{
/* Acer Iconia One 7 B1-750 */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Insyde"),
DMI_MATCH(DMI_PRODUCT_NAME, "VESPA2"),
},
.driver_data = (void *)&acer_b1_750_info,
},
{
/* Advantech MICA-071 */
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Advantech"),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "MICA-071"),
},
.driver_data = (void *)&advantech_mica_071_info,
},
{
/* Asus MeMO Pad 7 ME176C */
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "ME176C"),
},
.driver_data = (void *)&asus_me176c_info,
},
{
/* Asus TF103C */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
DMI_MATCH(DMI_PRODUCT_NAME, "TF103C"),
},
.driver_data = (void *)&asus_tf103c_info,
},
{
/* Chuwi Hi8 (CWI509) */
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"),
DMI_MATCH(DMI_BOARD_NAME, "BYT-PA03C"),
DMI_MATCH(DMI_SYS_VENDOR, "ilife"),
DMI_MATCH(DMI_PRODUCT_NAME, "S806"),
},
.driver_data = (void *)&chuwi_hi8_info,
},
{
/* Cyberbook T116 Android version */
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "Default string"),
DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"),
/* Above strings are much too generic, also match on SKU + BIOS date */
DMI_MATCH(DMI_PRODUCT_SKU, "20170531"),
DMI_MATCH(DMI_BIOS_DATE, "07/12/2017"),
},
.driver_data = (void *)&cyberbook_t116_info,
},
{
/* CZC P10T */
.ident = "CZC ODEON TPC-10 (\"P10T\")",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "CZC"),
DMI_MATCH(DMI_PRODUCT_NAME, "ODEON*TPC-10"),
},
.driver_data = (void *)&czc_p10t,
},
{
/* CZC P10T variant */
.ident = "ViewSonic ViewPad 10",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ViewSonic"),
DMI_MATCH(DMI_PRODUCT_NAME, "VPAD10"),
},
.driver_data = (void *)&czc_p10t,
},
{
/* Lenovo Yoga Book X90F / X90L */
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "CHERRYVIEW D1 PLATFORM"),
DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "YETI-11"),
},
.driver_data = (void *)&lenovo_yogabook_x90_info,
},
{
/* Lenovo Yoga Book X91F / X91L */
.matches = {
/* Inexact match to match F + L versions */
DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X91"),
},
.driver_data = (void *)&lenovo_yogabook_x91_info,
},
{
/*
* Lenovo Yoga Tablet 2 Pro 1380F/L (13")
* This has more or less the same BIOS as the 830F/L or 1050F/L
* (8" and 10") below, but unlike the 8"/10" models which share
* the same mainboard this model has a different mainboard.
* This match for the 13" model MUST come before the 8" + 10"
* match since that one will also match the 13" model!
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Intel Corp."),
DMI_MATCH(DMI_PRODUCT_NAME, "VALLEYVIEW C0 PLATFORM"),
DMI_MATCH(DMI_BOARD_NAME, "BYT-T FFD8"),
/* Full match so as to NOT match the 830/1050 BIOS */
DMI_MATCH(DMI_BIOS_VERSION, "BLADE_21.X64.0005.R00.1504101516"),
},
.driver_data = (void *)&lenovo_yoga_tab2_1380_info,
},
{
/*
* Lenovo Yoga Tablet 2 830F/L or 1050F/L
* The 8" and 10" Lenovo Yoga Tablet 2 use the same mainboard.
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Intel Corp."),
DMI_MATCH(DMI_PRODUCT_NAME, "VALLEYVIEW C0 PLATFORM"),
DMI_MATCH(DMI_BOARD_NAME, "BYT-T FFD8"),
/* Partial match on beginning of BIOS version */
DMI_MATCH(DMI_BIOS_VERSION, "BLADE_21"),
},
.driver_data = (void *)&lenovo_yoga_tab2_830_1050_info,
},
{
/* Lenovo Yoga Tab 3 Pro YT3-X90F */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Intel Corporation"),
DMI_MATCH(DMI_PRODUCT_VERSION, "Blade3-10A-001"),
},
.driver_data = (void *)&lenovo_yt3_info,
},
{
/* Medion Lifetab S10346 */
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"),
/* Above strings are much too generic, also match on BIOS date */
DMI_MATCH(DMI_BIOS_DATE, "10/22/2015"),
},
.driver_data = (void *)&medion_lifetab_s10346_info,
},
{
/* Nextbook Ares 8 (BYT version) */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Insyde"),
DMI_MATCH(DMI_PRODUCT_NAME, "M890BAP"),
},
.driver_data = (void *)&nextbook_ares8_info,
},
{
/* Nextbook Ares 8A (CHT version) */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Insyde"),
DMI_MATCH(DMI_PRODUCT_NAME, "CherryTrail"),
DMI_MATCH(DMI_BIOS_VERSION, "M882"),
},
.driver_data = (void *)&nextbook_ares8a_info,
},
{
/* Peaq C1010 */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "PEAQ"),
DMI_MATCH(DMI_PRODUCT_NAME, "PEAQ PMM C1010 MD99187"),
},
.driver_data = (void *)&peaq_c1010_info,
},
{
/* Vexia Edu Atla 10 tablet 5V version */
.matches = {
/* Having all 3 of these not set is somewhat unique */
DMI_MATCH(DMI_SYS_VENDOR, "To be filled by O.E.M."),
DMI_MATCH(DMI_PRODUCT_NAME, "To be filled by O.E.M."),
DMI_MATCH(DMI_BOARD_NAME, "To be filled by O.E.M."),
/* Above strings are too generic, also match on BIOS date */
DMI_MATCH(DMI_BIOS_DATE, "05/14/2015"),
},
.driver_data = (void *)&vexia_edu_atla10_5v_info,
},
{
/* Vexia Edu Atla 10 tablet 9V version */
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"),
/* Above strings are too generic, also match on BIOS date */
DMI_MATCH(DMI_BIOS_DATE, "08/25/2014"),
},
.driver_data = (void *)&vexia_edu_atla10_9v_info,
},
{
/* Whitelabel (sold as various brands) TM800A550L */
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"),
/* Above strings are too generic, also match on BIOS version */
DMI_MATCH(DMI_BIOS_VERSION, "ZY-8-BI-PX4S70VTR400-X423B-005-D"),
},
.driver_data = (void *)&whitelabel_tm800a550l_info,
},
{
/* Xiaomi Mi Pad 2 */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Xiaomi Inc"),
DMI_MATCH(DMI_PRODUCT_NAME, "Mipad2"),
},
.driver_data = (void *)&xiaomi_mipad2_info,
},
{ }
};
MODULE_DEVICE_TABLE(dmi, x86_android_tablet_ids);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,883 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* DMI based code to deal with broken DSDTs on X86 tablets which ship with
* Android as (part of) the factory image. 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>
*/
#include <linux/acpi.h>
#include <linux/gpio/machine.h>
#include <linux/gpio/property.h>
#include <linux/input-event-codes.h>
#include <linux/leds.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <dt-bindings/leds/common.h>
#include "shared-psy-info.h"
#include "x86-android-tablets.h"
/*
* Advantech MICA-071
* This is a standard Windows tablet, but it has an extra "quick launch" button
* which is not described in the ACPI tables in anyway.
* Use the x86-android-tablets infra to create a gpio-keys device for this.
*/
static const struct software_node advantech_mica_071_gpio_keys_node = {
.name = "prog1_key",
};
static const struct property_entry advantech_mica_071_prog1_key_props[] = {
PROPERTY_ENTRY_U32("linux,code", KEY_PROG1),
PROPERTY_ENTRY_STRING("label", "prog1_key"),
PROPERTY_ENTRY_GPIO("gpios", &baytrail_gpiochip_nodes[0], 2, GPIO_ACTIVE_LOW),
PROPERTY_ENTRY_U32("debounce-interval", 50),
{ }
};
static const struct software_node advantech_mica_071_prog1_key_node = {
.parent = &advantech_mica_071_gpio_keys_node,
.properties = advantech_mica_071_prog1_key_props,
};
static const struct software_node *advantech_mica_071_button_swnodes[] = {
&advantech_mica_071_gpio_keys_node,
&advantech_mica_071_prog1_key_node,
NULL
};
const struct x86_dev_info advantech_mica_071_info __initconst = {
.gpio_button_swnodes = advantech_mica_071_button_swnodes,
.gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/*
* When booted with the BIOS set to Android mode the Chuwi Hi8 (CWI509) DSDT
* contains a whole bunch of bogus ACPI I2C devices and is missing entries
* for the touchscreen and the accelerometer.
*/
static const struct property_entry chuwi_hi8_gsl1680_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 1665),
PROPERTY_ENTRY_U32("touchscreen-size-y", 1140),
PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"),
PROPERTY_ENTRY_BOOL("silead,home-button"),
PROPERTY_ENTRY_STRING("firmware-name", "gsl1680-chuwi-hi8.fw"),
{ }
};
static const struct software_node chuwi_hi8_gsl1680_node = {
.properties = chuwi_hi8_gsl1680_props,
};
static const char * const chuwi_hi8_mount_matrix[] = {
"1", "0", "0",
"0", "-1", "0",
"0", "0", "1"
};
static const struct property_entry chuwi_hi8_bma250e_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", chuwi_hi8_mount_matrix),
{ }
};
static const struct software_node chuwi_hi8_bma250e_node = {
.properties = chuwi_hi8_bma250e_props,
};
static const struct x86_i2c_client_info chuwi_hi8_i2c_clients[] __initconst = {
{
/* Silead touchscreen */
.board_info = {
.type = "gsl1680",
.addr = 0x40,
.swnode = &chuwi_hi8_gsl1680_node,
},
.adapter_path = "\\_SB_.I2C4",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x44,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
},
}, {
/* BMA250E accelerometer */
.board_info = {
.type = "bma250e",
.addr = 0x18,
.swnode = &chuwi_hi8_bma250e_node,
},
.adapter_path = "\\_SB_.I2C3",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FC:02",
.index = 23,
.trigger = ACPI_LEVEL_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
.con_id = "bma250e_irq",
},
},
};
static int __init chuwi_hi8_init(struct device *dev)
{
/*
* Avoid the acpi_unregister_gsi() call in x86_acpi_irq_helper_get()
* breaking the touchscreen + logging various errors when the Windows
* BIOS is used.
*/
if (acpi_dev_present("MSSL0001", NULL, 1))
return -ENODEV;
return 0;
}
const struct x86_dev_info chuwi_hi8_info __initconst = {
.i2c_client_info = chuwi_hi8_i2c_clients,
.i2c_client_count = ARRAY_SIZE(chuwi_hi8_i2c_clients),
.init = chuwi_hi8_init,
};
/*
* Cyberbook T116 Android version
* This comes in both Windows and Android versions and even on Android
* the DSDT is mostly sane. This tablet has 2 extra general purpose buttons
* in the button row with the power + volume-buttons labeled P and F.
* Use the x86-android-tablets infra to create a gpio-keys device for these.
*/
static const struct software_node cyberbook_t116_gpio_keys_node = {
.name = "prog_keys",
};
static const struct property_entry cyberbook_t116_prog1_key_props[] = {
PROPERTY_ENTRY_U32("linux,code", KEY_PROG1),
PROPERTY_ENTRY_STRING("label", "prog1_key"),
PROPERTY_ENTRY_GPIO("gpios", &cherryview_gpiochip_nodes[0], 30, GPIO_ACTIVE_LOW),
PROPERTY_ENTRY_U32("debounce-interval", 50),
{ }
};
static const struct software_node cyberbook_t116_prog1_key_node = {
.parent = &cyberbook_t116_gpio_keys_node,
.properties = cyberbook_t116_prog1_key_props,
};
static const struct property_entry cyberbook_t116_prog2_key_props[] = {
PROPERTY_ENTRY_U32("linux,code", KEY_PROG2),
PROPERTY_ENTRY_STRING("label", "prog2_key"),
PROPERTY_ENTRY_GPIO("gpios", &cherryview_gpiochip_nodes[3], 48, GPIO_ACTIVE_LOW),
PROPERTY_ENTRY_U32("debounce-interval", 50),
{ }
};
static const struct software_node cyberbook_t116_prog2_key_node = {
.parent = &cyberbook_t116_gpio_keys_node,
.properties = cyberbook_t116_prog2_key_props,
};
static const struct software_node *cyberbook_t116_buttons_swnodes[] = {
&cyberbook_t116_gpio_keys_node,
&cyberbook_t116_prog1_key_node,
&cyberbook_t116_prog2_key_node,
NULL
};
const struct x86_dev_info cyberbook_t116_info __initconst = {
.gpio_button_swnodes = cyberbook_t116_buttons_swnodes,
.gpiochip_type = X86_GPIOCHIP_CHERRYVIEW,
};
#define CZC_EC_EXTRA_PORT 0x68
#define CZC_EC_ANDROID_KEYS 0x63
static int __init czc_p10t_init(struct device *dev)
{
/*
* The device boots up in "Windows 7" mode, when the home button sends a
* Windows specific key sequence (Left Meta + D) and the second button
* sends an unknown one while also toggling the Radio Kill Switch.
* This is a surprising behavior when the second button is labeled "Back".
*
* The vendor-supplied Android-x86 build switches the device to a "Android"
* mode by writing value 0x63 to the I/O port 0x68. This just seems to just
* set bit 6 on address 0x96 in the EC region; switching the bit directly
* seems to achieve the same result. It uses a "p10t_switcher" to do the
* job. It doesn't seem to be able to do anything else, and no other use
* of the port 0x68 is known.
*
* In the Android mode, the home button sends just a single scancode,
* which can be handled in Linux userspace more reasonably and the back
* button only sends a scancode without toggling the kill switch.
* The scancode can then be mapped either to Back or RF Kill functionality
* in userspace, depending on how the button is labeled on that particular
* model.
*/
outb(CZC_EC_ANDROID_KEYS, CZC_EC_EXTRA_PORT);
return 0;
}
const struct x86_dev_info czc_p10t __initconst = {
.init = czc_p10t_init,
};
/* Medion Lifetab S10346 tablets have an Android factory image with everything hardcoded */
static const char * const medion_lifetab_s10346_accel_mount_matrix[] = {
"0", "1", "0",
"1", "0", "0",
"0", "0", "1"
};
static const struct property_entry medion_lifetab_s10346_accel_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", medion_lifetab_s10346_accel_mount_matrix),
{ }
};
static const struct software_node medion_lifetab_s10346_accel_node = {
.properties = medion_lifetab_s10346_accel_props,
};
/* Note the LCD panel is mounted upside down, this is correctly indicated in the VBT */
static const struct property_entry medion_lifetab_s10346_touchscreen_props[] = {
PROPERTY_ENTRY_BOOL("touchscreen-inverted-x"),
PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"),
PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_HIGH),
PROPERTY_ENTRY_GPIO("irq-gpios", &baytrail_gpiochip_nodes[2], 3, GPIO_ACTIVE_HIGH),
{ }
};
static const struct software_node medion_lifetab_s10346_touchscreen_node = {
.properties = medion_lifetab_s10346_touchscreen_props,
};
static const struct x86_i2c_client_info medion_lifetab_s10346_i2c_clients[] __initconst = {
{
/* kxtj21009 accelerometer */
.board_info = {
.type = "kxtj21009",
.addr = 0x0f,
.dev_name = "kxtj21009",
.swnode = &medion_lifetab_s10346_accel_node,
},
.adapter_path = "\\_SB_.I2C3",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FC:02",
.index = 23,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
.con_id = "kxtj21009_irq",
},
}, {
/* goodix touchscreen */
.board_info = {
.type = "GDIX1001:00",
.addr = 0x14,
.dev_name = "goodix_ts",
.swnode = &medion_lifetab_s10346_touchscreen_node,
},
.adapter_path = "\\_SB_.I2C4",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x44,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
},
},
};
const struct x86_dev_info medion_lifetab_s10346_info __initconst = {
.i2c_client_info = medion_lifetab_s10346_i2c_clients,
.i2c_client_count = ARRAY_SIZE(medion_lifetab_s10346_i2c_clients),
.gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/* Nextbook Ares 8 (BYT) tablets have an Android factory image with everything hardcoded */
static const char * const nextbook_ares8_accel_mount_matrix[] = {
"0", "-1", "0",
"-1", "0", "0",
"0", "0", "1"
};
static const struct property_entry nextbook_ares8_accel_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", nextbook_ares8_accel_mount_matrix),
{ }
};
static const struct software_node nextbook_ares8_accel_node = {
.properties = nextbook_ares8_accel_props,
};
static const struct property_entry nextbook_ares8_touchscreen_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 800),
PROPERTY_ENTRY_U32("touchscreen-size-y", 1280),
{ }
};
static const struct software_node nextbook_ares8_touchscreen_node = {
.properties = nextbook_ares8_touchscreen_props,
};
static const struct x86_i2c_client_info nextbook_ares8_i2c_clients[] __initconst = {
{
/* Freescale MMA8653FC accelerometer */
.board_info = {
.type = "mma8653",
.addr = 0x1d,
.dev_name = "mma8653",
.swnode = &nextbook_ares8_accel_node,
},
.adapter_path = "\\_SB_.I2C3",
}, {
/* FT5416DQ9 touchscreen controller */
.board_info = {
.type = "edt-ft5x06",
.addr = 0x38,
.dev_name = "ft5416",
.swnode = &nextbook_ares8_touchscreen_node,
},
.adapter_path = "\\_SB_.I2C4",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FC:02",
.index = 3,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
.con_id = "ft5416_irq",
},
},
};
const struct x86_dev_info nextbook_ares8_info __initconst = {
.i2c_client_info = nextbook_ares8_i2c_clients,
.i2c_client_count = ARRAY_SIZE(nextbook_ares8_i2c_clients),
.pdev_info = int3496_pdevs,
.pdev_count = 1,
.gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/* Nextbook Ares 8A (CHT) tablets have an Android factory image with everything hardcoded */
static const char * const nextbook_ares8a_accel_mount_matrix[] = {
"1", "0", "0",
"0", "-1", "0",
"0", "0", "1"
};
static const struct property_entry nextbook_ares8a_accel_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", nextbook_ares8a_accel_mount_matrix),
{ }
};
static const struct software_node nextbook_ares8a_accel_node = {
.properties = nextbook_ares8a_accel_props,
};
static const struct property_entry nextbook_ares8a_ft5416_props[] = {
PROPERTY_ENTRY_U32("touchscreen-size-x", 800),
PROPERTY_ENTRY_U32("touchscreen-size-y", 1280),
PROPERTY_ENTRY_GPIO("reset-gpios", &cherryview_gpiochip_nodes[1], 25, GPIO_ACTIVE_LOW),
{ }
};
static const struct software_node nextbook_ares8a_ft5416_node = {
.properties = nextbook_ares8a_ft5416_props,
};
static const struct x86_i2c_client_info nextbook_ares8a_i2c_clients[] __initconst = {
{
/* Freescale MMA8653FC accelerometer */
.board_info = {
.type = "mma8653",
.addr = 0x1d,
.dev_name = "mma8653",
.swnode = &nextbook_ares8a_accel_node,
},
.adapter_path = "\\_SB_.PCI0.I2C3",
}, {
/* FT5416DQ9 touchscreen controller */
.board_info = {
.type = "edt-ft5x06",
.addr = 0x38,
.dev_name = "ft5416",
.swnode = &nextbook_ares8a_ft5416_node,
},
.adapter_path = "\\_SB_.PCI0.I2C6",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FF:01",
.index = 17,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_LOW,
.con_id = "ft5416_irq",
},
},
};
const struct x86_dev_info nextbook_ares8a_info __initconst = {
.i2c_client_info = nextbook_ares8a_i2c_clients,
.i2c_client_count = ARRAY_SIZE(nextbook_ares8a_i2c_clients),
.gpiochip_type = X86_GPIOCHIP_CHERRYVIEW,
};
/*
* Peaq C1010
* This is a standard Windows tablet, but it has a special Dolby button.
* This button has a WMI interface, but that is broken. Instead of trying to
* use the broken WMI interface, instantiate a gpio-keys device for this.
*/
static const struct software_node peaq_c1010_gpio_keys_node = {
.name = "gpio_keys",
};
static const struct property_entry peaq_c1010_dolby_key_props[] = {
PROPERTY_ENTRY_U32("linux,code", KEY_SOUND),
PROPERTY_ENTRY_STRING("label", "dolby_key"),
PROPERTY_ENTRY_GPIO("gpios", &baytrail_gpiochip_nodes[0], 3, GPIO_ACTIVE_LOW),
PROPERTY_ENTRY_U32("debounce-interval", 50),
{ }
};
static const struct software_node peaq_c1010_dolby_key_node = {
.parent = &peaq_c1010_gpio_keys_node,
.properties = peaq_c1010_dolby_key_props,
};
static const struct software_node *peaq_c1010_button_swnodes[] = {
&peaq_c1010_gpio_keys_node,
&peaq_c1010_dolby_key_node,
NULL
};
const struct x86_dev_info peaq_c1010_info __initconst = {
.gpio_button_swnodes = peaq_c1010_button_swnodes,
.gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/*
* Whitelabel (sold as various brands) TM800A550L tablets.
* These tablet's DSDT contains a whole bunch of bogus ACPI I2C devices
* (removed through acpi_quirk_skip_i2c_client_enumeration()) and
* the touchscreen firmware node has the wrong GPIOs.
*/
static const char * const whitelabel_tm800a550l_accel_mount_matrix[] = {
"-1", "0", "0",
"0", "1", "0",
"0", "0", "1"
};
static const struct property_entry whitelabel_tm800a550l_accel_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", whitelabel_tm800a550l_accel_mount_matrix),
{ }
};
static const struct software_node whitelabel_tm800a550l_accel_node = {
.properties = whitelabel_tm800a550l_accel_props,
};
static const struct property_entry whitelabel_tm800a550l_goodix_props[] = {
PROPERTY_ENTRY_STRING("firmware-name", "gt912-tm800a550l.fw"),
PROPERTY_ENTRY_STRING("goodix,config-name", "gt912-tm800a550l.cfg"),
PROPERTY_ENTRY_U32("goodix,main-clk", 54),
PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_HIGH),
PROPERTY_ENTRY_GPIO("irq-gpios", &baytrail_gpiochip_nodes[2], 3, GPIO_ACTIVE_HIGH),
{ }
};
static const struct software_node whitelabel_tm800a550l_goodix_node = {
.properties = whitelabel_tm800a550l_goodix_props,
};
static const struct x86_i2c_client_info whitelabel_tm800a550l_i2c_clients[] __initconst = {
{
/* goodix touchscreen */
.board_info = {
.type = "GDIX1001:00",
.addr = 0x14,
.dev_name = "goodix_ts",
.swnode = &whitelabel_tm800a550l_goodix_node,
},
.adapter_path = "\\_SB_.I2C2",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x44,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
},
}, {
/* kxcj91008 accelerometer */
.board_info = {
.type = "kxcj91008",
.addr = 0x0f,
.dev_name = "kxcj91008",
.swnode = &whitelabel_tm800a550l_accel_node,
},
.adapter_path = "\\_SB_.I2C3",
},
};
const struct x86_dev_info whitelabel_tm800a550l_info __initconst = {
.i2c_client_info = whitelabel_tm800a550l_i2c_clients,
.i2c_client_count = ARRAY_SIZE(whitelabel_tm800a550l_i2c_clients),
.gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/*
* Vexia EDU ATLA 10 tablet 5V, Android 4.4 + Guadalinex Ubuntu tablet
* distributed to schools in the Spanish Andalucía region.
*/
static const struct property_entry vexia_edu_atla10_5v_touchscreen_props[] = {
PROPERTY_ENTRY_U32("hid-descr-addr", 0x0000),
PROPERTY_ENTRY_U32("post-reset-deassert-delay-ms", 120),
PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[1], 26, GPIO_ACTIVE_LOW),
{ }
};
static const struct software_node vexia_edu_atla10_5v_touchscreen_node = {
.properties = vexia_edu_atla10_5v_touchscreen_props,
};
static const struct x86_i2c_client_info vexia_edu_atla10_5v_i2c_clients[] __initconst = {
{
/* kxcjk1013 accelerometer */
.board_info = {
.type = "kxcjk1013",
.addr = 0x0f,
.dev_name = "kxcjk1013",
},
.adapter_path = "\\_SB_.I2C3",
}, {
/* touchscreen controller */
.board_info = {
.type = "hid-over-i2c",
.addr = 0x38,
.dev_name = "FTSC1000",
.swnode = &vexia_edu_atla10_5v_touchscreen_node,
},
.adapter_path = "\\_SB_.I2C4",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x44,
.trigger = ACPI_LEVEL_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
},
}
};
const struct x86_dev_info vexia_edu_atla10_5v_info __initconst = {
.i2c_client_info = vexia_edu_atla10_5v_i2c_clients,
.i2c_client_count = ARRAY_SIZE(vexia_edu_atla10_5v_i2c_clients),
.gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/*
* Vexia EDU ATLA 10 tablet 9V, Android 4.2 + Guadalinex Ubuntu tablet
* distributed to schools in the Spanish Andalucía region.
*/
static const char * const crystal_cove_pwrsrc_psy[] = { "crystal_cove_pwrsrc" };
static const struct property_entry vexia_edu_atla10_9v_ulpmc_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", crystal_cove_pwrsrc_psy),
{ }
};
static const struct software_node vexia_edu_atla10_9v_ulpmc_node = {
.properties = vexia_edu_atla10_9v_ulpmc_props,
};
static const char * const vexia_edu_atla10_9v_accel_mount_matrix[] = {
"0", "-1", "0",
"1", "0", "0",
"0", "0", "1"
};
static const struct property_entry vexia_edu_atla10_9v_accel_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", vexia_edu_atla10_9v_accel_mount_matrix),
{ }
};
static const struct software_node vexia_edu_atla10_9v_accel_node = {
.properties = vexia_edu_atla10_9v_accel_props,
};
static const struct property_entry vexia_edu_atla10_9v_touchscreen_props[] = {
PROPERTY_ENTRY_U32("hid-descr-addr", 0x0000),
PROPERTY_ENTRY_U32("post-reset-deassert-delay-ms", 120),
PROPERTY_ENTRY_GPIO("reset-gpios", &baytrail_gpiochip_nodes[0], 60, GPIO_ACTIVE_LOW),
{ }
};
static const struct software_node vexia_edu_atla10_9v_touchscreen_node = {
.properties = vexia_edu_atla10_9v_touchscreen_props,
};
static const struct property_entry vexia_edu_atla10_9v_pmic_props[] = {
PROPERTY_ENTRY_BOOL("linux,register-pwrsrc-power_supply"),
{ }
};
static const struct software_node vexia_edu_atla10_9v_pmic_node = {
.properties = vexia_edu_atla10_9v_pmic_props,
};
static const struct x86_i2c_client_info vexia_edu_atla10_9v_i2c_clients[] __initconst = {
{
/* I2C attached embedded controller, used to access fuel-gauge */
.board_info = {
.type = "vexia_atla10_ec",
.addr = 0x76,
.dev_name = "ulpmc",
.swnode = &vexia_edu_atla10_9v_ulpmc_node,
},
.adapter_path = "0000:00:18.1",
}, {
/* RT5642 audio codec */
.board_info = {
.type = "rt5640",
.addr = 0x1c,
.dev_name = "rt5640",
},
.adapter_path = "0000:00:18.2",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_GPIOINT,
.chip = "INT33FC:02",
.index = 4,
.trigger = ACPI_EDGE_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
.con_id = "rt5640_irq",
},
}, {
/* kxtj21009 accelerometer */
.board_info = {
.type = "kxtj21009",
.addr = 0x0f,
.dev_name = "kxtj21009",
.swnode = &vexia_edu_atla10_9v_accel_node,
},
.adapter_path = "0000:00:18.5",
}, {
/* FT5416DQ9 touchscreen controller */
.board_info = {
.type = "hid-over-i2c",
.addr = 0x38,
.dev_name = "FTSC1000",
.swnode = &vexia_edu_atla10_9v_touchscreen_node,
},
.adapter_path = "0000:00:18.6",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x45,
.trigger = ACPI_LEVEL_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
},
}, {
/* Crystal Cove PMIC */
.board_info = {
.type = "intel_soc_pmic_crc",
.addr = 0x6e,
.dev_name = "intel_soc_pmic_crc",
.swnode = &vexia_edu_atla10_9v_pmic_node,
},
.adapter_path = "0000:00:18.7",
.irq_data = {
.type = X86_ACPI_IRQ_TYPE_APIC,
.index = 0x43,
.trigger = ACPI_LEVEL_SENSITIVE,
.polarity = ACPI_ACTIVE_HIGH,
},
}
};
static const struct x86_serdev_info vexia_edu_atla10_9v_serdevs[] __initconst = {
{
.ctrl.pci.devfn = PCI_DEVFN(0x1e, 3),
.ctrl_devname = "serial0",
.serdev_hid = "OBDA8723",
},
};
static int __init vexia_edu_atla10_9v_init(struct device *dev)
{
struct pci_dev *pdev;
int ret;
/* Enable the Wifi module by setting the wifi_enable pin to 1 */
ret = x86_android_tablet_get_gpiod("INT33FC:02", 20, "wifi_enable",
false, GPIOD_OUT_HIGH, NULL);
if (ret)
return ret;
/* Reprobe the SDIO controller to enumerate the now enabled Wifi module */
pdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0x11, 0));
if (!pdev) {
pr_warn("Could not get PCI SDIO at devfn 0x%02x\n", PCI_DEVFN(0x11, 0));
return 0;
}
ret = device_reprobe(&pdev->dev);
if (ret)
pci_warn(pdev, "Reprobing error: %d\n", ret);
pci_dev_put(pdev);
return 0;
}
const struct x86_dev_info vexia_edu_atla10_9v_info __initconst = {
.i2c_client_info = vexia_edu_atla10_9v_i2c_clients,
.i2c_client_count = ARRAY_SIZE(vexia_edu_atla10_9v_i2c_clients),
.serdev_info = vexia_edu_atla10_9v_serdevs,
.serdev_count = ARRAY_SIZE(vexia_edu_atla10_9v_serdevs),
.init = vexia_edu_atla10_9v_init,
.use_pci = true,
.gpiochip_type = X86_GPIOCHIP_BAYTRAIL,
};
/*
* The firmware node for ktd2026 on Xaomi pad2. It composed of a RGB LED node
* with three subnodes for each color (B/G/R). The RGB LED node is named
* "multi-led" to align with the name in the device tree.
*/
/* Main firmware node for ktd2026 */
static const struct software_node ktd2026_node = {
.name = "ktd2026",
};
static const struct property_entry ktd2026_rgb_led_props[] = {
PROPERTY_ENTRY_U32("reg", 0),
PROPERTY_ENTRY_U32("color", LED_COLOR_ID_RGB),
PROPERTY_ENTRY_STRING("label", "mipad2:rgb:indicator"),
PROPERTY_ENTRY_STRING("linux,default-trigger", "bq27520-0-charging-orange-full-green"),
{ }
};
static const struct software_node ktd2026_rgb_led_node = {
.name = "multi-led",
.properties = ktd2026_rgb_led_props,
.parent = &ktd2026_node,
};
static const struct property_entry ktd2026_blue_led_props[] = {
PROPERTY_ENTRY_U32("reg", 0),
PROPERTY_ENTRY_U32("color", LED_COLOR_ID_BLUE),
{ }
};
static const struct software_node ktd2026_blue_led_node = {
.properties = ktd2026_blue_led_props,
.parent = &ktd2026_rgb_led_node,
};
static const struct property_entry ktd2026_green_led_props[] = {
PROPERTY_ENTRY_U32("reg", 1),
PROPERTY_ENTRY_U32("color", LED_COLOR_ID_GREEN),
{ }
};
static const struct software_node ktd2026_green_led_node = {
.properties = ktd2026_green_led_props,
.parent = &ktd2026_rgb_led_node,
};
static const struct property_entry ktd2026_red_led_props[] = {
PROPERTY_ENTRY_U32("reg", 2),
PROPERTY_ENTRY_U32("color", LED_COLOR_ID_RED),
{ }
};
static const struct software_node ktd2026_red_led_node = {
.properties = ktd2026_red_led_props,
.parent = &ktd2026_rgb_led_node,
};
static const struct software_node *ktd2026_node_group[] = {
&ktd2026_node,
&ktd2026_rgb_led_node,
&ktd2026_red_led_node,
&ktd2026_green_led_node,
&ktd2026_blue_led_node,
NULL
};
/*
* For the LEDs which backlight the Menu / Home / Back capacitive buttons on
* the bottom bezel. These are attached to a TPS61158 LED controller which
* is controlled by the "pwm_soc_lpss_2" PWM output.
*/
#define XIAOMI_MIPAD2_LED_PERIOD_NS 19200
#define XIAOMI_MIPAD2_LED_MAX_DUTY_NS 6000 /* From Android kernel */
static struct pwm_device *xiaomi_mipad2_led_pwm;
static int xiaomi_mipad2_brightness_set(struct led_classdev *led_cdev,
enum led_brightness val)
{
struct pwm_state state = {
.period = XIAOMI_MIPAD2_LED_PERIOD_NS,
.duty_cycle = XIAOMI_MIPAD2_LED_MAX_DUTY_NS * val / LED_FULL,
/* Always set PWM enabled to avoid the pin floating */
.enabled = true,
};
return pwm_apply_might_sleep(xiaomi_mipad2_led_pwm, &state);
}
static int __init xiaomi_mipad2_init(struct device *dev)
{
struct led_classdev *led_cdev;
xiaomi_mipad2_led_pwm = devm_pwm_get(dev, "pwm_soc_lpss_2");
if (IS_ERR(xiaomi_mipad2_led_pwm))
return dev_err_probe(dev, PTR_ERR(xiaomi_mipad2_led_pwm), "getting pwm\n");
led_cdev = devm_kzalloc(dev, sizeof(*led_cdev), GFP_KERNEL);
if (!led_cdev)
return -ENOMEM;
led_cdev->name = "mipad2:white:touch-buttons-backlight";
led_cdev->max_brightness = LED_FULL;
led_cdev->default_trigger = "input-events";
led_cdev->brightness_set_blocking = xiaomi_mipad2_brightness_set;
/* Turn LED off during suspend */
led_cdev->flags = LED_CORE_SUSPENDRESUME;
return devm_led_classdev_register(dev, led_cdev);
}
/*
* If the EFI bootloader is not Xiaomi's own signed Android loader, then the
* Xiaomi Mi Pad 2 X86 tablet sets OSID in the DSDT to 1 (Windows), causing
* a bunch of devices to be hidden.
*
* This takes care of instantiating the hidden devices manually.
*/
static const struct x86_i2c_client_info xiaomi_mipad2_i2c_clients[] __initconst = {
{
/* BQ27520 fuel-gauge */
.board_info = {
.type = "bq27520",
.addr = 0x55,
.dev_name = "bq27520",
.swnode = &fg_bq25890_supply_node,
},
.adapter_path = "\\_SB_.PCI0.I2C1",
}, {
/* KTD2026 RGB notification LED controller */
.board_info = {
.type = "ktd2026",
.addr = 0x30,
.dev_name = "ktd2026",
.swnode = &ktd2026_node,
},
.adapter_path = "\\_SB_.PCI0.I2C3",
},
};
const struct x86_dev_info xiaomi_mipad2_info __initconst = {
.i2c_client_info = xiaomi_mipad2_i2c_clients,
.i2c_client_count = ARRAY_SIZE(xiaomi_mipad2_i2c_clients),
.swnode_group = ktd2026_node_group,
.init = xiaomi_mipad2_init,
};
@@ -0,0 +1,186 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Shared psy info for 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>
*/
#include <linux/gpio/machine.h>
#include <linux/gpio/property.h>
#include <linux/platform_device.h>
#include <linux/power/bq24190_charger.h>
#include <linux/property.h>
#include <linux/regulator/machine.h>
#include "shared-psy-info.h"
#include "x86-android-tablets.h"
/* Generic / shared charger / battery settings */
const char * const tusb1211_chg_det_psy[] = { "tusb1211-charger-detect" };
const char * const bq24190_psy[] = { "bq24190-charger" };
const char * const bq25890_psy[] = { "bq25890-charger-0" };
static const struct property_entry fg_bq24190_supply_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_psy),
{ }
};
const struct software_node fg_bq24190_supply_node = {
.properties = fg_bq24190_supply_props,
};
static const struct property_entry fg_bq25890_supply_props[] = {
PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq25890_psy),
{ }
};
const struct software_node fg_bq25890_supply_node = {
.properties = fg_bq25890_supply_props,
};
static const u32 generic_lipo_battery_ovc_cap_celcius[] = { 25 };
static const u32 generic_lipo_4v2_battery_ovc_cap_table0[] = {
4200000, 100,
4150000, 95,
4110000, 90,
4075000, 85,
4020000, 80,
3982500, 75,
3945000, 70,
3907500, 65,
3870000, 60,
3853333, 55,
3836667, 50,
3820000, 45,
3803333, 40,
3786667, 35,
3770000, 30,
3750000, 25,
3730000, 20,
3710000, 15,
3690000, 10,
3610000, 5,
3350000, 0
};
static const u32 generic_lipo_hv_4v35_battery_ovc_cap_table0[] = {
4300000, 100,
4250000, 96,
4200000, 91,
4150000, 86,
4110000, 82,
4075000, 77,
4020000, 73,
3982500, 68,
3945000, 64,
3907500, 59,
3870000, 55,
3853333, 50,
3836667, 45,
3820000, 41,
3803333, 36,
3786667, 32,
3770000, 27,
3750000, 23,
3730000, 18,
3710000, 14,
3690000, 9,
3610000, 5,
3350000, 0
};
/* Standard LiPo (max 4.2V) settings used by most devs with a LiPo battery */
static const struct property_entry generic_lipo_4v2_battery_props[] = {
PROPERTY_ENTRY_STRING("compatible", "simple-battery"),
PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion-polymer"),
PROPERTY_ENTRY_U32("precharge-current-microamp", 256000),
PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000),
PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 2048000),
PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4208000),
PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000),
PROPERTY_ENTRY_U32_ARRAY("ocv-capacity-celsius",
generic_lipo_battery_ovc_cap_celcius),
PROPERTY_ENTRY_U32_ARRAY("ocv-capacity-table-0",
generic_lipo_4v2_battery_ovc_cap_table0),
{ }
};
const struct software_node generic_lipo_4v2_battery_node = {
.properties = generic_lipo_4v2_battery_props,
};
const struct software_node *generic_lipo_4v2_battery_swnodes[] = {
&generic_lipo_4v2_battery_node,
NULL
};
/* LiPo HighVoltage (max 4.35V) settings used by most devs with a HV battery */
static const struct property_entry generic_lipo_hv_4v35_battery_props[] = {
PROPERTY_ENTRY_STRING("compatible", "simple-battery"),
PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion"),
PROPERTY_ENTRY_U32("precharge-current-microamp", 256000),
PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000),
PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 1856000),
PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4352000),
PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000),
PROPERTY_ENTRY_U32_ARRAY("ocv-capacity-celsius",
generic_lipo_battery_ovc_cap_celcius),
PROPERTY_ENTRY_U32_ARRAY("ocv-capacity-table-0",
generic_lipo_hv_4v35_battery_ovc_cap_table0),
{ }
};
const struct software_node generic_lipo_hv_4v35_battery_node = {
.properties = generic_lipo_hv_4v35_battery_props,
};
const struct software_node *generic_lipo_hv_4v35_battery_swnodes[] = {
&generic_lipo_hv_4v35_battery_node,
NULL
};
/* For enabling the bq24190 5V boost based on id-pin */
static struct regulator_consumer_supply intel_int3496_consumer = {
.supply = "vbus",
.dev_name = "intel-int3496",
};
static const struct regulator_init_data bq24190_vbus_init_data = {
.constraints = {
.name = "bq24190_vbus",
.valid_ops_mask = REGULATOR_CHANGE_STATUS,
},
.consumer_supplies = &intel_int3496_consumer,
.num_consumer_supplies = 1,
};
struct bq24190_platform_data bq24190_pdata = {
.regulator_init_data = &bq24190_vbus_init_data,
};
const char * const bq24190_modules[] __initconst = {
"intel_crystal_cove_charger", /* For the bq24190 IRQ */
"bq24190_charger", /* For the Vbus regulator for intel-int3496 */
NULL
};
static const struct property_entry int3496_reference_props[] __initconst = {
PROPERTY_ENTRY_GPIO("vbus-gpios", &baytrail_gpiochip_nodes[1], 15, GPIO_ACTIVE_HIGH),
PROPERTY_ENTRY_GPIO("mux-gpios", &baytrail_gpiochip_nodes[2], 1, GPIO_ACTIVE_HIGH),
PROPERTY_ENTRY_GPIO("id-gpios", &baytrail_gpiochip_nodes[2], 18, GPIO_ACTIVE_HIGH),
{ }
};
/* Generic pdevs array and gpio-lookups for micro USB ID pin handling */
const struct platform_device_info int3496_pdevs[] __initconst = {
{
/* For micro USB ID pin handling */
.name = "intel-int3496",
.id = PLATFORM_DEVID_NONE,
.properties = int3496_reference_props,
},
};
@@ -0,0 +1,35 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
*
* Shared psy info for 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>
*/
#ifndef __PDX86_SHARED_PSY_INFO_H
#define __PDX86_SHARED_PSY_INFO_H
struct bq24190_platform_data;
struct platform_device_info;
struct software_node;
extern const char * const tusb1211_chg_det_psy[];
extern const char * const bq24190_psy[];
extern const char * const bq25890_psy[];
extern const struct software_node fg_bq24190_supply_node;
extern const struct software_node fg_bq25890_supply_node;
extern const struct software_node generic_lipo_4v2_battery_node;
extern const struct software_node *generic_lipo_4v2_battery_swnodes[];
extern const struct software_node generic_lipo_hv_4v35_battery_node;
extern const struct software_node *generic_lipo_hv_4v35_battery_swnodes[];
extern struct bq24190_platform_data bq24190_pdata;
extern const char * const bq24190_modules[];
extern const struct platform_device_info int3496_pdevs[];
#endif
@@ -0,0 +1,261 @@
// SPDX-License-Identifier: GPL-2.0
/*
* power_supply class (battery) driver for the I2C attached embedded controller
* found on Vexia EDU ATLA 10 (9V version) tablets.
*
* This is based on the ACPI Battery device in the DSDT which should work
* expect that it expects the I2C controller to be enumerated as an ACPI
* device and the tablet's BIOS enumerates all LPSS devices as PCI devices
* (and changing the LPSS BIOS settings from PCI -> ACPI does not work).
*
* Copyright (c) 2024 Hans de Goede <hansg@kernel.org>
*/
#include <linux/bits.h>
#include <linux/devm-helpers.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/power_supply.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <asm/byteorder.h>
/* State field uses ACPI Battery spec status bits */
#define ACPI_BATTERY_STATE_DISCHARGING BIT(0)
#define ACPI_BATTERY_STATE_CHARGING BIT(1)
#define ATLA10_EC_BATTERY_STATE_COMMAND 0x87
#define ATLA10_EC_BATTERY_INFO_COMMAND 0x88
/* From broken ACPI battery device in DSDT */
#define ATLA10_EC_VOLTAGE_MIN_DESIGN_uV 3750000
/* Update data every 5 seconds */
#define UPDATE_INTERVAL_JIFFIES (5 * HZ)
struct atla10_ec_battery_state {
u8 status; /* Using ACPI Battery spec status bits */
u8 capacity; /* Percent */
__le16 charge_now_mAh;
__le16 voltage_now_mV;
__le16 current_now_mA;
__le16 charge_full_mAh;
__le16 temp; /* centi degrees Celsius */
} __packed;
struct atla10_ec_battery_info {
__le16 charge_full_design_mAh;
__le16 voltage_now_mV; /* Should be design voltage, but is not ? */
__le16 charge_full_design2_mAh;
} __packed;
struct atla10_ec_data {
struct i2c_client *client;
struct power_supply *psy;
struct delayed_work work;
struct mutex update_lock;
struct atla10_ec_battery_info info;
struct atla10_ec_battery_state state;
bool valid; /* true if state is valid */
unsigned long last_update; /* In jiffies */
};
static int atla10_ec_cmd(struct atla10_ec_data *data, u8 cmd, u8 len, u8 *values)
{
struct device *dev = &data->client->dev;
u8 buf[I2C_SMBUS_BLOCK_MAX];
int ret;
ret = i2c_smbus_read_block_data(data->client, cmd, buf);
if (ret != len) {
dev_err(dev, "I2C command 0x%02x error: %d\n", cmd, ret);
return -EIO;
}
memcpy(values, buf, len);
return 0;
}
static int atla10_ec_update(struct atla10_ec_data *data)
{
int ret;
if (data->valid && time_before(jiffies, data->last_update + UPDATE_INTERVAL_JIFFIES))
return 0;
ret = atla10_ec_cmd(data, ATLA10_EC_BATTERY_STATE_COMMAND,
sizeof(data->state), (u8 *)&data->state);
if (ret)
return ret;
data->last_update = jiffies;
data->valid = true;
return 0;
}
static int atla10_ec_psy_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct atla10_ec_data *data = power_supply_get_drvdata(psy);
int charge_now_mAh, charge_full_mAh, ret;
guard(mutex)(&data->update_lock);
ret = atla10_ec_update(data);
if (ret)
return ret;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
if (data->state.status & ACPI_BATTERY_STATE_DISCHARGING)
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
else if (data->state.status & ACPI_BATTERY_STATE_CHARGING)
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else if (data->state.capacity == 100)
val->intval = POWER_SUPPLY_STATUS_FULL;
else
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = data->state.capacity;
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
/*
* The EC has a bug where it reports charge-full-design as
* charge-now when the battery is full. Clamp charge-now to
* charge-full to workaround this.
*/
charge_now_mAh = le16_to_cpu(data->state.charge_now_mAh);
charge_full_mAh = le16_to_cpu(data->state.charge_full_mAh);
val->intval = min(charge_now_mAh, charge_full_mAh) * 1000;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = le16_to_cpu(data->state.voltage_now_mV) * 1000;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = le16_to_cpu(data->state.current_now_mA) * 1000;
/*
* Documentation/ABI/testing/sysfs-class-power specifies
* negative current for discharging.
*/
if (data->state.status & ACPI_BATTERY_STATE_DISCHARGING)
val->intval = -val->intval;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
val->intval = le16_to_cpu(data->state.charge_full_mAh) * 1000;
break;
case POWER_SUPPLY_PROP_TEMP:
val->intval = le16_to_cpu(data->state.temp) / 10;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval = le16_to_cpu(data->info.charge_full_design_mAh) * 1000;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
val->intval = ATLA10_EC_VOLTAGE_MIN_DESIGN_uV;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = 1;
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LIPO;
break;
default:
return -EINVAL;
}
return 0;
}
static void atla10_ec_external_power_changed_work(struct work_struct *work)
{
struct atla10_ec_data *data = container_of(work, struct atla10_ec_data, work.work);
dev_dbg(&data->client->dev, "External power changed\n");
data->valid = false;
power_supply_changed(data->psy);
}
static void atla10_ec_external_power_changed(struct power_supply *psy)
{
struct atla10_ec_data *data = power_supply_get_drvdata(psy);
/* After charger plug in/out wait 0.5s for things to stabilize */
mod_delayed_work(system_percpu_wq, &data->work, HZ / 2);
}
static const enum power_supply_property atla10_ec_psy_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_TECHNOLOGY,
};
static const struct power_supply_desc atla10_ec_psy_desc = {
.name = "atla10_ec_battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = atla10_ec_psy_props,
.num_properties = ARRAY_SIZE(atla10_ec_psy_props),
.get_property = atla10_ec_psy_get_property,
.external_power_changed = atla10_ec_external_power_changed,
};
static int atla10_ec_probe(struct i2c_client *client)
{
struct power_supply_config psy_cfg = { };
struct device *dev = &client->dev;
struct atla10_ec_data *data;
int ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
psy_cfg.drv_data = data;
data->client = client;
ret = devm_mutex_init(dev, &data->update_lock);
if (ret)
return ret;
ret = devm_delayed_work_autocancel(dev, &data->work,
atla10_ec_external_power_changed_work);
if (ret)
return ret;
ret = atla10_ec_cmd(data, ATLA10_EC_BATTERY_INFO_COMMAND,
sizeof(data->info), (u8 *)&data->info);
if (ret)
return ret;
data->psy = devm_power_supply_register(dev, &atla10_ec_psy_desc, &psy_cfg);
return PTR_ERR_OR_ZERO(data->psy);
}
static const struct i2c_device_id atla10_ec_id_table[] = {
{ .name = "vexia_atla10_ec" },
{ }
};
MODULE_DEVICE_TABLE(i2c, atla10_ec_id_table);
static struct i2c_driver atla10_ec_driver = {
.driver = {
.name = "vexia_atla10_ec",
},
.probe = atla10_ec_probe,
.id_table = atla10_ec_id_table,
};
module_i2c_driver(atla10_ec_driver);
MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
MODULE_DESCRIPTION("Battery driver for Vexia EDU ATLA 10 tablet EC");
MODULE_LICENSE("GPL");
@@ -0,0 +1,138 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
*
* DMI based code to deal with broken DSDTs on X86 tablets which ship with
* Android as (part of) the factory image. 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>
*/
#ifndef __PDX86_X86_ANDROID_TABLETS_H
#define __PDX86_X86_ANDROID_TABLETS_H
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/irqdomain_defs.h>
#include <linux/device-id/dmi.h>
#include <linux/spi/spi.h>
struct gpio_desc;
struct platform_device_info;
struct software_node;
/*
* Helpers to get Linux IRQ numbers given a description of the IRQ source
* (either IOAPIC index, or GPIO chip name + pin-number).
*/
enum x86_acpi_irq_type {
X86_ACPI_IRQ_TYPE_NONE,
X86_ACPI_IRQ_TYPE_APIC,
X86_ACPI_IRQ_TYPE_GPIOINT,
X86_ACPI_IRQ_TYPE_PMIC,
};
enum x86_gpiochip_type {
X86_GPIOCHIP_UNSPECIFIED = 0,
X86_GPIOCHIP_BAYTRAIL,
X86_GPIOCHIP_CHERRYVIEW,
};
struct x86_acpi_irq_data {
char *chip; /* GPIO chip label (GPIOINT) or PMIC ACPI path (PMIC) */
enum x86_acpi_irq_type type;
enum irq_domain_bus_token domain;
int index;
int trigger; /* ACPI_EDGE_SENSITIVE / ACPI_LEVEL_SENSITIVE */
int polarity; /* ACPI_ACTIVE_HIGH / ACPI_ACTIVE_LOW / ACPI_ACTIVE_BOTH */
bool free_gpio; /* Release GPIO after getting IRQ (for TYPE_GPIOINT) */
const char *con_id;
};
/* Structs to describe devices to instantiate */
struct x86_i2c_client_info {
struct i2c_board_info board_info;
char *adapter_path;
struct x86_acpi_irq_data irq_data;
};
struct x86_spi_dev_info {
struct spi_board_info board_info;
char *ctrl_path;
struct x86_acpi_irq_data irq_data;
};
struct x86_serdev_info {
union {
struct {
const char *hid;
const char *uid;
} acpi;
struct {
unsigned int devfn;
} pci;
} ctrl;
const char *ctrl_devname;
/*
* ATM the serdev core only supports of or ACPI matching; and so far all
* Android x86 tablets DSDTs have usable serdev nodes, but sometimes
* under the wrong controller. So we just tie the existing serdev ACPI
* node to the right controller.
*/
const char *serdev_hid;
};
struct x86_dev_info {
const char * const *modules;
const struct software_node **swnode_group;
const struct x86_i2c_client_info *i2c_client_info;
const struct x86_spi_dev_info *spi_dev_info;
const struct platform_device_info *pdev_info;
const struct x86_serdev_info *serdev_info;
const struct software_node **gpio_button_swnodes;
int i2c_client_count;
int spi_dev_count;
int pdev_count;
int serdev_count;
int (*init)(struct device *dev);
void (*exit)(void);
bool use_pci;
enum x86_gpiochip_type gpiochip_type;
};
int x86_android_tablet_get_gpiod(const char *chip, int pin, const char *con_id,
bool active_low, enum gpiod_flags dflags,
struct gpio_desc **desc);
int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data);
/* Software nodes representing GPIO chips used by various tablets */
extern const struct software_node baytrail_gpiochip_nodes[];
extern const struct software_node cherryview_gpiochip_nodes[];
/*
* Extern declarations of x86_dev_info structs so there can be a single
* MODULE_DEVICE_TABLE(dmi, ...), while splitting the board descriptions.
*/
extern const struct x86_dev_info acer_a1_840_info;
extern const struct x86_dev_info acer_b1_750_info;
extern const struct x86_dev_info advantech_mica_071_info;
extern const struct x86_dev_info asus_me176c_info;
extern const struct x86_dev_info asus_tf103c_info;
extern const struct x86_dev_info chuwi_hi8_info;
extern const struct x86_dev_info cyberbook_t116_info;
extern const struct x86_dev_info czc_p10t;
extern const struct x86_dev_info lenovo_yogabook_x90_info;
extern const struct x86_dev_info lenovo_yogabook_x91_info;
extern const struct x86_dev_info lenovo_yoga_tab2_830_1050_info;
extern const struct x86_dev_info lenovo_yoga_tab2_1380_info;
extern const struct x86_dev_info lenovo_yt3_info;
extern const struct x86_dev_info medion_lifetab_s10346_info;
extern const struct x86_dev_info nextbook_ares8_info;
extern const struct x86_dev_info nextbook_ares8a_info;
extern const struct x86_dev_info peaq_c1010_info;
extern const struct x86_dev_info whitelabel_tm800a550l_info;
extern const struct x86_dev_info vexia_edu_atla10_5v_info;
extern const struct x86_dev_info vexia_edu_atla10_9v_info;
extern const struct x86_dev_info xiaomi_mipad2_info;
extern const struct dmi_system_id x86_android_tablet_ids[];
#endif