| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Generation of tables for particular device types |
| * |
| * Copyright 2019 Google LLC |
| * Mostly taken from coreboot file acpi_device.c |
| */ |
| |
| #include <common.h> |
| #include <dm.h> |
| #include <log.h> |
| #include <malloc.h> |
| #include <uuid.h> |
| #include <acpi/acpigen.h> |
| #include <acpi/acpi_dp.h> |
| #include <dm/acpi.h> |
| |
| static void acpi_dp_write_array(struct acpi_ctx *ctx, |
| const struct acpi_dp *array); |
| |
| static void acpi_dp_write_value(struct acpi_ctx *ctx, |
| const struct acpi_dp *prop) |
| { |
| switch (prop->type) { |
| case ACPI_DP_TYPE_INTEGER: |
| acpigen_write_integer(ctx, prop->integer); |
| break; |
| case ACPI_DP_TYPE_STRING: |
| case ACPI_DP_TYPE_CHILD: |
| acpigen_write_string(ctx, prop->string); |
| break; |
| case ACPI_DP_TYPE_REFERENCE: |
| acpigen_emit_namestring(ctx, prop->string); |
| break; |
| case ACPI_DP_TYPE_ARRAY: |
| acpi_dp_write_array(ctx, prop->array); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* Package (2) { "prop->name", VALUE } */ |
| static void acpi_dp_write_property(struct acpi_ctx *ctx, |
| const struct acpi_dp *prop) |
| { |
| acpigen_write_package(ctx, 2); |
| acpigen_write_string(ctx, prop->name); |
| acpi_dp_write_value(ctx, prop); |
| acpigen_pop_len(ctx); |
| } |
| |
| /* Write array of Device Properties */ |
| static void acpi_dp_write_array(struct acpi_ctx *ctx, |
| const struct acpi_dp *array) |
| { |
| const struct acpi_dp *dp; |
| char *pkg_count; |
| |
| /* Package element count determined as it is populated */ |
| pkg_count = acpigen_write_package(ctx, 0); |
| |
| /* |
| * Only acpi_dp of type DP_TYPE_TABLE is allowed to be an array. |
| * DP_TYPE_TABLE does not have a value to be written. Thus, start |
| * the loop from next type in the array. |
| */ |
| for (dp = array->next; dp; dp = dp->next) { |
| acpi_dp_write_value(ctx, dp); |
| (*pkg_count)++; |
| } |
| |
| acpigen_pop_len(ctx); |
| } |
| |
| static void acpi_dp_free(struct acpi_dp *dp) |
| { |
| assert(dp); |
| while (dp) { |
| struct acpi_dp *p = dp->next; |
| |
| switch (dp->type) { |
| case ACPI_DP_TYPE_CHILD: |
| acpi_dp_free(dp->child); |
| break; |
| case ACPI_DP_TYPE_ARRAY: |
| acpi_dp_free(dp->array); |
| break; |
| default: |
| break; |
| } |
| |
| free(dp); |
| dp = p; |
| } |
| } |
| |
| static int acpi_dp_write_internal(struct acpi_ctx *ctx, struct acpi_dp *table) |
| { |
| struct acpi_dp *dp, *prop; |
| char *dp_count, *prop_count = NULL; |
| int child_count = 0; |
| int ret; |
| |
| assert(table); |
| if (table->type != ACPI_DP_TYPE_TABLE) |
| return 0; |
| |
| /* Name (name) */ |
| acpigen_write_name(ctx, table->name); |
| |
| /* Device Property list starts with the next entry */ |
| prop = table->next; |
| |
| /* Package (DP), default to assuming no properties or children */ |
| dp_count = acpigen_write_package(ctx, 0); |
| |
| /* Print base properties */ |
| for (dp = prop; dp; dp = dp->next) { |
| if (dp->type == ACPI_DP_TYPE_CHILD) { |
| child_count++; |
| } else { |
| /* |
| * The UUID and package is only added when |
| * we come across the first property. This |
| * is to avoid creating a zero-length package |
| * in situations where there are only children. |
| */ |
| if (!prop_count) { |
| *dp_count += 2; |
| /* ToUUID (ACPI_DP_UUID) */ |
| ret = acpigen_write_uuid(ctx, ACPI_DP_UUID); |
| if (ret) |
| return log_msg_ret("touuid", ret); |
| /* |
| * Package (PROP), element count determined as |
| * it is populated |
| */ |
| prop_count = acpigen_write_package(ctx, 0); |
| } |
| (*prop_count)++; |
| acpi_dp_write_property(ctx, dp); |
| } |
| } |
| |
| if (prop_count) { |
| /* Package (PROP) length, if a package was written */ |
| acpigen_pop_len(ctx); |
| } |
| |
| if (child_count) { |
| /* Update DP package count to 2 or 4 */ |
| *dp_count += 2; |
| /* ToUUID (ACPI_DP_CHILD_UUID) */ |
| ret = acpigen_write_uuid(ctx, ACPI_DP_CHILD_UUID); |
| if (ret) |
| return log_msg_ret("child uuid", ret); |
| |
| /* Print child pointer properties */ |
| acpigen_write_package(ctx, child_count); |
| |
| for (dp = prop; dp; dp = dp->next) |
| if (dp->type == ACPI_DP_TYPE_CHILD) |
| acpi_dp_write_property(ctx, dp); |
| /* Package (CHILD) length */ |
| acpigen_pop_len(ctx); |
| } |
| |
| /* Package (DP) length */ |
| acpigen_pop_len(ctx); |
| |
| /* Recursively parse children into separate tables */ |
| for (dp = prop; dp; dp = dp->next) { |
| if (dp->type == ACPI_DP_TYPE_CHILD) { |
| ret = acpi_dp_write_internal(ctx, dp->child); |
| if (ret) |
| return log_msg_ret("dp child", ret); |
| } |
| } |
| |
| return 0; |
| } |
| |
| int acpi_dp_write(struct acpi_ctx *ctx, struct acpi_dp *table) |
| { |
| int ret; |
| |
| ret = acpi_dp_write_internal(ctx, table); |
| |
| /* Clean up */ |
| acpi_dp_free(table); |
| |
| if (ret) |
| return log_msg_ret("write", ret); |
| |
| return 0; |
| } |
| |
| static struct acpi_dp *acpi_dp_new(struct acpi_dp *dp, enum acpi_dp_type type, |
| const char *name) |
| { |
| struct acpi_dp *new; |
| |
| new = malloc(sizeof(struct acpi_dp)); |
| if (!new) |
| return NULL; |
| |
| memset(new, '\0', sizeof(*new)); |
| new->type = type; |
| new->name = name; |
| |
| if (dp) { |
| /* Add to end of property list */ |
| while (dp->next) |
| dp = dp->next; |
| dp->next = new; |
| } |
| |
| return new; |
| } |
| |
| struct acpi_dp *acpi_dp_new_table(const char *name) |
| { |
| return acpi_dp_new(NULL, ACPI_DP_TYPE_TABLE, name); |
| } |
| |
| struct acpi_dp *acpi_dp_add_integer(struct acpi_dp *dp, const char *name, |
| u64 value) |
| { |
| struct acpi_dp *new; |
| |
| assert(dp); |
| new = acpi_dp_new(dp, ACPI_DP_TYPE_INTEGER, name); |
| |
| if (new) |
| new->integer = value; |
| |
| return new; |
| } |
| |
| struct acpi_dp *acpi_dp_add_string(struct acpi_dp *dp, const char *name, |
| const char *string) |
| { |
| struct acpi_dp *new; |
| |
| assert(dp); |
| new = acpi_dp_new(dp, ACPI_DP_TYPE_STRING, name); |
| if (new) |
| new->string = string; |
| |
| return new; |
| } |
| |
| struct acpi_dp *acpi_dp_add_reference(struct acpi_dp *dp, const char *name, |
| const char *reference) |
| { |
| struct acpi_dp *new; |
| |
| assert(dp); |
| new = acpi_dp_new(dp, ACPI_DP_TYPE_REFERENCE, name); |
| if (new) |
| new->string = reference; |
| |
| return new; |
| } |
| |
| struct acpi_dp *acpi_dp_add_child(struct acpi_dp *dp, const char *name, |
| struct acpi_dp *child) |
| { |
| struct acpi_dp *new; |
| |
| assert(dp); |
| if (child->type != ACPI_DP_TYPE_TABLE) |
| return NULL; |
| |
| new = acpi_dp_new(dp, ACPI_DP_TYPE_CHILD, name); |
| if (new) { |
| new->child = child; |
| new->string = child->name; |
| } |
| |
| return new; |
| } |
| |
| struct acpi_dp *acpi_dp_add_array(struct acpi_dp *dp, struct acpi_dp *array) |
| { |
| struct acpi_dp *new; |
| |
| assert(dp); |
| assert(array); |
| if (array->type != ACPI_DP_TYPE_TABLE) |
| return NULL; |
| |
| new = acpi_dp_new(dp, ACPI_DP_TYPE_ARRAY, array->name); |
| if (new) |
| new->array = array; |
| |
| return new; |
| } |
| |
| struct acpi_dp *acpi_dp_add_integer_array(struct acpi_dp *dp, const char *name, |
| u64 *array, int len) |
| { |
| struct acpi_dp *dp_array; |
| int i; |
| |
| assert(dp); |
| if (len <= 0) |
| return NULL; |
| |
| dp_array = acpi_dp_new_table(name); |
| if (!dp_array) |
| return NULL; |
| |
| for (i = 0; i < len; i++) |
| if (!acpi_dp_add_integer(dp_array, NULL, array[i])) |
| break; |
| |
| if (!acpi_dp_add_array(dp, dp_array)) |
| return NULL; |
| |
| return dp_array; |
| } |
| |
| struct acpi_dp *acpi_dp_add_gpio(struct acpi_dp *dp, const char *name, |
| const char *ref, int index, int pin, |
| enum acpi_gpio_polarity polarity) |
| { |
| struct acpi_dp *gpio; |
| |
| assert(dp); |
| gpio = acpi_dp_new_table(name); |
| if (!gpio) |
| return NULL; |
| |
| if (!acpi_dp_add_reference(gpio, NULL, ref) || |
| !acpi_dp_add_integer(gpio, NULL, index) || |
| !acpi_dp_add_integer(gpio, NULL, pin) || |
| !acpi_dp_add_integer(gpio, NULL, polarity == ACPI_GPIO_ACTIVE_LOW)) |
| return NULL; |
| |
| if (!acpi_dp_add_array(dp, gpio)) |
| return NULL; |
| |
| return gpio; |
| } |
| |
| int acpi_dp_ofnode_copy_int(ofnode node, struct acpi_dp *dp, const char *prop) |
| { |
| int ret; |
| u32 val = 0; |
| |
| ret = ofnode_read_u32(node, prop, &val); |
| if (ret) |
| return ret; |
| if (!acpi_dp_add_integer(dp, prop, val)) |
| return log_ret(-ENOMEM); |
| |
| return 0; |
| } |
| |
| int acpi_dp_ofnode_copy_str(ofnode node, struct acpi_dp *dp, const char *prop) |
| { |
| const char *val; |
| |
| val = ofnode_read_string(node, prop); |
| if (!val) |
| return -EINVAL; |
| if (!acpi_dp_add_string(dp, prop, val)) |
| return log_ret(-ENOMEM); |
| |
| return 0; |
| } |
| |
| int acpi_dp_dev_copy_int(const struct udevice *dev, struct acpi_dp *dp, |
| const char *prop) |
| { |
| int ret; |
| u32 val = 0; |
| |
| ret = dev_read_u32(dev, prop, &val); |
| if (ret) |
| return ret; |
| if (!acpi_dp_add_integer(dp, prop, val)) |
| return log_ret(-ENOMEM); |
| |
| return ret; |
| } |
| |
| int acpi_dp_dev_copy_str(const struct udevice *dev, struct acpi_dp *dp, |
| const char *prop) |
| { |
| const char *val; |
| |
| val = dev_read_string(dev, prop); |
| if (!val) |
| return -EINVAL; |
| if (!acpi_dp_add_string(dp, prop, val)) |
| return log_ret(-ENOMEM); |
| |
| return 0; |
| } |