From 39613cda7c99a09083aa2cbfca22429c4d71a51e Mon Sep 17 00:00:00 2001 From: Ajay Kishore Date: Wed, 29 Jun 2016 20:03:10 +0530 Subject: [PATCH] qca: i2c: Enable i2c qup driver These files are copied from: https://source.codeaurora.org/quic/qsdk/oss/boot/uboot-1.0/tree/drivers/i2c/ipq40xx_i2c.c https://source.codeaurora.org/quic/qsdk/oss/boot/uboot-1.0/tree/drivers/i2c/ipq40xx_i2c.h https://source.codeaurora.org/quic/qsdk/oss/boot/uboot-1.0/tree/arch/arm/cpu/armv7/qca/clock.c https://source.codeaurora.org/quic/qsdk/oss/boot/uboot-1.0/tree/arch/arm/include/asm/arch-qcom-common/clk.h Change-Id: If80f05b01ee168d2bfde454b9534f9a1db559b12 Signed-off-by: Ajay Kishore --- board/qca/common/clk.h | 100 +++++++ board/qca/common/clock.c | 207 ++++++++++++++ drivers/i2c/qup_i2c.c | 605 +++++++++++++++++++++++++++++++++++++++ drivers/i2c/qup_i2c.h | 121 ++++++++ 4 files changed, 1033 insertions(+) create mode 100644 board/qca/common/clk.h create mode 100644 board/qca/common/clock.c create mode 100644 drivers/i2c/qup_i2c.c create mode 100644 drivers/i2c/qup_i2c.h diff --git a/board/qca/common/clk.h b/board/qca/common/clk.h new file mode 100644 index 0000000000..f2169f58df --- /dev/null +++ b/board/qca/common/clk.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef QCA_CLK_H +#define QCA_CLK_H + +#define MMC_IDENTIFY_MODE 0 +#define MMC_DATA_TRANSFER_MODE 1 + +#define GCC_BLSP1_UART2_APPS_CFG_RCGR 0x01803038 +#define GCC_BLSP1_UART2_APPS_M 0x0180303C +#define GCC_BLSP1_UART2_APPS_N 0x01803040 +#define GCC_BLSP1_UART2_APPS_D 0x01803044 +#define GCC_BLSP1_UART2_APPS_CMD_RCGR 0x01803034 +#define GCC_BLSP1_UART2_APPS_CBCR 0x0180302C + +#define GCC_UART_CFG_RCGR_MODE_MASK 0x3000 +#define GCC_UART_CFG_RCGR_SRCSEL_MASK 0x0700 +#define GCC_UART_CFG_RCGR_SRCDIV_MASK 0x001F + +#define GCC_UART_CFG_RCGR_MODE_SHIFT 12 +#define GCC_UART_CFG_RCGR_SRCSEL_SHIFT 8 +#define GCC_UART_CFG_RCGR_SRCDIV_SHIFT 0 + +#define UART2_RCGR_SRC_SEL 0x0 +#define UART2_RCGR_SRC_DIV 0x0 +#define UART2_RCGR_MODE 0x2 +#define UART2_CMD_RCGR_UPDATE 0x1 +#define UART2_CBCR_CLK_ENABLE 0x1 + +#define NOT_2D(two_d) (~two_d) +#define NOT_N_MINUS_M(n,m) (~(n - m)) +#define CLOCK_UPDATE_TIMEOUT_US 1000 + +#ifdef CONFIG_IPQ40XX_I2C +#define GCC_BLSP1_QUP1_I2C_APPS_CFG_RCGR 0x01802010 + +#define GCC_BLSP1_QUP1_I2C_APPS_M 0x0180303C +#define GCC_BLSP1_QUP1_I2C_APPS_N 0x01803040 +#define GCC_BLSP1_QUP1_I2C_APPS_D 0x01803044 +#define GCC_BLSP1_QUP1_I2C_APPS_CMD_RCGR 0x0180200C +#define GCC_BLSP1_QUP1_I2C_APPS_CBCR 0x01802008 + +#define GCC_I2C_CFG_RCGR_SRCSEL_MASK 0x0700 +#define GCC_I2C_CFG_RCGR_SRCDIV_MASK 0x001F + +#define GCC_I2C_CFG_RCGR_SRCSEL_SHIFT 8 +#define GCC_I2C_CFG_RCGR_SRCDIV_SHIFT 0 + +#define I2C0_RCGR_SRC_SEL 1 +#define I2C0_RCGR_SRC_DIV 20 +#define I2C0_CMD_RCGR_UPDATE 0x1 +#define I2C0_CBCR_CLK_ENABLE 0x1 +#define NOT_2D(two_d) (~two_d) +#define NOT_N_MINUS_M(n,m) (~(n - m)) +#define CLOCK_UPDATE_TIMEOUT_US 1000 +#endif + +#define GCC_PCIE_SLEEP_CBCR 0x0181D014 +#define GCC_PCIE_AXI_M_CBCR 0x0181D004 +#define GCC_PCIE_AXI_S_CBCR 0x0181D008 +#define GCC_PCIE_AHB_CBCR 0x0181D00C +#define PCIE_TIMEOUT_CNT 100 +#define ENABLE 0x1 +#define DISABLE 0x0 +#define BIT(s) (1< +#include +#include +#include +#include + +#define GCC_SDCC1_MISC 0x1818014 +#define GCC_SDCC1_APPS_CBCR 0x181800C +#define GCC_SDCC1_APPS_RCGR 0x1818008 +#define GCC_SDCC1_APPS_CMD_RCGR 0x1818004 + +void emmc_clock_config(int mode) +{ + /* Select SDCC clock source as DDR_PLL_SDCC1_CLK 192MHz */ + writel(0x100, GCC_SDCC1_APPS_RCGR); + /* Update APPS_CMD_RCGR to reflect source selection */ + writel(0x1, GCC_SDCC1_APPS_CMD_RCGR); + udelay(10); + + if (mode == MMC_IDENTIFY_MODE) { + /* Set root clock generator to bypass mode */ + writel(0x0, GCC_SDCC1_APPS_CBCR); + udelay(10); + /* Choose divider for 400KHz */ + writel(0x1e4 , GCC_SDCC1_MISC); + /* Enable root clock generator */ + writel(0x1, GCC_SDCC1_APPS_CBCR); + udelay(10); + } + if (mode == MMC_DATA_TRANSFER_MODE) { + /* Set root clock generator to bypass mode */ + writel(0x0, GCC_SDCC1_APPS_CBCR); + udelay(10); + /* Choose divider for 48MHz */ + writel(0x3, GCC_SDCC1_MISC); + /* Enable root clock generator */ + writel(0x1, GCC_SDCC1_APPS_CBCR); + udelay(10); + } +} +void emmc_clock_disable(void) +{ + /* Clear divider */ + writel(0x0, GCC_SDCC1_MISC); + +} + +void uart2_configure_mux(void) +{ + unsigned long cfg_rcgr; + + cfg_rcgr = readl(GCC_BLSP1_UART2_APPS_CFG_RCGR); + /* Clear mode, src sel, src div */ + cfg_rcgr &= ~(GCC_UART_CFG_RCGR_MODE_MASK | + GCC_UART_CFG_RCGR_SRCSEL_MASK | + GCC_UART_CFG_RCGR_SRCDIV_MASK); + + cfg_rcgr |= ((UART2_RCGR_SRC_SEL << GCC_UART_CFG_RCGR_SRCSEL_SHIFT) + & GCC_UART_CFG_RCGR_SRCSEL_MASK); + + cfg_rcgr |= ((UART2_RCGR_SRC_DIV << GCC_UART_CFG_RCGR_SRCDIV_SHIFT) + & GCC_UART_CFG_RCGR_SRCDIV_MASK); + + cfg_rcgr |= ((UART2_RCGR_MODE << GCC_UART_CFG_RCGR_MODE_SHIFT) + & GCC_UART_CFG_RCGR_MODE_MASK); + + writel(cfg_rcgr, GCC_BLSP1_UART2_APPS_CFG_RCGR); +} + +void uart2_set_rate_mnd(unsigned int m, + unsigned int n, unsigned int two_d) +{ + writel(m, GCC_BLSP1_UART2_APPS_M); + writel(NOT_N_MINUS_M(n, m), GCC_BLSP1_UART2_APPS_N); + writel(NOT_2D(two_d), GCC_BLSP1_UART2_APPS_D); +} + +int uart2_trigger_update(void) +{ + unsigned long cmd_rcgr; + int timeout = 0; + + cmd_rcgr = readl(GCC_BLSP1_UART2_APPS_CMD_RCGR); + cmd_rcgr |= UART2_CMD_RCGR_UPDATE; + writel(cmd_rcgr, GCC_BLSP1_UART2_APPS_CMD_RCGR); + + while (readl(GCC_BLSP1_UART2_APPS_CMD_RCGR) & UART2_CMD_RCGR_UPDATE) { + if (timeout++ >= CLOCK_UPDATE_TIMEOUT_US) { + printf("Timeout waiting for UART2 clock update\n"); + return -ETIMEDOUT; + } + udelay(1); + } + cmd_rcgr = readl(GCC_BLSP1_UART2_APPS_CMD_RCGR); + return 0; +} + +void uart2_toggle_clock(void) +{ + unsigned long cbcr_val; + + cbcr_val = readl(GCC_BLSP1_UART2_APPS_CBCR); + cbcr_val |= UART2_CBCR_CLK_ENABLE; + writel(cbcr_val, GCC_BLSP1_UART2_APPS_CBCR); +} + +void uart2_clock_config(unsigned int m, + unsigned int n, unsigned int two_d) +{ + uart2_configure_mux(); + uart2_set_rate_mnd(m, n, two_d); + uart2_trigger_update(); + uart2_toggle_clock(); +} + +#ifdef CONFIG_IPQ40XX_I2C +void i2c0_configure_mux(void) +{ + unsigned long cfg_rcgr; + + cfg_rcgr = readl(GCC_BLSP1_QUP1_I2C_APPS_CFG_RCGR); + /* Clear mode, src sel, src div */ + cfg_rcgr &= ~(GCC_I2C_CFG_RCGR_SRCSEL_MASK | + GCC_I2C_CFG_RCGR_SRCDIV_MASK); + + cfg_rcgr |= ((I2C0_RCGR_SRC_SEL << GCC_I2C_CFG_RCGR_SRCSEL_SHIFT) + & GCC_UART_CFG_RCGR_SRCSEL_MASK); + + cfg_rcgr |= ((I2C0_RCGR_SRC_DIV << GCC_I2C_CFG_RCGR_SRCDIV_SHIFT) + & GCC_UART_CFG_RCGR_SRCDIV_MASK); + + writel(cfg_rcgr, GCC_BLSP1_QUP1_I2C_APPS_CFG_RCGR); +} + +int i2c0_trigger_update(void) +{ + unsigned long cmd_rcgr; + int timeout = 0; + + cmd_rcgr = readl(GCC_BLSP1_QUP1_I2C_APPS_CMD_RCGR); + cmd_rcgr |= I2C0_CMD_RCGR_UPDATE; + writel(cmd_rcgr, GCC_BLSP1_QUP1_I2C_APPS_CMD_RCGR); + + while (readl(GCC_BLSP1_QUP1_I2C_APPS_CMD_RCGR) & I2C0_CMD_RCGR_UPDATE) { + if (timeout++ >= CLOCK_UPDATE_TIMEOUT_US) { + printf("Timeout waiting for I2C0 clock update\n"); + return -ETIMEDOUT; + } + udelay(1); + } + cmd_rcgr = readl(GCC_BLSP1_QUP1_I2C_APPS_CMD_RCGR); + return 0; +} + +void i2c0_toggle_clock(void) +{ + unsigned long cbcr_val; + + cbcr_val = readl(GCC_BLSP1_QUP1_I2C_APPS_CBCR); + cbcr_val |= I2C0_CBCR_CLK_ENABLE; + writel(cbcr_val, GCC_BLSP1_QUP1_I2C_APPS_CBCR); +} + +void i2c_clock_config(void) +{ + i2c0_configure_mux(); + i2c0_trigger_update(); + i2c0_toggle_clock(); +} +#endif + +int pcie_clock_enable(int clk_addr) +{ + unsigned int count = PCIE_TIMEOUT_CNT; + int state, val; + + writel(ENABLE, clk_addr); + do { + val = readl(clk_addr); + count--; + if (count == 0) { + printf("Timeout waiting for %d enable \n", clk_addr); + return -ETIMEDOUT; + } + state = (val & BIT(31)); + udelay(10); + } while (state); + return 0; +} + +void pcie_clock_disable(int clk_addr) +{ + writel(0, clk_addr); +} \ No newline at end of file diff --git a/drivers/i2c/qup_i2c.c b/drivers/i2c/qup_i2c.c new file mode 100644 index 0000000000..184bb2a5a8 --- /dev/null +++ b/drivers/i2c/qup_i2c.c @@ -0,0 +1,605 @@ +/* + * Copyright (c) 2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include "ipq40xx_i2c.h" +#include +#include +#include +#include "ipq40xx_cdp.h" + +static int i2c_base_addr; +static int i2c_hw_initialized; +static int i2c_board_initialized; + +/* + * Reset entire QUP and all mini cores + */ +static void i2c_reset(void) +{ + writel(0x1, (i2c_base_addr + QUP_SW_RESET_OFFSET)); + udelay(5); +} + +static int check_bit_state(uint32_t reg_addr, int bit_num, int val, + int us_delay) +{ + unsigned int count = TIMEOUT_CNT; + unsigned int bit_val = ((readl(reg_addr) >> bit_num) & 0x01); + + while (bit_val != val) { + count--; + if (count == 0) { + return -ETIMEDOUT; + } + udelay(us_delay); + bit_val = ((readl(reg_addr) >> bit_num) & 0x01); + } + + return SUCCESS; +} + +/* + * Check whether QUP State is valid + */ +static int check_qup_state_valid(void) +{ + return check_bit_state(i2c_base_addr + QUP_STATE_OFFSET, + QUP_STATE_VALID_BIT, + QUP_STATE_VALID, 1); +} + +/* + * Configure QUP Core state + */ +static int config_i2c_state(unsigned int state) +{ + uint32_t val; + int ret = SUCCESS; + + ret = check_qup_state_valid(); + if (ret != SUCCESS) + return ret; + + /* Set the state */ + val = readl(i2c_base_addr + QUP_STATE_OFFSET); + val = ((val & ~QUP_STATE_MASK) | state); + writel(val, (i2c_base_addr + QUP_STATE_OFFSET)); + ret = check_qup_state_valid(); + + return ret; +} + +/* + * Configure I2C IO Mode. + */ +void config_i2c_mode(void) +{ + int cfg; + + cfg = readl(i2c_base_addr + QUP_IO_MODES_OFFSET); + cfg |= (INPUT_FIFO_MODE | OUTPUT_FIFO_MODE | PACK_EN | UNPACK_EN); + writel(cfg, i2c_base_addr + QUP_IO_MODES_OFFSET); +} + +void i2c_qca_board_init(void) +{ + i2c_base_addr = gboard_param->i2c_cfg->i2c_base; + qca_configure_gpio(gboard_param->i2c_cfg->i2c_gpio, NO_OF_I2C_GPIOS); + + /* Configure the I2C clock */ + i2c_clock_config(); + + i2c_hw_initialized = 0; + i2c_board_initialized = 1; +} + +void i2c_qup_mini_core_init(void) +{ + int cfg; + + cfg = readl(i2c_base_addr + QUP_CONFIG_OFFSET); + cfg |= (QUP_CONFIG_MINI_CORE_I2C) | (I2C_BIT_WORD); + + writel(cfg, i2c_base_addr + QUP_CONFIG_OFFSET); + + writel(QUP_EN_VERSION_TWO_TAG, (i2c_base_addr + + QUP_I2C_MASTER_CONFIG_OFFSET)); +} + +/* + * QUP I2C Hardware Initialisation + */ +static int i2c_hw_init(void) +{ + int ret; + + /* QUP configuration */ + i2c_reset(); + + /* Set the BLSP QUP state */ + ret = check_qup_state_valid(); + if (ret) + return ret; + + writel(0,(i2c_base_addr + QUP_CONFIG_OFFSET)); + + writel(QUP_APP_CLK_ON_EN | QUP_CORE_CLK_ON_EN | + QUP_FIFO_CLK_GATE_EN, (i2c_base_addr + QUP_CONFIG_OFFSET)); + + writel(0, i2c_base_addr + QUP_I2C_MASTER_CLK_CTL_OFFSET); + writel(0, i2c_base_addr + QUP_TEST_CTRL_OFFSET); + writel(0, i2c_base_addr + QUP_IO_MODES_OFFSET); + writel(0, i2c_base_addr + QUP_OPERATIONAL_MASK_OFFSET); + + i2c_qup_mini_core_init(); + + config_i2c_mode(); + + writel(QUP_MX_READ_COUNT, i2c_base_addr + QUP_MX_READ_COUNT_OFFSET); + writel(QUP_MX_WRITE_COUNT, i2c_base_addr + QUP_MX_WRITE_COUNT_OFFSET); + + + writel(QUP_MX_INPUT_COUNT, i2c_base_addr + QUP_MX_INPUT_COUNT_OFFSET); + writel(QUP_MX_OUTPUT_COUNT, i2c_base_addr + QUP_MX_OUTPUT_COUNT_OFFSET); + + ret = config_i2c_state(QUP_STATE_RESET); + if (ret) + return ret; + + i2c_hw_initialized = 1; + + return SUCCESS; +} + +/* + * Function to check wheather Input or Output FIFO + * has data to be serviced. For invalid slaves, this + * flag will not be set. + */ +static int check_fifo_status(uint dir) +{ + unsigned int count = TIMEOUT_CNT; + unsigned int status_flag; + unsigned int val; + + if (dir == READ) { + do { + val = readl(i2c_base_addr + + QUP_OPERATIONAL_OFFSET); + count--; + if (count == 0) + return -ETIMEDOUT; + status_flag = val & INPUT_SERVICE_FLAG; + udelay(10); + } while (!status_flag); + + } else if (dir == WRITE) { + do { + val = readl(i2c_base_addr + + QUP_OPERATIONAL_OFFSET); + count--; + if (count == 0) + return -ETIMEDOUT; + status_flag = val & OUTPUT_FIFO_FULL; + udelay(10); + } while (status_flag); + } + return SUCCESS; +} + +/* + * Check whether the values in the OUTPUT FIFO are shifted out. + */ +static int check_write_done(void) +{ + unsigned int count = TIMEOUT_CNT; + unsigned int status_flag; + unsigned int val; + + do { + val = readl(i2c_base_addr + + QUP_OPERATIONAL_OFFSET); + count--; + if (count == 0) + return -ETIMEDOUT; + status_flag = val & OUTPUT_FIFO_NOT_EMPTY; + udelay(10); + } while (status_flag); + + return SUCCESS; +} + +int i2c_process_read_data(uint32_t data, uchar *buffer, int len) +{ + int idx = 0; + uint8_t data_8 = 0; + int index = 0; + int rd_len = len; + + while (index < 4 && rd_len) { + data_8 = QUP_I2C_DATA(data); + index++; + if (data_8 == QUP_I2C_DATA_READ_AND_STOP_SEQ) { + index++; + data = (data >> 16); + continue; + } + if (data_8 == QUP_I2C_STOP_SEQ) + break; + if (data_8 == QUP_I2C_NOP_PADDING) { + data = (data >> 8); + continue; + } + buffer[idx] = data_8; + rd_len--; + idx++; + data = (data >> 8); + } + return idx; +} + +uint32_t i2c_write_read_offset(uchar chip, int alen) +{ + uint32_t tag; + uint32_t *fifo; + + fifo = (uint32_t *) (i2c_base_addr + QUP_OUTPUT_FIFO_OFFSET); + tag = QUP_I2C_START_SEQ; + tag |= ((QUP_I2C_ADDR(chip)) | (I2C_WRITE)) << 8; + tag |= QUP_I2C_DATA_WRITE_SEQ << 16; + tag |= alen << 24; + writel(tag, fifo); + + return tag; +} + +uint32_t i2c_write_read_tag(uchar chip, uint addr, int alen, int data_len) +{ + uint32_t tag = 0; + uint32_t *fifo; + + fifo = (uint32_t *) (i2c_base_addr + QUP_OUTPUT_FIFO_OFFSET); + + if (alen == 2) { + /* based on the slave send msb 8 bits or lsb 8 bits first */ + tag = QUP_I2C_DATA(addr); + tag |= QUP_I2C_DATA(addr >> 8) << 8; + tag |= QUP_I2C_START_SEQ << 16; + tag |= ((QUP_I2C_ADDR(chip)) | (I2C_READ)) << 24; + writel(tag, fifo); + + tag = 0; + tag |= QUP_I2C_DATA_READ_AND_STOP_SEQ; + tag |= data_len << 8; + writel(tag, fifo); + } else if (alen == 1) { + tag = QUP_I2C_DATA(addr); + tag |= QUP_I2C_START_SEQ << 8; + tag |= ((QUP_I2C_ADDR(chip)) | (I2C_READ)) << 16; + tag |= (QUP_I2C_DATA_READ_AND_STOP_SEQ << 24); + writel(tag, fifo); + + tag = 0; + tag |= data_len; + writel(tag, fifo); + } else if (alen == 0) { + tag |= QUP_I2C_START_SEQ; + tag |= ((QUP_I2C_ADDR(chip)) | (I2C_READ)) << 8; + tag |= (QUP_I2C_DATA_READ_AND_STOP_SEQ << 16); + tag |= data_len << 24; + writel(tag, fifo); + } + return 0; +} + +int i2c_read(uchar chip, uint addr, int alen, uchar *buffer, int len) +{ + int ret = 0; + int nack = 0; + uint32_t data = 0; + uint8_t data_len = len; + uint32_t *fifo; + int idx = 0; + int cfg; + + i2c_base_addr = gboard_param->i2c_cfg->i2c_base; + + config_i2c_state(QUP_STATE_RESET); + + if (!i2c_board_initialized) { + i2c_qca_board_init(); + } + + if (!i2c_hw_initialized) { + i2c_hw_init(); + } + + writel(0x3C, i2c_base_addr + QUP_ERROR_FLAGS_OFFSET); + writel(0x3C, i2c_base_addr + QUP_ERROR_FLAGS_EN_OFFSET); + writel(0, i2c_base_addr + QUP_I2C_MASTER_STATUS_OFFSET); + + if (alen != 0) + writel((OUT_FIFO_RD_TAG_BYTE_CNT + alen), + i2c_base_addr + QUP_MX_WRITE_COUNT_OFFSET); + else + writel(OUT_FIFO_WR_TAG_BYTE_CNT, + i2c_base_addr + QUP_MX_WRITE_COUNT_OFFSET); + + writel((IN_FIFO_TAG_BYTE_CNT + data_len), + i2c_base_addr + QUP_MX_READ_COUNT_OFFSET); + + /* Set to RUN state */ + ret = config_i2c_state(QUP_STATE_RUN); + if (ret != SUCCESS) { + debug("State run failed\n"); + goto out; + } + + /* Configure the I2C Master clock */ + cfg = (QUP_INPUT_CLK / (I2C_CLK_100KHZ * 2)) - 3; + writel(cfg, i2c_base_addr + QUP_I2C_MASTER_CLK_CTL_OFFSET); + /* Write to FIFO in Pause State */ + /* Set to PAUSE state */ + ret = config_i2c_state(QUP_STATE_PAUSE); + if (ret != SUCCESS) { + debug("State Pause failed\n"); + goto out; + } + + fifo = (uint32_t *) (i2c_base_addr + QUP_OUTPUT_FIFO_OFFSET); + + if (alen != 0) + data = i2c_write_read_offset(chip, alen); + + data = i2c_write_read_tag(chip, addr, alen, data_len); + + /* Set to RUN state */ + ret = config_i2c_state(QUP_STATE_RUN); + if (ret != SUCCESS) { + debug("State run failed\n"); + goto out; + } + + mdelay(2); + ret = check_write_done(); + if (ret != SUCCESS) { + debug("Write done failed\n"); + goto out; + } + + nack = readl(i2c_base_addr + QUP_I2C_MASTER_STATUS_OFFSET) & NACK_BIT_MASK; + nack = nack >> NACK_BIT_SHIFT; + if (nack == 1) { + debug("NACK RECVD\n"); + return -ENACK; + } + + if (readl(i2c_base_addr + QUP_OPERATIONAL_OFFSET) + & OUTPUT_SERVICE_FLAG) { + writel(OUTPUT_SERVICE_FLAG, + i2c_base_addr + QUP_OPERATIONAL_OFFSET); + } + + fifo = (uint32_t *)(i2c_base_addr + QUP_INPUT_FIFO_OFFSET); + + mdelay(2); + ret = check_fifo_status(READ); + if (ret != SUCCESS) { + debug("Read status failed\n"); + goto out; + } + while (len) { + /* Read the data from the FIFO */ + data = readl(fifo); + + ret = i2c_process_read_data(data, buffer + idx, len); + if (ret) { + idx += ret; + len -= ret; + } + } + + if (readl(i2c_base_addr + QUP_OPERATIONAL_OFFSET) + & INPUT_SERVICE_FLAG) { + writel(INPUT_SERVICE_FLAG, + i2c_base_addr + QUP_OPERATIONAL_OFFSET); + } + (void)config_i2c_state(QUP_STATE_RESET); + return SUCCESS; +out: + /* + * Put the I2C Core back in the Reset State to end the transfer. + */ + (void)config_i2c_state(QUP_STATE_RESET); + writel(QUP_MX_READ_COUNT, i2c_base_addr + QUP_MX_READ_COUNT_OFFSET); + return ret; +} + +int create_data_byte(uint16_t *data, uchar *buffer, int len) +{ + int idx = 0; + if (len == 0) { + return 0; + } else { + *data = QUP_I2C_DATA(buffer[idx]); + idx++; + len--; + } + if (len == 0) { + return idx; + } else { + *data = (*data << 8); + *data |= QUP_I2C_DATA(buffer[idx]); + idx++; + len--; + } + return idx; +} + +uint32_t i2c_frame_wr_tag(uchar chip, uint8_t data_len, int alen) +{ + uint32_t tag; + + tag = QUP_I2C_START_SEQ; + tag |= (((QUP_I2C_ADDR(chip)) | (I2C_WRITE)) << 8); + tag |= (QUP_I2C_DATA_WRITE_AND_STOP_SEQ << 16); + tag |= (data_len + alen) << 24; + return tag; +} + +int i2c_write(uchar chip, uint addr, int alen, uchar *buffer, int len) +{ + int ret = 0; + int nack = 0; + int idx = 0; + int first = 1; + uint32_t data = 0; + uint16_t data_lsb_16 = 0; + uint16_t data_msb_16 = 0; + uint8_t data_len = len; + uint32_t *fifo; + uint32_t cfg; + + i2c_base_addr = gboard_param->i2c_cfg->i2c_base; + + /* Set to Reset State */ + ret = config_i2c_state(QUP_STATE_RESET); + + if (!i2c_board_initialized) { + i2c_qca_board_init(); + } + + if (!i2c_hw_initialized) { + i2c_hw_init(); + } + + writel(0x3C, i2c_base_addr + QUP_ERROR_FLAGS_OFFSET); + writel(0x3C, i2c_base_addr + QUP_ERROR_FLAGS_EN_OFFSET); + writel(0, i2c_base_addr + QUP_I2C_MASTER_STATUS_OFFSET); + + writel((OUT_FIFO_WR_TAG_BYTE_CNT + len + alen), + i2c_base_addr + QUP_MX_WRITE_COUNT_OFFSET); + + + /* Set to RUN state */ + ret = config_i2c_state(QUP_STATE_RUN); + if (ret != SUCCESS) { + debug("State run failed\n"); + goto out; + } + + /* Configure the I2C Master clock */ + cfg = (QUP_INPUT_CLK / (I2C_CLK_100KHZ * 2)) - 3; + writel(cfg, i2c_base_addr + QUP_I2C_MASTER_CLK_CTL_OFFSET); + + + /* Write to FIFO in Pause State */ + /* Set to PAUSE state */ + ret = config_i2c_state(QUP_STATE_PAUSE); + if (ret != SUCCESS) { + debug("State Pause failed\n"); + goto out; + } + fifo = (uint32_t *) (i2c_base_addr + QUP_OUTPUT_FIFO_OFFSET); + data = i2c_frame_wr_tag(chip, data_len, alen); + + /* Write tags to the FIFO along with Slave address + * and Write len */ + writel(data, fifo); + + while (len > 0) { + data_lsb_16 = 0; + data_msb_16 = 0; + data = 0; + if ((first == 1) && (alen != 0)) { + if (alen == 2) { + /* based on the slave send msb 8 bits or lsb 8 bits first */ + data_lsb_16 = QUP_I2C_DATA(addr); + data_lsb_16 |= QUP_I2C_DATA(addr >> 8) << 8; + } else if (alen == 1) { + data_lsb_16 = QUP_I2C_DATA(addr); + data_lsb_16 |= QUP_I2C_DATA(buffer[idx]) << 8; + idx++; + len --; + } + first = 0; + ret = 2; + } else { + ret = create_data_byte(&data_lsb_16, buffer + idx, len); + idx += ret; + len -= ret; + } + if(ret == 2) { + ret = create_data_byte(&data_msb_16, buffer + idx, len); + idx += ret; + len -= ret; + } + data |= data_msb_16; + data = (data << 16); + data |= data_lsb_16; + writel(data, fifo); + } + + /* Set to RUN state */ + ret = config_i2c_state(QUP_STATE_RUN); + if (ret != SUCCESS) { + debug("State Run failed\n"); + goto out; + } + + /* Clear Operational Flag */ + if (readl(i2c_base_addr + QUP_OPERATIONAL_OFFSET) + & OUTPUT_SERVICE_FLAG) { + writel(OUTPUT_SERVICE_FLAG, + i2c_base_addr + QUP_OPERATIONAL_OFFSET); + } + + mdelay(2); + ret = check_write_done(); + if (ret != SUCCESS) { + debug("Write done failed\n"); + goto out; + } + + nack = readl(i2c_base_addr + QUP_I2C_MASTER_STATUS_OFFSET) & NACK_BIT_MASK; + nack = nack >> NACK_BIT_SHIFT; + if (nack == 1) { + debug("NACK RECVD\n"); + return -ENACK; + } +out: + /* + * Put the I2C Core back in the Reset State to end the transfer. + */ + (void)config_i2c_state(QUP_STATE_RESET); + return ret; +} + +/* + * Probe the given I2C chip address. + * Returns 0 if a chip responded. + */ +int i2c_probe(uchar chip) +{ + uchar buf; + + /*send the third parameter alen as per the i2c slave*/ + return i2c_read(chip, 0x0, 0x0, &buf, 0x1); +} + +void i2c_init(int speed, int slaveaddr) +{ + debug("i2c_init(speed=%u, slaveaddr=0x%x)\n", speed, slaveaddr); +} \ No newline at end of file diff --git a/drivers/i2c/qup_i2c.h b/drivers/i2c/qup_i2c.h new file mode 100644 index 0000000000..c468ad4f53 --- /dev/null +++ b/drivers/i2c/qup_i2c.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define I2C_STATUS_ERROR_MASK 0x38000FC + + /* QUP Core register offsets */ +#define QUP_CONFIG_OFFSET 0x0 +#define QUP_STATE_OFFSET 0x4 +#define QUP_IO_MODES_OFFSET 0x8 +#define QUP_SW_RESET_OFFSET 0xc +#define QUP_TIME_OUT_OFFSET 0x10 +#define QUP_TIME_OUT_CURRENT_OFFSET 0x14 +#define QUP_OPERATIONAL_OFFSET 0x18 +#define QUP_ERROR_FLAGS_OFFSET 0x1c +#define QUP_ERROR_FLAGS_EN_OFFSET 0x20 +#define QUP_TEST_CTRL_OFFSET 0x24 +#define QUP_OPERATIONAL_MASK_OFFSET 0x28 +#define QUP_MX_OUTPUT_COUNT_OFFSET 0x100 +#define QUP_MX_OUTPUT_CNT_CURRENT_OFFSET 0x104 +#define QUP_OUTPUT_DEBUG_OFFSET 0x108 +#define QUP_OUTPUT_FIFO_WORD_CNT_OFFSET 0x10c +#define QUP_OUTPUT_FIFO_OFFSET 0x110 +#define QUP_MX_WRITE_COUNT_OFFSET 0x150 +#define QUP_WRITE_CNT_CURRENT_OFFSET 0x154 +#define QUP_MX_INPUT_COUNT_OFFSET 0x200 +#define QUP_MX_READ_COUNT_OFFSET 0x208 +#define QUP_MX_READ_CNT_CURRENT_OFFSET 0x20c +#define QUP_INPUT_DEBUG_OFFSET 0x210 +#define QUP_INPUT_FIFO_WORD_CNT_OFFSET 0x214 +#define QUP_INPUT_FIFO_OFFSET 0x218 +#define QUP_I2C_MASTER_CLK_CTL_OFFSET 0x400 +#define QUP_I2C_MASTER_STATUS_OFFSET 0x404 +#define QUP_I2C_MASTER_CONFIG_OFFSET 0x408 +#define QUP_MAX_OFFSET 0xfff + +#define QUP_MX_OUTPUT_COUNT 0x0000 +#define QUP_MX_INPUT_COUNT 0x0000 +#define QUP_MX_WRITE_COUNT 0x0000 +#define QUP_MX_READ_COUNT 0x0000 + +#define SUCCESS 0 +#define ENACK 1 +#define TIMEOUT_CNT 100 + +#define QUP_STATE_RESET 0x0 +#define QUP_STATE_RUN 0x1D +#define QUP_STATE_PAUSE 0x1F +#define QUP_STATE_VALID_BIT 2 +#define QUP_STATE_VALID 1 +#define QUP_STATE_MASK 0x3 + +#define NACK_BIT_MASK 0x8 +#define NACK_BIT_SHIFT 3 + +#define QUP_CONFIG_MINI_CORE_I2C (2 << 8) +#define I2C_BIT_WORD 0x7 +#define INPUT_FIFO_MODE (0x0 << 12) +#define OUTPUT_FIFO_MODE (0x0 << 10) +#define INPUT_BLOCK_MODE (0x01 << 12) +#define OUTPUT_BLOCK_MODE (0x01 << 10) +#define PACK_EN (0x01 << 15) +#define UNPACK_EN (0x01 << 14) +#define OUTPUT_BIT_SHIFT_EN (0x01 << 16) +#define ERROR_FLAGS_EN 0x3C +#define I2C_MASTER_STATUS_CLEAR 0xFFFFFC +#define QUP_DATA_AVAILABLE_FOR_READ (1 << 5) +#define OUTPUT_SERVICE_FLAG (1 << 8) +#define INPUT_SERVICE_FLAG (1 << 9) +#define MAX_OUTPUT_DONE_FLAG (1 << 10) +#define MAX_INPUT_DONE_FLAG (1 << 11) +#define OUTPUT_FIFO_NOT_EMPTY (1 << 4) +#define OUTPUT_FIFO_FULL (1 << 6) +#define INPUT_FIFO_NOT_EMPTY (1 << 5) +#define INPUT_FIFO_FULL (1 << 7) +#define QUP_APP_CLK_ON_EN (1 << 12) +#define QUP_CORE_CLK_ON_EN (1 << 13) +#define QUP_FIFO_CLK_GATE_EN (1 << 14) +#define QUP_EN_VERSION_TWO_TAG 1 + +#define I2C_WRITE 0x0 +#define I2C_READ 0x1 +#define QUP_I2C_START_SEQ 0x81 +#define QUP_I2C_DATA_WRITE_SEQ 0x82 +#define QUP_I2C_DATA_WRITE_AND_STOP_SEQ 0x83 +#define QUP_I2C_DATA_READ_SEQ 0x85 +#define QUP_I2C_DATA_READ_AND_STOP_SEQ 0x87 +#define QUP_I2C_STOP_SEQ 0x88 +#define QUP_I2C_START_AND_STOP_SEQ 0x8A +#define QUP_I2C_NOP_PADDING 0x97 + +#define OUT_FIFO_WR_TAG_BYTE_CNT 4 +#define OUT_FIFO_RD_TAG_BYTE_CNT 8 +#define IN_FIFO_TAG_BYTE_CNT 2 +#define OFFSET_BYTE_CNT 2 + +#define QUP_I2C_ADDR(x) ((x & 0xFF) << 1) +#define QUP_I2C_DATA(x) (x & 0xFF) + +enum dir { + READ, + WRITE, +}; + +/* I2C some pre-defined frequencies */ +#define I2C_CLK_1KHZ 1 +#define I2C_CLK_100KHZ 100 +#define I2C_CLK_400KHZ 400 +#define I2C_CLK_1MHZ 1000 +#define QUP_INPUT_CLK_TCXO 19200 +#define QUP_INPUT_CLK QUP_INPUT_CLK_TCXO +#define I2C_INPUT_CLK_TCXO_DIV4 ((I2C_INPUT_CLK_TCXO)/4)