/*
 * Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <inttypes.h>
#include <stddef.h>
#include <assert.h>
#include <string.h>

#include "cyw43_ll.h"
#include "cybt_shared_bus_driver.h"

// Bluetooth register corruption occurs if both wifi and bluetooth are fully utilised.
#define CYBT_CORRUPTION_TEST 1
#if CYBT_CORRUPTION_TEST
static cybt_fw_membuf_index_t last_buf_index;
static uint32_t last_host_ctrl_reg;
static uint32_t last_bt_ctrl_reg;

#include <stdio.h>

#endif

#ifndef NDEBUG
#define cybt_printf(format, args ...) printf(format,##args)
#else
#define cybt_printf(...)
#endif

#ifndef CYBT_DEBUG
#define CYBT_DEBUG 0
#endif

#if CYBT_DEBUG
#include <stdio.h>
#define cybt_debug(format, args ...) printf(format,##args)
#else
#define cybt_debug(format, ...) ((void)0)
#endif


/******************************************************************************
 *                                Constants
 ******************************************************************************/
#define BTFW_MEM_OFFSET             (0x19000000)

/* BIT0 => WLAN Power UP and BIT1=> WLAN Wake */
#define BT2WLAN_PWRUP_WAKE          (0x03)
#define BT2WLAN_PWRUP_ADDR          (0x640894)/* This address is specific to 43012B0 */

#define BTSDIO_OFFSET_HOST2BT_IN    (0x00002000)
#define BTSDIO_OFFSET_HOST2BT_OUT   (0x00002004)
#define BTSDIO_OFFSET_BT2HOST_IN    (0x00002008)
#define BTSDIO_OFFSET_BT2HOST_OUT   (0x0000200C)

#define H2B_BUF_ADDR                (buf_info.host2bt_buf_addr)
#define H2B_BUF_IN_ADDR             (buf_info.host2bt_in_addr)
#define H2B_BUF_OUT_ADDR            (buf_info.host2bt_out_addr)
#define B2H_BUF_ADDR                (buf_info.bt2host_buf_addr)
#define B2H_BUF_IN_ADDR             (buf_info.bt2host_in_addr)
#define B2H_BUF_OUT_ADDR            (buf_info.bt2host_out_addr)

static uint32_t wlan_ram_base_addr;
volatile uint32_t host_ctrl_cache_reg = 0;
#define WLAN_RAM_BASE_ADDR            (wlan_ram_base_addr)

// In wifi host driver these are all constants
#define BT_CTRL_REG_ADDR            ((uint32_t)0x18000c7c)
#define HOST_CTRL_REG_ADDR          ((uint32_t)0x18000d6c)
#define WLAN_RAM_BASE_REG_ADDR      ((uint32_t)0x18000d68)

typedef struct {
    uint32_t host2bt_buf_addr;
    uint32_t host2bt_in_addr;
    uint32_t host2bt_out_addr;
    uint32_t bt2host_buf_addr;
    uint32_t bt2host_in_addr;
    uint32_t bt2host_out_addr;
} cybt_fw_membuf_info_t;

cybt_fw_membuf_info_t buf_info;

#define BTFW_ADDR_MODE_UNKNOWN      (0)
#define BTFW_ADDR_MODE_EXTENDED     (1)
#define BTFW_ADDR_MODE_SEGMENT      (2)
#define BTFW_ADDR_MODE_LINEAR32     (3)

#define BTFW_HEX_LINE_TYPE_DATA                      (0)
#define BTFW_HEX_LINE_TYPE_END_OF_DATA               (1)
#define BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS  (2)
#define BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS          (4)
#define BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS    (5)

#define  BTSDIO_REG_DATA_VALID_BITMASK         (1 << 1)
#define  BTSDIO_REG_WAKE_BT_BITMASK            (1 << 17)
#define  BTSDIO_REG_SW_RDY_BITMASK             (1 << 24)

#define  BTSDIO_REG_BT_AWAKE_BITMASK           (1 << 8)
#define  BTSDIO_REG_FW_RDY_BITMASK             (1 << 24)

#define  BTSDIO_OFFSET_HOST_WRITE_BUF          (0)
#define  BTSDIO_OFFSET_HOST_READ_BUF           BTSDIO_FWBUF_SIZE

#define  BTSDIO_FWBUF_OPER_DELAY_US            (250)

#define ROUNDUP(x, a)               ((((x) + ((a) - 1)) / (a)) * (a))
#define ROUNDDN(x, a)               ((x) & ~((a) - 1))
#define ISALIGNED(a, x)             (((uint32_t)(a) & ((x) - 1)) == 0)

typedef struct cybt_fw_cb {
    const uint8_t *p_fw_mem_start;
    uint32_t fw_len;
    const uint8_t *p_next_line_start;
} cybt_fw_cb_t;

typedef struct hex_file_data {
    int addr_mode;
    uint16_t hi_addr;
    uint32_t dest_addr;
    uint8_t *p_ds;
} hex_file_data_t;

static cyw43_ll_t *cyw43_ll = NULL;

static cybt_result_t cybt_reg_write(uint32_t reg_addr, uint32_t value);
static cybt_result_t cybt_reg_read(uint32_t reg_addr, uint32_t *p_value);
static cybt_result_t cybt_mem_write(uint32_t mem_addr, const uint8_t *p_data, uint32_t data_len);
static cybt_result_t cybt_mem_read(uint32_t mem_addr, uint8_t *p_data, uint32_t data_len);

#if CYW43_USE_HEX_BTFW
const char *strnchr(const char *str, uint32_t len, int character) {
    const char *end = str + len;
    char c = (char)character;
    do {
        if (*str == c) {
            return str;
        }
    } while (++str <= end);
    return NULL;
}

static uint32_t cybt_fw_hex_read_line(cybt_fw_cb_t *p_btfw_cb,
    const char **p_line_start,
    int len
    ) {
    uint32_t str_len = 0;
    const char *p_str_end = NULL;

    if (NULL == p_btfw_cb || NULL == p_line_start) {
        return str_len;
    }

    *p_line_start = (const char *)p_btfw_cb->p_next_line_start;
    p_str_end = strnchr(*p_line_start, len, '\n');
    if (p_str_end == NULL) {
        return str_len;
    }

    str_len = (uint32_t)(p_str_end - *p_line_start);

    /* Advance file pointer past the string length */
    p_btfw_cb->p_next_line_start += str_len + 1;

    return str_len;
}

static inline uint8_t nibble_for_char(char c) {
    if ((c >= '0') && (c <= '9')) return c - '0';
    if ((c >= 'A') && (c <= 'F')) return c - 'A' + 10;
    return -1;
}

static inline uint8_t read_hex_byte(const char *str) {
    return nibble_for_char(*str) << 4 | nibble_for_char(*(str + 1));
}

static uint32_t read_hex(const char *str, int nchars) {
    uint32_t result = 0;
    assert(nchars > 0 && nchars <= 8 && nchars % 2 == 0);
    for(int pos = 0; pos < nchars; pos += 2) {
        result <<= 8;
        result |= read_hex_byte(str + pos);
    }
    return result;
}

static uint32_t cybt_fw_get_data(cybt_fw_cb_t *p_btfw_cb,
    hex_file_data_t *hfd
    ) {
    uint32_t line_len;
    uint16_t num_bytes, addr, data_pos, type, idx, octet;
    uint32_t abs_base_addr32 = 0;
    uint32_t data_len = 0;
    const char *p_line_start = NULL;

    if (NULL == p_btfw_cb || NULL == hfd->p_ds) {
        return data_len;
    }

    while (data_len == 0) {
        line_len = cybt_fw_hex_read_line(p_btfw_cb, &p_line_start, BTFW_MAX_STR_LEN);
        if (line_len == 0) {
            break;
        } else if (line_len > 9) {

            num_bytes = (uint16_t)read_hex(p_line_start + 1, 2);
            assert(num_bytes * 2 + 8 + 2 + 1 == line_len);

            int addr32 = read_hex(p_line_start + 3, 4);
            assert(addr32 <= 0xffff);
            addr = (uint16_t)addr32;
            type = (uint16_t)read_hex(p_line_start + 7, 2);
            assert(type <= 0xff);

            data_pos = 9;

            for (idx = 0; idx < num_bytes; idx++)
            {
                octet = (uint16_t)read_hex(p_line_start + data_pos, 2);
                hfd->p_ds[idx] = (uint8_t)(octet & 0x00FF);
                data_pos += 2;
            }

            if (type == BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS) {
                hfd->hi_addr = (hfd->p_ds[0] << 8) | hfd->p_ds[1];
                hfd->addr_mode = BTFW_ADDR_MODE_EXTENDED;
            } else if (type == BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS) {
                hfd->hi_addr = (hfd->p_ds[0] << 8) | hfd->p_ds[1];
                hfd->addr_mode = BTFW_ADDR_MODE_SEGMENT;
            } else if (type == BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS) {
                abs_base_addr32 = (hfd->p_ds[0] << 24) | (hfd->p_ds[1] << 16) |
                    (hfd->p_ds[2] << 8) | hfd->p_ds[3];
                hfd->addr_mode = BTFW_ADDR_MODE_LINEAR32;
            } else if (type == BTFW_HEX_LINE_TYPE_DATA) {
                hfd->dest_addr = addr;

                if (hfd->addr_mode == BTFW_ADDR_MODE_EXTENDED) {
                    hfd->dest_addr += (hfd->hi_addr << 16);
                } else if (hfd->addr_mode == BTFW_ADDR_MODE_SEGMENT) {
                    hfd->dest_addr += (hfd->hi_addr << 4);
                } else if (hfd->addr_mode == BTFW_ADDR_MODE_LINEAR32) {
                    hfd->dest_addr += abs_base_addr32;
                }

                data_len = num_bytes;
            }
        }
    }

    return data_len;
}
#else

static uint32_t cybt_fw_get_data(cybt_fw_cb_t *p_btfw_cb, hex_file_data_t *hfd) {
    uint32_t abs_base_addr32 = 0;
    while (true) {
        // 4 byte header
        uint8_t num_bytes = *(p_btfw_cb->p_next_line_start)++;
        uint16_t addr = *(p_btfw_cb->p_next_line_start)++ << 8;
        addr |= *(p_btfw_cb->p_next_line_start)++;
        uint8_t type = *(p_btfw_cb->p_next_line_start)++;

        // No data?
        if (num_bytes == 0) break;

        // Copy the data
        memcpy(hfd->p_ds, p_btfw_cb->p_next_line_start, num_bytes);
        p_btfw_cb->p_next_line_start += num_bytes;

        // Adjust address based on type
        if (type == BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS) {
            hfd->hi_addr = (hfd->p_ds[0] << 8) | hfd->p_ds[1];
            hfd->addr_mode = BTFW_ADDR_MODE_EXTENDED;
        } else if (type == BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS) {
            hfd->hi_addr = (hfd->p_ds[0] << 8) | hfd->p_ds[1];
            hfd->addr_mode = BTFW_ADDR_MODE_SEGMENT;
        } else if (type == BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS) {
            abs_base_addr32 = (hfd->p_ds[0] << 24) | (hfd->p_ds[1] << 16) |
                              (hfd->p_ds[2] << 8) | hfd->p_ds[3];
            hfd->addr_mode = BTFW_ADDR_MODE_LINEAR32;
        } else if (type == BTFW_HEX_LINE_TYPE_DATA) {
            hfd->dest_addr = addr;
            if (hfd->addr_mode == BTFW_ADDR_MODE_EXTENDED) {
                hfd->dest_addr += (hfd->hi_addr << 16);
            } else if (hfd->addr_mode == BTFW_ADDR_MODE_SEGMENT) {
                hfd->dest_addr += (hfd->hi_addr << 4);
            } else if (hfd->addr_mode == BTFW_ADDR_MODE_LINEAR32) {
                hfd->dest_addr += abs_base_addr32;
            }
            return num_bytes;
        }
    }
    return 0;
}

#endif

cybt_result_t cybt_fw_download(const uint8_t *p_bt_firmware,
                               uint32_t bt_firmware_len,
                               uint8_t *p_write_buf,
                               uint8_t *p_hex_buf) {
    cybt_fw_cb_t btfw_cb;
    hex_file_data_t hfd = {BTFW_ADDR_MODE_EXTENDED, 0, 0, NULL};
    uint8_t *p_mem_ptr;
    uint32_t data_len;

    if (cyw43_ll == NULL) {
        return CYBT_ERR_BADARG;
    }

    if (NULL == p_bt_firmware || 0 == bt_firmware_len || NULL == p_write_buf || NULL == p_hex_buf) {
        return CYBT_ERR_BADARG;
    }

    // BT firmware starts with length of version string including a null terminator
#if !CYW43_USE_HEX_BTFW
    uint8_t version_len = *p_bt_firmware;
    assert(*(p_bt_firmware + version_len) == 0);
#ifndef NDEBUG
    cybt_printf("BT FW download, version = %s\n", p_bt_firmware + 1);
#endif
    p_bt_firmware += version_len + 1; // skip over version
    p_bt_firmware += 1; // skip over record count
#endif

    p_mem_ptr = p_write_buf;
    if ((uint32_t) (uintptr_t) p_mem_ptr % BTFW_SD_ALIGN) {
        p_mem_ptr += (BTFW_SD_ALIGN - ((uint32_t) (uintptr_t) p_mem_ptr % BTFW_SD_ALIGN));
    }

    hfd.p_ds = p_hex_buf;

    btfw_cb.p_fw_mem_start = p_bt_firmware;
    btfw_cb.fw_len = bt_firmware_len;
    btfw_cb.p_next_line_start = p_bt_firmware;

    cybt_reg_write(BTFW_MEM_OFFSET + BT2WLAN_PWRUP_ADDR, BT2WLAN_PWRUP_WAKE);

    while ((data_len = cybt_fw_get_data(&btfw_cb, &hfd)) > 0) {
        uint32_t fwmem_start_addr, fwmem_start_data, fwmem_end_addr, fwmem_end_data;
        uint32_t write_data_len, idx, pad;

        fwmem_start_addr = BTFW_MEM_OFFSET + hfd.dest_addr;
        write_data_len = 0;

        /**
         * Make sure the start address is 4 byte aligned to avoid alignment issues
         * with SD host controllers
         */
        if (!ISALIGNED(fwmem_start_addr, 4)) {
            pad = fwmem_start_addr % 4;
            fwmem_start_addr = ROUNDDN(fwmem_start_addr, 4);

            cybt_mem_read(fwmem_start_addr, (uint8_t *) &fwmem_start_data, sizeof(uint32_t));

            for (idx = 0; idx < pad; idx++, write_data_len++) {
                p_mem_ptr[write_data_len] = (uint8_t) ((uint8_t *) &fwmem_start_data)[idx];
            }
        }
        memcpy(&(p_mem_ptr[write_data_len]), hfd.p_ds, data_len);
        write_data_len += data_len;

        /**
         * Make sure the length is multiple of 4bytes to avoid alignment issues
         * with SD host controllers
         */
        fwmem_end_addr = fwmem_start_addr + write_data_len;
        if (!ISALIGNED(fwmem_end_addr, 4)) {
            cybt_mem_read(ROUNDDN(fwmem_end_addr, 4), (uint8_t *) &fwmem_end_data, sizeof(uint32_t));
            for (idx = (fwmem_end_addr % 4); idx < 4; idx++, write_data_len++) {
                p_mem_ptr[write_data_len] = (uint8_t) ((uint8_t *) &fwmem_end_data)[idx];
            }
        }

        /*
         *  write ram
         */
        if (((fwmem_start_addr & 0xFFF) + write_data_len) <= 0x1000) {
            cybt_mem_write(fwmem_start_addr, p_mem_ptr, write_data_len);
        } else {
            uint32_t first_write_len = 0x1000 - (fwmem_start_addr & 0xFFF);
            cybt_mem_write(fwmem_start_addr, p_mem_ptr, first_write_len);
            cybt_mem_write(fwmem_start_addr + first_write_len,
                           p_mem_ptr + first_write_len,
                           write_data_len - first_write_len);
        }
    }

    return CYBT_SUCCESS;
}

cybt_result_t cybt_set_host_ready(void) {
    uint32_t reg_val;

    cybt_reg_read(HOST_CTRL_REG_ADDR, &reg_val);
    reg_val |= BTSDIO_REG_SW_RDY_BITMASK;
    cybt_reg_write(HOST_CTRL_REG_ADDR, reg_val);
#if CYBT_CORRUPTION_TEST
    last_host_ctrl_reg = reg_val;
#endif
    return CYBT_SUCCESS;
}

cybt_result_t cybt_toggle_bt_intr(void) {
    uint32_t reg_val, new_val;

    cybt_reg_read(HOST_CTRL_REG_ADDR, &reg_val);
#if CYBT_CORRUPTION_TEST
    if ((reg_val & ~(BTSDIO_REG_SW_RDY_BITMASK | BTSDIO_REG_WAKE_BT_BITMASK | BTSDIO_REG_DATA_VALID_BITMASK)) != 0) {
        cybt_printf("cybt_toggle_bt_intr read HOST_CTRL_REG_ADDR as 0x%08lx\n", reg_val);
        cybt_debug_dump();
        panic("cyw43 btsdio register corruption");
    }
    assert((reg_val & ~(BTSDIO_REG_SW_RDY_BITMASK | BTSDIO_REG_WAKE_BT_BITMASK | BTSDIO_REG_DATA_VALID_BITMASK)) == 0);
#endif
    new_val = reg_val ^ BTSDIO_REG_DATA_VALID_BITMASK;
    cybt_reg_write(HOST_CTRL_REG_ADDR, new_val);
#if CYBT_CORRUPTION_TEST
    last_host_ctrl_reg = new_val;
#endif
    return CYBT_SUCCESS;
}

cybt_result_t cybt_set_bt_intr(int value) {
    uint32_t reg_val, new_val;

    cybt_reg_read(HOST_CTRL_REG_ADDR, &reg_val);
    if (value) {
        new_val = reg_val | BTSDIO_REG_DATA_VALID_BITMASK;
    } else {
        new_val = reg_val & ~BTSDIO_REG_DATA_VALID_BITMASK;
    }
    cybt_reg_write(HOST_CTRL_REG_ADDR, new_val);
#if CYBT_CORRUPTION_TEST
    last_host_ctrl_reg = new_val;
#endif
    return CYBT_SUCCESS;
}

int cybt_ready(void) {
    uint32_t reg_val;
    cybt_reg_read(BT_CTRL_REG_ADDR, &reg_val);
#if CYBT_CORRUPTION_TEST
    if (reg_val & BTSDIO_REG_FW_RDY_BITMASK) {
        last_bt_ctrl_reg = reg_val;
    }
#endif
    return (reg_val & BTSDIO_REG_FW_RDY_BITMASK) ? 1 : 0;
}

int cybt_awake(void) {
    uint32_t reg_val;
    cybt_reg_read(BT_CTRL_REG_ADDR, &reg_val);
#if CYBT_CORRUPTION_TEST
    if (reg_val & BTSDIO_REG_BT_AWAKE_BITMASK) {
        last_bt_ctrl_reg = reg_val;
    }
#endif
    return (reg_val & BTSDIO_REG_BT_AWAKE_BITMASK) ? 1 : 0;
}

cybt_result_t cybt_set_bt_awake(int value) {
    uint32_t reg_val_before;
    cybt_reg_read(HOST_CTRL_REG_ADDR, &reg_val_before);

    uint32_t reg_val_after = reg_val_before;
    if (value)
        reg_val_after |= BTSDIO_REG_WAKE_BT_BITMASK;
    else
        reg_val_after &= ~BTSDIO_REG_WAKE_BT_BITMASK;

    if (reg_val_before != reg_val_after) {
        cybt_reg_write(HOST_CTRL_REG_ADDR, reg_val_after);
#if CYBT_CORRUPTION_TEST
        last_host_ctrl_reg = reg_val_after;
#endif
    }
    return 0;
}

void cybt_debug_dump(void) {
#if CYBT_CORRUPTION_TEST
    uint32_t reg_val = 0;
    cybt_fw_membuf_index_t buf_index;

    cybt_printf("WLAN_RAM_BASE_ADDR: 0x%08lx\n", WLAN_RAM_BASE_ADDR);
    cybt_printf("H2B_BUF_ADDR: 0x%08lx\n", H2B_BUF_ADDR);
    cybt_printf("B2H_BUF_ADDR: 0x%08lx\n", B2H_BUF_ADDR);

    cybt_reg_read(H2B_BUF_IN_ADDR, &buf_index.host2bt_in_val);
    cybt_printf("H2B_BUF_IN_ADDR: 0x%08lx = 0x%08lx (last 0x%08lx)\n", H2B_BUF_IN_ADDR, buf_index.host2bt_in_val,
                last_buf_index.host2bt_in_val);

    cybt_reg_read(H2B_BUF_OUT_ADDR, &buf_index.host2bt_out_val);
    cybt_printf("H2B_BUF_OUT_ADDR: 0x%08lx = 0x%08lx (last 0x%08lx)\n", H2B_BUF_OUT_ADDR, buf_index.host2bt_out_val,
                last_buf_index.host2bt_out_val);

    cybt_reg_read(B2H_BUF_IN_ADDR, &buf_index.bt2host_in_val);
    cybt_printf("B2H_BUF_IN_ADDR: 0x%08lx = 0x%08lx (last 0x%08lx)\n", B2H_BUF_IN_ADDR, buf_index.bt2host_in_val,
                last_buf_index.bt2host_in_val);

    cybt_reg_read(B2H_BUF_OUT_ADDR, &buf_index.bt2host_out_val);
    cybt_printf("B2H_BUF_OUT_ADDR: 0x%08lx = 0x%08lx (last 0x%08lx)\n", B2H_BUF_OUT_ADDR, buf_index.bt2host_out_val,
                last_buf_index.bt2host_out_val);

    cybt_reg_read(HOST_CTRL_REG_ADDR, &reg_val);
    cybt_printf("HOST_CTRL_REG_ADDR: 0x%08lx = 0x%08lx (last 0x%08lx)\n", HOST_CTRL_REG_ADDR, reg_val,
                last_host_ctrl_reg);

    cybt_reg_read(BT_CTRL_REG_ADDR, &reg_val);
    cybt_printf("BT_CTRL_REG_ADDR: 0x%08lx = 0x%08lx (last 0x%08lx)\n", BT_CTRL_REG_ADDR, reg_val, last_bt_ctrl_reg);
#endif
}

cybt_result_t cybt_get_bt_buf_index(cybt_fw_membuf_index_t *p_buf_index) {
    uint32_t buf[4];

    cybt_mem_read(H2B_BUF_IN_ADDR, (uint8_t *) buf, sizeof(buf));

    p_buf_index->host2bt_in_val = buf[0];
    p_buf_index->host2bt_out_val = buf[1];
    p_buf_index->bt2host_in_val = buf[2];
    p_buf_index->bt2host_out_val = buf[3];

    cybt_debug("cybt_get_bt_buf_index: h2b_in = 0x%08lx, h2b_out = 0x%08lx, b2h_in = 0x%08lx, b2h_out = 0x%08lx\n",
               p_buf_index->host2bt_in_val,
               p_buf_index->host2bt_out_val,
               p_buf_index->bt2host_in_val,
               p_buf_index->bt2host_out_val);

#if CYBT_CORRUPTION_TEST
    if (p_buf_index->host2bt_in_val >= BTSDIO_FWBUF_SIZE || p_buf_index->host2bt_out_val >= BTSDIO_FWBUF_SIZE ||
        p_buf_index->bt2host_in_val >= BTSDIO_FWBUF_SIZE || p_buf_index->bt2host_out_val >= BTSDIO_FWBUF_SIZE) {
        cybt_printf("cybt_get_bt_buf_index invalid buffer value\n");
        cybt_debug_dump();
    } else {
        memcpy((uint8_t *) &last_buf_index, (uint8_t *) p_buf_index, sizeof(cybt_fw_membuf_index_t));
    }
#endif

    assert(p_buf_index->host2bt_in_val < BTSDIO_FWBUF_SIZE);
    assert(p_buf_index->host2bt_out_val < BTSDIO_FWBUF_SIZE);
    assert(p_buf_index->bt2host_in_val < BTSDIO_FWBUF_SIZE);
    assert(p_buf_index->bt2host_out_val < BTSDIO_FWBUF_SIZE);

    return CYBT_SUCCESS;
}

static cybt_result_t cybt_reg_write(uint32_t reg_addr, uint32_t value) {
    cybt_debug("cybt_reg_write 0x%08lx 0x%08lx\n", reg_addr, value);
    cyw43_ll_write_backplane_reg(cyw43_ll, reg_addr, value);
    if (reg_addr == HOST_CTRL_REG_ADDR) {
        host_ctrl_cache_reg = value;
    }
    return CYBT_SUCCESS;
}

static cybt_result_t cybt_reg_read(uint32_t reg_addr, uint32_t *p_value) {
    if (reg_addr == HOST_CTRL_REG_ADDR) {
        *p_value = host_ctrl_cache_reg;
        return CYBT_SUCCESS;
    }
    *p_value = cyw43_ll_read_backplane_reg(cyw43_ll, reg_addr);
    cybt_debug("cybt_reg_read 0x%08lx == 0x%08lx\n", reg_addr, *p_value);
    return CYBT_SUCCESS;
}

#if CYBT_DEBUG
static void dump_bytes(const uint8_t *bptr, uint32_t len) {
    unsigned int i = 0;

    for (i = 0; i < len; i++) {
        if ((i & 0x07) == 0) {
            cybt_debug("\n        ");
        }
        cybt_debug("0x%02x", bptr[i]);
        if (i != (len - 1)) {
            cybt_debug(", ");
        }
    }
    cybt_debug("\n");
}
#define DUMP_BYTES dump_bytes
#else
#define DUMP_BYTES(...)
#endif

static cybt_result_t cybt_mem_write(uint32_t mem_addr, const uint8_t *p_data, uint32_t data_len) {
    cybt_debug("cybt_mem_write addr 0x%08lx len %ld\n", mem_addr, data_len);
    do {
        uint32_t transfer_size = (data_len > CYW43_BUS_MAX_BLOCK_SIZE) ? CYW43_BUS_MAX_BLOCK_SIZE : data_len;
        if ((mem_addr & 0xFFF) + transfer_size > 0x1000) {
            transfer_size = 0x1000 - (mem_addr & 0xFFF);
        }
        cyw43_ll_write_backplane_mem(cyw43_ll, mem_addr, transfer_size, p_data);
        cybt_debug("  write_mem addr 0x%08lx len %ld\n", mem_addr, transfer_size);
        DUMP_BYTES(p_data, transfer_size);
        data_len -= transfer_size;
        p_data += transfer_size;
        mem_addr += transfer_size;
    } while (data_len > 0);
    return CYBT_SUCCESS;
}

static cybt_result_t cybt_mem_read(uint32_t mem_addr, uint8_t *p_data, uint32_t data_len) {
    assert(data_len >= 4);
    cybt_debug("cybt_mem_read addr 0x%08lx len %ld\n", mem_addr, data_len);
    do {
        uint32_t transfer_size = (data_len > CYW43_BUS_MAX_BLOCK_SIZE) ? CYW43_BUS_MAX_BLOCK_SIZE : data_len;
        if ((mem_addr & 0xFFF) + transfer_size > 0x1000) {
            transfer_size = 0x1000 - (mem_addr & 0xFFF);
        }
        cyw43_ll_read_backplane_mem(cyw43_ll, mem_addr, transfer_size, p_data);
        cybt_debug("  read_mem addr 0x%08lx len %ld\n", mem_addr, transfer_size);
        DUMP_BYTES(p_data, transfer_size);
        data_len -= transfer_size;
        p_data += transfer_size;
        mem_addr += transfer_size;
    } while (data_len > 0);
    return CYBT_SUCCESS;
}

static uint32_t cybt_get_addr(cybt_addr_idx_t addr_idx) {
    uint32_t addr = 0;

    switch (addr_idx) {
        case H2B_BUF_ADDR_IDX:
            addr = H2B_BUF_ADDR;
            break;
        case H2B_BUF_IN_ADDR_IDX:
            addr = H2B_BUF_IN_ADDR;
            break;
        case H2B_BUF_OUT_ADDR_IDX:
            addr = H2B_BUF_OUT_ADDR;
            break;
        case B2H_BUF_ADDR_IDX:
            addr = B2H_BUF_ADDR;
            break;
        case B2H_BUF_IN_ADDR_IDX:
            addr = B2H_BUF_IN_ADDR;
            break;
        case B2H_BUF_OUT_ADDR_IDX:
            addr = B2H_BUF_OUT_ADDR;
            break;
        default:
            assert(0);
            break;
    }
    return addr;
}

cybt_result_t cybt_reg_write_idx(cybt_addr_idx_t reg_idx, uint32_t value) {
    assert(reg_idx == H2B_BUF_IN_ADDR_IDX || reg_idx == B2H_BUF_OUT_ADDR_IDX);
    assert(value < BTSDIO_FWBUF_SIZE); // writing out of bounds register value?
    if ((reg_idx != H2B_BUF_IN_ADDR_IDX && reg_idx != B2H_BUF_OUT_ADDR_IDX) || value >= BTSDIO_FWBUF_SIZE) {
        assert(0);
        return CYBT_ERR_BADARG;
    }
    uint32_t reg_addr = cybt_get_addr(reg_idx);
    return cybt_reg_write(reg_addr, value);
}

cybt_result_t cybt_mem_write_idx(cybt_addr_idx_t mem_idx, uint32_t offset, const uint8_t *p_data, uint32_t data_len) {
    assert(mem_idx == H2B_BUF_ADDR_IDX); // caller should only be writing to here?
    assert(offset + data_len <= BTSDIO_FWBUF_SIZE); // writing out of bounds?
    if (mem_idx != H2B_BUF_ADDR_IDX || (offset + data_len) > BTSDIO_FWBUF_SIZE) {
        assert(0);
        return CYBT_ERR_BADARG;
    }
    if (!ISALIGNED(p_data, 4)) {
        return CYBT_ERR_BADARG;
    }
    uint32_t mem_addr = cybt_get_addr(mem_idx) + offset;
    return cybt_mem_write(mem_addr, p_data, data_len);
}

cybt_result_t cybt_mem_read_idx(cybt_addr_idx_t mem_idx, uint32_t offset, uint8_t *p_data, uint32_t data_len) {
    assert(mem_idx == B2H_BUF_ADDR_IDX); // caller should only be reading from here?
    assert(offset + data_len <= BTSDIO_FWBUF_SIZE); // reading out of bounds?
    if (mem_idx != B2H_BUF_ADDR_IDX || (offset + data_len) > BTSDIO_FWBUF_SIZE) {
        assert(0);
        return CYBT_ERR_BADARG;
    }
    uint32_t mem_addr = cybt_get_addr(mem_idx) + offset;
    return cybt_mem_read(mem_addr, p_data, data_len);
}

cybt_result_t cybt_init_buffer(void) {
    int result;
    result = cybt_reg_read(WLAN_RAM_BASE_REG_ADDR, &WLAN_RAM_BASE_ADDR);
    if (CYBT_SUCCESS != result) {
        return result;
    }

    cybt_debug("hci_open(): btfw ram base = 0x%" PRIx32 "\n", WLAN_RAM_BASE_ADDR);

    // Fill in reg info
    // Data buffers
    H2B_BUF_ADDR = WLAN_RAM_BASE_ADDR + BTSDIO_OFFSET_HOST_WRITE_BUF;
    B2H_BUF_ADDR = WLAN_RAM_BASE_ADDR + BTSDIO_OFFSET_HOST_READ_BUF;

    // circular buffer indexes
    H2B_BUF_IN_ADDR = WLAN_RAM_BASE_ADDR + BTSDIO_OFFSET_HOST2BT_IN;
    H2B_BUF_OUT_ADDR = WLAN_RAM_BASE_ADDR + BTSDIO_OFFSET_HOST2BT_OUT;
    B2H_BUF_IN_ADDR = WLAN_RAM_BASE_ADDR + BTSDIO_OFFSET_BT2HOST_IN;
    B2H_BUF_OUT_ADDR = WLAN_RAM_BASE_ADDR + BTSDIO_OFFSET_BT2HOST_OUT;

    uint32_t reg_val = 0;
    cybt_reg_write(H2B_BUF_IN_ADDR, reg_val);
    cybt_reg_write(H2B_BUF_OUT_ADDR, reg_val);
    cybt_reg_write(B2H_BUF_IN_ADDR, reg_val);
    cybt_reg_write(B2H_BUF_OUT_ADDR, reg_val);

    return CYBT_SUCCESS;
}

void cybt_sharedbus_driver_init(cyw43_ll_t *driver) {
    cyw43_ll = driver;
}
