Re: [PATCH v3] platform/x86: x86-android-tablets: New driver for x86 Android tablets

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

On 12/23/21 20:07, Hans de Goede wrote:
> 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 then specified in their DSDT.
> 
> With the DSDT containing a random collection of devices which may or
> may not actually be present as well as missing devices which are
> actually present.
> 
> This driver, which loads only on affected models based on DMI matching,
> adds DMI based instantiating of kernel devices for devices which are
> missing from the DSDT, fixing e.g. battery monitoring, touchpads and/or
> accelerometers not working.
> 
> Note the Kconfig help text also refers to "various fixes" ATM there are
> no such fixes, but there are also known cases where entries are present
> in the DSDT but they contain bugs, such as missing/wrong GPIOs. The plan
> is to also add fixes for things like this here in the future.
> 
> This is the least ugly option to get these devices to fully work and to
> do so without adding any extra code to the main kernel image (vmlinuz)
> when built as a module.
> 
> Link: https://lore.kernel.org/platform-driver-x86/20211031162428.22368-1-hdegoede@xxxxxxxxxx/
> Signed-off-by: Hans de Goede <hdegoede@xxxxxxxxxx>

Note I've added this to pdx86/for-next now.

Regards,

Hans


> ---
> Changes in v3:
> - Refactor x86_instantiate_i2c_client() so that it no longer needs a ret
>   local variable and also remove the use of gotos
> - Move the x86_acpi_irq_helper_get() helper into x86-android-tablets.c,
>   atm that is the only user and it really is a kludge which we don't want
>   to encourage others to use
> 
> Changes in v2:
> - Use the new x86_acpi_irq_helper_get() helper
> - Fixup / improve a couple of comments
> ---
>  MAINTAINERS                                |   7 +
>  drivers/platform/x86/Kconfig               |  17 ++
>  drivers/platform/x86/Makefile              |   1 +
>  drivers/platform/x86/x86-android-tablets.c | 319 +++++++++++++++++++++
>  4 files changed, 344 insertions(+)
>  create mode 100644 drivers/platform/x86/x86-android-tablets.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 665c43d31ba3..7c22d50e9082 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -20732,6 +20732,13 @@ S:	Maintained
>  T:	git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git x86/mm
>  F:	arch/x86/mm/
>  
> +X86 PLATFORM ANDROID TABLETS DSDT FIXUP DRIVER
> +M:	Hans de Goede <hdegoede@xxxxxxxxxx>
> +L:	platform-driver-x86@xxxxxxxxxxxxxxx
> +S:	Maintained
> +T:	git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git
> +F:	drivers/platform/x86/x86-android-tablets.c
> +
>  X86 PLATFORM DRIVERS
>  M:	Hans de Goede <hdegoede@xxxxxxxxxx>
>  M:	Mark Gross <markgross@xxxxxxxxxx>
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index af8e5d13ed9e..f89d438b4368 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -1025,6 +1025,23 @@ config TOUCHSCREEN_DMI
>  	  the OS-image for the device. This option supplies the missing info.
>  	  Enable this for x86 tablets with Silead or Chipone touchscreens.
>  
> +config X86_ANDROID_TABLETS
> +	tristate "X86 Android tablet support"
> +	depends on I2C && ACPI
> +	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 config say M here.
> +
>  config FW_ATTR_CLASS
>  	tristate
>  
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 0f3ef40634b3..bd20b435c22b 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -114,6 +114,7 @@ obj-$(CONFIG_I2C_MULTI_INSTANTIATE)	+= i2c-multi-instantiate.o
>  obj-$(CONFIG_MLX_PLATFORM)		+= mlx-platform.o
>  obj-$(CONFIG_TOUCHSCREEN_DMI)		+= touchscreen_dmi.o
>  obj-$(CONFIG_WIRELESS_HOTKEY)		+= wireless-hotkey.o
> +obj-$(CONFIG_X86_ANDROID_TABLETS)	+= x86-android-tablets.o
>  
>  # Intel uncore drivers
>  obj-$(CONFIG_INTEL_IPS)				+= intel_ips.o
> diff --git a/drivers/platform/x86/x86-android-tablets.c b/drivers/platform/x86/x86-android-tablets.c
> new file mode 100644
> index 000000000000..0a14084cd92d
> --- /dev/null
> +++ b/drivers/platform/x86/x86-android-tablets.c
> @@ -0,0 +1,319 @@
> +// 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@xxxxxxxxxx>
> + */
> +
> +#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/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/string.h>
> +/* For gpio_get_desc() which is EXPORT_SYMBOL_GPL() */
> +#include "../../gpio/gpiolib.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,
> +};
> +
> +struct x86_acpi_irq_data {
> +	char *gpio_chip; /* GPIO chip label for X86_ACPI_IRQ_TYPE_GPIOINT */
> +	enum x86_acpi_irq_type type;
> +	int index;
> +	int trigger;  /* ACPI_EDGE_SENSITIVE / ACPI_LEVEL_SENSITIVE */
> +	int polarity; /* ACPI_ACTIVE_HIGH / ACPI_ACTIVE_LOW / ACPI_ACTIVE_BOTH */
> +};
> +
> +static int x86_acpi_irq_helper_gpiochip_find(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 gpio_desc *gpiod;
> +	struct gpio_chip *chip;
> +	unsigned int irq_type;
> +	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->gpio_chip, x86_acpi_irq_helper_gpiochip_find);
> +		if (!chip)
> +			return -EPROBE_DEFER;
> +
> +		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->gpio_chip, data->index);
> +			return ret;
> +		}
> +
> +		irq = gpiod_to_irq(gpiod);
> +		if (irq < 0) {
> +			pr_err("error %d getting IRQ %s %d\n", irq,
> +			       data->gpio_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;
> +	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_dev_info {
> +	const struct x86_i2c_client_info *i2c_client_info;
> +	int i2c_client_count;
> +};
> +
> +/*
> + * 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,
> +			.gpio_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),
> +};
> +
> +/*
> + * 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 = {
> +	{	/* Xiaomi Mi Pad 2 */
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "Xiaomi Inc"),
> +			DMI_MATCH(DMI_PRODUCT_NAME, "Mipad2"),
> +		},
> +		.driver_data = (void *)&xiaomi_mipad2_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,
> +	},
> +	{} /* Terminating entry */
> +};
> +MODULE_DEVICE_TABLE(dmi, x86_android_tablet_ids);
> +
> +static int i2c_client_count;
> +static struct i2c_client **i2c_clients;
> +
> +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 void x86_android_tablet_cleanup(void)
> +{
> +	int i;
> +
> +	for (i = 0; i < i2c_client_count; i++)
> +		i2c_unregister_device(i2c_clients[i]);
> +
> +	kfree(i2c_clients);
> +}
> +
> +static __init int x86_android_tablet_init(void)
> +{
> +	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;
> +
> +	i2c_client_count = dev_info->i2c_client_count;
> +
> +	i2c_clients = kcalloc(i2c_client_count, sizeof(*i2c_clients), GFP_KERNEL);
> +	if (!i2c_clients)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < dev_info->i2c_client_count; i++) {
> +		ret = x86_instantiate_i2c_client(dev_info, i);
> +		if (ret < 0) {
> +			x86_android_tablet_cleanup();
> +			break;
> +		}
> +	}
> +
> +	return ret;
> +}
> +
> +module_init(x86_android_tablet_init);
> +module_exit(x86_android_tablet_cleanup);
> +
> +MODULE_AUTHOR("Hans de Goede <hdegoede@xxxxxxxxxx");
> +MODULE_DESCRIPTION("X86 Android tablets DSDT fixups driver");
> +MODULE_LICENSE("GPL");
> 




[Index of Archives]     [Linux Kernel Development]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux