// 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 <hdegoede@redhat.com>
*/
/* * 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(constchar *chip, int pin, constchar *con_id, bool active_low, enum gpiod_flags dflags, struct gpio_desc **desc)
{ struct gpiod_lookup_table *lookup; struct gpio_desc *gpiod;
lookup = kzalloc(struct_size(lookup, table, 2), GFP_KERNEL); if (!lookup) return -ENOMEM;
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;
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;
}
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;
}
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;
}
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(conststruct x86_dev_info *dev_info, int idx)
{ conststruct 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;
}
staticvoid 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);
kfree(buttons);
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();
for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++)
gpiod_remove_lookup_table(gpiod_lookup_tables[i]);
software_node_unregister(bat_swnode);
}
static __init int x86_android_tablet_probe(struct platform_device *pdev)
{ conststruct x86_dev_info *dev_info; conststruct 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]);
bat_swnode = dev_info->bat_swnode; if (bat_swnode) {
ret = software_node_register(bat_swnode); if (ret) return ret;
}
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(&pdev->dev); if (ret < 0) {
x86_android_tablet_remove(pdev); return ret;
}
exit_handler = dev_info->exit;
}
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 = kcalloc(dev_info->pdev_count + 1, sizeof(*pdevs), GFP_KERNEL); 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;
}
}
for (i = 0; i < dev_info->gpio_button_count; i++) {
ret = x86_android_tablet_get_gpiod(dev_info->gpio_button[i].chip,
dev_info->gpio_button[i].pin,
dev_info->gpio_button[i].button.desc, false, GPIOD_IN, &gpiod); if (ret < 0) {
x86_android_tablet_remove(pdev); return ret;
}
buttons[i] = dev_info->gpio_button[i].button;
buttons[i].gpio = desc_to_gpio(gpiod); /* Release GPIO descriptor so that gpio-keys can request it */
devm_gpiod_put(&x86_android_tablet_device->dev, gpiod);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.