diff mbox series

[v3,25/35] acpi: Add support for a generic power sequence

Message ID 20200614025523.40183-14-sjg@chromium.org
State Superseded
Headers show
Series dm: Add programmatic generation of ACPI tables (part B) | expand

Commit Message

Simon Glass June 14, 2020, 2:55 a.m. UTC
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 at chromium.org>
Reviewed-by: Wolfgang Wallner <wolfgang.wallner at br-automation.com>
---

(no changes since v1)

 include/acpi/acpi_device.h | 41 ++++++++++++++++
 lib/acpi/acpi_device.c     | 99 ++++++++++++++++++++++++++++++++++++++
 test/dm/acpigen.c          | 68 ++++++++++++++++++++++++++
 3 files changed, 208 insertions(+)
diff mbox series

Patch

diff --git a/include/acpi/acpi_device.h b/include/acpi/acpi_device.h
index c0c96183e4..5019a79528 100644
--- a/include/acpi/acpi_device.h
+++ b/include/acpi/acpi_device.h
@@ -342,4 +342,45 @@  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_name: Name to use for access to dw0, e.g. "\\_SB.GPC0"
+ * @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_name,
+			      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
diff --git a/lib/acpi/acpi_device.c b/lib/acpi/acpi_device.c
index 38101c8b96..912369498a 100644
--- a/lib/acpi/acpi_device.c
+++ b/lib/acpi/acpi_device.c
@@ -382,6 +382,105 @@  int acpi_device_write_interrupt_or_gpio(struct acpi_ctx *ctx,
 	return 0;
 }
 
+/* PowerResource() with Enable and/or Reset control */
+int acpi_device_add_power_res(struct acpi_ctx *ctx, u32 tx_state_val,
+			      const char *dw0_name,
+			      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_name,
+						 &reset, true);
+		if (ret)
+			return log_msg_ret("reset1", ret);
+	}
+	if (has_enable) {
+		ret = acpigen_set_enable_tx_gpio(ctx, tx_state_val, dw0_name,
+						 &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_name,
+						 &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_name,
+						 &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_name,
+						 &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_name,
+						 &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_name,
+						 &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)
diff --git a/test/dm/acpigen.c b/test/dm/acpigen.c
index cbd0007365..3999825953 100644
--- a/test/dm/acpigen.c
+++ b/test/dm/acpigen.c
@@ -795,3 +795,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(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",
+					      &reset, 2, 3, &enable, 4, 5,
+					      &stop, 6, 7));
+	ut_asserteq(0x16a, 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[0x4e]);
+
+	/* enable set */
+	ut_asserteq(addr + enable.offset, get_unaligned((u32 *)(ptr + 0x6e)));
+	ut_asserteq(OR_OP, ptr[0x73]);
+
+	/* reset clear */
+	ut_asserteq(addr + reset.offset, get_unaligned((u32 *)(ptr + 0x97)));
+	ut_asserteq(NOT_OP, ptr[0x9c]);
+
+	/* stop set (disable, active low) */
+	ut_asserteq(addr_act_low + stop.offset,
+		    get_unaligned((u32 *)(ptr + 0xc3)));
+	ut_asserteq(OR_OP, ptr[0xc8]);
+
+	/* Now the 'OFF' sequence */
+	ut_asserteq_strn("_OFF", (char *)ptr + 0xe4);
+
+	/* stop clear (enable, active low) */
+	ut_asserteq(addr_act_low + stop.offset,
+		    get_unaligned((u32 *)(ptr + 0xf5)));
+	ut_asserteq(NOT_OP, ptr[0xfa]);
+
+	/* reset clear */
+	ut_asserteq(addr + reset.offset, get_unaligned((u32 *)(ptr + 0x121)));
+	ut_asserteq(OR_OP, ptr[0x126]);
+
+	/* enable clear */
+	ut_asserteq(addr + enable.offset, get_unaligned((u32 *)(ptr + 0x14a)));
+	ut_asserteq(NOT_OP, ptr[0x14f]);
+
+	free_context(&ctx);
+
+	return 0;
+}
+
+DM_TEST(dm_test_acpi_power_seq, DM_TESTF_SCAN_PDATA | DM_TESTF_SCAN_FDT);