// SPDX-License-Identifier: GPL-2.0-or-later
/*
*
* Bluetooth HCI Three-wire UART driver
*
* Copyright (C) 2012 Intel Corporation
*/
#include <linux/acpi.h>
#include <linux/errno.h>
#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
#include <linux/of_device.h>
#include <linux/serdev.h>
#include <linux/skbuff.h>
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
#include "btrtl.h"
#include "hci_uart.h"
#define HCI_3WIRE_ACK_PKT 0
#define HCI_3WIRE_LINK_PKT 15
/* Sliding window size */
#define H5_TX_WIN_MAX 4
#define H5_ACK_TIMEOUT msecs_to_jiffies(250)
#define H5_SYNC_TIMEOUT msecs_to_jiffies(100)
/*
* Maximum Three-wire packet:
* 4 byte header + max value for 12-bit length + 2 bytes for CRC
*/
#define H5_MAX_LEN (4 + 0xfff + 2)
/* Convenience macros for reading Three-wire header values */
#define H5_HDR_SEQ(hdr) ((hdr)[0] & 0x07)
#define H5_HDR_ACK(hdr) (((hdr)[0] >> 3) & 0x07)
#define H5_HDR_CRC(hdr) (((hdr)[0] >> 6) & 0x01)
#define H5_HDR_RELIABLE(hdr) (((hdr)[0] >> 7) & 0x01)
#define H5_HDR_PKT_TYPE(hdr) ((hdr)[1] & 0x0f)
#define H5_HDR_LEN(hdr) ((((hdr)[1] >> 4) & 0x0f) + ((hdr)[2] << 4))
#define SLIP_DELIMITER 0xc0
#define SLIP_ESC 0xdb
#define SLIP_ESC_DELIM 0xdc
#define SLIP_ESC_ESC 0xdd
/* H5 state flags */
enum {
H5_RX_ESC, /* SLIP escape mode */
H5_TX_ACK_REQ, /* Pending ack to send */
};
struct h5 {
/* Must be the first member, hci_serdev.c expects this. */
struct hci_uart serdev_hu;
struct sk_buff_head unack; /* Unack'ed packets queue */
struct sk_buff_head rel; /* Reliable packets queue */
struct sk_buff_head unrel; /* Unreliable packets queue */
unsigned long flags;
struct sk_buff *rx_skb; /* Receive buffer */
size_t rx_pending; /* Expecting more bytes */
u8 rx_ack; /* Last ack number received */
int (*rx_func)(struct hci_uart *hu, u8 c);
struct timer_list timer; /* Retransmission timer */
struct hci_uart *hu; /* Parent HCI UART */
u8 tx_seq; /* Next seq number to send */
u8 tx_ack; /* Next ack number to send */
u8 tx_win; /* Sliding window size */
enum {
H5_UNINITIALIZED,
H5_INITIALIZED,
H5_ACTIVE,
} state;
enum {
H5_AWAKE,
H5_SLEEPING,
H5_WAKING_UP,
} sleep;
const struct h5_vnd *vnd;
const char *id;
struct gpio_desc *enable_gpio;
struct gpio_desc *device_wake_gpio;
};
struct h5_vnd {
int (*setup)(struct h5 *h5);
void (*open)(struct h5 *h5);
void (*close)(struct h5 *h5);
int (*suspend)(struct h5 *h5);
int (*resume)(struct h5 *h5);
const struct acpi_gpio_mapping *acpi_gpio_map;
};
static void h5_reset_rx(struct h5 *h5);
static void h5_link_control(struct hci_uart *hu, const void *data, size_t len)
{
struct h5 *h5 = hu->priv;
struct sk_buff *nskb;
nskb = alloc_skb(3, GFP_ATOMIC);
if (!nskb)
return;
hci_skb_pkt_type(nskb) = HCI_3WIRE_LINK_PKT;
skb_put_data(nskb, data, len);
skb_queue_tail(&h5->unrel, nskb);
}
static u8 h5_cfg_field(struct h5 *h5)
{
/* Sliding window size (first 3 bits) */
return h5->tx_win & 0x07;
}
static void h5_timed_event(struct timer_list *t)
{
const unsigned char sync_req[] = { 0x01, 0x7e };
unsigned char conf_req[3] = { 0x03, 0xfc };
struct h5 *h5 = from_timer(h5, t, timer);
struct hci_uart *hu = h5->hu;
struct sk_buff *skb;
unsigned long flags;
BT_DBG("%s", hu->hdev->name);
if (h5->state == H5_UNINITIALIZED)
h5_link_control(hu, sync_req, sizeof(sync_req));
if (h5->state == H5_INITIALIZED) {
conf_req[2] = h5_cfg_field(h5);
h5_link_control(hu, conf_req, sizeof(conf_req));
}
if (h5->state != H5_ACTIVE) {
mod_timer(&h5->timer, jiffies + H5_SYNC_TIMEOUT);
goto wakeup;
}
if (h5->sleep != H5_AWAKE) {
h5->sleep = H5_SLEEPING;
goto wakeup;
}
BT_DBG("hu %p retransmitting %u pkts", hu, h5->unack.qlen);
spin_lock_irqsave_nested(&h5->unack.lock, flags, SINGLE_DEPTH_NESTING);
while ((skb = __skb_dequeue_tail(&h5->unack)) != NULL) {
h5->tx_seq = (h5->tx_seq - 1) & 0x07;
skb_queue_head(&h5->rel, skb);
}
spin_unlock_irqrestore(&h5->unack.lock, flags);
wakeup:
hci_uart_tx_wakeup(hu);
}
static void h5_peer_reset(struct hci_uart *hu)
{
struct h5 *h5 = hu->priv;
bt_dev_err(hu->hdev, "Peer device has reset");
h5->state = H5_UNINITIALIZED;
del_timer(&h5->timer);
skb_queue_purge(&h5->rel);
skb_queue_purge(&h5->unrel);
skb_queue_purge(&h5->unack);
h5->tx_seq = 0;
h5->tx_ack = 0;
/* Send reset request to upper stack */
hci_reset_dev(hu->hdev);
}
static int h5_open(struct hci_uart *hu)
{
struct h5 *h5;
const unsigned char sync[]