[PATCH 2/2] Input: soc_button_array: Add support for ACPI 6.0 Generic Button Device

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

 



Windows 10 tablets with gpio buttons will typically use the ACPI 6.0
Generic Button Device with a HID of ACPI0011 for these buttons.

The ACPI description for these in the ACPI0011 devices _DSD object uses
something resembling HID descriptors, except that instead of indicating
a bit index into a HID input report, the index indicates the _CRS index
for the GPIO.

The use of 1 interrupt per button, some of which need to be wakeup
sources, instead of using input reports makes it impossible to use the
HID subsystem for this.

This really is just another gpio-keys input device with the platform
data described in ACPI, so this commit adds parsing for this new way
to describe gpio-keys to the soc_button_array driver.

Signed-off-by: Hans de Goede <hdegoede@xxxxxxxxxx>
---
 drivers/input/misc/soc_button_array.c | 158 +++++++++++++++++++++++++++++++++-
 1 file changed, 157 insertions(+), 1 deletion(-)

diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c
index eb1ba4e..a52eb12 100644
--- a/drivers/input/misc/soc_button_array.c
+++ b/drivers/input/misc/soc_button_array.c
@@ -138,6 +138,152 @@ soc_button_device_create(struct platform_device *pdev,
 	return ERR_PTR(error);
 }
 
+static int soc_button_get_acpi_object_int(const union acpi_object *obj)
+{
+	if (obj->type != ACPI_TYPE_INTEGER)
+		return -1;
+
+	return obj->integer.value;
+}
+
+/* Parse a single ACPI0011 _DSD button descriptor */
+static int soc_button_parse_btn_desc(struct device *dev,
+				     const union acpi_object *desc,
+				     int collection_uid,
+				     struct soc_button_info *info)
+{
+	int upage, usage;
+
+	if (desc->type != ACPI_TYPE_PACKAGE ||
+	    desc->package.count != 5 ||
+	    /* First byte should be 1 (control) */
+	    soc_button_get_acpi_object_int(&desc->package.elements[0]) != 1 ||
+	    /* Third byte should be collection uid */
+	    soc_button_get_acpi_object_int(&desc->package.elements[2]) !=
+							    collection_uid) {
+		dev_err(dev, "Invalid ACPI Button Descriptor\n");
+		return -ENODEV;
+	}
+
+	info->event_type = EV_KEY;
+	info->acpi_index =
+		soc_button_get_acpi_object_int(&desc->package.elements[1]);
+	upage = soc_button_get_acpi_object_int(&desc->package.elements[3]);
+	usage = soc_button_get_acpi_object_int(&desc->package.elements[4]);
+
+	/*
+	 * The UUID: fa6bd625-9ce8-470d-a2c7-b3ca36c4282e descriptors use HID
+	 * usage page and usage codes, but otherwise the device is not HID
+	 * compliant: it uses one irq per button instead of generating HID
+	 * input reports and some buttons should generate wakeups where as
+	 * others should not, so we cannot use the HID subsystem.
+	 *
+	 * Luckily all devices only use a few usage page + usage combinations,
+	 * so we can simply check for the known combinations here.
+	 */
+	if (upage == 0x01 && usage == 0x81) {
+		info->name = "power";
+		info->event_code = KEY_POWER;
+		info->wakeup = true;
+	} else if (upage == 0x07 && usage == 0xe3) {
+		info->name = "home";
+		info->event_code = KEY_HOMEPAGE;
+		info->wakeup = true;
+	} else if (upage == 0x0c && usage == 0xe9) {
+		info->name = "volume_up";
+		info->event_code = KEY_VOLUMEUP;
+		info->autorepeat = true;
+	} else if (upage == 0x0c && usage == 0xea) {
+		info->name = "volume_down";
+		info->event_code = KEY_VOLUMEDOWN;
+		info->autorepeat = true;
+	} else {
+		dev_warn(dev, "Unknown button index %d upage %02x usage %02x, ignoring\n",
+			 info->acpi_index, upage, usage);
+		info->name = "unkown";
+		info->event_code = KEY_RESERVED;
+	}
+
+	return 0;
+}
+
+/* ACPI0011 _DSD btns descriptors UUID: fa6bd625-9ce8-470d-a2c7-b3ca36c4282e */
+static const u8 btns_desc_uuid[16] = {
+	0x25, 0xd6, 0x6b, 0xfa, 0xe8, 0x9c, 0x0d, 0x47,
+	0xa2, 0xc7, 0xb3, 0xca, 0x36, 0xc4, 0x28, 0x2e
+};
+
+/* Parse ACPI0011 _DSD button descriptors */
+static struct soc_button_info *soc_button_get_button_info(struct device *dev)
+{
+	struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER };
+	const union acpi_object *desc, *uuid, *btns_desc = NULL;
+	struct soc_button_info *button_info;
+	acpi_status status;
+	int i, btn, collection_uid = -1;
+
+	status = acpi_evaluate_object_typed(ACPI_HANDLE(dev), "_DSD", NULL,
+					    &buf, ACPI_TYPE_PACKAGE);
+	if (ACPI_FAILURE(status)) {
+		dev_err(dev, "ACPI _DSD object not found\n");
+		return NULL;
+	}
+
+	/* Look for the Button Descriptors UUID */
+	desc = buf.pointer;
+	for (i = 0; (i + 1) < desc->package.count; i += 2) {
+		uuid = &desc->package.elements[i];
+
+		if (uuid->type != ACPI_TYPE_BUFFER || uuid->buffer.length != 16
+		    || desc->package.elements[i + 1].type != ACPI_TYPE_PACKAGE)
+			break;;
+
+		if (memcmp(uuid->buffer.pointer, btns_desc_uuid, 16) == 0) {
+			btns_desc = &desc->package.elements[i + 1];
+			break;
+		}
+	}
+
+	if (!btns_desc) {
+		dev_err(dev, "ACPI Button Descriptors not found\n");
+		return NULL;
+	}
+
+	/* There are package.count - 1 buttons + 1 terminating empty entry */
+	button_info = devm_kcalloc(dev, btns_desc->package.count,
+				   sizeof(*button_info), GFP_KERNEL);
+	if (!button_info)
+		return NULL;
+
+	/* The first package describes the collection */
+	if (btns_desc->package.elements[0].type == ACPI_TYPE_PACKAGE &&
+	    btns_desc->package.elements[0].package.count == 5 &&
+	    /* First byte should be 0 (collection) */
+	    soc_button_get_acpi_object_int(
+		    &btns_desc->package.elements[0].package.elements[0]) == 0 &&
+	    /* Third byte should be 0 (top level collection) */
+	    soc_button_get_acpi_object_int(
+		    &btns_desc->package.elements[0].package.elements[2]) == 0) {
+		collection_uid = soc_button_get_acpi_object_int(
+			&btns_desc->package.elements[0].package.elements[1]);
+	}
+	if (collection_uid == -1) {
+		dev_err(dev, "Invalid Button Collection Descriptor\n");
+		return NULL;
+	}
+
+	/* Parse the button descriptors */
+	for (i = 1, btn = 0; i < btns_desc->package.count; i++, btn++) {
+		if (soc_button_parse_btn_desc(dev,
+					      &btns_desc->package.elements[i],
+					      collection_uid,
+					      &button_info[btn]))
+			return NULL;
+	}
+
+	return button_info;
+}
+
 static int soc_button_remove(struct platform_device *pdev)
 {
 	struct soc_button_data *priv = platform_get_drvdata(pdev);
@@ -165,7 +311,13 @@ static int soc_button_probe(struct platform_device *pdev)
 	if (!id)
 		return -ENODEV;
 
-	button_info = (struct soc_button_info *)id->driver_data;
+	if (!id->driver_data) {
+		button_info = soc_button_get_button_info(dev);
+		if (!button_info)
+			return -ENODEV;
+	} else {
+		button_info = (struct soc_button_info *)id->driver_data;
+	}
 
 	if (gpiod_count(dev, KBUILD_MODNAME) <= 0) {
 		dev_dbg(dev, "no GPIO attached, ignoring...\n");
@@ -195,6 +347,9 @@ static int soc_button_probe(struct platform_device *pdev)
 	if (!priv->children[0] && !priv->children[1])
 		return -ENODEV;
 
+	if (!id->driver_data)
+		devm_kfree(dev, button_info);
+
 	return 0;
 }
 
@@ -214,6 +369,7 @@ static struct soc_button_info soc_button_PNP0C40[] = {
 
 static const struct acpi_device_id soc_button_acpi_match[] = {
 	{ "PNP0C40", (unsigned long)soc_button_PNP0C40 },
+	{ "ACPI0011", 0 },
 	{ }
 };
 
-- 
2.9.3

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



[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux