Bluetooth: Add HCI SPI driver

This driver acts as a pass-through, taking raw HCI data, converting
it to SPI comms and vice versa.  It works in the same way as the
existing H:4 and H:5 drivers, only it uses SPI instead of UART.

In this first release, the only BLE board which has been tested is
the X-NUCLEO-IDB05A1:

  http://www.st.com/en/ecosystems/x-nucleo-idb05a1.html

Although the current supported SPI format works like the one below,
it should be trivial to adapt it to support other chips with a
different format.

SANITY CHECK = 0x02
SPI WRITE    = 0x0A
SPI READ     = 0x0B

Tx Format:

   [HOST] {SPI WRITE}     0x00          0x00  0x00  0x00  {HCI MESSAGE ...}
   [CHIP] {SANITY CHECK}  {FLASH SIZE}  0x00  0x00  0x00  {0xFF * MESSAGE LEN}

Rx Format:

   {IRQ LINE GOES HIGH}

   [HOST] {SPI READ}  0x00          0x00  0x00             0x00  {0xFF * BYTES TO READ}
   [CHIP] 0x02        {FLASH SIZE}  0x00  {BYTES TO READ}  0x00  {HCI MESSAGE ...}

Change-Id: I4a00711c922d9ea02c5e2afb0d16715e413b1ed5
Signed-off-by: Lee Jones <lee.jones@linaro.org>
This commit is contained in:
Lee Jones 2017-01-09 14:16:07 +00:00 committed by Johan Hedberg
commit 7890290e81
3 changed files with 433 additions and 0 deletions

View file

@ -48,6 +48,16 @@ config BLUETOOTH_H5
Bluetooth three-wire (H:5) UART driver. Implementation of HCI Bluetooth three-wire (H:5) UART driver. Implementation of HCI
Three-Wire UART Transport Layer. Three-Wire UART Transport Layer.
config BLUETOOTH_SPI
bool "SPI HCI"
select SPI
help
Supports Bluetooth ICs using SPI as the communication protocol.
HCI packets are sent and received as single Byte transferrs,
prepended after a known header. Headers may vary per device, so
additional platform specific knowlege may need to be added as
devices are. Current driver supports; ST X-NUCLEO BLE series.
config BLUETOOTH_NO_DRIVER config BLUETOOTH_NO_DRIVER
bool "No default HCI driver" bool "No default HCI driver"
help help
@ -73,6 +83,17 @@ config BLUETOOTH_UART_ON_DEV_NAME
This option specifies the name of UART device to be used This option specifies the name of UART device to be used
for Bluetooth. for Bluetooth.
config BLUETOOTH_SPI_DEV_NAME
string "Device Name of SPI Device for Bluetooth"
default "SPI_0"
depends on BLUETOOTH_SPI
help
This option specifies the name of SPI device to be used for Bluetooth.
On the controller side, this SPI device is used to encapsulate the
RAW HCI frames to send further up the stack. On the BLE stack side,
this device is used to reply back with HCI frames that are sent over
the air.
# Headroom that the driver needs for sending and receiving buffers. # Headroom that the driver needs for sending and receiving buffers.
# Add a new 'default' entry for each new driver. # Add a new 'default' entry for each new driver.
@ -84,6 +105,7 @@ config BLUETOOTH_HCI_SEND_RESERVE
default 0 default 0
default 0 if BLUETOOTH_H4 default 0 if BLUETOOTH_H4
default 1 if BLUETOOTH_H5 default 1 if BLUETOOTH_H5
default 1 if BLUETOOTH_SPI
# Needed headroom for incoming buffers (from controller) # Needed headroom for incoming buffers (from controller)
config BLUETOOTH_HCI_RECV_RESERVE config BLUETOOTH_HCI_RECV_RESERVE
@ -93,3 +115,62 @@ config BLUETOOTH_HCI_RECV_RESERVE
default 0 default 0
default 0 if BLUETOOTH_H4 default 0 if BLUETOOTH_H4
default 0 if BLUETOOTH_H5 default 0 if BLUETOOTH_H5
if BLUETOOTH_SPI
config BLUETOOTH_SPI_CHIP_SELECT_DEV_NAME
string "Chip Select (CS) line driver name"
help
This option specifies the name of GPIO driver controlling
the Chip Select (CS) line.
config BLUETOOTH_SPI_IRQ_DEV_NAME
string "IRQ line driver name"
help
This option specifies the name of GPIO driver controlling
the chip's IRQ line.
config BLUETOOTH_SPI_RESET_DEV_NAME
string "Reset line driver name"
help
This option specifies the name of GPIO driver controlling
the chip's Reset line.
config BLUETOOTH_SPI_CHIP_SELECT_PIN
int "SPI Chip Select (CS) line number"
help
This option specifies the Chip Select (CS) line number on the SPI
device
config BLUETOOTH_SPI_IRQ_PIN
int "SPI IRQ line number"
help
This option specifies the Reset line number on the SPI device
config BLUETOOTH_SPI_RESET_PIN
int "SPI Reset line number"
help
This option specifies the Reset line number on the SPI device
config BLUETOOTH_SPI_RX_BUFFER_SIZE
int "Receive buffer length"
default 96
help
This option specifies the size of the RX buffer. Try to keep this
as small as possible, since it's stored on the stack.
config BLUETOOTH_SPI_TX_BUFFER_SIZE
int "Transmit buffer length"
default 64
help
This option specifies the size of the TX buffer. Try to keep this
as small as possible, since it's stored on the stack.
config BLUETOOTH_SPI_MAX_CLK_FREQ
int "Maximum clock frequency for the HCI SPI interface"
default 5000000
help
This option specifies the maximum clock rate the HCI SPI
interface is capable of running at.
endif # BLUETOOTH_SPI

View file

@ -1,2 +1,3 @@
obj-$(CONFIG_BLUETOOTH_H4) += h4.o obj-$(CONFIG_BLUETOOTH_H4) += h4.o
obj-$(CONFIG_BLUETOOTH_H5) += h5.o obj-$(CONFIG_BLUETOOTH_H5) += h5.o
obj-$(CONFIG_BLUETOOTH_SPI) += spi.o

351
drivers/bluetooth/hci/spi.c Normal file
View file

@ -0,0 +1,351 @@
/* spi.c - SPI based Bluetooth driver */
/*
* Copyright (c) 2017 Linaro Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <gpio.h>
#include <init.h>
#include <spi.h>
#include <misc/util.h>
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLUETOOTH_DEBUG_HCI_DRIVER)
#include <bluetooth/log.h>
#include <bluetooth/hci_driver.h>
#define HCI_CMD 0x01
#define HCI_ACL 0x02
#define HCI_SCO 0x03
#define HCI_EVT 0x04
/* Special Values */
#define SPI_WRITE 0x0A
#define SPI_READ 0x0B
#define READY_NOW 0x02
#define EVT_BLUE_INITIALIZED 0x01
/* Offsets */
#define STATUS_HEADER_READY 0
#define STATUS_HEADER_TOREAD 3
#define EVT_HEADER_TYPE 0
#define EVT_HEADER_EVENT 1
#define EVT_HEADER_SIZE 2
#define EVT_VENDOR_CODE_LSB 3
#define EVT_VENDOR_CODE_MSB 4
#define CMD_OGF 1
#define CMD_OCF 2
#define GPIO_IRQ_PIN CONFIG_BLUETOOTH_SPI_IRQ_PIN
#define GPIO_CS_PIN CONFIG_BLUETOOTH_SPI_CHIP_SELECT_PIN
#define GPIO_RESET_PIN CONFIG_BLUETOOTH_SPI_RESET_PIN
#define MAX_RX_MSG_LEN CONFIG_BLUETOOTH_SPI_RX_BUFFER_SIZE
#define MAX_TX_MSG_LEN CONFIG_BLUETOOTH_SPI_TX_BUFFER_SIZE
static struct device *spi_dev;
static struct device *cs_dev;
static struct device *irq_dev;
static struct device *rst_dev;
static struct gpio_callback gpio_cb;
static K_SEM_DEFINE(sem_initialised, 0, 1);
static K_SEM_DEFINE(sem_request, 0, 1);
static K_SEM_DEFINE(sem_busy, 1, 1);
static BT_STACK_NOINIT(rx_stack, 448);
static struct spi_config spi_conf = {
.config = SPI_WORD(8),
.max_sys_freq = CONFIG_BLUETOOTH_SPI_MAX_CLK_FREQ,
};
#if defined(CONFIG_BLUETOOTH_DEBUG_HCI_DRIVER)
#include <misc/printk.h>
static inline void spi_dump_message(const uint8_t *pre, uint8_t *buf,
uint8_t size)
{
uint8_t i, c;
printk("%s (%d): ", pre, size);
for (i = 0; i < size; i++) {
c = buf[i];
printk("%x ", c);
if (c >= 31 && c <= 126) {
printk("[%c] ", c);
} else {
printk("[.] ");
}
}
printk("\n");
}
#else
static inline
void spi_dump_message(const uint8_t *pre, uint8_t *buf, uint8_t size) {}
#endif
static inline uint16_t bt_spi_get_cmd(uint8_t *txmsg)
{
return (txmsg[CMD_OCF] << 8) | txmsg[CMD_OGF];
}
static inline uint16_t bt_spi_get_evt(uint8_t *rxmsg)
{
return (rxmsg[EVT_VENDOR_CODE_MSB] << 8) | rxmsg[EVT_VENDOR_CODE_LSB];
}
static void bt_spi_isr(struct device *unused1, struct gpio_callback *unused2,
unsigned int unused3)
{
k_sem_give(&sem_request);
}
static void bt_spi_handle_vendor_evt(uint8_t *rxmsg)
{
switch (bt_spi_get_evt(rxmsg)) {
case EVT_BLUE_INITIALIZED:
k_sem_give(&sem_initialised);
default:
break;
}
}
static void bt_spi_rx_thread(void)
{
struct net_buf *buf;
uint8_t header_master[5] = { SPI_READ, 0x00, 0x00, 0x00, 0x00 };
uint8_t header_slave[5];
uint8_t rxmsg[MAX_RX_MSG_LEN];
uint8_t dummy = 0xFF, size, i;
while (true) {
k_sem_take(&sem_request, K_FOREVER);
k_sem_take(&sem_busy, K_FOREVER);
do {
gpio_pin_write(cs_dev, GPIO_CS_PIN, 1);
gpio_pin_write(cs_dev, GPIO_CS_PIN, 0);
spi_transceive(spi_dev,
header_master, 5, header_slave, 5);
} while (header_slave[STATUS_HEADER_TOREAD] == 0 ||
header_slave[STATUS_HEADER_TOREAD] == 0xFF);
size = header_slave[STATUS_HEADER_TOREAD];
for (i = 0; i < size; i++) {
spi_transceive(spi_dev, &dummy, 1, &rxmsg[i], 1);
}
gpio_pin_write(cs_dev, GPIO_CS_PIN, 1);
k_sem_give(&sem_busy);
spi_dump_message("RX:ed", rxmsg, size);
/* Vendor events are currently unsupported */
if (rxmsg[EVT_HEADER_EVENT] == BT_HCI_EVT_VENDOR) {
bt_spi_handle_vendor_evt(rxmsg);
continue;
}
switch (rxmsg[EVT_HEADER_TYPE]) {
case HCI_EVT:
buf = bt_buf_get_rx(K_FOREVER);
bt_buf_set_type(buf, BT_BUF_EVT);
break;
case HCI_ACL:
buf = bt_buf_get_rx(K_FOREVER);
bt_buf_set_type(buf, BT_BUF_ACL_IN);
break;
default:
BT_ERR("Unknown BT buf type %d", rxmsg[0]);
continue;
}
net_buf_add_mem(buf, &rxmsg[1], rxmsg[EVT_HEADER_SIZE] + 2);
if (rxmsg[EVT_HEADER_TYPE] == HCI_EVT &&
bt_hci_evt_is_prio(rxmsg[EVT_HEADER_EVENT])) {
bt_recv_prio(buf);
} else {
bt_recv(buf);
}
}
}
static int bt_spi_send(struct net_buf *buf)
{
uint8_t header[5] = { SPI_WRITE, 0x00, 0x00, 0x00, 0x00 };
uint8_t rxmsg[MAX_TX_MSG_LEN + 1]; /* Extra Byte to account for TYPE */
uint32_t pending;
if (buf->len > MAX_TX_MSG_LEN) {
BT_ERR("Message too long");
return -EINVAL;
}
/* Allow time for the read thread to handle interrupt */
while (true) {
gpio_pin_read(irq_dev, GPIO_IRQ_PIN, &pending);
if (!pending) {
break;
}
k_sleep(1);
}
k_sem_take(&sem_busy, K_FOREVER);
switch (bt_buf_get_type(buf)) {
case BT_BUF_ACL_OUT:
net_buf_push_u8(buf, HCI_ACL);
break;
case BT_BUF_CMD:
net_buf_push_u8(buf, HCI_CMD);
break;
default:
BT_ERR("Unsupported type");
k_sem_give(&sem_busy);
return -EINVAL;
}
/* Poll sanity values until device has woken-up */
do {
gpio_pin_write(cs_dev, GPIO_CS_PIN, 1);
gpio_pin_write(cs_dev, GPIO_CS_PIN, 0);
spi_transceive(spi_dev, header, 5, rxmsg, 5);
/*
* RX Header (rxmsg) must contain a sanity check Byte and size
* information. If it does not contain BOTH then it is
* sleeping or still in the initialisation stage (waking-up).
*/
} while (rxmsg[STATUS_HEADER_READY] != READY_NOW ||
(rxmsg[1] | rxmsg[2] | rxmsg[3] | rxmsg[4]) == 0);
/* Transmit the message */
spi_transceive(spi_dev, buf->data, buf->len, rxmsg, buf->len);
/* Deselect chip */
gpio_pin_write(cs_dev, GPIO_CS_PIN, 1);
k_sem_give(&sem_busy);
spi_dump_message("TX:ed", buf->data, buf->len);
/*
* Since a RESET has been requested, the chip will now restart.
* Unfortunately the BlueNRG will reply with "reset received" but
* since it does not send back a NOP, we have no way to tell when the
* RESET has actually taken palce. Instead, we use the vendor command
* EVT_BLUE_INITIALIZED as an indication that it is safe to proceed.
*/
if (bt_spi_get_cmd(buf->data) == BT_HCI_OP_RESET) {
k_sem_take(&sem_initialised, K_FOREVER);
}
net_buf_unref(buf);
return 0;
}
static int bt_spi_open(void)
{
/* Configure RST pin and hold BLE in Reset */
gpio_pin_configure(rst_dev, GPIO_RESET_PIN,
GPIO_DIR_OUT | GPIO_PUD_PULL_UP);
gpio_pin_write(rst_dev, GPIO_RESET_PIN, 0);
spi_configure(spi_dev, &spi_conf);
/* Configure the CS (Chip Select) pin */
gpio_pin_configure(cs_dev, GPIO_CS_PIN,
GPIO_DIR_OUT | GPIO_PUD_PULL_UP);
gpio_pin_write(cs_dev, GPIO_CS_PIN, 1);
/* Configure IRQ pin and the IRQ call-back/handler */
gpio_pin_configure(irq_dev, GPIO_IRQ_PIN,
GPIO_DIR_IN | GPIO_INT |
GPIO_INT_EDGE | GPIO_INT_ACTIVE_HIGH);
gpio_init_callback(&gpio_cb, bt_spi_isr, BIT(GPIO_IRQ_PIN));
if (gpio_add_callback(irq_dev, &gpio_cb)) {
return -EINVAL;
}
if (gpio_pin_enable_callback(irq_dev, GPIO_IRQ_PIN)) {
return -EINVAL;
}
/* Start RX thread */
k_thread_spawn(rx_stack, sizeof(rx_stack),
(k_thread_entry_t)bt_spi_rx_thread,
NULL, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT);
/* Take BLE out of reset */
gpio_pin_write(rst_dev, GPIO_RESET_PIN, 1);
/* Device will let us know when it's ready */
k_sem_take(&sem_initialised, K_FOREVER);
return 0;
}
static struct bt_hci_driver drv = {
.name = "BT SPI",
.bus = BT_HCI_DRIVER_BUS_SPI,
.open = bt_spi_open,
.send = bt_spi_send,
};
static int _bt_spi_init(struct device *unused)
{
ARG_UNUSED(unused);
spi_dev = device_get_binding(CONFIG_BLUETOOTH_SPI_DEV_NAME);
if (!spi_dev) {
BT_ERR("Failed to initialize SPI driver: %s",
CONFIG_BLUETOOTH_SPI_DEV_NAME);
return -EIO;
}
cs_dev = device_get_binding(CONFIG_BLUETOOTH_SPI_CHIP_SELECT_DEV_NAME);
if (!cs_dev) {
BT_ERR("Failed to initialize GPIO driver: %s",
CONFIG_BLUETOOTH_SPI_CHIP_SELECT_DEV_NAME);
return -EIO;
}
irq_dev = device_get_binding(CONFIG_BLUETOOTH_SPI_IRQ_DEV_NAME);
if (!irq_dev) {
BT_ERR("Failed to initialize GPIO driver: %s",
CONFIG_BLUETOOTH_SPI_IRQ_DEV_NAME);
return -EIO;
}
rst_dev = device_get_binding(CONFIG_BLUETOOTH_SPI_RESET_DEV_NAME);
if (!rst_dev) {
BT_ERR("Failed to initialize GPIO driver: %s",
CONFIG_BLUETOOTH_SPI_RESET_DEV_NAME);
return -EIO;
}
bt_hci_driver_register(&drv);
return 0;
}
SYS_INIT(_bt_spi_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);