From patchwork Mon Jul 9 15:56:59 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sean Wang X-Patchwork-Id: 10515119 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id CDF0960318 for ; Mon, 9 Jul 2018 15:58:12 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id B7D6428C81 for ; Mon, 9 Jul 2018 15:58:12 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id ABEE028C85; Mon, 9 Jul 2018 15:58:12 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID, MAILING_LIST_MULTI, UNPARSEABLE_RELAY autolearn=ham version=3.3.1 Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id A2DB928C81 for ; Mon, 9 Jul 2018 15:58:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:Cc:List-Subscribe:List-Help:List-Post: List-Archive:List-Unsubscribe:List-Id:MIME-Version:References:In-Reply-To: Message-ID:Date:Subject:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=uAZXLX4Mbbvn3g+ErfhZUPeRtyJnrlZMBfEaJDtUxzo=; b=OwGU48IhahtuIu vuaYx2F4BrP+BeyJutVi/1a+elaAYHYZ7kpeFwkkPIQ2AVCcCsQUF1iXyUJNzL3r/8CO43DuARLLf pwjYnTVLI0Ife7e9LcmUDEDAh/5+IW/BV0VWWBJ1gAAiz/jBx+ei9WRfR2yWttg6fhOKAIqCvW3k1 h5XqREwDKuwyuOyI6b0zGTgtzkPKQxAaNJGd8vStHBxQz/ICHiddYiH6j68yioJI+/0Ir66K16d2l BP2XQC4x2l2HilRDeWR8Hm8BLHGZ/1S/6etMPyywvv2Bss84DK3q9NZJk0Ki7BthRVhAAU9kIHt/s XdANE9sXy9hGAKlD5AHQ==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.90_1 #2 (Red Hat Linux)) id 1fcYYA-0006YU-Ov; Mon, 09 Jul 2018 15:58:06 +0000 Received: from [210.61.82.184] (helo=mailgw02.mediatek.com) by bombadil.infradead.org with esmtps (Exim 4.90_1 #2 (Red Hat Linux)) id 1fcYXU-0005er-Mr; Mon, 09 Jul 2018 15:57:27 +0000 X-UUID: 48ad6fcb7cc0475c91da98329c1a374e-20180709 Received: from mtkcas09.mediatek.inc [(172.21.101.178)] by mailgw02.mediatek.com (envelope-from ) (mhqrelay.mediatek.com ESMTP with TLS) with ESMTP id 1919547635; Mon, 09 Jul 2018 23:57:07 +0800 Received: from mtkcas07.mediatek.inc (172.21.101.84) by mtkmbs01n2.mediatek.inc (172.21.101.79) with Microsoft SMTP Server (TLS) id 15.0.1210.3; Mon, 9 Jul 2018 23:57:05 +0800 Received: from mtkswgap22.mediatek.inc (172.21.77.33) by mtkcas07.mediatek.inc (172.21.101.73) with Microsoft SMTP Server id 15.0.1210.3 via Frontend Transport; Mon, 9 Jul 2018 23:57:05 +0800 From: To: , , , Subject: [PATCH v5 3/7] Bluetooth: Add new serdev based driver for UART attached controllers Date: Mon, 9 Jul 2018 23:56:59 +0800 Message-ID: <2467b1409b05cb964e9c5f85e73c9e6e46b9667e.1531150733.git.sean.wang@mediatek.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: References: MIME-Version: 1.0 X-MTK: N X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20180709_085725_138200_0B4B54DC X-CRM114-Status: GOOD ( 24.00 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: devicetree@vger.kernel.org, linux-mediatek@lists.infradead.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-bluetooth@vger.kernel.org Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP From: Marcel Holtmann This is a from scratch written driver to run H:4 on serdev based system with a Bluetooth controller attached via an UART. It is currently tested on RPi3 and it has Broadcom integration. It is DT only and is missing GPIO and runtime power management integration. Also Apple or ACPI support is currently not added. To integrate with controllers from Intel and Qualcomm, similar handling like with btusb.c has to be done. A simple abstraction for that has been provided to make it similar to hci_uart. The goal is to run individual drivers on serdev capable systems so that we can retire hci_uart on these system and continue with a lot simpler and easier to maintain driver. It seems that hci_uart has too many race conditions due to handling TTY and line disciplines. And fixes for that are not really related to serdev based drivers. In a serdev only world it makes sense to remove any of the complex code. Signed-off-by: Marcel Holtmann --- drivers/bluetooth/Kconfig | 11 + drivers/bluetooth/Makefile | 1 + drivers/bluetooth/btuart.c | 506 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 518 insertions(+) create mode 100644 drivers/bluetooth/btuart.c diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig index f3c643a..00fdf5f 100644 --- a/drivers/bluetooth/Kconfig +++ b/drivers/bluetooth/Kconfig @@ -74,6 +74,17 @@ config BT_HCIBTSDIO Say Y here to compile support for Bluetooth SDIO devices into the kernel or say M to compile it as module (btsdio). +config BT_HCIBTUART + tristate "HCI UART driver" + depends on SERIAL_DEV_BUS + help + Bluetooth HCI UART driver. + This driver is required if you want to use Bluetooth device with + UART interface. + + Say Y here to compile support for Bluetooth UART devices into the + kernel or say M to compile it as module (btuart). + config BT_HCIUART tristate "HCI UART driver" depends on SERIAL_DEV_BUS || !SERIAL_DEV_BUS diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index ec16c55..60a19cb 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_BT_HCIBLUECARD) += bluecard_cs.o obj-$(CONFIG_BT_HCIBTUSB) += btusb.o obj-$(CONFIG_BT_HCIBTSDIO) += btsdio.o +obj-$(CONFIG_BT_HCIBTUART) += btuart.o obj-$(CONFIG_BT_INTEL) += btintel.o obj-$(CONFIG_BT_ATH3K) += ath3k.o diff --git a/drivers/bluetooth/btuart.c b/drivers/bluetooth/btuart.c new file mode 100644 index 0000000..a900aac --- /dev/null +++ b/drivers/bluetooth/btuart.c @@ -0,0 +1,506 @@ +/* + * + * Generic Bluetooth HCI UART driver + * + * Copyright (C) 2015-2018 Intel Corporation + * + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "h4_recv.h" +#include "btbcm.h" + +#define VERSION "1.0" + +struct btuart_vnd { + const struct h4_recv_pkt *recv_pkts; + int recv_pkts_cnt; + unsigned int manufacturer; + int (*open)(struct hci_dev *hdev); + int (*close)(struct hci_dev *hdev); + int (*setup)(struct hci_dev *hdev); +}; + +struct btuart_dev { + struct hci_dev *hdev; + struct serdev_device *serdev; + + struct work_struct tx_work; + unsigned long tx_state; + struct sk_buff_head txq; + + struct sk_buff *rx_skb; + + const struct btuart_vnd *vnd; +}; + +#define BTUART_TX_STATE_ACTIVE 1 +#define BTUART_TX_STATE_WAKEUP 2 + +static void btuart_tx_work(struct work_struct *work) +{ + struct btuart_dev *bdev = container_of(work, struct btuart_dev, + tx_work); + struct serdev_device *serdev = bdev->serdev; + struct hci_dev *hdev = bdev->hdev; + + while (1) { + clear_bit(BTUART_TX_STATE_WAKEUP, &bdev->tx_state); + + while (1) { + struct sk_buff *skb = skb_dequeue(&bdev->txq); + int len; + + if (!skb) + break; + + len = serdev_device_write_buf(serdev, skb->data, + skb->len); + hdev->stat.byte_tx += len; + + skb_pull(skb, len); + if (skb->len > 0) { + skb_queue_head(&bdev->txq, skb); + break; + } + + 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; + } + + kfree_skb(skb); + } + + if (!test_bit(BTUART_TX_STATE_WAKEUP, &bdev->tx_state)) + break; + } + + clear_bit(BTUART_TX_STATE_ACTIVE, &bdev->tx_state); +} + +static int btuart_tx_wakeup(struct btuart_dev *bdev) +{ + if (test_and_set_bit(BTUART_TX_STATE_ACTIVE, &bdev->tx_state)) { + set_bit(BTUART_TX_STATE_WAKEUP, &bdev->tx_state); + return 0; + } + + schedule_work(&bdev->tx_work); + return 0; +} + +static int btuart_open(struct hci_dev *hdev) +{ + struct btuart_dev *bdev = hci_get_drvdata(hdev); + int err; + + err = serdev_device_open(bdev->serdev); + if (err) { + bt_dev_err(hdev, "Unable to open UART device %s", + dev_name(&bdev->serdev->dev)); + return err; + } + + if (bdev->vnd->open) { + err = bdev->vnd->open(hdev); + if (err) { + serdev_device_close(bdev->serdev); + return err; + } + } + + return 0; +} + +static int btuart_close(struct hci_dev *hdev) +{ + struct btuart_dev *bdev = hci_get_drvdata(hdev); + int err; + + if (bdev->vnd->close) { + err = bdev->vnd->close(hdev); + if (err) + return err; + } + + serdev_device_close(bdev->serdev); + + return 0; +} + +static int btuart_flush(struct hci_dev *hdev) +{ + struct btuart_dev *bdev = hci_get_drvdata(hdev); + + /* Flush any pending characters */ + serdev_device_write_flush(bdev->serdev); + skb_queue_purge(&bdev->txq); + + cancel_work_sync(&bdev->tx_work); + + kfree_skb(bdev->rx_skb); + bdev->rx_skb = NULL; + + return 0; +} + +static int btuart_setup(struct hci_dev *hdev) +{ + struct btuart_dev *bdev = hci_get_drvdata(hdev); + + if (bdev->vnd->setup) + return bdev->vnd->setup(hdev); + + return 0; +} + +static int btuart_send_frame(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct btuart_dev *bdev = hci_get_drvdata(hdev); + + /* Prepend skb with frame type */ + memcpy(skb_push(skb, 1), &hci_skb_pkt_type(skb), 1); + skb_queue_tail(&bdev->txq, skb); + + btuart_tx_wakeup(bdev); + return 0; +} + +static int btuart_receive_buf(struct serdev_device *serdev, const u8 *data, + size_t count) +{ + struct btuart_dev *bdev = serdev_device_get_drvdata(serdev); + const struct btuart_vnd *vnd = bdev->vnd; + + bdev->rx_skb = h4_recv_buf(bdev->hdev, bdev->rx_skb, data, count, + vnd->recv_pkts, vnd->recv_pkts_cnt); + if (IS_ERR(bdev->rx_skb)) { + int err = PTR_ERR(bdev->rx_skb); + bt_dev_err(bdev->hdev, "Frame reassembly failed (%d)", err); + bdev->rx_skb = NULL; + return err; + } + + bdev->hdev->stat.byte_rx += count; + + return count; +} + +static void btuart_write_wakeup(struct serdev_device *serdev) +{ + struct btuart_dev *bdev = serdev_device_get_drvdata(serdev); + + btuart_tx_wakeup(bdev); +} + +static const struct serdev_device_ops btuart_client_ops = { + .receive_buf = btuart_receive_buf, + .write_wakeup = btuart_write_wakeup, +}; + +#define BCM_NULL_PKT 0x00 +#define BCM_NULL_SIZE 0 + +#define BCM_LM_DIAG_PKT 0x07 +#define BCM_LM_DIAG_SIZE 63 + +#define BCM_RECV_LM_DIAG \ + .type = BCM_LM_DIAG_PKT, \ + .hlen = BCM_LM_DIAG_SIZE, \ + .loff = 0, \ + .lsize = 0, \ + .maxlen = BCM_LM_DIAG_SIZE + +#define BCM_RECV_NULL \ + .type = BCM_NULL_PKT, \ + .hlen = BCM_NULL_SIZE, \ + .loff = 0, \ + .lsize = 0, \ + .maxlen = BCM_NULL_SIZE + +static int bcm_set_diag(struct hci_dev *hdev, bool enable) +{ + struct btuart_dev *bdev = hci_get_drvdata(hdev); + struct sk_buff *skb; + + if (!test_bit(HCI_RUNNING, &hdev->flags)) + return -ENETDOWN; + + skb = bt_skb_alloc(3, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + skb_put_u8(skb, BCM_LM_DIAG_PKT); + skb_put_u8(skb, 0xf0); + skb_put_u8(skb, enable); + + skb_queue_tail(&bdev->txq, skb); + btuart_tx_wakeup(bdev); + + return 0; +} + +static int bcm_set_baudrate(struct btuart_dev *bdev, unsigned int speed) +{ + struct hci_dev *hdev = bdev->hdev; + struct sk_buff *skb; + struct bcm_update_uart_baud_rate param; + + if (speed > 3000000) { + struct bcm_write_uart_clock_setting clock; + + clock.type = BCM_UART_CLOCK_48MHZ; + + bt_dev_dbg(hdev, "Set Controller clock (%d)", clock.type); + + /* This Broadcom specific command changes the UART's controller + * clock for baud rate > 3000000. + */ + skb = __hci_cmd_sync(hdev, 0xfc45, 1, &clock, HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + int err = PTR_ERR(skb); + bt_dev_err(hdev, "Failed to write clock (%d)", err); + return err; + } + + kfree_skb(skb); + } + + bt_dev_dbg(hdev, "Set Controller UART speed to %d bit/s", speed); + + param.zero = cpu_to_le16(0); + param.baud_rate = cpu_to_le32(speed); + + /* This Broadcom specific command changes the UART's controller baud + * rate. + */ + skb = __hci_cmd_sync(hdev, 0xfc18, sizeof(param), ¶m, + HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + int err = PTR_ERR(skb); + bt_dev_err(hdev, "Failed to write update baudrate (%d)", err); + return err; + } + + kfree_skb(skb); + + return 0; +} + +static int bcm_setup(struct hci_dev *hdev) +{ + struct btuart_dev *bdev = hci_get_drvdata(hdev); + char fw_name[64]; + const struct firmware *fw; + unsigned int speed; + int err; + + hdev->set_diag = bcm_set_diag; + hdev->set_bdaddr = btbcm_set_bdaddr; + + /* Init speed if any */ + speed = 115200; + + if (speed) + serdev_device_set_baudrate(bdev->serdev, speed); + + /* Operational speed if any */ + speed = 115200; + + if (speed) { + err = bcm_set_baudrate(bdev, speed); + if (err) + bt_dev_err(hdev, "Failed to set baudrate"); + else + serdev_device_set_baudrate(bdev->serdev, speed); + } + + err = btbcm_initialize(hdev, fw_name, sizeof(fw_name), false); + if (err) + return err; + + err = request_firmware(&fw, fw_name, &hdev->dev); + if (err < 0) { + bt_dev_warn(hdev, "Patch %s not found", fw_name); + return 0; + } + + err = btbcm_patchram(bdev->hdev, fw); + if (err) { + bt_dev_err(hdev, "Patching failed (%d)", err); + goto finalize; + } + + /* Init speed if any */ + speed = 115200; + + if (speed) + serdev_device_set_baudrate(bdev->serdev, speed); + + /* Operational speed if any */ + speed = 115200; + + if (speed) { + err = bcm_set_baudrate(bdev, speed); + if (!err) + serdev_device_set_baudrate(bdev->serdev, speed); + } + +finalize: + release_firmware(fw); + + err = btbcm_finalize(hdev); + if (err) + return err; + + return err; +} + +static const struct h4_recv_pkt bcm_recv_pkts[] = { + { H4_RECV_ACL, .recv = hci_recv_frame }, + { H4_RECV_SCO, .recv = hci_recv_frame }, + { H4_RECV_EVENT, .recv = hci_recv_frame }, + { BCM_RECV_LM_DIAG, .recv = hci_recv_diag }, + { BCM_RECV_NULL, .recv = hci_recv_diag }, +}; + +static const struct btuart_vnd bcm_vnd = { + .recv_pkts = bcm_recv_pkts, + .recv_pkts_cnt = ARRAY_SIZE(bcm_recv_pkts), + .manufacturer = 15, + .setup = bcm_setup, +}; + +static const struct h4_recv_pkt default_recv_pkts[] = { + { H4_RECV_ACL, .recv = hci_recv_frame }, + { H4_RECV_SCO, .recv = hci_recv_frame }, + { H4_RECV_EVENT, .recv = hci_recv_frame }, +}; + +static const struct btuart_vnd default_vnd = { + .recv_pkts = default_recv_pkts, + .recv_pkts_cnt = ARRAY_SIZE(default_recv_pkts), +}; + +static int btuart_probe(struct serdev_device *serdev) +{ + struct btuart_dev *bdev; + struct hci_dev *hdev; + + bdev = devm_kzalloc(&serdev->dev, sizeof(*bdev), GFP_KERNEL); + if (!bdev) + return -ENOMEM; + + /* Request the vendor specific data and callbacks */ + bdev->vnd = device_get_match_data(&serdev->dev); + if (!bdev->vnd) + bdev->vnd = &default_vnd; + + bdev->serdev = serdev; + serdev_device_set_drvdata(serdev, bdev); + + serdev_device_set_client_ops(serdev, &btuart_client_ops); + + INIT_WORK(&bdev->tx_work, btuart_tx_work); + skb_queue_head_init(&bdev->txq); + + /* Initialize and register HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + dev_err(&serdev->dev, "Can't allocate HCI device\n"); + return -ENOMEM; + } + + bdev->hdev = hdev; + + hdev->bus = HCI_UART; + hci_set_drvdata(hdev, bdev); + + /* Only when vendor specific setup callback is provided, consider + * the manufacturer information valid. This avoids filling in the + * value for Ericsson when nothing is specified. + */ + if (bdev->vnd->setup) + hdev->manufacturer = bdev->vnd->manufacturer; + + hdev->open = btuart_open; + hdev->close = btuart_close; + hdev->flush = btuart_flush; + hdev->setup = btuart_setup; + hdev->send = btuart_send_frame; + SET_HCIDEV_DEV(hdev, &serdev->dev); + + if (hci_register_dev(hdev) < 0) { + dev_err(&serdev->dev, "Can't register HCI device\n"); + hci_free_dev(hdev); + return -ENODEV; + } + + return 0; +} + +static void btuart_remove(struct serdev_device *serdev) +{ + struct btuart_dev *bdev = serdev_device_get_drvdata(serdev); + struct hci_dev *hdev = bdev->hdev; + + hci_unregister_dev(hdev); + hci_free_dev(hdev); +} + +#ifdef CONFIG_OF +static const struct of_device_id btuart_of_match_table[] = { + { .compatible = "brcm,bcm43438-bt", .data = &bcm_vnd }, + { } +}; +MODULE_DEVICE_TABLE(of, btuart_of_match_table); +#endif + +static struct serdev_device_driver btuart_driver = { + .probe = btuart_probe, + .remove = btuart_remove, + .driver = { + .name = "btuart", + .of_match_table = of_match_ptr(btuart_of_match_table), + }, +}; + +module_serdev_device_driver(btuart_driver); + +MODULE_AUTHOR("Marcel Holtmann "); +MODULE_DESCRIPTION("Generic Bluetooth UART driver ver " VERSION); +MODULE_VERSION(VERSION); +MODULE_LICENSE("GPL");