]> git.karo-electronics.de Git - linux-beck.git/blobdiff - drivers/acpi/acpi_lpss.c
Merge tag 'pm+acpi-3.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael...
[linux-beck.git] / drivers / acpi / acpi_lpss.c
index d79c6d7f598e618a028187a130dbe94efa238823..63407d264885a200db03d8b152d53efcfae13cbe 100644 (file)
 #include <linux/platform_device.h>
 #include <linux/platform_data/clk-lpss.h>
 #include <linux/pm_runtime.h>
+#include <linux/delay.h>
 
 #include "internal.h"
 
 ACPI_MODULE_NAME("acpi_lpss");
 
+#ifdef CONFIG_X86_INTEL_LPSS
+
+#define LPSS_ADDR(desc) ((unsigned long)&desc)
+
 #define LPSS_CLK_SIZE  0x04
 #define LPSS_LTR_SIZE  0x18
 
 /* Offsets relative to LPSS_PRIVATE_OFFSET */
+#define LPSS_CLK_DIVIDER_DEF_MASK      (BIT(1) | BIT(16))
 #define LPSS_GENERAL                   0x08
 #define LPSS_GENERAL_LTR_MODE_SW       BIT(2)
 #define LPSS_GENERAL_UART_RTS_OVRD     BIT(3)
@@ -43,6 +49,8 @@ ACPI_MODULE_NAME("acpi_lpss");
 #define LPSS_TX_INT                    0x20
 #define LPSS_TX_INT_MASK               BIT(1)
 
+#define LPSS_PRV_REG_COUNT             9
+
 struct lpss_shared_clock {
        const char *name;
        unsigned long rate;
@@ -57,7 +65,9 @@ struct lpss_device_desc {
        bool ltr_required;
        unsigned int prv_offset;
        size_t prv_size_override;
+       bool clk_divider;
        bool clk_gate;
+       bool save_ctx;
        struct lpss_shared_clock *shared_clock;
        void (*setup)(struct lpss_private_data *pdata);
 };
@@ -72,6 +82,7 @@ struct lpss_private_data {
        resource_size_t mmio_size;
        struct clk *clk;
        const struct lpss_device_desc *dev_desc;
+       u32 prv_reg_ctx[LPSS_PRV_REG_COUNT];
 };
 
 static void lpss_uart_setup(struct lpss_private_data *pdata)
@@ -89,6 +100,14 @@ static void lpss_uart_setup(struct lpss_private_data *pdata)
 }
 
 static struct lpss_device_desc lpt_dev_desc = {
+       .clk_required = true,
+       .prv_offset = 0x800,
+       .ltr_required = true,
+       .clk_divider = true,
+       .clk_gate = true,
+};
+
+static struct lpss_device_desc lpt_i2c_dev_desc = {
        .clk_required = true,
        .prv_offset = 0x800,
        .ltr_required = true,
@@ -99,6 +118,7 @@ static struct lpss_device_desc lpt_uart_dev_desc = {
        .clk_required = true,
        .prv_offset = 0x800,
        .ltr_required = true,
+       .clk_divider = true,
        .clk_gate = true,
        .setup = lpss_uart_setup,
 };
@@ -116,32 +136,25 @@ static struct lpss_shared_clock pwm_clock = {
 
 static struct lpss_device_desc byt_pwm_dev_desc = {
        .clk_required = true,
+       .save_ctx = true,
        .shared_clock = &pwm_clock,
 };
 
-static struct lpss_shared_clock uart_clock = {
-       .name = "uart_clk",
-       .rate = 44236800,
-};
-
 static struct lpss_device_desc byt_uart_dev_desc = {
        .clk_required = true,
        .prv_offset = 0x800,
+       .clk_divider = true,
        .clk_gate = true,
-       .shared_clock = &uart_clock,
+       .save_ctx = true,
        .setup = lpss_uart_setup,
 };
 
-static struct lpss_shared_clock spi_clock = {
-       .name = "spi_clk",
-       .rate = 50000000,
-};
-
 static struct lpss_device_desc byt_spi_dev_desc = {
        .clk_required = true,
        .prv_offset = 0x400,
+       .clk_divider = true,
        .clk_gate = true,
-       .shared_clock = &spi_clock,
+       .save_ctx = true,
 };
 
 static struct lpss_device_desc byt_sdio_dev_desc = {
@@ -156,44 +169,53 @@ static struct lpss_shared_clock i2c_clock = {
 static struct lpss_device_desc byt_i2c_dev_desc = {
        .clk_required = true,
        .prv_offset = 0x800,
+       .save_ctx = true,
        .shared_clock = &i2c_clock,
 };
 
+#else
+
+#define LPSS_ADDR(desc) (0UL)
+
+#endif /* CONFIG_X86_INTEL_LPSS */
+
 static const struct acpi_device_id acpi_lpss_device_ids[] = {
        /* Generic LPSS devices */
-       { "INTL9C60", (unsigned long)&lpss_dma_desc },
+       { "INTL9C60", LPSS_ADDR(lpss_dma_desc) },
 
        /* Lynxpoint LPSS devices */
-       { "INT33C0", (unsigned long)&lpt_dev_desc },
-       { "INT33C1", (unsigned long)&lpt_dev_desc },
-       { "INT33C2", (unsigned long)&lpt_dev_desc },
-       { "INT33C3", (unsigned long)&lpt_dev_desc },
-       { "INT33C4", (unsigned long)&lpt_uart_dev_desc },
-       { "INT33C5", (unsigned long)&lpt_uart_dev_desc },
-       { "INT33C6", (unsigned long)&lpt_sdio_dev_desc },
+       { "INT33C0", LPSS_ADDR(lpt_dev_desc) },
+       { "INT33C1", LPSS_ADDR(lpt_dev_desc) },
+       { "INT33C2", LPSS_ADDR(lpt_i2c_dev_desc) },
+       { "INT33C3", LPSS_ADDR(lpt_i2c_dev_desc) },
+       { "INT33C4", LPSS_ADDR(lpt_uart_dev_desc) },
+       { "INT33C5", LPSS_ADDR(lpt_uart_dev_desc) },
+       { "INT33C6", LPSS_ADDR(lpt_sdio_dev_desc) },
        { "INT33C7", },
 
        /* BayTrail LPSS devices */
-       { "80860F09", (unsigned long)&byt_pwm_dev_desc },
-       { "80860F0A", (unsigned long)&byt_uart_dev_desc },
-       { "80860F0E", (unsigned long)&byt_spi_dev_desc },
-       { "80860F14", (unsigned long)&byt_sdio_dev_desc },
-       { "80860F41", (unsigned long)&byt_i2c_dev_desc },
+       { "80860F09", LPSS_ADDR(byt_pwm_dev_desc) },
+       { "80860F0A", LPSS_ADDR(byt_uart_dev_desc) },
+       { "80860F0E", LPSS_ADDR(byt_spi_dev_desc) },
+       { "80860F14", LPSS_ADDR(byt_sdio_dev_desc) },
+       { "80860F41", LPSS_ADDR(byt_i2c_dev_desc) },
        { "INT33B2", },
        { "INT33FC", },
 
-       { "INT3430", (unsigned long)&lpt_dev_desc },
-       { "INT3431", (unsigned long)&lpt_dev_desc },
-       { "INT3432", (unsigned long)&lpt_dev_desc },
-       { "INT3433", (unsigned long)&lpt_dev_desc },
-       { "INT3434", (unsigned long)&lpt_uart_dev_desc },
-       { "INT3435", (unsigned long)&lpt_uart_dev_desc },
-       { "INT3436", (unsigned long)&lpt_sdio_dev_desc },
+       { "INT3430", LPSS_ADDR(lpt_dev_desc) },
+       { "INT3431", LPSS_ADDR(lpt_dev_desc) },
+       { "INT3432", LPSS_ADDR(lpt_i2c_dev_desc) },
+       { "INT3433", LPSS_ADDR(lpt_i2c_dev_desc) },
+       { "INT3434", LPSS_ADDR(lpt_uart_dev_desc) },
+       { "INT3435", LPSS_ADDR(lpt_uart_dev_desc) },
+       { "INT3436", LPSS_ADDR(lpt_sdio_dev_desc) },
        { "INT3437", },
 
        { }
 };
 
+#ifdef CONFIG_X86_INTEL_LPSS
+
 static int is_memory(struct acpi_resource *res, void *not_used)
 {
        struct resource r;
@@ -213,9 +235,11 @@ static int register_device_clock(struct acpi_device *adev,
 {
        const struct lpss_device_desc *dev_desc = pdata->dev_desc;
        struct lpss_shared_clock *shared_clock = dev_desc->shared_clock;
+       const char *devname = dev_name(&adev->dev);
        struct clk *clk = ERR_PTR(-ENODEV);
        struct lpss_clk_data *clk_data;
-       const char *parent;
+       const char *parent, *clk_name;
+       void __iomem *prv_base;
 
        if (!lpss_clk_dev)
                lpt_register_clock_device();
@@ -226,7 +250,7 @@ static int register_device_clock(struct acpi_device *adev,
 
        if (dev_desc->clkdev_name) {
                clk_register_clkdev(clk_data->clk, dev_desc->clkdev_name,
-                                   dev_name(&adev->dev));
+                                   devname);
                return 0;
        }
 
@@ -235,6 +259,7 @@ static int register_device_clock(struct acpi_device *adev,
                return -ENODATA;
 
        parent = clk_data->name;
+       prv_base = pdata->mmio_base + dev_desc->prv_offset;
 
        if (shared_clock) {
                clk = shared_clock->clk;
@@ -248,16 +273,41 @@ static int register_device_clock(struct acpi_device *adev,
        }
 
        if (dev_desc->clk_gate) {
-               clk = clk_register_gate(NULL, dev_name(&adev->dev), parent, 0,
-                                       pdata->mmio_base + dev_desc->prv_offset,
-                                       0, 0, NULL);
-               pdata->clk = clk;
+               clk = clk_register_gate(NULL, devname, parent, 0,
+                                       prv_base, 0, 0, NULL);
+               parent = devname;
+       }
+
+       if (dev_desc->clk_divider) {
+               /* Prevent division by zero */
+               if (!readl(prv_base))
+                       writel(LPSS_CLK_DIVIDER_DEF_MASK, prv_base);
+
+               clk_name = kasprintf(GFP_KERNEL, "%s-div", devname);
+               if (!clk_name)
+                       return -ENOMEM;
+               clk = clk_register_fractional_divider(NULL, clk_name, parent,
+                                                     0, prv_base,
+                                                     1, 15, 16, 15, 0, NULL);
+               parent = clk_name;
+
+               clk_name = kasprintf(GFP_KERNEL, "%s-update", devname);
+               if (!clk_name) {
+                       kfree(parent);
+                       return -ENOMEM;
+               }
+               clk = clk_register_gate(NULL, clk_name, parent,
+                                       CLK_SET_RATE_PARENT | CLK_SET_RATE_GATE,
+                                       prv_base, 31, 0, NULL);
+               kfree(parent);
+               kfree(clk_name);
        }
 
        if (IS_ERR(clk))
                return PTR_ERR(clk);
 
-       clk_register_clkdev(clk, NULL, dev_name(&adev->dev));
+       pdata->clk = clk;
+       clk_register_clkdev(clk, NULL, devname);
        return 0;
 }
 
@@ -268,12 +318,14 @@ static int acpi_lpss_create_device(struct acpi_device *adev,
        struct lpss_private_data *pdata;
        struct resource_list_entry *rentry;
        struct list_head resource_list;
+       struct platform_device *pdev;
        int ret;
 
        dev_desc = (struct lpss_device_desc *)id->driver_data;
-       if (!dev_desc)
-               return acpi_create_platform_device(adev, id);
-
+       if (!dev_desc) {
+               pdev = acpi_create_platform_device(adev);
+               return IS_ERR_OR_NULL(pdev) ? PTR_ERR(pdev) : 1;
+       }
        pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
        if (!pdata)
                return -ENOMEM;
@@ -323,10 +375,13 @@ static int acpi_lpss_create_device(struct acpi_device *adev,
                dev_desc->setup(pdata);
 
        adev->driver_data = pdata;
-       ret = acpi_create_platform_device(adev, id);
-       if (ret > 0)
-               return ret;
+       pdev = acpi_create_platform_device(adev);
+       if (!IS_ERR_OR_NULL(pdev)) {
+               device_enable_async_suspend(&pdev->dev);
+               return 1;
+       }
 
+       ret = PTR_ERR(pdev);
        adev->driver_data = NULL;
 
  err_out:
@@ -450,6 +505,126 @@ static void acpi_lpss_set_ltr(struct device *dev, s32 val)
        }
 }
 
+#ifdef CONFIG_PM
+/**
+ * acpi_lpss_save_ctx() - Save the private registers of LPSS device
+ * @dev: LPSS device
+ *
+ * Most LPSS devices have private registers which may loose their context when
+ * the device is powered down. acpi_lpss_save_ctx() saves those registers into
+ * prv_reg_ctx array.
+ */
+static void acpi_lpss_save_ctx(struct device *dev)
+{
+       struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
+       unsigned int i;
+
+       for (i = 0; i < LPSS_PRV_REG_COUNT; i++) {
+               unsigned long offset = i * sizeof(u32);
+
+               pdata->prv_reg_ctx[i] = __lpss_reg_read(pdata, offset);
+               dev_dbg(dev, "saving 0x%08x from LPSS reg at offset 0x%02lx\n",
+                       pdata->prv_reg_ctx[i], offset);
+       }
+}
+
+/**
+ * acpi_lpss_restore_ctx() - Restore the private registers of LPSS device
+ * @dev: LPSS device
+ *
+ * Restores the registers that were previously stored with acpi_lpss_save_ctx().
+ */
+static void acpi_lpss_restore_ctx(struct device *dev)
+{
+       struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
+       unsigned int i;
+
+       /*
+        * The following delay is needed or the subsequent write operations may
+        * fail. The LPSS devices are actually PCI devices and the PCI spec
+        * expects 10ms delay before the device can be accessed after D3 to D0
+        * transition.
+        */
+       msleep(10);
+
+       for (i = 0; i < LPSS_PRV_REG_COUNT; i++) {
+               unsigned long offset = i * sizeof(u32);
+
+               __lpss_reg_write(pdata->prv_reg_ctx[i], pdata, offset);
+               dev_dbg(dev, "restoring 0x%08x to LPSS reg at offset 0x%02lx\n",
+                       pdata->prv_reg_ctx[i], offset);
+       }
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int acpi_lpss_suspend_late(struct device *dev)
+{
+       int ret = pm_generic_suspend_late(dev);
+
+       if (ret)
+               return ret;
+
+       acpi_lpss_save_ctx(dev);
+       return acpi_dev_suspend_late(dev);
+}
+
+static int acpi_lpss_restore_early(struct device *dev)
+{
+       int ret = acpi_dev_resume_early(dev);
+
+       if (ret)
+               return ret;
+
+       acpi_lpss_restore_ctx(dev);
+       return pm_generic_resume_early(dev);
+}
+#endif /* CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_PM_RUNTIME
+static int acpi_lpss_runtime_suspend(struct device *dev)
+{
+       int ret = pm_generic_runtime_suspend(dev);
+
+       if (ret)
+               return ret;
+
+       acpi_lpss_save_ctx(dev);
+       return acpi_dev_runtime_suspend(dev);
+}
+
+static int acpi_lpss_runtime_resume(struct device *dev)
+{
+       int ret = acpi_dev_runtime_resume(dev);
+
+       if (ret)
+               return ret;
+
+       acpi_lpss_restore_ctx(dev);
+       return pm_generic_runtime_resume(dev);
+}
+#endif /* CONFIG_PM_RUNTIME */
+#endif /* CONFIG_PM */
+
+static struct dev_pm_domain acpi_lpss_pm_domain = {
+       .ops = {
+#ifdef CONFIG_PM_SLEEP
+               .suspend_late = acpi_lpss_suspend_late,
+               .restore_early = acpi_lpss_restore_early,
+               .prepare = acpi_subsys_prepare,
+               .complete = acpi_subsys_complete,
+               .suspend = acpi_subsys_suspend,
+               .resume_early = acpi_subsys_resume_early,
+               .freeze = acpi_subsys_freeze,
+               .poweroff = acpi_subsys_suspend,
+               .poweroff_late = acpi_subsys_suspend_late,
+#endif
+#ifdef CONFIG_PM_RUNTIME
+               .runtime_suspend = acpi_lpss_runtime_suspend,
+               .runtime_resume = acpi_lpss_runtime_resume,
+#endif
+       },
+};
+
 static int acpi_lpss_platform_notify(struct notifier_block *nb,
                                     unsigned long action, void *data)
 {
@@ -457,7 +632,6 @@ static int acpi_lpss_platform_notify(struct notifier_block *nb,
        struct lpss_private_data *pdata;
        struct acpi_device *adev;
        const struct acpi_device_id *id;
-       int ret = 0;
 
        id = acpi_match_device(acpi_lpss_device_ids, &pdev->dev);
        if (!id || !id->driver_data)
@@ -467,7 +641,7 @@ static int acpi_lpss_platform_notify(struct notifier_block *nb,
                return 0;
 
        pdata = acpi_driver_data(adev);
-       if (!pdata || !pdata->mmio_base || !pdata->dev_desc->ltr_required)
+       if (!pdata || !pdata->mmio_base)
                return 0;
 
        if (pdata->mmio_size < pdata->dev_desc->prv_offset + LPSS_LTR_SIZE) {
@@ -475,12 +649,27 @@ static int acpi_lpss_platform_notify(struct notifier_block *nb,
                return 0;
        }
 
-       if (action == BUS_NOTIFY_ADD_DEVICE)
-               ret = sysfs_create_group(&pdev->dev.kobj, &lpss_attr_group);
-       else if (action == BUS_NOTIFY_DEL_DEVICE)
-               sysfs_remove_group(&pdev->dev.kobj, &lpss_attr_group);
+       switch (action) {
+       case BUS_NOTIFY_BOUND_DRIVER:
+               if (pdata->dev_desc->save_ctx)
+                       pdev->dev.pm_domain = &acpi_lpss_pm_domain;
+               break;
+       case BUS_NOTIFY_UNBOUND_DRIVER:
+               if (pdata->dev_desc->save_ctx)
+                       pdev->dev.pm_domain = NULL;
+               break;
+       case BUS_NOTIFY_ADD_DEVICE:
+               if (pdata->dev_desc->ltr_required)
+                       return sysfs_create_group(&pdev->dev.kobj,
+                                                 &lpss_attr_group);
+       case BUS_NOTIFY_DEL_DEVICE:
+               if (pdata->dev_desc->ltr_required)
+                       sysfs_remove_group(&pdev->dev.kobj, &lpss_attr_group);
+       default:
+               break;
+       }
 
-       return ret;
+       return 0;
 }
 
 static struct notifier_block acpi_lpss_nb = {
@@ -519,3 +708,16 @@ void __init acpi_lpss_init(void)
                acpi_scan_add_handler(&lpss_handler);
        }
 }
+
+#else
+
+static struct acpi_scan_handler lpss_handler = {
+       .ids = acpi_lpss_device_ids,
+};
+
+void __init acpi_lpss_init(void)
+{
+       acpi_scan_add_handler(&lpss_handler);
+}
+
+#endif /* CONFIG_X86_INTEL_LPSS */