--- 0001/arch/sh/include/asm/device.h
+++ work/arch/sh/include/asm/device.h	2009-07-09 18:41:09.000000000 +0900
@@ -3,7 +3,9 @@
  *
  * This file is released under the GPLv2
  */
-#include <asm-generic/device.h>
+
+struct dev_archdata {
+};
 
 struct platform_device;
 /* allocate contiguous memory chunk and fill in struct resource */
@@ -12,3 +14,15 @@ int platform_resource_setup_memory(struc
 
 void plat_early_device_setup(void);
 
+#define PDEV_ARCHDATA_FLAG_INIT 0
+#define PDEV_ARCHDATA_FLAG_LIST 1
+#define PDEV_ARCHDATA_FLAG_SUSP 2
+
+struct pdev_archdata {
+#ifdef CONFIG_ARCH_SHMOBILE
+	int hw_blk_id;
+	unsigned long flags;
+	struct list_head entry;
+	struct work_struct work;
+#endif
+};
--- 0005/arch/sh/include/asm/hwblk.h
+++ work/arch/sh/include/asm/hwblk.h	2009-07-09 18:42:49.000000000 +0900
@@ -5,7 +5,9 @@
 #include <asm/io.h>
 
 #define HWBLK_CNT_USAGE 0
-#define HWBLK_CNT_NR 1
+#define HWBLK_CNT_IDLE 1
+#define HWBLK_CNT_DEVICES 2
+#define HWBLK_CNT_NR 3
 
 #define HWBLK_AREA_FLAG_PARENT (1 << 0) /* valid parent */
 
--- 0001/arch/sh/kernel/cpu/shmobile/Makefile
+++ work/arch/sh/kernel/cpu/shmobile/Makefile	2009-07-09 18:41:12.000000000 +0900
@@ -5,3 +5,4 @@
 # Power Management & Sleep mode
 obj-$(CONFIG_PM)	+= pm.o sleep.o
 obj-$(CONFIG_CPU_IDLE)	+= cpuidle.o
+obj-$(CONFIG_PM_RUNTIME)	+= pm_runtime.o
--- /dev/null
+++ work/arch/sh/kernel/cpu/shmobile/pm_runtime.c	2009-07-09 18:41:12.000000000 +0900
@@ -0,0 +1,206 @@
+/*
+ * arch/sh/kernel/cpu/shmobile/pm_runtime.c
+ *
+ * Runtime PM support code for SuperH Mobile
+ *
+ *  Copyright (C) 2009 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 <asm/hwblk.h>
+
+static DEFINE_SPINLOCK(hwblk_idle_lock);
+static LIST_HEAD(hwblk_idle_list);
+
+extern struct hwblk_info *hwblk_info;
+
+static void platform_pm_runtime_work(struct work_struct *work)
+{
+	struct platform_device *pdev = container_of(work,
+						    struct platform_device,
+						    archdata.work);
+	struct device *dev = &pdev->dev;
+	struct dev_pm_ops *dev_pm_ops = NULL;
+	unsigned long flags;
+	int hwblk = pdev->archdata.hw_blk_id;
+	int ret = 0;
+
+	pr_info("platform_pm_runtime_work() suspending \"%s\" [%d]\n",
+		dev_name(dev), hwblk);
+
+	if (dev->driver && dev->driver->pm)
+		dev_pm_ops = dev->driver->pm;
+
+	if (dev_pm_ops && dev_pm_ops->runtime_suspend) {
+		hwblk_enable(hwblk_info, hwblk);
+		ret = dev_pm_ops->runtime_suspend(dev);
+		hwblk_disable(hwblk_info, hwblk);
+	}
+
+	if (ret)
+		return;
+
+	/* remove device from idle list and decrease idle count */
+	spin_lock_irqsave(&hwblk_idle_lock, flags);
+	if (test_bit(PDEV_ARCHDATA_FLAG_LIST, &pdev->archdata.flags)) {
+		list_del(&pdev->archdata.entry);
+		__clear_bit(PDEV_ARCHDATA_FLAG_LIST, &pdev->archdata.flags);
+		__set_bit(PDEV_ARCHDATA_FLAG_SUSP, &pdev->archdata.flags);
+		hwblk_cnt_dec(hwblk_info, hwblk, HWBLK_CNT_IDLE);
+	}
+	spin_unlock_irqrestore(&hwblk_idle_lock, flags);
+
+	pr_info("platform_pm_runtime_work() suspended \"%s\" [%d]\n",
+		dev_name(dev), hwblk);
+}
+
+void platform_pm_runtime_suspend_idle(void)
+{
+	struct platform_device *pdev;
+	unsigned long flags;
+
+	/* schedule suspend for all devices on the idle list */
+	spin_lock_irqsave(&hwblk_idle_lock, flags);
+	list_for_each_entry(pdev, &hwblk_idle_list, archdata.entry)
+		schedule_work(&pdev->archdata.work);
+	spin_unlock_irqrestore(&hwblk_idle_lock, flags);
+}
+
+int platform_pm_runtime_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	unsigned long flags;
+	int hwblk = pdev->archdata.hw_blk_id;
+
+	if (!hwblk)
+		return -EINVAL;
+
+	pr_info("platform_pm_runtime_suspend() \"%s\" [%d]\n",
+		dev_name(dev), hwblk);
+
+	if (test_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags)) {
+		pr_warning("runtime_pm: driver should start from resume!\n");
+		return -EINVAL;
+	}
+
+	/* disable the clock */
+	hwblk_disable(hwblk_info, hwblk);
+
+	/* increase idle count */
+	hwblk_cnt_inc(hwblk_info, hwblk, HWBLK_CNT_IDLE);
+
+	/* put device on idle list */
+	spin_lock_irqsave(&hwblk_idle_lock, flags);
+	list_add_tail(&pdev->archdata.entry, &hwblk_idle_list);
+	__set_bit(PDEV_ARCHDATA_FLAG_LIST, &pdev->archdata.flags);
+	spin_unlock_irqrestore(&hwblk_idle_lock, flags);
+
+	return 0;
+};
+
+int platform_pm_runtime_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct dev_pm_ops *dev_pm_ops = NULL;
+	unsigned long flags;
+	int hwblk = pdev->archdata.hw_blk_id;
+	int ret = 0;
+
+	if (!hwblk)
+		return -EINVAL;
+
+	pr_info("platform_pm_runtime_resume() \"%s\" [%d]\n",
+		dev_name(dev), hwblk);
+
+	/* remove device from idle list if needed */
+	spin_lock_irqsave(&hwblk_idle_lock, flags);
+	if (test_bit(PDEV_ARCHDATA_FLAG_LIST, &pdev->archdata.flags)) {
+		list_del(&pdev->archdata.entry);
+		__clear_bit(PDEV_ARCHDATA_FLAG_LIST, &pdev->archdata.flags);
+	}
+	spin_unlock_irqrestore(&hwblk_idle_lock, flags);
+
+	/* decrease idle count if needed */
+	if (!test_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags) &&
+	    !test_bit(PDEV_ARCHDATA_FLAG_SUSP, &pdev->archdata.flags))
+		hwblk_cnt_dec(hwblk_info, hwblk, HWBLK_CNT_IDLE);
+
+	__clear_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags);
+
+	/* enable the clock */
+	hwblk_enable(hwblk_info, hwblk);
+
+	/* resume the device if needed */
+	if (test_bit(PDEV_ARCHDATA_FLAG_SUSP, &pdev->archdata.flags)) {
+
+		pr_info("platform_pm_runtime_resume() resuming \"%s\" [%d]\n",
+			dev_name(dev), hwblk);
+
+		if (dev->driver && dev->driver->pm)
+			dev_pm_ops = dev->driver->pm;
+
+		if (dev_pm_ops && dev_pm_ops->runtime_resume)
+			ret = dev_pm_ops->runtime_resume(dev);
+
+		__clear_bit(PDEV_ARCHDATA_FLAG_SUSP, &pdev->archdata.flags);
+	}
+
+	return ret;
+};
+
+static int __devinit platform_bus_notify(struct notifier_block *nb,
+					 unsigned long action, void *data)
+{
+	struct device *dev = data;
+	struct platform_device *pdev = to_platform_device(dev);
+	int hwblk = pdev->archdata.hw_blk_id;
+
+	/* ignore off-chip non-SoC platform devices */
+	if (!hwblk)
+		return 0;
+
+	switch (action) {
+	case BUS_NOTIFY_ADD_DEVICE:
+		INIT_LIST_HEAD(&pdev->archdata.entry);
+		INIT_WORK(&pdev->archdata.work, platform_pm_runtime_work);
+		/* platform devices without drivers should be disabled */
+		hwblk_enable(hwblk_info, hwblk);
+		hwblk_disable(hwblk_info, hwblk);
+		/* make sure driver re-inits itself once */
+		__set_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags);
+		break;
+	/* TODO: add BUS_NOTIFY_BIND_DRIVER and increase idle count */
+	case BUS_NOTIFY_BOUND_DRIVER:
+		/* keep track of number of devices in use per hwblk */
+		hwblk_cnt_inc(hwblk_info, hwblk, HWBLK_CNT_DEVICES);
+		break;
+	case BUS_NOTIFY_UNBOUND_DRIVER:
+		/* keep track of number of devices in use per hwblk */
+		hwblk_cnt_dec(hwblk_info, hwblk, HWBLK_CNT_DEVICES);
+		/* make sure driver re-inits itself once */
+		__set_bit(PDEV_ARCHDATA_FLAG_INIT, &pdev->archdata.flags);
+		break;
+	case BUS_NOTIFY_DEL_DEVICE:
+		break;
+	}
+	return 0;
+}
+
+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;
+}
+
+arch_initcall(sh_pm_runtime_init);
