Add support for following ACPI pin resources - PinFunction - PinConfig Pinctrl-acpi parses the ACPI table and generates list of pin descriptors that can be used by pin controller to set and config pin. See Documentation/driver-acpi/pin-control-acpi.rst for details Signed-off-by: Niyas Sait <niyas.sait@xxxxxxxxxx> --- Documentation/driver-api/index.rst | 1 + Documentation/driver-api/pin-control-acpi.rst | 200 +++++++ drivers/pinctrl/Makefile | 1 + drivers/pinctrl/core.c | 18 +- drivers/pinctrl/core.h | 3 + drivers/pinctrl/pinctrl-acpi.c | 525 ++++++++++++++++++ drivers/pinctrl/pinctrl-acpi.h | 77 +++ include/linux/pinctrl/pinctrl.h | 15 + 8 files changed, 836 insertions(+), 4 deletions(-) create mode 100644 Documentation/driver-api/pin-control-acpi.rst create mode 100644 drivers/pinctrl/pinctrl-acpi.c create mode 100644 drivers/pinctrl/pinctrl-acpi.h diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst index d3a58f77328e..8cb597db1a96 100644 --- a/Documentation/driver-api/index.rst +++ b/Documentation/driver-api/index.rst @@ -64,6 +64,7 @@ available subsections can be seen below. uio-howto firmware/index pin-control + pin-control-acpi gpio/index md/index media/index diff --git a/Documentation/driver-api/pin-control-acpi.rst b/Documentation/driver-api/pin-control-acpi.rst new file mode 100644 index 000000000000..5d179ba90e8f --- /dev/null +++ b/Documentation/driver-api/pin-control-acpi.rst @@ -0,0 +1,200 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Introduction +============ + +This document is an extension of the pin control subsystem in Linux [1] and describes +the pin control related ACPI properties supported in Linux kernel. + +On some platform, certains peripheral buses or devices could be connected to same +external pin header with a different pin configuration requirement. A simple example of +this would be a board where pins for I2C data and clock is multiplexed and shared with +GPIO pins for a display controller. Pins would require different +configuration in these two modes. For example, I2C functionality would require pin +bias to be set to pull up with pull strength of 10K Ohms and for GPIO functionality +pin bias needs to be set to pull down with pull strength of 20K Ohms, +input Schmitt-trigger enabled and a slew rate of 3. + +ACPI 6.2 version [2] introduced following resources to be able to describe different +pin functions and configurations required for devices. + +- PinFunction +- PinConfig +- PinGroup +- PinGroupFunction +- PinGroupConfig + +OSPM will have to handle the above resources and select the pin function and configuration +through vendor specific interfaces (e.g: memory mapped registers) for the devices to be +fully functional. + +Example 1 : I2C controller SDA/SCL muxed with display controller GPIO pin +========================================================================= + +.. code-block:: text + + // + // Description: GPIO + // + Device (GPI0) + { + Name (_HID, "PNPFFFE") + Name (_UID, 0x0) + Method (_STA) + { + Return(0xf) + } + Method (_CRS, 0x0, NotSerialized) + { + Name (RBUF, ResourceTemplate() + { + Memory32Fixed(ReadWrite, 0x4FE00000, 0x20) + Interrupt(ResourceConsumer, Level, ActiveHigh, Shared) {0x54} + }) + Return(RBUF) + } + } + + // + // Description: I2C controller 1 + // + Device (I2C1) + { + Name (_HID, "PNPFFFF") + Name (_UID, 0x0) + Method (_STA) + { + Return(0xf) + } + Method (_CRS, 0x0, NotSerialized) + { + Name (RBUF, ResourceTemplate() + { + Memory32Fixed(ReadWrite, 0x4F800000, 0x20) + Interrupt(ResourceConsumer, Level, ActiveHigh, Shared) {0x55} + PinFunction(Exclusive, PullDefault, 0x5, "\\_SB.GPI0", 0, ResourceConsumer, ) {2, 3} + // Configure 10k Pull up for I2C SDA/SCL pins + PinConfig(Exclusive, 0x01, 10000, "\\_SB.GPI0", 0, ResourceConsumer, ) {2, 3} + }) + Return(RBUF) + } + } + + // + // Description: Physical display panel + // + Device (SDIO) + { + Name (_HID, "PNPFFFD") + Name (_UID, 0x0) + Method (_STA) + { + Return(0xf) + } + Method (_CRS, 0x0, NotSerialized) + { + Name (RBUF, ResourceTemplate() + { + Memory32Fixed(ReadWrite, 0x4F900000, 0x20) + Interrupt(ResourceConsumer, Level, ActiveHigh, Shared) {0x57} + GpioIo(Shared, PullDefault, 0, 0, IoRestrictionNone, "\\_SB.GPI0",) {2, 3} + // Configure 20k Pull down + PinConfig(Exclusive, 0x02, 20000, "\\_SB.GPI0", 0, ResourceConsumer, ) {2, 3} + // Enable Schmitt-trigger + PinConfig(Exclusive, 0x0D, 1, "\\_SB.GPI0", 0, ResourceConsumer, ) {2, 3} + // Set slew rate to custom value 3 + PinConfig(Exclusive, 0x0B, 3, "\\_SB.GPI0", 0, ResourceConsumer, ) {2, 3} + }) + Return(RBUF) + } + } + +Notes for pin controller device driver developers +================================================= + +This section contains few examples and guidelines for device driver developers to +add bindings to handle ACPI pin resources. + +Pin control devices can add callbacks for following pinctrl_ops to handle ACPI +pin resources. + +.. code-block:: c + + struct pinctrl_ops { + ... + int (*acpi_node_to_map)(struct pinctrl_dev *pctldev, + struct pinctrl_acpi_resource *resource, + struct pinctrl_map **map, unsigned *num_maps); + void (*acpi_free_map)(struct pinctrl_dev *pctldev, + struct pinctrl_map *map, unsigned num_maps); + ... + } + +Following example demonstrate how the pinctrl_acpi_resource struct can be mapped +to generic pinctrl_map. + +.. code-block:: c + + int example_acpi_node_to_map(struct pinctrl_dev *pctldev, + struct pinctrl_acpi_resource *resource, + struct pinctrl_map **map, + unsigned *num_maps_out) + { + + ... + new_map = devm_kzalloc(pctldev->dev, sizeof(struct pinctrl_map), + GFP_KERNEL); + + switch (info->type) { + case PINCTRL_ACPI_PIN_FUNCTION: + new_map->type = PIN_MAP_TYPE_MUX_GROUP; + new_map->data.mux.group = example_pinctrl_find_pin_group( + info->function.function_number, + info->function.pins, info->function.npins); + new_map->data.mux.function = pinctrl_find_function(info->function.function_number); + break; + case PINCTRL_ACPI_PIN_CONFIG: + new_map->type = PIN_MAP_TYPE_CONFIGS_PIN; + new_map->data.configs.group_or_pin = pin_get_name(pctldev, info->config.pin); + new_map->data.configs.configs = devm_kcalloc( + pctldev->dev, info->config.nconfigs, + sizeof(unsigned long), GFP_KERNEL); + new_map->data.configs.num_configs = 0; + list_for_each_entry(config_node, info->config.configs, node) + new_map->data.configs.configs[new_map->data.configs.num_configs++] = + config_node->config; + break; + } + + ... + } + +Pin controller will have to map function numbers from ACPI to internal function numbers +and select appropriate group for pin muxing. Multiple pinctrl_map might need to generated +if more than one group needs to be activated. Above example just assumes all of the pins +belongs to a single group. + +Multiple configurations might need to be applied for a pin and ACPI could have multiple +resources to define them. E.g: + +.. code-block:: text + + // Configure 20k Pull down + PinConfig(Exclusive, 0x02, 20000, "\\_SB.GPI0", 0, ResourceConsumer, ) {2, 3} + // Enable Schmitt-trigger + PinConfig(Exclusive, 0x0D, 1, "\\_SB.GPI0", 0, ResourceConsumer, ) {2, 3} + // Set slew rate to custom value 3 + PinConfig(Exclusive, 0x0B, 3, "\\_SB.GPI0", 0, ResourceConsumer, ) {2, 3} + +ACPI pin controller will combine the configurations at the pin level and will invoke +acpi_node_to_map to map them to struct pinctrl_map. The above ACPI resources would +generate two struct pinctrl_acpi_resource descriptors, one for each pin, with list +of configs to apply for each pin. + +References +========== + +[1] Documentation/driver-api/pin-control.rst + +[2] ACPI Specifications, Version 6.2 - Section 19.6.102 to 19.6.106 + https://uefi.org/sites/default/files/resources/ACPI_6_2.pdf diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile index 89bfa01b5231..b5423465131f 100644 --- a/drivers/pinctrl/Makefile +++ b/drivers/pinctrl/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_PINMUX) += pinmux.o obj-$(CONFIG_PINCONF) += pinconf.o obj-$(CONFIG_GENERIC_PINCONF) += pinconf-generic.o obj-$(CONFIG_OF) += devicetree.o +obj-$(CONFIG_ACPI) += pinctrl-acpi.o obj-$(CONFIG_PINCTRL_AMD) += pinctrl-amd.o obj-$(CONFIG_PINCTRL_APPLE_GPIO) += pinctrl-apple-gpio.o diff --git a/drivers/pinctrl/core.c b/drivers/pinctrl/core.c index 9e57f4c62e60..3ed11e38e995 100644 --- a/drivers/pinctrl/core.c +++ b/drivers/pinctrl/core.c @@ -25,6 +25,7 @@ #include <linux/pinctrl/consumer.h> #include <linux/pinctrl/pinctrl.h> #include <linux/pinctrl/machine.h> +#include <linux/acpi.h> #ifdef CONFIG_GPIOLIB #include "../gpio/gpiolib.h" @@ -35,7 +36,7 @@ #include "devicetree.h" #include "pinmux.h" #include "pinconf.h" - +#include "pinctrl-acpi.h" static bool pinctrl_dummy_state; @@ -1042,9 +1043,15 @@ static struct pinctrl *create_pinctrl(struct device *dev, return ERR_PTR(-ENOMEM); p->dev = dev; INIT_LIST_HEAD(&p->states); - INIT_LIST_HEAD(&p->dt_maps); - ret = pinctrl_dt_to_map(p, pctldev); + if (has_acpi_companion(dev)) { + INIT_LIST_HEAD(&p->acpi_maps); + ret = pinctrl_acpi_to_map(p); + } else { + INIT_LIST_HEAD(&p->dt_maps); + ret = pinctrl_dt_to_map(p, pctldev); + } + if (ret < 0) { kfree(p); return ERR_PTR(ret); @@ -1168,7 +1175,10 @@ static void pinctrl_free(struct pinctrl *p, bool inlist) kfree(state); } - pinctrl_dt_free_maps(p); + if (has_acpi_companion(p->dev)) + pinctrl_acpi_free_maps(p); + else + pinctrl_dt_free_maps(p); if (inlist) list_del(&p->node); diff --git a/drivers/pinctrl/core.h b/drivers/pinctrl/core.h index 840103c40c14..603e36e175c7 100644 --- a/drivers/pinctrl/core.h +++ b/drivers/pinctrl/core.h @@ -72,6 +72,8 @@ struct pinctrl_dev { * @state: the current state * @dt_maps: the mapping table chunks dynamically parsed from device tree for * this device, if any + * @acpi_maps: the mapping table chunks dynamically parsed from ACPI for this + * device, if any * @users: reference count */ struct pinctrl { @@ -80,6 +82,7 @@ struct pinctrl { struct list_head states; struct pinctrl_state *state; struct list_head dt_maps; + struct list_head acpi_maps; struct kref users; }; diff --git a/drivers/pinctrl/pinctrl-acpi.c b/drivers/pinctrl/pinctrl-acpi.c new file mode 100644 index 000000000000..a510ff38c60d --- /dev/null +++ b/drivers/pinctrl/pinctrl-acpi.c @@ -0,0 +1,525 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ACPI helpers for PinCtrl API + * + * Copyright (C) 2022 Linaro Ltd. + * Author: Niyas Sait <niyas.sait@xxxxxxxxxx> + */ +#include <linux/acpi.h> +#include <linux/errno.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio/driver.h> +#include <linux/gpio/machine.h> +#include <linux/list.h> + +#include "pinctrl-acpi.h" +#include "core.h" + +/** + * struct pin_config_lookup_info - context to use for pin config look up + * @pctrl: pinctrl descriptor + * @config_maps: list of &struct config_map_info + */ +struct pin_config_lookup_info { + struct pinctrl *pctrl; + struct list_head config_maps; +}; + +/** + * struct config_map_info - context for config map + * @pin: pin for the configs + * @pinctrl_acpi: pin controller ACPI name + * @configs: list of &struct pinctrl_acpi_config_node + * @node: list node + * @nconfigs: number of configs + */ +struct config_map_info { + char *pinctrl_acpi; + unsigned int pin; + struct list_head configs; + struct list_head node; + size_t nconfigs; +}; + +/** + * struct pinctrl_acpi_map - mapping table chunk parsed from ACPI + * @node: list node for struct pinctrl's @acpi_maps field + * @pctldev: the pin controller that allocated this struct, and will free it + * @map: the mapping table entries + * @num_maps: number of mapping table entries + */ +struct pinctrl_acpi_map { + struct list_head node; + struct pinctrl_dev *pctldev; + struct pinctrl_map *map; + size_t num_maps; +}; + +static void acpi_free_map(struct pinctrl_dev *pctldev, struct pinctrl_map *map, + unsigned int num_maps) +{ + int i; + + for (i = 0; i < num_maps; ++i) { + kfree_const(map[i].dev_name); + map[i].dev_name = NULL; + } + + if (pctldev) { + const struct pinctrl_ops *ops = pctldev->desc->pctlops; + + if (ops->acpi_free_map) + ops->acpi_free_map(pctldev, map, num_maps); + } else { + kfree(map); + } +} + +/** + * pinctrl_acpi_free_maps() - free pinctrl ACPI maps + * @p: pinctrl descriptor for the device + */ +void pinctrl_acpi_free_maps(struct pinctrl *p) +{ + struct pinctrl_acpi_map *acpi_map, *tmp; + + list_for_each_entry_safe(acpi_map, tmp, &p->acpi_maps, node) { + pinctrl_unregister_mappings(acpi_map->map); + list_del(&acpi_map->node); + acpi_free_map(acpi_map->pctldev, acpi_map->map, + acpi_map->num_maps); + kfree(acpi_map); + } +} + +static int acpi_remember_or_free_map(struct pinctrl *p, const char *statename, + struct pinctrl_dev *pctldev, + struct pinctrl_map *map, + unsigned int num_maps) +{ + struct pinctrl_acpi_map *acpi_map; + int i; + + for (i = 0; i < num_maps; i++) { + const char *devname; + + devname = kstrdup_const(dev_name(p->dev), GFP_KERNEL); + if (!devname) + goto err_free_map; + + map[i].dev_name = devname; + map[i].name = statename; + if (pctldev) + map[i].ctrl_dev_name = dev_name(pctldev->dev); + } + + acpi_map = kzalloc(sizeof(*acpi_map), GFP_KERNEL); + if (!acpi_map) + goto err_free_map; + + acpi_map->pctldev = pctldev; + acpi_map->map = map; + acpi_map->num_maps = num_maps; + list_add_tail(&acpi_map->node, &p->acpi_maps); + + return pinctrl_register_mappings(map, num_maps); + +err_free_map: + acpi_free_map(pctldev, map, num_maps); + return -ENOMEM; +} + +static struct pinctrl_dev *get_pinctrl_dev_from_acpi_name(char *pinctrl_acpi) +{ + struct acpi_device *adev; + const char *dev_name; + acpi_status status; + acpi_handle handle; + + status = acpi_get_handle(NULL, pinctrl_acpi, &handle); + if (ACPI_FAILURE(status)) + return NULL; + + adev = acpi_get_acpi_dev(handle); + if (!adev) + return NULL; + + dev_name = acpi_dev_name(adev); + if (!dev_name) + return NULL; + + return get_pinctrl_dev_from_devname(dev_name); +} + +static int acpi_to_generic_pin_config(unsigned int acpi_param, + unsigned int acpi_value, + unsigned int *pin_config) +{ + enum pin_config_param genconf_param; + + switch (acpi_param) { + case ACPI_PIN_CONFIG_BIAS_PULL_UP: + genconf_param = PIN_CONFIG_BIAS_PULL_UP; + break; + case ACPI_PIN_CONFIG_BIAS_PULL_DOWN: + genconf_param = PIN_CONFIG_BIAS_PULL_DOWN; + break; + case ACPI_PIN_CONFIG_BIAS_DEFAULT: + genconf_param = PIN_CONFIG_BIAS_PULL_PIN_DEFAULT; + break; + case ACPI_PIN_CONFIG_BIAS_DISABLE: + genconf_param = PIN_CONFIG_BIAS_DISABLE; + break; + case ACPI_PIN_CONFIG_BIAS_HIGH_IMPEDANCE: + genconf_param = PIN_CONFIG_BIAS_HIGH_IMPEDANCE; + break; + case ACPI_PIN_CONFIG_BIAS_BUS_HOLD: + genconf_param = PIN_CONFIG_BIAS_BUS_HOLD; + break; + case ACPI_PIN_CONFIG_DRIVE_OPEN_DRAIN: + genconf_param = PIN_CONFIG_DRIVE_OPEN_DRAIN; + break; + case ACPI_PIN_CONFIG_DRIVE_OPEN_SOURCE: + genconf_param = PIN_CONFIG_DRIVE_OPEN_SOURCE; + break; + case ACPI_PIN_CONFIG_DRIVE_PUSH_PULL: + genconf_param = PIN_CONFIG_DRIVE_PUSH_PULL; + break; + case ACPI_PIN_CONFIG_DRIVE_STRENGTH: + genconf_param = PIN_CONFIG_DRIVE_STRENGTH; + break; + case ACPI_PIN_CONFIG_SLEW_RATE: + genconf_param = PIN_CONFIG_SLEW_RATE; + break; + case ACPI_PIN_CONFIG_INPUT_DEBOUNCE: + genconf_param = PIN_CONFIG_INPUT_DEBOUNCE; + break; + case ACPI_PIN_CONFIG_INPUT_SCHMITT_TRIGGER: + genconf_param = PIN_CONFIG_INPUT_SCHMITT_ENABLE; + break; + default: + return -EINVAL; + } + + *pin_config = pinconf_to_config_packed(genconf_param, acpi_value); + + return 0; +} + +static int add_to_config_map(struct list_head *config_maps, char *pinctrl_acpi, + unsigned int pin, unsigned long config) +{ + struct config_map_info *config_map; + struct pinctrl_acpi_config_node *config_node; + + config_node = + kzalloc(sizeof(struct pinctrl_acpi_config_node), GFP_KERNEL); + if (!config_node) + return -ENOMEM; + + config_node->config = config; + INIT_LIST_HEAD(&config_node->node); + + list_for_each_entry(config_map, config_maps, node) { + if (strcmp(config_map->pinctrl_acpi, pinctrl_acpi) == 0 && + config_map->pin == pin) { + list_add(&config_node->node, &config_map->configs); + config_map->nconfigs++; + return 0; + } + } + + config_map = kzalloc(sizeof(struct config_map_info), GFP_KERNEL); + if (!config_map) + goto err_free_config_node; + + config_map->pin = pin; + config_map->pinctrl_acpi = pinctrl_acpi; + config_map->nconfigs = 1; + INIT_LIST_HEAD(&config_map->node); + INIT_LIST_HEAD(&config_map->configs); + list_add(&config_node->node, &config_map->configs); + list_add(&config_map->node, config_maps); + + return 0; + +err_free_config_node: + kfree(config_node); + return -ENOMEM; +} + +static int +acpi_pin_resource_to_pinctrl_map(struct pinctrl *p, char *pinctrl_acpi, + struct pinctrl_acpi_resource *resource) +{ + const struct pinctrl_ops *ops; + struct pinctrl_map *new_map; + struct pinctrl_dev *pctldev; + unsigned int num_maps; + int ret; + + pctldev = get_pinctrl_dev_from_acpi_name(pinctrl_acpi); + if (!pctldev) { + dev_err(p->dev, "pctldev with ACPI name '%s' not found\n", + pinctrl_acpi); + return -ENXIO; + } + + ops = pctldev->desc->pctlops; + if (!ops->acpi_node_to_map) { + dev_err(p->dev, "pctldev %s doesn't support ACPI\n", + dev_name(pctldev->dev)); + return -ENXIO; + } + + ret = ops->acpi_node_to_map(pctldev, resource, &new_map, &num_maps); + if (ret < 0) + return ret; + + ret = acpi_remember_or_free_map(p, "default", pctldev, new_map, + num_maps); + + return ret; +} + +static int acpi_pin_function_to_pinctrl_map(struct pinctrl *p, + char *pinctrl_acpi, + unsigned int *pins, size_t npins, + int function) +{ + struct pinctrl_acpi_resource resource = { + .type = PINCTRL_ACPI_PIN_FUNCTION, + .function.pins = pins, + .function.npins = npins, + .function.function_number = function + }; + + return acpi_pin_resource_to_pinctrl_map(p, pinctrl_acpi, &resource); +} + +static int acpi_pin_config_to_pinctrl_map(struct pinctrl *p, char *pinctrl_acpi, + unsigned int pin, + struct list_head *configs, + size_t nconfigs) +{ + struct pinctrl_acpi_resource resource = { + .type = PINCTRL_ACPI_PIN_CONFIG, + .config.pin = pin, + .config.configs = configs, + .config.nconfigs = nconfigs + }; + + return acpi_pin_resource_to_pinctrl_map(p, pinctrl_acpi, &resource); +} + +static int +process_pin_config_from_pin_function(struct pinctrl *p, + struct acpi_resource_pin_function *ares) +{ + struct pinctrl_acpi_config_node config_node; + int i, ret; + + INIT_LIST_HEAD(&config_node.node); + + switch (ares->pin_config) { + case ACPI_PIN_CONFIG_PULLUP: + config_node.config = PIN_CONFIG_BIAS_PULL_UP; + break; + case ACPI_PIN_CONFIG_PULLDOWN: + config_node.config = PIN_CONFIG_BIAS_PULL_DOWN; + break; + default: + config_node.config = PIN_CONFIG_BIAS_PULL_PIN_DEFAULT; + break; + } + + for (i = 0; i < ares->pin_table_length; i++) { + ret = acpi_pin_config_to_pinctrl_map( + p, ares->resource_source.string_ptr, ares->pin_table[i], + &config_node.node, 1); + if (ret < 0) + return ret; + } + + return 0; +} + +static int process_pin_function(struct pinctrl *p, + struct acpi_resource_pin_function *ares) +{ + unsigned int function; + char *pinctrl_acpi; + unsigned int *pins; + size_t npins; + int i, ret; + + function = ares->function_number; + pinctrl_acpi = ares->resource_source.string_ptr; + npins = ares->pin_table_length; + pins = kcalloc(npins, sizeof(*pins), GFP_KERNEL); + if (!pins) + return -ENOMEM; + + for (i = 0; i < npins; i++) + pins[i] = ares->pin_table[i]; + + ret = acpi_pin_function_to_pinctrl_map(p, pinctrl_acpi, pins, npins, + function); + if (ret >= 0) + ret = process_pin_config_from_pin_function(p, ares); + + kfree(pins); + + return ret; +} + +static int process_pin_config(struct list_head *config_maps, + struct acpi_resource_pin_config *ares) +{ + unsigned int config; + int i, ret; + + ret = acpi_to_generic_pin_config(ares->pin_config_type, + ares->pin_config_value, &config); + if (ret < 0) + return ret; + + for (i = 0; i < ares->pin_table_length; i++) { + ret = add_to_config_map(config_maps, + ares->resource_source.string_ptr, + ares->pin_table[i], config); + if (ret < 0) + return ret; + } + + return 0; +} + + +static int parse_acpi_pin_function_resources(struct acpi_resource *ares, + void *data) +{ + struct pinctrl *p = data; + int ret; + + switch (ares->type) { + case ACPI_RESOURCE_TYPE_PIN_FUNCTION: + ret = process_pin_function(p, &ares->data.pin_function); + if (ret < 0) + return ret; + break; + } + + return 1; +} + +static int parse_acpi_pin_config_resources(struct acpi_resource *ares, + void *data) +{ + struct pin_config_lookup_info *info = data; + int ret; + + switch (ares->type) { + case ACPI_RESOURCE_TYPE_PIN_CONFIG: + ret = process_pin_config(&info->config_maps, + &ares->data.pin_config); + if (ret < 0) + return ret; + break; + } + + return 1; +} + +static int parse_acpi_pin_functions(struct pinctrl *p) +{ + struct list_head res_list; + struct acpi_device *adev; + int ret; + + INIT_LIST_HEAD(&res_list); + adev = ACPI_COMPANION(p->dev); + ret = acpi_dev_get_resources(adev, &res_list, + parse_acpi_pin_function_resources, p); + if (ret < 0) + return ret; + + acpi_dev_free_resource_list(&res_list); + + return 0; +} + +static void free_config_nodes(struct list_head *config_nodes) +{ + struct pinctrl_acpi_config_node *config_node, *tmp; + + list_for_each_entry_safe(config_node, tmp, config_nodes, node) { + list_del(&config_node->node); + kfree(config_node); + } +} + +static void free_config_maps(struct list_head *config_maps) +{ + struct config_map_info *config_map, *tmp; + + list_for_each_entry_safe(config_map, tmp, config_maps, node) { + list_del(&config_map->node); + free_config_nodes(&config_map->configs); + kfree(config_map); + } +} + +static int parse_acpi_pin_configs(struct pinctrl *p) +{ + struct pin_config_lookup_info info = { .pctrl = p }; + struct config_map_info *config_map; + struct list_head res_list; + int ret; + + INIT_LIST_HEAD(&res_list); + INIT_LIST_HEAD(&info.config_maps); + ret = acpi_dev_get_resources(ACPI_COMPANION(p->dev), &res_list, + parse_acpi_pin_config_resources, &info); + if (ret < 0) + return ret; + + list_for_each_entry(config_map, &info.config_maps, node) { + ret = acpi_pin_config_to_pinctrl_map( + p, config_map->pinctrl_acpi, config_map->pin, + &config_map->configs, config_map->nconfigs); + if (ret < 0) + break; + } + + free_config_maps(&info.config_maps); + acpi_dev_free_resource_list(&res_list); + + return ret; +} + +/** + * pinctrl_acpi_to_map() - pinctrl map from ACPI pin resources for given device + * @p: pinctrl descriptor for the device + * + * This will parse the ACPI pin resources for the device and creates pinctrl map. + * + * Return: Returns 0 on success, negative errno on failure. + */ +int pinctrl_acpi_to_map(struct pinctrl *p) +{ + int ret; + + if (!has_acpi_companion(p->dev)) + return -ENXIO; + + ret = parse_acpi_pin_functions(p); + if (ret < 0) + return ret; + + ret = parse_acpi_pin_configs(p); + if (ret < 0) + return ret; + + return 0; +} diff --git a/drivers/pinctrl/pinctrl-acpi.h b/drivers/pinctrl/pinctrl-acpi.h new file mode 100644 index 000000000000..9f9495f5ec6e --- /dev/null +++ b/drivers/pinctrl/pinctrl-acpi.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ACPI helpers for PinCtrl API + * + * Copyright (C) 2022 Linaro Ltd. + */ + +/** + * struct pinctrl_acpi_config_node - config node descriptor + * @config: generic pin config value + * @node: list node + */ +struct pinctrl_acpi_config_node { + unsigned long config; + struct list_head node; +}; + +/** + * struct pinctrl_acpi_pin_function - pin function descriptor + * @pins: pin array from ACPI resources + * @npins: number of entries in @pins + * @function_number: function number to apply for the pin + */ +struct pinctrl_acpi_pin_function { + unsigned int *pins; + size_t npins; + unsigned int function_number; +}; + +/** + * struct pinctrl_acpi_pin_group_config - pin config descriptor + * @pin: pin number + * @nconfigs: number of configs in @configs + * @configs: config list + */ +struct pinctrl_acpi_pin_config { + unsigned int pin; + size_t nconfigs; + struct list_head *configs; +}; + +/** + * enum pinctrl_acpi_resource_type - pinctrl acpi resource type + * @PINCTRL_ACPI_PIN_FUNCTION: pin function type + * @PINCTRL_ACPI_PIN_CONFIG: pin config type + */ +enum pinctrl_acpi_resource_type { + PINCTRL_ACPI_PIN_FUNCTION, + PINCTRL_ACPI_PIN_CONFIG, +}; + +/** + * struct pinctrl_acpi_resource - pinctrl acpi resource + * @type: resource type + * @function: pin function resource descriptor + * @config: pin config resource descriptor + */ +struct pinctrl_acpi_resource { + enum pinctrl_acpi_resource_type type; + union { + struct pinctrl_acpi_pin_function function; + struct pinctrl_acpi_pin_config config; + }; +}; + +#ifdef CONFIG_ACPI +int pinctrl_acpi_to_map(struct pinctrl *p); +void pinctrl_acpi_free_maps(struct pinctrl *p); +#else +static inline int pinctrl_acpi_to_map(struct pinctrl *p) +{ + return -ENXIO; +} +static inline void pinctrl_acpi_free_maps(struct pinctrl *p) +{ +} +#endif diff --git a/include/linux/pinctrl/pinctrl.h b/include/linux/pinctrl/pinctrl.h index 487117ccb1bc..a798351117e0 100644 --- a/include/linux/pinctrl/pinctrl.h +++ b/include/linux/pinctrl/pinctrl.h @@ -25,6 +25,7 @@ struct pinconf_ops; struct pin_config_item; struct gpio_chip; struct device_node; +struct pinctrl_acpi_resource; /** * struct pingroup - provides information on pingroup @@ -104,6 +105,15 @@ struct pinctrl_gpio_range { * allocated members of the mapping table entries themselves. This * function is optional, and may be omitted for pinctrl drivers that do * not support device tree. + * @acpi_node_to_map: process ACPI pin related properties, and create + * mapping table entries for it. These are returned through the @map and + * @num_maps output parameters. This function is optional, and may be + * omitted for pinctrl drivers that do not support ACPI. + * @acpi_free_map: free mapping table entries created via @acpi_node_to_map. The + * top-level @map pointer must be freed, along with any dynamically + * allocated members of the mapping table entries themselves. This + * function is optional, and may be omitted for pinctrl drivers that do + * not support ACPI. */ struct pinctrl_ops { int (*get_groups_count) (struct pinctrl_dev *pctldev); @@ -120,6 +130,11 @@ struct pinctrl_ops { struct pinctrl_map **map, unsigned *num_maps); void (*dt_free_map) (struct pinctrl_dev *pctldev, struct pinctrl_map *map, unsigned num_maps); + int (*acpi_node_to_map)(struct pinctrl_dev *pctldev, + struct pinctrl_acpi_resource *resource, + struct pinctrl_map **map, unsigned *num_maps); + void (*acpi_free_map)(struct pinctrl_dev *pctldev, + struct pinctrl_map *map, unsigned num_maps); }; /** -- 2.25.1