aboutsummaryrefslogtreecommitdiff
path: root/src/soc/qualcomm/ipq40xx/qup.c
diff options
context:
space:
mode:
authorVaradarajan Narayanan <varada@codeaurora.org>2016-03-02 16:57:10 +0530
committerPatrick Georgi <pgeorgi@google.com>2016-05-10 21:34:21 +0200
commita6935c2508c426f30d6bf5bcf4c3130277a0f998 (patch)
treee844bb803e8c069101fdf6ec47017af9dc832713 /src/soc/qualcomm/ipq40xx/qup.c
parentc84e2fe893e02de3c5d97d26d05462351ac90d91 (diff)
soc/qualcomm/ipq40xx: Initial commit for IPQ40xx SoC support
Copy 'ipq806x' files as a template BUG=chrome-os-partner:49249 TEST=None. Initial code not sure if it will even compile BRANCH=none Original-Commit-Id: dc6a5937953fe61cd4b5a99ca49f9371c4b712d4 Original-Change-Id: If171fcdd3b0561cb6b7dab5f8434de7ef711ea41 Original-Signed-off-by: Varadarajan Narayanan <varada@codeaurora.org> Original-Signed-off-by: Kan Yan <kyan@google.com> Original-Reviewed-on: https://chromium-review.googlesource.com/333178 Original-Commit-Ready: David Hendricks <dhendrix@chromium.org> Original-Tested-by: David Hendricks <dhendrix@chromium.org> Original-Reviewed-by: David Hendricks <dhendrix@chromium.org> squashed: soc/qualcomm/ipq40xx: Update ipq806x/storm references Since the files were taken from ipq806x/storm as template. Update those references to reflect ipq40xx/gale. BUG=chrome-os-partner:49249 TEST=None. Initial code not sure if it will even compile BRANCH=none Original-Commit-Id: c6c76d184cc92c09e6826fbdc7d7fac59b2cb69b Original-Change-Id: Ieae1bce25291243b4a6034d37a6949978f318997 Original-Signed-off-by: Varadarajan Narayanan <varada@codeaurora.org> Original-Reviewed-on: https://chromium-review.googlesource.com/333293 Original-Commit-Ready: David Hendricks <dhendrix@chromium.org> Original-Tested-by: David Hendricks <dhendrix@chromium.org> Original-Reviewed-by: David Hendricks <dhendrix@chromium.org> Change-Id: Ie5794c48131ae562861074b406106734541880d9 Signed-off-by: Patrick Georgi <pgeorgi@chromium.org> Reviewed-on: https://review.coreboot.org/14644 Tested-by: build bot (Jenkins) Reviewed-by: Martin Roth <martinroth@google.com>
Diffstat (limited to 'src/soc/qualcomm/ipq40xx/qup.c')
-rw-r--r--src/soc/qualcomm/ipq40xx/qup.c480
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;
+}