acpi: Support generation of GPIO descriptor

Add a function to write a GPIO descriptor to the generated ACPI code.

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>
[bmeng: Drop comment about the type always being ACPI_GPIO_TYPE_IO]
Signed-off-by: Bin Meng <bmeng.cn@gmail.com>
diff --git a/lib/acpi/acpi_device.c b/lib/acpi/acpi_device.c
index d854a45..bbe1cfc 100644
--- a/lib/acpi/acpi_device.c
+++ b/lib/acpi/acpi_device.c
@@ -12,6 +12,7 @@
 #include <log.h>
 #include <acpi/acpi_device.h>
 #include <acpi/acpigen.h>
+#include <asm-generic/gpio.h>
 #include <dm/acpi.h>
 
 /**
@@ -203,5 +204,155 @@
 	if (ret)
 		return log_msg_ret("write", ret);
 
+	return irq.pin;
+}
+
+/* ACPI 6.3 section 6.4.3.8.1 - GPIO Interrupt or I/O */
+int acpi_device_write_gpio(struct acpi_ctx *ctx, const struct acpi_gpio *gpio)
+{
+	void *start, *desc_length;
+	void *pin_table_offset, *vendor_data_offset, *resource_offset;
+	u16 flags = 0;
+	int pin;
+
+	if (gpio->type > ACPI_GPIO_TYPE_IO)
+		return -EINVAL;
+
+	start = acpigen_get_current(ctx);
+
+	/* Byte 0: Descriptor Type */
+	acpigen_emit_byte(ctx, ACPI_DESCRIPTOR_GPIO);
+
+	/* Byte 1-2: Length (fill in later) */
+	desc_length = largeres_write_len_f(ctx);
+
+	/* Byte 3: Revision ID */
+	acpigen_emit_byte(ctx, ACPI_GPIO_REVISION_ID);
+
+	/* Byte 4: GpioIo or GpioInt */
+	acpigen_emit_byte(ctx, gpio->type);
+
+	/*
+	 * Byte 5-6: General Flags
+	 *   [15:1]: 0 => Reserved
+	 *      [0]: 1 => ResourceConsumer
+	 */
+	acpigen_emit_word(ctx, 1 << 0);
+
+	switch (gpio->type) {
+	case ACPI_GPIO_TYPE_INTERRUPT:
+		/*
+		 * Byte 7-8: GPIO Interrupt Flags
+		 *   [15:5]: 0 => Reserved
+		 *      [4]: Wake     (0=NO_WAKE   1=WAKE)
+		 *      [3]: Sharing  (0=EXCLUSIVE 1=SHARED)
+		 *    [2:1]: Polarity (0=HIGH      1=LOW     2=BOTH)
+		 *      [0]: Mode     (0=LEVEL     1=EDGE)
+		 */
+		if (gpio->irq.mode == ACPI_IRQ_EDGE_TRIGGERED)
+			flags |= 1 << 0;
+		if (gpio->irq.shared == ACPI_IRQ_SHARED)
+			flags |= 1 << 3;
+		if (gpio->irq.wake == ACPI_IRQ_WAKE)
+			flags |= 1 << 4;
+
+		switch (gpio->irq.polarity) {
+		case ACPI_IRQ_ACTIVE_HIGH:
+			flags |= 0 << 1;
+			break;
+		case ACPI_IRQ_ACTIVE_LOW:
+			flags |= 1 << 1;
+			break;
+		case ACPI_IRQ_ACTIVE_BOTH:
+			flags |= 2 << 1;
+			break;
+		}
+		break;
+
+	case ACPI_GPIO_TYPE_IO:
+		/*
+		 * Byte 7-8: GPIO IO Flags
+		 *   [15:4]: 0 => Reserved
+		 *      [3]: Sharing  (0=EXCLUSIVE 1=SHARED)
+		 *      [2]: 0 => Reserved
+		 *    [1:0]: IO Restriction
+		 *           0 => IoRestrictionNone
+		 *           1 => IoRestrictionInputOnly
+		 *           2 => IoRestrictionOutputOnly
+		 *           3 => IoRestrictionNoneAndPreserve
+		 */
+		flags |= gpio->io_restrict & 3;
+		if (gpio->io_shared)
+			flags |= 1 << 3;
+		break;
+	}
+	acpigen_emit_word(ctx, flags);
+
+	/*
+	 * Byte 9: Pin Configuration
+	 *  0x01 => Default (no configuration applied)
+	 *  0x02 => Pull-up
+	 *  0x03 => Pull-down
+	 *  0x04-0x7F => Reserved
+	 *  0x80-0xff => Vendor defined
+	 */
+	acpigen_emit_byte(ctx, gpio->pull);
+
+	/* Byte 10-11: Output Drive Strength in 1/100 mA */
+	acpigen_emit_word(ctx, gpio->output_drive_strength);
+
+	/* Byte 12-13: Debounce Timeout in 1/100 ms */
+	acpigen_emit_word(ctx, gpio->interrupt_debounce_timeout);
+
+	/* Byte 14-15: Pin Table Offset, relative to start */
+	pin_table_offset = largeres_write_len_f(ctx);
+
+	/* Byte 16: Reserved */
+	acpigen_emit_byte(ctx, 0);
+
+	/* Byte 17-18: Resource Source Name Offset, relative to start */
+	resource_offset = largeres_write_len_f(ctx);
+
+	/* Byte 19-20: Vendor Data Offset, relative to start */
+	vendor_data_offset = largeres_write_len_f(ctx);
+
+	/* Byte 21-22: Vendor Data Length */
+	acpigen_emit_word(ctx, 0);
+
+	/* Fill in Pin Table Offset */
+	largeres_fill_from_len(ctx, pin_table_offset, start);
+
+	/* Pin Table, one word for each pin */
+	for (pin = 0; pin < gpio->pin_count; pin++)
+		acpigen_emit_word(ctx, gpio->pins[pin]);
+
+	/* Fill in Resource Source Name Offset */
+	largeres_fill_from_len(ctx, resource_offset, start);
+
+	/* Resource Source Name String */
+	acpigen_emit_string(ctx, gpio->resource);
+
+	/* Fill in Vendor Data Offset */
+	largeres_fill_from_len(ctx, vendor_data_offset, start);
+
+	/* Fill in GPIO Descriptor Length (account for len word) */
+	largeres_fill_len(ctx, desc_length);
+
+	return gpio->pins[0];
+}
+
+int acpi_device_write_gpio_desc(struct acpi_ctx *ctx,
+				const struct gpio_desc *desc)
+{
+	struct acpi_gpio gpio;
+	int ret;
+
+	ret = gpio_get_acpi(desc, &gpio);
+	if (ret)
+		return log_msg_ret("desc", ret);
+	ret = acpi_device_write_gpio(ctx, &gpio);
+	if (ret < 0)
+		return log_msg_ret("gpio", ret);
+
 	return 0;
 }