This driver is based on 3.18-rc1 & Hongzhou's gpio patch.
MTK I2C HW has some limitation.
1. If the i2c_msg number is more than one, STOP will be issued instead of
RS(Repeat Start) between each message.
Such as: "START + ADDR + DATA_n + STOP + START + ADDR + DATA_n + STOP ..."
2. Mediatek I2C controller support WRRD(write then read) mode, in WRRD
mode the Repeat Start will be issued between 2 messages.
In this driver if 2 messages is first write then read, the driver will
combine 2 messages using Write-Read mode so the RS will be issued between
the 2 messages.
Ex: W/R/R, driver will combine first W/R and then R.
The data series will be:
"START + WriteADDR + DATA + RS + ReadADDR + DATA + STOP + START + ReadADDR +
DATA + STOP".
3. Due to HW limitation, in this version the max transfer data length is 255
in one message. If want to transfer more than 255 bytes, HW needs the SW
driver to split the data. Take 600 bytes for example, the data need to be
divided into 3 parts 255 + 255 + 90. The data series will be:
"START + ADDR + DATA_255 + RS + ADDR + DATA_255 + RS + ADDR + DATA_90 + STOP"
instead of "START + ADDR + DATA_900 + STOP".
We haven't implement this yet, we will do this in the separate patch.
MT8135 can control I2C pins on PMIC(MT6397) by setting the i2c registers in
MT8135 side. In this case, driver should set OFFSET_PATH_DIR bit first, the
operation on other registers are still the same.
Xudong Chen (3):
dt-bindings: Add I2C bindings for mt65xx/mt81xx.
ARM: mediatek: Add I2C node for mt8135 and mt8127
I2C: mediatek: Add driver for MediaTek I2C controller
.../devicetree/bindings/i2c/i2c-mt6577.txt | 37 ++
arch/arm/boot/dts/mt8127.dtsi | 27 +
arch/arm/boot/dts/mt8135.dtsi | 51 ++
drivers/i2c/busses/Kconfig | 9 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-mt65xx.c | 728 +++++++++++++++++++++
6 files changed, 853 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-mt6577.txt
create mode 100644 drivers/i2c/busses/i2c-mt65xx.c
--
1.8.1.1.dirty
Add devicetree bindings for Mediatek Soc I2C driver.
Signed-off-by: Xudong Chen <[email protected]>
---
.../devicetree/bindings/i2c/i2c-mt6577.txt | 37 ++++++++++++++++++++++
1 file changed, 37 insertions(+)
create mode 100644 Documentation/devicetree/bindings/i2c/i2c-mt6577.txt
diff --git a/Documentation/devicetree/bindings/i2c/i2c-mt6577.txt b/Documentation/devicetree/bindings/i2c/i2c-mt6577.txt
new file mode 100644
index 0000000..b4012c8
--- /dev/null
+++ b/Documentation/devicetree/bindings/i2c/i2c-mt6577.txt
@@ -0,0 +1,37 @@
+* Mediatek's I2C controller
+
+The Mediatek's I2C controller is used to interface with I2C devices.
+
+Required properties:
+ - compatible: value should be either of the following.
+ (a) "mtk,mt6577-i2c", for i2c compatible with mt6577 i2c.
+ (b) "mtk,mt8127-i2c", for i2c compatible with mt8127 i2c.
+ (c) "mtk,mt8135-i2c", for i2c compatible with mt8135 i2c.
+ - reg: physical base address of the controller and dma base, length of memory mapped
+ region.
+ - interrupts: interrupt number to the cpu.
+ - clock-div: the fixed value for frequency divider of clock source in i2c module.
+ Each IC may be different.
+ - clocks: clock name from clock manager
+ - clock-names: clock name used in i2c driver probe
+
+Optional properties:
+ - clock-frequency: Frequency in Hz of the bus when transfer, the default value is 100000.
+ - mediatek,have-pmic: platform can control i2c form special pmic side.
+ - mediatek,have-dcm: platform has DCM(hardware digital clock manager) property.
+ - mediatek,use-push-pull: IO use push-pull mode.
+
+Example:
+
+ i2c0: i2c@1100d000 {
+ compatible = "mediatek,mt6577-i2c";
+ reg = <0x1100d000 0x70>,
+ <0x11000300 0x80>;
+ interrupts = <GIC_SPI 44 IRQ_TYPE_LEVEL_LOW>;
+ clock-frequency = <100000>;
+ mediatek,have-pmic;
+ clock-div = <16>;
+ clocks = <&i2c0_ck>, <&ap_dma_ck>;
+ clock-names = "main", "dma";
+ };
+
--
1.8.1.1.dirty
The mediatek SoCs have I2C controller that handle I2C transfer.
This patch include common I2C bus driver.
This driver is compatible with I2C controller on mt65xx/mt81xx.
Signed-off-by: Xudong Chen <[email protected]>
Change-Id: Icc17e326b9df46a226d536956e103f17b0382b6e
---
drivers/i2c/busses/Kconfig | 9 +
drivers/i2c/busses/Makefile | 1 +
drivers/i2c/busses/i2c-mt65xx.c | 728 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 738 insertions(+)
create mode 100644 drivers/i2c/busses/i2c-mt65xx.c
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 917c358..0d7d0a4 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -564,6 +564,15 @@ config I2C_MPC
This driver can also be built as a module. If so, the module
will be called i2c-mpc.
+config I2C_MT65XX
+ tristate "MediaTek I2C adapter"
+ depends on ARCH_MEDIATEK
+ help
+ This selects the MediaTek(R) Integrated Inter Circuit bus driver
+ for MT65xx and MT81xx.
+ If you want to use MediaTek(R) I2C interface, say Y or M here.
+ If unsure, say N.
+
config I2C_MV64XXX
tristate "Marvell mv64xxx I2C Controller"
depends on MV64X60 || PLAT_ORION || ARCH_SUNXI
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 78d56c5..7a9f0f3 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -54,6 +54,7 @@ obj-$(CONFIG_I2C_IMX) += i2c-imx.o
obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o
obj-$(CONFIG_I2C_KEMPLD) += i2c-kempld.o
obj-$(CONFIG_I2C_MPC) += i2c-mpc.o
+obj-$(CONFIG_I2C_MT65XX) += i2c-mt65xx.o
obj-$(CONFIG_I2C_MV64XXX) += i2c-mv64xxx.o
obj-$(CONFIG_I2C_MXS) += i2c-mxs.o
obj-$(CONFIG_I2C_NOMADIK) += i2c-nomadik.o
diff --git a/drivers/i2c/busses/i2c-mt65xx.c b/drivers/i2c/busses/i2c-mt65xx.c
new file mode 100644
index 0000000..8730153
--- /dev/null
+++ b/drivers/i2c/busses/i2c-mt65xx.c
@@ -0,0 +1,728 @@
+/*
+ * Copyright (c) 2014 MediaTek Inc.
+ * Author: Xudong.chen <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/wait.h>
+#include <linux/mm.h>
+#include <linux/dma-mapping.h>
+#include <linux/scatterlist.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/clk.h>
+
+#define I2C_HS_NACKERR (1 << 2)
+#define I2C_ACKERR (1 << 1)
+#define I2C_TRANSAC_COMP (1 << 0)
+#define I2C_TRANSAC_START (1 << 0)
+#define I2C_TIMING_STEP_DIV_MASK (0x3f << 0)
+#define I2C_TIMING_SAMPLE_COUNT_MASK (0x7 << 0)
+#define I2C_TIMING_SAMPLE_DIV_MASK (0x7 << 8)
+#define I2C_TIMING_DATA_READ_MASK (0x7 << 12)
+#define I2C_DCM_DISABLE 0x0000
+#define I2C_IO_CONFIG_OPEN_DRAIN 0x0003
+#define I2C_IO_CONFIG_PUSH_PULL 0x0000
+#define I2C_SOFT_RST 0x0001
+#define I2C_FIFO_ADDR_CLR 0x0001
+#define I2C_DELAY_LEN 0x0002
+#define I2C_ST_START_CON 0x8001
+#define I2C_FS_START_CON 0x1800
+#define I2C_TIME_CLR_VALUE 0x0000
+#define I2C_TIME_DEFAULT_VALUE 0x0003
+#define I2C_WRRD_TRANAC_VALUE 0x0002
+#define I2C_RD_TRANAC_VALUE 0x0001
+
+
+
+#define I2C_DMA_CON_TX 0x0000
+#define I2C_DMA_CON_RX 0x0001
+#define I2C_DMA_START_EN 0x0001
+#define I2C_DMA_INT_FLAG_NONE 0x0000
+#define I2C_DMA_CLR_FLAG 0x0000
+
+#define I2C_DEFAUT_SPEED 100000 /* hz */
+#define MAX_FS_MODE_SPEED 400000
+#define MAX_HS_MODE_SPEED 3400000
+#define MAX_DMA_TRANS_SIZE 255
+#define MAX_WRRD_TRANS_SIZE 32
+#define MAX_SAMPLE_CNT_DIV 8
+#define MAX_STEP_CNT_DIV 64
+#define MAX_HS_STEP_CNT_DIV 8
+
+#define I2C_CONTROL_RS (0x1 << 1)
+#define I2C_CONTROL_DMA_EN (0x1 << 2)
+#define I2C_CONTROL_CLK_EXT_EN (0x1 << 3)
+#define I2C_CONTROL_DIR_CHANGE (0x1 << 4)
+#define I2C_CONTROL_ACKERR_DET_EN (0x1 << 5)
+#define I2C_CONTROL_TRANSFER_LEN_CHANGE (0x1 << 6)
+#define I2C_CONTROL_WRAPPER (0x1 << 0)
+
+#define I2C_DRV_NAME "mt-i2c"
+
+enum DMA_REGS_OFFSET {
+ OFFSET_INT_FLAG = 0x0,
+ OFFSET_INT_EN = 0x04,
+ OFFSET_EN = 0x08,
+ OFFSET_CON = 0x18,
+ OFFSET_TX_MEM_ADDR = 0x1c,
+ OFFSET_RX_MEM_ADDR = 0x20,
+ OFFSET_TX_LEN = 0x24,
+ OFFSET_RX_LEN = 0x28,
+};
+
+enum i2c_trans_st_rs {
+ I2C_TRANS_STOP = 0,
+ I2C_TRANS_REPEATED_START,
+};
+
+enum {
+ FS_MODE,
+ HS_MODE,
+};
+
+enum mt_trans_op {
+ I2C_MASTER_WR = 1,
+ I2C_MASTER_RD,
+ I2C_MASTER_WRRD,
+};
+
+enum I2C_REGS_OFFSET {
+ OFFSET_DATA_PORT = 0x0,
+ OFFSET_SLAVE_ADDR = 0x04,
+ OFFSET_INTR_MASK = 0x08,
+ OFFSET_INTR_STAT = 0x0c,
+ OFFSET_CONTROL = 0x10,
+ OFFSET_TRANSFER_LEN = 0x14,
+ OFFSET_TRANSAC_LEN = 0x18,
+ OFFSET_DELAY_LEN = 0x1c,
+ OFFSET_TIMING = 0x20,
+ OFFSET_START = 0x24,
+ OFFSET_EXT_CONF = 0x28,
+ OFFSET_FIFO_STAT = 0x30,
+ OFFSET_FIFO_THRESH = 0x34,
+ OFFSET_FIFO_ADDR_CLR = 0x38,
+ OFFSET_IO_CONFIG = 0x40,
+ OFFSET_RSV_DEBUG = 0x44,
+ OFFSET_HS = 0x48,
+ OFFSET_SOFTRESET = 0x50,
+ OFFSET_DCM_EN = 0x54,
+ OFFSET_PATH_DIR = 0x60,
+ OFFSET_DEBUGSTAT = 0x64,
+ OFFSET_DEBUGCTRL = 0x68,
+ OFFSET_TRANSFER_LEN_AUX = 0x6c,
+};
+
+struct mt_i2c_data {
+ unsigned int clk_frequency; /* bus speed in Hz */
+ unsigned int flags;
+ unsigned int clk_src_div;
+};
+
+struct i2c_dma_buf {
+ u8 *vaddr;
+ dma_addr_t paddr;
+};
+
+struct mt_i2c {
+ struct i2c_adapter adap; /* i2c host adapter */
+ struct device *dev;
+ wait_queue_head_t wait; /* i2c transfer wait queue */
+ /* set in i2c probe */
+ void __iomem *base; /* i2c base addr */
+ void __iomem *pdmabase; /* dma base address*/
+ int irqnr; /* i2c interrupt number */
+ struct i2c_dma_buf dma_buf; /* memory alloc for DMA mode */
+ struct clk *clk_main; /* main clock for i2c bus */
+ struct clk *clk_dma; /* DMA clock for i2c via DMA */
+ struct clk *clk_pmic; /* PMIC clock for i2c from PMIC */
+ bool have_pmic; /* can use i2c pins form PMIC */
+ bool have_dcm; /* HW DCM function */
+ bool use_push_pull; /* IO config push-pull mode */
+ /* set when doing the transfer */
+ u16 irq_stat; /* interrupt status */
+ unsigned int speed_hz; /* The speed in transfer */
+ bool trans_stop; /* i2c transfer stop */
+ enum mt_trans_op op;
+ u16 msg_len;
+ u8 *msg_buf; /* pointer to msg data */
+ u16 msg_aux_len; /* WRRD mode to set AUX_LEN register*/
+ u16 addr; /* 7bit slave address, without read/write bit */
+ u16 timing_reg;
+ u16 high_speed_reg;
+};
+
+static inline void i2c_writew(u16 value, struct mt_i2c *i2c, u8 offset)
+{
+ writew(value, i2c->base + offset);
+}
+
+static inline u16 i2c_readw(struct mt_i2c *i2c, u8 offset)
+{
+ return readw(i2c->base + offset);
+}
+
+static inline void i2c_writel_dma(u32 value, struct mt_i2c *i2c, u8 offset)
+{
+ writel(value, i2c->pdmabase + offset);
+}
+
+static int mt_i2c_clock_enable(struct mt_i2c *i2c)
+{
+ int ret;
+
+ ret = clk_prepare_enable(i2c->clk_dma);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(i2c->clk_main);
+ if (ret)
+ goto err_main;
+
+ if (i2c->have_pmic) {
+ ret = clk_prepare_enable(i2c->clk_pmic);
+ if (ret)
+ goto err_pmic;
+ }
+ return 0;
+
+err_pmic:
+ clk_disable_unprepare(i2c->clk_main);
+err_main:
+ clk_disable_unprepare(i2c->clk_dma);
+
+ return ret;
+}
+
+static void mt_i2c_clock_disable(struct mt_i2c *i2c)
+{
+ if (i2c->have_pmic)
+ clk_disable_unprepare(i2c->clk_pmic);
+
+ clk_disable_unprepare(i2c->clk_main);
+ clk_disable_unprepare(i2c->clk_dma);
+}
+
+static void free_i2c_dma_bufs(struct mt_i2c *i2c)
+{
+ dma_free_coherent(i2c->adap.dev.parent, PAGE_SIZE,
+ i2c->dma_buf.vaddr, i2c->dma_buf.paddr);
+}
+
+static inline void mt_i2c_init_hw(struct mt_i2c *i2c)
+{
+ i2c_writew(I2C_SOFT_RST, i2c, OFFSET_SOFTRESET);
+ /* Set ioconfig */
+ if (i2c->use_push_pull)
+ i2c_writew(I2C_IO_CONFIG_PUSH_PULL, i2c, OFFSET_IO_CONFIG);
+ else
+ i2c_writew(I2C_IO_CONFIG_OPEN_DRAIN, i2c, OFFSET_IO_CONFIG);
+
+ if (i2c->have_dcm)
+ i2c_writew(I2C_DCM_DISABLE, i2c, OFFSET_DCM_EN);
+
+ i2c_writew(i2c->timing_reg, i2c, OFFSET_TIMING);
+ i2c_writew(i2c->high_speed_reg, i2c, OFFSET_HS);
+}
+
+/* calculate i2c port speed */
+static int i2c_set_speed(struct mt_i2c *i2c, unsigned int clk_src_in_hz)
+{
+ int mode;
+ unsigned int khz;
+ unsigned int step_cnt;
+ unsigned int sample_cnt;
+ unsigned int sclk;
+ unsigned int hclk;
+ unsigned int max_step_cnt;
+ unsigned int sample_div = MAX_SAMPLE_CNT_DIV;
+ unsigned int step_div;
+ unsigned int min_div;
+ unsigned int best_mul;
+ unsigned int cnt_mul;
+
+ if (i2c->speed_hz > MAX_HS_MODE_SPEED) {
+ return -EINVAL;
+ } else if (i2c->speed_hz > MAX_FS_MODE_SPEED) {
+ mode = HS_MODE;
+ max_step_cnt = MAX_HS_STEP_CNT_DIV;
+ } else {
+ mode = FS_MODE;
+ max_step_cnt = MAX_STEP_CNT_DIV;
+ }
+
+ step_div = max_step_cnt;
+ /* Find the best combination */
+ khz = i2c->speed_hz / 1000;
+ hclk = clk_src_in_hz / 1000;
+ min_div = ((hclk >> 1) + khz - 1) / khz;
+ best_mul = MAX_SAMPLE_CNT_DIV * max_step_cnt;
+
+ for (sample_cnt = 1; sample_cnt <= MAX_SAMPLE_CNT_DIV; sample_cnt++) {
+ step_cnt = (min_div + sample_cnt - 1) / sample_cnt;
+ cnt_mul = step_cnt * sample_cnt;
+ if (step_cnt > max_step_cnt)
+ continue;
+
+ if (cnt_mul < best_mul) {
+ best_mul = cnt_mul;
+ sample_div = sample_cnt;
+ step_div = step_cnt;
+ if (best_mul == min_div)
+ break;
+ }
+ }
+
+ sample_cnt = sample_div;
+ step_cnt = step_div;
+ sclk = hclk / (2 * sample_cnt * step_cnt);
+ if (sclk > khz) {
+ dev_dbg(i2c->dev, "%s mode: unsupported speed (%ldkhz)\n",
+ (mode == HS_MODE) ? "HS" : "ST/FT", (long int)khz);
+ return -ENOTSUPP;
+ }
+
+ step_cnt--;
+ sample_cnt--;
+
+ if (mode == HS_MODE) {
+ /* Set the hign speed mode register */
+ i2c->timing_reg = I2C_TIME_CLR_VALUE;
+ i2c->high_speed_reg = I2C_TIME_DEFAULT_VALUE |
+ (sample_cnt & I2C_TIMING_SAMPLE_COUNT_MASK) << 12 |
+ (step_cnt & I2C_TIMING_SAMPLE_COUNT_MASK) << 8;
+ } else {
+ i2c->timing_reg =
+ (sample_cnt & I2C_TIMING_SAMPLE_COUNT_MASK) << 8 |
+ (step_cnt & I2C_TIMING_STEP_DIV_MASK) << 0;
+ /* Disable the high speed transaction */
+ i2c->high_speed_reg = I2C_TIME_CLR_VALUE;
+ }
+
+ return 0;
+}
+
+static int mt_i2c_do_transfer(struct mt_i2c *i2c)
+{
+ u16 addr_reg;
+ u16 control_reg;
+ int tmo = i2c->adap.timeout;
+
+ i2c->trans_stop = false;
+ i2c->irq_stat = 0;
+
+ /* If use i2c pin from PMIC mt6397 side, need set PATH_DIR first */
+ if (i2c->have_pmic)
+ i2c_writew(I2C_CONTROL_WRAPPER, i2c, OFFSET_PATH_DIR);
+
+ control_reg = I2C_CONTROL_ACKERR_DET_EN |
+ I2C_CONTROL_CLK_EXT_EN | I2C_CONTROL_DMA_EN;
+ if (i2c->speed_hz > 400000)
+ control_reg |= I2C_CONTROL_RS;
+ if (i2c->op == I2C_MASTER_WRRD)
+ control_reg |= I2C_CONTROL_DIR_CHANGE | I2C_CONTROL_RS;
+ i2c_writew(control_reg, i2c, OFFSET_CONTROL);
+
+ /* set start condition */
+ if (i2c->speed_hz <= 100000)
+ i2c_writew(I2C_ST_START_CON, i2c, OFFSET_EXT_CONF);
+ else
+ i2c_writew(I2C_FS_START_CON, i2c, OFFSET_EXT_CONF);
+
+ if (~control_reg & I2C_CONTROL_RS)
+ i2c_writew(I2C_DELAY_LEN, i2c, OFFSET_DELAY_LEN);
+
+ addr_reg = i2c->addr << 1;
+ if (i2c->op == I2C_MASTER_RD)
+ addr_reg |= 0x1;
+ i2c_writew(addr_reg, i2c, OFFSET_SLAVE_ADDR);
+
+ /* Clear interrupt status */
+ i2c_writew(I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP,
+ i2c, OFFSET_INTR_STAT);
+ i2c_writew(I2C_FIFO_ADDR_CLR, i2c, OFFSET_FIFO_ADDR_CLR);
+
+ /* Enable interrupt */
+ i2c_writew(I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP,
+ i2c, OFFSET_INTR_MASK);
+
+ /* Set transfer and transaction len */
+ if (i2c->op == I2C_MASTER_WRRD) {
+ i2c_writew(i2c->msg_len | (i2c->msg_aux_len) << 8,
+ i2c, OFFSET_TRANSFER_LEN);
+ i2c_writew(I2C_WRRD_TRANAC_VALUE, i2c, OFFSET_TRANSAC_LEN);
+ } else {
+ i2c_writew(i2c->msg_len, i2c, OFFSET_TRANSFER_LEN);
+ i2c_writew(I2C_RD_TRANAC_VALUE, i2c, OFFSET_TRANSAC_LEN);
+ }
+
+ /* Prepare buffer data to start transfer */
+ if (i2c->op == I2C_MASTER_RD) {
+ i2c_writel_dma(I2C_DMA_INT_FLAG_NONE, i2c, OFFSET_INT_FLAG);
+ i2c_writel_dma(I2C_DMA_CON_RX, i2c, OFFSET_CON);
+ i2c_writel_dma((u32)i2c->msg_buf, i2c, OFFSET_RX_MEM_ADDR);
+ i2c_writel_dma(i2c->msg_len, i2c, OFFSET_RX_LEN);
+ } else if (i2c->op == I2C_MASTER_WR) {
+ i2c_writel_dma(I2C_DMA_INT_FLAG_NONE, i2c, OFFSET_INT_FLAG);
+ i2c_writel_dma(I2C_DMA_CON_TX, i2c, OFFSET_CON);
+ i2c_writel_dma((u32)i2c->msg_buf, i2c, OFFSET_TX_MEM_ADDR);
+ i2c_writel_dma(i2c->msg_len, i2c, OFFSET_TX_LEN);
+ } else {
+ i2c_writel_dma(I2C_DMA_CLR_FLAG, i2c, OFFSET_INT_FLAG);
+ i2c_writel_dma(I2C_DMA_CLR_FLAG, i2c, OFFSET_CON);
+ i2c_writel_dma((u32)i2c->msg_buf, i2c, OFFSET_TX_MEM_ADDR);
+ i2c_writel_dma((u32)i2c->dma_buf.paddr, i2c,
+ OFFSET_RX_MEM_ADDR);
+ i2c_writel_dma(i2c->msg_len, i2c, OFFSET_TX_LEN);
+ i2c_writel_dma(i2c->msg_aux_len, i2c, OFFSET_RX_LEN);
+ }
+
+ /* flush before sending start */
+ mb();
+ i2c_writel_dma(I2C_DMA_START_EN, i2c, OFFSET_EN);
+ i2c_writew(I2C_TRANSAC_START, i2c, OFFSET_START);
+
+ tmo = wait_event_timeout(i2c->wait, i2c->trans_stop, tmo);
+ if (tmo == 0) {
+ dev_dbg(i2c->dev, "addr: %x, transfer timeout\n", i2c->addr);
+ mt_i2c_init_hw(i2c);
+ return -ETIMEDOUT;
+ }
+
+ if (i2c->irq_stat & (I2C_HS_NACKERR | I2C_ACKERR)) {
+ dev_dbg(i2c->dev, "addr: %x, transfer ACK error\n", i2c->addr);
+ mt_i2c_init_hw(i2c);
+ return -EREMOTEIO;
+ }
+
+ dev_dbg(i2c->dev, "i2c transfer done.\n");
+
+ return 0;
+}
+
+static inline void mt_i2c_copy_to_dma(struct mt_i2c *i2c, struct i2c_msg *msg)
+{
+ /* if the operate is write, need to copy the data to DMA memory */
+ if (!(msg->flags & I2C_M_RD))
+ memcpy(i2c->dma_buf.vaddr, msg->buf, msg->len);
+}
+
+static inline void mt_i2c_copy_from_dma(struct mt_i2c *i2c,
+ struct i2c_msg *msg)
+{
+ /* if the operate is read, need to copy the data from DMA memory */
+ if (msg->flags & I2C_M_RD)
+ memcpy(msg->buf, i2c->dma_buf.vaddr, msg->len);
+}
+
+/*
+ * In MTK platform the STOP will be issued after each
+ * message was transferred which is not flow the clarify
+ * for i2c_transfer(), several I2C devices tolerate the STOP,
+ * but some device need Repeat-Start and do not compatible with STOP
+ * MTK platform has WRRD mode which can write then read with
+ * Repeat-Start between two message, so we combined two
+ * messages into one transaction.
+ * The max read length is 31
+ */
+static bool mt_i2c_should_combine(struct i2c_msg *msg)
+{
+ struct i2c_msg *next_msg = msg + 1;
+
+ if ((next_msg->len < MAX_WRRD_TRANS_SIZE) &&
+ msg->addr == next_msg->addr &&
+ !(msg->flags & I2C_M_RD) &&
+ (next_msg->flags & I2C_M_RD) == I2C_M_RD) {
+ return true;
+ }
+
+ return false;
+}
+
+static int mt_i2c_transfer(struct i2c_adapter *adap,
+ struct i2c_msg msgs[], int num)
+{
+ int ret;
+ int left_num = num;
+ struct mt_i2c *i2c = i2c_get_adapdata(adap);
+
+ ret = mt_i2c_clock_enable(i2c);
+ if (ret)
+ return ret;
+
+ while (left_num--) {
+ /* In MTK platform the max transfer number is 255 */
+ if (msgs->len > MAX_DMA_TRANS_SIZE) {
+ dev_dbg(i2c->dev,
+ " message data length is more than 255\n");
+ ret = -EINVAL;
+ goto err_exit;
+ }
+
+ if (msgs->addr == 0) {
+ dev_dbg(i2c->dev, " addr is invalid.\n");
+ ret = -EINVAL;
+ goto err_exit;
+ }
+
+ if (msgs->buf == NULL) {
+ dev_dbg(i2c->dev, " data buffer is NULL.\n");
+ ret = -EINVAL;
+ goto err_exit;
+ }
+
+ i2c->addr = msgs->addr;
+ i2c->msg_len = msgs->len;
+ i2c->msg_buf = msgs->buf;
+
+ if (msgs->flags & I2C_M_RD)
+ i2c->op = I2C_MASTER_RD;
+ else
+ i2c->op = I2C_MASTER_WR;
+
+ /* combined two messages into one transaction */
+ if (left_num >= 1 && mt_i2c_should_combine(msgs)) {
+ i2c->msg_aux_len = (msgs + 1)->len;
+ i2c->op = I2C_MASTER_WRRD;
+ left_num--;
+ }
+
+ /*
+ * always use DMA mode.
+ * 1st when write need copy the data of message to dma memory
+ * 2nd when read need copy the DMA data to the message buffer.
+ * The length should be less than 255.
+ */
+ mt_i2c_copy_to_dma(i2c, msgs);
+ i2c->msg_buf = (u8 *)i2c->dma_buf.paddr;
+ ret = mt_i2c_do_transfer(i2c);
+ if (ret < 0)
+ goto err_exit;
+
+ if (i2c->op == I2C_MASTER_WRRD)
+ mt_i2c_copy_from_dma(i2c, msgs + 1);
+ else
+ mt_i2c_copy_from_dma(i2c, msgs);
+
+ msgs++;
+
+ /* after combined two messages so we need ignore one */
+ if (left_num > 0 && i2c->op == I2C_MASTER_WRRD)
+ msgs++;
+ }
+ /* the return value is number of executed messages */
+ ret = num;
+
+err_exit:
+ mt_i2c_clock_disable(i2c);
+ return ret;
+}
+
+static irqreturn_t mt_i2c_irq(int irqno, void *dev_id)
+{
+ struct mt_i2c *i2c = dev_id;
+
+ /* Clear interrupt mask */
+ i2c_writew(~(I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP),
+ i2c, OFFSET_INTR_MASK);
+
+ i2c->irq_stat = i2c_readw(i2c, OFFSET_INTR_STAT);
+ i2c_writew(I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP,
+ i2c, OFFSET_INTR_STAT);
+ i2c->trans_stop = true;
+ wake_up(&i2c->wait);
+
+ return IRQ_HANDLED;
+}
+
+static u32 mt_i2c_functionality(struct i2c_adapter *adap)
+{
+ return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm mt_i2c_algorithm = {
+ .master_xfer = mt_i2c_transfer,
+ .functionality = mt_i2c_functionality,
+};
+
+static int mt_i2c_parse_dt(struct device_node *np, struct mt_i2c *i2c,
+ unsigned int *clk_src_div)
+{
+ i2c->speed_hz = I2C_DEFAUT_SPEED;
+ of_property_read_u32(np, "clock-frequency", &i2c->speed_hz);
+ of_property_read_u32(np, "clock-div", clk_src_div);
+ if (*clk_src_div == 0)
+ return -EINVAL;
+
+ i2c->have_pmic = of_property_read_bool(np, "mediatek,have-pmic");
+ i2c->have_dcm = of_property_read_bool(np, "mediatek,have-dcm");
+ i2c->use_push_pull =
+ of_property_read_bool(np, "mediatek,use-push-pull");
+
+ return 0;
+}
+
+static int mt_i2c_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct mt_i2c *i2c;
+ unsigned int clk_src_in_hz;
+ unsigned int clk_src_div;
+ struct resource *res;
+
+ i2c = devm_kzalloc(&pdev->dev, sizeof(struct mt_i2c), GFP_KERNEL);
+ if (i2c == NULL)
+ return -ENOMEM;
+
+ ret = mt_i2c_parse_dt(pdev->dev.of_node, i2c, &clk_src_div);
+ if (ret)
+ return -EINVAL;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ i2c->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(i2c->base))
+ return PTR_ERR(i2c->base);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+
+ i2c->pdmabase = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(i2c->pdmabase))
+ return PTR_ERR(i2c->pdmabase);
+
+ i2c->irqnr = platform_get_irq(pdev, 0);
+ if (i2c->irqnr <= 0)
+ return -EINVAL;
+
+ ret = devm_request_irq(&pdev->dev, i2c->irqnr, mt_i2c_irq,
+ IRQF_TRIGGER_NONE, I2C_DRV_NAME, i2c);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "Request I2C IRQ %d fail\n", i2c->irqnr);
+ return ret;
+ }
+
+ i2c->adap.dev.of_node = pdev->dev.of_node;
+ i2c->dev = &i2c->adap.dev;
+ i2c->adap.dev.parent = &pdev->dev;
+ i2c->adap.owner = THIS_MODULE;
+ i2c->adap.algo = &mt_i2c_algorithm;
+ i2c->adap.algo_data = NULL;
+ i2c->adap.timeout = 2 * HZ;
+ i2c->adap.retries = 1;
+
+ i2c->clk_main = devm_clk_get(&pdev->dev, "main");
+ if (IS_ERR(i2c->clk_main)) {
+ dev_err(&pdev->dev, "cannot get main clock\n");
+ return PTR_ERR(i2c->clk_main);
+ }
+
+ i2c->clk_dma = devm_clk_get(&pdev->dev, "dma");
+ if (IS_ERR(i2c->clk_dma)) {
+ dev_err(&pdev->dev, "cannot get dma clock\n");
+ return PTR_ERR(i2c->clk_dma);
+ }
+
+ if (i2c->have_pmic) {
+ i2c->clk_pmic = devm_clk_get(&pdev->dev, "pmic");
+ if (IS_ERR(i2c->clk_pmic)) {
+ dev_err(&pdev->dev, "cannot get pmic clock\n");
+ return PTR_ERR(i2c->clk_pmic);
+ }
+ clk_src_in_hz = clk_get_rate(i2c->clk_pmic) / clk_src_div;
+ } else {
+ clk_src_in_hz = clk_get_rate(i2c->clk_main) / clk_src_div;
+ }
+
+ dev_dbg(&pdev->dev, "clock source %p,clock src frequency %d\n",
+ i2c->clk_main, clk_src_in_hz);
+ strlcpy(i2c->adap.name, I2C_DRV_NAME, sizeof(i2c->adap.name));
+ init_waitqueue_head(&i2c->wait);
+
+ ret = i2c_set_speed(i2c, clk_src_in_hz);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to set the speed\n");
+ return -EINVAL;
+ }
+
+ ret = mt_i2c_clock_enable(i2c);
+ if (ret) {
+ dev_err(&pdev->dev, "clock enable failed!\n");
+ return ret;
+ }
+ mt_i2c_init_hw(i2c);
+ mt_i2c_clock_disable(i2c);
+
+ i2c->dma_buf.vaddr = dma_alloc_coherent(&pdev->dev,
+ PAGE_SIZE, &i2c->dma_buf.paddr, GFP_KERNEL);
+ if (i2c->dma_buf.vaddr == NULL) {
+ dev_err(&pdev->dev, "dma_alloc_coherent fail\n");
+ return -ENOMEM;
+ }
+
+ i2c_set_adapdata(&i2c->adap, i2c);
+ ret = i2c_add_adapter(&i2c->adap);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to add i2c bus to i2c core\n");
+ free_i2c_dma_bufs(i2c);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, i2c);
+
+ return 0;
+}
+
+static int mt_i2c_remove(struct platform_device *pdev)
+{
+ struct mt_i2c *i2c = platform_get_drvdata(pdev);
+
+ i2c_del_adapter(&i2c->adap);
+ free_i2c_dma_bufs(i2c);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static const struct of_device_id mtk_i2c_of_match[] = {
+ { .compatible = "mediatek,mt6577-i2c", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, mt_i2c_match);
+
+static struct platform_driver mt_i2c_driver = {
+ .probe = mt_i2c_probe,
+ .remove = mt_i2c_remove,
+ .driver = {
+ .name = I2C_DRV_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(mtk_i2c_of_match),
+ },
+};
+
+module_platform_driver(mt_i2c_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MediaTek I2C Bus Driver");
+MODULE_AUTHOR("Xudong Chen <[email protected]>");
--
1.8.1.1.dirty
Add I2C node to mt8135.dtsi and mt8127.dtsi
Signed-off-by: Xudong Chen <[email protected]>
---
arch/arm/boot/dts/mt8127.dtsi | 27 +++++++++++++++++++++++
arch/arm/boot/dts/mt8135.dtsi | 51 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 78 insertions(+)
diff --git a/arch/arm/boot/dts/mt8127.dtsi b/arch/arm/boot/dts/mt8127.dtsi
index 25c9f69..60d7685e 100644
--- a/arch/arm/boot/dts/mt8127.dtsi
+++ b/arch/arm/boot/dts/mt8127.dtsi
@@ -89,5 +89,32 @@
<0 0x10214000 0 0x2000>,
<0 0x10216000 0 0x2000>;
};
+
+ i2c0: i2c@11007000 {
+ compatible = "mediatek,mt8127-i2c",
+ "mediatek,mt6577-i2c";
+ reg = <0 0x11007000 0 0x70>,
+ <0 0x11000200 0 0x80>;
+ interrupts = <GIC_SPI 44 IRQ_TYPE_LEVEL_LOW>;
+ mediatek,have-dcm;
+ };
+
+ i2c1: i2c@11008000 {
+ compatible = "mediatek,mt8127-i2c",
+ "mediatek,mt6577-i2c";
+ reg = <0 0x11008000 0 0x70>,
+ <0 0x11000280 0 0x80>;
+ interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_LOW>;
+ mediatek,have-dcm;
+ };
+
+ i2c2: i2c@11009000 {
+ compatible = "mediatek,mt8127-i2c",
+ "mediatek,mt6577-i2c";
+ reg = <0 0x11009000 0 0x70>,
+ <0 0x11000300 0 0x80>;
+ interrupts = <GIC_SPI 46 IRQ_TYPE_LEVEL_LOW>;
+ mediatek,have-dcm;
+ };
};
};
diff --git a/arch/arm/boot/dts/mt8135.dtsi b/arch/arm/boot/dts/mt8135.dtsi
index 221ce09..1fbfc92 100644
--- a/arch/arm/boot/dts/mt8135.dtsi
+++ b/arch/arm/boot/dts/mt8135.dtsi
@@ -119,7 +119,58 @@
<0 0x1020C000 0 0x1000>;
gpio-controller;
#gpio-cells = <2>;
+
+ i2c0_pins_a: i2c0@0 {
+ mediatek,pins = <MT8135_PIN_100_SDA0__FUNC_SDA0 &pcfg_pull_none>,
+ <MT8135_PIN_101_SCL0__FUNC_SCL0 &pcfg_pull_none>;
+ };
+
+ i2c1_pins_a: i2c1@0 {
+ mediatek,pins = <MT8135_PIN_195_SDA1__FUNC_SDA1 &pcfg_pull_none>,
+ <MT8135_PIN_196_SCL1__FUNC_SCL1 &pcfg_pull_none>;
+ };
+
+ i2c2_pins_a: i2c2@0 {
+ mediatek,pins = <MT8135_PIN_193_SDA2__FUNC_SDA2 &pcfg_pull_none>,
+ <MT8135_PIN_194_SCL2__FUNC_SCL2 &pcfg_pull_none>;
+ };
+
+ i2c3_pins_a: i2c3@0 {
+ mediatek,pins = <MT8135_PIN_35_SCL3__FUNC_SCL3 &pcfg_pull_none>,
+ <MT8135_PIN_36_SDA3__FUNC_SDA3 &pcfg_pull_none>;
+ };
};
+ i2c0: i2c@1100d000 {
+ compatible = "mediatek,mt8135-i2c",
+ "mediatek,mt6577-i2c";
+ reg = <0 0x1100d000 0 0x70>,
+ <0 0x11000300 0 0x80>;
+ interrupts = <GIC_SPI 44 IRQ_TYPE_LEVEL_LOW>;
+ };
+
+ i2c1: i2c@1100e000 {
+ compatible = "mediatek,mt8135-i2c",
+ "mediatek,mt6577-i2c";
+ reg = <0 0x1100e000 0 0x70>,
+ <0 0x11000380 0 0x80>;
+ interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_LOW>;
+ };
+
+ i2c2: i2c@1100f000 {
+ compatible = "mediatek,mt8135-i2c",
+ "mediatek,mt6577-i2c";
+ reg = <0 0x1100f000 0 0x70>,
+ <0 0x11000400 0 0x80>;
+ interrupts = <GIC_SPI 46 IRQ_TYPE_LEVEL_LOW>;
+ };
+
+ i2c3: i2c@11010000 {
+ compatible = "mediatek,mt8135-i2c",
+ "mediatek,mt6577-i2c";
+ reg = <0 0x11010000 0 0x70>,
+ <0 0x11000480 0 0x80>;
+ interrupts = <GIC_SPI 47 IRQ_TYPE_LEVEL_LOW>;
+ };
};
};
--
1.8.1.1.dirty
Dear Sirs,
Missed the first paragraph, sorry for extra mail.
This series is the first version of Mediatek SoCs I2C controller common
bus driver, it is Request for Comment.
Because the clock driver for mediatek SoC is not ready yet(still work in
progress), so I delete the related clock code in dtsi file for now.
Best Regards,
Xudong
On Wed, 2014-10-29 at 13:37 +0800, Xudong Chen wrote:
> This driver is based on 3.18-rc1 & Hongzhou's gpio patch.
>
> MTK I2C HW has some limitation.
> 1. If the i2c_msg number is more than one, STOP will be issued instead of
> RS(Repeat Start) between each message.
> Such as: "START + ADDR + DATA_n + STOP + START + ADDR + DATA_n + STOP ..."
>
> 2. Mediatek I2C controller support WRRD(write then read) mode, in WRRD
> mode the Repeat Start will be issued between 2 messages.
> In this driver if 2 messages is first write then read, the driver will
> combine 2 messages using Write-Read mode so the RS will be issued between
> the 2 messages.
> Ex: W/R/R, driver will combine first W/R and then R.
> The data series will be:
> "START + WriteADDR + DATA + RS + ReadADDR + DATA + STOP + START + ReadADDR +
> DATA + STOP".
>
> 3. Due to HW limitation, in this version the max transfer data length is 255
> in one message. If want to transfer more than 255 bytes, HW needs the SW
> driver to split the data. Take 600 bytes for example, the data need to be
> divided into 3 parts 255 + 255 + 90. The data series will be:
> "START + ADDR + DATA_255 + RS + ADDR + DATA_255 + RS + ADDR + DATA_90 + STOP"
> instead of "START + ADDR + DATA_900 + STOP".
> We haven't implement this yet, we will do this in the separate patch.
>
> MT8135 can control I2C pins on PMIC(MT6397) by setting the i2c registers in
> MT8135 side. In this case, driver should set OFFSET_PATH_DIR bit first, the
> operation on other registers are still the same.
>
> Xudong Chen (3):
> dt-bindings: Add I2C bindings for mt65xx/mt81xx.
> ARM: mediatek: Add I2C node for mt8135 and mt8127
> I2C: mediatek: Add driver for MediaTek I2C controller
>
> .../devicetree/bindings/i2c/i2c-mt6577.txt | 37 ++
> arch/arm/boot/dts/mt8127.dtsi | 27 +
> arch/arm/boot/dts/mt8135.dtsi | 51 ++
> drivers/i2c/busses/Kconfig | 9 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-mt65xx.c | 728 +++++++++++++++++++++
> 6 files changed, 853 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/i2c/i2c-mt6577.txt
> create mode 100644 drivers/i2c/busses/i2c-mt65xx.c
>
> --
> 1.8.1.1.dirty
>
2014-10-29 6:37 GMT+01:00 Xudong Chen <[email protected]>:
> Add devicetree bindings for Mediatek Soc I2C driver.
>
> Signed-off-by: Xudong Chen <[email protected]>
> ---
> .../devicetree/bindings/i2c/i2c-mt6577.txt | 37 ++++++++++++++++++++++
> 1 file changed, 37 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/i2c/i2c-mt6577.txt
>
> diff --git a/Documentation/devicetree/bindings/i2c/i2c-mt6577.txt b/Documentation/devicetree/bindings/i2c/i2c-mt6577.txt
> new file mode 100644
> index 0000000..b4012c8
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/i2c/i2c-mt6577.txt
> @@ -0,0 +1,37 @@
> +* Mediatek's I2C controller
> +
> +The Mediatek's I2C controller is used to interface with I2C devices.
> +
> +Required properties:
> + - compatible: value should be either of the following.
> + (a) "mtk,mt6577-i2c", for i2c compatible with mt6577 i2c.
Looking on the datasheet of mt6577 it does not seem that this driver
is really compatible.
I was not able to find the PATH_DIR register neither in the datasheet
nor in the source code.
> + (b) "mtk,mt8127-i2c", for i2c compatible with mt8127 i2c.
> + (c) "mtk,mt8135-i2c", for i2c compatible with mt8135 i2c.
> + - reg: physical base address of the controller and dma base, length of memory mapped
> + region.
> + - interrupts: interrupt number to the cpu.
> + - clock-div: the fixed value for frequency divider of clock source in i2c module.
> + Each IC may be different.
> + - clocks: clock name from clock manager
> + - clock-names: clock name used in i2c driver probe
> +
> +Optional properties:
> + - clock-frequency: Frequency in Hz of the bus when transfer, the default value is 100000.
> + - mediatek,have-pmic: platform can control i2c form special pmic side.
> + - mediatek,have-dcm: platform has DCM(hardware digital clock manager) property.
> + - mediatek,use-push-pull: IO use push-pull mode.
> +
> +Example:
> +
> + i2c0: i2c@1100d000 {
> + compatible = "mediatek,mt6577-i2c";
> + reg = <0x1100d000 0x70>,
> + <0x11000300 0x80>;
> + interrupts = <GIC_SPI 44 IRQ_TYPE_LEVEL_LOW>;
> + clock-frequency = <100000>;
> + mediatek,have-pmic;
> + clock-div = <16>;
> + clocks = <&i2c0_ck>, <&ap_dma_ck>;
> + clock-names = "main", "dma";
> + };
> +
> --
> 1.8.1.1.dirty
>
--
motzblog.wordpress.com
2014-10-29 6:37 GMT+01:00 Xudong Chen <[email protected]>:
> The mediatek SoCs have I2C controller that handle I2C transfer.
> This patch include common I2C bus driver.
> This driver is compatible with I2C controller on mt65xx/mt81xx.
>
> Signed-off-by: Xudong Chen <[email protected]>
> Change-Id: Icc17e326b9df46a226d536956e103f17b0382b6e
> ---
> drivers/i2c/busses/Kconfig | 9 +
> drivers/i2c/busses/Makefile | 1 +
> drivers/i2c/busses/i2c-mt65xx.c | 728 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 738 insertions(+)
> create mode 100644 drivers/i2c/busses/i2c-mt65xx.c
>
> diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> index 917c358..0d7d0a4 100644
> --- a/drivers/i2c/busses/Kconfig
> +++ b/drivers/i2c/busses/Kconfig
> @@ -564,6 +564,15 @@ config I2C_MPC
> This driver can also be built as a module. If so, the module
> will be called i2c-mpc.
>
> +config I2C_MT65XX
> + tristate "MediaTek I2C adapter"
> + depends on ARCH_MEDIATEK
> + help
> + This selects the MediaTek(R) Integrated Inter Circuit bus driver
> + for MT65xx and MT81xx.
> + If you want to use MediaTek(R) I2C interface, say Y or M here.
> + If unsure, say N.
> +
> config I2C_MV64XXX
> tristate "Marvell mv64xxx I2C Controller"
> depends on MV64X60 || PLAT_ORION || ARCH_SUNXI
> diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> index 78d56c5..7a9f0f3 100644
> --- a/drivers/i2c/busses/Makefile
> +++ b/drivers/i2c/busses/Makefile
> @@ -54,6 +54,7 @@ obj-$(CONFIG_I2C_IMX) += i2c-imx.o
> obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o
> obj-$(CONFIG_I2C_KEMPLD) += i2c-kempld.o
> obj-$(CONFIG_I2C_MPC) += i2c-mpc.o
> +obj-$(CONFIG_I2C_MT65XX) += i2c-mt65xx.o
As it works for mt65xx and mt81xx series, better rename it to i2c-mtk.c
> obj-$(CONFIG_I2C_MV64XXX) += i2c-mv64xxx.o
> obj-$(CONFIG_I2C_MXS) += i2c-mxs.o
> obj-$(CONFIG_I2C_NOMADIK) += i2c-nomadik.o
> diff --git a/drivers/i2c/busses/i2c-mt65xx.c b/drivers/i2c/busses/i2c-mt65xx.c
> new file mode 100644
> index 0000000..8730153
> --- /dev/null
> +++ b/drivers/i2c/busses/i2c-mt65xx.c
> @@ -0,0 +1,728 @@
> +/*
> + * Copyright (c) 2014 MediaTek Inc.
> + * Author: Xudong.chen <[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License 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/kernel.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/i2c.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/sched.h>
> +#include <linux/delay.h>
> +#include <linux/errno.h>
> +#include <linux/err.h>
> +#include <linux/device.h>
> +#include <linux/platform_device.h>
> +#include <linux/wait.h>
> +#include <linux/mm.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/scatterlist.h>
> +#include <linux/io.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/clk.h>
> +
> +#define I2C_HS_NACKERR (1 << 2)
> +#define I2C_ACKERR (1 << 1)
> +#define I2C_TRANSAC_COMP (1 << 0)
> +#define I2C_TRANSAC_START (1 << 0)
> +#define I2C_TIMING_STEP_DIV_MASK (0x3f << 0)
> +#define I2C_TIMING_SAMPLE_COUNT_MASK (0x7 << 0)
> +#define I2C_TIMING_SAMPLE_DIV_MASK (0x7 << 8)
> +#define I2C_TIMING_DATA_READ_MASK (0x7 << 12)
> +#define I2C_DCM_DISABLE 0x0000
> +#define I2C_IO_CONFIG_OPEN_DRAIN 0x0003
> +#define I2C_IO_CONFIG_PUSH_PULL 0x0000
> +#define I2C_SOFT_RST 0x0001
> +#define I2C_FIFO_ADDR_CLR 0x0001
> +#define I2C_DELAY_LEN 0x0002
> +#define I2C_ST_START_CON 0x8001
> +#define I2C_FS_START_CON 0x1800
> +#define I2C_TIME_CLR_VALUE 0x0000
> +#define I2C_TIME_DEFAULT_VALUE 0x0003
> +#define I2C_WRRD_TRANAC_VALUE 0x0002
> +#define I2C_RD_TRANAC_VALUE 0x0001
> +
> +
> +
One new line is enough.
> +#define I2C_DMA_CON_TX 0x0000
> +#define I2C_DMA_CON_RX 0x0001
> +#define I2C_DMA_START_EN 0x0001
> +#define I2C_DMA_INT_FLAG_NONE 0x0000
> +#define I2C_DMA_CLR_FLAG 0x0000
> +
> +#define I2C_DEFAUT_SPEED 100000 /* hz */
> +#define MAX_FS_MODE_SPEED 400000
> +#define MAX_HS_MODE_SPEED 3400000
> +#define MAX_DMA_TRANS_SIZE 255
> +#define MAX_WRRD_TRANS_SIZE 32
> +#define MAX_SAMPLE_CNT_DIV 8
> +#define MAX_STEP_CNT_DIV 64
> +#define MAX_HS_STEP_CNT_DIV 8
> +
> +#define I2C_CONTROL_RS (0x1 << 1)
> +#define I2C_CONTROL_DMA_EN (0x1 << 2)
> +#define I2C_CONTROL_CLK_EXT_EN (0x1 << 3)
> +#define I2C_CONTROL_DIR_CHANGE (0x1 << 4)
> +#define I2C_CONTROL_ACKERR_DET_EN (0x1 << 5)
> +#define I2C_CONTROL_TRANSFER_LEN_CHANGE (0x1 << 6)
> +#define I2C_CONTROL_WRAPPER (0x1 << 0)
> +
> +#define I2C_DRV_NAME "mt-i2c"
> +
> +enum DMA_REGS_OFFSET {
> + OFFSET_INT_FLAG = 0x0,
> + OFFSET_INT_EN = 0x04,
> + OFFSET_EN = 0x08,
> + OFFSET_CON = 0x18,
> + OFFSET_TX_MEM_ADDR = 0x1c,
> + OFFSET_RX_MEM_ADDR = 0x20,
> + OFFSET_TX_LEN = 0x24,
> + OFFSET_RX_LEN = 0x28,
> +};
> +
> +enum i2c_trans_st_rs {
> + I2C_TRANS_STOP = 0,
> + I2C_TRANS_REPEATED_START,
> +};
> +
> +enum {
> + FS_MODE,
> + HS_MODE,
> +};
> +
> +enum mt_trans_op {
> + I2C_MASTER_WR = 1,
> + I2C_MASTER_RD,
> + I2C_MASTER_WRRD,
> +};
> +
> +enum I2C_REGS_OFFSET {
> + OFFSET_DATA_PORT = 0x0,
> + OFFSET_SLAVE_ADDR = 0x04,
> + OFFSET_INTR_MASK = 0x08,
> + OFFSET_INTR_STAT = 0x0c,
> + OFFSET_CONTROL = 0x10,
> + OFFSET_TRANSFER_LEN = 0x14,
> + OFFSET_TRANSAC_LEN = 0x18,
> + OFFSET_DELAY_LEN = 0x1c,
> + OFFSET_TIMING = 0x20,
> + OFFSET_START = 0x24,
> + OFFSET_EXT_CONF = 0x28,
> + OFFSET_FIFO_STAT = 0x30,
> + OFFSET_FIFO_THRESH = 0x34,
> + OFFSET_FIFO_ADDR_CLR = 0x38,
> + OFFSET_IO_CONFIG = 0x40,
> + OFFSET_RSV_DEBUG = 0x44,
> + OFFSET_HS = 0x48,
> + OFFSET_SOFTRESET = 0x50,
> + OFFSET_DCM_EN = 0x54,
> + OFFSET_PATH_DIR = 0x60,
> + OFFSET_DEBUGSTAT = 0x64,
> + OFFSET_DEBUGCTRL = 0x68,
> + OFFSET_TRANSFER_LEN_AUX = 0x6c,
> +};
> +
> +struct mt_i2c_data {
Please rename all mt_ prefixes to mtk_ prefix to keep consistency
throughout the kernel.
> + unsigned int clk_frequency; /* bus speed in Hz */
> + unsigned int flags;
> + unsigned int clk_src_div;
> +};
> +
> +struct i2c_dma_buf {
> + u8 *vaddr;
> + dma_addr_t paddr;
> +};
> +
> +struct mt_i2c {
> + struct i2c_adapter adap; /* i2c host adapter */
> + struct device *dev;
> + wait_queue_head_t wait; /* i2c transfer wait queue */
> + /* set in i2c probe */
> + void __iomem *base; /* i2c base addr */
> + void __iomem *pdmabase; /* dma base address*/
> + int irqnr; /* i2c interrupt number */
> + struct i2c_dma_buf dma_buf; /* memory alloc for DMA mode */
> + struct clk *clk_main; /* main clock for i2c bus */
> + struct clk *clk_dma; /* DMA clock for i2c via DMA */
> + struct clk *clk_pmic; /* PMIC clock for i2c from PMIC */
> + bool have_pmic; /* can use i2c pins form PMIC */
> + bool have_dcm; /* HW DCM function */
> + bool use_push_pull; /* IO config push-pull mode */
> + /* set when doing the transfer */
> + u16 irq_stat; /* interrupt status */
> + unsigned int speed_hz; /* The speed in transfer */
> + bool trans_stop; /* i2c transfer stop */
> + enum mt_trans_op op;
> + u16 msg_len;
> + u8 *msg_buf; /* pointer to msg data */
> + u16 msg_aux_len; /* WRRD mode to set AUX_LEN register*/
> + u16 addr; /* 7bit slave address, without read/write bit */
> + u16 timing_reg;
> + u16 high_speed_reg;
> +};
> +
> +static inline void i2c_writew(u16 value, struct mt_i2c *i2c, u8 offset)
> +{
> + writew(value, i2c->base + offset);
> +}
> +
> +static inline u16 i2c_readw(struct mt_i2c *i2c, u8 offset)
> +{
> + return readw(i2c->base + offset);
> +}
> +
> +static inline void i2c_writel_dma(u32 value, struct mt_i2c *i2c, u8 offset)
> +{
> + writel(value, i2c->pdmabase + offset);
> +}
> +
> +static int mt_i2c_clock_enable(struct mt_i2c *i2c)
> +{
> + int ret;
> +
> + ret = clk_prepare_enable(i2c->clk_dma);
> + if (ret)
> + return ret;
> +
> + ret = clk_prepare_enable(i2c->clk_main);
> + if (ret)
> + goto err_main;
> +
> + if (i2c->have_pmic) {
> + ret = clk_prepare_enable(i2c->clk_pmic);
> + if (ret)
> + goto err_pmic;
> + }
> + return 0;
> +
> +err_pmic:
> + clk_disable_unprepare(i2c->clk_main);
> +err_main:
> + clk_disable_unprepare(i2c->clk_dma);
> +
> + return ret;
> +}
> +
> +static void mt_i2c_clock_disable(struct mt_i2c *i2c)
> +{
> + if (i2c->have_pmic)
> + clk_disable_unprepare(i2c->clk_pmic);
> +
> + clk_disable_unprepare(i2c->clk_main);
> + clk_disable_unprepare(i2c->clk_dma);
> +}
> +
> +static void free_i2c_dma_bufs(struct mt_i2c *i2c)
> +{
> + dma_free_coherent(i2c->adap.dev.parent, PAGE_SIZE,
> + i2c->dma_buf.vaddr, i2c->dma_buf.paddr);
> +}
> +
> +static inline void mt_i2c_init_hw(struct mt_i2c *i2c)
> +{
> + i2c_writew(I2C_SOFT_RST, i2c, OFFSET_SOFTRESET);
> + /* Set ioconfig */
> + if (i2c->use_push_pull)
> + i2c_writew(I2C_IO_CONFIG_PUSH_PULL, i2c, OFFSET_IO_CONFIG);
> + else
> + i2c_writew(I2C_IO_CONFIG_OPEN_DRAIN, i2c, OFFSET_IO_CONFIG);
> +
> + if (i2c->have_dcm)
> + i2c_writew(I2C_DCM_DISABLE, i2c, OFFSET_DCM_EN);
> +
> + i2c_writew(i2c->timing_reg, i2c, OFFSET_TIMING);
> + i2c_writew(i2c->high_speed_reg, i2c, OFFSET_HS);
> +}
> +
> +/* calculate i2c port speed */
> +static int i2c_set_speed(struct mt_i2c *i2c, unsigned int clk_src_in_hz)
> +{
> + int mode;
> + unsigned int khz;
> + unsigned int step_cnt;
> + unsigned int sample_cnt;
> + unsigned int sclk;
> + unsigned int hclk;
> + unsigned int max_step_cnt;
> + unsigned int sample_div = MAX_SAMPLE_CNT_DIV;
> + unsigned int step_div;
> + unsigned int min_div;
> + unsigned int best_mul;
> + unsigned int cnt_mul;
> +
> + if (i2c->speed_hz > MAX_HS_MODE_SPEED) {
> + return -EINVAL;
> + } else if (i2c->speed_hz > MAX_FS_MODE_SPEED) {
> + mode = HS_MODE;
> + max_step_cnt = MAX_HS_STEP_CNT_DIV;
> + } else {
> + mode = FS_MODE;
> + max_step_cnt = MAX_STEP_CNT_DIV;
> + }
> +
> + step_div = max_step_cnt;
> + /* Find the best combination */
> + khz = i2c->speed_hz / 1000;
> + hclk = clk_src_in_hz / 1000;
> + min_div = ((hclk >> 1) + khz - 1) / khz;
> + best_mul = MAX_SAMPLE_CNT_DIV * max_step_cnt;
> +
> + for (sample_cnt = 1; sample_cnt <= MAX_SAMPLE_CNT_DIV; sample_cnt++) {
> + step_cnt = (min_div + sample_cnt - 1) / sample_cnt;
> + cnt_mul = step_cnt * sample_cnt;
> + if (step_cnt > max_step_cnt)
> + continue;
> +
> + if (cnt_mul < best_mul) {
> + best_mul = cnt_mul;
> + sample_div = sample_cnt;
> + step_div = step_cnt;
> + if (best_mul == min_div)
> + break;
> + }
> + }
> +
> + sample_cnt = sample_div;
> + step_cnt = step_div;
> + sclk = hclk / (2 * sample_cnt * step_cnt);
> + if (sclk > khz) {
> + dev_dbg(i2c->dev, "%s mode: unsupported speed (%ldkhz)\n",
> + (mode == HS_MODE) ? "HS" : "ST/FT", (long int)khz);
> + return -ENOTSUPP;
> + }
> +
> + step_cnt--;
> + sample_cnt--;
> +
> + if (mode == HS_MODE) {
> + /* Set the hign speed mode register */
> + i2c->timing_reg = I2C_TIME_CLR_VALUE;
> + i2c->high_speed_reg = I2C_TIME_DEFAULT_VALUE |
> + (sample_cnt & I2C_TIMING_SAMPLE_COUNT_MASK) << 12 |
> + (step_cnt & I2C_TIMING_SAMPLE_COUNT_MASK) << 8;
> + } else {
> + i2c->timing_reg =
> + (sample_cnt & I2C_TIMING_SAMPLE_COUNT_MASK) << 8 |
> + (step_cnt & I2C_TIMING_STEP_DIV_MASK) << 0;
> + /* Disable the high speed transaction */
> + i2c->high_speed_reg = I2C_TIME_CLR_VALUE;
> + }
> +
> + return 0;
> +}
> +
> +static int mt_i2c_do_transfer(struct mt_i2c *i2c)
> +{
> + u16 addr_reg;
> + u16 control_reg;
> + int tmo = i2c->adap.timeout;
> +
> + i2c->trans_stop = false;
> + i2c->irq_stat = 0;
> +
> + /* If use i2c pin from PMIC mt6397 side, need set PATH_DIR first */
> + if (i2c->have_pmic)
> + i2c_writew(I2C_CONTROL_WRAPPER, i2c, OFFSET_PATH_DIR);
So this is some sort of multiplexer bit, right?
I think in this case we need to build a multi function device (mfd)
where the pmic driver will set this bit.
> +
> + control_reg = I2C_CONTROL_ACKERR_DET_EN |
> + I2C_CONTROL_CLK_EXT_EN | I2C_CONTROL_DMA_EN;
> + if (i2c->speed_hz > 400000)
> + control_reg |= I2C_CONTROL_RS;
> + if (i2c->op == I2C_MASTER_WRRD)
> + control_reg |= I2C_CONTROL_DIR_CHANGE | I2C_CONTROL_RS;
> + i2c_writew(control_reg, i2c, OFFSET_CONTROL);
> +
> + /* set start condition */
> + if (i2c->speed_hz <= 100000)
> + i2c_writew(I2C_ST_START_CON, i2c, OFFSET_EXT_CONF);
> + else
> + i2c_writew(I2C_FS_START_CON, i2c, OFFSET_EXT_CONF);
> +
> + if (~control_reg & I2C_CONTROL_RS)
> + i2c_writew(I2C_DELAY_LEN, i2c, OFFSET_DELAY_LEN);
> +
> + addr_reg = i2c->addr << 1;
> + if (i2c->op == I2C_MASTER_RD)
> + addr_reg |= 0x1;
> + i2c_writew(addr_reg, i2c, OFFSET_SLAVE_ADDR);
> +
> + /* Clear interrupt status */
> + i2c_writew(I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP,
> + i2c, OFFSET_INTR_STAT);
> + i2c_writew(I2C_FIFO_ADDR_CLR, i2c, OFFSET_FIFO_ADDR_CLR);
> +
> + /* Enable interrupt */
> + i2c_writew(I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP,
> + i2c, OFFSET_INTR_MASK);
> +
> + /* Set transfer and transaction len */
> + if (i2c->op == I2C_MASTER_WRRD) {
> + i2c_writew(i2c->msg_len | (i2c->msg_aux_len) << 8,
> + i2c, OFFSET_TRANSFER_LEN);
> + i2c_writew(I2C_WRRD_TRANAC_VALUE, i2c, OFFSET_TRANSAC_LEN);
> + } else {
> + i2c_writew(i2c->msg_len, i2c, OFFSET_TRANSFER_LEN);
> + i2c_writew(I2C_RD_TRANAC_VALUE, i2c, OFFSET_TRANSAC_LEN);
> + }
> +
> + /* Prepare buffer data to start transfer */
> + if (i2c->op == I2C_MASTER_RD) {
> + i2c_writel_dma(I2C_DMA_INT_FLAG_NONE, i2c, OFFSET_INT_FLAG);
> + i2c_writel_dma(I2C_DMA_CON_RX, i2c, OFFSET_CON);
> + i2c_writel_dma((u32)i2c->msg_buf, i2c, OFFSET_RX_MEM_ADDR);
> + i2c_writel_dma(i2c->msg_len, i2c, OFFSET_RX_LEN);
> + } else if (i2c->op == I2C_MASTER_WR) {
> + i2c_writel_dma(I2C_DMA_INT_FLAG_NONE, i2c, OFFSET_INT_FLAG);
> + i2c_writel_dma(I2C_DMA_CON_TX, i2c, OFFSET_CON);
> + i2c_writel_dma((u32)i2c->msg_buf, i2c, OFFSET_TX_MEM_ADDR);
> + i2c_writel_dma(i2c->msg_len, i2c, OFFSET_TX_LEN);
> + } else {
> + i2c_writel_dma(I2C_DMA_CLR_FLAG, i2c, OFFSET_INT_FLAG);
> + i2c_writel_dma(I2C_DMA_CLR_FLAG, i2c, OFFSET_CON);
> + i2c_writel_dma((u32)i2c->msg_buf, i2c, OFFSET_TX_MEM_ADDR);
> + i2c_writel_dma((u32)i2c->dma_buf.paddr, i2c,
> + OFFSET_RX_MEM_ADDR);
> + i2c_writel_dma(i2c->msg_len, i2c, OFFSET_TX_LEN);
> + i2c_writel_dma(i2c->msg_aux_len, i2c, OFFSET_RX_LEN);
> + }
> +
> + /* flush before sending start */
> + mb();
> + i2c_writel_dma(I2C_DMA_START_EN, i2c, OFFSET_EN);
> + i2c_writew(I2C_TRANSAC_START, i2c, OFFSET_START);
> +
> + tmo = wait_event_timeout(i2c->wait, i2c->trans_stop, tmo);
> + if (tmo == 0) {
> + dev_dbg(i2c->dev, "addr: %x, transfer timeout\n", i2c->addr);
> + mt_i2c_init_hw(i2c);
> + return -ETIMEDOUT;
> + }
> +
> + if (i2c->irq_stat & (I2C_HS_NACKERR | I2C_ACKERR)) {
> + dev_dbg(i2c->dev, "addr: %x, transfer ACK error\n", i2c->addr);
> + mt_i2c_init_hw(i2c);
> + return -EREMOTEIO;
> + }
> +
> + dev_dbg(i2c->dev, "i2c transfer done.\n");
> +
> + return 0;
> +}
> +
> +static inline void mt_i2c_copy_to_dma(struct mt_i2c *i2c, struct i2c_msg *msg)
> +{
> + /* if the operate is write, need to copy the data to DMA memory */
> + if (!(msg->flags & I2C_M_RD))
> + memcpy(i2c->dma_buf.vaddr, msg->buf, msg->len);
> +}
> +
> +static inline void mt_i2c_copy_from_dma(struct mt_i2c *i2c,
> + struct i2c_msg *msg)
> +{
> + /* if the operate is read, need to copy the data from DMA memory */
> + if (msg->flags & I2C_M_RD)
> + memcpy(msg->buf, i2c->dma_buf.vaddr, msg->len);
> +}
> +
> +/*
> + * In MTK platform the STOP will be issued after each
> + * message was transferred which is not flow the clarify
> + * for i2c_transfer(), several I2C devices tolerate the STOP,
> + * but some device need Repeat-Start and do not compatible with STOP
> + * MTK platform has WRRD mode which can write then read with
> + * Repeat-Start between two message, so we combined two
> + * messages into one transaction.
> + * The max read length is 31
> + */
> +static bool mt_i2c_should_combine(struct i2c_msg *msg)
> +{
> + struct i2c_msg *next_msg = msg + 1;
> +
> + if ((next_msg->len < MAX_WRRD_TRANS_SIZE) &&
> + msg->addr == next_msg->addr &&
> + !(msg->flags & I2C_M_RD) &&
> + (next_msg->flags & I2C_M_RD) == I2C_M_RD) {
> + return true;
> + }
> +
> + return false;
> +}
> +
> +static int mt_i2c_transfer(struct i2c_adapter *adap,
> + struct i2c_msg msgs[], int num)
> +{
> + int ret;
> + int left_num = num;
> + struct mt_i2c *i2c = i2c_get_adapdata(adap);
> +
> + ret = mt_i2c_clock_enable(i2c);
> + if (ret)
> + return ret;
> +
> + while (left_num--) {
> + /* In MTK platform the max transfer number is 255 */
> + if (msgs->len > MAX_DMA_TRANS_SIZE) {
> + dev_dbg(i2c->dev,
> + " message data length is more than 255\n");
> + ret = -EINVAL;
> + goto err_exit;
> + }
> +
> + if (msgs->addr == 0) {
> + dev_dbg(i2c->dev, " addr is invalid.\n");
> + ret = -EINVAL;
> + goto err_exit;
> + }
> +
> + if (msgs->buf == NULL) {
> + dev_dbg(i2c->dev, " data buffer is NULL.\n");
> + ret = -EINVAL;
> + goto err_exit;
> + }
> +
> + i2c->addr = msgs->addr;
> + i2c->msg_len = msgs->len;
> + i2c->msg_buf = msgs->buf;
> +
> + if (msgs->flags & I2C_M_RD)
> + i2c->op = I2C_MASTER_RD;
> + else
> + i2c->op = I2C_MASTER_WR;
> +
> + /* combined two messages into one transaction */
> + if (left_num >= 1 && mt_i2c_should_combine(msgs)) {
> + i2c->msg_aux_len = (msgs + 1)->len;
> + i2c->op = I2C_MASTER_WRRD;
> + left_num--;
> + }
> +
> + /*
> + * always use DMA mode.
> + * 1st when write need copy the data of message to dma memory
> + * 2nd when read need copy the DMA data to the message buffer.
> + * The length should be less than 255.
> + */
> + mt_i2c_copy_to_dma(i2c, msgs);
> + i2c->msg_buf = (u8 *)i2c->dma_buf.paddr;
> + ret = mt_i2c_do_transfer(i2c);
> + if (ret < 0)
> + goto err_exit;
> +
> + if (i2c->op == I2C_MASTER_WRRD)
> + mt_i2c_copy_from_dma(i2c, msgs + 1);
> + else
> + mt_i2c_copy_from_dma(i2c, msgs);
> +
> + msgs++;
> +
> + /* after combined two messages so we need ignore one */
> + if (left_num > 0 && i2c->op == I2C_MASTER_WRRD)
> + msgs++;
> + }
> + /* the return value is number of executed messages */
> + ret = num;
> +
> +err_exit:
> + mt_i2c_clock_disable(i2c);
> + return ret;
> +}
> +
> +static irqreturn_t mt_i2c_irq(int irqno, void *dev_id)
> +{
> + struct mt_i2c *i2c = dev_id;
> +
> + /* Clear interrupt mask */
> + i2c_writew(~(I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP),
> + i2c, OFFSET_INTR_MASK);
> +
> + i2c->irq_stat = i2c_readw(i2c, OFFSET_INTR_STAT);
> + i2c_writew(I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP,
> + i2c, OFFSET_INTR_STAT);
> + i2c->trans_stop = true;
> + wake_up(&i2c->wait);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static u32 mt_i2c_functionality(struct i2c_adapter *adap)
> +{
> + return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR | I2C_FUNC_SMBUS_EMUL;
> +}
> +
> +static const struct i2c_algorithm mt_i2c_algorithm = {
> + .master_xfer = mt_i2c_transfer,
> + .functionality = mt_i2c_functionality,
> +};
> +
> +static int mt_i2c_parse_dt(struct device_node *np, struct mt_i2c *i2c,
> + unsigned int *clk_src_div)
> +{
> + i2c->speed_hz = I2C_DEFAUT_SPEED;
> + of_property_read_u32(np, "clock-frequency", &i2c->speed_hz);
> + of_property_read_u32(np, "clock-div", clk_src_div);
> + if (*clk_src_div == 0)
> + return -EINVAL;
> +
> + i2c->have_pmic = of_property_read_bool(np, "mediatek,have-pmic");
> + i2c->have_dcm = of_property_read_bool(np, "mediatek,have-dcm");
> + i2c->use_push_pull =
> + of_property_read_bool(np, "mediatek,use-push-pull");
> +
> + return 0;
> +}
> +
> +static int mt_i2c_probe(struct platform_device *pdev)
> +{
> + int ret = 0;
> + struct mt_i2c *i2c;
> + unsigned int clk_src_in_hz;
> + unsigned int clk_src_div;
> + struct resource *res;
> +
> + i2c = devm_kzalloc(&pdev->dev, sizeof(struct mt_i2c), GFP_KERNEL);
> + if (i2c == NULL)
> + return -ENOMEM;
> +
> + ret = mt_i2c_parse_dt(pdev->dev.of_node, i2c, &clk_src_div);
> + if (ret)
> + return -EINVAL;
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +
> + i2c->base = devm_ioremap_resource(&pdev->dev, res);
> + if (IS_ERR(i2c->base))
> + return PTR_ERR(i2c->base);
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> +
> + i2c->pdmabase = devm_ioremap_resource(&pdev->dev, res);
> + if (IS_ERR(i2c->pdmabase))
> + return PTR_ERR(i2c->pdmabase);
> +
> + i2c->irqnr = platform_get_irq(pdev, 0);
> + if (i2c->irqnr <= 0)
> + return -EINVAL;
> +
> + ret = devm_request_irq(&pdev->dev, i2c->irqnr, mt_i2c_irq,
> + IRQF_TRIGGER_NONE, I2C_DRV_NAME, i2c);
> + if (ret < 0) {
> + dev_err(&pdev->dev,
> + "Request I2C IRQ %d fail\n", i2c->irqnr);
> + return ret;
> + }
> +
> + i2c->adap.dev.of_node = pdev->dev.of_node;
> + i2c->dev = &i2c->adap.dev;
> + i2c->adap.dev.parent = &pdev->dev;
> + i2c->adap.owner = THIS_MODULE;
> + i2c->adap.algo = &mt_i2c_algorithm;
> + i2c->adap.algo_data = NULL;
> + i2c->adap.timeout = 2 * HZ;
> + i2c->adap.retries = 1;
> +
> + i2c->clk_main = devm_clk_get(&pdev->dev, "main");
> + if (IS_ERR(i2c->clk_main)) {
> + dev_err(&pdev->dev, "cannot get main clock\n");
> + return PTR_ERR(i2c->clk_main);
> + }
> +
> + i2c->clk_dma = devm_clk_get(&pdev->dev, "dma");
> + if (IS_ERR(i2c->clk_dma)) {
> + dev_err(&pdev->dev, "cannot get dma clock\n");
> + return PTR_ERR(i2c->clk_dma);
> + }
> +
> + if (i2c->have_pmic) {
> + i2c->clk_pmic = devm_clk_get(&pdev->dev, "pmic");
> + if (IS_ERR(i2c->clk_pmic)) {
> + dev_err(&pdev->dev, "cannot get pmic clock\n");
> + return PTR_ERR(i2c->clk_pmic);
> + }
> + clk_src_in_hz = clk_get_rate(i2c->clk_pmic) / clk_src_div;
> + } else {
> + clk_src_in_hz = clk_get_rate(i2c->clk_main) / clk_src_div;
> + }
> +
> + dev_dbg(&pdev->dev, "clock source %p,clock src frequency %d\n",
> + i2c->clk_main, clk_src_in_hz);
> + strlcpy(i2c->adap.name, I2C_DRV_NAME, sizeof(i2c->adap.name));
> + init_waitqueue_head(&i2c->wait);
> +
> + ret = i2c_set_speed(i2c, clk_src_in_hz);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to set the speed\n");
> + return -EINVAL;
> + }
> +
> + ret = mt_i2c_clock_enable(i2c);
> + if (ret) {
> + dev_err(&pdev->dev, "clock enable failed!\n");
> + return ret;
> + }
> + mt_i2c_init_hw(i2c);
> + mt_i2c_clock_disable(i2c);
> +
> + i2c->dma_buf.vaddr = dma_alloc_coherent(&pdev->dev,
> + PAGE_SIZE, &i2c->dma_buf.paddr, GFP_KERNEL);
> + if (i2c->dma_buf.vaddr == NULL) {
> + dev_err(&pdev->dev, "dma_alloc_coherent fail\n");
> + return -ENOMEM;
> + }
> +
> + i2c_set_adapdata(&i2c->adap, i2c);
> + ret = i2c_add_adapter(&i2c->adap);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to add i2c bus to i2c core\n");
> + free_i2c_dma_bufs(i2c);
> + return ret;
> + }
> +
> + platform_set_drvdata(pdev, i2c);
> +
> + return 0;
> +}
> +
> +static int mt_i2c_remove(struct platform_device *pdev)
> +{
> + struct mt_i2c *i2c = platform_get_drvdata(pdev);
> +
> + i2c_del_adapter(&i2c->adap);
> + free_i2c_dma_bufs(i2c);
> + platform_set_drvdata(pdev, NULL);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id mtk_i2c_of_match[] = {
> + { .compatible = "mediatek,mt6577-i2c", },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, mt_i2c_match);
> +
> +static struct platform_driver mt_i2c_driver = {
> + .probe = mt_i2c_probe,
> + .remove = mt_i2c_remove,
> + .driver = {
> + .name = I2C_DRV_NAME,
> + .owner = THIS_MODULE,
> + .of_match_table = of_match_ptr(mtk_i2c_of_match),
> + },
> +};
> +
> +module_platform_driver(mt_i2c_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("MediaTek I2C Bus Driver");
> +MODULE_AUTHOR("Xudong Chen <[email protected]>");
> --
> 1.8.1.1.dirty
>
--
motzblog.wordpress.com
> > +obj-$(CONFIG_I2C_MT65XX) += i2c-mt65xx.o
>
> As it works for mt65xx and mt81xx series, better rename it to i2c-mtk.c
That is a bit too generic IMO. It is likely that we will see some
totally different I2C core from MTK in the future. Plus, it is not
uncommon that a driver initially for SoC-X supports SoC-Y, too.
Thanks for pointing out the other remarks.
On Thu, 2014-10-30 at 14:25 +0100, Wolfram Sang wrote:
> > > +obj-$(CONFIG_I2C_MT65XX) += i2c-mt65xx.o
> >
> > As it works for mt65xx and mt81xx series, better rename it to i2c-mtk.c
>
> That is a bit too generic IMO. It is likely that we will see some
> totally different I2C core from MTK in the future. Plus, it is not
> uncommon that a driver initially for SoC-X supports SoC-Y, too.
Hi,
Yes, MTK TV SoCs(MT53xx) do use a different/incompatible I2C IP. So we
preferred to keep original file name.
Joe.C
On Thu, 2014-10-30 at 13:38 +0100, Matthias Brugger wrote:
> 2014-10-29 6:37 GMT+01:00 Xudong Chen <[email protected]>:
> > Add devicetree bindings for Mediatek Soc I2C driver.
> >
> > Signed-off-by: Xudong Chen <[email protected]>
> > ---
> > .../devicetree/bindings/i2c/i2c-mt6577.txt | 37 ++++++++++++++++++++++
> > 1 file changed, 37 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/i2c/i2c-mt6577.txt
> >
> > diff --git a/Documentation/devicetree/bindings/i2c/i2c-mt6577.txt b/Documentation/devicetree/bindings/i2c/i2c-mt6577.txt
> > new file mode 100644
> > index 0000000..b4012c8
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/i2c/i2c-mt6577.txt
> > @@ -0,0 +1,37 @@
> > +* Mediatek's I2C controller
> > +
> > +The Mediatek's I2C controller is used to interface with I2C devices.
> > +
> > +Required properties:
> > + - compatible: value should be either of the following.
> > + (a) "mtk,mt6577-i2c", for i2c compatible with mt6577 i2c.
>
> Looking on the datasheet of mt6577 it does not seem that this driver
> is really compatible.
> I was not able to find the PATH_DIR register neither in the datasheet
> nor in the source code.
>
Hi,
The PATH_DIR register named OFFSET_PATH_DIR(0x60) in the source code.
Only MT8135 supports this feature, so it does not contained in mt6577
datasheet.
I will modify the comment.
> > + (b) "mtk,mt8127-i2c", for i2c compatible with mt8127 i2c.
> > + (c) "mtk,mt8135-i2c", for i2c compatible with mt8135 i2c.
> > + - reg: physical base address of the controller and dma base, length of memory mapped
> > + region.
> > + - interrupts: interrupt number to the cpu.
> > + - clock-div: the fixed value for frequency divider of clock source in i2c module.
> > + Each IC may be different.
> > + - clocks: clock name from clock manager
> > + - clock-names: clock name used in i2c driver probe
> > +
> > +Optional properties:
> > + - clock-frequency: Frequency in Hz of the bus when transfer, the default value is 100000.
> > + - mediatek,have-pmic: platform can control i2c form special pmic side.
I will change this to:
- mediatek,have-pmic: platform can control i2c form special pmic side,
only MT8135 supports this.
Is this OK?
> > + - mediatek,have-dcm: platform has DCM(hardware digital clock manager) property.
> > + - mediatek,use-push-pull: IO use push-pull mode.
> > +
> > +Example:
> > +
> > + i2c0: i2c@1100d000 {
> > + compatible = "mediatek,mt6577-i2c";
> > + reg = <0x1100d000 0x70>,
> > + <0x11000300 0x80>;
> > + interrupts = <GIC_SPI 44 IRQ_TYPE_LEVEL_LOW>;
> > + clock-frequency = <100000>;
> > + mediatek,have-pmic;
> > + clock-div = <16>;
> > + clocks = <&i2c0_ck>, <&ap_dma_ck>;
> > + clock-names = "main", "dma";
> > + };
> > +
> > --
> > 1.8.1.1.dirty
> >
>
>
>
On Thu, 2014-10-30 at 14:16 +0100, Matthias Brugger wrote:
> 2014-10-29 6:37 GMT+01:00 Xudong Chen <[email protected]>:
> > The mediatek SoCs have I2C controller that handle I2C transfer.
> > This patch include common I2C bus driver.
> > This driver is compatible with I2C controller on mt65xx/mt81xx.
> >
> > Signed-off-by: Xudong Chen <[email protected]>
> > Change-Id: Icc17e326b9df46a226d536956e103f17b0382b6e
> > ---
> > drivers/i2c/busses/Kconfig | 9 +
> > drivers/i2c/busses/Makefile | 1 +
> > drivers/i2c/busses/i2c-mt65xx.c | 728 ++++++++++++++++++++++++++++++++++++++++
> > 3 files changed, 738 insertions(+)
> > create mode 100644 drivers/i2c/busses/i2c-mt65xx.c
> >
> > diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> > index 917c358..0d7d0a4 100644
> > --- a/drivers/i2c/busses/Kconfig
> > +++ b/drivers/i2c/busses/Kconfig
> > @@ -564,6 +564,15 @@ config I2C_MPC
> > This driver can also be built as a module. If so, the module
> > will be called i2c-mpc.
> >
> > +config I2C_MT65XX
> > + tristate "MediaTek I2C adapter"
> > + depends on ARCH_MEDIATEK
> > + help
> > + This selects the MediaTek(R) Integrated Inter Circuit bus driver
> > + for MT65xx and MT81xx.
> > + If you want to use MediaTek(R) I2C interface, say Y or M here.
> > + If unsure, say N.
> > +
> > config I2C_MV64XXX
> > tristate "Marvell mv64xxx I2C Controller"
> > depends on MV64X60 || PLAT_ORION || ARCH_SUNXI
> > diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> > index 78d56c5..7a9f0f3 100644
> > --- a/drivers/i2c/busses/Makefile
> > +++ b/drivers/i2c/busses/Makefile
> > @@ -54,6 +54,7 @@ obj-$(CONFIG_I2C_IMX) += i2c-imx.o
> > obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o
> > obj-$(CONFIG_I2C_KEMPLD) += i2c-kempld.o
> > obj-$(CONFIG_I2C_MPC) += i2c-mpc.o
> > +obj-$(CONFIG_I2C_MT65XX) += i2c-mt65xx.o
> > +
> > +static int mt_i2c_do_transfer(struct mt_i2c *i2c)
> > +{
> > + u16 addr_reg;
> > + u16 control_reg;
> > + int tmo = i2c->adap.timeout;
> > +
> > + i2c->trans_stop = false;
> > + i2c->irq_stat = 0;
> > +
> > + /* If use i2c pin from PMIC mt6397 side, need set PATH_DIR first */
> > + if (i2c->have_pmic)
> > + i2c_writew(I2C_CONTROL_WRAPPER, i2c, OFFSET_PATH_DIR);
>
> So this is some sort of multiplexer bit, right?
> I think in this case we need to build a multi function device (mfd)
> where the pmic driver will set this bit.
>
Hi,
This feature means the chip can use I2C pins from PMIC(mt6397).
In this case,
1. We actually control the register on mt8135 and the DMA/Control Logic
is on mt8135, only use pins from mt6397. If set register OFFSET_PATH_DIR
bit0 to 1, the I2C data will go/from PMIC pins.
2. We also need mt6397 pinctrl and clk manager driver to make it
actually work.
After looking up datasheets, the mt6589 and mt8135 support PMIC I2C, the
other chips mt6577/mt6582/mt6582/mt8127 do not.
Thanks!
Xudong.chen
2014-10-31 7:31 GMT+01:00 xudong chen <[email protected]>:
> On Thu, 2014-10-30 at 14:16 +0100, Matthias Brugger wrote:
>> 2014-10-29 6:37 GMT+01:00 Xudong Chen <[email protected]>:
>> > The mediatek SoCs have I2C controller that handle I2C transfer.
>> > This patch include common I2C bus driver.
>> > This driver is compatible with I2C controller on mt65xx/mt81xx.
>> >
>> > Signed-off-by: Xudong Chen <[email protected]>
>> > Change-Id: Icc17e326b9df46a226d536956e103f17b0382b6e
>> > ---
>> > drivers/i2c/busses/Kconfig | 9 +
>> > drivers/i2c/busses/Makefile | 1 +
>> > drivers/i2c/busses/i2c-mt65xx.c | 728 ++++++++++++++++++++++++++++++++++++++++
>> > 3 files changed, 738 insertions(+)
>> > create mode 100644 drivers/i2c/busses/i2c-mt65xx.c
>> >
>> > diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
>> > index 917c358..0d7d0a4 100644
>> > --- a/drivers/i2c/busses/Kconfig
>> > +++ b/drivers/i2c/busses/Kconfig
>> > @@ -564,6 +564,15 @@ config I2C_MPC
>> > This driver can also be built as a module. If so, the module
>> > will be called i2c-mpc.
>> >
>> > +config I2C_MT65XX
>> > + tristate "MediaTek I2C adapter"
>> > + depends on ARCH_MEDIATEK
>> > + help
>> > + This selects the MediaTek(R) Integrated Inter Circuit bus driver
>> > + for MT65xx and MT81xx.
>> > + If you want to use MediaTek(R) I2C interface, say Y or M here.
>> > + If unsure, say N.
>> > +
>> > config I2C_MV64XXX
>> > tristate "Marvell mv64xxx I2C Controller"
>> > depends on MV64X60 || PLAT_ORION || ARCH_SUNXI
>> > diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
>> > index 78d56c5..7a9f0f3 100644
>> > --- a/drivers/i2c/busses/Makefile
>> > +++ b/drivers/i2c/busses/Makefile
>> > @@ -54,6 +54,7 @@ obj-$(CONFIG_I2C_IMX) += i2c-imx.o
>> > obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o
>> > obj-$(CONFIG_I2C_KEMPLD) += i2c-kempld.o
>> > obj-$(CONFIG_I2C_MPC) += i2c-mpc.o
>> > +obj-$(CONFIG_I2C_MT65XX) += i2c-mt65xx.o
>> > +
>> > +static int mt_i2c_do_transfer(struct mt_i2c *i2c)
>> > +{
>> > + u16 addr_reg;
>> > + u16 control_reg;
>> > + int tmo = i2c->adap.timeout;
>> > +
>> > + i2c->trans_stop = false;
>> > + i2c->irq_stat = 0;
>> > +
>> > + /* If use i2c pin from PMIC mt6397 side, need set PATH_DIR first */
>> > + if (i2c->have_pmic)
>> > + i2c_writew(I2C_CONTROL_WRAPPER, i2c, OFFSET_PATH_DIR);
>>
>> So this is some sort of multiplexer bit, right?
>> I think in this case we need to build a multi function device (mfd)
>> where the pmic driver will set this bit.
>>
> Hi,
>
> This feature means the chip can use I2C pins from PMIC(mt6397).
> In this case,
> 1. We actually control the register on mt8135 and the DMA/Control Logic
> is on mt8135, only use pins from mt6397. If set register OFFSET_PATH_DIR
> bit0 to 1, the I2C data will go/from PMIC pins.
Sorry but I'm a bit puzzled. mt6397 is the PMIC, right?
So from the mt6589 datasheet it says:
"PATH_DIR Decides transmission direction
If set, the I2C data will go/from PMIC; otherwise, the I2C data will
go/from main die."
>From my understanding the I2C bus will be multiplexed depending on
this bit. The bit decides if it the data will be send to the PMIC or
to some other I2C peripheral.
So we need a mfd and the PMIC device driver sets/unsets the PATH_DIR register.
> 2. We also need mt6397 pinctrl and clk manager driver to make it
> actually work.
>
> After looking up datasheets, the mt6589 and mt8135 support PMIC I2C, the
> other chips mt6577/mt6582/mt6582/mt8127 do not.
Great, so please fix the dts bindings for the next patch version.
Cheers,
Matthias
>
> Thanks!
> Xudong.chen
>
>
--
motzblog.wordpress.com
2014-10-31 2:38 GMT+01:00 xudong chen <[email protected]>:
> On Thu, 2014-10-30 at 13:38 +0100, Matthias Brugger wrote:
>> 2014-10-29 6:37 GMT+01:00 Xudong Chen <[email protected]>:
>> > Add devicetree bindings for Mediatek Soc I2C driver.
>> >
>> > Signed-off-by: Xudong Chen <[email protected]>
>> > ---
>> > .../devicetree/bindings/i2c/i2c-mt6577.txt | 37 ++++++++++++++++++++++
>> > 1 file changed, 37 insertions(+)
>> > create mode 100644 Documentation/devicetree/bindings/i2c/i2c-mt6577.txt
>> >
>> > diff --git a/Documentation/devicetree/bindings/i2c/i2c-mt6577.txt b/Documentation/devicetree/bindings/i2c/i2c-mt6577.txt
>> > new file mode 100644
>> > index 0000000..b4012c8
>> > --- /dev/null
>> > +++ b/Documentation/devicetree/bindings/i2c/i2c-mt6577.txt
>> > @@ -0,0 +1,37 @@
>> > +* Mediatek's I2C controller
>> > +
>> > +The Mediatek's I2C controller is used to interface with I2C devices.
>> > +
>> > +Required properties:
>> > + - compatible: value should be either of the following.
>> > + (a) "mtk,mt6577-i2c", for i2c compatible with mt6577 i2c.
>>
>> Looking on the datasheet of mt6577 it does not seem that this driver
>> is really compatible.
>> I was not able to find the PATH_DIR register neither in the datasheet
>> nor in the source code.
>>
> Hi,
>
> The PATH_DIR register named OFFSET_PATH_DIR(0x60) in the source code.
> Only MT8135 supports this feature, so it does not contained in mt6577
> datasheet.
> I will modify the comment.
>
>> > + (b) "mtk,mt8127-i2c", for i2c compatible with mt8127 i2c.
>> > + (c) "mtk,mt8135-i2c", for i2c compatible with mt8135 i2c.
>> > + - reg: physical base address of the controller and dma base, length of memory mapped
>> > + region.
>> > + - interrupts: interrupt number to the cpu.
>> > + - clock-div: the fixed value for frequency divider of clock source in i2c module.
>> > + Each IC may be different.
>> > + - clocks: clock name from clock manager
>> > + - clock-names: clock name used in i2c driver probe
>> > +
>> > +Optional properties:
>> > + - clock-frequency: Frequency in Hz of the bus when transfer, the default value is 100000.
>> > + - mediatek,have-pmic: platform can control i2c form special pmic side.
>
> I will change this to:
> - mediatek,have-pmic: platform can control i2c form special pmic side,
> only MT8135 supports this.
>
> Is this OK?
We need a multi function device driver for the I2C and the PMIC.
Please have a look on my comments in the other thread.
Thanks,
Matthias
>
>> > + - mediatek,have-dcm: platform has DCM(hardware digital clock manager) property.
>> > + - mediatek,use-push-pull: IO use push-pull mode.
>> > +
>> > +Example:
>> > +
>> > + i2c0: i2c@1100d000 {
>> > + compatible = "mediatek,mt6577-i2c";
>> > + reg = <0x1100d000 0x70>,
>> > + <0x11000300 0x80>;
>> > + interrupts = <GIC_SPI 44 IRQ_TYPE_LEVEL_LOW>;
>> > + clock-frequency = <100000>;
>> > + mediatek,have-pmic;
>> > + clock-div = <16>;
>> > + clocks = <&i2c0_ck>, <&ap_dma_ck>;
>> > + clock-names = "main", "dma";
>> > + };
>> > +
>> > --
>> > 1.8.1.1.dirty
>> >
>>
>>
>>
>
>
--
motzblog.wordpress.com
On Fri, 2014-10-31 at 11:48 +0100, Matthias Brugger wrote:
> 2014-10-31 7:31 GMT+01:00 xudong chen <[email protected]>:
> > On Thu, 2014-10-30 at 14:16 +0100, Matthias Brugger wrote:
> >> 2014-10-29 6:37 GMT+01:00 Xudong Chen <[email protected]>:
> >> > The mediatek SoCs have I2C controller that handle I2C transfer.
> >> > This patch include common I2C bus driver.
> >> > This driver is compatible with I2C controller on mt65xx/mt81xx.
> >> >
> >> > Signed-off-by: Xudong Chen <[email protected]>
> >> > Change-Id: Icc17e326b9df46a226d536956e103f17b0382b6e
> >> > ---
> >> > drivers/i2c/busses/Kconfig | 9 +
> >> > drivers/i2c/busses/Makefile | 1 +
> >> > drivers/i2c/busses/i2c-mt65xx.c | 728 ++++++++++++++++++++++++++++++++++++++++
> >> > 3 files changed, 738 insertions(+)
> >> > create mode 100644 drivers/i2c/busses/i2c-mt65xx.c
> >> >
> >> > diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> >> > index 917c358..0d7d0a4 100644
> >> > --- a/drivers/i2c/busses/Kconfig
> >> > +++ b/drivers/i2c/busses/Kconfig
> >> > @@ -564,6 +564,15 @@ config I2C_MPC
> >> > This driver can also be built as a module. If so, the module
> >> > will be called i2c-mpc.
> >> >
> >> > +config I2C_MT65XX
> >> > + tristate "MediaTek I2C adapter"
> >> > + depends on ARCH_MEDIATEK
> >> > + help
> >> > + This selects the MediaTek(R) Integrated Inter Circuit bus driver
> >> > + for MT65xx and MT81xx.
> >> > + If you want to use MediaTek(R) I2C interface, say Y or M here.
> >> > + If unsure, say N.
> >> > +
> >> > config I2C_MV64XXX
> >> > tristate "Marvell mv64xxx I2C Controller"
> >> > depends on MV64X60 || PLAT_ORION || ARCH_SUNXI
> >> > diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> >> > index 78d56c5..7a9f0f3 100644
> >> > --- a/drivers/i2c/busses/Makefile
> >> > +++ b/drivers/i2c/busses/Makefile
> >> > @@ -54,6 +54,7 @@ obj-$(CONFIG_I2C_IMX) += i2c-imx.o
> >> > obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o
> >> > obj-$(CONFIG_I2C_KEMPLD) += i2c-kempld.o
> >> > obj-$(CONFIG_I2C_MPC) += i2c-mpc.o
> >> > +obj-$(CONFIG_I2C_MT65XX) += i2c-mt65xx.o
> >> > +
> >> > +static int mt_i2c_do_transfer(struct mt_i2c *i2c)
> >> > +{
> >> > + u16 addr_reg;
> >> > + u16 control_reg;
> >> > + int tmo = i2c->adap.timeout;
> >> > +
> >> > + i2c->trans_stop = false;
> >> > + i2c->irq_stat = 0;
> >> > +
> >> > + /* If use i2c pin from PMIC mt6397 side, need set PATH_DIR first */
> >> > + if (i2c->have_pmic)
> >> > + i2c_writew(I2C_CONTROL_WRAPPER, i2c, OFFSET_PATH_DIR);
> >>
> >> So this is some sort of multiplexer bit, right?
> >> I think in this case we need to build a multi function device (mfd)
> >> where the pmic driver will set this bit.
> >>
> > Hi,
> >
> > This feature means the chip can use I2C pins from PMIC(mt6397).
> > In this case,
> > 1. We actually control the register on mt8135 and the DMA/Control Logic
> > is on mt8135, only use pins from mt6397. If set register OFFSET_PATH_DIR
> > bit0 to 1, the I2C data will go/from PMIC pins.
>
> Sorry but I'm a bit puzzled. mt6397 is the PMIC, right?
> So from the mt6589 datasheet it says:
> "PATH_DIR Decides transmission direction
> If set, the I2C data will go/from PMIC; otherwise, the I2C data will
> go/from main die."
>
> From my understanding the I2C bus will be multiplexed depending on
> this bit. The bit decides if it the data will be send to the PMIC or
> to some other I2C peripheral.
>
> So we need a mfd and the PMIC device driver sets/unsets the PATH_DIR register.
Yes, mt6397 is the PMIC work with mt8135 or mt8127.
I think the datasheet is a little misleading here. AFAIK, on 8135 even
if your clear the bit for I2C4, there are no corresponding pins for this
i2c port on 8135. So it is not really multiplexed. Base on my
understanding this is more like "Set this bit if this a PMIC I2C port".
In order to use i2c4, we do need a PMIC pinmux driver to set pinmux
function for this i2c port. Since all the i2c control logic and
registers are on mt8135, it is strange to have PMIC device driver set
this bit on mt8135. Also, since mt8127 doesn't support this, it will be
even stranger when using this pmic and driver with mt8127.
Joe.C
2014-10-31 15:38 GMT+01:00 Yingjoe Chen <[email protected]>:
> On Fri, 2014-10-31 at 11:48 +0100, Matthias Brugger wrote:
>> 2014-10-31 7:31 GMT+01:00 xudong chen <[email protected]>:
>> > On Thu, 2014-10-30 at 14:16 +0100, Matthias Brugger wrote:
>> >> 2014-10-29 6:37 GMT+01:00 Xudong Chen <[email protected]>:
>> >> > The mediatek SoCs have I2C controller that handle I2C transfer.
>> >> > This patch include common I2C bus driver.
>> >> > This driver is compatible with I2C controller on mt65xx/mt81xx.
>> >> >
>> >> > Signed-off-by: Xudong Chen <[email protected]>
>> >> > Change-Id: Icc17e326b9df46a226d536956e103f17b0382b6e
>> >> > ---
>> >> > drivers/i2c/busses/Kconfig | 9 +
>> >> > drivers/i2c/busses/Makefile | 1 +
>> >> > drivers/i2c/busses/i2c-mt65xx.c | 728 ++++++++++++++++++++++++++++++++++++++++
>> >> > 3 files changed, 738 insertions(+)
>> >> > create mode 100644 drivers/i2c/busses/i2c-mt65xx.c
>> >> >
>> >> > diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
>> >> > index 917c358..0d7d0a4 100644
>> >> > --- a/drivers/i2c/busses/Kconfig
>> >> > +++ b/drivers/i2c/busses/Kconfig
>> >> > @@ -564,6 +564,15 @@ config I2C_MPC
>> >> > This driver can also be built as a module. If so, the module
>> >> > will be called i2c-mpc.
>> >> >
>> >> > +config I2C_MT65XX
>> >> > + tristate "MediaTek I2C adapter"
>> >> > + depends on ARCH_MEDIATEK
>> >> > + help
>> >> > + This selects the MediaTek(R) Integrated Inter Circuit bus driver
>> >> > + for MT65xx and MT81xx.
>> >> > + If you want to use MediaTek(R) I2C interface, say Y or M here.
>> >> > + If unsure, say N.
>> >> > +
>> >> > config I2C_MV64XXX
>> >> > tristate "Marvell mv64xxx I2C Controller"
>> >> > depends on MV64X60 || PLAT_ORION || ARCH_SUNXI
>> >> > diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
>> >> > index 78d56c5..7a9f0f3 100644
>> >> > --- a/drivers/i2c/busses/Makefile
>> >> > +++ b/drivers/i2c/busses/Makefile
>> >> > @@ -54,6 +54,7 @@ obj-$(CONFIG_I2C_IMX) += i2c-imx.o
>> >> > obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o
>> >> > obj-$(CONFIG_I2C_KEMPLD) += i2c-kempld.o
>> >> > obj-$(CONFIG_I2C_MPC) += i2c-mpc.o
>> >> > +obj-$(CONFIG_I2C_MT65XX) += i2c-mt65xx.o
>> >> > +
>> >> > +static int mt_i2c_do_transfer(struct mt_i2c *i2c)
>> >> > +{
>> >> > + u16 addr_reg;
>> >> > + u16 control_reg;
>> >> > + int tmo = i2c->adap.timeout;
>> >> > +
>> >> > + i2c->trans_stop = false;
>> >> > + i2c->irq_stat = 0;
>> >> > +
>> >> > + /* If use i2c pin from PMIC mt6397 side, need set PATH_DIR first */
>> >> > + if (i2c->have_pmic)
>> >> > + i2c_writew(I2C_CONTROL_WRAPPER, i2c, OFFSET_PATH_DIR);
>> >>
>> >> So this is some sort of multiplexer bit, right?
>> >> I think in this case we need to build a multi function device (mfd)
>> >> where the pmic driver will set this bit.
>> >>
>> > Hi,
>> >
>> > This feature means the chip can use I2C pins from PMIC(mt6397).
>> > In this case,
>> > 1. We actually control the register on mt8135 and the DMA/Control Logic
>> > is on mt8135, only use pins from mt6397. If set register OFFSET_PATH_DIR
>> > bit0 to 1, the I2C data will go/from PMIC pins.
>>
>> Sorry but I'm a bit puzzled. mt6397 is the PMIC, right?
>> So from the mt6589 datasheet it says:
>> "PATH_DIR Decides transmission direction
>> If set, the I2C data will go/from PMIC; otherwise, the I2C data will
>> go/from main die."
>>
>> From my understanding the I2C bus will be multiplexed depending on
>> this bit. The bit decides if it the data will be send to the PMIC or
>> to some other I2C peripheral.
>>
>> So we need a mfd and the PMIC device driver sets/unsets the PATH_DIR register.
>
> Yes, mt6397 is the PMIC work with mt8135 or mt8127.
>
> I think the datasheet is a little misleading here. AFAIK, on 8135 even
> if your clear the bit for I2C4, there are no corresponding pins for this
> i2c port on 8135. So it is not really multiplexed. Base on my
> understanding this is more like "Set this bit if this a PMIC I2C port".
I had a look at mt6589. The SoC has seven i2c controller. Looking at
the source code all controller with an ID bigger then 3 can be set to
pmic wrapper state.
So I would say that the PATH_DIR bit is not always bound to i2c4. We
should implement a driver which will work on all possible
configurations. We don't know if apart from mt6589 and mt8135 some
other SoC in the future will use this i2c controller.
Maybe you can check with the HW guys how this is implemented to shed
some light on the issue.
>
> In order to use i2c4, we do need a PMIC pinmux driver to set pinmux
> function for this i2c port. Since all the i2c control logic and
> registers are on mt8135, it is strange to have PMIC device driver set
> this bit on mt8135. Also, since mt8127 doesn't support this, it will be
> even stranger when using this pmic and driver with mt8127.
>
> Joe.C
>
>
--
motzblog.wordpress.com
On Fri, 2014-10-31 at 17:10 +0100, Matthias Brugger wrote:
> 2014-10-31 15:38 GMT+01:00 Yingjoe Chen <[email protected]>:
> > On Fri, 2014-10-31 at 11:48 +0100, Matthias Brugger wrote:
> >> 2014-10-31 7:31 GMT+01:00 xudong chen <[email protected]>:
> >> > On Thu, 2014-10-30 at 14:16 +0100, Matthias Brugger wrote:
> >> >> 2014-10-29 6:37 GMT+01:00 Xudong Chen <[email protected]>:
> >> >> > The mediatek SoCs have I2C controller that handle I2C transfer.
> >> >> > This patch include common I2C bus driver.
> >> >> > This driver is compatible with I2C controller on mt65xx/mt81xx.
> >> >> >
> >> >> > Signed-off-by: Xudong Chen <[email protected]>
> >> >> > Change-Id: Icc17e326b9df46a226d536956e103f17b0382b6e
> >> >> > ---
> >> >> > drivers/i2c/busses/Kconfig | 9 +
> >> >> > drivers/i2c/busses/Makefile | 1 +
> >> >> > drivers/i2c/busses/i2c-mt65xx.c | 728 ++++++++++++++++++++++++++++++++++++++++
> >> >> > 3 files changed, 738 insertions(+)
> >> >> > create mode 100644 drivers/i2c/busses/i2c-mt65xx.c
> >> >> >
> >> >> > diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
> >> >> > index 917c358..0d7d0a4 100644
> >> >> > --- a/drivers/i2c/busses/Kconfig
> >> >> > +++ b/drivers/i2c/busses/Kconfig
> >> >> > @@ -564,6 +564,15 @@ config I2C_MPC
> >> >> > This driver can also be built as a module. If so, the module
> >> >> > will be called i2c-mpc.
> >> >> >
> >> >> > +config I2C_MT65XX
> >> >> > + tristate "MediaTek I2C adapter"
> >> >> > + depends on ARCH_MEDIATEK
> >> >> > + help
> >> >> > + This selects the MediaTek(R) Integrated Inter Circuit bus driver
> >> >> > + for MT65xx and MT81xx.
> >> >> > + If you want to use MediaTek(R) I2C interface, say Y or M here.
> >> >> > + If unsure, say N.
> >> >> > +
> >> >> > config I2C_MV64XXX
> >> >> > tristate "Marvell mv64xxx I2C Controller"
> >> >> > depends on MV64X60 || PLAT_ORION || ARCH_SUNXI
> >> >> > diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
> >> >> > index 78d56c5..7a9f0f3 100644
> >> >> > --- a/drivers/i2c/busses/Makefile
> >> >> > +++ b/drivers/i2c/busses/Makefile
> >> >> > @@ -54,6 +54,7 @@ obj-$(CONFIG_I2C_IMX) += i2c-imx.o
> >> >> > obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o
> >> >> > obj-$(CONFIG_I2C_KEMPLD) += i2c-kempld.o
> >> >> > obj-$(CONFIG_I2C_MPC) += i2c-mpc.o
> >> >> > +obj-$(CONFIG_I2C_MT65XX) += i2c-mt65xx.o
> >> >> > +
> >> >> > +static int mt_i2c_do_transfer(struct mt_i2c *i2c)
> >> >> > +{
> >> >> > + u16 addr_reg;
> >> >> > + u16 control_reg;
> >> >> > + int tmo = i2c->adap.timeout;
> >> >> > +
> >> >> > + i2c->trans_stop = false;
> >> >> > + i2c->irq_stat = 0;
> >> >> > +
> >> >> > + /* If use i2c pin from PMIC mt6397 side, need set PATH_DIR first */
> >> >> > + if (i2c->have_pmic)
> >> >> > + i2c_writew(I2C_CONTROL_WRAPPER, i2c, OFFSET_PATH_DIR);
> >> >>
> >> >> So this is some sort of multiplexer bit, right?
> >> >> I think in this case we need to build a multi function device (mfd)
> >> >> where the pmic driver will set this bit.
> >> >>
> >> > Hi,
> >> >
> >> > This feature means the chip can use I2C pins from PMIC(mt6397).
> >> > In this case,
> >> > 1. We actually control the register on mt8135 and the DMA/Control Logic
> >> > is on mt8135, only use pins from mt6397. If set register OFFSET_PATH_DIR
> >> > bit0 to 1, the I2C data will go/from PMIC pins.
> >>
> >> Sorry but I'm a bit puzzled. mt6397 is the PMIC, right?
> >> So from the mt6589 datasheet it says:
> >> "PATH_DIR Decides transmission direction
> >> If set, the I2C data will go/from PMIC; otherwise, the I2C data will
> >> go/from main die."
> >>
> >> From my understanding the I2C bus will be multiplexed depending on
> >> this bit. The bit decides if it the data will be send to the PMIC or
> >> to some other I2C peripheral.
> >>
> >> So we need a mfd and the PMIC device driver sets/unsets the PATH_DIR register.
> >
> > Yes, mt6397 is the PMIC work with mt8135 or mt8127.
> >
> > I think the datasheet is a little misleading here. AFAIK, on 8135 even
> > if your clear the bit for I2C4, there are no corresponding pins for this
> > i2c port on 8135. So it is not really multiplexed. Base on my
> > understanding this is more like "Set this bit if this a PMIC I2C port".
>
> I had a look at mt6589. The SoC has seven i2c controller. Looking at
> the source code all controller with an ID bigger then 3 can be set to
> pmic wrapper state.
I have checked the mt8135 and mt6589 GPIO datasheet, there are pins on
mt8135 or mt6589 for i2c4~i2c6, we also can use i2c4~i2c6 modules on
mt8135 or mt6589 after set pinmux. Sorry about the misunderstand before.
I will add i2c4~i2c6 nodes to the mt8135.dtsi in next patch.
> So I would say that the PATH_DIR bit is not always bound to i2c4. We
> should implement a driver which will work on all possible
> configurations. We don't know if apart from mt6589 and mt8135 some
> other SoC in the future will use this i2c controller.
>
Because the mt6397 HW limitation, it's i2c module cannot be used when
apart from mt6589 or mt8135.
Take mt6589 for example:
If we want to use mt6397 i2c pins, it must work together with mt6589,
also we need to set the i2c control register on mt6589(DIR_PATH and the
other registers) and then the HW will auto handle the communication
between mt6397 and mt6589 internal(do not need any SW control), so data
can go/from mt6397 pins. In this case the i2c pins on mt6397 will work
just like the i2c pins is from mt6589.
> Maybe you can check with the HW guys how this is implemented to shed
> some light on the issue.
>
On Thu, Oct 30, 2014 at 02:16:04PM +0100, Matthias Brugger wrote:
> 2014-10-29 6:37 GMT+01:00 Xudong Chen <[email protected]>:
> > +static int mt_i2c_do_transfer(struct mt_i2c *i2c)
> > +{
> > + u16 addr_reg;
> > + u16 control_reg;
> > + int tmo = i2c->adap.timeout;
> > +
> > + i2c->trans_stop = false;
> > + i2c->irq_stat = 0;
> > +
> > + /* If use i2c pin from PMIC mt6397 side, need set PATH_DIR first */
> > + if (i2c->have_pmic)
> > + i2c_writew(I2C_CONTROL_WRAPPER, i2c, OFFSET_PATH_DIR);
>
> So this is some sort of multiplexer bit, right?
> I think in this case we need to build a multi function device (mfd)
> where the pmic driver will set this bit.
This bit is in the i2c controller register space, so it shouldn't be
controlled by some other driver. I think the configuration of this
register is already in the correct place. What I could think of is that
the PMIC could implement a pinctrl driver and the i2c controller driver
gets the pins from the PMIC. Anyway, the controlling of this bit would
still be in the i2c driver.
Sascha
--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |