diff options
Diffstat (limited to 'arch/arm/mach-socfpga/mailbox_s10.c')
-rw-r--r-- | arch/arm/mach-socfpga/mailbox_s10.c | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/arch/arm/mach-socfpga/mailbox_s10.c b/arch/arm/mach-socfpga/mailbox_s10.c new file mode 100644 index 0000000000..cccd1a4404 --- /dev/null +++ b/arch/arm/mach-socfpga/mailbox_s10.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2018 Intel Corporation <www.intel.com> + * + */ + +#include <common.h> +#include <wait_bit.h> +#include <asm/io.h> +#include <asm/arch/mailbox_s10.h> +#include <asm/arch/system_manager.h> +#include <asm/secure.h> + +DECLARE_GLOBAL_DATA_PTR; + +#define MBOX_READL(reg) \ + readl(SOCFPGA_MAILBOX_ADDRESS + (reg)) + +#define MBOX_WRITEL(data, reg) \ + writel(data, SOCFPGA_MAILBOX_ADDRESS + (reg)) + +#define MBOX_READ_RESP_BUF(rout) \ + MBOX_READL(MBOX_RESP_BUF + ((rout) * sizeof(u32))) + +#define MBOX_WRITE_CMD_BUF(data, cin) \ + MBOX_WRITEL(data, MBOX_CMD_BUF + ((cin) * sizeof(u32))) + +static __always_inline int mbox_polling_resp(u32 rout) +{ + u32 rin; + unsigned long i = ~0; + + while (i) { + rin = MBOX_READL(MBOX_RIN); + if (rout != rin) + return 0; + + i--; + } + + return -ETIMEDOUT; +} + +/* Check for available slot and write to circular buffer. + * It also update command valid offset (cin) register. + */ +static __always_inline int mbox_fill_cmd_circular_buff(u32 header, u32 len, + u32 *arg) +{ + u32 cin; + u32 cout; + u32 i; + + cin = MBOX_READL(MBOX_CIN) % MBOX_CMD_BUFFER_SIZE; + cout = MBOX_READL(MBOX_COUT) % MBOX_CMD_BUFFER_SIZE; + + /* if command buffer is full or not enough free space + * to fit the data + */ + if (((cin + 1) % MBOX_CMD_BUFFER_SIZE) == cout || + ((MBOX_CMD_BUFFER_SIZE - cin + cout - 1) % + MBOX_CMD_BUFFER_SIZE) < len) + return -ENOMEM; + + /* write header to circular buffer */ + MBOX_WRITE_CMD_BUF(header, cin++); + /* wrapping around when it reach the buffer size */ + cin %= MBOX_CMD_BUFFER_SIZE; + + /* write arguments */ + for (i = 0; i < len; i++) { + MBOX_WRITE_CMD_BUF(arg[i], cin++); + /* wrapping around when it reach the buffer size */ + cin %= MBOX_CMD_BUFFER_SIZE; + } + + /* write command valid offset */ + MBOX_WRITEL(cin, MBOX_CIN); + + return 0; +} + +/* Check the command and fill it into circular buffer */ +static __always_inline int mbox_prepare_cmd_only(u8 id, u32 cmd, + u8 is_indirect, u32 len, + u32 *arg) +{ + u32 header; + int ret; + + /* Total length is command + argument length */ + if ((len + 1) > MBOX_CMD_BUFFER_SIZE) + return -EINVAL; + + if (cmd > MBOX_MAX_CMD_INDEX) + return -EINVAL; + + header = MBOX_CMD_HEADER(MBOX_CLIENT_ID_UBOOT, id, len, + (is_indirect) ? 1 : 0, cmd); + + ret = mbox_fill_cmd_circular_buff(header, len, arg); + + return ret; +} + +/* Send command only without waiting for responses from SDM */ +static __always_inline int mbox_send_cmd_only_common(u8 id, u32 cmd, + u8 is_indirect, u32 len, + u32 *arg) +{ + int ret = mbox_prepare_cmd_only(id, cmd, is_indirect, len, arg); + /* write doorbell */ + MBOX_WRITEL(1, MBOX_DOORBELL_TO_SDM); + + return ret; +} + +/* Return number of responses received in buffer */ +static __always_inline int __mbox_rcv_resp(u32 *resp_buf, u32 resp_buf_max_len) +{ + u32 rin; + u32 rout; + u32 resp_len = 0; + + /* clear doorbell from SDM if it was SET */ + if (MBOX_READL(MBOX_DOORBELL_FROM_SDM) & 1) + MBOX_WRITEL(0, MBOX_DOORBELL_FROM_SDM); + + /* read current response offset */ + rout = MBOX_READL(MBOX_ROUT); + /* read response valid offset */ + rin = MBOX_READL(MBOX_RIN); + + while (rin != rout && (resp_len < resp_buf_max_len)) { + /* Response received */ + if (resp_buf) + resp_buf[resp_len++] = MBOX_READ_RESP_BUF(rout); + + rout++; + /* wrapping around when it reach the buffer size */ + rout %= MBOX_RESP_BUFFER_SIZE; + /* update next ROUT */ + MBOX_WRITEL(rout, MBOX_ROUT); + } + + return resp_len; +} + +/* Support one command and up to 31 words argument length only */ +static __always_inline int mbox_send_cmd_common(u8 id, u32 cmd, u8 is_indirect, + u32 len, u32 *arg, u8 urgent, + u32 *resp_buf_len, + u32 *resp_buf) +{ + u32 rin; + u32 resp; + u32 rout; + u32 status; + u32 resp_len; + u32 buf_len; + int ret; + + ret = mbox_prepare_cmd_only(id, cmd, is_indirect, len, arg); + if (ret) + return ret; + + if (urgent) { + /* Read status because it is toggled */ + status = MBOX_READL(MBOX_STATUS) & MBOX_STATUS_UA_MSK; + /* Send command as urgent command */ + MBOX_WRITEL(1, MBOX_URG); + } + + /* write doorbell */ + MBOX_WRITEL(1, MBOX_DOORBELL_TO_SDM); + + while (1) { + ret = ~0; + + /* Wait for doorbell from SDM */ + while (!MBOX_READL(MBOX_DOORBELL_FROM_SDM) && ret--) + ; + if (!ret) + return -ETIMEDOUT; + + /* clear interrupt */ + MBOX_WRITEL(0, MBOX_DOORBELL_FROM_SDM); + + if (urgent) { + u32 new_status = MBOX_READL(MBOX_STATUS); + /* urgent command doesn't have response */ + MBOX_WRITEL(0, MBOX_URG); + /* Urgent ACK is toggled */ + if ((new_status & MBOX_STATUS_UA_MSK) ^ status) + return 0; + + return -ECOMM; + } + + /* read current response offset */ + rout = MBOX_READL(MBOX_ROUT); + + /* read response valid offset */ + rin = MBOX_READL(MBOX_RIN); + + if (rout != rin) { + /* Response received */ + resp = MBOX_READ_RESP_BUF(rout); + rout++; + /* wrapping around when it reach the buffer size */ + rout %= MBOX_RESP_BUFFER_SIZE; + /* update next ROUT */ + MBOX_WRITEL(rout, MBOX_ROUT); + + /* check client ID and ID */ + if ((MBOX_RESP_CLIENT_GET(resp) == + MBOX_CLIENT_ID_UBOOT) && + (MBOX_RESP_ID_GET(resp) == id)) { + ret = MBOX_RESP_ERR_GET(resp); + if (ret) + return ret; + + if (resp_buf_len) { + buf_len = *resp_buf_len; + *resp_buf_len = 0; + } else { + buf_len = 0; + } + + resp_len = MBOX_RESP_LEN_GET(resp); + while (resp_len) { + ret = mbox_polling_resp(rout); + if (ret) + return ret; + /* we need to process response buffer + * even caller doesn't need it + */ + resp = MBOX_READ_RESP_BUF(rout); + rout++; + resp_len--; + rout %= MBOX_RESP_BUFFER_SIZE; + MBOX_WRITEL(rout, MBOX_ROUT); + if (buf_len) { + /* copy response to buffer */ + resp_buf[*resp_buf_len] = resp; + (*resp_buf_len)++; + buf_len--; + } + } + return ret; + } + } + }; + + return -EIO; +} + +int mbox_init(void) +{ + int ret; + + /* enable mailbox interrupts */ + MBOX_WRITEL(MBOX_ALL_INTRS, MBOX_FLAGS); + + /* Ensure urgent request is cleared */ + MBOX_WRITEL(0, MBOX_URG); + + /* Ensure the Doorbell Interrupt is cleared */ + MBOX_WRITEL(0, MBOX_DOORBELL_FROM_SDM); + + ret = mbox_send_cmd(MBOX_ID_UBOOT, MBOX_RESTART, MBOX_CMD_DIRECT, 0, + NULL, 1, 0, NULL); + if (ret) + return ret; + + /* Renable mailbox interrupts after MBOX_RESTART */ + MBOX_WRITEL(MBOX_ALL_INTRS, MBOX_FLAGS); + + return 0; +} + +#ifdef CONFIG_CADENCE_QSPI +int mbox_qspi_close(void) +{ + return mbox_send_cmd(MBOX_ID_UBOOT, MBOX_QSPI_CLOSE, MBOX_CMD_DIRECT, + 0, NULL, 0, 0, NULL); +} + +int mbox_qspi_open(void) +{ + static const struct socfpga_system_manager *sysmgr_regs = + (struct socfpga_system_manager *)SOCFPGA_SYSMGR_ADDRESS; + + int ret; + u32 resp_buf[1]; + u32 resp_buf_len; + + ret = mbox_send_cmd(MBOX_ID_UBOOT, MBOX_QSPI_OPEN, MBOX_CMD_DIRECT, + 0, NULL, 0, 0, NULL); + if (ret) { + /* retry again by closing and reopen the QSPI again */ + ret = mbox_qspi_close(); + if (ret) + return ret; + + ret = mbox_send_cmd(MBOX_ID_UBOOT, MBOX_QSPI_OPEN, + MBOX_CMD_DIRECT, 0, NULL, 0, 0, NULL); + if (ret) + return ret; + } + + /* HPS will directly control the QSPI controller, no longer mailbox */ + resp_buf_len = 1; + ret = mbox_send_cmd(MBOX_ID_UBOOT, MBOX_QSPI_DIRECT, MBOX_CMD_DIRECT, + 0, NULL, 0, (u32 *)&resp_buf_len, + (u32 *)&resp_buf); + if (ret) + goto error; + + /* We are getting QSPI ref clock and set into sysmgr boot register */ + printf("QSPI: Reference clock at %d Hz\n", resp_buf[0]); + writel(resp_buf[0], &sysmgr_regs->boot_scratch_cold0); + + return 0; + +error: + mbox_qspi_close(); + + return ret; +} +#endif /* CONFIG_CADENCE_QSPI */ + +int mbox_reset_cold(void) +{ + int ret; + + ret = mbox_send_cmd(MBOX_ID_UBOOT, MBOX_REBOOT_HPS, MBOX_CMD_DIRECT, + 0, NULL, 0, 0, NULL); + if (ret) { + /* mailbox sent failure, wait for watchdog to kick in */ + hang(); + } + return 0; +} + +int mbox_send_cmd(u8 id, u32 cmd, u8 is_indirect, u32 len, u32 *arg, + u8 urgent, u32 *resp_buf_len, u32 *resp_buf) +{ + return mbox_send_cmd_common(id, cmd, is_indirect, len, arg, urgent, + resp_buf_len, resp_buf); +} + +int __secure mbox_send_cmd_psci(u8 id, u32 cmd, u8 is_indirect, u32 len, + u32 *arg, u8 urgent, u32 *resp_buf_len, + u32 *resp_buf) +{ + return mbox_send_cmd_common(id, cmd, is_indirect, len, arg, urgent, + resp_buf_len, resp_buf); +} + +int mbox_send_cmd_only(u8 id, u32 cmd, u8 is_indirect, u32 len, u32 *arg) +{ + return mbox_send_cmd_only_common(id, cmd, is_indirect, len, arg); +} + +int __secure mbox_send_cmd_only_psci(u8 id, u32 cmd, u8 is_indirect, u32 len, + u32 *arg) +{ + return mbox_send_cmd_only_common(id, cmd, is_indirect, len, arg); +} + +int mbox_rcv_resp(u32 *resp_buf, u32 resp_buf_max_len) +{ + return __mbox_rcv_resp(resp_buf, resp_buf_max_len); +} + +int __secure mbox_rcv_resp_psci(u32 *resp_buf, u32 resp_buf_max_len) +{ + return __mbox_rcv_resp(resp_buf, resp_buf_max_len); +} |