acpi: Add support for writing a GPIO power sequence

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@chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner@br-automation.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
This commit is contained in:
Simon Glass 2020-07-07 13:12:01 -06:00 committed by Bin Meng
parent f9189d5ada
commit f8054dd8ba
3 changed files with 205 additions and 0 deletions

View File

@ -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
@ -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

View File

@ -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)
@ -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;
}

View File

@ -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);