mirror of
https://git.codelinaro.org/clo/qsdk/oss/boot/u-boot-2016.git
synced 2025-12-10 07:44:53 +01:00
Change-Id: Icd0c082fcc5d191745e4e4242dda5a7f3b22c4f0 Signed-off-by: sameeruddin shaik <samesh@codeaurora.org>
854 lines
19 KiB
C
854 lines
19 KiB
C
/*
|
|
* Copyright (c) 2015-2017 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 <common.h>
|
|
#include <i2c.h>
|
|
#include "qup_i2c.h"
|
|
#include <asm/io.h>
|
|
#include <asm/errno.h>
|
|
#include <fdtdec.h>
|
|
#include <dm/device.h>
|
|
#include <dm/root.h>
|
|
#include <mapmem.h>
|
|
#include <dm.h>
|
|
#include <asm/arch-qca-common/qca_common.h>
|
|
#include <asm/arch-qca-common/gpio.h>
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
static int src_clk_freq;
|
|
static int i2c_base_addr;
|
|
static int i2c_hw_initialized;
|
|
static int i2c_board_initialized;
|
|
static int io_mode;
|
|
static int clk_en;
|
|
static int qup_n_val;
|
|
static int qup_i2c_start_seq;
|
|
|
|
struct i2c_qup_bus {
|
|
int *i2c_base_addr;
|
|
};
|
|
|
|
/*
|
|
* 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);
|
|
writel(cfg | io_mode, i2c_base_addr + QUP_IO_MODES_OFFSET);
|
|
}
|
|
|
|
/*
|
|
* Configure sda and sck gpios.
|
|
*/
|
|
void config_i2c_gpio(void)
|
|
{
|
|
int i2c_node, gpio_node;
|
|
|
|
i2c_node = fdt_path_offset(gd->fdt_blob, "i2c0");
|
|
if (i2c_node >= 0) {
|
|
gpio_node = fdt_subnode_offset(gd->fdt_blob, i2c_node, "i2c_gpio");
|
|
if (gpio_node >= 0)
|
|
qca_gpio_init(gpio_node);
|
|
}
|
|
}
|
|
|
|
void i2c_qca_board_init(struct i2c_qup_bus *i2c_bus)
|
|
{
|
|
config_i2c_gpio();
|
|
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) | (qup_n_val);
|
|
|
|
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(int qup_version)
|
|
{
|
|
int ret;
|
|
|
|
/* QUP configuration */
|
|
i2c_reset();
|
|
|
|
/* Set the QUP state */
|
|
ret = check_qup_state_valid();
|
|
if (ret)
|
|
return ret;
|
|
if (qup_version != qup_v1) {
|
|
writel(0,(i2c_base_addr + QUP_CONFIG_OFFSET));
|
|
writel( clk_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);
|
|
/* Clear the flag and Acknowledge that the
|
|
* software has or will write the data.
|
|
*/
|
|
if (readl(i2c_base_addr + QUP_OPERATIONAL_OFFSET)
|
|
& OUTPUT_SERVICE_FLAG) {
|
|
writel(OUTPUT_SERVICE_FLAG, i2c_base_addr
|
|
+ QUP_OPERATIONAL_OFFSET);
|
|
}
|
|
}
|
|
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_data(struct i2c_qup_bus *i2c_bus, 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;
|
|
|
|
config_i2c_state(QUP_STATE_RESET);
|
|
|
|
if (!i2c_board_initialized) {
|
|
i2c_qca_board_init(i2c_bus);
|
|
}
|
|
|
|
if (!i2c_hw_initialized) {
|
|
i2c_hw_init(qup_v2);
|
|
}
|
|
|
|
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 = ((src_clk_freq / (I2C_CLK_100KHZ * 2)) - 3) & 0xff;
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
(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 i2c_read_data_qup_v1(struct i2c_qup_bus *i2c_bus, uchar chip, uint addr, int alen, uchar *buffer, int len)
|
|
{
|
|
int ret = 0;
|
|
uint32_t data = 0;
|
|
uint32_t *fifo;
|
|
int idx = 0;
|
|
int cfg;
|
|
|
|
config_i2c_state(QUP_STATE_RESET);
|
|
|
|
if (!i2c_board_initialized) {
|
|
i2c_qca_board_init(i2c_bus);
|
|
}
|
|
|
|
if (!i2c_hw_initialized) {
|
|
i2c_hw_init(qup_v1);
|
|
}
|
|
|
|
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);
|
|
|
|
/* 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 = ((src_clk_freq / (I2C_CLK_100KHZ * 2)) - 3) & 0xff;
|
|
writel(cfg, i2c_base_addr + QUP_I2C_MASTER_CLK_CTL_OFFSET);
|
|
|
|
/* Send a write request to the chip */
|
|
writel((qup_i2c_start_seq | QUP_I2C_ADDR(chip)),
|
|
i2c_base_addr + QUP_OUTPUT_FIFO_OFFSET);
|
|
|
|
writel((QUP_I2C_DATA_SEQ | QUP_I2C_DATA(addr)),
|
|
i2c_base_addr + QUP_OUTPUT_FIFO_OFFSET);
|
|
|
|
mdelay(2);
|
|
ret = check_write_done();
|
|
if (ret != SUCCESS) {
|
|
debug("Write done failed\n");
|
|
goto out;
|
|
}
|
|
|
|
ret = check_fifo_status(WRITE);
|
|
if (ret != SUCCESS)
|
|
goto out;
|
|
|
|
/* Send read request */
|
|
writel((qup_i2c_start_seq | (QUP_I2C_ADDR(chip)| QUP_I2C_SLAVE_READ)),
|
|
i2c_base_addr + QUP_OUTPUT_FIFO_OFFSET);
|
|
|
|
writel((QUP_I2C_RECV_SEQ | len),
|
|
i2c_base_addr + QUP_OUTPUT_FIFO_OFFSET);
|
|
|
|
fifo = (uint32_t *)(i2c_base_addr + QUP_INPUT_FIFO_OFFSET);
|
|
|
|
/*
|
|
* Wait some more to be sure that write operation is done
|
|
* in the QUP_INPUT_FIFO_OFFSET registeri before checking the
|
|
* fifo status
|
|
*/
|
|
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;
|
|
}
|
|
}
|
|
|
|
(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 |= (QUP_I2C_DATA(buffer[idx]) << 8);
|
|
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_data_qup_v1(struct i2c_qup_bus *i2c_bus, uchar chip, uint addr,
|
|
int alen, uchar *buffer, int len)
|
|
{
|
|
int ret = 0;
|
|
int idx = 0;
|
|
int cfg;
|
|
|
|
if (!i2c_board_initialized) {
|
|
i2c_qca_board_init(i2c_bus);
|
|
}
|
|
|
|
if(!i2c_hw_initialized) {
|
|
i2c_hw_init(qup_v1);
|
|
}
|
|
|
|
/* Set to RUN state */
|
|
ret = config_i2c_state(QUP_STATE_RUN);
|
|
if (ret != SUCCESS)
|
|
goto out;
|
|
|
|
/* Configure the I2C Master clock */
|
|
cfg = ((src_clk_freq / (I2C_CLK_100KHZ * 2)) - 3) & 0xff;
|
|
writel(cfg, i2c_base_addr + QUP_I2C_MASTER_CLK_CTL_OFFSET);
|
|
|
|
/* Send the write request */
|
|
writel((qup_i2c_start_seq | QUP_I2C_ADDR(chip)),
|
|
i2c_base_addr + QUP_OUTPUT_FIFO_OFFSET);
|
|
writel((QUP_I2C_DATA_SEQ | QUP_I2C_DATA(addr)),
|
|
i2c_base_addr + QUP_OUTPUT_FIFO_OFFSET);
|
|
|
|
while (len) {
|
|
if (len == 1) {
|
|
writel((QUP_I2C_STOP_SEQ | QUP_I2C_DATA(buffer[idx])),
|
|
i2c_base_addr + QUP_OUTPUT_FIFO_OFFSET);
|
|
} else {
|
|
writel((QUP_I2C_DATA_SEQ | QUP_I2C_DATA(buffer[idx])),
|
|
i2c_base_addr + QUP_OUTPUT_FIFO_OFFSET);
|
|
}
|
|
len--;
|
|
idx++;
|
|
|
|
ret = check_fifo_status(WRITE);
|
|
if (ret != SUCCESS)
|
|
goto out;
|
|
}
|
|
|
|
ret = check_write_done();
|
|
if (ret != SUCCESS)
|
|
goto out;
|
|
|
|
/* Set to PAUSE state */
|
|
ret = config_i2c_state(QUP_STATE_PAUSE);
|
|
if (ret != SUCCESS)
|
|
goto out;
|
|
|
|
return ret;
|
|
out:
|
|
/*
|
|
* Put the I2C Core back in the Reset State to end the transfer.
|
|
*/
|
|
(void)config_i2c_state(QUP_STATE_RESET);
|
|
return ret;
|
|
}
|
|
|
|
int i2c_write_data(struct i2c_qup_bus *i2c_bus, 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;
|
|
|
|
/* Set to Reset State */
|
|
ret = config_i2c_state(QUP_STATE_RESET);
|
|
|
|
if (!i2c_board_initialized) {
|
|
i2c_qca_board_init(i2c_bus);
|
|
}
|
|
|
|
if (!i2c_hw_initialized) {
|
|
i2c_hw_init(qup_v2);
|
|
}
|
|
|
|
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 = ((src_clk_freq / (I2C_CLK_100KHZ * 2)) - 3) & 0xff;
|
|
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;
|
|
}
|
|
|
|
static int qup_i2c_xfer(struct udevice *bus, struct i2c_msg *msg,
|
|
int nmsgs)
|
|
{
|
|
int ret;
|
|
|
|
struct i2c_qup_bus *i2c_bus = dev_get_priv(bus);
|
|
struct ipq_i2c_platdata *plat = bus->platdata;
|
|
plat->type = dev_get_driver_data(bus);
|
|
|
|
debug("i2c_xfer: %d messages\n", nmsgs);
|
|
for (; nmsgs > 0; nmsgs--, msg++) {
|
|
debug("i2c_xfer: chip=0x%x, len=0x%x\n", msg->addr, msg->len);
|
|
if (msg->flags & I2C_M_RD) {
|
|
if (plat->type == qup_v1)
|
|
ret = i2c_read_data_qup_v1(i2c_bus, msg->addr, 0, 0, msg->buf, msg->len);
|
|
else
|
|
ret = i2c_read_data(i2c_bus, msg->addr, 0, 0, msg->buf, msg->len);
|
|
} else {
|
|
if (plat->type == qup_v1)
|
|
ret = i2c_write_data_qup_v1(i2c_bus, msg->addr, 0, 0, msg->buf,
|
|
msg->len);
|
|
else
|
|
ret = i2c_write_data(i2c_bus, msg->addr, 0, 0, msg->buf,
|
|
msg->len);
|
|
}
|
|
if (ret) {
|
|
printf("i2c_write: error sending\n");
|
|
return -EREMOTEIO;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void qca_i2c_plat_data(struct udevice *bus)
|
|
{
|
|
struct ipq_i2c_platdata *plat = bus->platdata;
|
|
plat->type = dev_get_driver_data(bus);
|
|
|
|
if (plat-> type == qup_v1) {
|
|
io_mode = (INPUT_FIFO_MODE | OUTPUT_FIFO_MODE | OUTPUT_BIT_SHIFT_EN);
|
|
clk_en = (QUP_APP_CLK_ON_EN | QUP_CORE_CLK_ON_EN);
|
|
qup_n_val = I2C_BIT_WORD_V1;
|
|
qup_i2c_start_seq = QUP_I2C_START_SEQ_V1;
|
|
}
|
|
else {
|
|
io_mode = (INPUT_FIFO_MODE | OUTPUT_FIFO_MODE | PACK_EN | UNPACK_EN);
|
|
clk_en = (QUP_APP_CLK_ON_EN | QUP_CORE_CLK_ON_EN | QUP_FIFO_CLK_GATE_EN);
|
|
qup_n_val = I2C_BIT_WORD_V2;
|
|
qup_i2c_start_seq = QUP_I2C_START_SEQ_V2;
|
|
}
|
|
}
|
|
|
|
/* Probe to see if a chip is present. */
|
|
static int qup_i2c_probe_chip(struct udevice *bus, uint chip_addr,
|
|
uint chip_flags)
|
|
{
|
|
uchar buf[1];
|
|
|
|
struct i2c_qup_bus *i2c_bus = dev_get_priv(bus);
|
|
struct ipq_i2c_platdata *plat = bus->platdata;
|
|
plat->type = dev_get_driver_data(bus);
|
|
buf[0] = 0;
|
|
if (i2c_bus == NULL)
|
|
return -ENODEV;
|
|
i2c_bus->i2c_base_addr = (int *)dev_get_addr(bus);
|
|
i2c_base_addr = (int)i2c_bus->i2c_base_addr;
|
|
src_clk_freq = fdtdec_get_int(gd->fdt_blob, bus->of_offset, "clock-frequency", -1);
|
|
qca_i2c_plat_data(bus);
|
|
if (plat-> type == qup_v1)
|
|
return i2c_read_data_qup_v1(i2c_bus, chip_addr , 0x0, 0x0, buf, 0x1);
|
|
else
|
|
return i2c_read_data(i2c_bus, chip_addr , 0x0, 0x0, buf, 0x1);
|
|
}
|
|
|
|
static const struct dm_i2c_ops qup_i2c_ops = {
|
|
.xfer = qup_i2c_xfer,
|
|
.probe_chip = qup_i2c_probe_chip,
|
|
};
|
|
|
|
static const struct udevice_id qpic_ver_ids[] = {
|
|
{ .compatible = "qcom,i2c-qup-v1.1.1", .data = qup_v1},
|
|
{ .compatible = "qcom,qup-i2c", .data = qup_v2},
|
|
{ },
|
|
};
|
|
|
|
U_BOOT_DRIVER(i2c_qup) = {
|
|
.name = "i2c_qup",
|
|
.id = UCLASS_I2C,
|
|
.of_match = qpic_ver_ids,
|
|
.platdata_auto_alloc_size = sizeof(struct ipq_i2c_platdata),
|
|
.priv_auto_alloc_size = sizeof(struct i2c_qup_bus),
|
|
.ops = &qup_i2c_ops,
|
|
};
|