blob: b3cc0e18c6f2c3e5dbf47268d1e7cc191b4f6349 [file] [log] [blame]
/*
* Copyright 2024 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <stddef.h>
#include <zephyr/kernel.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/init.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/crc.h>
#include <string.h>
#define LOG_LEVEL CONFIG_BT_HCI_DRIVER_LOG_LEVEL
#include <zephyr/logging/log.h>
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;
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><offset><error><CRC8> */
len = A6REQ_PAYLOAD_LEN + 1;
} else if (fw_upload.hdr_sig == V3_START_INDICATION) {
/* CMD LINE: 0xAB <CHIP ID><SW loader REV 1 byte><CRC8> */
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_ERR("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_ERR("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);
speed = DT_PROP_OR(DT_DRV_INST(0), hci_operation_speed, 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)
{
if (dev != uart_dev) {
return -EINVAL;
}
return bt_nxp_ctlr_init();
}