]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - drivers/i2c/busses/i2c-i801.c
i2c: i801: store and restore the SLVCMD register at load and unload
[mirror_ubuntu-bionic-kernel.git] / drivers / i2c / busses / i2c-i801.c
index 08847e8b899872e2bc1bffd1d0936f63ee4f38d7..750c4753e2bc4df389e98c3b5cc952e9f1b1e9bb 100644 (file)
 #define SMBHSTCFG_HST_EN       1
 #define SMBHSTCFG_SMB_SMI_EN   2
 #define SMBHSTCFG_I2C_EN       4
+#define SMBHSTCFG_SPD_WD       0x10
 
 /* TCO configuration bits for TCOCTL */
 #define TCOCTL_EN              0x0100
@@ -242,6 +243,7 @@ struct i801_priv {
        struct i2c_adapter adapter;
        unsigned long smba;
        unsigned char original_hstcfg;
+       unsigned char original_slvcmd;
        struct pci_dev *pci_dev;
        unsigned int features;
 
@@ -865,9 +867,16 @@ static s32 i801_access(struct i2c_adapter *adap, u16 addr,
                block = 1;
                break;
        case I2C_SMBUS_I2C_BLOCK_DATA:
-               /* NB: page 240 of ICH5 datasheet shows that the R/#W
-                * bit should be cleared here, even when reading */
-               outb_p((addr & 0x7f) << 1, SMBHSTADD(priv));
+               /*
+                * NB: page 240 of ICH5 datasheet shows that the R/#W
+                * bit should be cleared here, even when reading.
+                * However if SPD Write Disable is set (Lynx Point and later),
+                * the read will fail if we don't set the R/#W bit.
+                */
+               outb_p(((addr & 0x7f) << 1) |
+                      ((priv->original_hstcfg & SMBHSTCFG_SPD_WD) ?
+                       (read_write & 0x01) : 0),
+                      SMBHSTADD(priv));
                if (read_write == I2C_SMBUS_READ) {
                        /* NB: page 240 of ICH5 datasheet also shows
                         * that DATA1 is the cmd field when reading */
@@ -954,13 +963,26 @@ static int i801_enable_host_notify(struct i2c_adapter *adapter)
        if (!priv->host_notify)
                return -ENOMEM;
 
-       outb_p(SMBSLVCMD_HST_NTFY_INTREN, SMBSLVCMD(priv));
+       priv->original_slvcmd = inb_p(SMBSLVCMD(priv));
+
+       if (!(SMBSLVCMD_HST_NTFY_INTREN & priv->original_slvcmd))
+               outb_p(SMBSLVCMD_HST_NTFY_INTREN | priv->original_slvcmd,
+                      SMBSLVCMD(priv));
+
        /* clear Host Notify bit to allow a new notification */
        outb_p(SMBSLVSTS_HST_NTFY_STS, SMBSLVSTS(priv));
 
        return 0;
 }
 
+static void i801_disable_host_notify(struct i801_priv *priv)
+{
+       if (!(priv->features & FEATURE_HOST_NOTIFY))
+               return;
+
+       outb_p(priv->original_slvcmd, SMBSLVCMD(priv));
+}
+
 static const struct i2c_algorithm smbus_algorithm = {
        .smbus_xfer     = i801_access,
        .functionality  = i801_func,
@@ -1573,6 +1595,8 @@ static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id)
                /* Disable SMBus interrupt feature if SMBus using SMI# */
                priv->features &= ~FEATURE_IRQ;
        }
+       if (temp & SMBHSTCFG_SPD_WD)
+               dev_info(&dev->dev, "SPD Write Disable is set\n");
 
        /* Clear special mode bits */
        if (priv->features & (FEATURE_SMBUS_PEC | FEATURE_BLOCK_BUFFER))
@@ -1653,6 +1677,7 @@ static void i801_remove(struct pci_dev *dev)
        pm_runtime_forbid(&dev->dev);
        pm_runtime_get_noresume(&dev->dev);
 
+       i801_disable_host_notify(priv);
        i801_del_mux(priv);
        i2c_del_adapter(&priv->adapter);
        i801_acpi_remove(priv);