Skip to content

Commit

Permalink
i2c-i801: Support SMBus multiplexing on Asus Z8 series
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Jean Delvare authored and Jean Delvare committed Oct 5, 2012
1 parent 01d56a6 commit 3ad7ea1
Show file tree
Hide file tree
Showing 2 changed files with 214 additions and 1 deletion.
1 change: 1 addition & 0 deletions drivers/i2c/busses/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
214 changes: 213 additions & 1 deletion drivers/i2c/busses/i2c-i801.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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)
{
Expand All @@ -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;
Expand Down Expand Up @@ -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);

Expand All @@ -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);

Expand Down

0 comments on commit 3ad7ea1

Please sign in to comment.