diff --git a/src/soc/qualcomm/ipq806x/gsbi.c b/src/soc/qualcomm/ipq806x/gsbi.c new file mode 100644 index 0000000000..be75d9a043 --- /dev/null +++ b/src/soc/qualcomm/ipq806x/gsbi.c @@ -0,0 +1,115 @@ +/* + * This file is part of the depthcharge project. + * + * Copyright (C) 2014 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 +#include "drivers/gpio/ipq806x.h" +#include "ipq806x_gsbi.h" + +//TODO: To be implemented as part of the iomap. +static int gsbi_base[] = { + 0x12440000, /*GSBI1*/ + 0x12480000, /*GSBI2*/ + 0x16200000, /*GSBI3*/ + 0x16300000, /*GSBI4*/ + 0x1A200000, /*GSBI5*/ + 0x16500000, /*GSBI6*/ + 0x16600000 /*GSBI7*/ +}; + +#define QUP_APPS_ADDR(N, os) ((void *)((0x009029C8+os)+(32*(N-1)))) +#define GSBI_HCLK_CTL(N) ((void *)(0x009029C0 + (32*(N-1)))) +#define GSBI_RESET(N) ((void *)(0x009029DC + (32*(N-1)))) +#define GSBI_CTL(N) ((void *)(gsbi_base[N-1])) + +#define GSBI_APPS_MD_OFFSET 0x0 +#define GSBI_APPS_NS_OFFSET 0x4 +#define GSBI_APPS_MAX_OFFSET 0xff + +#define GPIO_FUNC_I2C 0x1 + +gsbi_return_t gsbi_init(gsbi_id_t gsbi_id, gsbi_protocol_t protocol) +{ + unsigned i = 0; + unsigned qup_apps_ini[] = { + GSBI_APPS_NS_OFFSET, 0xf80b43, + GSBI_APPS_NS_OFFSET, 0xfc095b, + GSBI_APPS_NS_OFFSET, 0xfc015b, + GSBI_APPS_NS_OFFSET, 0xfc005b, + GSBI_APPS_NS_OFFSET, 0xA05, + GSBI_APPS_NS_OFFSET, 0x185, + GSBI_APPS_MD_OFFSET, 0x100fb, + GSBI_APPS_NS_OFFSET, 0xA05, + GSBI_APPS_NS_OFFSET, 0xfc015b, + GSBI_APPS_NS_OFFSET, 0xfc015b, + GSBI_APPS_NS_OFFSET, 0xfc095b, + GSBI_APPS_NS_OFFSET, 0xfc0b5b, + GSBI_APPS_MAX_OFFSET, 0x0 + }; + + gsbi_return_t ret = GSBI_SUCCESS; + + writel(0, GSBI_RESET(gsbi_id)); + + switch (gsbi_id) { + case GSBI_ID_4: { + /* Configure GPIOs 13 - SCL, 12 - SDA, 2mA gpio_en */ + gpio_tlmm_config_set(12, GPIO_FUNC_I2C, + GPIO_NO_PULL, GPIO_2MA, 1); + gpio_tlmm_config_set(13, GPIO_FUNC_I2C, + GPIO_NO_PULL, GPIO_2MA, 1); + } + break; + case GSBI_ID_1: { + /* Configure GPIOs 54 - SCL, 53 - SDA, 2mA gpio_en */ + gpio_tlmm_config_set(54, GPIO_FUNC_I2C, + GPIO_NO_PULL, GPIO_2MA, 1); + gpio_tlmm_config_set(53, GPIO_FUNC_I2C, + GPIO_NO_PULL, GPIO_2MA, 1); + } + break; + default: { + ret = GSBI_UNSUPPORTED; + goto bail_out; + } + } + + /*Select i2c protocol*/ + writel((2 << 4), GSBI_CTL(gsbi_id)); + + //TODO: Make use of clock API when available instead of the hardcoding. + /* Clock set to 24Mhz */ + for (i = 0; GSBI_APPS_MAX_OFFSET != qup_apps_ini[i]; i += 2) + writel(qup_apps_ini[i+1], + QUP_APPS_ADDR(gsbi_id, qup_apps_ini[i])); + + writel(((1 << 6)|(1 << 4)), GSBI_HCLK_CTL(gsbi_id)); + +bail_out: + return ret; +} diff --git a/src/soc/qualcomm/ipq806x/gsbi.h b/src/soc/qualcomm/ipq806x/gsbi.h new file mode 100644 index 0000000000..0da7a4796e --- /dev/null +++ b/src/soc/qualcomm/ipq806x/gsbi.h @@ -0,0 +1,60 @@ +/* + * This file is part of the depthcharge project. + * + * Copyright (C) 2014 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. + */ + +#ifndef __GSBI_TYPES_H__ +#define __GSBI_TYPES_H__ + +typedef enum { + GSBI_ID_1 = 1, + GSBI_ID_2, + GSBI_ID_3, + GSBI_ID_4, + GSBI_ID_5, + GSBI_ID_6, + GSBI_ID_7, +} gsbi_id_t; + +typedef enum { + GSBI_SUCCESS = 0, + GSBI_ID_ERROR, + GSBI_ERROR, + GSBI_UNSUPPORTED +} gsbi_return_t; + +typedef enum { + GSBI_PROTO_I2C_UIM = 1, + GSBI_PROTO_I2C_ONLY, + GSBI_PROTO_SPI_ONLY, + GSBI_PROTO_UART_FLOW_CTL, + GSBI_PROTO_UIM, + GSBI_PROTO_I2C_UART, +} gsbi_protocol_t; + +gsbi_return_t gsbi_init(gsbi_id_t gsbi_id, gsbi_protocol_t protocol); +#endif diff --git a/src/soc/qualcomm/ipq806x/i2c.c b/src/soc/qualcomm/ipq806x/i2c.c new file mode 100644 index 0000000000..9bae822a8e --- /dev/null +++ b/src/soc/qualcomm/ipq806x/i2c.c @@ -0,0 +1,143 @@ +/* + * This file is part of the depthcharge project. + * + * Copyright (C) 2014 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 +#include + +#include "base/container_of.h" +#include "drivers/bus/i2c/i2c.h" +#include "drivers/bus/i2c/ipq806x_qup.h" +#include "drivers/bus/i2c/ipq806x_gsbi.h" +#include "drivers/bus/i2c/ipq806x.h" + +static int i2c_init(unsigned gsbi_id) +{ + gsbi_return_t gsbi_ret = 0; + qup_return_t qup_ret = 0; + qup_config_t gsbi4_qup_config = { + QUP_MINICORE_I2C_MASTER, + 100000, + 24000000, + QUP_MODE_FIFO + }; + + gsbi_ret = gsbi_init(gsbi_id, GSBI_PROTO_I2C_ONLY); + if (GSBI_SUCCESS != gsbi_ret) + return 1; + + qup_ret = qup_init(gsbi_id, &gsbi4_qup_config); + if (QUP_SUCCESS != qup_ret) + return 1; + + qup_ret = qup_reset_i2c_master_status(gsbi_id); + if (QUP_SUCCESS != qup_ret) + return 1; + + return 0; +} + +static int i2c_read(uint32_t gsbi_id, uint8_t slave, + uint8_t *data, int data_len) +{ + qup_data_t obj; + qup_return_t qup_ret = 0; + + memset(&obj, 0, sizeof(obj)); + obj.protocol = QUP_MINICORE_I2C_MASTER; + obj.p.iic.addr = slave; + obj.p.iic.data_len = data_len; + obj.p.iic.data = data; + qup_ret = qup_recv_data(gsbi_id, &obj); + + if (QUP_SUCCESS != qup_ret) + return 1; + else + return 0; +} + +static int i2c_write(uint32_t gsbi_id, uint8_t slave, + uint8_t *data, int data_len, uint8_t stop_seq) +{ + qup_data_t obj; + qup_return_t qup_ret = 0; + + memset(&obj, 0, sizeof(obj)); + obj.protocol = QUP_MINICORE_I2C_MASTER; + obj.p.iic.addr = slave; + obj.p.iic.data_len = data_len; + obj.p.iic.data = data; + qup_ret = qup_send_data(gsbi_id, &obj, stop_seq); + + if (QUP_SUCCESS != qup_ret) + return 1; + else + return 0; +} + +static int i2c_transfer(struct I2cOps *me, I2cSeg *segments, int seg_count) +{ + Ipq806xI2c *bus = container_of(me, Ipq806xI2c, ops); + I2cSeg *seg = segments; + int ret = 0; + + if (!bus->initialized) + if (0 != i2c_init(bus->gsbi_id)) + return 1; + + while (seg_count--) { + if (seg->read) + ret = i2c_read(bus->gsbi_id, seg->chip, + seg->buf, seg->len); + else + ret = i2c_write(bus->gsbi_id, seg->chip, + seg->buf, seg->len, + (seg_count ? 0 : 1)); + seg++; + } + + if (QUP_SUCCESS != ret) { + qup_set_state(bus->gsbi_id, QUP_STATE_RESET); + return 1; + } + + return 0; +} + +Ipq806xI2c *new_ipq806x_i2c(unsigned gsbi_id) +{ + Ipq806xI2c *bus = 0; + + if (!i2c_init(gsbi_id)) { + bus = xzalloc(sizeof(*bus)); + bus->gsbi_id = gsbi_id; + bus->initialized = 1; + bus->ops.transfer = &i2c_transfer; + } + return bus; +} diff --git a/src/soc/qualcomm/ipq806x/qup.c b/src/soc/qualcomm/ipq806x/qup.c new file mode 100644 index 0000000000..5ac7f07e58 --- /dev/null +++ b/src/soc/qualcomm/ipq806x/qup.c @@ -0,0 +1,483 @@ +/* + * This file is part of the depthcharge project. + * + * Copyright (C) 2014 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 +#include +#include "ipq806x_qup.h" + +//TODO: refactor the following array to iomap driver. +static unsigned gsbi_qup_base[] = { + 0x12460000, /*gsbi 1*/ + 0x124A0000, /*gsbi 2*/ + 0x16280000, /*gsbi 3*/ + 0x16380000, /*gsbi 4*/ + 0x1A280000, /*gsbi 5*/ + 0x16580000, /*gsbi 6*/ + 0x16680000, /*gsbi 7*/ +}; + +#define QUP_ADDR(gsbi_num, reg) ((void *)((gsbi_qup_base[gsbi_num-1]) + (reg))) +#define MAX_DELAY_MS 100 + +static char *get_error_string(qup_return_t error) +{ + char *msg; + switch (error) { + case QUP_ERR_BAD_PARAM: + msg = "bad parameter"; + break; + case QUP_ERR_STATE_SET: + msg = "setting state failed"; + break; + case QUP_ERR_TIMEOUT: + msg = "timeout"; + break; + case QUP_ERR_UNSUPPORTED: + msg = "unsupported"; + break; + case QUP_ERR_I2C_INVALID_SLAVE_ADDR: + msg = "invalid slave address"; + break; + case QUP_ERR_XFER_FAIL: + msg = "transfer failed"; + break; + case QUP_ERR_UNDEFINED: + default: + msg = "undefined"; + break; + } + return msg; +} + +static qup_return_t qup_i2c_master_status(gsbi_id_t gsbi_id) +{ + qup_return_t ret = QUP_SUCCESS; + uint32_t reg_val = readl(QUP_ADDR(gsbi_id, QUP_I2C_MASTER_STATUS)); + + if (readl(QUP_ADDR(gsbi_id, QUP_ERROR_FLAGS))) + ret = QUP_ERR_XFER_FAIL; + else if (reg_val & QUP_I2C_INVALID_READ_ADDR) + ret = QUP_ERR_I2C_INVALID_SLAVE_ADDR; + else if (reg_val & (QUP_I2C_FAILED_MASK | + QUP_I2C_ARB_LOST | + QUP_I2C_BUS_ERROR)) + ret = QUP_ERR_XFER_FAIL; + + return ret; +} + +static qup_return_t qup_wait_for_state(gsbi_id_t gsbi_id, unsigned wait_for) +{ + qup_return_t ret = QUP_ERR_STATE_SET; + uint32_t val = 0; + uint32_t start_ts; + uint32_t d = MAX_DELAY_MS * timer_hz() / 1000; + uint8_t final_state = 0; + + start_ts = timer_raw_value(); + do { + val = readl(QUP_ADDR(gsbi_id, QUP_STATE)); + final_state = ((val & (QUP_STATE_VALID_MASK|QUP_STATE_MASK)) + == (QUP_STATE_VALID|wait_for)); + } while ((!final_state) && (start_ts > (timer_raw_value() - d))); + + if (final_state) + ret = QUP_SUCCESS; + + return ret; +} + +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; + uint32_t start_ts; + uint32_t d = MAX_DELAY_MS * timer_hz() / 1000; + + switch (mode) { + case QUP_MODE_FIFO: { + 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; + + writel(0x7C, QUP_ADDR(gsbi_id, QUP_ERROR_FLAGS)); + writel(0x7C, QUP_ADDR(gsbi_id, QUP_ERROR_FLAGS_EN)); + qup_reset_i2c_master_status(gsbi_id); + qup_set_state(gsbi_id, QUP_STATE_RUN); + + writel((QUP_I2C_START_SEQ | QUP_I2C_ADDR(addr)), + QUP_ADDR(gsbi_id, QUP_OUTPUT_FIFO)); + + while (data_len) { + if (data_len == 1 && stop_seq) { + writel((QUP_I2C_STOP_SEQ | + QUP_I2C_DATA(data_ptr[idx])), + QUP_ADDR(gsbi_id, + QUP_OUTPUT_FIFO)); + } else { + writel((QUP_I2C_DATA_SEQ | + QUP_I2C_DATA(data_ptr[idx])), + QUP_ADDR(gsbi_id, + QUP_OUTPUT_FIFO)); + } + data_len--; + idx++; + start_ts = timer_raw_value(); + while (data_len && readl(QUP_ADDR(gsbi_id, + QUP_OPERATIONAL)) & + OUTPUT_FIFO_FULL) { + ret = qup_i2c_master_status(gsbi_id); + if (QUP_SUCCESS != ret) + goto bailout; + if (start_ts < (timer_raw_value() - d)) { + ret = QUP_ERR_TIMEOUT; + goto bailout; + } + } + /* 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 (readl(QUP_ADDR(gsbi_id, QUP_OPERATIONAL)) & + OUTPUT_SERVICE_FLAG) { + writel(OUTPUT_SERVICE_FLAG, + QUP_ADDR(gsbi_id, + QUP_OPERATIONAL)); + } + } + + start_ts = timer_raw_value(); + while (((readl(QUP_ADDR(gsbi_id, QUP_OPERATIONAL))) & + OUTPUT_FIFO_NOT_EMPTY)) { + ret = qup_i2c_master_status(gsbi_id); + if (QUP_SUCCESS != ret) + goto bailout; + if (start_ts < (timer_raw_value() - d)) { + ret = QUP_ERR_TIMEOUT; + goto bailout; + } + } + + qup_set_state(gsbi_id, QUP_STATE_PAUSE); + ret = QUP_SUCCESS; + } + break; + + default: + ret = QUP_ERR_UNSUPPORTED; + } + +bailout: + if (QUP_SUCCESS != ret) { + qup_set_state(gsbi_id, QUP_STATE_RESET); + printf("%s() returns %s\n", __func__, get_error_string(ret)); + } + + return ret; +} + +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; + uint32_t start_ts; + uint32_t d = MAX_DELAY_MS * timer_hz() / 1000; + + switch (mode) { + case QUP_MODE_FIFO: { + 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; + + writel(0x7C, QUP_ADDR(gsbi_id, QUP_ERROR_FLAGS)); + writel(0x7C, QUP_ADDR(gsbi_id, QUP_ERROR_FLAGS_EN)); + qup_reset_i2c_master_status(gsbi_id); + qup_set_state(gsbi_id, QUP_STATE_RUN); + + writel((QUP_I2C_START_SEQ | + (QUP_I2C_ADDR(addr)| + QUP_I2C_SLAVE_READ)), + QUP_ADDR(gsbi_id, QUP_OUTPUT_FIFO)); + + writel((QUP_I2C_RECV_SEQ | data_len), + QUP_ADDR(gsbi_id, QUP_OUTPUT_FIFO)); + + start_ts = timer_raw_value(); + while ((readl(QUP_ADDR(gsbi_id, QUP_OPERATIONAL)) & + OUTPUT_FIFO_NOT_EMPTY)) { + ret = qup_i2c_master_status(gsbi_id); + if (QUP_SUCCESS != ret) + goto bailout; + if (start_ts < (timer_raw_value() - d)) { + ret = QUP_ERR_TIMEOUT; + goto bailout; + } + } + + writel(OUTPUT_SERVICE_FLAG, + QUP_ADDR(gsbi_id, QUP_OPERATIONAL)); + + while (data_len) { + unsigned data; + start_ts = timer_raw_value(); + while ((!((readl(QUP_ADDR(gsbi_id, QUP_OPERATIONAL))) & + INPUT_SERVICE_FLAG))) { + ret = qup_i2c_master_status(gsbi_id); + if (QUP_SUCCESS != ret) + goto bailout; + if (start_ts < (timer_raw_value() - d)) { + ret = QUP_ERR_TIMEOUT; + goto bailout; + } + } + + data = readl(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--; + writel(INPUT_SERVICE_FLAG, QUP_ADDR(gsbi_id, + QUP_OPERATIONAL)); + } 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--; + goto recv_done; + } else { + /* Tag: MINACK: Invalid master input data.*/ + goto recv_done; + } + } +recv_done: + writel(INPUT_SERVICE_FLAG, + QUP_ADDR(gsbi_id, QUP_OPERATIONAL)); + p_tx_obj->p.iic.data_len = idx; + qup_set_state(gsbi_id, QUP_STATE_PAUSE); + ret = QUP_SUCCESS; + } + break; + + default: + ret = QUP_ERR_UNSUPPORTED; + } + +bailout: + if (QUP_SUCCESS != ret) { + qup_set_state(gsbi_id, QUP_STATE_RESET); + printf("%s() returns %s\n", __func__, get_error_string(ret)); + } + + return ret; +} + +qup_return_t qup_init(gsbi_id_t gsbi_id, qup_config_t *config_ptr) +{ + qup_return_t ret = QUP_ERR_UNDEFINED; + uint32_t reg_val; + + /* Reset the QUP core.*/ + writel(0x1, QUP_ADDR(gsbi_id, QUP_SW_RESET)); + + /*Wait till the reset takes effect */ + ret = qup_wait_for_state(gsbi_id, QUP_STATE_RESET); + + if (QUP_SUCCESS != ret) + return ret; + + /*Reset the config*/ + writel(0, QUP_ADDR(gsbi_id, QUP_CONFIG)); + + /*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; + } + } + writel(reg_val, QUP_ADDR(gsbi_id, QUP_CONFIG)); + + /*Reset i2c clk cntl register*/ + writel(0, QUP_ADDR(gsbi_id, QUP_I2C_MASTER_CLK_CTL)); + + /*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; + } + } + writel(reg_val, QUP_ADDR(gsbi_id, QUP_IO_MODES)); + + /*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); + writel(reg_val, QUP_ADDR(gsbi_id, QUP_I2C_MASTER_CLK_CTL)); + +bailout: + if (QUP_SUCCESS != ret) + printf("%s() returns %s\n", __func__, get_error_string(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 = readl(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) { + writel(0x2, QUP_ADDR(gsbi_id, QUP_STATE)); + writel(0x2, QUP_ADDR(gsbi_id, QUP_STATE)); + } else { + writel(state, QUP_ADDR(gsbi_id, QUP_STATE)); + } + ret = qup_wait_for_state(gsbi_id, state); + } + return ret; +} + +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. + writel(0x3FFFFFC, QUP_ADDR(gsbi_id, QUP_I2C_MASTER_STATUS)); + return QUP_SUCCESS; +} + +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 == ((readl(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: { + uint8_t mode = (readl(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; + printf("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++) + printf(" %2.2x", + p_tx_obj->p.iic.data[i]); + printf("\n"); + } + break; + } + + default: + ret = QUP_ERR_UNSUPPORTED; + } + } + 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 == ((readl(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: { + uint8_t mode = (readl(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; + printf("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++) + printf(" %2.2x", + p_rx_obj->p.iic.data[i]); + printf("\n"); + } + break; + } + default: + ret = QUP_ERR_UNSUPPORTED; + } + } + return ret; +} diff --git a/src/soc/qualcomm/ipq806x/qup.h b/src/soc/qualcomm/ipq806x/qup.h new file mode 100644 index 0000000000..f848cf6413 --- /dev/null +++ b/src/soc/qualcomm/ipq806x/qup.h @@ -0,0 +1,207 @@ +/* + * This file is part of the depthcharge project. + * + * Copyright (C) 2014 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. + */ + +#ifndef __QUP_H__ +#define __QUP_H__ +#include "ipq806x_gsbi.h" + +/* QUP block registers */ +#define QUP_CONFIG 0x0 +#define QUP_STATE 0x4 +#define QUP_IO_MODES 0x8 +#define QUP_SW_RESET 0xc +#define QUP_TIME_OUT 0x10 +#define QUP_TIME_OUT_CURRENT 0x14 +#define QUP_OPERATIONAL 0x18 +#define QUP_ERROR_FLAGS 0x1c +#define QUP_ERROR_FLAGS_EN 0x20 +#define QUP_TEST_CTRL 0x24 +#define QUP_MX_OUTPUT_COUNT 0x100 +#define QUP_MX_OUTPUT_CNT_CURRENT 0x104 +#define QUP_OUTPUT_DEBUG 0x108 +#define QUP_OUTPUT_FIFO_WORD_CNT 0x10c +#define QUP_OUTPUT_FIFO 0x110 +#define QUP_MX_WRITE_COUNT 0x150 +#define QUP_WRITE_CNT_CURRENT 0x154 +#define QUP_MX_INPUT_COUNT 0x200 +#define QUP_READ_COUNT 0x208 +#define QUP_MX_READ_CNT_CURRENT 0x20c +#define QUP_INPUT_DEBUG 0x210 +#define QUP_INPUT_FIFO_WORD_CNT 0x214 +#define QUP_INPUT_FIFO 0x218 +#define QUP_I2C_MASTER_CLK_CTL 0x400 +#define QUP_I2C_MASTER_STATUS 0x404 + +#define OUTPUT_FIFO_FULL (1<<6) +#define INPUT_FIFO_NOT_EMPTY (1<<5) +#define OUTPUT_FIFO_NOT_EMPTY (1<<4) +#define INPUT_SERVICE_FLAG (1<<9) +#define OUTPUT_SERVICE_FLAG (1<<8) +#define QUP_OUTPUT_BIT_SHIFT_EN (1<<16) + +#define QUP_MODE_MASK (0x03) +#define QUP_OUTPUT_MODE_SHFT (10) +#define QUP_INPUT_MODE_SHFT (12) + +#define QUP_FS_DIVIDER_MASK (0xFF) + +#define QUP_MINI_CORE_PROTO_SHFT (8) +#define QUP_MINI_CORE_PROTO_MASK (0x0F) + +/* Mini-core states */ +#define QUP_STATE_RESET 0x0 +#define QUP_STATE_RUN 0x1 +#define QUP_STATE_PAUSE 0x3 +#define QUP_STATE_VALID (1<<2) +#define QUP_STATE_MASK 0x3 +#define QUP_STATE_VALID_MASK (1<<2) + +/* Tags for output FIFO */ +#define QUP_I2C_1CLK_NOOP_SEQ 0x1 /*MSB 8-bit NOP, LSB 8-bits 1 clk.*/ +#define QUP_I2C_START_SEQ (0x1 << 8) +#define QUP_I2C_DATA_SEQ (0x2 << 8) +#define QUP_I2C_STOP_SEQ (0x3 << 8) +#define QUP_I2C_RECV_SEQ (0x4 << 8) + +/* Tags for input FIFO */ +#define QUP_I2C_MIDATA_SEQ (0x5 << 8) +#define QUP_I2C_MISTOP_SEQ (0x6 << 8) +#define QUP_I2C_MINACK_SEQ (0x7 << 8) + +#define QUP_I2C_ADDR(x) ((x & 0xFF) << 1) +#define QUP_I2C_DATA(x) (x & 0xFF) +#define QUP_I2C_MI_TAG(x) (x & 0xFF00) +#define QUP_I2C_SLAVE_READ (0x1) + +/*Bit vals for I2C_MASTER_CLK_CTL register */ +#define QUP_HS_DIVIDER_SHFT (8) +#define QUP_DIVIDER_MIN_VAL (0x3) + +/* Bit masks for I2C_MASTER_STATUS register */ +#define QUP_I2C_INVALID_READ_SEQ (1 << 25) +#define QUP_I2C_INVALID_READ_ADDR (1 << 24) +#define QUP_I2C_INVALID_TAG (1 << 23) +#define QUP_I2C_FAILED_MASK (0x3 << 6) +#define QUP_I2C_ARB_LOST (1 << 4) +#define QUP_I2C_BUS_ERROR (1 << 2) + +typedef enum { + QUP_SUCCESS = 0, + QUP_ERR_BAD_PARAM, + QUP_ERR_STATE_SET, + QUP_ERR_TIMEOUT, + QUP_ERR_UNSUPPORTED, + QUP_ERR_I2C_INVALID_SLAVE_ADDR, + QUP_ERR_XFER_FAIL, + QUP_ERR_UNDEFINED, +} qup_return_t; + +typedef enum { + QUP_MINICORE_SPI = 1, + QUP_MINICORE_I2C_MASTER, + QUP_MINICORE_I2C_SLAVE +} qup_protocol_t; + +typedef enum { + QUP_MODE_FIFO = 0, + QUP_MODE_BLOCK, + QUP_MODE_DATAMOVER, +} qup_mode_t; + +typedef struct { + qup_protocol_t protocol; + unsigned clk_frequency; + unsigned src_frequency; + qup_mode_t mode; +} qup_config_t; + +typedef struct { + qup_protocol_t protocol; + union { + struct { + uint8_t addr; + uint8_t *data; + unsigned data_len; + } iic; + struct { + void *in; + void *out; + unsigned size; + } spi; + } p; +} qup_data_t; + +/* + * Initialize GSBI QUP block for FIFO I2C transfers. + * gsbi_id[IN]: GSBI for which QUP is to be initialized. + * config_ptr[IN]: configurations parameters for the QUP. + * + * return: QUP_SUCCESS, if initialization succeeds. + */ +qup_return_t qup_init(gsbi_id_t gsbi_id, qup_config_t *config_ptr); + +/* + * Set QUP state to run, pause, reset. + * gsbi_id[IN]: GSBI block for which QUP state is to be set. + * state[IN]: New state to transition to. + * + * return: QUP_SUCCESS, if state transition succeeds. + */ +qup_return_t qup_set_state(gsbi_id_t gsbi_id, uint32_t state); + +/* + * Reset the status bits set during an i2c transfer. + * gsbi_id[IN]: GSBI block for which i2c status bits are to be cleared. + * + * return: QUP_SUCCESS, if status bits are cleared successfully. + */ +qup_return_t qup_reset_i2c_master_status(gsbi_id_t gsbi_id); + +/* + * Send data to the peripheral on the bus. + * gsbi_id[IN]: GSBI block for which data is to be sent. + * p_tx_obj[IN]: Data to be sent to the slave on the bus. + * stop_seq[IN]: When set to non-zero QUP engine sends i2c stop sequnce. + * + * return: QUP_SUCCESS, when data is sent successfully to the peripheral. + */ +qup_return_t qup_send_data(gsbi_id_t gsbi_id, qup_data_t *p_tx_obj, + uint8_t stop_seq); + +/* + * Receive data from peripheral on the bus. + * gsbi_id[IN]: GSBI block from which data is to be received. + * p_tx_obj[IN]: length of data to be received, slave address. + * [OUT]: buffer filled with data from slave. + * + * return: QUP_SUCCESS, when data is received successfully. + */ +qup_return_t qup_recv_data(gsbi_id_t gsbi_id, qup_data_t *p_tx_obj); + +#endif //__QUP_H__