i2c: Add a generic driver to generate ACPI info
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>
This commit is contained in:
parent
bddbaf5edf
commit
fd42f263ce
42
doc/device-tree-bindings/i2c/generic-acpi.txt
Normal file
42
doc/device-tree-bindings/i2c/generic-acpi.txt
Normal file
@ -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;
|
||||
};
|
@ -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
|
||||
|
226
drivers/i2c/acpi_i2c.c
Normal file
226
drivers/i2c/acpi_i2c.c
Normal file
@ -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,
|
||||
};
|
15
drivers/i2c/acpi_i2c.h
Normal file
15
drivers/i2c/acpi_i2c.h
Normal file
@ -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
|
@ -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)
|
||||
};
|
||||
|
@ -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;
|
||||
@ -235,6 +237,59 @@ struct acpi_spi {
|
||||
const char *resource;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
|
@ -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
|
||||
|
||||
/*
|
||||
|
Loading…
Reference in New Issue
Block a user