2481 lines
68 KiB
Diff
2481 lines
68 KiB
Diff
|
From patchwork Sat Aug 13 03:14:32 2016
|
||
|
Content-Type: text/plain; charset="utf-8"
|
||
|
MIME-Version: 1.0
|
||
|
Content-Transfer-Encoding: 7bit
|
||
|
Subject: [RFC,1/7] tty: serial: omap: add UPF_BOOT_AUTOCONF flag for DT init
|
||
|
From: Sebastian Reichel <sre@kernel.org>
|
||
|
X-Patchwork-Id: 9278297
|
||
|
Message-Id: <1471058078-5579-2-git-send-email-sre@kernel.org>
|
||
|
To: Sebastian Reichel <sre@kernel.org>, Tony Lindgren <tony@atomide.com>,
|
||
|
Rob Herring <robh+dt@kernel.org>, Mark Rutland <mark.rutland@arm.com>,
|
||
|
Marcel Holtmann <marcel@holtmann.org>,
|
||
|
Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
|
||
|
Jiri Slaby <jslaby@suse.com>
|
||
|
Cc: Ville Tervo <ville.tervo@iki.fi>,
|
||
|
=?UTF-8?q?Filip=20Matijevi=C4=87?= <filip.matijevic.pz@gmail.com>,
|
||
|
Aaro Koskinen <aaro.koskinen@iki.fi>, Pavel Machek <pavel@ucw.cz>,
|
||
|
=?UTF-8?q?Pali=20Roh=C3=A1r?= <pali.rohar@gmail.com>,
|
||
|
ivo.g.dimitrov.75@gmail.com, linux-bluetooth@vger.kernel.org,
|
||
|
linux-serial@vger.kernel.org, linux-omap@vger.kernel.org,
|
||
|
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org
|
||
|
Date: Sat, 13 Aug 2016 05:14:32 +0200
|
||
|
|
||
|
---
|
||
|
drivers/tty/serial/omap-serial.c | 3 +++
|
||
|
1 file changed, 3 insertions(+)
|
||
|
Acked-by: Pavel Machek <pavel@ucw.cz>
|
||
|
|
||
|
diff --git a/drivers/tty/serial/omap-serial.c b/drivers/tty/serial/omap-serial.c
|
||
|
index a2a529994ba5..7c2c77789c2c 100644
|
||
|
--- a/drivers/tty/serial/omap-serial.c
|
||
|
+++ b/drivers/tty/serial/omap-serial.c
|
||
|
@@ -1542,6 +1542,9 @@ static struct omap_uart_port_info *of_get_uart_port_info(struct device *dev)
|
||
|
|
||
|
of_property_read_u32(dev->of_node, "clock-frequency",
|
||
|
&omap_up_info->uartclk);
|
||
|
+
|
||
|
+ omap_up_info->flags = UPF_BOOT_AUTOCONF;
|
||
|
+
|
||
|
return omap_up_info;
|
||
|
}
|
||
|
|
||
|
From 6102245c5711e73b83ad79ab0f2c3ec040262a87 Mon Sep 17 00:00:00 2001
|
||
|
From: Sebastian Reichel <sre@kernel.org>
|
||
|
Date: Tue, 28 Mar 2017 17:59:31 +0200
|
||
|
Subject: [PATCH 01/13] serdev: add serdev_device_wait_until_sent
|
||
|
|
||
|
Add method, which waits until the transmission buffer has been sent.
|
||
|
Note, that the change in ttyport_write_wakeup is related, since
|
||
|
tty_wait_until_sent will hang without that change.
|
||
|
|
||
|
Acked-by: Rob Herring <robh@kernel.org>
|
||
|
Acked-by: Pavel Machek <pavel@ucw.cz>
|
||
|
Signed-off-by: Sebastian Reichel <sre@kernel.org>
|
||
|
Signed-off-by: Rob Herring <robh@kernel.org>
|
||
|
---
|
||
|
drivers/tty/serdev/core.c | 11 +++++++++++
|
||
|
drivers/tty/serdev/serdev-ttyport.c | 18 ++++++++++++++----
|
||
|
include/linux/serdev.h | 3 +++
|
||
|
3 files changed, 28 insertions(+), 4 deletions(-)
|
||
|
|
||
|
diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c
|
||
|
index f4c6c90add78..a63b74031e22 100644
|
||
|
--- a/drivers/tty/serdev/core.c
|
||
|
+++ b/drivers/tty/serdev/core.c
|
||
|
@@ -173,6 +173,17 @@ void serdev_device_set_flow_control(struct serdev_device *serdev, bool enable)
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(serdev_device_set_flow_control);
|
||
|
|
||
|
+void serdev_device_wait_until_sent(struct serdev_device *serdev, long timeout)
|
||
|
+{
|
||
|
+ struct serdev_controller *ctrl = serdev->ctrl;
|
||
|
+
|
||
|
+ if (!ctrl || !ctrl->ops->wait_until_sent)
|
||
|
+ return;
|
||
|
+
|
||
|
+ ctrl->ops->wait_until_sent(ctrl, timeout);
|
||
|
+}
|
||
|
+EXPORT_SYMBOL_GPL(serdev_device_wait_until_sent);
|
||
|
+
|
||
|
static int serdev_drv_probe(struct device *dev)
|
||
|
{
|
||
|
const struct serdev_device_driver *sdrv = to_serdev_device_driver(dev->driver);
|
||
|
diff --git a/drivers/tty/serdev/serdev-ttyport.c b/drivers/tty/serdev/serdev-ttyport.c
|
||
|
index d05393594f15..50dc75c4d204 100644
|
||
|
--- a/drivers/tty/serdev/serdev-ttyport.c
|
||
|
+++ b/drivers/tty/serdev/serdev-ttyport.c
|
||
|
@@ -14,6 +14,7 @@
|
||
|
#include <linux/serdev.h>
|
||
|
#include <linux/tty.h>
|
||
|
#include <linux/tty_driver.h>
|
||
|
+#include <linux/poll.h>
|
||
|
|
||
|
#define SERPORT_ACTIVE 1
|
||
|
|
||
|
@@ -46,11 +47,11 @@ static void ttyport_write_wakeup(struct tty_port *port)
|
||
|
struct serdev_controller *ctrl = port->client_data;
|
||
|
struct serport *serport = serdev_controller_get_drvdata(ctrl);
|
||
|
|
||
|
- if (!test_and_clear_bit(TTY_DO_WRITE_WAKEUP, &port->tty->flags))
|
||
|
- return;
|
||
|
-
|
||
|
- if (test_bit(SERPORT_ACTIVE, &serport->flags))
|
||
|
+ if (test_and_clear_bit(TTY_DO_WRITE_WAKEUP, &port->tty->flags) &&
|
||
|
+ test_bit(SERPORT_ACTIVE, &serport->flags))
|
||
|
serdev_controller_write_wakeup(ctrl);
|
||
|
+
|
||
|
+ wake_up_interruptible_poll(&port->tty->write_wait, POLLOUT);
|
||
|
}
|
||
|
|
||
|
static const struct tty_port_client_operations client_ops = {
|
||
|
@@ -167,6 +168,14 @@ static void ttyport_set_flow_control(struct serdev_controller *ctrl, bool enable
|
||
|
tty_set_termios(tty, &ktermios);
|
||
|
}
|
||
|
|
||
|
+static void ttyport_wait_until_sent(struct serdev_controller *ctrl, long timeout)
|
||
|
+{
|
||
|
+ struct serport *serport = serdev_controller_get_drvdata(ctrl);
|
||
|
+ struct tty_struct *tty = serport->tty;
|
||
|
+
|
||
|
+ tty_wait_until_sent(tty, timeout);
|
||
|
+}
|
||
|
+
|
||
|
static const struct serdev_controller_ops ctrl_ops = {
|
||
|
.write_buf = ttyport_write_buf,
|
||
|
.write_flush = ttyport_write_flush,
|
||
|
@@ -175,6 +184,7 @@ static const struct serdev_controller_ops ctrl_ops = {
|
||
|
.close = ttyport_close,
|
||
|
.set_flow_control = ttyport_set_flow_control,
|
||
|
.set_baudrate = ttyport_set_baudrate,
|
||
|
+ .wait_until_sent = ttyport_wait_until_sent,
|
||
|
};
|
||
|
|
||
|
struct device *serdev_tty_port_register(struct tty_port *port,
|
||
|
diff --git a/include/linux/serdev.h b/include/linux/serdev.h
|
||
|
index 9519da6253a8..a308b206d204 100644
|
||
|
--- a/include/linux/serdev.h
|
||
|
+++ b/include/linux/serdev.h
|
||
|
@@ -81,6 +81,7 @@ struct serdev_controller_ops {
|
||
|
void (*close)(struct serdev_controller *);
|
||
|
void (*set_flow_control)(struct serdev_controller *, bool);
|
||
|
unsigned int (*set_baudrate)(struct serdev_controller *, unsigned int);
|
||
|
+ void (*wait_until_sent)(struct serdev_controller *, long);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
@@ -186,6 +187,7 @@ int serdev_device_open(struct serdev_device *);
|
||
|
void serdev_device_close(struct serdev_device *);
|
||
|
unsigned int serdev_device_set_baudrate(struct serdev_device *, unsigned int);
|
||
|
void serdev_device_set_flow_control(struct serdev_device *, bool);
|
||
|
+void serdev_device_wait_until_sent(struct serdev_device *, long);
|
||
|
int serdev_device_write_buf(struct serdev_device *, const unsigned char *, size_t);
|
||
|
void serdev_device_write_flush(struct serdev_device *);
|
||
|
int serdev_device_write_room(struct serdev_device *);
|
||
|
@@ -223,6 +225,7 @@ static inline unsigned int serdev_device_set_baudrate(struct serdev_device *sdev
|
||
|
return 0;
|
||
|
}
|
||
|
static inline void serdev_device_set_flow_control(struct serdev_device *sdev, bool enable) {}
|
||
|
+static inline void serdev_device_wait_until_sent(struct serdev_device *sdev, long timeout) {}
|
||
|
static inline int serdev_device_write_buf(struct serdev_device *sdev, const unsigned char *buf, size_t count)
|
||
|
{
|
||
|
return -ENODEV;
|
||
|
--
|
||
|
2.12.2
|
||
|
|
||
|
From 6e1713b03eab6f42251bad76ab05e7e1aa28b199 Mon Sep 17 00:00:00 2001
|
||
|
From: Sebastian Reichel <sre@kernel.org>
|
||
|
Date: Tue, 28 Mar 2017 17:59:32 +0200
|
||
|
Subject: [PATCH 02/13] serdev: implement get/set tiocm
|
||
|
|
||
|
Add method for getting and setting tiocm.
|
||
|
|
||
|
Acked-by: Pavel Machek <pavel@ucw.cz>
|
||
|
Acked-by: Rob Herring <robh@kernel.org>
|
||
|
Signed-off-by: Sebastian Reichel <sre@kernel.org>
|
||
|
Signed-off-by: Rob Herring <robh@kernel.org>
|
||
|
---
|
||
|
drivers/tty/serdev/core.c | 22 ++++++++++++++++++++++
|
||
|
drivers/tty/serdev/serdev-ttyport.c | 24 ++++++++++++++++++++++++
|
||
|
include/linux/serdev.h | 13 +++++++++++++
|
||
|
3 files changed, 59 insertions(+)
|
||
|
|
||
|
diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c
|
||
|
index a63b74031e22..1e1cbae3a0ea 100644
|
||
|
--- a/drivers/tty/serdev/core.c
|
||
|
+++ b/drivers/tty/serdev/core.c
|
||
|
@@ -184,6 +184,28 @@ void serdev_device_wait_until_sent(struct serdev_device *serdev, long timeout)
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(serdev_device_wait_until_sent);
|
||
|
|
||
|
+int serdev_device_get_tiocm(struct serdev_device *serdev)
|
||
|
+{
|
||
|
+ struct serdev_controller *ctrl = serdev->ctrl;
|
||
|
+
|
||
|
+ if (!ctrl || !ctrl->ops->get_tiocm)
|
||
|
+ return -ENOTSUPP;
|
||
|
+
|
||
|
+ return ctrl->ops->get_tiocm(ctrl);
|
||
|
+}
|
||
|
+EXPORT_SYMBOL_GPL(serdev_device_get_tiocm);
|
||
|
+
|
||
|
+int serdev_device_set_tiocm(struct serdev_device *serdev, int set, int clear)
|
||
|
+{
|
||
|
+ struct serdev_controller *ctrl = serdev->ctrl;
|
||
|
+
|
||
|
+ if (!ctrl || !ctrl->ops->set_tiocm)
|
||
|
+ return -ENOTSUPP;
|
||
|
+
|
||
|
+ return ctrl->ops->set_tiocm(ctrl, set, clear);
|
||
|
+}
|
||
|
+EXPORT_SYMBOL_GPL(serdev_device_set_tiocm);
|
||
|
+
|
||
|
static int serdev_drv_probe(struct device *dev)
|
||
|
{
|
||
|
const struct serdev_device_driver *sdrv = to_serdev_device_driver(dev->driver);
|
||
|
diff --git a/drivers/tty/serdev/serdev-ttyport.c b/drivers/tty/serdev/serdev-ttyport.c
|
||
|
index 50dc75c4d204..487c88f6aa0e 100644
|
||
|
--- a/drivers/tty/serdev/serdev-ttyport.c
|
||
|
+++ b/drivers/tty/serdev/serdev-ttyport.c
|
||
|
@@ -176,6 +176,28 @@ static void ttyport_wait_until_sent(struct serdev_controller *ctrl, long timeout
|
||
|
tty_wait_until_sent(tty, timeout);
|
||
|
}
|
||
|
|
||
|
+static int ttyport_get_tiocm(struct serdev_controller *ctrl)
|
||
|
+{
|
||
|
+ struct serport *serport = serdev_controller_get_drvdata(ctrl);
|
||
|
+ struct tty_struct *tty = serport->tty;
|
||
|
+
|
||
|
+ if (!tty->ops->tiocmget)
|
||
|
+ return -ENOTSUPP;
|
||
|
+
|
||
|
+ return tty->driver->ops->tiocmget(tty);
|
||
|
+}
|
||
|
+
|
||
|
+static int ttyport_set_tiocm(struct serdev_controller *ctrl, unsigned int set, unsigned int clear)
|
||
|
+{
|
||
|
+ struct serport *serport = serdev_controller_get_drvdata(ctrl);
|
||
|
+ struct tty_struct *tty = serport->tty;
|
||
|
+
|
||
|
+ if (!tty->ops->tiocmset)
|
||
|
+ return -ENOTSUPP;
|
||
|
+
|
||
|
+ return tty->driver->ops->tiocmset(tty, set, clear);
|
||
|
+}
|
||
|
+
|
||
|
static const struct serdev_controller_ops ctrl_ops = {
|
||
|
.write_buf = ttyport_write_buf,
|
||
|
.write_flush = ttyport_write_flush,
|
||
|
@@ -185,6 +207,8 @@ static const struct serdev_controller_ops ctrl_ops = {
|
||
|
.set_flow_control = ttyport_set_flow_control,
|
||
|
.set_baudrate = ttyport_set_baudrate,
|
||
|
.wait_until_sent = ttyport_wait_until_sent,
|
||
|
+ .get_tiocm = ttyport_get_tiocm,
|
||
|
+ .set_tiocm = ttyport_set_tiocm,
|
||
|
};
|
||
|
|
||
|
struct device *serdev_tty_port_register(struct tty_port *port,
|
||
|
diff --git a/include/linux/serdev.h b/include/linux/serdev.h
|
||
|
index a308b206d204..e29a270f603c 100644
|
||
|
--- a/include/linux/serdev.h
|
||
|
+++ b/include/linux/serdev.h
|
||
|
@@ -15,6 +15,7 @@
|
||
|
|
||
|
#include <linux/types.h>
|
||
|
#include <linux/device.h>
|
||
|
+#include <linux/termios.h>
|
||
|
|
||
|
struct serdev_controller;
|
||
|
struct serdev_device;
|
||
|
@@ -82,6 +83,8 @@ struct serdev_controller_ops {
|
||
|
void (*set_flow_control)(struct serdev_controller *, bool);
|
||
|
unsigned int (*set_baudrate)(struct serdev_controller *, unsigned int);
|
||
|
void (*wait_until_sent)(struct serdev_controller *, long);
|
||
|
+ int (*get_tiocm)(struct serdev_controller *);
|
||
|
+ int (*set_tiocm)(struct serdev_controller *, unsigned int, unsigned int);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
@@ -188,6 +191,8 @@ void serdev_device_close(struct serdev_device *);
|
||
|
unsigned int serdev_device_set_baudrate(struct serdev_device *, unsigned int);
|
||
|
void serdev_device_set_flow_control(struct serdev_device *, bool);
|
||
|
void serdev_device_wait_until_sent(struct serdev_device *, long);
|
||
|
+int serdev_device_get_tiocm(struct serdev_device *);
|
||
|
+int serdev_device_set_tiocm(struct serdev_device *, int, int);
|
||
|
int serdev_device_write_buf(struct serdev_device *, const unsigned char *, size_t);
|
||
|
void serdev_device_write_flush(struct serdev_device *);
|
||
|
int serdev_device_write_room(struct serdev_device *);
|
||
|
@@ -226,6 +231,14 @@ static inline unsigned int serdev_device_set_baudrate(struct serdev_device *sdev
|
||
|
}
|
||
|
static inline void serdev_device_set_flow_control(struct serdev_device *sdev, bool enable) {}
|
||
|
static inline void serdev_device_wait_until_sent(struct serdev_device *sdev, long timeout) {}
|
||
|
+static inline int serdev_device_get_tiocm(struct serdev_device *serdev)
|
||
|
+{
|
||
|
+ return -ENOTSUPP;
|
||
|
+}
|
||
|
+static inline int serdev_device_set_tiocm(struct serdev_device *serdev, int set, int clear)
|
||
|
+{
|
||
|
+ return -ENOTSUPP;
|
||
|
+}
|
||
|
static inline int serdev_device_write_buf(struct serdev_device *sdev, const unsigned char *buf, size_t count)
|
||
|
{
|
||
|
return -ENODEV;
|
||
|
--
|
||
|
2.12.2
|
||
|
|
||
|
From 9425a7e94dba6d5585c542c50f253f8fbf764a81 Mon Sep 17 00:00:00 2001
|
||
|
From: Sebastian Reichel <sre@kernel.org>
|
||
|
Date: Tue, 28 Mar 2017 17:59:33 +0200
|
||
|
Subject: [PATCH 03/13] serdev: add helpers for cts and rts handling
|
||
|
|
||
|
Add serdev helper functions for handling of cts and rts
|
||
|
lines using the serdev's tiocm functions.
|
||
|
|
||
|
Acked-by: Rob Herring <robh@kernel.org>
|
||
|
Signed-off-by: Sebastian Reichel <sre@kernel.org>
|
||
|
Signed-off-by: Rob Herring <robh@kernel.org>
|
||
|
---
|
||
|
include/linux/serdev.h | 31 +++++++++++++++++++++++++++++++
|
||
|
1 file changed, 31 insertions(+)
|
||
|
|
||
|
diff --git a/include/linux/serdev.h b/include/linux/serdev.h
|
||
|
index e29a270f603c..37395b8eb8f1 100644
|
||
|
--- a/include/linux/serdev.h
|
||
|
+++ b/include/linux/serdev.h
|
||
|
@@ -16,6 +16,7 @@
|
||
|
#include <linux/types.h>
|
||
|
#include <linux/device.h>
|
||
|
#include <linux/termios.h>
|
||
|
+#include <linux/delay.h>
|
||
|
|
||
|
struct serdev_controller;
|
||
|
struct serdev_device;
|
||
|
@@ -254,6 +255,36 @@ static inline int serdev_device_write_room(struct serdev_device *sdev)
|
||
|
|
||
|
#endif /* CONFIG_SERIAL_DEV_BUS */
|
||
|
|
||
|
+static inline bool serdev_device_get_cts(struct serdev_device *serdev)
|
||
|
+{
|
||
|
+ int status = serdev_device_get_tiocm(serdev);
|
||
|
+ return !!(status & TIOCM_CTS);
|
||
|
+}
|
||
|
+
|
||
|
+static inline int serdev_device_wait_for_cts(struct serdev_device *serdev, bool state, int timeout_ms)
|
||
|
+{
|
||
|
+ unsigned long timeout;
|
||
|
+ bool signal;
|
||
|
+
|
||
|
+ timeout = jiffies + msecs_to_jiffies(timeout_ms);
|
||
|
+ while (time_is_after_jiffies(timeout)) {
|
||
|
+ signal = serdev_device_get_cts(serdev);
|
||
|
+ if (signal == state)
|
||
|
+ return 0;
|
||
|
+ usleep_range(1000, 2000);
|
||
|
+ }
|
||
|
+
|
||
|
+ return -ETIMEDOUT;
|
||
|
+}
|
||
|
+
|
||
|
+static inline int serdev_device_set_rts(struct serdev_device *serdev, bool enable)
|
||
|
+{
|
||
|
+ if (enable)
|
||
|
+ return serdev_device_set_tiocm(serdev, TIOCM_RTS, 0);
|
||
|
+ else
|
||
|
+ return serdev_device_set_tiocm(serdev, 0, TIOCM_RTS);
|
||
|
+}
|
||
|
+
|
||
|
/*
|
||
|
* serdev hooks into TTY core
|
||
|
*/
|
||
|
--
|
||
|
2.12.2
|
||
|
|
||
|
From 8656be75e893514bac2aa817f1c1b35e8dbd9e5b Mon Sep 17 00:00:00 2001
|
||
|
From: Sebastian Reichel <sre@kernel.org>
|
||
|
Date: Tue, 28 Mar 2017 17:59:34 +0200
|
||
|
Subject: [PATCH 04/13] Bluetooth: hci_uart: add support for word alignment
|
||
|
|
||
|
This will be used by Nokia's H4+ protocol, which
|
||
|
uses 2-byte aligned packets.
|
||
|
|
||
|
Acked-by: Pavel Machek <pavel@ucw.cz>
|
||
|
Signed-off-by: Sebastian Reichel <sre@kernel.org>
|
||
|
Signed-off-by: Rob Herring <robh@kernel.org>
|
||
|
---
|
||
|
drivers/bluetooth/hci_h4.c | 17 +++++++++++++++++
|
||
|
drivers/bluetooth/hci_ldisc.c | 4 ++++
|
||
|
drivers/bluetooth/hci_uart.h | 3 +++
|
||
|
3 files changed, 24 insertions(+)
|
||
|
|
||
|
diff --git a/drivers/bluetooth/hci_h4.c b/drivers/bluetooth/hci_h4.c
|
||
|
index 635597b6e168..82e5a32b87a4 100644
|
||
|
--- a/drivers/bluetooth/hci_h4.c
|
||
|
+++ b/drivers/bluetooth/hci_h4.c
|
||
|
@@ -171,9 +171,20 @@ struct sk_buff *h4_recv_buf(struct hci_dev *hdev, struct sk_buff *skb,
|
||
|
const unsigned char *buffer, int count,
|
||
|
const struct h4_recv_pkt *pkts, int pkts_count)
|
||
|
{
|
||
|
+ struct hci_uart *hu = hci_get_drvdata(hdev);
|
||
|
+ u8 alignment = hu->alignment;
|
||
|
+
|
||
|
while (count) {
|
||
|
int i, len;
|
||
|
|
||
|
+ /* remove padding bytes from buffer */
|
||
|
+ for (; hu->padding && count > 0; hu->padding--) {
|
||
|
+ count--;
|
||
|
+ buffer++;
|
||
|
+ }
|
||
|
+ if (!count)
|
||
|
+ break;
|
||
|
+
|
||
|
if (!skb) {
|
||
|
for (i = 0; i < pkts_count; i++) {
|
||
|
if (buffer[0] != (&pkts[i])->type)
|
||
|
@@ -253,11 +264,17 @@ struct sk_buff *h4_recv_buf(struct hci_dev *hdev, struct sk_buff *skb,
|
||
|
}
|
||
|
|
||
|
if (!dlen) {
|
||
|
+ hu->padding = (skb->len - 1) % alignment;
|
||
|
+ hu->padding = (alignment - hu->padding) % alignment;
|
||
|
+
|
||
|
/* No more data, complete frame */
|
||
|
(&pkts[i])->recv(hdev, skb);
|
||
|
skb = NULL;
|
||
|
}
|
||
|
} else {
|
||
|
+ hu->padding = (skb->len - 1) % alignment;
|
||
|
+ hu->padding = (alignment - hu->padding) % alignment;
|
||
|
+
|
||
|
/* Complete frame */
|
||
|
(&pkts[i])->recv(hdev, skb);
|
||
|
skb = NULL;
|
||
|
diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c
|
||
|
index 9497c469efd2..0ec8a94bd712 100644
|
||
|
--- a/drivers/bluetooth/hci_ldisc.c
|
||
|
+++ b/drivers/bluetooth/hci_ldisc.c
|
||
|
@@ -459,6 +459,10 @@ static int hci_uart_tty_open(struct tty_struct *tty)
|
||
|
hu->tty = tty;
|
||
|
tty->receive_room = 65536;
|
||
|
|
||
|
+ /* disable alignment support by default */
|
||
|
+ hu->alignment = 1;
|
||
|
+ hu->padding = 0;
|
||
|
+
|
||
|
INIT_WORK(&hu->init_ready, hci_uart_init_work);
|
||
|
INIT_WORK(&hu->write_work, hci_uart_write_work);
|
||
|
|
||
|
diff --git a/drivers/bluetooth/hci_uart.h b/drivers/bluetooth/hci_uart.h
|
||
|
index 070139513e65..4aff50960cac 100644
|
||
|
--- a/drivers/bluetooth/hci_uart.h
|
||
|
+++ b/drivers/bluetooth/hci_uart.h
|
||
|
@@ -92,6 +92,9 @@ struct hci_uart {
|
||
|
|
||
|
unsigned int init_speed;
|
||
|
unsigned int oper_speed;
|
||
|
+
|
||
|
+ u8 alignment;
|
||
|
+ u8 padding;
|
||
|
};
|
||
|
|
||
|
/* HCI_UART proto flag bits */
|
||
|
--
|
||
|
2.12.2
|
||
|
|
||
|
From f2d830b35ce8c834eaa895ff29c461455024f05e Mon Sep 17 00:00:00 2001
|
||
|
From: Rob Herring <robh@kernel.org>
|
||
|
Date: Tue, 28 Mar 2017 17:59:35 +0200
|
||
|
Subject: [PATCH 05/13] Bluetooth: hci_uart: add serdev driver support library
|
||
|
|
||
|
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 <robh@kernel.org>
|
||
|
Cc: Marcel Holtmann <marcel@holtmann.org>
|
||
|
Cc: Gustavo Padovan <gustavo@padovan.org>
|
||
|
Cc: Johan Hedberg <johan.hedberg@gmail.com>
|
||
|
Cc: linux-bluetooth@vger.kernel.org
|
||
|
Acked-by: Pavel Machek <pavel@ucw.cz>
|
||
|
[Fix style issues reported by Pavel]
|
||
|
Signed-off-by: Sebastian Reichel <sre@kernel.org>
|
||
|
Signed-off-by: Rob Herring <robh@kernel.org>
|
||
|
---
|
||
|
drivers/bluetooth/Makefile | 1 +
|
||
|
drivers/bluetooth/hci_serdev.c | 361 +++++++++++++++++++++++++++++++++++++++++
|
||
|
drivers/bluetooth/hci_uart.h | 4 +
|
||
|
3 files changed, 366 insertions(+)
|
||
|
create mode 100644 drivers/bluetooth/hci_serdev.c
|
||
|
|
||
|
diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
|
||
|
index 80627187c8b6..fd571689eed6 100644
|
||
|
--- a/drivers/bluetooth/Makefile
|
||
|
+++ b/drivers/bluetooth/Makefile
|
||
|
@@ -29,6 +29,7 @@ btmrvl-y := btmrvl_main.o
|
||
|
btmrvl-$(CONFIG_DEBUG_FS) += btmrvl_debugfs.o
|
||
|
|
||
|
hci_uart-y := hci_ldisc.o
|
||
|
+hci_uart-$(CONFIG_SERIAL_DEV_BUS) += 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..f5ccb2c7ef92
|
||
|
--- /dev/null
|
||
|
+++ b/drivers/bluetooth/hci_serdev.c
|
||
|
@@ -0,0 +1,361 @@
|
||
|
+/*
|
||
|
+ * Bluetooth HCI serdev driver lib
|
||
|
+ *
|
||
|
+ * Copyright (C) 2017 Linaro, Ltd., Rob Herring <robh@kernel.org>
|
||
|
+ *
|
||
|
+ * Based on hci_ldisc.c:
|
||
|
+ *
|
||
|
+ * Copyright (C) 2000-2001 Qualcomm Incorporated
|
||
|
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
|
||
|
+ * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org>
|
||
|
+ *
|
||
|
+ * 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/kernel.h>
|
||
|
+#include <linux/types.h>
|
||
|
+#include <linux/serdev.h>
|
||
|
+#include <linux/skbuff.h>
|
||
|
+
|
||
|
+#include <net/bluetooth/bluetooth.h>
|
||
|
+#include <net/bluetooth/hci_core.h>
|
||
|
+
|
||
|
+#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 an error value?
|
||
|
+ */
|
||
|
+ do {
|
||
|
+ 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);
|
||
|
+ }
|
||
|
+ } while(test_bit(HCI_UART_TX_WAKEUP, &hu->tx_state));
|
||
|
+
|
||
|
+ 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)
|
||
|
+ BT_ERR("%s: failed to set baudrate", hdev->name);
|
||
|
+ else
|
||
|
+ 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);
|
||
|
+ }
|
||
|
+
|
||
|
+ kfree_skb(skb);
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+/** hci_uart_write_wakeup - transmit buffer wakeup
|
||
|
+ * @serdev: serial device
|
||
|
+ *
|
||
|
+ * This function is called by the serdev framework when it accepts
|
||
|
+ * more data being sent.
|
||
|
+ */
|
||
|
+static void hci_uart_write_wakeup(struct serdev_device *serdev)
|
||
|
+{
|
||
|
+ struct hci_uart *hu = serdev_device_get_drvdata(serdev);
|
||
|
+
|
||
|
+ BT_DBG("");
|
||
|
+
|
||
|
+ if (!hu || serdev != hu->serdev) {
|
||
|
+ WARN_ON(1);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (test_bit(HCI_UART_PROTO_READY, &hu->flags))
|
||
|
+ hci_uart_tx_wakeup(hu);
|
||
|
+}
|
||
|
+
|
||
|
+/** hci_uart_receive_buf - receive buffer wakeup
|
||
|
+ * @serdev: serial device
|
||
|
+ * @data: pointer to received data
|
||
|
+ * @count: count of received data in bytes
|
||
|
+ *
|
||
|
+ * This function is called by the serdev framework when it received data
|
||
|
+ * in the RX buffer.
|
||
|
+ *
|
||
|
+ * Return: number of processed bytes
|
||
|
+ */
|
||
|
+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) {
|
||
|
+ WARN_ON(1);
|
||
|
+ 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 4aff50960cac..1b41c661bbb8 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;
|
||
|
@@ -108,6 +110,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_init_tty(struct hci_uart *hu);
|
||
|
--
|
||
|
2.12.2
|
||
|
|
||
|
From c76b6c5106713b00e183ccb757bc15732d369a33 Mon Sep 17 00:00:00 2001
|
||
|
From: Sebastian Reichel <sre@kernel.org>
|
||
|
Date: Tue, 28 Mar 2017 17:59:36 +0200
|
||
|
Subject: [PATCH 06/13] Bluetooth: hci_serdev: do not open device in hci open
|
||
|
|
||
|
The device driver may need to communicate with the UART
|
||
|
device while the Bluetooth device is closed (e.g. due
|
||
|
to interrupts).
|
||
|
|
||
|
Acked-by: Pavel Machek <pavel@ucw.cz>
|
||
|
Signed-off-by: Sebastian Reichel <sre@kernel.org>
|
||
|
Signed-off-by: Rob Herring <robh@kernel.org>
|
||
|
---
|
||
|
drivers/bluetooth/hci_serdev.c | 12 +++---------
|
||
|
1 file changed, 3 insertions(+), 9 deletions(-)
|
||
|
|
||
|
diff --git a/drivers/bluetooth/hci_serdev.c b/drivers/bluetooth/hci_serdev.c
|
||
|
index f5ccb2c7ef92..3b8ac0ece3fb 100644
|
||
|
--- a/drivers/bluetooth/hci_serdev.c
|
||
|
+++ b/drivers/bluetooth/hci_serdev.c
|
||
|
@@ -104,13 +104,9 @@ static void hci_uart_write_work(struct work_struct *work)
|
||
|
/* 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);
|
||
|
+ return 0;
|
||
|
}
|
||
|
|
||
|
/* Reset device */
|
||
|
@@ -136,15 +132,11 @@ static int hci_uart_flush(struct hci_dev *hdev)
|
||
|
/* 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;
|
||
|
}
|
||
|
|
||
|
@@ -289,6 +281,8 @@ int hci_uart_register_device(struct hci_uart *hu,
|
||
|
|
||
|
BT_DBG("");
|
||
|
|
||
|
+ serdev_device_set_client_ops(hu->serdev, &hci_serdev_client_ops);
|
||
|
+
|
||
|
err = p->open(hu);
|
||
|
if (err)
|
||
|
return err;
|
||
|
--
|
||
|
2.12.2
|
||
|
|
||
|
From 7fe8800d90c5e0f614e72c475687a68a76592241 Mon Sep 17 00:00:00 2001
|
||
|
From: Sebastian Reichel <sre@kernel.org>
|
||
|
Date: Tue, 28 Mar 2017 17:59:37 +0200
|
||
|
Subject: [PATCH 07/13] Bluetooth: hci_serdev: allow modular drivers
|
||
|
|
||
|
For bluetooth protocol driver only supporting serdev it makes
|
||
|
sense to follow common practice and built them into their own
|
||
|
module.
|
||
|
|
||
|
Such modules need access to hci_uart_register_device and
|
||
|
hci_uart_tx_wakeup for using the common protocol helpers.
|
||
|
|
||
|
Signed-off-by: Sebastian Reichel <sre@kernel.org>
|
||
|
Signed-off-by: Rob Herring <robh@kernel.org>
|
||
|
---
|
||
|
drivers/bluetooth/hci_ldisc.c | 1 +
|
||
|
drivers/bluetooth/hci_serdev.c | 1 +
|
||
|
2 files changed, 2 insertions(+)
|
||
|
|
||
|
diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c
|
||
|
index 0ec8a94bd712..17bcbc13623f 100644
|
||
|
--- a/drivers/bluetooth/hci_ldisc.c
|
||
|
+++ b/drivers/bluetooth/hci_ldisc.c
|
||
|
@@ -134,6 +134,7 @@ int hci_uart_tx_wakeup(struct hci_uart *hu)
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
+EXPORT_SYMBOL_GPL(hci_uart_tx_wakeup);
|
||
|
|
||
|
static void hci_uart_write_work(struct work_struct *work)
|
||
|
{
|
||
|
diff --git a/drivers/bluetooth/hci_serdev.c b/drivers/bluetooth/hci_serdev.c
|
||
|
index 3b8ac0ece3fb..7de0edc0ff8c 100644
|
||
|
--- a/drivers/bluetooth/hci_serdev.c
|
||
|
+++ b/drivers/bluetooth/hci_serdev.c
|
||
|
@@ -353,3 +353,4 @@ int hci_uart_register_device(struct hci_uart *hu,
|
||
|
p->close(hu);
|
||
|
return err;
|
||
|
}
|
||
|
+EXPORT_SYMBOL_GPL(hci_uart_register_device);
|
||
|
--
|
||
|
2.12.2
|
||
|
|
||
|
From 68bfebdd86741d45508148c6fa13b5bd21116c7e Mon Sep 17 00:00:00 2001
|
||
|
From: Sebastian Reichel <sre@kernel.org>
|
||
|
Date: Tue, 28 Mar 2017 17:59:38 +0200
|
||
|
Subject: [PATCH 08/13] dt-bindings: net: bluetooth: Add nokia-bluetooth
|
||
|
|
||
|
Add binding document for serial bluetooth chips using
|
||
|
Nokia H4+ protocol.
|
||
|
|
||
|
Acked-by: Rob Herring <robh@kernel.org>
|
||
|
Signed-off-by: Sebastian Reichel <sre@kernel.org>
|
||
|
|
||
|
Changes since PATCHv1:
|
||
|
* change compatible strings
|
||
|
* mention active high/low state for GPIOs
|
||
|
Signed-off-by: Rob Herring <robh@kernel.org>
|
||
|
---
|
||
|
.../devicetree/bindings/net/nokia-bluetooth.txt | 51 ++++++++++++++++++++++
|
||
|
1 file changed, 51 insertions(+)
|
||
|
create mode 100644 Documentation/devicetree/bindings/net/nokia-bluetooth.txt
|
||
|
|
||
|
diff --git a/Documentation/devicetree/bindings/net/nokia-bluetooth.txt b/Documentation/devicetree/bindings/net/nokia-bluetooth.txt
|
||
|
new file mode 100644
|
||
|
index 000000000000..42be7dc9a70b
|
||
|
--- /dev/null
|
||
|
+++ b/Documentation/devicetree/bindings/net/nokia-bluetooth.txt
|
||
|
@@ -0,0 +1,51 @@
|
||
|
+Nokia Bluetooth Chips
|
||
|
+---------------------
|
||
|
+
|
||
|
+Nokia phones often come with UART connected bluetooth chips from different
|
||
|
+vendors and modified device API. Those devices speak a protocol named H4+
|
||
|
+(also known as h4p) by Nokia, which is similar to the H4 protocol from the
|
||
|
+Bluetooth standard. In addition to the H4 protocol it specifies two more
|
||
|
+UART status lines for wakeup of UART transceivers to improve power management
|
||
|
+and a few new packet types used to negotiate uart speed.
|
||
|
+
|
||
|
+Required properties:
|
||
|
+
|
||
|
+ - compatible: should contain "nokia,h4p-bluetooth" as well as one of the following:
|
||
|
+ * "brcm,bcm2048-nokia"
|
||
|
+ * "ti,wl1271-bluetooth-nokia"
|
||
|
+ - reset-gpios: GPIO specifier, used to reset the BT module (active low)
|
||
|
+ - bluetooth-wakeup-gpios: GPIO specifier, used to wakeup the BT module (active high)
|
||
|
+ - host-wakeup-gpios: GPIO specifier, used to wakeup the host processor (active high)
|
||
|
+ - clock-names: should be "sysclk"
|
||
|
+ - clocks: should contain a clock specifier for every name in clock-names
|
||
|
+
|
||
|
+Optional properties:
|
||
|
+
|
||
|
+ - None
|
||
|
+
|
||
|
+Example:
|
||
|
+
|
||
|
+/ {
|
||
|
+ /* controlled (enabled/disabled) directly by BT module */
|
||
|
+ bluetooth_clk: vctcxo {
|
||
|
+ compatible = "fixed-clock";
|
||
|
+ #clock-cells = <0>;
|
||
|
+ clock-frequency = <38400000>;
|
||
|
+ };
|
||
|
+};
|
||
|
+
|
||
|
+&uart2 {
|
||
|
+ pinctrl-names = "default";
|
||
|
+ pinctrl-0 = <&uart2_pins>;
|
||
|
+
|
||
|
+ bluetooth {
|
||
|
+ compatible = "ti,wl1271-bluetooth-nokia", "nokia,h4p-bluetooth";
|
||
|
+
|
||
|
+ reset-gpios = <&gpio1 26 GPIO_ACTIVE_LOW>; /* gpio26 */
|
||
|
+ host-wakeup-gpios = <&gpio4 5 GPIO_ACTIVE_HIGH>; /* gpio101 */
|
||
|
+ bluetooth-wakeup-gpios = <&gpio2 5 GPIO_ACTIVE_HIGH>; /* gpio37 */
|
||
|
+
|
||
|
+ clocks = <&bluetooth_clk>;
|
||
|
+ clock-names = "sysclk";
|
||
|
+ };
|
||
|
+};
|
||
|
--
|
||
|
2.12.2
|
||
|
|
||
|
From 90753299ed56ee7c5807c09b2185094a91a2cbbe Mon Sep 17 00:00:00 2001
|
||
|
From: Sebastian Reichel <sre@kernel.org>
|
||
|
Date: Tue, 28 Mar 2017 17:59:39 +0200
|
||
|
Subject: [PATCH 09/13] Bluetooth: add nokia driver
|
||
|
|
||
|
This adds a driver for the Nokia H4+ protocol, which is used
|
||
|
at least on the Nokia N9, N900 & N950.
|
||
|
|
||
|
Signed-off-by: Sebastian Reichel <sre@kernel.org>
|
||
|
Signed-off-by: Rob Herring <robh@kernel.org>
|
||
|
---
|
||
|
drivers/bluetooth/Kconfig | 12 +
|
||
|
drivers/bluetooth/Makefile | 2 +
|
||
|
drivers/bluetooth/hci_nokia.c | 819 ++++++++++++++++++++++++++++++++++++++++++
|
||
|
3 files changed, 833 insertions(+)
|
||
|
create mode 100644 drivers/bluetooth/hci_nokia.c
|
||
|
|
||
|
diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
|
||
|
index 08e054507d0b..479e2eacd1aa 100644
|
||
|
--- a/drivers/bluetooth/Kconfig
|
||
|
+++ b/drivers/bluetooth/Kconfig
|
||
|
@@ -86,6 +86,18 @@ config BT_HCIUART_H4
|
||
|
|
||
|
Say Y here to compile support for HCI UART (H4) protocol.
|
||
|
|
||
|
+config BT_HCIUART_NOKIA
|
||
|
+ tristate "UART Nokia H4+ protocol support"
|
||
|
+ depends on BT_HCIUART
|
||
|
+ depends on SERIAL_DEV_BUS
|
||
|
+ depends on PM
|
||
|
+ help
|
||
|
+ Nokia H4+ is serial protocol for communication between Bluetooth
|
||
|
+ device and host. This protocol is required for Bluetooth devices
|
||
|
+ with UART interface in Nokia devices.
|
||
|
+
|
||
|
+ Say Y here to compile support for Nokia's H4+ protocol.
|
||
|
+
|
||
|
config BT_HCIUART_BCSP
|
||
|
bool "BCSP protocol support"
|
||
|
depends on BT_HCIUART
|
||
|
diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
|
||
|
index fd571689eed6..a7f237320f4b 100644
|
||
|
--- a/drivers/bluetooth/Makefile
|
||
|
+++ b/drivers/bluetooth/Makefile
|
||
|
@@ -25,6 +25,8 @@ obj-$(CONFIG_BT_BCM) += btbcm.o
|
||
|
obj-$(CONFIG_BT_RTL) += btrtl.o
|
||
|
obj-$(CONFIG_BT_QCA) += btqca.o
|
||
|
|
||
|
+obj-$(CONFIG_BT_HCIUART_NOKIA) += hci_nokia.o
|
||
|
+
|
||
|
btmrvl-y := btmrvl_main.o
|
||
|
btmrvl-$(CONFIG_DEBUG_FS) += btmrvl_debugfs.o
|
||
|
|
||
|
diff --git a/drivers/bluetooth/hci_nokia.c b/drivers/bluetooth/hci_nokia.c
|
||
|
new file mode 100644
|
||
|
index 000000000000..c77f04af01bf
|
||
|
--- /dev/null
|
||
|
+++ b/drivers/bluetooth/hci_nokia.c
|
||
|
@@ -0,0 +1,819 @@
|
||
|
+/*
|
||
|
+ * Bluetooth HCI UART H4 driver with Nokia Extensions AKA Nokia H4+
|
||
|
+ *
|
||
|
+ * Copyright (C) 2015 Marcel Holtmann <marcel@holtmann.org>
|
||
|
+ * Copyright (C) 2015-2017 Sebastian Reichel <sre@kernel.org>
|
||
|
+ *
|
||
|
+ * 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/clk.h>
|
||
|
+#include <linux/kernel.h>
|
||
|
+#include <linux/types.h>
|
||
|
+#include <linux/interrupt.h>
|
||
|
+#include <linux/pm_runtime.h>
|
||
|
+#include <linux/firmware.h>
|
||
|
+#include <linux/slab.h>
|
||
|
+#include <linux/string.h>
|
||
|
+#include <linux/errno.h>
|
||
|
+#include <linux/skbuff.h>
|
||
|
+#include <linux/gpio/consumer.h>
|
||
|
+#include <linux/unaligned/le_struct.h>
|
||
|
+#include <net/bluetooth/bluetooth.h>
|
||
|
+#include <net/bluetooth/hci_core.h>
|
||
|
+#include <linux/serdev.h>
|
||
|
+
|
||
|
+#include "hci_uart.h"
|
||
|
+#include "btbcm.h"
|
||
|
+
|
||
|
+#define NOKIA_ID_BCM2048 0x04
|
||
|
+#define NOKIA_ID_TI1271 0x31
|
||
|
+
|
||
|
+#define FIRMWARE_BCM2048 "nokia/bcmfw.bin"
|
||
|
+#define FIRMWARE_TI1271 "nokia/ti1273.bin"
|
||
|
+
|
||
|
+#define HCI_NOKIA_NEG_PKT 0x06
|
||
|
+#define HCI_NOKIA_ALIVE_PKT 0x07
|
||
|
+#define HCI_NOKIA_RADIO_PKT 0x08
|
||
|
+
|
||
|
+#define HCI_NOKIA_NEG_HDR_SIZE 1
|
||
|
+#define HCI_NOKIA_MAX_NEG_SIZE 255
|
||
|
+#define HCI_NOKIA_ALIVE_HDR_SIZE 1
|
||
|
+#define HCI_NOKIA_MAX_ALIVE_SIZE 255
|
||
|
+#define HCI_NOKIA_RADIO_HDR_SIZE 2
|
||
|
+#define HCI_NOKIA_MAX_RADIO_SIZE 255
|
||
|
+
|
||
|
+#define NOKIA_PROTO_PKT 0x44
|
||
|
+#define NOKIA_PROTO_BYTE 0x4c
|
||
|
+
|
||
|
+#define NOKIA_NEG_REQ 0x00
|
||
|
+#define NOKIA_NEG_ACK 0x20
|
||
|
+#define NOKIA_NEG_NAK 0x40
|
||
|
+
|
||
|
+#define H4_TYPE_SIZE 1
|
||
|
+
|
||
|
+#define NOKIA_RECV_ALIVE \
|
||
|
+ .type = HCI_NOKIA_ALIVE_PKT, \
|
||
|
+ .hlen = HCI_NOKIA_ALIVE_HDR_SIZE, \
|
||
|
+ .loff = 0, \
|
||
|
+ .lsize = 1, \
|
||
|
+ .maxlen = HCI_NOKIA_MAX_ALIVE_SIZE \
|
||
|
+
|
||
|
+#define NOKIA_RECV_NEG \
|
||
|
+ .type = HCI_NOKIA_NEG_PKT, \
|
||
|
+ .hlen = HCI_NOKIA_NEG_HDR_SIZE, \
|
||
|
+ .loff = 0, \
|
||
|
+ .lsize = 1, \
|
||
|
+ .maxlen = HCI_NOKIA_MAX_NEG_SIZE \
|
||
|
+
|
||
|
+#define NOKIA_RECV_RADIO \
|
||
|
+ .type = HCI_NOKIA_RADIO_PKT, \
|
||
|
+ .hlen = HCI_NOKIA_RADIO_HDR_SIZE, \
|
||
|
+ .loff = 1, \
|
||
|
+ .lsize = 1, \
|
||
|
+ .maxlen = HCI_NOKIA_MAX_RADIO_SIZE \
|
||
|
+
|
||
|
+struct hci_nokia_neg_hdr {
|
||
|
+ u8 dlen;
|
||
|
+} __packed;
|
||
|
+
|
||
|
+struct hci_nokia_neg_cmd {
|
||
|
+ u8 ack;
|
||
|
+ u16 baud;
|
||
|
+ u16 unused1;
|
||
|
+ u8 proto;
|
||
|
+ u16 sys_clk;
|
||
|
+ u16 unused2;
|
||
|
+} __packed;
|
||
|
+
|
||
|
+#define NOKIA_ALIVE_REQ 0x55
|
||
|
+#define NOKIA_ALIVE_RESP 0xcc
|
||
|
+
|
||
|
+struct hci_nokia_alive_hdr {
|
||
|
+ u8 dlen;
|
||
|
+} __packed;
|
||
|
+
|
||
|
+struct hci_nokia_alive_pkt {
|
||
|
+ u8 mid;
|
||
|
+ u8 unused;
|
||
|
+} __packed;
|
||
|
+
|
||
|
+struct hci_nokia_neg_evt {
|
||
|
+ u8 ack;
|
||
|
+ u16 baud;
|
||
|
+ u16 unused1;
|
||
|
+ u8 proto;
|
||
|
+ u16 sys_clk;
|
||
|
+ u16 unused2;
|
||
|
+ u8 man_id;
|
||
|
+ u8 ver_id;
|
||
|
+} __packed;
|
||
|
+
|
||
|
+#define MAX_BAUD_RATE 3692300
|
||
|
+#define SETUP_BAUD_RATE 921600
|
||
|
+#define INIT_BAUD_RATE 120000
|
||
|
+
|
||
|
+struct hci_nokia_radio_hdr {
|
||
|
+ u8 evt;
|
||
|
+ u8 dlen;
|
||
|
+} __packed;
|
||
|
+
|
||
|
+struct nokia_bt_dev {
|
||
|
+ struct hci_uart hu;
|
||
|
+ struct serdev_device *serdev;
|
||
|
+
|
||
|
+ struct gpio_desc *reset;
|
||
|
+ struct gpio_desc *wakeup_host;
|
||
|
+ struct gpio_desc *wakeup_bt;
|
||
|
+ unsigned long sysclk_speed;
|
||
|
+
|
||
|
+ int wake_irq;
|
||
|
+ struct sk_buff *rx_skb;
|
||
|
+ struct sk_buff_head txq;
|
||
|
+ bdaddr_t bdaddr;
|
||
|
+
|
||
|
+ int init_error;
|
||
|
+ struct completion init_completion;
|
||
|
+
|
||
|
+ u8 man_id;
|
||
|
+ u8 ver_id;
|
||
|
+
|
||
|
+ bool initialized;
|
||
|
+ bool tx_enabled;
|
||
|
+ bool rx_enabled;
|
||
|
+};
|
||
|
+
|
||
|
+static int nokia_enqueue(struct hci_uart *hu, struct sk_buff *skb);
|
||
|
+
|
||
|
+static void nokia_flow_control(struct serdev_device *serdev, bool enable)
|
||
|
+{
|
||
|
+ if (enable) {
|
||
|
+ serdev_device_set_rts(serdev, true);
|
||
|
+ serdev_device_set_flow_control(serdev, true);
|
||
|
+ } else {
|
||
|
+ serdev_device_set_flow_control(serdev, false);
|
||
|
+ serdev_device_set_rts(serdev, false);
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static irqreturn_t wakeup_handler(int irq, void *data)
|
||
|
+{
|
||
|
+ struct nokia_bt_dev *btdev = data;
|
||
|
+ struct device *dev = &btdev->serdev->dev;
|
||
|
+ int wake_state = gpiod_get_value(btdev->wakeup_host);
|
||
|
+
|
||
|
+ if (btdev->rx_enabled == wake_state)
|
||
|
+ return IRQ_HANDLED;
|
||
|
+
|
||
|
+ if (wake_state)
|
||
|
+ pm_runtime_get(dev);
|
||
|
+ else
|
||
|
+ pm_runtime_put(dev);
|
||
|
+
|
||
|
+ btdev->rx_enabled = wake_state;
|
||
|
+
|
||
|
+ return IRQ_HANDLED;
|
||
|
+}
|
||
|
+
|
||
|
+static int nokia_reset(struct hci_uart *hu)
|
||
|
+{
|
||
|
+ struct nokia_bt_dev *btdev = hu->priv;
|
||
|
+ struct device *dev = &btdev->serdev->dev;
|
||
|
+ int err;
|
||
|
+
|
||
|
+ /* reset routine */
|
||
|
+ gpiod_set_value_cansleep(btdev->reset, 1);
|
||
|
+ gpiod_set_value_cansleep(btdev->wakeup_bt, 1);
|
||
|
+
|
||
|
+ msleep(100);
|
||
|
+
|
||
|
+ /* safety check */
|
||
|
+ err = gpiod_get_value_cansleep(btdev->wakeup_host);
|
||
|
+ if (err == 1) {
|
||
|
+ dev_err(dev, "reset: host wakeup not low!");
|
||
|
+ return -EPROTO;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* flush queue */
|
||
|
+ serdev_device_write_flush(btdev->serdev);
|
||
|
+
|
||
|
+ /* init uart */
|
||
|
+ nokia_flow_control(btdev->serdev, false);
|
||
|
+ serdev_device_set_baudrate(btdev->serdev, INIT_BAUD_RATE);
|
||
|
+
|
||
|
+ gpiod_set_value_cansleep(btdev->reset, 0);
|
||
|
+
|
||
|
+ /* wait for cts */
|
||
|
+ err = serdev_device_wait_for_cts(btdev->serdev, true, 200);
|
||
|
+ if (err < 0) {
|
||
|
+ dev_err(dev, "CTS not received: %d", err);
|
||
|
+ return err;
|
||
|
+ }
|
||
|
+
|
||
|
+ nokia_flow_control(btdev->serdev, true);
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int nokia_send_alive_packet(struct hci_uart *hu)
|
||
|
+{
|
||
|
+ struct nokia_bt_dev *btdev = hu->priv;
|
||
|
+ struct device *dev = &btdev->serdev->dev;
|
||
|
+ struct hci_nokia_alive_hdr *hdr;
|
||
|
+ struct hci_nokia_alive_pkt *pkt;
|
||
|
+ struct sk_buff *skb;
|
||
|
+ int len;
|
||
|
+
|
||
|
+ init_completion(&btdev->init_completion);
|
||
|
+
|
||
|
+ len = H4_TYPE_SIZE + sizeof(*hdr) + sizeof(*pkt);
|
||
|
+ skb = bt_skb_alloc(len, GFP_KERNEL);
|
||
|
+ if (!skb)
|
||
|
+ return -ENOMEM;
|
||
|
+
|
||
|
+ hci_skb_pkt_type(skb) = HCI_NOKIA_ALIVE_PKT;
|
||
|
+ memset(skb->data, 0x00, len);
|
||
|
+
|
||
|
+ hdr = (struct hci_nokia_alive_hdr *)skb_put(skb, sizeof(*hdr));
|
||
|
+ hdr->dlen = sizeof(*pkt);
|
||
|
+ pkt = (struct hci_nokia_alive_pkt *)skb_put(skb, sizeof(*pkt));
|
||
|
+ pkt->mid = NOKIA_ALIVE_REQ;
|
||
|
+
|
||
|
+ nokia_enqueue(hu, skb);
|
||
|
+ hci_uart_tx_wakeup(hu);
|
||
|
+
|
||
|
+ dev_dbg(dev, "Alive sent");
|
||
|
+
|
||
|
+ if (!wait_for_completion_interruptible_timeout(&btdev->init_completion,
|
||
|
+ msecs_to_jiffies(1000))) {
|
||
|
+ return -ETIMEDOUT;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (btdev->init_error < 0)
|
||
|
+ return btdev->init_error;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int nokia_send_negotiation(struct hci_uart *hu)
|
||
|
+{
|
||
|
+ struct nokia_bt_dev *btdev = hu->priv;
|
||
|
+ struct device *dev = &btdev->serdev->dev;
|
||
|
+ struct hci_nokia_neg_cmd *neg_cmd;
|
||
|
+ struct hci_nokia_neg_hdr *neg_hdr;
|
||
|
+ struct sk_buff *skb;
|
||
|
+ int len, err;
|
||
|
+ u16 baud = DIV_ROUND_CLOSEST(btdev->sysclk_speed * 10, SETUP_BAUD_RATE);
|
||
|
+ int sysclk = btdev->sysclk_speed / 1000;
|
||
|
+
|
||
|
+ len = H4_TYPE_SIZE + sizeof(*neg_hdr) + sizeof(*neg_cmd);
|
||
|
+ skb = bt_skb_alloc(len, GFP_KERNEL);
|
||
|
+ if (!skb)
|
||
|
+ return -ENOMEM;
|
||
|
+
|
||
|
+ hci_skb_pkt_type(skb) = HCI_NOKIA_NEG_PKT;
|
||
|
+
|
||
|
+ neg_hdr = (struct hci_nokia_neg_hdr *)skb_put(skb, sizeof(*neg_hdr));
|
||
|
+ neg_hdr->dlen = sizeof(*neg_cmd);
|
||
|
+
|
||
|
+ neg_cmd = (struct hci_nokia_neg_cmd *)skb_put(skb, sizeof(*neg_cmd));
|
||
|
+ neg_cmd->ack = NOKIA_NEG_REQ;
|
||
|
+ neg_cmd->baud = cpu_to_le16(baud);
|
||
|
+ neg_cmd->unused1 = 0x0000;
|
||
|
+ neg_cmd->proto = NOKIA_PROTO_BYTE;
|
||
|
+ neg_cmd->sys_clk = cpu_to_le16(sysclk);
|
||
|
+ neg_cmd->unused2 = 0x0000;
|
||
|
+
|
||
|
+ btdev->init_error = 0;
|
||
|
+ init_completion(&btdev->init_completion);
|
||
|
+
|
||
|
+ nokia_enqueue(hu, skb);
|
||
|
+ hci_uart_tx_wakeup(hu);
|
||
|
+
|
||
|
+ dev_dbg(dev, "Negotiation sent");
|
||
|
+
|
||
|
+ if (!wait_for_completion_interruptible_timeout(&btdev->init_completion,
|
||
|
+ msecs_to_jiffies(10000))) {
|
||
|
+ return -ETIMEDOUT;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (btdev->init_error < 0)
|
||
|
+ return btdev->init_error;
|
||
|
+
|
||
|
+ /* Change to previously negotiated speed. Flow Control
|
||
|
+ * is disabled until bluetooth adapter is ready to avoid
|
||
|
+ * broken bytes being received.
|
||
|
+ */
|
||
|
+ nokia_flow_control(btdev->serdev, false);
|
||
|
+ serdev_device_set_baudrate(btdev->serdev, SETUP_BAUD_RATE);
|
||
|
+ err = serdev_device_wait_for_cts(btdev->serdev, true, 200);
|
||
|
+ if (err < 0) {
|
||
|
+ dev_err(dev, "CTS not received: %d", err);
|
||
|
+ return err;
|
||
|
+ }
|
||
|
+ nokia_flow_control(btdev->serdev, true);
|
||
|
+
|
||
|
+ dev_dbg(dev, "Negotiation successful");
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int nokia_setup_fw(struct hci_uart *hu)
|
||
|
+{
|
||
|
+ struct nokia_bt_dev *btdev = hu->priv;
|
||
|
+ struct device *dev = &btdev->serdev->dev;
|
||
|
+ const char *fwname;
|
||
|
+ const struct firmware *fw;
|
||
|
+ const u8 *fw_ptr;
|
||
|
+ size_t fw_size;
|
||
|
+ int err;
|
||
|
+
|
||
|
+ dev_dbg(dev, "setup firmware");
|
||
|
+
|
||
|
+ if (btdev->man_id == NOKIA_ID_BCM2048) {
|
||
|
+ fwname = FIRMWARE_BCM2048;
|
||
|
+ } else if (btdev->man_id == NOKIA_ID_TI1271) {
|
||
|
+ fwname = FIRMWARE_TI1271;
|
||
|
+ } else {
|
||
|
+ dev_err(dev, "Unsupported bluetooth device!");
|
||
|
+ return -ENODEV;
|
||
|
+ }
|
||
|
+
|
||
|
+ err = request_firmware(&fw, fwname, dev);
|
||
|
+ if (err < 0) {
|
||
|
+ dev_err(dev, "%s: Failed to load Nokia firmware file (%d)",
|
||
|
+ hu->hdev->name, err);
|
||
|
+ return err;
|
||
|
+ }
|
||
|
+
|
||
|
+ fw_ptr = fw->data;
|
||
|
+ fw_size = fw->size;
|
||
|
+
|
||
|
+ while (fw_size >= 4) {
|
||
|
+ u16 pkt_size = get_unaligned_le16(fw_ptr);
|
||
|
+ u8 pkt_type = fw_ptr[2];
|
||
|
+ const struct hci_command_hdr *cmd;
|
||
|
+ u16 opcode;
|
||
|
+ struct sk_buff *skb;
|
||
|
+
|
||
|
+ switch (pkt_type) {
|
||
|
+ case HCI_COMMAND_PKT:
|
||
|
+ cmd = (struct hci_command_hdr *)(fw_ptr + 3);
|
||
|
+ opcode = le16_to_cpu(cmd->opcode);
|
||
|
+
|
||
|
+ skb = __hci_cmd_sync(hu->hdev, opcode, cmd->plen,
|
||
|
+ fw_ptr + 3 + HCI_COMMAND_HDR_SIZE,
|
||
|
+ HCI_INIT_TIMEOUT);
|
||
|
+ if (IS_ERR(skb)) {
|
||
|
+ err = PTR_ERR(skb);
|
||
|
+ dev_err(dev, "%s: FW command %04x failed (%d)",
|
||
|
+ hu->hdev->name, opcode, err);
|
||
|
+ goto done;
|
||
|
+ }
|
||
|
+ kfree_skb(skb);
|
||
|
+ break;
|
||
|
+ case HCI_NOKIA_RADIO_PKT:
|
||
|
+ case HCI_NOKIA_NEG_PKT:
|
||
|
+ case HCI_NOKIA_ALIVE_PKT:
|
||
|
+ break;
|
||
|
+ }
|
||
|
+
|
||
|
+ fw_ptr += pkt_size + 2;
|
||
|
+ fw_size -= pkt_size + 2;
|
||
|
+ }
|
||
|
+
|
||
|
+done:
|
||
|
+ release_firmware(fw);
|
||
|
+ return err;
|
||
|
+}
|
||
|
+
|
||
|
+static int nokia_setup(struct hci_uart *hu)
|
||
|
+{
|
||
|
+ struct nokia_bt_dev *btdev = hu->priv;
|
||
|
+ struct device *dev = &btdev->serdev->dev;
|
||
|
+ int err;
|
||
|
+
|
||
|
+ btdev->initialized = false;
|
||
|
+
|
||
|
+ nokia_flow_control(btdev->serdev, false);
|
||
|
+
|
||
|
+ pm_runtime_get_sync(dev);
|
||
|
+
|
||
|
+ if (btdev->tx_enabled) {
|
||
|
+ gpiod_set_value_cansleep(btdev->wakeup_bt, 0);
|
||
|
+ pm_runtime_put(&btdev->serdev->dev);
|
||
|
+ btdev->tx_enabled = false;
|
||
|
+ }
|
||
|
+
|
||
|
+ dev_dbg(dev, "protocol setup");
|
||
|
+
|
||
|
+ /* 0. reset connection */
|
||
|
+ err = nokia_reset(hu);
|
||
|
+ if (err < 0) {
|
||
|
+ dev_err(dev, "Reset failed: %d", err);
|
||
|
+ goto out;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* 1. negotiate speed etc */
|
||
|
+ err = nokia_send_negotiation(hu);
|
||
|
+ if (err < 0) {
|
||
|
+ dev_err(dev, "Negotiation failed: %d", err);
|
||
|
+ goto out;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* 2. verify correct setup using alive packet */
|
||
|
+ err = nokia_send_alive_packet(hu);
|
||
|
+ if (err < 0) {
|
||
|
+ dev_err(dev, "Alive check failed: %d", err);
|
||
|
+ goto out;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* 3. send firmware */
|
||
|
+ err = nokia_setup_fw(hu);
|
||
|
+ if (err < 0) {
|
||
|
+ dev_err(dev, "Could not setup FW: %d", err);
|
||
|
+ goto out;
|
||
|
+ }
|
||
|
+
|
||
|
+ nokia_flow_control(btdev->serdev, false);
|
||
|
+ serdev_device_set_baudrate(btdev->serdev, MAX_BAUD_RATE);
|
||
|
+ nokia_flow_control(btdev->serdev, true);
|
||
|
+
|
||
|
+ if (btdev->man_id == NOKIA_ID_BCM2048) {
|
||
|
+ hu->hdev->set_bdaddr = btbcm_set_bdaddr;
|
||
|
+ set_bit(HCI_QUIRK_INVALID_BDADDR, &hu->hdev->quirks);
|
||
|
+ dev_dbg(dev, "bcm2048 has invalid bluetooth address!");
|
||
|
+ }
|
||
|
+
|
||
|
+ dev_dbg(dev, "protocol setup done!");
|
||
|
+
|
||
|
+ gpiod_set_value_cansleep(btdev->wakeup_bt, 0);
|
||
|
+ pm_runtime_put(dev);
|
||
|
+ btdev->tx_enabled = false;
|
||
|
+ btdev->initialized = true;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+out:
|
||
|
+ pm_runtime_put(dev);
|
||
|
+
|
||
|
+ return err;
|
||
|
+}
|
||
|
+
|
||
|
+static int nokia_open(struct hci_uart *hu)
|
||
|
+{
|
||
|
+ struct device *dev = &hu->serdev->dev;
|
||
|
+
|
||
|
+ dev_dbg(dev, "protocol open");
|
||
|
+
|
||
|
+ serdev_device_open(hu->serdev);
|
||
|
+
|
||
|
+ pm_runtime_enable(dev);
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int nokia_flush(struct hci_uart *hu)
|
||
|
+{
|
||
|
+ struct nokia_bt_dev *btdev = hu->priv;
|
||
|
+
|
||
|
+ dev_dbg(&btdev->serdev->dev, "flush device");
|
||
|
+
|
||
|
+ skb_queue_purge(&btdev->txq);
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int nokia_close(struct hci_uart *hu)
|
||
|
+{
|
||
|
+ struct nokia_bt_dev *btdev = hu->priv;
|
||
|
+ struct device *dev = &btdev->serdev->dev;
|
||
|
+
|
||
|
+ dev_dbg(dev, "close device");
|
||
|
+
|
||
|
+ btdev->initialized = false;
|
||
|
+
|
||
|
+ skb_queue_purge(&btdev->txq);
|
||
|
+
|
||
|
+ kfree_skb(btdev->rx_skb);
|
||
|
+
|
||
|
+ /* disable module */
|
||
|
+ gpiod_set_value(btdev->reset, 1);
|
||
|
+ gpiod_set_value(btdev->wakeup_bt, 0);
|
||
|
+
|
||
|
+ pm_runtime_disable(&btdev->serdev->dev);
|
||
|
+ serdev_device_close(btdev->serdev);
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+/* Enqueue frame for transmittion (padding, crc, etc) */
|
||
|
+static int nokia_enqueue(struct hci_uart *hu, struct sk_buff *skb)
|
||
|
+{
|
||
|
+ struct nokia_bt_dev *btdev = hu->priv;
|
||
|
+ int err;
|
||
|
+
|
||
|
+ /* Prepend skb with frame type */
|
||
|
+ memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1);
|
||
|
+
|
||
|
+ /* Packets must be word aligned */
|
||
|
+ if (skb->len % 2) {
|
||
|
+ err = skb_pad(skb, 1);
|
||
|
+ if (err)
|
||
|
+ return err;
|
||
|
+ *skb_put(skb, 1) = 0x00;
|
||
|
+ }
|
||
|
+
|
||
|
+ skb_queue_tail(&btdev->txq, skb);
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int nokia_recv_negotiation_packet(struct hci_dev *hdev,
|
||
|
+ struct sk_buff *skb)
|
||
|
+{
|
||
|
+ struct hci_uart *hu = hci_get_drvdata(hdev);
|
||
|
+ struct nokia_bt_dev *btdev = hu->priv;
|
||
|
+ struct device *dev = &btdev->serdev->dev;
|
||
|
+ struct hci_nokia_neg_hdr *hdr;
|
||
|
+ struct hci_nokia_neg_evt *evt;
|
||
|
+ int ret = 0;
|
||
|
+
|
||
|
+ hdr = (struct hci_nokia_neg_hdr *)skb->data;
|
||
|
+ if (hdr->dlen != sizeof(*evt)) {
|
||
|
+ btdev->init_error = -EIO;
|
||
|
+ ret = -EIO;
|
||
|
+ goto finish_neg;
|
||
|
+ }
|
||
|
+
|
||
|
+ evt = (struct hci_nokia_neg_evt *)skb_pull(skb, sizeof(*hdr));
|
||
|
+
|
||
|
+ if (evt->ack != NOKIA_NEG_ACK) {
|
||
|
+ dev_err(dev, "Negotiation received: wrong reply");
|
||
|
+ btdev->init_error = -EINVAL;
|
||
|
+ ret = -EINVAL;
|
||
|
+ goto finish_neg;
|
||
|
+ }
|
||
|
+
|
||
|
+ btdev->man_id = evt->man_id;
|
||
|
+ btdev->ver_id = evt->ver_id;
|
||
|
+
|
||
|
+ dev_dbg(dev, "Negotiation received: baud=%u:clk=%u:manu=%u:vers=%u",
|
||
|
+ evt->baud, evt->sys_clk, evt->man_id, evt->ver_id);
|
||
|
+
|
||
|
+finish_neg:
|
||
|
+ complete(&btdev->init_completion);
|
||
|
+ kfree_skb(skb);
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static int nokia_recv_alive_packet(struct hci_dev *hdev, struct sk_buff *skb)
|
||
|
+{
|
||
|
+ struct hci_uart *hu = hci_get_drvdata(hdev);
|
||
|
+ struct nokia_bt_dev *btdev = hu->priv;
|
||
|
+ struct device *dev = &btdev->serdev->dev;
|
||
|
+ struct hci_nokia_alive_hdr *hdr;
|
||
|
+ struct hci_nokia_alive_pkt *pkt;
|
||
|
+ int ret = 0;
|
||
|
+
|
||
|
+ hdr = (struct hci_nokia_alive_hdr *)skb->data;
|
||
|
+ if (hdr->dlen != sizeof(*pkt)) {
|
||
|
+ dev_err(dev, "Corrupted alive message");
|
||
|
+ btdev->init_error = -EIO;
|
||
|
+ ret = -EIO;
|
||
|
+ goto finish_alive;
|
||
|
+ }
|
||
|
+
|
||
|
+ pkt = (struct hci_nokia_alive_pkt *)skb_pull(skb, sizeof(*hdr));
|
||
|
+
|
||
|
+ if (pkt->mid != NOKIA_ALIVE_RESP) {
|
||
|
+ dev_err(dev, "Alive received: invalid response: 0x%02x!",
|
||
|
+ pkt->mid);
|
||
|
+ btdev->init_error = -EINVAL;
|
||
|
+ ret = -EINVAL;
|
||
|
+ goto finish_alive;
|
||
|
+ }
|
||
|
+
|
||
|
+ dev_dbg(dev, "Alive received");
|
||
|
+
|
||
|
+finish_alive:
|
||
|
+ complete(&btdev->init_completion);
|
||
|
+ kfree_skb(skb);
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static int nokia_recv_radio(struct hci_dev *hdev, struct sk_buff *skb)
|
||
|
+{
|
||
|
+ /* Packets received on the dedicated radio channel are
|
||
|
+ * HCI events and so feed them back into the core.
|
||
|
+ */
|
||
|
+ hci_skb_pkt_type(skb) = HCI_EVENT_PKT;
|
||
|
+ return hci_recv_frame(hdev, skb);
|
||
|
+}
|
||
|
+
|
||
|
+/* Recv data */
|
||
|
+static const struct h4_recv_pkt nokia_recv_pkts[] = {
|
||
|
+ { H4_RECV_ACL, .recv = hci_recv_frame },
|
||
|
+ { H4_RECV_SCO, .recv = hci_recv_frame },
|
||
|
+ { H4_RECV_EVENT, .recv = hci_recv_frame },
|
||
|
+ { NOKIA_RECV_ALIVE, .recv = nokia_recv_alive_packet },
|
||
|
+ { NOKIA_RECV_NEG, .recv = nokia_recv_negotiation_packet },
|
||
|
+ { NOKIA_RECV_RADIO, .recv = nokia_recv_radio },
|
||
|
+};
|
||
|
+
|
||
|
+static int nokia_recv(struct hci_uart *hu, const void *data, int count)
|
||
|
+{
|
||
|
+ struct nokia_bt_dev *btdev = hu->priv;
|
||
|
+ struct device *dev = &btdev->serdev->dev;
|
||
|
+ int err;
|
||
|
+
|
||
|
+ if (!test_bit(HCI_UART_REGISTERED, &hu->flags))
|
||
|
+ return -EUNATCH;
|
||
|
+
|
||
|
+ btdev->rx_skb = h4_recv_buf(hu->hdev, btdev->rx_skb, data, count,
|
||
|
+ nokia_recv_pkts, ARRAY_SIZE(nokia_recv_pkts));
|
||
|
+ if (IS_ERR(btdev->rx_skb)) {
|
||
|
+ err = PTR_ERR(btdev->rx_skb);
|
||
|
+ dev_err(dev, "Frame reassembly failed (%d)", err);
|
||
|
+ btdev->rx_skb = NULL;
|
||
|
+ return err;
|
||
|
+ }
|
||
|
+
|
||
|
+ return count;
|
||
|
+}
|
||
|
+
|
||
|
+static struct sk_buff *nokia_dequeue(struct hci_uart *hu)
|
||
|
+{
|
||
|
+ struct nokia_bt_dev *btdev = hu->priv;
|
||
|
+ struct device *dev = &btdev->serdev->dev;
|
||
|
+ struct sk_buff *result = skb_dequeue(&btdev->txq);
|
||
|
+
|
||
|
+ if (!btdev->initialized)
|
||
|
+ return result;
|
||
|
+
|
||
|
+ if (btdev->tx_enabled == !!result)
|
||
|
+ return result;
|
||
|
+
|
||
|
+ if (result) {
|
||
|
+ pm_runtime_get_sync(dev);
|
||
|
+ gpiod_set_value_cansleep(btdev->wakeup_bt, 1);
|
||
|
+ } else {
|
||
|
+ serdev_device_wait_until_sent(btdev->serdev, 0);
|
||
|
+ gpiod_set_value_cansleep(btdev->wakeup_bt, 0);
|
||
|
+ pm_runtime_put(dev);
|
||
|
+ }
|
||
|
+
|
||
|
+ btdev->tx_enabled = !!result;
|
||
|
+
|
||
|
+ return result;
|
||
|
+}
|
||
|
+
|
||
|
+static const struct hci_uart_proto nokia_proto = {
|
||
|
+ .id = HCI_UART_NOKIA,
|
||
|
+ .name = "Nokia",
|
||
|
+ .open = nokia_open,
|
||
|
+ .close = nokia_close,
|
||
|
+ .recv = nokia_recv,
|
||
|
+ .enqueue = nokia_enqueue,
|
||
|
+ .dequeue = nokia_dequeue,
|
||
|
+ .flush = nokia_flush,
|
||
|
+ .setup = nokia_setup,
|
||
|
+ .manufacturer = 1,
|
||
|
+};
|
||
|
+
|
||
|
+static int nokia_bluetooth_serdev_probe(struct serdev_device *serdev)
|
||
|
+{
|
||
|
+ struct device *dev = &serdev->dev;
|
||
|
+ struct nokia_bt_dev *btdev;
|
||
|
+ struct clk *sysclk;
|
||
|
+ int err = 0;
|
||
|
+
|
||
|
+ btdev = devm_kzalloc(dev, sizeof(*btdev), GFP_KERNEL);
|
||
|
+ if (!btdev)
|
||
|
+ return -ENOMEM;
|
||
|
+
|
||
|
+ btdev->hu.serdev = btdev->serdev = serdev;
|
||
|
+ serdev_device_set_drvdata(serdev, btdev);
|
||
|
+
|
||
|
+ btdev->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
|
||
|
+ if (IS_ERR(btdev->reset)) {
|
||
|
+ err = PTR_ERR(btdev->reset);
|
||
|
+ dev_err(dev, "could not get reset gpio: %d", err);
|
||
|
+ return err;
|
||
|
+ }
|
||
|
+
|
||
|
+ btdev->wakeup_host = devm_gpiod_get(dev, "host-wakeup", GPIOD_IN);
|
||
|
+ if (IS_ERR(btdev->wakeup_host)) {
|
||
|
+ err = PTR_ERR(btdev->wakeup_host);
|
||
|
+ dev_err(dev, "could not get host wakeup gpio: %d", err);
|
||
|
+ return err;
|
||
|
+ }
|
||
|
+
|
||
|
+ btdev->wake_irq = gpiod_to_irq(btdev->wakeup_host);
|
||
|
+
|
||
|
+ err = devm_request_threaded_irq(dev, btdev->wake_irq, NULL,
|
||
|
+ wakeup_handler,
|
||
|
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||
|
+ "wakeup", btdev);
|
||
|
+ if (err) {
|
||
|
+ dev_err(dev, "could request wakeup irq: %d", err);
|
||
|
+ return err;
|
||
|
+ }
|
||
|
+
|
||
|
+ btdev->wakeup_bt = devm_gpiod_get(dev, "bluetooth-wakeup",
|
||
|
+ GPIOD_OUT_LOW);
|
||
|
+ if (IS_ERR(btdev->wakeup_bt)) {
|
||
|
+ err = PTR_ERR(btdev->wakeup_bt);
|
||
|
+ dev_err(dev, "could not get BT wakeup gpio: %d", err);
|
||
|
+ return err;
|
||
|
+ }
|
||
|
+
|
||
|
+ sysclk = devm_clk_get(dev, "sysclk");
|
||
|
+ if (IS_ERR(sysclk)) {
|
||
|
+ err = PTR_ERR(sysclk);
|
||
|
+ dev_err(dev, "could not get sysclk: %d", err);
|
||
|
+ return err;
|
||
|
+ }
|
||
|
+
|
||
|
+ clk_prepare_enable(sysclk);
|
||
|
+ btdev->sysclk_speed = clk_get_rate(sysclk);
|
||
|
+ clk_disable_unprepare(sysclk);
|
||
|
+
|
||
|
+ skb_queue_head_init(&btdev->txq);
|
||
|
+
|
||
|
+ btdev->hu.priv = btdev;
|
||
|
+ btdev->hu.alignment = 2; /* Nokia H4+ is word aligned */
|
||
|
+
|
||
|
+ err = hci_uart_register_device(&btdev->hu, &nokia_proto);
|
||
|
+ if (err) {
|
||
|
+ dev_err(dev, "could not register bluetooth uart: %d", err);
|
||
|
+ return err;
|
||
|
+ }
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static void nokia_bluetooth_serdev_remove(struct serdev_device *serdev)
|
||
|
+{
|
||
|
+ struct nokia_bt_dev *btdev = serdev_device_get_drvdata(serdev);
|
||
|
+ struct hci_uart *hu = &btdev->hu;
|
||
|
+ struct hci_dev *hdev = hu->hdev;
|
||
|
+
|
||
|
+ cancel_work_sync(&hu->write_work);
|
||
|
+
|
||
|
+ hci_unregister_dev(hdev);
|
||
|
+ hci_free_dev(hdev);
|
||
|
+ hu->proto->close(hu);
|
||
|
+
|
||
|
+ pm_runtime_disable(&btdev->serdev->dev);
|
||
|
+}
|
||
|
+
|
||
|
+static int nokia_bluetooth_runtime_suspend(struct device *dev)
|
||
|
+{
|
||
|
+ struct serdev_device *serdev = to_serdev_device(dev);
|
||
|
+
|
||
|
+ nokia_flow_control(serdev, false);
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int nokia_bluetooth_runtime_resume(struct device *dev)
|
||
|
+{
|
||
|
+ struct serdev_device *serdev = to_serdev_device(dev);
|
||
|
+
|
||
|
+ nokia_flow_control(serdev, true);
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static const struct dev_pm_ops nokia_bluetooth_pm_ops = {
|
||
|
+ SET_RUNTIME_PM_OPS(nokia_bluetooth_runtime_suspend,
|
||
|
+ nokia_bluetooth_runtime_resume,
|
||
|
+ NULL)
|
||
|
+};
|
||
|
+
|
||
|
+#ifdef CONFIG_OF
|
||
|
+static const struct of_device_id nokia_bluetooth_of_match[] = {
|
||
|
+ { .compatible = "nokia,h4p-bluetooth", },
|
||
|
+ {},
|
||
|
+};
|
||
|
+MODULE_DEVICE_TABLE(of, nokia_bluetooth_of_match);
|
||
|
+#endif
|
||
|
+
|
||
|
+static struct serdev_device_driver nokia_bluetooth_serdev_driver = {
|
||
|
+ .probe = nokia_bluetooth_serdev_probe,
|
||
|
+ .remove = nokia_bluetooth_serdev_remove,
|
||
|
+ .driver = {
|
||
|
+ .name = "nokia-bluetooth",
|
||
|
+ .pm = &nokia_bluetooth_pm_ops,
|
||
|
+ .of_match_table = of_match_ptr(nokia_bluetooth_of_match),
|
||
|
+ },
|
||
|
+};
|
||
|
+
|
||
|
+module_serdev_device_driver(nokia_bluetooth_serdev_driver);
|
||
|
--
|
||
|
2.12.2
|
||
|
|
||
|
From 5593378d18f2487bdce87a687b4263c9626510cb Mon Sep 17 00:00:00 2001
|
||
|
From: Rob Herring <robh@kernel.org>
|
||
|
Date: Wed, 5 Apr 2017 12:10:13 -0500
|
||
|
Subject: [PATCH 10/13] dt-bindings: net: Add TI WiLink shared transport
|
||
|
binding
|
||
|
|
||
|
Add serial slave device binding for the TI WiLink series of Bluetooth/FM/GPS
|
||
|
devices.
|
||
|
|
||
|
Signed-off-by: Rob Herring <robh@kernel.org>
|
||
|
Cc: Mark Rutland <mark.rutland@arm.com>
|
||
|
Cc: netdev@vger.kernel.org
|
||
|
Cc: devicetree@vger.kernel.org
|
||
|
---
|
||
|
.../devicetree/bindings/net/ti,wilink-st.txt | 35 ++++++++++++++++++++++
|
||
|
1 file changed, 35 insertions(+)
|
||
|
create mode 100644 Documentation/devicetree/bindings/net/ti,wilink-st.txt
|
||
|
|
||
|
diff --git a/Documentation/devicetree/bindings/net/ti,wilink-st.txt b/Documentation/devicetree/bindings/net/ti,wilink-st.txt
|
||
|
new file mode 100644
|
||
|
index 000000000000..cbad73a84ac4
|
||
|
--- /dev/null
|
||
|
+++ b/Documentation/devicetree/bindings/net/ti,wilink-st.txt
|
||
|
@@ -0,0 +1,35 @@
|
||
|
+TI WiLink 7/8 (wl12xx/wl18xx) Shared Transport BT/FM/GPS devices
|
||
|
+
|
||
|
+TI WiLink devices have a UART interface for providing Bluetooth, FM radio,
|
||
|
+and GPS over what's called "shared transport". The shared transport is
|
||
|
+standard BT HCI protocol with additional channels for the other functions.
|
||
|
+
|
||
|
+These devices also have a separate WiFi interface as described in
|
||
|
+wireless/ti,wlcore.txt.
|
||
|
+
|
||
|
+This bindings follows the UART slave device binding in
|
||
|
+../serial/slave-device.txt.
|
||
|
+
|
||
|
+Required properties:
|
||
|
+ - compatible: should be one of the following:
|
||
|
+ "ti,wl1271-st"
|
||
|
+ "ti,wl1273-st"
|
||
|
+ "ti,wl1831-st"
|
||
|
+ "ti,wl1835-st"
|
||
|
+ "ti,wl1837-st"
|
||
|
+
|
||
|
+Optional properties:
|
||
|
+ - enable-gpios : GPIO signal controlling enabling of BT. Active high.
|
||
|
+ - vio-supply : Vio input supply (1.8V)
|
||
|
+ - vbat-supply : Vbat input supply (2.9-4.8V)
|
||
|
+
|
||
|
+Example:
|
||
|
+
|
||
|
+&serial0 {
|
||
|
+ compatible = "ns16550a";
|
||
|
+ ...
|
||
|
+ bluetooth {
|
||
|
+ compatible = "ti,wl1835-st";
|
||
|
+ enable-gpios = <&gpio1 7 GPIO_ACTIVE_HIGH>;
|
||
|
+ };
|
||
|
+};
|
||
|
--
|
||
|
2.12.2
|
||
|
|
||
|
From d22a4564d1b8e1eeb7c442171cedcc3ae809cfff Mon Sep 17 00:00:00 2001
|
||
|
From: Rob Herring <robh@kernel.org>
|
||
|
Date: Tue, 17 Jan 2017 09:58:10 -0600
|
||
|
Subject: [PATCH 11/13] bluetooth: hci_uart: remove unused hci_uart_init_tty
|
||
|
|
||
|
There are no users of hci_uart_init_tty, so remove it.
|
||
|
|
||
|
Signed-off-by: Rob Herring <robh@kernel.org>
|
||
|
Cc: Marcel Holtmann <marcel@holtmann.org>
|
||
|
Cc: Gustavo Padovan <gustavo@padovan.org>
|
||
|
Cc: Johan Hedberg <johan.hedberg@gmail.com>
|
||
|
Cc: linux-bluetooth@vger.kernel.org
|
||
|
---
|
||
|
drivers/bluetooth/hci_ldisc.c | 19 -------------------
|
||
|
drivers/bluetooth/hci_uart.h | 1 -
|
||
|
2 files changed, 20 deletions(-)
|
||
|
|
||
|
diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c
|
||
|
index 17bcbc13623f..cec4438ede01 100644
|
||
|
--- a/drivers/bluetooth/hci_ldisc.c
|
||
|
+++ b/drivers/bluetooth/hci_ldisc.c
|
||
|
@@ -319,25 +319,6 @@ void hci_uart_set_speeds(struct hci_uart *hu, unsigned int init_speed,
|
||
|
hu->oper_speed = oper_speed;
|
||
|
}
|
||
|
|
||
|
-void hci_uart_init_tty(struct hci_uart *hu)
|
||
|
-{
|
||
|
- struct tty_struct *tty = hu->tty;
|
||
|
- struct ktermios ktermios;
|
||
|
-
|
||
|
- /* Bring the UART into a known 8 bits no parity hw fc state */
|
||
|
- ktermios = tty->termios;
|
||
|
- ktermios.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP |
|
||
|
- INLCR | IGNCR | ICRNL | IXON);
|
||
|
- ktermios.c_oflag &= ~OPOST;
|
||
|
- ktermios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
||
|
- ktermios.c_cflag &= ~(CSIZE | PARENB);
|
||
|
- ktermios.c_cflag |= CS8;
|
||
|
- ktermios.c_cflag |= CRTSCTS;
|
||
|
-
|
||
|
- /* tty_set_termios() return not checked as it is always 0 */
|
||
|
- tty_set_termios(tty, &ktermios);
|
||
|
-}
|
||
|
-
|
||
|
void hci_uart_set_baudrate(struct hci_uart *hu, unsigned int speed)
|
||
|
{
|
||
|
struct tty_struct *tty = hu->tty;
|
||
|
diff --git a/drivers/bluetooth/hci_uart.h b/drivers/bluetooth/hci_uart.h
|
||
|
index 1b41c661bbb8..2b05e557fad0 100644
|
||
|
--- a/drivers/bluetooth/hci_uart.h
|
||
|
+++ b/drivers/bluetooth/hci_uart.h
|
||
|
@@ -114,7 +114,6 @@ 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_init_tty(struct hci_uart *hu);
|
||
|
void hci_uart_set_baudrate(struct hci_uart *hu, unsigned int speed);
|
||
|
void hci_uart_set_flow_control(struct hci_uart *hu, bool enable);
|
||
|
void hci_uart_set_speeds(struct hci_uart *hu, unsigned int init_speed,
|
||
|
--
|
||
|
2.12.2
|
||
|
|
||
|
From 4664fe8eb293288c1fba2ebc50ac50131f6125cf Mon Sep 17 00:00:00 2001
|
||
|
From: Rob Herring <robh@kernel.org>
|
||
|
Date: Wed, 18 Jan 2017 11:41:25 -0600
|
||
|
Subject: [PATCH 12/13] bluetooth: hci_uart: add LL protocol serdev driver
|
||
|
support
|
||
|
|
||
|
Turns out that the LL protocol and the TI-ST are the same thing AFAICT.
|
||
|
The TI-ST adds firmware loading, GPIO control, and shared access for
|
||
|
NFC, FM radio, etc. For now, we're only implementing what is needed for
|
||
|
BT. This mirrors other drivers like BCM and Intel, but uses the new
|
||
|
serdev bus.
|
||
|
|
||
|
The firmware loading is greatly simplified by using existing
|
||
|
infrastructure to send commands. It may be a bit slower than the
|
||
|
original code using synchronous functions, but the real bottleneck is
|
||
|
likely doing firmware load at 115.2kbps.
|
||
|
|
||
|
Signed-off-by: Rob Herring <robh@kernel.org>
|
||
|
Cc: Marcel Holtmann <marcel@holtmann.org>
|
||
|
Cc: Gustavo Padovan <gustavo@padovan.org>
|
||
|
Cc: Johan Hedberg <johan.hedberg@gmail.com>
|
||
|
Cc: linux-bluetooth@vger.kernel.org
|
||
|
---
|
||
|
drivers/bluetooth/hci_ll.c | 261 ++++++++++++++++++++++++++++++++++++++++++++-
|
||
|
1 file changed, 260 insertions(+), 1 deletion(-)
|
||
|
|
||
|
diff --git a/drivers/bluetooth/hci_ll.c b/drivers/bluetooth/hci_ll.c
|
||
|
index 02692fe30279..9b2054f5502d 100644
|
||
|
--- a/drivers/bluetooth/hci_ll.c
|
||
|
+++ b/drivers/bluetooth/hci_ll.c
|
||
|
@@ -34,20 +34,23 @@
|
||
|
#include <linux/sched.h>
|
||
|
#include <linux/types.h>
|
||
|
#include <linux/fcntl.h>
|
||
|
+#include <linux/firmware.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/ptrace.h>
|
||
|
#include <linux/poll.h>
|
||
|
|
||
|
#include <linux/slab.h>
|
||
|
-#include <linux/tty.h>
|
||
|
#include <linux/errno.h>
|
||
|
#include <linux/string.h>
|
||
|
#include <linux/signal.h>
|
||
|
#include <linux/ioctl.h>
|
||
|
+#include <linux/serdev.h>
|
||
|
#include <linux/skbuff.h>
|
||
|
+#include <linux/ti_wilink_st.h>
|
||
|
|
||
|
#include <net/bluetooth/bluetooth.h>
|
||
|
#include <net/bluetooth/hci_core.h>
|
||
|
+#include <linux/gpio/consumer.h>
|
||
|
|
||
|
#include "hci_uart.h"
|
||
|
|
||
|
@@ -76,6 +79,12 @@ struct hcill_cmd {
|
||
|
u8 cmd;
|
||
|
} __packed;
|
||
|
|
||
|
+struct ll_device {
|
||
|
+ struct hci_uart hu;
|
||
|
+ struct serdev_device *serdev;
|
||
|
+ struct gpio_desc *enable_gpio;
|
||
|
+};
|
||
|
+
|
||
|
struct ll_struct {
|
||
|
unsigned long rx_state;
|
||
|
unsigned long rx_count;
|
||
|
@@ -136,6 +145,9 @@ static int ll_open(struct hci_uart *hu)
|
||
|
|
||
|
hu->priv = ll;
|
||
|
|
||
|
+ if (hu->serdev)
|
||
|
+ serdev_device_open(hu->serdev);
|
||
|
+
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
@@ -164,6 +176,13 @@ static int ll_close(struct hci_uart *hu)
|
||
|
|
||
|
kfree_skb(ll->rx_skb);
|
||
|
|
||
|
+ if (hu->serdev) {
|
||
|
+ struct ll_device *lldev = serdev_device_get_drvdata(hu->serdev);
|
||
|
+ gpiod_set_value_cansleep(lldev->enable_gpio, 0);
|
||
|
+
|
||
|
+ serdev_device_close(hu->serdev);
|
||
|
+ }
|
||
|
+
|
||
|
hu->priv = NULL;
|
||
|
|
||
|
kfree(ll);
|
||
|
@@ -505,9 +524,245 @@ static struct sk_buff *ll_dequeue(struct hci_uart *hu)
|
||
|
return skb_dequeue(&ll->txq);
|
||
|
}
|
||
|
|
||
|
+#ifdef CONFIG_SERIAL_DEV_BUS
|
||
|
+static int read_local_version(struct hci_dev *hdev)
|
||
|
+{
|
||
|
+ int err = 0;
|
||
|
+ unsigned short version = 0;
|
||
|
+ struct sk_buff *skb;
|
||
|
+ struct hci_rp_read_local_version *ver;
|
||
|
+
|
||
|
+ skb = __hci_cmd_sync(hdev, HCI_OP_READ_LOCAL_VERSION, 0, NULL, HCI_INIT_TIMEOUT);
|
||
|
+ if (IS_ERR(skb)) {
|
||
|
+ bt_dev_err(hdev, "Reading TI version information failed (%ld)",
|
||
|
+ PTR_ERR(skb));
|
||
|
+ err = PTR_ERR(skb);
|
||
|
+ goto out;
|
||
|
+ }
|
||
|
+ if (skb->len != sizeof(*ver)) {
|
||
|
+ err = -EILSEQ;
|
||
|
+ goto out;
|
||
|
+ }
|
||
|
+
|
||
|
+ ver = (struct hci_rp_read_local_version *)skb->data;
|
||
|
+ if (le16_to_cpu(ver->manufacturer) != 13) {
|
||
|
+ err = -ENODEV;
|
||
|
+ goto out;
|
||
|
+ }
|
||
|
+
|
||
|
+ version = le16_to_cpu(ver->lmp_subver);
|
||
|
+
|
||
|
+out:
|
||
|
+ if (err) bt_dev_err(hdev, "Failed to read TI version info: %d", err);
|
||
|
+ kfree_skb(skb);
|
||
|
+ return err ? err : version;
|
||
|
+}
|
||
|
+
|
||
|
+/**
|
||
|
+ * download_firmware -
|
||
|
+ * internal function which parses through the .bts firmware
|
||
|
+ * script file intreprets SEND, DELAY actions only as of now
|
||
|
+ */
|
||
|
+static int download_firmware(struct ll_device *lldev)
|
||
|
+{
|
||
|
+ unsigned short chip, min_ver, maj_ver;
|
||
|
+ int version, err, len;
|
||
|
+ unsigned char *ptr, *action_ptr;
|
||
|
+ unsigned char bts_scr_name[40]; /* 40 char long bts scr name? */
|
||
|
+ const struct firmware *fw;
|
||
|
+ struct sk_buff *skb;
|
||
|
+ struct hci_command *cmd;
|
||
|
+
|
||
|
+ version = read_local_version(lldev->hu.hdev);
|
||
|
+ if (version < 0)
|
||
|
+ return version;
|
||
|
+
|
||
|
+ chip = (version & 0x7C00) >> 10;
|
||
|
+ min_ver = (version & 0x007F);
|
||
|
+ maj_ver = (version & 0x0380) >> 7;
|
||
|
+ if (version & 0x8000)
|
||
|
+ maj_ver |= 0x0008;
|
||
|
+
|
||
|
+ snprintf(bts_scr_name, sizeof(bts_scr_name),
|
||
|
+ "ti-connectivity/TIInit_%d.%d.%d.bts",
|
||
|
+ chip, maj_ver, min_ver);
|
||
|
+
|
||
|
+ err = request_firmware(&fw, bts_scr_name, &lldev->serdev->dev);
|
||
|
+ if (err || !fw->data || !fw->size) {
|
||
|
+ bt_dev_err(lldev->hu.hdev, "request_firmware failed(errno %d) for %s",
|
||
|
+ err, bts_scr_name);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+ ptr = (void *)fw->data;
|
||
|
+ len = fw->size;
|
||
|
+ /* bts_header to remove out magic number and
|
||
|
+ * version
|
||
|
+ */
|
||
|
+ ptr += sizeof(struct bts_header);
|
||
|
+ len -= sizeof(struct bts_header);
|
||
|
+
|
||
|
+ while (len > 0 && ptr) {
|
||
|
+ bt_dev_dbg(lldev->hu.hdev, " action size %d, type %d ",
|
||
|
+ ((struct bts_action *)ptr)->size,
|
||
|
+ ((struct bts_action *)ptr)->type);
|
||
|
+
|
||
|
+ action_ptr = &(((struct bts_action *)ptr)->data[0]);
|
||
|
+
|
||
|
+ switch (((struct bts_action *)ptr)->type) {
|
||
|
+ case ACTION_SEND_COMMAND: /* action send */
|
||
|
+ bt_dev_dbg(lldev->hu.hdev, "S");
|
||
|
+ cmd = (struct hci_command *)action_ptr;
|
||
|
+ if (cmd->opcode == 0xff36) {
|
||
|
+ /* ignore remote change
|
||
|
+ * baud rate HCI VS command */
|
||
|
+ bt_dev_warn(lldev->hu.hdev, "change remote baud rate command in firmware");
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ if (cmd->prefix != 1)
|
||
|
+ bt_dev_dbg(lldev->hu.hdev, "command type %d\n", cmd->prefix);
|
||
|
+
|
||
|
+ skb = __hci_cmd_sync(lldev->hu.hdev, cmd->opcode, cmd->plen, &cmd->speed, HCI_INIT_TIMEOUT);
|
||
|
+ if (IS_ERR(skb)) {
|
||
|
+ bt_dev_err(lldev->hu.hdev, "send command failed\n");
|
||
|
+ goto out_rel_fw;
|
||
|
+ }
|
||
|
+ kfree_skb(skb);
|
||
|
+ break;
|
||
|
+ case ACTION_WAIT_EVENT: /* wait */
|
||
|
+ /* no need to wait as command was synchronous */
|
||
|
+ bt_dev_dbg(lldev->hu.hdev, "W");
|
||
|
+ break;
|
||
|
+ case ACTION_DELAY: /* sleep */
|
||
|
+ bt_dev_info(lldev->hu.hdev, "sleep command in scr");
|
||
|
+ mdelay(((struct bts_action_delay *)action_ptr)->msec);
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ len -= (sizeof(struct bts_action) +
|
||
|
+ ((struct bts_action *)ptr)->size);
|
||
|
+ ptr += sizeof(struct bts_action) +
|
||
|
+ ((struct bts_action *)ptr)->size;
|
||
|
+ }
|
||
|
+
|
||
|
+out_rel_fw:
|
||
|
+ /* fw download complete */
|
||
|
+ release_firmware(fw);
|
||
|
+ return err;
|
||
|
+}
|
||
|
+
|
||
|
+static int ll_setup(struct hci_uart *hu)
|
||
|
+{
|
||
|
+ int err, retry = 3;
|
||
|
+ struct ll_device *lldev;
|
||
|
+ struct serdev_device *serdev = hu->serdev;
|
||
|
+ u32 speed;
|
||
|
+
|
||
|
+ if (!serdev)
|
||
|
+ return 0;
|
||
|
+
|
||
|
+ lldev = serdev_device_get_drvdata(serdev);
|
||
|
+
|
||
|
+ serdev_device_set_flow_control(serdev, true);
|
||
|
+
|
||
|
+ do {
|
||
|
+ /* Configure BT_EN to HIGH state */
|
||
|
+ gpiod_set_value_cansleep(lldev->enable_gpio, 0);
|
||
|
+ msleep(5);
|
||
|
+ gpiod_set_value_cansleep(lldev->enable_gpio, 1);
|
||
|
+ msleep(100);
|
||
|
+
|
||
|
+ err = download_firmware(lldev);
|
||
|
+ if (!err)
|
||
|
+ break;
|
||
|
+
|
||
|
+ /* Toggle BT_EN and retry */
|
||
|
+ bt_dev_err(hu->hdev, "download firmware failed, retrying...");
|
||
|
+ } while (retry--);
|
||
|
+
|
||
|
+ if (err)
|
||
|
+ return err;
|
||
|
+
|
||
|
+ /* 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 (speed) {
|
||
|
+ struct sk_buff *skb = __hci_cmd_sync(hu->hdev, 0xff36, sizeof(speed), &speed, HCI_INIT_TIMEOUT);
|
||
|
+ if (!IS_ERR(skb)) {
|
||
|
+ kfree_skb(skb);
|
||
|
+ serdev_device_set_baudrate(serdev, speed);
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static const struct hci_uart_proto llp;
|
||
|
+
|
||
|
+static int hci_ti_probe(struct serdev_device *serdev)
|
||
|
+{
|
||
|
+ struct hci_uart *hu;
|
||
|
+ struct ll_device *lldev;
|
||
|
+ u32 max_speed = 3000000;
|
||
|
+
|
||
|
+ lldev = devm_kzalloc(&serdev->dev, sizeof(struct ll_device), GFP_KERNEL);
|
||
|
+ if (!lldev)
|
||
|
+ return -ENOMEM;
|
||
|
+ hu = &lldev->hu;
|
||
|
+
|
||
|
+ serdev_device_set_drvdata(serdev, lldev);
|
||
|
+ lldev->serdev = hu->serdev = serdev;
|
||
|
+
|
||
|
+ lldev->enable_gpio = devm_gpiod_get_optional(&serdev->dev, "enable", GPIOD_OUT_LOW);
|
||
|
+ if (IS_ERR(lldev->enable_gpio))
|
||
|
+ return PTR_ERR(lldev->enable_gpio);
|
||
|
+
|
||
|
+ of_property_read_u32(serdev->dev.of_node, "max-speed", &max_speed);
|
||
|
+ hci_uart_set_speeds(hu, 115200, max_speed);
|
||
|
+
|
||
|
+ return hci_uart_register_device(hu, &llp);
|
||
|
+}
|
||
|
+
|
||
|
+static void hci_ti_remove(struct serdev_device *serdev)
|
||
|
+{
|
||
|
+ struct ll_device *lldev = serdev_device_get_drvdata(serdev);
|
||
|
+ struct hci_uart *hu = &lldev->hu;
|
||
|
+ struct hci_dev *hdev = hu->hdev;
|
||
|
+
|
||
|
+ cancel_work_sync(&hu->write_work);
|
||
|
+
|
||
|
+ hci_unregister_dev(hdev);
|
||
|
+ hci_free_dev(hdev);
|
||
|
+ hu->proto->close(hu);
|
||
|
+}
|
||
|
+
|
||
|
+static const struct of_device_id hci_ti_of_match[] = {
|
||
|
+ { .compatible = "ti,wl1831-st" },
|
||
|
+ { .compatible = "ti,wl1835-st" },
|
||
|
+ { .compatible = "ti,wl1837-st" },
|
||
|
+ {},
|
||
|
+};
|
||
|
+MODULE_DEVICE_TABLE(of, hci_ti_of_match);
|
||
|
+
|
||
|
+static struct serdev_device_driver hci_ti_drv = {
|
||
|
+ .driver = {
|
||
|
+ .name = "hci-ti",
|
||
|
+ .of_match_table = of_match_ptr(hci_ti_of_match),
|
||
|
+ },
|
||
|
+ .probe = hci_ti_probe,
|
||
|
+ .remove = hci_ti_remove,
|
||
|
+};
|
||
|
+#else
|
||
|
+#define ll_setup NULL
|
||
|
+#endif
|
||
|
+
|
||
|
static const struct hci_uart_proto llp = {
|
||
|
.id = HCI_UART_LL,
|
||
|
.name = "LL",
|
||
|
+ .setup = ll_setup,
|
||
|
.open = ll_open,
|
||
|
.close = ll_close,
|
||
|
.recv = ll_recv,
|
||
|
@@ -518,10 +773,14 @@ static const struct hci_uart_proto llp = {
|
||
|
|
||
|
int __init ll_init(void)
|
||
|
{
|
||
|
+ serdev_device_driver_register(&hci_ti_drv);
|
||
|
+
|
||
|
return hci_uart_register_proto(&llp);
|
||
|
}
|
||
|
|
||
|
int __exit ll_deinit(void)
|
||
|
{
|
||
|
+ serdev_device_driver_unregister(&hci_ti_drv);
|
||
|
+
|
||
|
return hci_uart_unregister_proto(&llp);
|
||
|
}
|
||
|
--
|
||
|
2.12.2
|
||
|
|
||
|
From 33bbcaa7b1a4639a5e149e725a674324e7487b17 Mon Sep 17 00:00:00 2001
|
||
|
From: Rob Herring <robh@kernel.org>
|
||
|
Date: Mon, 21 Nov 2016 15:21:56 -0600
|
||
|
Subject: [PATCH 13/13] arm64: dts: hikey: add WL1835 Bluetooth device node
|
||
|
|
||
|
This adds the serial slave device for the WL1835 Bluetooth interface.
|
||
|
|
||
|
Signed-off-by: Rob Herring <robh@kernel.org>
|
||
|
Cc: Wei Xu <xuwei5@hisilicon.com>
|
||
|
Cc: Mark Rutland <mark.rutland@arm.com>
|
||
|
---
|
||
|
arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts | 5 +++++
|
||
|
1 file changed, 5 insertions(+)
|
||
|
|
||
|
diff --git a/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts b/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts
|
||
|
index dba3c131c62c..9b4ba7169210 100644
|
||
|
--- a/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts
|
||
|
+++ b/arch/arm64/boot/dts/hisilicon/hi6220-hikey.dts
|
||
|
@@ -98,6 +98,11 @@
|
||
|
assigned-clocks = <&sys_ctrl HI6220_UART1_SRC>;
|
||
|
assigned-clock-rates = <150000000>;
|
||
|
status = "ok";
|
||
|
+
|
||
|
+ bluetooth {
|
||
|
+ compatible = "ti,wl1835-st";
|
||
|
+ enable-gpios = <&gpio1 7 GPIO_ACTIVE_HIGH>;
|
||
|
+ };
|
||
|
};
|
||
|
|
||
|
uart2: uart@f7112000 {
|
||
|
--
|
||
|
2.12.2
|
||
|
|