Skip to content

Commit

Permalink
Bluetooth: hci_bcm: Support Apple GPIO handling
Browse files Browse the repository at this point in the history
Enable Bluetooth on the following Macs which do not specify the GPIOs
for device wakeup and shutdown in the DSDT but instead provide custom
ACPI methods to toggle the pins:

    MacBook8,1     2015  12"
    MacBook9,1     2016  12"
    MacBook10,1    2017  12"
    MacBookPro13,1 2016  13"
    MacBookPro13,2 2016  13" with Touch	Bar
    MacBookPro13,3 2016  15" with Touch	Bar
    MacBookPro14,1 2017  13"
    MacBookPro14,2 2017  13" with Touch	Bar
    MacBookPro14,3 2017  15" with Touch	Bar

The custom ACPI methods are called:

    BTLP (Low Power) takes one argument, toggles device wakeup GPIO
    BTPU (Power Up) tells SMC to drive shutdown GPIO high
    BTPD (Power Down) tells SMC to drive shutdown GPIO low
    BTRS (Reset) calls BTPU followed by BTPD
    BTRB unknown, not present on all MacBooks

Search for the BTLP, BTPU and BTPD methods on ->probe, cache them in
struct bcm_device and invoke them instead of toggling the GPIO pins
if the machine is a Mac.

Additionally, set the baudrate based on a custom device property
provided by Apple in lieu of _CRS resources.

The host wakeup pin goes into the SMC which handles it transparently to
the OS, so there's no need to request an IRQ for it.

The existing code is a bit fishy as it ignores the return value of
bcm_gpio_set_power(), even though portions of it may fail (such as
enabling the clock).  The present commit returns an error if calling
the ACPI methods fails and the code needs to be fixed up in a separate
commit to evaluate the return value of bcm_gpio_set_power() and
bcm_gpio_set_device_wake(), as well as check for failure of clock
enablement and so on.

Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=110901
Reported-by: Leif Liddy <leif.liddy@gmail.com>
Cc: Ronald Tschalär <ronald@innovation.ch>
Cc: Peter Y. Chuang <peter@novelist.xyz>
Cc: Mika Westerberg <mika.westerberg@linux.intel.com>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Lukas Wunner <lukas@wunner.de>
  • Loading branch information
l1k committed Aug 13, 2017
1 parent 4c70527 commit 8883d62
Showing 1 changed file with 67 additions and 6 deletions.
73 changes: 67 additions & 6 deletions drivers/bluetooth/hci_bcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <linux/firmware.h>
#include <linux/module.h>
#include <linux/acpi.h>
#include <linux/platform_data/x86/apple.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/gpio/consumer.h>
Expand Down Expand Up @@ -58,14 +59,17 @@ struct bcm_device {
struct clk *clk;
bool clk_enabled;

u32 init_speed;
u32 init_speed, oper_speed;
int irq;
u8 irq_polarity;

#ifdef CONFIG_PM
struct hci_uart *hu;
bool is_suspended; /* suspend/resume flag */
#endif
#ifdef CONFIG_ACPI
acpi_handle btlp, btpu, btpd;
#endif
};

struct bcm_data {
Expand All @@ -79,6 +83,42 @@ struct bcm_data {
static DEFINE_MUTEX(bcm_device_lock);
static LIST_HEAD(bcm_device_list);

#ifdef CONFIG_ACPI
static int bcm_apple_set_power(struct bcm_device *dev, bool enable)
{
return ACPI_SUCCESS(acpi_evaluate_object(enable ? dev->btpu : dev->btpd,
NULL, NULL, NULL));
}

static int bcm_apple_set_device_wake(struct bcm_device *dev, bool enable)
{
return ACPI_SUCCESS(acpi_execute_simple_method(dev->btlp,
NULL, enable));
}

static bool bcm_apple_probe(struct bcm_device *dev)
{
struct acpi_device *adev = ACPI_COMPANION(&dev->pdev->dev);
acpi_handle dev_handle = adev->handle;
const union acpi_object *obj;

if (!acpi_dev_get_property(adev, "baud", ACPI_TYPE_BUFFER, &obj) &&
obj->buffer.length == 8)
dev->oper_speed = *(u64 *)obj->buffer.pointer;

return ACPI_SUCCESS(acpi_get_handle(dev_handle, "BTLP", &dev->btlp)) &&
ACPI_SUCCESS(acpi_get_handle(dev_handle, "BTPU", &dev->btpu)) &&
ACPI_SUCCESS(acpi_get_handle(dev_handle, "BTPD", &dev->btpd));
}
#else
static inline int bcm_apple_set_power(struct bcm_device *dev, bool powered)
{ return -EINVAL; }
static inline int bcm_apple_set_device_wake(struct bcm_device *dev, bool enable)
{ return -EINVAL; }
static inline bool bcm_apple_probe(struct bcm_device *dev)
{ return -EINVAL; }
#endif

static int bcm_set_baudrate(struct hci_uart *hu, unsigned int speed)
{
struct hci_dev *hdev = hu->hdev;
Expand Down Expand Up @@ -145,11 +185,19 @@ static bool bcm_device_exists(struct bcm_device *device)

static int bcm_gpio_set_power(struct bcm_device *dev, bool powered)
{
int err;

if (powered && !IS_ERR(dev->clk) && !dev->clk_enabled)
clk_prepare_enable(dev->clk);

gpiod_set_value(dev->shutdown, powered);
gpiod_set_value(dev->device_wakeup, powered);
if (!x86_apple_machine) {
gpiod_set_value(dev->shutdown, powered);
gpiod_set_value(dev->device_wakeup, powered);
} else {
err = bcm_apple_set_power(dev, powered);
if (err)
return err;
}

if (!powered && !IS_ERR(dev->clk) && dev->clk_enabled)
clk_disable_unprepare(dev->clk);
Expand All @@ -159,6 +207,16 @@ static int bcm_gpio_set_power(struct bcm_device *dev, bool powered)
return 0;
}

static int bcm_gpio_set_device_wake(struct bcm_device *dev, bool enable)
{
if (!x86_apple_machine) {
gpiod_set_value(dev->device_wakeup, enable);
return 0;
} else {
return bcm_apple_set_device_wake(dev, enable);
}
}

#ifdef CONFIG_PM
static irqreturn_t bcm_host_wake(int irq, void *data)
{
Expand Down Expand Up @@ -301,6 +359,7 @@ static int bcm_open(struct hci_uart *hu)
if (hu->tty->dev->parent == dev->pdev->dev.parent) {
bcm->dev = dev;
hu->init_speed = dev->init_speed;
hu->oper_speed = dev->oper_speed;
#ifdef CONFIG_PM
dev->hu = hu;
#endif
Expand Down Expand Up @@ -522,7 +581,7 @@ static int bcm_suspend_device(struct device *dev)

/* Suspend the device */
if (bdev->device_wakeup) {
gpiod_set_value(bdev->device_wakeup, false);
bcm_gpio_set_device_wake(bdev, false);
bt_dev_dbg(bdev, "suspend, delaying 15 ms");
mdelay(15);
}
Expand All @@ -537,7 +596,7 @@ static int bcm_resume_device(struct device *dev)
bt_dev_dbg(bdev, "");

if (bdev->device_wakeup) {
gpiod_set_value(bdev->device_wakeup, true);
bcm_gpio_set_device_wake(bdev, true);
bt_dev_dbg(bdev, "resume, delaying 15 ms");
mdelay(15);
}
Expand Down Expand Up @@ -747,7 +806,9 @@ static int bcm_platform_probe(struct bcm_device *dev)
/* Make sure at-least one of the GPIO is defined and that
* a name is specified for this instance
*/
if ((!dev->device_wakeup && !dev->shutdown) || !dev->name) {
if ((!dev->device_wakeup && !dev->shutdown &&
(!x86_apple_machine || !bcm_apple_probe(dev))) ||
!dev->name) {
dev_err(&pdev->dev, "invalid platform data\n");
return -EINVAL;
}
Expand Down

0 comments on commit 8883d62

Please sign in to comment.