nuttx/arch/arm/src/rtl8720c/ameba_hci.c
Xiang Xiao f1236da21c fs: Make the binary(no process) mode as the default
POSIX require file system shouldn't enable the \r and \n conversion by default
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
2022-06-07 20:22:26 +03:00

510 lines
11 KiB
C

/****************************************************************************
* arch/arm/src/rtl8720c/ameba_hci.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <fcntl.h>
#include <nuttx/serial/serial.h>
#include <nuttx/net/netdev.h>
#include <nuttx/net/netdev/netdev.h>
#include "amebaz_hci_board.h"
/****************************************************************************
* Private Data
****************************************************************************/
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define AMEBAZ_WIRELESS_NAME "wlan0"
#define H4_CMD 0x01
#define H4_EVT 0x04
#define HCI_H4_HDR_SIZE 1
#define HCI_CMD_HDR_SIZE 3
typedef struct
{
struct file_operations i_ops;
struct file filep;
} hci_dev_t;
struct bt_hci_evt_hdr
{
uint8_t evt;
uint8_t len;
};
/****************************************************************************
* Public Functions
****************************************************************************/
static int hci_recv(struct file *filep, uint8_t *buf, size_t count)
{
ssize_t ret;
ssize_t nread = 0;
while (count != nread)
{
ret = file_read(filep, buf + nread, count - nread);
if (ret < 0)
{
if (ret == -EAGAIN)
{
continue;
}
else
{
return ret;
}
}
nread += ret;
}
return nread;
}
static int hci_send(struct file *filep, uint8_t *buf, size_t count)
{
ssize_t ret;
ssize_t nwritten = 0;
while (nwritten != count)
{
ret = file_write(filep, buf + nwritten, count - nwritten);
if (ret < 0)
{
if (ret == -EAGAIN)
{
continue;
}
else
{
return ret;
}
}
nwritten += ret;
}
return nwritten;
}
#ifdef HCI_START_IQK
static int hci_do_iqk(struct file *filep)
{
/* OpCode: 0xfd4a, h4 data_len: Cmd(8), Event(7) */
unsigned char command[8];
struct t_data
{
uint8_t offset;
uint16_t value;
};
struct t_data data[4] =
{
{0x00, 0x4000},
{0x01, 0x0f88},
{0x02, 0x3400},
{0x3f, 0x0700}
};
if (0 == hci_check_iqk())
{
return 0;
}
for (unsigned char i = 0; i < 4; i++)
{
command[0] = H4_CMD;
command[1] = 0x4a;
command[2] = 0xfd;
command[3] = 4;
command[4] = data[i].offset;
command[5] = (uint8_t)(data[i].value >> 0);
command[6] = (uint8_t)(data[i].value >> 8);
command[7] = 0;
if (hci_send(filep, command, 8) != 8)
{
return -EIO;
}
hci_recv(filep, command, 1);
if (command[0] != H4_EVT)
{
return -EIO;
}
hci_recv(filep, command + 1, sizeof(struct bt_hci_evt_hdr));
hci_recv(filep, command + 3, command[2]);
/* Check OpCode and Status */
if (!(command[4] == 0x4a && command[5] == 0xfd) || command[6] != 0x00)
{
return -EIO;
}
}
if (hci_start_iqk())
{
return -EIO;
}
return 0;
}
#endif
static int hci_check_local_ver(struct file *filep)
{
/* OpCode: 0x1001, h4 buf_len: Cmd(1+3=4), Event(1+14=15) */
unsigned char command[15];
command[0] = H4_CMD;
command[1] = 0x01;
command[2] = 0x10;
command[3] = 0;
if (hci_send(filep, command, 4) != 4)
{
return -EIO;
}
hci_recv(filep, command, 1);
if (command[0] != H4_EVT)
{
return -EIO;
}
hci_recv(filep, command + 1, sizeof(struct bt_hci_evt_hdr));
hci_recv(filep, command + 3, command[2]);
/* Check OpCode and Status */
if (!(command[4] == 0x01 && command[5] == 0x10) || command[6] != 0x00)
{
return -EIO;
}
/* Only Check LMP Subversion */
uint16_t lmp_sbuver =
((uint16_t)command[13]) | (((uint16_t)command[14]) << 8);
if (BT_DEFAUT_LMP_SUBVER != lmp_sbuver)
{
return -EALREADY;
}
return 0;
}
static int hci_check_local_rom_ver(struct file *filep)
{
/* OpCode: 0xfc6d, h4 buf_len: Cmd(1+3=4), Event(1+7=8) */
unsigned char command[8];
command[0] = H4_CMD;
command[1] = 0x6d;
command[2] = 0xfc;
command[3] = 0;
if (hci_send(filep, command, 4) != 4)
{
return -EIO;
}
hci_recv(filep, command, 1);
if (command[0] != H4_EVT)
{
return -EIO;
}
hci_recv(filep, command + 1, sizeof(struct bt_hci_evt_hdr));
hci_recv(filep, command + 3, command[2]);
/* Check OpCode and Status */
if (!(command[4] == 0x6d && command[5] == 0xfc) || command[6] != 0x00)
{
return -EIO;
}
/* Get Chip Id (Rom_Ver+1) and Find Patch */
if (hci_find_fw_patch(command[7] + 1))
{
return -EIO;
}
return 0;
}
static int hci_update_baudrate(struct file *filep)
{
/* OpCode: 0xfc17, h4 buf_len: Cmd(1+7=8), Event(1+6=7) */
unsigned char command[8];
struct termios toptions;
uint32_t bt_baudrate;
uint32_t uart_baudrate;
command[0] = H4_CMD;
command[1] = 0x17;
command[2] = 0xfc;
command[3] = sizeof(uint32_t);
if (hci_get_baudrate(&bt_baudrate, &uart_baudrate))
{
return -EIO;
}
memcpy(&command[4], &bt_baudrate, sizeof(uint32_t));
if (hci_send(filep, command, 8) != 8)
{
return -EIO;
}
hci_recv(filep, command, 1);
if (command[0] != H4_EVT)
{
return -EIO;
}
hci_recv(filep, command + 1, sizeof(struct bt_hci_evt_hdr));
hci_recv(filep, command + 3, command[2]);
/* Check OpCode and Status */
if (!(command[4] == 0x17 && command[5] == 0xfc) || command[6] != 0x00)
{
return -EIO;
}
file_ioctl(filep, TCGETS, (unsigned long)&toptions);
cfsetispeed(&toptions, uart_baudrate);
cfsetospeed(&toptions, uart_baudrate);
return file_ioctl(filep, TCSETS, (unsigned long)&toptions);
}
static int hci_load_firmware(struct file *filep)
{
int header_size = HCI_H4_HDR_SIZE + HCI_CMD_HDR_SIZE;
uint8_t command[AMEBAZ_COMMAND_FRAGMENT_SIZE + header_size];
struct net_driver_s *drv;
int buffer_size;
uint8_t *addr;
int i;
int ret;
drv = netdev_findbyname(AMEBAZ_WIRELESS_NAME);
if (drv == NULL)
{
return -EINVAL;
}
addr = drv->d_mac.ether.ether_addr_octet;
if (hci_set_init_config_mac(addr, 1))
{
return -EIO;
}
while (hci_fetch_command(command) != AMEBAZ_COMMAND_DONE)
{
command[0] = H4_CMD;
command[1] = 0x20;
command[2] = 0xfc;
buffer_size = header_size + command[3];
usleep(10);
ret = hci_send(filep, command, buffer_size);
if (ret != buffer_size)
{
return ret;
}
hci_recv(filep, command, 1);
if (H4_EVT != command[0])
{
return -EIO;
}
hci_recv(filep, command + 1, 2);
hci_recv(filep, command + 3, command[2]);
/* Check OpCode and Status */
if (!(command[4] == 0x20 && command[5] == 0xfc) || command[6] != 0x00)
{
return -EIO;
}
}
return OK;
}
static int hci_update_efuse_iqk(struct file *filep)
{
/* OpCode: 0xfd91, h4 buf_len: Cmd(1+15=16), Event(1+6=7) */
unsigned char command[16];
command[0] = H4_CMD;
command[1] = 0x91;
command[2] = 0xfd;
if (hci_get_efuse_iqk_data(command + 3))
{
return -EIO;
}
if (hci_send(filep, command, sizeof(command)) != sizeof(command))
{
return -EIO;
}
hci_recv(filep, command, 1);
if (command[0] != H4_EVT)
{
return -EIO;
}
hci_recv(filep, command + 1, sizeof(struct bt_hci_evt_hdr));
hci_recv(filep, command + 3, command[2]);
return 0;
}
static int hci_open(struct file *filep)
{
struct inode *inode = filep->f_inode;
hci_dev_t *dev = inode->i_private;
int ret;
ret = file_open(&dev->filep,
CONFIG_AMEBA_HCI_DEV_NAME, O_RDWR);
if (ret < 0)
{
return ret;
}
if (hci_board_init())
{
goto bail;
}
#ifdef HCI_START_IQK
ret = hci_do_iqk(&dev->filep);
if (ret < 0)
{
goto bail;
}
#endif
ret = hci_check_local_ver(&dev->filep);
if (ret < 0)
{
if (-EALREADY == ret)
{
ret = 0;
}
goto bail;
}
ret = hci_check_local_rom_ver(&dev->filep);
if (ret < 0)
{
goto bail;
}
ret = hci_update_baudrate(&dev->filep);
if (ret < 0)
{
goto bail;
}
ret = hci_load_firmware(&dev->filep);
if (ret < 0)
{
goto bail;
}
ret = hci_update_efuse_iqk(&dev->filep);
bail:
ret = hci_board_init_done();
if (ret < 0)
{
file_close(&dev->filep);
}
return ret;
}
static int hci_close(struct file *filep)
{
struct inode *inode = filep->f_inode;
hci_dev_t *dev = inode->i_private;
/* FIXME: BT PowerOff */
return file_close(&dev->filep);
}
static ssize_t hci_read(struct file *filep,
char *buffer, size_t buflen)
{
struct inode *inode = filep->f_inode;
hci_dev_t *dev = inode->i_private;
return file_read(&dev->filep, buffer, buflen);
}
static ssize_t hci_write(struct file *filep, const char *buffer,
size_t buflen)
{
struct inode *inode = filep->f_inode;
hci_dev_t *dev = inode->i_private;
return file_write(&dev->filep, buffer, buflen);
}
static int hci_ioctl(struct file *filep, int cmd, unsigned long arg)
{
struct inode *inode = filep->f_inode;
hci_dev_t *dev = inode->i_private;
return file_ioctl(&dev->filep, cmd, arg);
}
static int hci_poll(struct file *filep,
struct pollfd *fds, bool setup)
{
struct inode *inode = filep->f_inode;
hci_dev_t *dev = inode->i_private;
return file_poll(&dev->filep, fds, setup);
}
static hci_dev_t g_hcidev =
{
.i_ops =
{
.open = hci_open,
.close = hci_close,
.read = hci_read,
.write = hci_write,
.ioctl = hci_ioctl,
.poll = hci_poll
},
};
int amebaz_bt_hci_uart_register(const char *path)
{
return register_driver(path, &g_hcidev.i_ops, 0666, &g_hcidev);
}