Message ID | 20200614025523.40183-13-sjg@chromium.org |
---|---|
State | Superseded |
Headers | show |
Series | dm: Add programmatic generation of ACPI tables (part B) | expand |
Hi Simon, On Sun, Jun 14, 2020 at 10:55 AM Simon Glass <sjg at chromium.org> wrote: > > Power to some devices is controlled by GPIOs. Add a way to generate ACPI > code to enable and disable a GPIO so that this can be handled within an > ACPI method. > > Signed-off-by: Simon Glass <sjg at chromium.org> > Reviewed-by: Wolfgang Wallner <wolfgang.wallner at br-automation.com> > --- > > (no changes since v1) > > include/acpi/acpigen.h | 12 +++++++ > lib/acpi/acpigen.c | 80 ++++++++++++++++++++++++++++++++++++++++++ > test/dm/acpigen.c | 54 ++++++++++++++++++++++++++++ > 3 files changed, 146 insertions(+) > > diff --git a/include/acpi/acpigen.h b/include/acpi/acpigen.h > index d7a7f2f538..d68dca5dde 100644 > --- a/include/acpi/acpigen.h > +++ b/include/acpi/acpigen.h > @@ -13,6 +13,7 @@ > #include <linux/types.h> > > struct acpi_ctx; > +struct acpi_gpio; > > /* Top 4 bits of the value used to indicate a three-byte length value */ > #define ACPI_PKG_LEN_3_BYTES 0x80 > @@ -340,4 +341,15 @@ void acpigen_write_power_res(struct acpi_ctx *ctx, const char *name, uint level, > uint order, const char *const dev_states[], > size_t dev_states_count); > > +/* > + * Helper functions for enabling/disabling Tx GPIOs based on the GPIO > + * polarity. These functions end up calling acpigen_soc_{set,clear}_tx_gpio to > + * make callbacks into SoC acpigen code. > + * > + * Returns 0 on success and -1 on error. > + */ > +int acpigen_set_enable_tx_gpio(struct acpi_ctx *ctx, u32 tx_state_val, > + const char *dw0_name, struct acpi_gpio *gpio, What is dw0? Is dw0 generic stuff or GPIO specific? Need a comment block for parameter descriptions. > + bool enable); > + > #endif > diff --git a/lib/acpi/acpigen.c b/lib/acpi/acpigen.c > index 5df6ebe334..e0af36f775 100644 > --- a/lib/acpi/acpigen.c > +++ b/lib/acpi/acpigen.c > @@ -13,6 +13,7 @@ > #include <log.h> > #include <uuid.h> > #include <acpi/acpigen.h> > +#include <acpi/acpi_device.h> > #include <dm/acpi.h> > > u8 *acpigen_get_current(struct acpi_ctx *ctx) > @@ -392,3 +393,82 @@ void acpigen_write_debug_string(struct acpi_ctx *ctx, const char *str) > acpigen_write_string(ctx, str); > acpigen_emit_ext_op(ctx, DEBUG_OP); > } > + > +/** > + * acpigen_get_dw0_in_local5() - Generate code to put dw0 cfg0 in local5 Could you please explain why we put dw0 to local5? How about other local objects? > + * > + * @ctx: ACPI context pointer > + * @dw0_name: Name to use (e.g. "\\_SB.GPC0") > + * @addr: GPIO pin configuration register address > + * > + * Store (\_SB.GPC0 (addr), Local5) > + * \_SB.GPC0 is used to read cfg0 value from dw0. It is typically defined in > + * the board's gpiolib.asl > + */ > +static void acpigen_get_dw0_in_local5(struct acpi_ctx *ctx, > + const char *dw0_name, ulong addr) > +{ > + acpigen_write_store(ctx); > + acpigen_emit_namestring(ctx, dw0_name); > + acpigen_write_integer(ctx, addr); > + acpigen_emit_byte(ctx, LOCAL5_OP); > +} > + > +/** > + * acpigen_set_gpio_val() - Set value of TX GPIO to on/off > + * > + * @ctx: ACPI context pointer > + * @dw0_name: Name to use (e.g. "\\_SB.GPC0") > + * @gpio_num: GPIO number to adjust > + * @vaL: true to set on, false to set off > + */ > +static int acpigen_set_gpio_val(struct acpi_ctx *ctx, u32 tx_state_val, > + const char *dw0_name, struct acpi_gpio *gpio, > + bool val) > +{ > + acpigen_get_dw0_in_local5(ctx, dw0_name, gpio->pin0_addr); > + > + if (val) { > + /* Or (Local5, PAD_CFG0_TX_STATE, Local5) */ > + acpigen_write_or(ctx, LOCAL5_OP, tx_state_val, LOCAL5_OP); > + } else { > + /* Not (PAD_CFG0_TX_STATE, Local6) */ > + acpigen_write_not(ctx, tx_state_val, LOCAL6_OP); > + > + /* And (Local5, Local6, Local5) */ > + acpigen_write_and(ctx, LOCAL5_OP, LOCAL6_OP, LOCAL5_OP); > + } > + > + /* > + * \_SB.SPC0 (addr, Local5) > + * \_SB.SPC0 is used to write cfg0 value in dw0. It is defined in > + * gpiolib.asl. > + */ > + acpigen_emit_namestring(ctx, dw0_name); > + acpigen_write_integer(ctx, gpio->pin0_addr); > + acpigen_emit_byte(ctx, LOCAL5_OP); > + > + return 0; > +} > + > +/* > + * Helper functions for enabling/disabling Tx GPIOs based on the GPIO > + * polarity. These functions end up calling acpigen_{set,clear}_tx_gpio to > + * make callbacks into SoC acpigen code. > + * > + * Returns 0 on success and -1 on error. > + */ > +int acpigen_set_enable_tx_gpio(struct acpi_ctx *ctx, u32 tx_state_val, > + const char *dw0_name, struct acpi_gpio *gpio, > + bool enable) > +{ > + bool set; > + int ret; > + > + set = gpio->polarity == ACPI_GPIO_ACTIVE_HIGH ? enable : !enable; > + ret = acpigen_set_gpio_val(ctx, tx_state_val, dw0_name, gpio, set); > + if (ret) > + return log_msg_ret("call", ret); > + > + return 0; > +} > diff --git a/test/dm/acpigen.c b/test/dm/acpigen.c > index e66508932b..cbd0007365 100644 > --- a/test/dm/acpigen.c > +++ b/test/dm/acpigen.c > @@ -741,3 +741,57 @@ static int dm_test_acpi_power_res(struct unit_test_state *uts) > return 0; > } > DM_TEST(dm_test_acpi_power_res, 0); > + > +/* Test writing ACPI code to toggle a GPIO */ > +static int dm_test_acpi_gpio_toggle(struct unit_test_state *uts) > +{ > + const uint addr = 0x80012; > + const int txbit = BIT(2); > + struct gpio_desc desc; > + struct acpi_gpio gpio; > + struct acpi_ctx *ctx; > + struct udevice *dev; > + u8 *ptr; > + > + ut_assertok(alloc_context(&ctx)); > + > + ut_assertok(uclass_get_device(UCLASS_TEST_FDT, 0, &dev)); > + ut_asserteq_str("a-test", dev->name); > + ut_assertok(gpio_request_by_name(dev, "test2-gpios", 2, &desc, 0)); > + ut_assertok(gpio_get_acpi(&desc, &gpio)); > + > + /* Spot-check the results - see sb_gpio_get_acpi() */ > + ptr = acpigen_get_current(ctx); > + acpigen_set_enable_tx_gpio(ctx, txbit, "\\_SB.GPC0", &gpio, true); > + acpigen_set_enable_tx_gpio(ctx, txbit, "\\_SB.GPC0", &gpio, false); > + > + /* Since this GPIO is active low, we expect it to be cleared here */ > + ut_asserteq(STORE_OP, ptr[0]); > + ut_asserteq_strn("_SB_GPC0", (char *)ptr + 3); > + ut_asserteq(addr + desc.offset, get_unaligned((u32 *)(ptr + 0xc))); > + ut_asserteq(LOCAL5_OP, ptr[0x10]); > + > + ut_asserteq(NOT_OP, ptr[0x11]); > + ut_asserteq(txbit, ptr[0x12]); > + ut_asserteq(AND_OP, ptr[0x14]); > + ut_asserteq_strn("_SB_GPC0", (char *)ptr + 0x1a); > + ut_asserteq(addr + desc.offset, get_unaligned((u32 *)(ptr + 0x23))); > + ut_asserteq(LOCAL5_OP, ptr[0x27]); > + > + /* Now the second one, which should be set */ > + ut_asserteq_strn("_SB_GPC0", (char *)ptr + 0x2b); > + ut_asserteq(addr + desc.offset, get_unaligned((u32 *)(ptr + 0x34))); > + ut_asserteq(LOCAL5_OP, ptr[0x38]); > + > + ut_asserteq(OR_OP, ptr[0x39]); > + ut_asserteq(txbit, ptr[0x3b]); > + ut_asserteq_strn("_SB_GPC0", (char *)ptr + 0x3f); > + ut_asserteq(addr + desc.offset, get_unaligned((u32 *)(ptr + 0x48))); > + ut_asserteq(LOCAL5_OP, ptr[0x4c]); > + ut_asserteq(0x4d, acpigen_get_current(ctx) - ptr); > + > + free_context(&ctx); > + > + return 0; > +} > +DM_TEST(dm_test_acpi_gpio_toggle, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT); Regards, Bin
diff --git a/include/acpi/acpigen.h b/include/acpi/acpigen.h index d7a7f2f538..d68dca5dde 100644 --- a/include/acpi/acpigen.h +++ b/include/acpi/acpigen.h @@ -13,6 +13,7 @@ #include <linux/types.h> struct acpi_ctx; +struct acpi_gpio; /* Top 4 bits of the value used to indicate a three-byte length value */ #define ACPI_PKG_LEN_3_BYTES 0x80 @@ -340,4 +341,15 @@ void acpigen_write_power_res(struct acpi_ctx *ctx, const char *name, uint level, uint order, const char *const dev_states[], size_t dev_states_count); +/* + * Helper functions for enabling/disabling Tx GPIOs based on the GPIO + * polarity. These functions end up calling acpigen_soc_{set,clear}_tx_gpio to + * make callbacks into SoC acpigen code. + * + * Returns 0 on success and -1 on error. + */ +int acpigen_set_enable_tx_gpio(struct acpi_ctx *ctx, u32 tx_state_val, + const char *dw0_name, struct acpi_gpio *gpio, + bool enable); + #endif diff --git a/lib/acpi/acpigen.c b/lib/acpi/acpigen.c index 5df6ebe334..e0af36f775 100644 --- a/lib/acpi/acpigen.c +++ b/lib/acpi/acpigen.c @@ -13,6 +13,7 @@ #include <log.h> #include <uuid.h> #include <acpi/acpigen.h> +#include <acpi/acpi_device.h> #include <dm/acpi.h> u8 *acpigen_get_current(struct acpi_ctx *ctx) @@ -392,3 +393,82 @@ void acpigen_write_debug_string(struct acpi_ctx *ctx, const char *str) acpigen_write_string(ctx, str); acpigen_emit_ext_op(ctx, DEBUG_OP); } + +/** + * acpigen_get_dw0_in_local5() - Generate code to put dw0 cfg0 in local5 + * + * @ctx: ACPI context pointer + * @dw0_name: Name to use (e.g. "\\_SB.GPC0") + * @addr: GPIO pin configuration register address + * + * Store (\_SB.GPC0 (addr), Local5) + * \_SB.GPC0 is used to read cfg0 value from dw0. It is typically defined in + * the board's gpiolib.asl + */ +static void acpigen_get_dw0_in_local5(struct acpi_ctx *ctx, + const char *dw0_name, ulong addr) +{ + acpigen_write_store(ctx); + acpigen_emit_namestring(ctx, dw0_name); + acpigen_write_integer(ctx, addr); + acpigen_emit_byte(ctx, LOCAL5_OP); +} + +/** + * acpigen_set_gpio_val() - Set value of TX GPIO to on/off + * + * @ctx: ACPI context pointer + * @dw0_name: Name to use (e.g. "\\_SB.GPC0") + * @gpio_num: GPIO number to adjust + * @vaL: true to set on, false to set off + */ +static int acpigen_set_gpio_val(struct acpi_ctx *ctx, u32 tx_state_val, + const char *dw0_name, struct acpi_gpio *gpio, + bool val) +{ + acpigen_get_dw0_in_local5(ctx, dw0_name, gpio->pin0_addr); + + if (val) { + /* Or (Local5, PAD_CFG0_TX_STATE, Local5) */ + acpigen_write_or(ctx, LOCAL5_OP, tx_state_val, LOCAL5_OP); + } else { + /* Not (PAD_CFG0_TX_STATE, Local6) */ + acpigen_write_not(ctx, tx_state_val, LOCAL6_OP); + + /* And (Local5, Local6, Local5) */ + acpigen_write_and(ctx, LOCAL5_OP, LOCAL6_OP, LOCAL5_OP); + } + + /* + * \_SB.SPC0 (addr, Local5) + * \_SB.SPC0 is used to write cfg0 value in dw0. It is defined in + * gpiolib.asl. + */ + acpigen_emit_namestring(ctx, dw0_name); + acpigen_write_integer(ctx, gpio->pin0_addr); + acpigen_emit_byte(ctx, LOCAL5_OP); + + return 0; +} + +/* + * Helper functions for enabling/disabling Tx GPIOs based on the GPIO + * polarity. These functions end up calling acpigen_{set,clear}_tx_gpio to + * make callbacks into SoC acpigen code. + * + * Returns 0 on success and -1 on error. + */ +int acpigen_set_enable_tx_gpio(struct acpi_ctx *ctx, u32 tx_state_val, + const char *dw0_name, struct acpi_gpio *gpio, + bool enable) +{ + bool set; + int ret; + + set = gpio->polarity == ACPI_GPIO_ACTIVE_HIGH ? enable : !enable; + ret = acpigen_set_gpio_val(ctx, tx_state_val, dw0_name, gpio, set); + if (ret) + return log_msg_ret("call", ret); + + return 0; +} diff --git a/test/dm/acpigen.c b/test/dm/acpigen.c index e66508932b..cbd0007365 100644 --- a/test/dm/acpigen.c +++ b/test/dm/acpigen.c @@ -741,3 +741,57 @@ static int dm_test_acpi_power_res(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_acpi_power_res, 0); + +/* Test writing ACPI code to toggle a GPIO */ +static int dm_test_acpi_gpio_toggle(struct unit_test_state *uts) +{ + const uint addr = 0x80012; + const int txbit = BIT(2); + struct gpio_desc desc; + struct acpi_gpio gpio; + struct acpi_ctx *ctx; + struct udevice *dev; + u8 *ptr; + + ut_assertok(alloc_context(&ctx)); + + ut_assertok(uclass_get_device(UCLASS_TEST_FDT, 0, &dev)); + ut_asserteq_str("a-test", dev->name); + ut_assertok(gpio_request_by_name(dev, "test2-gpios", 2, &desc, 0)); + ut_assertok(gpio_get_acpi(&desc, &gpio)); + + /* Spot-check the results - see sb_gpio_get_acpi() */ + ptr = acpigen_get_current(ctx); + acpigen_set_enable_tx_gpio(ctx, txbit, "\\_SB.GPC0", &gpio, true); + acpigen_set_enable_tx_gpio(ctx, txbit, "\\_SB.GPC0", &gpio, false); + + /* Since this GPIO is active low, we expect it to be cleared here */ + ut_asserteq(STORE_OP, ptr[0]); + ut_asserteq_strn("_SB_GPC0", (char *)ptr + 3); + ut_asserteq(addr + desc.offset, get_unaligned((u32 *)(ptr + 0xc))); + ut_asserteq(LOCAL5_OP, ptr[0x10]); + + ut_asserteq(NOT_OP, ptr[0x11]); + ut_asserteq(txbit, ptr[0x12]); + ut_asserteq(AND_OP, ptr[0x14]); + ut_asserteq_strn("_SB_GPC0", (char *)ptr + 0x1a); + ut_asserteq(addr + desc.offset, get_unaligned((u32 *)(ptr + 0x23))); + ut_asserteq(LOCAL5_OP, ptr[0x27]); + + /* Now the second one, which should be set */ + ut_asserteq_strn("_SB_GPC0", (char *)ptr + 0x2b); + ut_asserteq(addr + desc.offset, get_unaligned((u32 *)(ptr + 0x34))); + ut_asserteq(LOCAL5_OP, ptr[0x38]); + + ut_asserteq(OR_OP, ptr[0x39]); + ut_asserteq(txbit, ptr[0x3b]); + ut_asserteq_strn("_SB_GPC0", (char *)ptr + 0x3f); + ut_asserteq(addr + desc.offset, get_unaligned((u32 *)(ptr + 0x48))); + ut_asserteq(LOCAL5_OP, ptr[0x4c]); + ut_asserteq(0x4d, acpigen_get_current(ctx) - ptr); + + free_context(&ctx); + + return 0; +} +DM_TEST(dm_test_acpi_gpio_toggle, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);