mirror of
https://github.com/sipeed/LicheeRV-Nano-Build.git
synced 2026-09-10 20:59:59 -05:00
add rtl8733 support, add logo support
This commit is contained in:
@@ -51,3 +51,10 @@ mcopy -i install/xxx/xxx.img@@1s wifi.sta ::/
|
||||
cd mountpoint
|
||||
touch xxx
|
||||
```
|
||||
|
||||
# logo
|
||||
|
||||
```
|
||||
./host/make_logo.sh input.jpeg logo.jpeg
|
||||
mcopy -i install/xxx/xxx.img@@1s logo.jpeg ::/
|
||||
```
|
||||
|
||||
@@ -57,15 +57,26 @@
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&uart0 {
|
||||
clocks = <&clk CV181X_CLK_UART0>;
|
||||
/delete-property/ clock-frequency;
|
||||
};
|
||||
|
||||
&uart1 {
|
||||
clocks = <&clk CV181X_CLK_UART1>;
|
||||
/delete-property/ clock-frequency;
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&uart2 {
|
||||
clocks = <&clk CV181X_CLK_UART2>;
|
||||
/delete-property/ clock-frequency;
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
&uart3 {
|
||||
clocks = <&clk CV181X_CLK_UART3>;
|
||||
/delete-property/ clock-frequency;
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
@@ -213,12 +224,12 @@
|
||||
reg_names = "core_mem";
|
||||
src-frequency = <375000000>;
|
||||
min-frequency = <400000>; // 400Khz
|
||||
//max-frequency = <50000000>; // 50Mhz
|
||||
max-frequency = <50000000>; // 50Mhz
|
||||
//max-frequency = <45000000>; // 45Mhz
|
||||
//max-frequency = <40000000>; // 40Mhz
|
||||
//max-frequency = <35000000>; // 35Mhz
|
||||
//max-frequency = <30000000>; // 30Mhz
|
||||
max-frequency = <25000000>; // 25Mhz
|
||||
//max-frequency = <25000000>; // 25Mhz
|
||||
//max-frequency = <20000000>; // 20Mhz
|
||||
//max-frequency = <15000000>; // 15Mhz
|
||||
//max-frequency = <10000000>; // 10Mhz
|
||||
|
||||
@@ -123,7 +123,7 @@ CONFIG_I2C_DESIGNWARE_SLAVE=y
|
||||
CONFIG_I2C_CHARDEV=y
|
||||
CONFIG_I2C_MUX=y
|
||||
CONFIG_I2C_DESIGNWARE_PLATFORM=y
|
||||
CONFIG_I2C_GPIO=y
|
||||
CONFIG_I2C_GPIO=m
|
||||
# CONFIG_PTP_1588_CLOCK is not set
|
||||
CONFIG_PINCTRL=y
|
||||
CONFIG_CVITEK_PINCTRL_CV1835=y
|
||||
@@ -495,7 +495,7 @@ CONFIG_USB_CONFIGFS_F_UAC1=y
|
||||
CONFIG_USB_CONFIGFS_F_UVC=y
|
||||
CONFIG_BUG=n
|
||||
CONFIG_ADVISE_SYSCALLS=n
|
||||
CONFIG_TIMERFD=n
|
||||
CONFIG_TIMERFD=y
|
||||
|
||||
# for licheervnano
|
||||
CONFIG_FB_SIMPLE=y
|
||||
@@ -511,6 +511,9 @@ CONFIG_BT_HS=y
|
||||
CONFIG_BT_LE=y
|
||||
CONFIG_BT_HCIUART=y
|
||||
CONFIG_BT_HCIUART_BCSP=y
|
||||
CONFIG_BT_HCIUART_H4=y
|
||||
CONFIG_BT_HCIUART_3WIRE=y
|
||||
CONFIG_BT_HCIUART_RTL=y
|
||||
CONFIG_SERIAL_DEV_BUS=y
|
||||
CONFIG_SERIAL_DEV_CTRL_TTYPORT=y
|
||||
CONFIG_SWAP=y
|
||||
@@ -555,7 +558,7 @@ CONFIG_BACKLIGHT_PWM=m
|
||||
CONFIG_SPI=y
|
||||
CONFIG_SPI_DESIGNWARE=y
|
||||
CONFIG_SPI_DW_MMIO=y
|
||||
CONFIG_SPI_GPIO=y
|
||||
CONFIG_SPI_GPIO=m
|
||||
CONFIG_SPI_SPIDEV=y
|
||||
|
||||
# these driver may broken codec driver
|
||||
|
||||
@@ -113,8 +113,8 @@ int cvi_board_init(void)
|
||||
mmio_write_32(0x030010EC, 0x3); // GPIOB 0 GPIO_MODE
|
||||
|
||||
// for licheervnano beta
|
||||
mmio_write_32(0x030010ac, 0x4); // PWRGPIO 2 PWM 10
|
||||
//mmio_write_32(0x030010ac, 0x0); // PWRGPIO 2 GPIO_MODE
|
||||
//mmio_write_32(0x030010ac, 0x4); // PWRGPIO 2 PWM 10
|
||||
mmio_write_32(0x030010ac, 0x0); // PWRGPIO 2 GPIO_MODE
|
||||
|
||||
// camera function
|
||||
//mmio_write_32(0x0300116C, 0x5); // RX4N CAM_MCLK0 for alpha
|
||||
|
||||
@@ -8,6 +8,7 @@ image boot.vfat {
|
||||
"usb.rndis0",
|
||||
"wifi.sta",
|
||||
"gt9xx",
|
||||
"logo.jpeg",
|
||||
"ver",
|
||||
}
|
||||
}
|
||||
|
||||
BIN
build/tools/common/sd_tools/logo.jpeg
Normal file
BIN
build/tools/common/sd_tools/logo.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
@@ -33,6 +33,7 @@ touch ${output_dir}/input/gt9xx
|
||||
touch ${output_dir}/input/fb
|
||||
echo ${image} > ${output_dir}/input/ver
|
||||
cp -fv ${THISDIR}/genimage_rootless.cfg ${output_dir}/genimage.cfg
|
||||
cp -fv ${THISDIR}/logo.jpeg ${output_dir}/input/logo.jpeg
|
||||
sed -i -e "s/duo.img/${image}/g" ${output_dir}/genimage.cfg
|
||||
cd ${output_dir}/
|
||||
${THISDIR}/genimage
|
||||
|
||||
@@ -5,6 +5,7 @@ then
|
||||
. /etc/profile
|
||||
if [ ! -e /boot/alpha ]
|
||||
then
|
||||
devmem 0x030010AC 0x04 # PINMUX PWM10
|
||||
blpwm="/sys/class/pwm/pwmchip8/pwm2/"
|
||||
if [ ! -e "${blpwm}" ]
|
||||
then
|
||||
|
||||
@@ -8,6 +8,7 @@ then
|
||||
insmod cfg80211.ko
|
||||
insmod 3rd/aic8800_bsp.ko
|
||||
insmod 3rd/aic8800_fdrv.ko
|
||||
insmod 3rd/8733bs.ko
|
||||
echo "OK"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
48
host/make_logo.sh
Executable file
48
host/make_logo.sh
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/bin/sh
|
||||
|
||||
usage() {
|
||||
echo "usage: $0 input output"
|
||||
echo "output image must be logo.jpeg"
|
||||
exit 1
|
||||
}
|
||||
|
||||
src=${1}
|
||||
dst=${2}
|
||||
rm -rf ${dst}
|
||||
|
||||
if [ -z ${dst} ]
|
||||
then
|
||||
usage
|
||||
fi
|
||||
|
||||
if [ ! -e ${src} ]
|
||||
then
|
||||
usage
|
||||
fi
|
||||
|
||||
|
||||
get_picture_w() {
|
||||
convert "${1}" -print "%w" /dev/null
|
||||
}
|
||||
|
||||
get_picture_h() {
|
||||
convert "${1}" -print "%h" /dev/null
|
||||
}
|
||||
|
||||
W=$(get_picture_w ${src})
|
||||
H=$(get_picture_h ${src})
|
||||
|
||||
echo "picture w : ${W}"
|
||||
echo "picture h : ${H}"
|
||||
|
||||
picturetoyuv420p() {
|
||||
ffmpeg -i ${1} -pix_fmt yuv420p ${2}
|
||||
}
|
||||
|
||||
yuv420ptopicture() {
|
||||
ffmpeg -pix_fmt yuv420p -s ${3}x${4} -i ${1} ${2}
|
||||
}
|
||||
|
||||
picturetoyuv420p $src $dst
|
||||
yuv420ptopicture $src $dst $W $H
|
||||
|
||||
@@ -12,6 +12,10 @@ config WLAN_VENDOR_REALTEK
|
||||
|
||||
if WLAN_VENDOR_REALTEK
|
||||
|
||||
config RTL8733BS
|
||||
tristate "rtl8733bs sdio wifi"
|
||||
default y
|
||||
|
||||
source "drivers/net/wireless/realtek/rtl818x/Kconfig"
|
||||
source "drivers/net/wireless/realtek/rtlwifi/Kconfig"
|
||||
source "drivers/net/wireless/realtek/rtl8xxxu/Kconfig"
|
||||
|
||||
@@ -8,6 +8,7 @@ obj-$(CONFIG_WLAN_VENDOR_BROADCOM) += broadcom/
|
||||
obj-$(CONFIG_WLAN_VENDOR_MEDIATEK) += mediatek/
|
||||
obj-$(CONFIG_WLAN_VENDOR_ICOMMSEMI) += icommsemi/
|
||||
obj-y += rtl8733bs/
|
||||
obj-y += rtl8733bs_bt_sdio/
|
||||
|
||||
all:
|
||||
$(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNEL_DIR) M=$(shell pwd) modules
|
||||
|
||||
@@ -102,7 +102,7 @@ EXTRA_CFLAGS += -DCONFIG_RTW_ANDROID=$(CONFIG_RTW_ANDROID)
|
||||
endif
|
||||
|
||||
########################## Debug ###########################
|
||||
CONFIG_RTW_DEBUG = y
|
||||
CONFIG_RTW_DEBUG = n
|
||||
# default log level is _DRV_INFO_ = 4,
|
||||
# please refer to "How_to_set_driver_debug_log_level.doc" to set the available level.
|
||||
CONFIG_RTW_LOG_LEVEL = 4
|
||||
|
||||
@@ -1012,6 +1012,7 @@ static int rtw_drv_init(
|
||||
struct acpi_device *adev;
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(CONFIG_ACPI) && defined(CONFIG_GPIO_WAKEUP)
|
||||
handle = ACPI_HANDLE(&func->dev);
|
||||
|
||||
@@ -1123,7 +1124,10 @@ free_if_vir:
|
||||
free_dvobj:
|
||||
if (status != _SUCCESS)
|
||||
sdio_dvobj_deinit(func);
|
||||
|
||||
|
||||
exit:
|
||||
printk("%s: sdio device id , class = 0x%02X, vendor = 0x%04X, device = 0x%04X\n", __func__, id->class, id->vendor, id->device);
|
||||
return status == _SUCCESS ? 0 : -ENODEV;
|
||||
}
|
||||
|
||||
|
||||
20
osdrv/extdrv/wireless/rtl8733bs_bt_sdio/Makefile
Normal file
20
osdrv/extdrv/wireless/rtl8733bs_bt_sdio/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
# the share variable is exported from wifi, the following is required
|
||||
ccflags-y += -DCONFIG_COMBO_MULTISDIO_EXPORT_FROM_RTW
|
||||
KBUILD_EXTRA_SYMBOLS=/lib/firmware/Module.symvers
|
||||
# please mark the above two lines when testing btrtksdio driver without wifi driver
|
||||
|
||||
ifneq ($(KERNELRELEASE),)
|
||||
obj-m += btrtksdio.o
|
||||
btrtksdio-y := btrtl.o btrtk_sdio.o rtk_coex.o
|
||||
else
|
||||
PWD := $(shell pwd)
|
||||
KVER := $(shell uname -r)
|
||||
KDIR := /lib/modules/$(KVER)/build
|
||||
|
||||
all:
|
||||
make ${KBUILD_EXTRA_SYMBOLS} -C $(KDIR) M=$(PWD) modules
|
||||
|
||||
clean:
|
||||
make -C $(KDIR) M=$(PWD) clean
|
||||
|
||||
endif
|
||||
3
osdrv/extdrv/wireless/rtl8733bs_bt_sdio/README
Normal file
3
osdrv/extdrv/wireless/rtl8733bs_bt_sdio/README
Normal file
@@ -0,0 +1,3 @@
|
||||
only working on:
|
||||
{SDIO_DEVICE(0x024c, 0xB73A), .class = SDIO_CLASS_WLAN, .driver_data = RTL8733B}, /* SDIO multi */
|
||||
|
||||
920
osdrv/extdrv/wireless/rtl8733bs_bt_sdio/btrtk_sdio.c
Normal file
920
osdrv/extdrv/wireless/rtl8733bs_bt_sdio/btrtk_sdio.c
Normal file
@@ -0,0 +1,920 @@
|
||||
/*
|
||||
*
|
||||
* Generic Bluetooth SDIO driver
|
||||
*
|
||||
* Copyright (C) 2007 Cambridge Silicon Radio Ltd.
|
||||
* Copyright (C) 2007 Marcel Holtmann <marcel@holtmann.org>
|
||||
* Copyright (C) 2018 Realtek Semiconductor Corp
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/version.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/timer.h>
|
||||
|
||||
#include <linux/mmc/sdio_ids.h>
|
||||
#include <linux/mmc/sdio_func.h>
|
||||
|
||||
#include <net/bluetooth/bluetooth.h>
|
||||
#include <net/bluetooth/hci_core.h>
|
||||
|
||||
#include "btrtl.h"
|
||||
|
||||
#define VERSION "0.12.4489ddc.20210423-153940"
|
||||
|
||||
#define BTSDIO_DMA_ALIGN 8
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0)
|
||||
#define hci_skb_pkt_type(skb) bt_cb((skb))->pkt_type
|
||||
#endif
|
||||
|
||||
#define BTCOEX
|
||||
#define CONFIG_COEX
|
||||
/* #define CONFIG_TEST */
|
||||
#define RTL8821DS_LPS
|
||||
|
||||
#ifdef BTCOEX
|
||||
#include "rtk_coex.h"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_COMBO_MULTISDIO_EXPORT_FROM_RTW
|
||||
extern int rtw_sdio_multi_state;
|
||||
#else
|
||||
int rtw_sdio_multi_state;
|
||||
EXPORT_SYMBOL(rtw_sdio_multi_state);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_TEST
|
||||
static int delay_time = 10;
|
||||
static int loop_time = 100; /* default per 100ms */
|
||||
|
||||
#endif /* CONFIG_TEST */
|
||||
|
||||
static const struct sdio_device_id btsdio_table[] = {
|
||||
/* Generic Bluetooth Type-A SDIO device */
|
||||
{ SDIO_DEVICE_CLASS(SDIO_CLASS_BT_A) },
|
||||
|
||||
/* Generic Bluetooth Type-B SDIO device */
|
||||
{ SDIO_DEVICE_CLASS(SDIO_CLASS_BT_B) },
|
||||
|
||||
/* Generic Bluetooth AMP controller */
|
||||
{ SDIO_DEVICE_CLASS(SDIO_CLASS_BT_AMP) },
|
||||
|
||||
{ } /* Terminating entry */
|
||||
};
|
||||
|
||||
char *pkt_type_str[] = {"command", "acl", "sco", "event"};
|
||||
MODULE_DEVICE_TABLE(sdio, btsdio_table);
|
||||
|
||||
struct btsdio_data {
|
||||
struct hci_dev *hdev;
|
||||
struct sdio_func *func;
|
||||
|
||||
struct work_struct work;
|
||||
|
||||
struct sk_buff_head txq;
|
||||
int int_count;
|
||||
|
||||
struct task_struct *thread;
|
||||
wait_queue_head_t wq;
|
||||
|
||||
spinlock_t lock;
|
||||
|
||||
#ifdef CONFIG_TEST
|
||||
struct delayed_work timer_loop;
|
||||
struct delayed_work timer_delay;
|
||||
struct workqueue_struct *test_workqueue;
|
||||
#endif
|
||||
|
||||
#ifdef RTL8821DS_LPS
|
||||
unsigned long last_busy;
|
||||
#endif
|
||||
};
|
||||
|
||||
#define REG_RDAT 0x00 /* Receiver Data */
|
||||
#define REG_TDAT 0x00 /* Transmitter Data */
|
||||
#define REG_PC_RRT 0x10 /* Read Packet Control */
|
||||
#define REG_PC_WRT 0x11 /* Write Packet Control */
|
||||
#define REG_RTC_STAT 0x12 /* Retry Control Status */
|
||||
#define REG_RTC_SET 0x12 /* Retry Control Set */
|
||||
#define REG_INTRD 0x13 /* Interrupt Indication */
|
||||
#define REG_CL_INTRD 0x13 /* Interrupt Clear */
|
||||
#define REG_EN_INTRD 0x14 /* Interrupt Enable */
|
||||
#define REG_MD_STAT 0x20 /* Bluetooth Mode Status */
|
||||
#define REG_MD_SET 0x20 /* Bluetooth Mode Set */
|
||||
#define REG_FIFO_STATUS 0x41
|
||||
#define REG_WAKEUP 0x40 /* Wake-up register */
|
||||
|
||||
#ifdef CONFIG_TEST
|
||||
|
||||
static void loop_work_func(struct work_struct *work)
|
||||
{
|
||||
struct btsdio_data *data;
|
||||
|
||||
data = container_of(work, struct btsdio_data, timer_delay.work);
|
||||
queue_delayed_work(data->test_workqueue, &data->timer_loop,
|
||||
msecs_to_jiffies(loop_time));
|
||||
queue_delayed_work(data->test_workqueue, &data->timer_delay,
|
||||
msecs_to_jiffies(delay_time));
|
||||
|
||||
rtw_sdio_multi_state = 1;
|
||||
|
||||
/* BT_INFO("multi_state = %d: BT claiming bus.", rtw_sdio_multi_state);
|
||||
*/
|
||||
}
|
||||
|
||||
static void delay_work_func(struct work_struct *work)
|
||||
{
|
||||
struct btsdio_data *data;
|
||||
struct sdio_func *func;
|
||||
int err;
|
||||
|
||||
data = container_of(work, struct btsdio_data, timer_delay.work);
|
||||
func = data->func;
|
||||
|
||||
rtw_sdio_multi_state = 0;
|
||||
|
||||
sdio_claim_host(func);
|
||||
sdio_writeb(func, 0x02, 0x40, &err);
|
||||
sdio_release_host(func);
|
||||
|
||||
if (err)
|
||||
BT_ERR("Write sdio reg in delay timer, error %d", err);
|
||||
|
||||
/* BT_INFO("multi_state = %d: BT releasing bus.", rtw_sdio_multi_state);
|
||||
*/
|
||||
}
|
||||
|
||||
#endif /* CONFIG_TEST */
|
||||
|
||||
static int wait_for_txfifo_ready(struct sdio_func *func)
|
||||
{
|
||||
u8 result;
|
||||
int err;
|
||||
|
||||
for (;;) {
|
||||
result = sdio_readb(func, REG_FIFO_STATUS, &err);
|
||||
if (err) {
|
||||
BT_ERR("Read error %d while waiting for tx fifo ready",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((result & 0x01) || (result & 0x02))
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
static int wait_for_rxfifo_ready(struct sdio_func *func)
|
||||
{
|
||||
u8 result;
|
||||
int err;
|
||||
|
||||
for (;;) {
|
||||
result = sdio_readb(func, REG_FIFO_STATUS, &err);
|
||||
if (err) {
|
||||
BT_ERR("Read error %d while waiting for rx fifo ready",
|
||||
err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((result & 0x04) || (result & 0x08))
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef RTL8821DS_LPS
|
||||
static int wait_for_io_ready(struct sdio_func *func)
|
||||
{
|
||||
u8 result;
|
||||
int err = 0;
|
||||
|
||||
for (;;) {
|
||||
/* read function 0 cccr 0x03 reg that is io ready register */
|
||||
result = sdio_f0_readb(func, 0x03, &err);
|
||||
if (err) {
|
||||
BT_ERR("wait for io ready error %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (result & (1 << 2))
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int btsdio_tx_packet(struct btsdio_data *data, struct sk_buff *skb)
|
||||
{
|
||||
int err;
|
||||
int pkt_type;
|
||||
#ifdef CONFIG_COEX
|
||||
struct sdio_func *func = data->func;
|
||||
#endif
|
||||
unsigned int len;
|
||||
struct sk_buff *tmpskb;
|
||||
|
||||
#ifdef RTL8821DS_LPS
|
||||
long elapsed;
|
||||
u8 reg_val;
|
||||
#endif
|
||||
|
||||
pkt_type = hci_skb_pkt_type(skb);
|
||||
BT_DBG("%s", data->hdev->name);
|
||||
/* BT_INFO("tx packet: pkt_type %s, pkt_len: %d",
|
||||
* pkt_type_str[pkt_type - 1], skb->len);
|
||||
*/
|
||||
|
||||
#ifdef BTCOEX
|
||||
switch (pkt_type) {
|
||||
case HCI_COMMAND_PKT:
|
||||
rtk_btcoex_parse_cmd(skb->data, skb->len);
|
||||
break;
|
||||
case HCI_ACLDATA_PKT:
|
||||
rtk_btcoex_parse_l2cap_data_tx(skb->data, skb->len);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Prepend Type-A header */
|
||||
skb_push(skb, 4);
|
||||
if (1) {
|
||||
skb->data[0] = skb->len & 0xff;
|
||||
skb->data[1] = (skb->len >> 8) & 0xff;
|
||||
skb->data[2] = (skb->len >> 16) & 0xff;
|
||||
} else {
|
||||
skb->data[2] = skb->len & 0xff;
|
||||
skb->data[1] = (skb->len >> 8) & 0xff;
|
||||
skb->data[0] = (skb->len >> 16) & 0xff;
|
||||
}
|
||||
|
||||
skb->data[3] = hci_skb_pkt_type(skb);
|
||||
|
||||
if ((unsigned long)skb->data & (BTSDIO_DMA_ALIGN - 1)) {
|
||||
tmpskb = bt_skb_alloc(skb->len, GFP_ATOMIC);
|
||||
if (!tmpskb) {
|
||||
BT_ERR("Could not alloc tmp skb for btrtksdio tx");
|
||||
return -ENOMEM;
|
||||
}
|
||||
memcpy(skb_put(tmpskb, skb->len), skb->data, skb->len);
|
||||
if ((unsigned long)tmpskb->data & (BTSDIO_DMA_ALIGN - 1))
|
||||
BT_ERR("Packet address not aligned, %p, %p",
|
||||
skb->data, tmpskb->data);
|
||||
kfree_skb(skb);
|
||||
skb = tmpskb;
|
||||
}
|
||||
|
||||
/* Realtek btsdio tx timing
|
||||
* 1) repeatedly check fifo status until at least one tx fifo ready
|
||||
* 2) write sdio header(4 bytes) and write len <= 128 ? len : 128
|
||||
* bytes data
|
||||
* 3) if not done, repeat the following until done:
|
||||
* 3.1) check fifo status until at least one tx fifo ready
|
||||
* 3.2) write at most 128 bytes data;
|
||||
*/
|
||||
|
||||
sdio_claim_host(data->func);
|
||||
|
||||
#ifdef RTL8821DS_LPS
|
||||
/* Check if the controller is in LPS */
|
||||
elapsed = jiffies - data->last_busy;
|
||||
if (elapsed >= 0 && elapsed < msecs_to_jiffies(5000))
|
||||
goto exit_lps;
|
||||
|
||||
/* if jiffies has wrapped around (elapsed < 0) or elapsed is equal to
|
||||
* or bigger than 5s, assume 8821DS has been in LPS and wake it up.
|
||||
*/
|
||||
BT_INFO("Tx: wake up controller that may be in LPS");
|
||||
reg_val = 0x01;
|
||||
sdio_writeb(func, reg_val, REG_WAKEUP, &err);
|
||||
if (err) {
|
||||
BT_ERR("Write REG_WAKEUP error %d", err);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Wait for the Bluetooth IO Ready */
|
||||
err = wait_for_io_ready(data->func);
|
||||
if (err)
|
||||
goto exit;
|
||||
|
||||
exit_lps:
|
||||
data->last_busy = jiffies;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_COEX
|
||||
rtw_sdio_multi_state = 1;
|
||||
/* BT_INFO("multi_state = %d: BT claiming bus.", rtw_sdio_multi_state);
|
||||
*/
|
||||
#endif
|
||||
|
||||
err = wait_for_txfifo_ready(data->func);
|
||||
if (err)
|
||||
goto exit;
|
||||
|
||||
err = sdio_writesb(data->func, REG_TDAT, skb->data, 4);
|
||||
if (err) {
|
||||
/* TODO: retry for header */
|
||||
BT_ERR("Write BTSDIO packet header error %d", err);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* get rid of header on success */
|
||||
skb_pull(skb, 4);
|
||||
data->hdev->stat.byte_tx += 4;
|
||||
|
||||
/* NOTE: FIFO is already ready to read the first part of payload */
|
||||
do {
|
||||
if (skb->len <= 128)
|
||||
len = skb->len;
|
||||
else
|
||||
len = 128;
|
||||
err = sdio_writesb(data->func, REG_TDAT, skb->data, len);
|
||||
if (err) {
|
||||
BT_ERR("Couldn't write %u byptes to card error %d",
|
||||
len, err);
|
||||
goto exit;
|
||||
}
|
||||
data->hdev->stat.byte_tx += len;
|
||||
skb_pull(skb, len);
|
||||
err = wait_for_txfifo_ready(data->func);
|
||||
if (err)
|
||||
break;
|
||||
} while (skb->len);
|
||||
exit:
|
||||
|
||||
#ifdef CONFIG_COEX
|
||||
rtw_sdio_multi_state = 0;
|
||||
|
||||
sdio_writeb(func, 0x02, 0x40, &err);
|
||||
if (err)
|
||||
BT_ERR("rtlsdio coex tx error %d", err);
|
||||
/* BT_INFO("multi_state = %d: BT releasing bus.", rtw_sdio_multi_state);
|
||||
*/
|
||||
#endif
|
||||
|
||||
sdio_release_host(data->func);
|
||||
kfree_skb(skb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btsdio_rx_packet(struct btsdio_data *data)
|
||||
{
|
||||
u8 hdr[4] __aligned(4);
|
||||
struct sk_buff *skb;
|
||||
int err;
|
||||
unsigned int len;
|
||||
unsigned int hci_len;
|
||||
#ifdef CONFIG_COEX
|
||||
struct sdio_func *func = data->func;
|
||||
#endif
|
||||
|
||||
BT_DBG("%s", data->hdev->name);
|
||||
sdio_claim_host(data->func);
|
||||
err = sdio_readsb(data->func, hdr, REG_RDAT, 4);
|
||||
if (err < 0) {
|
||||
BT_ERR("Read BTSDIO packet header error %d", err);
|
||||
sdio_release_host(data->func);
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef RTL8821DS_LPS
|
||||
/* If we can read packet from controller, the controller is awake */
|
||||
data->last_busy = jiffies;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_COEX
|
||||
rtw_sdio_multi_state = 1;
|
||||
/* BT_INFO("multi_state = %d: BT claiming bus.", rtw_sdio_multi_state);
|
||||
*/
|
||||
#endif
|
||||
|
||||
if (1)
|
||||
len = hdr[0] | (hdr[1] << 8) | (hdr[2] << 16);
|
||||
else
|
||||
len = hdr[2] | (hdr[1] << 8) | (hdr[0] << 16);
|
||||
|
||||
if (len < 4 || len > 65543)
|
||||
return -EILSEQ;
|
||||
|
||||
hci_len = len - 4;
|
||||
skb = bt_skb_alloc(hci_len, GFP_KERNEL);
|
||||
if (!skb) {
|
||||
BT_ERR("Couldn't alloc skb for rx");
|
||||
/* Out of memory. Prepare a read retry and just
|
||||
* return with the expectation that the next time
|
||||
* we're called we'll have more memory.
|
||||
*/
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Realtek btsdio rx timing:
|
||||
* if hci_len <= 256, read it. Otherwise read 256, then repeat following
|
||||
* repeatedly check for rx fifo confition until at least one is empty,
|
||||
* then read 128.
|
||||
*/
|
||||
|
||||
/* NOTE: FIFO is already ready to write the first part of payload */
|
||||
len = 256;
|
||||
do {
|
||||
if (hci_len < len)
|
||||
len = hci_len;
|
||||
|
||||
/* For the first time, read 256-byte data */
|
||||
err = sdio_readsb(data->func, skb_put(skb, len), REG_RDAT, len);
|
||||
if (err) {
|
||||
BT_ERR("Read payload error %d", err);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
hci_len -= len;
|
||||
data->hdev->stat.byte_rx += len;
|
||||
|
||||
if (!hci_len)
|
||||
break;
|
||||
|
||||
len = 128;
|
||||
err = wait_for_rxfifo_ready(data->func);
|
||||
if (err)
|
||||
break;
|
||||
} while (hci_len);
|
||||
|
||||
exit:
|
||||
#ifdef CONFIG_COEX
|
||||
rtw_sdio_multi_state = 0;
|
||||
|
||||
sdio_writeb(func, 0x02, 0x40, &err);
|
||||
if (err)
|
||||
BT_ERR("rtlsdio coex rx error %d", err);
|
||||
/* BT_INFO("multi_state = %d: BT releasing bus.", rtw_sdio_multi_state);
|
||||
*/
|
||||
#endif
|
||||
sdio_release_host(data->func);
|
||||
/* BT_INFO("rx packet: pkt_type %s, pkt_len: %d",
|
||||
* pkt_type_str[hdr[3] - 1], len - 4);
|
||||
*/
|
||||
|
||||
hci_skb_pkt_type(skb) = hdr[3];
|
||||
#ifdef BTCOEX
|
||||
switch (hci_skb_pkt_type(skb)) {
|
||||
case HCI_EVENT_PKT:
|
||||
rtk_btcoex_parse_event(skb->data, skb->len);
|
||||
break;
|
||||
case HCI_ACLDATA_PKT:
|
||||
rtk_btcoex_parse_l2cap_data_rx(skb->data, skb->len);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
err = hci_recv_frame(data->hdev, skb);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void btsdio_interrupt(struct sdio_func *func)
|
||||
{
|
||||
struct btsdio_data *data = sdio_get_drvdata(func);
|
||||
u8 intrd;
|
||||
ulong flags;
|
||||
int err;
|
||||
|
||||
BT_DBG("%s", data->hdev->name);
|
||||
|
||||
/* BT_INFO("Got Data Interrupt"); */
|
||||
intrd = sdio_readb(func, REG_INTRD, &err);
|
||||
if (!err && (intrd & 0x01)) {
|
||||
sdio_writeb(func, 0x01, REG_CL_INTRD, &err);
|
||||
if (err) {
|
||||
BT_ERR("Clear REG_CL_INTRD error %d", err);
|
||||
return;
|
||||
}
|
||||
spin_lock_irqsave(&data->lock, flags);
|
||||
data->int_count++;
|
||||
spin_unlock_irqrestore(&data->lock, flags);
|
||||
wake_up_interruptible(&data->wq);
|
||||
} else {
|
||||
BT_ERR("Unknown interrupt, err %d, intrd 0x%02x", err, intrd);
|
||||
}
|
||||
}
|
||||
|
||||
static int btsdio_open(struct hci_dev *hdev)
|
||||
{
|
||||
struct btsdio_data *data = hci_get_drvdata(hdev);
|
||||
int err;
|
||||
#ifdef RTL8821DS_LPS
|
||||
u8 reg_val;
|
||||
#endif
|
||||
|
||||
BT_DBG("%s", hdev->name);
|
||||
|
||||
sdio_claim_host(data->func);
|
||||
|
||||
#ifdef RTL8821DS_LPS
|
||||
reg_val = 0x01;
|
||||
sdio_writeb(data->func, reg_val, REG_WAKEUP, &err);
|
||||
if (err) {
|
||||
BT_ERR("Write REG_WAKEUP error while open, %d", err);
|
||||
goto release;
|
||||
}
|
||||
|
||||
data->last_busy = jiffies;
|
||||
#endif
|
||||
|
||||
err = sdio_enable_func(data->func);
|
||||
if (err < 0)
|
||||
goto release;
|
||||
|
||||
err = sdio_claim_irq(data->func, btsdio_interrupt);
|
||||
if (err < 0) {
|
||||
sdio_disable_func(data->func);
|
||||
goto release;
|
||||
}
|
||||
|
||||
if (data->func->class == SDIO_CLASS_BT_B) {
|
||||
sdio_writeb(data->func, 0x00, REG_MD_SET, &err);
|
||||
if (err) {
|
||||
BT_ERR("Clear REG_MD_SET error %d", err);
|
||||
goto release;
|
||||
}
|
||||
}
|
||||
|
||||
sdio_writeb(data->func, 0x01, REG_EN_INTRD, &err);
|
||||
if (err) {
|
||||
BT_ERR("Set REG_EN_INTRD error %d", err);
|
||||
goto release;
|
||||
}
|
||||
|
||||
#ifdef BTCOEX
|
||||
rtk_btcoex_open(hdev);
|
||||
#endif
|
||||
|
||||
release:
|
||||
sdio_release_host(data->func);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int btsdio_close(struct hci_dev *hdev)
|
||||
{
|
||||
int err;
|
||||
struct btsdio_data *data = hci_get_drvdata(hdev);
|
||||
|
||||
BT_DBG("%s", hdev->name);
|
||||
|
||||
sdio_claim_host(data->func);
|
||||
|
||||
sdio_writeb(data->func, 0x00, REG_EN_INTRD, &err);
|
||||
if (err)
|
||||
BT_ERR("Clear REG_EN_INTRD error %d", err);
|
||||
|
||||
sdio_release_irq(data->func);
|
||||
|
||||
sdio_disable_func(data->func);
|
||||
|
||||
sdio_release_host(data->func);
|
||||
|
||||
#ifdef BTCOEX
|
||||
rtk_btcoex_close();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btsdio_flush(struct hci_dev *hdev)
|
||||
{
|
||||
struct btsdio_data *data = hci_get_drvdata(hdev);
|
||||
|
||||
BT_DBG("%s", hdev->name);
|
||||
|
||||
skb_queue_purge(&data->txq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btsdio_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
|
||||
{
|
||||
|
||||
struct btsdio_data *data = hci_get_drvdata(hdev);
|
||||
|
||||
/* BT_INFO("btsdio_send_frame"); */
|
||||
BT_DBG("%s", hdev->name);
|
||||
|
||||
switch (hci_skb_pkt_type(skb)) {
|
||||
case HCI_COMMAND_PKT:
|
||||
hdev->stat.cmd_tx++;
|
||||
break;
|
||||
|
||||
case HCI_ACLDATA_PKT:
|
||||
hdev->stat.acl_tx++;
|
||||
break;
|
||||
|
||||
case HCI_SCODATA_PKT:
|
||||
hdev->stat.sco_tx++;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EILSEQ;
|
||||
}
|
||||
|
||||
skb_queue_tail(&data->txq, skb);
|
||||
/* schedule_work(&data->work); */
|
||||
wake_up_interruptible(&data->wq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int main_thread(void *dat)
|
||||
{
|
||||
struct btsdio_data *data;
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 13, 0)
|
||||
wait_queue_entry_t wait;
|
||||
#else
|
||||
wait_queue_t wait;
|
||||
#endif
|
||||
ulong flags;
|
||||
int err;
|
||||
struct sk_buff *skb;
|
||||
|
||||
data = dat;
|
||||
|
||||
init_waitqueue_entry(&wait, current);
|
||||
|
||||
for (;;) {
|
||||
add_wait_queue(&data->wq, &wait);
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
|
||||
if (kthread_should_stop()) {
|
||||
BT_INFO("break from main thread");
|
||||
break;
|
||||
}
|
||||
if (data->int_count == 0 && skb_queue_empty(&data->txq)) {
|
||||
/* BT_INFO("main thread sleeping"); */
|
||||
schedule();
|
||||
}
|
||||
set_current_state(TASK_RUNNING);
|
||||
remove_wait_queue(&data->wq, &wait);
|
||||
|
||||
/* BT_INFO("main thread woke up"); */
|
||||
|
||||
spin_lock_irqsave(&data->lock, flags);
|
||||
if (data->int_count != 0) {
|
||||
data->int_count = 0;
|
||||
spin_unlock_irqrestore(&data->lock, flags);
|
||||
btsdio_rx_packet(data);
|
||||
} else
|
||||
spin_unlock_irqrestore(&data->lock, flags);
|
||||
|
||||
skb = skb_dequeue(&data->txq);
|
||||
if (skb) {
|
||||
err = btsdio_tx_packet(data, skb);
|
||||
if (err < 0) {
|
||||
BT_ERR("BTSDIO Tx packet error %d", err);
|
||||
data->hdev->stat.err_tx++;
|
||||
skb_queue_head(&data->txq, skb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int btsdio_probe(struct sdio_func *func,
|
||||
const struct sdio_device_id *id)
|
||||
{
|
||||
printk("rtk bt sdio driver probe\n\r");
|
||||
struct btsdio_data *data;
|
||||
struct hci_dev *hdev;
|
||||
struct sdio_func_tuple *tuple = func->tuples;
|
||||
int err;
|
||||
u8 reg_val;
|
||||
|
||||
BT_INFO("func %p id %p class 0x%04x", func, id, func->class);
|
||||
|
||||
while (tuple) {
|
||||
BT_DBG("code 0x%x size %d", tuple->code, tuple->size);
|
||||
tuple = tuple->next;
|
||||
}
|
||||
|
||||
data = devm_kzalloc(&func->dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->func = func;
|
||||
|
||||
data->int_count = 0;
|
||||
|
||||
/* INIT_WORK(&data->work, btsdio_work); */
|
||||
|
||||
skb_queue_head_init(&data->txq);
|
||||
|
||||
init_waitqueue_head(&data->wq);
|
||||
|
||||
spin_lock_init(&data->lock);
|
||||
|
||||
if (func->vendor == 0x024c &&
|
||||
(func->device == 0xb73a || func->device == 0x885a)) {
|
||||
sdio_claim_host(func);
|
||||
/* read and update */
|
||||
reg_val = sdio_readb(func, 0x71, &err);
|
||||
if (err) {
|
||||
BT_ERR("Read 0x71 register failure, %d", err);
|
||||
} else {
|
||||
reg_val |= (1 << 2);
|
||||
sdio_writeb(func, reg_val, 0x71, &err);
|
||||
if (err)
|
||||
BT_ERR("Enable ECO function error, %d", err);
|
||||
}
|
||||
sdio_release_host(func);
|
||||
}
|
||||
|
||||
#ifdef RTL8821DS_LPS
|
||||
reg_val = 0x01;
|
||||
sdio_claim_host(func);
|
||||
sdio_writeb(func, reg_val, REG_WAKEUP, &err);
|
||||
sdio_release_host(func);
|
||||
if (err) {
|
||||
BT_ERR("Write REG_WAKEUP error while probe, %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
data->last_busy = jiffies;
|
||||
#endif
|
||||
|
||||
data->thread = kthread_run(main_thread, data, "btrtk");
|
||||
|
||||
hdev = hci_alloc_dev();
|
||||
if (!hdev) {
|
||||
BT_ERR("Couldn't alloc hdev");
|
||||
err = -ENOMEM;
|
||||
goto err_alloc_dev;
|
||||
}
|
||||
|
||||
hdev->bus = HCI_SDIO;
|
||||
hci_set_drvdata(hdev, data);
|
||||
|
||||
if (id->class == SDIO_CLASS_BT_AMP)
|
||||
hdev->dev_type = HCI_AMP;
|
||||
else
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0)
|
||||
hdev->dev_type = HCI_BREDR;
|
||||
#else
|
||||
hdev->dev_type = HCI_PRIMARY;
|
||||
#endif
|
||||
|
||||
data->hdev = hdev;
|
||||
|
||||
SET_HCIDEV_DEV(hdev, &func->dev);
|
||||
|
||||
hdev->open = btsdio_open;
|
||||
hdev->setup = btrtl_setup_8821ds;
|
||||
hdev->close = btsdio_close;
|
||||
hdev->flush = btsdio_flush;
|
||||
hdev->send = btsdio_send_frame;
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 1)
|
||||
set_bit(HCI_QUIRK_RESET_ON_CLOSE, &hdev->quirks);
|
||||
BT_DBG("set_bit(HCI_QUIRK_RESET_ON_CLOSE, &hdev->quirks);");
|
||||
#endif
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
|
||||
set_bit(HCI_QUIRK_SIMULTANEOUS_DISCOVERY, &hdev->quirks);
|
||||
#endif
|
||||
|
||||
err = hci_register_dev(hdev);
|
||||
if (err < 0) {
|
||||
BT_ERR("Couldn't register hdev");
|
||||
goto err_hci_reg;
|
||||
}
|
||||
|
||||
#ifdef BTCOEX
|
||||
rtk_btcoex_probe(hdev);
|
||||
#endif
|
||||
|
||||
sdio_set_drvdata(func, data);
|
||||
|
||||
#ifdef CONFIG_TEST
|
||||
BT_INFO("BT interrupt every %d mills", loop_time);
|
||||
|
||||
data->test_workqueue = create_workqueue("rtlsdiotest");
|
||||
if (!data->test_workqueue) {
|
||||
BT_ERR("Couldn't test create workqueue");
|
||||
err = -ENOMEM;
|
||||
goto err_create_wq;
|
||||
}
|
||||
|
||||
INIT_DELAYED_WORK(&data->timer_loop, loop_work_func);
|
||||
INIT_DELAYED_WORK(&data->timer_delay, delay_work_func);
|
||||
|
||||
queue_delayed_work(data->test_workqueue, &data->timer_loop,
|
||||
msecs_to_jiffies(loop_time));
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
|
||||
#ifdef CONFIG_TEST
|
||||
err_create_wq:
|
||||
sdio_set_drvdata(func, NULL);
|
||||
hci_unregister_dev(hdev);
|
||||
#endif
|
||||
err_hci_reg:
|
||||
hci_free_dev(hdev);
|
||||
err_alloc_dev:
|
||||
kthread_stop(data->thread);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void btsdio_remove(struct sdio_func *func)
|
||||
{
|
||||
struct btsdio_data *data = sdio_get_drvdata(func);
|
||||
struct hci_dev *hdev;
|
||||
|
||||
BT_DBG("func %p", func);
|
||||
|
||||
if (!data)
|
||||
return;
|
||||
|
||||
hdev = data->hdev;
|
||||
|
||||
#ifdef CONFIG_TEST
|
||||
cancel_delayed_work_sync(&data->timer_loop);
|
||||
cancel_delayed_work_sync(&data->timer_delay);
|
||||
flush_workqueue(data->test_workqueue);
|
||||
destroy_workqueue(data->test_workqueue);
|
||||
#endif
|
||||
|
||||
sdio_claim_host(func);
|
||||
sdio_release_irq(func);
|
||||
sdio_disable_func(func);
|
||||
sdio_release_host(func);
|
||||
|
||||
kthread_stop(data->thread);
|
||||
|
||||
sdio_set_drvdata(func, NULL);
|
||||
|
||||
hci_unregister_dev(hdev);
|
||||
|
||||
hci_free_dev(hdev);
|
||||
}
|
||||
|
||||
static struct sdio_driver btsdio_driver = {
|
||||
.name = "btstdsdio",
|
||||
.probe = btsdio_probe,
|
||||
.remove = btsdio_remove,
|
||||
.id_table = btsdio_table,
|
||||
};
|
||||
|
||||
static int __init btsdio_init(void)
|
||||
{
|
||||
BT_INFO("Realtek Bluetooth SDIO driver ver %s", VERSION);
|
||||
#ifdef BTCOEX
|
||||
rtk_btcoex_init();
|
||||
#endif
|
||||
return sdio_register_driver(&btsdio_driver);
|
||||
}
|
||||
|
||||
static void __exit btsdio_exit(void)
|
||||
{
|
||||
sdio_unregister_driver(&btsdio_driver);
|
||||
#ifdef BTCOEX
|
||||
rtk_btcoex_exit();
|
||||
#endif
|
||||
}
|
||||
|
||||
module_init(btsdio_init);
|
||||
module_exit(btsdio_exit);
|
||||
|
||||
#ifdef CONFIG_TEST
|
||||
module_param(delay_time, int, 0644);
|
||||
module_param(loop_time, int, 0644);
|
||||
#endif
|
||||
|
||||
MODULE_DESCRIPTION("Realtek Bluetooth SDIO driver ver " VERSION);
|
||||
MODULE_VERSION(VERSION);
|
||||
MODULE_LICENSE("GPL");
|
||||
638
osdrv/extdrv/wireless/rtl8733bs_bt_sdio/btrtl.c
Normal file
638
osdrv/extdrv/wireless/rtl8733bs_bt_sdio/btrtl.c
Normal file
@@ -0,0 +1,638 @@
|
||||
/*
|
||||
* Bluetooth support for Realtek devices
|
||||
*
|
||||
* Copyright (C) 2015 Endless Mobile, Inc.
|
||||
* Copyright (C) 2018 Realtek Semiconductor Corp
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <asm/unaligned.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/ctype.h>
|
||||
|
||||
#include <net/bluetooth/bluetooth.h>
|
||||
#include <net/bluetooth/hci_core.h>
|
||||
|
||||
#include "btrtl.h"
|
||||
|
||||
#define VERSION "0.1"
|
||||
|
||||
#define BDADDR_STRING_LEN 17
|
||||
#define BDADDR_FILE "/opt/bdaddr"
|
||||
|
||||
#define RTL_EPATCH_SIGNATURE "Realtech"
|
||||
#define RTL_ROM_LMP_8821DS 0x8822
|
||||
#define RTL_ROM_LMP_8723FS 0x8723
|
||||
|
||||
#define IC_MATCH_FL_LMPSUBV (1 << 0)
|
||||
#define IC_MATCH_FL_HCIREV (1 << 1)
|
||||
#define IC_INFO(lmps, hcir) \
|
||||
.match_flags = IC_MATCH_FL_LMPSUBV | IC_MATCH_FL_HCIREV, \
|
||||
.lmp_subver = (lmps), \
|
||||
.hci_rev = (hcir)
|
||||
|
||||
struct id_table {
|
||||
__u16 match_flags;
|
||||
__u16 lmp_subver;
|
||||
__u16 hci_rev;
|
||||
bool config_needed;
|
||||
char *fw_name;
|
||||
char *cfg_name;
|
||||
};
|
||||
|
||||
static const struct id_table ic_id_table[] = {
|
||||
{ IC_INFO(RTL_ROM_LMP_8821DS, 0xc),
|
||||
.config_needed = false,
|
||||
.fw_name = "rtl_bt/rtl8821ds_fw",
|
||||
.cfg_name = "rtl_bt/rtl8821ds_config" },
|
||||
{ IC_INFO(RTL_ROM_LMP_8723FS, 0xf),
|
||||
.config_needed = false,
|
||||
.fw_name = "rtl_bt/rtl8723fs_fw",
|
||||
.cfg_name = "rtl_bt/rtl8723fs_config" },
|
||||
};
|
||||
|
||||
struct rtl_config_item {
|
||||
u16 offset;
|
||||
u8 len;
|
||||
u8 data[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct rtl_config_hdr {
|
||||
u8 sign[4];
|
||||
u16 len;
|
||||
struct rtl_config_item item[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct cfg_list_item {
|
||||
struct list_head list;
|
||||
struct rtl_config_item *item;
|
||||
} __attribute__((packed));
|
||||
|
||||
static char *bdaddr = "ff:ff:ff:ff:ff:ff";
|
||||
module_param(bdaddr, charp, 0644);
|
||||
|
||||
static int rtl_read_rom_version(struct hci_dev *hdev, u8 *version)
|
||||
{
|
||||
struct rtl_rom_version_evt *rom_version;
|
||||
struct sk_buff *skb;
|
||||
|
||||
/* Read RTL ROM version command */
|
||||
skb = __hci_cmd_sync(hdev, 0xfc6d, 0, NULL, HCI_INIT_TIMEOUT);
|
||||
if (IS_ERR(skb)) {
|
||||
BT_ERR("%s: Read ROM version failed (%ld)",
|
||||
hdev->name, PTR_ERR(skb));
|
||||
return PTR_ERR(skb);
|
||||
}
|
||||
|
||||
if (skb->len != sizeof(*rom_version)) {
|
||||
BT_ERR("%s: RTL version event length mismatch", hdev->name);
|
||||
kfree_skb(skb);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
rom_version = (struct rtl_rom_version_evt *)skb->data;
|
||||
bt_dev_info(hdev, "rom_version status=%x version=%x",
|
||||
rom_version->status, rom_version->version);
|
||||
|
||||
*version = rom_version->version;
|
||||
|
||||
kfree_skb(skb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtlbt_parse_firmware(struct hci_dev *hdev, u16 lmp_subver,
|
||||
const struct firmware *fw,
|
||||
unsigned char **_buf)
|
||||
{
|
||||
const u8 extension_sig[] = { 0x51, 0x04, 0xfd, 0x77 };
|
||||
struct rtl_epatch_header *epatch_info;
|
||||
unsigned char *buf;
|
||||
int i, ret, len;
|
||||
size_t min_size;
|
||||
u8 opcode, length, data, rom_version = 0;
|
||||
int project_id = -1;
|
||||
const unsigned char *fwptr, *chip_id_base;
|
||||
const unsigned char *patch_length_base, *patch_offset_base;
|
||||
u32 patch_offset = 0;
|
||||
u16 patch_length, num_patches;
|
||||
static const struct {
|
||||
__u16 lmp_subver;
|
||||
__u8 id;
|
||||
} project_id_to_lmp_subver[] = {
|
||||
{ RTL_ROM_LMP_8821DS, 11 }, /* 8821DS */
|
||||
{ RTL_ROM_LMP_8821DS, 13 }, /* 8821DS */
|
||||
{ RTL_ROM_LMP_8723FS, 19 }, /* 8723FS */
|
||||
};
|
||||
|
||||
ret = rtl_read_rom_version(hdev, &rom_version);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
min_size = sizeof(struct rtl_epatch_header) + sizeof(extension_sig) + 3;
|
||||
if (fw->size < min_size)
|
||||
return -EINVAL;
|
||||
|
||||
fwptr = fw->data + fw->size - sizeof(extension_sig);
|
||||
if (memcmp(fwptr, extension_sig, sizeof(extension_sig)) != 0) {
|
||||
BT_ERR("%s: extension section signature mismatch", hdev->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Loop from the end of the firmware parsing instructions, until
|
||||
* we find an instruction that identifies the "project ID" for the
|
||||
* hardware supported by this firwmare file.
|
||||
* Once we have that, we double-check that that project_id is suitable
|
||||
* for the hardware we are working with.
|
||||
*/
|
||||
while (fwptr >= fw->data + (sizeof(struct rtl_epatch_header) + 3)) {
|
||||
opcode = *--fwptr;
|
||||
length = *--fwptr;
|
||||
data = *--fwptr;
|
||||
|
||||
BT_DBG("check op=%x len=%x data=%x", opcode, length, data);
|
||||
|
||||
if (opcode == 0xff) /* EOF */
|
||||
break;
|
||||
|
||||
if (length == 0) {
|
||||
BT_ERR("%s: found instruction with length 0",
|
||||
hdev->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (opcode == 0 && length == 1) {
|
||||
project_id = data;
|
||||
break;
|
||||
}
|
||||
|
||||
fwptr -= length;
|
||||
}
|
||||
|
||||
if (project_id < 0) {
|
||||
BT_ERR("%s: failed to find version instruction", hdev->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Find project_id in table */
|
||||
for (i = 0; i < ARRAY_SIZE(project_id_to_lmp_subver); i++) {
|
||||
if (project_id == project_id_to_lmp_subver[i].id)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i >= ARRAY_SIZE(project_id_to_lmp_subver)) {
|
||||
BT_ERR("%s: unknown project id %d", hdev->name, project_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (lmp_subver != project_id_to_lmp_subver[i].lmp_subver) {
|
||||
BT_ERR("%s: firmware is for %x but this is a %x", hdev->name,
|
||||
project_id_to_lmp_subver[i].lmp_subver, lmp_subver);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
epatch_info = (struct rtl_epatch_header *)fw->data;
|
||||
if (memcmp(epatch_info->signature, RTL_EPATCH_SIGNATURE, 8) != 0) {
|
||||
BT_ERR("%s: bad EPATCH signature", hdev->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
num_patches = le16_to_cpu(epatch_info->num_patches);
|
||||
BT_DBG("fw_version=%x, num_patches=%d",
|
||||
le32_to_cpu(epatch_info->fw_version), num_patches);
|
||||
|
||||
/* After the rtl_epatch_header there is a funky patch metadata section.
|
||||
* Assuming 2 patches, the layout is:
|
||||
* ChipID1 ChipID2 PatchLength1 PatchLength2 PatchOffset1 PatchOffset2
|
||||
*
|
||||
* Find the right patch for this chip.
|
||||
*/
|
||||
min_size += 8 * num_patches;
|
||||
if (fw->size < min_size)
|
||||
return -EINVAL;
|
||||
|
||||
chip_id_base = fw->data + sizeof(struct rtl_epatch_header);
|
||||
patch_length_base = chip_id_base + (sizeof(u16) * num_patches);
|
||||
patch_offset_base = patch_length_base + (sizeof(u16) * num_patches);
|
||||
for (i = 0; i < num_patches; i++) {
|
||||
u16 chip_id = get_unaligned_le16(chip_id_base +
|
||||
(i * sizeof(u16)));
|
||||
if (chip_id == rom_version + 1) {
|
||||
patch_length = get_unaligned_le16(patch_length_base +
|
||||
(i * sizeof(u16)));
|
||||
patch_offset = get_unaligned_le32(patch_offset_base +
|
||||
(i * sizeof(u32)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!patch_offset) {
|
||||
BT_ERR("%s: didn't find patch for chip id %d",
|
||||
hdev->name, rom_version);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
BT_DBG("length=%x offset=%x index %d", patch_length, patch_offset, i);
|
||||
min_size = patch_offset + patch_length;
|
||||
if (fw->size < min_size)
|
||||
return -EINVAL;
|
||||
|
||||
/* Copy the firmware into a new buffer and write the version at
|
||||
* the end.
|
||||
*/
|
||||
len = patch_length;
|
||||
buf = kmemdup(fw->data + patch_offset, patch_length, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(buf + patch_length - 4, &epatch_info->fw_version, 4);
|
||||
|
||||
*_buf = buf;
|
||||
return len;
|
||||
}
|
||||
|
||||
static int rtl_download_firmware(struct hci_dev *hdev,
|
||||
const unsigned char *data, int fw_len)
|
||||
{
|
||||
struct rtl_download_cmd *dl_cmd;
|
||||
int frag_num = fw_len / RTL_FRAG_LEN + 1;
|
||||
int frag_len = RTL_FRAG_LEN;
|
||||
int ret = 0;
|
||||
int i, j;
|
||||
|
||||
dl_cmd = kmalloc(sizeof(struct rtl_download_cmd), GFP_KERNEL);
|
||||
if (!dl_cmd)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < frag_num; i++) {
|
||||
struct sk_buff *skb;
|
||||
|
||||
BT_DBG("download fw (%d/%d)", i, frag_num);
|
||||
|
||||
if (i > 0x7f)
|
||||
j = (i & 0x7f) + 1;
|
||||
else
|
||||
j = i;
|
||||
|
||||
dl_cmd->index = j;
|
||||
if (i == (frag_num - 1)) {
|
||||
dl_cmd->index |= 0x80; /* data end */
|
||||
frag_len = fw_len % RTL_FRAG_LEN;
|
||||
}
|
||||
memcpy(dl_cmd->data, data, frag_len);
|
||||
|
||||
/* Send download command */
|
||||
skb = __hci_cmd_sync(hdev, 0xfc20, frag_len + 1, dl_cmd,
|
||||
HCI_INIT_TIMEOUT);
|
||||
if (IS_ERR(skb)) {
|
||||
BT_ERR("%s: download fw command failed (%ld)",
|
||||
hdev->name, PTR_ERR(skb));
|
||||
ret = PTR_ERR(skb);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (skb->len != sizeof(struct rtl_download_response)) {
|
||||
BT_ERR("%s: download fw event length mismatch",
|
||||
hdev->name);
|
||||
kfree_skb(skb);
|
||||
ret = -EIO;
|
||||
goto out;
|
||||
}
|
||||
|
||||
kfree_skb(skb);
|
||||
data += RTL_FRAG_LEN;
|
||||
}
|
||||
|
||||
out:
|
||||
kfree(dl_cmd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int bachk(const char *str)
|
||||
{
|
||||
if (!str)
|
||||
return -1;
|
||||
|
||||
if (strlen(str) != 17)
|
||||
return -1;
|
||||
|
||||
while (*str) {
|
||||
if (!isxdigit(*str++))
|
||||
return -1;
|
||||
|
||||
if (!isxdigit(*str++))
|
||||
return -1;
|
||||
|
||||
if (*str == 0)
|
||||
break;
|
||||
|
||||
if (*str++ != ':')
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int rtl_parse_config(u8 **buff, int len)
|
||||
{
|
||||
u8 sign[4] = { 0x55, 0xab, 0x23, 0x87 };
|
||||
struct rtl_config_hdr *cfg_hdr = (void *)*buff;
|
||||
u16 data_len;
|
||||
struct rtl_config_item *item;
|
||||
u8 *pbuf;
|
||||
u16 i;
|
||||
struct list_head list_configs;
|
||||
struct cfg_list_item *n;
|
||||
u8 bdaddr_cfg = 0;
|
||||
u8 bdaddr_buf[6];
|
||||
struct list_head *pos, *next;
|
||||
|
||||
if (!*buff)
|
||||
return len;
|
||||
|
||||
if (strcasecmp(bdaddr, "ff:ff:ff:ff:ff:ff")) {
|
||||
if (!bachk(bdaddr)) {
|
||||
char *str = bdaddr;
|
||||
int j;
|
||||
|
||||
BT_INFO("local public address %s", bdaddr);
|
||||
bdaddr_cfg = 1;
|
||||
for (j = 5; j >= 0; j--, str += 3)
|
||||
bdaddr_buf[j] = simple_strtoul(str, NULL, 16);
|
||||
} else
|
||||
BT_WARN("Invalid address %s", bdaddr);
|
||||
}
|
||||
|
||||
if (memcmp(cfg_hdr->sign, sign, 4)) {
|
||||
BT_ERR("signature [%02x %02x %02x %02x] error",
|
||||
cfg_hdr->sign[0], cfg_hdr->sign[1],
|
||||
cfg_hdr->sign[2], cfg_hdr->sign[3]);
|
||||
return 0;
|
||||
}
|
||||
data_len = le16_to_cpu(cfg_hdr->len);
|
||||
|
||||
if (data_len != len - sizeof(*cfg_hdr)) {
|
||||
BT_ERR("data len %u is not matched to %u",
|
||||
data_len, (u16)(len - sizeof(*cfg_hdr)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&list_configs);
|
||||
i = 0;
|
||||
pbuf = *buff + sizeof(*cfg_hdr);
|
||||
item = (struct rtl_config_item *)pbuf;
|
||||
while (i < data_len) {
|
||||
u16 ofs;
|
||||
|
||||
ofs = le16_to_cpu(item->offset);
|
||||
n = kzalloc(sizeof(*n), GFP_KERNEL);
|
||||
if (n) {
|
||||
n->item = item;
|
||||
list_add_tail(&n->list, &list_configs);
|
||||
} else {
|
||||
BT_ERR("Couldn't alloc item for %04x", ofs);
|
||||
}
|
||||
i += (item->len + sizeof(*item));
|
||||
item = (struct rtl_config_item *)(pbuf + i);
|
||||
}
|
||||
|
||||
list_for_each_safe(pos, next, &list_configs) {
|
||||
n = list_entry(pos, struct cfg_list_item, list);
|
||||
if (le16_to_cpu(n->item->offset) == 0x0030 && bdaddr_cfg) {
|
||||
memcpy(n->item->data, bdaddr_buf, 6);
|
||||
bdaddr_cfg = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Add bdaddr */
|
||||
if (bdaddr_cfg) {
|
||||
u8 *b;
|
||||
|
||||
b = kzalloc(len + 9, GFP_KERNEL);
|
||||
if (!b) {
|
||||
BT_ERR("Couldn't alloc config buffer");
|
||||
return len;
|
||||
}
|
||||
|
||||
memcpy(b, *buff, len);
|
||||
|
||||
data_len += 9;
|
||||
b[sizeof(cfg_hdr->sign)] = (data_len & 0xff);
|
||||
b[sizeof(cfg_hdr->sign) + 1] = ((data_len >> 8) & 0xff);
|
||||
|
||||
b[len] = 0x30;
|
||||
b[len + 1] = 0x00;
|
||||
b[len + 2] = 6;
|
||||
memcpy(b + len + 3, bdaddr_buf, 6);
|
||||
|
||||
len += 9;
|
||||
kfree(*buff);
|
||||
*buff = b;
|
||||
}
|
||||
|
||||
/* free list */
|
||||
list_for_each_safe(pos, next, &list_configs) {
|
||||
n = list_entry(pos, struct cfg_list_item, list);
|
||||
list_del(pos);
|
||||
kfree(n);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int rtl_load_config(struct hci_dev *hdev, const char *name, u8 **buff)
|
||||
{
|
||||
const struct firmware *fw;
|
||||
int ret;
|
||||
|
||||
bt_dev_info(hdev, "rtl: loading %s", name);
|
||||
ret = request_firmware(&fw, name, &hdev->dev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
ret = fw->size;
|
||||
*buff = kmemdup(fw->data, ret, GFP_KERNEL);
|
||||
if (!*buff)
|
||||
ret = -ENOMEM;
|
||||
|
||||
release_firmware(fw);
|
||||
|
||||
/* Parse config */
|
||||
ret = rtl_parse_config(buff, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int send_hci_reset_command(struct hci_dev *hdev)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
int ret = 0;
|
||||
|
||||
skb = __hci_cmd_sync(hdev, HCI_OP_RESET, 0, NULL,
|
||||
HCI_INIT_TIMEOUT);
|
||||
if (IS_ERR(skb)) {
|
||||
BT_ERR("%s: send HCI reset command failed (%ld)",
|
||||
hdev->name, PTR_ERR(skb));
|
||||
ret = PTR_ERR(skb);
|
||||
}
|
||||
|
||||
kfree_skb(skb);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int setup_rtl8821ds(struct hci_dev *hdev, u16 hci_rev,
|
||||
u16 lmp_subver)
|
||||
{
|
||||
unsigned char *fw_data = NULL;
|
||||
const struct firmware *fw;
|
||||
int ret;
|
||||
int cfg_sz;
|
||||
u8 *cfg_buff = NULL;
|
||||
u8 *tbuff;
|
||||
char *cfg_name = NULL;
|
||||
char *fw_name = NULL;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ic_id_table); i++) {
|
||||
if ((ic_id_table[i].match_flags & IC_MATCH_FL_LMPSUBV) &&
|
||||
(ic_id_table[i].lmp_subver != lmp_subver))
|
||||
continue;
|
||||
if ((ic_id_table[i].match_flags & IC_MATCH_FL_HCIREV) &&
|
||||
(ic_id_table[i].hci_rev != hci_rev))
|
||||
continue;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (i >= ARRAY_SIZE(ic_id_table)) {
|
||||
BT_ERR("%s: unknown IC info, lmp subver %04x, hci rev %04x",
|
||||
hdev->name, lmp_subver, hci_rev);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
cfg_name = ic_id_table[i].cfg_name;
|
||||
|
||||
if (cfg_name) {
|
||||
cfg_sz = rtl_load_config(hdev, cfg_name, &cfg_buff);
|
||||
if (cfg_sz < 0) {
|
||||
cfg_sz = 0;
|
||||
if (ic_id_table[i].config_needed)
|
||||
BT_ERR("Necessary config file %s not found\n",
|
||||
cfg_name);
|
||||
}
|
||||
} else
|
||||
cfg_sz = 0;
|
||||
|
||||
fw_name = ic_id_table[i].fw_name;
|
||||
bt_dev_info(hdev, "rtl: loading %s", fw_name);
|
||||
ret = request_firmware(&fw, fw_name, &hdev->dev);
|
||||
if (ret < 0) {
|
||||
BT_ERR("%s: Failed to load %s", hdev->name, fw_name);
|
||||
goto err_req_fw;
|
||||
}
|
||||
|
||||
ret = rtlbt_parse_firmware(hdev, lmp_subver, fw, &fw_data);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
if (cfg_sz) {
|
||||
tbuff = kzalloc(ret + cfg_sz, GFP_KERNEL);
|
||||
if (!tbuff) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
memcpy(tbuff, fw_data, ret);
|
||||
kfree(fw_data);
|
||||
|
||||
memcpy(tbuff + ret, cfg_buff, cfg_sz);
|
||||
ret += cfg_sz;
|
||||
|
||||
fw_data = tbuff;
|
||||
}
|
||||
|
||||
bt_dev_info(hdev, "cfg_sz %d, total size %d", cfg_sz, ret);
|
||||
|
||||
ret = rtl_download_firmware(hdev, fw_data, ret);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = send_hci_reset_command(hdev);
|
||||
|
||||
out:
|
||||
release_firmware(fw);
|
||||
kfree(fw_data);
|
||||
err_req_fw:
|
||||
if (cfg_sz)
|
||||
kfree(cfg_buff);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct sk_buff *btrtl_read_local_version(struct hci_dev *hdev)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
|
||||
skb = __hci_cmd_sync(hdev, HCI_OP_READ_LOCAL_VERSION, 0, NULL,
|
||||
HCI_INIT_TIMEOUT);
|
||||
if (IS_ERR(skb)) {
|
||||
BT_ERR("%s: HCI_OP_READ_LOCAL_VERSION failed (%ld)",
|
||||
hdev->name, PTR_ERR(skb));
|
||||
return skb;
|
||||
}
|
||||
|
||||
if (skb->len != sizeof(struct hci_rp_read_local_version)) {
|
||||
BT_ERR("%s: HCI_OP_READ_LOCAL_VERSION event length mismatch",
|
||||
hdev->name);
|
||||
kfree_skb(skb);
|
||||
return ERR_PTR(-EIO);
|
||||
}
|
||||
|
||||
return skb;
|
||||
}
|
||||
|
||||
int btrtl_setup_8821ds(struct hci_dev *hdev)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
struct hci_rp_read_local_version *resp;
|
||||
u16 hci_rev, lmp_subver;
|
||||
|
||||
skb = btrtl_read_local_version(hdev);
|
||||
if (IS_ERR(skb))
|
||||
return PTR_ERR(skb);
|
||||
|
||||
resp = (struct hci_rp_read_local_version *)skb->data;
|
||||
bt_dev_info(hdev, "rtl: examining hci_ver=%02x hci_rev=%04x "
|
||||
"lmp_ver=%02x lmp_subver=%04x",
|
||||
resp->hci_ver, resp->hci_rev,
|
||||
resp->lmp_ver, resp->lmp_subver);
|
||||
|
||||
hci_rev = le16_to_cpu(resp->hci_rev);
|
||||
lmp_subver = le16_to_cpu(resp->lmp_subver);
|
||||
kfree_skb(skb);
|
||||
|
||||
switch (lmp_subver) {
|
||||
case RTL_ROM_LMP_8821DS:
|
||||
case RTL_ROM_LMP_8723FS:
|
||||
return setup_rtl8821ds(hdev, hci_rev, lmp_subver);
|
||||
default:
|
||||
bt_dev_info(hdev, "rtl: assuming no firmware upload needed");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
MODULE_DESCRIPTION("Bluetooth support for Realtek devices ver " VERSION);
|
||||
MODULE_VERSION(VERSION);
|
||||
MODULE_LICENSE("GPL");
|
||||
42
osdrv/extdrv/wireless/rtl8733bs_bt_sdio/btrtl.h
Normal file
42
osdrv/extdrv/wireless/rtl8733bs_bt_sdio/btrtl.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Bluetooth support for Realtek devices
|
||||
*
|
||||
* Copyright (C) 2015 Endless Mobile, Inc.
|
||||
* Copyright (C) 2018 Realtek Semiconductor Corp
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
*/
|
||||
|
||||
#define RTL_FRAG_LEN 252
|
||||
|
||||
struct rtl_download_cmd {
|
||||
__u8 index;
|
||||
__u8 data[RTL_FRAG_LEN];
|
||||
} __packed;
|
||||
|
||||
struct rtl_download_response {
|
||||
__u8 status;
|
||||
__u8 index;
|
||||
} __packed;
|
||||
|
||||
struct rtl_rom_version_evt {
|
||||
__u8 status;
|
||||
__u8 version;
|
||||
} __packed;
|
||||
|
||||
struct rtl_epatch_header {
|
||||
__u8 signature[8];
|
||||
__le32 fw_version;
|
||||
__le16 num_patches;
|
||||
} __packed;
|
||||
|
||||
int btrtl_setup_8821ds(struct hci_dev *hdev);
|
||||
2987
osdrv/extdrv/wireless/rtl8733bs_bt_sdio/rtk_coex.c
Normal file
2987
osdrv/extdrv/wireless/rtl8733bs_bt_sdio/rtk_coex.c
Normal file
File diff suppressed because it is too large
Load Diff
352
osdrv/extdrv/wireless/rtl8733bs_bt_sdio/rtk_coex.h
Normal file
352
osdrv/extdrv/wireless/rtl8733bs_bt_sdio/rtk_coex.h
Normal file
@@ -0,0 +1,352 @@
|
||||
#include <net/bluetooth/hci_core.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
/***********************************
|
||||
** Realtek - For coexistence **
|
||||
***********************************/
|
||||
#define BTRTL_HCIUSB 0
|
||||
#define BTRTL_HCIUART 1
|
||||
|
||||
#define BTRTL_HCI_IF BTRTL_HCIUSB
|
||||
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
|
||||
#define CONNECT_PORT 30001
|
||||
#define CONNECT_PORT_WIFI 30000
|
||||
|
||||
#define invite_req "INVITE_REQ"
|
||||
#define invite_rsp "INVITE_RSP"
|
||||
#define attend_req "ATTEND_REQ"
|
||||
#define attend_ack "ATTEND_ACK"
|
||||
#define wifi_leave "WIFI_LEAVE"
|
||||
#define leave_ack "LEAVE_ACK"
|
||||
#define bt_leave "BT_LEAVE"
|
||||
|
||||
#define HCI_OP_PERIODIC_INQ 0x0403
|
||||
#define HCI_EV_LE_META 0x3e
|
||||
#define HCI_EV_LE_CONN_COMPLETE 0x01
|
||||
#define HCI_EV_LE_CONN_UPDATE_COMPLETE 0x03
|
||||
|
||||
//vendor cmd to fw
|
||||
#define HCI_VENDOR_ENABLE_PROFILE_REPORT_COMMAND 0xfc18
|
||||
#define HCI_VENDOR_SET_PROFILE_REPORT_COMMAND 0xfc19
|
||||
#define HCI_VENDOR_MAILBOX_CMD 0xfc8f
|
||||
#define HCI_VENDOR_SET_BITPOOL 0xfc51
|
||||
|
||||
//subcmd to fw
|
||||
#define HCI_VENDOR_SUB_CMD_WIFI_CHANNEL_AND_BANDWIDTH_CMD 0x11
|
||||
#define HCI_VENDOR_SUB_CMD_WIFI_FORCE_TX_POWER_CMD 0x17
|
||||
#define HCI_VENDOR_SUB_CMD_BT_ENABLE_IGNORE_WLAN_ACT_CMD 0x1B
|
||||
#define HCI_VENDOR_SUB_CMD_BT_REPORT_CONN_SCO_INQ_INFO 0x23
|
||||
#define HCI_VENDOR_SUB_CMD_BT_AUTO_REPORT_STATUS_INFO 0x27
|
||||
#define HCI_VENDOR_SUB_CMD_BT_AUTO_REPORT_ENABLE 0x28
|
||||
#define HCI_VENDOR_SUB_CMD_BT_SET_TXRETRY_REPORT_PARAM 0x29
|
||||
#define HCI_VENDOR_SUB_CMD_BT_SET_PTATABLE 0x2A
|
||||
#define HCI_VENDOR_SUB_CMD_SET_BT_PSD_MODE 0x31
|
||||
#define HCI_VENDOR_SUB_CMD_SET_BT_LNA_CONSTRAINT 0x32
|
||||
#define HCI_VENDOR_SUB_CMD_GET_AFH_MAP_L 0x40
|
||||
#define HCI_VENDOR_SUB_CMD_GET_AFH_MAP_M 0x41
|
||||
#define HCI_VENDOR_SUB_CMD_GET_AFH_MAP_H 0x42
|
||||
#define HCI_VENDOR_SUB_CMD_RD_REG_REQ 0x43
|
||||
#define HCI_VENDOR_SUB_CMD_WR_REG_REQ 0x44
|
||||
|
||||
#define HCI_EV_VENDOR_SPECIFIC 0xff
|
||||
|
||||
//sub event from fw start
|
||||
#define HCI_VENDOR_PTA_REPORT_EVENT 0x24
|
||||
#define HCI_VENDOR_PTA_AUTO_REPORT_EVENT 0x25
|
||||
|
||||
//vendor cmd to wifi driver
|
||||
#define HCI_GRP_VENDOR_SPECIFIC (0x3f << 10)
|
||||
#define HCI_OP_HCI_EXTENSION_VERSION_NOTIFY (0x0100 | HCI_GRP_VENDOR_SPECIFIC)
|
||||
#define HCI_OP_BT_OPERATION_NOTIFY (0x0102 | HCI_GRP_VENDOR_SPECIFIC)
|
||||
#define HCI_OP_HCI_BT_INFO_NOTIFY (0x0106 | HCI_GRP_VENDOR_SPECIFIC)
|
||||
#define HCI_OP_HCI_BT_COEX_NOTIFY (0x0107 | HCI_GRP_VENDOR_SPECIFIC)
|
||||
#define HCI_OP_HCI_BT_PATCH_VER_NOTIFY (0x0108 | HCI_GRP_VENDOR_SPECIFIC)
|
||||
#define HCI_OP_HCI_BT_AFH_MAP_NOTIFY (0x0109 | HCI_GRP_VENDOR_SPECIFIC)
|
||||
#define HCI_OP_HCI_BT_REGISTER_VALUE_NOTIFY (0x010a | HCI_GRP_VENDOR_SPECIFIC)
|
||||
|
||||
//bt info reason to wifi
|
||||
#define HOST_RESPONSE 0 //Host response when receive the BT Info Control Event
|
||||
#define POLLING_RESPONSE 1 //The BT Info response for polling by BT firmware.
|
||||
#define AUTO_REPORT 2 //BT auto report by BT firmware.
|
||||
#define STACK_REPORT_WHILE_DEVICE_D2 3 //Stack report when BT firmware is under power save state(ex:D2)
|
||||
|
||||
// vendor event from wifi
|
||||
#define RTK_HS_EXTENSION_EVENT_WIFI_SCAN 0x01
|
||||
#define RTK_HS_EXTENSION_EVENT_RADIO_STATUS_NOTIFY 0x02
|
||||
#define RTK_HS_EXTENSION_EVENT_HCI_BT_INFO_CONTROL 0x03
|
||||
#define RTK_HS_EXTENSION_EVENT_HCI_BT_COEX_CONTROL 0x04
|
||||
|
||||
//op code from wifi
|
||||
#define BT_PATCH_VERSION_QUERY 0x00
|
||||
#define IGNORE_WLAN_ACTIVE_CONTROL 0x01
|
||||
#define LNA_CONSTRAIN_CONTROL 0x02
|
||||
#define BT_POWER_DECREASE_CONTROL 0x03
|
||||
#define BT_PSD_MODE_CONTROL 0x04
|
||||
#define WIFI_BW_CHNL_NOTIFY 0x05
|
||||
#define QUERY_BT_AFH_MAP 0x06
|
||||
#define BT_REGISTER_ACCESS 0x07
|
||||
|
||||
//bt operation to notify
|
||||
#define BT_OPCODE_NONE 0
|
||||
#define BT_OPCODE_INQUIRY_START 1
|
||||
#define BT_OPCODE_INQUIRY_END 2
|
||||
#define BT_OPCODE_PAGE_START 3
|
||||
#define BT_OPCODE_PAGE_SUCCESS_END 4
|
||||
#define BT_OPCODE_PAGE_UNSUCCESS_END 5
|
||||
#define BT_OPCODE_PAIR_START 6
|
||||
#define BT_OPCODE_PAIR_END 7
|
||||
#define BT_OPCODE_ENABLE_BT 8
|
||||
#define BT_OPCODE_DISABLE_BT 9
|
||||
|
||||
#define HCI_EXTENSION_VERSION 0x0004
|
||||
#define HCI_CMD_PREAMBLE_SIZE 3
|
||||
#define PAN_PACKET_COUNT 5
|
||||
|
||||
#define STREAM_TO_UINT16(u16, p) {u16 = ((uint16_t)(*(p)) + (((uint16_t)(*((p) + 1))) << 8)); (p) += 2;}
|
||||
#define UINT16_TO_STREAM(p, u16) {*(p)++ = (uint8_t)(u16); *(p)++ = (uint8_t)((u16) >> 8);}
|
||||
|
||||
#define PSM_SDP 0x0001
|
||||
#define PSM_RFCOMM 0x0003
|
||||
#define PSM_PAN 0x000F
|
||||
#define PSM_HID 0x0011
|
||||
#define PSM_HID_INT 0x0013
|
||||
#define PSM_AVCTP 0x0017
|
||||
#define PSM_AVDTP 0x0019
|
||||
#define PSM_FTP 0x1001
|
||||
#define PSM_BIP 0x1003
|
||||
#define PSM_OPP 0x1015
|
||||
//--add more if needed--//
|
||||
|
||||
enum {
|
||||
profile_sco = 0,
|
||||
profile_hid = 1,
|
||||
profile_a2dp = 2,
|
||||
profile_pan = 3,
|
||||
profile_hid_interval = 4,
|
||||
profile_hogp = 5,
|
||||
profile_voice = 6,
|
||||
profile_sink = 7,
|
||||
profile_max = 8
|
||||
};
|
||||
|
||||
#define A2DP_SIGNAL 0x01
|
||||
#define A2DP_MEDIA 0x02
|
||||
//profile info data
|
||||
typedef struct {
|
||||
struct list_head list;
|
||||
uint16_t handle;
|
||||
uint16_t psm;
|
||||
uint16_t dcid;
|
||||
uint16_t scid;
|
||||
uint8_t profile_index;
|
||||
uint8_t flags;
|
||||
} rtk_prof_info, *prtk_prof_info;
|
||||
|
||||
//profile info for each connection
|
||||
typedef struct rtl_hci_conn {
|
||||
struct list_head list;
|
||||
uint16_t handle;
|
||||
uint8_t type; // 0:l2cap, 1:sco/esco, 2:le
|
||||
uint8_t profile_bitmap;
|
||||
int8_t profile_refcount[8];
|
||||
} rtk_conn_prof, *prtk_conn_prof;
|
||||
|
||||
#ifdef RTB_SOFTWARE_MAILBOX
|
||||
|
||||
struct rtl_btinfo {
|
||||
u8 cmd;
|
||||
u8 len;
|
||||
u8 data[6];
|
||||
};
|
||||
#define RTL_BTINFO_LEN (sizeof(struct rtl_btinfo))
|
||||
/* typedef struct {
|
||||
* uint8_t cmd_index;
|
||||
* uint8_t cmd_length;
|
||||
* uint8_t link_status;
|
||||
* uint8_t retry_cnt;
|
||||
* uint8_t rssi;
|
||||
* uint8_t mailbox_info;
|
||||
* uint16_t acl_throughput;
|
||||
* } hci_linkstatus_report; */
|
||||
|
||||
typedef struct {
|
||||
uint8_t type;
|
||||
uint32_t offset;
|
||||
uint32_t value;
|
||||
} hci_mailbox_register;
|
||||
|
||||
struct rtl_btinfo_ctl {
|
||||
uint8_t polling_enable;
|
||||
uint8_t polling_time;
|
||||
uint8_t autoreport_enable;
|
||||
};
|
||||
#endif /* RTB_SOFTWARE_MAILBOX */
|
||||
|
||||
#define MAX_LEN_OF_HCI_EV 32
|
||||
#define NUM_RTL_HCI_EV 32
|
||||
struct rtl_hci_ev {
|
||||
__u8 data[MAX_LEN_OF_HCI_EV];
|
||||
__u16 len;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
#define L2_MAX_SUBSEC_LEN 128
|
||||
#define L2_MAX_PKTS 16
|
||||
struct rtl_l2_buff {
|
||||
__u8 data[L2_MAX_SUBSEC_LEN];
|
||||
__u16 len;
|
||||
__u16 out;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
struct rtl_coex_struct {
|
||||
struct list_head conn_hash; //hash for connections
|
||||
struct list_head profile_list; //hash for profile info
|
||||
struct hci_dev *hdev;
|
||||
#ifdef RTB_SOFTWARE_MAILBOX
|
||||
struct socket *udpsock;
|
||||
struct sockaddr_in addr;
|
||||
struct sockaddr_in wifi_addr;
|
||||
struct timer_list polling_timer;
|
||||
#endif
|
||||
struct timer_list a2dp_count_timer;
|
||||
struct timer_list pan_count_timer;
|
||||
struct timer_list hogp_count_timer;
|
||||
#ifdef RTB_SOFTWARE_MAILBOX
|
||||
struct workqueue_struct *sock_wq;
|
||||
struct delayed_work sock_work;
|
||||
#endif
|
||||
struct workqueue_struct *fw_wq;
|
||||
struct delayed_work fw_work;
|
||||
struct delayed_work l2_work;
|
||||
#ifdef RTB_SOFTWARE_MAILBOX
|
||||
struct sock *sk;
|
||||
#endif
|
||||
struct urb *urb;
|
||||
spinlock_t spin_lock_sock;
|
||||
spinlock_t spin_lock_profile;
|
||||
uint32_t a2dp_packet_count;
|
||||
uint32_t pan_packet_count;
|
||||
uint32_t hogp_packet_count;
|
||||
uint32_t voice_packet_count;
|
||||
uint8_t profile_bitmap;
|
||||
uint8_t profile_status;
|
||||
int8_t profile_refcount[8];
|
||||
uint8_t ispairing;
|
||||
uint8_t isinquirying;
|
||||
uint8_t ispaging;
|
||||
#ifdef RTB_SOFTWARE_MAILBOX
|
||||
uint8_t wifi_state;
|
||||
uint8_t autoreport;
|
||||
uint8_t polling_enable;
|
||||
uint8_t polling_interval;
|
||||
uint8_t piconet_id;
|
||||
uint8_t mode;
|
||||
uint8_t afh_map[10];
|
||||
#endif
|
||||
uint16_t hci_reversion;
|
||||
uint16_t lmp_subversion;
|
||||
#ifdef RTB_SOFTWARE_MAILBOX
|
||||
uint8_t wifi_on;
|
||||
uint8_t sock_open;
|
||||
#endif
|
||||
unsigned long cmd_last_tx;
|
||||
|
||||
/* hci ev buff */
|
||||
struct list_head ev_used_list;
|
||||
struct list_head ev_free_list;
|
||||
|
||||
spinlock_t rxlock;
|
||||
__u8 pkt_type;
|
||||
__u16 expect;
|
||||
__u8 *tbuff;
|
||||
__u16 elen;
|
||||
__u8 back_buff[HCI_MAX_EVENT_SIZE];
|
||||
|
||||
/* l2cap rx buff */
|
||||
struct list_head l2_used_list;
|
||||
struct list_head l2_free_list;
|
||||
|
||||
/* buff addr and size */
|
||||
spinlock_t buff_lock;
|
||||
unsigned long pages_addr;
|
||||
unsigned long buff_size;
|
||||
|
||||
#define RTL_COEX_RUNNING (1 << 0)
|
||||
unsigned long flags;
|
||||
|
||||
};
|
||||
|
||||
#ifdef __LITTLE_ENDIAN
|
||||
struct sbc_frame_hdr {
|
||||
uint8_t syncword:8; /* Sync word */
|
||||
uint8_t subbands:1; /* Subbands */
|
||||
uint8_t allocation_method:1; /* Allocation method */
|
||||
uint8_t channel_mode:2; /* Channel mode */
|
||||
uint8_t blocks:2; /* Blocks */
|
||||
uint8_t sampling_frequency:2; /* Sampling frequency */
|
||||
uint8_t bitpool:8; /* Bitpool */
|
||||
uint8_t crc_check:8; /* CRC check */
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/* NOTE: The code is copied from pa.
|
||||
* only the bit field in 8-bit is affected by endian, not the 16-bit or 32-bit.
|
||||
* why?
|
||||
*/
|
||||
struct rtp_header {
|
||||
unsigned cc:4;
|
||||
unsigned x:1;
|
||||
unsigned p:1;
|
||||
unsigned v:2;
|
||||
|
||||
unsigned pt:7;
|
||||
unsigned m:1;
|
||||
|
||||
uint16_t sequence_number;
|
||||
uint32_t timestamp;
|
||||
uint32_t ssrc;
|
||||
uint32_t csrc[0];
|
||||
} __attribute__ ((packed));
|
||||
|
||||
#else
|
||||
/* big endian */
|
||||
struct sbc_frame_hdr {
|
||||
uint8_t syncword:8; /* Sync word */
|
||||
uint8_t sampling_frequency:2; /* Sampling frequency */
|
||||
uint8_t blocks:2; /* Blocks */
|
||||
uint8_t channel_mode:2; /* Channel mode */
|
||||
uint8_t allocation_method:1; /* Allocation method */
|
||||
uint8_t subbands:1; /* Subbands */
|
||||
uint8_t bitpool:8; /* Bitpool */
|
||||
uint8_t crc_check:8; /* CRC check */
|
||||
} __attribute__ ((packed));
|
||||
|
||||
struct rtp_header {
|
||||
unsigned v:2;
|
||||
unsigned p:1;
|
||||
unsigned x:1;
|
||||
unsigned cc:4;
|
||||
|
||||
unsigned m:1;
|
||||
unsigned pt:7;
|
||||
|
||||
uint16_t sequence_number;
|
||||
uint32_t timestamp;
|
||||
uint32_t ssrc;
|
||||
uint32_t csrc[0];
|
||||
} __attribute__ ((packed));
|
||||
#endif /* __LITTLE_ENDIAN */
|
||||
|
||||
void rtk_btcoex_parse_event(uint8_t *buffer, int count);
|
||||
void rtk_btcoex_parse_cmd(uint8_t *buffer, int count);
|
||||
void rtk_btcoex_parse_l2cap_data_tx(uint8_t *buffer, int count);
|
||||
void rtk_btcoex_parse_l2cap_data_rx(uint8_t *buffer, int count);
|
||||
|
||||
void rtk_btcoex_open(struct hci_dev *hdev);
|
||||
void rtk_btcoex_close(void);
|
||||
void rtk_btcoex_probe(struct hci_dev *hdev);
|
||||
void rtk_btcoex_init(void);
|
||||
void rtk_btcoex_exit(void);
|
||||
@@ -22,10 +22,10 @@
|
||||
|
||||
// this function borken on licheervnano
|
||||
// this function make lcd show green color on blank
|
||||
//#define START_VL "startvl 0 " LOGO_READ_ADDR " " LOGO_RESERVED_ADDR " " LOGOSIZE " " VO_ALIGNMENT ";"
|
||||
#define START_VL "startvl 0 " LOGO_READ_ADDR " " LOGO_RESERVED_ADDR " " LOGOSIZE " " VO_ALIGNMENT ";"
|
||||
#ifndef START_VL
|
||||
#define START_VL " "
|
||||
#endif
|
||||
#define SET_VO_BG "setvobg 0 0xffffffff;"
|
||||
#define SET_VO_BG "setvobg 0 0xffffffff ; "
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user