From f6505fbabc426b9e293da5bb702ace2eb1ccf87d Mon Sep 17 00:00:00 2001 From: Feng Kan Date: Fri, 24 Apr 2015 15:17:50 -0700 Subject: i2c: add SLIMpro I2C device driver on APM X-Gene platform Add SLIMpro I2C device driver on APM X-Gene platform. This I2C device driver use the SLIMpro Mailbox driver to tunnel message to the SLIMpro coprocessor to do the work of accessing I2C components. Signed-off-by: Feng Kan Signed-off-by: Hieu Le Signed-off-by: Wolfram Sang --- .../devicetree/bindings/i2c/i2c-xgene-slimpro.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Documentation/devicetree/bindings/i2c/i2c-xgene-slimpro.txt (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/i2c/i2c-xgene-slimpro.txt b/Documentation/devicetree/bindings/i2c/i2c-xgene-slimpro.txt new file mode 100644 index 000000000000..f6b2c20cfbf6 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/i2c-xgene-slimpro.txt @@ -0,0 +1,15 @@ +APM X-Gene SLIMpro Mailbox I2C Driver + +An I2C controller accessed over the "SLIMpro" mailbox. + +Required properties : + + - compatible : should be "apm,xgene-slimpro-i2c" + - mboxes : use the label reference for the mailbox as the first parameter. + The second parameter is the channel number. + +Example : + i2cslimpro { + compatible = "apm,xgene-slimpro-i2c"; + mboxes = <&mailbox 0>; + }; -- cgit v1.2.3 From 976cf2056ccf1be1759f8c122d194c117c879e11 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 14 May 2015 14:40:04 +0200 Subject: i2c: slave: docs: be more precise about the prerequsites There was some confusion what was needed to utilize the slave support, so let's be more precise about this. Add an introductory paragraph to the development section while we are here. Signed-off-by: Wolfram Sang Acked-by: Geert Uytterhoeven Signed-off-by: Wolfram Sang --- Documentation/i2c/slave-interface | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) (limited to 'Documentation') diff --git a/Documentation/i2c/slave-interface b/Documentation/i2c/slave-interface index 389bb5d61854..02b086510b6c 100644 --- a/Documentation/i2c/slave-interface +++ b/Documentation/i2c/slave-interface @@ -3,16 +3,16 @@ Linux I2C slave interface description by Wolfram Sang in 2014-15 -Linux can also be an I2C slave in case I2C controllers have slave support. -Besides this HW requirement, one also needs a software backend providing the -actual functionality. An example for this is the slave-eeprom driver, which -acts as a dual memory driver. While another I2C master on the bus can access it -like a regular EEPROM, the Linux I2C slave can access the content via sysfs and -retrieve/provide information as needed. The software backend driver and the I2C -bus driver communicate via events. Here is a small graph visualizing the data -flow and the means by which data is transported. The dotted line marks only one -example. The backend could also use e.g. a character device, be in-kernel -only, or something completely different: +Linux can also be an I2C slave if the I2C controller in use has slave +functionality. For that to work, one needs slave support in the bus driver plus +a hardware independent software backend providing the actual functionality. An +example for the latter is the slave-eeprom driver, which acts as a dual memory +driver. While another I2C master on the bus can access it like a regular +EEPROM, the Linux I2C slave can access the content via sysfs and handle data as +needed. The backend driver and the I2C bus driver communicate via events. Here +is a small graph visualizing the data flow and the means by which data is +transported. The dotted line marks only one example. The backend could also +use a character device, be in-kernel only, or something completely different: e.g. sysfs I2C slave events I/O registers @@ -43,6 +43,11 @@ behaviour and setup. Developer manual ================ +First, the events which are used by the bus driver and the backend will be +described in detail. After that, some implementation hints for extending bus +drivers and writing backends will be given. + + I2C slave events ---------------- -- cgit v1.2.3 From ce38815d39eac9c73d37dd4b0039c87f15dcbe18 Mon Sep 17 00:00:00 2001 From: Xudong Chen Date: Thu, 21 May 2015 16:53:28 +0800 Subject: I2C: mediatek: Add driver for MediaTek I2C controller 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 Signed-off-by: Liguo Zhang Signed-off-by: Eddie Huang Acked-by: Sascha Hauer Signed-off-by: Wolfram Sang --- .../devicetree/bindings/i2c/i2c-mt6577.txt | 41 ++ drivers/i2c/busses/Kconfig | 9 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-mt65xx.c | 686 +++++++++++++++++++++ 4 files changed, 737 insertions(+) create mode 100644 Documentation/devicetree/bindings/i2c/i2c-mt6577.txt create mode 100644 drivers/i2c/busses/i2c-mt65xx.c (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/i2c/i2c-mt6577.txt b/Documentation/devicetree/bindings/i2c/i2c-mt6577.txt new file mode 100644 index 000000000000..0ce6fa3242f0 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/i2c-mt6577.txt @@ -0,0 +1,41 @@ +* 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) "mediatek,mt6577-i2c", for i2c compatible with mt6577 i2c. + (b) "mediatek,mt6589-i2c", for i2c compatible with mt6589 i2c. + (c) "mediatek,mt8127-i2c", for i2c compatible with mt8127 i2c. + (d) "mediatek,mt8135-i2c", for i2c compatible with mt8135 i2c. + (e) "mediatek,mt8173-i2c", for i2c compatible with mt8173 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: Must include "main" and "dma", if enable have-pmic need include + "pmic" extra. + +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. + Only mt6589 and mt8135 support this feature. + - mediatek,use-push-pull: IO config use push-pull mode. + +Example: + + i2c0: i2c@1100d000 { + compatible = "mediatek,mt6577-i2c"; + reg = <0x1100d000 0x70>, + <0x11000300 0x80>; + interrupts = ; + clock-frequency = <400000>; + mediatek,have-pmic; + clock-div = <16>; + clocks = <&i2c0_ck>, <&ap_dma_ck>; + clock-names = "main", "dma"; + }; + diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 1d759c3767e7..8a639dce5173 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -620,6 +620,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 || COMPILE_TEST + 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 d8093022ee4c..65995423c680 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -60,6 +60,7 @@ obj-$(CONFIG_I2C_JZ4780) += i2c-jz4780.o obj-$(CONFIG_I2C_KEMPLD) += i2c-kempld.o obj-$(CONFIG_I2C_MESON) += i2c-meson.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 000000000000..b1d2d7cc6db0 --- /dev/null +++ b/drivers/i2c/busses/i2c-mt65xx.c @@ -0,0 +1,686 @@ +/* + * Copyright (c) 2014 MediaTek Inc. + * Author: Xudong Chen + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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_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_FS_TIME_INIT_VALUE 0x1303 +#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_DEFAULT_SPEED 100000 /* hz */ +#define MAX_FS_MODE_SPEED 400000 +#define MAX_HS_MODE_SPEED 3400000 +#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 "i2c-mt65xx" + +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 mtk_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 mtk_i2c_compatible { + const struct i2c_adapter_quirks *quirks; + unsigned char pmic_i2c: 1; + unsigned char dcm: 1; +}; + +struct mtk_i2c { + struct i2c_adapter adap; /* i2c host adapter */ + struct device *dev; + struct completion msg_complete; + + /* set in i2c probe */ + void __iomem *base; /* i2c base addr */ + void __iomem *pdmabase; /* dma base address*/ + 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 from PMIC */ + bool use_push_pull; /* IO config push-pull mode */ + + u16 irq_stat; /* interrupt status */ + unsigned int speed_hz; /* The speed in transfer */ + enum mtk_trans_op op; + u16 timing_reg; + u16 high_speed_reg; + const struct mtk_i2c_compatible *dev_comp; +}; + +static const struct i2c_adapter_quirks mt6577_i2c_quirks = { + .flags = I2C_AQ_COMB_WRITE_THEN_READ, + .max_num_msgs = 1, + .max_write_len = 255, + .max_read_len = 255, + .max_comb_1st_msg_len = 255, + .max_comb_2nd_msg_len = 31, +}; + +static const struct mtk_i2c_compatible mt6577_compat = { + .quirks = &mt6577_i2c_quirks, + .pmic_i2c = 0, + .dcm = 1, +}; + +static const struct mtk_i2c_compatible mt6589_compat = { + .quirks = &mt6577_i2c_quirks, + .pmic_i2c = 1, + .dcm = 0, +}; + +static const struct of_device_id mtk_i2c_of_match[] = { + { .compatible = "mediatek,mt6577-i2c", .data = &mt6577_compat }, + { .compatible = "mediatek,mt6589-i2c", .data = &mt6589_compat }, + {} +}; +MODULE_DEVICE_TABLE(of, mtk_i2c_of_match); + +static int mtk_i2c_clock_enable(struct mtk_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 mtk_i2c_clock_disable(struct mtk_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 mtk_i2c_init_hw(struct mtk_i2c *i2c) +{ + u16 control_reg; + + writew(I2C_SOFT_RST, i2c->base + OFFSET_SOFTRESET); + + /* Set ioconfig */ + if (i2c->use_push_pull) + writew(I2C_IO_CONFIG_PUSH_PULL, i2c->base + OFFSET_IO_CONFIG); + else + writew(I2C_IO_CONFIG_OPEN_DRAIN, i2c->base + OFFSET_IO_CONFIG); + + if (i2c->dev_comp->dcm) + writew(I2C_DCM_DISABLE, i2c->base + OFFSET_DCM_EN); + + writew(i2c->timing_reg, i2c->base + OFFSET_TIMING); + writew(i2c->high_speed_reg, i2c->base + OFFSET_HS); + + /* If use i2c pin from PMIC mt6397 side, need set PATH_DIR first */ + if (i2c->have_pmic) + writew(I2C_CONTROL_WRAPPER, i2c->base + OFFSET_PATH_DIR); + + control_reg = I2C_CONTROL_ACKERR_DET_EN | + I2C_CONTROL_CLK_EXT_EN | I2C_CONTROL_DMA_EN; + writew(control_reg, i2c->base + OFFSET_CONTROL); + writew(I2C_DELAY_LEN, i2c->base + OFFSET_DELAY_LEN); +} + +/* + * Calculate i2c port speed + * + * Hardware design: + * i2c_bus_freq = parent_clk / (clock_div * 2 * sample_cnt * step_cnt) + * clock_div: fixed in hardware, but may be various in different SoCs + * + * The calculation want to pick the highest bus frequency that is still + * less than or equal to i2c->speed_hz. The calculation try to get + * sample_cnt and step_cn + */ +static int mtk_i2c_set_speed(struct mtk_i2c *i2c, unsigned int parent_clk, + unsigned int clock_div) +{ + unsigned int clk_src; + unsigned int step_cnt; + unsigned int sample_cnt; + unsigned int max_step_cnt; + unsigned int target_speed; + unsigned int base_sample_cnt = MAX_SAMPLE_CNT_DIV; + unsigned int base_step_cnt; + unsigned int opt_div; + unsigned int best_mul; + unsigned int cnt_mul; + + clk_src = parent_clk / clock_div; + target_speed = i2c->speed_hz; + + if (target_speed > MAX_HS_MODE_SPEED) + target_speed = MAX_HS_MODE_SPEED; + + if (target_speed > MAX_FS_MODE_SPEED) + max_step_cnt = MAX_HS_STEP_CNT_DIV; + else + max_step_cnt = MAX_STEP_CNT_DIV; + + base_step_cnt = max_step_cnt; + /* Find the best combination */ + opt_div = DIV_ROUND_UP(clk_src >> 1, target_speed); + best_mul = MAX_SAMPLE_CNT_DIV * max_step_cnt; + + /* Search for the best pair (sample_cnt, step_cnt) with + * 0 < sample_cnt < MAX_SAMPLE_CNT_DIV + * 0 < step_cnt < max_step_cnt + * sample_cnt * step_cnt >= opt_div + * optimizing for sample_cnt * step_cnt being minimal + */ + for (sample_cnt = 1; sample_cnt <= MAX_SAMPLE_CNT_DIV; sample_cnt++) { + step_cnt = DIV_ROUND_UP(opt_div, sample_cnt); + cnt_mul = step_cnt * sample_cnt; + if (step_cnt > max_step_cnt) + continue; + + if (cnt_mul < best_mul) { + best_mul = cnt_mul; + base_sample_cnt = sample_cnt; + base_step_cnt = step_cnt; + if (best_mul == opt_div) + break; + } + } + + sample_cnt = base_sample_cnt; + step_cnt = base_step_cnt; + + if ((clk_src / (2 * sample_cnt * step_cnt)) > target_speed) { + /* In this case, hardware can't support such + * low i2c_bus_freq + */ + dev_dbg(i2c->dev, "Unsupported speed (%uhz)\n", target_speed); + return -EINVAL; + } + + step_cnt--; + sample_cnt--; + + if (target_speed > MAX_FS_MODE_SPEED) { + /* Set the high speed mode register */ + i2c->timing_reg = I2C_FS_TIME_INIT_VALUE; + i2c->high_speed_reg = I2C_TIME_DEFAULT_VALUE | + (sample_cnt << 12) | (step_cnt << 8); + } else { + i2c->timing_reg = (sample_cnt << 8) | (step_cnt << 0); + /* Disable the high speed transaction */ + i2c->high_speed_reg = I2C_TIME_CLR_VALUE; + } + + return 0; +} + +static int mtk_i2c_do_transfer(struct mtk_i2c *i2c, struct i2c_msg *msgs) +{ + u16 addr_reg; + u16 control_reg; + dma_addr_t rpaddr = 0; + dma_addr_t wpaddr = 0; + int ret; + + i2c->irq_stat = 0; + + reinit_completion(&i2c->msg_complete); + + control_reg = readw(i2c->base + OFFSET_CONTROL) & + ~(I2C_CONTROL_DIR_CHANGE | I2C_CONTROL_RS); + 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; + + writew(control_reg, i2c->base + OFFSET_CONTROL); + + /* set start condition */ + if (i2c->speed_hz <= 100000) + writew(I2C_ST_START_CON, i2c->base + OFFSET_EXT_CONF); + else + writew(I2C_FS_START_CON, i2c->base + OFFSET_EXT_CONF); + + addr_reg = msgs->addr << 1; + if (i2c->op == I2C_MASTER_RD) + addr_reg |= 0x1; + + writew(addr_reg, i2c->base + OFFSET_SLAVE_ADDR); + + /* Clear interrupt status */ + writew(I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP, + i2c->base + OFFSET_INTR_STAT); + writew(I2C_FIFO_ADDR_CLR, i2c->base + OFFSET_FIFO_ADDR_CLR); + + /* Enable interrupt */ + writew(I2C_HS_NACKERR | I2C_ACKERR | I2C_TRANSAC_COMP, + i2c->base + OFFSET_INTR_MASK); + + /* Set transfer and transaction len */ + if (i2c->op == I2C_MASTER_WRRD) { + writew(msgs->len | ((msgs + 1)->len) << 8, + i2c->base + OFFSET_TRANSFER_LEN); + writew(I2C_WRRD_TRANAC_VALUE, i2c->base + OFFSET_TRANSAC_LEN); + } else { + writew(msgs->len, i2c->base + OFFSET_TRANSFER_LEN); + writew(I2C_RD_TRANAC_VALUE, i2c->base + OFFSET_TRANSAC_LEN); + } + + /* Prepare buffer data to start transfer */ + if (i2c->op == I2C_MASTER_RD) { + writel(I2C_DMA_INT_FLAG_NONE, i2c->pdmabase + OFFSET_INT_FLAG); + writel(I2C_DMA_CON_RX, i2c->pdmabase + OFFSET_CON); + rpaddr = dma_map_single(i2c->dev, msgs->buf, + msgs->len, DMA_FROM_DEVICE); + if (dma_mapping_error(i2c->dev, rpaddr)) + return -ENOMEM; + writel((u32)rpaddr, i2c->pdmabase + OFFSET_RX_MEM_ADDR); + writel(msgs->len, i2c->pdmabase + OFFSET_RX_LEN); + } else if (i2c->op == I2C_MASTER_WR) { + writel(I2C_DMA_INT_FLAG_NONE, i2c->pdmabase + OFFSET_INT_FLAG); + writel(I2C_DMA_CON_TX, i2c->pdmabase + OFFSET_CON); + wpaddr = dma_map_single(i2c->dev, msgs->buf, + msgs->len, DMA_TO_DEVICE); + if (dma_mapping_error(i2c->dev, wpaddr)) + return -ENOMEM; + writel((u32)wpaddr, i2c->pdmabase + OFFSET_TX_MEM_ADDR); + writel(msgs->len, i2c->pdmabase + OFFSET_TX_LEN); + } else { + writel(I2C_DMA_CLR_FLAG, i2c->pdmabase + OFFSET_INT_FLAG); + writel(I2C_DMA_CLR_FLAG, i2c->pdmabase + OFFSET_CON); + wpaddr = dma_map_single(i2c->dev, msgs->buf, + msgs->len, DMA_TO_DEVICE); + if (dma_mapping_error(i2c->dev, wpaddr)) + return -ENOMEM; + rpaddr = dma_map_single(i2c->dev, (msgs + 1)->buf, + (msgs + 1)->len, + DMA_FROM_DEVICE); + if (dma_mapping_error(i2c->dev, rpaddr)) { + dma_unmap_single(i2c->dev, wpaddr, + msgs->len, DMA_TO_DEVICE); + return -ENOMEM; + } + writel((u32)wpaddr, i2c->pdmabase + OFFSET_TX_MEM_ADDR); + writel((u32)rpaddr, i2c->pdmabase + OFFSET_RX_MEM_ADDR); + writel(msgs->len, i2c->pdmabase + OFFSET_TX_LEN); + writel((msgs + 1)->len, i2c->pdmabase + OFFSET_RX_LEN); + } + + writel(I2C_DMA_START_EN, i2c->pdmabase + OFFSET_EN); + writew(I2C_TRANSAC_START, i2c->base + OFFSET_START); + + ret = wait_for_completion_timeout(&i2c->msg_complete, + i2c->adap.timeout); + + /* Clear interrupt mask */ + writew(~(I2C_HS_NACKERR | I2C_ACKERR | + I2C_TRANSAC_COMP), i2c->base + OFFSET_INTR_MASK); + + if (i2c->op == I2C_MASTER_WR) { + dma_unmap_single(i2c->dev, wpaddr, + msgs->len, DMA_TO_DEVICE); + } else if (i2c->op == I2C_MASTER_RD) { + dma_unmap_single(i2c->dev, rpaddr, + msgs->len, DMA_FROM_DEVICE); + } else { + dma_unmap_single(i2c->dev, wpaddr, msgs->len, + DMA_TO_DEVICE); + dma_unmap_single(i2c->dev, rpaddr, (msgs + 1)->len, + DMA_FROM_DEVICE); + } + + if (ret == 0) { + dev_dbg(i2c->dev, "addr: %x, transfer timeout\n", msgs->addr); + mtk_i2c_init_hw(i2c); + return -ETIMEDOUT; + } + + completion_done(&i2c->msg_complete); + + if (i2c->irq_stat & (I2C_HS_NACKERR | I2C_ACKERR)) { + dev_dbg(i2c->dev, "addr: %x, transfer ACK error\n", msgs->addr); + mtk_i2c_init_hw(i2c); + return -ENXIO; + } + + return 0; +} + +static int mtk_i2c_transfer(struct i2c_adapter *adap, + struct i2c_msg msgs[], int num) +{ + int ret; + int left_num = num; + struct mtk_i2c *i2c = i2c_get_adapdata(adap); + + ret = mtk_i2c_clock_enable(i2c); + if (ret) + return ret; + + if (!msgs->buf) { + dev_dbg(i2c->dev, "data buffer is NULL.\n"); + ret = -EINVAL; + goto err_exit; + } + + if (msgs->flags & I2C_M_RD) + i2c->op = I2C_MASTER_RD; + else + i2c->op = I2C_MASTER_WR; + + if (num > 1) { + /* combined two messages into one transaction */ + i2c->op = I2C_MASTER_WRRD; + left_num--; + } + + /* always use DMA mode. */ + ret = mtk_i2c_do_transfer(i2c, msgs); + if (ret < 0) + goto err_exit; + + /* the return value is number of executed messages */ + ret = num; + +err_exit: + mtk_i2c_clock_disable(i2c); + return ret; +} + +static irqreturn_t mtk_i2c_irq(int irqno, void *dev_id) +{ + struct mtk_i2c *i2c = dev_id; + + i2c->irq_stat = readw(i2c->base + OFFSET_INTR_STAT); + writew(I2C_HS_NACKERR | I2C_ACKERR + | I2C_TRANSAC_COMP, i2c->base + OFFSET_INTR_STAT); + + complete(&i2c->msg_complete); + + return IRQ_HANDLED; +} + +static u32 mtk_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm mtk_i2c_algorithm = { + .master_xfer = mtk_i2c_transfer, + .functionality = mtk_i2c_functionality, +}; + +static int mtk_i2c_parse_dt(struct device_node *np, struct mtk_i2c *i2c, + unsigned int *clk_src_div) +{ + int ret; + + ret = of_property_read_u32(np, "clock-frequency", &i2c->speed_hz); + if (ret < 0) + i2c->speed_hz = I2C_DEFAULT_SPEED; + + ret = of_property_read_u32(np, "clock-div", clk_src_div); + if (ret < 0) + return ret; + + if (*clk_src_div == 0) + return -EINVAL; + + i2c->have_pmic = of_property_read_bool(np, "mediatek,have-pmic"); + i2c->use_push_pull = + of_property_read_bool(np, "mediatek,use-push-pull"); + + return 0; +} + +static int mtk_i2c_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id; + int ret = 0; + struct mtk_i2c *i2c; + struct clk *clk; + unsigned int clk_src_div; + struct resource *res; + int irq; + + i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + + ret = mtk_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); + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return irq; + + init_completion(&i2c->msg_complete); + + of_id = of_match_node(mtk_i2c_of_match, pdev->dev.of_node); + if (!of_id) + return -EINVAL; + + i2c->dev_comp = of_id->data; + i2c->adap.dev.of_node = pdev->dev.of_node; + i2c->dev = &pdev->dev; + i2c->adap.dev.parent = &pdev->dev; + i2c->adap.owner = THIS_MODULE; + i2c->adap.algo = &mtk_i2c_algorithm; + i2c->adap.quirks = i2c->dev_comp->quirks; + i2c->adap.timeout = 2 * HZ; + i2c->adap.retries = 1; + + if (i2c->have_pmic && !i2c->dev_comp->pmic_i2c) + return -EINVAL; + + 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); + } + + clk = i2c->clk_main; + 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 = i2c->clk_pmic; + } + + strlcpy(i2c->adap.name, I2C_DRV_NAME, sizeof(i2c->adap.name)); + + ret = mtk_i2c_set_speed(i2c, clk_get_rate(clk), clk_src_div); + if (ret) { + dev_err(&pdev->dev, "Failed to set the speed.\n"); + return -EINVAL; + } + + ret = mtk_i2c_clock_enable(i2c); + if (ret) { + dev_err(&pdev->dev, "clock enable failed!\n"); + return ret; + } + mtk_i2c_init_hw(i2c); + mtk_i2c_clock_disable(i2c); + + ret = devm_request_irq(&pdev->dev, irq, mtk_i2c_irq, + IRQF_TRIGGER_NONE, I2C_DRV_NAME, i2c); + if (ret < 0) { + dev_err(&pdev->dev, + "Request I2C IRQ %d fail\n", irq); + return ret; + } + + 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"); + return ret; + } + + platform_set_drvdata(pdev, i2c); + + return 0; +} + +static int mtk_i2c_remove(struct platform_device *pdev) +{ + struct mtk_i2c *i2c = platform_get_drvdata(pdev); + + i2c_del_adapter(&i2c->adap); + + return 0; +} + +static struct platform_driver mtk_i2c_driver = { + .probe = mtk_i2c_probe, + .remove = mtk_i2c_remove, + .driver = { + .name = I2C_DRV_NAME, + .of_match_table = of_match_ptr(mtk_i2c_of_match), + }, +}; + +module_platform_driver(mtk_i2c_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MediaTek I2C Bus Driver"); +MODULE_AUTHOR("Xudong Chen "); -- cgit v1.2.3 From 0ba82c955749937ea9dc09b62b8414eb4f0755e7 Mon Sep 17 00:00:00 2001 From: Cyrille Pitchen Date: Tue, 9 Jun 2015 18:22:16 +0200 Subject: i2c: at91: update documentation for DT bindings add a new value "atmel,sama5d2-i2c" for the "compatible" property. add a new optional property "atmel,fifo-size" to enable FIFO support when available. add missing optional properties "dmas" and "dma-names". Signed-off-by: Cyrille Pitchen Acked-by: Ludovic Desroches Signed-off-by: Wolfram Sang --- Documentation/devicetree/bindings/i2c/i2c-at91.txt | 30 ++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/i2c/i2c-at91.txt b/Documentation/devicetree/bindings/i2c/i2c-at91.txt index 388f0a275fba..6e81dc153f3b 100644 --- a/Documentation/devicetree/bindings/i2c/i2c-at91.txt +++ b/Documentation/devicetree/bindings/i2c/i2c-at91.txt @@ -2,8 +2,8 @@ I2C for Atmel platforms Required properties : - compatible : Must be "atmel,at91rm9200-i2c", "atmel,at91sam9261-i2c", - "atmel,at91sam9260-i2c", "atmel,at91sam9g20-i2c", "atmel,at91sam9g10-i2c" - or "atmel,at91sam9x5-i2c" + "atmel,at91sam9260-i2c", "atmel,at91sam9g20-i2c", "atmel,at91sam9g10-i2c", + "atmel,at91sam9x5-i2c" or "atmel,sama5d2-i2c" - reg: physical base address of the controller and length of memory mapped region. - interrupts: interrupt number to the cpu. @@ -13,6 +13,10 @@ Required properties : Optional properties: - clock-frequency: Desired I2C bus frequency in Hz, otherwise defaults to 100000 +- dmas: A list of two dma specifiers, one for each entry in dma-names. +- dma-names: should contain "tx" and "rx". +- atmel,fifo-size: maximum number of data the RX and TX FIFOs can store for FIFO + capable I2C controllers. - Child nodes conforming to i2c bus binding Examples : @@ -32,3 +36,25 @@ i2c0: i2c@fff84000 { pagesize = <128>; } } + +i2c0: i2c@f8034600 { + compatible = "atmel,sama5d2-i2c"; + reg = <0xf8034600 0x100>; + interrupts = <19 IRQ_TYPE_LEVEL_HIGH 7>; + dmas = <&dma0 + (AT91_XDMAC_DT_MEM_IF(0) | AT91_XDMAC_DT_PER_IF(1)) + AT91_XDMAC_DT_PERID(11)>, + <&dma0 + (AT91_XDMAC_DT_MEM_IF(0) | AT91_XDMAC_DT_PER_IF(1)) + AT91_XDMAC_DT_PERID(12)>; + dma-names = "tx", "rx"; + #address-cells = <1>; + #size-cells = <0>; + clocks = <&flx0>; + atmel,fifo-size = <16>; + + wm8731: wm8731@1a { + compatible = "wm8731"; + reg = <0x1a>; + }; +}; -- cgit v1.2.3 From dd1aa2524bc55345ede0b13853c54e1b0be22b9f Mon Sep 17 00:00:00 2001 From: Kamal Dasu Date: Tue, 9 Jun 2015 15:36:20 -0400 Subject: i2c: brcmstb: Add Broadcom settop SoC i2c controller driver Adding support for i2c controller driver for Broadcom settop SoCs. Signed-off-by: Kamal Dasu [wsa: removed superfluous owner in platform_driver] Signed-off-by: Wolfram Sang --- .../devicetree/bindings/i2c/i2c-brcmstb.txt | 28 + drivers/i2c/busses/Kconfig | 10 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-brcmstb.c | 694 +++++++++++++++++++++ 4 files changed, 733 insertions(+) create mode 100644 Documentation/devicetree/bindings/i2c/i2c-brcmstb.txt create mode 100644 drivers/i2c/busses/i2c-brcmstb.c (limited to 'Documentation') diff --git a/Documentation/devicetree/bindings/i2c/i2c-brcmstb.txt b/Documentation/devicetree/bindings/i2c/i2c-brcmstb.txt new file mode 100644 index 000000000000..d6f724efdcf2 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/i2c-brcmstb.txt @@ -0,0 +1,28 @@ +Broadcom stb bsc iic master controller + +Required properties: + +- compatible: should be "brcm,brcmstb-i2c" +- clock-frequency: 32-bit decimal value of iic master clock freqency in Hz + valid values are 375000, 390000, 187500, 200000 + 93750, 97500, 46875 and 50000 +- reg: specifies the base physical address and size of the registers + +Optional properties : + +- interrupt-parent: specifies the phandle to the parent interrupt controller + this one is cascaded from +- interrupts: specifies the interrupt number, the irq line to be used +- interrupt-names: Interrupt name string + +Example: + +bsca: i2c@f0406200 { + clock-frequency = <390000>; + compatible = "brcm,brcmstb-i2c"; + interrupt-parent = <&irq0_intc>; + reg = <0xf0406200 0x58>; + interrupts = <0x18>; + interrupt-names = "upg_bsca"; +}; + diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 8a639dce5173..b8c3d6b53861 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -392,6 +392,16 @@ config I2C_BCM_KONA If you do not need KONA I2C interface, say N. +config I2C_BRCMSTB + tristate "BRCM Settop I2C controller" + depends on ARCH_BRCMSTB || COMPILE_TEST + default y + help + If you say yes to this option, support will be included for the + I2C interface on the Broadcom Settop SoCs. + + If you do not need I2C interface, say N. + config I2C_BLACKFIN_TWI tristate "Blackfin TWI I2C support" depends on BLACKFIN diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 65995423c680..e5f537c80da0 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -106,6 +106,7 @@ obj-$(CONFIG_I2C_VIPERBOARD) += i2c-viperboard.o # Other I2C/SMBus bus drivers obj-$(CONFIG_I2C_ACORN) += i2c-acorn.o obj-$(CONFIG_I2C_BCM_KONA) += i2c-bcm-kona.o +obj-$(CONFIG_I2C_BRCMSTB) += i2c-brcmstb.o obj-$(CONFIG_I2C_CROS_EC_TUNNEL) += i2c-cros-ec-tunnel.o obj-$(CONFIG_I2C_ELEKTOR) += i2c-elektor.o obj-$(CONFIG_I2C_OPAL) += i2c-opal.o diff --git a/drivers/i2c/busses/i2c-brcmstb.c b/drivers/i2c/busses/i2c-brcmstb.c new file mode 100644 index 000000000000..8e9637eea512 --- /dev/null +++ b/drivers/i2c/busses/i2c-brcmstb.c @@ -0,0 +1,694 @@ +/* + * Copyright (C) 2014 Broadcom Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define N_DATA_REGS 8 +#define N_DATA_BYTES (N_DATA_REGS * 4) + +/* BSC count register field definitions */ +#define BSC_CNT_REG1_MASK 0x0000003f +#define BSC_CNT_REG1_SHIFT 0 +#define BSC_CNT_REG2_MASK 0x00000fc0 +#define BSC_CNT_REG2_SHIFT 6 + +/* BSC CTL register field definitions */ +#define BSC_CTL_REG_DTF_MASK 0x00000003 +#define BSC_CTL_REG_SCL_SEL_MASK 0x00000030 +#define BSC_CTL_REG_SCL_SEL_SHIFT 4 +#define BSC_CTL_REG_INT_EN_MASK 0x00000040 +#define BSC_CTL_REG_INT_EN_SHIFT 6 +#define BSC_CTL_REG_DIV_CLK_MASK 0x00000080 + +/* BSC_IIC_ENABLE r/w enable and interrupt field defintions */ +#define BSC_IIC_EN_RESTART_MASK 0x00000040 +#define BSC_IIC_EN_NOSTART_MASK 0x00000020 +#define BSC_IIC_EN_NOSTOP_MASK 0x00000010 +#define BSC_IIC_EN_NOACK_MASK 0x00000004 +#define BSC_IIC_EN_INTRP_MASK 0x00000002 +#define BSC_IIC_EN_ENABLE_MASK 0x00000001 + +/* BSC_CTLHI control register field definitions */ +#define BSC_CTLHI_REG_INPUT_SWITCHING_LEVEL_MASK 0x00000080 +#define BSC_CTLHI_REG_DATAREG_SIZE_MASK 0x00000040 +#define BSC_CTLHI_REG_IGNORE_ACK_MASK 0x00000002 +#define BSC_CTLHI_REG_WAIT_DIS_MASK 0x00000001 + +#define I2C_TIMEOUT 100 /* msecs */ + +/* Condition mask used for non combined transfer */ +#define COND_RESTART BSC_IIC_EN_RESTART_MASK +#define COND_NOSTART BSC_IIC_EN_NOSTART_MASK +#define COND_NOSTOP BSC_IIC_EN_NOSTOP_MASK +#define COND_START_STOP (COND_RESTART | COND_NOSTART | COND_NOSTOP) + +/* BSC data transfer direction */ +#define DTF_WR_MASK 0x00000000 +#define DTF_RD_MASK 0x00000001 +/* BSC data transfer direction combined format */ +#define DTF_RD_WR_MASK 0x00000002 +#define DTF_WR_RD_MASK 0x00000003 + +#define INT_ENABLE true +#define INT_DISABLE false + +/* BSC block register map structure to cache fields to be written */ +struct bsc_regs { + u32 chip_address; /* slave address */ + u32 data_in[N_DATA_REGS]; /* tx data buffer*/ + u32 cnt_reg; /* rx/tx data length */ + u32 ctl_reg; /* control register */ + u32 iic_enable; /* xfer enable and status */ + u32 data_out[N_DATA_REGS]; /* rx data buffer */ + u32 ctlhi_reg; /* more control fields */ + u32 scl_param; /* reserved */ +}; + +struct bsc_clk_param { + u32 hz; + u32 scl_mask; + u32 div_mask; +}; + +enum bsc_xfer_cmd { + CMD_WR, + CMD_RD, + CMD_WR_NOACK, + CMD_RD_NOACK, +}; + +static char const *cmd_string[] = { + [CMD_WR] = "WR", + [CMD_RD] = "RD", + [CMD_WR_NOACK] = "WR NOACK", + [CMD_RD_NOACK] = "RD NOACK", +}; + +enum bus_speeds { + SPD_375K, + SPD_390K, + SPD_187K, + SPD_200K, + SPD_93K, + SPD_97K, + SPD_46K, + SPD_50K +}; + +static const struct bsc_clk_param bsc_clk[] = { + [SPD_375K] = { + .hz = 375000, + .scl_mask = SPD_375K << BSC_CTL_REG_SCL_SEL_SHIFT, + .div_mask = 0 + }, + [SPD_390K] = { + .hz = 390000, + .scl_mask = SPD_390K << BSC_CTL_REG_SCL_SEL_SHIFT, + .div_mask = 0 + }, + [SPD_187K] = { + .hz = 187500, + .scl_mask = SPD_187K << BSC_CTL_REG_SCL_SEL_SHIFT, + .div_mask = 0 + }, + [SPD_200K] = { + .hz = 200000, + .scl_mask = SPD_200K << BSC_CTL_REG_SCL_SEL_SHIFT, + .div_mask = 0 + }, + [SPD_93K] = { + .hz = 93750, + .scl_mask = SPD_375K << BSC_CTL_REG_SCL_SEL_SHIFT, + .div_mask = BSC_CTL_REG_DIV_CLK_MASK + }, + [SPD_97K] = { + .hz = 97500, + .scl_mask = SPD_390K << BSC_CTL_REG_SCL_SEL_SHIFT, + .div_mask = BSC_CTL_REG_DIV_CLK_MASK + }, + [SPD_46K] = { + .hz = 46875, + .scl_mask = SPD_187K << BSC_CTL_REG_SCL_SEL_SHIFT, + .div_mask = BSC_CTL_REG_DIV_CLK_MASK + }, + [SPD_50K] = { + .hz = 50000, + .scl_mask = SPD_200K << BSC_CTL_REG_SCL_SEL_SHIFT, + .div_mask = BSC_CTL_REG_DIV_CLK_MASK + } +}; + +struct brcmstb_i2c_dev { + struct device *device; + void __iomem *base; + void __iomem *irq_base; + int irq; + struct bsc_regs *bsc_regmap; + struct i2c_adapter adapter; + struct completion done; + bool is_suspended; + u32 clk_freq_hz; +}; + +/* register accessors for both be and le cpu arch */ +#ifdef CONFIG_CPU_BIG_ENDIAN +#define __bsc_readl(_reg) ioread32be(_reg) +#define __bsc_writel(_val, _reg) iowrite32be(_val, _reg) +#else +#define __bsc_readl(_reg) ioread32(_reg) +#define __bsc_writel(_val, _reg) iowrite32(_val, _reg) +#endif + +#define bsc_readl(_dev, _reg) \ + __bsc_readl(_dev->base + offsetof(struct bsc_regs, _reg)) + +#define bsc_writel(_dev, _val, _reg) \ + __bsc_writel(_val, _dev->base + offsetof(struct bsc_regs, _reg)) + +static void brcmstb_i2c_enable_disable_irq(struct brcmstb_i2c_dev *dev, + bool int_en) +{ + + if (int_en) + /* Enable BSC CTL interrupt line */ + dev->bsc_regmap->ctl_reg |= BSC_CTL_REG_INT_EN_MASK; + else + /* Disable BSC CTL interrupt line */ + dev->bsc_regmap->ctl_reg &= ~BSC_CTL_REG_INT_EN_MASK; + + barrier(); + bsc_writel(dev, dev->bsc_regmap->ctl_reg, ctl_reg); +} + +static irqreturn_t brcmstb_i2c_isr(int irq, void *devid) +{ + struct brcmstb_i2c_dev *dev = devid; + u32 status_bsc_ctl = bsc_readl(dev, ctl_reg); + u32 status_iic_intrp = bsc_readl(dev, iic_enable); + + dev_dbg(dev->device, "isr CTL_REG %x IIC_EN %x\n", + status_bsc_ctl, status_iic_intrp); + + if (!(status_bsc_ctl & BSC_CTL_REG_INT_EN_MASK)) + return IRQ_NONE; + + brcmstb_i2c_enable_disable_irq(dev, INT_DISABLE); + complete_all(&dev->done); + + dev_dbg(dev->device, "isr handled"); + return IRQ_HANDLED; +} + +/* Wait for device to be ready */ +static int brcmstb_i2c_wait_if_busy(struct brcmstb_i2c_dev *dev) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(I2C_TIMEOUT); + + while ((bsc_readl(dev, iic_enable) & BSC_IIC_EN_INTRP_MASK)) { + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + cpu_relax(); + } + return 0; +} + +/* i2c xfer completion function, handles both irq and polling mode */ +static int brcmstb_i2c_wait_for_completion(struct brcmstb_i2c_dev *dev) +{ + int ret = 0; + unsigned long timeout = msecs_to_jiffies(I2C_TIMEOUT); + + if (dev->irq >= 0) { + if (!wait_for_completion_timeout(&dev->done, timeout)) + ret = -ETIMEDOUT; + } else { + /* we are in polling mode */ + u32 bsc_intrp; + unsigned long time_left = jiffies + timeout; + + do { + bsc_intrp = bsc_readl(dev, iic_enable) & + BSC_IIC_EN_INTRP_MASK; + if (time_after(jiffies, time_left)) { + ret = -ETIMEDOUT; + break; + } + cpu_relax(); + } while (!bsc_intrp); + } + + if (dev->irq < 0 || ret == -ETIMEDOUT) + brcmstb_i2c_enable_disable_irq(dev, INT_DISABLE); + + return ret; +} + +/* Set xfer START/STOP conditions for subsequent transfer */ +static void brcmstb_set_i2c_start_stop(struct brcmstb_i2c_dev *dev, + u32 cond_flag) +{ + u32 regval = dev->bsc_regmap->iic_enable; + + dev->bsc_regmap->iic_enable = (regval & ~COND_START_STOP) | cond_flag; +} + +/* Send I2C request check completion */ +static int brcmstb_send_i2c_cmd(struct brcmstb_i2c_dev *dev, + enum bsc_xfer_cmd cmd) +{ + int rc = 0; + struct bsc_regs *pi2creg = dev->bsc_regmap; + + /* Make sure the hardware is ready */ + rc = brcmstb_i2c_wait_if_busy(dev); + if (rc < 0) + return rc; + + /* only if we are in interrupt mode */ + if (dev->irq >= 0) + reinit_completion(&dev->done); + + /* enable BSC CTL interrupt line */ + brcmstb_i2c_enable_disable_irq(dev, INT_ENABLE); + + /* initiate transfer by setting iic_enable */ + pi2creg->iic_enable |= BSC_IIC_EN_ENABLE_MASK; + bsc_writel(dev, pi2creg->iic_enable, iic_enable); + + /* Wait for transaction to finish or timeout */ + rc = brcmstb_i2c_wait_for_completion(dev); + if (rc) { + dev_dbg(dev->device, "intr timeout for cmd %s\n", + cmd_string[cmd]); + goto cmd_out; + } + + if ((CMD_RD || CMD_WR) && + bsc_readl(dev, iic_enable) & BSC_IIC_EN_NOACK_MASK) { + rc = -EREMOTEIO; + dev_dbg(dev->device, "controller received NOACK intr for %s\n", + cmd_string[cmd]); + } + +cmd_out: + bsc_writel(dev, 0, cnt_reg); + bsc_writel(dev, 0, iic_enable); + + return rc; +} + +/* Actual data transfer through the BSC master */ +static int brcmstb_i2c_xfer_bsc_data(struct brcmstb_i2c_dev *dev, + u8 *buf, unsigned int len, + struct i2c_msg *pmsg) +{ + int cnt, byte, rc; + enum bsc_xfer_cmd cmd; + u32 ctl_reg; + struct bsc_regs *pi2creg = dev->bsc_regmap; + int no_ack = pmsg->flags & I2C_M_IGNORE_NAK; + + /* see if the transaction needs to check NACK conditions */ + if (no_ack || len <= N_DATA_BYTES) { + cmd = (pmsg->flags & I2C_M_RD) ? CMD_RD_NOACK + : CMD_WR_NOACK; + pi2creg->ctlhi_reg |= BSC_CTLHI_REG_IGNORE_ACK_MASK; + } else { + cmd = (pmsg->flags & I2C_M_RD) ? CMD_RD : CMD_WR; + pi2creg->ctlhi_reg &= ~BSC_CTLHI_REG_IGNORE_ACK_MASK; + } + bsc_writel(dev, pi2creg->ctlhi_reg, ctlhi_reg); + + /* set data transfer direction */ + ctl_reg = pi2creg->ctl_reg & ~BSC_CTL_REG_DTF_MASK; + if (cmd == CMD_WR || cmd == CMD_WR_NOACK) + pi2creg->ctl_reg = ctl_reg | DTF_WR_MASK; + else + pi2creg->ctl_reg = ctl_reg | DTF_RD_MASK; + + /* set the read/write length */ + bsc_writel(dev, BSC_CNT_REG1_MASK & (len << BSC_CNT_REG1_SHIFT), + cnt_reg); + + /* Write data into data_in register */ + if (cmd == CMD_WR || cmd == CMD_WR_NOACK) { + for (cnt = 0; cnt < len; cnt += 4) { + u32 word = 0; + + for (byte = 0; byte < 4; byte++) { + word >>= 8; + if ((cnt + byte) < len) + word |= buf[cnt + byte] << 24; + } + bsc_writel(dev, word, data_in[cnt >> 2]); + } + } + + /* Initiate xfer, the function will return on completion */ + rc = brcmstb_send_i2c_cmd(dev, cmd); + + if (rc != 0) { + dev_dbg(dev->device, "%s failure", cmd_string[cmd]); + return rc; + } + + if (cmd == CMD_RD || cmd == CMD_RD_NOACK) { + for (cnt = 0; cnt < len; cnt += 4) { + u32 data = bsc_readl(dev, data_out[cnt >> 2]); + + for (byte = 0; byte < 4 && + (byte + cnt) < len; byte++) { + buf[cnt + byte] = data & 0xff; + data >>= 8; + } + } + } + + return 0; +} + +/* Write a single byte of data to the i2c bus */ +static int brcmstb_i2c_write_data_byte(struct brcmstb_i2c_dev *dev, + u8 *buf, unsigned int nak_expected) +{ + enum bsc_xfer_cmd cmd = nak_expected ? CMD_WR : CMD_WR_NOACK; + + bsc_writel(dev, 1, cnt_reg); + bsc_writel(dev, *buf, data_in); + + return brcmstb_send_i2c_cmd(dev, cmd); +} + +/* Send i2c address */ +static int brcmstb_i2c_do_addr(struct brcmstb_i2c_dev *dev, + struct i2c_msg *msg) +{ + unsigned char addr; + + if (msg->flags & I2C_M_TEN) { + /* First byte is 11110XX0 where XX is upper 2 bits */ + addr = 0xF0 | ((msg->addr & 0x300) >> 7); + bsc_writel(dev, addr, chip_address); + + /* Second byte is the remaining 8 bits */ + addr = msg->addr & 0xFF; + if (brcmstb_i2c_write_data_byte(dev, &addr, 0) < 0) + return -EREMOTEIO; + + if (msg->flags & I2C_M_RD) { + /* For read, send restart without stop condition */ + brcmstb_set_i2c_start_stop(dev, COND_RESTART + | COND_NOSTOP); + /* Then re-send the first byte with the read bit set */ + addr = 0xF0 | ((msg->addr & 0x300) >> 7) | 0x01; + if (brcmstb_i2c_write_data_byte(dev, &addr, 0) < 0) + return -EREMOTEIO; + + } + } else { + addr = msg->addr << 1; + if (msg->flags & I2C_M_RD) + addr |= 1; + + bsc_writel(dev, addr, chip_address); + } + + return 0; +} + +/* Master transfer function */ +static int brcmstb_i2c_xfer(struct i2c_adapter *adapter, + struct i2c_msg msgs[], int num) +{ + struct brcmstb_i2c_dev *dev = i2c_get_adapdata(adapter); + struct i2c_msg *pmsg; + int rc = 0; + int i; + int bytes_to_xfer; + u8 *tmp_buf; + int len = 0; + + if (dev->is_suspended) + return -EBUSY; + + /* Loop through all messages */ + for (i = 0; i < num; i++) { + pmsg = &msgs[i]; + len = pmsg->len; + tmp_buf = pmsg->buf; + + dev_dbg(dev->device, + "msg# %d/%d flg %x buf %x len %d\n", i, + num - 1, pmsg->flags, + pmsg->buf ? pmsg->buf[0] : '0', pmsg->len); + + if (i < (num - 1) && (msgs[i + 1].flags & I2C_M_NOSTART)) + brcmstb_set_i2c_start_stop(dev, ~(COND_START_STOP)); + else + brcmstb_set_i2c_start_stop(dev, + COND_RESTART | COND_NOSTOP); + + /* Send slave address */ + if (!(pmsg->flags & I2C_M_NOSTART)) { + rc = brcmstb_i2c_do_addr(dev, pmsg); + if (rc < 0) { + dev_dbg(dev->device, + "NACK for addr %2.2x msg#%d rc = %d\n", + pmsg->addr, i, rc); + goto out; + } + } + + /* Perform data transfer */ + while (len) { + bytes_to_xfer = min(len, N_DATA_BYTES); + + if (len <= N_DATA_BYTES && i == (num - 1)) + brcmstb_set_i2c_start_stop(dev, + ~(COND_START_STOP)); + + rc = brcmstb_i2c_xfer_bsc_data(dev, tmp_buf, + bytes_to_xfer, pmsg); + if (rc < 0) + goto out; + + len -= bytes_to_xfer; + tmp_buf += bytes_to_xfer; + } + } + + rc = num; +out: + return rc; + +} + +static u32 brcmstb_i2c_functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_10BIT_ADDR + | I2C_FUNC_NOSTART | I2C_FUNC_PROTOCOL_MANGLING; +} + +static const struct i2c_algorithm brcmstb_i2c_algo = { + .master_xfer = brcmstb_i2c_xfer, + .functionality = brcmstb_i2c_functionality, +}; + +static void brcmstb_i2c_set_bus_speed(struct brcmstb_i2c_dev *dev) +{ + int i = 0, num_speeds = ARRAY_SIZE(bsc_clk); + u32 clk_freq_hz = dev->clk_freq_hz; + + for (i = 0; i < num_speeds; i++) { + if (bsc_clk[i].hz == clk_freq_hz) { + dev->bsc_regmap->ctl_reg &= ~(BSC_CTL_REG_SCL_SEL_MASK + | BSC_CTL_REG_DIV_CLK_MASK); + dev->bsc_regmap->ctl_reg |= (bsc_clk[i].scl_mask | + bsc_clk[i].div_mask); + bsc_writel(dev, dev->bsc_regmap->ctl_reg, ctl_reg); + break; + } + } + + /* in case we did not get find a valid speed */ + if (i == num_speeds) { + i = (bsc_readl(dev, ctl_reg) & BSC_CTL_REG_SCL_SEL_MASK) >> + BSC_CTL_REG_SCL_SEL_SHIFT; + dev_warn(dev->device, "leaving current clock-frequency @ %dHz\n", + bsc_clk[i].hz); + } +} + +static void brcmstb_i2c_set_bsc_reg_defaults(struct brcmstb_i2c_dev *dev) +{ + /* 4 byte data register */ + dev->bsc_regmap->ctlhi_reg = BSC_CTLHI_REG_DATAREG_SIZE_MASK; + bsc_writel(dev, dev->bsc_regmap->ctlhi_reg, ctlhi_reg); + /* set bus speed */ + brcmstb_i2c_set_bus_speed(dev); +} + +static int brcmstb_i2c_probe(struct platform_device *pdev) +{ + int rc = 0; + struct brcmstb_i2c_dev *dev; + struct i2c_adapter *adap; + struct resource *iomem; + const char *int_name; + + /* Allocate memory for private data structure */ + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->bsc_regmap = devm_kzalloc(&pdev->dev, sizeof(struct bsc_regs *), + GFP_KERNEL); + if (!dev->bsc_regmap) + return -ENOMEM; + + platform_set_drvdata(pdev, dev); + dev->device = &pdev->dev; + init_completion(&dev->done); + + /* Map hardware registers */ + iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dev->base = devm_ioremap_resource(dev->device, iomem); + if (IS_ERR(dev->base)) { + rc = -ENOMEM; + goto probe_errorout; + } + + rc = of_property_read_string(dev->device->of_node, "interrupt-names", + &int_name); + if (rc < 0) + int_name = NULL; + + /* Get the interrupt number */ + dev->irq = platform_get_irq(pdev, 0); + + /* disable the bsc interrupt line */ + brcmstb_i2c_enable_disable_irq(dev, INT_DISABLE); + + /* register the ISR handler */ + rc = devm_request_irq(&pdev->dev, dev->irq, brcmstb_i2c_isr, + IRQF_SHARED, + int_name ? int_name : pdev->name, + dev); + + if (rc) { + dev_dbg(dev->device, "falling back to polling mode"); + dev->irq = -1; + } + + if (of_property_read_u32(dev->device->of_node, + "clock-frequency", &dev->clk_freq_hz)) { + dev_warn(dev->device, "setting clock-frequency@%dHz\n", + bsc_clk[0].hz); + dev->clk_freq_hz = bsc_clk[0].hz; + } + + brcmstb_i2c_set_bsc_reg_defaults(dev); + + /* Add the i2c adapter */ + adap = &dev->adapter; + i2c_set_adapdata(adap, dev); + adap->owner = THIS_MODULE; + strlcpy(adap->name, "Broadcom STB : ", sizeof(adap->name)); + if (int_name) + strlcat(adap->name, int_name, sizeof(adap->name)); + adap->algo = &brcmstb_i2c_algo; + adap->dev.parent = &pdev->dev; + adap->dev.of_node = pdev->dev.of_node; + rc = i2c_add_adapter(adap); + if (rc) { + dev_err(dev->device, "failed to add adapter\n"); + goto probe_errorout; + } + + dev_info(dev->device, "%s@%dhz registered in %s mode\n", + int_name ? int_name : " ", dev->clk_freq_hz, + (dev->irq >= 0) ? "interrupt" : "polling"); + + return 0; + +probe_errorout: + return rc; +} + +static int brcmstb_i2c_remove(struct platform_device *pdev) +{ + struct brcmstb_i2c_dev *dev = platform_get_drvdata(pdev); + + i2c_del_adapter(&dev->adapter); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int brcmstb_i2c_suspend(struct device *dev) +{ + struct brcmstb_i2c_dev *i2c_dev = dev_get_drvdata(dev); + + i2c_lock_adapter(&i2c_dev->adapter); + i2c_dev->is_suspended = true; + i2c_unlock_adapter(&i2c_dev->adapter); + + return 0; +} + +static int brcmstb_i2c_resume(struct device *dev) +{ + struct brcmstb_i2c_dev *i2c_dev = dev_get_drvdata(dev); + + i2c_lock_adapter(&i2c_dev->adapter); + brcmstb_i2c_set_bsc_reg_defaults(i2c_dev); + i2c_dev->is_suspended = false; + i2c_unlock_adapter(&i2c_dev->adapter); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(brcmstb_i2c_pm, brcmstb_i2c_suspend, + brcmstb_i2c_resume); + +static const struct of_device_id brcmstb_i2c_of_match[] = { + {.compatible = "brcm,brcmstb-i2c"}, + {}, +}; +MODULE_DEVICE_TABLE(of, brcmstb_i2c_of_match); + +static struct platform_driver brcmstb_i2c_driver = { + .driver = { + .name = "brcmstb-i2c", + .of_match_table = brcmstb_i2c_of_match, + .pm = &brcmstb_i2c_pm, + }, + .probe = brcmstb_i2c_probe, + .remove = brcmstb_i2c_remove, +}; +module_platform_driver(brcmstb_i2c_driver); + +MODULE_AUTHOR("Kamal Dasu "); +MODULE_DESCRIPTION("Broadcom Settop I2C Driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3