[PATCH] drivers: PMC MSP71xx TWI driver

From: Marc St-Jean
Date: Thu Mar 08 2007 - 18:24:43 EST


[PATCH] drivers: PMC MSP71xx TWI driver

Patch to add TWI driver for the PMC-Sierra MSP71xx devices.

This patch references some platform support files previously
submitted to the linux-mips@xxxxxxxxxxxxxx list.

Thanks,
Marc

Signed-off-by: Marc St-Jean <Marc_St-Jean@xxxxxxxxxxxxxx>
---
Re-posting patch with recommended changes:
-Cleanup on style and formatting for comments, macros, etc.
-Moved some driver private data structures from .h to .c.
-Removed casting of RHS void *.
-Changed priv_data.lock from a semaphore to a mutex.

drivers/i2c/algos/Kconfig | 9
drivers/i2c/algos/Makefile | 1
drivers/i2c/algos/i2c-algo-pmctwi.c | 197 ++++++++++++++++
drivers/i2c/busses/Kconfig | 7
drivers/i2c/busses/Makefile | 1
drivers/i2c/busses/i2c-pmcmsp.c | 419 ++++++++++++++++++++++++++++++++++++
include/linux/i2c-algo-pmctwi.h | 135 +++++++++++
7 files changed, 768 insertions(+), 1 deletion(-)

diff --git a/drivers/i2c/algos/Kconfig b/drivers/i2c/algos/Kconfig
index af02034..794f7bb 100644
--- a/drivers/i2c/algos/Kconfig
+++ b/drivers/i2c/algos/Kconfig
@@ -49,5 +49,12 @@ config I2C_ALGO_SGI
Supports the SGI interfaces like the ones found on SGI Indy VINO
or SGI O2 MACE.

-endmenu
+config I2C_ALGO_PMCTWI
+ tristate "I2C PMC TWI interfaces"
+ depends on I2C && PMC_MSP
+ help
+ Implements the PMC TWI SoC algorithm for various implementations.

+ Be sure to select the proper bus for your platform below.
+
+endmenu
diff --git a/drivers/i2c/algos/Makefile b/drivers/i2c/algos/Makefile
index cac1051..2645d00 100644
--- a/drivers/i2c/algos/Makefile
+++ b/drivers/i2c/algos/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_I2C_ALGOBIT) += i2c-algo-bit.o
obj-$(CONFIG_I2C_ALGOPCF) += i2c-algo-pcf.o
obj-$(CONFIG_I2C_ALGOPCA) += i2c-algo-pca.o
obj-$(CONFIG_I2C_ALGO_SGI) += i2c-algo-sgi.o
+obj-$(CONFIG_I2C_ALGO_PMCTWI) += i2c-algo-pmctwi.o

ifeq ($(CONFIG_I2C_DEBUG_ALGO),y)
EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/i2c/algos/i2c-algo-pmctwi.c b/drivers/i2c/algos/i2c-algo-pmctwi.c
new file mode 100644
index 0000000..160f0e9
--- /dev/null
+++ b/drivers/i2c/algos/i2c-algo-pmctwi.c
@@ -0,0 +1,197 @@
+/*
+ * Device-independent algorithm for using PMC-TWI SoC busses.
+ *
+ * See drivers/i2c/busses/i2c-pmcmsp.c for an example implementation.
+ *
+ * Copyright 2005-2007 PMC-Sierra, Inc.
+ *
+ * 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; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-pmctwi.h>
+
+/*
+ * Sends an i2c command out on the adapter
+ * Return -1 on error.
+ */
+static int pmctwi_master_xfer(struct i2c_adapter *adap,
+ struct i2c_msg *m, int num)
+{
+ struct pmctwi_data *busops = adap->algo_data;
+ int i, ret = 0, dual, probe;
+ struct i2c_msg *cmsg, *nmsg;
+ u8 detect_buffer[] = { 0 };
+
+ for (i = 0; i < num; i++) {
+ struct pmctwi_cmd cmd;
+ struct pmctwi_cfg oldcfg, newcfg;
+
+ probe = 0;
+ dual = 0;
+ cmsg = m + i;
+ nmsg = NULL;
+
+ if ((num - i) >= 2) {
+ /* Check for a dual write-then-read command */
+ nmsg = cmsg + 1;
+ dual = !((cmsg->flags & I2C_M_RD)) &&
+ (nmsg->flags & I2C_M_RD) &&
+ (cmsg->addr == nmsg->addr);
+ }
+
+ if (dual)
+ dev_dbg(&adap->dev, "Doing ops %d&%d of %d\n",
+ (i + 1), (i + 2), num);
+ else
+ dev_dbg(&adap->dev, "Doing op %d of %d\n",
+ (i + 1), num);
+
+ if (dual) {
+ cmd.type = PMCTWI_CMD_WRITE_READ;
+ cmd.write_len = cmsg->len;
+ cmd.write_data = (u8*)cmsg->buf;
+ cmd.read_len = nmsg->len;
+ cmd.read_data = (u8*)nmsg->buf;
+ } else if (cmsg->flags & I2C_M_RD) {
+ cmd.type = PMCTWI_CMD_READ;
+ cmd.read_len = cmsg->len;
+ cmd.read_data = (u8*)cmsg->buf;
+ cmd.write_len = 0;
+ cmd.write_data = NULL;
+ } else {
+ cmd.type = PMCTWI_CMD_WRITE;
+ cmd.read_len = 0;
+ cmd.read_data = NULL;
+ cmd.write_len = cmsg->len;
+ cmd.write_data = (u8*)cmsg->buf;
+ }
+
+ if (cmsg->len == 0) {
+ if (cmsg->flags & I2C_M_RD) {
+ dev_dbg(&adap->dev,
+ "Read of 0 bytes! (illegal!)\n");
+ return -1;
+ } else {
+ dev_dbg(&adap->dev,
+ "Probing for slave at 0x%02x\n",
+ (cmsg->addr & 0xff));
+ probe = 1;
+
+ /*
+ * Probe is a special read of 1 byte.
+ * We don't care about the result, we just
+ * want to see that it is successful.
+ */
+ cmd.write_len = 1;
+ cmd.write_data = detect_buffer;
+ cmd.read_len = 1;
+ cmd.read_data = NULL;
+ }
+ }
+
+ cmd.addr = cmsg->addr;
+
+ if (probe || (cmsg->flags & I2C_M_TEN)) {
+ busops->get_twi_config(&newcfg, busops->data);
+ busops->get_twi_config(&oldcfg, busops->data);
+
+ /* For probes, we don't want any retries */
+ if (probe)
+ newcfg.nak = 0;
+
+ /* Set the special 10-bit address flag, if required */
+ if (cmsg->flags & I2C_M_TEN)
+ newcfg.add10 = 1;
+
+ busops->set_twi_config(&newcfg, busops->data);
+ }
+
+ ret = busops->xfer_cmd(&cmd, busops->data);
+
+ if (probe || (cmsg->flags & I2C_M_TEN))
+ busops->set_twi_config(&oldcfg, busops->data);
+
+ switch (ret) {
+ case PMCTWI_XFER_LOST_ARBITRATION:
+ dev_dbg(&adap->dev, "We lost arbitration: "
+ "Could not become bus master\n");
+ break;
+ case PMCTWI_XFER_NO_RESPONSE:
+ dev_dbg(&adap->dev, "No response\n");
+ break;
+ case PMCTWI_XFER_DATA_COLLISION:
+ dev_dbg(&adap->dev, "Data collision\n");
+ break;
+ case PMCTWI_XFER_BUSY:
+ dev_dbg(&adap->dev, "Port was busy\n");
+ /*
+ * TODO: We could potentially loop and retry
+ * in this case
+ */
+ break;
+ case PMCTWI_XFER_TIMEOUT:
+ dev_dbg(&adap->dev, "Transfer timeout\n");
+ break;
+ }
+ if (ret != PMCTWI_XFER_OK) {
+ if (probe)
+ dev_dbg(&adap->dev, "Probe failed\n");
+ return -1;
+ }
+
+ if (dual) {
+ dev_dbg(&adap->dev,
+ "SMBus read 0x%02x from reg 0x%02x\n",
+ nmsg->buf[0], cmsg->buf[0]);
+
+ /* Skip one more ahead, since we just did 2 commands */
+ i++;
+ } else {
+ if (probe)
+ dev_dbg(&adap->dev, "Probe successful\n");
+ else
+ dev_dbg(&adap->dev,
+ "I2C %s %d bytes successfully\n",
+ (cmsg->flags & I2C_M_RD) ?
+ "read" : "wrote",
+ cmsg->len);
+ }
+ }
+
+ return 0;
+}
+
+static u32 pmctwi_i2c_func(struct i2c_adapter *adapter)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR | I2C_FUNC_SMBUS_EMUL;
+}
+
+struct i2c_algorithm pmctwi_algorithm = {
+ .master_xfer = pmctwi_master_xfer,
+ .smbus_xfer = NULL,
+ .algo_control = NULL, /* TODO: someday */
+ .functionality = pmctwi_i2c_func,
+};
+
+EXPORT_SYMBOL(pmctwi_algorithm);
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 4d44a2d..f2998b8 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -573,4 +573,11 @@ config I2C_PNX
This driver can also be built as a module. If so, the module
will be called i2c-pnx.

+config I2C_PMCMSP
+ tristate "PMC MSP I2C Controller"
+ depends on I2C && PMC_MSP
+ select I2C_ALGO_PMCTWI
+ help
+ Supports the special PMC TWI SoC chip on the MSP platform
+
endmenu
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 03505aa..8923878 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -30,6 +30,7 @@ obj-$(CONFIG_I2C_PARPORT_LIGHT) += i2c-parport-light.o
obj-$(CONFIG_I2C_PASEMI) += i2c-pasemi.o
obj-$(CONFIG_I2C_PCA_ISA) += i2c-pca-isa.o
obj-$(CONFIG_I2C_PIIX4) += i2c-piix4.o
+obj-$(CONFIG_I2C_PMCMSP) += i2c-pmcmsp.o
obj-$(CONFIG_I2C_PNX) += i2c-pnx.o
obj-$(CONFIG_I2C_PROSAVAGE) += i2c-prosavage.o
obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
diff --git a/drivers/i2c/busses/i2c-pmcmsp.c b/drivers/i2c/busses/i2c-pmcmsp.c
new file mode 100644
index 0000000..a8b9cfa
--- /dev/null
+++ b/drivers/i2c/busses/i2c-pmcmsp.c
@@ -0,0 +1,419 @@
+/*
+ * Specific bus support for PMC-TWI compliant implementation on 7120 chip
+ *
+ * Copyright 2005-2007 PMC-Sierra, Inc.
+ *
+ * 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; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/completion.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-pmctwi.h>
+#include <linux/mutex.h>
+
+#include <asm/io.h>
+
+#include <msp_regs.h>
+#include <msp_cic_int.h>
+
+#define MSP_TW_SF_CLK_REG_OFFSET 0x00
+#define MSP_TW_HS_CLK_REG_OFFSET 0x04
+#define MSP_TW_CFG_REG_OFFSET 0x08
+#define MSP_TW_CMD_REG_OFFSET 0x0c
+#define MSP_TW_ADD_REG_OFFSET 0x10
+#define MSP_TW_DAT_0_REG_OFFSET 0x14
+#define MSP_TW_DAT_1_REG_OFFSET 0x18
+#define MSP_TW_INT_STS_REG_OFFSET 0x1c
+#define MSP_TW_INT_MSK_REG_OFFSET 0x20
+#define MSP_TW_BUSY_REG_OFFSET 0x24
+#define MSP_TW_REG_SIZE 0x28
+
+#define MSP_TW_INT_STS_DONE (1 << 0)
+#define MSP_TW_INT_STS_LOST_ARBITRATION (1 << 1)
+#define MSP_TW_INT_STS_NO_RESPONSE (1 << 2)
+#define MSP_TW_INT_STS_DATA_COLLISION (1 << 3)
+#define MSP_TW_INT_STS_BUSY (1 << 4)
+#define MSP_TW_INT_STS_ALL 0x1f
+
+#define MSP_MAX_BYTES_PER_RW 8
+#define MSP_MAX_POLL 5
+#define MSP_POLL_DELAY 10
+#define MSP_IRQ_TIMEOUT (MSP_MAX_POLL * MSP_POLL_DELAY)
+
+#define MSP_TW_IRQ MSP_INT_2WIRE
+/* Use the following instead to disable interrupt mode */
+/* #define MSP_TW_IRQ 0 */
+
+/* IO Operation macros */
+#define pmcmsp_twi_readl __raw_readl
+#define pmcmsp_twi_writel __raw_writel
+
+struct pmcmsp_priv_data {
+ void __iomem *iobase; /* iomapped base for IO */
+ enum pmctwi_xfer_result last_result; /* result of last xfer */
+ int irq; /* IRQ to use (0 disables) */
+ struct completion wait; /* Completion struct for xfer */
+ struct mutex lock; /* Used for threadsafeness */
+};
+
+static struct i2c_adapter pmcmsp_twi_adapter;
+
+/* The default settings */
+const static struct pmctwi_clockcfg pmctwi_defclockcfg = {
+ .standard = {
+ .filter = 0x3,
+ .clock = 0x1f,
+ },
+ .highspeed = {
+ .filter = 0x3,
+ .clock = 0x1f,
+ },
+};
+
+const static struct pmctwi_cfg pmctwi_deftwicfg = {
+ .arbf = 0x03,
+ .nak = 0x03,
+ .add10 = 0x00,
+ .mst_code = 0x00,
+ .arb = 0x01,
+ .highspeed = 0x00,
+};
+
+/*
+ * Gets the current clock configuration
+ */
+static void pmcmsp_get_clock_config(struct pmctwi_clockcfg *cfg, void *data)
+{
+ struct pmcmsp_priv_data *p = data;
+
+ mutex_lock(&p->lock);
+ pmctwi_reg_to_clock(pmcmsp_twi_readl(p->iobase +
+ MSP_TW_SF_CLK_REG_OFFSET),
+ &(cfg->standard));
+ pmctwi_reg_to_clock(pmcmsp_twi_readl(p->iobase +
+ MSP_TW_HS_CLK_REG_OFFSET),
+ &(cfg->highspeed));
+ mutex_unlock(&p->lock);
+}
+
+/*
+ * Sets the current clock configuration
+ */
+static void pmcmsp_set_clock_config(const struct pmctwi_clockcfg *cfg,
+ void *data)
+{
+ struct pmcmsp_priv_data *p = data;
+
+ mutex_lock(&p->lock);
+ pmcmsp_twi_writel(pmctwi_clock_to_reg(&(cfg->standard)),
+ p->iobase + MSP_TW_SF_CLK_REG_OFFSET);
+ pmcmsp_twi_writel(pmctwi_clock_to_reg(&(cfg->highspeed)),
+ p->iobase + MSP_TW_HS_CLK_REG_OFFSET);
+ mutex_unlock(&p->lock);
+}
+
+/*
+ * Gets the current TWI bus configuration
+ */
+static void pmcmsp_get_twi_config(struct pmctwi_cfg *cfg, void *data)
+{
+ struct pmcmsp_priv_data *p = data;
+
+ mutex_lock(&p->lock);
+ pmctwi_reg_to_cfg(pmcmsp_twi_readl(
+ p->iobase + MSP_TW_CFG_REG_OFFSET), cfg);
+ mutex_unlock(&p->lock);
+}
+
+/*
+ * Sets the current TWI bus configuration
+ */
+static void pmcmsp_set_twi_config(const struct pmctwi_cfg *cfg, void *data)
+{
+ struct pmcmsp_priv_data *p = data;
+
+ mutex_lock(&p->lock);
+ pmcmsp_twi_writel(pmctwi_cfg_to_reg(cfg),
+ p->iobase + MSP_TW_CFG_REG_OFFSET);
+ mutex_unlock(&p->lock);
+}
+
+/*
+ * Parses the 'int_sts' register and returns a well-defined error code
+ */
+static enum pmctwi_xfer_result pmcmsp_get_result(u32 reg)
+{
+ if (reg & MSP_TW_INT_STS_LOST_ARBITRATION) {
+ dev_dbg(&pmcmsp_twi_adapter.dev,
+ " Result: Lost arbitration\n");
+ return PMCTWI_XFER_LOST_ARBITRATION;
+ } else if (reg & MSP_TW_INT_STS_NO_RESPONSE) {
+ dev_dbg(&pmcmsp_twi_adapter.dev,
+ " Result: No response\n");
+ return PMCTWI_XFER_NO_RESPONSE;
+ } else if (reg & MSP_TW_INT_STS_DATA_COLLISION) {
+ dev_dbg(&pmcmsp_twi_adapter.dev,
+ " Result: Data collision\n");
+ return PMCTWI_XFER_DATA_COLLISION;
+ } else if (reg & MSP_TW_INT_STS_BUSY) {
+ dev_dbg(&pmcmsp_twi_adapter.dev,
+ " Result: Bus busy\n");
+ return PMCTWI_XFER_BUSY;
+ }
+
+ dev_dbg(&pmcmsp_twi_adapter.dev, " Result: Operation succeeded\n");
+ return PMCTWI_XFER_OK;
+}
+
+/*
+ * Polls the 'busy' register until the command is complete
+ * Note: Assumes p->lock is held.
+ */
+static void pmcmsp_poll_complete(struct pmcmsp_priv_data* p)
+{
+ int i;
+ u32 val = 0;
+
+ for (i = 0; i < MSP_MAX_POLL; i++) {
+ val = pmcmsp_twi_readl(p->iobase + MSP_TW_BUSY_REG_OFFSET);
+ if (val == 0) {
+ u32 reason = pmcmsp_twi_readl(p->iobase +
+ MSP_TW_INT_STS_REG_OFFSET);
+ pmcmsp_twi_writel(reason, p->iobase +
+ MSP_TW_INT_STS_REG_OFFSET);
+ p->last_result = pmcmsp_get_result(reason);
+ return;
+ }
+ udelay(MSP_POLL_DELAY);
+ }
+
+ dev_dbg(&pmcmsp_twi_adapter.dev, " Result: Poll timeout\n");
+ p->last_result = PMCTWI_XFER_TIMEOUT;
+}
+
+/*
+ * In interrupt mode, handle the interrupt
+ * Note: Assumes p->lock is held.
+ */
+static irqreturn_t pmcmsp_interrupt(int irq, void* data)
+{
+ struct pmcmsp_priv_data *p = data;
+
+ u32 reason = pmcmsp_twi_readl(p->iobase + MSP_TW_INT_STS_REG_OFFSET);
+ pmcmsp_twi_writel(reason, p->iobase + MSP_TW_INT_STS_REG_OFFSET);
+
+ dev_dbg(&pmcmsp_twi_adapter.dev, " Got interrupt 0x%08x\n",
+ reason);
+ if (!(reason & MSP_TW_INT_STS_DONE))
+ return IRQ_NONE;
+
+ p->last_result = pmcmsp_get_result(reason);
+ complete(&p->wait);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Do the transfer (low level):
+ * - May use interrupt-driven or polling, depending on if an IRQ is
+ * presently registered
+ * Note: Assumes p->lock is held
+ */
+static enum pmctwi_xfer_result pmcmsp_do_xfer(u32 reg,
+ struct pmcmsp_priv_data *p)
+{
+ dev_dbg(&pmcmsp_twi_adapter.dev, " Writing cmd reg 0x%08x\n", reg);
+ pmcmsp_twi_writel(reg, p->iobase + MSP_TW_CMD_REG_OFFSET);
+ if (p->irq) {
+ unsigned long timeleft = wait_for_completion_timeout(
+ &p->wait, MSP_IRQ_TIMEOUT);
+ if (timeleft == 0) {
+ dev_dbg(&pmcmsp_twi_adapter.dev,
+ " Result: IRQ timeout\n");
+ complete(&p->wait);
+ p->last_result = PMCTWI_XFER_TIMEOUT;
+ }
+ } else
+ pmcmsp_poll_complete(p);
+
+ return p->last_result;
+}
+
+/*
+ * Helper routine, converts 'pmctwi_cmd' struct to register format
+ */
+static inline u32 pmcmsp_cmd_to_reg(const struct pmctwi_cmd *cmd)
+{
+ return (u32)(((cmd->type & 0x3) << 8) |
+ (((cmd->write_len - 1) & 0x7) << 4) |
+ (((cmd->read_len - 1) & 0x7) << 0));
+}
+
+/*
+ * Do the transfer (high level)
+ */
+static enum pmctwi_xfer_result pmcmsp_xfer_cmd(struct pmctwi_cmd *cmd,
+ void *data)
+{
+ struct pmcmsp_priv_data *p = data;
+ u64 *write_data, *read_data;
+ enum pmctwi_xfer_result retval;
+
+ write_data = (u64*)cmd->write_data;
+ read_data = (u64*)cmd->read_data;
+
+ if ((cmd->type == PMCTWI_CMD_WRITE && cmd->write_len == 0) ||
+ (cmd->type == PMCTWI_CMD_READ && cmd->read_len == 0) ||
+ (cmd->type == PMCTWI_CMD_WRITE_READ &&
+ (cmd->read_len == 0 || cmd->write_len == 0))) {
+ printk(KERN_ERR "%s: Cannot transfer less than 1 byte\n",
+ __FUNCTION__);
+ return -1;
+ }
+
+ if ((cmd->read_len > MSP_MAX_BYTES_PER_RW) ||
+ (cmd->write_len > MSP_MAX_BYTES_PER_RW)) {
+ printk(KERN_ERR "%s: Cannot transfer more than %d bytes\n",
+ __FUNCTION__, MSP_MAX_BYTES_PER_RW);
+ }
+
+ mutex_lock(&p->lock);
+ dev_dbg(&pmcmsp_twi_adapter.dev, "Setting address to 0x%08x\n",
+ cmd->addr);
+ pmcmsp_twi_writel(cmd->addr, p->iobase + MSP_TW_ADD_REG_OFFSET);
+
+ if ((cmd->type == PMCTWI_CMD_WRITE) ||
+ (cmd->type == PMCTWI_CMD_WRITE_READ)) {
+ u64 tmp = be64_to_cpu(*write_data);
+ tmp >>= (8 - cmd->write_len) * 8;
+ dev_dbg(&pmcmsp_twi_adapter.dev, "Writing 0x%016llx\n", tmp);
+ pmcmsp_twi_writel((u32)(tmp & 0x00000000ffffffffLL),
+ p->iobase + MSP_TW_DAT_0_REG_OFFSET);
+ if (cmd->write_len > 4)
+ pmcmsp_twi_writel((u32)(tmp >> 32),
+ p->iobase + MSP_TW_DAT_1_REG_OFFSET);
+ }
+
+ retval = pmcmsp_do_xfer(pmcmsp_cmd_to_reg(cmd), p);
+
+ if ((cmd->type == PMCTWI_CMD_READ) ||
+ (cmd->type == PMCTWI_CMD_WRITE_READ)) {
+ int i;
+ u64 rmsk = ~(0xffffffffffffffffLL << (cmd->read_len*8));
+ u64 tmp = (u64)pmcmsp_twi_readl(p->iobase +
+ MSP_TW_DAT_0_REG_OFFSET);
+ if (cmd->read_len > 4)
+ tmp |= (u64)pmcmsp_twi_readl(p->iobase +
+ MSP_TW_DAT_1_REG_OFFSET) << 32;
+ tmp &= rmsk;
+ dev_dbg(&pmcmsp_twi_adapter.dev, "Read 0x%016llx\n", tmp);
+
+ for (i = 0; i < cmd->read_len; i++)
+ cmd->read_data[i] = (u8)((tmp >> i) & 0xff);
+ }
+ mutex_unlock(&p->lock);
+
+ return retval;
+}
+
+static struct pmcmsp_priv_data pmcmsp_priv;
+
+static struct pmctwi_data pmcmsp_twi_algdata = {
+ .data = (void*)&pmcmsp_priv,
+ .get_clock_config = pmcmsp_get_clock_config,
+ .set_clock_config = pmcmsp_set_clock_config,
+ .get_twi_config = pmcmsp_get_twi_config,
+ .set_twi_config = pmcmsp_set_twi_config,
+ .xfer_cmd = pmcmsp_xfer_cmd,
+};
+
+static struct i2c_adapter pmcmsp_twi_adapter = {
+ .owner = THIS_MODULE,
+ .class = I2C_CLASS_HWMON,
+ .algo = &pmctwi_algorithm,
+ .algo_data = &pmcmsp_twi_algdata,
+ .name = "PMC MSP TWI/SMB/I2C driver",
+};
+
+static int __init pmcmsp_twi_init(void)
+{
+ pmcmsp_priv.iobase = ioremap(MSP_TWI_BASE, MSP_TW_REG_SIZE);
+ if (pmcmsp_priv.iobase == NULL)
+ return -ENOMEM;
+
+ init_completion(&pmcmsp_priv.wait);
+ mutex_init(&pmcmsp_priv.lock);
+ pmcmsp_priv.irq = MSP_TW_IRQ;
+
+ if (pmcmsp_priv.irq) {
+ int rc = request_irq(pmcmsp_priv.irq, pmcmsp_interrupt,
+ SA_SHIRQ | SA_INTERRUPT | SA_SAMPLE_RANDOM,
+ "TWI", &pmcmsp_priv);
+ if (rc == 0) {
+ /*
+ * Enable 'DONE' interrupt only
+ *
+ * If you enable all interrupts, you will get one on
+ * error and another when the operation completes.
+ * This way you only have to handle one interrupt,
+ * but you can still check all result flags.
+ */
+ pmcmsp_twi_writel(MSP_TW_INT_STS_DONE,
+ pmcmsp_priv.iobase +
+ MSP_TW_INT_MSK_REG_OFFSET);
+ } else {
+ printk(KERN_WARNING
+ "Could not assign TWI IRQ handler "
+ "to irq %d (continuing with poll)\n",
+ pmcmsp_priv.irq);
+ pmcmsp_priv.irq = 0;
+ }
+ }
+
+ pmcmsp_set_clock_config(&pmctwi_defclockcfg, &pmcmsp_priv);
+ pmcmsp_set_twi_config(&pmctwi_deftwicfg, &pmcmsp_priv);
+
+ printk(KERN_WARNING "Registering MSP7120 I2C adapter\n");
+
+ return i2c_add_adapter(&pmcmsp_twi_adapter);
+}
+
+static void __exit pmcmsp_twi_exit(void)
+{
+ if (pmcmsp_priv.irq) {
+ pmcmsp_twi_writel(0,
+ pmcmsp_priv.iobase + MSP_TW_INT_MSK_REG_OFFSET);
+ free_irq(pmcmsp_priv.irq, &pmcmsp_priv);
+ }
+ i2c_del_adapter(&pmcmsp_twi_adapter);
+}
+
+MODULE_DESCRIPTION("PMC MSP TWI/SMB/I2C driver");
+MODULE_LICENSE("GPL");
+
+module_init(pmcmsp_twi_init);
+module_exit(pmcmsp_twi_exit);
diff --git a/include/linux/i2c-algo-pmctwi.h b/include/linux/i2c-algo-pmctwi.h
new file mode 100644
index 0000000..7b6ca58
--- /dev/null
+++ b/include/linux/i2c-algo-pmctwi.h
@@ -0,0 +1,135 @@
+/*
+ * Device-independent algorithm for using PMC-TWI SoC busses.
+ *
+ * See drivers/i2c/busses/i2c-pmcmsp.c for an example implementation.
+ *
+ * Copyright 2005-2007 PMC-Sierra, Inc.
+ *
+ * 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; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef I2C_ALGO_PMCTWI_H
+#define I2C_ALGO_PMCTWI_H
+
+#include <linux/i2c.h>
+
+/* TWI command type */
+enum pmctwi_cmd_type {
+ PMCTWI_CMD_WRITE = 0, /* Write only */
+ PMCTWI_CMD_READ = 1, /* Read only */
+ PMCTWI_CMD_WRITE_READ = 2, /* Write then Read */
+ PMCTWI_CMD_RESERVED = 3,
+};
+
+/* Corresponds to a PMCTWI clock configuration register */
+struct pmctwi_clock {
+ u8 filter; /* Bits 15:12, default = 0x03 */
+ u16 clock; /* Bits 9:0, default = 0x001f */
+};
+
+static inline u32 pmctwi_clock_to_reg(const struct pmctwi_clock *clock) {
+ return (u32)(((clock->filter & 0xf) << 12) |
+ (clock->clock & 0x03ff));
+}
+
+static inline void pmctwi_reg_to_clock(u32 reg, struct pmctwi_clock *clock) {
+ clock->filter = (u8)((reg >> 12) & 0xf);
+ clock->clock = (u16)(reg & 0x03ff);
+}
+
+struct pmctwi_clockcfg {
+ struct pmctwi_clock standard; /* The standard/fast clock config */
+ struct pmctwi_clock highspeed; /* The highspeed clock config */
+};
+
+/* Corresponds to the main TWI configuration register */
+struct pmctwi_cfg {
+ u8 arbf; /* Bits 15:12, default=0x03 */
+ u8 nak; /* Bits 11:8, default=0x03 */
+ u8 add10; /* Bit 7, default=0x00 */
+ u8 mst_code; /* Bits 6:4, default=0x00 */
+ u8 arb; /* Bit 1, default=0x01 */
+ u8 highspeed; /* Bit 0, default=0x00 */
+};
+
+static inline u32 pmctwi_cfg_to_reg(const struct pmctwi_cfg *cfg) {
+ return (u32)(((cfg->arbf & 0xf) << 12) |
+ ((cfg->nak & 0xf) << 8) |
+ ((cfg->add10 & 0x1) << 7) |
+ ((cfg->mst_code & 0x7) << 4) |
+ ((cfg->arb & 0x1) << 1) |
+ ((cfg->highspeed & 0x1) << 0));
+}
+
+static inline void pmctwi_reg_to_cfg(u32 reg, struct pmctwi_cfg *cfg) {
+ cfg->arbf = (u8)((reg >> 12) & 0xf);
+ cfg->nak = (u8)((reg >> 8) & 0xf);
+ cfg->add10 = (u8)((reg >> 7) & 0x1);
+ cfg->mst_code = (u8)((reg >> 4) & 0x7);
+ cfg->arb = (u8)((reg >> 1) & 0x1);
+ cfg->highspeed = (u8)(reg & 0x1);
+}
+
+/* A single pmctwi command to issue */
+struct pmctwi_cmd {
+ u16 addr; /* The slave address (7 or 10 bits) */
+ enum pmctwi_cmd_type type; /* The command type */
+ u8 write_len; /* Number of bytes in the write buffer */
+ u8 read_len; /* Number of bytes in the read buffer */
+ u8*write_data; /* Buffer of characters to send */
+ u8 *read_data; /* Buffer to fill with incoming data */
+};
+
+/* The possible results of the xferCmd */
+enum pmctwi_xfer_result {
+ PMCTWI_XFER_OK = 0,
+ PMCTWI_XFER_TIMEOUT,
+ PMCTWI_XFER_BUSY,
+ PMCTWI_XFER_DATA_COLLISION,
+ PMCTWI_XFER_NO_RESPONSE,
+ PMCTWI_XFER_LOST_ARBITRATION,
+};
+
+/* The set of operations each bus must implement to use this algorithm */
+struct pmctwi_data {
+ void *data; /* Optional bus-specific data */
+
+ /* Get both clock configuration registers */
+ void (*get_clock_config)(struct pmctwi_clockcfg *cfg, void *data);
+
+ /* Set both clock configuration registers */
+ void (*set_clock_config)(const struct pmctwi_clockcfg *cfg,
+ void *data);
+
+ /* Get the TWI configuration register */
+ void (*get_twi_config)(struct pmctwi_cfg *cfg, void *data);
+
+ /* Set the TWI configuration register */
+ void (*set_twi_config)(const struct pmctwi_cfg *cfg, void *data);
+
+ /* Send the command, reading and/or writing all data specified */
+ enum pmctwi_xfer_result (*xfer_cmd)(struct pmctwi_cmd *cmd,
+ void *data);
+};
+
+extern struct i2c_algorithm pmctwi_algorithm;
+
+#endif /* I2C_ALGO_PMCTWI_H */
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/