Bluetooth: nble: Add UART driver for Nordic BLE chip

Adds basic support for communication with Nordic BLE (NBLE) chip
connected to UART.

Change-Id: I3651e291ec18805a63ecd3d240dce62273e3c498
Signed-off-by: Andrei Emeltchenko <andrei.emeltchenko@intel.com>
This commit is contained in:
Andrei Emeltchenko 2016-01-13 17:14:39 +02:00 committed by Anas Nashif
commit baeb34a950
5 changed files with 583 additions and 1 deletions

View file

@ -324,6 +324,17 @@ config BLUETOOTH_UART_IRQ_PRI
endif endif
if NBLE
config NBLE_UART_ON_DEV_NAME
default UART_NS16550_PORT_0_NAME
config NBLE_UART_IRQ
default UART_NS16550_PORT_0_IRQ
config NBLE_UART_IRQ_PRI
default UART_NS16550_PORT_0_IRQ_PRI
endif
if UART_PIPE if UART_PIPE
config UART_PIPE_ON_DEV_NAME config UART_PIPE_ON_DEV_NAME

View file

@ -37,6 +37,28 @@ config BLUETOOTH_MAX_PAIRED
int int
default 7 default 7
config NBLE_UART_ON_DEV_NAME
string "Device Name of UART Device for Nordic BLE"
default "UART_0"
depends on NBLE
help
This option specifies the name of UART device to be used
for Nordic BLE.
config NBLE_UART_IRQ
int "IRQ of UART Device for Nordic BLE"
depends on NBLE
help
This option specifies the IRQ of UART device to be used
for Nordic BLE.
config NBLE_UART_IRQ_PRI
int "IRQ Priority of UART Device for Nordic BLE"
depends on NBLE
help
This option specifies the IRQ priority of UART device to be used
for Nordic BLE.
config NBLE config NBLE
bool "Support for custom Nordic Semiconductor BLE protocol" bool "Support for custom Nordic Semiconductor BLE protocol"
default n default n
@ -44,6 +66,8 @@ config NBLE
select BLUETOOTH_CENTRAL select BLUETOOTH_CENTRAL
select BLUETOOTH_GATT_CLIENT select BLUETOOTH_GATT_CLIENT
select BLUETOOTH_SMP select BLUETOOTH_SMP
select UART_INTERRUPT_DRIVEN
select NET_BUF
help help
Enables support for using Nordic Semiconductor nRF51 Bluetooth Enables support for using Nordic Semiconductor nRF51 Bluetooth
LE chips with a custom firmware. The API for this is a subset of LE chips with a custom firmware. The API for this is a subset of

View file

@ -1 +1 @@
obj-$(CONFIG_NBLE) += gap.o conn.o gatt.o obj-$(CONFIG_NBLE) += gap.o conn.o gatt.o uart.o

374
drivers/nble/uart.c Normal file
View file

@ -0,0 +1,374 @@
/* uart.c - Nordic BLE UART based Bluetooth driver */
/*
* Copyright (c) 2016 Intel Corporation
*
* 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 <nanokernel.h>
#include <board.h>
#include <init.h>
#include <uart.h>
#include <string.h>
#include <net/buf.h>
#include <bluetooth/log.h>
#include "uart.h"
/* TODO: check size */
#define NBLE_IPC_COUNT 1
#define NBLE_BUF_SIZE 100
static struct nano_fifo rx;
static NET_BUF_POOL(rx_pool, NBLE_IPC_COUNT, NBLE_BUF_SIZE, &rx, NULL, 0);
enum {
STATUS_TX_IDLE = 0,
STATUS_TX_BUSY,
STATUS_TX_DONE,
};
enum {
STATUS_RX_IDLE = 0,
STATUS_RX_HDR,
STATUS_RX_DATA
};
/**
* Describes the uart IPC to handle
*/
struct ipc_uart_info {
int uart_num; /* UART device to use */
uint32_t irq_vector; /* IRQ number */
uint32_t irq_mask; /* IRQ mask */
/* callback to be called to set wake state when TX is starting
* or ending
*/
void (*tx_cb)(bool wake_state, void*);
void *tx_cb_param; /* tx_cb function parameter */
};
struct ipc_uart {
uint8_t *tx_data;
uint8_t *rx_ptr;
struct ipc_uart_channels channels[IPC_UART_MAX_CHANNEL];
struct ipc_uart_header tx_hdr;
struct ipc_uart_header rx_hdr;
uint16_t send_counter;
uint16_t rx_size;
uint8_t tx_state;
uint8_t rx_state;
uint8_t uart_enabled;
/* protect against multiple wakelock and wake assert calls */
uint8_t tx_wakelock_acquired;
/* TODO: remove once IRQ will take a parameter */
struct device *device;
};
static struct ipc_uart ipc;
static struct device *nble_dev;
static void uart_frame_recv(uint16_t len, uint8_t *p_data)
{
BT_DBG("rcv: len: %d data len %d src %d channel %d",
ipc.rx_hdr.len, len, ipc.rx_hdr.src_cpu_id, ipc.rx_hdr.channel);
if ((ipc.rx_hdr.channel < IPC_UART_MAX_CHANNEL) &&
(ipc.channels[ipc.rx_hdr.channel].cb != NULL)) {
ipc.channels[ipc.rx_hdr.channel].cb(ipc.rx_hdr.channel,
IPC_MSG_TYPE_MESSAGE,
len,
p_data);
} else {
BT_ERR("uart_ipc: bad channel %d", ipc.rx_hdr.channel);
}
}
static int nble_read(struct device *uart, uint8_t *buf,
size_t len, size_t min)
{
int total = 0;
while (len) {
int rx;
rx = uart_fifo_read(uart, buf, len);
if (rx == 0) {
BT_DBG("Got zero bytes from UART");
if (total < min) {
continue;
}
break;
}
BT_DBG("read %d remaining %d", rx, len - rx);
len -= rx;
total += rx;
buf += rx;
}
return total;
}
static size_t nble_discard(struct device *uart, size_t len)
{
/* FIXME: correct size for nble */
uint8_t buf[33];
return uart_fifo_read(uart, buf, min(len, sizeof(buf)));
}
void bt_uart_isr(void *unused)
{
static struct net_buf *buf;
static int remaining;
ARG_UNUSED(unused);
while (uart_irq_update(nble_dev) && uart_irq_is_pending(nble_dev)) {
int read;
if (!uart_irq_rx_ready(nble_dev)) {
if (uart_irq_tx_ready(nble_dev)) {
BT_DBG("transmit ready");
/*
* Implementing ISR based transmit requires
* extra API for uart such as
* uart_line_status(), etc. The support was
* removed from the recent code, using polling
* for transmit for now.
*/
} else {
BT_DBG("spurious interrupt");
}
continue;
}
/* Beginning of a new packet */
if (!remaining) {
struct ipc_uart_header hdr;
/* Get packet type */
read = nble_read(nble_dev, (uint8_t *)&hdr,
sizeof(hdr), sizeof(hdr));
if (read != sizeof(hdr)) {
BT_WARN("Unable to read NBLE header");
continue;
}
remaining = hdr.len;
buf = net_buf_get(&rx, 0);
if (!buf) {
BT_ERR("No available IPC buffers");
}
#if 0
} else {
memcpy(net_buf_add(buf, sizeof(hdr)), &hdr,
sizeof(hdr));
}
#endif
BT_DBG("need to get %u bytes", remaining);
if (buf && remaining > net_buf_tailroom(buf)) {
BT_ERR("Not enough space in buffer");
net_buf_unref(buf);
buf = NULL;
}
}
if (!buf) {
read = nble_discard(nble_dev, remaining);
BT_WARN("Discarded %d bytes", read);
remaining -= read;
continue;
}
read = nble_read(nble_dev, net_buf_tail(buf), remaining, 0);
buf->len += read;
remaining -= read;
BT_DBG("received %d bytes", read);
if (!remaining) {
BT_DBG("full packet received");
/* Pass buffer to the stack */
uart_frame_recv(buf->len, buf->data);
net_buf_unref(buf);
buf = NULL;
}
}
}
void *ipc_uart_channel_open(int channel_id,
int (*cb)(int, int, int, void *))
{
struct ipc_uart_channels *chan;
if (channel_id > (IPC_UART_MAX_CHANNEL - 1))
return NULL;
chan = &ipc.channels[channel_id];
if (chan->state != IPC_CHANNEL_STATE_CLOSED)
return NULL;
chan->state = IPC_CHANNEL_STATE_OPEN;
chan->cb = cb;
ipc.uart_enabled = 1;
return chan;
}
void ipc_uart_close_channel(int channel_id)
{
ipc.channels[channel_id].state = IPC_CHANNEL_STATE_CLOSED;
ipc.channels[channel_id].cb = NULL;
ipc.channels[channel_id].index = channel_id;
ipc.uart_enabled = 0;
}
static void uart_poll_bytes(uint8_t *buf, size_t len)
{
while (len--) {
uart_poll_out(nble_dev, *buf++);
}
}
int ipc_uart_ns16550_send_pdu(struct device *dev, void *handle, int len,
void *p_data)
{
struct ipc_uart_channels *chan = (struct ipc_uart_channels *)handle;
struct ipc_uart_header hdr;
if (ipc.tx_state == STATUS_TX_BUSY) {
return IPC_UART_TX_BUSY;
}
/* It is eventually possible to be in DONE state
* (sending last bytes of previous message),
* so we move immediately to BUSY and configure the next frame
*/
/* FIXME: needed? */
ipc.tx_state = STATUS_TX_BUSY;
/* Using polling for transmit */
/* Send header */
hdr.len = len;
hdr.channel = chan->index;
hdr.src_cpu_id = 0;
uart_poll_bytes((uint8_t *)&hdr, sizeof(hdr));
/* Send data */
uart_poll_bytes(p_data, len);
return IPC_UART_ERROR_OK;
}
void ipc_uart_ns16550_set_tx_cb(struct device *dev, void (*cb)(bool, void*),
void *param)
{
struct ipc_uart_info *info = dev->driver_data;
info->tx_cb = cb;
info->tx_cb_param = param;
}
static int ipc_uart_ns16550_init(struct device *dev)
{
struct ipc_uart_info *info = dev->driver_data;
int i;
/* Fail init if no info defined */
if (!info) {
BT_ERR("No driver data found");
return -1;
}
for (i = 0; i < IPC_UART_MAX_CHANNEL; i++) {
ipc_uart_close_channel(i);
}
/* Set dev used in irq handler */
ipc.device = dev;
ipc.uart_enabled = 0;
/* Initialize the reception pointer */
ipc.rx_size = sizeof(ipc.rx_hdr);
ipc.rx_ptr = (uint8_t *)&ipc.rx_hdr;
ipc.rx_state = STATUS_RX_IDLE;
return 0;
}
int nble_open(void)
{
BT_DBG("");
uart_irq_rx_disable(nble_dev);
uart_irq_tx_disable(nble_dev);
IRQ_CONNECT(CONFIG_NBLE_UART_IRQ, CONFIG_NBLE_UART_IRQ_PRI,
bt_uart_isr, 0, UART_IRQ_FLAGS);
irq_enable(CONFIG_NBLE_UART_IRQ);
/* Drain the fifo */
while (uart_irq_rx_ready(nble_dev)) {
unsigned char c;
uart_fifo_read(nble_dev, &c, 1);
}
uart_irq_rx_enable(nble_dev);
return 0;
}
struct ipc_uart_info info;
static int _bt_nble_init(struct device *unused)
{
ARG_UNUSED(unused);
nble_dev = device_get_binding(CONFIG_NBLE_UART_ON_DEV_NAME);
if (!nble_dev) {
return DEV_INVALID_CONF;
}
net_buf_pool_init(rx_pool);
nble_dev->driver_data = &info;
ipc_uart_ns16550_init(nble_dev);
/* TODO: Register nble driver */
return DEV_OK;
}
DEVICE_INIT(bt_nble, "", _bt_nble_init, NULL, NULL, NANOKERNEL,
CONFIG_KERNEL_INIT_PRIORITY_DEVICE);

173
drivers/nble/uart.h Normal file
View file

@ -0,0 +1,173 @@
/* uart.h - Nordic BLE UART based Bluetooth driver */
/*
* Copyright (c) 2015 Intel Corporation
*
* 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.
*/
/**
* IPC Uart power management driver.
*/
extern struct driver ipc_uart_ns16550_driver;
enum IPC_UART_RESULT_CODES {
IPC_UART_ERROR_OK = 0,
IPC_UART_ERROR_DATA_TO_BIG,
/**< A transmission is already ongoing, message is NOT sent */
IPC_UART_TX_BUSY
};
/**
* Definitions valid for NONE sync IPC UART headers.
*/
/**
* @note this structure must be self-aligned and self-packed
*/
struct ipc_uart_header {
uint16_t len; /**< Length of IPC message. */
uint8_t channel; /**< Channel number of IPC message. */
uint8_t src_cpu_id; /**< CPU id of IPC sender. */
};
#define IPC_CHANNEL_STATE_CLOSED 0
#define IPC_CHANNEL_STATE_OPEN 1
#define IPC_UART_MAX_CHANNEL 1
struct ipc_uart_channels {
uint16_t index;
uint16_t state;
int (*cb)(int chan, int request, int len, void *data);
};
void ipc_uart_isr(void);
void ipc_uart_ns16550_disable(struct device *dev);
/**
* This function triggers the sending of PDU buffer over UART.
*
* This constructs an IPC message header and triggers the sending of it and
* message buffer. If a transmission is already ongoing, it will fail.
* In this case upper layer needs to queue the message buffer.
*
* @param dev structure of the opened device
* @param handle structure of opened IPC uart channel
* @param len length of message to send
* @param p_data message buffer to send
*
* @return IPC_UART_ERROR_OK TX has been initiated, IPC_UART_TX_BUSY a
* transmission is already going, message needs to be queued
*
* @note This function needs to be executed with (UART) irq off to avoid
* pre-emption from uart_ipc_isr causing state variable corruption.
* It also called from uart_ipc_isr() to send the next IPC message.
*/
int ipc_uart_ns16550_send_pdu(struct device *dev, void *handle, int len,
void *p_data);
/**
* This function registers a callback function being called on TX start/end.
*
* This constructs an IPC message header and triggers the sending of it and
* message buffer. If a transmission is already ongoing, it will fail. In
* this case upper layer needs to queue the message buffer.
*
* @param dev structure of the opened device
* @param cb callback function for OOB sleep mode handling called at tx start
* and end
* @param param parameter passed to cb when being called
*/
void ipc_uart_ns16550_set_tx_cb(struct device *dev, void (*cb)(bool, void*),
void *param);
/**
* Opens a UART channel for QRK/BLE Core IPC, and defines the callback function
* for receiving IPC messages.
*
* @param channel IPC channel ID to use
* @param cb Callback to handle messages
*
* @return
* - Pointer to channel structure if success,
* - NULL if opening fails.
*/
void *ipc_uart_channel_open(int channel, int (*cb)(int chan, int request,
int len, void *data));
/**
* The frame is a message.
*/
#define IPC_MSG_TYPE_MESSAGE 0x1
/**
* Requests to free a message.
*/
#define IPC_MSG_TYPE_FREE 0x2
/**
* Sets the MessageBox as synchronized.
*/
#define IPC_MSG_TYPE_SYNC 0x3
/**
* Allocates a port.
*
* This request is always flowing from a slave to the master.
*/
#define IPC_REQUEST_ALLOC_PORT 0x10
/**
* Registers a service.
*
* This request is always flowing from a slave to the master.
*/
#define IPC_REQUEST_REGISTER_SERVICE 0x11
/**
* Unregisters a service.
*/
#define IPC_REQUEST_DEREGISTER_SERVICE 0x12
/**
* The message is for test commands engine.
*/
#define IPC_REQUEST_REG_TCMD_ENGINE 0x13
/**
* Registers a Service Manager Proxy to the Service Manager.
*
* This request always flow from a slave to the master.
*/
#define IPC_REQUEST_REGISTER_PROXY 0x14
/**
* Notifies a panic (for log dump).
*/
#define IPC_PANIC_NOTIFICATION 0x15
/**
* The message is for power management.
*/
#define IPC_REQUEST_POWER_MANAGEMENT 0x16
/**
* Sends the log of a slave to the master (for log aggregation).
*/
#define IPC_REQUEST_LOGGER 0x17
/**
* The message is for power management (deep sleep).
*/
#define IPC_REQUEST_INFRA_PM 0x18