summary refs log tree commit diff
diff options
context:
space:
mode:
-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);