All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms
@ 2010-04-09 10:29 Alan Cox
  2010-04-21 20:16 ` Andrew Morton
  0 siblings, 1 reply; 16+ messages in thread
From: Alan Cox @ 2010-04-09 10:29 UTC (permalink / raw)
  To: mingo, linux-kernel

It would be nice to get this into the tree in some form as a pile of the
driver stuff pending from Intel depends upon it. It's currently slotted into
arch/x86 as the IPC interface is very much part of the hardware, so don't
be fooled by its apparent PCI interface.

--

From: Sreedhara DS <sreedhara.ds@intel.com>

The IPC is used to bridge the communications between kernel and SCU on
some embedded Intel x86 platforms.

(Some API tweaking Alan Cox)

Signed-off-by: Sreedhara DS <sreedhara.ds@intel.com>
Signed-off-by: Alan Cox <alan@linux.intel.com>
---

 arch/x86/Kconfig                     |    8 
 arch/x86/include/asm/intel_scu_ipc.h |   93 ++++
 arch/x86/kernel/Makefile             |    1 
 arch/x86/kernel/intel_scu_ipc.c      |  882 ++++++++++++++++++++++++++++++++++
 4 files changed, 984 insertions(+), 0 deletions(-)
 create mode 100644 arch/x86/include/asm/intel_scu_ipc.h
 create mode 100644 arch/x86/kernel/intel_scu_ipc.c


diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index e9d7bf7..f423aea 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -410,6 +410,14 @@ config X86_MRST
 	  nor standard legacy replacement devices/features. e.g. Moorestown does
 	  not contain i8259, i8254, HPET, legacy BIOS, most of the io ports.
 
+config INTEL_SCU_IPC
+       bool "Intel SCU IPC Support"
+	depends on X86_MRST
+	default y
+	---help---
+	 IPC is used to bridge the communications between kernel and SCU on
+	 some embedded Intel x86 platforms.
+
 config X86_RDC321X
 	bool "RDC R-321x SoC"
 	depends on X86_32
diff --git a/arch/x86/include/asm/intel_scu_ipc.h b/arch/x86/include/asm/intel_scu_ipc.h
new file mode 100644
index 0000000..0d96dda
--- /dev/null
+++ b/arch/x86/include/asm/intel_scu_ipc.h
@@ -0,0 +1,93 @@
+#ifndef __INTEL_SCU_IPC_H__
+#define  __INTEL_SCU_IPC_H__
+
+struct scu_fw_version {
+	u8 rtMinorVer;		/* SCU RT Firmware Minor Revision */
+	u8 rtMajorVer;		/* SCU RT Firmware Major Revision */
+	u8 romMinorVer;		/* SCU ROM Firmware Minor Revision */
+	u8 romMajorVer;		/* SCU ROM Firmware Major Revision */
+	u8 punitMinorVer;	/* P-unit Microcode Minor Revision */
+	u8 punitMajorVer;	/* P-unit Microcode Major Revision */
+	u8 iaMinorVer;		/* IA-32 Firmware Minor Revision */
+	u8 iaMajorVer;		/* IA-32 Firmware Major Revision */
+	u8 ftlMinorVer;		/* FTL Driver Minor Revision */
+	u8 ftlMajorVer;		/* FTL Driver Major Revision */
+	u8 valMinorVer;		/* Validation Hooks Minor Revision */
+	u8 valMajorVer;		/* Validation Hooks Major Revision */
+	u8 ifwiMinorVer;	/* IFWI Minor Revision */
+	u8 ifwiMajorVer;	/* IFWI Major Revision */
+} ;
+
+struct battery_property {
+	u32 capacity;	/* Charger capacity */
+	u8  crnt;	/* Quick charge current value*/
+	u8  volt;	/* Fine adjustment of constant charge voltage */
+	u8  prot;	/* CHRGPROT register value */
+	u8  prot2;	/* CHRGPROT1 register value */
+	u8 timer;	/* Charging timer */
+} __attribute__((packed));
+
+/* Read single register */
+int intel_scu_ipc_ioread8(u16 addr, u8 *data);
+
+/* Read two registers */
+int intel_scu_ipc_ioread16(u16 addr, u16 *data);
+
+/* Read four registers */
+int intel_scu_ipc_ioread32(u16 addr, u32 *data);
+
+/* Write single register */
+int intel_scu_ipc_iowrite8(u16 addr, u8 data);
+
+/* Write two registers */
+int intel_scu_ipc_iowrite16(u16 addr, u16 data);
+
+/* Write four registers */
+int intel_scu_ipc_iowrite32(u16 addr, u32 data);
+
+/*
+ * Update single register based on the mask
+ * First element of data is register value and second element is mask value
+ */
+int intel_scu_ipc_update_register(u16 addr, u8 data[2]);
+
+/*
+ * Indirect register read
+ * Can be used when SCCB(System Controller Configuration Block) register
+ * HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ */
+int intel_scu_ipc_register_read(u32 addr, u32 *data);
+
+/*
+ * Indirect register write
+ * Can be used when SCCB(System Controller Configuration Block) register
+ * HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ */
+int intel_scu_ipc_register_write(u32 addr, u32 data);
+
+/* Read the coulomb counter accumulator to monitor the battery charge level */
+int intel_scu_ipc_battery_cc_read(u32 *value);
+
+/* Write the coulomb counter accumulator */
+int intel_scu_ipc_battery_cc_write(u32 value);
+
+/* Read battery properties */
+int intel_scu_ipc_battery_property(struct battery_property *prop);
+
+/*
+ * Used to enable Timer Services for Kernel Watchdog Operation permitting
+ * automatic reset or reboot of the system if tripped.
+ * Set watchdog timer value, returns 0 on success, 1 on error
+ */
+int intel_scu_ipc_set_watchdog(u32 warn_threshold, u32 reset_threshold);
+
+/* I2C control api */
+int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data);
+
+/* Read FW version */
+int intel_scu_ipc_fw_version(struct scu_fw_version *version);
+
+/* Update FW version */
+int intel_scu_ipc_fw_update(u8 *buffer, u32 length);
+
+#endif
diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile
index 4c58352..7aecd44 100644
--- a/arch/x86/kernel/Makefile
+++ b/arch/x86/kernel/Makefile
@@ -107,6 +107,7 @@ scx200-y			+= scx200_32.o
 
 obj-$(CONFIG_OLPC)		+= olpc.o
 obj-$(CONFIG_X86_MRST)		+= mrst.o
+obj-$(CONFIG_INTEL_SCU_IPC)	+= intel_scu_ipc.o
 
 microcode-y				:= microcode_core.o
 microcode-$(CONFIG_MICROCODE_INTEL)	+= microcode_intel.o
diff --git a/arch/x86/kernel/intel_scu_ipc.c b/arch/x86/kernel/intel_scu_ipc.c
new file mode 100644
index 0000000..258a88a
--- /dev/null
+++ b/arch/x86/kernel/intel_scu_ipc.c
@@ -0,0 +1,882 @@
+/*
+ * intel_scu_ipc.c: Driver for the Intel SCU IPC mechanism
+ *
+ * (C) Copyright 2008-2010 Intel Corporation
+ * Author: Sreedhara DS (sreedhara.ds@intel.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2
+ * of the License.
+ *
+ * SCU runing in ARC processor communicates with other entity running in IA
+ * core through IPC mechanism which in turn messaging between IA core ad SCU.
+ * SCU has two IPC mechanism IPC-1 and IPC-2. IPC-1 is used between IA32 and
+ * SCU where IPC-2 is used between P-Unit and SCU. This driver delas with
+ * IPC-1 Driver provides an API for power control unit registers (e.g. MSIC)
+ * along with other APIs.
+ */
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/sysdev.h>
+#include <linux/pm.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <asm/setup.h>
+#include <asm/intel_scu_ipc.h>
+
+/* IPC defines the following message types */
+#define IPCMSG_WATCHDOG_TIMER 0xF8 /* Set Kernel Watchdog Threshold */
+#define IPCMSG_BATTERY        0xEF /* Coulomb Counter Accumulator */
+#define IPCMSG_FW_UPDATE      0xFE /* Firmware update */
+#define IPCMSG_PCNTRL         0xFF /* Power controller unit read/write */
+#define IPCMSG_FW_REVISION    0xF4 /* Get firmware revision */
+
+/* Command id associated with message IPCMSG_PCNTRL */
+#define IPC_CMD_PCNTRL_W      0 /* Register write */
+#define IPC_CMD_PCNTRL_R      1 /* Register read */
+#define IPC_CMD_PCNTRL_M      2 /* Register read-modify-write */
+
+/* Miscelaneous Command ids */
+#define IPC_CMD_INDIRECT_RD   2 /* 32bit indirect read */
+#define IPC_CMD_INDIRECT_WR   5 /* 32bit indirect write */
+
+/*
+ * IPC register summary
+ *
+ * IPC register blocks are memory mapped at fixed address of 0xFF11C000
+ * To read or write information to the SCU, driver writes to IPC-1 memory
+ * mapped registers (base address 0xFF11C000). The following is the IPC
+ * mechanism
+ *
+ * 1. IA core cDMI interface claims this transaction and converts it to a
+ *    Transaction Layer Packet (TLP) message which is sent across the cDMI.
+ *
+ * 2. South Complex cDMI block receives this message and writes it to
+ *    the IPC-1 register block, causing an interrupt to the SCU
+ *
+ * 3. SCU firmware decodes this interrupt and IPC message and the appropriate
+ *    message handler is called within firmware.
+ */
+
+#define IPC_BASE_ADDR     0xFF11C000	/* IPC1 base register address */
+#define IPC_MAX_ADDR      0x100		/* Maximum IPC regisers */
+#define IPC_WWBUF_SIZE    16		/* IPC Write buffer Size */
+#define IPC_RWBUF_SIZE    16		/* IPC Read buffer Size */
+#define IPC_I2C_BASE      0xFF12B000	/* I2C control register base address */
+#define IPC_I2C_MAX_ADDR  0x10		/* Maximum I2C regisers */
+
+static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id);
+static void ipc_remove(struct pci_dev *pdev);
+
+struct intel_scu_ipc_dev {
+	struct pci_dev *pdev;
+	void __iomem *ipc_base;
+	void __iomem *i2c_base;
+	void __iomem *pci_base;
+};
+
+static struct intel_scu_ipc_dev  ipcdev; /* Only one for now */
+
+static int platform = 1;
+module_param(platform, int, 0);
+MODULE_PARM_DESC(platform, "1 for moorestown platform");
+
+/*
+ * Command Register (Write Only):
+ * A write to this register results in an interrupt to the SCU core processor
+ * Format:
+ * |rfu2(8) | size(8) | command id(4) | rfu1(3) | ioc(1) | command(8)|
+ */
+#define IPC_COMMAND_REG		ipcdev.ipc_base
+
+/*
+ * Status Register (Read Only):
+ * Driver will read this register to get the ready/busy status of the IPC
+ * block and error status of the IPC command that was just processed by SCU
+ * Format:
+ * |rfu3(8)|error code(8)|initiator id(8)|cmd id(4)|rfu1(2)|error(1)|busy(1)|
+ */
+#define IPC_STATUS_REG		(ipcdev.ipc_base + 0x04)
+
+/*
+ * IPC Source Pointer (Write Only):
+ * Use content as pointer for read location
+*/
+#define IPC_SPTR_REG		(ipcdev.ipc_base + 0x08)
+
+/*
+ * IPC destination Pointer (Write Only):
+ * Use content as pointer for destination write
+*/
+#define IPC_DPTR_REG		(ipcdev.ipc_base + 0x0C)
+
+/*
+ * IPC Write Buffer (Write Only):
+ * 16-byte buffer for sending data associated with IPC command to
+ * SCU. Size of the data is specified in the IPC_COMMAND_REG register
+*/
+#define IPC_WRITE_BUFFER	(ipcdev.ipc_base + 0x80)
+
+/*
+ * IPC Read Buffer (Read Only):
+ * 16 byte buffer for receiving data from SCU, if IPC command
+ * processing results in response data
+*/
+#define IPC_READ_BUFFER		(ipcdev.ipc_base + 0x90)
+
+#define IPC_I2C_CNTRL_ADDR	ipcdev.i2c_base
+#define I2C_DATA_ADDR		(ipcdev.i2c_base + 0x04)
+
+static DEFINE_MUTEX(ipclock); /* lock used to prevent multiple call to SCU */
+
+static inline void ipc_command(u32 cmd) /* Send ipc command */
+{
+	writel(cmd, IPC_COMMAND_REG);
+}
+
+static inline void ipc_write(u32 data, u32 offset) /* Write ipc data */
+{
+	writel(data, IPC_WRITE_BUFFER + offset);
+}
+
+static inline void ipc_write_dptr(u32 data) /* Write dptr data */
+{
+	writel(data, IPC_DPTR_REG);
+}
+
+static inline void ipc_write_sptr(u32 data) /* Write dptr data */
+{
+	writel(data, IPC_SPTR_REG);
+}
+
+static inline u8 ipc_readb(u32 offset) /* Read ipc byte data */
+{
+	return readb(IPC_READ_BUFFER + offset);
+}
+
+static inline u8 ipc_readl(u32 offset) /* Read ipc u32 data */
+{
+	return readl(IPC_READ_BUFFER + offset);
+}
+
+static inline int busy_loop(void) /* Wait till scu status is busy */
+{
+	u32 status = 0;
+	u32 loop_count = 0;
+
+	status = __raw_readl(IPC_STATUS_REG);
+	while (status & 1) {
+		udelay(1); /* scu processing time is in few u secods */
+		status = __raw_readl(IPC_STATUS_REG);
+		loop_count++;
+		/* break if scu doesn't reset busy bit after huge retry */
+		if (loop_count > 100000)
+			return -ETIMEDOUT;
+	}
+	return (status >> 1) & 1;
+}
+
+/* Read/Write power control(PMIC in Langwell, MSIC in PenWell) registers */
+static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id)
+{
+	int nc;
+	u32 offset = 0;
+	u32 err = 0;
+	u8 cbuf[IPC_WWBUF_SIZE] = { '\0' };
+	u32 *wbuf = (u32 *)&cbuf;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+
+	if (platform == 1) {
+		/* Entry is 4 bytes for read/write, 5 bytes for read modify */
+		for (nc = 0; nc < count; nc++) {
+			cbuf[offset] = addr[nc];
+			cbuf[offset + 1] = addr[nc] >> 8;
+			if (id != IPC_CMD_PCNTRL_R)
+				cbuf[offset + 2] = data[nc];
+			if (id == IPC_CMD_PCNTRL_M) {
+				cbuf[offset + 3] = data[nc + 1];
+				offset += 1;
+			}
+			offset += 3;
+		}
+		for (nc = 0, offset = 0; nc < count; nc++, offset += 4)
+			ipc_write(wbuf[nc], offset); /* Write wbuff */
+
+	} else {
+		for (nc = 0, offset = 0; nc < count; nc++, offset += 2)
+			ipc_write(addr[nc], offset); /* Write addresses */
+		if (id != IPC_CMD_PCNTRL_R) {
+			for (nc = 0; nc < count; nc++, offset++)
+				ipc_write(data[nc], offset); /* Write data */
+			if (id == IPC_CMD_PCNTRL_M)
+				ipc_write(data[nc + 1], offset); /* Mask value*/
+		}
+	}
+
+	if (id != IPC_CMD_PCNTRL_M)
+		ipc_command((count*3) << 16 |  id << 12 | 0 << 8 | op);
+	else
+		ipc_command((count*4) << 16 |  id << 12 | 0 << 8 | op);
+
+	err = busy_loop();
+
+	if (id == IPC_CMD_PCNTRL_R) { /* Read rbuf */
+		/* Workaround: values are read as 0 without memcpy_fromio */
+		memcpy_fromio(cbuf, IPC_READ_BUFFER, 16);
+		if (platform == 1) {
+			for (nc = 0, offset = 2; nc < count; nc++, offset += 3)
+				data[nc] = ipc_readb(offset);
+		} else {
+			for (nc = 0; nc < count; nc++)
+				data[nc] = ipc_readb(nc);
+		}
+	}
+	mutex_unlock(&ipclock);
+	return err;
+}
+
+/**
+ *	intel_scu_ipc_ioread8		-	read a word via the SCU
+ *	@addr: register on SCU
+ *	@data: return pointer for read byte
+ *
+ *	Read a single register. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_ioread8(u16 addr, u8 *data)
+{
+	return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread8);
+
+/**
+ *	intel_scu_ipc_ioread16		-	read a word via the SCU
+ *	@addr: register on SCU
+ *	@data: return pointer for read word
+ *
+ *	Read a register pair. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_ioread16(u16 addr, u16 *data)
+{
+	u16 x[2] = {addr, addr + 1 };
+	return pwr_reg_rdwr(x, (u8 *)data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread16);
+
+/**
+ *	intel_scu_ipc_ioread32		-	read a dword via the SCU
+ *	@addr: register on SCU
+ *	@data: return pointer for read dword
+ *
+ *	Read four registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_ioread32(u16 addr, u32 *data)
+{
+	u16 x[4] = {addr, addr + 1, addr + 2, addr + 3};
+	return pwr_reg_rdwr(x, (u8 *)data, 4, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread32);
+
+/**
+ *	intel_scu_ipc_iowrite8		-	write a byte via the SCU
+ *	@addr: register on SCU
+ *	@data: byte to write
+ *
+ *	Write a single register. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_iowrite8(u16 addr, u8 data)
+{
+	return pwr_reg_rdwr(&addr, &data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite8);
+
+/**
+ *	intel_scu_ipc_iowrite16		-	write a word via the SCU
+ *	@addr: register on SCU
+ *	@data: word to write
+ *
+ *	Write two registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_iowrite16(u16 addr, u16 data)
+{
+	u16 x[2] = {addr, addr + 1 };
+	return pwr_reg_rdwr(x, (u8 *)&data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite16);
+
+/**
+ *	intel_scu_ipc_iowrite32		-	write a dword via the SCU
+ *	@addr: register on SCU
+ *	@data: dword to write
+ *
+ *	Write four registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_iowrite32(u16 addr, u32 data)
+{
+	u16 x[4] = {addr, addr + 1, addr + 2, addr + 3};
+	return pwr_reg_rdwr(x, (u8 *)&data, 4, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite32);
+
+/**
+ *	intel_scu_ipc_update_register	-	r/m/w a register
+ *	@addr: register address
+ *	@data: bits to update and mask
+ *
+ *	Read-modify-write power control unit register. The first byte of the
+ *	data argument must be register value and second is mask value
+ *	mask is a bitmap that indicates which bits to update.
+ *	0 = masked. Don't modify this bit, 1 = modify this bit.
+ *	returns 0 on success or an error code.
+ *
+ *	This function may sleep. Locking between SCU accesses is handled
+ *	for the caller.
+ */
+int intel_scu_ipc_update_register(u16 addr, u8 data[2])
+{
+	return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_M);
+}
+EXPORT_SYMBOL(intel_scu_ipc_update_register);
+
+/**
+ *	intel_scu_ipc_register_read	-	32bit indirect read
+ *	@addr: register address
+ *	@value: 32bit value return
+ *
+ *	Performs IA 32 bit indirect read, returns 0 on success, or an
+ *	error code.
+ *
+ *	Can be used when SCCB(System Controller Configuration Block) register
+ *	HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_register_read(u32 addr, u32 *value)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_write_sptr(addr);
+	ipc_command(4 << 16 | IPC_CMD_INDIRECT_RD);
+	err = busy_loop();
+	*value = ipc_readl(0);
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_register_read);
+
+/**
+ *	intel_scu_ipc_register_write	-	32bit indirect write
+ *	@addr: register address
+ *	@value: 32bit value to write
+ *
+ *	Performs IA 32 bit indirect write, returns 0 on success, or an
+ *	error code.
+ *
+ *	Can be used when SCCB(System Controller Configuration Block) register
+ *	HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_register_write(u32 addr, u32 value)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_write_dptr(addr);
+	ipc_write(value, 0);
+	ipc_command(4 << 16 | IPC_CMD_INDIRECT_WR);
+	err = busy_loop();
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_register_write);
+
+/* Battery coulomb counter accumulator commands */
+#define IPC_CMD_CC_WR  0 /* Update coulomb counter value */
+#define IPC_CMD_CC_RD  1 /* Read coulomb counter value */
+#define IPC_CMD_BATTERY_PROPERTY 2 /* Read Battery property */
+
+/**
+ *	intel_scu_ipc_battery_cc_read	-	read battery cc
+ *	@value: battery coulomb counter read
+ *
+ *	Reads the battery couloumb counter value, returns 0 on success, or
+ *	an error code
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_battery_cc_read(u32 *value)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_command(IPC_CMD_CC_RD << 12 | IPCMSG_BATTERY);
+	err = busy_loop();
+	*value = ipc_readl(0);
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_battery_cc_read);
+
+/**
+ *	intel_scu_ipc_battery_property_get	-	fetch properties
+ *	@prop: battery properties
+ *
+ *	Retrieve the battery properties from the power management
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_battery_cc_write(u32 data)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_write(data, 0);
+	ipc_command(IPC_CMD_CC_WR << 12 | IPCMSG_BATTERY);
+	err = busy_loop();
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_battery_cc_write);
+
+/**
+ *	intel_scu_ipc_battery_property_get	-	fetch properties
+ *	@prop: battery properties
+ *
+ *	Retrieve the battery properties from the power management
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_battery_property_get(struct battery_property *prop)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_command(IPC_CMD_BATTERY_PROPERTY << 12 | IPCMSG_BATTERY);
+	err = busy_loop();
+	prop->capacity = ipc_readb(0);
+	prop->crnt = ipc_readb(4);
+	prop->volt = ipc_readb(5);
+	prop->prot = ipc_readb(6);
+	prop->prot2 = ipc_readb(7);
+	prop->timer = ipc_readb(8);
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_battery_property_get);
+
+/**
+ *	intel_scu_ipc_set_watchdog	-	watchdog configuration
+ *	@warn_threshold - warning timeout
+ *	@reset_threshold - reset timeout
+ *
+ *	Used to enable Timer Services for Kernel Watchdog Operation permitting
+ *	automatic reset or reboot of the system if tripped.
+ *	Set watchdog timer value, returns 0 on success, 1 on error
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_set_watchdog(u32 warn_threshold, u32 reset_threshold)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_write(warn_threshold, 0);
+	ipc_write(reset_threshold, 4);
+	ipc_command(2 << 16 | IPCMSG_WATCHDOG_TIMER);
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_set_watchdog);
+
+/**
+ *	intel_scu_ipc_fw_version	-	firmware information
+ *	@version: retrieved firmware version information
+ *
+ *	Read the firmware information from the SCU.
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_fw_version(struct scu_fw_version *version)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_command(IPCMSG_FW_REVISION);
+	err = busy_loop();
+	version->rtMinorVer = ipc_readb(0);
+	version->rtMajorVer = ipc_readb(1);
+	version->romMinorVer = ipc_readb(2);
+	version->romMajorVer = ipc_readb(3);
+	version->punitMinorVer = ipc_readb(4);
+	version->punitMajorVer = ipc_readb(5);
+	version->iaMinorVer = ipc_readb(6);
+	version->iaMajorVer = ipc_readb(7);
+	version->ftlMinorVer = ipc_readb(8);
+	version->ftlMajorVer = ipc_readb(9);
+	version->valMinorVer = ipc_readb(10);
+	version->valMajorVer = ipc_readb(11);
+	/* Bytes 12 and 13 are unused */
+	version->ifwiMinorVer = ipc_readb(14);
+	version->ifwiMajorVer = ipc_readb(15);
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_fw_version);
+
+/*I2C commands */
+#define IPC_I2C_WRITE 1 /* I2C Write command */
+#define IPC_I2C_READ  2 /* I2C Read command */
+
+/**
+ *	intel_scu_ipc_i2c_cntrl		-	I2C read/write operations
+ *	@addr: I2C address + command bits
+ *	@data: data to read/write
+ *
+ *	Perform an an I2C read/write operation via the SCU. All locking is
+ *	handled for the caller. This function may sleep.
+ *
+ *	Returns an error code or 0 on success.
+ */
+int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data)
+{
+	u32 cmd = 0;
+
+	mutex_lock(&ipclock);
+	cmd = (addr >> 24) & 0xFF;
+	if (cmd == IPC_I2C_READ) {
+		writel(addr, IPC_I2C_CNTRL_ADDR);
+		mdelay(1);/*Write Not getting updated without delay*/
+		*data = readl(I2C_DATA_ADDR);
+	} else if (cmd == IPC_I2C_WRITE) {
+		writel(addr, I2C_DATA_ADDR);
+		mdelay(1);
+		writel(addr, IPC_I2C_CNTRL_ADDR);
+	} else {
+		dev_err(&ipcdev.pdev->dev,
+			"intel_scu_ipc: I2C INVALID_CMD = 0x%x\n", cmd);
+
+		mutex_unlock(&ipclock);
+		return -1;
+	}
+	mutex_unlock(&ipclock);
+	return 0;
+}
+EXPORT_SYMBOL(intel_scu_ipc_i2c_cntrl);
+
+#define IPC_FW_LOAD_ADDR 0xFFFC0000 /* Storage location for FW image */
+#define IPC_FW_UPDATE_MBOX_ADDR 0xFFFFDFF4 /* Mailbox between ipc and scu */
+#define IPC_MAX_FW_SIZE 262144 /* 256K storage size for loading the FW image */
+#define IPC_FW_MIP_HEADER_SIZE 2048 /* Firmware MIP header size */
+/* IPC inform SCU to get ready for update process */
+#define IPC_CMD_FW_UPDATE_READY  0x10FE
+/* IPC inform SCU to go for update process */
+#define IPC_CMD_FW_UPDATE_GO     0x20FE
+/* Status code for fw update */
+#define IPC_FW_UPDATE_SUCCESS	0x444f4e45 /* Status code 'DONE' */
+#define IPC_FW_UPDATE_BADN	0x4241444E /* Status code 'BADN' */
+#define IPC_FW_TXHIGH		0x54784849 /* Status code 'IPC_FW_TXHIGH' */
+#define IPC_FW_TXLOW		0x54784c4f /* Status code 'IPC_FW_TXLOW' */
+
+struct fw_update_mailbox {
+	u32    status;
+	u32    scu_flag;
+	u32    driver_flag;
+};
+
+
+/**
+ *	intel_scu_ipc_fw_update	-	 Firmware update utility
+ *	@buffer: firmware buffer
+ *	@length: size of firmware buffer
+ *
+ *	This function provides an interface to load the firmware into
+ *	the SCU. Returns 0 on success or -1 on failure
+ */
+int intel_scu_ipc_fw_update(u8 *buffer, u32 length)
+{
+	void __iomem *fw_update_base;
+	void __iomem *mailbox_base;
+	int retry_cnt = 0;
+
+	struct fw_update_mailbox *mailbox = NULL;
+
+	mutex_lock(&ipclock);
+	fw_update_base = ioremap_nocache(IPC_FW_LOAD_ADDR, (128*1024));
+	if (fw_update_base == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENOMEM;
+	}
+	mailbox_base = ioremap_nocache(IPC_FW_UPDATE_MBOX_ADDR,
+					sizeof(struct fw_update_mailbox));
+	if (mailbox_base == NULL) {
+		iounmap(fw_update_base);
+		mutex_unlock(&ipclock);
+		return -ENOMEM;
+	}
+
+	mailbox = (struct fw_update_mailbox *)mailbox_base;
+
+	ipc_command(IPC_CMD_FW_UPDATE_READY);
+
+	/* Intitialize mailbox */
+	mailbox->status = 0;
+	mailbox->scu_flag = 0;
+	mailbox->driver_flag = 0;
+
+	/* Driver copies the 2KB MIP header to SRAM at 0xFFFC0000*/
+	memcpy_toio((u8 *)(fw_update_base), buffer, 0x800);
+
+	/* Driver sends "FW Update" IPC command (CMD_ID 0xFE; MSG_ID 0x02).
+	* Upon receiving this command, SCU will write the 2K MIP header
+	* from 0xFFFC0000 into NAND.
+	* SCU will write a status code into the Mailbox, and then set scu_flag.
+	*/
+
+	ipc_command(IPC_CMD_FW_UPDATE_GO);
+
+	/*Driver stalls until scu_flag is set */
+	while (mailbox->scu_flag != 1) {
+		rmb();
+		mdelay(1);
+	}
+
+	/* Driver checks Mailbox status.
+	 * If the status is 'BADN', then abort (bad NAND).
+	 * If the status is 'IPC_FW_TXLOW', then continue.
+	 */
+	while (mailbox->status != IPC_FW_TXLOW) {
+		rmb();
+		mdelay(10);
+	}
+	mdelay(10);
+
+update_retry:
+	if (retry_cnt > 5)
+		goto update_end;
+
+	if (mailbox->status != IPC_FW_TXLOW)
+		goto update_end;
+	buffer = buffer+0x800;
+	memcpy_toio((u8 *)(fw_update_base), buffer, 0x20000);
+	mailbox->driver_flag = 0x1;
+	while (mailbox->scu_flag == 1) {
+		rmb();
+		mdelay(1);
+	}
+
+	/* check for 'BADN' */
+	if (mailbox->status == IPC_FW_UPDATE_BADN)
+		goto update_end;
+
+	while (mailbox->status != IPC_FW_TXHIGH) {
+		rmb();
+		mdelay(10);
+	}
+	mdelay(10);
+
+	if (mailbox->status != IPC_FW_TXHIGH)
+		goto update_end;
+	buffer = buffer+0x20000;
+	memcpy_toio((u8 *)(fw_update_base), buffer, 0x20000);
+	mailbox->driver_flag = 0;
+	while (mailbox->scu_flag == 0) {
+		rmb();
+		mdelay(1);
+	}
+
+	/* check for 'BADN' */
+	if (mailbox->status == IPC_FW_UPDATE_BADN)
+		goto update_end;
+
+	if (mailbox->status == IPC_FW_TXLOW) {
+		++retry_cnt;
+		goto update_retry;
+	}
+
+update_end:
+	iounmap(fw_update_base);
+	iounmap(mailbox_base);
+	mutex_unlock(&ipclock);
+	if (mailbox->status == IPC_FW_UPDATE_SUCCESS)
+		return 0;
+	return -1;
+}
+EXPORT_SYMBOL(intel_scu_ipc_fw_update);
+
+/*
+ * Interrupt handler gets called when ioc bit of IPC_COMMAND_REG set to 1
+ * When ioc bit is set to 1, caller api must wait for interrupt handler called
+ * which in turn unlocks the caller api. Currently this is not used
+ *
+ * This is edge triggered so we need take no action to clear anything
+ */
+static irqreturn_t ioc(int irq, void *dev_id)
+{
+	return IRQ_HANDLED;
+}
+
+/**
+ *	ipc_probe	-	probe an Intel SCU IPC
+ *	@dev: the PCI device matching
+ *	@id: entry in the match table
+ *
+ *	Enable and install an intel SCU IPC. This appears in the PCI space
+ *	but uses some hard coded addresses as well.
+ */
+static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+	int err;
+	resource_size_t pci_resource;
+
+	if (ipcdev.pdev)		/* We support only one SCU */
+		return -EBUSY;
+
+	ipcdev.pdev = pci_dev_get(dev);
+
+	err = pci_enable_device(dev);
+	if (err)
+		return err;
+
+	err = pci_request_regions(dev, "ipc_mrst");
+	if (err)
+		return err;
+
+	pci_resource = pci_resource_start(dev, 0);
+	if (!pci_resource)
+		return -ENOMEM;
+
+	if (request_irq(dev->irq, ioc, 0, "ipc_mrst", &ipcdev))
+		return -EBUSY;
+
+	ipcdev.ipc_base = ioremap_nocache(IPC_BASE_ADDR, IPC_MAX_ADDR);
+	if (!ipcdev.ipc_base)
+		return -ENOMEM;
+
+	ipcdev.i2c_base = ioremap_nocache(IPC_I2C_BASE, IPC_I2C_MAX_ADDR);
+	if (!ipcdev.i2c_base) {
+		iounmap(ipcdev.ipc_base);
+		return -ENOMEM;
+	}
+
+	ipcdev.pci_base = ioremap_nocache(pci_resource, 0x1000);
+	if (!ipcdev.pci_base) {
+		iounmap(ipcdev.ipc_base);
+		iounmap(ipcdev.i2c_base);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/**
+ *	ipc_remove	-	remove a bound IPC device
+ *	@pdev: PCI device
+ *
+ *	In practice the SCU is not removable but this function is also
+ *	called for each device on a module unload or cleanup which is the
+ *	path that will get used.
+ *
+ *	Free up the mappings and release the PCI resources
+ */
+static void ipc_remove(struct pci_dev *pdev)
+{
+	free_irq(pdev->irq, &ipcdev);
+	pci_release_regions(pdev);
+	pci_dev_put(ipcdev.pdev);
+	iounmap(ipcdev.ipc_base);
+	iounmap(ipcdev.i2c_base);
+	iounmap(ipcdev.pci_base);
+	ipcdev.pdev = NULL;
+}
+
+static const struct pci_device_id pci_ids[] = {
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x080e)},
+	{ 0,}
+};
+MODULE_DEVICE_TABLE(pci, pci_ids);
+
+static struct pci_driver ipc_driver = {
+	.name = "ipc_mrst",
+	.id_table = pci_ids,
+	.probe = ipc_probe,
+	.remove = ipc_remove,
+};
+
+
+static int __init intel_scu_ipc_init(void)
+{
+	return  pci_register_driver(&ipc_driver);
+}
+
+static void __exit intel_scu_ipc_exit(void)
+{
+	pci_unregister_driver(&ipc_driver);
+}
+
+MODULE_AUTHOR("Sreedhara DS <sreedhara.ds@intel.com>");
+MODULE_DESCRIPTION("Intel SCU IPC driver");
+MODULE_LICENSE("GPL");
+
+module_init(intel_scu_ipc_init);
+module_exit(intel_scu_ipc_exit);


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms
  2010-04-09 10:29 [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms Alan Cox
@ 2010-04-21 20:16 ` Andrew Morton
  2010-04-21 20:26   ` Alan Cox
  2010-04-22 13:16   ` Alan Cox
  0 siblings, 2 replies; 16+ messages in thread
From: Andrew Morton @ 2010-04-21 20:16 UTC (permalink / raw)
  To: Alan Cox; +Cc: mingo, linux-kernel, Sreedhara DS

On Fri, 09 Apr 2010 11:29:23 +0100
Alan Cox <alan@linux.intel.com> wrote:

> It would be nice to get this into the tree in some form as a pile of the
> driver stuff pending from Intel depends upon it. It's currently slotted into
> arch/x86 as the IPC interface is very much part of the hardware, so don't
> be fooled by its apparent PCI interface.
> 
> --
> 
> From: Sreedhara DS <sreedhara.ds@intel.com>
> 
> The IPC is used to bridge the communications between kernel and SCU on
> some embedded Intel x86 platforms.
> 
> (Some API tweaking Alan Cox)

It's be nice to have some words or a link describing what an IPC
actually _is_.

>
> ...
> 
> +struct battery_property {
> +	u32 capacity;	/* Charger capacity */
> +	u8  crnt;	/* Quick charge current value*/
> +	u8  volt;	/* Fine adjustment of constant charge voltage */
> +	u8  prot;	/* CHRGPROT register value */
> +	u8  prot2;	/* CHRGPROT1 register value */
> +	u8 timer;	/* Charging timer */
> +} __attribute__((packed));

__packed, please.  (I've requested that this be added to checkpatch,
but Mr Checkpatch is asleep).

>
> ...
> 
> +struct intel_scu_ipc_dev {
> +	struct pci_dev *pdev;
> +	void __iomem *ipc_base;
> +	void __iomem *i2c_base;
> +	void __iomem *pci_base;
> +};
> +
> +static struct intel_scu_ipc_dev  ipcdev; /* Only one for now */

Could do

static struct intel_scu_ipc_dev {
	...
} ipcdev;

if so inclined.

> +static int platform = 1;
> +module_param(platform, int, 0);
> +MODULE_PARM_DESC(platform, "1 for moorestown platform");
> +
> +/*
> + * Command Register (Write Only):
> + * A write to this register results in an interrupt to the SCU core processor
> + * Format:
> + * |rfu2(8) | size(8) | command id(4) | rfu1(3) | ioc(1) | command(8)|
> + */
> +#define IPC_COMMAND_REG		ipcdev.ipc_base
>
> +/*
> + * Status Register (Read Only):
> + * Driver will read this register to get the ready/busy status of the IPC
> + * block and error status of the IPC command that was just processed by SCU
> + * Format:
> + * |rfu3(8)|error code(8)|initiator id(8)|cmd id(4)|rfu1(2)|error(1)|busy(1)|
> + */
> +#define IPC_STATUS_REG		(ipcdev.ipc_base + 0x04)
> +
> +/*
> + * IPC Source Pointer (Write Only):
> + * Use content as pointer for read location
> +*/
> +#define IPC_SPTR_REG		(ipcdev.ipc_base + 0x08)
> +
> +/*
> + * IPC destination Pointer (Write Only):
> + * Use content as pointer for destination write
> +*/
> +#define IPC_DPTR_REG		(ipcdev.ipc_base + 0x0C)
> +
> +/*
> + * IPC Write Buffer (Write Only):
> + * 16-byte buffer for sending data associated with IPC command to
> + * SCU. Size of the data is specified in the IPC_COMMAND_REG register
> +*/
> +#define IPC_WRITE_BUFFER	(ipcdev.ipc_base + 0x80)
> +
> +/*
> + * IPC Read Buffer (Read Only):
> + * 16 byte buffer for receiving data from SCU, if IPC command
> + * processing results in response data
> +*/
> +#define IPC_READ_BUFFER		(ipcdev.ipc_base + 0x90)
> +
> +#define IPC_I2C_CNTRL_ADDR	ipcdev.i2c_base
> +#define I2C_DATA_ADDR		(ipcdev.i2c_base + 0x04)

Do we actually need these aliases?  Why not open-code
`ipcdev.ipc_base', etc in the very few places where these macros are
used?

>
> ...
> 
> +static inline int busy_loop(void) /* Wait till scu status is busy */
> +{
> +	u32 status = 0;
> +	u32 loop_count = 0;
> +
> +	status = __raw_readl(IPC_STATUS_REG);
> +	while (status & 1) {
> +		udelay(1); /* scu processing time is in few u secods */
> +		status = __raw_readl(IPC_STATUS_REG);
> +		loop_count++;
> +		/* break if scu doesn't reset busy bit after huge retry */
> +		if (loop_count > 100000)
> +			return -ETIMEDOUT;

I'd suggest adding a printk if this failure happens.  Otherwise the
results will be pretty mysterious.

> +	}
> +	return (status >> 1) & 1;
> +}

This function has seven-odd callsites and is waaaaaaaay to fat and slow
to be inlined.

> +/* Read/Write power control(PMIC in Langwell, MSIC in PenWell) registers */
> +static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id)
> +{
> +	int nc;
> +	u32 offset = 0;
> +	u32 err = 0;
> +	u8 cbuf[IPC_WWBUF_SIZE] = { '\0' };

Actually, `= { }' will suffice.

> +	u32 *wbuf = (u32 *)&cbuf;
> +
> +	mutex_lock(&ipclock);
> +	if (ipcdev.pdev == NULL) {
> +		mutex_unlock(&ipclock);
> +		return -ENODEV;
> +	}
> +
> +	if (platform == 1) {
> +		/* Entry is 4 bytes for read/write, 5 bytes for read modify */
> +		for (nc = 0; nc < count; nc++) {
> +			cbuf[offset] = addr[nc];
> +			cbuf[offset + 1] = addr[nc] >> 8;
> +			if (id != IPC_CMD_PCNTRL_R)
> +				cbuf[offset + 2] = data[nc];
> +			if (id == IPC_CMD_PCNTRL_M) {
> +				cbuf[offset + 3] = data[nc + 1];
> +				offset += 1;
> +			}
> +			offset += 3;
> +		}
> +		for (nc = 0, offset = 0; nc < count; nc++, offset += 4)
> +			ipc_write(wbuf[nc], offset); /* Write wbuff */
> +
> +	} else {
> +		for (nc = 0, offset = 0; nc < count; nc++, offset += 2)
> +			ipc_write(addr[nc], offset); /* Write addresses */
> +		if (id != IPC_CMD_PCNTRL_R) {
> +			for (nc = 0; nc < count; nc++, offset++)
> +				ipc_write(data[nc], offset); /* Write data */
> +			if (id == IPC_CMD_PCNTRL_M)
> +				ipc_write(data[nc + 1], offset); /* Mask value*/
> +		}
> +	}
> +
> +	if (id != IPC_CMD_PCNTRL_M)
> +		ipc_command((count*3) << 16 |  id << 12 | 0 << 8 | op);
> +	else
> +		ipc_command((count*4) << 16 |  id << 12 | 0 << 8 | op);
> +
> +	err = busy_loop();
> +
> +	if (id == IPC_CMD_PCNTRL_R) { /* Read rbuf */
> +		/* Workaround: values are read as 0 without memcpy_fromio */
> +		memcpy_fromio(cbuf, IPC_READ_BUFFER, 16);

Should we still be doing this if busy_loop() failed?

(Lots of dittoes on this question)

> +		if (platform == 1) {
> +			for (nc = 0, offset = 2; nc < count; nc++, offset += 3)
> +				data[nc] = ipc_readb(offset);
> +		} else {
> +			for (nc = 0; nc < count; nc++)
> +				data[nc] = ipc_readb(nc);
> +		}
> +	}
> +	mutex_unlock(&ipclock);
> +	return err;
> +}

I wonder if this function would look better if cbuf had type u16[],

>
> ...
> 
> +int intel_scu_ipc_register_read(u32 addr, u32 *value)
> +{
> +	u32 err = 0;
> +
> +	mutex_lock(&ipclock);
> +	if (ipcdev.pdev == NULL) {

This check happens a lot.  Can it really happen?

> +		mutex_unlock(&ipclock);
> +		return -ENODEV;
> +	}
> +	ipc_write_sptr(addr);
> +	ipc_command(4 << 16 | IPC_CMD_INDIRECT_RD);
> +	err = busy_loop();
> +	*value = ipc_readl(0);
> +	mutex_unlock(&ipclock);
> +	return err;
> +}
> +EXPORT_SYMBOL(intel_scu_ipc_register_read);
> +
>
> ...
> 
> +int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data)
> +{
> +	u32 cmd = 0;
> +
> +	mutex_lock(&ipclock);
> +	cmd = (addr >> 24) & 0xFF;
> +	if (cmd == IPC_I2C_READ) {
> +		writel(addr, IPC_I2C_CNTRL_ADDR);
> +		mdelay(1);/*Write Not getting updated without delay*/

Odd commenting layout.

> +		*data = readl(I2C_DATA_ADDR);
> +	} else if (cmd == IPC_I2C_WRITE) {
> +		writel(addr, I2C_DATA_ADDR);
> +		mdelay(1);
> +		writel(addr, IPC_I2C_CNTRL_ADDR);
> +	} else {
> +		dev_err(&ipcdev.pdev->dev,
> +			"intel_scu_ipc: I2C INVALID_CMD = 0x%x\n", cmd);
> +
> +		mutex_unlock(&ipclock);
> +		return -1;
> +	}
> +	mutex_unlock(&ipclock);
> +	return 0;
> +}
>
> ...
> 
> +int intel_scu_ipc_fw_update(u8 *buffer, u32 length)
> +{
> +	void __iomem *fw_update_base;
> +	void __iomem *mailbox_base;
> +	int retry_cnt = 0;
> +
> +	struct fw_update_mailbox *mailbox = NULL;

Nuke random newline.

> +	mutex_lock(&ipclock);
> +	fw_update_base = ioremap_nocache(IPC_FW_LOAD_ADDR, (128*1024));
> +	if (fw_update_base == NULL) {
> +		mutex_unlock(&ipclock);
> +		return -ENOMEM;
> +	}
> +	mailbox_base = ioremap_nocache(IPC_FW_UPDATE_MBOX_ADDR,
> +					sizeof(struct fw_update_mailbox));
> +	if (mailbox_base == NULL) {
> +		iounmap(fw_update_base);
> +		mutex_unlock(&ipclock);
> +		return -ENOMEM;
> +	}
> +
> +	mailbox = (struct fw_update_mailbox *)mailbox_base;

I think mailbox_base could/should have had type `struct
fw_update_mailbox __iomem *'.  ioremap_nocache() should handle that
cleanly, and this cast goes away.

Otherwise, this cast is missing an __iomem.

And shouldn't `mailbox' have a __iomem too?  It's all a bit confuddled.

> +	ipc_command(IPC_CMD_FW_UPDATE_READY);
> +
> +	/* Intitialize mailbox */
> +	mailbox->status = 0;
> +	mailbox->scu_flag = 0;
> +	mailbox->driver_flag = 0;

So this is driectly writing into iomem.

> +	/* Driver copies the 2KB MIP header to SRAM at 0xFFFC0000*/
> +	memcpy_toio((u8 *)(fw_update_base), buffer, 0x800);
> +
> +	/* Driver sends "FW Update" IPC command (CMD_ID 0xFE; MSG_ID 0x02).
> +	* Upon receiving this command, SCU will write the 2K MIP header
> +	* from 0xFFFC0000 into NAND.
> +	* SCU will write a status code into the Mailbox, and then set scu_flag.
> +	*/

Do we need to do something here to ensure that all the above writes
have landed?

> +	ipc_command(IPC_CMD_FW_UPDATE_GO);
> +
> +	/*Driver stalls until scu_flag is set */

Odd comment layout.

> +	while (mailbox->scu_flag != 1) {
> +		rmb();
> +		mdelay(1);
> +	}
> +
> +	/* Driver checks Mailbox status.
> +	 * If the status is 'BADN', then abort (bad NAND).
> +	 * If the status is 'IPC_FW_TXLOW', then continue.
> +	 */
> +	while (mailbox->status != IPC_FW_TXLOW) {
> +		rmb();
> +		mdelay(10);
> +	}
> +	mdelay(10);

Would be nice to explain the mysterious mdelay()s to the reader. 
What's it for?  Why 10?

> +update_retry:
> +	if (retry_cnt > 5)
> +		goto update_end;
> +
> +	if (mailbox->status != IPC_FW_TXLOW)
> +		goto update_end;
> +	buffer = buffer+0x800;
> +	memcpy_toio((u8 *)(fw_update_base), buffer, 0x20000);
> +	mailbox->driver_flag = 0x1;
> +	while (mailbox->scu_flag == 1) {
> +		rmb();
> +		mdelay(1);
> +	}
> +
> +	/* check for 'BADN' */
> +	if (mailbox->status == IPC_FW_UPDATE_BADN)
> +		goto update_end;
> +
> +	while (mailbox->status != IPC_FW_TXHIGH) {
> +		rmb();
> +		mdelay(10);
> +	}
> +	mdelay(10);
> +
> +	if (mailbox->status != IPC_FW_TXHIGH)
> +		goto update_end;
> +	buffer = buffer+0x20000;
> +	memcpy_toio((u8 *)(fw_update_base), buffer, 0x20000);
> +	mailbox->driver_flag = 0;
> +	while (mailbox->scu_flag == 0) {

Is there anything to prevent the compiler (or hardware?) from caching
->scu_flag from the previous read?

> +		rmb();
> +		mdelay(1);
> +	}
> +
> +	/* check for 'BADN' */
> +	if (mailbox->status == IPC_FW_UPDATE_BADN)
> +		goto update_end;
> +
> +	if (mailbox->status == IPC_FW_TXLOW) {
> +		++retry_cnt;
> +		goto update_retry;
> +	}
> +
> +update_end:
> +	iounmap(fw_update_base);
> +	iounmap(mailbox_base);
> +	mutex_unlock(&ipclock);
> +	if (mailbox->status == IPC_FW_UPDATE_SUCCESS)

Confused.  `mailbox' equals `mailbox_base', and `mailbox_base' just got
iounmapped.  Shouldn't this oops?

> +		return 0;
> +	return -1;
> +}
> +EXPORT_SYMBOL(intel_scu_ipc_fw_update);
>
> ...
> 


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms
  2010-04-21 20:16 ` Andrew Morton
@ 2010-04-21 20:26   ` Alan Cox
  2010-04-22 13:16   ` Alan Cox
  1 sibling, 0 replies; 16+ messages in thread
From: Alan Cox @ 2010-04-21 20:26 UTC (permalink / raw)
  To: Andrew Morton; +Cc: Alan Cox, mingo, linux-kernel, Sreedhara DS

> It's be nice to have some words or a link describing what an IPC
> actually _is_.

"Inter-process" ("or" I guess) "communication"

I'll go through the other stuff. The driver is a few versions on from the
one you reviewed but quite a few of those problems are still present.

I'll then post up a new version to the platform driver list.

Alan

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms
  2010-04-21 20:16 ` Andrew Morton
  2010-04-21 20:26   ` Alan Cox
@ 2010-04-22 13:16   ` Alan Cox
  2010-04-22 13:41     ` Andrew Morton
  1 sibling, 1 reply; 16+ messages in thread
From: Alan Cox @ 2010-04-22 13:16 UTC (permalink / raw)
  To: Andrew Morton; +Cc: Alan Cox, mingo, linux-kernel, Sreedhara DS

> __packed, please.  (I've requested that this be added to checkpatch,
> but Mr Checkpatch is asleep).

Done


> > +static inline int busy_loop(void) /* Wait till scu status is busy */
> > +{
> > +	u32 status = 0;
> > +	u32 loop_count = 0;
> > +
> > +	status = __raw_readl(IPC_STATUS_REG);
> > +	while (status & 1) {
> > +		udelay(1); /* scu processing time is in few u secods */
> > +		status = __raw_readl(IPC_STATUS_REG);
> > +		loop_count++;
> > +		/* break if scu doesn't reset busy bit after huge retry */
> > +		if (loop_count > 100000)
> > +			return -ETIMEDOUT;

> This function has seven-odd callsites and is waaaaaaaay to fat and slow
> to be inlined.

Looking at the asm I'm not convinced.


> > +	if (id == IPC_CMD_PCNTRL_R) { /* Read rbuf */
> > +		/* Workaround: values are read as 0 without memcpy_fromio */
> > +		memcpy_fromio(cbuf, IPC_READ_BUFFER, 16);
> 
> Should we still be doing this if busy_loop() failed?
> 
> (Lots of dittoes on this question)

Does no harm

> > +}
> 
> I wonder if this function would look better if cbuf had type u16[],

No - we do byte aligned access to it. and cbuf[offset + 1.5] is sadly not
supported by C (although I suspect you can make it work in C++ ;))

> > +	mutex_lock(&ipclock);
> > +	if (ipcdev.pdev == NULL) {
> 
> This check happens a lot.  Can it really happen?

Yes.

> I think mailbox_base could/should have had type `struct
> fw_update_mailbox __iomem *'.  ioremap_nocache() should handle that
> cleanly, and this cast goes away.

Definitely - and the rest them cleans up by magic

Alan

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms
  2010-04-22 13:16   ` Alan Cox
@ 2010-04-22 13:41     ` Andrew Morton
  0 siblings, 0 replies; 16+ messages in thread
From: Andrew Morton @ 2010-04-22 13:41 UTC (permalink / raw)
  To: Alan Cox; +Cc: Alan Cox, mingo, linux-kernel, Sreedhara DS

On Thu, 22 Apr 2010 14:16:11 +0100 Alan Cox <alan@lxorguk.ukuu.org.uk> wrote:

> > > +static inline int busy_loop(void) /* Wait till scu status is busy */
> > > +{
> > > +	u32 status = 0;
> > > +	u32 loop_count = 0;
> > > +
> > > +	status = __raw_readl(IPC_STATUS_REG);
> > > +	while (status & 1) {
> > > +		udelay(1); /* scu processing time is in few u secods */
> > > +		status = __raw_readl(IPC_STATUS_REG);
> > > +		loop_count++;
> > > +		/* break if scu doesn't reset busy bit after huge retry */
> > > +		if (loop_count > 100000)
> > > +			return -ETIMEDOUT;
> 
> > This function has seven-odd callsites and is waaaaaaaay to fat and slow
> > to be inlined.
> 
> Looking at the asm I'm not convinced.

I tried two version of gcc and both of them just refuse to inline
the function anyway.

If the function is changed to __always_inline, gcc will then inline it.

                  text    data     bss     dec     hex filename
inline            6182     960    1680    8822    2276 arch/x86/kernel/intel_scu_ipc.o
__always_inline   6582     928    1760    9270    2436 arch/x86/kernel/intel_scu_ipc.o

I agree with gcc ;)

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms
  2010-04-26 17:13               ` Alan Cox
@ 2010-05-07 18:25                 ` Matthew Garrett
  0 siblings, 0 replies; 16+ messages in thread
From: Matthew Garrett @ 2010-05-07 18:25 UTC (permalink / raw)
  To: Alan Cox; +Cc: platform-driver-x86, akpm, sreedhara.ds, mingo

Thanks, applied for -next.

-- 
Matthew Garrett | mjg59@srcf.ucam.org

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms
  2010-04-26 17:06             ` Matthew Garrett
@ 2010-04-26 17:13               ` Alan Cox
  2010-05-07 18:25                 ` Matthew Garrett
  0 siblings, 1 reply; 16+ messages in thread
From: Alan Cox @ 2010-04-26 17:13 UTC (permalink / raw)
  To: Matthew Garrett; +Cc: platform-driver-x86, akpm, sreedhara.ds, mingo

On Mon, 26 Apr 2010 18:06:16 +0100
Matthew Garrett <mjg59@srcf.ucam.org> wrote:

> On Mon, Apr 26, 2010 at 05:28:00PM +0100, Alan Cox wrote:
> > > I'm really not terribly comfortable merging enabling code for an 
> > > architecture if there's no clear plan for merging critical parts
> > > of that architecture.
> > 
> > I'm not interested in playing graphics driver politics. If I could
> > do something short term about the graphics situation I would.
> > Furthermore there isn't anything I can do about it (even if it does
> > involve its own *clean* mode setting to keep KeithP from exploding)
> > until the core merges are done.
> 
> It's been pointed out to me that there are non-graphical targets for 
> this platform as well, so in that case there's no real argument on my 
> side. I'll queue this for .35.

Here's a better one. This has the modify API fix pulled in and the
various command locks fixed by the simple expedient of passing copies
of the data around. As its at most about 5 dwords it's not going to
kill anyone and it cleans up a fair bit

Alan
--

commit e0d3f5da70f26f39853a000a7cfc9575047c63d7
Author: Sreedhara DS <sreedhara.ds@intel.com>
Date:   Mon Apr 26 17:05:45 2010 +0100

    IPC driver for Intel Mobile Internet Device (MID) platforms
    
    The IPC (inter processor communications) is used to provide the communications
    between kernel and system control units on some embedded Intel x86 platforms.

    (Various bits of clean up and restructuring by Alan Cox)
    
    Signed-off-by: Sreedhara DS <sreedhara.ds@intel.com>
    Signed-off-by: Alan Cox <alan@linux.intel.com>

diff --git a/arch/x86/include/asm/intel_scu_ipc.h b/arch/x86/include/asm/intel_scu_ipc.h
new file mode 100644
index 0000000..6c6170b
--- /dev/null
+++ b/arch/x86/include/asm/intel_scu_ipc.h
@@ -0,0 +1,55 @@
+#ifndef _ASM_X86_INTEL_SCU_IPC_H_
+#define  _ASM_X86_INTEL_SCU_IPC_H_
+
+/* Read single register */
+int intel_scu_ipc_ioread8(u16 addr, u8 *data);
+
+/* Read two sequential registers */
+int intel_scu_ipc_ioread16(u16 addr, u16 *data);
+
+/* Read four sequential registers */
+int intel_scu_ipc_ioread32(u16 addr, u32 *data);
+
+/* Read a vector */
+int intel_scu_ipc_readv(u16 *addr, u8 *data, int len);
+
+/* Write single register */
+int intel_scu_ipc_iowrite8(u16 addr, u8 data);
+
+/* Write two sequential registers */
+int intel_scu_ipc_iowrite16(u16 addr, u16 data);
+
+/* Write four sequential registers */
+int intel_scu_ipc_iowrite32(u16 addr, u32 data);
+
+/* Write a vector */
+int intel_scu_ipc_writev(u16 *addr, u8 *data, int len);
+
+/* Update single register based on the mask */
+int intel_scu_ipc_update_register(u16 addr, u8 data, u8 mask);
+
+/*
+ * Indirect register read
+ * Can be used when SCCB(System Controller Configuration Block) register
+ * HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ */
+int intel_scu_ipc_register_read(u32 addr, u32 *data);
+
+/*
+ * Indirect register write
+ * Can be used when SCCB(System Controller Configuration Block) register
+ * HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ */
+int intel_scu_ipc_register_write(u32 addr, u32 data);
+
+/* Issue commands to the SCU with or without data */
+int intel_scu_ipc_simple_command(int cmd, int sub);
+int intel_scu_ipc_command(int cmd, int sub, u32 *in, int inlen,
+							u32 * out, int outlen);
+/* I2C control api */
+int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data);
+
+/* Update FW version */
+int intel_scu_ipc_fw_update(u8 *buffer, u32 length);
+
+#endif
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 7bec458..09060a0 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -526,4 +526,12 @@ config ACPI_CMPC
 	  keys as input device, backlight device, tablet and accelerometer
 	  devices.
 
+config INTEL_SCU_IPC
+	bool "Intel SCU IPC Support"
+	depends on X86_MRST
+	default y
+	---help---
+	  IPC is used to bridge the communications between kernel and SCU on
+	  some embedded Intel x86 platforms.
+
 endif # X86_PLATFORM_DEVICES
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index a906490..8770bfe 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -25,3 +25,4 @@ obj-$(CONFIG_ACPI_ASUS)		+= asus_acpi.o
 obj-$(CONFIG_TOPSTAR_LAPTOP)	+= topstar-laptop.o
 obj-$(CONFIG_ACPI_TOSHIBA)	+= toshiba_acpi.o
 obj-$(CONFIG_TOSHIBA_BT_RFKILL)	+= toshiba_bluetooth.o
+obj-$(CONFIG_INTEL_SCU_IPC)	+= intel_scu_ipc.o
diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c
new file mode 100644
index 0000000..2c49ad2
--- /dev/null
+++ b/drivers/platform/x86/intel_scu_ipc.c
@@ -0,0 +1,829 @@
+/*
+ * intel_scu_ipc.c: Driver for the Intel SCU IPC mechanism
+ *
+ * (C) Copyright 2008-2010 Intel Corporation
+ * Author: Sreedhara DS (sreedhara.ds@intel.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2
+ * of the License.
+ *
+ * SCU runing in ARC processor communicates with other entity running in IA
+ * core through IPC mechanism which in turn messaging between IA core ad SCU.
+ * SCU has two IPC mechanism IPC-1 and IPC-2. IPC-1 is used between IA32 and
+ * SCU where IPC-2 is used between P-Unit and SCU. This driver delas with
+ * IPC-1 Driver provides an API for power control unit registers (e.g. MSIC)
+ * along with other APIs.
+ */
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/sysdev.h>
+#include <linux/pm.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <asm/setup.h>
+#include <asm/intel_scu_ipc.h>
+
+/* IPC defines the following message types */
+#define IPCMSG_WATCHDOG_TIMER 0xF8 /* Set Kernel Watchdog Threshold */
+#define IPCMSG_BATTERY        0xEF /* Coulomb Counter Accumulator */
+#define IPCMSG_FW_UPDATE      0xFE /* Firmware update */
+#define IPCMSG_PCNTRL         0xFF /* Power controller unit read/write */
+#define IPCMSG_FW_REVISION    0xF4 /* Get firmware revision */
+
+/* Command id associated with message IPCMSG_PCNTRL */
+#define IPC_CMD_PCNTRL_W      0 /* Register write */
+#define IPC_CMD_PCNTRL_R      1 /* Register read */
+#define IPC_CMD_PCNTRL_M      2 /* Register read-modify-write */
+
+/* Miscelaneous Command ids */
+#define IPC_CMD_INDIRECT_RD   2 /* 32bit indirect read */
+#define IPC_CMD_INDIRECT_WR   5 /* 32bit indirect write */
+
+/*
+ * IPC register summary
+ *
+ * IPC register blocks are memory mapped at fixed address of 0xFF11C000
+ * To read or write information to the SCU, driver writes to IPC-1 memory
+ * mapped registers (base address 0xFF11C000). The following is the IPC
+ * mechanism
+ *
+ * 1. IA core cDMI interface claims this transaction and converts it to a
+ *    Transaction Layer Packet (TLP) message which is sent across the cDMI.
+ *
+ * 2. South Complex cDMI block receives this message and writes it to
+ *    the IPC-1 register block, causing an interrupt to the SCU
+ *
+ * 3. SCU firmware decodes this interrupt and IPC message and the appropriate
+ *    message handler is called within firmware.
+ */
+
+#define IPC_BASE_ADDR     0xFF11C000	/* IPC1 base register address */
+#define IPC_MAX_ADDR      0x100		/* Maximum IPC regisers */
+#define IPC_WWBUF_SIZE    16		/* IPC Write buffer Size */
+#define IPC_RWBUF_SIZE    16		/* IPC Read buffer Size */
+#define IPC_I2C_BASE      0xFF12B000	/* I2C control register base address */
+#define IPC_I2C_MAX_ADDR  0x10		/* Maximum I2C regisers */
+
+static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id);
+static void ipc_remove(struct pci_dev *pdev);
+
+struct intel_scu_ipc_dev {
+	struct pci_dev *pdev;
+	void __iomem *ipc_base;
+	void __iomem *i2c_base;
+};
+
+static struct intel_scu_ipc_dev  ipcdev; /* Only one for now */
+
+static int platform = 1;
+module_param(platform, int, 0);
+MODULE_PARM_DESC(platform, "1 for moorestown platform");
+
+
+
+
+/*
+ * IPC Read Buffer (Read Only):
+ * 16 byte buffer for receiving data from SCU, if IPC command
+ * processing results in response data
+ */
+#define IPC_READ_BUFFER		0x90
+
+#define IPC_I2C_CNTRL_ADDR	0
+#define I2C_DATA_ADDR		0x04
+
+static DEFINE_MUTEX(ipclock); /* lock used to prevent multiple call to SCU */
+
+/*
+ * Command Register (Write Only):
+ * A write to this register results in an interrupt to the SCU core processor
+ * Format:
+ * |rfu2(8) | size(8) | command id(4) | rfu1(3) | ioc(1) | command(8)|
+ */
+static inline void ipc_command(u32 cmd) /* Send ipc command */
+{
+	writel(cmd, ipcdev.ipc_base);
+}
+
+/*
+ * IPC Write Buffer (Write Only):
+ * 16-byte buffer for sending data associated with IPC command to
+ * SCU. Size of the data is specified in the IPC_COMMAND_REG register
+ */
+static inline void ipc_data_writel(u32 data, u32 offset) /* Write ipc data */
+{
+	writel(data, ipcdev.ipc_base + 0x80 + offset);
+}
+
+/*
+ * IPC destination Pointer (Write Only):
+ * Use content as pointer for destination write
+ */
+static inline void ipc_write_dptr(u32 data) /* Write dptr data */
+{
+	writel(data, ipcdev.ipc_base + 0x0C);
+}
+
+/*
+ * IPC Source Pointer (Write Only):
+ * Use content as pointer for read location
+*/
+static inline void ipc_write_sptr(u32 data) /* Write dptr data */
+{
+	writel(data, ipcdev.ipc_base + 0x08);
+}
+
+/*
+ * Status Register (Read Only):
+ * Driver will read this register to get the ready/busy status of the IPC
+ * block and error status of the IPC command that was just processed by SCU
+ * Format:
+ * |rfu3(8)|error code(8)|initiator id(8)|cmd id(4)|rfu1(2)|error(1)|busy(1)|
+ */
+
+static inline u8 ipc_read_status(void)
+{
+	return __raw_readl(ipcdev.ipc_base + 0x04);
+}
+
+static inline u8 ipc_data_readb(u32 offset) /* Read ipc byte data */
+{
+	return readb(ipcdev.ipc_base + IPC_READ_BUFFER + offset);
+}
+
+static inline u8 ipc_data_readl(u32 offset) /* Read ipc u32 data */
+{
+	return readl(ipcdev.ipc_base + IPC_READ_BUFFER + offset);
+}
+
+static inline int busy_loop(void) /* Wait till scu status is busy */
+{
+	u32 status = 0;
+	u32 loop_count = 0;
+
+	status = ipc_read_status();
+	while (status & 1) {
+		udelay(1); /* scu processing time is in few u secods */
+		status = ipc_read_status();
+		loop_count++;
+		/* break if scu doesn't reset busy bit after huge retry */
+		if (loop_count > 100000) {
+			dev_err(&ipcdev.pdev->dev, "IPC timed out");
+			return -ETIMEDOUT;
+		}
+	}
+	return (status >> 1) & 1;
+}
+
+/* Read/Write power control(PMIC in Langwell, MSIC in PenWell) registers */
+static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id)
+{
+	int nc;
+	u32 offset = 0;
+	u32 err = 0;
+	u8 cbuf[IPC_WWBUF_SIZE] = { '\0' };
+	u32 *wbuf = (u32 *)&cbuf;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+
+	if (platform == 1) {
+		/* Entry is 4 bytes for read/write, 5 bytes for read modify */
+		for (nc = 0; nc < count; nc++) {
+			cbuf[offset] = addr[nc];
+			cbuf[offset + 1] = addr[nc] >> 8;
+			if (id != IPC_CMD_PCNTRL_R)
+				cbuf[offset + 2] = data[nc];
+			if (id == IPC_CMD_PCNTRL_M) {
+				cbuf[offset + 3] = data[nc + 1];
+				offset += 1;
+			}
+			offset += 3;
+		}
+		for (nc = 0, offset = 0; nc < count; nc++, offset += 4)
+			ipc_data_writel(wbuf[nc], offset); /* Write wbuff */
+
+	} else {
+		for (nc = 0, offset = 0; nc < count; nc++, offset += 2)
+			ipc_data_writel(addr[nc], offset); /* Write addresses */
+		if (id != IPC_CMD_PCNTRL_R) {
+			for (nc = 0; nc < count; nc++, offset++)
+				ipc_data_writel(data[nc], offset); /* Write data */
+			if (id == IPC_CMD_PCNTRL_M)
+				ipc_data_writel(data[nc + 1], offset); /* Mask value*/
+		}
+	}
+
+	if (id != IPC_CMD_PCNTRL_M)
+		ipc_command((count * 3) << 16 |  id << 12 | 0 << 8 | op);
+	else
+		ipc_command((count * 4) << 16 |  id << 12 | 0 << 8 | op);
+
+	err = busy_loop();
+
+	if (id == IPC_CMD_PCNTRL_R) { /* Read rbuf */
+		/* Workaround: values are read as 0 without memcpy_fromio */
+		memcpy_fromio(cbuf, ipcdev.ipc_base + IPC_READ_BUFFER, 16);
+		if (platform == 1) {
+			for (nc = 0, offset = 2; nc < count; nc++, offset += 3)
+				data[nc] = ipc_data_readb(offset);
+		} else {
+			for (nc = 0; nc < count; nc++)
+				data[nc] = ipc_data_readb(nc);
+		}
+	}
+	mutex_unlock(&ipclock);
+	return err;
+}
+
+/**
+ *	intel_scu_ipc_ioread8		-	read a word via the SCU
+ *	@addr: register on SCU
+ *	@data: return pointer for read byte
+ *
+ *	Read a single register. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_ioread8(u16 addr, u8 *data)
+{
+	return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread8);
+
+/**
+ *	intel_scu_ipc_ioread16		-	read a word via the SCU
+ *	@addr: register on SCU
+ *	@data: return pointer for read word
+ *
+ *	Read a register pair. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_ioread16(u16 addr, u16 *data)
+{
+	u16 x[2] = {addr, addr + 1 };
+	return pwr_reg_rdwr(x, (u8 *)data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread16);
+
+/**
+ *	intel_scu_ipc_ioread32		-	read a dword via the SCU
+ *	@addr: register on SCU
+ *	@data: return pointer for read dword
+ *
+ *	Read four registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_ioread32(u16 addr, u32 *data)
+{
+	u16 x[4] = {addr, addr + 1, addr + 2, addr + 3};
+	return pwr_reg_rdwr(x, (u8 *)data, 4, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread32);
+
+/**
+ *	intel_scu_ipc_iowrite8		-	write a byte via the SCU
+ *	@addr: register on SCU
+ *	@data: byte to write
+ *
+ *	Write a single register. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_iowrite8(u16 addr, u8 data)
+{
+	return pwr_reg_rdwr(&addr, &data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite8);
+
+/**
+ *	intel_scu_ipc_iowrite16		-	write a word via the SCU
+ *	@addr: register on SCU
+ *	@data: word to write
+ *
+ *	Write two registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_iowrite16(u16 addr, u16 data)
+{
+	u16 x[2] = {addr, addr + 1 };
+	return pwr_reg_rdwr(x, (u8 *)&data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite16);
+
+/**
+ *	intel_scu_ipc_iowrite32		-	write a dword via the SCU
+ *	@addr: register on SCU
+ *	@data: dword to write
+ *
+ *	Write four registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_iowrite32(u16 addr, u32 data)
+{
+	u16 x[4] = {addr, addr + 1, addr + 2, addr + 3};
+	return pwr_reg_rdwr(x, (u8 *)&data, 4, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite32);
+
+/**
+ *	intel_scu_ipc_readvv		-	read a set of registers
+ *	@addr: register list
+ *	@data: bytes to return
+ *	@len: length of array
+ *
+ *	Read registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	The largest array length permitted by the hardware is 5 items.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_readv(u16 *addr, u8 *data, int len)
+{
+	return pwr_reg_rdwr(addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_readv);
+
+/**
+ *	intel_scu_ipc_writev		-	write a set of registers
+ *	@addr: register list
+ *	@data: bytes to write
+ *	@len: length of array
+ *
+ *	Write registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	The largest array length permitted by the hardware is 5 items.
+ *
+ *	This function may sleep.
+ *
+ */
+int intel_scu_ipc_writev(u16 *addr, u8 *data, int len)
+{
+	return pwr_reg_rdwr(addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_writev);
+
+
+/**
+ *	intel_scu_ipc_update_register	-	r/m/w a register
+ *	@addr: register address
+ *	@bits: bits to update
+ *	@mask: mask of bits to update
+ *
+ *	Read-modify-write power control unit register. The first data argument
+ *	must be register value and second is mask value
+ *	mask is a bitmap that indicates which bits to update.
+ *	0 = masked. Don't modify this bit, 1 = modify this bit.
+ *	returns 0 on success or an error code.
+ *
+ *	This function may sleep. Locking between SCU accesses is handled
+ *	for the caller.
+ */
+int intel_scu_ipc_update_register(u16 addr, u8 bits, u8 mask)
+{
+	u8 data[2] = { bits, mask };
+	return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_M);
+}
+EXPORT_SYMBOL(intel_scu_ipc_update_register);
+
+/**
+ *	intel_scu_ipc_register_read	-	32bit indirect read
+ *	@addr: register address
+ *	@value: 32bit value return
+ *
+ *	Performs IA 32 bit indirect read, returns 0 on success, or an
+ *	error code.
+ *
+ *	Can be used when SCCB(System Controller Configuration Block) register
+ *	HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_register_read(u32 addr, u32 *value)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_write_sptr(addr);
+	ipc_command(4 << 16 | IPC_CMD_INDIRECT_RD);
+	err = busy_loop();
+	*value = ipc_data_readl(0);
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_register_read);
+
+/**
+ *	intel_scu_ipc_register_write	-	32bit indirect write
+ *	@addr: register address
+ *	@value: 32bit value to write
+ *
+ *	Performs IA 32 bit indirect write, returns 0 on success, or an
+ *	error code.
+ *
+ *	Can be used when SCCB(System Controller Configuration Block) register
+ *	HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_register_write(u32 addr, u32 value)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_write_dptr(addr);
+	ipc_data_writel(value, 0);
+	ipc_command(4 << 16 | IPC_CMD_INDIRECT_WR);
+	err = busy_loop();
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_register_write);
+
+/**
+ *	intel_scu_ipc_simple_command	-	send a simple command
+ *	@cmd: command
+ *	@sub: sub type
+ *
+ *	Issue a simple command to the SCU. Do not use this interface if
+ *	you must then access data as any data values may be overwritten
+ *	by another SCU access by the time this function returns.
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_simple_command(int cmd, int sub)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_command(cmd << 12 | sub);
+	err = busy_loop();
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_simple_command);
+
+/**
+ *	intel_scu_ipc_command	-	command with data
+ *	@cmd: command
+ *	@sub: sub type
+ *	@in: input data
+ *	@inlen: input length
+ *	@out: output data
+ *	@outlein: output length
+ *
+ *	Issue a command to the SCU which involves data transfers. Do the
+ *	data copies under the lock but leave it for the caller to interpret
+ */
+
+int intel_scu_ipc_command(int cmd, int sub, u32 *in ,int inlen,
+							u32 * out, int outlen)
+{
+	u32 err = 0;
+	int i = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+
+	for (i = 0; i < inlen; i++)
+		ipc_data_writel(*in++, 4 * i);
+
+	ipc_command(cmd << 12 | sub);
+	err = busy_loop();
+
+	for (i = 0; i < outlen; i++)
+		*out++ = ipc_data_readl(4 * i);
+
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_command);
+
+/*I2C commands */
+#define IPC_I2C_WRITE 1 /* I2C Write command */
+#define IPC_I2C_READ  2 /* I2C Read command */
+
+/**
+ *	intel_scu_ipc_i2c_cntrl		-	I2C read/write operations
+ *	@addr: I2C address + command bits
+ *	@data: data to read/write
+ *
+ *	Perform an an I2C read/write operation via the SCU. All locking is
+ *	handled for the caller. This function may sleep.
+ *
+ *	Returns an error code or 0 on success.
+ *
+ *	This has to be in the IPC driver for the locking.
+ */
+int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data)
+{
+	u32 cmd = 0;
+
+	mutex_lock(&ipclock);
+	cmd = (addr >> 24) & 0xFF;
+	if (cmd == IPC_I2C_READ) {
+		writel(addr, ipcdev.i2c_base + IPC_I2C_CNTRL_ADDR);
+		/* Write not getting updated without delay */
+		mdelay(1);
+		*data = readl(ipcdev.i2c_base + I2C_DATA_ADDR);
+	} else if (cmd == IPC_I2C_WRITE) {
+		writel(addr, ipcdev.i2c_base + I2C_DATA_ADDR);
+		mdelay(1);
+		writel(addr, ipcdev.i2c_base + IPC_I2C_CNTRL_ADDR);
+	} else {
+		dev_err(&ipcdev.pdev->dev,
+			"intel_scu_ipc: I2C INVALID_CMD = 0x%x\n", cmd);
+
+		mutex_unlock(&ipclock);
+		return -1;
+	}
+	mutex_unlock(&ipclock);
+	return 0;
+}
+EXPORT_SYMBOL(intel_scu_ipc_i2c_cntrl);
+
+#define IPC_FW_LOAD_ADDR 0xFFFC0000 /* Storage location for FW image */
+#define IPC_FW_UPDATE_MBOX_ADDR 0xFFFFDFF4 /* Mailbox between ipc and scu */
+#define IPC_MAX_FW_SIZE 262144 /* 256K storage size for loading the FW image */
+#define IPC_FW_MIP_HEADER_SIZE 2048 /* Firmware MIP header size */
+/* IPC inform SCU to get ready for update process */
+#define IPC_CMD_FW_UPDATE_READY  0x10FE
+/* IPC inform SCU to go for update process */
+#define IPC_CMD_FW_UPDATE_GO     0x20FE
+/* Status code for fw update */
+#define IPC_FW_UPDATE_SUCCESS	0x444f4e45 /* Status code 'DONE' */
+#define IPC_FW_UPDATE_BADN	0x4241444E /* Status code 'BADN' */
+#define IPC_FW_TXHIGH		0x54784849 /* Status code 'IPC_FW_TXHIGH' */
+#define IPC_FW_TXLOW		0x54784c4f /* Status code 'IPC_FW_TXLOW' */
+
+struct fw_update_mailbox {
+	u32    status;
+	u32    scu_flag;
+	u32    driver_flag;
+};
+
+
+/**
+ *	intel_scu_ipc_fw_update	-	 Firmware update utility
+ *	@buffer: firmware buffer
+ *	@length: size of firmware buffer
+ *
+ *	This function provides an interface to load the firmware into
+ *	the SCU. Returns 0 on success or -1 on failure
+ */
+int intel_scu_ipc_fw_update(u8 *buffer, u32 length)
+{
+	void __iomem *fw_update_base;
+	struct fw_update_mailbox __iomem *mailbox = NULL;
+	int retry_cnt = 0;
+	u32 status;
+
+	mutex_lock(&ipclock);
+	fw_update_base = ioremap_nocache(IPC_FW_LOAD_ADDR, (128*1024));
+	if (fw_update_base == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENOMEM;
+	}
+	mailbox = ioremap_nocache(IPC_FW_UPDATE_MBOX_ADDR,
+					sizeof(struct fw_update_mailbox));
+	if (mailbox == NULL) {
+		iounmap(fw_update_base);
+		mutex_unlock(&ipclock);
+		return -ENOMEM;
+	}
+
+	ipc_command(IPC_CMD_FW_UPDATE_READY);
+
+	/* Intitialize mailbox */
+	writel(0, &mailbox->status);
+	writel(0, &mailbox->scu_flag);
+	writel(0, &mailbox->driver_flag);
+
+	/* Driver copies the 2KB MIP header to SRAM at 0xFFFC0000*/
+	memcpy_toio(fw_update_base, buffer, 0x800);
+
+	/* Driver sends "FW Update" IPC command (CMD_ID 0xFE; MSG_ID 0x02).
+	* Upon receiving this command, SCU will write the 2K MIP header
+	* from 0xFFFC0000 into NAND.
+	* SCU will write a status code into the Mailbox, and then set scu_flag.
+	*/
+
+	ipc_command(IPC_CMD_FW_UPDATE_GO);
+
+	/*Driver stalls until scu_flag is set */
+	while (readl(&mailbox->scu_flag) != 1) {
+		rmb();
+		mdelay(1);
+	}
+
+	/* Driver checks Mailbox status.
+	 * If the status is 'BADN', then abort (bad NAND).
+	 * If the status is 'IPC_FW_TXLOW', then continue.
+	 */
+	while (readl(&mailbox->status) != IPC_FW_TXLOW) {
+		rmb();
+		mdelay(10);
+	}
+	mdelay(10);
+
+update_retry:
+	if (retry_cnt > 5)
+		goto update_end;
+
+	if (readl(&mailbox->status) != IPC_FW_TXLOW)
+		goto update_end;
+	buffer = buffer + 0x800;
+	memcpy_toio(fw_update_base, buffer, 0x20000);
+	writel(1, &mailbox->driver_flag);
+	while (readl(&mailbox->scu_flag) == 1) {
+		rmb();
+		mdelay(1);
+	}
+
+	/* check for 'BADN' */
+	if (readl(&mailbox->status) == IPC_FW_UPDATE_BADN)
+		goto update_end;
+
+	while (readl(&mailbox->status) != IPC_FW_TXHIGH) {
+		rmb();
+		mdelay(10);
+	}
+	mdelay(10);
+
+	if (readl(&mailbox->status) != IPC_FW_TXHIGH)
+		goto update_end;
+
+	buffer = buffer + 0x20000;
+	memcpy_toio(fw_update_base, buffer, 0x20000);
+	writel(0, &mailbox->driver_flag);
+
+	while (mailbox->scu_flag == 0) {
+		rmb();
+		mdelay(1);
+	}
+
+	/* check for 'BADN' */
+	if (readl(&mailbox->status) == IPC_FW_UPDATE_BADN)
+		goto update_end;
+
+	if (readl(&mailbox->status) == IPC_FW_TXLOW) {
+		++retry_cnt;
+		goto update_retry;
+	}
+
+update_end:
+	status = readl(&mailbox->status);
+
+	iounmap(fw_update_base);
+	iounmap(mailbox);
+	mutex_unlock(&ipclock);
+
+	if (status == IPC_FW_UPDATE_SUCCESS)
+		return 0;
+	return -1;
+}
+EXPORT_SYMBOL(intel_scu_ipc_fw_update);
+
+/*
+ * Interrupt handler gets called when ioc bit of IPC_COMMAND_REG set to 1
+ * When ioc bit is set to 1, caller api must wait for interrupt handler called
+ * which in turn unlocks the caller api. Currently this is not used
+ *
+ * This is edge triggered so we need take no action to clear anything
+ */
+static irqreturn_t ioc(int irq, void *dev_id)
+{
+	return IRQ_HANDLED;
+}
+
+/**
+ *	ipc_probe	-	probe an Intel SCU IPC
+ *	@dev: the PCI device matching
+ *	@id: entry in the match table
+ *
+ *	Enable and install an intel SCU IPC. This appears in the PCI space
+ *	but uses some hard coded addresses as well.
+ */
+static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+	int err;
+	resource_size_t pci_resource;
+
+	if (ipcdev.pdev)		/* We support only one SCU */
+		return -EBUSY;
+
+	ipcdev.pdev = pci_dev_get(dev);
+
+	err = pci_enable_device(dev);
+	if (err)
+		return err;
+
+	err = pci_request_regions(dev, "intel_scu_ipc");
+	if (err)
+		return err;
+
+	pci_resource = pci_resource_start(dev, 0);
+	if (!pci_resource)
+		return -ENOMEM;
+
+	if (request_irq(dev->irq, ioc, 0, "intel_scu_ipc", &ipcdev))
+		return -EBUSY;
+
+	ipcdev.ipc_base = ioremap_nocache(IPC_BASE_ADDR, IPC_MAX_ADDR);
+	if (!ipcdev.ipc_base)
+		return -ENOMEM;
+
+	ipcdev.i2c_base = ioremap_nocache(IPC_I2C_BASE, IPC_I2C_MAX_ADDR);
+	if (!ipcdev.i2c_base) {
+		iounmap(ipcdev.ipc_base);
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+/**
+ *	ipc_remove	-	remove a bound IPC device
+ *	@pdev: PCI device
+ *
+ *	In practice the SCU is not removable but this function is also
+ *	called for each device on a module unload or cleanup which is the
+ *	path that will get used.
+ *
+ *	Free up the mappings and release the PCI resources
+ */
+static void ipc_remove(struct pci_dev *pdev)
+{
+	free_irq(pdev->irq, &ipcdev);
+	pci_release_regions(pdev);
+	pci_dev_put(ipcdev.pdev);
+	iounmap(ipcdev.ipc_base);
+	iounmap(ipcdev.i2c_base);
+	ipcdev.pdev = NULL;
+}
+
+static const struct pci_device_id pci_ids[] = {
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x080e)},
+	{ 0,}
+};
+MODULE_DEVICE_TABLE(pci, pci_ids);
+
+static struct pci_driver ipc_driver = {
+	.name = "intel_scu_ipc",
+	.id_table = pci_ids,
+	.probe = ipc_probe,
+	.remove = ipc_remove,
+};
+
+
+static int __init intel_scu_ipc_init(void)
+{
+	return  pci_register_driver(&ipc_driver);
+}
+
+static void __exit intel_scu_ipc_exit(void)
+{
+	pci_unregister_driver(&ipc_driver);
+}
+
+MODULE_AUTHOR("Sreedhara DS <sreedhara.ds@intel.com>");
+MODULE_DESCRIPTION("Intel SCU IPC driver");
+MODULE_LICENSE("GPL");
+
+module_init(intel_scu_ipc_init);
+module_exit(intel_scu_ipc_exit);

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms
  2010-04-26 16:28           ` Alan Cox
@ 2010-04-26 17:06             ` Matthew Garrett
  2010-04-26 17:13               ` Alan Cox
  0 siblings, 1 reply; 16+ messages in thread
From: Matthew Garrett @ 2010-04-26 17:06 UTC (permalink / raw)
  To: Alan Cox; +Cc: platform-driver-x86, akpm, sreedhara.ds, mingo

On Mon, Apr 26, 2010 at 05:28:00PM +0100, Alan Cox wrote:
> > I'm really not terribly comfortable merging enabling code for an 
> > architecture if there's no clear plan for merging critical parts of
> > that architecture.
> 
> I'm not interested in playing graphics driver politics. If I could do
> something short term about the graphics situation I would. Furthermore
> there isn't anything I can do about it (even if it does involve its
> own *clean* mode setting to keep KeithP from exploding) until the
> core merges are done.

It's been pointed out to me that there are non-graphical targets for 
this platform as well, so in that case there's no real argument on my 
side. I'll queue this for .35.

-- 
Matthew Garrett | mjg59@srcf.ucam.org

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms
  2010-04-26 16:22         ` Matthew Garrett
@ 2010-04-26 16:28           ` Alan Cox
  2010-04-26 17:06             ` Matthew Garrett
  0 siblings, 1 reply; 16+ messages in thread
From: Alan Cox @ 2010-04-26 16:28 UTC (permalink / raw)
  To: Matthew Garrett; +Cc: platform-driver-x86, akpm, sreedhara.ds, mingo

> I'm really not terribly comfortable merging enabling code for an 
> architecture if there's no clear plan for merging critical parts of
> that architecture.

I'm not interested in playing graphics driver politics. If I could do
something short term about the graphics situation I would. Furthermore
there isn't anything I can do about it (even if it does involve its
own *clean* mode setting to keep KeithP from exploding) until the
core merges are done.

As has already been pointed out large parts of the enabling code are
already merged into other bits of the kernel.

Alan

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms
  2010-04-26 15:39       ` Alan Cox
@ 2010-04-26 16:22         ` Matthew Garrett
  2010-04-26 16:28           ` Alan Cox
  0 siblings, 1 reply; 16+ messages in thread
From: Matthew Garrett @ 2010-04-26 16:22 UTC (permalink / raw)
  To: Alan Cox; +Cc: platform-driver-x86, akpm, sreedhara.ds, mingo

On Mon, Apr 26, 2010 at 04:39:58PM +0100, Alan Cox wrote:
> > The case I'm worried about is where the graphics driver never gets
> > into a mergable state and we end up with a pile of mainline code that
> > can't sanely run on the hardware. As long as the plan is to get it to
> > avoid duplicating the entire Intel modesetting code and including its
> > own TTM then that seems fair enough, but Intel's track record on this
> > side of things hasn't been great.
> 
> I don't track the public DRM lists but the last I saw on that on the
> archives was Keith Packard blocking patches to enable such a merge.
> You'd have to ask him about it. On the acceleration side mrst/psb
> graphics are not i9xx style.

I'm really not terribly comfortable merging enabling code for an 
architecture if there's no clear plan for merging critical parts of that 
architecture.

-- 
Matthew Garrett | mjg59@srcf.ucam.org

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms
  2010-04-26 14:55   ` Alan Cox
@ 2010-04-26 15:43     ` Matthew Garrett
  2010-04-26 15:39       ` Alan Cox
  0 siblings, 1 reply; 16+ messages in thread
From: Matthew Garrett @ 2010-04-26 15:43 UTC (permalink / raw)
  To: Alan Cox; +Cc: platform-driver-x86, akpm, sreedhara.ds, mingo

On Mon, Apr 26, 2010 at 03:55:34PM +0100, Alan Cox wrote:
> > related query in passing - the graphics driver for Moorestown hasn't 
> > been submitted yet, and the version in Meego doesn't look like it's 
> > especially upstreamable. I seem to recall that Moorestown was going
> > to lack port IO, so does it have VGA compatibility? If not, is there
> > any use in mainline Moorestown support until support for the graphics
> > is submitted?
> 
> Graphics is very very late on the merge list for obvious reasons. It's
> not even practical to start looking at upstreaming any of the graphics
> stuff until the core platform support is present and you've got a base
> platform to work from. A fair bit of the core MRST stuff is already
> merged (eg SFI).

The case I'm worried about is where the graphics driver never gets into 
a mergable state and we end up with a pile of mainline code that can't 
sanely run on the hardware. As long as the plan is to get it to avoid 
duplicating the entire Intel modesetting code and including its own TTM 
then that seems fair enough, but Intel's track record on this side of 
things hasn't been great.

> > Would seem neater as two separate arguments rather than an array...
> 
> There are patches further down the pile that correct that one funnily
> enough.

Good to know. Worth getting them in before merging, given that it 
changes the interface?

> > there's also another IPC mechanism for talking to P-Unit, which isn't
> > explained here but at a guess is some kind of hip-hop group and thus
> > outside the scope of this driver?
> 
> Correct except for the hip-hop group. Although hopefully someone will
> run out and form P-Unit at this point.

Ok. It'd be nice if the comment were slightly clearer.

> > > +#define IPC_BASE_ADDR     0xFF11C000	/* IPC1 base register
> > > address */
> > 
> > The address is guaranteed to be stable? This doesn't need to come
> > from SFI or anything?
> 
> It's stable. Getting it to come out of the "PCI" BARs  would make many
> people happy.

But not possible at the moment? Fair enough.

> > > +static int platform = 1;
> > > +module_param(platform, int, 0);
> > > +MODULE_PARM_DESC(platform, "1 for moorestown platform");
> > 
> > What do non-1 values mean? It looks like everything other than 1 
> > provides some default behaviour. Could you check for invalid values
> > and abort in order to avoid breakage if someone depends on this?
> 
> There are two message formats (don't blame me I wasn't involved !)
> depending upon the exact platform. This will eventually get
> autodetected but Ingo hasn't even responded to that patch despite
> three sends of it. We already sort of expose the needed info but at the
> moment it requires drivers go rudely grovelling into x86 boot struct
> internals because there is no wrapper function.

Ok. I'd prefer it if passing random numbers resulted in failure rather 
than default behaviour, but if there's never going to be another 
platform that uses the Moorestown format then that's not a real problem.

> > > +static inline u8 ipc_readl(u32 offset) /* Read ipc u32 data */
> > 
> > read16 in places and readl in others?
> 
> One is the exposed external API, the other is a little one line
> internal helper that does unrelated things.

It just made me double-take.

> > > +int intel_scu_ipc_battery_cc_read(u32 *value)
> > 
> > I'm a bit confused by the cc functions. The actual battery code isn't
> > in this driver, so why are these functions not in the battery driver?
> 
> Because the locking is in this driver. You can either smear the locking
> all over the kernel or put the message format aware functions in one
> place. I have some ideas how to tackle this better longer term.

Ok.

> > > +int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data)
> > 
> > Same here. If the i2c functionality is in this device, why not keep
> > the full i2c driver code here?
> 
> Good question - we don't need the I2C bit yet so we could just drop it
> for the moment and add it back layer wherever it belongs (however see
> 'smear the locking all over')

Fair enough.

> > All of these symbols are EXPORT_SYMBOL. Is there a reason they're not 
> > EXPORT_SYMBOL_GPL?
> 
> That is how I inherited the driver code and it's not a driver internal
> interface so I believe EXPORT_SYMBOL is correct based upon Linus
> description of _GPL.

Ok. We expect the platform to be usable without non-GPL drivers that 
require the IPC mechanism?
-- 
Matthew Garrett | mjg59@srcf.ucam.org

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms
  2010-04-26 15:43     ` Matthew Garrett
@ 2010-04-26 15:39       ` Alan Cox
  2010-04-26 16:22         ` Matthew Garrett
  0 siblings, 1 reply; 16+ messages in thread
From: Alan Cox @ 2010-04-26 15:39 UTC (permalink / raw)
  To: Matthew Garrett; +Cc: platform-driver-x86, akpm, sreedhara.ds, mingo

> The case I'm worried about is where the graphics driver never gets
> into a mergable state and we end up with a pile of mainline code that
> can't sanely run on the hardware. As long as the plan is to get it to
> avoid duplicating the entire Intel modesetting code and including its
> own TTM then that seems fair enough, but Intel's track record on this
> side of things hasn't been great.

I don't track the public DRM lists but the last I saw on that on the
archives was Keith Packard blocking patches to enable such a merge.
You'd have to ask him about it. On the acceleration side mrst/psb
graphics are not i9xx style.

> > There are patches further down the pile that correct that one
> > funnily enough.
> 
> Good to know. Worth getting them in before merging, given that it 
> changes the interface?

I'll pull that one in and push the I²C out.

> Ok. I'd prefer it if passing random numbers resulted in failure
> rather than default behaviour, but if there's never going to be
> another platform that uses the Moorestown format then that's not a
> real problem.

I'd like to think the format would remain the same. 

> > One is the exposed external API, the other is a little one line
> > internal helper that does unrelated things.
> 
> It just made me double-take.

Good reason to rename it.

> > > > +int intel_scu_ipc_battery_cc_read(u32 *value)
> > > 
> > > I'm a bit confused by the cc functions. The actual battery code
> > > isn't in this driver, so why are these functions not in the
> > > battery driver?
> > 
> > Because the locking is in this driver. You can either smear the
> > locking all over the kernel or put the message format aware
> > functions in one place. I have some ideas how to tackle this better
> > longer term.
> 
> Ok.

Having had a wander down the beach and watched the waves a bit I've got
a cunning plan. Watch this space...

> > That is how I inherited the driver code and it's not a driver
> > internal interface so I believe EXPORT_SYMBOL is correct based upon
> > Linus description of _GPL.
> 
> Ok. We expect the platform to be usable without non-GPL drivers that 
> require the IPC mechanism?

Certainly. The Meego tree as is should give you a usable platform
(barring X graphics accel which isn't something I am in a position to
fix unfortunately). There is lots in that tree which isn't pretty and
I'm trying to get stuff out of that tree into the kernel while doing
the laundry on the way.

Alan
--
To unsubscribe from this list: send the line "unsubscribe platform-driver-x86" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms
  2010-04-23 14:30 Alan Cox
@ 2010-04-26 15:09 ` Matthew Garrett
  2010-04-26 14:55   ` Alan Cox
  0 siblings, 1 reply; 16+ messages in thread
From: Matthew Garrett @ 2010-04-26 15:09 UTC (permalink / raw)
  To: Alan Cox; +Cc: platform-driver-x86, akpm, sreedhara.ds

On Fri, Apr 23, 2010 at 03:30:59PM +0100, Alan Cox wrote:

Seems basically fine, with a few comments inline. One not especially 
related query in passing - the graphics driver for Moorestown hasn't 
been submitted yet, and the version in Meego doesn't look like it's 
especially upstreamable. I seem to recall that Moorestown was going to 
lack port IO, so does it have VGA compatibility? If not, is there any 
use in mainline Moorestown support until support for the graphics is 
submitted?

> +/* Read single register */
> +int intel_scu_ipc_ioread8(u16 addr, u8 *data);
> +
> +/* Read two registers */
> +int intel_scu_ipc_ioread16(u16 addr, u16 *data);
> +
> +/* Read four registers */
> +int intel_scu_ipc_ioread32(u16 addr, u32 *data);

Are these comments really needed?

> + * Update single register based on the mask
> + * First element of data is register value and second element is mask value
> + */
> +int intel_scu_ipc_update_register(u16 addr, u8 data[2]);

Would seem neater as two separate arguments rather than an array...

> + * SCU runing in ARC processor communicates with other entity running in IA
> + * core through IPC mechanism which in turn messaging between IA core ad SCU.
> + * SCU has two IPC mechanism IPC-1 and IPC-2. IPC-1 is used between IA32 and
> + * SCU where IPC-2 is used between P-Unit and SCU. This driver delas with
> + * IPC-1 Driver provides an API for power control unit registers (e.g. MSIC)
> + * along with other APIs.

This isn't especially clear. I think I understand what it's talking 
about (There's a service processor and an x86, and this driver is 
responsible for handing the communication between the two - but there's 
also another IPC mechanism for talking to P-Unit, which isn't explained 
here but at a guess is some kind of hip-hop group and thus outside the 
scope of this driver?

> +#define IPC_BASE_ADDR     0xFF11C000	/* IPC1 base register address */

The address is guaranteed to be stable? This doesn't need to come from 
SFI or anything?

> +static struct intel_scu_ipc_dev  ipcdev; /* Only one for now */

If this is going to change in future then it might be sane to get it 
right now.

> +static int platform = 1;
> +module_param(platform, int, 0);
> +MODULE_PARM_DESC(platform, "1 for moorestown platform");

What do non-1 values mean? It looks like everything other than 1 
provides some default behaviour. Could you check for invalid values and 
abort in order to avoid breakage if someone depends on this?

> +static inline u8 ipc_readl(u32 offset) /* Read ipc u32 data */

read16 in places and readl in others?

> +int intel_scu_ipc_battery_cc_read(u32 *value)

I'm a bit confused by the cc functions. The actual battery code isn't in 
this driver, so why are these functions not in the battery driver?

> +int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data)

Same here. If the i2c functionality is in this device, why not keep the 
full i2c driver code here?

> +	mutex_lock(&ipclock);
> +	cmd = (addr >> 24) & 0xFF;
> +	if (cmd == IPC_I2C_READ) {
> +		writel(addr, ipcdev.i2c_base + IPC_I2C_CNTRL_ADDR);
> +		/* Write not getting updated without delay */

It needs a delay rather than a posting read?

> +/*
> + * Interrupt handler gets called when ioc bit of IPC_COMMAND_REG set to 1
> + * When ioc bit is set to 1, caller api must wait for interrupt handler called
> + * which in turn unlocks the caller api. Currently this is not used

Is this a problem? It looks like ioc can only be set if someone passes 
it in via intel_scu_ipc_simple_command() and that's a pretty obvious 
"Don't do that", but it'd be nice to get rid of the busy looping.

All of these symbols are EXPORT_SYMBOL. Is there a reason they're not 
EXPORT_SYMBOL_GPL?

-- 
Matthew Garrett | mjg59@srcf.ucam.org

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms
  2010-04-26 15:09 ` Matthew Garrett
@ 2010-04-26 14:55   ` Alan Cox
  2010-04-26 15:43     ` Matthew Garrett
  0 siblings, 1 reply; 16+ messages in thread
From: Alan Cox @ 2010-04-26 14:55 UTC (permalink / raw)
  To: Matthew Garrett; +Cc: platform-driver-x86, akpm, sreedhara.ds, mingo

> related query in passing - the graphics driver for Moorestown hasn't 
> been submitted yet, and the version in Meego doesn't look like it's 
> especially upstreamable. I seem to recall that Moorestown was going
> to lack port IO, so does it have VGA compatibility? If not, is there
> any use in mainline Moorestown support until support for the graphics
> is submitted?

Graphics is very very late on the merge list for obvious reasons. It's
not even practical to start looking at upstreaming any of the graphics
stuff until the core platform support is present and you've got a base
platform to work from. A fair bit of the core MRST stuff is already
merged (eg SFI).
 
> > +/* Read single register */
> > +int intel_scu_ipc_ioread8(u16 addr, u8 *data);
> > +
> > +/* Read two registers */
> > +int intel_scu_ipc_ioread16(u16 addr, u16 *data);
> > +
> > +/* Read four registers */
> > +int intel_scu_ipc_ioread32(u16 addr, u32 *data);
> 
> Are these comments really needed?

I left them in because the IPC is seeing four 8bit registers on its
side not one 32bit 'register'. It's not usually an important
distinction but it can be.

> > + * Update single register based on the mask
> > + * First element of data is register value and second element is
> > mask value
> > + */
> > +int intel_scu_ipc_update_register(u16 addr, u8 data[2]);
> 
> Would seem neater as two separate arguments rather than an array...

There are patches further down the pile that correct that one funnily
enough.

> there's also another IPC mechanism for talking to P-Unit, which isn't
> explained here but at a guess is some kind of hip-hop group and thus
> outside the scope of this driver?

Correct except for the hip-hop group. Although hopefully someone will
run out and form P-Unit at this point.

> > +#define IPC_BASE_ADDR     0xFF11C000	/* IPC1 base register
> > address */
> 
> The address is guaranteed to be stable? This doesn't need to come
> from SFI or anything?

It's stable. Getting it to come out of the "PCI" BARs  would make many
people happy.

> > +static struct intel_scu_ipc_dev  ipcdev; /* Only one for now */
> 
> If this is going to change in future then it might be sane to get it 
> right now.

I can't possibly comment on unreleased product and plans but I'm quite
happy with it as is.

> > +static int platform = 1;
> > +module_param(platform, int, 0);
> > +MODULE_PARM_DESC(platform, "1 for moorestown platform");
> 
> What do non-1 values mean? It looks like everything other than 1 
> provides some default behaviour. Could you check for invalid values
> and abort in order to avoid breakage if someone depends on this?

There are two message formats (don't blame me I wasn't involved !)
depending upon the exact platform. This will eventually get
autodetected but Ingo hasn't even responded to that patch despite
three sends of it. We already sort of expose the needed info but at the
moment it requires drivers go rudely grovelling into x86 boot struct
internals because there is no wrapper function.

(cc Ingo to prod him again)

> > +static inline u8 ipc_readl(u32 offset) /* Read ipc u32 data */
> 
> read16 in places and readl in others?

One is the exposed external API, the other is a little one line
internal helper that does unrelated things.

> > +int intel_scu_ipc_battery_cc_read(u32 *value)
> 
> I'm a bit confused by the cc functions. The actual battery code isn't
> in this driver, so why are these functions not in the battery driver?

Because the locking is in this driver. You can either smear the locking
all over the kernel or put the message format aware functions in one
place. I have some ideas how to tackle this better longer term.

> > +int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data)
> 
> Same here. If the i2c functionality is in this device, why not keep
> the full i2c driver code here?

Good question - we don't need the I2C bit yet so we could just drop it
for the moment and add it back layer wherever it belongs (however see
'smear the locking all over')

> 
> > +	mutex_lock(&ipclock);
> > +	cmd = (addr >> 24) & 0xFF;
> > +	if (cmd == IPC_I2C_READ) {
> > +		writel(addr, ipcdev.i2c_base + IPC_I2C_CNTRL_ADDR);
> > +		/* Write not getting updated without delay */
> 
> It needs a delay rather than a posting read?

It's not PCI bus, it's magic ;)

> > +/*
> > + * Interrupt handler gets called when ioc bit of IPC_COMMAND_REG
> > set to 1
> > + * When ioc bit is set to 1, caller api must wait for interrupt
> > handler called
> > + * which in turn unlocks the caller api. Currently this is not used
> 
> Is this a problem? It looks like ioc can only be set if someone
> passes it in via intel_scu_ipc_simple_command() and that's a pretty
> obvious "Don't do that", but it'd be nice to get rid of the busy
> looping.

If anything the direction will continue to be not using the interrupt.
We catch the IRQ just in case an IRQ pops up from somewhere.

> All of these symbols are EXPORT_SYMBOL. Is there a reason they're not 
> EXPORT_SYMBOL_GPL?

That is how I inherited the driver code and it's not a driver internal
interface so I believe EXPORT_SYMBOL is correct based upon Linus
description of _GPL.




^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms
@ 2010-04-23 14:30 Alan Cox
  2010-04-26 15:09 ` Matthew Garrett
  0 siblings, 1 reply; 16+ messages in thread
From: Alan Cox @ 2010-04-23 14:30 UTC (permalink / raw)
  To: platform-driver-x86, mjg, akpm, sreedhara.ds

From: Sreedhara DS <sreedhara.ds@intel.com>

The IPC (inter processor communications) is used to provide the communications
between kernel and system control units on some embedded Intel x86 platforms.

(Various bits of cleanup - Alan Cox)

Signed-off-by: Sreedhara DS <sreedhara.ds@intel.com>
Signed-off-by: Alan Cox <alan@linux.intel.com>
---

 arch/x86/include/asm/intel_scu_ipc.h |  101 ++++
 drivers/platform/x86/Kconfig         |    8 
 drivers/platform/x86/Makefile        |    1 
 drivers/platform/x86/intel_scu_ipc.c |  945 ++++++++++++++++++++++++++++++++++
 4 files changed, 1055 insertions(+), 0 deletions(-)
 create mode 100644 arch/x86/include/asm/intel_scu_ipc.h
 create mode 100644 drivers/platform/x86/intel_scu_ipc.c


diff --git a/arch/x86/include/asm/intel_scu_ipc.h b/arch/x86/include/asm/intel_scu_ipc.h
new file mode 100644
index 0000000..55303d6
--- /dev/null
+++ b/arch/x86/include/asm/intel_scu_ipc.h
@@ -0,0 +1,101 @@
+#ifndef _ASM_X86_INTEL_SCU_IPC_H_
+#define  _ASM_X86_INTEL_SCU_IPC_H_
+
+struct scu_fw_version {
+	u8 rtMinorVer;		/* SCU RT Firmware Minor Revision */
+	u8 rtMajorVer;		/* SCU RT Firmware Major Revision */
+	u8 romMinorVer;		/* SCU ROM Firmware Minor Revision */
+	u8 romMajorVer;		/* SCU ROM Firmware Major Revision */
+	u8 punitMinorVer;	/* P-unit Microcode Minor Revision */
+	u8 punitMajorVer;	/* P-unit Microcode Major Revision */
+	u8 iaMinorVer;		/* IA-32 Firmware Minor Revision */
+	u8 iaMajorVer;		/* IA-32 Firmware Major Revision */
+	u8 ftlMinorVer;		/* FTL Driver Minor Revision */
+	u8 ftlMajorVer;		/* FTL Driver Major Revision */
+	u8 valMinorVer;		/* Validation Hooks Minor Revision */
+	u8 valMajorVer;		/* Validation Hooks Major Revision */
+	u8 ifwiMinorVer;	/* IFWI Minor Revision */
+	u8 ifwiMajorVer;	/* IFWI Major Revision */
+} ;
+
+struct battery_property {
+	u32 capacity;	/* Charger capacity */
+	u8  crnt;	/* Quick charge current value*/
+	u8  volt;	/* Fine adjustment of constant charge voltage */
+	u8  prot;	/* CHRGPROT register value */
+	u8  prot2;	/* CHRGPROT1 register value */
+	u8 timer;	/* Charging timer */
+} __packed;
+
+/* Read single register */
+int intel_scu_ipc_ioread8(u16 addr, u8 *data);
+
+/* Read two registers */
+int intel_scu_ipc_ioread16(u16 addr, u16 *data);
+
+/* Read four registers */
+int intel_scu_ipc_ioread32(u16 addr, u32 *data);
+
+/* Read a vector */
+int intel_scu_ipc_readv(u16 *addr, u8 *data, int len);
+
+/* Write single register */
+int intel_scu_ipc_iowrite8(u16 addr, u8 data);
+
+/* Write two registers */
+int intel_scu_ipc_iowrite16(u16 addr, u16 data);
+
+/* Write four registers */
+int intel_scu_ipc_iowrite32(u16 addr, u32 data);
+
+/* Write a vector */
+int intel_scu_ipc_writev(u16 *addr, u8 *data, int len);
+
+/*
+ * Update single register based on the mask
+ * First element of data is register value and second element is mask value
+ */
+int intel_scu_ipc_update_register(u16 addr, u8 data[2]);
+
+/*
+ * Indirect register read
+ * Can be used when SCCB(System Controller Configuration Block) register
+ * HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ */
+int intel_scu_ipc_register_read(u32 addr, u32 *data);
+
+/*
+ * Indirect register write
+ * Can be used when SCCB(System Controller Configuration Block) register
+ * HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ */
+int intel_scu_ipc_register_write(u32 addr, u32 data);
+
+int intel_scu_ipc_simple_command(int cmd, int sub);
+
+/* Read the coulomb counter accumulator to monitor the battery charge level */
+int intel_scu_ipc_battery_cc_read(u32 *value);
+
+/* Write the coulomb counter accumulator */
+int intel_scu_ipc_battery_cc_write(u32 value);
+
+/* Read battery properties */
+int intel_scu_ipc_battery_property(struct battery_property *prop);
+
+/*
+ * Used to enable Timer Services for Kernel Watchdog Operation permitting
+ * automatic reset or reboot of the system if tripped.
+ * Set watchdog timer value, returns 0 on success, 1 on error
+ */
+int intel_scu_ipc_set_watchdog(u32 warn_threshold, u32 reset_threshold);
+
+/* I2C control api */
+int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data);
+
+/* Read FW version */
+int intel_scu_ipc_fw_version(struct scu_fw_version *version);
+
+/* Update FW version */
+int intel_scu_ipc_fw_update(u8 *buffer, u32 length);
+
+#endif
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 7bec458..09060a0 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -526,4 +526,12 @@ config ACPI_CMPC
 	  keys as input device, backlight device, tablet and accelerometer
 	  devices.
 
+config INTEL_SCU_IPC
+	bool "Intel SCU IPC Support"
+	depends on X86_MRST
+	default y
+	---help---
+	  IPC is used to bridge the communications between kernel and SCU on
+	  some embedded Intel x86 platforms.
+
 endif # X86_PLATFORM_DEVICES
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index a906490..8770bfe 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -25,3 +25,4 @@ obj-$(CONFIG_ACPI_ASUS)		+= asus_acpi.o
 obj-$(CONFIG_TOPSTAR_LAPTOP)	+= topstar-laptop.o
 obj-$(CONFIG_ACPI_TOSHIBA)	+= toshiba_acpi.o
 obj-$(CONFIG_TOSHIBA_BT_RFKILL)	+= toshiba_bluetooth.o
+obj-$(CONFIG_INTEL_SCU_IPC)	+= intel_scu_ipc.o
diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c
new file mode 100644
index 0000000..e2ff677
--- /dev/null
+++ b/drivers/platform/x86/intel_scu_ipc.c
@@ -0,0 +1,945 @@
+/*
+ * intel_scu_ipc.c: Driver for the Intel SCU IPC mechanism
+ *
+ * (C) Copyright 2008-2010 Intel Corporation
+ * Author: Sreedhara DS (sreedhara.ds@intel.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2
+ * of the License.
+ *
+ * SCU runing in ARC processor communicates with other entity running in IA
+ * core through IPC mechanism which in turn messaging between IA core ad SCU.
+ * SCU has two IPC mechanism IPC-1 and IPC-2. IPC-1 is used between IA32 and
+ * SCU where IPC-2 is used between P-Unit and SCU. This driver delas with
+ * IPC-1 Driver provides an API for power control unit registers (e.g. MSIC)
+ * along with other APIs.
+ */
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/sysdev.h>
+#include <linux/pm.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <asm/setup.h>
+#include <asm/intel_scu_ipc.h>
+
+/* IPC defines the following message types */
+#define IPCMSG_WATCHDOG_TIMER 0xF8 /* Set Kernel Watchdog Threshold */
+#define IPCMSG_BATTERY        0xEF /* Coulomb Counter Accumulator */
+#define IPCMSG_FW_UPDATE      0xFE /* Firmware update */
+#define IPCMSG_PCNTRL         0xFF /* Power controller unit read/write */
+#define IPCMSG_FW_REVISION    0xF4 /* Get firmware revision */
+
+/* Command id associated with message IPCMSG_PCNTRL */
+#define IPC_CMD_PCNTRL_W      0 /* Register write */
+#define IPC_CMD_PCNTRL_R      1 /* Register read */
+#define IPC_CMD_PCNTRL_M      2 /* Register read-modify-write */
+
+/* Miscelaneous Command ids */
+#define IPC_CMD_INDIRECT_RD   2 /* 32bit indirect read */
+#define IPC_CMD_INDIRECT_WR   5 /* 32bit indirect write */
+
+/*
+ * IPC register summary
+ *
+ * IPC register blocks are memory mapped at fixed address of 0xFF11C000
+ * To read or write information to the SCU, driver writes to IPC-1 memory
+ * mapped registers (base address 0xFF11C000). The following is the IPC
+ * mechanism
+ *
+ * 1. IA core cDMI interface claims this transaction and converts it to a
+ *    Transaction Layer Packet (TLP) message which is sent across the cDMI.
+ *
+ * 2. South Complex cDMI block receives this message and writes it to
+ *    the IPC-1 register block, causing an interrupt to the SCU
+ *
+ * 3. SCU firmware decodes this interrupt and IPC message and the appropriate
+ *    message handler is called within firmware.
+ */
+
+#define IPC_BASE_ADDR     0xFF11C000	/* IPC1 base register address */
+#define IPC_MAX_ADDR      0x100		/* Maximum IPC regisers */
+#define IPC_WWBUF_SIZE    16		/* IPC Write buffer Size */
+#define IPC_RWBUF_SIZE    16		/* IPC Read buffer Size */
+#define IPC_I2C_BASE      0xFF12B000	/* I2C control register base address */
+#define IPC_I2C_MAX_ADDR  0x10		/* Maximum I2C regisers */
+
+static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id);
+static void ipc_remove(struct pci_dev *pdev);
+
+struct intel_scu_ipc_dev {
+	struct pci_dev *pdev;
+	void __iomem *ipc_base;
+	void __iomem *i2c_base;
+};
+
+static struct intel_scu_ipc_dev  ipcdev; /* Only one for now */
+
+static int platform = 1;
+module_param(platform, int, 0);
+MODULE_PARM_DESC(platform, "1 for moorestown platform");
+
+
+
+
+/*
+ * IPC Read Buffer (Read Only):
+ * 16 byte buffer for receiving data from SCU, if IPC command
+ * processing results in response data
+ */
+#define IPC_READ_BUFFER		0x90
+
+#define IPC_I2C_CNTRL_ADDR	0
+#define I2C_DATA_ADDR		0x04
+
+static DEFINE_MUTEX(ipclock); /* lock used to prevent multiple call to SCU */
+
+/*
+ * Command Register (Write Only):
+ * A write to this register results in an interrupt to the SCU core processor
+ * Format:
+ * |rfu2(8) | size(8) | command id(4) | rfu1(3) | ioc(1) | command(8)|
+ */
+static inline void ipc_command(u32 cmd) /* Send ipc command */
+{
+	writel(cmd, ipcdev.ipc_base);
+}
+
+/*
+ * IPC Write Buffer (Write Only):
+ * 16-byte buffer for sending data associated with IPC command to
+ * SCU. Size of the data is specified in the IPC_COMMAND_REG register
+ */
+static inline void ipc_write(u32 data, u32 offset) /* Write ipc data */
+{
+	writel(data, ipcdev.ipc_base + 0x80 + offset);
+}
+
+/*
+ * IPC destination Pointer (Write Only):
+ * Use content as pointer for destination write
+ */
+static inline void ipc_write_dptr(u32 data) /* Write dptr data */
+{
+	writel(data, ipcdev.ipc_base + 0x0C);
+}
+
+/*
+ * IPC Source Pointer (Write Only):
+ * Use content as pointer for read location
+*/
+static inline void ipc_write_sptr(u32 data) /* Write dptr data */
+{
+	writel(data, ipcdev.ipc_base + 0x08);
+}
+
+/*
+ * Status Register (Read Only):
+ * Driver will read this register to get the ready/busy status of the IPC
+ * block and error status of the IPC command that was just processed by SCU
+ * Format:
+ * |rfu3(8)|error code(8)|initiator id(8)|cmd id(4)|rfu1(2)|error(1)|busy(1)|
+ */
+
+static inline u8 ipc_read_status(void)
+{
+	return __raw_readl(ipcdev.ipc_base + 0x04);
+}
+
+static inline u8 ipc_readb(u32 offset) /* Read ipc byte data */
+{
+	return readb(ipcdev.ipc_base + IPC_READ_BUFFER + offset);
+}
+
+static inline u8 ipc_readl(u32 offset) /* Read ipc u32 data */
+{
+	return readl(ipcdev.ipc_base + IPC_READ_BUFFER + offset);
+}
+
+static inline int busy_loop(void) /* Wait till scu status is busy */
+{
+	u32 status = 0;
+	u32 loop_count = 0;
+
+	status = ipc_read_status();
+	while (status & 1) {
+		udelay(1); /* scu processing time is in few u secods */
+		status = ipc_read_status();
+		loop_count++;
+		/* break if scu doesn't reset busy bit after huge retry */
+		if (loop_count > 100000) {
+			dev_err(&ipcdev.pdev->dev, "IPC timed out");
+			return -ETIMEDOUT;
+		}
+	}
+	return (status >> 1) & 1;
+}
+
+/* Read/Write power control(PMIC in Langwell, MSIC in PenWell) registers */
+static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id)
+{
+	int nc;
+	u32 offset = 0;
+	u32 err = 0;
+	u8 cbuf[IPC_WWBUF_SIZE] = { '\0' };
+	u32 *wbuf = (u32 *)&cbuf;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+
+	if (platform == 1) {
+		/* Entry is 4 bytes for read/write, 5 bytes for read modify */
+		for (nc = 0; nc < count; nc++) {
+			cbuf[offset] = addr[nc];
+			cbuf[offset + 1] = addr[nc] >> 8;
+			if (id != IPC_CMD_PCNTRL_R)
+				cbuf[offset + 2] = data[nc];
+			if (id == IPC_CMD_PCNTRL_M) {
+				cbuf[offset + 3] = data[nc + 1];
+				offset += 1;
+			}
+			offset += 3;
+		}
+		for (nc = 0, offset = 0; nc < count; nc++, offset += 4)
+			ipc_write(wbuf[nc], offset); /* Write wbuff */
+
+	} else {
+		for (nc = 0, offset = 0; nc < count; nc++, offset += 2)
+			ipc_write(addr[nc], offset); /* Write addresses */
+		if (id != IPC_CMD_PCNTRL_R) {
+			for (nc = 0; nc < count; nc++, offset++)
+				ipc_write(data[nc], offset); /* Write data */
+			if (id == IPC_CMD_PCNTRL_M)
+				ipc_write(data[nc + 1], offset); /* Mask value*/
+		}
+	}
+
+	if (id != IPC_CMD_PCNTRL_M)
+		ipc_command((count * 3) << 16 |  id << 12 | 0 << 8 | op);
+	else
+		ipc_command((count * 4) << 16 |  id << 12 | 0 << 8 | op);
+
+	err = busy_loop();
+
+	if (id == IPC_CMD_PCNTRL_R) { /* Read rbuf */
+		/* Workaround: values are read as 0 without memcpy_fromio */
+		memcpy_fromio(cbuf, ipcdev.ipc_base + IPC_READ_BUFFER, 16);
+		if (platform == 1) {
+			for (nc = 0, offset = 2; nc < count; nc++, offset += 3)
+				data[nc] = ipc_readb(offset);
+		} else {
+			for (nc = 0; nc < count; nc++)
+				data[nc] = ipc_readb(nc);
+		}
+	}
+	mutex_unlock(&ipclock);
+	return err;
+}
+
+/**
+ *	intel_scu_ipc_ioread8		-	read a word via the SCU
+ *	@addr: register on SCU
+ *	@data: return pointer for read byte
+ *
+ *	Read a single register. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_ioread8(u16 addr, u8 *data)
+{
+	return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread8);
+
+/**
+ *	intel_scu_ipc_ioread16		-	read a word via the SCU
+ *	@addr: register on SCU
+ *	@data: return pointer for read word
+ *
+ *	Read a register pair. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_ioread16(u16 addr, u16 *data)
+{
+	u16 x[2] = {addr, addr + 1 };
+	return pwr_reg_rdwr(x, (u8 *)data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread16);
+
+/**
+ *	intel_scu_ipc_ioread32		-	read a dword via the SCU
+ *	@addr: register on SCU
+ *	@data: return pointer for read dword
+ *
+ *	Read four registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_ioread32(u16 addr, u32 *data)
+{
+	u16 x[4] = {addr, addr + 1, addr + 2, addr + 3};
+	return pwr_reg_rdwr(x, (u8 *)data, 4, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread32);
+
+/**
+ *	intel_scu_ipc_iowrite8		-	write a byte via the SCU
+ *	@addr: register on SCU
+ *	@data: byte to write
+ *
+ *	Write a single register. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_iowrite8(u16 addr, u8 data)
+{
+	return pwr_reg_rdwr(&addr, &data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite8);
+
+/**
+ *	intel_scu_ipc_iowrite16		-	write a word via the SCU
+ *	@addr: register on SCU
+ *	@data: word to write
+ *
+ *	Write two registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_iowrite16(u16 addr, u16 data)
+{
+	u16 x[2] = {addr, addr + 1 };
+	return pwr_reg_rdwr(x, (u8 *)&data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite16);
+
+/**
+ *	intel_scu_ipc_iowrite32		-	write a dword via the SCU
+ *	@addr: register on SCU
+ *	@data: dword to write
+ *
+ *	Write four registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_iowrite32(u16 addr, u32 data)
+{
+	u16 x[4] = {addr, addr + 1, addr + 2, addr + 3};
+	return pwr_reg_rdwr(x, (u8 *)&data, 4, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite32);
+
+/**
+ *	intel_scu_ipc_readvv		-	read a set of registers
+ *	@addr: register list
+ *	@data: bytes to return
+ *	@len: length of array
+ *
+ *	Read registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	The largest array length permitted by the hardware is 5 items.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_readv(u16 *addr, u8 *data, int len)
+{
+	return pwr_reg_rdwr(addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_readv);
+
+/**
+ *	intel_scu_ipc_writev		-	write a set of registers
+ *	@addr: register list
+ *	@data: bytes to write
+ *	@len: length of array
+ *
+ *	Write registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	The largest array length permitted by the hardware is 5 items.
+ *
+ *	This function may sleep.
+ *
+ */
+int intel_scu_ipc_writev(u16 *addr, u8 *data, int len)
+{
+	return pwr_reg_rdwr(addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_writev);
+
+
+/**
+ *	intel_scu_ipc_update_register	-	r/m/w a register
+ *	@addr: register address
+ *	@data: bits to update and mask
+ *
+ *	Read-modify-write power control unit register. The first byte of the
+ *	data argument must be register value and second is mask value
+ *	mask is a bitmap that indicates which bits to update.
+ *	0 = masked. Don't modify this bit, 1 = modify this bit.
+ *	returns 0 on success or an error code.
+ *
+ *	This function may sleep. Locking between SCU accesses is handled
+ *	for the caller.
+ */
+int intel_scu_ipc_update_register(u16 addr, u8 data[2])
+{
+	return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_M);
+}
+EXPORT_SYMBOL(intel_scu_ipc_update_register);
+
+/**
+ *	intel_scu_ipc_register_read	-	32bit indirect read
+ *	@addr: register address
+ *	@value: 32bit value return
+ *
+ *	Performs IA 32 bit indirect read, returns 0 on success, or an
+ *	error code.
+ *
+ *	Can be used when SCCB(System Controller Configuration Block) register
+ *	HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_register_read(u32 addr, u32 *value)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_write_sptr(addr);
+	ipc_command(4 << 16 | IPC_CMD_INDIRECT_RD);
+	err = busy_loop();
+	*value = ipc_readl(0);
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_register_read);
+
+/**
+ *	intel_scu_ipc_register_write	-	32bit indirect write
+ *	@addr: register address
+ *	@value: 32bit value to write
+ *
+ *	Performs IA 32 bit indirect write, returns 0 on success, or an
+ *	error code.
+ *
+ *	Can be used when SCCB(System Controller Configuration Block) register
+ *	HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_register_write(u32 addr, u32 value)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_write_dptr(addr);
+	ipc_write(value, 0);
+	ipc_command(4 << 16 | IPC_CMD_INDIRECT_WR);
+	err = busy_loop();
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_register_write);
+
+/**
+ *	intel_scu_ipc_simple_command	-	send a simple command
+ *	@cmd: command
+ *	@sub: sub type
+ *
+ *	Issue a simple command to the SCU. Do not use this interface if
+ *	you must then access data as any data values may be overwritten
+ *	by another SCU access by the time this function returns.
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_simple_command(int cmd, int sub)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_command(cmd << 12 | sub);
+	err = busy_loop();
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_simple_command);
+
+
+/* Battery coulomb counter accumulator commands */
+#define IPC_CMD_CC_WR  0 /* Update coulomb counter value */
+#define IPC_CMD_CC_RD  1 /* Read coulomb counter value */
+#define IPC_CMD_BATTERY_PROPERTY 2 /* Read Battery property */
+
+/**
+ *	intel_scu_ipc_battery_cc_read	-	read battery cc
+ *	@value: battery coulomb counter read
+ *
+ *	Reads the battery couloumb counter value, returns 0 on success, or
+ *	an error code
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_battery_cc_read(u32 *value)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_command(IPC_CMD_CC_RD << 12 | IPCMSG_BATTERY);
+	err = busy_loop();
+	*value = ipc_readl(0);
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_battery_cc_read);
+
+/**
+ *	intel_scu_ipc_battery_property_get	-	fetch properties
+ *	@prop: battery properties
+ *
+ *	Retrieve the battery properties from the power management
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_battery_cc_write(u32 data)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_write(data, 0);
+	ipc_command(IPC_CMD_CC_WR << 12 | IPCMSG_BATTERY);
+	err = busy_loop();
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_battery_cc_write);
+
+/**
+ *	intel_scu_ipc_battery_property_get	-	fetch properties
+ *	@prop: battery properties
+ *
+ *	Retrieve the battery properties from the power management
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_battery_property_get(struct battery_property *prop)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_command(IPC_CMD_BATTERY_PROPERTY << 12 | IPCMSG_BATTERY);
+	err = busy_loop();
+	prop->capacity = ipc_readb(0);
+	prop->crnt = ipc_readb(4);
+	prop->volt = ipc_readb(5);
+	prop->prot = ipc_readb(6);
+	prop->prot2 = ipc_readb(7);
+	prop->timer = ipc_readb(8);
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_battery_property_get);
+
+/**
+ *	intel_scu_ipc_set_watchdog	-	watchdog configuration
+ *	@warn_threshold - warning timeout
+ *	@reset_threshold - reset timeout
+ *
+ *	Used to enable Timer Services for Kernel Watchdog Operation permitting
+ *	automatic reset or reboot of the system if tripped.
+ *	Set watchdog timer value, returns 0 on success, 1 on error
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_set_watchdog(u32 warn_threshold, u32 reset_threshold)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_write(warn_threshold, 0);
+	ipc_write(reset_threshold, 4);
+	ipc_command(2 << 16 | IPCMSG_WATCHDOG_TIMER);
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_set_watchdog);
+
+/**
+ *	intel_scu_ipc_fw_version	-	firmware information
+ *	@version: retrieved firmware version information
+ *
+ *	Read the firmware information from the SCU.
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_fw_version(struct scu_fw_version *version)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_command(IPCMSG_FW_REVISION);
+	err = busy_loop();
+	version->rtMinorVer = ipc_readb(0);
+	version->rtMajorVer = ipc_readb(1);
+	version->romMinorVer = ipc_readb(2);
+	version->romMajorVer = ipc_readb(3);
+	version->punitMinorVer = ipc_readb(4);
+	version->punitMajorVer = ipc_readb(5);
+	version->iaMinorVer = ipc_readb(6);
+	version->iaMajorVer = ipc_readb(7);
+	version->ftlMinorVer = ipc_readb(8);
+	version->ftlMajorVer = ipc_readb(9);
+	version->valMinorVer = ipc_readb(10);
+	version->valMajorVer = ipc_readb(11);
+	/* Bytes 12 and 13 are unused */
+	version->ifwiMinorVer = ipc_readb(14);
+	version->ifwiMajorVer = ipc_readb(15);
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_fw_version);
+
+/*I2C commands */
+#define IPC_I2C_WRITE 1 /* I2C Write command */
+#define IPC_I2C_READ  2 /* I2C Read command */
+
+/**
+ *	intel_scu_ipc_i2c_cntrl		-	I2C read/write operations
+ *	@addr: I2C address + command bits
+ *	@data: data to read/write
+ *
+ *	Perform an an I2C read/write operation via the SCU. All locking is
+ *	handled for the caller. This function may sleep.
+ *
+ *	Returns an error code or 0 on success.
+ */
+int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data)
+{
+	u32 cmd = 0;
+
+	mutex_lock(&ipclock);
+	cmd = (addr >> 24) & 0xFF;
+	if (cmd == IPC_I2C_READ) {
+		writel(addr, ipcdev.i2c_base + IPC_I2C_CNTRL_ADDR);
+		/* Write not getting updated without delay */
+		mdelay(1);
+		*data = readl(ipcdev.i2c_base + I2C_DATA_ADDR);
+	} else if (cmd == IPC_I2C_WRITE) {
+		writel(addr, ipcdev.i2c_base + I2C_DATA_ADDR);
+		mdelay(1);
+		writel(addr, ipcdev.i2c_base + IPC_I2C_CNTRL_ADDR);
+	} else {
+		dev_err(&ipcdev.pdev->dev,
+			"intel_scu_ipc: I2C INVALID_CMD = 0x%x\n", cmd);
+
+		mutex_unlock(&ipclock);
+		return -1;
+	}
+	mutex_unlock(&ipclock);
+	return 0;
+}
+EXPORT_SYMBOL(intel_scu_ipc_i2c_cntrl);
+
+#define IPC_FW_LOAD_ADDR 0xFFFC0000 /* Storage location for FW image */
+#define IPC_FW_UPDATE_MBOX_ADDR 0xFFFFDFF4 /* Mailbox between ipc and scu */
+#define IPC_MAX_FW_SIZE 262144 /* 256K storage size for loading the FW image */
+#define IPC_FW_MIP_HEADER_SIZE 2048 /* Firmware MIP header size */
+/* IPC inform SCU to get ready for update process */
+#define IPC_CMD_FW_UPDATE_READY  0x10FE
+/* IPC inform SCU to go for update process */
+#define IPC_CMD_FW_UPDATE_GO     0x20FE
+/* Status code for fw update */
+#define IPC_FW_UPDATE_SUCCESS	0x444f4e45 /* Status code 'DONE' */
+#define IPC_FW_UPDATE_BADN	0x4241444E /* Status code 'BADN' */
+#define IPC_FW_TXHIGH		0x54784849 /* Status code 'IPC_FW_TXHIGH' */
+#define IPC_FW_TXLOW		0x54784c4f /* Status code 'IPC_FW_TXLOW' */
+
+struct fw_update_mailbox {
+	u32    status;
+	u32    scu_flag;
+	u32    driver_flag;
+};
+
+
+/**
+ *	intel_scu_ipc_fw_update	-	 Firmware update utility
+ *	@buffer: firmware buffer
+ *	@length: size of firmware buffer
+ *
+ *	This function provides an interface to load the firmware into
+ *	the SCU. Returns 0 on success or -1 on failure
+ */
+int intel_scu_ipc_fw_update(u8 *buffer, u32 length)
+{
+	void __iomem *fw_update_base;
+	struct fw_update_mailbox __iomem *mailbox = NULL;
+	int retry_cnt = 0;
+	u32 status;
+
+	mutex_lock(&ipclock);
+	fw_update_base = ioremap_nocache(IPC_FW_LOAD_ADDR, (128*1024));
+	if (fw_update_base == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENOMEM;
+	}
+	mailbox = ioremap_nocache(IPC_FW_UPDATE_MBOX_ADDR,
+					sizeof(struct fw_update_mailbox));
+	if (mailbox == NULL) {
+		iounmap(fw_update_base);
+		mutex_unlock(&ipclock);
+		return -ENOMEM;
+	}
+
+	ipc_command(IPC_CMD_FW_UPDATE_READY);
+
+	/* Intitialize mailbox */
+	writel(0, &mailbox->status);
+	writel(0, &mailbox->scu_flag);
+	writel(0, &mailbox->driver_flag);
+
+	/* Driver copies the 2KB MIP header to SRAM at 0xFFFC0000*/
+	memcpy_toio(fw_update_base, buffer, 0x800);
+
+	/* Driver sends "FW Update" IPC command (CMD_ID 0xFE; MSG_ID 0x02).
+	* Upon receiving this command, SCU will write the 2K MIP header
+	* from 0xFFFC0000 into NAND.
+	* SCU will write a status code into the Mailbox, and then set scu_flag.
+	*/
+
+	ipc_command(IPC_CMD_FW_UPDATE_GO);
+
+	/*Driver stalls until scu_flag is set */
+	while (readl(&mailbox->scu_flag) != 1) {
+		rmb();
+		mdelay(1);
+	}
+
+	/* Driver checks Mailbox status.
+	 * If the status is 'BADN', then abort (bad NAND).
+	 * If the status is 'IPC_FW_TXLOW', then continue.
+	 */
+	while (readl(&mailbox->status) != IPC_FW_TXLOW) {
+		rmb();
+		mdelay(10);
+	}
+	mdelay(10);
+
+update_retry:
+	if (retry_cnt > 5)
+		goto update_end;
+
+	if (readl(&mailbox->status) != IPC_FW_TXLOW)
+		goto update_end;
+	buffer = buffer + 0x800;
+	memcpy_toio(fw_update_base, buffer, 0x20000);
+	writel(1, &mailbox->driver_flag);
+	while (readl(&mailbox->scu_flag) == 1) {
+		rmb();
+		mdelay(1);
+	}
+
+	/* check for 'BADN' */
+	if (readl(&mailbox->status) == IPC_FW_UPDATE_BADN)
+		goto update_end;
+
+	while (readl(&mailbox->status) != IPC_FW_TXHIGH) {
+		rmb();
+		mdelay(10);
+	}
+	mdelay(10);
+
+	if (readl(&mailbox->status) != IPC_FW_TXHIGH)
+		goto update_end;
+
+	buffer = buffer + 0x20000;
+	memcpy_toio(fw_update_base, buffer, 0x20000);
+	writel(0, &mailbox->driver_flag);
+
+	while (mailbox->scu_flag == 0) {
+		rmb();
+		mdelay(1);
+	}
+
+	/* check for 'BADN' */
+	if (readl(&mailbox->status) == IPC_FW_UPDATE_BADN)
+		goto update_end;
+
+	if (readl(&mailbox->status) == IPC_FW_TXLOW) {
+		++retry_cnt;
+		goto update_retry;
+	}
+
+update_end:
+	status = readl(&mailbox->status);
+
+	iounmap(fw_update_base);
+	iounmap(mailbox);
+	mutex_unlock(&ipclock);
+
+	if (status == IPC_FW_UPDATE_SUCCESS)
+		return 0;
+	return -1;
+}
+EXPORT_SYMBOL(intel_scu_ipc_fw_update);
+
+/*
+ * Interrupt handler gets called when ioc bit of IPC_COMMAND_REG set to 1
+ * When ioc bit is set to 1, caller api must wait for interrupt handler called
+ * which in turn unlocks the caller api. Currently this is not used
+ *
+ * This is edge triggered so we need take no action to clear anything
+ */
+static irqreturn_t ioc(int irq, void *dev_id)
+{
+	return IRQ_HANDLED;
+}
+
+/**
+ *	ipc_probe	-	probe an Intel SCU IPC
+ *	@dev: the PCI device matching
+ *	@id: entry in the match table
+ *
+ *	Enable and install an intel SCU IPC. This appears in the PCI space
+ *	but uses some hard coded addresses as well.
+ */
+static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+	int err;
+	resource_size_t pci_resource;
+
+	if (ipcdev.pdev)		/* We support only one SCU */
+		return -EBUSY;
+
+	ipcdev.pdev = pci_dev_get(dev);
+
+	err = pci_enable_device(dev);
+	if (err)
+		return err;
+
+	err = pci_request_regions(dev, "intel_scu_ipc");
+	if (err)
+		return err;
+
+	pci_resource = pci_resource_start(dev, 0);
+	if (!pci_resource)
+		return -ENOMEM;
+
+	if (request_irq(dev->irq, ioc, 0, "intel_scu_ipc", &ipcdev))
+		return -EBUSY;
+
+	ipcdev.ipc_base = ioremap_nocache(IPC_BASE_ADDR, IPC_MAX_ADDR);
+	if (!ipcdev.ipc_base)
+		return -ENOMEM;
+
+	ipcdev.i2c_base = ioremap_nocache(IPC_I2C_BASE, IPC_I2C_MAX_ADDR);
+	if (!ipcdev.i2c_base) {
+		iounmap(ipcdev.ipc_base);
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+/**
+ *	ipc_remove	-	remove a bound IPC device
+ *	@pdev: PCI device
+ *
+ *	In practice the SCU is not removable but this function is also
+ *	called for each device on a module unload or cleanup which is the
+ *	path that will get used.
+ *
+ *	Free up the mappings and release the PCI resources
+ */
+static void ipc_remove(struct pci_dev *pdev)
+{
+	free_irq(pdev->irq, &ipcdev);
+	pci_release_regions(pdev);
+	pci_dev_put(ipcdev.pdev);
+	iounmap(ipcdev.ipc_base);
+	iounmap(ipcdev.i2c_base);
+	ipcdev.pdev = NULL;
+}
+
+static const struct pci_device_id pci_ids[] = {
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x080e)},
+	{ 0,}
+};
+MODULE_DEVICE_TABLE(pci, pci_ids);
+
+static struct pci_driver ipc_driver = {
+	.name = "intel_scu_ipc",
+	.id_table = pci_ids,
+	.probe = ipc_probe,
+	.remove = ipc_remove,
+};
+
+
+static int __init intel_scu_ipc_init(void)
+{
+	return  pci_register_driver(&ipc_driver);
+}
+
+static void __exit intel_scu_ipc_exit(void)
+{
+	pci_unregister_driver(&ipc_driver);
+}
+
+MODULE_AUTHOR("Sreedhara DS <sreedhara.ds@intel.com>");
+MODULE_DESCRIPTION("Intel SCU IPC driver");
+MODULE_LICENSE("GPL");
+
+module_init(intel_scu_ipc_init);
+module_exit(intel_scu_ipc_exit);


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms
@ 2010-04-21 12:25 Alan Cox
  0 siblings, 0 replies; 16+ messages in thread
From: Alan Cox @ 2010-04-21 12:25 UTC (permalink / raw)
  To: platform-driver-x86, mjg

From: Sreedhara DS <sreedhara.ds@intel.com>

The IPC is used to bridge the communications between kernel and SCU on
some embedded Intel x86 platforms.

(Some reworking and additional functions added by Alan Cox)

Signed-off-by: Sreedhara DS <sreedhara.ds@intel.com>
Signed-off-by: Alan Cox <alan@linux.intel.com>
---

 arch/x86/include/asm/intel_scu_ipc.h |  101 ++++
 drivers/platform/x86/Kconfig         |    8 
 drivers/platform/x86/Makefile        |    1 
 drivers/platform/x86/intel_scu_ipc.c |  951 ++++++++++++++++++++++++++++++++++
 4 files changed, 1061 insertions(+), 0 deletions(-)
 create mode 100644 arch/x86/include/asm/intel_scu_ipc.h
 create mode 100644 drivers/platform/x86/intel_scu_ipc.c


diff --git a/arch/x86/include/asm/intel_scu_ipc.h b/arch/x86/include/asm/intel_scu_ipc.h
new file mode 100644
index 0000000..3332e32
--- /dev/null
+++ b/arch/x86/include/asm/intel_scu_ipc.h
@@ -0,0 +1,101 @@
+#ifndef _ASM_X86_INTEL_SCU_IPC_H_
+#define  _ASM_X86_INTEL_SCU_IPC_H_
+
+struct scu_fw_version {
+	u8 rtMinorVer;		/* SCU RT Firmware Minor Revision */
+	u8 rtMajorVer;		/* SCU RT Firmware Major Revision */
+	u8 romMinorVer;		/* SCU ROM Firmware Minor Revision */
+	u8 romMajorVer;		/* SCU ROM Firmware Major Revision */
+	u8 punitMinorVer;	/* P-unit Microcode Minor Revision */
+	u8 punitMajorVer;	/* P-unit Microcode Major Revision */
+	u8 iaMinorVer;		/* IA-32 Firmware Minor Revision */
+	u8 iaMajorVer;		/* IA-32 Firmware Major Revision */
+	u8 ftlMinorVer;		/* FTL Driver Minor Revision */
+	u8 ftlMajorVer;		/* FTL Driver Major Revision */
+	u8 valMinorVer;		/* Validation Hooks Minor Revision */
+	u8 valMajorVer;		/* Validation Hooks Major Revision */
+	u8 ifwiMinorVer;	/* IFWI Minor Revision */
+	u8 ifwiMajorVer;	/* IFWI Major Revision */
+} ;
+
+struct battery_property {
+	u32 capacity;	/* Charger capacity */
+	u8  crnt;	/* Quick charge current value*/
+	u8  volt;	/* Fine adjustment of constant charge voltage */
+	u8  prot;	/* CHRGPROT register value */
+	u8  prot2;	/* CHRGPROT1 register value */
+	u8 timer;	/* Charging timer */
+} __attribute__((packed));
+
+/* Read single register */
+int intel_scu_ipc_ioread8(u16 addr, u8 *data);
+
+/* Read two registers */
+int intel_scu_ipc_ioread16(u16 addr, u16 *data);
+
+/* Read four registers */
+int intel_scu_ipc_ioread32(u16 addr, u32 *data);
+
+/* Read a vector */
+int intel_scu_ipc_readv(u16 *addr, u8 *data, int len);
+
+/* Write single register */
+int intel_scu_ipc_iowrite8(u16 addr, u8 data);
+
+/* Write two registers */
+int intel_scu_ipc_iowrite16(u16 addr, u16 data);
+
+/* Write four registers */
+int intel_scu_ipc_iowrite32(u16 addr, u32 data);
+
+/* Write a vector */
+int intel_scu_ipc_writev(u16 *addr, u8 *data, int len);
+
+/*
+ * Update single register based on the mask
+ * First element of data is register value and second element is mask value
+ */
+int intel_scu_ipc_update_register(u16 addr, u8 data[2]);
+
+/*
+ * Indirect register read
+ * Can be used when SCCB(System Controller Configuration Block) register
+ * HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ */
+int intel_scu_ipc_register_read(u32 addr, u32 *data);
+
+/*
+ * Indirect register write
+ * Can be used when SCCB(System Controller Configuration Block) register
+ * HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ */
+int intel_scu_ipc_register_write(u32 addr, u32 data);
+
+int intel_scu_ipc_simple_command(int cmd, int sub);
+
+/* Read the coulomb counter accumulator to monitor the battery charge level */
+int intel_scu_ipc_battery_cc_read(u32 *value);
+
+/* Write the coulomb counter accumulator */
+int intel_scu_ipc_battery_cc_write(u32 value);
+
+/* Read battery properties */
+int intel_scu_ipc_battery_property(struct battery_property *prop);
+
+/*
+ * Used to enable Timer Services for Kernel Watchdog Operation permitting
+ * automatic reset or reboot of the system if tripped.
+ * Set watchdog timer value, returns 0 on success, 1 on error
+ */
+int intel_scu_ipc_set_watchdog(u32 warn_threshold, u32 reset_threshold);
+
+/* I2C control api */
+int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data);
+
+/* Read FW version */
+int intel_scu_ipc_fw_version(struct scu_fw_version *version);
+
+/* Update FW version */
+int intel_scu_ipc_fw_update(u8 *buffer, u32 length);
+
+#endif
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 7bec458..09060a0 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -526,4 +526,12 @@ config ACPI_CMPC
 	  keys as input device, backlight device, tablet and accelerometer
 	  devices.
 
+config INTEL_SCU_IPC
+	bool "Intel SCU IPC Support"
+	depends on X86_MRST
+	default y
+	---help---
+	  IPC is used to bridge the communications between kernel and SCU on
+	  some embedded Intel x86 platforms.
+
 endif # X86_PLATFORM_DEVICES
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index a906490..8770bfe 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -25,3 +25,4 @@ obj-$(CONFIG_ACPI_ASUS)		+= asus_acpi.o
 obj-$(CONFIG_TOPSTAR_LAPTOP)	+= topstar-laptop.o
 obj-$(CONFIG_ACPI_TOSHIBA)	+= toshiba_acpi.o
 obj-$(CONFIG_TOSHIBA_BT_RFKILL)	+= toshiba_bluetooth.o
+obj-$(CONFIG_INTEL_SCU_IPC)	+= intel_scu_ipc.o
diff --git a/drivers/platform/x86/intel_scu_ipc.c b/drivers/platform/x86/intel_scu_ipc.c
new file mode 100644
index 0000000..6bfe3dc
--- /dev/null
+++ b/drivers/platform/x86/intel_scu_ipc.c
@@ -0,0 +1,951 @@
+/*
+ * intel_scu_ipc.c: Driver for the Intel SCU IPC mechanism
+ *
+ * (C) Copyright 2008-2010 Intel Corporation
+ * Author: Sreedhara DS (sreedhara.ds@intel.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2
+ * of the License.
+ *
+ * SCU runing in ARC processor communicates with other entity running in IA
+ * core through IPC mechanism which in turn messaging between IA core ad SCU.
+ * SCU has two IPC mechanism IPC-1 and IPC-2. IPC-1 is used between IA32 and
+ * SCU where IPC-2 is used between P-Unit and SCU. This driver delas with
+ * IPC-1 Driver provides an API for power control unit registers (e.g. MSIC)
+ * along with other APIs.
+ */
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/sysdev.h>
+#include <linux/pm.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <asm/setup.h>
+#include <asm/intel_scu_ipc.h>
+
+/* IPC defines the following message types */
+#define IPCMSG_WATCHDOG_TIMER 0xF8 /* Set Kernel Watchdog Threshold */
+#define IPCMSG_BATTERY        0xEF /* Coulomb Counter Accumulator */
+#define IPCMSG_FW_UPDATE      0xFE /* Firmware update */
+#define IPCMSG_PCNTRL         0xFF /* Power controller unit read/write */
+#define IPCMSG_FW_REVISION    0xF4 /* Get firmware revision */
+
+/* Command id associated with message IPCMSG_PCNTRL */
+#define IPC_CMD_PCNTRL_W      0 /* Register write */
+#define IPC_CMD_PCNTRL_R      1 /* Register read */
+#define IPC_CMD_PCNTRL_M      2 /* Register read-modify-write */
+
+/* Miscelaneous Command ids */
+#define IPC_CMD_INDIRECT_RD   2 /* 32bit indirect read */
+#define IPC_CMD_INDIRECT_WR   5 /* 32bit indirect write */
+
+/*
+ * IPC register summary
+ *
+ * IPC register blocks are memory mapped at fixed address of 0xFF11C000
+ * To read or write information to the SCU, driver writes to IPC-1 memory
+ * mapped registers (base address 0xFF11C000). The following is the IPC
+ * mechanism
+ *
+ * 1. IA core cDMI interface claims this transaction and converts it to a
+ *    Transaction Layer Packet (TLP) message which is sent across the cDMI.
+ *
+ * 2. South Complex cDMI block receives this message and writes it to
+ *    the IPC-1 register block, causing an interrupt to the SCU
+ *
+ * 3. SCU firmware decodes this interrupt and IPC message and the appropriate
+ *    message handler is called within firmware.
+ */
+
+#define IPC_BASE_ADDR     0xFF11C000	/* IPC1 base register address */
+#define IPC_MAX_ADDR      0x100		/* Maximum IPC regisers */
+#define IPC_WWBUF_SIZE    16		/* IPC Write buffer Size */
+#define IPC_RWBUF_SIZE    16		/* IPC Read buffer Size */
+#define IPC_I2C_BASE      0xFF12B000	/* I2C control register base address */
+#define IPC_I2C_MAX_ADDR  0x10		/* Maximum I2C regisers */
+
+static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id);
+static void ipc_remove(struct pci_dev *pdev);
+
+struct intel_scu_ipc_dev {
+	struct pci_dev *pdev;
+	void __iomem *ipc_base;
+	void __iomem *i2c_base;
+	void __iomem *pci_base;
+};
+
+static struct intel_scu_ipc_dev  ipcdev; /* Only one for now */
+
+static int platform = 1;
+module_param(platform, int, 0);
+MODULE_PARM_DESC(platform, "1 for moorestown platform");
+
+/*
+ * Command Register (Write Only):
+ * A write to this register results in an interrupt to the SCU core processor
+ * Format:
+ * |rfu2(8) | size(8) | command id(4) | rfu1(3) | ioc(1) | command(8)|
+ */
+#define IPC_COMMAND_REG		ipcdev.ipc_base
+
+/*
+ * Status Register (Read Only):
+ * Driver will read this register to get the ready/busy status of the IPC
+ * block and error status of the IPC command that was just processed by SCU
+ * Format:
+ * |rfu3(8)|error code(8)|initiator id(8)|cmd id(4)|rfu1(2)|error(1)|busy(1)|
+ */
+#define IPC_STATUS_REG		(ipcdev.ipc_base + 0x04)
+
+/*
+ * IPC Source Pointer (Write Only):
+ * Use content as pointer for read location
+*/
+#define IPC_SPTR_REG		(ipcdev.ipc_base + 0x08)
+
+/*
+ * IPC destination Pointer (Write Only):
+ * Use content as pointer for destination write
+*/
+#define IPC_DPTR_REG		(ipcdev.ipc_base + 0x0C)
+
+/*
+ * IPC Write Buffer (Write Only):
+ * 16-byte buffer for sending data associated with IPC command to
+ * SCU. Size of the data is specified in the IPC_COMMAND_REG register
+*/
+#define IPC_WRITE_BUFFER	(ipcdev.ipc_base + 0x80)
+
+/*
+ * IPC Read Buffer (Read Only):
+ * 16 byte buffer for receiving data from SCU, if IPC command
+ * processing results in response data
+*/
+#define IPC_READ_BUFFER		(ipcdev.ipc_base + 0x90)
+
+#define IPC_I2C_CNTRL_ADDR	ipcdev.i2c_base
+#define I2C_DATA_ADDR		(ipcdev.i2c_base + 0x04)
+
+static DEFINE_MUTEX(ipclock); /* lock used to prevent multiple call to SCU */
+
+static inline void ipc_command(u32 cmd) /* Send ipc command */
+{
+	writel(cmd, IPC_COMMAND_REG);
+}
+
+static inline void ipc_write(u32 data, u32 offset) /* Write ipc data */
+{
+	writel(data, IPC_WRITE_BUFFER + offset);
+}
+
+static inline void ipc_write_dptr(u32 data) /* Write dptr data */
+{
+	writel(data, IPC_DPTR_REG);
+}
+
+static inline void ipc_write_sptr(u32 data) /* Write dptr data */
+{
+	writel(data, IPC_SPTR_REG);
+}
+
+static inline u8 ipc_readb(u32 offset) /* Read ipc byte data */
+{
+	return readb(IPC_READ_BUFFER + offset);
+}
+
+static inline u8 ipc_readl(u32 offset) /* Read ipc u32 data */
+{
+	return readl(IPC_READ_BUFFER + offset);
+}
+
+static inline int busy_loop(void) /* Wait till scu status is busy */
+{
+	u32 status = 0;
+	u32 loop_count = 0;
+
+	status = __raw_readl(IPC_STATUS_REG);
+	while (status & 1) {
+		udelay(1); /* scu processing time is in few u secods */
+		status = __raw_readl(IPC_STATUS_REG);
+		loop_count++;
+		/* break if scu doesn't reset busy bit after huge retry */
+		if (loop_count > 100000)
+			return -ETIMEDOUT;
+	}
+	return (status >> 1) & 1;
+}
+
+/* Read/Write power control(PMIC in Langwell, MSIC in PenWell) registers */
+static int pwr_reg_rdwr(u16 *addr, u8 *data, u32 count, u32 op, u32 id)
+{
+	int nc;
+	u32 offset = 0;
+	u32 err = 0;
+	u8 cbuf[IPC_WWBUF_SIZE] = { '\0' };
+	u32 *wbuf = (u32 *)&cbuf;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+
+	if (platform == 1) {
+		/* Entry is 4 bytes for read/write, 5 bytes for read modify */
+		for (nc = 0; nc < count; nc++) {
+			cbuf[offset] = addr[nc];
+			cbuf[offset + 1] = addr[nc] >> 8;
+			if (id != IPC_CMD_PCNTRL_R)
+				cbuf[offset + 2] = data[nc];
+			if (id == IPC_CMD_PCNTRL_M) {
+				cbuf[offset + 3] = data[nc + 1];
+				offset += 1;
+			}
+			offset += 3;
+		}
+		for (nc = 0, offset = 0; nc < count; nc++, offset += 4)
+			ipc_write(wbuf[nc], offset); /* Write wbuff */
+
+	} else {
+		for (nc = 0, offset = 0; nc < count; nc++, offset += 2)
+			ipc_write(addr[nc], offset); /* Write addresses */
+		if (id != IPC_CMD_PCNTRL_R) {
+			for (nc = 0; nc < count; nc++, offset++)
+				ipc_write(data[nc], offset); /* Write data */
+			if (id == IPC_CMD_PCNTRL_M)
+				ipc_write(data[nc + 1], offset); /* Mask value*/
+		}
+	}
+
+	if (id != IPC_CMD_PCNTRL_M)
+		ipc_command((count*3) << 16 |  id << 12 | 0 << 8 | op);
+	else
+		ipc_command((count*4) << 16 |  id << 12 | 0 << 8 | op);
+
+	err = busy_loop();
+
+	if (id == IPC_CMD_PCNTRL_R) { /* Read rbuf */
+		/* Workaround: values are read as 0 without memcpy_fromio */
+		memcpy_fromio(cbuf, IPC_READ_BUFFER, 16);
+		if (platform == 1) {
+			for (nc = 0, offset = 2; nc < count; nc++, offset += 3)
+				data[nc] = ipc_readb(offset);
+		} else {
+			for (nc = 0; nc < count; nc++)
+				data[nc] = ipc_readb(nc);
+		}
+	}
+	mutex_unlock(&ipclock);
+	return err;
+}
+
+/**
+ *	intel_scu_ipc_ioread8		-	read a word via the SCU
+ *	@addr: register on SCU
+ *	@data: return pointer for read byte
+ *
+ *	Read a single register. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_ioread8(u16 addr, u8 *data)
+{
+	return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread8);
+
+/**
+ *	intel_scu_ipc_ioread16		-	read a word via the SCU
+ *	@addr: register on SCU
+ *	@data: return pointer for read word
+ *
+ *	Read a register pair. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_ioread16(u16 addr, u16 *data)
+{
+	u16 x[2] = {addr, addr + 1 };
+	return pwr_reg_rdwr(x, (u8 *)data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread16);
+
+/**
+ *	intel_scu_ipc_ioread32		-	read a dword via the SCU
+ *	@addr: register on SCU
+ *	@data: return pointer for read dword
+ *
+ *	Read four registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_ioread32(u16 addr, u32 *data)
+{
+	u16 x[4] = {addr, addr + 1, addr + 2, addr + 3};
+	return pwr_reg_rdwr(x, (u8 *)data, 4, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_ioread32);
+
+/**
+ *	intel_scu_ipc_iowrite8		-	write a byte via the SCU
+ *	@addr: register on SCU
+ *	@data: byte to write
+ *
+ *	Write a single register. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_iowrite8(u16 addr, u8 data)
+{
+	return pwr_reg_rdwr(&addr, &data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite8);
+
+/**
+ *	intel_scu_ipc_iowrite16		-	write a word via the SCU
+ *	@addr: register on SCU
+ *	@data: word to write
+ *
+ *	Write two registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_iowrite16(u16 addr, u16 data)
+{
+	u16 x[2] = {addr, addr + 1 };
+	return pwr_reg_rdwr(x, (u8 *)&data, 2, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite16);
+
+/**
+ *	intel_scu_ipc_iowrite32		-	write a dword via the SCU
+ *	@addr: register on SCU
+ *	@data: dword to write
+ *
+ *	Write four registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_iowrite32(u16 addr, u32 data)
+{
+	u16 x[4] = {addr, addr + 1, addr + 2, addr + 3};
+	return pwr_reg_rdwr(x, (u8 *)&data, 4, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_iowrite32);
+
+/**
+ *	intel_scu_ipc_readvv		-	read a set of registers
+ *	@addr: register list
+ *	@data: bytes to return
+ *	@len: length of array
+ *
+ *	Read registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	The largest array length permitted by the hardware is 5 items.
+ *
+ *	This function may sleep.
+ */
+int intel_scu_ipc_readv(u16 *addr, u8 *data, int len)
+{
+	return pwr_reg_rdwr(addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_R);
+}
+EXPORT_SYMBOL(intel_scu_ipc_readv);
+
+/**
+ *	intel_scu_ipc_writev		-	write a set of registers
+ *	@addr: register list
+ *	@data: bytes to write
+ *	@len: length of array
+ *
+ *	Write registers. Returns 0 on success or an error code. All
+ *	locking between SCU accesses is handled for the caller.
+ *
+ *	The largest array length permitted by the hardware is 5 items.
+ *
+ *	This function may sleep.
+ *
+ */
+int intel_scu_ipc_writev(u16 *addr, u8 *data, int len)
+{
+	return pwr_reg_rdwr(addr, data, len, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_W);
+}
+EXPORT_SYMBOL(intel_scu_ipc_writev);
+
+
+/**
+ *	intel_scu_ipc_update_register	-	r/m/w a register
+ *	@addr: register address
+ *	@data: bits to update and mask
+ *
+ *	Read-modify-write power control unit register. The first byte of the
+ *	data argument must be register value and second is mask value
+ *	mask is a bitmap that indicates which bits to update.
+ *	0 = masked. Don't modify this bit, 1 = modify this bit.
+ *	returns 0 on success or an error code.
+ *
+ *	This function may sleep. Locking between SCU accesses is handled
+ *	for the caller.
+ */
+int intel_scu_ipc_update_register(u16 addr, u8 data[2])
+{
+	return pwr_reg_rdwr(&addr, data, 1, IPCMSG_PCNTRL, IPC_CMD_PCNTRL_M);
+}
+EXPORT_SYMBOL(intel_scu_ipc_update_register);
+
+/**
+ *	intel_scu_ipc_register_read	-	32bit indirect read
+ *	@addr: register address
+ *	@value: 32bit value return
+ *
+ *	Performs IA 32 bit indirect read, returns 0 on success, or an
+ *	error code.
+ *
+ *	Can be used when SCCB(System Controller Configuration Block) register
+ *	HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_register_read(u32 addr, u32 *value)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_write_sptr(addr);
+	ipc_command(4 << 16 | IPC_CMD_INDIRECT_RD);
+	err = busy_loop();
+	*value = ipc_readl(0);
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_register_read);
+
+/**
+ *	intel_scu_ipc_register_write	-	32bit indirect write
+ *	@addr: register address
+ *	@value: 32bit value to write
+ *
+ *	Performs IA 32 bit indirect write, returns 0 on success, or an
+ *	error code.
+ *
+ *	Can be used when SCCB(System Controller Configuration Block) register
+ *	HRIM(Honor Restricted IPC Messages) is set (bit 23)
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_register_write(u32 addr, u32 value)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_write_dptr(addr);
+	ipc_write(value, 0);
+	ipc_command(4 << 16 | IPC_CMD_INDIRECT_WR);
+	err = busy_loop();
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_register_write);
+
+/**
+ *	intel_scu_ipc_simple_command	-	send a simple command
+ *	@cmd: command
+ *	@sub: sub type
+ *
+ *	Issue a simple command to the SCU. Do not use this interface if
+ *	you must then access data as any data values may be overwritten
+ *	by another SCU access by the time this function returns.
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_simple_command(int cmd, int sub)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_command(cmd << 12 | sub);
+	err = busy_loop();
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_simple_command);
+
+
+/* Battery coulomb counter accumulator commands */
+#define IPC_CMD_CC_WR  0 /* Update coulomb counter value */
+#define IPC_CMD_CC_RD  1 /* Read coulomb counter value */
+#define IPC_CMD_BATTERY_PROPERTY 2 /* Read Battery property */
+
+/**
+ *	intel_scu_ipc_battery_cc_read	-	read battery cc
+ *	@value: battery coulomb counter read
+ *
+ *	Reads the battery couloumb counter value, returns 0 on success, or
+ *	an error code
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_battery_cc_read(u32 *value)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_command(IPC_CMD_CC_RD << 12 | IPCMSG_BATTERY);
+	err = busy_loop();
+	*value = ipc_readl(0);
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_battery_cc_read);
+
+/**
+ *	intel_scu_ipc_battery_property_get	-	fetch properties
+ *	@prop: battery properties
+ *
+ *	Retrieve the battery properties from the power management
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_battery_cc_write(u32 data)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_write(data, 0);
+	ipc_command(IPC_CMD_CC_WR << 12 | IPCMSG_BATTERY);
+	err = busy_loop();
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_battery_cc_write);
+
+/**
+ *	intel_scu_ipc_battery_property_get	-	fetch properties
+ *	@prop: battery properties
+ *
+ *	Retrieve the battery properties from the power management
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_battery_property_get(struct battery_property *prop)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_command(IPC_CMD_BATTERY_PROPERTY << 12 | IPCMSG_BATTERY);
+	err = busy_loop();
+	prop->capacity = ipc_readb(0);
+	prop->crnt = ipc_readb(4);
+	prop->volt = ipc_readb(5);
+	prop->prot = ipc_readb(6);
+	prop->prot2 = ipc_readb(7);
+	prop->timer = ipc_readb(8);
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_battery_property_get);
+
+/**
+ *	intel_scu_ipc_set_watchdog	-	watchdog configuration
+ *	@warn_threshold - warning timeout
+ *	@reset_threshold - reset timeout
+ *
+ *	Used to enable Timer Services for Kernel Watchdog Operation permitting
+ *	automatic reset or reboot of the system if tripped.
+ *	Set watchdog timer value, returns 0 on success, 1 on error
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_set_watchdog(u32 warn_threshold, u32 reset_threshold)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_write(warn_threshold, 0);
+	ipc_write(reset_threshold, 4);
+	ipc_command(2 << 16 | IPCMSG_WATCHDOG_TIMER);
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_set_watchdog);
+
+/**
+ *	intel_scu_ipc_fw_version	-	firmware information
+ *	@version: retrieved firmware version information
+ *
+ *	Read the firmware information from the SCU.
+ *
+ *	This function may sleep. Locking for SCU accesses is handled for
+ *	the caller.
+ */
+int intel_scu_ipc_fw_version(struct scu_fw_version *version)
+{
+	u32 err = 0;
+
+	mutex_lock(&ipclock);
+	if (ipcdev.pdev == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENODEV;
+	}
+	ipc_command(IPCMSG_FW_REVISION);
+	err = busy_loop();
+	version->rtMinorVer = ipc_readb(0);
+	version->rtMajorVer = ipc_readb(1);
+	version->romMinorVer = ipc_readb(2);
+	version->romMajorVer = ipc_readb(3);
+	version->punitMinorVer = ipc_readb(4);
+	version->punitMajorVer = ipc_readb(5);
+	version->iaMinorVer = ipc_readb(6);
+	version->iaMajorVer = ipc_readb(7);
+	version->ftlMinorVer = ipc_readb(8);
+	version->ftlMajorVer = ipc_readb(9);
+	version->valMinorVer = ipc_readb(10);
+	version->valMajorVer = ipc_readb(11);
+	/* Bytes 12 and 13 are unused */
+	version->ifwiMinorVer = ipc_readb(14);
+	version->ifwiMajorVer = ipc_readb(15);
+	mutex_unlock(&ipclock);
+	return err;
+}
+EXPORT_SYMBOL(intel_scu_ipc_fw_version);
+
+/*I2C commands */
+#define IPC_I2C_WRITE 1 /* I2C Write command */
+#define IPC_I2C_READ  2 /* I2C Read command */
+
+/**
+ *	intel_scu_ipc_i2c_cntrl		-	I2C read/write operations
+ *	@addr: I2C address + command bits
+ *	@data: data to read/write
+ *
+ *	Perform an an I2C read/write operation via the SCU. All locking is
+ *	handled for the caller. This function may sleep.
+ *
+ *	Returns an error code or 0 on success.
+ */
+int intel_scu_ipc_i2c_cntrl(u32 addr, u32 *data)
+{
+	u32 cmd = 0;
+
+	mutex_lock(&ipclock);
+	cmd = (addr >> 24) & 0xFF;
+	if (cmd == IPC_I2C_READ) {
+		writel(addr, IPC_I2C_CNTRL_ADDR);
+		mdelay(1);/*Write Not getting updated without delay*/
+		*data = readl(I2C_DATA_ADDR);
+	} else if (cmd == IPC_I2C_WRITE) {
+		writel(addr, I2C_DATA_ADDR);
+		mdelay(1);
+		writel(addr, IPC_I2C_CNTRL_ADDR);
+	} else {
+		dev_err(&ipcdev.pdev->dev,
+			"intel_scu_ipc: I2C INVALID_CMD = 0x%x\n", cmd);
+
+		mutex_unlock(&ipclock);
+		return -1;
+	}
+	mutex_unlock(&ipclock);
+	return 0;
+}
+EXPORT_SYMBOL(intel_scu_ipc_i2c_cntrl);
+
+#define IPC_FW_LOAD_ADDR 0xFFFC0000 /* Storage location for FW image */
+#define IPC_FW_UPDATE_MBOX_ADDR 0xFFFFDFF4 /* Mailbox between ipc and scu */
+#define IPC_MAX_FW_SIZE 262144 /* 256K storage size for loading the FW image */
+#define IPC_FW_MIP_HEADER_SIZE 2048 /* Firmware MIP header size */
+/* IPC inform SCU to get ready for update process */
+#define IPC_CMD_FW_UPDATE_READY  0x10FE
+/* IPC inform SCU to go for update process */
+#define IPC_CMD_FW_UPDATE_GO     0x20FE
+/* Status code for fw update */
+#define IPC_FW_UPDATE_SUCCESS	0x444f4e45 /* Status code 'DONE' */
+#define IPC_FW_UPDATE_BADN	0x4241444E /* Status code 'BADN' */
+#define IPC_FW_TXHIGH		0x54784849 /* Status code 'IPC_FW_TXHIGH' */
+#define IPC_FW_TXLOW		0x54784c4f /* Status code 'IPC_FW_TXLOW' */
+
+struct fw_update_mailbox {
+	u32    status;
+	u32    scu_flag;
+	u32    driver_flag;
+};
+
+
+/**
+ *	intel_scu_ipc_fw_update	-	 Firmware update utility
+ *	@buffer: firmware buffer
+ *	@length: size of firmware buffer
+ *
+ *	This function provides an interface to load the firmware into
+ *	the SCU. Returns 0 on success or -1 on failure
+ */
+int intel_scu_ipc_fw_update(u8 *buffer, u32 length)
+{
+	void __iomem *fw_update_base;
+	void __iomem *mailbox_base;
+	int retry_cnt = 0;
+
+	struct fw_update_mailbox *mailbox = NULL;
+
+	mutex_lock(&ipclock);
+	fw_update_base = ioremap_nocache(IPC_FW_LOAD_ADDR, (128*1024));
+	if (fw_update_base == NULL) {
+		mutex_unlock(&ipclock);
+		return -ENOMEM;
+	}
+	mailbox_base = ioremap_nocache(IPC_FW_UPDATE_MBOX_ADDR,
+					sizeof(struct fw_update_mailbox));
+	if (mailbox_base == NULL) {
+		iounmap(fw_update_base);
+		mutex_unlock(&ipclock);
+		return -ENOMEM;
+	}
+
+	mailbox = (struct fw_update_mailbox *)mailbox_base;
+
+	ipc_command(IPC_CMD_FW_UPDATE_READY);
+
+	/* Intitialize mailbox */
+	mailbox->status = 0;
+	mailbox->scu_flag = 0;
+	mailbox->driver_flag = 0;
+
+	/* Driver copies the 2KB MIP header to SRAM at 0xFFFC0000*/
+	memcpy_toio((u8 *)(fw_update_base), buffer, 0x800);
+
+	/* Driver sends "FW Update" IPC command (CMD_ID 0xFE; MSG_ID 0x02).
+	* Upon receiving this command, SCU will write the 2K MIP header
+	* from 0xFFFC0000 into NAND.
+	* SCU will write a status code into the Mailbox, and then set scu_flag.
+	*/
+
+	ipc_command(IPC_CMD_FW_UPDATE_GO);
+
+	/*Driver stalls until scu_flag is set */
+	while (mailbox->scu_flag != 1) {
+		rmb();
+		mdelay(1);
+	}
+
+	/* Driver checks Mailbox status.
+	 * If the status is 'BADN', then abort (bad NAND).
+	 * If the status is 'IPC_FW_TXLOW', then continue.
+	 */
+	while (mailbox->status != IPC_FW_TXLOW) {
+		rmb();
+		mdelay(10);
+	}
+	mdelay(10);
+
+update_retry:
+	if (retry_cnt > 5)
+		goto update_end;
+
+	if (mailbox->status != IPC_FW_TXLOW)
+		goto update_end;
+	buffer = buffer+0x800;
+	memcpy_toio((u8 *)(fw_update_base), buffer, 0x20000);
+	mailbox->driver_flag = 0x1;
+	while (mailbox->scu_flag == 1) {
+		rmb();
+		mdelay(1);
+	}
+
+	/* check for 'BADN' */
+	if (mailbox->status == IPC_FW_UPDATE_BADN)
+		goto update_end;
+
+	while (mailbox->status != IPC_FW_TXHIGH) {
+		rmb();
+		mdelay(10);
+	}
+	mdelay(10);
+
+	if (mailbox->status != IPC_FW_TXHIGH)
+		goto update_end;
+	buffer = buffer+0x20000;
+	memcpy_toio((u8 *)(fw_update_base), buffer, 0x20000);
+	mailbox->driver_flag = 0;
+	while (mailbox->scu_flag == 0) {
+		rmb();
+		mdelay(1);
+	}
+
+	/* check for 'BADN' */
+	if (mailbox->status == IPC_FW_UPDATE_BADN)
+		goto update_end;
+
+	if (mailbox->status == IPC_FW_TXLOW) {
+		++retry_cnt;
+		goto update_retry;
+	}
+
+update_end:
+	iounmap(fw_update_base);
+	iounmap(mailbox_base);
+	mutex_unlock(&ipclock);
+	if (mailbox->status == IPC_FW_UPDATE_SUCCESS)
+		return 0;
+	return -1;
+}
+EXPORT_SYMBOL(intel_scu_ipc_fw_update);
+
+/*
+ * Interrupt handler gets called when ioc bit of IPC_COMMAND_REG set to 1
+ * When ioc bit is set to 1, caller api must wait for interrupt handler called
+ * which in turn unlocks the caller api. Currently this is not used
+ *
+ * This is edge triggered so we need take no action to clear anything
+ */
+static irqreturn_t ioc(int irq, void *dev_id)
+{
+	return IRQ_HANDLED;
+}
+
+/**
+ *	ipc_probe	-	probe an Intel SCU IPC
+ *	@dev: the PCI device matching
+ *	@id: entry in the match table
+ *
+ *	Enable and install an intel SCU IPC. This appears in the PCI space
+ *	but uses some hard coded addresses as well.
+ */
+static int ipc_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+	int err;
+	resource_size_t pci_resource;
+
+	if (ipcdev.pdev)		/* We support only one SCU */
+		return -EBUSY;
+
+	ipcdev.pdev = pci_dev_get(dev);
+
+	err = pci_enable_device(dev);
+	if (err)
+		return err;
+
+	err = pci_request_regions(dev, "ipc_mrst");
+	if (err)
+		return err;
+
+	pci_resource = pci_resource_start(dev, 0);
+	if (!pci_resource)
+		return -ENOMEM;
+
+	if (request_irq(dev->irq, ioc, 0, "ipc_mrst", &ipcdev))
+		return -EBUSY;
+
+	ipcdev.ipc_base = ioremap_nocache(IPC_BASE_ADDR, IPC_MAX_ADDR);
+	if (!ipcdev.ipc_base)
+		return -ENOMEM;
+
+	ipcdev.i2c_base = ioremap_nocache(IPC_I2C_BASE, IPC_I2C_MAX_ADDR);
+	if (!ipcdev.i2c_base) {
+		iounmap(ipcdev.ipc_base);
+		return -ENOMEM;
+	}
+
+	ipcdev.pci_base = ioremap_nocache(pci_resource, 0x1000);
+	if (!ipcdev.pci_base) {
+		iounmap(ipcdev.ipc_base);
+		iounmap(ipcdev.i2c_base);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/**
+ *	ipc_remove	-	remove a bound IPC device
+ *	@pdev: PCI device
+ *
+ *	In practice the SCU is not removable but this function is also
+ *	called for each device on a module unload or cleanup which is the
+ *	path that will get used.
+ *
+ *	Free up the mappings and release the PCI resources
+ */
+static void ipc_remove(struct pci_dev *pdev)
+{
+	free_irq(pdev->irq, &ipcdev);
+	pci_release_regions(pdev);
+	pci_dev_put(ipcdev.pdev);
+	iounmap(ipcdev.ipc_base);
+	iounmap(ipcdev.i2c_base);
+	iounmap(ipcdev.pci_base);
+	ipcdev.pdev = NULL;
+}
+
+static const struct pci_device_id pci_ids[] = {
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x080e)},
+	{ 0,}
+};
+MODULE_DEVICE_TABLE(pci, pci_ids);
+
+static struct pci_driver ipc_driver = {
+	.name = "ipc_mrst",
+	.id_table = pci_ids,
+	.probe = ipc_probe,
+	.remove = ipc_remove,
+};
+
+
+static int __init intel_scu_ipc_init(void)
+{
+	return  pci_register_driver(&ipc_driver);
+}
+
+static void __exit intel_scu_ipc_exit(void)
+{
+	pci_unregister_driver(&ipc_driver);
+}
+
+MODULE_AUTHOR("Sreedhara DS <sreedhara.ds@intel.com>");
+MODULE_DESCRIPTION("Intel SCU IPC driver");
+MODULE_LICENSE("GPL");
+
+module_init(intel_scu_ipc_init);
+module_exit(intel_scu_ipc_exit);


^ permalink raw reply related	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2010-05-07 18:25 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-04-09 10:29 [PATCH] IPC driver for Intel Mobile Internet Device (MID) platforms Alan Cox
2010-04-21 20:16 ` Andrew Morton
2010-04-21 20:26   ` Alan Cox
2010-04-22 13:16   ` Alan Cox
2010-04-22 13:41     ` Andrew Morton
2010-04-21 12:25 Alan Cox
2010-04-23 14:30 Alan Cox
2010-04-26 15:09 ` Matthew Garrett
2010-04-26 14:55   ` Alan Cox
2010-04-26 15:43     ` Matthew Garrett
2010-04-26 15:39       ` Alan Cox
2010-04-26 16:22         ` Matthew Garrett
2010-04-26 16:28           ` Alan Cox
2010-04-26 17:06             ` Matthew Garrett
2010-04-26 17:13               ` Alan Cox
2010-05-07 18:25                 ` Matthew Garrett

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.