diff mbox

[6/6] gpio / ACPI: Add support for ACPI GPIO operation regions

Message ID 1393257611-18031-7-git-send-email-mika.westerberg@linux.intel.com (mailing list archive)
State Changes Requested, archived
Headers show

Commit Message

Mika Westerberg Feb. 24, 2014, 4 p.m. UTC
GPIO operation regions is a new feature introduced in ACPI 5.0
specification. This feature adds a way for platform ASL code to call back
to OS GPIO driver and toggle GPIO pins.

An example ASL code from Lenovo Miix 2 tablet with only relevant part
listed:

 Device (\_SB.GPO0)
 {
     Name (AVBL, Zero)
     Method (_REG, 2, NotSerialized)
     {
         If (LEqual (Arg0, 0x08))
         {
             // Marks the region available
             Store (Arg1, AVBL)
         }
     }

     OperationRegion (GPOP, GeneralPurposeIo, Zero, 0x0C)
     Field (GPOP, ByteAcc, NoLock, Preserve)
     {
         Connection (
             GpioIo (Exclusive, PullDefault, 0, 0, IoRestrictionOutputOnly,
                     "\\_SB.GPO0", 0x00, ResourceConsumer,,)
             {
                 0x003B
             }
         ),
         SHD3,   1,
     }
 }

 Device (SHUB)
 {
     Method (_PS0, 0, Serialized)
     {
         If (LEqual (\_SB.GPO0.AVBL, One))
         {
             Store (One, \_SB.GPO0.SHD3)
             Sleep (0x32)
         }
     }
     Method (_PS3, 0, Serialized)
     {
         If (LEqual (\_SB.GPO0.AVBL, One))
         {
             Store (Zero, \_SB.GPO0.SHD3)
         }
     }
 }

The sensor hub (SHUB) device uses GPIO connection SHD3 to power the device
whenever the GPIO operation region is available.

Implement the support by registering GPIO operation region handlers for all
GPIO devices that have an ACPI handle. First time the GPIO is used by the
ASL code we make sure that the GPIO stays requested until the GPIO chip
driver itself is unloaded. If we find out that the GPIO is already
requested we just toggle it according to the value got from ASL code.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/gpio/gpiolib-acpi.c | 156 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 156 insertions(+)

Comments

Rafael J. Wysocki Feb. 25, 2014, 2:55 p.m. UTC | #1
On Monday, February 24, 2014 06:00:11 PM Mika Westerberg wrote:
> GPIO operation regions is a new feature introduced in ACPI 5.0
> specification. This feature adds a way for platform ASL code to call back
> to OS GPIO driver and toggle GPIO pins.
> 
> An example ASL code from Lenovo Miix 2 tablet with only relevant part
> listed:
> 
>  Device (\_SB.GPO0)
>  {
>      Name (AVBL, Zero)
>      Method (_REG, 2, NotSerialized)
>      {
>          If (LEqual (Arg0, 0x08))
>          {
>              // Marks the region available
>              Store (Arg1, AVBL)
>          }
>      }
> 
>      OperationRegion (GPOP, GeneralPurposeIo, Zero, 0x0C)
>      Field (GPOP, ByteAcc, NoLock, Preserve)
>      {
>          Connection (
>              GpioIo (Exclusive, PullDefault, 0, 0, IoRestrictionOutputOnly,
>                      "\\_SB.GPO0", 0x00, ResourceConsumer,,)
>              {
>                  0x003B
>              }
>          ),
>          SHD3,   1,
>      }
>  }
> 
>  Device (SHUB)
>  {
>      Method (_PS0, 0, Serialized)
>      {
>          If (LEqual (\_SB.GPO0.AVBL, One))
>          {
>              Store (One, \_SB.GPO0.SHD3)
>              Sleep (0x32)
>          }
>      }
>      Method (_PS3, 0, Serialized)
>      {
>          If (LEqual (\_SB.GPO0.AVBL, One))
>          {
>              Store (Zero, \_SB.GPO0.SHD3)
>          }
>      }
>  }
> 
> The sensor hub (SHUB) device uses GPIO connection SHD3 to power the device
> whenever the GPIO operation region is available.

I would add more explanation of the ASL above here.  Basically, how it is
supposed to work and what's the handler's role in it.

> Implement the support by registering GPIO operation region handlers for all
> GPIO devices that have an ACPI handle. First time the GPIO is used by the
> ASL code we make sure that the GPIO stays requested until the GPIO chip
> driver itself is unloaded. If we find out that the GPIO is already
> requested we just toggle it according to the value got from ASL code.

The patch itself looks good to me.

> Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
> ---
>  drivers/gpio/gpiolib-acpi.c | 156 ++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 156 insertions(+)
> 
> diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c
> index 275735f390af..0133d38c447f 100644
> --- a/drivers/gpio/gpiolib-acpi.c
> +++ b/drivers/gpio/gpiolib-acpi.c
> @@ -16,6 +16,7 @@
>  #include <linux/export.h>
>  #include <linux/acpi.h>
>  #include <linux/interrupt.h>
> +#include <linux/mutex.h>
>  
>  #include "gpiolib.h"
>  
> @@ -26,7 +27,20 @@ struct acpi_gpio_event {
>  	unsigned int irq;
>  };
>  
> +struct acpi_gpio_connection {
> +	struct list_head node;
> +	struct gpio_desc *desc;
> +};
> +
>  struct acpi_gpio_chip {
> +	/*
> +	 * ACPICA requires that the first field of the context parameter
> +	 * passed to acpi_install_address_space_handler() is large enough
> +	 * to hold struct acpi_connection_info.
> +	 */
> +	struct acpi_connection_info conn_info;
> +	struct list_head conns;
> +	struct mutex conn_lock;
>  	struct gpio_chip *chip;
>  	struct list_head events;
>  };
> @@ -334,6 +348,146 @@ struct gpio_desc *acpi_get_gpiod_by_index(struct device *dev, int index,
>  	return lookup.desc ? lookup.desc : ERR_PTR(-ENOENT);
>  }
>  
> +static acpi_status
> +acpi_gpio_adr_space_handler(u32 function, acpi_physical_address address,
> +			    u32 bits, u64 *value, void *handler_context,
> +			    void *region_context)
> +{
> +	struct acpi_gpio_chip *achip = region_context;
> +	struct gpio_chip *chip = achip->chip;
> +	struct acpi_resource_gpio *agpio;
> +	struct acpi_resource *ares;
> +	acpi_status status;
> +	bool pull;
> +	int i;
> +
> +	status = acpi_buffer_to_resource(achip->conn_info.connection,
> +					 achip->conn_info.length, &ares);
> +	if (ACPI_FAILURE(status))
> +		return status;
> +
> +	if (WARN_ON(ares->type != ACPI_RESOURCE_TYPE_GPIO)) {
> +		ACPI_FREE(ares);
> +		return AE_BAD_PARAMETER;
> +	}
> +
> +	agpio = &ares->data.gpio;
> +	pull = agpio->pin_config == ACPI_PIN_CONFIG_PULLUP;
> +
> +	if (WARN_ON(agpio->io_restriction == ACPI_IO_RESTRICT_INPUT &&
> +	    function == ACPI_WRITE)) {
> +		ACPI_FREE(ares);
> +		return AE_BAD_PARAMETER;
> +	}
> +
> +	for (i = 0; i < agpio->pin_table_length; i++) {
> +		unsigned pin = agpio->pin_table[i];
> +		struct acpi_gpio_connection *conn;
> +		struct gpio_desc *desc;
> +		bool found;
> +
> +		desc = gpiochip_get_desc(chip, pin);
> +		if (IS_ERR(desc)) {
> +			status = AE_ERROR;
> +			goto out;
> +		}
> +
> +		mutex_lock(&achip->conn_lock);
> +
> +		found = false;
> +		list_for_each_entry(conn, &achip->conns, node) {
> +			if (conn->desc == desc) {
> +				found = true;
> +				break;
> +			}
> +		}
> +		if (!found) {
> +			int ret;
> +
> +			ret = gpiochip_request_own_desc(desc, "ACPI:OpRegion");
> +			if (ret) {
> +				status = AE_ERROR;
> +				mutex_unlock(&achip->conn_lock);
> +				goto out;
> +			}
> +
> +			switch (agpio->io_restriction) {
> +			case ACPI_IO_RESTRICT_INPUT:
> +				gpiod_direction_input(desc);
> +				break;
> +			case ACPI_IO_RESTRICT_OUTPUT:
> +				gpiod_direction_output(desc, pull);
> +				break;
> +			default:
> +				/*
> +				 * Assume that the BIOS has configured the
> +				 * direction and pull accordingly.
> +				 */
> +				break;
> +			}
> +
> +			conn = kzalloc(sizeof(*conn), GFP_KERNEL);
> +			if (!conn) {
> +				status = AE_NO_MEMORY;
> +				mutex_unlock(&achip->conn_lock);
> +				goto out;
> +			}
> +
> +			conn->desc = desc;
> +
> +			list_add_tail(&conn->node, &achip->conns);
> +		}
> +
> +		mutex_unlock(&achip->conn_lock);
> +
> +
> +		if (function == ACPI_WRITE)
> +			gpiod_set_raw_value(desc, !!((1 << i) & *value));
> +		else
> +			*value |= gpiod_get_raw_value(desc) << i;
> +	}
> +
> +out:
> +	ACPI_FREE(ares);
> +	return status;
> +}
> +
> +static void acpi_gpiochip_request_regions(struct acpi_gpio_chip *achip)
> +{
> +	struct gpio_chip *chip = achip->chip;
> +	acpi_handle handle = ACPI_HANDLE(chip->dev);
> +	acpi_status status;
> +
> +	INIT_LIST_HEAD(&achip->conns);
> +	mutex_init(&achip->conn_lock);
> +	status = acpi_install_address_space_handler(handle, ACPI_ADR_SPACE_GPIO,
> +						    acpi_gpio_adr_space_handler,
> +						    NULL, achip);
> +	if (ACPI_FAILURE(status))
> +		dev_err(chip->dev, "Failed to install GPIO OpRegion handler\n");
> +}
> +
> +static void acpi_gpiochip_free_regions(struct acpi_gpio_chip *achip)
> +{
> +	struct gpio_chip *chip = achip->chip;
> +	acpi_handle handle = ACPI_HANDLE(chip->dev);
> +	struct acpi_gpio_connection *conn, *tmp;
> +	acpi_status status;
> +
> +	status = acpi_remove_address_space_handler(handle, ACPI_ADR_SPACE_GPIO,
> +						   acpi_gpio_adr_space_handler);
> +	if (ACPI_FAILURE(status)) {
> +		dev_err(chip->dev, "Failed to remove GPIO OpRegion handler\n");
> +		return;
> +	}
> +
> +	list_for_each_entry_safe_reverse(conn, tmp, &achip->conns, node) {
> +		gpiochip_free_own_desc(conn->desc);
> +		list_del(&conn->node);
> +		kfree(conn);
> +	}
> +}
> +
>  void acpi_gpiochip_add(struct gpio_chip *chip)
>  {
>  	struct acpi_gpio_chip *achip;
> @@ -361,6 +515,7 @@ void acpi_gpiochip_add(struct gpio_chip *chip)
>  	}
>  
>  	acpi_gpiochip_request_interrupts(achip);
> +	acpi_gpiochip_request_regions(achip);
>  }
>  
>  void acpi_gpiochip_remove(struct gpio_chip *chip)
> @@ -379,6 +534,7 @@ void acpi_gpiochip_remove(struct gpio_chip *chip)
>  		return;
>  	}
>  
> +	acpi_gpiochip_free_regions(achip);
>  	acpi_gpiochip_free_interrupts(achip);
>  
>  	acpi_detach_data(handle, acpi_gpio_chip_dh);
>
Mika Westerberg Feb. 26, 2014, 9:11 a.m. UTC | #2
On Tue, Feb 25, 2014 at 03:55:02PM +0100, Rafael J. Wysocki wrote:
> On Monday, February 24, 2014 06:00:11 PM Mika Westerberg wrote:
> > GPIO operation regions is a new feature introduced in ACPI 5.0
> > specification. This feature adds a way for platform ASL code to call back
> > to OS GPIO driver and toggle GPIO pins.
> > 
> > An example ASL code from Lenovo Miix 2 tablet with only relevant part
> > listed:
> > 
> >  Device (\_SB.GPO0)
> >  {
> >      Name (AVBL, Zero)
> >      Method (_REG, 2, NotSerialized)
> >      {
> >          If (LEqual (Arg0, 0x08))
> >          {
> >              // Marks the region available
> >              Store (Arg1, AVBL)
> >          }
> >      }
> > 
> >      OperationRegion (GPOP, GeneralPurposeIo, Zero, 0x0C)
> >      Field (GPOP, ByteAcc, NoLock, Preserve)
> >      {
> >          Connection (
> >              GpioIo (Exclusive, PullDefault, 0, 0, IoRestrictionOutputOnly,
> >                      "\\_SB.GPO0", 0x00, ResourceConsumer,,)
> >              {
> >                  0x003B
> >              }
> >          ),
> >          SHD3,   1,
> >      }
> >  }
> > 
> >  Device (SHUB)
> >  {
> >      Method (_PS0, 0, Serialized)
> >      {
> >          If (LEqual (\_SB.GPO0.AVBL, One))
> >          {
> >              Store (One, \_SB.GPO0.SHD3)
> >              Sleep (0x32)
> >          }
> >      }
> >      Method (_PS3, 0, Serialized)
> >      {
> >          If (LEqual (\_SB.GPO0.AVBL, One))
> >          {
> >              Store (Zero, \_SB.GPO0.SHD3)
> >          }
> >      }
> >  }
> > 
> > The sensor hub (SHUB) device uses GPIO connection SHD3 to power the device
> > whenever the GPIO operation region is available.
> 
> I would add more explanation of the ASL above here.  Basically, how it is
> supposed to work and what's the handler's role in it.

OK, I will do that in the next revision.

> > Implement the support by registering GPIO operation region handlers for all
> > GPIO devices that have an ACPI handle. First time the GPIO is used by the
> > ASL code we make sure that the GPIO stays requested until the GPIO chip
> > driver itself is unloaded. If we find out that the GPIO is already
> > requested we just toggle it according to the value got from ASL code.
> 
> The patch itself looks good to me.

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

Patch

diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c
index 275735f390af..0133d38c447f 100644
--- a/drivers/gpio/gpiolib-acpi.c
+++ b/drivers/gpio/gpiolib-acpi.c
@@ -16,6 +16,7 @@ 
 #include <linux/export.h>
 #include <linux/acpi.h>
 #include <linux/interrupt.h>
+#include <linux/mutex.h>
 
 #include "gpiolib.h"
 
@@ -26,7 +27,20 @@  struct acpi_gpio_event {
 	unsigned int irq;
 };
 
+struct acpi_gpio_connection {
+	struct list_head node;
+	struct gpio_desc *desc;
+};
+
 struct acpi_gpio_chip {
+	/*
+	 * ACPICA requires that the first field of the context parameter
+	 * passed to acpi_install_address_space_handler() is large enough
+	 * to hold struct acpi_connection_info.
+	 */
+	struct acpi_connection_info conn_info;
+	struct list_head conns;
+	struct mutex conn_lock;
 	struct gpio_chip *chip;
 	struct list_head events;
 };
@@ -334,6 +348,146 @@  struct gpio_desc *acpi_get_gpiod_by_index(struct device *dev, int index,
 	return lookup.desc ? lookup.desc : ERR_PTR(-ENOENT);
 }
 
+static acpi_status
+acpi_gpio_adr_space_handler(u32 function, acpi_physical_address address,
+			    u32 bits, u64 *value, void *handler_context,
+			    void *region_context)
+{
+	struct acpi_gpio_chip *achip = region_context;
+	struct gpio_chip *chip = achip->chip;
+	struct acpi_resource_gpio *agpio;
+	struct acpi_resource *ares;
+	acpi_status status;
+	bool pull;
+	int i;
+
+	status = acpi_buffer_to_resource(achip->conn_info.connection,
+					 achip->conn_info.length, &ares);
+	if (ACPI_FAILURE(status))
+		return status;
+
+	if (WARN_ON(ares->type != ACPI_RESOURCE_TYPE_GPIO)) {
+		ACPI_FREE(ares);
+		return AE_BAD_PARAMETER;
+	}
+
+	agpio = &ares->data.gpio;
+	pull = agpio->pin_config == ACPI_PIN_CONFIG_PULLUP;
+
+	if (WARN_ON(agpio->io_restriction == ACPI_IO_RESTRICT_INPUT &&
+	    function == ACPI_WRITE)) {
+		ACPI_FREE(ares);
+		return AE_BAD_PARAMETER;
+	}
+
+	for (i = 0; i < agpio->pin_table_length; i++) {
+		unsigned pin = agpio->pin_table[i];
+		struct acpi_gpio_connection *conn;
+		struct gpio_desc *desc;
+		bool found;
+
+		desc = gpiochip_get_desc(chip, pin);
+		if (IS_ERR(desc)) {
+			status = AE_ERROR;
+			goto out;
+		}
+
+		mutex_lock(&achip->conn_lock);
+
+		found = false;
+		list_for_each_entry(conn, &achip->conns, node) {
+			if (conn->desc == desc) {
+				found = true;
+				break;
+			}
+		}
+		if (!found) {
+			int ret;
+
+			ret = gpiochip_request_own_desc(desc, "ACPI:OpRegion");
+			if (ret) {
+				status = AE_ERROR;
+				mutex_unlock(&achip->conn_lock);
+				goto out;
+			}
+
+			switch (agpio->io_restriction) {
+			case ACPI_IO_RESTRICT_INPUT:
+				gpiod_direction_input(desc);
+				break;
+			case ACPI_IO_RESTRICT_OUTPUT:
+				gpiod_direction_output(desc, pull);
+				break;
+			default:
+				/*
+				 * Assume that the BIOS has configured the
+				 * direction and pull accordingly.
+				 */
+				break;
+			}
+
+			conn = kzalloc(sizeof(*conn), GFP_KERNEL);
+			if (!conn) {
+				status = AE_NO_MEMORY;
+				mutex_unlock(&achip->conn_lock);
+				goto out;
+			}
+
+			conn->desc = desc;
+
+			list_add_tail(&conn->node, &achip->conns);
+		}
+
+		mutex_unlock(&achip->conn_lock);
+
+
+		if (function == ACPI_WRITE)
+			gpiod_set_raw_value(desc, !!((1 << i) & *value));
+		else
+			*value |= gpiod_get_raw_value(desc) << i;
+	}
+
+out:
+	ACPI_FREE(ares);
+	return status;
+}
+
+static void acpi_gpiochip_request_regions(struct acpi_gpio_chip *achip)
+{
+	struct gpio_chip *chip = achip->chip;
+	acpi_handle handle = ACPI_HANDLE(chip->dev);
+	acpi_status status;
+
+	INIT_LIST_HEAD(&achip->conns);
+	mutex_init(&achip->conn_lock);
+	status = acpi_install_address_space_handler(handle, ACPI_ADR_SPACE_GPIO,
+						    acpi_gpio_adr_space_handler,
+						    NULL, achip);
+	if (ACPI_FAILURE(status))
+		dev_err(chip->dev, "Failed to install GPIO OpRegion handler\n");
+}
+
+static void acpi_gpiochip_free_regions(struct acpi_gpio_chip *achip)
+{
+	struct gpio_chip *chip = achip->chip;
+	acpi_handle handle = ACPI_HANDLE(chip->dev);
+	struct acpi_gpio_connection *conn, *tmp;
+	acpi_status status;
+
+	status = acpi_remove_address_space_handler(handle, ACPI_ADR_SPACE_GPIO,
+						   acpi_gpio_adr_space_handler);
+	if (ACPI_FAILURE(status)) {
+		dev_err(chip->dev, "Failed to remove GPIO OpRegion handler\n");
+		return;
+	}
+
+	list_for_each_entry_safe_reverse(conn, tmp, &achip->conns, node) {
+		gpiochip_free_own_desc(conn->desc);
+		list_del(&conn->node);
+		kfree(conn);
+	}
+}
+
 void acpi_gpiochip_add(struct gpio_chip *chip)
 {
 	struct acpi_gpio_chip *achip;
@@ -361,6 +515,7 @@  void acpi_gpiochip_add(struct gpio_chip *chip)
 	}
 
 	acpi_gpiochip_request_interrupts(achip);
+	acpi_gpiochip_request_regions(achip);
 }
 
 void acpi_gpiochip_remove(struct gpio_chip *chip)
@@ -379,6 +534,7 @@  void acpi_gpiochip_remove(struct gpio_chip *chip)
 		return;
 	}
 
+	acpi_gpiochip_free_regions(achip);
 	acpi_gpiochip_free_interrupts(achip);
 
 	acpi_detach_data(handle, acpi_gpio_chip_dh);