From patchwork Tue Jul 7 19:12:01 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 240915 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Tue, 7 Jul 2020 13:12:01 -0600 Subject: [PATCH v4 24/35] acpi: Add support for writing a GPIO power sequence In-Reply-To: <20200707191212.2542638-1-sjg@chromium.org> References: <20200707191212.2542638-1-sjg@chromium.org> Message-ID: <20200707191212.2542638-15-sjg@chromium.org> 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 Reviewed-by: Wolfgang Wallner Reviewed-by: Bin Meng --- Changes in v4: - Update functions to return GPIO/IRQ pin number - Use a separate variables for reading and writing DW0 - Correct bug in acpigen_set_gpio_val() by putting tx_state_val in LOCAL0 - Fix up the comment for acpigen_set_enable_tx_gpio() - Explain access to DW0 register a bit better and provide a sample - Expand the comments for acpigen_get_dw0_in_local5() - Fix 'diabl' typo include/acpi/acpigen.h | 56 ++++++++++++++++++++++++++++ lib/acpi/acpigen.c | 85 ++++++++++++++++++++++++++++++++++++++++++ test/dm/acpigen.c | 64 +++++++++++++++++++++++++++++++ 3 files changed, 205 insertions(+) diff --git a/include/acpi/acpigen.h b/include/acpi/acpigen.h index 2c07a56208..d06d2c0c73 100644 --- a/include/acpi/acpigen.h +++ b/include/acpi/acpigen.h @@ -13,6 +13,7 @@ #include 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 @@ -343,4 +344,59 @@ 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); +/** + * acpigen_set_enable_tx_gpio() - Emit ACPI code to enable/disable a GPIO + * + * This emits code to either enable to disable a Tx GPIO. It takes account of + * the GPIO polarity. + * + * The code needs access to the DW0 register for the pad being used. This is + * provided by gpio->pin0_addr and ACPI methods must be defined for the board + * which can read and write the pad's DW0 register given this address: + * @dw0_read: takes a single argument, the DW0 address + * returns the DW0 value + * @dw0:write: takes two arguments, the DW0 address and the value to write + * no return value + * + * Example code (-- means comment): + * + * -- Get Pad Configuration DW0 register value + * Method (GPC0, 0x1, Serialized) + * { + * -- Arg0 - GPIO DW0 address + * Store (Arg0, Local0) + * OperationRegion (PDW0, SystemMemory, Local0, 4) + * Field (PDW0, AnyAcc, NoLock, Preserve) { + * TEMP, 32 + * } + * Return (TEMP) + * } + * + * -- Set Pad Configuration DW0 register value + * Method (SPC0, 0x2, Serialized) + * { + * -- Arg0 - GPIO DW0 address + * -- Arg1 - Value for DW0 register + * Store (Arg0, Local0) + * OperationRegion (PDW0, SystemMemory, Local0, 4) + * Field (PDW0, AnyAcc, NoLock, Preserve) { + * TEMP,32 + * } + * Store (Arg1, TEMP) + * } + * + * + * @ctx: ACPI context pointer + * @tx_state_val: Mask to use to toggle the TX state on the GPIO pin, e,g. + * PAD_CFG0_TX_STATE + * @dw0_read: Method name to use to read dw0, e.g. "\\_SB.GPC0" + * @dw0_write: Method name to use to read dw0, e.g. "\\_SB.SPC0" + * @gpio: GPIO to change + * @enable: true to enable GPIO, false to disable + * Returns 0 on success, -ve on error. + */ +int acpigen_set_enable_tx_gpio(struct acpi_ctx *ctx, u32 tx_state_val, + const char *dw0_read, const char *dw0_write, + struct acpi_gpio *gpio, bool enable); + #endif diff --git a/lib/acpi/acpigen.c b/lib/acpi/acpigen.c index 70d734245b..45216c1f9d 100644 --- a/lib/acpi/acpigen.c +++ b/lib/acpi/acpigen.c @@ -13,6 +13,7 @@ #include #include #include +#include #include u8 *acpigen_get_current(struct acpi_ctx *ctx) @@ -395,3 +396,87 @@ 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 + * + * 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 + * + * The value needs to be stored in a local variable so that it can be used in + * expressions in the ACPI code. + * + * @ctx: ACPI context pointer + * @dw0_read: Name to use to read dw0, e.g. "\\_SB.GPC0" + * @addr: GPIO pin configuration register address + * + */ +static void acpigen_get_dw0_in_local5(struct acpi_ctx *ctx, + const char *dw0_read, ulong addr) +{ + acpigen_write_store(ctx); + acpigen_emit_namestring(ctx, dw0_read); + acpigen_write_integer(ctx, addr); + acpigen_emit_byte(ctx, LOCAL5_OP); +} + +/** + * acpigen_set_gpio_val() - Emit code to set value of TX GPIO to on/off + * + * @ctx: ACPI context pointer + * @dw0_read: Method name to use to read dw0, e.g. "\\_SB.GPC0" + * @dw0_write: Method name to use to read dw0, e.g. "\\_SB.SPC0" + * @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_read, const char *dw0_write, + struct acpi_gpio *gpio, bool val) +{ + acpigen_get_dw0_in_local5(ctx, dw0_read, gpio->pin0_addr); + + /* Store (0x40, Local0) */ + acpigen_write_store(ctx); + acpigen_write_integer(ctx, tx_state_val); + acpigen_emit_byte(ctx, LOCAL0_OP); + + if (val) { + /* Or (Local5, PAD_CFG0_TX_STATE, Local5) */ + acpigen_write_or(ctx, LOCAL5_OP, LOCAL0_OP, LOCAL5_OP); + } else { + /* Not (PAD_CFG0_TX_STATE, Local6) */ + acpigen_write_not(ctx, LOCAL0_OP, 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_write); + acpigen_write_integer(ctx, gpio->pin0_addr); + acpigen_emit_byte(ctx, LOCAL5_OP); + + return 0; +} + +int acpigen_set_enable_tx_gpio(struct acpi_ctx *ctx, u32 tx_state_val, + const char *dw0_read, const char *dw0_write, + 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_read, dw0_write, 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 df062a210c..9b699fc26a 100644 --- a/test/dm/acpigen.c +++ b/test/dm/acpigen.c @@ -742,3 +742,67 @@ 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", "\\_SB.SPC0", + &gpio, true); + acpigen_set_enable_tx_gpio(ctx, txbit, "\\_SB.GPC0", "\\_SB.SPC0", + &gpio, false); + + /* Since this GPIO is active low, we expect it to be cleared here */ + ut_asserteq(STORE_OP, *ptr); + 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(STORE_OP, ptr[0x11]); + ut_asserteq(BYTE_PREFIX, ptr[0x12]); + ut_asserteq(txbit, ptr[0x13]); + ut_asserteq(LOCAL0_OP, ptr[0x14]); + + ut_asserteq(NOT_OP, ptr[0x15]); + ut_asserteq(LOCAL0_OP, ptr[0x16]); + ut_asserteq(LOCAL6_OP, ptr[0x17]); + ut_asserteq(AND_OP, ptr[0x18]); + ut_asserteq_strn("_SB_SPC0", (char *)ptr + 0x1e); + ut_asserteq(addr + desc.offset, get_unaligned((u32 *)(ptr + 0x27))); + ut_asserteq(LOCAL5_OP, ptr[0x2b]); + + /* Now the second one, which should be set */ + ut_asserteq_strn("_SB_GPC0", (char *)ptr + 0x2f); + ut_asserteq(addr + desc.offset, get_unaligned((u32 *)(ptr + 0x38))); + ut_asserteq(LOCAL5_OP, ptr[0x3c]); + + ut_asserteq(STORE_OP, ptr[0x3d]); + + ut_asserteq(OR_OP, ptr[0x41]); + ut_asserteq(LOCAL0_OP, ptr[0x43]); + ut_asserteq_strn("_SB_SPC0", (char *)ptr + 0x47); + ut_asserteq(addr + desc.offset, get_unaligned((u32 *)(ptr + 0x50))); + ut_asserteq(LOCAL5_OP, ptr[0x54]); + ut_asserteq(0x55, 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);