2014-12-16 16:17:42

by Octavian Purdila

[permalink] [raw]
Subject: [PATCH 0/4] dln2: add support for ACPI

This patch sets adds support for ACPI enumeration for devices
connected to the DLN2 bridge via a custom ACPI table.

The first two patches fix a couple of issues with the ACPI load/unload
table APIs.

The 3rd patch adds ACPI support to the MFD driver and the last patch
configures pull-up/pull-down on GPIO pins based on the APCI
configuration.

Octavian Purdila (4):
ACPICA: take ACPI_MTX_INTERPRETER in acpi_unload_table_id
ACPICA: don't release ACPI_MTX_TABLES in
acpi_tb_install_standard_table
mfd: dln2: add support for ACPI
gpio: dln2: add support for ACPI pin configuration

Documentation/acpi/dln2-acpi.txt | 62 ++++++++++++++++++
drivers/acpi/acpica/tbinstal.c | 1 -
drivers/acpi/acpica/tbxface.c | 7 ++
drivers/gpio/gpio-dln2.c | 76 ++++++++++++++++++++++
drivers/mfd/dln2.c | 134 +++++++++++++++++++++++++++++++++++++++
5 files changed, 279 insertions(+), 1 deletion(-)
create mode 100644 Documentation/acpi/dln2-acpi.txt

--
1.9.1


2014-12-16 16:17:45

by Octavian Purdila

[permalink] [raw]
Subject: [PATCH 1/4] ACPICA: take ACPI_MTX_INTERPRETER in acpi_unload_table_id

acpi_tb_delete_namespace_by_owner expects ACPI_MTX_INTERPRETER to be
taken. This fixes the following issue:

ACPI Error: Mutex [0x0] is not acquired, cannot release (20141107/utmutex-322)
Call Trace:
[<ffffffff81b0bd28>] dump_stack+0x4f/0x7b
[<ffffffff81546bfc>] acpi_ut_release_mutex+0x47/0x67
[<ffffffff81542cf1>] acpi_tb_delete_namespace_by_owner+0x57/0x8d
[<ffffffff81543ef1>] acpi_unload_table_id+0x3a/0x5e

Signed-off-by: Octavian Purdila <[email protected]>
---
drivers/acpi/acpica/tbxface.c | 7 +++++++
1 file changed, 7 insertions(+)

diff --git a/drivers/acpi/acpica/tbxface.c b/drivers/acpi/acpica/tbxface.c
index 6482b0d..9520ae1 100644
--- a/drivers/acpi/acpica/tbxface.c
+++ b/drivers/acpi/acpica/tbxface.c
@@ -281,6 +281,11 @@ acpi_status acpi_unload_table_id(acpi_owner_id id)

ACPI_FUNCTION_TRACE(acpi_unload_table_id);

+ status = acpi_ut_acquire_mutex(ACPI_MTX_INTERPRETER);
+ if (ACPI_FAILURE(status)) {
+ return_ACPI_STATUS(status);
+ }
+
/* Find table in the global table list */
for (i = 0; i < acpi_gbl_root_table_list.current_table_count; ++i) {
if (id != acpi_gbl_root_table_list.tables[i].owner_id) {
@@ -297,6 +302,8 @@ acpi_status acpi_unload_table_id(acpi_owner_id id)
acpi_tb_set_table_loaded_flag(i, FALSE);
break;
}
+
+ (void)acpi_ut_release_mutex(ACPI_MTX_INTERPRETER);
return_ACPI_STATUS(status);
}

--
1.9.1

2014-12-16 16:17:49

by Octavian Purdila

[permalink] [raw]
Subject: [PATCH 2/4] ACPICA: don't release ACPI_MTX_TABLES in acpi_tb_install_standard_table

ACPI_MTX_TABLES is taken and released by the callers of
acpi_tb_install_standard_table so releasing it in the function itself
is causing the following error if the table is reloaded:

ACPI Error: Mutex [0x2] is not acquired, cannot release (20141107/utmutex-321)
Call Trace:
[<ffffffff81b0bd48>] dump_stack+0x4f/0x7b
[<ffffffff81546bf5>] acpi_ut_release_mutex+0x47/0x67
[<ffffffff81544357>] acpi_load_table+0x73/0xcb

Signed-off-by: Octavian Purdila <[email protected]>
---
drivers/acpi/acpica/tbinstal.c | 1 -
1 file changed, 1 deletion(-)

diff --git a/drivers/acpi/acpica/tbinstal.c b/drivers/acpi/acpica/tbinstal.c
index 755b90c..c0b39f3 100644
--- a/drivers/acpi/acpica/tbinstal.c
+++ b/drivers/acpi/acpica/tbinstal.c
@@ -346,7 +346,6 @@ acpi_tb_install_standard_table(acpi_physical_address address,
*/
acpi_tb_uninstall_table(&new_table_desc);
*table_index = i;
- (void)acpi_ut_release_mutex(ACPI_MTX_TABLES);
return_ACPI_STATUS(AE_OK);
}
}
--
1.9.1

2014-12-16 16:17:52

by Octavian Purdila

[permalink] [raw]
Subject: [PATCH 4/4] gpio: dln2: add support for ACPI pin configuration

This patch configures the pull-up/pull-down properties based on the
ACPI configuration. It scans the children of the DLN2 root entry and
looks for GPIO resources and applies the pull-up/pull-down
configurations on the pins.

Signed-off-by: Octavian Purdila <[email protected]>
---
drivers/gpio/gpio-dln2.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 76 insertions(+)

diff --git a/drivers/gpio/gpio-dln2.c b/drivers/gpio/gpio-dln2.c
index 2fdb138..98189d5 100644
--- a/drivers/gpio/gpio-dln2.c
+++ b/drivers/gpio/gpio-dln2.c
@@ -19,6 +19,7 @@
#include <linux/gpio/driver.h>
#include <linux/platform_device.h>
#include <linux/mfd/dln2.h>
+#include <linux/acpi.h>

#define DLN2_GPIO_ID 0x01

@@ -36,6 +37,10 @@
#define DLN2_GPIO_PIN_GET_DIRECTION DLN2_CMD(0x14, DLN2_GPIO_ID)
#define DLN2_GPIO_PIN_SET_EVENT_CFG DLN2_CMD(0x1E, DLN2_GPIO_ID)
#define DLN2_GPIO_PIN_GET_EVENT_CFG DLN2_CMD(0x1F, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_PULLUP_ENABLE DLN2_CMD(0x18, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_PULLUP_DISABLE DLN2_CMD(0x19, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_PULLDOWN_ENABLE DLN2_CMD(0x20, DLN2_GPIO_ID)
+#define CMD_GPIO_PIN_PULLDOWN_DISABLE DLN2_CMD(0x21, DLN2_GPIO_ID)

#define DLN2_GPIO_EVENT_NONE 0
#define DLN2_GPIO_EVENT_CHANGE 1
@@ -428,6 +433,75 @@ static void dln2_gpio_event(struct platform_device *pdev, u16 echo,
}
}

+#if IS_ENABLED(CONFIG_ACPI)
+static int dln2_gpio_do_acpi_setup(struct acpi_resource *ares, void *data)
+{
+ int i;
+ u16 cmd[2];
+ const char *info;
+ struct dln2_gpio *dln2 = data;
+ const struct acpi_resource_gpio *agpio = &ares->data.gpio;
+
+ if (ares->type != ACPI_RESOURCE_TYPE_GPIO)
+ return 1;
+
+ switch (agpio->pin_config) {
+ case ACPI_PIN_CONFIG_PULLUP:
+ info = "pullup";
+ cmd[0] = CMD_GPIO_PIN_PULLDOWN_DISABLE;
+ cmd[1] = CMD_GPIO_PIN_PULLUP_ENABLE;
+ break;
+ case ACPI_PIN_CONFIG_PULLDOWN:
+ info = "pulldown";
+ cmd[0] = CMD_GPIO_PIN_PULLDOWN_ENABLE;
+ cmd[1] = CMD_GPIO_PIN_PULLUP_DISABLE;
+ break;
+ case ACPI_PIN_CONFIG_NOPULL:
+ info = "nopull";
+ cmd[0] = CMD_GPIO_PIN_PULLDOWN_DISABLE;
+ cmd[1] = CMD_GPIO_PIN_PULLUP_DISABLE;
+ break;
+ default:
+ return 1;
+ }
+
+ for (i = 0; i < agpio->pin_table_length; i++) {
+ int pin = agpio->pin_table[i];
+
+ dev_info(dln2->gpio.dev, "setting %s on pin %d\n", info, pin);
+ dln2_gpio_pin_cmd(dln2, cmd[0], pin);
+ dln2_gpio_pin_cmd(dln2, cmd[1], pin);
+ }
+
+ return 1;
+}
+
+static void dln2_gpio_acpi_setup(struct dln2_gpio *dln2)
+{
+ struct acpi_device *adev, *child;
+ acpi_handle handle;
+
+ handle = ACPI_HANDLE(dln2->gpio.dev);
+ if (!handle || acpi_bus_get_device(handle, &adev))
+ return;
+
+ list_for_each_entry(child, &adev->children, node) {
+ LIST_HEAD(res);
+ int ret;
+
+ ret = acpi_dev_get_resources(child, &res,
+ dln2_gpio_do_acpi_setup, dln2);
+ if (ret < 0)
+ continue;
+ acpi_dev_free_resource_list(&res);
+ }
+}
+#else
+static void dln2_gpio_acpi_setup(struct dln2_gpio *dln2)
+{
+}
+#endif
+
static int dln2_gpio_probe(struct platform_device *pdev)
{
struct dln2_gpio *dln2;
@@ -492,6 +566,8 @@ static int dln2_gpio_probe(struct platform_device *pdev)
goto out_gpiochip_remove;
}

+ dln2_gpio_acpi_setup(dln2);
+
return 0;

out_gpiochip_remove:
--
1.9.1

2014-12-16 16:18:29

by Octavian Purdila

[permalink] [raw]
Subject: [PATCH 3/4] mfd: dln2: add support for ACPI

This patch adds support to load a custom ACPI table that describes
devices connected via the DLN2 USB to I2C/SPI/GPIO bridge.

The ACPI table can be loaded either externally (from QEMU or with
CONFIG_ACPI_CUSTOM_DSDT) or it can be loaded as firmware file with the
name dln2.aml. The driver looks for an ACPI device entry with _HID set
to "DLN20000" and makes it the ACPI companion for DLN2 USB
sub-drivers.

Signed-off-by: Octavian Purdila <[email protected]>
---
Documentation/acpi/dln2-acpi.txt | 62 ++++++++++++++++++
drivers/mfd/dln2.c | 134 +++++++++++++++++++++++++++++++++++++++
2 files changed, 196 insertions(+)
create mode 100644 Documentation/acpi/dln2-acpi.txt

diff --git a/Documentation/acpi/dln2-acpi.txt b/Documentation/acpi/dln2-acpi.txt
new file mode 100644
index 0000000..d76605f
--- /dev/null
+++ b/Documentation/acpi/dln2-acpi.txt
@@ -0,0 +1,62 @@
+Diolan DLN2 custom APCI table
+
+The Diolan DLN2 is an USB to I2C/SPI/GPIO bridge and as such it can be used to
+connect to various I2C or SPI devices. Because these busses lack an enumeration
+protocol, the driver obtains various information about the device (such as I2C
+address and GPIO pins) from either ACPI or device tree.
+
+To allow enumerating devices and their properties via ACPI, the Diolan
+driver looks for an ACPI tree with the root _HID set to "DLN20000". If
+it finds such an ACPI object it will set the ACPI companion to the
+DLN2 MFD driver and from their it will be propagated to all its
+sub-devices (I2C, GPIO, SPI).
+
+The user can either load the custom DSDT table with three methods:
+
+1. Via QEMU (see -acpitable)
+
+2. Via the CONFIG_ACPI_CUSTOM_DSDT kernel config option (see
+Documentation/acpi/dsdt-override.txt)
+
+3. By placing the custom DSDT in the firmware paths in a file name
+dln2.aml.
+
+Here is an example ACPI table that enumerates a BMC150 accelerometer
+and defines its I2C address and GPIO pin used as an interrupt source:
+
+DefinitionBlock ("ssdt.aml", "SSDT", 1, "INTEL ", "CpuDptf", 0x00000003)
+{
+ Device (DLN0)
+ {
+ Name (_ADR, Zero)
+ Name (_HID, "DLN2000")
+
+ Device (STAC)
+ {
+ Name (_ADR, Zero)
+ Name (_HID, "BMC150A")
+ Name (_CID, "INTACCL")
+ Name (_UID, One)
+
+ Method (_CRS, 0, Serialized)
+ {
+ Name (RBUF, ResourceTemplate ()
+ {
+ I2cSerialBus (0x0010, ControllerInitiated, 0x00061A80,
+ AddressingMode7Bit, "\\DLN0",
+ 0x00, ResourceConsumer, ,)
+
+ GpioInt (Level, ActiveHigh, Exclusive, PullDown, 0x0000,
+ "\\DLN0", 0x00, ResourceConsumer, , )
+ { // Pin list
+ 0
+ }
+ })
+ Return (RBUF)
+ }
+ }
+ }
+}
+
+The resources defined in the devices under the DLN0 are those
+supported by the I2C, GPIO and SPI sub-systems.
diff --git a/drivers/mfd/dln2.c b/drivers/mfd/dln2.c
index f9c4a0b..93f6d1d 100644
--- a/drivers/mfd/dln2.c
+++ b/drivers/mfd/dln2.c
@@ -23,6 +23,8 @@
#include <linux/mfd/core.h>
#include <linux/mfd/dln2.h>
#include <linux/rculist.h>
+#include <linux/acpi.h>
+#include <linux/firmware.h>

struct dln2_header {
__le16 size;
@@ -714,6 +716,134 @@ static void dln2_stop(struct dln2_dev *dln2)

dln2_stop_rx_urbs(dln2);
}
+
+#if IS_ENABLED(CONFIG_ACPI)
+
+static struct dln2_acpi_info {
+ const struct firmware *fw;
+ acpi_owner_id table_id;
+ struct acpi_device *dev;
+ int users;
+} dln2_acpi_info;
+
+static DEFINE_MUTEX(dln2_acpi_lock);
+
+static acpi_status dln2_find_acpi_handle(acpi_handle handle, u32 level,
+ void *ctxt, void **retv)
+{
+ acpi_handle *dln2_handle = (acpi_handle *)retv;
+
+ *dln2_handle = handle;
+
+ return AE_CTRL_TERMINATE;
+}
+
+static void dln2_probe_acpi(struct dln2_dev *dln2)
+{
+ struct device *dev = &dln2->interface->dev;
+ struct dln2_acpi_info *ai = &dln2_acpi_info;
+ acpi_handle h = NULL;
+ int ret;
+ bool fw_loaded = false;
+
+ mutex_lock(&dln2_acpi_lock);
+
+ if (ai->dev)
+ goto out_success;
+
+ /*
+ * Look for the DLN2000 HID in case the ACPI table was loaded
+ * externally (e.g. from qemu).
+ */
+ acpi_get_devices("DLN20000", dln2_find_acpi_handle, NULL, &h);
+ if (!h) {
+ /* Try to load the ACPI table via a firmware file */
+ ret = request_firmware(&ai->fw, "dln2.aml", NULL);
+ if (ret)
+ goto out_unlock;
+
+ ret = acpi_load_table((void *)ai->fw->data);
+ if (ret) {
+ dev_err(dev, "invalid ACPI table\n");
+ goto out_release_fw;
+ }
+
+ acpi_get_devices("DLN20000", dln2_find_acpi_handle, NULL, &h);
+ if (!h) {
+ dev_err(dev, "not a DLN2 ACPI table\n");
+ goto out_leak_fw;
+ }
+
+ ret = acpi_get_id(h, &ai->table_id);
+ if (ret) {
+ dev_err(dev, "acpi_get_id failed: %d\n", ret);
+ goto out_leak_fw;
+ }
+
+ ret = acpi_bus_scan(h);
+ if (ret) {
+ dev_err(dev, "acpi_bus_scan failed: %d\n", ret);
+ goto out_leak_fw;
+ }
+
+ fw_loaded = true;
+ }
+
+ ret = acpi_bus_get_device(h, &ai->dev);
+ if (ret) {
+ dev_err(dev, "failed to get ACPI device: %d\n", ret);
+ if (fw_loaded) {
+ acpi_unload_table_id(ai->table_id);
+ goto out_leak_fw;
+ }
+ goto out_unlock;
+ }
+
+out_success:
+ ACPI_COMPANION_SET(dev, ai->dev);
+ ai->users++;
+ mutex_unlock(&dln2_acpi_lock);
+ return;
+
+out_release_fw:
+ release_firmware(ai->fw);
+out_leak_fw:
+ /*
+ * Once a table is loaded we can't release the firmware anymore because
+ * acpi_unload_table does not actually unload the table but keeps it in
+ * memory to speed up subsequent loads.
+ */
+ ai->fw = NULL;
+out_unlock:
+ mutex_unlock(&dln2_acpi_lock);
+}
+
+static void dln2_disconnect_acpi(struct dln2_dev *dln2)
+{
+ struct dln2_acpi_info *ai = &dln2_acpi_info;
+
+ mutex_lock(&dln2_acpi_lock);
+ if (--ai->users == 0 && ai->fw) {
+ acpi_scan_lock_acquire();
+ acpi_bus_trim(ai->dev);
+ acpi_scan_lock_release();
+ acpi_unload_table_id(ai->table_id);
+ ai->dev = NULL;
+ /* we can't release firmware see comment in dln2_probe_acpi */
+ ai->fw = NULL;
+ }
+ mutex_unlock(&dln2_acpi_lock);
+}
+#else
+static void dln2_probe_acpi(struct dln2_dev *dln2)
+{
+}
+
+static void dln2_disconnect_acpi(struct dln2_dev *dln2)
+{
+}
+#endif
+
static void dln2_disconnect(struct usb_interface *interface)
{
struct dln2_dev *dln2 = usb_get_intfdata(interface);
@@ -722,6 +852,8 @@ static void dln2_disconnect(struct usb_interface *interface)

mfd_remove_devices(&interface->dev);

+ dln2_disconnect_acpi(dln2);
+
dln2_free(dln2);
}

@@ -774,6 +906,8 @@ static int dln2_probe(struct usb_interface *interface,
goto out_stop_rx;
}

+ dln2_probe_acpi(dln2);
+
ret = mfd_add_hotplug_devices(dev, dln2_devs, ARRAY_SIZE(dln2_devs));
if (ret != 0) {
dev_err(dev, "failed to add mfd devices to core\n");
--
1.9.1

2014-12-16 18:14:21

by Sergei Shtylyov

[permalink] [raw]
Subject: Re: [PATCH 1/4] ACPICA: take ACPI_MTX_INTERPRETER in acpi_unload_table_id

Hello.

On 12/16/2014 07:12 PM, Octavian Purdila wrote:

> acpi_tb_delete_namespace_by_owner expects ACPI_MTX_INTERPRETER to be
> taken. This fixes the following issue:

> ACPI Error: Mutex [0x0] is not acquired, cannot release (20141107/utmutex-322)
> Call Trace:
> [<ffffffff81b0bd28>] dump_stack+0x4f/0x7b
> [<ffffffff81546bfc>] acpi_ut_release_mutex+0x47/0x67
> [<ffffffff81542cf1>] acpi_tb_delete_namespace_by_owner+0x57/0x8d
> [<ffffffff81543ef1>] acpi_unload_table_id+0x3a/0x5e

> Signed-off-by: Octavian Purdila <[email protected]>
> ---
> drivers/acpi/acpica/tbxface.c | 7 +++++++
> 1 file changed, 7 insertions(+)

> diff --git a/drivers/acpi/acpica/tbxface.c b/drivers/acpi/acpica/tbxface.c
> index 6482b0d..9520ae1 100644
> --- a/drivers/acpi/acpica/tbxface.c
> +++ b/drivers/acpi/acpica/tbxface.c
> @@ -281,6 +281,11 @@ acpi_status acpi_unload_table_id(acpi_owner_id id)
>
> ACPI_FUNCTION_TRACE(acpi_unload_table_id);
>
> + status = acpi_ut_acquire_mutex(ACPI_MTX_INTERPRETER);
> + if (ACPI_FAILURE(status)) {
> + return_ACPI_STATUS(status);
> + }

{} not needed here. Please run your patches thru scripts/checkpatch.pl, it
should complain in this case.

> +
> /* Find table in the global table list */
> for (i = 0; i < acpi_gbl_root_table_list.current_table_count; ++i) {
> if (id != acpi_gbl_root_table_list.tables[i].owner_id) {
> @@ -297,6 +302,8 @@ acpi_status acpi_unload_table_id(acpi_owner_id id)
> acpi_tb_set_table_loaded_flag(i, FALSE);
> break;
> }
> +
> + (void)acpi_ut_release_mutex(ACPI_MTX_INTERPRETER);

Cast to *void* not necessary either.

[...]

WBR, Sergei

2014-12-16 19:32:48

by Octavian Purdila

[permalink] [raw]
Subject: Re: [PATCH 1/4] ACPICA: take ACPI_MTX_INTERPRETER in acpi_unload_table_id

On Tue, Dec 16, 2014 at 8:14 PM, Sergei Shtylyov
<[email protected]> wrote:
> Hello.
>
> On 12/16/2014 07:12 PM, Octavian Purdila wrote:
>
>> acpi_tb_delete_namespace_by_owner expects ACPI_MTX_INTERPRETER to be
>> taken. This fixes the following issue:
>
>
>> ACPI Error: Mutex [0x0] is not acquired, cannot release
>> (20141107/utmutex-322)
>> Call Trace:
>> [<ffffffff81b0bd28>] dump_stack+0x4f/0x7b
>> [<ffffffff81546bfc>] acpi_ut_release_mutex+0x47/0x67
>> [<ffffffff81542cf1>] acpi_tb_delete_namespace_by_owner+0x57/0x8d
>> [<ffffffff81543ef1>] acpi_unload_table_id+0x3a/0x5e
>
>
>> Signed-off-by: Octavian Purdila <[email protected]>
>> ---
>> drivers/acpi/acpica/tbxface.c | 7 +++++++
>> 1 file changed, 7 insertions(+)
>
>
>> diff --git a/drivers/acpi/acpica/tbxface.c b/drivers/acpi/acpica/tbxface.c
>> index 6482b0d..9520ae1 100644
>> --- a/drivers/acpi/acpica/tbxface.c
>> +++ b/drivers/acpi/acpica/tbxface.c
>> @@ -281,6 +281,11 @@ acpi_status acpi_unload_table_id(acpi_owner_id id)
>>
>> ACPI_FUNCTION_TRACE(acpi_unload_table_id);
>>
>> + status = acpi_ut_acquire_mutex(ACPI_MTX_INTERPRETER);
>> + if (ACPI_FAILURE(status)) {
>> + return_ACPI_STATUS(status);
>> + }
>
>
> {} not needed here. Please run your patches thru scripts/checkpatch.pl,
> it should complain in this case.
>

I always run checkpatch. It does not complain.

>> +
>> /* Find table in the global table list */
>> for (i = 0; i < acpi_gbl_root_table_list.current_table_count; ++i)
>> {
>> if (id != acpi_gbl_root_table_list.tables[i].owner_id) {
>> @@ -297,6 +302,8 @@ acpi_status acpi_unload_table_id(acpi_owner_id id)
>> acpi_tb_set_table_loaded_flag(i, FALSE);
>> break;
>> }
>> +
>> + (void)acpi_ut_release_mutex(ACPI_MTX_INTERPRETER);
>
>
> Cast to *void* not necessary either.
>

AFAICS ACPICA has a slight different coding style than the rest of the
kernel and I kept using it in these patches. Rafael, please let me
know if I am wrong.