Re: Touchpad stickiness on AMD laptops (was Dell Inspiron/XPS)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello,
feature host notify is now implemented. Trackpoint / touchpad is working pretty stable with high sample rate. But ... i can't disable interrupts. It can generatete 10 000 interrupts/s in extreme case with loaded pinctrl_amd when device is idle. But it works: https://youtu.be/L4oKt500kNo

SMBus is probably implemented correctly. It looks, like RMI4 is sending notifications constantly, even when interrupt bits are clean.

Attached patch is full of hacks, i warn anyone who would like to use it, i am not responsible for any hardware damage and hard-coded values like interrupt number should be changed manually.

Now more details:

Windows driver is fully interrupt driven. Therefore i tried to implement interrupt driven transaction. This was pretty easy. First implemented function was quick write.

I thought that interrupt is triggered after each transaction. After first transaction no interrupt was triggered. Each next transaction triggered interrupt 7us after starting transaction. This was impossible, because quick write contains 10 bits (start, 7 bit address, r/w and ack) transferred at 100kHz (readed from PM register AsfClkSel). Minimum time is 100us. Interrupt was generated after cleaning of interrupt bit or after disabling device. IRQ 7 has probably wrong type / polarity.

Lets look at relevant section of DSDT:

Scope (_SB.PCI0)
{
    Device (SMB1)
    {
        Name (_HID, "SMB0001")  // _HID: Hardware ID
        Name (_CRS, ResourceTemplate ()  // _CRS: Current Resource Settings
        {
            IO (Decode16,
                0x0B20,             // Range Minimum
                0x0B20,             // Range Maximum
                0x20,               // Alignment
                0x20,               // Length
                )
            IRQ (Level, ActiveLow, Shared, )
                {7}
        })
        Method (_STA, 0, NotSerialized)  // _STA: Status
        {
            Return (0x0F)
        }
    }
}

Interrupt 7 is level triggered and has active low state (standard value for PCI). Now lets look at /proc/interrupts

IR-IO-APIC    2-edge      timer
IR-IO-APIC    1-edge      i8042
IR-IO-APIC    7-edge      piix4_smbus
IR-IO-APIC    8-edge      rtc0
IR-IO-APIC    9-fasteoi   acpi
IR-IO-APIC   10-edge      AMDI0010:00
IR-IO-APIC   11-edge      AMDI0010:01
IR-IO-APIC   12-edge      i8042

This probably happens at signal level:

- r reset host interrupt status
- s start transaction
- e end transaction

11111111111111111111111111      111111111111111111
                         0      1                0
                         0      1                0
                         0      1                0
                         00000000                0000000
        |     |          |      |     |          |
       r-1   s-1        e-1    r-2   s-2        e-2

Interrupt is triggered on rising edge (r-2). I have tried to set type / polarity using devm_request_irq / irq_set_irq_type, but this has no effect. Temporary i have written hack to arch/x86/kernel/apic/io_apic.c:

if (mp_irq_entries == 7) {
    m->irqflag = MP_IRQPOL_ACTIVE_LOW | MP_IRQTRIG_LEVEL;
}

Now interrupt are triggered at right time with correct polarity.

PCI uses level triggered interrupts. Interrupt flags must be cleared. When there is not cleared interrupt flag, then interrupt will be triggered again and again. ASF has two interrupt flags: HostStatus.Intr and AsfStatus.SlaveIntr.

There is no problem with HostStatus.Intr. On slave event AsfStatus contains value 0x40 (6th bit set) instead of 0x20 (from documentation). Writing to 0x40 clears pending interrupt, 0x20 not. Disassembled windows driver writes to 0x40. AMD should fix this register in documentation.

Now i can load psmouse synaptics_intertouch=1 and device works great. Moving cursor causes bus collisions and device permanently sends host notify messages (cca 300 events / s), but it's stable.

After unloading psmouse or waiting few minutes device stops sending interrupts. After touching device interrupts are generated again. When i am not touching touchpad, interrupt status register contains zero, but host notifications are generated long time after last touch and with very high frequency. Interrupts are generated even when i don't call i2c_handle_smbus_host_notify. It can't be cause of infinite loop.

Now i don't know why RMI4 sends constantly notifications. I don't have current documentation (my documentation don't contains any new function like F03). Maybe there is new method to clear interrupts..
diff --git a/arch/x86/kernel/apic/io_apic.c b/arch/x86/kernel/apic/io_apic.c
index c1bb38493..7a8114bca 100644
--- a/arch/x86/kernel/apic/io_apic.c
+++ b/arch/x86/kernel/apic/io_apic.c
@@ -213,6 +213,9 @@ void mp_save_irq(struct mpc_intsrc *m)
 			return;
 	}
 
+	if (mp_irq_entries == 7) {
+		m->irqflag = MP_IRQPOL_ACTIVE_LOW | MP_IRQTRIG_LEVEL;
+	}
 	memcpy(&mp_irqs[mp_irq_entries], m, sizeof(*m));
 	if (++mp_irq_entries == MAX_IRQ_SOURCES)
 		panic("Max # of irq sources exceeded!!\n");
diff --git a/drivers/acpi/acpi_platform.c b/drivers/acpi/acpi_platform.c
index 78d621290..dc3ca60a5 100644
--- a/drivers/acpi/acpi_platform.c
+++ b/drivers/acpi/acpi_platform.c
@@ -25,7 +25,7 @@ static const struct acpi_device_id forbidden_id_list[] = {
 	{"PNP0200",  0},	/* AT DMA Controller */
 	{"ACPI0009", 0},	/* IOxAPIC */
 	{"ACPI000A", 0},	/* IOAPIC */
-	{"SMB0001",  0},	/* ACPI SMBUS virtual device */
+	//{"SMB0001",  0},	/* ACPI SMBUS virtual device */
 	{"", 0},
 };
 
diff --git a/drivers/i2c/busses/i2c-piix4.c b/drivers/i2c/busses/i2c-piix4.c
index 8c1b31ed0..6c516e4ea 100644
--- a/drivers/i2c/busses/i2c-piix4.c
+++ b/drivers/i2c/busses/i2c-piix4.c
@@ -33,7 +33,7 @@
 #include <linux/dmi.h>
 #include <linux/acpi.h>
 #include <linux/io.h>
-
+#include <linux/irq.h>
 
 /* PIIX4 SMBus address offsets */
 #define SMBHSTSTS	(0 + piix4_smba)
@@ -49,8 +49,49 @@
 #define SMBSLVEVT	(0xA + piix4_smba)
 #define SMBSLVDAT	(0xC + piix4_smba)
 
+/* ASF register offsets */
+#define ASF_PEC                   (0x08 + piix4_smba)
+#define ASF_LISTEN_ADR            (0x09 + piix4_smba)
+#define ASF_STATUS                (0x0a + piix4_smba)
+#define ASF_STATUS_MASK0          (0x0b + piix4_smba)
+#define ASF_STATUS_MASK1          (0x0c + piix4_smba)
+#define ASF_SLAVE_STATUS          (0x0d + piix4_smba)
+#define ASF_REMOTE_CTRL_ADR       (0x0e + piix4_smba)
+#define ASF_SENSOR_ADR            (0x0f + piix4_smba)
+#define ASF_DATA_READ_POINTER     (0x10 + piix4_smba)
+#define ASF_DATA_WRITE_POINTER    (0x11 + piix4_smba)
+#define ASF_SET_DATA_READ_POINTER (0x12 + piix4_smba)
+#define ASF_DATA_BANK_SEL         (0x13 + piix4_smba)
+#define ASF_SEMAPHORE             (0x14 + piix4_smba)
+#define ASF_SLAVE_EN              (0x15 + piix4_smba)
+#define ASF_DELAY_MASTER          (0x16 + piix4_smba)
+
+/* ASF ListenAdr */
+#define ASF_LISTEN_ADR_EN BIT(0)
+
+/* ASF status */
+#define ASF_SLAVE_INTR BIT(6)
+
+/* ASF Host status */
+#define ASF_HOST_INTR BIT(1)
+
+/* ASF Data bank sel */
+#define ASF_SET_READ_DATA_BANK_OFFSET 4
+#define ASF_DATA_BANK_LAST_TOUCH BIT(0)
+#define ASF_DATA_BANK_0_FULL BIT(2)
+#define ASF_DATA_BANK_1_FULL BIT(3)
+#define ASF_READ_HOST_DATA_BANK BIT(7)
+
+/* ASF semaphore */
+#define ASF_HOST_SEMAPHORE BIT(0)
+#define ASF_CLR_HOST_SEMAPHORE BIT(1)
+
+/* ASF slave */
+#define ASF_SLAVE_INTR_EN BIT(1)
+#define ASF_KILL_SLAVE BIT(4)
+
 /* count for request_region */
-#define SMBIOSIZE	9
+#define SMBIOSIZE	0x20
 
 /* PCI Address Constants */
 #define SMBBA		0x090
@@ -77,6 +118,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 +139,12 @@
 #define SB800_PIIX4_PORT_IDX_MASK_KERNCZ	0x18
 #define SB800_PIIX4_PORT_IDX_SHIFT_KERNCZ	3
 
+#define SB800_PIIX4_FCH_PM_DECODEEN_MMIO_EN     BIT(1)
+#define SB800_PIIX4_FCH_PM_ADDR                 0xFED80300
+#define SB800_PIIX4_FCH_PM_SIZE                 8
+
+#define AMD_PCI_SMBUS_REVISION_MMIO             0x51
+
 /* insmod parameters */
 
 /* If force is set to anything different from 0, we forcibly enable the
@@ -114,6 +162,7 @@ MODULE_PARM_DESC(force_addr,
 		 "EXTREMELY DANGEROUS!");
 
 static int srvrworks_csb5_delay;
+static bool is_amd_kerncz = false;
 static struct pci_driver piix4_driver;
 
 static const struct dmi_system_id piix4_dmi_blacklist[] = {
@@ -155,6 +204,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 +217,94 @@ struct i2c_piix4_adapdata {
 	bool sb800_main;
 	bool notify_imc;
 	u8 port;		/* Port number, shifted */
+	struct sb800_mmio_cfg mmio_cfg;
+	struct completion *completion;
 };
 
+/*
+static void piix4_asf_dump_registers(struct i2c_adapter *piix4_adapter, char *label)
+{
+	struct i2c_piix4_adapdata *adapdata = i2c_get_adapdata(piix4_adapter);
+	unsigned short piix4_smba = adapdata->smba;
+	int i;
+	u8 d[0x17];
+
+	for (i = 0; i < 0x17; ++i) {
+		if (i == 2 || i == 7) {
+			d[i] = 0;
+		}
+		else {
+			d[i] = inb_p(i + piix4_smba);
+		}
+	}
+
+	dev_dbg(&piix4_adapter->dev, "ASF registers: %s    %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x    %02x%02x %02x%02x %02x%02x %02x%02x\n", label, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], d[11], d[12], d[13], d[14], d[15], d[16], d[17], d[18], d[19], d[20], d[21], d[22], d[23]);
+}
+*/
+
+static int piix4_sb800_region_setup(struct device *dev,
+				    struct sb800_mmio_cfg *mmio_cfg)
+{
+	if (mmio_cfg->use_mmio) {
+		struct resource *res;
+		void __iomem *addr;
+
+		res = request_mem_region(SB800_PIIX4_FCH_PM_ADDR,
+					 SB800_PIIX4_FCH_PM_SIZE,
+					 "sb800_piix4_smb");
+		if (!res) {
+			dev_err(dev,
+				"SMB 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, "SMB base address mapping failed.\n");
+			return -ENOMEM;
+		}
+
+		mmio_cfg->res = res;
+		mmio_cfg->addr = addr;
+	} else {
+		if (!request_muxed_region(SB800_PIIX4_SMB_IDX,
+					  SB800_PIIX4_SMB_MAP_SIZE,
+					  "sb800_piix4_smb")) {
+			dev_err(dev,
+				"SMB 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);
+		mmio_cfg->addr = NULL;
+
+		release_resource(mmio_cfg->res);
+		mmio_cfg->res = NULL;
+	} else {
+		release_region(SB800_PIIX4_SMB_IDX,
+			       SB800_PIIX4_SMB_MAP_SIZE);
+	}
+}
+
+static bool piix4_sb800_use_mmio(struct pci_dev *PIIX4_dev)
+{
+	return (PIIX4_dev->vendor == PCI_VENDOR_ID_AMD &&
+		PIIX4_dev->device == PCI_DEVICE_ID_AMD_KERNCZ_SMBUS &&
+		PIIX4_dev->revision >= AMD_PCI_SMBUS_REVISION_MMIO);
+}
+
 static int piix4_setup(struct pci_dev *PIIX4_dev,
 		       const struct pci_device_id *id)
 {
@@ -263,12 +404,58 @@ 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_setup(&PIIX4_dev->dev, &mmio_cfg);
+	if (retval)
+		return retval;
+
+	if (mmio_cfg.use_mmio) {
+		iowrite32(ioread32(mmio_cfg.addr + 4) | SB800_PIIX4_FCH_PM_DECODEEN_MMIO_EN,
+			  mmio_cfg.addr + 4);
+
+		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;
+	}
+
+	return retval;
+}
+
 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;
+	int retval;
 
 	/* SB800 and later SMBus does not support forcing address */
 	if (force || force_addr) {
@@ -290,29 +477,10 @@ 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);
-
-	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;
-	}
+	retval = piix4_setup_sb800_smba(PIIX4_dev, smb_en,
+					aux, &smb_en_status, &piix4_smba);
+	if (retval)
+		return retval;
 
 	if (!smb_en_status) {
 		dev_err(&PIIX4_dev->dev,
@@ -338,23 +506,18 @@ static int piix4_setup_sb800(struct pci_dev *PIIX4_dev,
 	}
 
 	/* Request the SMBus I2C bus config region */
-	if (!request_region(piix4_smba + i2ccfg_offset, 1, "i2ccfg")) {
-		dev_err(&PIIX4_dev->dev, "SMBus I2C bus config region "
-			"0x%x already in use!\n", piix4_smba + i2ccfg_offset);
-		release_region(piix4_smba, SMBIOSIZE);
-		return -EBUSY;
-	}
-	i2ccfg = inb_p(piix4_smba + i2ccfg_offset);
-	release_region(piix4_smba + i2ccfg_offset, 1);
+	if (!aux) {
+		i2ccfg = inb_p(piix4_smba + i2ccfg_offset);
 
-	if (i2ccfg & 1)
-		dev_dbg(&PIIX4_dev->dev, "Using IRQ for SMBus\n");
-	else
-		dev_dbg(&PIIX4_dev->dev, "Using SMI# for SMBus\n");
+		if (i2ccfg & 1)
+			dev_dbg(&PIIX4_dev->dev, "Using IRQ for SMBus\n");
+		else
+			dev_dbg(&PIIX4_dev->dev, "Using SMI# for SMBus\n");
 
-	dev_info(&PIIX4_dev->dev,
-		 "SMBus Host Controller at 0x%x, revision %d\n",
-		 piix4_smba, i2ccfg >> 4);
+		dev_info(&PIIX4_dev->dev,
+		 	"SMBus Host Controller at 0x%x, revision %d\n",
+		 	piix4_smba, i2ccfg >> 4);
+	}
 
 	/* Find which register is used for port selection */
 	if (PIIX4_dev->vendor == PCI_VENDOR_ID_AMD ||
@@ -434,6 +597,101 @@ static int piix4_setup_aux(struct pci_dev *PIIX4_dev,
 	return piix4_smba;
 }
 
+static struct i2c_adapter *piix4_main_adapters[PIIX4_MAX_ADAPTERS];
+static struct i2c_adapter *piix4_aux_adapter;
+static int piix4_adapter_count;
+
+
+static u8 read_asf_data_bank(struct i2c_adapter *piix4_adapter, u8 bank_number)
+{
+	struct i2c_piix4_adapdata *adapdata = i2c_get_adapdata(piix4_adapter);
+	unsigned short piix4_smba = adapdata->smba;
+	u8 broadcast, addr, bank_sel;
+
+	outb_p(bank_number << ASF_SET_READ_DATA_BANK_OFFSET, ASF_DATA_BANK_SEL);
+	bank_sel = inb_p(ASF_DATA_BANK_SEL);
+	inb_p(SMBHSTCNT); // reset DataIndex
+	broadcast = inb_p(SMBBLKDAT);
+	addr = inb_p(SMBBLKDAT);
+
+	dev_dbg(&piix4_adapter->dev, "BankSel=%02x Data=%02x %02x\n", bank_sel, broadcast, addr);
+
+	if (broadcast != 0x10) {
+		return 0;
+	}
+
+	return addr;
+}
+
+
+static irqreturn_t piix4_isr(int irq, void *dev_id)
+{
+	struct i2c_adapter *piix4_adapter = (struct i2c_adapter *)dev_id;
+	struct i2c_piix4_adapdata *adapdata = i2c_get_adapdata(piix4_adapter);
+	unsigned short piix4_smba = adapdata->smba;
+
+	u8 host_status;
+	u8 bank_sel;
+	u8 asf_status;
+	u8 address[2] = {0x00, 0x00};
+	u8 *current_address;
+
+	inb_p(SMBHSTCNT);
+	host_status = inb_p(SMBHSTSTS);
+
+	// Clear HostStatus Intr and complete waiting
+	if (host_status & ASF_HOST_INTR) {
+		outb_p(ASF_HOST_INTR, SMBHSTSTS);
+
+		if (adapdata->completion) {
+			complete(adapdata->completion); // Notify caller
+		}
+	}
+
+	current_address = &address[0];
+
+	bank_sel = inb_p(ASF_DATA_BANK_SEL); // DataBankSel
+
+	if ((bank_sel & ASF_DATA_BANK_LAST_TOUCH) == 0) { // Last touched bank is 0
+		if (bank_sel & ASF_DATA_BANK_1_FULL) {
+			*current_address = read_asf_data_bank(piix4_adapter, 1);
+			current_address++;
+		}
+		if (bank_sel & ASF_DATA_BANK_0_FULL) {
+			*current_address = read_asf_data_bank(piix4_adapter, 0);
+		}
+	}
+	else { // Last touched bank is 1
+		if (bank_sel & ASF_DATA_BANK_0_FULL) {
+			*current_address = read_asf_data_bank(piix4_adapter, 0);
+			current_address++;
+		}
+		if (bank_sel & ASF_DATA_BANK_1_FULL) {
+			*current_address = read_asf_data_bank(piix4_adapter, 1);
+		}
+	}
+
+	outb_p(bank_sel & (ASF_DATA_BANK_0_FULL | ASF_DATA_BANK_1_FULL), ASF_DATA_BANK_SEL); // Clear DataBankxFull
+
+	// Trigger notifications
+	if (address[0] != 0x00) {
+		i2c_handle_smbus_host_notify(piix4_aux_adapter, address[0] >> 1);
+	}
+	if (address[1] != 0x00) {
+		i2c_handle_smbus_host_notify(piix4_aux_adapter, address[1] >> 1);
+	}
+
+	// Clean ASFStatus SlaveIntr
+	asf_status = inb_p(ASF_STATUS);
+	if (asf_status & ASF_SLAVE_INTR) {
+		outb_p(ASF_SLAVE_INTR, (ASF_STATUS)); // ASFStatus SlaveIntr? (in doc 0x20)
+	}
+
+	dev_dbg(&piix4_adapter->dev,"Interrupt HostStatus=%02x BankSel=%02x AsfStatus=%02x\n", host_status, bank_sel, asf_status);
+
+	return IRQ_HANDLED;
+}
+
 static int piix4_transaction(struct i2c_adapter *piix4_adapter)
 {
 	struct i2c_piix4_adapdata *adapdata = i2c_get_adapdata(piix4_adapter);
@@ -463,20 +721,30 @@ static int piix4_transaction(struct i2c_adapter *piix4_adapter)
 	/* start the transaction by setting bit 6 */
 	outb_p(inb(SMBHSTCNT) | 0x040, SMBHSTCNT);
 
-	/* We will always wait for a fraction of a second! (See PIIX4 docs errata) */
-	if (srvrworks_csb5_delay) /* Extra delay for SERVERWORKS_CSB5 */
-		usleep_range(2000, 2100);
-	else
-		usleep_range(250, 500);
-
-	while ((++timeout < MAX_TIMEOUT) &&
-	       ((temp = inb_p(SMBHSTSTS)) & 0x01))
-		usleep_range(250, 500);
-
-	/* If the SMBus is still busy, we give up */
-	if (timeout == MAX_TIMEOUT) {
-		dev_err(&piix4_adapter->dev, "SMBus Timeout!\n");
-		result = -ETIMEDOUT;
+	if (adapdata->completion) {
+		timeout = wait_for_completion_timeout(adapdata->completion, msecs_to_jiffies(100));
+		if (timeout == 0) {
+			dev_err(&piix4_adapter->dev, "SMBus Timeout!\n");
+			result = -ETIMEDOUT;
+		}
+		temp = inb_p(SMBHSTSTS);
+	}
+	else {
+		/* We will always wait for a fraction of a second! (See PIIX4 docs errata) */
+		if (srvrworks_csb5_delay) /* Extra delay for SERVERWORKS_CSB5 */
+			usleep_range(2000, 2100);
+		else
+			usleep_range(250, 500);
+
+		while ((++timeout < MAX_TIMEOUT) &&
+		       ((temp = inb_p(SMBHSTSTS)) & 0x01))
+			usleep_range(250, 500);
+
+		/* If the SMBus is still busy, we give up */
+		if (timeout == MAX_TIMEOUT) {
+			dev_err(&piix4_adapter->dev, "SMBus Timeout!\n");
+			result = -ETIMEDOUT;
+		}
 	}
 
 	if (temp & 0x10) {
@@ -560,6 +828,9 @@ static s32 piix4_access(struct i2c_adapter * adap, u16 addr,
 			if (len == 0 || len > I2C_SMBUS_BLOCK_MAX)
 				return -EINVAL;
 			outb_p(len, SMBHSTDAT0);
+			if (is_amd_kerncz && adap == piix4_aux_adapter) {
+				outb_p(ASF_READ_HOST_DATA_BANK, ASF_DATA_BANK_SEL); // Set DataBankSel to host bank
+			}
 			inb_p(SMBHSTCNT);	/* Reset SMBBLKDAT */
 			for (i = 1; i <= len; i++)
 				outb_p(data->block[i], SMBBLKDAT);
@@ -591,8 +862,12 @@ static s32 piix4_access(struct i2c_adapter * adap, u16 addr,
 		break;
 	case PIIX4_BLOCK_DATA:
 		data->block[0] = inb_p(SMBHSTDAT0);
-		if (data->block[0] == 0 || data->block[0] > I2C_SMBUS_BLOCK_MAX)
+		if (data->block[0] == 0 || data->block[0] > I2C_SMBUS_BLOCK_MAX) {
 			return -EPROTO;
+		}
+		if (is_amd_kerncz && adap == piix4_aux_adapter) {
+			outb_p(ASF_READ_HOST_DATA_BANK, ASF_DATA_BANK_SEL);
+		}
 		inb_p(SMBHSTCNT);	/* Reset SMBBLKDAT */
 		for (i = 1; i <= data->block[0]; i++)
 			data->block[i] = inb_p(SMBBLKDAT);
@@ -662,6 +937,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;
+
+	if (mmio_cfg->use_mmio) {
+		smba_en_lo = ioread8(mmio_cfg->addr + piix4_port_sel_sb800);
+
+		if ((smba_en_lo & piix4_port_mask_sb800) != port)
+			iowrite8((smba_en_lo & ~piix4_port_mask_sb800) | port,
+				 mmio_cfg->addr + piix4_port_sel_sb800);
+	} else {
+		outb_p(piix4_port_sel_sb800, SB800_PIIX4_SMB_IDX);
+		smba_en_lo = inb_p(SB800_PIIX4_SMB_IDX + 1);
+
+		if ((smba_en_lo & piix4_port_mask_sb800) != port)
+			outb_p((smba_en_lo & ~piix4_port_mask_sb800) | port,
+			       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 +976,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_setup(&adap->dev, &adapdata->mmio_cfg);
+	if (retval)
+		return retval;
 
 	/* Request the SMBUS semaphore, avoid conflicts with the IMC */
 	smbslvcnt  = inb_p(SMBSLVCNT);
@@ -738,18 +1036,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 +1050,43 @@ 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;
+}
+
+static s32 piix4_access_asf(struct i2c_adapter *adap, u16 addr,
+		 unsigned short flags, char read_write,
+		 u8 command, int size, union i2c_smbus_data *data)
+{
+	struct i2c_piix4_adapdata *adapdata = i2c_get_adapdata(adap);
+	unsigned short piix4_smba = adapdata->smba;
+	int timeout = 0;
+	int retval = 0;
+	u8 temp;
+
+	if (adapdata->completion) {
+		reinit_completion(adapdata->completion);
+	}
+
+	// Acquire IMC semaphore
+	outb_p(ASF_HOST_SEMAPHORE, ASF_SEMAPHORE);
+	while ((++timeout < MAX_TIMEOUT) && (!((temp = inb_p(ASF_SEMAPHORE)) & ASF_HOST_SEMAPHORE))) {
+		usleep_range(250, 500);
+	}
+	if ((temp & ASF_HOST_SEMAPHORE) == 0) {
+		dev_dbg(&adap->dev, "Host semaphore not acquired\n");
+		return -EBUSY;
+	}
+
+	outb_p(inb_p(ASF_SLAVE_EN) | ASF_KILL_SLAVE, ASF_SLAVE_EN); // Kill slave
+
+	retval = piix4_access(adap, addr, flags, read_write, command, size, data);
+
+	outb_p(inb_p(ASF_SLAVE_EN) & (~ASF_KILL_SLAVE), ASF_SLAVE_EN); // Enable slave
+
+	// Release semphore
+	outb_p(ASF_CLR_HOST_SEMAPHORE, ASF_SEMAPHORE);
+
 	return retval;
 }
 
@@ -766,7 +1094,7 @@ static u32 piix4_func(struct i2c_adapter *adapter)
 {
 	return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
 	    I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
-	    I2C_FUNC_SMBUS_BLOCK_DATA;
+	    I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_HOST_NOTIFY;
 }
 
 static const struct i2c_algorithm smbus_algorithm = {
@@ -779,6 +1107,11 @@ static const struct i2c_algorithm piix4_smbus_algorithm_sb800 = {
 	.functionality	= piix4_func,
 };
 
+static const struct i2c_algorithm piix4_smbus_algorithm_asf = {
+	.smbus_xfer	= piix4_access_asf,
+	.functionality	= piix4_func,
+};
+
 static const struct pci_device_id piix4_ids[] = {
 	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3) },
 	{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82443MX_3) },
@@ -805,12 +1138,8 @@ static const struct pci_device_id piix4_ids[] = {
 
 MODULE_DEVICE_TABLE (pci, piix4_ids);
 
-static struct i2c_adapter *piix4_main_adapters[PIIX4_MAX_ADAPTERS];
-static struct i2c_adapter *piix4_aux_adapter;
-static int piix4_adapter_count;
-
-static int piix4_add_adapter(struct pci_dev *dev, unsigned short smba,
-			     bool sb800_main, u8 port, bool notify_imc,
+static int piix4_add_adapter(struct pci_dev *dev, unsigned short piix4_smba,
+			     bool sb800_main, int irq, u8 port, bool notify_imc,
 			     u8 hw_port_nr, const char *name,
 			     struct i2c_adapter **padap)
 {
@@ -820,26 +1149,28 @@ static int piix4_add_adapter(struct pci_dev *dev, unsigned short smba,
 
 	adap = kzalloc(sizeof(*adap), GFP_KERNEL);
 	if (adap == NULL) {
-		release_region(smba, SMBIOSIZE);
+		release_region(piix4_smba, SMBIOSIZE);
 		return -ENOMEM;
 	}
 
 	adap->owner = THIS_MODULE;
 	adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
 	adap->algo = sb800_main ? &piix4_smbus_algorithm_sb800
-				: &smbus_algorithm;
+				: &piix4_smbus_algorithm_asf;
 
 	adapdata = kzalloc(sizeof(*adapdata), GFP_KERNEL);
 	if (adapdata == NULL) {
 		kfree(adap);
-		release_region(smba, SMBIOSIZE);
+		release_region(piix4_smba, SMBIOSIZE);
 		return -ENOMEM;
 	}
 
-	adapdata->smba = smba;
+	adapdata->smba = piix4_smba;
 	adapdata->sb800_main = sb800_main;
 	adapdata->port = port << piix4_port_shift_sb800;
 	adapdata->notify_imc = notify_imc;
+	adapdata->mmio_cfg.use_mmio = piix4_sb800_use_mmio(dev);
+	adapdata->completion = NULL;
 
 	/* set up the sysfs linkage to our parent device */
 	adap->dev.parent = &dev->dev;
@@ -851,7 +1182,7 @@ static int piix4_add_adapter(struct pci_dev *dev, unsigned short smba,
 	}
 
 	snprintf(adap->name, sizeof(adap->name),
-		"SMBus PIIX4 adapter%s at %04x", name, smba);
+		"SMBus PIIX4 adapter%s at %04x", name, piix4_smba);
 
 	i2c_set_adapdata(adap, adapdata);
 
@@ -859,11 +1190,29 @@ static int piix4_add_adapter(struct pci_dev *dev, unsigned short smba,
 	if (retval) {
 		kfree(adapdata);
 		kfree(adap);
-		release_region(smba, SMBIOSIZE);
+		release_region(piix4_smba, SMBIOSIZE);
 		return retval;
 	}
 
 	*padap = adap;
+
+	if (irq >= 0) {
+		if (!devm_request_irq(&dev->dev, dev->irq, piix4_isr, IRQF_SHARED, "piix4_smbus", *padap)) {
+			adapdata->completion = kzalloc(sizeof(*adapdata->completion), GFP_KERNEL);
+			if (adapdata->completion == NULL) {
+				kfree(adapdata);
+				kfree(adap);
+				release_region(piix4_smba, SMBIOSIZE);
+				return -ENOMEM;
+			}
+			init_completion(adapdata->completion);
+			dev_info(&dev->dev, "SMBus using irq %d\n", dev->irq);
+			outb_p(inb_p(ASF_SLAVE_EN) | ASF_KILL_SLAVE, ASF_SLAVE_EN); // Kill slave
+			outb_p(inb_p(ASF_SLAVE_EN) & (~ASF_KILL_SLAVE), ASF_SLAVE_EN); // Enable slave
+			outb_p((0x08 << 1) | ASF_LISTEN_ADR_EN, ASF_LISTEN_ADR); // Listen SMBus broadcast
+		}
+	}
+
 	return 0;
 }
 
@@ -885,7 +1234,7 @@ static int piix4_add_adapters_sb800(struct pci_dev *dev, unsigned short smba,
 	for (port = 0; port < piix4_adapter_count; port++) {
 		u8 hw_port_nr = port == 0 ? 0 : port + 1;
 
-		retval = piix4_add_adapter(dev, smba, true, port, notify_imc,
+		retval = piix4_add_adapter(dev, smba, true, -1, port, notify_imc,
 					   hw_port_nr,
 					   piix4_main_port_names_sb800[port],
 					   &piix4_main_adapters[port]);
@@ -957,7 +1306,7 @@ static int piix4_probe(struct pci_dev *dev, const struct pci_device_id *id)
 			return retval;
 
 		/* Try to register main SMBus adapter, give up if we can't */
-		retval = piix4_add_adapter(dev, retval, false, 0, false, 0,
+		retval = piix4_add_adapter(dev, retval, false, 0, false, -1, 0,
 					   "", &piix4_main_adapters[0]);
 		if (retval < 0)
 			return retval;
@@ -980,12 +1329,13 @@ static int piix4_probe(struct pci_dev *dev, const struct pci_device_id *id)
 	    (dev->device == PCI_DEVICE_ID_AMD_HUDSON2_SMBUS ||
 	     dev->device == PCI_DEVICE_ID_AMD_KERNCZ_SMBUS)) {
 		retval = piix4_setup_sb800(dev, id, 1);
+		is_amd_kerncz = true;
 	}
 
 	if (retval > 0) {
-		/* Try to add the aux adapter if it exists,
+		/* try to add the aux adapter if it exists,
 		 * piix4_add_adapter will clean up if this fails */
-		piix4_add_adapter(dev, retval, false, 0, false, 1,
+		piix4_add_adapter(dev, retval, false, 0, false, dev->irq, 1,
 				  is_sb800 ? piix4_aux_port_name_sb800 : "",
 				  &piix4_aux_adapter);
 	}
@@ -1001,6 +1351,9 @@ static void piix4_adap_remove(struct i2c_adapter *adap)
 		i2c_del_adapter(adap);
 		if (adapdata->port == (0 << piix4_port_shift_sb800))
 			release_region(adapdata->smba, SMBIOSIZE);
+		if (adapdata->completion) {
+			kfree(adapdata->completion);
+		}
 		kfree(adapdata);
 		kfree(adap);
 	}
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index 4537d1ea1..22d2375b7 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -743,6 +743,16 @@ static void quirk_piix4_acpi(struct pci_dev *dev)
 DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL,	PCI_DEVICE_ID_INTEL_82371AB_3,	quirk_piix4_acpi);
 DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL,	PCI_DEVICE_ID_INTEL_82443MX_3,	quirk_piix4_acpi);
 
+
+
+static void quirk_piix4_amd(struct pci_dev *dev) {
+	printk(KERN_INFO "piix4 fixing interrupt line");
+	pci_write_config_byte(dev, PCI_INTERRUPT_LINE, 7);
+	dev->irq = 7;
+}
+DECLARE_PCI_FIXUP_EARLY(PCI_VENDOR_ID_AMD,	PCI_DEVICE_ID_AMD_KERNCZ_SMBUS,	quirk_piix4_amd);
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_AMD,	PCI_DEVICE_ID_AMD_KERNCZ_SMBUS,	quirk_piix4_amd);
+
 #define ICH_PMBASE	0x40
 #define ICH_ACPI_CNTL	0x44
 #define  ICH4_ACPI_EN	0x10

[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux