summaryrefslogtreecommitdiffstats
path: root/drivers/i2c/busses/i2c-piix4.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/i2c/busses/i2c-piix4.c')
-rw-r--r--drivers/i2c/busses/i2c-piix4.c213
1 files changed, 167 insertions, 46 deletions
diff --git a/drivers/i2c/busses/i2c-piix4.c b/drivers/i2c/busses/i2c-piix4.c
index 8c1b31ed0c42..ac8e7d60672a 100644
--- a/drivers/i2c/busses/i2c-piix4.c
+++ b/drivers/i2c/busses/i2c-piix4.c
@@ -77,6 +77,7 @@
/* SB800 constants */
#define SB800_PIIX4_SMB_IDX 0xcd6
+#define SB800_PIIX4_SMB_MAP_SIZE 2
#define KERNCZ_IMC_IDX 0x3e
#define KERNCZ_IMC_DATA 0x3f
@@ -97,6 +98,9 @@
#define SB800_PIIX4_PORT_IDX_MASK_KERNCZ 0x18
#define SB800_PIIX4_PORT_IDX_SHIFT_KERNCZ 3
+#define SB800_PIIX4_FCH_PM_ADDR 0xFED80300
+#define SB800_PIIX4_FCH_PM_SIZE 8
+
/* insmod parameters */
/* If force is set to anything different from 0, we forcibly enable the
@@ -155,6 +159,12 @@ static const char *piix4_main_port_names_sb800[PIIX4_MAX_ADAPTERS] = {
};
static const char *piix4_aux_port_name_sb800 = " port 1";
+struct sb800_mmio_cfg {
+ void __iomem *addr;
+ struct resource *res;
+ bool use_mmio;
+};
+
struct i2c_piix4_adapdata {
unsigned short smba;
@@ -162,8 +172,75 @@ struct i2c_piix4_adapdata {
bool sb800_main;
bool notify_imc;
u8 port; /* Port number, shifted */
+ struct sb800_mmio_cfg mmio_cfg;
};
+static int piix4_sb800_region_request(struct device *dev,
+ struct sb800_mmio_cfg *mmio_cfg)
+{
+ if (mmio_cfg->use_mmio) {
+ struct resource *res;
+ void __iomem *addr;
+
+ res = request_mem_region_muxed(SB800_PIIX4_FCH_PM_ADDR,
+ SB800_PIIX4_FCH_PM_SIZE,
+ "sb800_piix4_smb");
+ if (!res) {
+ dev_err(dev,
+ "SMBus base address memory region 0x%x already in use.\n",
+ SB800_PIIX4_FCH_PM_ADDR);
+ return -EBUSY;
+ }
+
+ addr = ioremap(SB800_PIIX4_FCH_PM_ADDR,
+ SB800_PIIX4_FCH_PM_SIZE);
+ if (!addr) {
+ release_resource(res);
+ dev_err(dev, "SMBus base address mapping failed.\n");
+ return -ENOMEM;
+ }
+
+ mmio_cfg->res = res;
+ mmio_cfg->addr = addr;
+
+ return 0;
+ }
+
+ if (!request_muxed_region(SB800_PIIX4_SMB_IDX, SB800_PIIX4_SMB_MAP_SIZE,
+ "sb800_piix4_smb")) {
+ dev_err(dev,
+ "SMBus base address index region 0x%x already in use.\n",
+ SB800_PIIX4_SMB_IDX);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static void piix4_sb800_region_release(struct device *dev,
+ struct sb800_mmio_cfg *mmio_cfg)
+{
+ if (mmio_cfg->use_mmio) {
+ iounmap(mmio_cfg->addr);
+ release_resource(mmio_cfg->res);
+ return;
+ }
+
+ release_region(SB800_PIIX4_SMB_IDX, SB800_PIIX4_SMB_MAP_SIZE);
+}
+
+static bool piix4_sb800_use_mmio(struct pci_dev *PIIX4_dev)
+{
+ /*
+ * cd6h/cd7h port I/O accesses can be disabled on AMD processors
+ * w/ SMBus PCI revision ID 0x51 or greater. MMIO is supported on
+ * the same processors and is the recommended access method.
+ */
+ return (PIIX4_dev->vendor == PCI_VENDOR_ID_AMD &&
+ PIIX4_dev->device == PCI_DEVICE_ID_AMD_KERNCZ_SMBUS &&
+ PIIX4_dev->revision >= 0x51);
+}
+
static int piix4_setup(struct pci_dev *PIIX4_dev,
const struct pci_device_id *id)
{
@@ -263,12 +340,61 @@ static int piix4_setup(struct pci_dev *PIIX4_dev,
return piix4_smba;
}
+static int piix4_setup_sb800_smba(struct pci_dev *PIIX4_dev,
+ u8 smb_en,
+ u8 aux,
+ u8 *smb_en_status,
+ unsigned short *piix4_smba)
+{
+ struct sb800_mmio_cfg mmio_cfg;
+ u8 smba_en_lo;
+ u8 smba_en_hi;
+ int retval;
+
+ mmio_cfg.use_mmio = piix4_sb800_use_mmio(PIIX4_dev);
+ retval = piix4_sb800_region_request(&PIIX4_dev->dev, &mmio_cfg);
+ if (retval)
+ return retval;
+
+ if (mmio_cfg.use_mmio) {
+ smba_en_lo = ioread8(mmio_cfg.addr);
+ smba_en_hi = ioread8(mmio_cfg.addr + 1);
+ } else {
+ outb_p(smb_en, SB800_PIIX4_SMB_IDX);
+ smba_en_lo = inb_p(SB800_PIIX4_SMB_IDX + 1);
+ outb_p(smb_en + 1, SB800_PIIX4_SMB_IDX);
+ smba_en_hi = inb_p(SB800_PIIX4_SMB_IDX + 1);
+ }
+
+ piix4_sb800_region_release(&PIIX4_dev->dev, &mmio_cfg);
+
+ if (!smb_en) {
+ *smb_en_status = smba_en_lo & 0x10;
+ *piix4_smba = smba_en_hi << 8;
+ if (aux)
+ *piix4_smba |= 0x20;
+ } else {
+ *smb_en_status = smba_en_lo & 0x01;
+ *piix4_smba = ((smba_en_hi << 8) | smba_en_lo) & 0xffe0;
+ }
+
+ if (!*smb_en_status) {
+ dev_err(&PIIX4_dev->dev,
+ "SMBus Host Controller not enabled!\n");
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
static int piix4_setup_sb800(struct pci_dev *PIIX4_dev,
const struct pci_device_id *id, u8 aux)
{
unsigned short piix4_smba;
- u8 smba_en_lo, smba_en_hi, smb_en, smb_en_status, port_sel;
+ u8 smb_en, smb_en_status, port_sel;
u8 i2ccfg, i2ccfg_offset = 0x10;
+ struct sb800_mmio_cfg mmio_cfg;
+ int retval;
/* SB800 and later SMBus does not support forcing address */
if (force || force_addr) {
@@ -290,35 +416,11 @@ static int piix4_setup_sb800(struct pci_dev *PIIX4_dev,
else
smb_en = (aux) ? 0x28 : 0x2c;
- if (!request_muxed_region(SB800_PIIX4_SMB_IDX, 2, "sb800_piix4_smb")) {
- dev_err(&PIIX4_dev->dev,
- "SMB base address index region 0x%x already in use.\n",
- SB800_PIIX4_SMB_IDX);
- return -EBUSY;
- }
-
- outb_p(smb_en, SB800_PIIX4_SMB_IDX);
- smba_en_lo = inb_p(SB800_PIIX4_SMB_IDX + 1);
- outb_p(smb_en + 1, SB800_PIIX4_SMB_IDX);
- smba_en_hi = inb_p(SB800_PIIX4_SMB_IDX + 1);
+ retval = piix4_setup_sb800_smba(PIIX4_dev, smb_en, aux, &smb_en_status,
+ &piix4_smba);
- release_region(SB800_PIIX4_SMB_IDX, 2);
-
- if (!smb_en) {
- smb_en_status = smba_en_lo & 0x10;
- piix4_smba = smba_en_hi << 8;
- if (aux)
- piix4_smba |= 0x20;
- } else {
- smb_en_status = smba_en_lo & 0x01;
- piix4_smba = ((smba_en_hi << 8) | smba_en_lo) & 0xffe0;
- }
-
- if (!smb_en_status) {
- dev_err(&PIIX4_dev->dev,
- "SMBus Host Controller not enabled!\n");
- return -ENODEV;
- }
+ if (retval)
+ return retval;
if (acpi_check_region(piix4_smba, SMBIOSIZE, piix4_driver.name))
return -ENODEV;
@@ -371,10 +473,11 @@ static int piix4_setup_sb800(struct pci_dev *PIIX4_dev,
piix4_port_shift_sb800 = SB800_PIIX4_PORT_IDX_SHIFT;
}
} else {
- if (!request_muxed_region(SB800_PIIX4_SMB_IDX, 2,
- "sb800_piix4_smb")) {
+ mmio_cfg.use_mmio = piix4_sb800_use_mmio(PIIX4_dev);
+ retval = piix4_sb800_region_request(&PIIX4_dev->dev, &mmio_cfg);
+ if (retval) {
release_region(piix4_smba, SMBIOSIZE);
- return -EBUSY;
+ return retval;
}
outb_p(SB800_PIIX4_PORT_IDX_SEL, SB800_PIIX4_SMB_IDX);
@@ -384,7 +487,7 @@ static int piix4_setup_sb800(struct pci_dev *PIIX4_dev,
SB800_PIIX4_PORT_IDX;
piix4_port_mask_sb800 = SB800_PIIX4_PORT_IDX_MASK;
piix4_port_shift_sb800 = SB800_PIIX4_PORT_IDX_SHIFT;
- release_region(SB800_PIIX4_SMB_IDX, 2);
+ piix4_sb800_region_release(&PIIX4_dev->dev, &mmio_cfg);
}
dev_info(&PIIX4_dev->dev,
@@ -662,6 +765,29 @@ static void piix4_imc_wakeup(void)
release_region(KERNCZ_IMC_IDX, 2);
}
+static int piix4_sb800_port_sel(u8 port, struct sb800_mmio_cfg *mmio_cfg)
+{
+ u8 smba_en_lo, val;
+
+ if (mmio_cfg->use_mmio) {
+ smba_en_lo = ioread8(mmio_cfg->addr + piix4_port_sel_sb800);
+ val = (smba_en_lo & ~piix4_port_mask_sb800) | port;
+ if (smba_en_lo != val)
+ iowrite8(val, mmio_cfg->addr + piix4_port_sel_sb800);
+
+ return (smba_en_lo & piix4_port_mask_sb800);
+ }
+
+ outb_p(piix4_port_sel_sb800, SB800_PIIX4_SMB_IDX);
+ smba_en_lo = inb_p(SB800_PIIX4_SMB_IDX + 1);
+
+ val = (smba_en_lo & ~piix4_port_mask_sb800) | port;
+ if (smba_en_lo != val)
+ outb_p(val, SB800_PIIX4_SMB_IDX + 1);
+
+ return (smba_en_lo & piix4_port_mask_sb800);
+}
+
/*
* Handles access to multiple SMBus ports on the SB800.
* The port is selected by bits 2:1 of the smb_en register (0x2c).
@@ -678,12 +804,12 @@ static s32 piix4_access_sb800(struct i2c_adapter *adap, u16 addr,
unsigned short piix4_smba = adapdata->smba;
int retries = MAX_TIMEOUT;
int smbslvcnt;
- u8 smba_en_lo;
- u8 port;
+ u8 prev_port;
int retval;
- if (!request_muxed_region(SB800_PIIX4_SMB_IDX, 2, "sb800_piix4_smb"))
- return -EBUSY;
+ retval = piix4_sb800_region_request(&adap->dev, &adapdata->mmio_cfg);
+ if (retval)
+ return retval;
/* Request the SMBUS semaphore, avoid conflicts with the IMC */
smbslvcnt = inb_p(SMBSLVCNT);
@@ -738,18 +864,12 @@ static s32 piix4_access_sb800(struct i2c_adapter *adap, u16 addr,
}
}
- outb_p(piix4_port_sel_sb800, SB800_PIIX4_SMB_IDX);
- smba_en_lo = inb_p(SB800_PIIX4_SMB_IDX + 1);
-
- port = adapdata->port;
- if ((smba_en_lo & piix4_port_mask_sb800) != port)
- outb_p((smba_en_lo & ~piix4_port_mask_sb800) | port,
- SB800_PIIX4_SMB_IDX + 1);
+ prev_port = piix4_sb800_port_sel(adapdata->port, &adapdata->mmio_cfg);
retval = piix4_access(adap, addr, flags, read_write,
command, size, data);
- outb_p(smba_en_lo, SB800_PIIX4_SMB_IDX + 1);
+ piix4_sb800_port_sel(prev_port, &adapdata->mmio_cfg);
/* Release the semaphore */
outb_p(smbslvcnt | 0x20, SMBSLVCNT);
@@ -758,7 +878,7 @@ static s32 piix4_access_sb800(struct i2c_adapter *adap, u16 addr,
piix4_imc_wakeup();
release:
- release_region(SB800_PIIX4_SMB_IDX, 2);
+ piix4_sb800_region_release(&adap->dev, &adapdata->mmio_cfg);
return retval;
}
@@ -836,6 +956,7 @@ static int piix4_add_adapter(struct pci_dev *dev, unsigned short smba,
return -ENOMEM;
}
+ adapdata->mmio_cfg.use_mmio = piix4_sb800_use_mmio(dev);
adapdata->smba = smba;
adapdata->sb800_main = sb800_main;
adapdata->port = port << piix4_port_shift_sb800;