367 lines
9.2 KiB
C
367 lines
9.2 KiB
C
/*
|
|
* 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;
|
|
}
|