diff mbox series

[056/108] i2c: Add a generic driver to generate ACPI info

Message ID 20200126220508.56.I12e1ee504597159411b44512d27bb8f81576605a@changeid
State New
Headers show
Series RFC: dm: Add programatic generation of ACPI tables | expand

Commit Message

Simon Glass Jan. 27, 2020, 5:06 a.m. UTC
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 at chromium.org>
---

 doc/device-tree-bindings/i2c/generic-acpi.txt |  42 ++++
 drivers/i2c/Makefile                          |   3 +
 drivers/i2c/acpi_i2c.c                        | 228 ++++++++++++++++++
 drivers/i2c/acpi_i2c.h                        |  15 ++
 drivers/i2c/i2c-uclass.c                      |  14 ++
 include/i2c.h                                 |  17 ++
 6 files changed, 319 insertions(+)
 create mode 100644 doc/device-tree-bindings/i2c/generic-acpi.txt
 create mode 100644 drivers/i2c/acpi_i2c.c
 create mode 100644 drivers/i2c/acpi_i2c.h
diff mbox series

Patch

diff --git a/doc/device-tree-bindings/i2c/generic-acpi.txt b/doc/device-tree-bindings/i2c/generic-acpi.txt
new file mode 100644
index 0000000000..b1a4e8d752
--- /dev/null
+++ b/doc/device-tree-bindings/i2c/generic-acpi.txt
@@ -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-m s: 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)
+ - acpi,hid-desc-reg-offset : HID register offset (for Human Interface Devices)
+
+Example
+-------
+
+	elan-touchscreen at 10 {
+		compatible = "i2c-chip";
+		reg = <0x10>;
+		acpi,hid = "ELAN0001";
+		acpi,desc = "ELAN Touchscreen";
+		interrupts-extended = <&acpi_gpe GPIO_21_IRQ
+			IRQ_TYPE_EDGE_FALLING>;
+		acpi,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;
+	};
diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
index f5a471f887..43e58b8cac 100644
--- a/drivers/i2c/Makefile
+++ b/drivers/i2c/Makefile
@@ -3,6 +3,9 @@ 
 # (C) Copyright 2000-2007
 # Wolfgang Denk, DENX Software Engineering, wd at 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
index 0000000000..53609b2137
--- /dev/null
+++ b/drivers/i2c/acpi_i2c.c
@@ -0,0 +1,228 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2019 Google LLC
+ */
+
+#include <common.h>
+#include <acpi_device.h>
+#include <acpigen.h>
+#include <dm.h>
+#include <i2c.h>
+#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;
+	struct acpi_i2c_priv *priv = dev_get_priv(dev);
+	char scope[ACPI_PATH_MAX];
+	char name[ACPI_NAME_MAX];
+	struct acpi_dp *dsd = NULL;
+	int curindex = 0;
+	int ret;
+
+	ret = acpi_device_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 (priv->cid)
+		acpigen_write_name_string(ctx, "_CID", priv->cid);
+	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)
+			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);
+		/* Add generic property list (not supported) */
+		assert(!priv->property_count);
+		acpi_dp_add_property_list(dsd, NULL, priv->property_count);
+		acpi_dp_write(ctx, dsd);
+	}
+
+	/* Power Resource */
+	if (priv->has_power_resource) {
+		struct acpi_gpio reset_gpio, enable_gpio, stop_gpio;
+
+		gpio_get_acpi(&priv->reset_gpio, &reset_gpio);
+		gpio_get_acpi(&priv->enable_gpio, &enable_gpio);
+		gpio_get_acpi(&priv->stop_gpio, &stop_gpio);
+		const struct acpi_power_res_params power_res_params = {
+			&reset_gpio,
+			priv->reset_delay_ms,
+			priv->reset_off_delay_ms,
+			&enable_gpio,
+			priv->enable_delay_ms,
+			priv->enable_off_delay_ms,
+			&stop_gpio,
+			priv->stop_delay_ms,
+			priv->stop_off_delay_ms
+		};
+		ret = acpi_device_add_power_res(ctx, &power_res_params);
+		if (ret)
+			return log_msg_ret("power", ret);
+	}
+	if (priv->hid_desc_reg_offset) {
+		struct dsm_i2c_hid_config dsm_config = {
+			.hid_desc_reg_offset = priv->hid_desc_reg_offset,
+		};
+
+		acpi_device_write_dsm_i2c_hid(ctx, &dsm_config);
+	}
+
+	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);
+	priv->cid = dev_read_string(dev, "acpi,cid");
+	dev_read_u32(dev, "acpi,uid", &priv->uid);
+	priv->desc = dev_read_string(dev, "acpi,desc");
+	dev_read_u32(dev, "acpi,wake", &priv->wake);
+	priv->probed = dev_read_bool(dev, "acpi,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, "acpi,hid-desc-reg-offset",
+		     &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);
+	dev_read_u32(dev, "stop-off-delay-ms", &priv->hid_desc_reg_offset);
+
+	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
index 0000000000..1f4be29601
--- /dev/null
+++ b/drivers/i2c/acpi_i2c.h
@@ -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
diff --git a/drivers/i2c/i2c-uclass.c b/drivers/i2c/i2c-uclass.c
index a577988bfb..5a3bee57b9 100644
--- a/drivers/i2c/i2c-uclass.c
+++ b/drivers/i2c/i2c-uclass.c
@@ -4,16 +4,19 @@ 
  */
 
 #include <common.h>
+#include <acpi_device.h>
 #include <dm.h>
 #include <errno.h>
 #include <i2c.h>
 #include <malloc.h>
+#include <dm/acpi.h>
 #include <dm/device-internal.h>
 #include <dm/lists.h>
 #include <dm/pinctrl.h>
 #if CONFIG_IS_ENABLED(DM_GPIO)
 #include <asm/gpio.h>
 #endif
+#include "acpi_i2c.h"
 
 #define I2C_MAX_OFFSET_LEN	4
 
@@ -728,7 +731,18 @@  UCLASS_DRIVER(i2c_generic) = {
 	.name		= "i2c_generic",
 };
 
+static const struct udevice_id generic_chip_i2c_ids[] = {
+	{ .compatible = "i2c-chip" },
+	{ }
+};
+
 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)
 };
diff --git a/include/i2c.h b/include/i2c.h
index 41ee5875e7..721b4305dd 100644
--- a/include/i2c.h
+++ b/include/i2c.h
@@ -538,6 +538,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
 
 /*