| /* |
| * arch/arm/mach-shmobile/pm_runtime.c |
| * |
| * Runtime PM support code for SuperH Mobile ARM |
| * |
| * Copyright (C) 2009-2010 Magnus Damm |
| * |
| * This file is subject to the terms and conditions of the GNU General Public |
| * License. See the file "COPYING" in the main directory of this archive |
| * for more details. |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/kernel.h> |
| #include <linux/io.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/platform_device.h> |
| #include <linux/clk.h> |
| #include <linux/sh_clk.h> |
| #include <linux/bitmap.h> |
| |
| #ifdef CONFIG_PM_RUNTIME |
| #define BIT_ONCE 0 |
| #define BIT_ACTIVE 1 |
| #define BIT_CLK_ENABLED 2 |
| |
| struct pm_runtime_data { |
| unsigned long flags; |
| struct clk *clk; |
| }; |
| |
| static void __devres_release(struct device *dev, void *res) |
| { |
| struct pm_runtime_data *prd = res; |
| |
| dev_dbg(dev, "__devres_release()\n"); |
| |
| if (test_bit(BIT_CLK_ENABLED, &prd->flags)) |
| clk_disable(prd->clk); |
| |
| if (test_bit(BIT_ACTIVE, &prd->flags)) |
| clk_put(prd->clk); |
| } |
| |
| static struct pm_runtime_data *__to_prd(struct device *dev) |
| { |
| return devres_find(dev, __devres_release, NULL, NULL); |
| } |
| |
| static void platform_pm_runtime_init(struct device *dev, |
| struct pm_runtime_data *prd) |
| { |
| if (prd && !test_and_set_bit(BIT_ONCE, &prd->flags)) { |
| prd->clk = clk_get(dev, NULL); |
| if (!IS_ERR(prd->clk)) { |
| set_bit(BIT_ACTIVE, &prd->flags); |
| dev_info(dev, "clocks managed by runtime pm\n"); |
| } |
| } |
| } |
| |
| static void platform_pm_runtime_bug(struct device *dev, |
| struct pm_runtime_data *prd) |
| { |
| if (prd && !test_and_set_bit(BIT_ONCE, &prd->flags)) |
| dev_err(dev, "runtime pm suspend before resume\n"); |
| } |
| |
| int platform_pm_runtime_suspend(struct device *dev) |
| { |
| struct pm_runtime_data *prd = __to_prd(dev); |
| |
| dev_dbg(dev, "platform_pm_runtime_suspend()\n"); |
| |
| platform_pm_runtime_bug(dev, prd); |
| |
| if (prd && test_bit(BIT_ACTIVE, &prd->flags)) { |
| clk_disable(prd->clk); |
| clear_bit(BIT_CLK_ENABLED, &prd->flags); |
| } |
| |
| return 0; |
| } |
| |
| int platform_pm_runtime_resume(struct device *dev) |
| { |
| struct pm_runtime_data *prd = __to_prd(dev); |
| |
| dev_dbg(dev, "platform_pm_runtime_resume()\n"); |
| |
| platform_pm_runtime_init(dev, prd); |
| |
| if (prd && test_bit(BIT_ACTIVE, &prd->flags)) { |
| clk_enable(prd->clk); |
| set_bit(BIT_CLK_ENABLED, &prd->flags); |
| } |
| |
| return 0; |
| } |
| |
| int platform_pm_runtime_idle(struct device *dev) |
| { |
| /* suspend synchronously to disable clocks immediately */ |
| return pm_runtime_suspend(dev); |
| } |
| |
| static int platform_bus_notify(struct notifier_block *nb, |
| unsigned long action, void *data) |
| { |
| struct device *dev = data; |
| struct pm_runtime_data *prd; |
| |
| dev_dbg(dev, "platform_bus_notify() %ld !\n", action); |
| |
| if (action == BUS_NOTIFY_BIND_DRIVER) { |
| prd = devres_alloc(__devres_release, sizeof(*prd), GFP_KERNEL); |
| if (prd) |
| devres_add(dev, prd); |
| else |
| dev_err(dev, "unable to alloc memory for runtime pm\n"); |
| } |
| |
| return 0; |
| } |
| |
| #else /* CONFIG_PM_RUNTIME */ |
| |
| static int platform_bus_notify(struct notifier_block *nb, |
| unsigned long action, void *data) |
| { |
| struct device *dev = data; |
| struct clk *clk; |
| |
| dev_dbg(dev, "platform_bus_notify() %ld !\n", action); |
| |
| switch (action) { |
| case BUS_NOTIFY_BIND_DRIVER: |
| clk = clk_get(dev, NULL); |
| if (!IS_ERR(clk)) { |
| clk_enable(clk); |
| clk_put(clk); |
| dev_info(dev, "runtime pm disabled, clock forced on\n"); |
| } |
| break; |
| case BUS_NOTIFY_UNBOUND_DRIVER: |
| clk = clk_get(dev, NULL); |
| if (!IS_ERR(clk)) { |
| clk_disable(clk); |
| clk_put(clk); |
| dev_info(dev, "runtime pm disabled, clock forced off\n"); |
| } |
| break; |
| } |
| |
| return 0; |
| } |
| |
| #endif /* CONFIG_PM_RUNTIME */ |
| |
| static struct notifier_block platform_bus_notifier = { |
| .notifier_call = platform_bus_notify |
| }; |
| |
| static int __init sh_pm_runtime_init(void) |
| { |
| bus_register_notifier(&platform_bus_type, &platform_bus_notifier); |
| return 0; |
| } |
| core_initcall(sh_pm_runtime_init); |