summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorJean Delvare <khali@linux-fr.org>2012-10-05 22:23:53 +0200
committerJean Delvare <khali@endymion.delvare>2012-10-05 22:23:53 +0200
commit3ad7ea18ae8c7ddda46b7276e0bda73e707ea9c1 (patch)
tree7d2fca72b185fc7dd985306e9cc16caecdbdad04 /drivers
parent01d56a6aa1c3a506426c8f3ff87c8d698023f336 (diff)
downloadlwn-3ad7ea18ae8c7ddda46b7276e0bda73e707ea9c1.tar.gz
lwn-3ad7ea18ae8c7ddda46b7276e0bda73e707ea9c1.zip
i2c-i801: Support SMBus multiplexing on Asus Z8 series
Add support for SMBus multiplexing on Asus Z8 motherboard series. On these boards, the memory slots are behind a GPIO-controlled I2C multiplexer. Models with 6 or 12 memory slots have 2 segments behind the multiplexer, while models with 18 memory slots have 3 such segments. On these boards, only the memory slots are behind the multiplexer, so it is possible to keep the autodetection mechanism. The code is generic enough so it could work on other boards as long as the multiplexer is controlled by GPIO pins. For other forms of multiplexing (for example using an I2C device) additional code will be needed. Thanks to Asus for providing a board to develop and test this feature, as well as all the technical information required. At the moment, the GPIO driver must be loaded before the i2c-i801 driver, but I hope to solve this soon, using deferred probing on the i2c-mux-gpio side. Signed-off-by: Jean Delvare <khali@linux-fr.org>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/i2c/busses/Kconfig1
-rw-r--r--drivers/i2c/busses/i2c-i801.c214
2 files changed, 214 insertions, 1 deletions
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 7f69f500daf2..ff01c389e2da 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -81,6 +81,7 @@ config I2C_I801
tristate "Intel 82801 (ICH/PCH)"
depends on PCI
select CHECK_SIGNATURE if X86 && DMI
+ select GPIOLIB if I2C_MUX
help
If you say yes to this option, support will be included for the Intel
801 family of mainboard I2C interfaces. Specifically, the following
diff --git a/drivers/i2c/busses/i2c-i801.c b/drivers/i2c/busses/i2c-i801.c
index 33e9b0c09af2..bb1d7291cf21 100644
--- a/drivers/i2c/busses/i2c-i801.c
+++ b/drivers/i2c/busses/i2c-i801.c
@@ -80,6 +80,13 @@
#include <linux/dmi.h>
#include <linux/slab.h>
#include <linux/wait.h>
+#include <linux/err.h>
+
+#if defined CONFIG_I2C_MUX || defined CONFIG_I2C_MUX_MODULE
+#include <linux/gpio.h>
+#include <linux/i2c-mux-gpio.h>
+#include <linux/platform_device.h>
+#endif
/* I801 SMBus address offsets */
#define SMBHSTSTS(p) (0 + (p)->smba)
@@ -158,6 +165,15 @@
#define PCI_DEVICE_ID_INTEL_LYNXPOINT_SMBUS 0x8c22
#define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_SMBUS 0x9c22
+struct i801_mux_config {
+ char *gpio_chip;
+ unsigned values[3];
+ int n_values;
+ unsigned classes[3];
+ unsigned gpios[2]; /* Relative to gpio_chip->base */
+ int n_gpios;
+};
+
struct i801_priv {
struct i2c_adapter adapter;
unsigned long smba;
@@ -175,6 +191,12 @@ struct i801_priv {
int count;
int len;
u8 *data;
+
+#if defined CONFIG_I2C_MUX || defined CONFIG_I2C_MUX_MODULE
+ const struct i801_mux_config *mux_drvdata;
+ unsigned mux_priv[2];
+ struct platform_device *mux_pdev;
+#endif
};
static struct pci_driver i801_driver;
@@ -900,6 +922,193 @@ static void __init input_apanel_init(void) {}
static void __devinit i801_probe_optional_slaves(struct i801_priv *priv) {}
#endif /* CONFIG_X86 && CONFIG_DMI */
+#if defined CONFIG_I2C_MUX || defined CONFIG_I2C_MUX_MODULE
+static struct i801_mux_config i801_mux_config_asus_z8_d12 = {
+ .gpio_chip = "gpio_ich",
+ .values = { 0x02, 0x03 },
+ .n_values = 2,
+ .classes = { I2C_CLASS_SPD, I2C_CLASS_SPD },
+ .gpios = { 52, 53 },
+ .n_gpios = 2,
+};
+
+static struct i801_mux_config i801_mux_config_asus_z8_d18 = {
+ .gpio_chip = "gpio_ich",
+ .values = { 0x02, 0x03, 0x01 },
+ .n_values = 3,
+ .classes = { I2C_CLASS_SPD, I2C_CLASS_SPD, I2C_CLASS_SPD },
+ .gpios = { 52, 53 },
+ .n_gpios = 2,
+};
+
+static struct dmi_system_id __devinitdata mux_dmi_table[] = {
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC."),
+ DMI_MATCH(DMI_BOARD_NAME, "Z8NA-D6(C)"),
+ },
+ .driver_data = &i801_mux_config_asus_z8_d12,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC."),
+ DMI_MATCH(DMI_BOARD_NAME, "Z8P(N)E-D12(X)"),
+ },
+ .driver_data = &i801_mux_config_asus_z8_d12,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC."),
+ DMI_MATCH(DMI_BOARD_NAME, "Z8NH-D12"),
+ },
+ .driver_data = &i801_mux_config_asus_z8_d12,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC."),
+ DMI_MATCH(DMI_BOARD_NAME, "Z8PH-D12/IFB"),
+ },
+ .driver_data = &i801_mux_config_asus_z8_d12,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC."),
+ DMI_MATCH(DMI_BOARD_NAME, "Z8NR-D12"),
+ },
+ .driver_data = &i801_mux_config_asus_z8_d12,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC."),
+ DMI_MATCH(DMI_BOARD_NAME, "Z8P(N)H-D12"),
+ },
+ .driver_data = &i801_mux_config_asus_z8_d12,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC."),
+ DMI_MATCH(DMI_BOARD_NAME, "Z8PG-D18"),
+ },
+ .driver_data = &i801_mux_config_asus_z8_d18,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC."),
+ DMI_MATCH(DMI_BOARD_NAME, "Z8PE-D18"),
+ },
+ .driver_data = &i801_mux_config_asus_z8_d18,
+ },
+ {
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC."),
+ DMI_MATCH(DMI_BOARD_NAME, "Z8PS-D12"),
+ },
+ .driver_data = &i801_mux_config_asus_z8_d12,
+ },
+ { }
+};
+
+static int __devinit match_gpio_chip_by_label(struct gpio_chip *chip,
+ void *data)
+{
+ return !strcmp(chip->label, data);
+}
+
+/* Setup multiplexing if needed */
+static int __devinit i801_add_mux(struct i801_priv *priv)
+{
+ struct device *dev = &priv->adapter.dev;
+ const struct i801_mux_config *mux_config;
+ struct gpio_chip *gpio;
+ struct i2c_mux_gpio_platform_data gpio_data;
+ int i, err;
+
+ if (!priv->mux_drvdata)
+ return 0;
+ mux_config = priv->mux_drvdata;
+
+ /* Find GPIO chip */
+ gpio = gpiochip_find(mux_config->gpio_chip, match_gpio_chip_by_label);
+ if (gpio) {
+ dev_info(dev,
+ "GPIO chip %s found, SMBus multiplexing enabled\n",
+ mux_config->gpio_chip);
+ } else {
+ dev_err(dev,
+ "GPIO chip %s not found, SMBus multiplexing disabled\n",
+ mux_config->gpio_chip);
+ return -ENODEV;
+ }
+
+ /* Find absolute GPIO pin numbers */
+ if (ARRAY_SIZE(priv->mux_priv) < mux_config->n_gpios) {
+ dev_err(dev, "i801_priv.mux_priv too small (%zu, need %d)\n",
+ ARRAY_SIZE(priv->mux_priv), mux_config->n_gpios);
+ return -ENODEV;
+ }
+ for (i = 0; i < mux_config->n_gpios; i++)
+ priv->mux_priv[i] = gpio->base + mux_config->gpios[i];
+
+ /* Prepare the platform data */
+ memset(&gpio_data, 0, sizeof(struct i2c_mux_gpio_platform_data));
+ gpio_data.parent = priv->adapter.nr;
+ gpio_data.values = mux_config->values;
+ gpio_data.n_values = mux_config->n_values;
+ gpio_data.classes = mux_config->classes;
+ gpio_data.gpios = priv->mux_priv;
+ gpio_data.n_gpios = mux_config->n_gpios;
+ gpio_data.idle = I2C_MUX_GPIO_NO_IDLE;
+
+ /* Register the mux device */
+ priv->mux_pdev = platform_device_register_data(dev, "i2c-mux-gpio",
+ priv->mux_priv[0], &gpio_data,
+ sizeof(struct i2c_mux_gpio_platform_data));
+ if (IS_ERR(priv->mux_pdev)) {
+ err = PTR_ERR(priv->mux_pdev);
+ priv->mux_pdev = NULL;
+ dev_err(dev, "Failed to register i2c-mux-gpio device\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static void __devexit i801_del_mux(struct i801_priv *priv)
+{
+ if (priv->mux_pdev)
+ platform_device_unregister(priv->mux_pdev);
+}
+
+static unsigned int __devinit i801_get_adapter_class(struct i801_priv *priv)
+{
+ const struct dmi_system_id *id;
+ const struct i801_mux_config *mux_config;
+ unsigned int class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+ int i;
+
+ id = dmi_first_match(mux_dmi_table);
+ if (id) {
+ /* Remove from branch classes from trunk */
+ mux_config = id->driver_data;
+ for (i = 0; i < mux_config->n_values; i++)
+ class &= ~mux_config->classes[i];
+
+ /* Remember for later */
+ priv->mux_drvdata = mux_config;
+ }
+
+ return class;
+}
+#else
+static inline int i801_add_mux(struct i801_priv *priv) { return 0; }
+static inline void i801_del_mux(struct i801_priv *priv) { }
+
+static inline unsigned int i801_get_adapter_class(struct i801_priv *priv)
+{
+ return I2C_CLASS_HWMON | I2C_CLASS_SPD;
+}
+#endif
+
static int __devinit i801_probe(struct pci_dev *dev,
const struct pci_device_id *id)
{
@@ -913,7 +1122,7 @@ static int __devinit i801_probe(struct pci_dev *dev,
i2c_set_adapdata(&priv->adapter, priv);
priv->adapter.owner = THIS_MODULE;
- priv->adapter.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
+ priv->adapter.class = i801_get_adapter_class(priv);
priv->adapter.algo = &smbus_algorithm;
priv->pci_dev = dev;
@@ -1033,6 +1242,8 @@ static int __devinit i801_probe(struct pci_dev *dev,
}
i801_probe_optional_slaves(priv);
+ /* We ignore errors - multiplexing is optional */
+ i801_add_mux(priv);
pci_set_drvdata(dev, priv);
@@ -1052,6 +1263,7 @@ static void __devexit i801_remove(struct pci_dev *dev)
{
struct i801_priv *priv = pci_get_drvdata(dev);
+ i801_del_mux(priv);
i2c_del_adapter(&priv->adapter);
pci_write_config_byte(dev, SMBHSTCFG, priv->original_hstcfg);