| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (C) 2018 Intel Corporation <www.intel.com> |
| */ |
| |
| #include <altera.h> |
| #include <log.h> |
| #include <time.h> |
| #include <watchdog.h> |
| #include <asm/arch/mailbox_s10.h> |
| #include <asm/arch/smc_api.h> |
| #include <linux/delay.h> |
| #include <linux/errno.h> |
| #include <linux/intel-smc.h> |
| #include <linux/string.h> |
| |
| #define RECONFIG_STATUS_POLL_RESP_TIMEOUT_MS 60000 |
| #define RECONFIG_STATUS_INTERVAL_DELAY_US 1000000 |
| |
| #if !defined(CONFIG_XPL_BUILD) && defined(CONFIG_SPL_ATF) |
| |
| #define BITSTREAM_CHUNK_SIZE 0xFFFF0 |
| #define RECONFIG_STATUS_POLL_RETRY_MAX 100 |
| |
| /* |
| * Polling the FPGA configuration status. |
| * Return 0 for success, non-zero for error. |
| */ |
| static int reconfig_status_polling_resp(void) |
| { |
| int ret; |
| unsigned long start = get_timer(0); |
| |
| while (1) { |
| ret = invoke_smc(INTEL_SIP_SMC_FPGA_CONFIG_ISDONE, NULL, 0, |
| NULL, 0); |
| |
| if (!ret) |
| return 0; /* configuration success */ |
| |
| if (ret != INTEL_SIP_SMC_STATUS_BUSY) |
| return ret; |
| |
| if (get_timer(start) > RECONFIG_STATUS_POLL_RESP_TIMEOUT_MS) |
| return -ETIMEDOUT; /* time out */ |
| |
| puts("."); |
| udelay(RECONFIG_STATUS_INTERVAL_DELAY_US); |
| schedule(); |
| } |
| |
| return -ETIMEDOUT; |
| } |
| |
| static int send_bitstream(const void *rbf_data, size_t rbf_size) |
| { |
| int i; |
| u64 res_buf[3]; |
| u64 args[2]; |
| u32 xfer_count = 0; |
| int ret, wr_ret = 0, retry = 0; |
| size_t buf_size = (rbf_size > BITSTREAM_CHUNK_SIZE) ? |
| BITSTREAM_CHUNK_SIZE : rbf_size; |
| |
| while (rbf_size || xfer_count) { |
| if (!wr_ret && rbf_size) { |
| args[0] = (u64)rbf_data; |
| args[1] = buf_size; |
| wr_ret = invoke_smc(INTEL_SIP_SMC_FPGA_CONFIG_WRITE, |
| args, 2, NULL, 0); |
| |
| debug("wr_ret = %d, rbf_data = %p, buf_size = %08lx\n", |
| wr_ret, rbf_data, buf_size); |
| |
| if (wr_ret) |
| continue; |
| |
| rbf_size -= buf_size; |
| rbf_data += buf_size; |
| |
| if (buf_size >= rbf_size) |
| buf_size = rbf_size; |
| |
| xfer_count++; |
| puts("."); |
| } else { |
| ret = invoke_smc( |
| INTEL_SIP_SMC_FPGA_CONFIG_COMPLETED_WRITE, |
| NULL, 0, res_buf, ARRAY_SIZE(res_buf)); |
| if (!ret) { |
| for (i = 0; i < ARRAY_SIZE(res_buf); i++) { |
| if (!res_buf[i]) |
| break; |
| xfer_count--; |
| wr_ret = 0; |
| retry = 0; |
| } |
| } else if (ret != |
| INTEL_SIP_SMC_STATUS_BUSY) |
| return ret; |
| else if (!xfer_count) |
| return INTEL_SIP_SMC_STATUS_ERROR; |
| |
| if (++retry >= RECONFIG_STATUS_POLL_RETRY_MAX) |
| return -ETIMEDOUT; |
| |
| udelay(20000); |
| } |
| schedule(); |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * This is the interface used by FPGA driver. |
| * Return 0 for success, non-zero for error. |
| */ |
| int intel_sdm_mb_load(Altera_desc *desc, const void *rbf_data, size_t rbf_size) |
| { |
| int ret; |
| u64 arg = 1; |
| |
| debug("Invoking FPGA_CONFIG_START...\n"); |
| |
| ret = invoke_smc(INTEL_SIP_SMC_FPGA_CONFIG_START, &arg, 1, NULL, 0); |
| |
| if (ret) { |
| puts("Failure in RECONFIG mailbox command!\n"); |
| return ret; |
| } |
| |
| ret = send_bitstream(rbf_data, rbf_size); |
| if (ret) { |
| puts("Error sending bitstream!\n"); |
| return ret; |
| } |
| |
| /* Make sure we don't send MBOX_RECONFIG_STATUS too fast */ |
| udelay(RECONFIG_STATUS_INTERVAL_DELAY_US); |
| |
| debug("Polling with MBOX_RECONFIG_STATUS...\n"); |
| ret = reconfig_status_polling_resp(); |
| if (ret) { |
| puts("FPGA reconfiguration failed!"); |
| return ret; |
| } |
| |
| puts("FPGA reconfiguration OK!\n"); |
| |
| return ret; |
| } |
| |
| #else |
| |
| static const struct mbox_cfgstat_state { |
| int err_no; |
| const char *error_name; |
| } mbox_cfgstat_state[] = { |
| {MBOX_CFGSTAT_STATE_IDLE, "FPGA in idle mode."}, |
| {MBOX_CFGSTAT_STATE_CONFIG, "FPGA in config mode."}, |
| {MBOX_CFGSTAT_STATE_FAILACK, "Acknowledgment failed!"}, |
| {MBOX_CFGSTAT_STATE_ERROR_INVALID, "Invalid bitstream!"}, |
| {MBOX_CFGSTAT_STATE_ERROR_CORRUPT, "Corrupted bitstream!"}, |
| {MBOX_CFGSTAT_STATE_ERROR_AUTH, "Authentication failed!"}, |
| {MBOX_CFGSTAT_STATE_ERROR_CORE_IO, "I/O error!"}, |
| {MBOX_CFGSTAT_STATE_ERROR_HARDWARE, "Hardware error!"}, |
| {MBOX_CFGSTAT_STATE_ERROR_FAKE, "Fake error!"}, |
| {MBOX_CFGSTAT_STATE_ERROR_BOOT_INFO, "Error in boot info!"}, |
| {MBOX_CFGSTAT_STATE_ERROR_QSPI_ERROR, "Error in QSPI!"}, |
| {MBOX_RESP_ERROR, "Mailbox general error!"}, |
| {-ETIMEDOUT, "I/O timeout error"}, |
| {-1, "Unknown error!"} |
| }; |
| |
| #define MBOX_CFGSTAT_MAX ARRAY_SIZE(mbox_cfgstat_state) |
| |
| static const char *mbox_cfgstat_to_str(int err) |
| { |
| int i; |
| |
| for (i = 0; i < MBOX_CFGSTAT_MAX - 1; i++) { |
| if (mbox_cfgstat_state[i].err_no == err) |
| return mbox_cfgstat_state[i].error_name; |
| } |
| |
| return mbox_cfgstat_state[MBOX_CFGSTAT_MAX - 1].error_name; |
| } |
| |
| /* |
| * Add the ongoing transaction's command ID into pending list and return |
| * the command ID for next transfer. |
| */ |
| static u8 add_transfer(u32 *xfer_pending_list, size_t list_size, u8 id) |
| { |
| int i; |
| |
| for (i = 0; i < list_size; i++) { |
| if (xfer_pending_list[i]) |
| continue; |
| xfer_pending_list[i] = id; |
| debug("ID(%d) added to transaction pending list\n", id); |
| /* |
| * Increment command ID for next transaction. |
| * Valid command ID (4 bits) is from 1 to 15. |
| */ |
| id = (id % 15) + 1; |
| break; |
| } |
| |
| return id; |
| } |
| |
| /* |
| * Check whether response ID match the command ID in the transfer |
| * pending list. If a match is found in the transfer pending list, |
| * it clears the transfer pending list and return the matched |
| * command ID. |
| */ |
| static int get_and_clr_transfer(u32 *xfer_pending_list, size_t list_size, |
| u8 id) |
| { |
| int i; |
| |
| for (i = 0; i < list_size; i++) { |
| if (id != xfer_pending_list[i]) |
| continue; |
| xfer_pending_list[i] = 0; |
| return id; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Polling the FPGA configuration status. |
| * Return 0 for success, non-zero for error. |
| */ |
| static int reconfig_status_polling_resp(void) |
| { |
| int ret; |
| unsigned long start = get_timer(0); |
| |
| while (1) { |
| ret = mbox_get_fpga_config_status(MBOX_RECONFIG_STATUS); |
| if (!ret) |
| return 0; /* configuration success */ |
| |
| if (ret != MBOX_CFGSTAT_STATE_CONFIG) |
| return ret; |
| |
| if (get_timer(start) > RECONFIG_STATUS_POLL_RESP_TIMEOUT_MS) |
| break; /* time out */ |
| |
| puts("."); |
| udelay(RECONFIG_STATUS_INTERVAL_DELAY_US); |
| schedule(); |
| } |
| |
| return -ETIMEDOUT; |
| } |
| |
| static u32 get_resp_hdr(u32 *r_index, u32 *w_index, u32 *resp_count, |
| u32 *resp_buf, u32 buf_size, u32 client_id) |
| { |
| u32 buf[MBOX_RESP_BUFFER_SIZE]; |
| u32 mbox_hdr; |
| u32 resp_len; |
| u32 hdr_len; |
| u32 i; |
| |
| if (*resp_count < buf_size) { |
| u32 rcv_len_max = buf_size - *resp_count; |
| |
| if (rcv_len_max > MBOX_RESP_BUFFER_SIZE) |
| rcv_len_max = MBOX_RESP_BUFFER_SIZE; |
| resp_len = mbox_rcv_resp(buf, rcv_len_max); |
| |
| for (i = 0; i < resp_len; i++) { |
| resp_buf[(*w_index)++] = buf[i]; |
| *w_index %= buf_size; |
| (*resp_count)++; |
| } |
| } |
| |
| /* No response in buffer */ |
| if (*resp_count == 0) |
| return 0; |
| |
| mbox_hdr = resp_buf[*r_index]; |
| |
| hdr_len = MBOX_RESP_LEN_GET(mbox_hdr); |
| |
| /* Insufficient header length to return a mailbox header */ |
| if ((*resp_count - 1) < hdr_len) |
| return 0; |
| |
| *r_index += (hdr_len + 1); |
| *r_index %= buf_size; |
| *resp_count -= (hdr_len + 1); |
| |
| /* Make sure response belongs to us */ |
| if (MBOX_RESP_CLIENT_GET(mbox_hdr) != client_id) |
| return 0; |
| |
| return mbox_hdr; |
| } |
| |
| /* Send bit stream data to SDM via RECONFIG_DATA mailbox command */ |
| static int send_reconfig_data(const void *rbf_data, size_t rbf_size, |
| u32 xfer_max, u32 buf_size_max) |
| { |
| u32 response_buffer[MBOX_RESP_BUFFER_SIZE]; |
| u32 xfer_pending[MBOX_RESP_BUFFER_SIZE]; |
| u32 resp_rindex = 0; |
| u32 resp_windex = 0; |
| u32 resp_count = 0; |
| u32 xfer_count = 0; |
| int resp_err = 0; |
| u8 cmd_id = 1; |
| u32 args[3]; |
| int ret; |
| |
| debug("SDM xfer_max = %d\n", xfer_max); |
| debug("SDM buf_size_max = %x\n\n", buf_size_max); |
| |
| memset(xfer_pending, 0, sizeof(xfer_pending)); |
| |
| while (rbf_size || xfer_count) { |
| if (!resp_err && rbf_size && xfer_count < xfer_max) { |
| args[0] = MBOX_ARG_DESC_COUNT(1); |
| args[1] = (u64)rbf_data; |
| if (rbf_size >= buf_size_max) { |
| args[2] = buf_size_max; |
| rbf_size -= buf_size_max; |
| rbf_data += buf_size_max; |
| } else { |
| args[2] = (u64)rbf_size; |
| rbf_size = 0; |
| } |
| |
| resp_err = mbox_send_cmd_only(cmd_id, MBOX_RECONFIG_DATA, |
| MBOX_CMD_INDIRECT, 3, args); |
| if (!resp_err) { |
| xfer_count++; |
| cmd_id = add_transfer(xfer_pending, |
| MBOX_RESP_BUFFER_SIZE, |
| cmd_id); |
| } |
| puts("."); |
| } else { |
| u32 resp_hdr = get_resp_hdr(&resp_rindex, &resp_windex, |
| &resp_count, |
| response_buffer, |
| MBOX_RESP_BUFFER_SIZE, |
| MBOX_CLIENT_ID_UBOOT); |
| |
| /* |
| * If no valid response header found or |
| * non-zero length from RECONFIG_DATA |
| */ |
| if (!resp_hdr || MBOX_RESP_LEN_GET(resp_hdr)) |
| continue; |
| |
| /* Check for response's status */ |
| if (!resp_err) { |
| resp_err = MBOX_RESP_ERR_GET(resp_hdr); |
| debug("Response error code: %08x\n", resp_err); |
| } |
| |
| ret = get_and_clr_transfer(xfer_pending, |
| MBOX_RESP_BUFFER_SIZE, |
| MBOX_RESP_ID_GET(resp_hdr)); |
| if (ret) { |
| /* Claim and reuse the ID */ |
| cmd_id = (u8)ret; |
| xfer_count--; |
| } |
| |
| if (resp_err && !xfer_count) |
| return resp_err; |
| } |
| schedule(); |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * This is the interface used by FPGA driver. |
| * Return 0 for success, non-zero for error. |
| */ |
| int intel_sdm_mb_load(Altera_desc *desc, const void *rbf_data, size_t rbf_size) |
| { |
| int ret; |
| u32 resp_len = 2; |
| u32 resp_buf[2]; |
| |
| debug("Sending MBOX_RECONFIG...\n"); |
| ret = mbox_send_cmd(MBOX_ID_UBOOT, MBOX_RECONFIG, MBOX_CMD_DIRECT, 0, |
| NULL, 0, &resp_len, resp_buf); |
| if (ret) { |
| puts("Failure in RECONFIG mailbox command!\n"); |
| return ret; |
| } |
| |
| ret = send_reconfig_data(rbf_data, rbf_size, resp_buf[0], resp_buf[1]); |
| if (ret) { |
| printf("RECONFIG_DATA error: %08x, %s\n", ret, |
| mbox_cfgstat_to_str(ret)); |
| return ret; |
| } |
| |
| /* Make sure we don't send MBOX_RECONFIG_STATUS too fast */ |
| udelay(RECONFIG_STATUS_INTERVAL_DELAY_US); |
| |
| debug("Polling with MBOX_RECONFIG_STATUS...\n"); |
| ret = reconfig_status_polling_resp(); |
| if (ret) { |
| printf("RECONFIG_STATUS Error: %08x, %s\n", ret, |
| mbox_cfgstat_to_str(ret)); |
| return ret; |
| } |
| |
| puts("FPGA reconfiguration OK!\n"); |
| |
| return ret; |
| } |
| #endif |