diff options
Diffstat (limited to 'drivers/platform/x86/x86-android-tablets/core.c')
| -rw-r--r-- | drivers/platform/x86/x86-android-tablets/core.c | 426 |
1 files changed, 295 insertions, 131 deletions
diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c index 2fd6060a31bb..6588fae30356 100644 --- a/drivers/platform/x86/x86-android-tablets/core.c +++ b/drivers/platform/x86/x86-android-tablets/core.c @@ -5,49 +5,71 @@ * devices typically have a bunch of things hardcoded, rather than specified * in their DSDT. * - * Copyright (C) 2021-2023 Hans de Goede <hdegoede@redhat.com> + * Copyright (C) 2021-2023 Hans de Goede <hansg@kernel.org> */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/acpi.h> +#include <linux/device.h> #include <linux/dmi.h> -#include <linux/gpio/driver.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" -/* For gpiochip_get_desc() which is EXPORT_SYMBOL_GPL() */ -#include "../../../gpio/gpiolib.h" -#include "../../../gpio/gpiolib-acpi.h" +#include "../serdev_helpers.h" -static int gpiochip_find_match_label(struct gpio_chip *gc, void *data) -{ - return gc->label && !strcmp(gc->label, data); -} +static struct platform_device *x86_android_tablet_device; -int x86_android_tablet_get_gpiod(const char *label, int pin, struct gpio_desc **desc) +/* + * 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; - struct gpio_chip *chip; - chip = gpiochip_find((void *)label, gpiochip_find_match_label); - if (!chip) { - pr_err("error cannot find GPIO chip %s\n", label); - return -ENODEV; - } + lookup = kzalloc(struct_size(lookup, table, 2), GFP_KERNEL); + 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); - gpiod = gpiochip_get_desc(chip, pin); if (IS_ERR(gpiod)) { - pr_err("error %ld getting GPIO %s %d\n", PTR_ERR(gpiod), label, pin); + pr_err("error %ld getting GPIO %s %d\n", PTR_ERR(gpiod), chip, pin); return PTR_ERR(gpiod); } - *desc = gpiod; + if (desc) + *desc = gpiod; + return 0; } @@ -67,7 +89,7 @@ int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data) /* * 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. + * to avoid -EBUSY errors in this case. */ acpi_unregister_gsi(data->index); irq = acpi_register_gsi(NULL, data->index, data->trigger, data->polarity); @@ -77,7 +99,8 @@ int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data) 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, &gpiod); + ret = x86_android_tablet_get_gpiod(data->chip, data->index, data->con_id, + false, GPIOD_ASIS, &gpiod); if (ret) return ret; @@ -91,6 +114,9 @@ int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data) 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); @@ -119,36 +145,78 @@ int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data) } 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 struct gpio_keys_button *buttons; -static struct gpiod_lookup_table * const *gpiod_lookup_tables; -static const struct software_node *bat_swnode; +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; - 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; - } + if (dev_info->use_pci) + adap = get_i2c_adap_by_pci_parent(client_info); + else + adap = get_i2c_adap_by_handle(client_info); - 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; @@ -163,40 +231,80 @@ static __init int x86_instantiate_i2c_client(const struct x86_dev_info *dev_info return 0; } -static __init int x86_instantiate_serdev(const struct x86_serdev_info *info, int idx) +static __init int x86_instantiate_spi_dev(const struct x86_dev_info *dev_info, int idx) { - struct acpi_device *ctrl_adev, *serdev_adev; - struct serdev_device *serdev; - struct device *ctrl_dev; - int ret = -ENODEV; + 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; - 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); + 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; } - 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; + 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; } - /* 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; + 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; } - /* 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; + 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)); @@ -219,29 +327,61 @@ static __init int x86_instantiate_serdev(const struct x86_serdev_info *info, int put_serdev_adev: acpi_dev_put(serdev_adev); -put_ctrl_adev: - acpi_dev_put(ctrl_adev); +put_ctrl_dev: + put_device(ctrl_dev); return ret; } -static void x86_android_tablet_cleanup(void) +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 x86_android_tablet_remove(struct platform_device *pdev) { int i; - for (i = 0; i < serdev_count; i++) { + for (i = serdev_count - 1; i >= 0; i--) { if (serdevs[i]) serdev_device_remove(serdevs[i]); } kfree(serdevs); - for (i = 0; i < pdev_count; i++) + for (i = pdev_count - 1; i >= 0; i--) platform_device_unregister(pdevs[i]); kfree(pdevs); - kfree(buttons); - for (i = 0; i < i2c_client_count; i++) + 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); @@ -249,17 +389,15 @@ static void x86_android_tablet_cleanup(void) 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); + software_node_unregister_node_group(gpio_button_swnodes); + software_node_unregister_node_group(swnode_group); + software_node_unregister_node_group(gpiochip_node_group); } -static __init int x86_android_tablet_init(void) +static __init int x86_android_tablet_probe(struct platform_device *pdev) { 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); @@ -267,20 +405,8 @@ static __init int x86_android_tablet_init(void) 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); - } + /* 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, @@ -289,21 +415,33 @@ static __init int x86_android_tablet_init(void) 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; + 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; } - 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]); + ret = software_node_register_node_group(gpiochip_node_group); + if (ret) + 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(); + ret = dev_info->init(&pdev->dev); if (ret < 0) { - x86_android_tablet_cleanup(); + x86_android_tablet_remove(pdev); return ret; } exit_handler = dev_info->exit; @@ -311,7 +449,7 @@ static __init int x86_android_tablet_init(void) i2c_clients = kcalloc(dev_info->i2c_client_count, sizeof(*i2c_clients), GFP_KERNEL); if (!i2c_clients) { - x86_android_tablet_cleanup(); + x86_android_tablet_remove(pdev); return -ENOMEM; } @@ -319,15 +457,30 @@ static __init int x86_android_tablet_init(void) for (i = 0; i < i2c_client_count; i++) { ret = x86_instantiate_i2c_client(dev_info, i); if (ret < 0) { - x86_android_tablet_cleanup(); + x86_android_tablet_remove(pdev); + return ret; + } + } + + spi_devs = kcalloc(dev_info->spi_dev_count, sizeof(*spi_devs), GFP_KERNEL); + 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 (optional) gpio_keys_button pdev */ + /* + 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_cleanup(); + x86_android_tablet_remove(pdev); return -ENOMEM; } @@ -335,57 +488,47 @@ static __init int x86_android_tablet_init(void) 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]); + ret = PTR_ERR(pdevs[i]); + x86_android_tablet_remove(pdev); + return ret; } } serdevs = kcalloc(dev_info->serdev_count, sizeof(*serdevs), GFP_KERNEL); if (!serdevs) { - x86_android_tablet_cleanup(); + 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->serdev_info[i], i); + ret = x86_instantiate_serdev(dev_info, i); if (ret < 0) { - x86_android_tablet_cleanup(); + x86_android_tablet_remove(pdev); return ret; } } - if (dev_info->gpio_button_count) { - struct gpio_keys_platform_data pdata = { }; - struct gpio_desc *gpiod; - - buttons = kcalloc(dev_info->gpio_button_count, sizeof(*buttons), GFP_KERNEL); - if (!buttons) { - x86_android_tablet_cleanup(); - return -ENOMEM; - } - - 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, &gpiod); - if (ret < 0) { - x86_android_tablet_cleanup(); - return ret; - } + if (dev_info->gpio_button_swnodes) { + struct platform_device_info button_info = { + .name = "gpio-keys", + .id = PLATFORM_DEVID_AUTO, + }; - buttons[i] = dev_info->gpio_button[i].button; - buttons[i].gpio = desc_to_gpio(gpiod); + ret = software_node_register_node_group(dev_info->gpio_button_swnodes); + if (ret < 0) { + x86_android_tablet_remove(pdev); + return ret; } - pdata.buttons = buttons; - pdata.nbuttons = dev_info->gpio_button_count; + gpio_button_swnodes = dev_info->gpio_button_swnodes; - pdevs[pdev_count] = platform_device_register_data(NULL, "gpio-keys", - PLATFORM_DEVID_AUTO, - &pdata, sizeof(pdata)); + 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])) { - x86_android_tablet_cleanup(); - return PTR_ERR(pdevs[pdev_count]); + ret = PTR_ERR(pdevs[pdev_count]); + x86_android_tablet_remove(pdev); + return ret; } pdev_count++; } @@ -393,9 +536,30 @@ static __init int x86_android_tablet_init(void) 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); -module_exit(x86_android_tablet_cleanup); -MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); +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"); |
