#include <linux/errno.h>
 #include <linux/skbuff.h>
 #include <linux/firmware.h>
+#include <linux/module.h>
 #include <linux/wait.h>
+#include <linux/tty.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/acpi.h>
 
 #include <net/bluetooth/bluetooth.h>
 #include <net/bluetooth/hci_core.h>
 #define STATE_FIRMWARE_FAILED  3
 #define STATE_BOOTING          4
 
+struct intel_device {
+       struct list_head list;
+       struct platform_device *pdev;
+       struct gpio_desc *reset;
+};
+
+static LIST_HEAD(intel_device_list);
+static DEFINE_SPINLOCK(intel_device_list_lock);
+
 struct intel_data {
        struct sk_buff *rx_skb;
        struct sk_buff_head txq;
        }
 }
 
+static int intel_wait_booting(struct hci_uart *hu)
+{
+       struct intel_data *intel = hu->priv;
+       int err;
+
+       err = wait_on_bit_timeout(&intel->flags, STATE_BOOTING,
+                                 TASK_INTERRUPTIBLE,
+                                 msecs_to_jiffies(1000));
+
+       if (err == 1) {
+               BT_ERR("%s: Device boot interrupted", hu->hdev->name);
+               return -EINTR;
+       }
+
+       if (err) {
+               BT_ERR("%s: Device boot timeout", hu->hdev->name);
+               return -ETIMEDOUT;
+       }
+
+       return err;
+}
+
+static int intel_set_power(struct hci_uart *hu, bool powered)
+{
+       struct list_head *p;
+       int err = -ENODEV;
+
+       spin_lock(&intel_device_list_lock);
+
+       list_for_each(p, &intel_device_list) {
+               struct intel_device *idev = list_entry(p, struct intel_device,
+                                                      list);
+
+               /* tty device and pdev device should share the same parent
+                * which is the UART port.
+                */
+               if (hu->tty->dev->parent != idev->pdev->dev.parent)
+                       continue;
+
+               if (!idev->reset) {
+                       err = -ENOTSUPP;
+                       break;
+               }
+
+               BT_INFO("hu %p, Switching compatible pm device (%s) to %u",
+                       hu, dev_name(&idev->pdev->dev), powered);
+
+               gpiod_set_value(idev->reset, powered);
+       }
+
+       spin_unlock(&intel_device_list_lock);
+
+       return err;
+}
+
 static int intel_open(struct hci_uart *hu)
 {
        struct intel_data *intel;
        skb_queue_head_init(&intel->txq);
 
        hu->priv = intel;
+
+       if (!intel_set_power(hu, true))
+               set_bit(STATE_BOOTING, &intel->flags);
+
        return 0;
 }
 
 
        BT_DBG("hu %p", hu);
 
+       intel_set_power(hu, false);
+
        skb_queue_purge(&intel->txq);
        kfree_skb(intel->rx_skb);
        kfree(intel);
        struct hci_dev *hdev = hu->hdev;
        u8 speed_cmd[] = { 0x06, 0xfc, 0x01, 0x00 };
        struct sk_buff *skb;
+       int err;
+
+       /* This can be the first command sent to the chip, check
+        * that the controller is ready.
+        */
+       err = intel_wait_booting(hu);
+
+       clear_bit(STATE_BOOTING, &intel->flags);
+
+       /* In case of timeout, try to continue anyway */
+       if (err && err != ETIMEDOUT)
+               return err;
 
        BT_INFO("%s: Change controller speed to %d", hdev->name, speed);
 
        if (oper_speed && init_speed && oper_speed != init_speed)
                speed_change = 1;
 
+       /* Check that the controller is ready */
+       err = intel_wait_booting(hu);
+
+       clear_bit(STATE_BOOTING, &intel->flags);
+
+       /* In case of timeout, try to continue anyway */
+       if (err && err != ETIMEDOUT)
+               return err;
+
        set_bit(STATE_BOOTLOADER, &intel->flags);
 
        /* Read the Intel version information to determine if the device
         */
        BT_INFO("%s: Waiting for device to boot", hdev->name);
 
-       err = wait_on_bit_timeout(&intel->flags, STATE_BOOTING,
-                                 TASK_INTERRUPTIBLE,
-                                 msecs_to_jiffies(1000));
-
-       if (err == 1) {
-               BT_ERR("%s: Device boot interrupted", hdev->name);
-               return -EINTR;
-       }
+       err = intel_wait_booting(hu);
+       if (err)
+               return err;
 
-       if (err) {
-               BT_ERR("%s: Device boot timeout", hdev->name);
-               return -ETIMEDOUT;
-       }
+       clear_bit(STATE_BOOTING, &intel->flags);
 
        rettime = ktime_get();
        delta = ktime_sub(rettime, calltime);
        struct intel_data *intel = hu->priv;
        struct hci_event_hdr *hdr;
 
-       if (!test_bit(STATE_BOOTLOADER, &intel->flags))
+       if (!test_bit(STATE_BOOTLOADER, &intel->flags) &&
+           !test_bit(STATE_BOOTING, &intel->flags))
                goto recv;
 
        hdr = (void *)skb->data;
        .dequeue        = intel_dequeue,
 };
 
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id intel_acpi_match[] = {
+       { "INT33E1", 0 },
+       { },
+};
+MODULE_DEVICE_TABLE(acpi, intel_acpi_match);
+
+static int intel_acpi_probe(struct intel_device *idev)
+{
+       const struct acpi_device_id *id;
+
+       id = acpi_match_device(intel_acpi_match, &idev->pdev->dev);
+       if (!id)
+               return -ENODEV;
+
+       return 0;
+}
+#else
+static int intel_acpi_probe(struct intel_device *idev)
+{
+       return -ENODEV;
+}
+#endif
+
+static int intel_probe(struct platform_device *pdev)
+{
+       struct intel_device *idev;
+
+       idev = devm_kzalloc(&pdev->dev, sizeof(*idev), GFP_KERNEL);
+       if (!idev)
+               return -ENOMEM;
+
+       idev->pdev = pdev;
+
+       if (ACPI_HANDLE(&pdev->dev)) {
+               int err = intel_acpi_probe(idev);
+               if (err)
+                       return err;
+       } else {
+               return -ENODEV;
+       }
+
+       idev->reset = devm_gpiod_get_optional(&pdev->dev, "reset",
+                                             GPIOD_OUT_LOW);
+       if (IS_ERR(idev->reset)) {
+               dev_err(&pdev->dev, "Unable to retrieve gpio\n");
+               return PTR_ERR(idev->reset);
+       }
+
+       platform_set_drvdata(pdev, idev);
+
+       /* Place this instance on the device list */
+       spin_lock(&intel_device_list_lock);
+       list_add_tail(&idev->list, &intel_device_list);
+       spin_unlock(&intel_device_list_lock);
+
+       dev_info(&pdev->dev, "registered.\n");
+
+       return 0;
+}
+
+static int intel_remove(struct platform_device *pdev)
+{
+       struct intel_device *idev = platform_get_drvdata(pdev);
+
+       spin_lock(&intel_device_list_lock);
+       list_del(&idev->list);
+       spin_unlock(&intel_device_list_lock);
+
+       dev_info(&pdev->dev, "unregistered.\n");
+
+       return 0;
+}
+
+static struct platform_driver intel_driver = {
+       .probe = intel_probe,
+       .remove = intel_remove,
+       .driver = {
+               .name = "hci_intel",
+               .acpi_match_table = ACPI_PTR(intel_acpi_match),
+       },
+};
+
 int __init intel_init(void)
 {
+       platform_driver_register(&intel_driver);
+
        return hci_uart_register_proto(&intel_proto);
 }
 
 int __exit intel_deinit(void)
 {
+       platform_driver_unregister(&intel_driver);
+
        return hci_uart_unregister_proto(&intel_proto);
 }