i2c: Add a generic driver to generate ACPI info
authorSimon Glass <sjg@chromium.org>
Tue, 22 Sep 2020 18:45:01 +0000 (12:45 -0600)
committerBin Meng <bmeng.cn@gmail.com>
Fri, 25 Sep 2020 03:27:15 +0000 (11:27 +0800)
Many I2C devices produce roughly the same ACPI data with just things like
the GPIO/interrupt information being different.

This can be handled by a generic driver along with some information in the
device tree.

Add a generic i2c driver for this purpose.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Heiko Schocher <hs@denx.de>
doc/device-tree-bindings/i2c/generic-acpi.txt [new file with mode: 0644]
drivers/i2c/Makefile
drivers/i2c/acpi_i2c.c [new file with mode: 0644]
drivers/i2c/acpi_i2c.h [new file with mode: 0644]
drivers/i2c/i2c-uclass.c
include/acpi/acpi_device.h
include/i2c.h

diff --git a/doc/device-tree-bindings/i2c/generic-acpi.txt b/doc/device-tree-bindings/i2c/generic-acpi.txt
new file mode 100644 (file)
index 0000000..3510a71
--- /dev/null
@@ -0,0 +1,42 @@
+I2C generic device
+==================
+
+This is used only to generate ACPI tables for an I2C device.
+
+Required properties :
+
+ - compatible : "i2c-chip";
+ - reg : I2C chip address
+ - acpi,hid : HID name for the device
+
+Optional properies in addition to device.txt:
+
+ - reset-gpios : GPIO used to assert reset to the device
+ - irq-gpios : GPIO used for interrupt (if Interrupt is not used)
+ - stop-gpios : GPIO used to stop the device
+ - interrupts-extended : Interrupt to use for the device
+ - reset-delay-ms : Delay after de-asserting reset, in ms
+ - reset-off-delay-ms : Delay after asserting reset (during power off)
+ - enable-delay-ms : Delay after asserting enable
+ - enable-off-delay-ms : Delay after de-asserting enable (during power off)
+ - stop-delay-ms : Delay after de-aserting stop
+ - stop-off-delay-ms : Delay after asserting stop (during power off)
+ - hid-descr-addr : HID register offset (for Human Interface Devices)
+
+Example
+-------
+
+       elan-touchscreen@10 {
+               compatible = "i2c-chip";
+               reg = <0x10>;
+               acpi,hid = "ELAN0001";
+               acpi,ddn = "ELAN Touchscreen";
+               interrupts-extended = <&acpi_gpe GPIO_21_IRQ
+                       IRQ_TYPE_EDGE_FALLING>;
+               linux,probed;
+               reset-gpios = <&gpio_n GPIO_36 GPIO_ACTIVE_HIGH>;
+               reset-delay-ms = <20>;
+               enable-gpios = <&gpio_n GPIO_152 GPIO_ACTIVE_HIGH>;
+               enable-delay-ms = <1>;
+               acpi,has-power-resource;
+       };
index f7b2786..bd248cb 100644 (file)
@@ -3,6 +3,9 @@
 # (C) Copyright 2000-2007
 # Wolfgang Denk, DENX Software Engineering, wd@denx.de.
 obj-$(CONFIG_DM_I2C) += i2c-uclass.o
+ifdef CONFIG_ACPIGEN
+obj-$(CONFIG_DM_I2C) += acpi_i2c.o
+endif
 obj-$(CONFIG_DM_I2C_GPIO) += i2c-gpio.o
 obj-$(CONFIG_$(SPL_)I2C_CROS_EC_TUNNEL) += cros_ec_tunnel.o
 obj-$(CONFIG_$(SPL_)I2C_CROS_EC_LDO) += cros_ec_ldo.o
diff --git a/drivers/i2c/acpi_i2c.c b/drivers/i2c/acpi_i2c.c
new file mode 100644 (file)
index 0000000..57d2968
--- /dev/null
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2019 Google LLC
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <i2c.h>
+#include <log.h>
+#include <acpi/acpi_device.h>
+#include <acpi/acpigen.h>
+#include <acpi/acpi_dp.h>
+#ifdef CONFIG_X86
+#include <asm/intel_pinctrl_defs.h>
+#endif
+#include <asm-generic/gpio.h>
+#include <dm/acpi.h>
+
+static bool acpi_i2c_add_gpios_to_crs(struct acpi_i2c_priv *priv)
+{
+       /*
+        * Return false if:
+        * 1. Request to explicitly disable export of GPIOs in CRS, or
+        * 2. Both reset and enable GPIOs are not provided.
+        */
+       if (priv->disable_gpio_export_in_crs ||
+           (!dm_gpio_is_valid(&priv->reset_gpio) &&
+            !dm_gpio_is_valid(&priv->enable_gpio)))
+               return false;
+
+       return true;
+}
+
+static int acpi_i2c_write_gpio(struct acpi_ctx *ctx, struct gpio_desc *gpio,
+                              int *curindex)
+{
+       int ret;
+
+       if (!dm_gpio_is_valid(gpio))
+               return -ENOENT;
+
+       acpi_device_write_gpio_desc(ctx, gpio);
+       ret = *curindex;
+       (*curindex)++;
+
+       return ret;
+}
+
+int acpi_i2c_fill_ssdt(const struct udevice *dev, struct acpi_ctx *ctx)
+{
+       int reset_gpio_index = -1, enable_gpio_index = -1, irq_gpio_index = -1;
+       enum i2c_device_t type = dev_get_driver_data(dev);
+       struct acpi_i2c_priv *priv = dev_get_priv(dev);
+       struct acpi_dp *dsd = NULL;
+       char scope[ACPI_PATH_MAX];
+       char name[ACPI_NAME_MAX];
+       int tx_state_val;
+       int curindex = 0;
+       int ret;
+
+#ifdef CONFIG_X86
+       tx_state_val = PAD_CFG0_TX_STATE;
+#elif defined(CONFIG_SANDBOX)
+       tx_state_val = BIT(7);  /* test value */
+#else
+#error "Not supported on this architecture"
+#endif
+       ret = acpi_get_name(dev, name);
+       if (ret)
+               return log_msg_ret("name", ret);
+       ret = acpi_device_scope(dev, scope, sizeof(scope));
+       if (ret)
+               return log_msg_ret("scope", ret);
+
+       /* Device */
+       acpigen_write_scope(ctx, scope);
+       acpigen_write_device(ctx, name);
+       acpigen_write_name_string(ctx, "_HID", priv->hid);
+       if (type == I2C_DEVICE_HID_OVER_I2C)
+               acpigen_write_name_string(ctx, "_CID", "PNP0C50");
+       acpigen_write_name_integer(ctx, "_UID", priv->uid);
+       acpigen_write_name_string(ctx, "_DDN", priv->desc);
+       acpigen_write_sta(ctx, acpi_device_status(dev));
+
+       /* Resources */
+       acpigen_write_name(ctx, "_CRS");
+       acpigen_write_resourcetemplate_header(ctx);
+       acpi_device_write_i2c_dev(ctx, dev);
+
+       /* Use either Interrupt() or GpioInt() */
+       if (dm_gpio_is_valid(&priv->irq_gpio)) {
+               irq_gpio_index = acpi_i2c_write_gpio(ctx, &priv->irq_gpio,
+                                                    &curindex);
+       } else {
+               ret = acpi_device_write_interrupt_irq(ctx, &priv->irq);
+               if (ret < 0)
+                       return log_msg_ret("irq", ret);
+       }
+
+       if (acpi_i2c_add_gpios_to_crs(priv)) {
+               reset_gpio_index = acpi_i2c_write_gpio(ctx, &priv->reset_gpio,
+                                                      &curindex);
+               enable_gpio_index = acpi_i2c_write_gpio(ctx, &priv->enable_gpio,
+                                                       &curindex);
+       }
+       acpigen_write_resourcetemplate_footer(ctx);
+
+       /* Wake capabilities */
+       if (priv->wake) {
+               acpigen_write_name_integer(ctx, "_S0W", 4);
+               acpigen_write_prw(ctx, priv->wake, 3);
+       }
+
+       /* DSD */
+       if (priv->probed || priv->property_count || priv->compat_string ||
+           reset_gpio_index >= 0 || enable_gpio_index >= 0 ||
+           irq_gpio_index >= 0) {
+               char path[ACPI_PATH_MAX];
+
+               ret = acpi_device_path(dev, path, sizeof(path));
+               if (ret)
+                       return log_msg_ret("path", ret);
+
+               dsd = acpi_dp_new_table("_DSD");
+               if (priv->compat_string)
+                       acpi_dp_add_string(dsd, "compatible",
+                                          priv->compat_string);
+               if (priv->probed)
+                       acpi_dp_add_integer(dsd, "linux,probed", 1);
+               if (irq_gpio_index >= 0)
+                       acpi_dp_add_gpio(dsd, "irq-gpios", path,
+                                        irq_gpio_index, 0,
+                                        priv->irq_gpio.flags &
+                                        GPIOD_ACTIVE_LOW ?
+                                        ACPI_GPIO_ACTIVE_LOW : 0);
+               if (reset_gpio_index >= 0)
+                       acpi_dp_add_gpio(dsd, "reset-gpios", path,
+                                        reset_gpio_index, 0,
+                                        priv->reset_gpio.flags &
+                                        GPIOD_ACTIVE_LOW ?
+                                        ACPI_GPIO_ACTIVE_LOW : 0);
+               if (enable_gpio_index >= 0)
+                       acpi_dp_add_gpio(dsd, "enable-gpios", path,
+                                        enable_gpio_index, 0,
+                                        priv->enable_gpio.flags &
+                                        GPIOD_ACTIVE_LOW ?
+                                        ACPI_GPIO_ACTIVE_LOW : 0);
+               /* Generic property list is not supported */
+               acpi_dp_write(ctx, dsd);
+       }
+
+       /* Power Resource */
+       if (priv->has_power_resource) {
+               ret = acpi_device_add_power_res(ctx, tx_state_val,
+                       "\\_SB.GPC0", "\\_SB.SPC0",
+                       &priv->reset_gpio, priv->reset_delay_ms,
+                       priv->reset_off_delay_ms, &priv->enable_gpio,
+                       priv->enable_delay_ms, priv->enable_off_delay_ms,
+                       &priv->stop_gpio, priv->stop_delay_ms,
+                       priv->stop_off_delay_ms);
+               if (ret)
+                       return log_msg_ret("power", ret);
+       }
+       if (priv->hid_desc_reg_offset) {
+               ret = acpi_device_write_dsm_i2c_hid(ctx,
+                                                   priv->hid_desc_reg_offset);
+               if (ret)
+                       return log_msg_ret("dsm", ret);
+       }
+
+       acpigen_pop_len(ctx); /* Device */
+       acpigen_pop_len(ctx); /* Scope */
+
+       return 0;
+}
+
+int acpi_i2c_ofdata_to_platdata(struct udevice *dev)
+{
+       struct acpi_i2c_priv *priv = dev_get_priv(dev);
+
+       gpio_request_by_name(dev, "reset-gpios", 0, &priv->reset_gpio,
+                            GPIOD_IS_OUT);
+       gpio_request_by_name(dev, "enable-gpios", 0, &priv->enable_gpio,
+                            GPIOD_IS_OUT);
+       gpio_request_by_name(dev, "irq-gpios", 0, &priv->irq_gpio, GPIOD_IS_IN);
+       gpio_request_by_name(dev, "stop-gpios", 0, &priv->stop_gpio,
+                            GPIOD_IS_OUT);
+       irq_get_by_index(dev, 0, &priv->irq);
+       priv->hid = dev_read_string(dev, "acpi,hid");
+       if (!priv->hid)
+               return log_msg_ret("hid", -EINVAL);
+       dev_read_u32(dev, "acpi,uid", &priv->uid);
+       priv->desc = dev_read_string(dev, "acpi,ddn");
+       dev_read_u32(dev, "acpi,wake", &priv->wake);
+       priv->probed = dev_read_bool(dev, "linux,probed");
+       priv->compat_string = dev_read_string(dev, "acpi,compatible");
+       priv->has_power_resource = dev_read_bool(dev,
+                                                "acpi,has-power-resource");
+       dev_read_u32(dev, "hid-descr-addr", &priv->hid_desc_reg_offset);
+       dev_read_u32(dev, "reset-delay-ms", &priv->reset_delay_ms);
+       dev_read_u32(dev, "reset-off-delay-ms", &priv->reset_off_delay_ms);
+       dev_read_u32(dev, "enable-delay-ms", &priv->enable_delay_ms);
+       dev_read_u32(dev, "enable-off-delay-ms", &priv->enable_off_delay_ms);
+       dev_read_u32(dev, "stop-delay-ms", &priv->stop_delay_ms);
+       dev_read_u32(dev, "stop-off-delay-ms", &priv->stop_off_delay_ms);
+
+       return 0;
+}
+
+/* Use name specified in priv or build one from I2C address */
+static int acpi_i2c_get_name(const struct udevice *dev, char *out_name)
+{
+       struct dm_i2c_chip *chip = dev_get_parent_platdata(dev);
+       struct acpi_i2c_priv *priv = dev_get_priv(dev);
+
+       snprintf(out_name, ACPI_NAME_MAX,
+                priv->hid_desc_reg_offset ? "H%03X" : "D%03X",
+                chip->chip_addr);
+
+       return 0;
+}
+
+struct acpi_ops acpi_i2c_ops = {
+       .fill_ssdt      = acpi_i2c_fill_ssdt,
+       .get_name       = acpi_i2c_get_name,
+};
diff --git a/drivers/i2c/acpi_i2c.h b/drivers/i2c/acpi_i2c.h
new file mode 100644 (file)
index 0000000..1f4be29
--- /dev/null
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright 2019 Google LLC
+ */
+
+#ifndef __ACPI_I2C_H
+#define __ACPI_I2C_H
+
+#include <dm/acpi.h>
+
+extern struct acpi_ops acpi_i2c_ops;
+
+int acpi_i2c_ofdata_to_platdata(struct udevice *dev);
+
+#endif
index 2373aa2..5c4626b 100644 (file)
@@ -9,6 +9,8 @@
 #include <i2c.h>
 #include <log.h>
 #include <malloc.h>
+#include <acpi/acpi_device.h>
+#include <dm/acpi.h>
 #include <dm/device-internal.h>
 #include <dm/lists.h>
 #include <dm/pinctrl.h>
@@ -16,6 +18,7 @@
 #include <asm/gpio.h>
 #endif
 #include <linux/delay.h>
+#include "acpi_i2c.h"
 
 #define I2C_MAX_OFFSET_LEN     4
 
@@ -749,7 +752,21 @@ UCLASS_DRIVER(i2c_generic) = {
        .name           = "i2c_generic",
 };
 
+static const struct udevice_id generic_chip_i2c_ids[] = {
+       { .compatible = "i2c-chip", .data = I2C_DEVICE_GENERIC },
+#if CONFIG_IS_ENABLED(ACPIGEN)
+       { .compatible = "hid-over-i2c", .data = I2C_DEVICE_HID_OVER_I2C },
+#endif
+       { }
+};
+
 U_BOOT_DRIVER(i2c_generic_chip_drv) = {
        .name           = "i2c_generic_chip_drv",
        .id             = UCLASS_I2C_GENERIC,
+       .of_match       = generic_chip_i2c_ids,
+#if CONFIG_IS_ENABLED(ACPIGEN)
+       .ofdata_to_platdata     = acpi_i2c_ofdata_to_platdata,
+       .priv_auto_alloc_size   = sizeof(struct acpi_i2c_priv),
+#endif
+       ACPI_OPS_PTR(&acpi_i2c_ops)
 };
index a5b1221..1b838fc 100644 (file)
@@ -10,7 +10,9 @@
 #define __ACPI_DEVICE_H
 
 #include <i2c.h>
+#include <irq.h>
 #include <spi.h>
+#include <asm-generic/gpio.h>
 #include <linux/bitops.h>
 
 struct acpi_ctx;
@@ -236,6 +238,59 @@ struct acpi_spi {
 };
 
 /**
+ * struct acpi_i2c_priv - Information read from device tree
+ *
+ * This is used by devices which want to specify various pieces of ACPI
+ * information, including power control. It allows a generic function to
+ * generate the information for ACPI, based on device-tree properties.
+ *
+ * @disable_gpio_export_in_crs: Don't export GPIOs in the CRS
+ * @reset_gpio: GPIO used to assert reset to the device
+ * @enable_gpio: GPIO used to enable the device
+ * @stop_gpio: GPIO used to stop the device
+ * @irq_gpio: GPIO used for interrupt (if @irq is not used)
+ * @irq: IRQ used for interrupt (if @irq_gpio is not used)
+ * @hid: _HID value for device (required)
+ * @uid: _UID value for device
+ * @desc: _DDN value for device
+ * @wake: Wake event, e.g. GPE0_DW1_15; 0 if none
+ * @property_count: Number of other DSD properties (currently always 0)
+ * @probed: true set set 'linux,probed' property
+ * @compat_string: Device tree compatible string to report through ACPI
+ * @has_power_resource: true if this device has a power resource
+ * @reset_delay_ms: Delay after de-asserting reset, in ms
+ * @reset_off_delay_ms: Delay after asserting reset (during power off)
+ * @enable_delay_ms: Delay after asserting enable
+ * @enable_off_delay_ms: Delay after de-asserting enable (during power off)
+ * @stop_delay_ms: Delay after de-aserting stop
+ * @stop_off_delay_ms: Delay after asserting stop (during power off)
+ * @hid_desc_reg_offset: HID register offset (for Human Interface Devices)
+ */
+struct acpi_i2c_priv {
+       bool disable_gpio_export_in_crs;
+       struct gpio_desc reset_gpio;
+       struct gpio_desc enable_gpio;
+       struct gpio_desc irq_gpio;
+       struct gpio_desc stop_gpio;
+       struct irq irq;
+       const char *hid;
+       u32 uid;
+       const char *desc;
+       u32 wake;
+       u32 property_count;
+       bool probed;
+       const char *compat_string;
+       bool has_power_resource;
+       u32 reset_delay_ms;
+       u32 reset_off_delay_ms;
+       u32 enable_delay_ms;
+       u32 enable_off_delay_ms;
+       u32 stop_delay_ms;
+       u32 stop_off_delay_ms;
+       u32 hid_desc_reg_offset;
+};
+
+/**
  * acpi_device_path() - Get the full path to an ACPI device
  *
  * This gets the full path in the form XXXX.YYYY.ZZZZ where XXXX is the root
index 1d792db..880aa80 100644 (file)
@@ -58,6 +58,12 @@ enum i2c_address_mode {
        I2C_MODE_10_BIT
 };
 
+/** enum i2c_device_t - Types of I2C devices, used for compatible strings */
+enum i2c_device_t {
+       I2C_DEVICE_GENERIC,
+       I2C_DEVICE_HID_OVER_I2C,
+};
+
 struct udevice;
 /**
  * struct dm_i2c_chip - information about an i2c chip
@@ -558,6 +564,23 @@ int i2c_emul_find(struct udevice *dev, struct udevice **emulp);
  */
 struct udevice *i2c_emul_get_device(struct udevice *emul);
 
+/* ACPI operations for generic I2C devices */
+extern struct acpi_ops i2c_acpi_ops;
+
+/**
+ * acpi_i2c_ofdata_to_platdata() - Read properties intended for ACPI
+ *
+ * This reads the generic I2C properties from the device tree, so that these
+ * can be used to create ACPI information for the device.
+ *
+ * See the i2c/generic-acpi.txt binding file for information about the
+ * properties.
+ *
+ * @dev: I2C device to process
+ * @return 0 if OK, -EINVAL if acpi,hid is not present
+ */
+int acpi_i2c_ofdata_to_platdata(struct udevice *dev);
+
 #ifndef CONFIG_DM_I2C
 
 /*