From patchwork Wed Jan 18 18:04:59 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rob Herring X-Patchwork-Id: 91821 Delivered-To: patch@linaro.org Received: by 10.140.20.99 with SMTP id 90csp1137750qgi; Wed, 18 Jan 2017 10:05:39 -0800 (PST) X-Received: by 10.99.64.133 with SMTP id n127mr5479929pga.104.1484762738864; Wed, 18 Jan 2017 10:05:38 -0800 (PST) Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id 3si922837pli.45.2017.01.18.10.05.38; Wed, 18 Jan 2017 10:05:38 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752961AbdARSFW (ORCPT + 25 others); Wed, 18 Jan 2017 13:05:22 -0500 Received: from mail-oi0-f65.google.com ([209.85.218.65]:34952 "EHLO mail-oi0-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752747AbdARSFD (ORCPT ); Wed, 18 Jan 2017 13:05:03 -0500 Received: by mail-oi0-f65.google.com with SMTP id x84so1457179oix.2; Wed, 18 Jan 2017 10:05:03 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=Vf8VSnprqyg+ce3lR8NpCl5/z391E6bEo5eDSJs1d/o=; b=Z4qtkVCowU772cScU6ece0Lb4iitRj7OPcYoY/iaBKeZY/mtA3U06jA2dP/qkdBjDg ht+qmW/b2lKQtQrT67hdRenputc8BnnavmgcgVD+xKEans6EtPND5+rr3xVKbMPxHrtP mggQe2iJWbOJGHhdkdPSvyKuw7rW+uJCEL4F1IZ7cIn0sR2LMY9AZVe9CMT2NEjwEw2o XxRHn6F2Fu5SrSjK7hLPLDexbfOpWTXYtolxNLAIrMYVbdlqrWMYvl88cKPpYs8C1S20 vlLlHrQsADi4dpqxtIWnduVyCiokhbz/CG7EAoRkNywGMkxG0xdB/HGB8/AZgRZCDuLa wHNw== X-Gm-Message-State: AIkVDXIS2MNG+BB6Z7lZPAkX8fCZxuXTWiWEhMrE4YAaM81Loue4f2023nplXsfAeazFnQ== X-Received: by 10.202.7.4 with SMTP id 4mr2016011oih.58.1484762702951; Wed, 18 Jan 2017 10:05:02 -0800 (PST) Received: from rob-hp-laptop.herring.priv (72-48-98-129.dyn.grandenetworks.net. [72.48.98.129]) by smtp.googlemail.com with ESMTPSA id r41sm490765otc.40.2017.01.18.10.05.01 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 18 Jan 2017 10:05:02 -0800 (PST) From: Rob Herring To: Marcel Holtmann Cc: linux-kernel@vger.kernel.org, john.stultz@linaro.org, Eyal Reizer , Gigi Joseph , Gustavo Padovan , Johan Hedberg , linux-bluetooth@vger.kernel.org Subject: [RFC 1/2] bluetooth: hci_uart: add serdev driver support library Date: Wed, 18 Jan 2017 12:04:59 -0600 Message-Id: <20170118180500.7791-2-robh@kernel.org> X-Mailer: git-send-email 2.10.1 In-Reply-To: <20170118180500.7791-1-robh@kernel.org> References: <20170118180500.7791-1-robh@kernel.org> Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org This adds library functions for serdev based BT drivers. This is largely copied from hci_ldisc.c and modified to use serdev calls. There's a little bit of duplication, but I avoided intermixing this as the ldisc code should eventually go away. Signed-off-by: Rob Herring Cc: Marcel Holtmann Cc: Gustavo Padovan Cc: Johan Hedberg Cc: linux-bluetooth@vger.kernel.org --- drivers/bluetooth/Makefile | 2 +- drivers/bluetooth/hci_serdev.c | 370 +++++++++++++++++++++++++++++++++++++++++ drivers/bluetooth/hci_uart.h | 4 + 3 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 drivers/bluetooth/hci_serdev.c -- 2.10.1 diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile index 80627187c8b6..a69b18ac704a 100644 --- a/drivers/bluetooth/Makefile +++ b/drivers/bluetooth/Makefile @@ -28,7 +28,7 @@ obj-$(CONFIG_BT_QCA) += btqca.o btmrvl-y := btmrvl_main.o btmrvl-$(CONFIG_DEBUG_FS) += btmrvl_debugfs.o -hci_uart-y := hci_ldisc.o +hci_uart-y := hci_ldisc.o hci_serdev.o hci_uart-$(CONFIG_BT_HCIUART_H4) += hci_h4.o hci_uart-$(CONFIG_BT_HCIUART_BCSP) += hci_bcsp.o hci_uart-$(CONFIG_BT_HCIUART_LL) += hci_ll.o diff --git a/drivers/bluetooth/hci_serdev.c b/drivers/bluetooth/hci_serdev.c new file mode 100644 index 000000000000..b2f7293a3162 --- /dev/null +++ b/drivers/bluetooth/hci_serdev.c @@ -0,0 +1,370 @@ +/* + * + * Bluetooth HCI serdev driver lib + * + * Copyright (C) 2017 Linaro, Ltd., Rob Herring + * + * Based on hci_ldisc.c: + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2004-2005 Marcel Holtmann + * + * + * 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 "hci_uart.h" + +struct serdev_device_ops hci_serdev_client_ops; + +static inline void hci_uart_tx_complete(struct hci_uart *hu, int pkt_type) +{ + struct hci_dev *hdev = hu->hdev; + + /* Update HCI stat counters */ + switch (pkt_type) { + 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; + } +} + +static inline struct sk_buff *hci_uart_dequeue(struct hci_uart *hu) +{ + struct sk_buff *skb = hu->tx_skb; + + if (!skb) + skb = hu->proto->dequeue(hu); + else + hu->tx_skb = NULL; + + return skb; +} + +static void hci_uart_write_work(struct work_struct *work) +{ + struct hci_uart *hu = container_of(work, struct hci_uart, write_work); + struct serdev_device *serdev = hu->serdev; + struct hci_dev *hdev = hu->hdev; + struct sk_buff *skb; + + /* REVISIT: should we cope with bad skbs or ->write() returning + * and error value ? + */ + +restart: + clear_bit(HCI_UART_TX_WAKEUP, &hu->tx_state); + + while ((skb = hci_uart_dequeue(hu))) { + int len; + + len = serdev_device_write_buf(serdev, skb->data, skb->len); + hdev->stat.byte_tx += len; + + skb_pull(skb, len); + if (skb->len) { + hu->tx_skb = skb; + break; + } + + hci_uart_tx_complete(hu, hci_skb_pkt_type(skb)); + kfree_skb(skb); + } + + if (test_bit(HCI_UART_TX_WAKEUP, &hu->tx_state)) + goto restart; + + clear_bit(HCI_UART_SENDING, &hu->tx_state); +} + +/* ------- Interface to HCI layer ------ */ +/* Initialize device */ +static int hci_uart_open(struct hci_dev *hdev) +{ + struct hci_uart *hu = hci_get_drvdata(hdev); + + BT_DBG("%s %p", hdev->name, hdev); + + serdev_device_set_client_ops(hu->serdev, &hci_serdev_client_ops); + + return serdev_device_open(hu->serdev); +} + +/* Reset device */ +static int hci_uart_flush(struct hci_dev *hdev) +{ + struct hci_uart *hu = hci_get_drvdata(hdev); + + BT_DBG("hdev %p serdev %p", hdev, hu->serdev); + + if (hu->tx_skb) { + kfree_skb(hu->tx_skb); hu->tx_skb = NULL; + } + + /* Flush any pending characters in the driver and discipline. */ + serdev_device_write_flush(hu->serdev); + + if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) + hu->proto->flush(hu); + + return 0; +} + +/* Close device */ +static int hci_uart_close(struct hci_dev *hdev) +{ + struct hci_uart *hu = hci_get_drvdata(hdev); + + BT_DBG("hdev %p", hdev); + + hci_uart_flush(hdev); + hdev->flush = NULL; + + serdev_device_close(hu->serdev); + + return 0; +} + +/* Send frames from HCI layer */ +static int hci_uart_send_frame(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct hci_uart *hu = hci_get_drvdata(hdev); + + BT_DBG("%s: type %d len %d", hdev->name, hci_skb_pkt_type(skb), + skb->len); + + hu->proto->enqueue(hu, skb); + + hci_uart_tx_wakeup(hu); + + return 0; +} + +static int hci_uart_setup(struct hci_dev *hdev) +{ + struct hci_uart *hu = hci_get_drvdata(hdev); + struct hci_rp_read_local_version *ver; + struct sk_buff *skb; + unsigned int speed; + int err; + + /* Init speed if any */ + if (hu->init_speed) + speed = hu->init_speed; + else if (hu->proto->init_speed) + speed = hu->proto->init_speed; + else + speed = 0; + + if (speed) + serdev_device_set_baudrate(hu->serdev, speed); + + /* Operational speed if any */ + if (hu->oper_speed) + speed = hu->oper_speed; + else if (hu->proto->oper_speed) + speed = hu->proto->oper_speed; + else + speed = 0; + + if (hu->proto->set_baudrate && speed) { + err = hu->proto->set_baudrate(hu, speed); + if (!err) + serdev_device_set_baudrate(hu->serdev, speed); + } + + if (hu->proto->setup) + return hu->proto->setup(hu); + + if (!test_bit(HCI_UART_VND_DETECT, &hu->hdev_flags)) + return 0; + + skb = __hci_cmd_sync(hdev, HCI_OP_READ_LOCAL_VERSION, 0, NULL, + HCI_INIT_TIMEOUT); + if (IS_ERR(skb)) { + BT_ERR("%s: Reading local version information failed (%ld)", + hdev->name, PTR_ERR(skb)); + return 0; + } + + if (skb->len != sizeof(*ver)) { + BT_ERR("%s: Event length mismatch for version information", + hdev->name); + goto done; + } + +done: + kfree_skb(skb); + return 0; +} + +/* hci_uart_write_wakeup() + * + * Callback for transmit wakeup. Called when low level + * device driver can accept more send data. + * + * Arguments: tty pointer to associated tty instance data + * Return Value: None + */ +static void hci_uart_write_wakeup(struct serdev_device *serdev) +{ + struct hci_uart *hu = serdev_device_get_drvdata(serdev); + + BT_DBG(""); + + if (!hu) + return; + + if (serdev != hu->serdev) + return; + + if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) + hci_uart_tx_wakeup(hu); +} + +/* hci_uart_receive_buf() + * + * Called by tty low level driver when receive data is + * available. + * + * Arguments: tty pointer to tty isntance data + * data pointer to received data + * flags pointer to flags for data + * count count of received data in bytes + * + * Return Value: None + */ +static int hci_uart_receive_buf(struct serdev_device *serdev, const u8 *data, + size_t count) +{ + struct hci_uart *hu = serdev_device_get_drvdata(serdev); + + if (!hu || serdev != hu->serdev) + return 0; + + if (!test_bit(HCI_UART_PROTO_READY, &hu->flags)) + return 0; + + /* It does not need a lock here as it is already protected by a mutex in + * tty caller + */ + hu->proto->recv(hu, data, count); + + if (hu->hdev) + hu->hdev->stat.byte_rx += count; + + return count; +} + +struct serdev_device_ops hci_serdev_client_ops = { + .receive_buf = hci_uart_receive_buf, + .write_wakeup = hci_uart_write_wakeup, +}; + +int hci_uart_register_device(struct hci_uart *hu, const struct hci_uart_proto *p) +{ + int err; + struct hci_dev *hdev; + + BT_DBG(""); + + err = p->open(hu); + if (err) + return err; + + hu->proto = p; + set_bit(HCI_UART_PROTO_READY, &hu->flags); + + /* Initialize and register HCI device */ + hdev = hci_alloc_dev(); + if (!hdev) { + BT_ERR("Can't allocate HCI device"); + err = -ENOMEM; + goto err_alloc; + } + + hu->hdev = hdev; + + hdev->bus = HCI_UART; + hci_set_drvdata(hdev, hu); + + INIT_WORK(&hu->write_work, hci_uart_write_work); + + /* 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 (hu->proto->setup) + hdev->manufacturer = hu->proto->manufacturer; + + hdev->open = hci_uart_open; + hdev->close = hci_uart_close; + hdev->flush = hci_uart_flush; + hdev->send = hci_uart_send_frame; + hdev->setup = hci_uart_setup; + SET_HCIDEV_DEV(hdev, &hu->serdev->dev); + + if (test_bit(HCI_UART_RAW_DEVICE, &hu->hdev_flags)) + set_bit(HCI_QUIRK_RAW_DEVICE, &hdev->quirks); + + if (test_bit(HCI_UART_EXT_CONFIG, &hu->hdev_flags)) + set_bit(HCI_QUIRK_EXTERNAL_CONFIG, &hdev->quirks); + + if (!test_bit(HCI_UART_RESET_ON_INIT, &hu->hdev_flags)) + set_bit(HCI_QUIRK_RESET_ON_CLOSE, &hdev->quirks); + + if (test_bit(HCI_UART_CREATE_AMP, &hu->hdev_flags)) + hdev->dev_type = HCI_AMP; + else + hdev->dev_type = HCI_PRIMARY; + + if (test_bit(HCI_UART_INIT_PENDING, &hu->hdev_flags)) + return 0; + + if (hci_register_dev(hdev) < 0) { + BT_ERR("Can't register HCI device"); + err = -ENODEV; + goto err_register; + } + + set_bit(HCI_UART_REGISTERED, &hu->flags); + + return 0; + +err_register: + hci_free_dev(hdev); +err_alloc: + clear_bit(HCI_UART_PROTO_READY, &hu->flags); + p->close(hu); + return err; +} diff --git a/drivers/bluetooth/hci_uart.h b/drivers/bluetooth/hci_uart.h index cb7f4b4aeca0..4796a29d575f 100644 --- a/drivers/bluetooth/hci_uart.h +++ b/drivers/bluetooth/hci_uart.h @@ -58,6 +58,7 @@ #define HCI_UART_VND_DETECT 5 struct hci_uart; +struct serdev_device; struct hci_uart_proto { unsigned int id; @@ -77,6 +78,7 @@ struct hci_uart_proto { struct hci_uart { struct tty_struct *tty; + struct serdev_device *serdev; struct hci_dev *hdev; unsigned long flags; unsigned long hdev_flags; @@ -105,6 +107,8 @@ struct hci_uart { int hci_uart_register_proto(const struct hci_uart_proto *p); int hci_uart_unregister_proto(const struct hci_uart_proto *p); +int hci_uart_register_device(struct hci_uart *hu, const struct hci_uart_proto *p); + int hci_uart_tx_wakeup(struct hci_uart *hu); int hci_uart_init_ready(struct hci_uart *hu); void hci_uart_set_baudrate(struct hci_uart *hu, unsigned int speed);