d385150bb0
This commits create the dts binding for Ambiq BT HCI instance. And create the SPI based common HCI driver for Ambiq Apollox Blue SoC and the extended soc driver for HCI. Signed-off-by: Aaron Ye <aye@ambiq.com>
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;
|
|
}
|