diff options
Diffstat (limited to 'src/soc/qualcomm/ipq40xx/qup.c')
-rw-r--r-- | src/soc/qualcomm/ipq40xx/qup.c | 480 |
1 files changed, 480 insertions, 0 deletions
diff --git a/src/soc/qualcomm/ipq40xx/qup.c b/src/soc/qualcomm/ipq40xx/qup.c new file mode 100644 index 0000000000..c876d30c67 --- /dev/null +++ b/src/soc/qualcomm/ipq40xx/qup.c @@ -0,0 +1,480 @@ +/* + * 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: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 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 <arch/io.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 gsbi_qup_base[] = { + (unsigned)GSBI_QUP1_BASE, + (unsigned)GSBI_QUP2_BASE, + (unsigned)GSBI_QUP3_BASE, + (unsigned)GSBI_QUP4_BASE, + (unsigned)GSBI_QUP5_BASE, + (unsigned)GSBI_QUP6_BASE, + (unsigned)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 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 data_len = p_tx_obj->p.iic.data_len; + unsigned 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 data_len = p_tx_obj->p.iic.data_len; + unsigned 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; + } + /* 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 curr_state = read32(QUP_ADDR(gsbi_id, QUP_STATE)); + + if ((state >= QUP_STATE_RESET && 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; +} |