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:
parent
d325faa670
commit
baeb34a950
5 changed files with 583 additions and 1 deletions
|
@ -324,6 +324,17 @@ config BLUETOOTH_UART_IRQ_PRI
|
|||
|
||||
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
|
||||
|
||||
config UART_PIPE_ON_DEV_NAME
|
||||
|
|
|
@ -37,6 +37,28 @@ config BLUETOOTH_MAX_PAIRED
|
|||
int
|
||||
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
|
||||
bool "Support for custom Nordic Semiconductor BLE protocol"
|
||||
default n
|
||||
|
@ -44,6 +66,8 @@ config NBLE
|
|||
select BLUETOOTH_CENTRAL
|
||||
select BLUETOOTH_GATT_CLIENT
|
||||
select BLUETOOTH_SMP
|
||||
select UART_INTERRUPT_DRIVEN
|
||||
select NET_BUF
|
||||
help
|
||||
Enables support for using Nordic Semiconductor nRF51 Bluetooth
|
||||
LE chips with a custom firmware. The API for this is a subset of
|
||||
|
|
|
@ -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
374
drivers/nble/uart.c
Normal 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
173
drivers/nble/uart.h
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue