i2c-i801: Support SMBus multiplexing on Asus Z8 series
Jean Delvare [Fri, 5 Oct 2012 20:23:53 +0000 (22:23 +0200)]
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>

drivers/i2c/busses/Kconfig
drivers/i2c/busses/i2c-i801.c

index 7f69f50..ff01c38 100644 (file)
@@ -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
index 33e9b0c..bb1d729 100644 (file)
 #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)
 #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);