// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018 MediaTek Inc.
/*
* Bluetooth support for MediaTek serial devices
*
* Author: Sean Wang <sean.wang@mediatek.com>
*
*/
#include <asm/unaligned.h>
#include <linux/atomic.h>
#include <linux/clk.h>
#include <linux/firmware.h>
#include <linux/gpio/consumer.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/pinctrl/consumer.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/serdev.h>
#include <linux/skbuff.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
#include "h4_recv.h"
#define VERSION "0.2"
#define FIRMWARE_MT7622 "mediatek/mt7622pr2h.bin"
#define FIRMWARE_MT7663 "mediatek/mt7663pr2h.bin"
#define FIRMWARE_MT7668 "mediatek/mt7668pr2h.bin"
#define MTK_STP_TLR_SIZE 2
#define BTMTKUART_TX_STATE_ACTIVE 1
#define BTMTKUART_TX_STATE_WAKEUP 2
#define BTMTKUART_TX_WAIT_VND_EVT 3
#define BTMTKUART_REQUIRED_WAKEUP 4
#define BTMTKUART_FLAG_STANDALONE_HW BIT(0)
enum {
MTK_WMT_PATCH_DWNLD = 0x1,
MTK_WMT_TEST = 0x2,
MTK_WMT_WAKEUP = 0x3,
MTK_WMT_HIF = 0x4,
MTK_WMT_FUNC_CTRL = 0x6,
MTK_WMT_RST = 0x7,
MTK_WMT_SEMAPHORE = 0x17,
};
enum {
BTMTK_WMT_INVALID,
BTMTK_WMT_PATCH_UNDONE,
BTMTK_WMT_PATCH_DONE,
BTMTK_WMT_ON_UNDONE,
BTMTK_WMT_ON_DONE,
BTMTK_WMT_ON_PROGRESS,
};
struct mtk_stp_hdr {
u8 prefix;
__be16 dlen;
u8 cs;
} __packed;
struct btmtkuart_data {
unsigned int flags;
const char *fwname;
};
struct mtk_wmt_hdr {
u8 dir;
u8 op;
__le16 dlen;
u8 flag;
} __packed;
struct mtk_hci_wmt_cmd {
struct mtk_wmt_hdr hdr;
u8 data[256];
} __packed;
struct btmtk_hci_wmt_evt {
struct hci_event_hdr hhdr;
struct mtk_wmt_hdr whdr;
} __packed;
struct btmtk_hci_wmt_evt_funcc {
struct btmtk_hci_wmt_evt hwhdr;
__be16 status;
} __packed;
struct btmtk_tci_sleep {
u8 mode;
__le16 duration;
__le16 host_duration;
u8 host_wakeup_pin;
u8 time_compensation;
} __packed;
struct btmtk_hci_wmt_params {
u8 op;
u8 flag;
u16 dlen;
const void *data;
u32 *status;
};
struct btmtkuart_dev {
struct hci_dev *hdev;
struct serdev_device *serdev;
struct clk *clk;
struct clk *osc;
struct regulator *vcc;
struct gpio_desc *reset;
struct gpio_desc *boot;
struct pinctrl *pinctrl;
struct pinctrl_state *pins_runtime;
struct pinctrl_state *pins_boot;
speed_t desired_speed;
speed_t curr_speed;
struct work_struct tx_work;
unsigned long tx_state;
struct sk_buff_head txq;
struct sk_buff *rx_skb;
struct sk_buff *evt_skb;
u8 stp_pad[6];
u8 stp_cursor;
u16 stp_dlen;
const struct btmtkuart_data *data;
};
#define btmtkuart_is_standalone(bdev) \
((bdev)->data->flags & BTMTKUART_FLAG_STANDALONE_HW)
#define btmtkuart_is_builtin_soc(bdev) \
!((bdev)->data->flags & BTMTKUART_FLAG_STANDALONE_HW)
static int mtk_hci_wmt_sync(struct hci_dev *hdev,
struct btmtk_hci_wmt_params *wmt_params)
{
struct btmtkuart_dev *bdev = hci_get_drvdata(hdev);
struct btmtk_hci_wmt_evt_funcc *wmt_evt_funcc;
u32 hlen, status = BTMTK_WMT_INVALID;
struct btmtk_hci_wmt_evt *wmt_evt;
struct mtk_hci_wmt_cmd wc;
struct mtk_wmt_hdr *hdr;
int err;
hlen = sizeof(*hdr) + wmt_params->dlen;
if (hlen > 255) {
err = -EINVAL;
goto err_free_skb;
}
hdr = (struct mtk_wmt_hdr *)&wc;
hdr->dir = 1;
hdr->op = wmt_params->op;
hdr->dlen = cpu_to_le16(wmt_params->dlen + 1);
hdr->flag = wmt_params->flag;
memcpy(wc.data, wmt_params->data, wmt_params->dlen);
set_bit(BTMTKUART_TX_WAIT_VND_EVT, &bdev->tx_state);
err = __hci_cmd_send(hdev, 0xfc6f, hlen, &wc);
if (err < 0) {
clear_bit(BTMTKUART_TX_WAIT_VND_EVT, &bdev->tx_state);
goto err_free_skb;
}
/* The vendor specific WMT commands are all answered by a vendor
* specific event and will not have the Command Status or Command
* Complete as with usual HCI command flow control.
*
* After sending the command, wait for BTMTKUART_TX_WAIT_VND_EVT
* state to be cleared. The driver specific event receive routine
* will clear that state and with that indicate completion of the
* WMT command.
*/
err = wait_on_bit_timeout(&bdev->tx_state, BTMTKUART_TX_WAIT_VND_EVT,
TASK_INTERRUPTIBLE, HCI_INIT_TIMEOUT);
if (err == -EINTR) {
bt_dev_err(hdev, "Execution of wmt command interrupted");
clear_bit(BTMTKUART_TX_WAIT_VND_EVT, &bdev->tx_stat