[PATCH 2/2] gpio / ACPI: add support for GPIO operation regions

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



GPIO operation regions is a new feature introduced in ACPI 5.0
specification. In practise it means that now ASL code can toggle GPIOs with
the help of the OS GPIO driver.

An imaginary example of such ASL code:

	Device (\SB.GPIO)
	{
		// _REG is called when the operation region handler is
		// installed.
		Method (_REG)
		{
			...
		}

		OperationRegion (GPOP, GeneralPurposeIo, 0, 0xc)
		Field (GPOP, ByteAcc, NoLock, Preserve)
		{
			Connection
			(
	                    GpioIo (Exclusive, PullDefault, 0x0000, 0x0000,
				    IoRestrictionOutputOnly, "\\_SB.GPIO",
				    0x00, ResourceConsumer,,)
				{
				    0x0005
				}
			),
			PWR0, 1
		}

		...
	}

	Device (\SB.DEV0)
	{
		...

		Method (_PS0, 0, Serialized)
		{
			// Toggle the GPIO
			Store (1, \SB.GPIO.PWR0)
		}

		Method (_PS3, 0, Serialized)
		{
			Store (0, \SB.GPIO.PRW0)
		}
	}

The device \SB.DEV0 is powered on by asserting \SB.GPIO.PWR0 GPIO line. So
when the OS calls _PS0 or _PS3, that GPIO line should be set to 1 or 0 by
the actual GPIO driver.

In order to support GPIO operation regions we install ACPI address space
handler for the device (provided that it has an ACPI handle). This handler
uses the standard GPIO APIs to toggle the pin according to what the ASL
code does.

Since there is need to attach more data to the ACPI object, we create a new
structure acpi_gpio_chip_data that contains data for both GPIO operation
regions and for ACPI event handling and make the event handling code to use
this new structure.

Signed-off-by: Mika Westerberg <mika.westerberg@xxxxxxxxxxxxxxx>
---
 drivers/gpio/gpiolib-acpi.c | 131 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 126 insertions(+), 5 deletions(-)

diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c
index e12a08e..f52536a 100644
--- a/drivers/gpio/gpiolib-acpi.c
+++ b/drivers/gpio/gpiolib-acpi.c
@@ -24,6 +24,17 @@ struct acpi_gpio_evt_pin {
 	unsigned int irq;
 };
 
+struct acpi_gpio_chip_data {
+	/*
+	 * acpi_install_address_space_handler() wants to put the connection
+	 * information at the start of the context structure we pass it, in
+	 * case we are dealing with GPIO operation regions.
+	 */
+	struct acpi_connection_info ci;
+	struct gpio_chip *chip;
+	struct list_head *evt_pins;
+};
+
 static int acpi_gpiochip_find(struct gpio_chip *gc, void *data)
 {
 	if (!gc->dev)
@@ -86,7 +97,7 @@ static irqreturn_t acpi_gpio_irq_handler_evt(int irq, void *data)
 	return IRQ_HANDLED;
 }
 
-static void acpi_gpio_evt_dh(acpi_handle handle, void *data)
+static void acpi_gpio_chip_dh(acpi_handle handle, void *data)
 {
 	/* The address of this function is used as a key. */
 }
@@ -127,12 +138,16 @@ static void acpi_gpiochip_request_interrupts(struct gpio_chip *chip)
 	if (ACPI_SUCCESS(status)) {
 		evt_pins = kzalloc(sizeof(*evt_pins), GFP_KERNEL);
 		if (evt_pins) {
+			struct acpi_gpio_chip_data *data;
+
 			INIT_LIST_HEAD(evt_pins);
-			status = acpi_attach_data(handle, acpi_gpio_evt_dh,
-						  evt_pins);
+			status = acpi_get_data(handle, acpi_gpio_chip_dh,
+					       (void **)&data);
 			if (ACPI_FAILURE(status)) {
 				kfree(evt_pins);
 				evt_pins = NULL;
+			} else {
+				data->evt_pins = evt_pins;
 			}
 		}
 	}
@@ -293,6 +308,7 @@ static void acpi_gpiochip_free_interrupts(struct gpio_chip *chip)
 	acpi_status status;
 	struct list_head *evt_pins;
 	struct acpi_gpio_evt_pin *evt_pin, *ep;
+	struct acpi_gpio_chip_data *data;
 
 	if (!chip->dev || !chip->to_irq)
 		return;
@@ -301,28 +317,133 @@ static void acpi_gpiochip_free_interrupts(struct gpio_chip *chip)
 	if (!handle)
 		return;
 
-	status = acpi_get_data(handle, acpi_gpio_evt_dh, (void **)&evt_pins);
+	status = acpi_get_data(handle, acpi_gpio_chip_dh, (void **)&data);
 	if (ACPI_FAILURE(status))
 		return;
 
+	evt_pins = data->evt_pins;
+	data->evt_pins = NULL;
+
 	list_for_each_entry_safe_reverse(evt_pin, ep, evt_pins, node) {
 		devm_free_irq(chip->dev, evt_pin->irq, evt_pin);
 		list_del(&evt_pin->node);
 		kfree(evt_pin);
 	}
 
-	acpi_detach_data(handle, acpi_gpio_evt_dh);
 	kfree(evt_pins);
 }
 
+static acpi_status
+acpi_gpio_adr_space_handler(u32 function, acpi_physical_address address,
+			    u32 bits, u64 *value, void *handler_context,
+			    void *reqion_context)
+{
+	struct acpi_gpio_chip_data *data = handler_context;
+	struct gpio_chip *chip = data->chip;
+	struct acpi_resource *ares;
+	int ret, gpio = -EINVAL;
+	unsigned long flags = 0;
+	acpi_status status;
+
+	status = acpi_buffer_to_resource(data->ci.connection, data->ci.length,
+					 &ares);
+	if (ACPI_FAILURE(status))
+		return status;
+
+	if (ares->type == ACPI_RESOURCE_TYPE_GPIO) {
+		const struct acpi_resource_gpio *agpio = &ares->data.gpio;
+
+		switch (agpio->io_restriction) {
+		case ACPI_IO_RESTRICT_NONE:
+			flags = GPIOF_DIR_IN | GPIOF_DIR_OUT;
+			break;
+		case ACPI_IO_RESTRICT_INPUT:
+			flags = GPIOF_DIR_IN;
+			break;
+		case ACPI_IO_RESTRICT_OUTPUT:
+			flags = GPIOF_DIR_OUT;
+			break;
+		}
+
+		gpio = acpi_get_gpio(agpio->resource_source.string_ptr,
+				     agpio->pin_table[0]);
+	}
+
+	ACPI_FREE(ares);
+
+	if (!gpio_is_valid(gpio)) {
+		dev_err(chip->dev, "GpioOpReg: invalid GPIO resource\n");
+		return AE_ERROR;
+	}
+
+	ret = gpio_request_one(gpio, flags, "acpi_gpio_adr_space");
+	if (ret) {
+		dev_err(chip->dev, "GpioOpReg: failed to request GPIO\n");
+		return AE_ERROR;
+	}
+
+	if (function == ACPI_WRITE)
+		gpio_set_value(gpio, (int)*value);
+	else
+		*value = gpio_get_value(gpio);
+
+	gpio_free(gpio);
+	return AE_OK;
+}
+
 void acpi_gpiochip_add(struct gpio_chip *chip)
 {
+	struct acpi_gpio_chip_data *data;
+	acpi_handle handle;
+	acpi_status status;
+
+	handle = ACPI_HANDLE(chip->dev);
+	if (!handle)
+		return;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return;
+
+	status = acpi_attach_data(handle, acpi_gpio_chip_dh, data);
+	if (ACPI_FAILURE(status)) {
+		kfree(data);
+		return;
+	}
+
+	data->chip = chip;
+
+	status = acpi_install_address_space_handler(handle, ACPI_ADR_SPACE_GPIO,
+						    acpi_gpio_adr_space_handler,
+						    NULL, data);
+	if (ACPI_FAILURE(status)) {
+		acpi_detach_data(handle, acpi_gpio_chip_dh);
+		kfree(data);
+		return;
+	}
+
 	acpi_gpiochip_request_interrupts(chip);
 }
 EXPORT_SYMBOL_GPL(acpi_gpiochip_add);
 
 void acpi_gpiochip_remove(struct gpio_chip *chip)
 {
+	struct acpi_gpio_chip_data *data;
+	acpi_handle handle;
+	acpi_status status;
+
+	handle = ACPI_HANDLE(chip->dev);
+	if (!handle)
+		return;
+
+	status = acpi_get_data(handle, acpi_gpio_chip_dh, (void **)&data);
+	if (ACPI_FAILURE(status))
+		return;
+
 	acpi_gpiochip_free_interrupts(chip);
+	acpi_remove_address_space_handler(handle, ACPI_ADR_SPACE_GPIO,
+					  acpi_gpio_adr_space_handler);
+	acpi_detach_data(handle, acpi_gpio_chip_dh);
+	kfree(data);
 }
 EXPORT_SYMBOL_GPL(acpi_gpiochip_remove);
-- 
1.8.4.rc3

--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux IBM ACPI]     [Linux Power Management]     [Linux Kernel]     [Linux Laptop]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Device Mapper]     [Linux Resources]

  Powered by Linux