mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-01 08:59:33 +00:00
acpi: Add support for a generic power sequence
Add a way for devices to enable and disable themselves using ACPI code that updates GPIOs. This takes several timing parameters and supports enable, reset and stop. 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:
parent
f8054dd8ba
commit
740630ba73
3 changed files with 209 additions and 0 deletions
|
@ -342,4 +342,46 @@ int acpi_device_write_i2c_dev(struct acpi_ctx *ctx, const struct udevice *dev);
|
|||
*/
|
||||
int acpi_device_write_spi_dev(struct acpi_ctx *ctx, const struct udevice *dev);
|
||||
|
||||
/**
|
||||
* acpi_device_add_power_res() - Add a basic PowerResource block for a device
|
||||
*
|
||||
* This includes GPIOs to control enable, reset and stop operation of the
|
||||
* device. Each GPIO is optional, but at least one must be provided.
|
||||
* This can be applied to any device that has power control, so is fairly
|
||||
* generic.
|
||||
*
|
||||
* Reset - Put the device into / take the device out of reset.
|
||||
* Enable - Enable / disable power to device.
|
||||
* Stop - Stop / start operation of device.
|
||||
*
|
||||
* @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: Name to use to read dw0, e.g. "\\_SB.GPC0"
|
||||
* @dw0_write: Name to use to read dw0, e.g. "\\_SB.SPC0"
|
||||
* @reset_gpio: GPIO used to take device out of reset or to put it into reset
|
||||
* @reset_delay_ms: Delay to be inserted after device is taken out of reset
|
||||
* (_ON method delay)
|
||||
* @reset_off_delay_ms: Delay to be inserted after device is put into reset
|
||||
* (_OFF method delay)
|
||||
* @enable_gpio: GPIO used to enable device
|
||||
* @enable_delay_ms: Delay to be inserted after device is enabled
|
||||
* @enable_off_delay_ms: Delay to be inserted after device is disabled
|
||||
* (_OFF method delay)
|
||||
* @stop_gpio: GPIO used to stop operation of device
|
||||
* @stop_delay_ms: Delay to be inserted after disabling stop (_ON method delay)
|
||||
* @stop_off_delay_ms: Delay to be inserted after enabling stop.
|
||||
* (_OFF method delay)
|
||||
*
|
||||
* @return 0 if OK, -ve if at least one GPIO is not provided
|
||||
*/
|
||||
int acpi_device_add_power_res(struct acpi_ctx *ctx, u32 tx_state_val,
|
||||
const char *dw0_read, const char *dw0_write,
|
||||
const struct gpio_desc *reset_gpio,
|
||||
uint reset_delay_ms, uint reset_off_delay_ms,
|
||||
const struct gpio_desc *enable_gpio,
|
||||
uint enable_delay_ms, uint enable_off_delay_ms,
|
||||
const struct gpio_desc *stop_gpio,
|
||||
uint stop_delay_ms, uint stop_off_delay_ms);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -386,6 +386,105 @@ int acpi_device_write_interrupt_or_gpio(struct acpi_ctx *ctx,
|
|||
return pin;
|
||||
}
|
||||
|
||||
/* PowerResource() with Enable and/or Reset control */
|
||||
int acpi_device_add_power_res(struct acpi_ctx *ctx, u32 tx_state_val,
|
||||
const char *dw0_read, const char *dw0_write,
|
||||
const struct gpio_desc *reset_gpio,
|
||||
uint reset_delay_ms, uint reset_off_delay_ms,
|
||||
const struct gpio_desc *enable_gpio,
|
||||
uint enable_delay_ms, uint enable_off_delay_ms,
|
||||
const struct gpio_desc *stop_gpio,
|
||||
uint stop_delay_ms, uint stop_off_delay_ms)
|
||||
{
|
||||
static const char *const power_res_dev_states[] = { "_PR0", "_PR3" };
|
||||
struct acpi_gpio reset, enable, stop;
|
||||
bool has_reset, has_enable, has_stop;
|
||||
int ret;
|
||||
|
||||
gpio_get_acpi(reset_gpio, &reset);
|
||||
gpio_get_acpi(enable_gpio, &enable);
|
||||
gpio_get_acpi(stop_gpio, &stop);
|
||||
has_reset = reset.pins[0];
|
||||
has_enable = enable.pins[0];
|
||||
has_stop = stop.pins[0];
|
||||
|
||||
if (!has_reset && !has_enable && !has_stop)
|
||||
return -EINVAL;
|
||||
|
||||
/* PowerResource (PRIC, 0, 0) */
|
||||
acpigen_write_power_res(ctx, "PRIC", 0, 0, power_res_dev_states,
|
||||
ARRAY_SIZE(power_res_dev_states));
|
||||
|
||||
/* Method (_STA, 0, NotSerialized) { Return (0x1) } */
|
||||
acpigen_write_sta(ctx, 0x1);
|
||||
|
||||
/* Method (_ON, 0, Serialized) */
|
||||
acpigen_write_method_serialized(ctx, "_ON", 0);
|
||||
if (reset_gpio) {
|
||||
ret = acpigen_set_enable_tx_gpio(ctx, tx_state_val, dw0_read,
|
||||
dw0_write, &reset, true);
|
||||
if (ret)
|
||||
return log_msg_ret("reset1", ret);
|
||||
}
|
||||
if (has_enable) {
|
||||
ret = acpigen_set_enable_tx_gpio(ctx, tx_state_val, dw0_read,
|
||||
dw0_write, &enable, true);
|
||||
if (ret)
|
||||
return log_msg_ret("enable1", ret);
|
||||
if (enable_delay_ms)
|
||||
acpigen_write_sleep(ctx, enable_delay_ms);
|
||||
}
|
||||
if (has_reset) {
|
||||
ret = acpigen_set_enable_tx_gpio(ctx, tx_state_val, dw0_read,
|
||||
dw0_write, &reset, false);
|
||||
if (ret)
|
||||
return log_msg_ret("reset2", ret);
|
||||
if (reset_delay_ms)
|
||||
acpigen_write_sleep(ctx, reset_delay_ms);
|
||||
}
|
||||
if (has_stop) {
|
||||
ret = acpigen_set_enable_tx_gpio(ctx, tx_state_val, dw0_read,
|
||||
dw0_write, &stop, false);
|
||||
if (ret)
|
||||
return log_msg_ret("stop1", ret);
|
||||
if (stop_delay_ms)
|
||||
acpigen_write_sleep(ctx, stop_delay_ms);
|
||||
}
|
||||
acpigen_pop_len(ctx); /* _ON method */
|
||||
|
||||
/* Method (_OFF, 0, Serialized) */
|
||||
acpigen_write_method_serialized(ctx, "_OFF", 0);
|
||||
if (has_stop) {
|
||||
ret = acpigen_set_enable_tx_gpio(ctx, tx_state_val, dw0_read,
|
||||
dw0_write, &stop, true);
|
||||
if (ret)
|
||||
return log_msg_ret("stop2", ret);
|
||||
if (stop_off_delay_ms)
|
||||
acpigen_write_sleep(ctx, stop_off_delay_ms);
|
||||
}
|
||||
if (has_reset) {
|
||||
ret = acpigen_set_enable_tx_gpio(ctx, tx_state_val, dw0_read,
|
||||
dw0_write, &reset, true);
|
||||
if (ret)
|
||||
return log_msg_ret("reset3", ret);
|
||||
if (reset_off_delay_ms)
|
||||
acpigen_write_sleep(ctx, reset_off_delay_ms);
|
||||
}
|
||||
if (has_enable) {
|
||||
ret = acpigen_set_enable_tx_gpio(ctx, tx_state_val, dw0_read,
|
||||
dw0_write, &enable, false);
|
||||
if (ret)
|
||||
return log_msg_ret("enable2", ret);
|
||||
if (enable_off_delay_ms)
|
||||
acpigen_write_sleep(ctx, enable_off_delay_ms);
|
||||
}
|
||||
acpigen_pop_len(ctx); /* _OFF method */
|
||||
|
||||
acpigen_pop_len(ctx); /* PowerResource PRIC */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ACPI 6.3 section 6.4.3.8.2.1 - I2cSerialBus() */
|
||||
static void acpi_device_write_i2c(struct acpi_ctx *ctx,
|
||||
const struct acpi_i2c *i2c)
|
||||
|
|
|
@ -806,3 +806,71 @@ static int dm_test_acpi_gpio_toggle(struct unit_test_state *uts)
|
|||
return 0;
|
||||
}
|
||||
DM_TEST(dm_test_acpi_gpio_toggle, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
|
||||
|
||||
/* Test writing ACPI code to output power-sequence info */
|
||||
static int dm_test_acpi_power_seq(struct unit_test_state *uts)
|
||||
{
|
||||
struct gpio_desc reset, enable, stop;
|
||||
const uint addr = 0xc00dc, addr_act_low = 0x80012;
|
||||
const int txbit = BIT(2);
|
||||
struct acpi_ctx *ctx;
|
||||
struct udevice *dev;
|
||||
u8 *ptr;
|
||||
|
||||
ut_assertok(acpi_test_alloc_context_size(&ctx, 400));
|
||||
|
||||
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", 0, &reset, 0));
|
||||
ut_assertok(gpio_request_by_name(dev, "test2-gpios", 1, &enable, 0));
|
||||
ut_assertok(gpio_request_by_name(dev, "test2-gpios", 2, &stop, 0));
|
||||
ptr = acpigen_get_current(ctx);
|
||||
|
||||
ut_assertok(acpi_device_add_power_res(ctx, txbit, "\\_SB.GPC0",
|
||||
"\\_SB.SPC0", &reset, 2, 3,
|
||||
&enable, 4, 5, &stop, 6, 7));
|
||||
ut_asserteq(0x186, acpigen_get_current(ctx) - ptr);
|
||||
ut_asserteq_strn("PRIC", (char *)ptr + 0x18);
|
||||
|
||||
/* First the 'ON' sequence - spot check */
|
||||
ut_asserteq_strn("_ON_", (char *)ptr + 0x38);
|
||||
|
||||
/* reset set */
|
||||
ut_asserteq(addr + reset.offset, get_unaligned((u32 *)(ptr + 0x49)));
|
||||
ut_asserteq(OR_OP, ptr[0x52]);
|
||||
|
||||
/* enable set */
|
||||
ut_asserteq(addr + enable.offset, get_unaligned((u32 *)(ptr + 0x72)));
|
||||
ut_asserteq(OR_OP, ptr[0x7b]);
|
||||
|
||||
/* reset clear */
|
||||
ut_asserteq(addr + reset.offset, get_unaligned((u32 *)(ptr + 0x9f)));
|
||||
ut_asserteq(NOT_OP, ptr[0xa8]);
|
||||
|
||||
/* stop set (disable, active low) */
|
||||
ut_asserteq(addr_act_low + stop.offset,
|
||||
get_unaligned((u32 *)(ptr + 0xcf)));
|
||||
ut_asserteq(OR_OP, ptr[0xd8]);
|
||||
|
||||
/* Now the 'OFF' sequence */
|
||||
ut_asserteq_strn("_OFF", (char *)ptr + 0xf4);
|
||||
|
||||
/* stop clear (enable, active low) */
|
||||
ut_asserteq(addr_act_low + stop.offset,
|
||||
get_unaligned((u32 *)(ptr + 0x105)));
|
||||
ut_asserteq(NOT_OP, ptr[0x10e]);
|
||||
|
||||
/* reset clear */
|
||||
ut_asserteq(addr + reset.offset, get_unaligned((u32 *)(ptr + 0x135)));
|
||||
ut_asserteq(OR_OP, ptr[0x13e]);
|
||||
|
||||
/* enable clear */
|
||||
ut_asserteq(addr + enable.offset, get_unaligned((u32 *)(ptr + 0x162)));
|
||||
ut_asserteq(NOT_OP, ptr[0x16b]);
|
||||
|
||||
free_context(&ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DM_TEST(dm_test_acpi_power_seq, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);
|
||||
|
|
Loading…
Reference in a new issue