* [PATCH] i2c: QUP based bus driver for Qualcomm MSM chipsets
@ 2010-07-23 2:47 Kenneth Heitke
2010-07-23 7:11 ` Trilok Soni
2010-07-26 11:05 ` srinidhi
0 siblings, 2 replies; 8+ messages in thread
From: Kenneth Heitke @ 2010-07-23 2:47 UTC (permalink / raw)
To: khali, ben-linux
Cc: linux-arm-msm, Kenneth Heitke, Crane Cai, Samuel Ortiz,
Linus Walleij, Ralf Baechle, srinidhi kasagar, linux-i2c,
linux-kernel
This bus driver supports the QUP i2c hardware controller in the Qualcomm
MSM SOCs. The Qualcomm Universal Peripheral Engine (QUP) is a general
purpose data path engine with input/output FIFOs and an embedded i2c
mini-core. The driver supports FIFO mode (for low bandwidth applications)
and block mode (interrupt generated for each block-size data transfer).
The driver currently does not support DMA transfers.
Signed-off-by: Kenneth Heitke <kheitke@codeaurora.org>
---
drivers/i2c/busses/Kconfig | 12 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-qup.c | 1080 ++++++++++++++++++++++++++++++++++++++++++
include/linux/i2c-msm.h | 29 ++
4 files changed, 1122 insertions(+), 0 deletions(-)
create mode 100644 drivers/i2c/busses/i2c-qup.c
create mode 100644 include/linux/i2c-msm.h
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index bceafbf..03d8f8f 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -521,6 +521,18 @@ config I2C_PXA_SLAVE
is necessary for systems where the PXA may be a target on the
I2C bus.
+config I2C_QUP
+ tristate "Qualcomm MSM QUP I2C Controller"
+ depends on I2C && HAVE_CLK && (ARCH_MSM7X30 || ARCH_MSM8X60 || \
+ (ARCH_QSD8X50 && MSM_SOC_REV_A))
+ help
+ If you say yes to this option, support will be included for the
+ built-in QUP I2C interface on Qualcomm MSM family processors.
+
+ The Qualcomm Universal Peripheral Engine (QUP) is a general
+ purpose data path engine with input/output FIFOs and an
+ embedded I2C mini-core.
+
config I2C_S3C2410
tristate "S3C2410 I2C Driver"
depends on ARCH_S3C2410 || ARCH_S3C64XX
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 936880b..6a52572 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -50,6 +50,7 @@ obj-$(CONFIG_I2C_PCA_PLATFORM) += i2c-pca-platform.o
obj-$(CONFIG_I2C_PMCMSP) += i2c-pmcmsp.o
obj-$(CONFIG_I2C_PNX) += i2c-pnx.o
obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
+obj-$(CONFIG_I2C_QUP) += i2c-qup.o
obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
diff --git a/drivers/i2c/busses/i2c-qup.c b/drivers/i2c/busses/i2c-qup.c
new file mode 100644
index 0000000..3d7abab
--- /dev/null
+++ b/drivers/i2c/busses/i2c-qup.c
@@ -0,0 +1,1080 @@
+/* Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+/*
+ * QUP driver for Qualcomm MSM platforms
+ *
+ */
+
+/* #define DEBUG */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/timer.h>
+#include <linux/i2c-msm.h>
+
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("0.2");
+MODULE_ALIAS("platform:i2c_qup");
+MODULE_AUTHOR("Sagar Dharia <sdharia@codeaurora.org>");
+
+/* QUP Registers */
+enum {
+ QUP_CONFIG = 0x0,
+ QUP_STATE = 0x4,
+ QUP_IO_MODE = 0x8,
+ QUP_SW_RESET = 0xC,
+ QUP_OPERATIONAL = 0x18,
+ QUP_ERROR_FLAGS = 0x1C,
+ QUP_ERROR_FLAGS_EN = 0x20,
+ QUP_MX_READ_CNT = 0x208,
+ QUP_MX_INPUT_CNT = 0x200,
+ QUP_MX_WR_CNT = 0x100,
+ QUP_OUT_DEBUG = 0x108,
+ QUP_OUT_FIFO_CNT = 0x10C,
+ QUP_OUT_FIFO_BASE = 0x110,
+ QUP_IN_READ_CUR = 0x20C,
+ QUP_IN_DEBUG = 0x210,
+ QUP_IN_FIFO_CNT = 0x214,
+ QUP_IN_FIFO_BASE = 0x218,
+ QUP_I2C_CLK_CTL = 0x400,
+ QUP_I2C_STATUS = 0x404,
+};
+
+/* QUP States and reset values */
+enum {
+ QUP_RESET_STATE = 0,
+ QUP_RUN_STATE = 1U,
+ QUP_STATE_MASK = 3U,
+ QUP_PAUSE_STATE = 3U,
+ QUP_STATE_VALID = 1U << 2,
+ QUP_I2C_MAST_GEN = 1U << 4,
+ QUP_OPERATIONAL_RESET = 0xFF0,
+ QUP_I2C_STATUS_RESET = 0xFFFFFC,
+};
+
+/* QUP OPERATIONAL FLAGS */
+enum {
+ QUP_OUT_SVC_FLAG = 1U << 8,
+ QUP_IN_SVC_FLAG = 1U << 9,
+ QUP_MX_INPUT_DONE = 1U << 11,
+};
+
+/* I2C mini core related values */
+enum {
+ I2C_MINI_CORE = 2U << 8,
+ I2C_N_VAL = 0xF,
+};
+
+/* Packing Unpacking words in FIFOs , and IO modes*/
+enum {
+ QUP_WR_BLK_MODE = 1U << 10,
+ QUP_RD_BLK_MODE = 1U << 12,
+ QUP_UNPACK_EN = 1U << 14,
+ QUP_PACK_EN = 1U << 15,
+};
+
+/* QUP tags */
+enum {
+ QUP_OUT_NOP = 0,
+ QUP_OUT_START = 1U << 8,
+ QUP_OUT_DATA = 2U << 8,
+ QUP_OUT_STOP = 3U << 8,
+ QUP_OUT_REC = 4U << 8,
+ QUP_IN_DATA = 5U << 8,
+ QUP_IN_STOP = 6U << 8,
+ QUP_IN_NACK = 7U << 8,
+};
+
+/* Status, Error flags */
+enum {
+ I2C_STATUS_WR_BUFFER_FULL = 1U << 0,
+ I2C_STATUS_BUS_ACTIVE = 1U << 8,
+ I2C_STATUS_ERROR_MASK = 0x38000FC,
+ QUP_I2C_NACK_FLAG = 1U << 3,
+ QUP_IN_NOT_EMPTY = 1U << 5,
+ QUP_STATUS_ERROR_FLAGS = 0x7C,
+};
+
+/* GSBI Control Register */
+enum {
+ GSBI_I2C_PROTOCOL_CODE = 0x2 << 4, /* I2C protocol */
+};
+
+#define QUP_MAX_RETRIES 2000
+#define QUP_SRC_CLK_RATE 19200000 /* Default source clock rate */
+
+struct qup_i2c_dev {
+ struct device *dev;
+ void __iomem *base; /* virtual */
+ void __iomem *gsbi; /* virtual */
+ int in_irq;
+ int out_irq;
+ int err_irq;
+ int num_irqs;
+ struct clk *clk;
+ struct clk *pclk;
+ struct i2c_adapter adapter;
+
+ struct i2c_msg *msg;
+ int pos;
+ int cnt;
+ int err;
+ int mode;
+ int clk_ctl;
+ int one_bit_t;
+ int out_fifo_sz;
+ int in_fifo_sz;
+ int out_blk_sz;
+ int in_blk_sz;
+ int wr_sz;
+ struct msm_i2c_platform_data *pdata;
+ int suspended;
+ int clk_state;
+ struct timer_list pwr_timer;
+ struct mutex mlock;
+ void *complete;
+};
+
+#ifdef DEBUG
+static void
+qup_print_status(struct qup_i2c_dev *dev)
+{
+ uint32_t val;
+ val = readl(dev->base+QUP_CONFIG);
+ dev_dbg(dev->dev, "Qup config is :0x%x\n", val);
+ val = readl(dev->base+QUP_STATE);
+ dev_dbg(dev->dev, "Qup state is :0x%x\n", val);
+ val = readl(dev->base+QUP_IO_MODE);
+ dev_dbg(dev->dev, "Qup mode is :0x%x\n", val);
+}
+#else
+static inline void qup_print_status(struct qup_i2c_dev *dev)
+{
+}
+#endif
+
+static irqreturn_t
+qup_i2c_interrupt(int irq, void *devid)
+{
+ struct qup_i2c_dev *dev = devid;
+ uint32_t status = readl(dev->base + QUP_I2C_STATUS);
+ uint32_t errors = readl(dev->base + QUP_ERROR_FLAGS);
+ uint32_t op_flgs = readl(dev->base + QUP_OPERATIONAL);
+ int err = 0;
+
+ if (!dev->msg)
+ return IRQ_HANDLED;
+
+ if (status & I2C_STATUS_ERROR_MASK) {
+ dev_err(dev->dev, "QUP: I2C status flags :0x%x, irq:%d\n",
+ status, irq);
+ err = -status;
+ /* Clear Error interrupt if it's a level triggered interrupt*/
+ if (dev->num_irqs == 1)
+ writel(QUP_RESET_STATE, dev->base+QUP_STATE);
+ goto intr_done;
+ }
+
+ if (errors & QUP_STATUS_ERROR_FLAGS) {
+ dev_err(dev->dev, "QUP: QUP status flags :0x%x\n", errors);
+ err = -errors;
+ /* Clear Error interrupt if it's a level triggered interrupt*/
+ if (dev->num_irqs == 1)
+ writel(errors & QUP_STATUS_ERROR_FLAGS,
+ dev->base + QUP_ERROR_FLAGS);
+ goto intr_done;
+ }
+
+ if ((dev->num_irqs == 3) && (dev->msg->flags == I2C_M_RD)
+ && (irq == dev->out_irq))
+ return IRQ_HANDLED;
+ if (op_flgs & QUP_OUT_SVC_FLAG)
+ writel(QUP_OUT_SVC_FLAG, dev->base + QUP_OPERATIONAL);
+ if (dev->msg->flags == I2C_M_RD) {
+ if ((op_flgs & QUP_MX_INPUT_DONE) ||
+ (op_flgs & QUP_IN_SVC_FLAG))
+ writel(QUP_IN_SVC_FLAG, dev->base + QUP_OPERATIONAL);
+ else
+ return IRQ_HANDLED;
+ }
+
+intr_done:
+ dev_dbg(dev->dev, "QUP intr= %d, i2c status=0x%x, qup status = 0x%x\n",
+ irq, status, errors);
+ qup_print_status(dev);
+ dev->err = err;
+ complete(dev->complete);
+ return IRQ_HANDLED;
+}
+
+static void
+qup_i2c_pwr_mgmt(struct qup_i2c_dev *dev, unsigned int state)
+{
+ dev->clk_state = state;
+ if (state != 0) {
+ clk_enable(dev->clk);
+ if (dev->pclk)
+ clk_enable(dev->pclk);
+ } else {
+ clk_disable(dev->clk);
+ if (dev->pclk)
+ clk_disable(dev->pclk);
+ }
+}
+
+static void
+qup_i2c_pwr_timer(unsigned long data)
+{
+ struct qup_i2c_dev *dev = (struct qup_i2c_dev *) data;
+ dev_dbg(dev->dev, "QUP_Power: Inactivity based power management\n");
+ if (dev->clk_state == 1)
+ qup_i2c_pwr_mgmt(dev, 0);
+}
+
+static int
+qup_i2c_poll_writeready(struct qup_i2c_dev *dev)
+{
+ uint32_t retries = 0;
+
+ while (retries != QUP_MAX_RETRIES) {
+ uint32_t status = readl(dev->base + QUP_I2C_STATUS);
+
+ if (!(status & I2C_STATUS_WR_BUFFER_FULL)) {
+ if (!(status & I2C_STATUS_BUS_ACTIVE))
+ return 0;
+ else /* 1-bit delay before we check for bus busy */
+ udelay(dev->one_bit_t);
+ }
+ if (retries++ == 1000)
+ udelay(100);
+ }
+ qup_print_status(dev);
+ return -ETIMEDOUT;
+}
+
+static int
+qup_i2c_poll_state(struct qup_i2c_dev *dev, uint32_t state)
+{
+ uint32_t retries = 0;
+
+ dev_dbg(dev->dev, "Polling Status for state:0x%x\n", state);
+
+ while (retries != QUP_MAX_RETRIES) {
+ uint32_t status = readl(dev->base + QUP_STATE);
+
+ if ((status & (QUP_STATE_VALID | state)) ==
+ (QUP_STATE_VALID | state))
+ return 0;
+ else if (retries++ == 1000)
+ udelay(100);
+ }
+ return -ETIMEDOUT;
+}
+
+#ifdef DEBUG
+static void qup_verify_fifo(struct qup_i2c_dev *dev, uint32_t val,
+ uint32_t addr, int rdwr)
+{
+ if (rdwr)
+ dev_dbg(dev->dev, "RD:Wrote 0x%x to out_ff:0x%x\n", val, addr);
+ else
+ dev_dbg(dev->dev, "WR:Wrote 0x%x to out_ff:0x%x\n", val, addr);
+}
+#else
+static inline void qup_verify_fifo(struct qup_i2c_dev *dev, uint32_t val,
+ uint32_t addr, int rdwr)
+{
+}
+#endif
+
+static void
+qup_issue_read(struct qup_i2c_dev *dev, struct i2c_msg *msg, int *idx,
+ uint32_t carry_over)
+{
+ uint16_t addr = (msg->addr << 1) | 1;
+ /* QUP limit 256 bytes per read. By HW design, 0 in the 8-bit field
+ * is treated as 256 byte read.
+ */
+ uint16_t rd_len = ((dev->cnt == 256) ? 0 : dev->cnt);
+
+ if (*idx % 4) {
+ writel(carry_over | ((QUP_OUT_START | addr) << 16),
+ dev->base + QUP_OUT_FIFO_BASE);/* + (*idx-2)); */
+
+ qup_verify_fifo(dev, carry_over |
+ ((QUP_OUT_START | addr) << 16), (uint32_t)dev->base
+ + QUP_OUT_FIFO_BASE + (*idx - 2), 1);
+ writel((QUP_OUT_REC | rd_len),
+ dev->base + QUP_OUT_FIFO_BASE);/* + (*idx+2)); */
+
+ qup_verify_fifo(dev, (QUP_OUT_REC | rd_len),
+ (uint32_t)dev->base + QUP_OUT_FIFO_BASE + (*idx + 2), 1);
+ } else {
+ writel(((QUP_OUT_REC | rd_len) << 16) | QUP_OUT_START | addr,
+ dev->base + QUP_OUT_FIFO_BASE);/* + (*idx)); */
+
+ qup_verify_fifo(dev, QUP_OUT_REC << 16 | rd_len << 16 |
+ QUP_OUT_START | addr,
+ (uint32_t)dev->base + QUP_OUT_FIFO_BASE + (*idx), 1);
+ }
+ *idx += 4;
+}
+
+static void
+qup_issue_write(struct qup_i2c_dev *dev, struct i2c_msg *msg, int rem,
+ int *idx, uint32_t *carry_over)
+{
+ int entries = dev->cnt;
+ int empty_sl = dev->wr_sz - ((*idx) >> 1);
+ int i = 0;
+ uint32_t val = 0;
+ uint32_t last_entry = 0;
+ uint16_t addr = msg->addr << 1;
+
+ if (dev->pos == 0) {
+ if (*idx % 4) {
+ writel(*carry_over | ((QUP_OUT_START | addr) << 16),
+ dev->base + QUP_OUT_FIFO_BASE);
+
+ qup_verify_fifo(dev, *carry_over | QUP_OUT_DATA << 16 |
+ addr << 16, (uint32_t)dev->base +
+ QUP_OUT_FIFO_BASE + (*idx) - 2, 0);
+ } else
+ val = QUP_OUT_START | addr;
+ *idx += 2;
+ i++;
+ entries++;
+ } else {
+ /* Avoid setp time issue by adding 1 NOP when number of bytes
+ * are more than FIFO/BLOCK size. setup time issue can't appear
+ * otherwise since next byte to be written will always be ready
+ */
+ val = (QUP_OUT_NOP | 1);
+ *idx += 2;
+ i++;
+ entries++;
+ }
+ if (entries > empty_sl)
+ entries = empty_sl;
+
+ for (; i < (entries - 1); i++) {
+ if (*idx % 4) {
+ writel(val | ((QUP_OUT_DATA |
+ msg->buf[dev->pos]) << 16),
+ dev->base + QUP_OUT_FIFO_BASE);
+
+ qup_verify_fifo(dev, val | QUP_OUT_DATA << 16 |
+ msg->buf[dev->pos] << 16, (uint32_t)dev->base +
+ QUP_OUT_FIFO_BASE + (*idx) - 2, 0);
+ } else
+ val = QUP_OUT_DATA | msg->buf[dev->pos];
+ (*idx) += 2;
+ dev->pos++;
+ }
+ if (dev->pos < (msg->len - 1))
+ last_entry = QUP_OUT_DATA;
+ else if (rem > 1) /* not last array entry */
+ last_entry = QUP_OUT_DATA;
+ else
+ last_entry = QUP_OUT_STOP;
+ if ((*idx % 4) == 0) {
+ /*
+ * If read-start and read-command end up in different fifos, it
+ * may result in extra-byte being read due to extra-read cycle.
+ * Avoid that by inserting NOP as the last entry of fifo only
+ * if write command(s) leave 1 space in fifo.
+ */
+ if (rem > 1) {
+ struct i2c_msg *next = msg + 1;
+ if (next->addr == msg->addr && (next->flags | I2C_M_RD)
+ && *idx == ((dev->wr_sz*2) - 4)) {
+ writel(((last_entry | msg->buf[dev->pos]) |
+ ((1 | QUP_OUT_NOP) << 16)), dev->base +
+ QUP_OUT_FIFO_BASE);/* + (*idx) - 2); */
+ *idx += 2;
+ } else
+ *carry_over = (last_entry | msg->buf[dev->pos]);
+ } else {
+ writel((last_entry | msg->buf[dev->pos]),
+ dev->base + QUP_OUT_FIFO_BASE);/* + (*idx) - 2); */
+
+ qup_verify_fifo(dev, last_entry | msg->buf[dev->pos],
+ (uint32_t)dev->base + QUP_OUT_FIFO_BASE +
+ (*idx), 0);
+ }
+ } else {
+ writel(val | ((last_entry | msg->buf[dev->pos]) << 16),
+ dev->base + QUP_OUT_FIFO_BASE);/* + (*idx) - 2); */
+
+ qup_verify_fifo(dev, val | (last_entry << 16) |
+ (msg->buf[dev->pos] << 16), (uint32_t)dev->base +
+ QUP_OUT_FIFO_BASE + (*idx) - 2, 0);
+ }
+
+ *idx += 2;
+ dev->pos++;
+ dev->cnt = msg->len - dev->pos;
+}
+
+static int
+qup_update_state(struct qup_i2c_dev *dev, uint32_t state)
+{
+ if (qup_i2c_poll_state(dev, 0) != 0)
+ return -EIO;
+ writel(state, dev->base + QUP_STATE);
+ if (qup_i2c_poll_state(dev, state) != 0)
+ return -EIO;
+ return 0;
+}
+
+static int
+qup_set_read_mode(struct qup_i2c_dev *dev, int rd_len)
+{
+ uint32_t wr_mode = (dev->wr_sz < dev->out_fifo_sz) ?
+ QUP_WR_BLK_MODE : 0;
+ if (rd_len > 256) {
+ dev_err(dev->dev, "HW doesn't support READs > 256 bytes\n");
+ return -EPROTONOSUPPORT;
+ }
+ if (rd_len <= dev->in_fifo_sz) {
+ writel(wr_mode | QUP_PACK_EN | QUP_UNPACK_EN,
+ dev->base + QUP_IO_MODE);
+ writel(rd_len, dev->base + QUP_MX_READ_CNT);
+ } else {
+ writel(wr_mode | QUP_RD_BLK_MODE |
+ QUP_PACK_EN | QUP_UNPACK_EN, dev->base + QUP_IO_MODE);
+ writel(rd_len, dev->base + QUP_MX_INPUT_CNT);
+ }
+ return 0;
+}
+
+static int
+qup_set_wr_mode(struct qup_i2c_dev *dev, int rem)
+{
+ int total_len = 0;
+ int ret = 0;
+ if (dev->msg->len >= (dev->out_fifo_sz - 1)) {
+ total_len = dev->msg->len + 1 +
+ (dev->msg->len/(dev->out_blk_sz-1));
+ writel(QUP_WR_BLK_MODE | QUP_PACK_EN | QUP_UNPACK_EN,
+ dev->base + QUP_IO_MODE);
+ dev->wr_sz = dev->out_blk_sz;
+ } else
+ writel(QUP_PACK_EN | QUP_UNPACK_EN,
+ dev->base + QUP_IO_MODE);
+
+ if (rem > 1) {
+ struct i2c_msg *next = dev->msg + 1;
+ if (next->addr == dev->msg->addr &&
+ next->flags == I2C_M_RD) {
+ ret = qup_set_read_mode(dev, next->len);
+ /* make sure read start & read command are in 1 blk */
+ if ((total_len % dev->out_blk_sz) ==
+ (dev->out_blk_sz - 1))
+ total_len += 3;
+ else
+ total_len += 2;
+ }
+ }
+ /* WRITE COUNT register valid/used only in block mode */
+ if (dev->wr_sz == dev->out_blk_sz)
+ writel(total_len, dev->base + QUP_MX_WR_CNT);
+ return ret;
+}
+
+static int
+qup_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
+{
+ DECLARE_COMPLETION_ONSTACK(complete);
+ struct qup_i2c_dev *dev = i2c_get_adapdata(adap);
+ int ret;
+ int rem = num;
+ long timeout;
+ int err;
+
+ del_timer_sync(&dev->pwr_timer);
+ mutex_lock(&dev->mlock);
+
+ if (dev->suspended) {
+ mutex_unlock(&dev->mlock);
+ return -EIO;
+ }
+
+ if (dev->clk_state == 0) {
+ if (dev->clk_ctl == 0) {
+ if (dev->pdata->src_clk_rate > 0)
+ clk_set_rate(dev->clk,
+ dev->pdata->src_clk_rate);
+ else
+ dev->pdata->src_clk_rate = QUP_SRC_CLK_RATE;
+ }
+ qup_i2c_pwr_mgmt(dev, 1);
+ }
+
+ /* Initialize QUP registers during first transfer */
+ if (dev->clk_ctl == 0) {
+ int fs_div;
+ int hs_div;
+ uint32_t fifo_reg;
+ writel(GSBI_I2C_PROTOCOL_CODE, dev->gsbi);
+
+ fs_div = ((dev->pdata->src_clk_rate
+ / dev->pdata->clk_freq) / 2) - 3;
+ hs_div = 3;
+ dev->clk_ctl = ((hs_div & 0x7) << 8) | (fs_div & 0xff);
+ fifo_reg = readl(dev->base + QUP_IO_MODE);
+ if (fifo_reg & 0x3)
+ dev->out_blk_sz = (fifo_reg & 0x3) * 16;
+ else
+ dev->out_blk_sz = 16;
+ if (fifo_reg & 0x60)
+ dev->in_blk_sz = ((fifo_reg & 0x60) >> 5) * 16;
+ else
+ dev->in_blk_sz = 16;
+ /*
+ * The block/fifo size w.r.t. 'actual data' is 1/2 due to 'tag'
+ * associated with each byte written/received
+ */
+ dev->out_blk_sz /= 2;
+ dev->in_blk_sz /= 2;
+ dev->out_fifo_sz = dev->out_blk_sz *
+ (2 << ((fifo_reg & 0x1C) >> 2));
+ dev->in_fifo_sz = dev->in_blk_sz *
+ (2 << ((fifo_reg & 0x380) >> 7));
+ dev_dbg(dev->dev, "QUP IN:bl:%d, ff:%d, OUT:bl:%d, ff:%d\n",
+ dev->in_blk_sz, dev->in_fifo_sz,
+ dev->out_blk_sz, dev->out_fifo_sz);
+ }
+
+ if (dev->num_irqs == 3) {
+ enable_irq(dev->in_irq);
+ enable_irq(dev->out_irq);
+ }
+ enable_irq(dev->err_irq);
+ writel(1, dev->base + QUP_SW_RESET);
+ ret = qup_i2c_poll_state(dev, QUP_RESET_STATE);
+ if (ret) {
+ dev_err(dev->dev, "QUP Busy:Trying to recover\n");
+ goto out_err;
+ }
+
+ /* Initialize QUP registers */
+ writel(0, dev->base + QUP_CONFIG);
+ writel(QUP_OPERATIONAL_RESET, dev->base + QUP_OPERATIONAL);
+ writel(QUP_STATUS_ERROR_FLAGS, dev->base + QUP_ERROR_FLAGS_EN);
+
+ writel(I2C_MINI_CORE | I2C_N_VAL, dev->base + QUP_CONFIG);
+
+ /* Initialize I2C mini core registers */
+ writel(0, dev->base + QUP_I2C_CLK_CTL);
+ writel(QUP_I2C_STATUS_RESET, dev->base + QUP_I2C_STATUS);
+
+ dev->cnt = msgs->len;
+ dev->pos = 0;
+ dev->msg = msgs;
+ while (rem) {
+ bool filled = false;
+
+ dev->wr_sz = dev->out_fifo_sz;
+ dev->err = 0;
+ dev->complete = &complete;
+
+ if (qup_i2c_poll_state(dev, QUP_I2C_MAST_GEN) != 0) {
+ ret = -EIO;
+ goto out_err;
+ }
+
+ qup_print_status(dev);
+ /* HW limits Read upto 256 bytes in 1 read without stop */
+ if (dev->msg->flags & I2C_M_RD) {
+ ret = qup_set_read_mode(dev, dev->cnt);
+ if (ret != 0)
+ goto out_err;
+ } else {
+ ret = qup_set_wr_mode(dev, rem);
+ if (ret != 0)
+ goto out_err;
+ /* Don't fill block till we get interrupt */
+ if (dev->wr_sz == dev->out_blk_sz)
+ filled = true;
+ }
+
+ err = qup_update_state(dev, QUP_RUN_STATE);
+ if (err < 0) {
+ ret = err;
+ goto out_err;
+ }
+
+ qup_print_status(dev);
+ writel(dev->clk_ctl, dev->base + QUP_I2C_CLK_CTL);
+
+ do {
+ int idx = 0;
+ uint32_t carry_over = 0;
+
+ /* Transition to PAUSE state only possible from RUN */
+ err = qup_update_state(dev, QUP_PAUSE_STATE);
+ if (err < 0) {
+ ret = err;
+ goto out_err;
+ }
+
+ qup_print_status(dev);
+ /* This operation is Write, check the next operation
+ * and decide mode
+ */
+ while (filled == false) {
+ if ((msgs->flags & I2C_M_RD) &&
+ (dev->cnt == msgs->len))
+ qup_issue_read(dev, msgs, &idx,
+ carry_over);
+ else if (!(msgs->flags & I2C_M_RD))
+ qup_issue_write(dev, msgs, rem, &idx,
+ &carry_over);
+ if (idx >= (dev->wr_sz << 1))
+ filled = true;
+ /* Start new message */
+ if (filled == false) {
+ if (msgs->flags & I2C_M_RD)
+ filled = true;
+ else if (rem > 1) {
+ /* Only combine operations with
+ * same address
+ */
+ struct i2c_msg *next = msgs + 1;
+ if (next->addr != msgs->addr ||
+ next->flags == 0)
+ filled = true;
+ else {
+ rem--;
+ msgs++;
+ dev->msg = msgs;
+ dev->pos = 0;
+ dev->cnt = msgs->len;
+ }
+ } else
+ filled = true;
+ }
+ }
+ err = qup_update_state(dev, QUP_RUN_STATE);
+ if (err < 0) {
+ ret = err;
+ goto out_err;
+ }
+ dev_dbg(dev->dev, "idx:%d, rem:%d, num:%d, mode:%d\n",
+ idx, rem, num, dev->mode);
+
+ qup_print_status(dev);
+ timeout = wait_for_completion_timeout(&complete,
+ msecs_to_jiffies(dev->out_fifo_sz));
+ if (!timeout) {
+ dev_err(dev->dev, "Transaction timed out\n");
+ writel(1, dev->base + QUP_SW_RESET);
+ ret = -ETIMEDOUT;
+ goto out_err;
+ }
+ if (dev->err) {
+ if (dev->err & QUP_I2C_NACK_FLAG)
+ dev_err(dev->dev,
+ "I2C slave addr:0x%x not connected\n",
+ dev->msg->addr);
+ else
+ dev_err(dev->dev,
+ "QUP data xfer error %d\n", dev->err);
+ ret = dev->err;
+ goto out_err;
+ }
+ if (dev->msg->flags & I2C_M_RD) {
+ int i;
+ uint32_t dval = 0;
+ for (i = 0; dev->pos < dev->msg->len; i++,
+ dev->pos++) {
+ uint32_t rd_status = readl(dev->base +
+ QUP_OPERATIONAL);
+ if (i % 2 == 0) {
+ if ((rd_status &
+ QUP_IN_NOT_EMPTY) == 0)
+ break;
+ dval = readl(dev->base +
+ QUP_IN_FIFO_BASE);
+ dev->msg->buf[dev->pos] =
+ dval & 0xFF;
+ } else
+ dev->msg->buf[dev->pos] =
+ ((dval & 0xFF0000) >>
+ 16);
+ }
+ dev->cnt -= i;
+ } else
+ filled = false; /* refill output FIFO */
+ } while (dev->cnt > 0);
+ if (dev->cnt == 0) {
+ rem--;
+ msgs++;
+ if (rem) {
+ dev->pos = 0;
+ dev->cnt = msgs->len;
+ dev->msg = msgs;
+ }
+ }
+ /* Wait for I2C bus to be idle */
+ ret = qup_i2c_poll_writeready(dev);
+ if (ret) {
+ dev_err(dev->dev,
+ "Error waiting for write ready\n");
+ goto out_err;
+ }
+ }
+
+ ret = num;
+ out_err:
+ dev->complete = NULL;
+ dev->msg = NULL;
+ dev->pos = 0;
+ dev->err = 0;
+ dev->cnt = 0;
+ disable_irq(dev->err_irq);
+ if (dev->num_irqs == 3) {
+ disable_irq(dev->in_irq);
+ disable_irq(dev->out_irq);
+ }
+ dev->pwr_timer.expires = jiffies + 3*HZ;
+ add_timer(&dev->pwr_timer);
+ mutex_unlock(&dev->mlock);
+ return ret;
+}
+
+static u32
+qup_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
+}
+
+static const struct i2c_algorithm qup_i2c_algo = {
+ .master_xfer = qup_i2c_xfer,
+ .functionality = qup_i2c_func,
+};
+
+static int __devinit
+qup_i2c_probe(struct platform_device *pdev)
+{
+ struct qup_i2c_dev *dev;
+ struct resource *qup_mem, *gsbi_mem, *qup_io, *gsbi_io;
+ struct resource *in_irq, *out_irq, *err_irq;
+ struct clk *clk, *pclk;
+ int ret = 0;
+ struct msm_i2c_platform_data *pdata;
+
+ dev_dbg(&pdev->dev, "qup_i2c_probe\n");
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "platform data not initialized\n");
+ return -ENOSYS;
+ }
+ qup_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "qup_phys_addr");
+ if (!qup_mem) {
+ dev_err(&pdev->dev, "no qup mem resource?\n");
+ return -ENODEV;
+ }
+ gsbi_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "gsbi_qup_i2c_addr");
+ if (!gsbi_mem) {
+ dev_err(&pdev->dev, "no gsbi mem resource?\n");
+ return -ENODEV;
+ }
+
+ /*
+ * We only have 1 interrupt for new hardware targets and in_irq,
+ * out_irq will be NULL for those platforms
+ */
+ in_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ "qup_in_intr");
+
+ out_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ "qup_out_intr");
+
+ err_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ "qup_err_intr");
+ if (!err_irq) {
+ dev_err(&pdev->dev, "no error irq resource?\n");
+ return -ENODEV;
+ }
+
+ qup_io = request_mem_region(qup_mem->start, resource_size(qup_mem),
+ pdev->name);
+ if (!qup_io) {
+ dev_err(&pdev->dev, "QUP region already claimed\n");
+ return -EBUSY;
+ }
+ gsbi_io = request_mem_region(gsbi_mem->start, resource_size(gsbi_mem),
+ pdev->name);
+ if (!gsbi_io) {
+ release_mem_region(qup_mem->start, resource_size(qup_mem));
+ dev_err(&pdev->dev, "GSBI region already claimed\n");
+ return -EBUSY;
+ }
+
+ clk = clk_get(&pdev->dev, "qup_clk");
+ if (IS_ERR(clk)) {
+ dev_err(&pdev->dev, "Could not get clock\n");
+ ret = PTR_ERR(clk);
+ goto err_clk_get_failed;
+ }
+
+ pclk = clk_get(&pdev->dev, "qup_pclk");
+ if (IS_ERR(pclk))
+ pclk = NULL;
+
+ if (!pdata->msm_i2c_config_gpio) {
+ dev_err(&pdev->dev, "config_gpio function not initialized\n");
+ ret = -ENOSYS;
+ goto err_config_failed;
+ }
+
+ /* We support frequencies upto FAST Mode(400KHz) */
+ if (pdata->clk_freq <= 0 ||
+ pdata->clk_freq > 400000) {
+ dev_err(&pdev->dev, "clock frequency not supported\n");
+ ret = -EIO;
+ goto err_config_failed;
+ }
+
+ dev = kzalloc(sizeof(struct qup_i2c_dev), GFP_KERNEL);
+ if (!dev) {
+ ret = -ENOMEM;
+ goto err_alloc_dev_failed;
+ }
+
+ dev->dev = &pdev->dev;
+ if (in_irq)
+ dev->in_irq = in_irq->start;
+ if (out_irq)
+ dev->out_irq = out_irq->start;
+ dev->err_irq = err_irq->start;
+ if (in_irq && out_irq)
+ dev->num_irqs = 3;
+ else
+ dev->num_irqs = 1;
+ dev->clk = clk;
+ dev->pclk = pclk;
+ dev->base = ioremap(qup_mem->start, resource_size(qup_mem));
+ if (!dev->base) {
+ ret = -ENOMEM;
+ goto err_ioremap_failed;
+ }
+
+ /* Configure GSBI block to use I2C functionality */
+ dev->gsbi = ioremap(gsbi_mem->start, resource_size(gsbi_mem));
+ if (!dev->gsbi) {
+ ret = -ENOMEM;
+ goto err_gsbi_failed;
+ }
+
+ platform_set_drvdata(pdev, dev);
+
+ dev->one_bit_t = USEC_PER_SEC/pdata->clk_freq;
+ dev->pdata = pdata;
+ dev->clk_ctl = 0;
+
+ /*
+ * We use num_irqs to also indicate if we got 3 interrupts or just 1.
+ * If we have just 1, we use err_irq as the general purpose irq
+ * and handle the changes in ISR accordingly
+ * Per Hardware guidelines, if we have 3 interrupts, they are always
+ * edge triggering, and if we have 1, it's always level-triggering
+ */
+ if (dev->num_irqs == 3) {
+ ret = request_irq(dev->in_irq, qup_i2c_interrupt,
+ IRQF_TRIGGER_RISING, "qup_in_intr", dev);
+ if (ret) {
+ dev_err(&pdev->dev, "request_in_irq failed\n");
+ goto err_request_irq_failed;
+ }
+ /*
+ * We assume out_irq exists if in_irq does since platform
+ * configuration either has 3 interrupts assigned to QUP or 1
+ */
+ ret = request_irq(dev->out_irq, qup_i2c_interrupt,
+ IRQF_TRIGGER_RISING, "qup_out_intr", dev);
+ if (ret) {
+ dev_err(&pdev->dev, "request_out_irq failed\n");
+ free_irq(dev->in_irq, dev);
+ goto err_request_irq_failed;
+ }
+ ret = request_irq(dev->err_irq, qup_i2c_interrupt,
+ IRQF_TRIGGER_RISING, "qup_err_intr", dev);
+ if (ret) {
+ dev_err(&pdev->dev, "request_err_irq failed\n");
+ free_irq(dev->out_irq, dev);
+ free_irq(dev->in_irq, dev);
+ goto err_request_irq_failed;
+ }
+ } else {
+ ret = request_irq(dev->err_irq, qup_i2c_interrupt,
+ IRQF_TRIGGER_HIGH, "qup_err_intr", dev);
+ if (ret) {
+ dev_err(&pdev->dev, "request_err_irq failed\n");
+ goto err_request_irq_failed;
+ }
+ }
+ disable_irq(dev->err_irq);
+ if (dev->num_irqs == 3) {
+ disable_irq(dev->in_irq);
+ disable_irq(dev->out_irq);
+ }
+ i2c_set_adapdata(&dev->adapter, dev);
+ dev->adapter.algo = &qup_i2c_algo;
+ strlcpy(dev->adapter.name,
+ "QUP I2C adapter",
+ sizeof(dev->adapter.name));
+ dev->adapter.nr = pdev->id;
+ pdata->msm_i2c_config_gpio(dev->adapter.nr, 1);
+
+ dev->suspended = 0;
+ mutex_init(&dev->mlock);
+ dev->clk_state = 0;
+ setup_timer(&dev->pwr_timer, qup_i2c_pwr_timer, (unsigned long) dev);
+
+ ret = i2c_add_numbered_adapter(&dev->adapter);
+ if (ret) {
+ dev_err(&pdev->dev, "i2c_add_adapter failed\n");
+ if (dev->num_irqs == 3) {
+ free_irq(dev->out_irq, dev);
+ free_irq(dev->in_irq, dev);
+ }
+ free_irq(dev->err_irq, dev);
+ } else
+ return 0;
+
+err_request_irq_failed:
+ iounmap(dev->gsbi);
+err_gsbi_failed:
+ iounmap(dev->base);
+err_ioremap_failed:
+ kfree(dev);
+err_alloc_dev_failed:
+err_config_failed:
+ clk_put(clk);
+ if (pclk)
+ clk_put(pclk);
+err_clk_get_failed:
+ release_mem_region(gsbi_mem->start, resource_size(gsbi_mem));
+ release_mem_region(qup_mem->start, resource_size(qup_mem));
+ return ret;
+}
+
+static int __devexit
+qup_i2c_remove(struct platform_device *pdev)
+{
+ struct qup_i2c_dev *dev = platform_get_drvdata(pdev);
+ struct resource *qup_mem, *gsbi_mem;
+
+ /* Grab mutex to ensure ongoing transaction is over */
+ mutex_lock(&dev->mlock);
+ dev->suspended = 1;
+ mutex_unlock(&dev->mlock);
+ mutex_destroy(&dev->mlock);
+ del_timer_sync(&dev->pwr_timer);
+ if (dev->clk_state != 0)
+ qup_i2c_pwr_mgmt(dev, 0);
+ platform_set_drvdata(pdev, NULL);
+ if (dev->num_irqs == 3) {
+ free_irq(dev->out_irq, dev);
+ free_irq(dev->in_irq, dev);
+ }
+ free_irq(dev->err_irq, dev);
+ i2c_del_adapter(&dev->adapter);
+ clk_put(dev->clk);
+ if (dev->pclk)
+ clk_put(dev->pclk);
+ iounmap(dev->gsbi);
+ iounmap(dev->base);
+ kfree(dev);
+ gsbi_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "gsbi_qup_i2c_addr");
+ release_mem_region(gsbi_mem->start, resource_size(gsbi_mem));
+ qup_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "qup_phys_addr");
+ release_mem_region(qup_mem->start, resource_size(qup_mem));
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int qup_i2c_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct qup_i2c_dev *qup = platform_get_drvdata(pdev);
+
+ /* Grab mutex to ensure ongoing transaction is over */
+ mutex_lock(&qup->mlock);
+ qup->suspended = 1;
+ mutex_unlock(&qup->mlock);
+ del_timer_sync(&qup->pwr_timer);
+ if (qup->clk_state != 0)
+ qup_i2c_pwr_mgmt(qup, 0);
+ return 0;
+}
+
+static int qup_i2c_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct qup_i2c_dev *qup = platform_get_drvdata(pdev);
+
+ qup->suspended = 0;
+ return 0;
+}
+
+static const SIMPLE_DEV_PM_OPS(qup_i2c_pm_ops,
+ qup_i2c_suspend, qup_i2c_resume);
+#endif /* CONFIG_PM */
+
+static struct platform_driver qup_i2c_driver = {
+ .probe = qup_i2c_probe,
+ .remove = __devexit_p(qup_i2c_remove),
+ .driver = {
+ .name = "qup_i2c",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &qup_i2c_pm_ops,
+#endif
+ },
+};
+
+/* QUP may be needed to bring up other drivers */
+static int __init
+qup_i2c_init_driver(void)
+{
+ return platform_driver_register(&qup_i2c_driver);
+}
+arch_initcall(qup_i2c_init_driver);
+
+static void __exit qup_i2c_exit_driver(void)
+{
+ platform_driver_unregister(&qup_i2c_driver);
+}
+module_exit(qup_i2c_exit_driver);
diff --git a/include/linux/i2c-msm.h b/include/linux/i2c-msm.h
new file mode 100644
index 0000000..a09dea7
--- /dev/null
+++ b/include/linux/i2c-msm.h
@@ -0,0 +1,29 @@
+/*
+ * Qualcomm MSM i2c Controller Platform Data
+ *
+ * Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+#ifndef __LINUX_I2C_MSM_H
+#define __LINUX_I2C_MSM_H
+
+struct msm_i2c_platform_data {
+ int clk_freq;
+ int src_clk_rate;
+ int (*msm_i2c_config_gpio)(int iface, int config_type);
+};
+
+#endif /* __LINUX_I2C_MSM_H */
--
1.7.1.1
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH] i2c: QUP based bus driver for Qualcomm MSM chipsets
2010-07-23 2:47 [PATCH] i2c: QUP based bus driver for Qualcomm MSM chipsets Kenneth Heitke
@ 2010-07-23 7:11 ` Trilok Soni
2010-07-23 18:56 ` Kenneth Heitke
2010-07-26 11:05 ` srinidhi
1 sibling, 1 reply; 8+ messages in thread
From: Trilok Soni @ 2010-07-23 7:11 UTC (permalink / raw)
To: Kenneth Heitke
Cc: khali, ben-linux, linux-arm-msm, Crane Cai, Samuel Ortiz,
Linus Walleij, Ralf Baechle, srinidhi kasagar, linux-i2c,
linux-kernel
Hi Ken,
On 7/23/2010 8:17 AM, Kenneth Heitke wrote:
> This bus driver supports the QUP i2c hardware controller in the Qualcomm
> MSM SOCs. The Qualcomm Universal Peripheral Engine (QUP) is a general
> purpose data path engine with input/output FIFOs and an embedded i2c
> mini-core. The driver supports FIFO mode (for low bandwidth applications)
> and block mode (interrupt generated for each block-size data transfer).
> The driver currently does not support DMA transfers.
>
> Signed-off-by: Kenneth Heitke <kheitke@codeaurora.org>
Thanks for the posting the driver.
> +
> +static void
> +qup_i2c_pwr_timer(unsigned long data)
> +{
> + struct qup_i2c_dev *dev = (struct qup_i2c_dev *) data;
> + dev_dbg(dev->dev, "QUP_Power: Inactivity based power management\n");
> + if (dev->clk_state == 1)
> + qup_i2c_pwr_mgmt(dev, 0);
> +}
Why this timer can't be converted to run-time PM functionality? I see this very much related
to what we are introducing in the Runtime-PM functionality, isn't it?
> +
> + if (!pdata->msm_i2c_config_gpio) {
> + dev_err(&pdev->dev, "config_gpio function not initialized\n");
> + ret = -ENOSYS;
> + goto err_config_failed;
> + }
I don't agree here. What if I do all the gpio configuration from the bootloader itself,
because I know that the device I am working on is production device and don't change
its configuration, then why I should provide the pdata hooks from the board files?
Please also specify what are the operations we are doing in the msm_i2c_config_gpio?
> +
> + /* We support frequencies upto FAST Mode(400KHz) */
> + if (pdata->clk_freq <= 0 ||
> + pdata->clk_freq > 400000) {
> + dev_err(&pdev->dev, "clock frequency not supported\n");
Which freq? Should we add that freq value in the debug message?
> + ret = -EIO;
> + goto err_config_failed;
> + }
> +
> + dev = kzalloc(sizeof(struct qup_i2c_dev), GFP_KERNEL);
> + if (!dev) {
> + ret = -ENOMEM;
> + goto err_alloc_dev_failed;
> + }
> +
> + dev->dev = &pdev->dev;
> + if (in_irq)
> + dev->in_irq = in_irq->start;
> + if (out_irq)
> + dev->out_irq = out_irq->start;
> + dev->err_irq = err_irq->start;
> + if (in_irq && out_irq)
> + dev->num_irqs = 3;
> + else
> + dev->num_irqs = 1;
> + dev->clk = clk;
> + dev->pclk = pclk;
> + dev->base = ioremap(qup_mem->start, resource_size(qup_mem));
> + if (!dev->base) {
> + ret = -ENOMEM;
> + goto err_ioremap_failed;
> + }
> +
> + /* Configure GSBI block to use I2C functionality */
> + dev->gsbi = ioremap(gsbi_mem->start, resource_size(gsbi_mem));
> + if (!dev->gsbi) {
> + ret = -ENOMEM;
> + goto err_gsbi_failed;
> + }
> +
> + platform_set_drvdata(pdev, dev);
> +
> + dev->one_bit_t = USEC_PER_SEC/pdata->clk_freq;
> + dev->pdata = pdata;
> + dev->clk_ctl = 0;
> +
> + /*
> + * We use num_irqs to also indicate if we got 3 interrupts or just 1.
> + * If we have just 1, we use err_irq as the general purpose irq
> + * and handle the changes in ISR accordingly
> + * Per Hardware guidelines, if we have 3 interrupts, they are always
> + * edge triggering, and if we have 1, it's always level-triggering
> + */
> + if (dev->num_irqs == 3) {
> + ret = request_irq(dev->in_irq, qup_i2c_interrupt,
> + IRQF_TRIGGER_RISING, "qup_in_intr", dev);
> + if (ret) {
> + dev_err(&pdev->dev, "request_in_irq failed\n");
> + goto err_request_irq_failed;
> + }
> + /*
> + * We assume out_irq exists if in_irq does since platform
> + * configuration either has 3 interrupts assigned to QUP or 1
> + */
> + ret = request_irq(dev->out_irq, qup_i2c_interrupt,
> + IRQF_TRIGGER_RISING, "qup_out_intr", dev);
> + if (ret) {
> + dev_err(&pdev->dev, "request_out_irq failed\n");
> + free_irq(dev->in_irq, dev);
> + goto err_request_irq_failed;
> + }
> + ret = request_irq(dev->err_irq, qup_i2c_interrupt,
> + IRQF_TRIGGER_RISING, "qup_err_intr", dev);
> + if (ret) {
> + dev_err(&pdev->dev, "request_err_irq failed\n");
> + free_irq(dev->out_irq, dev);
> + free_irq(dev->in_irq, dev);
> + goto err_request_irq_failed;
> + }
> + } else {
> + ret = request_irq(dev->err_irq, qup_i2c_interrupt,
> + IRQF_TRIGGER_HIGH, "qup_err_intr", dev);
> + if (ret) {
> + dev_err(&pdev->dev, "request_err_irq failed\n");
> + goto err_request_irq_failed;
> + }
> + }
> + disable_irq(dev->err_irq);
> + if (dev->num_irqs == 3) {
> + disable_irq(dev->in_irq);
> + disable_irq(dev->out_irq);
> + }
> + i2c_set_adapdata(&dev->adapter, dev);
> + dev->adapter.algo = &qup_i2c_algo;
> + strlcpy(dev->adapter.name,
> + "QUP I2C adapter",
> + sizeof(dev->adapter.name));
> + dev->adapter.nr = pdev->id;
> + pdata->msm_i2c_config_gpio(dev->adapter.nr, 1);
Why there is no error check here?
---Trilok Soni
--
Sent by a consultant of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] i2c: QUP based bus driver for Qualcomm MSM chipsets
2010-07-23 7:11 ` Trilok Soni
@ 2010-07-23 18:56 ` Kenneth Heitke
2010-07-24 15:08 ` Trilok Soni
0 siblings, 1 reply; 8+ messages in thread
From: Kenneth Heitke @ 2010-07-23 18:56 UTC (permalink / raw)
To: Trilok Soni
Cc: khali, ben-linux, linux-arm-msm, Crane Cai, Samuel Ortiz,
Linus Walleij, Ralf Baechle, srinidhi kasagar, linux-i2c,
linux-kernel
Trilok Soni wrote:
> Hi Ken,
>
> On 7/23/2010 8:17 AM, Kenneth Heitke wrote:
>> This bus driver supports the QUP i2c hardware controller in the Qualcomm
>> MSM SOCs. The Qualcomm Universal Peripheral Engine (QUP) is a general
>> purpose data path engine with input/output FIFOs and an embedded i2c
>> mini-core. The driver supports FIFO mode (for low bandwidth applications)
>> and block mode (interrupt generated for each block-size data transfer).
>> The driver currently does not support DMA transfers.
>>
>> Signed-off-by: Kenneth Heitke <kheitke@codeaurora.org>
>
> Thanks for the posting the driver.
>
>> +
>> +static void
>> +qup_i2c_pwr_timer(unsigned long data)
>> +{
>> + struct qup_i2c_dev *dev = (struct qup_i2c_dev *) data;
>> + dev_dbg(dev->dev, "QUP_Power: Inactivity based power management\n");
>> + if (dev->clk_state == 1)
>> + qup_i2c_pwr_mgmt(dev, 0);
>> +}
>
> Why this timer can't be converted to run-time PM functionality? I see this very much related
> to what we are introducing in the Runtime-PM functionality, isn't it?
Thanks, We will investigate further.
>
>> +
>> + if (!pdata->msm_i2c_config_gpio) {
>> + dev_err(&pdev->dev, "config_gpio function not initialized\n");
>> + ret = -ENOSYS;
>> + goto err_config_failed;
>> + }
>
> I don't agree here. What if I do all the gpio configuration from the bootloader itself,
> because I know that the device I am working on is production device and don't change
> its configuration, then why I should provide the pdata hooks from the board files?
The reason that this hook is required is so that the driver can enable
and disable the GPIOs as needed (although we currently only enable them).
>
> Please also specify what are the operations we are doing in the msm_i2c_config_gpio?
Takes ownership of the GPIOs that are needed and assigns the ownership
to the i2c controller. The drive strength is also set.
>
>> +
>> + /* We support frequencies upto FAST Mode(400KHz) */
>> + if (pdata->clk_freq <= 0 ||
>> + pdata->clk_freq > 400000) {
>> + dev_err(&pdev->dev, "clock frequency not supported\n");
>
> Which freq? Should we add that freq value in the debug message?
I'll add that. Thanks.
>
>> + ret = -EIO;
>> + goto err_config_failed;
>> + }
>> +
>> + dev = kzalloc(sizeof(struct qup_i2c_dev), GFP_KERNEL);
>> + if (!dev) {
>> + ret = -ENOMEM;
>> + goto err_alloc_dev_failed;
>> + }
>> +
>> + dev->dev = &pdev->dev;
>> + if (in_irq)
>> + dev->in_irq = in_irq->start;
>> + if (out_irq)
>> + dev->out_irq = out_irq->start;
>> + dev->err_irq = err_irq->start;
>> + if (in_irq && out_irq)
>> + dev->num_irqs = 3;
>> + else
>> + dev->num_irqs = 1;
>> + dev->clk = clk;
>> + dev->pclk = pclk;
>> + dev->base = ioremap(qup_mem->start, resource_size(qup_mem));
>> + if (!dev->base) {
>> + ret = -ENOMEM;
>> + goto err_ioremap_failed;
>> + }
>> +
>> + /* Configure GSBI block to use I2C functionality */
>> + dev->gsbi = ioremap(gsbi_mem->start, resource_size(gsbi_mem));
>> + if (!dev->gsbi) {
>> + ret = -ENOMEM;
>> + goto err_gsbi_failed;
>> + }
>> +
>> + platform_set_drvdata(pdev, dev);
>> +
>> + dev->one_bit_t = USEC_PER_SEC/pdata->clk_freq;
>> + dev->pdata = pdata;
>> + dev->clk_ctl = 0;
>> +
>> + /*
>> + * We use num_irqs to also indicate if we got 3 interrupts or just 1.
>> + * If we have just 1, we use err_irq as the general purpose irq
>> + * and handle the changes in ISR accordingly
>> + * Per Hardware guidelines, if we have 3 interrupts, they are always
>> + * edge triggering, and if we have 1, it's always level-triggering
>> + */
>> + if (dev->num_irqs == 3) {
>> + ret = request_irq(dev->in_irq, qup_i2c_interrupt,
>> + IRQF_TRIGGER_RISING, "qup_in_intr", dev);
>> + if (ret) {
>> + dev_err(&pdev->dev, "request_in_irq failed\n");
>> + goto err_request_irq_failed;
>> + }
>> + /*
>> + * We assume out_irq exists if in_irq does since platform
>> + * configuration either has 3 interrupts assigned to QUP or 1
>> + */
>> + ret = request_irq(dev->out_irq, qup_i2c_interrupt,
>> + IRQF_TRIGGER_RISING, "qup_out_intr", dev);
>> + if (ret) {
>> + dev_err(&pdev->dev, "request_out_irq failed\n");
>> + free_irq(dev->in_irq, dev);
>> + goto err_request_irq_failed;
>> + }
>> + ret = request_irq(dev->err_irq, qup_i2c_interrupt,
>> + IRQF_TRIGGER_RISING, "qup_err_intr", dev);
>> + if (ret) {
>> + dev_err(&pdev->dev, "request_err_irq failed\n");
>> + free_irq(dev->out_irq, dev);
>> + free_irq(dev->in_irq, dev);
>> + goto err_request_irq_failed;
>> + }
>> + } else {
>> + ret = request_irq(dev->err_irq, qup_i2c_interrupt,
>> + IRQF_TRIGGER_HIGH, "qup_err_intr", dev);
>> + if (ret) {
>> + dev_err(&pdev->dev, "request_err_irq failed\n");
>> + goto err_request_irq_failed;
>> + }
>> + }
>> + disable_irq(dev->err_irq);
>> + if (dev->num_irqs == 3) {
>> + disable_irq(dev->in_irq);
>> + disable_irq(dev->out_irq);
>> + }
>> + i2c_set_adapdata(&dev->adapter, dev);
>> + dev->adapter.algo = &qup_i2c_algo;
>> + strlcpy(dev->adapter.name,
>> + "QUP I2C adapter",
>> + sizeof(dev->adapter.name));
>> + dev->adapter.nr = pdev->id;
>> + pdata->msm_i2c_config_gpio(dev->adapter.nr, 1);
>
> Why there is no error check here?
Currently the configuration function returns a void but I'll change that
to return an error code and check for it here.
> ---Trilok Soni
>
--
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] i2c: QUP based bus driver for Qualcomm MSM chipsets
2010-07-23 18:56 ` Kenneth Heitke
@ 2010-07-24 15:08 ` Trilok Soni
0 siblings, 0 replies; 8+ messages in thread
From: Trilok Soni @ 2010-07-24 15:08 UTC (permalink / raw)
To: Kenneth Heitke
Cc: khali, ben-linux, linux-arm-msm, Crane Cai, Samuel Ortiz,
Linus Walleij, Ralf Baechle, srinidhi kasagar, linux-i2c,
linux-kernel
Hi Ken,
>
>>
>>> +
>>> + if (!pdata->msm_i2c_config_gpio) {
>>> + dev_err(&pdev->dev, "config_gpio function not initialized\n");
>>> + ret = -ENOSYS;
>>> + goto err_config_failed;
>>> + }
>>
>> I don't agree here. What if I do all the gpio configuration from the bootloader itself,
>> because I know that the device I am working on is production device and don't change
>> its configuration, then why I should provide the pdata hooks from the board files?
>
> The reason that this hook is required is so that the driver can enable and disable the GPIOs as needed (although we currently only enable them).
>
>>
>> Please also specify what are the operations we are doing in the msm_i2c_config_gpio?
>
> Takes ownership of the GPIOs that are needed and assigns the ownership to the i2c controller. The drive strength is also set.
Right, so due to these reasons I prefer not to have __must__ have check for pdata->msm_i2c_config_gpio hook's unavailability. It
might happen that some of the systems do all these ownership and pad muxing into the bootloader itself.
---Trilok Soni
--
Sent by a consultant of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] i2c: QUP based bus driver for Qualcomm MSM chipsets
2010-07-23 2:47 [PATCH] i2c: QUP based bus driver for Qualcomm MSM chipsets Kenneth Heitke
2010-07-23 7:11 ` Trilok Soni
@ 2010-07-26 11:05 ` srinidhi
2010-08-12 1:50 ` Kenneth Heitke
1 sibling, 1 reply; 8+ messages in thread
From: srinidhi @ 2010-07-26 11:05 UTC (permalink / raw)
To: Kenneth Heitke
Cc: khali@linux-fr.org, ben-linux@fluff.org,
linux-arm-msm@vger.kernel.org, Crane Cai, Samuel Ortiz,
Linus WALLEIJ, Ralf Baechle, linux-i2c@vger.kernel.org,
linux-kernel@vger.kernel.org
On Fri, 2010-07-23 at 04:47 +0200, Kenneth Heitke wrote:
> This bus driver supports the QUP i2c hardware controller in the Qualcomm
> MSM SOCs. The Qualcomm Universal Peripheral Engine (QUP) is a general
> purpose data path engine with input/output FIFOs and an embedded i2c
> mini-core. The driver supports FIFO mode (for low bandwidth applications)
> and block mode (interrupt generated for each block-size data transfer).
> The driver currently does not support DMA transfers.
>
> Signed-off-by: Kenneth Heitke <kheitke@codeaurora.org>
> ---
> drivers/i2c/busses/Kconfig | 12 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-qup.c | 1080 ++++++++++++++++++++++++++++++++++++++++++
> include/linux/i2c-msm.h | 29 ++
> 4 files changed, 1122 insertions(+), 0 deletions(-)
> create mode 100644 drivers/i2c/busses/i2c-qup.c
> create mode 100644 include/linux/i2c-msm.h
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index bceafbf..03d8f8f 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -521,6 +521,18 @@ config I2C_PXA_SLAVE
> is necessary for systems where the PXA may be a target on the
> I2C bus.
>
> +config I2C_QUP
> + tristate "Qualcomm MSM QUP I2C Controller"
> + depends on I2C && HAVE_CLK && (ARCH_MSM7X30 || ARCH_MSM8X60 || \
> + (ARCH_QSD8X50 && MSM_SOC_REV_A))
I think HAVE_CLK is redundant here
> + help
> + If you say yes to this option, support will be included for the
> + built-in QUP I2C interface on Qualcomm MSM family processors.
> +
> + The Qualcomm Universal Peripheral Engine (QUP) is a general
> + purpose data path engine with input/output FIFOs and an
> + embedded I2C mini-core.
> +
> config I2C_S3C2410
> tristate "S3C2410 I2C Driver"
> depends on ARCH_S3C2410 || ARCH_S3C64XX
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 936880b..6a52572 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -50,6 +50,7 @@ obj-$(CONFIG_I2C_PCA_PLATFORM) += i2c-pca-platform.o
> obj-$(CONFIG_I2C_PMCMSP) += i2c-pmcmsp.o
> obj-$(CONFIG_I2C_PNX) += i2c-pnx.o
> obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
> +obj-$(CONFIG_I2C_QUP) += i2c-qup.o
> obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
> obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
> obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
> diff --git a/drivers/i2c/busses/i2c-qup.c b/drivers/i2c/busses/i2c-qup.c
> new file mode 100644
> index 0000000..3d7abab
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-qup.c
> @@ -0,0 +1,1080 @@
> +/* Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * 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., 51 Franklin Street, Fifth Floor, Boston, MA
> + * 02110-1301, USA.
> + *
> + */
> +/*
> + * QUP driver for Qualcomm MSM platforms
> + *
> + */
> +
> +/* #define DEBUG */
> +
> +#include <linux/clk.h>
> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +#include <linux/delay.h>
> +#include <linux/io.h>
> +#include <linux/slab.h>
> +#include <linux/mutex.h>
> +#include <linux/timer.h>
> +#include <linux/i2c-msm.h>
I do not understand why yo need to expose this controller's private data
to the whole Linux world. Shouldn't this be <mach/i2c-msm.h>?
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_VERSION("0.2");
> +MODULE_ALIAS("platform:i2c_qup");
> +MODULE_AUTHOR("Sagar Dharia <sdharia@codeaurora.org>");
> +
> +/* QUP Registers */
> +enum {
> + QUP_CONFIG = 0x0,
> + QUP_STATE = 0x4,
> + QUP_IO_MODE = 0x8,
> + QUP_SW_RESET = 0xC,
> + QUP_OPERATIONAL = 0x18,
> + QUP_ERROR_FLAGS = 0x1C,
> + QUP_ERROR_FLAGS_EN = 0x20,
> + QUP_MX_READ_CNT = 0x208,
> + QUP_MX_INPUT_CNT = 0x200,
> + QUP_MX_WR_CNT = 0x100,
> + QUP_OUT_DEBUG = 0x108,
> + QUP_OUT_FIFO_CNT = 0x10C,
> + QUP_OUT_FIFO_BASE = 0x110,
> + QUP_IN_READ_CUR = 0x20C,
> + QUP_IN_DEBUG = 0x210,
> + QUP_IN_FIFO_CNT = 0x214,
> + QUP_IN_FIFO_BASE = 0x218,
> + QUP_I2C_CLK_CTL = 0x400,
> + QUP_I2C_STATUS = 0x404,
> +};
anonymous?
> +
> +/* QUP States and reset values */
> +enum {
> + QUP_RESET_STATE = 0,
> + QUP_RUN_STATE = 1U,
> + QUP_STATE_MASK = 3U,
> + QUP_PAUSE_STATE = 3U,
> + QUP_STATE_VALID = 1U << 2,
> + QUP_I2C_MAST_GEN = 1U << 4,
> + QUP_OPERATIONAL_RESET = 0xFF0,
> + QUP_I2C_STATUS_RESET = 0xFFFFFC,
> +};
anonymous, ditto for all the following enums.
> +
> +/* QUP OPERATIONAL FLAGS */
> +enum {
> + QUP_OUT_SVC_FLAG = 1U << 8,
> + QUP_IN_SVC_FLAG = 1U << 9,
> + QUP_MX_INPUT_DONE = 1U << 11,
> +};
> +
> +/* I2C mini core related values */
> +enum {
> + I2C_MINI_CORE = 2U << 8,
> + I2C_N_VAL = 0xF,
> +};
> +
> +/* Packing Unpacking words in FIFOs , and IO modes*/
> +enum {
> + QUP_WR_BLK_MODE = 1U << 10,
> + QUP_RD_BLK_MODE = 1U << 12,
> + QUP_UNPACK_EN = 1U << 14,
> + QUP_PACK_EN = 1U << 15,
> +};
> +
> +/* QUP tags */
> +enum {
> + QUP_OUT_NOP = 0,
> + QUP_OUT_START = 1U << 8,
> + QUP_OUT_DATA = 2U << 8,
> + QUP_OUT_STOP = 3U << 8,
> + QUP_OUT_REC = 4U << 8,
> + QUP_IN_DATA = 5U << 8,
> + QUP_IN_STOP = 6U << 8,
> + QUP_IN_NACK = 7U << 8,
> +};
> +
> +/* Status, Error flags */
> +enum {
> + I2C_STATUS_WR_BUFFER_FULL = 1U << 0,
> + I2C_STATUS_BUS_ACTIVE = 1U << 8,
> + I2C_STATUS_ERROR_MASK = 0x38000FC,
> + QUP_I2C_NACK_FLAG = 1U << 3,
> + QUP_IN_NOT_EMPTY = 1U << 5,
> + QUP_STATUS_ERROR_FLAGS = 0x7C,
> +};
> +
> +/* GSBI Control Register */
> +enum {
> + GSBI_I2C_PROTOCOL_CODE = 0x2 << 4, /* I2C protocol */
> +};
> +
> +#define QUP_MAX_RETRIES 2000
> +#define QUP_SRC_CLK_RATE 19200000 /* Default source clock rate */
why do want this while having this module dependent on HAVE_CLK
> +
> +struct qup_i2c_dev {
> + struct device *dev;
> + void __iomem *base; /* virtual */
> + void __iomem *gsbi; /* virtual */
> + int in_irq;
> + int out_irq;
> + int err_irq;
> + int num_irqs;
> + struct clk *clk;
> + struct clk *pclk;
> + struct i2c_adapter adapter;
> +
> + struct i2c_msg *msg;
> + int pos;
> + int cnt;
> + int err;
> + int mode;
> + int clk_ctl;
> + int one_bit_t;
> + int out_fifo_sz;
> + int in_fifo_sz;
> + int out_blk_sz;
> + int in_blk_sz;
> + int wr_sz;
> + struct msm_i2c_platform_data *pdata;
> + int suspended;
> + int clk_state;
> + struct timer_list pwr_timer;
> + struct mutex mlock;
> + void *complete;
> +};
too many local parameters. Do you really need all of this? Moreover it
is not readable. Would suggest to document it at least using kernel-doc
style.
> +
> +#ifdef DEBUG
> +static void
> +qup_print_status(struct qup_i2c_dev *dev)
> +{
> + uint32_t val;
> + val = readl(dev->base+QUP_CONFIG);
space after & before '+'. checkpatch would've complained this about..
> + dev_dbg(dev->dev, "Qup config is :0x%x\n", val);
> + val = readl(dev->base+QUP_STATE);
ditto
> + dev_dbg(dev->dev, "Qup state is :0x%x\n", val);
> + val = readl(dev->base+QUP_IO_MODE);
ditto
> + dev_dbg(dev->dev, "Qup mode is :0x%x\n", val);
> +}
> +#else
> +static inline void qup_print_status(struct qup_i2c_dev *dev)
> +{
> +}
> +#endif
> +
> +static irqreturn_t
> +qup_i2c_interrupt(int irq, void *devid)
> +{
> + struct qup_i2c_dev *dev = devid;
> + uint32_t status = readl(dev->base + QUP_I2C_STATUS);
> + uint32_t errors = readl(dev->base + QUP_ERROR_FLAGS);
> + uint32_t op_flgs = readl(dev->base + QUP_OPERATIONAL);
> + int err = 0;
> +
> + if (!dev->msg)
> + return IRQ_HANDLED;
> +
> + if (status & I2C_STATUS_ERROR_MASK) {
> + dev_err(dev->dev, "QUP: I2C status flags :0x%x, irq:%d\n",
> + status, irq);
> + err = -status;
> + /* Clear Error interrupt if it's a level triggered interrupt*/
/*<space> ... <space>*/
> + if (dev->num_irqs == 1)
> + writel(QUP_RESET_STATE, dev->base+QUP_STATE);
> + goto intr_done;
> + }
> +
> + if (errors & QUP_STATUS_ERROR_FLAGS) {
> + dev_err(dev->dev, "QUP: QUP status flags :0x%x\n", errors);
> + err = -errors;
> + /* Clear Error interrupt if it's a level triggered interrupt*/
ditto
> + if (dev->num_irqs == 1)
> + writel(errors & QUP_STATUS_ERROR_FLAGS,
> + dev->base + QUP_ERROR_FLAGS);
> + goto intr_done;
> + }
> +
> + if ((dev->num_irqs == 3) && (dev->msg->flags == I2C_M_RD)
> + && (irq == dev->out_irq))
> + return IRQ_HANDLED;
> + if (op_flgs & QUP_OUT_SVC_FLAG)
> + writel(QUP_OUT_SVC_FLAG, dev->base + QUP_OPERATIONAL);
> + if (dev->msg->flags == I2C_M_RD) {
> + if ((op_flgs & QUP_MX_INPUT_DONE) ||
> + (op_flgs & QUP_IN_SVC_FLAG))
> + writel(QUP_IN_SVC_FLAG, dev->base + QUP_OPERATIONAL);
> + else
> + return IRQ_HANDLED;
> + }
> +
> +intr_done:
> + dev_dbg(dev->dev, "QUP intr= %d, i2c status=0x%x, qup status = 0x%x\n",
> + irq, status, errors);
> + qup_print_status(dev);
> + dev->err = err;
> + complete(dev->complete);
> + return IRQ_HANDLED;
> +}
> +
> +static void
> +qup_i2c_pwr_mgmt(struct qup_i2c_dev *dev, unsigned int state)
> +{
> + dev->clk_state = state;
> + if (state != 0) {
> + clk_enable(dev->clk);
> + if (dev->pclk)
> + clk_enable(dev->pclk);
> + } else {
> + clk_disable(dev->clk);
> + if (dev->pclk)
> + clk_disable(dev->pclk);
> + }
> +}
> +
> +static void
> +qup_i2c_pwr_timer(unsigned long data)
> +{
> + struct qup_i2c_dev *dev = (struct qup_i2c_dev *) data;
> + dev_dbg(dev->dev, "QUP_Power: Inactivity based power management\n");
> + if (dev->clk_state == 1)
> + qup_i2c_pwr_mgmt(dev, 0);
> +}
> +
> +static int
> +qup_i2c_poll_writeready(struct qup_i2c_dev *dev)
> +{
> + uint32_t retries = 0;
> +
> + while (retries != QUP_MAX_RETRIES) {
> + uint32_t status = readl(dev->base + QUP_I2C_STATUS);
> +
> + if (!(status & I2C_STATUS_WR_BUFFER_FULL)) {
> + if (!(status & I2C_STATUS_BUS_ACTIVE))
> + return 0;
> + else /* 1-bit delay before we check for bus busy */
> + udelay(dev->one_bit_t);
> + }
> + if (retries++ == 1000)
> + udelay(100);
> + }
> + qup_print_status(dev);
> + return -ETIMEDOUT;
> +}
> +
> +static int
> +qup_i2c_poll_state(struct qup_i2c_dev *dev, uint32_t state)
> +{
> + uint32_t retries = 0;
> +
> + dev_dbg(dev->dev, "Polling Status for state:0x%x\n", state);
&dev->dev
> +
> + while (retries != QUP_MAX_RETRIES) {
> + uint32_t status = readl(dev->base + QUP_STATE);
> +
> + if ((status & (QUP_STATE_VALID | state)) ==
> + (QUP_STATE_VALID | state))
> + return 0;
> + else if (retries++ == 1000)
> + udelay(100);
> + }
> + return -ETIMEDOUT;
> +}
> +
> +#ifdef DEBUG
> +static void qup_verify_fifo(struct qup_i2c_dev *dev, uint32_t val,
> + uint32_t addr, int rdwr)
> +{
> + if (rdwr)
> + dev_dbg(dev->dev, "RD:Wrote 0x%x to out_ff:0x%x\n", val, addr);
> + else
> + dev_dbg(dev->dev, "WR:Wrote 0x%x to out_ff:0x%x\n", val, addr);
> +}
> +#else
> +static inline void qup_verify_fifo(struct qup_i2c_dev *dev, uint32_t val,
> + uint32_t addr, int rdwr)
> +{
> +}
> +#endif
> +
> +static void
> +qup_issue_read(struct qup_i2c_dev *dev, struct i2c_msg *msg, int *idx,
> + uint32_t carry_over)
> +{
> + uint16_t addr = (msg->addr << 1) | 1;
> + /* QUP limit 256 bytes per read. By HW design, 0 in the 8-bit field
> + * is treated as 256 byte read.
> + */
multi line comments would be neater, as you have used it in other places
of this code.
> + uint16_t rd_len = ((dev->cnt == 256) ? 0 : dev->cnt);
> +
> + if (*idx % 4) {
> + writel(carry_over | ((QUP_OUT_START | addr) << 16),
> + dev->base + QUP_OUT_FIFO_BASE);/* + (*idx-2)); */
remove the dead commented code above and below as it is not carrying any
meaningful thoughts about the calculation
> +
> + qup_verify_fifo(dev, carry_over |
> + ((QUP_OUT_START | addr) << 16), (uint32_t)dev->base
> + + QUP_OUT_FIFO_BASE + (*idx - 2), 1);
qup_verify_fifo is _not_ actually verifying anything, better remove this
function and its usage..
> + writel((QUP_OUT_REC | rd_len),
> + dev->base + QUP_OUT_FIFO_BASE);/* + (*idx+2)); */
> +
> + qup_verify_fifo(dev, (QUP_OUT_REC | rd_len),
> + (uint32_t)dev->base + QUP_OUT_FIFO_BASE + (*idx + 2), 1);
> + } else {
> + writel(((QUP_OUT_REC | rd_len) << 16) | QUP_OUT_START | addr,
> + dev->base + QUP_OUT_FIFO_BASE);/* + (*idx)); */
> +
> + qup_verify_fifo(dev, QUP_OUT_REC << 16 | rd_len << 16 |
> + QUP_OUT_START | addr,
> + (uint32_t)dev->base + QUP_OUT_FIFO_BASE + (*idx), 1);
> + }
> + *idx += 4;
> +}
> +
> +static void
> +qup_issue_write(struct qup_i2c_dev *dev, struct i2c_msg *msg, int rem,
> + int *idx, uint32_t *carry_over)
> +{
> + int entries = dev->cnt;
> + int empty_sl = dev->wr_sz - ((*idx) >> 1);
> + int i = 0;
> + uint32_t val = 0;
> + uint32_t last_entry = 0;
> + uint16_t addr = msg->addr << 1;
> +
> + if (dev->pos == 0) {
> + if (*idx % 4) {
> + writel(*carry_over | ((QUP_OUT_START | addr) << 16),
> + dev->base + QUP_OUT_FIFO_BASE);
> +
> + qup_verify_fifo(dev, *carry_over | QUP_OUT_DATA << 16 |
> + addr << 16, (uint32_t)dev->base +
> + QUP_OUT_FIFO_BASE + (*idx) - 2, 0);
> + } else
> + val = QUP_OUT_START | addr;
> + *idx += 2;
> + i++;
> + entries++;
> + } else {
> + /* Avoid setp time issue by adding 1 NOP when number of bytes
s/setp/setup
> + * are more than FIFO/BLOCK size. setup time issue can't appear
> + * otherwise since next byte to be written will always be ready
> + */
> + val = (QUP_OUT_NOP | 1);
> + *idx += 2;
> + i++;
> + entries++;
> + }
> + if (entries > empty_sl)
> + entries = empty_sl;
> +
> + for (; i < (entries - 1); i++) {
> + if (*idx % 4) {
> + writel(val | ((QUP_OUT_DATA |
> + msg->buf[dev->pos]) << 16),
> + dev->base + QUP_OUT_FIFO_BASE);
> +
> + qup_verify_fifo(dev, val | QUP_OUT_DATA << 16 |
> + msg->buf[dev->pos] << 16, (uint32_t)dev->base +
> + QUP_OUT_FIFO_BASE + (*idx) - 2, 0);
> + } else
> + val = QUP_OUT_DATA | msg->buf[dev->pos];
> + (*idx) += 2;
> + dev->pos++;
> + }
> + if (dev->pos < (msg->len - 1))
> + last_entry = QUP_OUT_DATA;
> + else if (rem > 1) /* not last array entry */
> + last_entry = QUP_OUT_DATA;
> + else
> + last_entry = QUP_OUT_STOP;
> + if ((*idx % 4) == 0) {
> + /*
> + * If read-start and read-command end up in different fifos, it
> + * may result in extra-byte being read due to extra-read cycle.
> + * Avoid that by inserting NOP as the last entry of fifo only
> + * if write command(s) leave 1 space in fifo.
> + */
> + if (rem > 1) {
> + struct i2c_msg *next = msg + 1;
> + if (next->addr == msg->addr && (next->flags | I2C_M_RD)
> + && *idx == ((dev->wr_sz*2) - 4)) {
> + writel(((last_entry | msg->buf[dev->pos]) |
> + ((1 | QUP_OUT_NOP) << 16)), dev->base +
> + QUP_OUT_FIFO_BASE);/* + (*idx) - 2); */
> + *idx += 2;
> + } else
> + *carry_over = (last_entry | msg->buf[dev->pos]);
> + } else {
> + writel((last_entry | msg->buf[dev->pos]),
> + dev->base + QUP_OUT_FIFO_BASE);/* + (*idx) - 2); */
> +
> + qup_verify_fifo(dev, last_entry | msg->buf[dev->pos],
> + (uint32_t)dev->base + QUP_OUT_FIFO_BASE +
> + (*idx), 0);
wrap this around, should be possible within 80 chars..
> + }
> + } else {
> + writel(val | ((last_entry | msg->buf[dev->pos]) << 16),
> + dev->base + QUP_OUT_FIFO_BASE);/* + (*idx) - 2); */
> +
> + qup_verify_fifo(dev, val | (last_entry << 16) |
> + (msg->buf[dev->pos] << 16), (uint32_t)dev->base +
> + QUP_OUT_FIFO_BASE + (*idx) - 2, 0);
> + }
> +
> + *idx += 2;
> + dev->pos++;
> + dev->cnt = msg->len - dev->pos;
> +}
> +
> +static int
> +qup_update_state(struct qup_i2c_dev *dev, uint32_t state)
> +{
> + if (qup_i2c_poll_state(dev, 0) != 0)
> + return -EIO;
> + writel(state, dev->base + QUP_STATE);
> + if (qup_i2c_poll_state(dev, state) != 0)
> + return -EIO;
> + return 0;
> +}
> +
> +static int
> +qup_set_read_mode(struct qup_i2c_dev *dev, int rd_len)
> +{
> + uint32_t wr_mode = (dev->wr_sz < dev->out_fifo_sz) ?
> + QUP_WR_BLK_MODE : 0;
> + if (rd_len > 256) {
> + dev_err(dev->dev, "HW doesn't support READs > 256 bytes\n");
Shouldn't this be &dev->dev?
> + return -EPROTONOSUPPORT;
AFAIK this is not an i2c fault error code at all.
> + }
> + if (rd_len <= dev->in_fifo_sz) {
> + writel(wr_mode | QUP_PACK_EN | QUP_UNPACK_EN,
> + dev->base + QUP_IO_MODE);
> + writel(rd_len, dev->base + QUP_MX_READ_CNT);
> + } else {
> + writel(wr_mode | QUP_RD_BLK_MODE |
> + QUP_PACK_EN | QUP_UNPACK_EN, dev->base + QUP_IO_MODE);
> + writel(rd_len, dev->base + QUP_MX_INPUT_CNT);
> + }
> + return 0;
> +}
> +
> +static int
> +qup_set_wr_mode(struct qup_i2c_dev *dev, int rem)
> +{
> + int total_len = 0;
> + int ret = 0;
> + if (dev->msg->len >= (dev->out_fifo_sz - 1)) {
> + total_len = dev->msg->len + 1 +
> + (dev->msg->len/(dev->out_blk_sz-1));
> + writel(QUP_WR_BLK_MODE | QUP_PACK_EN | QUP_UNPACK_EN,
> + dev->base + QUP_IO_MODE);
> + dev->wr_sz = dev->out_blk_sz;
> + } else
> + writel(QUP_PACK_EN | QUP_UNPACK_EN,
> + dev->base + QUP_IO_MODE);
> +
> + if (rem > 1) {
> + struct i2c_msg *next = dev->msg + 1;
> + if (next->addr == dev->msg->addr &&
> + next->flags == I2C_M_RD) {
> + ret = qup_set_read_mode(dev, next->len);
> + /* make sure read start & read command are in 1 blk */
> + if ((total_len % dev->out_blk_sz) ==
> + (dev->out_blk_sz - 1))
> + total_len += 3;
> + else
> + total_len += 2;
> + }
> + }
> + /* WRITE COUNT register valid/used only in block mode */
> + if (dev->wr_sz == dev->out_blk_sz)
> + writel(total_len, dev->base + QUP_MX_WR_CNT);
> + return ret;
> +}
> +
> +static int
> +qup_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
> +{
> + DECLARE_COMPLETION_ONSTACK(complete);
> + struct qup_i2c_dev *dev = i2c_get_adapdata(adap);
> + int ret;
> + int rem = num;
> + long timeout;
> + int err;
> +
> + del_timer_sync(&dev->pwr_timer);
> + mutex_lock(&dev->mlock);
> +
> + if (dev->suspended) {
> + mutex_unlock(&dev->mlock);
> + return -EIO;
you have failed here not because of any faulty IO operation. Would
suggest to use the proper error codes accordingly.
> + }
> +
> + if (dev->clk_state == 0) {
> + if (dev->clk_ctl == 0) {
> + if (dev->pdata->src_clk_rate > 0)
> + clk_set_rate(dev->clk,
> + dev->pdata->src_clk_rate);
> + else
> + dev->pdata->src_clk_rate = QUP_SRC_CLK_RATE;
> + }
> + qup_i2c_pwr_mgmt(dev, 1);
> + }
> +
> + /* Initialize QUP registers during first transfer */
> + if (dev->clk_ctl == 0) {
> + int fs_div;
> + int hs_div;
> + uint32_t fifo_reg;
> + writel(GSBI_I2C_PROTOCOL_CODE, dev->gsbi);
> +
> + fs_div = ((dev->pdata->src_clk_rate
> + / dev->pdata->clk_freq) / 2) - 3;
> + hs_div = 3;
> + dev->clk_ctl = ((hs_div & 0x7) << 8) | (fs_div & 0xff);
> + fifo_reg = readl(dev->base + QUP_IO_MODE);
> + if (fifo_reg & 0x3)
> + dev->out_blk_sz = (fifo_reg & 0x3) * 16;
> + else
> + dev->out_blk_sz = 16;
> + if (fifo_reg & 0x60)
> + dev->in_blk_sz = ((fifo_reg & 0x60) >> 5) * 16;
> + else
> + dev->in_blk_sz = 16;
> + /*
> + * The block/fifo size w.r.t. 'actual data' is 1/2 due to 'tag'
> + * associated with each byte written/received
> + */
> + dev->out_blk_sz /= 2;
> + dev->in_blk_sz /= 2;
> + dev->out_fifo_sz = dev->out_blk_sz *
> + (2 << ((fifo_reg & 0x1C) >> 2));
> + dev->in_fifo_sz = dev->in_blk_sz *
> + (2 << ((fifo_reg & 0x380) >> 7));
> + dev_dbg(dev->dev, "QUP IN:bl:%d, ff:%d, OUT:bl:%d, ff:%d\n",
> + dev->in_blk_sz, dev->in_fifo_sz,
> + dev->out_blk_sz, dev->out_fifo_sz);
> + }
> +
> + if (dev->num_irqs == 3) {
> + enable_irq(dev->in_irq);
> + enable_irq(dev->out_irq);
> + }
> + enable_irq(dev->err_irq);
> + writel(1, dev->base + QUP_SW_RESET);
> + ret = qup_i2c_poll_state(dev, QUP_RESET_STATE);
> + if (ret) {
> + dev_err(dev->dev, "QUP Busy:Trying to recover\n");
> + goto out_err;
> + }
> +
> + /* Initialize QUP registers */
> + writel(0, dev->base + QUP_CONFIG);
> + writel(QUP_OPERATIONAL_RESET, dev->base + QUP_OPERATIONAL);
> + writel(QUP_STATUS_ERROR_FLAGS, dev->base + QUP_ERROR_FLAGS_EN);
> +
> + writel(I2C_MINI_CORE | I2C_N_VAL, dev->base + QUP_CONFIG);
> +
> + /* Initialize I2C mini core registers */
> + writel(0, dev->base + QUP_I2C_CLK_CTL);
> + writel(QUP_I2C_STATUS_RESET, dev->base + QUP_I2C_STATUS);
> +
> + dev->cnt = msgs->len;
> + dev->pos = 0;
> + dev->msg = msgs;
> + while (rem) {
> + bool filled = false;
> +
> + dev->wr_sz = dev->out_fifo_sz;
> + dev->err = 0;
> + dev->complete = &complete;
> +
> + if (qup_i2c_poll_state(dev, QUP_I2C_MAST_GEN) != 0) {
> + ret = -EIO;
> + goto out_err;
> + }
> +
> + qup_print_status(dev);
> + /* HW limits Read upto 256 bytes in 1 read without stop */
> + if (dev->msg->flags & I2C_M_RD) {
> + ret = qup_set_read_mode(dev, dev->cnt);
> + if (ret != 0)
> + goto out_err;
> + } else {
> + ret = qup_set_wr_mode(dev, rem);
> + if (ret != 0)
> + goto out_err;
> + /* Don't fill block till we get interrupt */
> + if (dev->wr_sz == dev->out_blk_sz)
> + filled = true;
> + }
> +
> + err = qup_update_state(dev, QUP_RUN_STATE);
> + if (err < 0) {
> + ret = err;
> + goto out_err;
> + }
> +
> + qup_print_status(dev);
> + writel(dev->clk_ctl, dev->base + QUP_I2C_CLK_CTL);
> +
> + do {
> + int idx = 0;
> + uint32_t carry_over = 0;
> +
> + /* Transition to PAUSE state only possible from RUN */
> + err = qup_update_state(dev, QUP_PAUSE_STATE);
> + if (err < 0) {
> + ret = err;
> + goto out_err;
> + }
> +
> + qup_print_status(dev);
> + /* This operation is Write, check the next operation
> + * and decide mode
> + */
use consistent commenting style
> + while (filled == false) {
> + if ((msgs->flags & I2C_M_RD) &&
> + (dev->cnt == msgs->len))
> + qup_issue_read(dev, msgs, &idx,
> + carry_over);
> + else if (!(msgs->flags & I2C_M_RD))
> + qup_issue_write(dev, msgs, rem, &idx,
> + &carry_over);
> + if (idx >= (dev->wr_sz << 1))
> + filled = true;
> + /* Start new message */
> + if (filled == false) {
> + if (msgs->flags & I2C_M_RD)
> + filled = true;
> + else if (rem > 1) {
> + /* Only combine operations with
> + * same address
> + */
> + struct i2c_msg *next = msgs + 1;
> + if (next->addr != msgs->addr ||
> + next->flags == 0)
> + filled = true;
> + else {
> + rem--;
> + msgs++;
> + dev->msg = msgs;
> + dev->pos = 0;
> + dev->cnt = msgs->len;
> + }
> + } else
> + filled = true;
> + }
> + }
> + err = qup_update_state(dev, QUP_RUN_STATE);
> + if (err < 0) {
> + ret = err;
> + goto out_err;
> + }
> + dev_dbg(dev->dev, "idx:%d, rem:%d, num:%d, mode:%d\n",
> + idx, rem, num, dev->mode);
> +
> + qup_print_status(dev);
> + timeout = wait_for_completion_timeout(&complete,
> + msecs_to_jiffies(dev->out_fifo_sz));
what is this out_fifo_sz doing here?
> + if (!timeout) {
> + dev_err(dev->dev, "Transaction timed out\n");
> + writel(1, dev->base + QUP_SW_RESET);
> + ret = -ETIMEDOUT;
> + goto out_err;
> + }
> + if (dev->err) {
> + if (dev->err & QUP_I2C_NACK_FLAG)
> + dev_err(dev->dev,
> + "I2C slave addr:0x%x not connected\n",
> + dev->msg->addr);
> + else
> + dev_err(dev->dev,
> + "QUP data xfer error %d\n", dev->err);
> + ret = dev->err;
> + goto out_err;
> + }
> + if (dev->msg->flags & I2C_M_RD) {
> + int i;
> + uint32_t dval = 0;
> + for (i = 0; dev->pos < dev->msg->len; i++,
> + dev->pos++) {
> + uint32_t rd_status = readl(dev->base +
> + QUP_OPERATIONAL);
> + if (i % 2 == 0) {
> + if ((rd_status &
> + QUP_IN_NOT_EMPTY) == 0)
> + break;
> + dval = readl(dev->base +
> + QUP_IN_FIFO_BASE);
> + dev->msg->buf[dev->pos] =
> + dval & 0xFF;
> + } else
> + dev->msg->buf[dev->pos] =
> + ((dval & 0xFF0000) >>
> + 16);
> + }
> + dev->cnt -= i;
> + } else
> + filled = false; /* refill output FIFO */
> + } while (dev->cnt > 0);
> + if (dev->cnt == 0) {
> + rem--;
> + msgs++;
> + if (rem) {
> + dev->pos = 0;
> + dev->cnt = msgs->len;
> + dev->msg = msgs;
> + }
> + }
> + /* Wait for I2C bus to be idle */
> + ret = qup_i2c_poll_writeready(dev);
> + if (ret) {
> + dev_err(dev->dev,
> + "Error waiting for write ready\n");
> + goto out_err;
> + }
> + }
> +
> + ret = num;
> + out_err:
> + dev->complete = NULL;
> + dev->msg = NULL;
> + dev->pos = 0;
> + dev->err = 0;
> + dev->cnt = 0;
> + disable_irq(dev->err_irq);
> + if (dev->num_irqs == 3) {
> + disable_irq(dev->in_irq);
> + disable_irq(dev->out_irq);
> + }
> + dev->pwr_timer.expires = jiffies + 3*HZ;
> + add_timer(&dev->pwr_timer);
> + mutex_unlock(&dev->mlock);
> + return ret;
> +}
(...)
Srinidhi
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] i2c: QUP based bus driver for Qualcomm MSM chipsets
2010-07-26 11:05 ` srinidhi
@ 2010-08-12 1:50 ` Kenneth Heitke
0 siblings, 0 replies; 8+ messages in thread
From: Kenneth Heitke @ 2010-08-12 1:50 UTC (permalink / raw)
To: srinidhi.kasagar
Cc: khali@linux-fr.org, ben-linux@fluff.org,
linux-arm-msm@vger.kernel.org, Crane Cai, Samuel Ortiz,
Linus WALLEIJ, Ralf Baechle, linux-i2c@vger.kernel.org,
linux-kernel@vger.kernel.org
Srinidhi,
Thanks for your feedback.
srinidhi wrote:
> On Fri, 2010-07-23 at 04:47 +0200, Kenneth Heitke wrote:
>> This bus driver supports the QUP i2c hardware controller in the Qualcomm
>> MSM SOCs. The Qualcomm Universal Peripheral Engine (QUP) is a general
>> purpose data path engine with input/output FIFOs and an embedded i2c
>> mini-core. The driver supports FIFO mode (for low bandwidth applications)
>> and block mode (interrupt generated for each block-size data transfer).
>> The driver currently does not support DMA transfers.
>>
>> Signed-off-by: Kenneth Heitke <kheitke@codeaurora.org>
>> ---
>> drivers/i2c/busses/Kconfig | 12 +
>> drivers/i2c/busses/Makefile | 1 +
>> drivers/i2c/busses/i2c-qup.c | 1080 ++++++++++++++++++++++++++++++++++++++++++
>> include/linux/i2c-msm.h | 29 ++
>> 4 files changed, 1122 insertions(+), 0 deletions(-)
>> create mode 100644 drivers/i2c/busses/i2c-qup.c
>> create mode 100644 include/linux/i2c-msm.h
>>
>> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
>> index bceafbf..03d8f8f 100644
>> --- a/drivers/i2c/busses/Kconfig
>> +++ b/drivers/i2c/busses/Kconfig
>> @@ -521,6 +521,18 @@ config I2C_PXA_SLAVE
>> is necessary for systems where the PXA may be a target on the
>> I2C bus.
>>
>> +config I2C_QUP
>> + tristate "Qualcomm MSM QUP I2C Controller"
>> + depends on I2C && HAVE_CLK && (ARCH_MSM7X30 || ARCH_MSM8X60 || \
>> + (ARCH_QSD8X50 && MSM_SOC_REV_A))
>
> I think HAVE_CLK is redundant here
>
>> + help
>> + If you say yes to this option, support will be included for the
>> + built-in QUP I2C interface on Qualcomm MSM family processors.
>> +
>> + The Qualcomm Universal Peripheral Engine (QUP) is a general
>> + purpose data path engine with input/output FIFOs and an
>> + embedded I2C mini-core.
>> +
>> config I2C_S3C2410
>> tristate "S3C2410 I2C Driver"
>> depends on ARCH_S3C2410 || ARCH_S3C64XX
>> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
>> index 936880b..6a52572 100644
>> --- a/drivers/i2c/busses/Makefile
>> +++ b/drivers/i2c/busses/Makefile
>> @@ -50,6 +50,7 @@ obj-$(CONFIG_I2C_PCA_PLATFORM) += i2c-pca-platform.o
>> obj-$(CONFIG_I2C_PMCMSP) += i2c-pmcmsp.o
>> obj-$(CONFIG_I2C_PNX) += i2c-pnx.o
>> obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
>> +obj-$(CONFIG_I2C_QUP) += i2c-qup.o
>> obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
>> obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
>> obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
>> diff --git a/drivers/i2c/busses/i2c-qup.c b/drivers/i2c/busses/i2c-qup.c
>> new file mode 100644
>> index 0000000..3d7abab
>> --- /dev/null
>> +++ b/drivers/i2c/busses/i2c-qup.c
>> @@ -0,0 +1,1080 @@
>> +/* Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved.
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 and
>> + * only version 2 as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> + * GNU General Public License for more details.
>> + *
>> + * 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., 51 Franklin Street, Fifth Floor, Boston, MA
>> + * 02110-1301, USA.
>> + *
>> + */
>> +/*
>> + * QUP driver for Qualcomm MSM platforms
>> + *
>> + */
>> +
>> +/* #define DEBUG */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/err.h>
>> +#include <linux/init.h>
>> +#include <linux/i2c.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/delay.h>
>> +#include <linux/io.h>
>> +#include <linux/slab.h>
>> +#include <linux/mutex.h>
>> +#include <linux/timer.h>
>> +#include <linux/i2c-msm.h>
>
> I do not understand why yo need to expose this controller's private data
> to the whole Linux world. Shouldn't this be <mach/i2c-msm.h>?
>
>> +
>> +MODULE_LICENSE("GPL v2");
>> +MODULE_VERSION("0.2");
>> +MODULE_ALIAS("platform:i2c_qup");
>> +MODULE_AUTHOR("Sagar Dharia <sdharia@codeaurora.org>");
>> +
>> +/* QUP Registers */
>> +enum {
>> + QUP_CONFIG = 0x0,
>> + QUP_STATE = 0x4,
>> + QUP_IO_MODE = 0x8,
>> + QUP_SW_RESET = 0xC,
>> + QUP_OPERATIONAL = 0x18,
>> + QUP_ERROR_FLAGS = 0x1C,
>> + QUP_ERROR_FLAGS_EN = 0x20,
>> + QUP_MX_READ_CNT = 0x208,
>> + QUP_MX_INPUT_CNT = 0x200,
>> + QUP_MX_WR_CNT = 0x100,
>> + QUP_OUT_DEBUG = 0x108,
>> + QUP_OUT_FIFO_CNT = 0x10C,
>> + QUP_OUT_FIFO_BASE = 0x110,
>> + QUP_IN_READ_CUR = 0x20C,
>> + QUP_IN_DEBUG = 0x210,
>> + QUP_IN_FIFO_CNT = 0x214,
>> + QUP_IN_FIFO_BASE = 0x218,
>> + QUP_I2C_CLK_CTL = 0x400,
>> + QUP_I2C_STATUS = 0x404,
>> +};
>
> anonymous?
>
>> +
>> +/* QUP States and reset values */
>> +enum {
>> + QUP_RESET_STATE = 0,
>> + QUP_RUN_STATE = 1U,
>> + QUP_STATE_MASK = 3U,
>> + QUP_PAUSE_STATE = 3U,
>> + QUP_STATE_VALID = 1U << 2,
>> + QUP_I2C_MAST_GEN = 1U << 4,
>> + QUP_OPERATIONAL_RESET = 0xFF0,
>> + QUP_I2C_STATUS_RESET = 0xFFFFFC,
>> +};
>
> anonymous, ditto for all the following enums.
>
>> +
>> +/* QUP OPERATIONAL FLAGS */
>> +enum {
>> + QUP_OUT_SVC_FLAG = 1U << 8,
>> + QUP_IN_SVC_FLAG = 1U << 9,
>> + QUP_MX_INPUT_DONE = 1U << 11,
>> +};
>> +
>> +/* I2C mini core related values */
>> +enum {
>> + I2C_MINI_CORE = 2U << 8,
>> + I2C_N_VAL = 0xF,
>> +};
>> +
>> +/* Packing Unpacking words in FIFOs , and IO modes*/
>> +enum {
>> + QUP_WR_BLK_MODE = 1U << 10,
>> + QUP_RD_BLK_MODE = 1U << 12,
>> + QUP_UNPACK_EN = 1U << 14,
>> + QUP_PACK_EN = 1U << 15,
>> +};
>> +
>> +/* QUP tags */
>> +enum {
>> + QUP_OUT_NOP = 0,
>> + QUP_OUT_START = 1U << 8,
>> + QUP_OUT_DATA = 2U << 8,
>> + QUP_OUT_STOP = 3U << 8,
>> + QUP_OUT_REC = 4U << 8,
>> + QUP_IN_DATA = 5U << 8,
>> + QUP_IN_STOP = 6U << 8,
>> + QUP_IN_NACK = 7U << 8,
>> +};
>> +
>> +/* Status, Error flags */
>> +enum {
>> + I2C_STATUS_WR_BUFFER_FULL = 1U << 0,
>> + I2C_STATUS_BUS_ACTIVE = 1U << 8,
>> + I2C_STATUS_ERROR_MASK = 0x38000FC,
>> + QUP_I2C_NACK_FLAG = 1U << 3,
>> + QUP_IN_NOT_EMPTY = 1U << 5,
>> + QUP_STATUS_ERROR_FLAGS = 0x7C,
>> +};
>> +
>> +/* GSBI Control Register */
>> +enum {
>> + GSBI_I2C_PROTOCOL_CODE = 0x2 << 4, /* I2C protocol */
>> +};
>> +
>> +#define QUP_MAX_RETRIES 2000
>> +#define QUP_SRC_CLK_RATE 19200000 /* Default source clock rate */
>
> why do want this while having this module dependent on HAVE_CLK
>
>> +
>> +struct qup_i2c_dev {
>> + struct device *dev;
>> + void __iomem *base; /* virtual */
>> + void __iomem *gsbi; /* virtual */
>> + int in_irq;
>> + int out_irq;
>> + int err_irq;
>> + int num_irqs;
>> + struct clk *clk;
>> + struct clk *pclk;
>> + struct i2c_adapter adapter;
>> +
>> + struct i2c_msg *msg;
>> + int pos;
>> + int cnt;
>> + int err;
>> + int mode;
>> + int clk_ctl;
>> + int one_bit_t;
>> + int out_fifo_sz;
>> + int in_fifo_sz;
>> + int out_blk_sz;
>> + int in_blk_sz;
>> + int wr_sz;
>> + struct msm_i2c_platform_data *pdata;
>> + int suspended;
>> + int clk_state;
>> + struct timer_list pwr_timer;
>> + struct mutex mlock;
>> + void *complete;
>> +};
>
> too many local parameters. Do you really need all of this? Moreover it
> is not readable. Would suggest to document it at least using kernel-doc
> style.
I believe everything is needed but I will improve the documentation
>
>> +
>> +#ifdef DEBUG
>> +static void
>> +qup_print_status(struct qup_i2c_dev *dev)
>> +{
>> + uint32_t val;
>> + val = readl(dev->base+QUP_CONFIG);
>
> space after & before '+'. checkpatch would've complained this about..
>
>> + dev_dbg(dev->dev, "Qup config is :0x%x\n", val);
>> + val = readl(dev->base+QUP_STATE);
>
> ditto
>
>> + dev_dbg(dev->dev, "Qup state is :0x%x\n", val);
>> + val = readl(dev->base+QUP_IO_MODE);
>
> ditto
>
>> + dev_dbg(dev->dev, "Qup mode is :0x%x\n", val);
>> +}
>> +#else
>> +static inline void qup_print_status(struct qup_i2c_dev *dev)
>> +{
>> +}
>> +#endif
>> +
>> +static irqreturn_t
>> +qup_i2c_interrupt(int irq, void *devid)
>> +{
>> + struct qup_i2c_dev *dev = devid;
>> + uint32_t status = readl(dev->base + QUP_I2C_STATUS);
>> + uint32_t errors = readl(dev->base + QUP_ERROR_FLAGS);
>> + uint32_t op_flgs = readl(dev->base + QUP_OPERATIONAL);
>> + int err = 0;
>> +
>> + if (!dev->msg)
>> + return IRQ_HANDLED;
>> +
>> + if (status & I2C_STATUS_ERROR_MASK) {
>> + dev_err(dev->dev, "QUP: I2C status flags :0x%x, irq:%d\n",
>> + status, irq);
>> + err = -status;
>> + /* Clear Error interrupt if it's a level triggered interrupt*/
>
> /*<space> ... <space>*/
>
>> + if (dev->num_irqs == 1)
>> + writel(QUP_RESET_STATE, dev->base+QUP_STATE);
>> + goto intr_done;
>> + }
>> +
>> + if (errors & QUP_STATUS_ERROR_FLAGS) {
>> + dev_err(dev->dev, "QUP: QUP status flags :0x%x\n", errors);
>> + err = -errors;
>> + /* Clear Error interrupt if it's a level triggered interrupt*/
>
> ditto
>
>> + if (dev->num_irqs == 1)
>> + writel(errors & QUP_STATUS_ERROR_FLAGS,
>> + dev->base + QUP_ERROR_FLAGS);
>> + goto intr_done;
>> + }
>> +
>> + if ((dev->num_irqs == 3) && (dev->msg->flags == I2C_M_RD)
>> + && (irq == dev->out_irq))
>> + return IRQ_HANDLED;
>> + if (op_flgs & QUP_OUT_SVC_FLAG)
>> + writel(QUP_OUT_SVC_FLAG, dev->base + QUP_OPERATIONAL);
>> + if (dev->msg->flags == I2C_M_RD) {
>> + if ((op_flgs & QUP_MX_INPUT_DONE) ||
>> + (op_flgs & QUP_IN_SVC_FLAG))
>> + writel(QUP_IN_SVC_FLAG, dev->base + QUP_OPERATIONAL);
>> + else
>> + return IRQ_HANDLED;
>> + }
>> +
>> +intr_done:
>> + dev_dbg(dev->dev, "QUP intr= %d, i2c status=0x%x, qup status = 0x%x\n",
>> + irq, status, errors);
>> + qup_print_status(dev);
>> + dev->err = err;
>> + complete(dev->complete);
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static void
>> +qup_i2c_pwr_mgmt(struct qup_i2c_dev *dev, unsigned int state)
>> +{
>> + dev->clk_state = state;
>> + if (state != 0) {
>> + clk_enable(dev->clk);
>> + if (dev->pclk)
>> + clk_enable(dev->pclk);
>> + } else {
>> + clk_disable(dev->clk);
>> + if (dev->pclk)
>> + clk_disable(dev->pclk);
>> + }
>> +}
>> +
>> +static void
>> +qup_i2c_pwr_timer(unsigned long data)
>> +{
>> + struct qup_i2c_dev *dev = (struct qup_i2c_dev *) data;
>> + dev_dbg(dev->dev, "QUP_Power: Inactivity based power management\n");
>> + if (dev->clk_state == 1)
>> + qup_i2c_pwr_mgmt(dev, 0);
>> +}
>> +
>> +static int
>> +qup_i2c_poll_writeready(struct qup_i2c_dev *dev)
>> +{
>> + uint32_t retries = 0;
>> +
>> + while (retries != QUP_MAX_RETRIES) {
>> + uint32_t status = readl(dev->base + QUP_I2C_STATUS);
>> +
>> + if (!(status & I2C_STATUS_WR_BUFFER_FULL)) {
>> + if (!(status & I2C_STATUS_BUS_ACTIVE))
>> + return 0;
>> + else /* 1-bit delay before we check for bus busy */
>> + udelay(dev->one_bit_t);
>> + }
>> + if (retries++ == 1000)
>> + udelay(100);
>> + }
>> + qup_print_status(dev);
>> + return -ETIMEDOUT;
>> +}
>> +
>> +static int
>> +qup_i2c_poll_state(struct qup_i2c_dev *dev, uint32_t state)
>> +{
>> + uint32_t retries = 0;
>> +
>> + dev_dbg(dev->dev, "Polling Status for state:0x%x\n", state);
>
> &dev->dev
dev->dev is a pointer to a struct device so this should be valid unless
I am missing something.
>
>
> (...)
>
> Srinidhi
>
>
>
--
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH] i2c: QUP based bus driver for Qualcomm MSM chipsets
@ 2011-04-12 2:02 Kenneth Heitke
2011-04-12 4:57 ` Mark Brown
0 siblings, 1 reply; 8+ messages in thread
From: Kenneth Heitke @ 2011-04-12 2:02 UTC (permalink / raw)
To: davidb-sgV2jX0FEOL9JmXXK+q4OQ, dwalker-zu3NM2574RrQT0dZR+AlfA,
ben-linux-elnMNo+KYs3YtjvyW6yDsg
Cc: tsoni-sgV2jX0FEOL9JmXXK+q4OQ,
linux-arm-msm-u79uwXL29TY76Z2rM5mHXA,
sdharia-sgV2jX0FEOL9JmXXK+q4OQ, arve-z5hGa2qSFaRBDgjK7y7TUQ,
swetland-hpIqsD4AKlfQT0dZR+AlfA, Kenneth Heitke,
bryanh-sgV2jX0FEOL9JmXXK+q4OQ, linux-lFZ/pmaqli7XmaaqVzeoHQ,
khali-PUYAD+kWke1g9hUCZPvPmw, seth.heasley-ral2JQCrhuEAvxtiuMwx3w,
w.sang-bIcnvbaLZ9MEGnE8C9+IrQ,
tomoya-linux-ECg8zkTtlr0C6LszWs/t0g,
guenter.roeck-IzeFyvvaP7pWk0Htik3J/w,
linus.walleij-0IS4wlFg1OjSUeElwK9/Pw, open list:ARM PORT,
open list, open list:I2C SUBSYSTEM
This bus driver supports the QUP i2c hardware controller in the Qualcomm
MSM SOCs. The Qualcomm Universal Peripheral Engine (QUP) is a general
purpose data path engine with input/output FIFOs and an embedded i2c
mini-core. The driver supports FIFO mode (for low bandwidth applications)
and block mode (interrupt generated for each block-size data transfer).
The driver currently does not support DMA transfers.
Signed-off-by: Kenneth Heitke <kheitke-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>
---
The header file (msm_qup_i2c.h) defines the platform data used by the
QUP driver. This file can be picked up by the i2c maintainers but also
needs to be Acked by the msm maintainers (linux-arm-msm).
---
arch/arm/mach-msm/include/mach/msm_qup_i2c.h | 24 +
drivers/i2c/busses/Kconfig | 11 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-qup.c | 1194 ++++++++++++++++++++++++++
4 files changed, 1230 insertions(+), 0 deletions(-)
create mode 100644 arch/arm/mach-msm/include/mach/msm_qup_i2c.h
create mode 100644 drivers/i2c/busses/i2c-qup.c
diff --git a/arch/arm/mach-msm/include/mach/msm_qup_i2c.h b/arch/arm/mach-msm/include/mach/msm_qup_i2c.h
new file mode 100644
index 0000000..a96c3c2
--- /dev/null
+++ b/arch/arm/mach-msm/include/mach/msm_qup_i2c.h
@@ -0,0 +1,24 @@
+/*
+ * Qualcomm MSM QUP i2c Controller Platform Data
+ *
+ * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __MSM_QUP_I2C_MSM_H
+#define __MSM_QUP_I2C_MSM_H
+
+struct msm_qup_i2c_platform_data {
+ int bus_freq; /* I2C bus frequency (Hz) */
+ int src_clk_rate; /* I2C controller clock rate (Hz) */
+};
+
+#endif /* __MSM_QUP_I2C_MSM_H */
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 326652f..ecf0896 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -564,6 +564,17 @@ config I2C_PXA_SLAVE
is necessary for systems where the PXA may be a target on the
I2C bus.
+config I2C_QUP
+ tristate "Qualcomm MSM QUP I2C Controller"
+ depends on ARCH_MSM
+ help
+ If you say yes to this option, support will be included for the
+ built-in QUP I2C interface on Qualcomm MSM family processors.
+
+ The Qualcomm Universal Peripheral Engine (QUP) is a general
+ purpose data path engine with input/output FIFOs and an
+ embedded I2C mini-core.
+
config HAVE_S3C2410_I2C
bool
help
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index e6cf294..0516104 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -55,6 +55,7 @@ obj-$(CONFIG_I2C_PNX) += i2c-pnx.o
obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o
obj-$(CONFIG_I2C_PXA) += i2c-pxa.o
obj-$(CONFIG_I2C_PXA_PCI) += i2c-pxa-pci.o
+obj-$(CONFIG_I2C_QUP) += i2c-qup.o
obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o
obj-$(CONFIG_I2C_S6000) += i2c-s6000.o
obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o
diff --git a/drivers/i2c/busses/i2c-qup.c b/drivers/i2c/busses/i2c-qup.c
new file mode 100644
index 0000000..d769b25
--- /dev/null
+++ b/drivers/i2c/busses/i2c-qup.c
@@ -0,0 +1,1194 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <linux/mutex.h>
+#include <mach/msm_qup_i2c.h>
+
+/* QUP Registers */
+enum qup_registers {
+ QUP_CONFIG = 0x0,
+ QUP_STATE = 0x4,
+ QUP_IO_MODE = 0x8,
+ QUP_SW_RESET = 0xC,
+ QUP_OPERATIONAL = 0x18,
+ QUP_ERROR_FLAGS = 0x1C,
+ QUP_ERROR_FLAGS_EN = 0x20,
+ QUP_MX_READ_CNT = 0x208,
+ QUP_MX_INPUT_CNT = 0x200,
+ QUP_MX_WR_CNT = 0x100,
+ QUP_OUT_DEBUG = 0x108,
+ QUP_OUT_FIFO_CNT = 0x10C,
+ QUP_OUT_FIFO_BASE = 0x110,
+ QUP_IN_READ_CUR = 0x20C,
+ QUP_IN_DEBUG = 0x210,
+ QUP_IN_FIFO_CNT = 0x214,
+ QUP_IN_FIFO_BASE = 0x218,
+ QUP_I2C_CLK_CTL = 0x400,
+ QUP_I2C_STATUS = 0x404,
+};
+
+/* QUP States and reset values */
+enum qup_state {
+ QUP_RESET_STATE = 0,
+ QUP_RUN_STATE = 1U,
+ QUP_STATE_MASK = 3U,
+ QUP_PAUSE_STATE = 3U,
+ QUP_STATE_VALID = 1U << 2,
+ QUP_I2C_MAST_GEN = 1U << 4,
+ QUP_OPERATIONAL_RESET = 0xFF0,
+ QUP_I2C_STATUS_RESET = 0xFFFFFC,
+};
+
+/* QUP OPERATIONAL FLAGS */
+enum qup_operational {
+ QUP_OUT_SVC_FLAG = 1U << 8,
+ QUP_IN_SVC_FLAG = 1U << 9,
+ QUP_MX_INPUT_DONE = 1U << 11,
+};
+
+/* I2C mini core related values */
+enum qup_config {
+ I2C_MINI_CORE = 2U << 8,
+ I2C_N_VAL = 0xF,
+};
+
+/* Packing Unpacking words in FIFOs, and IO modes */
+#define QUP_OUTPUT_FIFO_SIZE_SHIFT 2
+#define QUP_INPUT_BLK_SIZE_SHIFT 5
+#define QUP_INPUT_FIFO_SIZE_SHIFT 7
+
+enum qup_io_modes {
+ QUP_OUTPUT_BLK_SIZE = 3U,
+ QUP_OUTPUT_FIFO_SIZE = 7U << QUP_OUTPUT_FIFO_SIZE_SHIFT,
+ QUP_INPUT_BLK_SIZE = 3U << QUP_INPUT_BLK_SIZE_SHIFT,
+ QUP_INPUT_FIFO_SIZE = 7U << QUP_INPUT_FIFO_SIZE_SHIFT,
+ QUP_WR_BLK_MODE = 1U << 10,
+ QUP_RD_BLK_MODE = 1U << 12,
+ QUP_UNPACK_EN = 1U << 14,
+ QUP_PACK_EN = 1U << 15,
+};
+
+/* QUP tags */
+enum qup_tags {
+ QUP_OUT_NOP = 0,
+ QUP_OUT_START = 1U << 8,
+ QUP_OUT_DATA = 2U << 8,
+ QUP_OUT_STOP = 3U << 8,
+ QUP_OUT_REC = 4U << 8,
+ QUP_IN_DATA = 5U << 8,
+ QUP_IN_STOP = 6U << 8,
+ QUP_IN_NACK = 7U << 8,
+};
+
+/* Status, Error flags */
+enum qup_status {
+ I2C_STATUS_WR_BUFFER_FULL = 1U << 0,
+ I2C_STATUS_BUS_ACTIVE = 1U << 8,
+ I2C_STATUS_ERROR_MASK = 0x38000FC,
+ QUP_I2C_NACK_FLAG = 1U << 3,
+ QUP_IN_NOT_EMPTY = 1U << 5,
+ QUP_STATUS_ERROR_FLAGS = 0x7C,
+};
+
+/* GSBI Control Register */
+enum gsbi_ctrl_reg {
+ GSBI_I2C_PROTOCOL_CODE = 0x2 << 4, /* I2C protocol */
+};
+
+/* I2C Master clock control */
+#define QUP_CLK_CTL_FS_DIVIDER_MASK 0xff
+#define QUP_CLK_CTL_HS_DIVIDER_SHIFT 0x08
+#define QUP_CLK_CTL_HS_DIVIDER_MASK 0x07
+#define QUP_CLK_CTL_HS_DIVIDER_MIN 0x03
+
+#define QUP_MAX_RETRIES 2000
+#define QUP_SRC_CLK_RATE 19200000 /* Default source clock rate */
+#define QUP_AUTOSUSPEND_DELAY (3*MSEC_PER_SEC)
+
+/**
+ * struct qup_i2c_dev
+ * @dev: parent platform device.
+ * @base: location of QUP controller I/O area in memory.
+ * @gsbi: location of QUP GSBI block I/O area in memory.
+ * @in_irq: assigned interrupt line for input interrupts.
+ * @out_irq: assigned interrupt line for output interrupts.
+ * @err_irq: assigned interrupt line for error interrupts.
+ * @num_irqs: the number of supported interrupt lines (usually 1 or 3).
+ * @clk: the I2C master clock (the I2C bus clock is derived from this clock).
+ * @pclk: hardware core clock. Needs to be enabled to access the QUP registers.
+ * @adapter: corresponding I2C adapter.
+ * @msg: the current i2c_msg segment being processed.
+ * @pos: the current position (index) into the message buffer.
+ * @cnt: the number of bytes remaining to be transferred in the current segment.
+ * @err: error status reported from the interrupt handler.
+ * @clk_ctl: clock control (clock divider values). A non-zero value indicates
+ * that the clocks have been initialized.
+ * @one_bit_t: calculated time (in usecs) to transfer one bit of data on
+ * the I2C bus (used, as necesary, for delay loops).
+ * @out_fifo_sz: the QUP output FIFO size (in bytes).
+ * @in_fifo_sz: the QUP input FIFO size (in bytes).
+ * @out_blk_sz: the QUP output block size (in bytes).
+ * @in_blk_sz: the QUP input block size (in bytes).
+ * @wr_sz: max bytes to write (either output FIFO size or output block size).
+ * @pdata: platform data.
+ * @suspended: the suspend state.
+ * @mlock: mutex used to protect this struct.
+ * @complete: completion function to signal the end of an i2c_msg transfer.
+ *
+ * Early QUP controller used three separate interrupt lines for input, output,
+ * and error interrupts. Later versions share a single interrupt line.
+ * The num_irq field indicates whether or not there is a shared interrupt line.
+ */
+
+struct qup_i2c_dev {
+ struct device *dev;
+ void __iomem *base; /* virtual */
+ void __iomem *gsbi; /* virtual */
+ int in_irq;
+ int out_irq;
+ int err_irq;
+ int num_irqs;
+ struct clk *clk;
+ struct clk *pclk;
+ struct i2c_adapter adapter;
+ struct i2c_msg *msg;
+ int pos;
+ int cnt;
+ int err;
+ int clk_ctl;
+ int one_bit_t;
+ int out_fifo_sz;
+ int in_fifo_sz;
+ int out_blk_sz;
+ int in_blk_sz;
+ int wr_sz;
+ struct msm_qup_i2c_platform_data *pdata;
+ int suspended;
+ struct mutex mlock;
+ void *complete;
+};
+
+#ifdef DEBUG
+static void
+qup_print_status(struct qup_i2c_dev *dev)
+{
+ uint32_t val;
+ val = readl(dev->base + QUP_CONFIG);
+ dev_dbg(dev->dev, "Qup config is :0x%x\n", val);
+ val = readl(dev->base + QUP_STATE);
+ dev_dbg(dev->dev, "Qup state is :0x%x\n", val);
+ val = readl(dev->base + QUP_IO_MODE);
+ dev_dbg(dev->dev, "Qup mode is :0x%x\n", val);
+}
+#else
+static inline void qup_print_status(struct qup_i2c_dev *dev)
+{
+}
+#endif
+
+static inline void qup_i2c_clk_enable(struct qup_i2c_dev *dev)
+{
+ dev_dbg(dev->dev, "%s\n", __func__);
+
+ clk_enable(dev->clk);
+ clk_enable(dev->pclk);
+}
+
+static inline void qup_i2c_clk_disable(struct qup_i2c_dev *dev)
+{
+ dev_dbg(dev->dev, "%s\n", __func__);
+
+ clk_disable(dev->clk);
+ clk_disable(dev->pclk);
+}
+
+static inline void qup_i2c_pwr_enable(struct qup_i2c_dev *dev)
+{
+ dev_dbg(dev->dev, "%s\n", __func__);
+
+ if (pm_runtime_enabled(dev->dev))
+ pm_runtime_get_sync(dev->dev);
+ else
+ qup_i2c_clk_enable(dev);
+}
+
+static inline void qup_i2c_pwr_disable(struct qup_i2c_dev *dev)
+{
+ dev_dbg(dev->dev, "%s\n", __func__);
+
+ if (pm_runtime_enabled(dev->dev)) {
+ pm_runtime_mark_last_busy(dev->dev);
+ pm_runtime_put_sync(dev->dev);
+ } else
+ qup_i2c_clk_disable(dev);
+}
+
+static irqreturn_t
+qup_i2c_interrupt(int irq, void *devid)
+{
+ struct qup_i2c_dev *dev = devid;
+ uint32_t status = readl(dev->base + QUP_I2C_STATUS);
+ uint32_t errors = readl(dev->base + QUP_ERROR_FLAGS);
+ uint32_t op_flgs = readl(dev->base + QUP_OPERATIONAL);
+ int err = 0;
+
+ if (!dev->msg || !dev->complete) {
+ /* Clear Error interrupt if it's a level triggered interrupt */
+ if (dev->num_irqs == 1)
+ writel(QUP_RESET_STATE, dev->base + QUP_STATE);
+ return IRQ_HANDLED;
+ }
+
+ if (status & I2C_STATUS_ERROR_MASK) {
+ dev_dbg(dev->dev, "QUP: I2C status flags :0x%x, irq:%d\n",
+ status, irq);
+ err = status;
+ /* Clear Error interrupt if it's a level triggered interrupt */
+ if (dev->num_irqs == 1)
+ writel(QUP_RESET_STATE, dev->base + QUP_STATE);
+ goto intr_done;
+ }
+
+ if (errors & QUP_STATUS_ERROR_FLAGS) {
+ dev_dbg(dev->dev, "QUP: QUP status flags :0x%x\n", errors);
+ err = -errors;
+ /* Clear Error interrupt if it's a level triggered interrupt */
+ if (dev->num_irqs == 1)
+ writel(errors & QUP_STATUS_ERROR_FLAGS,
+ dev->base + QUP_ERROR_FLAGS);
+ goto intr_done;
+ }
+
+ if ((dev->num_irqs == 3) && (dev->msg->flags == I2C_M_RD)
+ && (irq == dev->out_irq))
+ return IRQ_HANDLED;
+ if (op_flgs & QUP_OUT_SVC_FLAG)
+ writel(QUP_OUT_SVC_FLAG, dev->base + QUP_OPERATIONAL);
+ if (dev->msg->flags == I2C_M_RD) {
+ if ((op_flgs & QUP_MX_INPUT_DONE) ||
+ (op_flgs & QUP_IN_SVC_FLAG))
+ writel(QUP_IN_SVC_FLAG, dev->base + QUP_OPERATIONAL);
+ else
+ return IRQ_HANDLED;
+ }
+
+intr_done:
+ dev_dbg(dev->dev, "QUP intr= %d, i2c status=0x%x, qup status = 0x%x\n",
+ irq, status, errors);
+ qup_print_status(dev);
+ dev->err = err;
+ complete(dev->complete);
+ return IRQ_HANDLED;
+}
+
+static int
+qup_i2c_poll_writeready(struct qup_i2c_dev *dev, int rem)
+{
+ uint32_t retries = 0;
+
+ while (retries != QUP_MAX_RETRIES) {
+ uint32_t status = readl(dev->base + QUP_I2C_STATUS);
+
+ if (!(status & I2C_STATUS_WR_BUFFER_FULL)) {
+ if (((dev->msg->flags & I2C_M_RD) || (rem == 0)) &&
+ !(status & I2C_STATUS_BUS_ACTIVE))
+ return 0;
+ else if ((dev->msg->flags == 0) && (rem > 0))
+ return 0;
+ else /* 1-bit delay before we check for bus busy */
+ udelay(dev->one_bit_t);
+ }
+ if (retries++ == 1000)
+ udelay(100);
+ }
+ qup_print_status(dev);
+ return -ETIMEDOUT;
+}
+
+static int
+qup_i2c_poll_state(struct qup_i2c_dev *dev, uint32_t state)
+{
+ uint32_t retries = 0;
+
+ dev_dbg(dev->dev, "Polling Status for state:0x%x\n", state);
+
+ while (retries != QUP_MAX_RETRIES) {
+ uint32_t status = readl(dev->base + QUP_STATE);
+
+ if ((status & (QUP_STATE_VALID | state)) ==
+ (QUP_STATE_VALID | state))
+ return 0;
+ else if (retries++ == 1000)
+ udelay(100);
+ }
+ return -ETIMEDOUT;
+}
+
+#ifdef DEBUG
+static void qup_print_fifo_entry(struct qup_i2c_dev *dev, uint32_t val,
+ uint32_t offset, int rdwr)
+{
+ if (rdwr)
+ dev_dbg(dev->dev, "RD:Wrote 0x%x to out_ff:0x%p\n",
+ val, dev->base + QUP_OUT_FIFO_BASE + offset);
+ else
+ dev_dbg(dev->dev, "WR:Wrote 0x%x to out_ff:0x%p\n",
+ val, dev->base + QUP_OUT_FIFO_BASE + offset);
+}
+#else
+static inline void qup_print_fifo_entry(struct qup_i2c_dev *dev, uint32_t val,
+ uint32_t offset, int rdwr)
+{
+}
+#endif
+
+static void
+qup_issue_read(struct qup_i2c_dev *dev, struct i2c_msg *msg, int *idx,
+ uint32_t carry_over)
+{
+ uint16_t addr = (msg->addr << 1) | 1;
+ /*
+ * QUP limit 256 bytes per read. By HW design, 0 in the 8-bit field
+ * is treated as 256 byte read.
+ */
+ uint16_t rd_len = ((dev->cnt == 256) ? 0 : dev->cnt);
+
+ if (*idx % 4) {
+ writel(carry_over | ((QUP_OUT_START | addr) << 16),
+ dev->base + QUP_OUT_FIFO_BASE);
+
+ qup_print_fifo_entry(dev, carry_over |
+ ((QUP_OUT_START | addr) << 16), *idx - 2, 1);
+ writel((QUP_OUT_REC | rd_len),
+ dev->base + QUP_OUT_FIFO_BASE);
+
+ qup_print_fifo_entry(dev, (QUP_OUT_REC | rd_len), *idx + 2, 1);
+ } else {
+ writel(((QUP_OUT_REC | rd_len) << 16) | QUP_OUT_START | addr,
+ dev->base + QUP_OUT_FIFO_BASE);
+
+ qup_print_fifo_entry(dev, QUP_OUT_REC << 16 | rd_len << 16 |
+ QUP_OUT_START | addr, *idx, 1);
+ }
+ *idx += 4;
+}
+
+static void
+qup_issue_write(struct qup_i2c_dev *dev, struct i2c_msg *msg, int rem,
+ int *idx, uint32_t *carry_over)
+{
+ int entries = dev->cnt;
+ int empty_sl = dev->wr_sz - ((*idx) >> 1);
+ int i = 0;
+ uint32_t val = 0;
+ uint32_t last_entry = 0;
+ uint16_t addr = msg->addr << 1;
+
+ if (dev->pos == 0) {
+ if (*idx % 4) {
+ writel(*carry_over | ((QUP_OUT_START | addr) << 16),
+ dev->base + QUP_OUT_FIFO_BASE);
+
+ qup_print_fifo_entry(dev,
+ *carry_over | QUP_OUT_START << 16 |
+ addr << 16, *idx - 2, 0);
+ } else
+ val = QUP_OUT_START | addr;
+ *idx += 2;
+ i++;
+ entries++;
+ } else {
+ /*
+ * Avoid setup time issue by adding 1 NOP when number of bytes
+ * are more than FIFO/BLOCK size. setup time issue can't appear
+ * otherwise since next byte to be written will always be ready
+ */
+ val = (QUP_OUT_NOP | 1);
+ *idx += 2;
+ i++;
+ entries++;
+ }
+ if (entries > empty_sl)
+ entries = empty_sl;
+
+ for (; i < (entries - 1); i++) {
+ if (*idx % 4) {
+ writel(val | ((QUP_OUT_DATA |
+ msg->buf[dev->pos]) << 16),
+ dev->base + QUP_OUT_FIFO_BASE);
+
+ qup_print_fifo_entry(dev, val | QUP_OUT_DATA << 16 |
+ msg->buf[dev->pos] << 16, *idx - 2, 0);
+ } else
+ val = QUP_OUT_DATA | msg->buf[dev->pos];
+ (*idx) += 2;
+ dev->pos++;
+ }
+ if (dev->pos < (msg->len - 1))
+ last_entry = QUP_OUT_DATA;
+ else if (rem > 1) /* not last array entry */
+ last_entry = QUP_OUT_DATA;
+ else
+ last_entry = QUP_OUT_STOP;
+ if ((*idx % 4) == 0) {
+ /*
+ * If read-start and read-command end up in different fifos, it
+ * may result in extra-byte being read due to extra-read cycle.
+ * Avoid that by inserting NOP as the last entry of fifo only
+ * if write command(s) leave 1 space in fifo.
+ */
+ if (rem > 1) {
+ struct i2c_msg *next = msg + 1;
+ if (next->addr == msg->addr && (next->flags | I2C_M_RD)
+ && *idx == ((dev->wr_sz*2) - 4)) {
+ writel(((last_entry | msg->buf[dev->pos]) |
+ ((1 | QUP_OUT_NOP) << 16)), dev->base +
+ QUP_OUT_FIFO_BASE);
+ *idx += 2;
+ } else if (next->flags == 0 && dev->pos == msg->len - 1
+ && *idx < (dev->wr_sz*2)) {
+ /* Last byte of an intermittent write */
+ writel((last_entry | msg->buf[dev->pos]),
+ dev->base + QUP_OUT_FIFO_BASE);
+
+ qup_print_fifo_entry(dev,
+ last_entry | msg->buf[dev->pos],
+ *idx, 0);
+ *idx += 2;
+ } else
+ *carry_over = (last_entry | msg->buf[dev->pos]);
+ } else {
+ writel((last_entry | msg->buf[dev->pos]),
+ dev->base + QUP_OUT_FIFO_BASE);
+
+ qup_print_fifo_entry(dev,
+ last_entry | msg->buf[dev->pos], *idx, 0);
+ }
+ } else {
+ writel(val | ((last_entry | msg->buf[dev->pos]) << 16),
+ dev->base + QUP_OUT_FIFO_BASE);
+
+ qup_print_fifo_entry(dev, val | (last_entry << 16) |
+ (msg->buf[dev->pos] << 16), *idx - 2, 0);
+ }
+
+ *idx += 2;
+ dev->pos++;
+ dev->cnt = msg->len - dev->pos;
+}
+
+static int
+qup_update_state(struct qup_i2c_dev *dev, uint32_t state)
+{
+ if (qup_i2c_poll_state(dev, 0) != 0)
+ return -EIO;
+ writel(state, dev->base + QUP_STATE);
+ if (qup_i2c_poll_state(dev, state) != 0)
+ return -EIO;
+ return 0;
+}
+
+static void
+qup_set_read_mode(struct qup_i2c_dev *dev, int rd_len)
+{
+ uint32_t wr_mode = (dev->wr_sz < dev->out_fifo_sz) ?
+ QUP_WR_BLK_MODE : 0;
+ if (rd_len > 256) {
+ dev_dbg(dev->dev, "HW limit: Breaking reads in chunk of 256\n");
+ rd_len = 256;
+ }
+ if (rd_len <= dev->in_fifo_sz) {
+ writel(wr_mode | QUP_PACK_EN | QUP_UNPACK_EN,
+ dev->base + QUP_IO_MODE);
+ writel(rd_len, dev->base + QUP_MX_READ_CNT);
+ } else {
+ writel(wr_mode | QUP_RD_BLK_MODE |
+ QUP_PACK_EN | QUP_UNPACK_EN, dev->base + QUP_IO_MODE);
+ writel(rd_len, dev->base + QUP_MX_INPUT_CNT);
+ }
+}
+
+static int
+qup_set_wr_mode(struct qup_i2c_dev *dev, int rem)
+{
+ int total_len = 0;
+ int ret = 0;
+ if (dev->msg->len >= (dev->out_fifo_sz - 1)) {
+ total_len = dev->msg->len + 1 +
+ (dev->msg->len/(dev->out_blk_sz-1));
+ writel(QUP_WR_BLK_MODE | QUP_PACK_EN | QUP_UNPACK_EN,
+ dev->base + QUP_IO_MODE);
+ dev->wr_sz = dev->out_blk_sz;
+ } else
+ writel(QUP_PACK_EN | QUP_UNPACK_EN,
+ dev->base + QUP_IO_MODE);
+
+ if (rem > 1) {
+ struct i2c_msg *next = dev->msg + 1;
+ if (next->addr == dev->msg->addr &&
+ next->flags == I2C_M_RD) {
+ qup_set_read_mode(dev, next->len);
+ /* make sure read start & read command are in 1 blk */
+ if ((total_len % dev->out_blk_sz) ==
+ (dev->out_blk_sz - 1))
+ total_len += 3;
+ else
+ total_len += 2;
+ }
+ }
+ /* WRITE COUNT register valid/used only in block mode */
+ if (dev->wr_sz == dev->out_blk_sz)
+ writel(total_len, dev->base + QUP_MX_WR_CNT);
+ return ret;
+}
+
+static int
+qup_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
+{
+ DECLARE_COMPLETION_ONSTACK(complete);
+ struct qup_i2c_dev *dev = i2c_get_adapdata(adap);
+ int ret;
+ int rem = num;
+ long timeout;
+ int err;
+
+ mutex_lock(&dev->mlock);
+
+ if (dev->suspended) {
+ mutex_unlock(&dev->mlock);
+ return -EBUSY;
+ }
+
+ if (dev->clk_ctl == 0) {
+ if (dev->pdata->src_clk_rate > 0)
+ clk_set_rate(dev->clk, dev->pdata->src_clk_rate);
+ else
+ dev->pdata->src_clk_rate = QUP_SRC_CLK_RATE;
+ }
+
+ qup_i2c_pwr_enable(dev);
+
+ /* Initialize QUP registers during first transfer */
+ if (dev->clk_ctl == 0) {
+ int fs_div;
+ int hs_div;
+ uint32_t fifo_reg;
+
+ /* Configure GSBI block to use I2C functionality */
+ writel(GSBI_I2C_PROTOCOL_CODE, dev->gsbi);
+
+ /* FS clk = I2C clk / (2*(FS_DIVIDER+3)) */
+ fs_div = ((dev->pdata->src_clk_rate
+ / dev->pdata->bus_freq) / 2) - 3;
+ hs_div = QUP_CLK_CTL_HS_DIVIDER_MIN;
+
+ dev->clk_ctl = (fs_div & QUP_CLK_CTL_FS_DIVIDER_MASK) |
+ ((hs_div & QUP_CLK_CTL_HS_DIVIDER_MASK) <<
+ QUP_CLK_CTL_HS_DIVIDER_SHIFT);
+
+ fifo_reg = readl(dev->base + QUP_IO_MODE);
+ if (fifo_reg & QUP_OUTPUT_BLK_SIZE)
+ dev->out_blk_sz = (fifo_reg & QUP_OUTPUT_BLK_SIZE) * 16;
+ else
+ dev->out_blk_sz = 16;
+ if (fifo_reg & QUP_INPUT_BLK_SIZE)
+ dev->in_blk_sz = ((fifo_reg & QUP_INPUT_BLK_SIZE) >>
+ QUP_INPUT_BLK_SIZE_SHIFT) * 16;
+ else
+ dev->in_blk_sz = 16;
+ /*
+ * The block/fifo size w.r.t. 'actual data' is 1/2 due to 'tag'
+ * associated with each byte written/received
+ */
+ dev->out_blk_sz /= 2;
+ dev->in_blk_sz /= 2;
+ dev->out_fifo_sz = dev->out_blk_sz *
+ (2 << ((fifo_reg & QUP_OUTPUT_FIFO_SIZE) >>
+ QUP_OUTPUT_FIFO_SIZE_SHIFT));
+ dev->in_fifo_sz = dev->in_blk_sz *
+ (2 << ((fifo_reg & QUP_INPUT_FIFO_SIZE) >>
+ QUP_INPUT_FIFO_SIZE_SHIFT));
+ dev_dbg(dev->dev, "QUP IN:bl:%d, ff:%d, OUT:bl:%d, ff:%d\n",
+ dev->in_blk_sz, dev->in_fifo_sz,
+ dev->out_blk_sz, dev->out_fifo_sz);
+ }
+
+ if (dev->num_irqs == 3) {
+ enable_irq(dev->in_irq);
+ enable_irq(dev->out_irq);
+ }
+ enable_irq(dev->err_irq);
+ writel(1, dev->base + QUP_SW_RESET);
+ ret = qup_i2c_poll_state(dev, QUP_RESET_STATE);
+ if (ret) {
+ dev_err(dev->dev, "QUP Busy:Trying to recover\n");
+ goto out_err;
+ }
+
+ /* Initialize QUP registers */
+ writel(0, dev->base + QUP_CONFIG);
+ writel(QUP_OPERATIONAL_RESET, dev->base + QUP_OPERATIONAL);
+ writel(QUP_STATUS_ERROR_FLAGS, dev->base + QUP_ERROR_FLAGS_EN);
+
+ writel(I2C_MINI_CORE | I2C_N_VAL, dev->base + QUP_CONFIG);
+
+ /* Initialize I2C mini core registers */
+ writel(0, dev->base + QUP_I2C_CLK_CTL);
+ writel(QUP_I2C_STATUS_RESET, dev->base + QUP_I2C_STATUS);
+
+ while (rem) {
+ bool filled = false;
+
+ dev->cnt = msgs->len - dev->pos;
+ dev->msg = msgs;
+
+ dev->wr_sz = dev->out_fifo_sz;
+ dev->err = 0;
+ dev->complete = &complete;
+
+ if (qup_i2c_poll_state(dev, QUP_I2C_MAST_GEN) != 0) {
+ ret = -EIO;
+ goto out_err;
+ }
+
+ qup_print_status(dev);
+ /* HW limits Read upto 256 bytes in 1 read without stop */
+ if (dev->msg->flags & I2C_M_RD) {
+ qup_set_read_mode(dev, dev->cnt);
+ if (dev->cnt > 256)
+ dev->cnt = 256;
+ } else {
+ ret = qup_set_wr_mode(dev, rem);
+ if (ret != 0)
+ goto out_err;
+ /* Don't fill block till we get interrupt */
+ if (dev->wr_sz == dev->out_blk_sz)
+ filled = true;
+ }
+
+ err = qup_update_state(dev, QUP_RUN_STATE);
+ if (err < 0) {
+ ret = err;
+ goto out_err;
+ }
+
+ qup_print_status(dev);
+ writel(dev->clk_ctl, dev->base + QUP_I2C_CLK_CTL);
+
+ do {
+ int idx = 0;
+ uint32_t carry_over = 0;
+
+ /* Transition to PAUSE state only possible from RUN */
+ err = qup_update_state(dev, QUP_PAUSE_STATE);
+ if (err < 0) {
+ ret = err;
+ goto out_err;
+ }
+
+ qup_print_status(dev);
+ /*
+ * This operation is Write, check the next operation
+ * and decide mode
+ */
+ while (filled == false) {
+ if ((msgs->flags & I2C_M_RD))
+ qup_issue_read(dev, msgs, &idx,
+ carry_over);
+ else if (!(msgs->flags & I2C_M_RD))
+ qup_issue_write(dev, msgs, rem, &idx,
+ &carry_over);
+ if (idx >= (dev->wr_sz << 1))
+ filled = true;
+ /* Start new message */
+ if (filled == false) {
+ if (msgs->flags & I2C_M_RD)
+ filled = true;
+ else if (rem > 1) {
+ /*
+ * Only combine operations with
+ * same address
+ */
+ struct i2c_msg *next = msgs + 1;
+ if (next->addr != msgs->addr ||
+ next->flags == 0)
+ filled = true;
+ else {
+ rem--;
+ msgs++;
+ dev->msg = msgs;
+ dev->pos = 0;
+ dev->cnt = msgs->len;
+ if (msgs->len > 256)
+ dev->cnt = 256;
+ }
+ } else
+ filled = true;
+ }
+ }
+ err = qup_update_state(dev, QUP_RUN_STATE);
+ if (err < 0) {
+ ret = err;
+ goto out_err;
+ }
+ dev_dbg(dev->dev, "idx:%d, rem:%d, num:%d\n",
+ idx, rem, num);
+
+ qup_print_status(dev);
+ timeout = wait_for_completion_timeout(&complete, HZ);
+ if (!timeout) {
+ uint32_t istatus = readl(dev->base +
+ QUP_I2C_STATUS);
+ uint32_t qstatus = readl(dev->base +
+ QUP_ERROR_FLAGS);
+ uint32_t op_flgs = readl(dev->base +
+ QUP_OPERATIONAL);
+
+ dev_err(dev->dev, "Transaction timed out\n");
+ dev_err(dev->dev, "I2C Status: %x\n", istatus);
+ dev_err(dev->dev, "QUP Status: %x\n", qstatus);
+ dev_err(dev->dev, "OP Flags: %x\n", op_flgs);
+ writel(1, dev->base + QUP_SW_RESET);
+ ret = -ETIMEDOUT;
+ goto out_err;
+ }
+ if (dev->err) {
+ if (dev->err > 0 &&
+ dev->err & QUP_I2C_NACK_FLAG)
+ dev_err(dev->dev,
+ "I2C slave addr:0x%x not connected\n",
+ dev->msg->addr);
+ else if (dev->err < 0) {
+ dev_err(dev->dev,
+ "QUP data xfer error %d\n", dev->err);
+ ret = dev->err;
+ goto out_err;
+ }
+ ret = -dev->err;
+ goto out_err;
+ }
+ if (dev->msg->flags & I2C_M_RD) {
+ int i;
+ uint32_t dval = 0;
+ for (i = 0; dev->pos < dev->msg->len; i++,
+ dev->pos++) {
+ uint32_t rd_status = readl(dev->base +
+ QUP_OPERATIONAL);
+ if (i % 2 == 0) {
+ if ((rd_status &
+ QUP_IN_NOT_EMPTY) == 0)
+ break;
+ dval = readl(dev->base +
+ QUP_IN_FIFO_BASE);
+ dev->msg->buf[dev->pos] =
+ dval & 0xFF;
+ } else
+ dev->msg->buf[dev->pos] =
+ ((dval & 0xFF0000) >>
+ 16);
+ }
+ dev->cnt -= i;
+ } else
+ filled = false; /* refill output FIFO */
+ dev_dbg(dev->dev, "pos:%d, len:%d, cnt:%d\n",
+ dev->pos, msgs->len, dev->cnt);
+ } while (dev->cnt > 0);
+ if (dev->cnt == 0) {
+ if (msgs->len == dev->pos) {
+ rem--;
+ msgs++;
+ dev->pos = 0;
+ }
+ if (rem) {
+ err = qup_update_state(dev, QUP_RESET_STATE);
+ if (err < 0) {
+ ret = err;
+ goto out_err;
+ }
+ }
+ }
+ /* Wait for I2C bus to be idle */
+ ret = qup_i2c_poll_writeready(dev, rem);
+ if (ret) {
+ dev_err(dev->dev,
+ "Error waiting for write ready\n");
+ goto out_err;
+ }
+ }
+
+ ret = num;
+out_err:
+ dev->complete = NULL;
+ dev->msg = NULL;
+ dev->pos = 0;
+ dev->err = 0;
+ dev->cnt = 0;
+ disable_irq(dev->err_irq);
+ if (dev->num_irqs == 3) {
+ disable_irq(dev->in_irq);
+ disable_irq(dev->out_irq);
+ }
+ qup_i2c_pwr_disable(dev);
+ mutex_unlock(&dev->mlock);
+ return ret;
+}
+
+static u32
+qup_i2c_func(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
+}
+
+static const struct i2c_algorithm qup_i2c_algo = {
+ .master_xfer = qup_i2c_xfer,
+ .functionality = qup_i2c_func,
+};
+
+static int __devinit
+qup_i2c_probe(struct platform_device *pdev)
+{
+ struct qup_i2c_dev *dev;
+ struct resource *qup_mem, *gsbi_mem, *qup_io, *gsbi_io;
+ struct resource *in_irq, *out_irq, *err_irq;
+ struct clk *clk, *pclk;
+ int ret = 0;
+ struct msm_qup_i2c_platform_data *pdata;
+
+ dev_dbg(&pdev->dev, "qup_i2c_probe\n");
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "platform data not initialized\n");
+ return -ENOSYS;
+ }
+
+ /* We support frequencies upto FAST Mode(400KHz) */
+ if (pdata->bus_freq <= 0 || pdata->bus_freq > 400000) {
+ dev_err(&pdev->dev, "clock frequency not supported: %d Hz\n",
+ pdata->bus_freq);
+ return -EINVAL;
+ }
+
+ qup_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "qup_phys_addr");
+ if (!qup_mem) {
+ dev_err(&pdev->dev, "no qup mem resource?\n");
+ return -ENXIO;
+ }
+
+ gsbi_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "gsbi_qup_i2c_addr");
+ if (!gsbi_mem) {
+ dev_err(&pdev->dev, "no gsbi mem resource?\n");
+ return -ENXIO;
+ }
+
+ /*
+ * We only have 1 interrupt for new hardware targets and in_irq,
+ * out_irq will be NULL for those platforms
+ */
+ in_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ "qup_in_intr");
+
+ out_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ "qup_out_intr");
+
+ err_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ "qup_err_intr");
+ if (!err_irq) {
+ dev_err(&pdev->dev, "no error irq resource?\n");
+ return -ENXIO;
+ }
+
+ qup_io = devm_request_mem_region(&pdev->dev, qup_mem->start,
+ resource_size(qup_mem), pdev->name);
+ if (!qup_io) {
+ dev_err(&pdev->dev, "QUP region already claimed\n");
+ return -EBUSY;
+ }
+
+ gsbi_io = devm_request_mem_region(&pdev->dev, gsbi_mem->start,
+ resource_size(gsbi_mem), pdev->name);
+ if (!gsbi_io) {
+ dev_err(&pdev->dev, "GSBI region already claimed\n");
+ return -EBUSY;
+ }
+
+ dev = devm_kzalloc(&pdev->dev, sizeof(struct qup_i2c_dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ dev->dev = &pdev->dev;
+ dev->base = devm_ioremap(&pdev->dev,
+ qup_mem->start, resource_size(qup_mem));
+ if (!dev->base)
+ return -ENOMEM;
+
+ dev->gsbi = devm_ioremap(&pdev->dev,
+ gsbi_mem->start, resource_size(gsbi_mem));
+ if (!dev->gsbi)
+ return -ENOMEM;
+
+ clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(clk)) {
+ dev_err(&pdev->dev, "Could not get clock\n");
+ ret = PTR_ERR(clk);
+ goto err_clk_get_failed;
+ }
+
+ pclk = clk_get(&pdev->dev, "pclk");
+ if (IS_ERR(pclk)) {
+ dev_err(&pdev->dev, "Could not get pclock\n");
+ ret = PTR_ERR(pclk);
+ goto err_pclk_get_failed;
+ }
+
+ if (in_irq)
+ dev->in_irq = in_irq->start;
+ if (out_irq)
+ dev->out_irq = out_irq->start;
+ dev->err_irq = err_irq->start;
+ if (in_irq && out_irq)
+ dev->num_irqs = 3;
+ else
+ dev->num_irqs = 1;
+ dev->clk = clk;
+ dev->pclk = pclk;
+
+ platform_set_drvdata(pdev, dev);
+
+ dev->one_bit_t = DIV_ROUND_UP(USEC_PER_SEC, pdata->bus_freq);
+ dev->pdata = pdata;
+ dev->clk_ctl = 0;
+ dev->pos = 0;
+
+ /*
+ * We use num_irqs to also indicate if we got 3 interrupts or just 1.
+ * If we have just 1, we use err_irq as the general purpose irq
+ * and handle the changes in ISR accordingly
+ * Per Hardware guidelines, if we have 3 interrupts, they are always
+ * edge triggering, and if we have 1, it's always level-triggering
+ */
+ if (dev->num_irqs == 3) {
+ ret = request_irq(dev->in_irq, qup_i2c_interrupt,
+ IRQF_TRIGGER_RISING, "qup_in_intr", dev);
+ if (ret) {
+ dev_err(&pdev->dev, "request_in_irq failed\n");
+ goto err_request_irq_failed;
+ }
+ /*
+ * We assume out_irq exists if in_irq does since platform
+ * configuration either has 3 interrupts assigned to QUP or 1
+ */
+ ret = request_irq(dev->out_irq, qup_i2c_interrupt,
+ IRQF_TRIGGER_RISING, "qup_out_intr", dev);
+ if (ret) {
+ dev_err(&pdev->dev, "request_out_irq failed\n");
+ free_irq(dev->in_irq, dev);
+ goto err_request_irq_failed;
+ }
+ ret = request_irq(dev->err_irq, qup_i2c_interrupt,
+ IRQF_TRIGGER_RISING, "qup_err_intr", dev);
+ if (ret) {
+ dev_err(&pdev->dev, "request_err_irq failed\n");
+ free_irq(dev->out_irq, dev);
+ free_irq(dev->in_irq, dev);
+ goto err_request_irq_failed;
+ }
+ } else {
+ ret = request_irq(dev->err_irq, qup_i2c_interrupt,
+ IRQF_TRIGGER_HIGH, "qup_err_intr", dev);
+ if (ret) {
+ dev_err(&pdev->dev, "request_err_irq failed\n");
+ goto err_request_irq_failed;
+ }
+ }
+ disable_irq(dev->err_irq);
+ if (dev->num_irqs == 3) {
+ disable_irq(dev->in_irq);
+ disable_irq(dev->out_irq);
+ }
+ i2c_set_adapdata(&dev->adapter, dev);
+ dev->adapter.algo = &qup_i2c_algo;
+ strlcpy(dev->adapter.name,
+ "QUP I2C adapter",
+ sizeof(dev->adapter.name));
+ dev->adapter.nr = pdev->id;
+ dev->adapter.dev.parent = &pdev->dev;
+
+ dev->suspended = 0;
+ mutex_init(&dev->mlock);
+
+ pm_runtime_use_autosuspend(&pdev->dev);
+ pm_runtime_set_autosuspend_delay(&pdev->dev, QUP_AUTOSUSPEND_DELAY);
+ pm_runtime_enable(&pdev->dev);
+
+ ret = i2c_add_numbered_adapter(&dev->adapter);
+ if (ret) {
+ dev_err(&pdev->dev, "i2c_add_adapter failed\n");
+ if (dev->num_irqs == 3) {
+ free_irq(dev->out_irq, dev);
+ free_irq(dev->in_irq, dev);
+ }
+ free_irq(dev->err_irq, dev);
+ } else
+ return 0;
+
+ pm_runtime_disable(&pdev->dev);
+
+err_request_irq_failed:
+ clk_put(pclk);
+err_pclk_get_failed:
+ clk_put(clk);
+err_clk_get_failed:
+ return ret;
+}
+
+static int __devexit
+qup_i2c_remove(struct platform_device *pdev)
+{
+ struct qup_i2c_dev *dev = platform_get_drvdata(pdev);
+
+ /* Grab mutex to ensure ongoing transaction is over */
+ mutex_lock(&dev->mlock);
+ dev->suspended = 1;
+ mutex_unlock(&dev->mlock);
+ mutex_destroy(&dev->mlock);
+ qup_i2c_clk_disable(dev);
+ platform_set_drvdata(pdev, NULL);
+ if (dev->num_irqs == 3) {
+ free_irq(dev->out_irq, dev);
+ free_irq(dev->in_irq, dev);
+ }
+ free_irq(dev->err_irq, dev);
+ i2c_del_adapter(&dev->adapter);
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_set_suspended(&pdev->dev);
+ clk_put(dev->clk);
+ clk_put(dev->pclk);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int qup_i2c_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct qup_i2c_dev *qup = platform_get_drvdata(pdev);
+ dev_dbg(dev, "%s\n", __func__);
+
+ /* Grab mutex to ensure ongoing transaction is over */
+ mutex_lock(&qup->mlock);
+ qup->suspended = 1;
+ mutex_unlock(&qup->mlock);
+
+ if (pm_runtime_enabled(dev) && !pm_runtime_suspended(dev))
+ qup_i2c_clk_disable(qup);
+
+ return 0;
+}
+
+static int qup_i2c_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct qup_i2c_dev *qup = platform_get_drvdata(pdev);
+ dev_dbg(dev, "%s\n", __func__);
+
+ if (pm_runtime_enabled(dev) && !pm_runtime_suspended(dev))
+ qup_i2c_clk_enable(qup);
+
+ qup->suspended = 0;
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_PM_RUNTIME
+static int i2c_qup_runtime_idle(struct device *dev)
+{
+ dev_dbg(dev, "pm_runtime: idle...\n");
+
+ pm_request_autosuspend(dev);
+ return -EAGAIN;
+}
+
+static int i2c_qup_runtime_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct qup_i2c_dev *qup = platform_get_drvdata(pdev);
+
+ dev_dbg(dev, "pm_runtime: suspending...\n");
+
+ qup_i2c_clk_disable(qup);
+ return 0;
+}
+
+static int i2c_qup_runtime_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct qup_i2c_dev *qup = platform_get_drvdata(pdev);
+
+ dev_dbg(dev, "pm_runtime: resuming...\n");
+
+ qup_i2c_clk_enable(qup);
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops i2c_qup_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(
+ qup_i2c_suspend,
+ qup_i2c_resume
+ )
+ SET_RUNTIME_PM_OPS(
+ i2c_qup_runtime_suspend,
+ i2c_qup_runtime_resume,
+ i2c_qup_runtime_idle
+ )
+};
+
+static struct platform_driver qup_i2c_driver = {
+ .probe = qup_i2c_probe,
+ .remove = __devexit_p(qup_i2c_remove),
+ .driver = {
+ .name = "qup_i2c",
+ .owner = THIS_MODULE,
+ .pm = &i2c_qup_dev_pm_ops,
+ },
+};
+
+/* QUP may be needed to bring up other drivers */
+static int __init
+qup_i2c_init_driver(void)
+{
+ return platform_driver_register(&qup_i2c_driver);
+}
+arch_initcall(qup_i2c_init_driver);
+
+static void __exit qup_i2c_exit_driver(void)
+{
+ platform_driver_unregister(&qup_i2c_driver);
+}
+module_exit(qup_i2c_exit_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:i2c_qup");
+MODULE_AUTHOR("Sagar Dharia <sdharia-sgV2jX0FEOL9JmXXK+q4OQ@public.gmane.org>");
--
1.7.3.3
--
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH] i2c: QUP based bus driver for Qualcomm MSM chipsets
2011-04-12 2:02 Kenneth Heitke
@ 2011-04-12 4:57 ` Mark Brown
0 siblings, 0 replies; 8+ messages in thread
From: Mark Brown @ 2011-04-12 4:57 UTC (permalink / raw)
To: Kenneth Heitke
Cc: davidb, dwalker, ben-linux, tsoni, linux, linus.walleij,
linux-arm-msm, seth.heasley, w.sang, tomoya-linux, arve,
open list:I2C SUBSYSTEM, swetland, khali, open list, bryanh,
sdharia, open list:ARM PORT, guenter.roeck
On Mon, Apr 11, 2011 at 08:02:27PM -0600, Kenneth Heitke wrote:
> mini-core. The driver supports FIFO mode (for low bandwidth applications)
> and block mode (interrupt generated for each block-size data transfer).
> The driver currently does not support DMA transfers.
> +static inline void qup_i2c_pwr_disable(struct qup_i2c_dev *dev)
> +{
> + dev_dbg(dev->dev, "%s\n", __func__);
> +
> + if (pm_runtime_enabled(dev->dev)) {
> + pm_runtime_mark_last_busy(dev->dev);
> + pm_runtime_put_sync(dev->dev);
> + } else
> + qup_i2c_clk_disable(dev);
> +}
What happens if someone changes the pm_runtime configuration in between
a pwr_disable() and the corresponding enable?
> +static void
> +qup_issue_read(struct qup_i2c_dev *dev, struct i2c_msg *msg, int *idx,
> + uint32_t carry_over)
> +{
> + uint16_t addr = (msg->addr << 1) | 1;
> + /*
> + * QUP limit 256 bytes per read. By HW design, 0 in the 8-bit field
> + * is treated as 256 byte read.
> + */
> + uint16_t rd_len = ((dev->cnt == 256) ? 0 : dev->cnt);
This is a substantila incompatibility with most I2C ccontrollers i the
kernel. Is it possible for the driver to deal with this transparently,
for example by expanding into a number of continued transfers? If not
we should add support to the I2C core for determining transfer limits,
the 256 bytes limit is pretty low.
> + /* HW limits Read upto 256 bytes in 1 read without stop */
> + if (dev->msg->flags & I2C_M_RD) {
> + qup_set_read_mode(dev, dev->cnt);
> + if (dev->cnt > 256)
> + dev->cnt = 256;
This definitely seems buggy - there's no error returned to the caller?
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2011-04-12 4:57 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-07-23 2:47 [PATCH] i2c: QUP based bus driver for Qualcomm MSM chipsets Kenneth Heitke
2010-07-23 7:11 ` Trilok Soni
2010-07-23 18:56 ` Kenneth Heitke
2010-07-24 15:08 ` Trilok Soni
2010-07-26 11:05 ` srinidhi
2010-08-12 1:50 ` Kenneth Heitke
-- strict thread matches above, loose matches on Subject: below --
2011-04-12 2:02 Kenneth Heitke
2011-04-12 4:57 ` Mark Brown
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).