Skip to content
Navigation Menu
Toggle navigation
Sign in
In this repository
All GitHub Enterprise
↵
Jump to
↵
No suggested jump to results
In this repository
All GitHub Enterprise
↵
Jump to
↵
In this organization
All GitHub Enterprise
↵
Jump to
↵
In this repository
All GitHub Enterprise
↵
Jump to
↵
Sign in
Reseting focus
You signed in with another tab or window.
Reload
to refresh your session.
You signed out in another tab or window.
Reload
to refresh your session.
You switched accounts on another tab or window.
Reload
to refresh your session.
Dismiss alert
{{ message }}
mariux64
/
linux
Public
Notifications
You must be signed in to change notification settings
Fork
0
Star
0
Code
Issues
2
Pull requests
0
Actions
Projects
0
Wiki
Security
Insights
Additional navigation options
Code
Issues
Pull requests
Actions
Projects
Wiki
Security
Insights
Files
442bf56
Documentation
LICENSES
arch
block
certs
crypto
drivers
accessibility
acpi
amba
android
ata
atm
auxdisplay
base
bcma
block
bluetooth
bus
cdrom
char
clk
clocksource
comedi
connector
counter
cpufreq
cpuidle
crypto
cxl
dax
dca
devfreq
dio
dma-buf
dma
edac
eisa
extcon
firewire
firmware
fpga
fsi
gnss
gpio
gpu
greybus
hid
hsi
hv
hwmon
hwspinlock
hwtracing
i2c
i3c
idle
iio
infiniband
input
interconnect
iommu
ipack
irqchip
isdn
leds
macintosh
mailbox
mcb
md
media
memory
memstick
message
mfd
misc
mmc
most
mtd
mux
net
nfc
ntb
nubus
nvdimm
nvme
nvmem
of
opp
parisc
parport
pci
pcmcia
perf
phy
pinctrl
platform
chrome
goldfish
mellanox
mips
olpc
surface
x86
dell
intel
Kconfig
Makefile
acer-wireless.c
acer-wmi.c
acerhdf.c
adv_swbutton.c
amd-pmc.c
amilo-rfkill.c
apple-gmux.c
asus-laptop.c
asus-nb-wmi.c
asus-tf103c-dock.c
asus-wireless.c
asus-wmi.c
asus-wmi.h
barco-p50-gpio.c
classmate-laptop.c
compal-laptop.c
dual_accel_detect.h
eeepc-laptop.c
eeepc-wmi.c
firmware_attributes_class.c
firmware_attributes_class.h
fujitsu-laptop.c
fujitsu-tablet.c
gigabyte-wmi.c
gpd-pocket-fan.c
hdaps.c
hp-wmi.c
hp_accel.c
huawei-wmi.c
i2c-multi-instantiate.c
ibm_rtl.c
ideapad-laptop.c
intel_ips.c
intel_ips.h
intel_scu_ipc.c
intel_scu_ipcutil.c
intel_scu_pcidrv.c
intel_scu_pltdrv.c
intel_scu_wdt.c
lenovo-yogabook-wmi.c
lg-laptop.c
meraki-mx100.c
mlx-platform.c
msi-laptop.c
msi-wmi.c
mxm-wmi.c
nvidia-wmi-ec-backlight.c
panasonic-laptop.c
pcengines-apuv2.c
peaq-wmi.c
pmc_atom.c
samsung-laptop.c
samsung-q10.c
simatic-ipc.c
sony-laptop.c
system76_acpi.c
tc1100-wmi.c
think-lmi.c
think-lmi.h
thinkpad_acpi.c
topstar-laptop.c
toshiba-wmi.c
toshiba_acpi.c
toshiba_bluetooth.c
toshiba_haps.c
touchscreen_dmi.c
uv_sysfs.c
wireless-hotkey.c
wmi-bmof.c
wmi.c
x86-android-tablets.c
xiaomi-wmi.c
xo1-rfkill.c
xo15-ebook.c
Kconfig
Makefile
pnp
power
powercap
pps
ps3
ptp
pwm
rapidio
ras
regulator
remoteproc
reset
rpmsg
rtc
s390
sbus
scsi
sh
siox
slimbus
soc
soundwire
spi
spmi
ssb
staging
target
tc
tee
thermal
thunderbolt
tty
uio
usb
vdpa
vfio
vhost
video
virt
virtio
visorbus
vlynq
vme
w1
watchdog
xen
zorro
Kconfig
Makefile
fs
include
init
ipc
kernel
lib
mm
net
samples
scripts
security
sound
tools
usr
virt
.clang-format
.cocciconfig
.get_maintainer.ignore
.gitattributes
.gitignore
.mailmap
COPYING
CREDITS
Kbuild
Kconfig
MAINTAINERS
Makefile
README
Breadcrumbs
linux
/
drivers
/
platform
/
x86
/
x86-android-tablets.c
Blame
Blame
Latest commit
History
History
904 lines (797 loc) · 23.5 KB
Breadcrumbs
linux
/
drivers
/
platform
/
x86
/
x86-android-tablets.c
Top
File metadata and controls
Code
Blame
904 lines (797 loc) · 23.5 KB
Raw
// SPDX-License-Identifier: GPL-2.0+ /* * 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 Hans de Goede <hdegoede@redhat.com> */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/acpi.h> #include <linux/dmi.h> #include <linux/gpio/consumer.h> #include <linux/gpio/driver.h> #include <linux/gpio/machine.h> #include <linux/i2c.h> #include <linux/irq.h> #include <linux/irqdomain.h> #include <linux/module.h> #include <linux/mod_devicetable.h> #include <linux/platform_device.h> #include <linux/power/bq24190_charger.h> #include <linux/serdev.h> #include <linux/string.h> /* For gpio_get_desc() which is EXPORT_SYMBOL_GPL() */ #include "../../gpio/gpiolib.h" #include "../../gpio/gpiolib-acpi.h" /* * Helper code 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, }; 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 */ }; static int gpiochip_find_match_label(struct gpio_chip *gc, void *data) { return gc->label && !strcmp(gc->label, data); } static 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; struct gpio_chip *chip; unsigned int irq_type; acpi_handle handle; acpi_status status; int irq, ret; switch (data->type) { case X86_ACPI_IRQ_TYPE_APIC: 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 */ chip = gpiochip_find(data->chip, gpiochip_find_match_label); if (!chip) { pr_err("error cannot find GPIO chip %s\n", data->chip); return -ENODEV; } gpiod = gpiochip_get_desc(chip, data->index); if (IS_ERR(gpiod)) { ret = PTR_ERR(gpiod); pr_err("error %d getting GPIO %s %d\n", ret, data->chip, data->index); 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); 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; } acpi_bus_get_device(handle, &adev); 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; } } struct x86_i2c_client_info { struct i2c_board_info board_info; char *adapter_path; struct x86_acpi_irq_data irq_data; }; struct x86_serdev_info { const char *ctrl_hid; const char *ctrl_uid; const char *ctrl_devname; /* * ATM the serdev core only supports of or ACPI matching; and sofar 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 { char *invalid_aei_gpiochip; const char * const *modules; struct gpiod_lookup_table * const *gpiod_lookup_tables; const struct x86_i2c_client_info *i2c_client_info; const struct platform_device_info *pdev_info; const struct x86_serdev_info *serdev_info; int i2c_client_count; int pdev_count; int serdev_count; int (*init)(void); void (*exit)(void); }; /* Generic / shared bq24190 settings */ static const char * const bq24190_suppliers[] = { "tusb1210-psy" }; static const struct property_entry bq24190_props[] = { PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_suppliers), PROPERTY_ENTRY_BOOL("omit-battery-class"), PROPERTY_ENTRY_BOOL("disable-reset"), { } }; static const struct software_node bq24190_node = { .properties = bq24190_props, }; /* For enableing 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, }; static struct bq24190_platform_data bq24190_pdata = { .regulator_init_data = &bq24190_vbus_init_data, }; static const char * const bq24190_modules[] __initconst = { "intel_crystal_cove_charger", /* For the bq24190 IRQ */ "bq24190_charger", /* For the Vbus regulator for intel-int3496 */ NULL }; /* Generic pdevs array and gpio-lookups for micro USB ID pin handling */ static const struct platform_device_info int3496_pdevs[] __initconst = { { /* For micro USB ID pin handling */ .name = "intel-int3496", .id = PLATFORM_DEVID_NONE, }, }; static struct gpiod_lookup_table int3496_gpo2_pin22_gpios = { .dev_id = "intel-int3496", .table = { GPIO_LOOKUP("INT33FC:02", 22, "id", GPIO_ACTIVE_HIGH), { } }, }; /* Asus ME176C tablets have an Android factory img 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 x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = { { /* bq24190 battery charger */ .board_info = { .type = "bq24190", .addr = 0x6b, .dev_name = "bq24190", .swnode = &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", }, .adapter_path = "\\_SB_.I2C1", }, { /* ak09911 compass */ .board_info = { .type = "ak09911", .addr = 0x0c, .dev_name = "ak09911", }, .adapter_path = "\\_SB_.I2C5", }, { /* kxtj21009 accel */ .board_info = { .type = "kxtj21009", .addr = 0x0f, .dev_name = "kxtj21009", .swnode = &asus_me176c_accel_node, }, .adapter_path = "\\_SB_.I2C5", }, { /* goodix touchscreen */ .board_info = { .type = "GDIX1001:00", .addr = 0x14, .dev_name = "goodix_ts", }, .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_hid = "80860F0A", .ctrl_uid = "2", .ctrl_devname = "serial0", .serdev_hid = "BCM2E3A", }, }; static struct gpiod_lookup_table asus_me176c_goodix_gpios = { .dev_id = "i2c-goodix_ts", .table = { GPIO_LOOKUP("INT33FC:00", 60, "reset", GPIO_ACTIVE_HIGH), GPIO_LOOKUP("INT33FC:02", 28, "irq", GPIO_ACTIVE_HIGH), { } }, }; static struct gpiod_lookup_table * const asus_me176c_gpios[] = { &int3496_gpo2_pin22_gpios, &asus_me176c_goodix_gpios, NULL }; static 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 = int3496_pdevs, .pdev_count = ARRAY_SIZE(int3496_pdevs), .serdev_info = asus_me176c_serdevs, .serdev_count = ARRAY_SIZE(asus_me176c_serdevs), .gpiod_lookup_tables = asus_me176c_gpios, .modules = bq24190_modules, .invalid_aei_gpiochip = "INT33FC:02", }; /* Asus TF103C tablets have an Android factory img 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 x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst = { { /* bq24190 battery charger */ .board_info = { .type = "bq24190", .addr = 0x6b, .dev_name = "bq24190", .swnode = &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", }, .adapter_path = "\\_SB_.I2C1", }, { /* ak09911 compass */ .board_info = { .type = "ak09911", .addr = 0x0c, .dev_name = "ak09911", }, .adapter_path = "\\_SB_.I2C5", }, { /* kxtj21009 accel */ .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, }, }, }; static struct gpiod_lookup_table * const asus_tf103c_gpios[] = { &int3496_gpo2_pin22_gpios, NULL }; static 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 = int3496_pdevs, .pdev_count = ARRAY_SIZE(int3496_pdevs), .gpiod_lookup_tables = asus_tf103c_gpios, .modules = bq24190_modules, .invalid_aei_gpiochip = "INT33FC:02", }; /* * 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, }, }, }; static 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), }; /* * 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 fwnode 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), { } }; 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 accel */ .board_info = { .type = "kxcj91008", .addr = 0x0f, .dev_name = "kxcj91008", .swnode = &whitelabel_tm800a550l_accel_node, }, .adapter_path = "\\_SB_.I2C3", }, }; static struct gpiod_lookup_table whitelabel_tm800a550l_goodix_gpios = { .dev_id = "i2c-goodix_ts", .table = { GPIO_LOOKUP("INT33FC:01", 26, "reset", GPIO_ACTIVE_HIGH), GPIO_LOOKUP("INT33FC:02", 3, "irq", GPIO_ACTIVE_HIGH), { } }, }; static struct gpiod_lookup_table * const whitelabel_tm800a550l_gpios[] = { &whitelabel_tm800a550l_goodix_gpios, NULL }; static 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), .gpiod_lookup_tables = whitelabel_tm800a550l_gpios, }; /* * 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 char * const bq27520_suppliers[] = { "bq25890-charger" }; static const struct property_entry bq27520_props[] = { PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq27520_suppliers), { } }; static const struct software_node bq27520_node = { .properties = bq27520_props, }; 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 = &bq27520_node, }, .adapter_path = "\\_SB_.PCI0.I2C1", }, { /* KTD2026 RGB notification LED controller */ .board_info = { .type = "ktd2026", .addr = 0x30, .dev_name = "ktd2026", }, .adapter_path = "\\_SB_.PCI0.I2C3", }, }; static 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), }; static const struct dmi_system_id x86_android_tablet_ids[] __initconst = { { /* 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, }, { /* 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); static int i2c_client_count; static int pdev_count; static int serdev_count; static struct i2c_client **i2c_clients; static struct platform_device **pdevs; static struct serdev_device **serdevs; static struct gpiod_lookup_table * const *gpiod_lookup_tables; static void (*exit_handler)(void); 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; acpi_handle handle; acpi_status status; board_info.irq = x86_acpi_irq_helper_get(&client_info->irq_data); if (board_info.irq < 0) return board_info.irq; 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 -ENODEV; } adap = i2c_acpi_find_adapter_by_handle(handle); 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_serdev(const struct x86_serdev_info *info, int idx) { struct acpi_device *ctrl_adev, *serdev_adev; struct serdev_device *serdev; struct device *ctrl_dev; int ret = -ENODEV; ctrl_adev = acpi_dev_get_first_match_dev(info->ctrl_hid, info->ctrl_uid, -1); if (!ctrl_adev) { pr_err("error could not get %s/%s ctrl adev\n", info->ctrl_hid, info->ctrl_uid); return -ENODEV; } 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_adev; } /* get_first_physical_node() returns a weak ref, no need to put() it */ ctrl_dev = acpi_get_first_physical_node(ctrl_adev); if (!ctrl_dev) { pr_err("error could not get %s/%s ctrl physical dev\n", info->ctrl_hid, info->ctrl_uid); goto put_serdev_adev; } /* ctrl_dev now points to the controller's parent, get the controller */ ctrl_dev = device_find_child_by_name(ctrl_dev, info->ctrl_devname); if (!ctrl_dev) { pr_err("error could not get %s/%s %s ctrl dev\n", info->ctrl_hid, info->ctrl_uid, info->ctrl_devname); goto put_serdev_adev; } 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_adev: acpi_dev_put(ctrl_adev); return ret; } static void x86_android_tablet_cleanup(void) { int i; for (i = 0; i < serdev_count; i++) { if (serdevs[i]) serdev_device_remove(serdevs[i]); } kfree(serdevs); for (i = 0; i < pdev_count; i++) platform_device_unregister(pdevs[i]); kfree(pdevs); for (i = 0; i < i2c_client_count; i++) i2c_unregister_device(i2c_clients[i]); kfree(i2c_clients); if (exit_handler) exit_handler(); for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++) gpiod_remove_lookup_table(gpiod_lookup_tables[i]); } static __init int x86_android_tablet_init(void) { const struct x86_dev_info *dev_info; const struct dmi_system_id *id; struct gpio_chip *chip; int i, ret = 0; id = dmi_first_match(x86_android_tablet_ids); if (!id) return -ENODEV; dev_info = id->driver_data; /* * The broken DSDTs on these devices often also include broken * _AEI (ACPI Event Interrupt) handlers, disable these. */ if (dev_info->invalid_aei_gpiochip) { chip = gpiochip_find(dev_info->invalid_aei_gpiochip, gpiochip_find_match_label); if (!chip) { pr_err("error cannot find GPIO chip %s\n", dev_info->invalid_aei_gpiochip); return -ENODEV; } acpi_gpiochip_free_interrupts(chip); } /* * 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]); gpiod_lookup_tables = dev_info->gpiod_lookup_tables; for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++) gpiod_add_lookup_table(gpiod_lookup_tables[i]); if (dev_info->init) { ret = dev_info->init(); if (ret < 0) { x86_android_tablet_cleanup(); return ret; } exit_handler = dev_info->exit; } i2c_clients = kcalloc(dev_info->i2c_client_count, sizeof(*i2c_clients), GFP_KERNEL); if (!i2c_clients) { x86_android_tablet_cleanup(); 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_cleanup(); return ret; } } pdevs = kcalloc(dev_info->pdev_count, sizeof(*pdevs), GFP_KERNEL); if (!pdevs) { x86_android_tablet_cleanup(); 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])) { x86_android_tablet_cleanup(); return PTR_ERR(pdevs[i]); } } serdevs = kcalloc(dev_info->serdev_count, sizeof(*serdevs), GFP_KERNEL); if (!serdevs) { x86_android_tablet_cleanup(); return -ENOMEM; } serdev_count = dev_info->serdev_count; for (i = 0; i < serdev_count; i++) { ret = x86_instantiate_serdev(&dev_info->serdev_info[i], i); if (ret < 0) { x86_android_tablet_cleanup(); return ret; } } return 0; } module_init(x86_android_tablet_init); module_exit(x86_android_tablet_cleanup); MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com"); MODULE_DESCRIPTION("X86 Android tablets DSDT fixups driver"); MODULE_LICENSE("GPL");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
You can’t perform that action at this time.