nuttx/drivers/wireless/bluetooth/bt_uart_bcm4343x.c

476 lines
13 KiB
C

/****************************************************************************
* drivers/wireless/bluetooth/bt_uart_bcm4343x.c
*
* Copyright (C) 2019 Gregory Nutt. All rights reserved.
* Author: Dave Marples <dave@marples.net>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <debug.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <semaphore.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <nuttx/arch.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/irq.h>
#include <nuttx/kmalloc.h>
#include <nuttx/kthread.h>
#include <nuttx/semaphore.h>
#include <nuttx/serial/tioctl.h>
#include <nuttx/wireless/bluetooth/bt_uart.h>
#include <nuttx/wireless/bluetooth/bt_uart_shim.h>
#include <termios.h>
#include "bt_uart.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define HCIUART_DEFAULT_SPEED 2000000
#define HCIUART_LOW_SPEED 115200
/****************************************************************************
* Private Data
****************************************************************************/
static const uint8_t g_hcd_patchram_command = 0x2e;
static const uint8_t g_hcd_launch_command = 0x4e;
static const uint8_t g_hcd_write_command = 0x4c;
static const uint8_t g_hcd_command_byte2 = 0xfc;
/****************************************************************************
* Public Data
****************************************************************************/
extern const long int g_bt_firmware_len;
extern const uint8_t g_bt_firmware_hcd[];
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: hciuartCB
*
* Callback from hcirx indicating that there are data to read.
*
* Input Parameters:
* lower - an instance of the lower half driver interface
* param - pointer to the semaphore to indicate data readiness
*
* Returned Value:
* None
*
****************************************************************************/
static void hciuart_cb(FAR const struct btuart_lowerhalf_s *lower,
FAR void *param)
{
FAR sem_t *rxsem = (FAR sem_t *)param;
/* Data has arrived - post to release the semaphore */
nxsem_post(rxsem);
}
/****************************************************************************
* Name: uartwriteconf
*
* Write to HCI UART and get confirmation back
*
* Input Parameters:
* lower - an instance of the lower half driver interface
* rxsem - pointer to the semaphore to indicate data readiness
* dout - pointer to data to send
* doutl - length of out data
* cmp - pointer to comparison data on inbound side
* maxl - length of data to receive on inbound side
*
* Returned Value:
* 0 or error code.
*
****************************************************************************/
static int uartwriteconf(FAR const struct btuart_lowerhalf_s *lower,
FAR sem_t *rxsem,
FAR const uint8_t *dout, uint32_t doutl,
FAR const uint8_t *cmp, uint32_t maxl)
{
int ret;
int gotlen = 0;
FAR uint8_t *din;
struct timespec abstime;
DEBUGASSERT(lower != NULL);
lower->rxdrain(lower);
ret = lower->write(lower, dout, doutl);
if (ret < 0)
{
wlerr("Failed to write\n");
goto exit_uartwriteconf_nofree;
}
if ((cmp == NULL) || (maxl == 0))
{
ret = OK;
goto exit_uartwriteconf_nofree;
}
/* We've been asked to check the response too ... */
din = kmm_malloc(maxl);
while (gotlen < maxl)
{
do
{
ret = clock_gettime(CLOCK_REALTIME, &abstime);
if (ret < 0)
{
goto exit_uartwriteconf;
}
/* Add the offset to the time in the future */
abstime.tv_nsec += NSEC_PER_SEC / 10;
if (abstime.tv_nsec >= NSEC_PER_SEC)
{
abstime.tv_nsec -= NSEC_PER_SEC;
abstime.tv_sec++;
}
ret = nxsem_timedwait(rxsem, &abstime);
if (ret == -ETIMEDOUT)
{
/* We didn't receive enough message, so fall out */
wlerr("Response timed out\n");
goto exit_uartwriteconf;
}
}
while (ret == -EINTR);
ret = lower->read(lower, &din[gotlen], maxl - gotlen);
if (ret < 0)
{
wlerr("Couldn't read\n");
ret = -ECOMM;
goto exit_uartwriteconf;
}
gotlen += ret;
}
ret = ((memcmp(din, cmp, maxl) == 0) ? 0 : -ECOMM);
exit_uartwriteconf:
free(din);
exit_uartwriteconf_nofree:
return ret;
}
/****************************************************************************
* Name: set_baudrate
*
* Set baudrate to be used in future for UART comms. In case of error the
* current baudrate is not changed.
*
* Input Parameters:
* lower - an instance of the lower half driver interface
* rxsem - pointer to the semaphore to indicate data readiness
* targetspeed - new baudrate to be used
*
* Returned Value:
* 0 or error code.
*
****************************************************************************/
static int set_baudrate(FAR const struct btuart_lowerhalf_s *lower,
FAR sem_t *rxsem, int targetspeed)
{
int ret;
uint8_t baudrate_cmd[] =
{
0x01, 0x18, g_hcd_command_byte2, 0x06, 0x00, 0x00,
targetspeed & 0xff, (targetspeed >> 8) & 0xff,
(targetspeed >> 16) & 0xff, (targetspeed >> 24) & 0xff
};
const uint8_t baudrate_conf[] =
{
0x04, 0x0e, 0x04, 0x01, 0x18, g_hcd_command_byte2, 0x00
};
ret = uartwriteconf(lower, rxsem, baudrate_cmd, sizeof(baudrate_cmd),
baudrate_conf, sizeof(baudrate_conf));
if (ret == OK)
{
ret = lower->setbaud(lower, targetspeed);
}
return ret;
}
/****************************************************************************
* Name: load_bcm4343x_firmware
*
* Attempt to load firmware into target device.
*
* Input Parameters:
* lower - an instance of the lower half driver interface
*
* Returned Value:
* 0 or error code.
*
****************************************************************************/
static int load_bcm4343x_firmware(FAR const struct btuart_lowerhalf_s *lower)
{
FAR uint8_t *rp = (FAR uint8_t *)g_bt_firmware_hcd;
int ret = OK;
sem_t rxsem;
uint8_t command;
uint8_t txlen;
uint8_t istx = 1;
uint32_t blockattempts;
/* Various responses to upload commands */
const uint8_t command_resp[] =
{
0x04, 0x0e, 0x04, 0x01, g_hcd_write_command, g_hcd_command_byte2, 0x00
};
const uint8_t launch_resp[] =
{
0x04, 0x0e, 0x04, 0x01, g_hcd_launch_command, g_hcd_command_byte2, 0x00
};
const uint8_t download_resp[] =
{
0x04, 0x0e, 0x04, 0x01, g_hcd_patchram_command, g_hcd_command_byte2, 0x00
};
/* Command to switch the chip into download mode */
const uint8_t enter_download_mode[] =
{
0x01, g_hcd_patchram_command, g_hcd_command_byte2, 0x00
};
/* Let's temporarily connect to the hci uart rx callback so we can get data */
lower->rxattach(lower, hciuart_cb, &rxsem);
lower->rxenable(lower, true);
nxsem_init(&rxsem, 0, 0);
nxsem_setprotocol(&rxsem, SEM_PRIO_NONE);
/* It is possible this could fail if modem is already at high speed, so we
* can safely ignore error return value.
*/
lower->setbaud(lower, HCIUART_LOW_SPEED);
set_baudrate(lower, &rxsem, HCIUART_DEFAULT_SPEED);
/* New baudrate is established, prepare to receive firmware */
ret = uartwriteconf(lower, &rxsem,
enter_download_mode, sizeof(enter_download_mode),
download_resp, sizeof(download_resp));
if (ret != OK)
{
wlerr("Failed to enter download mode\n");
ret = -ECOMM;
goto load_bcm4343x_firmware_finished;
}
/* ...so now we can spin, pushing firmware into the chip */
while (rp < (g_bt_firmware_hcd + g_bt_firmware_len))
{
command = rp[0];
txlen = rp[2];
if (command == g_hcd_launch_command)
{
break;
}
/* Try a few times for each block, just in case */
blockattempts = 0;
do
{
lower->write(lower, &istx, 1);
ret = uartwriteconf(lower, &rxsem, rp, 3 + txlen,
command_resp, sizeof(command_resp));
if (ret)
{
wlwarn("block offset %x upload failed, retrying\n",
rp - g_bt_firmware_hcd);
}
}
while ((ret != 0) && (++blockattempts < 3));
if (ret != 0)
{
wlerr("block upload repeatedly failed, aborting\n");
goto load_bcm4343x_firmware_finished;
}
/* The +3 in here is the header bytes from the source file */
rp += 3 + txlen;
}
/* To have gotten here we must've uploaded correctly, or barfed out */
if (command == g_hcd_launch_command)
{
lower->write(lower, &istx, 1);
ret = uartwriteconf(lower, &rxsem, rp, 3 + txlen,
launch_resp, sizeof(launch_resp));
if (ret != 0)
{
wlerr("failed to launch firmware\n");
goto load_bcm4343x_firmware_finished;
}
/* Give everything time to start up */
nxsig_usleep(1000000);
/* Once the firmware has booted it goes back to low speed,
* so kick it up again
*/
lower->setbaud(lower, HCIUART_LOW_SPEED);
ret = set_baudrate(lower, &rxsem, HCIUART_DEFAULT_SPEED);
}
else
{
ret = -ECOMM;
}
load_bcm4343x_firmware_finished:
lower->rxattach(lower, NULL, NULL);
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: btuart_register
*
* Create the UART-based Bluetooth device and register it with the
* Bluetooth stack.
*
* Input Parameters:
* lower - an instance of the lower half driver interface
*
* Returned Value:
* Zero is returned on success; a negated errno value is returned on any
* failure.
*
****************************************************************************/
int btuart_register(FAR const struct btuart_lowerhalf_s *lower)
{
FAR struct btuart_upperhalf_s *upper;
int ret;
wlinfo("lower %p\n", lower);
DEBUGASSERT(lower != NULL);
/* Allocate a new instance of the upper half driver state structure */
upper = (FAR struct btuart_upperhalf_s *)
kmm_zalloc(sizeof(struct btuart_upperhalf_s));
if (upper == NULL)
{
wlerr("ERROR: Failed to allocate upper-half state\n");
return -ENOMEM;
}
/* Initialize the upper half driver state */
upper->dev.head_reserve = H4_HEADER_SIZE;
upper->dev.open = btuart_open;
upper->dev.send = btuart_send;
upper->lower = lower;
/* Load firmware */
ret = load_bcm4343x_firmware(lower);
if (ret < 0)
{
wlerr("ERROR: Firmware error\n");
kmm_free(upper);
return -EINVAL;
}
/* And register the driver with the network and the Bluetooth stack. */
ret = bt_netdev_register(&upper->dev);
if (ret < 0)
{
wlerr("ERROR: bt_driver_register failed: %d\n", ret);
kmm_free(upper);
}
return ret;
}