| /* |
| * Copyright (c) 2023 Ambiq Micro Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * @brief Ambiq Apollox Blue SoC extended driver for SPI based HCI. |
| */ |
| |
| #define DT_DRV_COMPAT ambiq_bt_hci_spi |
| |
| #include <zephyr/init.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/drivers/bluetooth/hci_driver.h> |
| #include <zephyr/bluetooth/hci.h> |
| #include <zephyr/bluetooth/hci_raw.h> |
| |
| #define LOG_LEVEL CONFIG_BT_HCI_DRIVER_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(bt_apollox_driver); |
| |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/clock_control/clock_control_ambiq.h> |
| |
| #include "apollox_blue.h" |
| #include "am_devices_cooper.h" |
| |
| #define HCI_SPI_NODE DT_COMPAT_GET_ANY_STATUS_OKAY(ambiq_bt_hci_spi) |
| #define SPI_DEV_NODE DT_BUS(HCI_SPI_NODE) |
| #define CLK_32M_NODE DT_NODELABEL(xo32m) |
| #define CLK_32K_NODE DT_NODELABEL(xo32k) |
| |
| /* Command/response for SPI operation */ |
| #define SPI_WRITE 0x80 |
| #define SPI_READ 0x04 |
| #define READY_BYTE0 0x68 |
| #define READY_BYTE1 0xA8 |
| |
| /* Maximum attempts of SPI write */ |
| #define SPI_WRITE_TIMEOUT 200 |
| |
| #define SPI_MAX_RX_MSG_LEN 258 |
| |
| static const struct gpio_dt_spec irq_gpio = GPIO_DT_SPEC_GET(HCI_SPI_NODE, irq_gpios); |
| static const struct gpio_dt_spec rst_gpio = GPIO_DT_SPEC_GET(HCI_SPI_NODE, reset_gpios); |
| static const struct gpio_dt_spec cs_gpio = GPIO_DT_SPEC_GET(SPI_DEV_NODE, cs_gpios); |
| static const struct gpio_dt_spec clkreq_gpio = GPIO_DT_SPEC_GET(HCI_SPI_NODE, clkreq_gpios); |
| |
| static struct gpio_callback irq_gpio_cb; |
| static struct gpio_callback clkreq_gpio_cb; |
| |
| static const struct device *clk32m_dev = DEVICE_DT_GET(CLK_32M_NODE); |
| static const struct device *clk32k_dev = DEVICE_DT_GET(CLK_32K_NODE); |
| |
| extern void bt_packet_irq_isr(const struct device *unused1, struct gpio_callback *unused2, |
| uint32_t unused3); |
| |
| static bool irq_pin_state(void) |
| { |
| int pin_state; |
| |
| pin_state = gpio_pin_get_dt(&irq_gpio); |
| LOG_DBG("IRQ Pin: %d", pin_state); |
| return pin_state > 0; |
| } |
| |
| static bool clkreq_pin_state(void) |
| { |
| int pin_state; |
| |
| pin_state = gpio_pin_get_dt(&clkreq_gpio); |
| LOG_DBG("CLKREQ Pin: %d", pin_state); |
| return pin_state > 0; |
| } |
| |
| static void bt_clkreq_isr(const struct device *unused1, struct gpio_callback *unused2, |
| uint32_t unused3) |
| { |
| if (clkreq_pin_state()) { |
| /* Enable XO32MHz */ |
| clock_control_on(clk32m_dev, |
| (clock_control_subsys_t)CLOCK_CONTROL_AMBIQ_TYPE_HFXTAL_BLE); |
| gpio_pin_interrupt_configure_dt(&clkreq_gpio, GPIO_INT_EDGE_FALLING); |
| } else { |
| /* Disable XO32MHz */ |
| clock_control_off(clk32m_dev, |
| (clock_control_subsys_t)CLOCK_CONTROL_AMBIQ_TYPE_HFXTAL_BLE); |
| gpio_pin_interrupt_configure_dt(&clkreq_gpio, GPIO_INT_EDGE_RISING); |
| } |
| } |
| |
| static void bt_apollo_controller_ready_wait(void) |
| { |
| /* The CS pin is used to wake up the controller as well. If the controller is not ready |
| * to receive the SPI packet, need to inactivate the CS at first and reconfigure the pin |
| * to CS function again before next sending attempt. |
| */ |
| gpio_pin_configure_dt(&cs_gpio, GPIO_OUTPUT_INACTIVE); |
| k_busy_wait(200); |
| PINCTRL_DT_DEFINE(SPI_DEV_NODE); |
| pinctrl_apply_state(PINCTRL_DT_DEV_CONFIG_GET(SPI_DEV_NODE), PINCTRL_STATE_DEFAULT); |
| k_busy_wait(2000); |
| } |
| |
| static void bt_apollo_controller_reset(void) |
| { |
| /* Reset the controller*/ |
| gpio_pin_set_dt(&rst_gpio, 1); |
| |
| /* Take controller out of reset */ |
| k_sleep(K_MSEC(10)); |
| gpio_pin_set_dt(&rst_gpio, 0); |
| |
| /* Give the controller some time to boot */ |
| k_sleep(K_MSEC(500)); |
| } |
| |
| int bt_apollo_spi_send(uint8_t *data, uint16_t len, bt_spi_transceive_fun transceive) |
| { |
| int ret; |
| uint8_t command[1] = {SPI_WRITE}; |
| uint8_t response[2] = {0, 0}; |
| uint16_t fail_count = 0; |
| |
| do { |
| /* Check if the controller is ready to receive the HCI packets. */ |
| ret = transceive(command, 1, response, 2); |
| if ((response[0] != READY_BYTE0) || (response[1] != READY_BYTE1) || ret) { |
| bt_apollo_controller_ready_wait(); |
| } else { |
| /* Transmit the message */ |
| ret = transceive(data, len, NULL, 0); |
| if (ret) { |
| LOG_ERR("SPI write error %d", ret); |
| } |
| break; |
| } |
| } while (fail_count++ < SPI_WRITE_TIMEOUT); |
| |
| return ret; |
| } |
| |
| int bt_apollo_spi_rcv(uint8_t *data, uint16_t *len, bt_spi_transceive_fun transceive) |
| { |
| int ret; |
| uint8_t command[1] = {SPI_READ}; |
| uint8_t response[2] = {0, 0}; |
| uint16_t read_size = 0; |
| |
| do { |
| /* Skip if the IRQ pin is not in high state */ |
| if (!irq_pin_state()) { |
| ret = -1; |
| break; |
| } |
| |
| /* Check the available packet bytes */ |
| ret = transceive(command, 1, response, 2); |
| if (ret) { |
| break; |
| } |
| |
| /* Check if the read size is acceptable */ |
| read_size = (uint16_t)(response[0] | response[1] << 8); |
| if ((read_size == 0) || (read_size > SPI_MAX_RX_MSG_LEN)) { |
| ret = -1; |
| break; |
| } |
| |
| *len = read_size; |
| |
| /* Read the HCI data from controller */ |
| ret = transceive(NULL, 0, data, read_size); |
| |
| if (ret) { |
| LOG_ERR("SPI read error %d", ret); |
| break; |
| } |
| } while (0); |
| |
| return ret; |
| } |
| |
| bool bt_apollo_vnd_rcv_ongoing(uint8_t *data, uint16_t len) |
| { |
| /* The vendor specific handshake command/response is incompatible with |
| * standard Bluetooth HCI format, need to handle the received packets |
| * specifically. |
| */ |
| if (am_devices_cooper_get_initialize_state() != AM_DEVICES_COOPER_STATE_INITIALIZED) { |
| am_devices_cooper_handshake_recv(data, len); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| int bt_hci_transport_setup(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| |
| int ret; |
| |
| /* Configure the XO32MHz and XO32kHz clocks.*/ |
| clock_control_configure(clk32k_dev, NULL, NULL); |
| clock_control_configure(clk32m_dev, NULL, NULL); |
| |
| /* Enable XO32kHz for Controller */ |
| clock_control_on(clk32k_dev, (clock_control_subsys_t)CLOCK_CONTROL_AMBIQ_TYPE_LFXTAL); |
| |
| /* Enable XO32MHz for Controller */ |
| clock_control_on(clk32m_dev, (clock_control_subsys_t)CLOCK_CONTROL_AMBIQ_TYPE_HFXTAL_BLE); |
| |
| /* Configure RST pin and hold BLE in Reset */ |
| ret = gpio_pin_configure_dt(&rst_gpio, GPIO_OUTPUT_ACTIVE); |
| if (ret) { |
| return ret; |
| } |
| |
| /* Configure IRQ pin and register the callback */ |
| ret = gpio_pin_configure_dt(&irq_gpio, GPIO_INPUT); |
| if (ret) { |
| return ret; |
| } |
| |
| gpio_init_callback(&irq_gpio_cb, bt_packet_irq_isr, BIT(irq_gpio.pin)); |
| ret = gpio_add_callback(irq_gpio.port, &irq_gpio_cb); |
| if (ret) { |
| return ret; |
| } |
| |
| /* Configure CLKREQ pin and register the callback */ |
| ret = gpio_pin_configure_dt(&clkreq_gpio, GPIO_INPUT); |
| if (ret) { |
| return ret; |
| } |
| |
| gpio_init_callback(&clkreq_gpio_cb, bt_clkreq_isr, BIT(clkreq_gpio.pin)); |
| ret = gpio_add_callback(clkreq_gpio.port, &clkreq_gpio_cb); |
| if (ret) { |
| return ret; |
| } |
| |
| /* Configure the interrupt edge for CLKREQ pin */ |
| gpio_pin_interrupt_configure_dt(&clkreq_gpio, GPIO_INT_EDGE_RISING); |
| |
| /* Take controller out of reset */ |
| k_sleep(K_MSEC(10)); |
| gpio_pin_set_dt(&rst_gpio, 0); |
| |
| /* Give the controller some time to boot */ |
| k_sleep(K_MSEC(500)); |
| |
| /* Configure the interrupt edge for IRQ pin */ |
| gpio_pin_interrupt_configure_dt(&irq_gpio, GPIO_INT_EDGE_RISING); |
| |
| return 0; |
| } |
| |
| int bt_apollo_controller_init(spi_transmit_fun transmit) |
| { |
| int ret; |
| am_devices_cooper_callback_t cb = { |
| .write = transmit, |
| .reset = bt_apollo_controller_reset, |
| }; |
| |
| /* Initialize the BLE controller */ |
| ret = am_devices_cooper_init(&cb); |
| if (ret == AM_DEVICES_COOPER_STATUS_SUCCESS) { |
| am_devices_cooper_set_initialize_state(AM_DEVICES_COOPER_STATE_INITIALIZED); |
| LOG_INF("BT controller initialized"); |
| } else { |
| am_devices_cooper_set_initialize_state(AM_DEVICES_COOPER_STATE_INITIALIZE_FAIL); |
| LOG_ERR("BT controller initialization fail"); |
| } |
| |
| return ret; |
| } |
| |
| static int bt_apollo_set_nvds(void) |
| { |
| int ret; |
| struct net_buf *buf; |
| |
| #if defined(CONFIG_BT_HCI_RAW) |
| struct bt_hci_cmd_hdr hdr; |
| |
| hdr.opcode = sys_cpu_to_le16(HCI_VSC_UPDATE_NVDS_CFG_CMD_OPCODE); |
| hdr.param_len = HCI_VSC_UPDATE_NVDS_CFG_CMD_LENGTH; |
| buf = bt_buf_get_tx(BT_BUF_CMD, K_NO_WAIT, &hdr, sizeof(hdr)); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| net_buf_add_mem(buf, &am_devices_cooper_nvds[0], HCI_VSC_UPDATE_NVDS_CFG_CMD_LENGTH); |
| ret = bt_send(buf); |
| |
| if (!ret) { |
| /* Give some time to make NVDS take effect in BLE controller */ |
| k_sleep(K_MSEC(5)); |
| |
| /* Need to send reset command to make the NVDS take effect */ |
| hdr.opcode = sys_cpu_to_le16(BT_HCI_OP_RESET); |
| hdr.param_len = 0; |
| buf = bt_buf_get_tx(BT_BUF_CMD, K_NO_WAIT, &hdr, sizeof(hdr)); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| ret = bt_send(buf); |
| } |
| #else |
| uint8_t *p; |
| |
| buf = bt_hci_cmd_create(HCI_VSC_UPDATE_NVDS_CFG_CMD_OPCODE, |
| HCI_VSC_UPDATE_NVDS_CFG_CMD_LENGTH); |
| if (!buf) { |
| return -ENOBUFS; |
| } |
| |
| p = net_buf_add(buf, HCI_VSC_UPDATE_NVDS_CFG_CMD_LENGTH); |
| memcpy(p, &am_devices_cooper_nvds[0], HCI_VSC_UPDATE_NVDS_CFG_CMD_LENGTH); |
| ret = bt_hci_cmd_send_sync(HCI_VSC_UPDATE_NVDS_CFG_CMD_OPCODE, buf, NULL); |
| |
| if (!ret) { |
| /* Give some time to make NVDS take effect in BLE controller */ |
| k_sleep(K_MSEC(5)); |
| } |
| #endif /* defined(CONFIG_BT_HCI_RAW) */ |
| |
| return ret; |
| } |
| |
| int bt_apollo_vnd_setup(void) |
| { |
| int ret; |
| |
| /* Set the NVDS parameters to BLE controller */ |
| ret = bt_apollo_set_nvds(); |
| |
| return ret; |
| } |
| |
| int bt_apollo_dev_init(void) |
| { |
| if (!gpio_is_ready_dt(&irq_gpio)) { |
| LOG_ERR("IRQ GPIO device not ready"); |
| return -ENODEV; |
| } |
| |
| if (!gpio_is_ready_dt(&rst_gpio)) { |
| LOG_ERR("Reset GPIO device not ready"); |
| return -ENODEV; |
| } |
| |
| if (!gpio_is_ready_dt(&clkreq_gpio)) { |
| LOG_ERR("CLKREQ GPIO device not ready"); |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |