Skip to content

Commit

Permalink
Bluetooth: hci_qca: Add serdev support
Browse files Browse the repository at this point in the history
Add support for Qualcomm serial slave devices. Probe the serial device,
retrieve its maximum speed and register a new hci uart device.

Signed-off-by: Thierry Escande <thierry.escande@linaro.org>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
  • Loading branch information
Thierry Escande authored and Marcel Holtmann committed May 18, 2018
1 parent 52b0900 commit 05ba533
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 2 deletions.
1 change: 1 addition & 0 deletions drivers/bluetooth/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ config BT_HCIUART_BCM
config BT_HCIUART_QCA
bool "Qualcomm Atheros protocol support"
depends on BT_HCIUART
depends on BT_HCIUART_SERDEV
select BT_HCIUART_H4
select BT_QCA
help
Expand Down
110 changes: 108 additions & 2 deletions drivers/bluetooth/hci_qca.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@
*/

#include <linux/kernel.h>
#include <linux/clk.h>
#include <linux/debugfs.h>
#include <linux/gpio/consumer.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/serdev.h>

#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
Expand All @@ -50,6 +55,9 @@
#define IBS_TX_IDLE_TIMEOUT_MS 2000
#define BAUDRATE_SETTLE_TIMEOUT_MS 300

/* susclk rate */
#define SUSCLK_RATE_32KHZ 32768

/* HCI_IBS transmit side sleep protocol states */
enum tx_ibs_states {
HCI_IBS_TX_ASLEEP,
Expand Down Expand Up @@ -111,6 +119,12 @@ struct qca_data {
u64 votes_off;
};

struct qca_serdev {
struct hci_uart serdev_hu;
struct gpio_desc *bt_en;
struct clk *susclk;
};

static void __serial_clock_on(struct tty_struct *tty)
{
/* TODO: Some chipset requires to enable UART clock on client
Expand Down Expand Up @@ -386,6 +400,7 @@ static void hci_ibs_wake_retrans_timeout(struct timer_list *t)
/* Initialize protocol */
static int qca_open(struct hci_uart *hu)
{
struct qca_serdev *qcadev;
struct qca_data *qca;

BT_DBG("hu %p qca_open", hu);
Expand Down Expand Up @@ -444,6 +459,13 @@ static int qca_open(struct hci_uart *hu)
timer_setup(&qca->tx_idle_timer, hci_ibs_tx_idle_timeout, 0);
qca->tx_idle_delay = IBS_TX_IDLE_TIMEOUT_MS;

if (hu->serdev) {
serdev_device_open(hu->serdev);

qcadev = serdev_device_get_drvdata(hu->serdev);
gpiod_set_value_cansleep(qcadev->bt_en, 1);
}

BT_DBG("HCI_UART_QCA open, tx_idle_delay=%u, wake_retrans=%u",
qca->tx_idle_delay, qca->wake_retrans);

Expand Down Expand Up @@ -512,6 +534,7 @@ static int qca_flush(struct hci_uart *hu)
/* Close protocol */
static int qca_close(struct hci_uart *hu)
{
struct qca_serdev *qcadev;
struct qca_data *qca = hu->priv;

BT_DBG("hu %p qca close", hu);
Expand All @@ -525,6 +548,13 @@ static int qca_close(struct hci_uart *hu)
destroy_workqueue(qca->workqueue);
qca->hu = NULL;

if (hu->serdev) {
serdev_device_close(hu->serdev);

qcadev = serdev_device_get_drvdata(hu->serdev);
gpiod_set_value_cansleep(qcadev->bt_en, 0);
}

kfree_skb(qca->rx_skb);

hu->priv = NULL;
Expand Down Expand Up @@ -885,6 +915,14 @@ static int qca_set_baudrate(struct hci_dev *hdev, uint8_t baudrate)
return 0;
}

static inline void host_set_baudrate(struct hci_uart *hu, unsigned int speed)
{
if (hu->serdev)
serdev_device_set_baudrate(hu->serdev, speed);
else
hci_uart_set_baudrate(hu, speed);
}

static int qca_setup(struct hci_uart *hu)
{
struct hci_dev *hdev = hu->hdev;
Expand All @@ -905,7 +943,7 @@ static int qca_setup(struct hci_uart *hu)
speed = hu->proto->init_speed;

if (speed)
hci_uart_set_baudrate(hu, speed);
host_set_baudrate(hu, speed);

/* Setup user speed if needed */
speed = 0;
Expand All @@ -924,7 +962,7 @@ static int qca_setup(struct hci_uart *hu)
ret);
return ret;
}
hci_uart_set_baudrate(hu, speed);
host_set_baudrate(hu, speed);
}

/* Setup patch / NVM configurations */
Expand Down Expand Up @@ -964,12 +1002,80 @@ static struct hci_uart_proto qca_proto = {
.dequeue = qca_dequeue,
};

static int qca_serdev_probe(struct serdev_device *serdev)
{
struct qca_serdev *qcadev;
int err;

qcadev = devm_kzalloc(&serdev->dev, sizeof(*qcadev), GFP_KERNEL);
if (!qcadev)
return -ENOMEM;

qcadev->serdev_hu.serdev = serdev;
serdev_device_set_drvdata(serdev, qcadev);

qcadev->bt_en = devm_gpiod_get(&serdev->dev, "enable",
GPIOD_OUT_LOW);
if (IS_ERR(qcadev->bt_en)) {
dev_err(&serdev->dev, "failed to acquire enable gpio\n");
return PTR_ERR(qcadev->bt_en);
}

qcadev->susclk = devm_clk_get(&serdev->dev, NULL);
if (IS_ERR(qcadev->susclk)) {
dev_err(&serdev->dev, "failed to acquire clk\n");
return PTR_ERR(qcadev->susclk);
}

err = clk_set_rate(qcadev->susclk, SUSCLK_RATE_32KHZ);
if (err)
return err;

err = clk_prepare_enable(qcadev->susclk);
if (err)
return err;

err = hci_uart_register_device(&qcadev->serdev_hu, &qca_proto);
if (err)
clk_disable_unprepare(qcadev->susclk);

return err;
}

static void qca_serdev_remove(struct serdev_device *serdev)
{
struct qca_serdev *qcadev = serdev_device_get_drvdata(serdev);

hci_uart_unregister_device(&qcadev->serdev_hu);

clk_disable_unprepare(qcadev->susclk);
}

static const struct of_device_id qca_bluetooth_of_match[] = {
{ .compatible = "qcom,qca6174-bt" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, qca_bluetooth_of_match);

static struct serdev_device_driver qca_serdev_driver = {
.probe = qca_serdev_probe,
.remove = qca_serdev_remove,
.driver = {
.name = "hci_uart_qca",
.of_match_table = qca_bluetooth_of_match,
},
};

int __init qca_init(void)
{
serdev_device_driver_register(&qca_serdev_driver);

return hci_uart_register_proto(&qca_proto);
}

int __exit qca_deinit(void)
{
serdev_device_driver_unregister(&qca_serdev_driver);

return hci_uart_unregister_proto(&qca_proto);
}

0 comments on commit 05ba533

Please sign in to comment.