57e8909081
Signed-off-by: Martin Roth <martin@coreboot.org> Change-Id: I9c1228d3f9e7a12fe30c48e3b1f143520fed875c Reviewed-on: https://review.coreboot.org/c/coreboot/+/36332 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Patrick Rudolph <siro@das-labor.org>
474 lines
13 KiB
C
474 lines
13 KiB
C
/*
|
|
* This file is part of the coreboot project.
|
|
*
|
|
* Copyright (C) 2014 - 2015 The Linux Foundation. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. The name of the author may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <device/mmio.h>
|
|
#include <console/console.h>
|
|
#include <delay.h>
|
|
#include <soc/iomap.h>
|
|
#include <stdlib.h>
|
|
#include <soc/qup.h>
|
|
|
|
#define TIMEOUT_CNT 100000
|
|
|
|
//TODO: refactor the following array to iomap driver.
|
|
static unsigned int gsbi_qup_base[] = {
|
|
(unsigned int)GSBI_QUP1_BASE,
|
|
(unsigned int)GSBI_QUP2_BASE,
|
|
(unsigned int)GSBI_QUP3_BASE,
|
|
(unsigned int)GSBI_QUP4_BASE,
|
|
(unsigned int)GSBI_QUP5_BASE,
|
|
(unsigned int)GSBI_QUP6_BASE,
|
|
(unsigned int)GSBI_QUP7_BASE,
|
|
};
|
|
|
|
#define QUP_ADDR(gsbi_num, reg) ((void *)((gsbi_qup_base[gsbi_num-1]) + (reg)))
|
|
|
|
static qup_return_t qup_i2c_master_status(gsbi_id_t gsbi_id)
|
|
{
|
|
uint32_t reg_val = read32(QUP_ADDR(gsbi_id, QUP_I2C_MASTER_STATUS));
|
|
|
|
if (read32(QUP_ADDR(gsbi_id, QUP_ERROR_FLAGS)))
|
|
return QUP_ERR_XFER_FAIL;
|
|
if (reg_val & QUP_I2C_INVALID_READ_ADDR)
|
|
return QUP_ERR_I2C_INVALID_SLAVE_ADDR;
|
|
if (reg_val & QUP_I2C_FAILED_MASK)
|
|
return QUP_ERR_I2C_FAILED;
|
|
if (reg_val & QUP_I2C_ARB_LOST)
|
|
return QUP_ERR_I2C_ARB_LOST;
|
|
if (reg_val & QUP_I2C_BUS_ERROR)
|
|
return QUP_ERR_I2C_BUS_ERROR;
|
|
if (reg_val & QUP_I2C_INVALID_WRITE)
|
|
return QUP_ERR_I2C_INVALID_WRITE;
|
|
if (reg_val & QUP_I2C_PACKET_NACK)
|
|
return QUP_ERR_I2C_NACK;
|
|
if (reg_val & QUP_I2C_INVALID_TAG)
|
|
return QUP_ERR_I2C_INVALID_TAG;
|
|
|
|
return QUP_SUCCESS;
|
|
}
|
|
|
|
static int check_bit_state(uint32_t *reg, int wait_for)
|
|
{
|
|
unsigned int count = TIMEOUT_CNT;
|
|
|
|
while ((read32(reg) & (QUP_STATE_VALID_MASK | QUP_STATE_MASK)) !=
|
|
(QUP_STATE_VALID | wait_for)) {
|
|
if (count == 0)
|
|
return QUP_ERR_TIMEOUT;
|
|
count--;
|
|
udelay(1);
|
|
}
|
|
|
|
return QUP_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Check whether GSBIn_QUP State is valid
|
|
*/
|
|
static qup_return_t qup_wait_for_state(gsbi_id_t gsbi_id, unsigned int wait_for)
|
|
{
|
|
return check_bit_state(QUP_ADDR(gsbi_id, QUP_STATE), wait_for);
|
|
}
|
|
|
|
qup_return_t qup_reset_i2c_master_status(gsbi_id_t gsbi_id)
|
|
{
|
|
/*
|
|
* Writing a one clears the status bits.
|
|
* Bit31-25, Bit1 and Bit0 are reserved.
|
|
*/
|
|
//TODO: Define each status bit. OR all status bits in a single macro.
|
|
write32(QUP_ADDR(gsbi_id, QUP_I2C_MASTER_STATUS), 0x3FFFFFC);
|
|
return QUP_SUCCESS;
|
|
}
|
|
|
|
static qup_return_t qup_reset_master_status(gsbi_id_t gsbi_id)
|
|
{
|
|
write32(QUP_ADDR(gsbi_id, QUP_ERROR_FLAGS), 0x7C);
|
|
write32(QUP_ADDR(gsbi_id, QUP_ERROR_FLAGS_EN), 0x7C);
|
|
qup_reset_i2c_master_status(gsbi_id);
|
|
return QUP_SUCCESS;
|
|
}
|
|
|
|
static qup_return_t qup_fifo_wait_for(gsbi_id_t gsbi_id, uint32_t status)
|
|
{
|
|
qup_return_t ret = QUP_ERR_UNDEFINED;
|
|
unsigned int count = TIMEOUT_CNT;
|
|
|
|
while (!(read32(QUP_ADDR(gsbi_id, QUP_OPERATIONAL)) & status)) {
|
|
ret = qup_i2c_master_status(gsbi_id);
|
|
if (ret)
|
|
return ret;
|
|
if (count == 0)
|
|
return QUP_ERR_TIMEOUT;
|
|
count--;
|
|
}
|
|
|
|
return QUP_SUCCESS;
|
|
}
|
|
|
|
static qup_return_t qup_fifo_wait_while(gsbi_id_t gsbi_id, uint32_t status)
|
|
{
|
|
qup_return_t ret = QUP_ERR_UNDEFINED;
|
|
unsigned int count = TIMEOUT_CNT;
|
|
|
|
while (read32(QUP_ADDR(gsbi_id, QUP_OPERATIONAL)) & status) {
|
|
ret = qup_i2c_master_status(gsbi_id);
|
|
if (ret)
|
|
return ret;
|
|
if (count == 0)
|
|
return QUP_ERR_TIMEOUT;
|
|
count--;
|
|
}
|
|
|
|
return QUP_SUCCESS;
|
|
}
|
|
|
|
static qup_return_t qup_i2c_write_fifo(gsbi_id_t gsbi_id, qup_data_t *p_tx_obj,
|
|
uint8_t stop_seq)
|
|
{
|
|
qup_return_t ret = QUP_ERR_UNDEFINED;
|
|
uint8_t addr = p_tx_obj->p.iic.addr;
|
|
uint8_t *data_ptr = p_tx_obj->p.iic.data;
|
|
unsigned int data_len = p_tx_obj->p.iic.data_len;
|
|
unsigned int idx = 0;
|
|
|
|
qup_reset_master_status(gsbi_id);
|
|
qup_set_state(gsbi_id, QUP_STATE_RUN);
|
|
|
|
write32(QUP_ADDR(gsbi_id, QUP_OUTPUT_FIFO),
|
|
(QUP_I2C_START_SEQ | QUP_I2C_ADDR(addr)));
|
|
|
|
while (data_len) {
|
|
if (data_len == 1 && stop_seq) {
|
|
write32(QUP_ADDR(gsbi_id, QUP_OUTPUT_FIFO),
|
|
QUP_I2C_STOP_SEQ | QUP_I2C_DATA(data_ptr[idx]));
|
|
} else {
|
|
write32(QUP_ADDR(gsbi_id, QUP_OUTPUT_FIFO),
|
|
QUP_I2C_DATA_SEQ | QUP_I2C_DATA(data_ptr[idx]));
|
|
}
|
|
data_len--;
|
|
idx++;
|
|
if (data_len) {
|
|
ret = qup_fifo_wait_while(gsbi_id, OUTPUT_FIFO_FULL);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
/* Hardware sets the OUTPUT_SERVICE_FLAG flag to 1 when
|
|
OUTPUT_FIFO_NOT_EMPTY flag in the QUP_OPERATIONAL
|
|
register changes from 1 to 0, indicating that software
|
|
can write more data to the output FIFO. Software should
|
|
set OUTPUT_SERVICE_FLAG to 1 to clear it to 0, which
|
|
means that software knows to return to fill the output
|
|
FIFO with data.
|
|
*/
|
|
if (read32(QUP_ADDR(gsbi_id, QUP_OPERATIONAL)) &
|
|
OUTPUT_SERVICE_FLAG) {
|
|
write32(QUP_ADDR(gsbi_id, QUP_OPERATIONAL),
|
|
OUTPUT_SERVICE_FLAG);
|
|
}
|
|
}
|
|
|
|
ret = qup_fifo_wait_while(gsbi_id, OUTPUT_FIFO_NOT_EMPTY);
|
|
if (ret)
|
|
return ret;
|
|
|
|
qup_set_state(gsbi_id, QUP_STATE_PAUSE);
|
|
return qup_i2c_master_status(gsbi_id);
|
|
}
|
|
|
|
static qup_return_t qup_i2c_write(gsbi_id_t gsbi_id, uint8_t mode,
|
|
qup_data_t *p_tx_obj, uint8_t stop_seq)
|
|
{
|
|
qup_return_t ret = QUP_ERR_UNDEFINED;
|
|
|
|
switch (mode) {
|
|
case QUP_MODE_FIFO:
|
|
ret = qup_i2c_write_fifo(gsbi_id, p_tx_obj, stop_seq);
|
|
break;
|
|
default:
|
|
ret = QUP_ERR_UNSUPPORTED;
|
|
}
|
|
|
|
if (ret) {
|
|
qup_set_state(gsbi_id, QUP_STATE_RESET);
|
|
printk(BIOS_ERR, "%s() failed (%d)\n", __func__, ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static qup_return_t qup_i2c_read_fifo(gsbi_id_t gsbi_id, qup_data_t *p_tx_obj)
|
|
{
|
|
qup_return_t ret = QUP_ERR_UNDEFINED;
|
|
uint8_t addr = p_tx_obj->p.iic.addr;
|
|
uint8_t *data_ptr = p_tx_obj->p.iic.data;
|
|
unsigned int data_len = p_tx_obj->p.iic.data_len;
|
|
unsigned int idx = 0;
|
|
|
|
qup_reset_master_status(gsbi_id);
|
|
qup_set_state(gsbi_id, QUP_STATE_RUN);
|
|
|
|
write32(QUP_ADDR(gsbi_id, QUP_OUTPUT_FIFO),
|
|
QUP_I2C_START_SEQ | (QUP_I2C_ADDR(addr) | QUP_I2C_SLAVE_READ));
|
|
|
|
write32(QUP_ADDR(gsbi_id, QUP_OUTPUT_FIFO),
|
|
QUP_I2C_RECV_SEQ | data_len);
|
|
|
|
ret = qup_fifo_wait_while(gsbi_id, OUTPUT_FIFO_NOT_EMPTY);
|
|
if (ret)
|
|
return ret;
|
|
|
|
write32(QUP_ADDR(gsbi_id, QUP_OPERATIONAL), OUTPUT_SERVICE_FLAG);
|
|
|
|
while (data_len) {
|
|
uint32_t data;
|
|
|
|
ret = qup_fifo_wait_for(gsbi_id, INPUT_SERVICE_FLAG);
|
|
if (ret)
|
|
return ret;
|
|
|
|
data = read32(QUP_ADDR(gsbi_id, QUP_INPUT_FIFO));
|
|
|
|
/*
|
|
* Process tag and corresponding data value. For I2C master
|
|
* mini-core, data in FIFO is composed of 16 bits and is divided
|
|
* into an 8-bit tag for the upper bits and 8-bit data for the
|
|
* lower bits. The 8-bit tag indicates whether the byte is the
|
|
* last byte, or if a bus error happened during the receipt of
|
|
* the byte.
|
|
*/
|
|
if ((QUP_I2C_MI_TAG(data)) == QUP_I2C_MIDATA_SEQ) {
|
|
/* Tag: MIDATA = Master input data.*/
|
|
data_ptr[idx] = QUP_I2C_DATA(data);
|
|
idx++;
|
|
data_len--;
|
|
write32(QUP_ADDR(gsbi_id, QUP_OPERATIONAL),
|
|
INPUT_SERVICE_FLAG);
|
|
} else if (QUP_I2C_MI_TAG(data) == QUP_I2C_MISTOP_SEQ) {
|
|
/* Tag: MISTOP: Last byte of master input. */
|
|
data_ptr[idx] = QUP_I2C_DATA(data);
|
|
idx++;
|
|
data_len--;
|
|
break;
|
|
} else {
|
|
/* Tag: MINACK: Invalid master input data.*/
|
|
break;
|
|
}
|
|
}
|
|
|
|
write32(QUP_ADDR(gsbi_id, QUP_OPERATIONAL), INPUT_SERVICE_FLAG);
|
|
p_tx_obj->p.iic.data_len = idx;
|
|
qup_set_state(gsbi_id, QUP_STATE_PAUSE);
|
|
|
|
return QUP_SUCCESS;
|
|
}
|
|
|
|
static qup_return_t qup_i2c_read(gsbi_id_t gsbi_id, uint8_t mode,
|
|
qup_data_t *p_tx_obj)
|
|
{
|
|
qup_return_t ret = QUP_ERR_UNDEFINED;
|
|
|
|
switch (mode) {
|
|
case QUP_MODE_FIFO:
|
|
ret = qup_i2c_read_fifo(gsbi_id, p_tx_obj);
|
|
break;
|
|
default:
|
|
ret = QUP_ERR_UNSUPPORTED;
|
|
}
|
|
|
|
if (ret) {
|
|
qup_set_state(gsbi_id, QUP_STATE_RESET);
|
|
printk(BIOS_ERR, "%s() failed (%d)\n", __func__, ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
qup_return_t qup_init(gsbi_id_t gsbi_id, const qup_config_t *config_ptr)
|
|
{
|
|
qup_return_t ret = QUP_ERR_UNDEFINED;
|
|
uint32_t reg_val;
|
|
|
|
/* Reset the QUP core.*/
|
|
write32(QUP_ADDR(gsbi_id, QUP_SW_RESET), 0x1);
|
|
|
|
/*Wait till the reset takes effect */
|
|
ret = qup_wait_for_state(gsbi_id, QUP_STATE_RESET);
|
|
if (ret)
|
|
goto bailout;
|
|
|
|
/* Reset the config */
|
|
write32(QUP_ADDR(gsbi_id, QUP_CONFIG), 0);
|
|
|
|
/*Program the config register*/
|
|
/*Set N value*/
|
|
reg_val = 0x0F;
|
|
/*Set protocol*/
|
|
switch (config_ptr->protocol) {
|
|
case QUP_MINICORE_I2C_MASTER:
|
|
reg_val |= ((config_ptr->protocol &
|
|
QUP_MINI_CORE_PROTO_MASK) <<
|
|
QUP_MINI_CORE_PROTO_SHFT);
|
|
break;
|
|
default:
|
|
ret = QUP_ERR_UNSUPPORTED;
|
|
goto bailout;
|
|
}
|
|
write32(QUP_ADDR(gsbi_id, QUP_CONFIG), reg_val);
|
|
|
|
/*Reset i2c clk cntl register*/
|
|
write32(QUP_ADDR(gsbi_id, QUP_I2C_MASTER_CLK_CTL), 0);
|
|
|
|
/*Set QUP IO Mode*/
|
|
switch (config_ptr->mode) {
|
|
case QUP_MODE_FIFO:
|
|
reg_val = QUP_OUTPUT_BIT_SHIFT_EN |
|
|
((config_ptr->mode & QUP_MODE_MASK) <<
|
|
QUP_OUTPUT_MODE_SHFT) |
|
|
((config_ptr->mode & QUP_MODE_MASK) <<
|
|
QUP_INPUT_MODE_SHFT);
|
|
break;
|
|
default:
|
|
ret = QUP_ERR_UNSUPPORTED;
|
|
goto bailout;
|
|
}
|
|
write32(QUP_ADDR(gsbi_id, QUP_IO_MODES), reg_val);
|
|
|
|
/*Set i2c clk cntl*/
|
|
reg_val = (QUP_DIVIDER_MIN_VAL << QUP_HS_DIVIDER_SHFT);
|
|
reg_val |= ((((config_ptr->src_frequency / config_ptr->clk_frequency)
|
|
/ 2) - QUP_DIVIDER_MIN_VAL) &
|
|
QUP_FS_DIVIDER_MASK);
|
|
write32(QUP_ADDR(gsbi_id, QUP_I2C_MASTER_CLK_CTL), reg_val);
|
|
|
|
bailout:
|
|
if (ret)
|
|
printk(BIOS_ERR, "failed to init qup (%d)\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
qup_return_t qup_set_state(gsbi_id_t gsbi_id, uint32_t state)
|
|
{
|
|
qup_return_t ret = QUP_ERR_UNDEFINED;
|
|
unsigned int curr_state = read32(QUP_ADDR(gsbi_id, QUP_STATE));
|
|
|
|
if (state <= QUP_STATE_PAUSE && (curr_state & QUP_STATE_VALID_MASK)) {
|
|
/*
|
|
* For PAUSE_STATE to RESET_STATE transition,
|
|
* two writes of 10[binary]) are required for the
|
|
* transition to complete.
|
|
*/
|
|
if (QUP_STATE_PAUSE == curr_state && QUP_STATE_RESET == state) {
|
|
write32(QUP_ADDR(gsbi_id, QUP_STATE), 0x2);
|
|
write32(QUP_ADDR(gsbi_id, QUP_STATE), 0x2);
|
|
} else {
|
|
write32(QUP_ADDR(gsbi_id, QUP_STATE), state);
|
|
}
|
|
ret = qup_wait_for_state(gsbi_id, state);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static qup_return_t qup_i2c_send_data(gsbi_id_t gsbi_id, qup_data_t *p_tx_obj,
|
|
uint8_t stop_seq)
|
|
{
|
|
qup_return_t ret = QUP_ERR_UNDEFINED;
|
|
uint8_t mode = (read32(QUP_ADDR(gsbi_id, QUP_IO_MODES)) >>
|
|
QUP_OUTPUT_MODE_SHFT) & QUP_MODE_MASK;
|
|
|
|
ret = qup_i2c_write(gsbi_id, mode, p_tx_obj, stop_seq);
|
|
if (0) {
|
|
int i;
|
|
printk(BIOS_DEBUG, "i2c tx bus %d device %2.2x:",
|
|
gsbi_id, p_tx_obj->p.iic.addr);
|
|
for (i = 0; i < p_tx_obj->p.iic.data_len; i++)
|
|
printk(BIOS_DEBUG, " %2.2x", p_tx_obj->p.iic.data[i]);
|
|
printk(BIOS_DEBUG, "\n");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
qup_return_t qup_send_data(gsbi_id_t gsbi_id, qup_data_t *p_tx_obj,
|
|
uint8_t stop_seq)
|
|
{
|
|
qup_return_t ret = QUP_ERR_UNDEFINED;
|
|
|
|
if (p_tx_obj->protocol == ((read32(QUP_ADDR(gsbi_id, QUP_CONFIG)) >>
|
|
QUP_MINI_CORE_PROTO_SHFT) & QUP_MINI_CORE_PROTO_MASK)) {
|
|
switch (p_tx_obj->protocol) {
|
|
case QUP_MINICORE_I2C_MASTER:
|
|
ret = qup_i2c_send_data(gsbi_id, p_tx_obj, stop_seq);
|
|
break;
|
|
default:
|
|
ret = QUP_ERR_UNSUPPORTED;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static qup_return_t qup_i2c_recv_data(gsbi_id_t gsbi_id, qup_data_t *p_rx_obj)
|
|
{
|
|
qup_return_t ret = QUP_ERR_UNDEFINED;
|
|
uint8_t mode = (read32(QUP_ADDR(gsbi_id, QUP_IO_MODES)) >>
|
|
QUP_INPUT_MODE_SHFT) & QUP_MODE_MASK;
|
|
|
|
ret = qup_i2c_read(gsbi_id, mode, p_rx_obj);
|
|
if (0) {
|
|
int i;
|
|
printk(BIOS_DEBUG, "i2c rxed on bus %d device %2.2x:",
|
|
gsbi_id, p_rx_obj->p.iic.addr);
|
|
for (i = 0; i < p_rx_obj->p.iic.data_len; i++)
|
|
printk(BIOS_DEBUG, " %2.2x", p_rx_obj->p.iic.data[i]);
|
|
printk(BIOS_DEBUG, "\n");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
qup_return_t qup_recv_data(gsbi_id_t gsbi_id, qup_data_t *p_rx_obj)
|
|
{
|
|
qup_return_t ret = QUP_ERR_UNDEFINED;
|
|
|
|
if (p_rx_obj->protocol == ((read32(QUP_ADDR(gsbi_id, QUP_CONFIG)) >>
|
|
QUP_MINI_CORE_PROTO_SHFT) & QUP_MINI_CORE_PROTO_MASK)) {
|
|
switch (p_rx_obj->protocol) {
|
|
case QUP_MINICORE_I2C_MASTER:
|
|
ret = qup_i2c_recv_data(gsbi_id, p_rx_obj);
|
|
break;
|
|
default:
|
|
ret = QUP_ERR_UNSUPPORTED;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|