/* * Copyright 2024 NXP * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_LEVEL CONFIG_BT_HCI_DRIVER_LOG_LEVEL #include LOG_MODULE_REGISTER(bt_nxp_ctlr); #include "common/bt_str.h" #include "bt_nxp_ctlr_fw.h" #define DT_DRV_COMPAT nxp_bt_hci_uart #define FW_UPLOAD_CHANGE_TIMEOUT_RETRY_COUNT 6 static const struct device *uart_dev = DEVICE_DT_GET(DT_INST_GPARENT(0)); #if DT_NODE_HAS_PROP(DT_DRV_INST(0), sdio_reset_gpios) struct gpio_dt_spec sdio_reset = GPIO_DT_SPEC_GET(DT_DRV_INST(0), sdio_reset_gpios); #endif /* DT_NODE_HAS_PROP(DT_DRV_INST(0), sdio_reset_gpios) */ #if DT_NODE_HAS_PROP(DT_DRV_INST(0), w_disable_gpios) struct gpio_dt_spec w_disable = GPIO_DT_SPEC_GET(DT_DRV_INST(0), w_disable_gpios); #endif /* DT_NODE_HAS_PROP(DT_DRV_INST(0), w_disable_gpios) */ struct nxp_ctlr_dev_data { uint32_t primary_speed; bool primary_flowcontrol; uint32_t secondary_speed; bool secondary_flowcontrol; }; static struct nxp_ctlr_dev_data uart_dev_data; #define DI 0x07U #define POLYNOMIAL 0x04c11db7UL #define CRC32_LEN 4 static unsigned long crc_table[256U]; static bool made_table; static void fw_upload_gen_crc32_table(void) { int i, j; unsigned long crc_accum; for (i = 0; i < 256; i++) { crc_accum = ((unsigned long)i << 24); for (j = 0; j < 8; j++) { if (crc_accum & 0x80000000L) { crc_accum = (crc_accum << 1) ^ POLYNOMIAL; } else { crc_accum = (crc_accum << 1); } } crc_table[i] = crc_accum; } } static unsigned char fw_upload_crc8(unsigned char *array, unsigned char len) { unsigned char crc_8 = 0xff; crc_8 = crc8(array, len, DI, crc_8, false); return crc_8; } static unsigned long fw_upload_update_crc32(unsigned long crc_accum, char *data_blk_ptr, int data_blk_size) { unsigned int i, j; for (j = 0; j < data_blk_size; j++) { i = ((unsigned int)(crc_accum >> 24) ^ *data_blk_ptr++) & 0xff; crc_accum = (crc_accum << 8) ^ crc_table[i]; } return crc_accum; } #define CMD4 0x4U #define CMD6 0x6U #define CMD7 0x7U #define V1_HEADER_DATA_REQ 0xa5U #define V1_START_INDICATION 0xaaU #define V1_REQUEST_ACK 0x5aU #define V3_START_INDICATION 0xabU #define V3_HEADER_DATA_REQ 0xa7U #define V3_REQUEST_ACK 0x7aU #define V3_TIMEOUT_ACK 0x7bU #define V3_CRC_ERROR 0x7cU #define REQ_HEADER_LEN 1U #define A6REQ_PAYLOAD_LEN 8U #define AbREQ_PAYLOAD_LEN 3U #define CRC_ERR_BIT BIT(0) #define NAK_REC_BIT BIT(1) #define TIMEOUT_REC_ACK_BIT BIT(2) #define TIMEOUT_REC_HEAD_BIT BIT(3) #define TIMEOUT_REC_DATA_BIT BIT(4) #define INVALID_CMD_REC_BIT BIT(5) #define WIFI_MIC_FAIL_BIT BIT(6) #define BT_MIC_FAIL_BIT BIT(7) #define CMD_HDR_LEN 16 /* CMD5 Header to change bootloader baud rate */ static uint8_t cmd5_hdrData[CMD_HDR_LEN] = {0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x77, 0xdb, 0xfd, 0xe0}; /* CMD7 Header to change timeout of bootloader */ static uint8_t cmd7_hdrData[CMD_HDR_LEN] = {0x07, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, 0x88, 0xf8, 0xba}; enum bt_nxp_ctlr_version { VER1 = 1, VER2, VER3, }; struct change_speed_config { uint32_t clk_div_addr; uint32_t clk_div_val; uint32_t uart_clk_div_addr; uint32_t uart_clk_div_val; uint32_t mcr_addr; uint32_t mcr_val; uint32_t reinit_addr; uint32_t reinit_val; uint32_t icr_addr; uint32_t icr_val; uint32_t fcr_addr; uint32_t fcr_val; }; #define SEND_BUFFER_MAX_LENGTH 0xFFFFU /* Maximum 2 byte value */ #define RECV_RING_BUFFER_LENGTH 1024 struct nxp_ctlr_fw_upload_state { uint8_t version; uint8_t hdr_sig; uint8_t buffer[A6REQ_PAYLOAD_LEN + REQ_HEADER_LEN + 1]; uint8_t send_buffer[SEND_BUFFER_MAX_LENGTH + 1]; struct { uint8_t buffer[RECV_RING_BUFFER_LENGTH]; uint32_t head; uint32_t tail; struct k_sem sem; } rx; uint16_t length; uint32_t offset; uint16_t error; uint8_t crc8; uint32_t last_offset; uint8_t change_speed_buffer[sizeof(struct change_speed_config) + CRC32_LEN]; uint32_t fw_length; uint32_t current_length; const uint8_t *fw; uint32_t cmd7_change_timeout_len; uint32_t change_speed_buffer_len; bool wait_hdr_sig; bool is_hdr_data; bool is_error_case; bool is_cmd7_req; bool is_entry_point_req; bool is_setup_done; uint8_t last_5bytes_buffer[6]; }; static struct nxp_ctlr_fw_upload_state fw_upload; static int fw_upload_read_data(uint8_t *buffer, uint32_t len) { int err; while (len > 0) { err = k_sem_take(&fw_upload.rx.sem, K_MSEC(CONFIG_BT_H4_NXP_CTLR_WAIT_HDR_SIG_TIMEOUT)); if (err < 0) { LOG_ERR("Fail to read data"); return err; } *buffer = fw_upload.rx.buffer[fw_upload.rx.tail]; buffer++; fw_upload.rx.tail++; fw_upload.rx.tail = fw_upload.rx.tail % sizeof(fw_upload.rx.buffer); len--; } return 0; } static void fw_upload_read_to_clear(void) { uint32_t key; key = irq_lock(); k_sem_reset(&fw_upload.rx.sem); fw_upload.rx.head = 0; fw_upload.rx.tail = 0; irq_unlock(key); } static int fw_upload_wait_for_hdr_sig(void) { int err; int64_t end; char c; end = k_uptime_get() + CONFIG_BT_H4_NXP_CTLR_WAIT_HDR_SIG_TIMEOUT; fw_upload.hdr_sig = 0xFF; while (k_uptime_get() < end) { err = fw_upload_read_data(&c, 1); if (err < 0) { k_msleep(1); continue; } if ((c == V1_HEADER_DATA_REQ) || (c == V1_START_INDICATION) || (c == V3_START_INDICATION) || (c == V3_HEADER_DATA_REQ)) { LOG_DBG("HDR SIG found 0x%02X", c); fw_upload.hdr_sig = c; if (fw_upload.version == 0) { if ((c == V3_START_INDICATION) || (c == V3_HEADER_DATA_REQ)) { fw_upload.version = VER3; } else { fw_upload.version = VER1; } } return 0; } } LOG_ERR("HDR SIG not found"); return -EIO; } static void fw_upload_write_data(const uint8_t *buffer, uint32_t len) { for (int i = 0; i < len; i++) { uart_poll_out(uart_dev, buffer[i]); } } static int fw_upload_request_check_crc(uint8_t *buffer, uint8_t request) { uint8_t crc; if (request == V3_HEADER_DATA_REQ) { crc = fw_upload_crc8(buffer, A6REQ_PAYLOAD_LEN + REQ_HEADER_LEN); if (crc != buffer[A6REQ_PAYLOAD_LEN + REQ_HEADER_LEN]) { LOG_ERR("Request %d, CRC check failed", request); return -EINVAL; } } else if (request == V3_START_INDICATION) { crc = fw_upload_crc8(buffer, AbREQ_PAYLOAD_LEN + REQ_HEADER_LEN); if (crc != buffer[AbREQ_PAYLOAD_LEN + REQ_HEADER_LEN]) { LOG_ERR("Request %d, CRC check failed", request); return -EINVAL; } } else { LOG_ERR("Invalid request %d", request); } return 0; } static void fw_upload_send_ack(uint8_t ack) { if ((ack == V3_REQUEST_ACK) || (ack == V3_CRC_ERROR)) { /* prepare crc for 0x7A or 0x7C */ fw_upload.buffer[0] = ack; fw_upload.buffer[1] = fw_upload_crc8(fw_upload.buffer, 1); fw_upload_write_data(fw_upload.buffer, 2); LOG_DBG("ACK = %x, CRC = %x", ack, fw_upload.buffer[1]); } else if (ack == V3_TIMEOUT_ACK) { /* prepare crc for 0x7B */ fw_upload.buffer[0] = ack; sys_put_le32(fw_upload.offset, &fw_upload.buffer[1]); fw_upload.buffer[5] = fw_upload_crc8(fw_upload.buffer, 5); fw_upload_write_data(fw_upload.buffer, 6); LOG_DBG("ACK = %x, CRC = %x", ack, fw_upload.buffer[5]); } else { LOG_ERR("Invalid ack"); } } static int fw_upload_wait_req(bool secondary_speed) { int err; uint32_t len; uint8_t buffer[10]; buffer[0] = fw_upload.hdr_sig; if (fw_upload.hdr_sig == V3_HEADER_DATA_REQ) { /* CMD LINE: 0xA7 */ len = A6REQ_PAYLOAD_LEN + 1; } else if (fw_upload.hdr_sig == V3_START_INDICATION) { /* CMD LINE: 0xAB */ len = AbREQ_PAYLOAD_LEN + 1; } else { return -EINVAL; } err = fw_upload_read_data(&buffer[1], len); if (err < 0) { LOG_ERR("Fail to read req"); return err; } err = fw_upload_request_check_crc(buffer, fw_upload.hdr_sig); if (err != 0) { LOG_ERR("Fail to check CRC"); fw_upload_send_ack(V3_CRC_ERROR); return err; } if (fw_upload.hdr_sig == V3_HEADER_DATA_REQ) { fw_upload.length = sys_get_le16(&buffer[1]); fw_upload.offset = sys_get_le32(&buffer[3]); fw_upload.error = sys_get_le16(&buffer[7]); fw_upload.crc8 = buffer[9]; LOG_DBG("Req: %hhd, %hd, %d, %hd, %hhd", fw_upload.hdr_sig, fw_upload.length, fw_upload.offset, fw_upload.error, fw_upload.crc8); } else if (fw_upload.hdr_sig == V3_START_INDICATION) { uint16_t chip_id; fw_upload_send_ack(V3_REQUEST_ACK); chip_id = sys_get_le16(&buffer[1]); LOG_DBG("Indicate: %hhd, %hd, %hhd, %hhd", fw_upload.hdr_sig, chip_id, buffer[3], buffer[4]); if (!secondary_speed) { return -EINVAL; } } return 0; } static int fw_upload_change_timeout(void) { int err = 0; bool first = true; uint8_t retry = FW_UPLOAD_CHANGE_TIMEOUT_RETRY_COUNT; LOG_DBG(""); fw_upload_gen_crc32_table(); while (true) { err = fw_upload_wait_for_hdr_sig(); if (err) { continue; } if (fw_upload.version == VER1) { return 0; } else if (fw_upload.version == VER3) { err = fw_upload_wait_req(true); if (err) { continue; } if (fw_upload.length == 0) { continue; } if (fw_upload.error == 0) { if (first || (fw_upload.last_offset == fw_upload.offset)) { fw_upload_send_ack(V3_REQUEST_ACK); fw_upload_write_data(cmd7_hdrData, fw_upload.length > CMD_HDR_LEN ? CMD_HDR_LEN : fw_upload.length); fw_upload.last_offset = fw_upload.offset; first = false; } else { fw_upload.cmd7_change_timeout_len = CMD_HDR_LEN; fw_upload.wait_hdr_sig = false; return 0; } } else { if (retry > 0) { retry--; fw_upload_send_ack(V3_TIMEOUT_ACK); } else { LOG_ERR("Fail to change timeout with response err %d", fw_upload.error); return -ENOTSUP; } } } else { LOG_ERR("Unsupported version %d", fw_upload.version); return -ENOTSUP; } } return -EINVAL; } typedef struct { uint32_t uartBaudRate; uint32_t uartDivisio; uint32_t uartClkDivisor; } uart_baudrate_clkDiv_map_t; static const uart_baudrate_clkDiv_map_t clk_div_map[] = { {115200U, 16U, 0x0075F6FDU}, {1000000U, 2U, 0x00800000U}, {3000000U, 1U, 0x00C00000U}, }; static int fw_upload_change_speed_config(struct change_speed_config *config, uint32_t speed) { config->clk_div_addr = 0x7f00008fU; config->uart_clk_div_addr = 0x7f000090U; config->mcr_addr = 0x7f000091U; config->reinit_addr = 0x7f000092U; config->icr_addr = 0x7f000093U; config->fcr_addr = 0x7f000094U; config->mcr_val = 0x00000022U; config->reinit_val = 0x00000001U; config->icr_val = 0x000000c7U; config->fcr_val = 0x000000c7U; for (int i = 0; i < ARRAY_SIZE(clk_div_map); i++) { if (speed == clk_div_map[i].uartBaudRate) { config->clk_div_val = clk_div_map[i].uartClkDivisor; config->uart_clk_div_val = clk_div_map[i].uartDivisio; return 0; } } return -ENOTSUP; } static uint16_t fw_upload_wait_length(uint8_t flag) { uint8_t buffer[4]; uint16_t len; uint16_t len_comp; int err; uint8_t ack; err = fw_upload_read_data(buffer, sizeof(buffer)); if (err < 0) { return 0; } len = sys_get_le16(buffer); len_comp = sys_get_le16(buffer); if ((len ^ len_comp) == 0xFFFF) { LOG_DBG("remote asks for %d bytes", len); /* Successful. Send back the ack. */ if ((fw_upload.hdr_sig == V1_HEADER_DATA_REQ) || (fw_upload.hdr_sig == V1_START_INDICATION)) { ack = V1_REQUEST_ACK; fw_upload_write_data(&ack, 1); if (fw_upload.hdr_sig == V1_START_INDICATION) { /* Eliminated longjmp(resync, 1); returning restart status */ return (uint16_t)V1_START_INDICATION; } } } else { LOG_ERR("remote asks len %d bytes", len); LOG_ERR("remote asks len_comp %d bytes", len_comp); /* Failure due to mismatch. */ ack = 0xbf; fw_upload_write_data(&ack, 1); /* Start all over again. */ if (flag) { /* Eliminated longjmp(resync, 1); returning restart status */ return (uint16_t)V1_START_INDICATION; } len = 0; } return len; } static uint32_t fw_upload_get_payload_length(uint8_t *cmd) { uint32_t len; len = sys_get_le32(&cmd[8]); return len; } static void fw_upload_get_hdr_start(uint8_t *buffer) { int err; bool done = false; uint32_t count = 0; while (!done) { err = fw_upload_read_data(&fw_upload.hdr_sig, 1); if (err >= 0) { if (fw_upload.hdr_sig == V1_HEADER_DATA_REQ) { buffer[count++] = fw_upload.hdr_sig; done = true; LOG_DBG("Found header %x", fw_upload.hdr_sig); } } else { LOG_ERR("Fail to read HDR sig %d", err); return; } } err = fw_upload_read_data(&buffer[count], 4); if (err < 0) { LOG_ERR("Fail to read HDR payload %d", err); } } static int fw_upload_len_valid(uint8_t *buffer, uint16_t *length) { uint16_t len; uint16_t len_comp; len = sys_get_le16(&buffer[1]); len_comp = sys_get_le16(&buffer[3]); if ((len ^ len_comp) == 0xFFFFU) { *length = len; return 0; } else { return -EINVAL; } } static int fw_upload_get_last_5bytes(uint8_t *buffer) { int err; uint32_t payload_len; uint16_t len = 0; memset(fw_upload.last_5bytes_buffer, 0, sizeof(fw_upload.last_5bytes_buffer)); fw_upload_get_hdr_start(fw_upload.last_5bytes_buffer); err = fw_upload_len_valid(fw_upload.last_5bytes_buffer, &len); if (err >= 0) { LOG_DBG("Valid len %d", len); } else { LOG_ERR("Invalid HDR"); return err; } payload_len = fw_upload_get_payload_length(buffer); if ((len == CMD_HDR_LEN) || ((uint32_t)len == payload_len)) { LOG_DBG("Len valid"); fw_upload.is_error_case = false; return 0; } LOG_DBG("Len invalid"); fw_upload.is_error_case = true; return -EINVAL; } static void fw_upload_update_result(uint32_t payload_len, uint16_t *sending_len, bool *first_chunk_sent) { if (fw_upload.is_cmd7_req || fw_upload.is_entry_point_req) { *sending_len = CMD_HDR_LEN; *first_chunk_sent = true; } else { *sending_len = payload_len; *first_chunk_sent = false; if (*sending_len == CMD_HDR_LEN) { fw_upload.is_hdr_data = true; } } } static int fw_upload_write_hdr_and_payload(uint16_t len_to_send, uint8_t *buffer, bool new_speed) { int err; uint32_t payload_len; bool send_done = false; uint16_t sending_len = CMD_HDR_LEN; bool first_chunk_sent = false; LOG_DBG(""); payload_len = fw_upload_get_payload_length(buffer); while (!send_done) { if (sending_len == len_to_send) { if ((sending_len == CMD_HDR_LEN) && (!fw_upload.is_hdr_data)) { if ((first_chunk_sent == false) || (first_chunk_sent && fw_upload.is_error_case)) { LOG_DBG("Send first chunk: len %d", sending_len); fw_upload_write_data(buffer, sending_len); fw_upload_update_result(payload_len, &sending_len, &first_chunk_sent); } else { send_done = true; break; } } else { LOG_DBG("Send data: len %d", sending_len); if (sending_len) { fw_upload_write_data(&buffer[CMD_HDR_LEN], sending_len); first_chunk_sent = true; sending_len = CMD_HDR_LEN; fw_upload.is_hdr_data = false; if (new_speed) { return 0; } } else { LOG_DBG("Download Complete"); return 0; } } } else { if ((len_to_send & 0x01) == 0x01) { if (len_to_send == (CMD_HDR_LEN + 1)) { LOG_DBG("Resending first chunk..."); fw_upload_write_data(buffer, len_to_send - 1); sending_len = payload_len; first_chunk_sent = false; } else if (len_to_send == (payload_len + 1)) { LOG_DBG("Resending second chunk..."); fw_upload_write_data(&buffer[CMD_HDR_LEN], len_to_send - 1); sending_len = CMD_HDR_LEN; first_chunk_sent = true; } } else if (len_to_send == CMD_HDR_LEN) { LOG_DBG("Resending send buffer..."); fw_upload_write_data(buffer, len_to_send); sending_len = payload_len; first_chunk_sent = false; } else if (len_to_send == payload_len) { LOG_DBG("Resending second chunk..."); fw_upload_write_data(&buffer[CMD_HDR_LEN], len_to_send); sending_len = CMD_HDR_LEN; first_chunk_sent = true; } } err = fw_upload_get_last_5bytes(buffer); if (err < 0) { LOG_ERR("Fail to get response"); return err; } if (fw_upload_len_valid(fw_upload.last_5bytes_buffer, &len_to_send) == 0) { fw_upload_send_ack(V1_REQUEST_ACK); LOG_DBG("BOOT_HEADER_ACK 0x5a sent"); } } return len_to_send; } static int fw_upload_uart_reconfig(uint32_t speed, bool flow_control) { struct uart_config config; int err; config.baudrate = speed; config.data_bits = UART_CFG_DATA_BITS_8; config.flow_ctrl = flow_control ? UART_CFG_FLOW_CTRL_RTS_CTS : UART_CFG_FLOW_CTRL_NONE; config.parity = UART_CFG_PARITY_NONE; config.stop_bits = UART_CFG_STOP_BITS_1; uart_irq_rx_disable(uart_dev); uart_irq_tx_disable(uart_dev); fw_upload_read_to_clear(); err = uart_configure(uart_dev, &config); uart_irq_rx_enable(uart_dev); return err; } static int fw_upload_change_speed(uint8_t hdr) { int err; uint32_t hdr_len; bool load_payload = false; bool recovery = false; uint16_t len_to_send; uint32_t crc; err = fw_upload_change_speed_config( (struct change_speed_config *)fw_upload.change_speed_buffer, uart_dev_data.secondary_speed); if (err) { return err; } hdr_len = sizeof(fw_upload.change_speed_buffer); fw_upload_gen_crc32_table(); crc = sys_cpu_to_le32(hdr_len); memcpy(cmd5_hdrData + 8, &crc, 4); crc = fw_upload_update_crc32(0, (char *)cmd5_hdrData, 12); crc = sys_cpu_to_be32(crc); memcpy(cmd5_hdrData + 12, &crc, CRC32_LEN); crc = fw_upload_update_crc32(0, (char *)fw_upload.change_speed_buffer, (int)sizeof(struct change_speed_config)); crc = sys_cpu_to_be32(crc); memcpy(&fw_upload.change_speed_buffer[sizeof(struct change_speed_config)], &crc, CRC32_LEN); while (true) { err = fw_upload_wait_for_hdr_sig(); if (hdr && (err == 0)) { if (load_payload) { if (fw_upload.version == VER3) { fw_upload.change_speed_buffer_len = CMD_HDR_LEN + fw_upload.length; } return 0; } } else { if (recovery) { return -ETIME; } if (load_payload) { LOG_ERR("HDR cannot be received by using second speed. receovery " "speed"); err = fw_upload_uart_reconfig(uart_dev_data.primary_speed, uart_dev_data.primary_flowcontrol); if (err) { return err; } load_payload = false; recovery = true; continue; } } if (fw_upload.version == VER1) { len_to_send = fw_upload_wait_length(0); if (len_to_send == V1_START_INDICATION) { return -EINVAL; } else if (len_to_send == 0) { continue; } else if (len_to_send == CMD_HDR_LEN) { memcpy(fw_upload.send_buffer, cmd5_hdrData, CMD_HDR_LEN); memcpy(&fw_upload.send_buffer[CMD_HDR_LEN], fw_upload.change_speed_buffer, hdr_len); err = fw_upload_write_hdr_and_payload(len_to_send, fw_upload.send_buffer, true); if (err < 0) { return err; } LOG_DBG("Change speed to %d", uart_dev_data.secondary_speed); err = fw_upload_uart_reconfig(uart_dev_data.secondary_speed, uart_dev_data.secondary_flowcontrol); if (err) { return err; } load_payload = true; } else { fw_upload_write_data(fw_upload.change_speed_buffer, hdr_len); LOG_DBG("Change speed to %d", uart_dev_data.secondary_speed); err = fw_upload_uart_reconfig(uart_dev_data.secondary_speed, uart_dev_data.secondary_flowcontrol); if (err) { return err; } load_payload = true; } } else if (fw_upload.version == VER3) { err = fw_upload_wait_req(true); if (!(!hdr || (err == 0))) { continue; } if (fw_upload.length && (fw_upload.hdr_sig == V3_HEADER_DATA_REQ)) { if (fw_upload.error != 0) { fw_upload_send_ack(V3_TIMEOUT_ACK); continue; } fw_upload_send_ack(V3_REQUEST_ACK); hdr = true; if (fw_upload.length == CMD_HDR_LEN) { LOG_DBG("Send CMD5"); fw_upload_write_data(cmd5_hdrData, fw_upload.length); fw_upload.last_offset = fw_upload.offset; } else { LOG_DBG("Send UA RT config"); fw_upload_write_data(fw_upload.change_speed_buffer, fw_upload.length); LOG_DBG("Change speed to %d", uart_dev_data.secondary_speed); err = fw_upload_uart_reconfig( uart_dev_data.secondary_speed, uart_dev_data.secondary_flowcontrol); if (err) { return err; } load_payload = true; } } } } return 0; } static int fw_upload_v1_send_data(uint16_t len) { uint32_t cmd; uint32_t data_len; int ret_len; memset(fw_upload.send_buffer, 0, sizeof(fw_upload.send_buffer)); fw_upload.is_cmd7_req = false; fw_upload.is_entry_point_req = false; if ((fw_upload.fw_length - fw_upload.current_length) < len) { len = fw_upload.fw_length - fw_upload.current_length; } memcpy(fw_upload.send_buffer, fw_upload.fw + fw_upload.current_length, len); fw_upload.current_length += len; cmd = sys_get_le32(fw_upload.send_buffer); if (cmd == CMD7) { fw_upload.is_cmd7_req = true; data_len = 0; } else { data_len = fw_upload_get_payload_length(fw_upload.send_buffer); if ((data_len > (sizeof(fw_upload.send_buffer) - len)) || ((data_len + fw_upload.current_length) > fw_upload.fw_length)) { LOG_ERR("Invalid FW at %d/%d", fw_upload.current_length, fw_upload.fw_length); return -EINVAL; } memcpy(&fw_upload.send_buffer[len], fw_upload.fw + fw_upload.current_length, data_len); fw_upload.current_length += data_len; if ((fw_upload.current_length < fw_upload.fw_length) && ((cmd == CMD6) || (cmd == CMD4))) { fw_upload.is_entry_point_req = true; } } ret_len = fw_upload_write_hdr_and_payload(len, fw_upload.send_buffer, false); LOG_DBG("FW upload %d/%d", fw_upload.current_length, fw_upload.fw_length); return ret_len; } static int fw_upload_v3_send_data(void) { uint32_t start; LOG_DBG("Sending offset %d", fw_upload.offset); if (fw_upload.offset == fw_upload.last_offset) { LOG_WRN("Resending offset %d ...", fw_upload.offset); fw_upload_write_data(fw_upload.send_buffer, fw_upload.length); return fw_upload.length; } memset(fw_upload.send_buffer, 0, sizeof(fw_upload.send_buffer)); start = fw_upload.offset - fw_upload.cmd7_change_timeout_len - fw_upload.change_speed_buffer_len; if (start >= fw_upload.fw_length) { LOG_ERR("Invalid fw offset"); return -EINVAL; } if ((fw_upload.length + start) > fw_upload.fw_length) { fw_upload.length = fw_upload.fw_length - start; } memcpy(fw_upload.send_buffer, fw_upload.fw + start, fw_upload.length); fw_upload.current_length = start + fw_upload.length; fw_upload_write_data(fw_upload.send_buffer, fw_upload.length); fw_upload.last_offset = fw_upload.offset; return fw_upload.length; } static int fw_uploading(const uint8_t *fw, uint32_t fw_length) { int err; bool secondary_speed = false; uint16_t len_to_send; fw_upload.wait_hdr_sig = true; fw_upload.is_hdr_data = false; fw_upload.is_error_case = false; fw_upload.is_cmd7_req = false; fw_upload.is_entry_point_req = false; fw_upload.last_offset = 0xFFFFU; err = fw_upload_change_timeout(); LOG_DBG("Change timeout hdr flag %d (err %d)", fw_upload.wait_hdr_sig, err); if (err) { return err; } fw_upload_read_to_clear(); if (uart_dev_data.secondary_speed && (uart_dev_data.secondary_speed != uart_dev_data.primary_speed)) { LOG_DBG("Change speed to %d", uart_dev_data.secondary_speed); err = fw_upload_change_speed(fw_upload.wait_hdr_sig); if (err != 0) { LOG_ERR("Fail to change speed"); return err; } secondary_speed = true; } fw_upload.fw_length = fw_length; fw_upload.current_length = 0; fw_upload.fw = fw; while (true) { err = fw_upload_wait_for_hdr_sig(); if (secondary_speed && (err != 0)) { return -ETIME; } secondary_speed = false; if (fw_upload.version == VER1) { len_to_send = fw_upload_wait_length(true); if (len_to_send == V1_START_INDICATION) { continue; } while (len_to_send > 0) { len_to_send = fw_upload_v1_send_data(len_to_send); } if (fw_upload.current_length >= fw_upload.fw_length) { LOG_DBG("FW download done"); return 0; } LOG_ERR("FW download failed"); return len_to_send; } else if (fw_upload.version == VER3) { if (fw_upload.hdr_sig == V3_START_INDICATION) { fw_upload_wait_req(false); continue; } err = fw_upload_wait_req(false); if (err) { LOG_ERR("Fail to wait req"); return err; } if (fw_upload.length) { if (fw_upload.error == 0) { fw_upload_send_ack(V3_REQUEST_ACK); err = fw_upload_v3_send_data(); if (err < 0) { LOG_ERR("FW download failed"); return err; } } else { LOG_DBG("Error occurs %d", fw_upload.error); fw_upload_send_ack(V3_TIMEOUT_ACK); if (fw_upload.error & BT_MIC_FAIL_BIT) { fw_upload.change_speed_buffer_len = 0; fw_upload.current_length = 0; fw_upload.last_offset = 0; } } } else { if (fw_upload.error == 0) { fw_upload_send_ack(V3_REQUEST_ACK); LOG_DBG("FW download done"); return 0; } LOG_DBG("Error occurs %d", fw_upload.error); fw_upload_send_ack(V3_TIMEOUT_ACK); if (fw_upload.error & BT_MIC_FAIL_BIT) { fw_upload.change_speed_buffer_len = 0; fw_upload.current_length = 0; fw_upload.last_offset = 0; } } } else { return -ENOTSUP; } } return -EINVAL; } static void bt_nxp_ctlr_uart_isr(const struct device *unused, void *user_data) { int err = 0; int count = 0; ARG_UNUSED(unused); ARG_UNUSED(user_data); while (uart_irq_update(uart_dev) && uart_irq_is_pending(uart_dev)) { err = uart_poll_in(uart_dev, &fw_upload.rx.buffer[fw_upload.rx.head]); if (err >= 0) { fw_upload.rx.head++; fw_upload.rx.head = fw_upload.rx.head % sizeof(fw_upload.rx.buffer); count++; } } while (count > 0) { k_sem_give(&fw_upload.rx.sem); count--; } } static int bt_nxp_ctlr_init(void) { int err; uint32_t speed; bool flowcontrol_of_hci; if (!device_is_ready(uart_dev)) { return -ENODEV; } speed = DT_PROP(DT_INST_GPARENT(0), current_speed); uart_dev_data.primary_speed = DT_PROP_OR(DT_DRV_INST(0), fw_download_primary_speed, speed); uart_dev_data.secondary_speed = DT_PROP_OR(DT_DRV_INST(0), fw_download_secondary_speed, speed); flowcontrol_of_hci = (bool)DT_PROP_OR(DT_DRV_INST(0), hw_flow_control, false); uart_dev_data.primary_flowcontrol = (bool)DT_PROP_OR(DT_DRV_INST(0), fw_download_primary_flowcontrol, false); uart_dev_data.secondary_flowcontrol = (bool)DT_PROP_OR(DT_DRV_INST(0), fw_download_secondary_flowcontrol, false); #if DT_NODE_HAS_PROP(DT_DRV_INST(0), sdio_reset_gpios) || \ DT_NODE_HAS_PROP(DT_DRV_INST(0), w_disable_gpios) #if DT_NODE_HAS_PROP(DT_DRV_INST(0), sdio_reset_gpios) /* Check BT REG_ON gpio instance */ if (!gpio_is_ready_dt(&sdio_reset)) { LOG_ERR("Error: failed to configure sdio_reset %s pin %d", sdio_reset.port->name, sdio_reset.pin); return -EIO; } /* Configure sdio_reset as output */ err = gpio_pin_configure_dt(&sdio_reset, GPIO_OUTPUT); if (err) { LOG_ERR("Error %d: failed to configure sdio_reset %s pin %d", err, sdio_reset.port->name, sdio_reset.pin); return err; } err = gpio_pin_set_dt(&sdio_reset, 0); if (err) { return err; } #endif /* DT_NODE_HAS_PROP(DT_DRV_INST(0), sdio_reset_gpios) */ #if DT_NODE_HAS_PROP(DT_DRV_INST(0), w_disable_gpios) /* Check BT REG_ON gpio instance */ if (!gpio_is_ready_dt(&w_disable)) { LOG_ERR("Error: failed to configure w_disable %s pin %d", w_disable.port->name, w_disable.pin); return -EIO; } /* Configure w_disable as output */ err = gpio_pin_configure_dt(&w_disable, GPIO_OUTPUT); if (err) { LOG_ERR("Error %d: failed to configure w_disable %s pin %d", err, w_disable.port->name, w_disable.pin); return err; } err = gpio_pin_set_dt(&w_disable, 0); if (err) { return err; } #endif /* DT_NODE_HAS_PROP(DT_DRV_INST(0), w_disable_gpios) */ /* wait for reset done */ k_sleep(K_MSEC(100)); #if DT_NODE_HAS_PROP(DT_DRV_INST(0), sdio_reset_gpios) err = gpio_pin_set_dt(&sdio_reset, 1); if (err) { return err; } #endif /* DT_NODE_HAS_PROP(DT_DRV_INST(0), sdio_reset_gpios) */ #if DT_NODE_HAS_PROP(DT_DRV_INST(0), w_disable_gpios) err = gpio_pin_set_dt(&w_disable, 1); if (err) { return err; } #endif /* DT_NODE_HAS_PROP(DT_DRV_INST(0), w_disable_gpios) */ #endif uart_irq_rx_disable(uart_dev); uart_irq_tx_disable(uart_dev); fw_upload.rx.head = 0; fw_upload.rx.tail = 0; k_sem_init(&fw_upload.rx.sem, 0, sizeof(fw_upload.rx.buffer)); uart_irq_callback_set(uart_dev, bt_nxp_ctlr_uart_isr); made_table = false; err = fw_upload_uart_reconfig(uart_dev_data.primary_speed, uart_dev_data.primary_flowcontrol); if (err) { LOG_ERR("Fail to config uart"); return err; } uart_irq_rx_enable(uart_dev); err = fw_uploading(bt_fw_bin, bt_fw_bin_len); if (err) { LOG_ERR("Fail to upload firmware"); return err; } (void)fw_upload_uart_reconfig(speed, flowcontrol_of_hci); uart_irq_rx_disable(uart_dev); uart_irq_tx_disable(uart_dev); k_sleep(K_MSEC(CONFIG_BT_H4_NXP_CTLR_WAIT_TIME_AFTER_UPLOAD)); return 0; } int bt_hci_transport_setup(const struct device *dev) { int ret = 0; if (dev != uart_dev) { return -EINVAL; } if (!fw_upload.is_setup_done) { ret = bt_nxp_ctlr_init(); } return ret; } #define BT_HCI_VSC_BAUDRATE_UPDATE_LENGTH 4 #define BT_HCI_VSC_BAUDRATE_UPDATE_OPCODE BT_OP(BT_OGF_VS, 0x09) static int bt_hci_baudrate_update(const struct device *dev, uint32_t baudrate) { int err; struct net_buf *buf; buf = bt_hci_cmd_create(BT_HCI_VSC_BAUDRATE_UPDATE_OPCODE, BT_HCI_VSC_BAUDRATE_UPDATE_LENGTH); if (!buf) { LOG_ERR("Fail to allocate buffer"); return -ENOBUFS; } /* Add new baudrate to the buffer */ net_buf_add_le32(buf, baudrate); err = bt_hci_cmd_send_sync(BT_HCI_VSC_BAUDRATE_UPDATE_OPCODE, buf, NULL); if (err) { LOG_ERR("Fail to send baudrate update cmd"); return err; } return 0; } int bt_h4_vnd_setup(const struct device *dev) { int err; uint32_t default_speed; uint32_t operation_speed; bool flowcontrol_of_hci; if (dev != uart_dev) { return -EINVAL; } if (!device_is_ready(uart_dev)) { return -ENODEV; } default_speed = DT_PROP(DT_INST_GPARENT(0), current_speed); operation_speed = DT_PROP_OR(DT_DRV_INST(0), hci_operation_speed, default_speed); flowcontrol_of_hci = (bool)DT_PROP_OR(DT_DRV_INST(0), hw_flow_control, false); if (operation_speed == default_speed) { return 0; } if (!fw_upload.is_setup_done) { err = bt_hci_baudrate_update(dev, operation_speed); if (err) { return err; } /* BT waiting time after controller bandrate updated */ (void)k_msleep(CONFIG_BT_H4_NXP_CTLR_WAIT_TIME_AFTER_BAUDRATE_UPDATE); } err = fw_upload_uart_reconfig(operation_speed, flowcontrol_of_hci); if (err) { LOG_ERR("Fail to update uart bandrate"); return err; } fw_upload.is_setup_done = true; return 0; }