linux-omap.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 1/1] OMAP3: PM: Add the wakeup source driver
@ 2009-03-03 10:37 Kim Kyuwon
  2009-03-17 21:47 ` Kevin Hilman
  0 siblings, 1 reply; 3+ messages in thread
From: Kim Kyuwon @ 2009-03-03 10:37 UTC (permalink / raw)
  To: OMAP; +Cc: Tony Lindgren, Kevin Hilman, 박경민

Sometimes, it is necessary to find out "what does wake up my board?".
Notifying wake-up source feature may be used to blame unexpected
wake-up events which increase power consumption. And user mode
applications can act smartly according to the wake-up event to
minimize power consumption. This driver uses sysfs interface to give
information to user mode applications like:

cat /sys/power/wakeup_irq
cat /sys/power/wakeup_event

This driver also privides the unified GPIO wake-up source
configuration. specific GPIO settings in the board files are:

/* Wakeup source configuration */
static struct gpio_wake boardname_gpio_wake[] = {
	{ 23,	IRQF_TRIGGER_RISING | IRQF_SHARED,	"BT_WAKEUP",	1},
	{ 24,	IRQF_TRIGGER_RISING | IRQF_SHARED,	"USB_DETECT",	1},
};

static struct omap_wake_platform_data boardname_wake_data = {
	.qpio_wakes		= boardname_gpio_wake,
	.gpio_wake_num		= ARRAY_SIZE(boardname_gpio_wake),
};

static struct platform_device boardname_wakeup = {
	.name			= "omap-wake",
	.id			= -1,
	.dev			= {
		.platform_data	= &boardname_wake_data,
	},
};

Signed-off-by: Kim Kyuwon <q1.kim@samsung.com>
---
 arch/arm/mach-omap2/Makefile           |    1 +
 arch/arm/mach-omap2/irq.c              |   41 +++
 arch/arm/mach-omap2/prcm-common.h      |    4 +
 arch/arm/mach-omap2/prm-regbits-34xx.h |    6 +
 arch/arm/mach-omap2/wake34xx.c         |  470 ++++++++++++++++++++++++++++++++
 arch/arm/plat-omap/Kconfig             |    9 +
 arch/arm/plat-omap/include/mach/irqs.h |    1 +
 arch/arm/plat-omap/include/mach/wake.h |   29 ++
 8 files changed, 561 insertions(+), 0 deletions(-)
 create mode 100644 arch/arm/mach-omap2/wake34xx.c
 create mode 100644 arch/arm/plat-omap/include/mach/wake.h

diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile
index ba65cab..1ab87e7 100644
--- a/arch/arm/mach-omap2/Makefile
+++ b/arch/arm/mach-omap2/Makefile
@@ -25,6 +25,7 @@ obj-$(CONFIG_ARCH_OMAP2)		+= pm24xx.o
 obj-$(CONFIG_ARCH_OMAP24XX)		+= sleep24xx.o
 obj-$(CONFIG_ARCH_OMAP3)		+= pm34xx.o sleep34xx.o
 obj-$(CONFIG_PM_DEBUG)			+= pm-debug.o
+obj-$(CONFIG_OMAP_WAKE)			+= wake34xx.o
 endif

 # SmartReflex driver
diff --git a/arch/arm/mach-omap2/irq.c b/arch/arm/mach-omap2/irq.c
index 2842fe8..21f3d83 100644
--- a/arch/arm/mach-omap2/irq.c
+++ b/arch/arm/mach-omap2/irq.c
@@ -177,6 +177,47 @@ int omap_irq_pending(void)
 	return 0;
 }

+/*
+ * Get the first pending MPU IRQ number from 'irq_start'.
+ * If none, return -1.
+ */
+int omap_get_pending_mpu_irq(unsigned int irq_start)
+{
+	struct omap_irq_bank *bank = irq_banks;
+	int irq, bits_skip, bit_start;
+
+	if (irq_start >= bank->nr_irqs)
+		return -1;
+
+	bits_skip = irq_start % IRQ_BITS_PER_REG;
+	bit_start = irq_start - bits_skip;
+
+	for (irq = bit_start; irq < bank->nr_irqs; irq += IRQ_BITS_PER_REG) {
+		int ret, i, limit, offset = irq & (~(IRQ_BITS_PER_REG - 1));
+
+		ret = intc_bank_read_reg(bank, (INTC_PENDING_IRQ0 + offset));
+		if (!ret)
+			continue;
+
+		limit = IRQ_BITS_PER_REG;
+
+		if (bit_start == irq) {
+			ret >>= bits_skip;
+			limit -= bits_skip;
+		} else
+			bits_skip = 0;
+
+		for (i = 0; i < limit; i++) {
+			if (ret & 0x1)
+				return irq + i + bits_skip;
+			else
+				ret >>= 1;
+		}
+	}
+
+	return -1;
+}
+
 void __init omap_init_irq(void)
 {
 	unsigned long nr_of_irqs = 0;
diff --git a/arch/arm/mach-omap2/prcm-common.h
b/arch/arm/mach-omap2/prcm-common.h
index cb1ae84..1f340aa 100644
--- a/arch/arm/mach-omap2/prcm-common.h
+++ b/arch/arm/mach-omap2/prcm-common.h
@@ -273,6 +273,10 @@
 #define OMAP3430_ST_D2D_SHIFT				3
 #define OMAP3430_ST_D2D_MASK				(1 << 3)

+/* PM_WKST3_CORE, CM_IDLEST3_CORE shared bits */
+#define OMAP3430_ST_USBTLL_SHIFT			2
+#define OMAP3430_ST_USBTLL_MASK				(1 << 2)
+
 /* CM_FCLKEN_WKUP, CM_ICLKEN_WKUP, PM_WKEN_WKUP shared bits */
 #define OMAP3430_EN_GPIO1				(1 << 3)
 #define OMAP3430_EN_GPIO1_SHIFT				3
diff --git a/arch/arm/mach-omap2/prm-regbits-34xx.h
b/arch/arm/mach-omap2/prm-regbits-34xx.h
index d73eee8..661d37d 100644
--- a/arch/arm/mach-omap2/prm-regbits-34xx.h
+++ b/arch/arm/mach-omap2/prm-regbits-34xx.h
@@ -332,6 +332,8 @@
 /* PM_IVA2GRPSEL1_CORE specific bits */

 /* PM_WKST1_CORE specific bits */
+#define OMAP3430_ST_MMC3_SHIFT				30
+#define OMAP3430_ST_MMC3_MASK				(1 << 30)

 /* PM_PWSTCTRL_CORE specific bits */
 #define OMAP3430_MEM2ONSTATE_SHIFT			18
@@ -373,6 +375,7 @@
 /* PM_IVA2GRPSEL_WKUP specific bits */

 /* PM_WKST_WKUP specific bits */
+#define OMAP3430_ST_IO_CHAIN				(1 << 16)
 #define OMAP3430_ST_IO					(1 << 8)

 /* PRM_CLKSEL */
@@ -430,6 +433,9 @@

 /* PM_PREPWSTST_PER specific bits */

+/* PM_WKST_USBHOST specific bits */
+#define OMAP3430_ST_USBHOST				(1 << 0)
+
 /* RM_RSTST_EMU specific bits */

 /* PM_PWSTST_EMU specific bits */
diff --git a/arch/arm/mach-omap2/wake34xx.c b/arch/arm/mach-omap2/wake34xx.c
new file mode 100644
index 0000000..2390bca
--- /dev/null
+++ b/arch/arm/mach-omap2/wake34xx.c
@@ -0,0 +1,466 @@
+/*
+ * wake34xx.c
+ *
+ * Copyright (c) 2009 Samsung Eletronics
+ *
+ * Author: Kim Kyuwon <q1.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+
+#include <mach/gpio.h>
+#include <mach/wake.h>
+
+#include "prm-regbits-34xx.h"
+
+/*
+ * Sometimes, it is necessary to find out "what does wake up my board?"
+ * Notifying wake-up source feature may be used to blame unexpected wake-up
+ * events which increase power consumption. And user mode applications can
+ * act smartly according to the wake-up event to minimize power consumption.
+ * This driver uses sysfs interface to give information to user mode
+ * applications.
+ */
+
+#define WAKE_STR_LEN	64
+
+char wakeup_irq[WAKE_STR_LEN] = "None";
+char wakeup_event[WAKE_STR_LEN] = "None";
+
+struct wake_event {
+	int 		index;
+	const char 	*name;
+};
+
+static struct wake_event omap3_wkup_events[] = {
+	{ OMAP3430_ST_IO_CHAIN,			"ST_IO" },
+	{ OMAP3430_ST_IO,			"ST_SR2" },
+	{ OMAP3430_ST_SR2_MASK,			"ST_SR2" },
+	{ OMAP3430_ST_SR1_MASK,			"ST_SR1" },
+	{ OMAP3430_ST_GPIO1_MASK,		"ST_GPIO1" },
+	{ OMAP3430_ST_GPT12_MASK,		"ST_GPT12" },
+	{ OMAP3430_ST_GPT1_MASK,		"ST_GPT1" },
+};
+
+static struct wake_event omap3_per_events[] = {
+	{ OMAP3430_ST_GPIO6_MASK,		"ST_GPIO6" },
+	{ OMAP3430_ST_GPIO5_MASK,		"ST_GPIO5" },
+	{ OMAP3430_ST_GPIO4_MASK,		"ST_GPIO4" },
+	{ OMAP3430_ST_GPIO3_MASK,		"ST_GPIO3" },
+	{ OMAP3430_ST_GPIO2_MASK,		"ST_GPIO2" },
+	{ OMAP3430_ST_UART3_MASK,		"ST_UART3" },
+	{ OMAP3430_ST_GPT9_MASK,		"ST_GPT9" },
+	{ OMAP3430_ST_GPT8_MASK,		"ST_GPT8" },
+	{ OMAP3430_ST_GPT7_MASK,		"ST_GPT7" },
+	{ OMAP3430_ST_GPT6_MASK,		"ST_GPT6" },
+	{ OMAP3430_ST_GPT5_MASK,		"ST_GPT5" },
+	{ OMAP3430_ST_GPT4_MASK,		"ST_GPT4" },
+	{ OMAP3430_ST_GPT3_MASK,		"ST_GPT3" },
+	{ OMAP3430_ST_GPT2_MASK,		"ST_GPT2" },
+	{ OMAP3430_EN_MCBSP4,			"EN_MCBSP4" },
+	{ OMAP3430_EN_MCBSP3,			"EN_MCBSP3" },
+	{ OMAP3430_EN_MCBSP2,			"EN_MCBSP2" },
+};
+
+static struct wake_event omap3_core1_events[] = {
+	{ OMAP3430_ST_MMC3_MASK,		"ST_MMC3" },
+	{ OMAP3430_ST_MMC2_MASK,		"ST_MMC2" },
+	{ OMAP3430_ST_MMC1_MASK,		"ST_MMC1" },
+	{ OMAP3430_ST_MCSPI4_MASK,		"ST_MCSPI4" },
+	{ OMAP3430_ST_MCSPI3_MASK,		"ST_MCSPI3" },
+	{ OMAP3430_ST_MCSPI2_MASK,		"ST_MCSPI2" },
+	{ OMAP3430_ST_MCSPI1_MASK,		"ST_MCSPI1" },
+	{ OMAP3430_ST_I2C3_MASK,		"ST_I2C3" },
+	{ OMAP3430_ST_I2C2_MASK,		"ST_I2C2" },
+	{ OMAP3430_ST_I2C1_MASK,		"ST_I2C1" },
+	{ OMAP3430_ST_UART1_MASK,		"ST_UART1" },
+	{ OMAP3430_ST_GPT11_MASK,		"ST_GPT11" },
+	{ OMAP3430_ST_GPT10_MASK,		"ST_GPT10" },
+	{ OMAP3430_ST_MCBSP5_MASK,		"ST_MCBSP5" },
+	{ OMAP3430_ST_MCBSP1_MASK,		"ST_MCBSP1" },
+};
+
+static struct wake_event omap3es1_core1_events[] = {
+	{ OMAP3430ES1_ST_FSHOSTUSB_MASK,	"ST_FSHOSTUSB" },
+	{ OMAP3430ES1_ST_HSOTGUSB_MASK,		"ST_HSOTGUSB" },
+	{ OMAP3430_ST_D2D_MASK,			"ST_D2D" },
+};
+
+static struct wake_event omap3es2_core1_events[] = {
+	{ OMAP3430ES2_ST_HSOTGUSB_STDBY_MASK,	"ST_HSOTGUSB" },
+};
+
+static struct wake_event omap3_core2_events[] = {
+	{ OMAP3430_ST_USBTLL_MASK,		"ST_USBTLL" },
+};
+
+static struct wake_event omap3_usbhost_events[] = {
+	{ OMAP3430_ST_USBHOST,			"ST_USBHOST" },
+};
+
+static ssize_t wakeup_source_show(struct kobject *kobj,
+				struct kobj_attribute *attr, char *buf);
+
+static void omap_wake_update_event(char *event)
+{
+	int len;
+
+	if (wakeup_event[0])
+		len = strlen(wakeup_event) + strlen(event) + 2;
+	else
+		len = strlen(wakeup_event) + strlen(event);
+
+	if (len > WAKE_STR_LEN - 1) {
+		printk(KERN_ERR "Can't record the wakeup event: %s\n", event);
+		return;
+	}
+
+	if (wakeup_event[0])
+		strcat(wakeup_event, ", ");
+	strcat(wakeup_event, event);
+}
+
+static void omap_wake_detect_wkup(u32 wkst, char *event)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(omap3_wkup_events); i++)
+		if (wkst & omap3_wkup_events[i].index) {
+			sprintf(event, "%s",
+					omap3_wkup_events[i].name);
+			return;
+		}
+
+	sprintf(event, "WKUP:0x%08x", wkst);
+}
+
+static void omap_wake_detect_per(u32 wkst, char *event)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(omap3_per_events); i++)
+		if (wkst & omap3_per_events[i].index) {
+			sprintf(event, "%s",
+					omap3_per_events[i].name);
+			return;
+		}
+
+	sprintf(event, "PER:0x%08x", wkst);
+}
+
+static void omap_wake_detect_core1(u32 wkst, char *event)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(omap3_core1_events); i++)
+		if (wkst & omap3_core1_events[i].index) {
+			sprintf(event, "%s",
+					omap3_core1_events[i].name);
+			return;
+		}
+
+	if (omap_rev() == OMAP3430_REV_ES1_0) {
+		for (i = 0; i < ARRAY_SIZE(omap3es1_core1_events); i++) {
+			if (wkst & omap3es1_core1_events[i].index) {
+				sprintf(event, "%s",
+					omap3es1_core1_events[i].name);
+				return;
+			}
+		}
+	} else {
+		for (i = 0; i < ARRAY_SIZE(omap3es2_core1_events); i++) {
+			if (wkst & omap3es2_core1_events[i].index) {
+				sprintf(event, "%s",
+					omap3es2_core1_events[i].name);
+				return;
+			}
+		}
+	}
+
+	sprintf(event, "CORE1:0x%08x", wkst);
+}
+
+static void omap_wake_detect_core2(u32 wkst, char *event)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(omap3_core2_events); i++)
+		if (wkst & omap3_core2_events[i].index) {
+			sprintf(event, "%s",
+					omap3_core2_events[i].name);
+			return;
+		}
+
+	sprintf(event, "CORE2:0x%08x", wkst);
+}
+
+static void omap_wake_detect_usbhost(u32 wkst, char *event)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(omap3_usbhost_events); i++)
+		if (wkst & omap3_usbhost_events[i].index) {
+			sprintf(event, "%s",
+					omap3_usbhost_events[i].name);
+			return;
+		}
+
+	sprintf(event, "USBHOST:0x%08x", wkst);
+}
+
+/* Detect wake-up events */
+static void omap_wake_detect_wakeup(void)
+{
+	u32 wkst;
+	char buf[32] = {0, };
+	int irq, len;
+
+	/* Initialize global string variables */
+	memset(wakeup_irq, 0x0, WAKE_STR_LEN);
+	memset(wakeup_event, 0x0, WAKE_STR_LEN);
+
+	/* IRQ */
+	irq = omap_get_pending_mpu_irq(0);
+	while (irq >= 0) {
+		if (irq == INT_34XX_SYS_NIRQ)
+			omap_wake_update_event("sys_nirq");
+
+		len = strlen(wakeup_irq) + sprintf(buf, "%d", irq);
+		if (len > WAKE_STR_LEN - 1)
+			break;
+
+		strcat(wakeup_irq, buf);
+
+		irq = omap_get_pending_mpu_irq(irq + 1);
+		if (irq >= 0) {
+			len = strlen(wakeup_irq) + 2;
+			if (len > WAKE_STR_LEN - 1)
+				break;
+
+			strcat(wakeup_irq, ", ");
+		}
+	}
+	if (!wakeup_irq[0])
+		strncpy(wakeup_irq, "Unknown", WAKE_STR_LEN - 1);
+	printk(KERN_INFO "Wake-up IRQ: %s\n", wakeup_irq);
+
+	/* WKUP */
+	wkst = prm_read_mod_reg(WKUP_MOD, PM_WKST);
+	if (wkst) {
+		omap_wake_detect_wkup(wkst, buf);
+		omap_wake_update_event(buf);
+	}
+
+	/* PER */
+	wkst = prm_read_mod_reg(OMAP3430_PER_MOD, PM_WKST);
+	if (wkst) {
+		omap_wake_detect_per(wkst, buf);
+		omap_wake_update_event(buf);
+	}
+
+	/* CORE */
+	wkst = prm_read_mod_reg(CORE_MOD, PM_WKST1);
+	if (wkst) {
+		omap_wake_detect_core1(wkst, buf);
+		omap_wake_update_event(buf);
+	}
+	wkst = prm_read_mod_reg(CORE_MOD, OMAP3430ES2_PM_WKST3);
+	if (wkst) {
+		omap_wake_detect_core2(wkst, buf);
+		omap_wake_update_event(buf);
+	}
+
+	/* USBHOST */
+	wkst = prm_read_mod_reg(OMAP3430ES2_USBHOST_MOD, PM_WKST);
+	if (wkst) {
+		omap_wake_detect_usbhost(wkst, buf);
+		omap_wake_update_event(buf);
+	}
+
+	if (!wakeup_event[0])
+		strncpy(wakeup_event, "Unknown", WAKE_STR_LEN - 1);
+}
+
+static struct kobj_attribute wakeup_irq_attr =
+	__ATTR(wakeup_irq, 0644, wakeup_source_show, NULL);
+
+static struct kobj_attribute wakeup_event_attr =
+	__ATTR(wakeup_event, 0644, wakeup_source_show, NULL);
+
+static ssize_t wakeup_source_show(struct kobject *kobj,
+				struct kobj_attribute *attr, char *buf)
+{
+	if (attr == &wakeup_irq_attr)
+		return sprintf(buf, "%s\n", wakeup_irq);
+	else if (attr == &wakeup_event_attr)
+		return sprintf(buf, "%s\n", wakeup_event);
+	else
+		return -EINVAL;
+}
+
+static irqreturn_t omap_wake_detect_gpio(int irq, void *dev_id)
+{
+	if (!strncmp(wakeup_event, "Unknown", WAKE_STR_LEN - 1))
+		strncpy(wakeup_event, dev_id, WAKE_STR_LEN - 1);
+	else
+		omap_wake_update_event(dev_id);
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit omap_wake_probe(struct platform_device *pdev)
+{
+	struct omap_wake_platform_data *pdata = pdev->dev.platform_data;
+	struct gpio_wake *gw;
+	int i, ret;
+
+	/*
+	 * It may be good to configure GPIO wake-up sources in each driver.
+	 * Buf if the specific device driver doesn't exist, you can use
+	 * omap-wake driver to configure gpio wake-up sources.
+	 */
+	for (i = 0; i < pdata->gpio_wake_num; i++) {
+		gw = pdata->qpio_wakes + i;
+
+		if (gw->request) {
+			ret = gpio_request(gw->gpio, gw->name);
+			if (ret) {
+				dev_err(&pdev->dev, "can't request gpio%d"
+					", return %d\n", gw->gpio, ret);
+				goto failed_free_gpio;
+			}
+		}
+		gpio_direction_input(gw->gpio);
+		enable_irq_wake(gpio_to_irq(gw->gpio));
+	}
+
+	ret = sysfs_create_file(power_kobj, &wakeup_irq_attr.attr);
+	if (ret)
+		dev_err(&pdev->dev, "sysfs_create_file %s failed: %d\n",
+					wakeup_irq_attr.attr.name, ret);
+
+	ret = sysfs_create_file(power_kobj, &wakeup_event_attr.attr);
+	if (ret)
+		dev_err(&pdev->dev, "sysfs_create_file %s failed: %d\n",
+					wakeup_event_attr.attr.name, ret);
+
+	return 0;
+
+failed_free_gpio:
+	for (i--; i >= 0; i--) {
+		gw = pdata->qpio_wakes + i;
+
+		if (gw->request)
+			gpio_free(gw->gpio);
+	}
+
+	return ret;
+}
+
+static int __devexit omap_wake_remove(struct platform_device *pdev)
+{
+	struct omap_wake_platform_data *pdata = pdev->dev.platform_data;
+	struct gpio_wake *gw;
+	int i;
+
+	for (i = 0; i < pdata->gpio_wake_num; i++) {
+		gw = pdata->qpio_wakes + i;
+
+		if (gw->request)
+			gpio_free(gw->gpio);
+
+		disable_irq_wake(gpio_to_irq(gw->gpio));
+	}
+
+	return 0;
+}
+
+static int omap_wake_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct omap_wake_platform_data *pdata = pdev->dev.platform_data;
+	struct gpio_wake *gw;
+	int i, ret;
+
+	for (i = 0; i < pdata->gpio_wake_num; i++) {
+		gw = pdata->qpio_wakes + i;
+
+		ret = request_irq(gpio_to_irq(gw->gpio), omap_wake_detect_gpio,
+				gw->irqflag, gw->name, (void *)gw->name);
+		if (ret) {
+			dev_err(&pdev->dev, "can't get IRQ%d, return %d\n",
+					gpio_to_irq(gw->gpio), ret);
+			goto failed_free_irq;
+		}
+	}
+
+	return 0;
+
+failed_free_irq:
+	for (i--; i >= 0; i--) {
+		gw = pdata->qpio_wakes + i;
+		free_irq(gpio_to_irq(gw->gpio),  (void *)gw->name);
+	}
+
+	return ret;
+}
+
+static int omap_wake_resume_early(struct platform_device *pdev)
+{
+	omap_wake_detect_wakeup();
+
+	return 0;
+}
+
+static int omap_wake_resume(struct platform_device *pdev)
+{
+	struct omap_wake_platform_data *pdata = pdev->dev.platform_data;
+	struct gpio_wake *gw;
+	int i;
+
+	for (i = 0; i < pdata->gpio_wake_num; i++) {
+		gw = pdata->qpio_wakes + i;
+
+		free_irq(gpio_to_irq(gw->gpio), (void *)gw->name);
+	}
+
+	printk(KERN_INFO "Wake-up event: %s\n", wakeup_event);
+
+	return 0;
+}
+
+static struct platform_driver omap_wake_driver = {
+	.probe          = omap_wake_probe,
+	.remove		= __devexit_p(omap_wake_remove),
+	.suspend	= omap_wake_suspend,
+	.resume_early	= omap_wake_resume_early,
+	.resume		= omap_wake_resume,
+	.driver         = {
+		.name   = "omap-wake",
+		.owner  = THIS_MODULE,
+	},
+};
+
+static int __init omap_wake_init(void)
+{
+	return platform_driver_register(&omap_wake_driver);
+}
+
+module_init(omap_wake_init);
+
+static void __exit omap_wake_exit(void)
+{
+	platform_driver_unregister(&omap_wake_driver);
+}
+module_exit(omap_wake_exit);
+
+MODULE_AUTHOR("Kim Kyuwon <q1.kim@samsung.com>");
+MODULE_DESCRIPTION("OMAP34xx wakeup driver");
+MODULE_LICENSE("GPL v2");
diff --git a/arch/arm/plat-omap/Kconfig b/arch/arm/plat-omap/Kconfig
index 407bd32..7a73c95 100644
--- a/arch/arm/plat-omap/Kconfig
+++ b/arch/arm/plat-omap/Kconfig
@@ -176,6 +176,15 @@ config OMAP_MBOX_FWK
 	  Say Y here if you want to use OMAP Mailbox framework support for
 	  DSP, IVA1.0 and IVA2 in OMAP1/2/3.

+config OMAP_WAKE
+	tristate "OMAP34xx wakeup source support"
+	depends on ARCH_OMAP34XX && PM
+	default n
+	help
+	  Select this option if you want to know what kind of wake-up event
+	  wakes up your board from the low power mode. And this option
+	  provides the unified GPIO wake-up source configuration.
+
 choice
         prompt "System timer"
 	default OMAP_MPU_TIMER
diff --git a/arch/arm/plat-omap/include/mach/irqs.h
b/arch/arm/plat-omap/include/mach/irqs.h
index d12c39f..656cc04 100644
--- a/arch/arm/plat-omap/include/mach/irqs.h
+++ b/arch/arm/plat-omap/include/mach/irqs.h
@@ -385,6 +385,7 @@
 #ifndef __ASSEMBLY__
 extern void omap_init_irq(void);
 extern int omap_irq_pending(void);
+extern int omap_get_pending_mpu_irq(unsigned int irq_start);
 #endif

 #include <mach/hardware.h>
diff --git a/arch/arm/plat-omap/include/mach/wake.h
b/arch/arm/plat-omap/include/mach/wake.h
new file mode 100644
index 0000000..213b263
--- /dev/null
+++ b/arch/arm/plat-omap/include/mach/wake.h
@@ -0,0 +1,30 @@
+/*
+ * wake.h
+ *
+ * Copyright (c) 2009 Samsung Eletronics
+ *
+ * Author: Kim Kyuwon <q1.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#ifndef _WAKE_H_
+#define _WAKE_H_
+
+struct gpio_wake {
+	unsigned int 	gpio;
+	unsigned long	irqflag;
+	const char 	*name;
+	int		request;
+};
+
+struct omap_wake_platform_data{
+	struct gpio_wake	*qpio_wakes;
+	int			gpio_wake_num;
+};
+
+#endif /* _WAKE_H_ */
+
-- 
1.5.2.5


-- 
Q1

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH 1/1] OMAP3: PM: Add the wakeup source driver
  2009-03-03 10:37 [PATCH 1/1] OMAP3: PM: Add the wakeup source driver Kim Kyuwon
@ 2009-03-17 21:47 ` Kevin Hilman
  2009-03-19  4:19   ` Kim Kyuwon
  0 siblings, 1 reply; 3+ messages in thread
From: Kevin Hilman @ 2009-03-17 21:47 UTC (permalink / raw)
  To: Kim Kyuwon; +Cc: OMAP, Tony Lindgren, 박경민

Kim Kyuwon <chammoru@gmail.com> writes:

> Sometimes, it is necessary to find out "what does wake up my board?".
> Notifying wake-up source feature may be used to blame unexpected
> wake-up events which increase power consumption. And user mode
> applications can act smartly according to the wake-up event to
> minimize power consumption. 

Hi Kim,

This is a very useful feature, and something that's is lacking in the
current PM code.  Thank you so much for this contribution.

Currently this driver only works for wakeups from suspend.  I think
the description should be updated to specify that this only affects
wakeups from susupend, and not wakeups from idle.

Speaking of which, have you considered extending it to handle wakeups
from idle?  Currently tools like powertop give a pretty good idea as
to why the system is coming out of idle, so this may not be necessary,
but was just wondering if you had considered it.

More comments and suggestions on general organization below.

> This driver uses sysfs interface to give
> information to user mode applications like:
>
> cat /sys/power/wakeup_irq
> cat /sys/power/wakeup_event

If only suspend/resume is being handled, maybe 'resume' is a better
name than 'wakeup'.  These would be better named:

  /sys/power/omap_resume_irq
  /sys/power/omap_resume_event

> This driver also privides the unified GPIO wake-up source
> configuration. specific GPIO settings in the board files are:
>
> /* Wakeup source configuration */
> static struct gpio_wake boardname_gpio_wake[] = {
> 	{ 23,	IRQF_TRIGGER_RISING | IRQF_SHARED,	"BT_WAKEUP",	1},
> 	{ 24,	IRQF_TRIGGER_RISING | IRQF_SHARED,	"USB_DETECT",	1},
> };

Based on the way this works, I think the board code should not
be requird to set IRQF_SHARED.  I think this should be explicitly
ORed in in the wake_suspend hook which requests the IRQ since this will
not work correctly without using IRQF_SHARED.

> static struct omap_wake_platform_data boardname_wake_data = {
> 	.qpio_wakes		= boardname_gpio_wake,
> 	.gpio_wake_num		= ARRAY_SIZE(boardname_gpio_wake),
> };

I assume that 'qpio' is a typo and should be gpio?

Also, I'm not sure I agree with adding a generic GPIO wakeup
interface.  I believe this should be done by the driver using the GPIO
itself.  However, I can see this being useful to test external
wakeup events when no driver is present.

> static struct platform_device boardname_wakeup = {
> 	.name			= "omap-wake",
> 	.id			= -1,
> 	.dev			= {
> 		.platform_data	= &boardname_wake_data,
> 	},
> };
>
> Signed-off-by: Kim Kyuwon <q1.kim@samsung.com>
> ---
>  arch/arm/mach-omap2/Makefile           |    1 +
>  arch/arm/mach-omap2/irq.c              |   41 +++
>  arch/arm/mach-omap2/prcm-common.h      |    4 +
>  arch/arm/mach-omap2/prm-regbits-34xx.h |    6 +
>  arch/arm/mach-omap2/wake34xx.c         |  470 ++++++++++++++++++++++++++++++++
>  arch/arm/plat-omap/Kconfig             |    9 +
>  arch/arm/plat-omap/include/mach/irqs.h |    1 +
>  arch/arm/plat-omap/include/mach/wake.h |   29 ++
>  8 files changed, 561 insertions(+), 0 deletions(-)
>  create mode 100644 arch/arm/mach-omap2/wake34xx.c
>  create mode 100644 arch/arm/plat-omap/include/mach/wake.h
>
> diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile
> index ba65cab..1ab87e7 100644
> --- a/arch/arm/mach-omap2/Makefile
> +++ b/arch/arm/mach-omap2/Makefile
> @@ -25,6 +25,7 @@ obj-$(CONFIG_ARCH_OMAP2)		+= pm24xx.o
>  obj-$(CONFIG_ARCH_OMAP24XX)		+= sleep24xx.o
>  obj-$(CONFIG_ARCH_OMAP3)		+= pm34xx.o sleep34xx.o
>  obj-$(CONFIG_PM_DEBUG)			+= pm-debug.o
> +obj-$(CONFIG_OMAP_WAKE)			+= wake34xx.o
>  endif
>
>  # SmartReflex driver
> diff --git a/arch/arm/mach-omap2/irq.c b/arch/arm/mach-omap2/irq.c
> index 2842fe8..21f3d83 100644
> --- a/arch/arm/mach-omap2/irq.c
> +++ b/arch/arm/mach-omap2/irq.c
> @@ -177,6 +177,47 @@ int omap_irq_pending(void)
>  	return 0;
>  }
>
> +/*
> + * Get the first pending MPU IRQ number from 'irq_start'.
> + * If none, return -1.
> + */
> +int omap_get_pending_mpu_irq(unsigned int irq_start)
> +{
> +	struct omap_irq_bank *bank = irq_banks;
> +	int irq, bits_skip, bit_start;
> +
> +	if (irq_start >= bank->nr_irqs)
> +		return -1;

return -EINVAL;

> +	bits_skip = irq_start % IRQ_BITS_PER_REG;
> +	bit_start = irq_start - bits_skip;
> +
> +	for (irq = bit_start; irq < bank->nr_irqs; irq += IRQ_BITS_PER_REG) {
> +		int ret, i, limit, offset = irq & (~(IRQ_BITS_PER_REG - 1));

The name 'ret' is normally used for a return value, and you're not using it
that way.

> +		ret = intc_bank_read_reg(bank, (INTC_PENDING_IRQ0 + offset));
> +		if (!ret)
> +			continue;
> +
> +		limit = IRQ_BITS_PER_REG;
> +
> +		if (bit_start == irq) {
> +			ret >>= bits_skip;
> +			limit -= bits_skip;
> +		} else
> +			bits_skip = 0;
> +
> +		for (i = 0; i < limit; i++) {
> +			if (ret & 0x1)
> +				return irq + i + bits_skip;
> +			else
> +				ret >>= 1;
> +		}
> +	}

I think you could do this more efficiently using find_first_bit() and
find_next_bit().  See <linux/bitops.h>

> +	return -1;

return -EINVAL;

> +}
> +

In general, I think I would like to see the actual suspend/resume path code
optimizied a little more.  Leave the string processing and printing to
the sysfs code or CONFIG_PM_DEBUG code in the resume path.

Here's my idea:  in irq.c, just have a get_pending_irqs() function which
returns the IRQ_PENDINGx registers (only 1 for OMAP2 but 3 for OMAP3.)
The suspend/resume path just saves these away for use by printing code
in the resume path (CONFIG_PM_DEBUG) and the sysfs path.

>  void __init omap_init_irq(void)
>  {
>  	unsigned long nr_of_irqs = 0;
> diff --git a/arch/arm/mach-omap2/prcm-common.h
> b/arch/arm/mach-omap2/prcm-common.h
> index cb1ae84..1f340aa 100644
> --- a/arch/arm/mach-omap2/prcm-common.h
> +++ b/arch/arm/mach-omap2/prcm-common.h
> @@ -273,6 +273,10 @@
>  #define OMAP3430_ST_D2D_SHIFT				3
>  #define OMAP3430_ST_D2D_MASK				(1 << 3)
>
> +/* PM_WKST3_CORE, CM_IDLEST3_CORE shared bits */
> +#define OMAP3430_ST_USBTLL_SHIFT			2
> +#define OMAP3430_ST_USBTLL_MASK				(1 << 2)
> +
>  /* CM_FCLKEN_WKUP, CM_ICLKEN_WKUP, PM_WKEN_WKUP shared bits */
>  #define OMAP3430_EN_GPIO1				(1 << 3)
>  #define OMAP3430_EN_GPIO1_SHIFT				3
> diff --git a/arch/arm/mach-omap2/prm-regbits-34xx.h
> b/arch/arm/mach-omap2/prm-regbits-34xx.h
> index d73eee8..661d37d 100644
> --- a/arch/arm/mach-omap2/prm-regbits-34xx.h
> +++ b/arch/arm/mach-omap2/prm-regbits-34xx.h
> @@ -332,6 +332,8 @@
>  /* PM_IVA2GRPSEL1_CORE specific bits */
>
>  /* PM_WKST1_CORE specific bits */
> +#define OMAP3430_ST_MMC3_SHIFT				30
> +#define OMAP3430_ST_MMC3_MASK				(1 << 30)
>
>  /* PM_PWSTCTRL_CORE specific bits */
>  #define OMAP3430_MEM2ONSTATE_SHIFT			18
> @@ -373,6 +375,7 @@
>  /* PM_IVA2GRPSEL_WKUP specific bits */
>
>  /* PM_WKST_WKUP specific bits */
> +#define OMAP3430_ST_IO_CHAIN				(1 << 16)
>  #define OMAP3430_ST_IO					(1 << 8)
>
>  /* PRM_CLKSEL */
> @@ -430,6 +433,9 @@
>
>  /* PM_PREPWSTST_PER specific bits */
>
> +/* PM_WKST_USBHOST specific bits */
> +#define OMAP3430_ST_USBHOST				(1 << 0)
> +
>  /* RM_RSTST_EMU specific bits */
>
>  /* PM_PWSTST_EMU specific bits */
> diff --git a/arch/arm/mach-omap2/wake34xx.c b/arch/arm/mach-omap2/wake34xx.c
> new file mode 100644
> index 0000000..2390bca
> --- /dev/null
> +++ b/arch/arm/mach-omap2/wake34xx.c
> @@ -0,0 +1,466 @@
> +/*
> + * wake34xx.c
> + *
> + * Copyright (c) 2009 Samsung Eletronics
> + *
> + * Author: Kim Kyuwon <q1.kim@samsung.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/platform_device.h>
> +#include <linux/interrupt.h>
> +
> +#include <mach/gpio.h>
> +#include <mach/wake.h>
> +
> +#include "prm-regbits-34xx.h"
> +
> +/*
> + * Sometimes, it is necessary to find out "what does wake up my board?"
> + * Notifying wake-up source feature may be used to blame unexpected wake-up
> + * events which increase power consumption. And user mode applications can
> + * act smartly according to the wake-up event to minimize power consumption.
> + * This driver uses sysfs interface to give information to user mode
> + * applications.
> + */
> +
> +#define WAKE_STR_LEN	64
> +
> +char wakeup_irq[WAKE_STR_LEN] = "None";
> +char wakeup_event[WAKE_STR_LEN] = "None";
> +
> +struct wake_event {
> +	int 		index;
> +	const char 	*name;
> +};

The name 'index' isn't quite right here.  This is a bitmask
used compare against the PM_WKST_<domain> register.  I suggest
'u32 mask' or something.

> +static struct wake_event omap3_wkup_events[] = {
> +	{ OMAP3430_ST_IO_CHAIN,			"ST_IO" },
> +	{ OMAP3430_ST_IO,			"ST_SR2" },
> +	{ OMAP3430_ST_SR2_MASK,			"ST_SR2" },
> +	{ OMAP3430_ST_SR1_MASK,			"ST_SR1" },
> +	{ OMAP3430_ST_GPIO1_MASK,		"ST_GPIO1" },
> +	{ OMAP3430_ST_GPT12_MASK,		"ST_GPT12" },
> +	{ OMAP3430_ST_GPT1_MASK,		"ST_GPT1" },
> +};
> +
> +static struct wake_event omap3_per_events[] = {
> +	{ OMAP3430_ST_GPIO6_MASK,		"ST_GPIO6" },
> +	{ OMAP3430_ST_GPIO5_MASK,		"ST_GPIO5" },
> +	{ OMAP3430_ST_GPIO4_MASK,		"ST_GPIO4" },
> +	{ OMAP3430_ST_GPIO3_MASK,		"ST_GPIO3" },
> +	{ OMAP3430_ST_GPIO2_MASK,		"ST_GPIO2" },
> +	{ OMAP3430_ST_UART3_MASK,		"ST_UART3" },
> +	{ OMAP3430_ST_GPT9_MASK,		"ST_GPT9" },
> +	{ OMAP3430_ST_GPT8_MASK,		"ST_GPT8" },
> +	{ OMAP3430_ST_GPT7_MASK,		"ST_GPT7" },
> +	{ OMAP3430_ST_GPT6_MASK,		"ST_GPT6" },
> +	{ OMAP3430_ST_GPT5_MASK,		"ST_GPT5" },
> +	{ OMAP3430_ST_GPT4_MASK,		"ST_GPT4" },
> +	{ OMAP3430_ST_GPT3_MASK,		"ST_GPT3" },
> +	{ OMAP3430_ST_GPT2_MASK,		"ST_GPT2" },
> +	{ OMAP3430_EN_MCBSP4,			"EN_MCBSP4" },
> +	{ OMAP3430_EN_MCBSP3,			"EN_MCBSP3" },
> +	{ OMAP3430_EN_MCBSP2,			"EN_MCBSP2" },
> +};
> +
> +static struct wake_event omap3_core1_events[] = {
> +	{ OMAP3430_ST_MMC3_MASK,		"ST_MMC3" },
> +	{ OMAP3430_ST_MMC2_MASK,		"ST_MMC2" },
> +	{ OMAP3430_ST_MMC1_MASK,		"ST_MMC1" },
> +	{ OMAP3430_ST_MCSPI4_MASK,		"ST_MCSPI4" },
> +	{ OMAP3430_ST_MCSPI3_MASK,		"ST_MCSPI3" },
> +	{ OMAP3430_ST_MCSPI2_MASK,		"ST_MCSPI2" },
> +	{ OMAP3430_ST_MCSPI1_MASK,		"ST_MCSPI1" },
> +	{ OMAP3430_ST_I2C3_MASK,		"ST_I2C3" },
> +	{ OMAP3430_ST_I2C2_MASK,		"ST_I2C2" },
> +	{ OMAP3430_ST_I2C1_MASK,		"ST_I2C1" },
> +	{ OMAP3430_ST_UART1_MASK,		"ST_UART1" },
> +	{ OMAP3430_ST_GPT11_MASK,		"ST_GPT11" },
> +	{ OMAP3430_ST_GPT10_MASK,		"ST_GPT10" },
> +	{ OMAP3430_ST_MCBSP5_MASK,		"ST_MCBSP5" },
> +	{ OMAP3430_ST_MCBSP1_MASK,		"ST_MCBSP1" },
> +};
> +
> +static struct wake_event omap3es1_core1_events[] = {
> +	{ OMAP3430ES1_ST_FSHOSTUSB_MASK,	"ST_FSHOSTUSB" },
> +	{ OMAP3430ES1_ST_HSOTGUSB_MASK,		"ST_HSOTGUSB" },
> +	{ OMAP3430_ST_D2D_MASK,			"ST_D2D" },
> +};
> +
> +static struct wake_event omap3es2_core1_events[] = {
> +	{ OMAP3430ES2_ST_HSOTGUSB_STDBY_MASK,	"ST_HSOTGUSB" },
> +};
> +
> +static struct wake_event omap3_core2_events[] = {
> +	{ OMAP3430_ST_USBTLL_MASK,		"ST_USBTLL" },
> +};
> +
> +static struct wake_event omap3_usbhost_events[] = {
> +	{ OMAP3430_ST_USBHOST,			"ST_USBHOST" },
> +};
> +
> +static ssize_t wakeup_source_show(struct kobject *kobj,
> +				struct kobj_attribute *attr, char *buf);
> +
> +static void omap_wake_update_event(char *event)
> +{
> +	int len;
> +
> +	if (wakeup_event[0])
> +		len = strlen(wakeup_event) + strlen(event) + 2;
> +	else
> +		len = strlen(wakeup_event) + strlen(event);
> +
> +	if (len > WAKE_STR_LEN - 1) {
> +		printk(KERN_ERR "Can't record the wakeup event: %s\n", event);
> +		return;
> +	}
> +
> +	if (wakeup_event[0])
> +		strcat(wakeup_event, ", ");
> +	strcat(wakeup_event, event);
> +}
> +
> +static void omap_wake_detect_wkup(u32 wkst, char *event)
> +{
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(omap3_wkup_events); i++)
> +		if (wkst & omap3_wkup_events[i].index) {
> +			sprintf(event, "%s",
> +					omap3_wkup_events[i].name);
> +			return;
> +		}
> +
> +	sprintf(event, "WKUP:0x%08x", wkst);
> +}

I don't think the string processing should be done in the actual
wakeup path.  Rather, do the string processing only when the /sys
entries are used.  We want to keep the suspend and resume path fast
wherever possible.

Also, this (re)reading of the WKST registers could probably be saved
as this is done in the PRCM interrupt handler in pm34xx.c after each
wakeup.

Maybe what should be done is for the PRCM interrupt handler to save
the last values of the WKST registers for each domain, and then we add
a function to prcm34xx.c which returns these registers.  Then in your
wakeup code, you call that func to get the WKST values.  Any non-zero
WKST values could be parsed and displayed, but the parsing and
printing of these should probably be conditional on CONFIG_PM_DEBUG=y
so the default path stays fast.

> +static void omap_wake_detect_per(u32 wkst, char *event) { int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(omap3_per_events); i++)
> +		if (wkst & omap3_per_events[i].index) {
> +			sprintf(event, "%s",
> +					omap3_per_events[i].name);
> +			return;
> +		}
> +
> +	sprintf(event, "PER:0x%08x", wkst);
> +}
> +
> +static void omap_wake_detect_core1(u32 wkst, char *event)
> +{
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(omap3_core1_events); i++)
> +		if (wkst & omap3_core1_events[i].index) {
> +			sprintf(event, "%s",
> +					omap3_core1_events[i].name);
> +			return;
> +		}
> +
> +	if (omap_rev() == OMAP3430_REV_ES1_0) {
> +		for (i = 0; i < ARRAY_SIZE(omap3es1_core1_events); i++) {
> +			if (wkst & omap3es1_core1_events[i].index) {
> +				sprintf(event, "%s",
> +					omap3es1_core1_events[i].name);
> +				return;
> +			}
> +		}
> +	} else {
> +		for (i = 0; i < ARRAY_SIZE(omap3es2_core1_events); i++) {
> +			if (wkst & omap3es2_core1_events[i].index) {
> +				sprintf(event, "%s",
> +					omap3es2_core1_events[i].name);
> +				return;
> +			}
> +		}
> +	}
> +
> +	sprintf(event, "CORE1:0x%08x", wkst);
> +}
> +
> +static void omap_wake_detect_core2(u32 wkst, char *event)
> +{
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(omap3_core2_events); i++)
> +		if (wkst & omap3_core2_events[i].index) {
> +			sprintf(event, "%s",
> +					omap3_core2_events[i].name);
> +			return;
> +		}
> +
> +	sprintf(event, "CORE2:0x%08x", wkst);
> +}
> +
> +static void omap_wake_detect_usbhost(u32 wkst, char *event)
> +{
> +	int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(omap3_usbhost_events); i++)
> +		if (wkst & omap3_usbhost_events[i].index) {
> +			sprintf(event, "%s",
> +					omap3_usbhost_events[i].name);
> +			return;
> +		}
> +
> +	sprintf(event, "USBHOST:0x%08x", wkst);
> +}

These omap_wake_detect_* functions are all basically the same function
but for a different powerdomain.  Can you convert all these to a single
function which gets passed the powerdomain and the events array?

> +/* Detect wake-up events */
> +static void omap_wake_detect_wakeup(void)
> +{
> +	u32 wkst;
> +	char buf[32] = {0, };
> +	int irq, len;
> +
> +	/* Initialize global string variables */
> +	memset(wakeup_irq, 0x0, WAKE_STR_LEN);
> +	memset(wakeup_event, 0x0, WAKE_STR_LEN);

Is this needed every time?  Isn't there already a default case
which sets it to 'None'?

> +	/* IRQ */
> +	irq = omap_get_pending_mpu_irq(0);
> +	while (irq >= 0) {
> +		if (irq == INT_34XX_SYS_NIRQ)
> +			omap_wake_update_event("sys_nirq");
> +
> +		len = strlen(wakeup_irq) + sprintf(buf, "%d", irq);
> +		if (len > WAKE_STR_LEN - 1)
> +			break;
> +
> +		strcat(wakeup_irq, buf);
> +
> +		irq = omap_get_pending_mpu_irq(irq + 1);
> +		if (irq >= 0) {
> +			len = strlen(wakeup_irq) + 2;
> +			if (len > WAKE_STR_LEN - 1)
> +				break;
> +
> +			strcat(wakeup_irq, ", ");
> +		}
> +	}
> +	if (!wakeup_irq[0])
> +		strncpy(wakeup_irq, "Unknown", WAKE_STR_LEN - 1);
> +	printk(KERN_INFO "Wake-up IRQ: %s\n", wakeup_irq);
> +

Here is where you could just call into pm34xx.c to get the WKST
registers from the last PRCM interrupt.  

> +	/* WKUP */
> +	wkst = prm_read_mod_reg(WKUP_MOD, PM_WKST);
> +	if (wkst) {
> +		omap_wake_detect_wkup(wkst, buf);
> +		omap_wake_update_event(buf);
> +	}
>
> +	/* PER */
> +	wkst = prm_read_mod_reg(OMAP3430_PER_MOD, PM_WKST);
> +	if (wkst) {
> +		omap_wake_detect_per(wkst, buf);
> +		omap_wake_update_event(buf);
> +	}
> +
> +	/* CORE */
> +	wkst = prm_read_mod_reg(CORE_MOD, PM_WKST1);
> +	if (wkst) {
> +		omap_wake_detect_core1(wkst, buf);
> +		omap_wake_update_event(buf);
> +	}
> +	wkst = prm_read_mod_reg(CORE_MOD, OMAP3430ES2_PM_WKST3);
> +	if (wkst) {
> +		omap_wake_detect_core2(wkst, buf);
> +		omap_wake_update_event(buf);
> +	}
> +
> +	/* USBHOST */
> +	wkst = prm_read_mod_reg(OMAP3430ES2_USBHOST_MOD, PM_WKST);
> +	if (wkst) {
> +		omap_wake_detect_usbhost(wkst, buf);
> +		omap_wake_update_event(buf);
> +	}
>
> +	if (!wakeup_event[0])
> +		strncpy(wakeup_event, "Unknown", WAKE_STR_LEN - 1);
> +}
> +
> +static struct kobj_attribute wakeup_irq_attr =
> +	__ATTR(wakeup_irq, 0644, wakeup_source_show, NULL);
> +
> +static struct kobj_attribute wakeup_event_attr =
> +	__ATTR(wakeup_event, 0644, wakeup_source_show, NULL);
> +
> +static ssize_t wakeup_source_show(struct kobject *kobj,
> +				struct kobj_attribute *attr, char *buf)
> +{
> +	if (attr == &wakeup_irq_attr)
> +		return sprintf(buf, "%s\n", wakeup_irq);
> +	else if (attr == &wakeup_event_attr)
> +		return sprintf(buf, "%s\n", wakeup_event);
> +	else
> +		return -EINVAL;
> +}
> +
> +static irqreturn_t omap_wake_detect_gpio(int irq, void *dev_id)
> +{
> +	if (!strncmp(wakeup_event, "Unknown", WAKE_STR_LEN - 1))
> +		strncpy(wakeup_event, dev_id, WAKE_STR_LEN - 1);
> +	else
> +		omap_wake_update_event(dev_id);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int __devinit omap_wake_probe(struct platform_device *pdev)
> +{
> +	struct omap_wake_platform_data *pdata = pdev->dev.platform_data;
> +	struct gpio_wake *gw;
> +	int i, ret;
> +
> +	/*
> +	 * It may be good to configure GPIO wake-up sources in each driver.
> +	 * Buf if the specific device driver doesn't exist, you can use
> +	 * omap-wake driver to configure gpio wake-up sources.
> +	 */
> +	for (i = 0; i < pdata->gpio_wake_num; i++) {
> +		gw = pdata->qpio_wakes + i;
> +
> +		if (gw->request) {
> +			ret = gpio_request(gw->gpio, gw->name);
> +			if (ret) {
> +				dev_err(&pdev->dev, "can't request gpio%d"
> +					", return %d\n", gw->gpio, ret);
> +				goto failed_free_gpio;
> +			}
> +		}
> +		gpio_direction_input(gw->gpio);
> +		enable_irq_wake(gpio_to_irq(gw->gpio));
> +	}
> +
> +	ret = sysfs_create_file(power_kobj, &wakeup_irq_attr.attr);
> +	if (ret)
> +		dev_err(&pdev->dev, "sysfs_create_file %s failed: %d\n",
> +					wakeup_irq_attr.attr.name, ret);
> +
> +	ret = sysfs_create_file(power_kobj, &wakeup_event_attr.attr);
> +	if (ret)
> +		dev_err(&pdev->dev, "sysfs_create_file %s failed: %d\n",
> +					wakeup_event_attr.attr.name, ret);
> +
> +	return 0;
> +
> +failed_free_gpio:
> +	for (i--; i >= 0; i--) {
> +		gw = pdata->qpio_wakes + i;
> +
> +		if (gw->request)
> +			gpio_free(gw->gpio);
> +	}
> +
> +	return ret;
> +}
> +
> +static int __devexit omap_wake_remove(struct platform_device *pdev)
> +{
> +	struct omap_wake_platform_data *pdata = pdev->dev.platform_data;
> +	struct gpio_wake *gw;
> +	int i;
> +
> +	for (i = 0; i < pdata->gpio_wake_num; i++) {
> +		gw = pdata->qpio_wakes + i;
> +
> +		if (gw->request)
> +			gpio_free(gw->gpio);
> +
> +		disable_irq_wake(gpio_to_irq(gw->gpio));
> +	}
> +
> +	return 0;
> +}
> +
> +static int omap_wake_suspend(struct platform_device *pdev, pm_message_t state)
> +{
> +	struct omap_wake_platform_data *pdata = pdev->dev.platform_data;
> +	struct gpio_wake *gw;
> +	int i, ret;
> +
> +	for (i = 0; i < pdata->gpio_wake_num; i++) {
> +		gw = pdata->qpio_wakes + i;
> +
> +		ret = request_irq(gpio_to_irq(gw->gpio), omap_wake_detect_gpio,
> +				gw->irqflag, gw->name, (void *)gw->name);
> +		if (ret) {
> +			dev_err(&pdev->dev, "can't get IRQ%d, return %d\n",
> +					gpio_to_irq(gw->gpio), ret);
> +			goto failed_free_irq;
> +		}
> +	}
> +
> +	return 0;
> +
> +failed_free_irq:
> +	for (i--; i >= 0; i--) {
> +		gw = pdata->qpio_wakes + i;
> +		free_irq(gpio_to_irq(gw->gpio),  (void *)gw->name);
> +	}
> +
> +	return ret;
> +}
> +
> +static int omap_wake_resume_early(struct platform_device *pdev)
> +{
> +	omap_wake_detect_wakeup();
> +
> +	return 0;
> +}
> +
> +static int omap_wake_resume(struct platform_device *pdev)
> +{
> +	struct omap_wake_platform_data *pdata = pdev->dev.platform_data;
> +	struct gpio_wake *gw;
> +	int i;
> +
> +	for (i = 0; i < pdata->gpio_wake_num; i++) {
> +		gw = pdata->qpio_wakes + i;
> +
> +		free_irq(gpio_to_irq(gw->gpio), (void *)gw->name);
> +	}
> +
> +	printk(KERN_INFO "Wake-up event: %s\n", wakeup_event);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver omap_wake_driver = {
> +	.probe          = omap_wake_probe,
> +	.remove		= __devexit_p(omap_wake_remove),
> +	.suspend	= omap_wake_suspend,
> +	.resume_early	= omap_wake_resume_early,
> +	.resume		= omap_wake_resume,
> +	.driver         = {
> +		.name   = "omap-wake",
> +		.owner  = THIS_MODULE,
> +	},
> +};
> +
> +static int __init omap_wake_init(void)
> +{
> +	return platform_driver_register(&omap_wake_driver);
> +}
> +
> +module_init(omap_wake_init);
> +
> +static void __exit omap_wake_exit(void)
> +{
> +	platform_driver_unregister(&omap_wake_driver);
> +}
> +module_exit(omap_wake_exit);
> +
> +MODULE_AUTHOR("Kim Kyuwon <q1.kim@samsung.com>");
> +MODULE_DESCRIPTION("OMAP34xx wakeup driver");
> +MODULE_LICENSE("GPL v2");
> diff --git a/arch/arm/plat-omap/Kconfig b/arch/arm/plat-omap/Kconfig
> index 407bd32..7a73c95 100644
> --- a/arch/arm/plat-omap/Kconfig
> +++ b/arch/arm/plat-omap/Kconfig
> @@ -176,6 +176,15 @@ config OMAP_MBOX_FWK
>  	  Say Y here if you want to use OMAP Mailbox framework support for
>  	  DSP, IVA1.0 and IVA2 in OMAP1/2/3.
>
> +config OMAP_WAKE
> +	tristate "OMAP34xx wakeup source support"
> +	depends on ARCH_OMAP34XX && PM
> +	default n
> +	help
> +	  Select this option if you want to know what kind of wake-up event
> +	  wakes up your board from the low power mode. And this option
> +	  provides the unified GPIO wake-up source configuration.
> +
>  choice
>          prompt "System timer"
>  	default OMAP_MPU_TIMER
> diff --git a/arch/arm/plat-omap/include/mach/irqs.h
> b/arch/arm/plat-omap/include/mach/irqs.h
> index d12c39f..656cc04 100644
> --- a/arch/arm/plat-omap/include/mach/irqs.h
> +++ b/arch/arm/plat-omap/include/mach/irqs.h
> @@ -385,6 +385,7 @@
>  #ifndef __ASSEMBLY__
>  extern void omap_init_irq(void);
>  extern int omap_irq_pending(void);
> +extern int omap_get_pending_mpu_irq(unsigned int irq_start);
>  #endif
>
>  #include <mach/hardware.h>
> diff --git a/arch/arm/plat-omap/include/mach/wake.h
> b/arch/arm/plat-omap/include/mach/wake.h
> new file mode 100644
> index 0000000..213b263
> --- /dev/null
> +++ b/arch/arm/plat-omap/include/mach/wake.h
> @@ -0,0 +1,30 @@
> +/*
> + * wake.h
> + *
> + * Copyright (c) 2009 Samsung Eletronics
> + *
> + * Author: Kim Kyuwon <q1.kim@samsung.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#ifndef _WAKE_H_
> +#define _WAKE_H_
> +
> +struct gpio_wake {
> +	unsigned int 	gpio;
> +	unsigned long	irqflag;
> +	const char 	*name;
> +	int		request;
> +};
> +
> +struct omap_wake_platform_data{
> +	struct gpio_wake	*qpio_wakes;
> +	int			gpio_wake_num;
> +};
> +
> +#endif /* _WAKE_H_ */
> +
> -- 
> 1.5.2.5
>
>
> -- 
> Q1

Kevin

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH 1/1] OMAP3: PM: Add the wakeup source driver
  2009-03-17 21:47 ` Kevin Hilman
@ 2009-03-19  4:19   ` Kim Kyuwon
  0 siblings, 0 replies; 3+ messages in thread
From: Kim Kyuwon @ 2009-03-19  4:19 UTC (permalink / raw)
  To: Kevin Hilman; +Cc: OMAP, Tony Lindgren, 박경민

On Wed, Mar 18, 2009 at 6:47 AM, Kevin Hilman
<khilman@deeprootsystems.com> wrote:
> Kim Kyuwon <chammoru@gmail.com> writes:
>
>> Sometimes, it is necessary to find out "what does wake up my board?".
>> Notifying wake-up source feature may be used to blame unexpected
>> wake-up events which increase power consumption. And user mode
>> applications can act smartly according to the wake-up event to
>> minimize power consumption.
>
> Hi Kim,
>
> This is a very useful feature, and something that's is lacking in the
> current PM code.  Thank you so much for this contribution.

Hi Kevin,

Thank you very much for your encouragement, comments and suggestions.
I tried to follow most of your suggestions, but I can miss something.
Please check my new patch again.
My answers are below and I'll send the new patch right away.

> Currently this driver only works for wakeups from suspend.  I think
> the description should be updated to specify that this only affects
> wakeups from susupend, and not wakeups from idle.

OK, I fixed it.

> Speaking of which, have you considered extending it to handle wakeups
> from idle?  Currently tools like powertop give a pretty good idea as
> to why the system is coming out of idle, so this may not be necessary,
> but was just wondering if you had considered it.

I designed this driver only for wakeups from suspend, but thanks for
letting me know that.

> More comments and suggestions on general organization below.
>
>> This driver uses sysfs interface to give
>> information to user mode applications like:
>>
>> cat /sys/power/wakeup_irq
>> cat /sys/power/wakeup_event
>
> If only suspend/resume is being handled, maybe 'resume' is a better
> name than 'wakeup'.  These would be better named:
>
>  /sys/power/omap_resume_irq
>  /sys/power/omap_resume_event

OK, I changed those. But I wish not to replace 'wake' or 'wakeup'
strings with 'resume' in other symbols and filenames.

>> This driver also privides the unified GPIO wake-up source
>> configuration. specific GPIO settings in the board files are:
>>
>> /* Wakeup source configuration */
>> static struct gpio_wake boardname_gpio_wake[] = {
>>       { 23,   IRQF_TRIGGER_RISING | IRQF_SHARED,      "BT_WAKEUP",    1},
>>       { 24,   IRQF_TRIGGER_RISING | IRQF_SHARED,      "USB_DETECT",   1},
>> };
>
> Based on the way this works, I think the board code should not
> be requird to set IRQF_SHARED.  I think this should be explicitly
> ORed in in the wake_suspend hook which requests the IRQ since this will
> not work correctly without using IRQF_SHARED.

Thanks, I fixed it.

>> static struct omap_wake_platform_data boardname_wake_data = {
>>       .qpio_wakes             = boardname_gpio_wake,
>>       .gpio_wake_num          = ARRAY_SIZE(boardname_gpio_wake),
>> };
>
> I assume that 'qpio' is a typo and should be gpio?

Oops, thanks, I fixed it.

> Also, I'm not sure I agree with adding a generic GPIO wakeup
> interface.  I believe this should be done by the driver using the GPIO
> itself.  However, I can see this being useful to test external
> wakeup events when no driver is present.

I agree with you that it's better that each driver configure GPIO
wakeup. But, as you said, GPIO wakeup interface in this driver is
useful when the specific driver doesn't exist.

Somewhat different topic: All OMAP boards on which l-o tree kernel is
running have the same wake-up source setup as written in the
prcm_setup_regs() and omap3_pm_init(). But I think this wake-up policy
is a board specific feature, so I wish this driver can take charge of
board specific wake-up configurations in the future. [So... I think
GPIO wakeup interface would become a part of it :)]

>> static struct platform_device boardname_wakeup = {
>>       .name                   = "omap-wake",
>>       .id                     = -1,
>>       .dev                    = {
>>               .platform_data  = &boardname_wake_data,
>>       },
>> };
>>
>> Signed-off-by: Kim Kyuwon <q1.kim@samsung.com>
>> ---
>>  arch/arm/mach-omap2/Makefile           |    1 +
>>  arch/arm/mach-omap2/irq.c              |   41 +++
>>  arch/arm/mach-omap2/prcm-common.h      |    4 +
>>  arch/arm/mach-omap2/prm-regbits-34xx.h |    6 +
>>  arch/arm/mach-omap2/wake34xx.c         |  470 ++++++++++++++++++++++++++++++++
>>  arch/arm/plat-omap/Kconfig             |    9 +
>>  arch/arm/plat-omap/include/mach/irqs.h |    1 +
>>  arch/arm/plat-omap/include/mach/wake.h |   29 ++
>>  8 files changed, 561 insertions(+), 0 deletions(-)
>>  create mode 100644 arch/arm/mach-omap2/wake34xx.c
>>  create mode 100644 arch/arm/plat-omap/include/mach/wake.h
>>
>> diff --git a/arch/arm/mach-omap2/Makefile b/arch/arm/mach-omap2/Makefile
>> index ba65cab..1ab87e7 100644
>> --- a/arch/arm/mach-omap2/Makefile
>> +++ b/arch/arm/mach-omap2/Makefile
>> @@ -25,6 +25,7 @@ obj-$(CONFIG_ARCH_OMAP2)            += pm24xx.o
>>  obj-$(CONFIG_ARCH_OMAP24XX)          += sleep24xx.o
>>  obj-$(CONFIG_ARCH_OMAP3)             += pm34xx.o sleep34xx.o
>>  obj-$(CONFIG_PM_DEBUG)                       += pm-debug.o
>> +obj-$(CONFIG_OMAP_WAKE)                      += wake34xx.o
>>  endif
>>
>>  # SmartReflex driver
>> diff --git a/arch/arm/mach-omap2/irq.c b/arch/arm/mach-omap2/irq.c
>> index 2842fe8..21f3d83 100644
>> --- a/arch/arm/mach-omap2/irq.c
>> +++ b/arch/arm/mach-omap2/irq.c
>> @@ -177,6 +177,47 @@ int omap_irq_pending(void)
>>       return 0;
>>  }
>>
>> +/*
>> + * Get the first pending MPU IRQ number from 'irq_start'.
>> + * If none, return -1.
>> + */
>> +int omap_get_pending_mpu_irq(unsigned int irq_start)
>> +{
>> +     struct omap_irq_bank *bank = irq_banks;
>> +     int irq, bits_skip, bit_start;
>> +
>> +     if (irq_start >= bank->nr_irqs)
>> +             return -1;
>
> return -EINVAL;

OK, I fixed it.

>> +     bits_skip = irq_start % IRQ_BITS_PER_REG;
>> +     bit_start = irq_start - bits_skip;
>> +
>> +     for (irq = bit_start; irq < bank->nr_irqs; irq += IRQ_BITS_PER_REG) {
>> +             int ret, i, limit, offset = irq & (~(IRQ_BITS_PER_REG - 1));
>
> The name 'ret' is normally used for a return value, and you're not using it
> that way.

OK, I changed it.

>> +             ret = intc_bank_read_reg(bank, (INTC_PENDING_IRQ0 + offset));
>> +             if (!ret)
>> +                     continue;
>> +
>> +             limit = IRQ_BITS_PER_REG;
>> +
>> +             if (bit_start == irq) {
>> +                     ret >>= bits_skip;
>> +                     limit -= bits_skip;
>> +             } else
>> +                     bits_skip = 0;
>> +
>> +             for (i = 0; i < limit; i++) {
>> +                     if (ret & 0x1)
>> +                             return irq + i + bits_skip;
>> +                     else
>> +                             ret >>= 1;
>> +             }
>> +     }
>
> I think you could do this more efficiently using find_first_bit() and
> find_next_bit().  See <linux/bitops.h>

Oh, It becomes beautiful. thanks.

>> +     return -1;
>
> return -EINVAL;
>

Ok.

>> +}
>> +
>
> In general, I think I would like to see the actual suspend/resume path code
> optimizied a little more.  Leave the string processing and printing to
> the sysfs code or CONFIG_PM_DEBUG code in the resume path.
>
> Here's my idea:  in irq.c, just have a get_pending_irqs() function which
> returns the IRQ_PENDINGx registers (only 1 for OMAP2 but 3 for OMAP3.)
> The suspend/resume path just saves these away for use by printing code
> in the resume path (CONFIG_PM_DEBUG) and the sysfs path.

That's good idea. I modified as what you said.

>>  void __init omap_init_irq(void)
>>  {
>>       unsigned long nr_of_irqs = 0;
>> diff --git a/arch/arm/mach-omap2/prcm-common.h
>> b/arch/arm/mach-omap2/prcm-common.h
>> index cb1ae84..1f340aa 100644
>> --- a/arch/arm/mach-omap2/prcm-common.h
>> +++ b/arch/arm/mach-omap2/prcm-common.h
>> @@ -273,6 +273,10 @@
>>  #define OMAP3430_ST_D2D_SHIFT                                3
>>  #define OMAP3430_ST_D2D_MASK                         (1 << 3)
>>
>> +/* PM_WKST3_CORE, CM_IDLEST3_CORE shared bits */
>> +#define OMAP3430_ST_USBTLL_SHIFT                     2
>> +#define OMAP3430_ST_USBTLL_MASK                              (1 << 2)
>> +
>>  /* CM_FCLKEN_WKUP, CM_ICLKEN_WKUP, PM_WKEN_WKUP shared bits */
>>  #define OMAP3430_EN_GPIO1                            (1 << 3)
>>  #define OMAP3430_EN_GPIO1_SHIFT                              3
>> diff --git a/arch/arm/mach-omap2/prm-regbits-34xx.h
>> b/arch/arm/mach-omap2/prm-regbits-34xx.h
>> index d73eee8..661d37d 100644
>> --- a/arch/arm/mach-omap2/prm-regbits-34xx.h
>> +++ b/arch/arm/mach-omap2/prm-regbits-34xx.h
>> @@ -332,6 +332,8 @@
>>  /* PM_IVA2GRPSEL1_CORE specific bits */
>>
>>  /* PM_WKST1_CORE specific bits */
>> +#define OMAP3430_ST_MMC3_SHIFT                               30
>> +#define OMAP3430_ST_MMC3_MASK                                (1 << 30)
>>
>>  /* PM_PWSTCTRL_CORE specific bits */
>>  #define OMAP3430_MEM2ONSTATE_SHIFT                   18
>> @@ -373,6 +375,7 @@
>>  /* PM_IVA2GRPSEL_WKUP specific bits */
>>
>>  /* PM_WKST_WKUP specific bits */
>> +#define OMAP3430_ST_IO_CHAIN                         (1 << 16)
>>  #define OMAP3430_ST_IO                                       (1 << 8)
>>
>>  /* PRM_CLKSEL */
>> @@ -430,6 +433,9 @@
>>
>>  /* PM_PREPWSTST_PER specific bits */
>>
>> +/* PM_WKST_USBHOST specific bits */
>> +#define OMAP3430_ST_USBHOST                          (1 << 0)
>> +
>>  /* RM_RSTST_EMU specific bits */
>>
>>  /* PM_PWSTST_EMU specific bits */
>> diff --git a/arch/arm/mach-omap2/wake34xx.c b/arch/arm/mach-omap2/wake34xx.c
>> new file mode 100644
>> index 0000000..2390bca
>> --- /dev/null
>> +++ b/arch/arm/mach-omap2/wake34xx.c
>> @@ -0,0 +1,466 @@
>> +/*
>> + * wake34xx.c
>> + *
>> + * Copyright (c) 2009 Samsung Eletronics
>> + *
>> + * Author: Kim Kyuwon <q1.kim@samsung.com>
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + *
>> + */
>> +
>> +#include <linux/kernel.h>
>> +#include <linux/init.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/interrupt.h>
>> +
>> +#include <mach/gpio.h>
>> +#include <mach/wake.h>
>> +
>> +#include "prm-regbits-34xx.h"
>> +
>> +/*
>> + * Sometimes, it is necessary to find out "what does wake up my board?"
>> + * Notifying wake-up source feature may be used to blame unexpected wake-up
>> + * events which increase power consumption. And user mode applications can
>> + * act smartly according to the wake-up event to minimize power consumption.
>> + * This driver uses sysfs interface to give information to user mode
>> + * applications.
>> + */
>> +
>> +#define WAKE_STR_LEN 64
>> +
>> +char wakeup_irq[WAKE_STR_LEN] = "None";
>> +char wakeup_event[WAKE_STR_LEN] = "None";
>> +
>> +struct wake_event {
>> +     int             index;
>> +     const char      *name;
>> +};
>
> The name 'index' isn't quite right here.  This is a bitmask
> used compare against the PM_WKST_<domain> register.  I suggest
> 'u32 mask' or something.

I like 'u32 mask', thanks.

>> +static struct wake_event omap3_wkup_events[] = {
>> +     { OMAP3430_ST_IO_CHAIN,                 "ST_IO" },
>> +     { OMAP3430_ST_IO,                       "ST_SR2" },
>> +     { OMAP3430_ST_SR2_MASK,                 "ST_SR2" },
>> +     { OMAP3430_ST_SR1_MASK,                 "ST_SR1" },
>> +     { OMAP3430_ST_GPIO1_MASK,               "ST_GPIO1" },
>> +     { OMAP3430_ST_GPT12_MASK,               "ST_GPT12" },
>> +     { OMAP3430_ST_GPT1_MASK,                "ST_GPT1" },
>> +};
>> +
>> +static struct wake_event omap3_per_events[] = {
>> +     { OMAP3430_ST_GPIO6_MASK,               "ST_GPIO6" },
>> +     { OMAP3430_ST_GPIO5_MASK,               "ST_GPIO5" },
>> +     { OMAP3430_ST_GPIO4_MASK,               "ST_GPIO4" },
>> +     { OMAP3430_ST_GPIO3_MASK,               "ST_GPIO3" },
>> +     { OMAP3430_ST_GPIO2_MASK,               "ST_GPIO2" },
>> +     { OMAP3430_ST_UART3_MASK,               "ST_UART3" },
>> +     { OMAP3430_ST_GPT9_MASK,                "ST_GPT9" },
>> +     { OMAP3430_ST_GPT8_MASK,                "ST_GPT8" },
>> +     { OMAP3430_ST_GPT7_MASK,                "ST_GPT7" },
>> +     { OMAP3430_ST_GPT6_MASK,                "ST_GPT6" },
>> +     { OMAP3430_ST_GPT5_MASK,                "ST_GPT5" },
>> +     { OMAP3430_ST_GPT4_MASK,                "ST_GPT4" },
>> +     { OMAP3430_ST_GPT3_MASK,                "ST_GPT3" },
>> +     { OMAP3430_ST_GPT2_MASK,                "ST_GPT2" },
>> +     { OMAP3430_EN_MCBSP4,                   "EN_MCBSP4" },
>> +     { OMAP3430_EN_MCBSP3,                   "EN_MCBSP3" },
>> +     { OMAP3430_EN_MCBSP2,                   "EN_MCBSP2" },
>> +};
>> +
>> +static struct wake_event omap3_core1_events[] = {
>> +     { OMAP3430_ST_MMC3_MASK,                "ST_MMC3" },
>> +     { OMAP3430_ST_MMC2_MASK,                "ST_MMC2" },
>> +     { OMAP3430_ST_MMC1_MASK,                "ST_MMC1" },
>> +     { OMAP3430_ST_MCSPI4_MASK,              "ST_MCSPI4" },
>> +     { OMAP3430_ST_MCSPI3_MASK,              "ST_MCSPI3" },
>> +     { OMAP3430_ST_MCSPI2_MASK,              "ST_MCSPI2" },
>> +     { OMAP3430_ST_MCSPI1_MASK,              "ST_MCSPI1" },
>> +     { OMAP3430_ST_I2C3_MASK,                "ST_I2C3" },
>> +     { OMAP3430_ST_I2C2_MASK,                "ST_I2C2" },
>> +     { OMAP3430_ST_I2C1_MASK,                "ST_I2C1" },
>> +     { OMAP3430_ST_UART1_MASK,               "ST_UART1" },
>> +     { OMAP3430_ST_GPT11_MASK,               "ST_GPT11" },
>> +     { OMAP3430_ST_GPT10_MASK,               "ST_GPT10" },
>> +     { OMAP3430_ST_MCBSP5_MASK,              "ST_MCBSP5" },
>> +     { OMAP3430_ST_MCBSP1_MASK,              "ST_MCBSP1" },
>> +};
>> +
>> +static struct wake_event omap3es1_core1_events[] = {
>> +     { OMAP3430ES1_ST_FSHOSTUSB_MASK,        "ST_FSHOSTUSB" },
>> +     { OMAP3430ES1_ST_HSOTGUSB_MASK,         "ST_HSOTGUSB" },
>> +     { OMAP3430_ST_D2D_MASK,                 "ST_D2D" },
>> +};
>> +
>> +static struct wake_event omap3es2_core1_events[] = {
>> +     { OMAP3430ES2_ST_HSOTGUSB_STDBY_MASK,   "ST_HSOTGUSB" },
>> +};
>> +
>> +static struct wake_event omap3_core2_events[] = {
>> +     { OMAP3430_ST_USBTLL_MASK,              "ST_USBTLL" },
>> +};
>> +
>> +static struct wake_event omap3_usbhost_events[] = {
>> +     { OMAP3430_ST_USBHOST,                  "ST_USBHOST" },
>> +};
>> +
>> +static ssize_t wakeup_source_show(struct kobject *kobj,
>> +                             struct kobj_attribute *attr, char *buf);
>> +
>> +static void omap_wake_update_event(char *event)
>> +{
>> +     int len;
>> +
>> +     if (wakeup_event[0])
>> +             len = strlen(wakeup_event) + strlen(event) + 2;
>> +     else
>> +             len = strlen(wakeup_event) + strlen(event);
>> +
>> +     if (len > WAKE_STR_LEN - 1) {
>> +             printk(KERN_ERR "Can't record the wakeup event: %s\n", event);
>> +             return;
>> +     }
>> +
>> +     if (wakeup_event[0])
>> +             strcat(wakeup_event, ", ");
>> +     strcat(wakeup_event, event);
>> +}
>> +
>> +static void omap_wake_detect_wkup(u32 wkst, char *event)
>> +{
>> +     int i;
>> +
>> +     for (i = 0; i < ARRAY_SIZE(omap3_wkup_events); i++)
>> +             if (wkst & omap3_wkup_events[i].index) {
>> +                     sprintf(event, "%s",
>> +                                     omap3_wkup_events[i].name);
>> +                     return;
>> +             }
>> +
>> +     sprintf(event, "WKUP:0x%08x", wkst);
>> +}
>
> I don't think the string processing should be done in the actual
> wakeup path.  Rather, do the string processing only when the /sys
> entries are used.  We want to keep the suspend and resume path fast
> wherever possible.
>
> Also, this (re)reading of the WKST registers could probably be saved
> as this is done in the PRCM interrupt handler in pm34xx.c after each
> wakeup.
>
> Maybe what should be done is for the PRCM interrupt handler to save
> the last values of the WKST registers for each domain, and then we add
> a function to prcm34xx.c which returns these registers.  Then in your
> wakeup code, you call that func to get the WKST values.  Any non-zero
> WKST values could be parsed and displayed, but the parsing and
> printing of these should probably be conditional on CONFIG_PM_DEBUG=y
> so the default path stays fast.

I modified it as you said.

>> +static void omap_wake_detect_per(u32 wkst, char *event) { int i;
>> +
>> +     for (i = 0; i < ARRAY_SIZE(omap3_per_events); i++)
>> +             if (wkst & omap3_per_events[i].index) {
>> +                     sprintf(event, "%s",
>> +                                     omap3_per_events[i].name);
>> +                     return;
>> +             }
>> +
>> +     sprintf(event, "PER:0x%08x", wkst);
>> +}
>> +
>> +static void omap_wake_detect_core1(u32 wkst, char *event)
>> +{
>> +     int i;
>> +
>> +     for (i = 0; i < ARRAY_SIZE(omap3_core1_events); i++)
>> +             if (wkst & omap3_core1_events[i].index) {
>> +                     sprintf(event, "%s",
>> +                                     omap3_core1_events[i].name);
>> +                     return;
>> +             }
>> +
>> +     if (omap_rev() == OMAP3430_REV_ES1_0) {
>> +             for (i = 0; i < ARRAY_SIZE(omap3es1_core1_events); i++) {
>> +                     if (wkst & omap3es1_core1_events[i].index) {
>> +                             sprintf(event, "%s",
>> +                                     omap3es1_core1_events[i].name);
>> +                             return;
>> +                     }
>> +             }
>> +     } else {
>> +             for (i = 0; i < ARRAY_SIZE(omap3es2_core1_events); i++) {
>> +                     if (wkst & omap3es2_core1_events[i].index) {
>> +                             sprintf(event, "%s",
>> +                                     omap3es2_core1_events[i].name);
>> +                             return;
>> +                     }
>> +             }
>> +     }
>> +
>> +     sprintf(event, "CORE1:0x%08x", wkst);
>> +}
>> +
>> +static void omap_wake_detect_core2(u32 wkst, char *event)
>> +{
>> +     int i;
>> +
>> +     for (i = 0; i < ARRAY_SIZE(omap3_core2_events); i++)
>> +             if (wkst & omap3_core2_events[i].index) {
>> +                     sprintf(event, "%s",
>> +                                     omap3_core2_events[i].name);
>> +                     return;
>> +             }
>> +
>> +     sprintf(event, "CORE2:0x%08x", wkst);
>> +}
>> +
>> +static void omap_wake_detect_usbhost(u32 wkst, char *event)
>> +{
>> +     int i;
>> +
>> +     for (i = 0; i < ARRAY_SIZE(omap3_usbhost_events); i++)
>> +             if (wkst & omap3_usbhost_events[i].index) {
>> +                     sprintf(event, "%s",
>> +                                     omap3_usbhost_events[i].name);
>> +                     return;
>> +             }
>> +
>> +     sprintf(event, "USBHOST:0x%08x", wkst);
>> +}
>
> These omap_wake_detect_* functions are all basically the same function
> but for a different powerdomain.  Can you convert all these to a single
> function which gets passed the powerdomain and the events array?

Sure, I can. But omap_wake_detect_core1() function is a little bit
different from others, because it checks OMAP3430 revision. So I will
just add a helper function which gets passed the the powerdomain and
the events array, but only do 'for' loop. If you are not comfortable
with this, please tell me again.

>> +/* Detect wake-up events */
>> +static void omap_wake_detect_wakeup(void)
>> +{
>> +     u32 wkst;
>> +     char buf[32] = {0, };
>> +     int irq, len;
>> +
>> +     /* Initialize global string variables */
>> +     memset(wakeup_irq, 0x0, WAKE_STR_LEN);
>> +     memset(wakeup_event, 0x0, WAKE_STR_LEN);
>
> Is this needed every time?  Isn't there already a default case
> which sets it to 'None'?

I did strcat operation, so it was needed. But I removed in new version.

>> +     /* IRQ */
>> +     irq = omap_get_pending_mpu_irq(0);
>> +     while (irq >= 0) {
>> +             if (irq == INT_34XX_SYS_NIRQ)
>> +                     omap_wake_update_event("sys_nirq");
>> +
>> +             len = strlen(wakeup_irq) + sprintf(buf, "%d", irq);
>> +             if (len > WAKE_STR_LEN - 1)
>> +                     break;
>> +
>> +             strcat(wakeup_irq, buf);
>> +
>> +             irq = omap_get_pending_mpu_irq(irq + 1);
>> +             if (irq >= 0) {
>> +                     len = strlen(wakeup_irq) + 2;
>> +                     if (len > WAKE_STR_LEN - 1)
>> +                             break;
>> +
>> +                     strcat(wakeup_irq, ", ");
>> +             }
>> +     }
>> +     if (!wakeup_irq[0])
>> +             strncpy(wakeup_irq, "Unknown", WAKE_STR_LEN - 1);
>> +     printk(KERN_INFO "Wake-up IRQ: %s\n", wakeup_irq);
>> +
>
> Here is where you could just call into pm34xx.c to get the WKST
> registers from the last PRCM interrupt.

I see.

>> +     /* WKUP */
>> +     wkst = prm_read_mod_reg(WKUP_MOD, PM_WKST);
>> +     if (wkst) {
>> +             omap_wake_detect_wkup(wkst, buf);
>> +             omap_wake_update_event(buf);
>> +     }
>>
>> +     /* PER */
>> +     wkst = prm_read_mod_reg(OMAP3430_PER_MOD, PM_WKST);
>> +     if (wkst) {
>> +             omap_wake_detect_per(wkst, buf);
>> +             omap_wake_update_event(buf);
>> +     }
>> +
>> +     /* CORE */
>> +     wkst = prm_read_mod_reg(CORE_MOD, PM_WKST1);
>> +     if (wkst) {
>> +             omap_wake_detect_core1(wkst, buf);
>> +             omap_wake_update_event(buf);
>> +     }
>> +     wkst = prm_read_mod_reg(CORE_MOD, OMAP3430ES2_PM_WKST3);
>> +     if (wkst) {
>> +             omap_wake_detect_core2(wkst, buf);
>> +             omap_wake_update_event(buf);
>> +     }
>> +
>> +     /* USBHOST */
>> +     wkst = prm_read_mod_reg(OMAP3430ES2_USBHOST_MOD, PM_WKST);
>> +     if (wkst) {
>> +             omap_wake_detect_usbhost(wkst, buf);
>> +             omap_wake_update_event(buf);
>> +     }
>>
>> +     if (!wakeup_event[0])
>> +             strncpy(wakeup_event, "Unknown", WAKE_STR_LEN - 1);
>> +}
>> +
>> +static struct kobj_attribute wakeup_irq_attr =
>> +     __ATTR(wakeup_irq, 0644, wakeup_source_show, NULL);
>> +
>> +static struct kobj_attribute wakeup_event_attr =
>> +     __ATTR(wakeup_event, 0644, wakeup_source_show, NULL);
>> +
>> +static ssize_t wakeup_source_show(struct kobject *kobj,
>> +                             struct kobj_attribute *attr, char *buf)
>> +{
>> +     if (attr == &wakeup_irq_attr)
>> +             return sprintf(buf, "%s\n", wakeup_irq);
>> +     else if (attr == &wakeup_event_attr)
>> +             return sprintf(buf, "%s\n", wakeup_event);
>> +     else
>> +             return -EINVAL;
>> +}
>> +
>> +static irqreturn_t omap_wake_detect_gpio(int irq, void *dev_id)
>> +{
>> +     if (!strncmp(wakeup_event, "Unknown", WAKE_STR_LEN - 1))
>> +             strncpy(wakeup_event, dev_id, WAKE_STR_LEN - 1);
>> +     else
>> +             omap_wake_update_event(dev_id);
>> +
>> +     return IRQ_HANDLED;
>> +}
>> +
>> +static int __devinit omap_wake_probe(struct platform_device *pdev)
>> +{
>> +     struct omap_wake_platform_data *pdata = pdev->dev.platform_data;
>> +     struct gpio_wake *gw;
>> +     int i, ret;
>> +
>> +     /*
>> +      * It may be good to configure GPIO wake-up sources in each driver.
>> +      * Buf if the specific device driver doesn't exist, you can use
>> +      * omap-wake driver to configure gpio wake-up sources.
>> +      */
>> +     for (i = 0; i < pdata->gpio_wake_num; i++) {
>> +             gw = pdata->qpio_wakes + i;
>> +
>> +             if (gw->request) {
>> +                     ret = gpio_request(gw->gpio, gw->name);
>> +                     if (ret) {
>> +                             dev_err(&pdev->dev, "can't request gpio%d"
>> +                                     ", return %d\n", gw->gpio, ret);
>> +                             goto failed_free_gpio;
>> +                     }
>> +             }
>> +             gpio_direction_input(gw->gpio);
>> +             enable_irq_wake(gpio_to_irq(gw->gpio));
>> +     }
>> +
>> +     ret = sysfs_create_file(power_kobj, &wakeup_irq_attr.attr);
>> +     if (ret)
>> +             dev_err(&pdev->dev, "sysfs_create_file %s failed: %d\n",
>> +                                     wakeup_irq_attr.attr.name, ret);
>> +
>> +     ret = sysfs_create_file(power_kobj, &wakeup_event_attr.attr);
>> +     if (ret)
>> +             dev_err(&pdev->dev, "sysfs_create_file %s failed: %d\n",
>> +                                     wakeup_event_attr.attr.name, ret);
>> +
>> +     return 0;
>> +
>> +failed_free_gpio:
>> +     for (i--; i >= 0; i--) {
>> +             gw = pdata->qpio_wakes + i;
>> +
>> +             if (gw->request)
>> +                     gpio_free(gw->gpio);
>> +     }
>> +
>> +     return ret;
>> +}
>> +
>> +static int __devexit omap_wake_remove(struct platform_device *pdev)
>> +{
>> +     struct omap_wake_platform_data *pdata = pdev->dev.platform_data;
>> +     struct gpio_wake *gw;
>> +     int i;
>> +
>> +     for (i = 0; i < pdata->gpio_wake_num; i++) {
>> +             gw = pdata->qpio_wakes + i;
>> +
>> +             if (gw->request)
>> +                     gpio_free(gw->gpio);
>> +
>> +             disable_irq_wake(gpio_to_irq(gw->gpio));
>> +     }
>> +
>> +     return 0;
>> +}
>> +
>> +static int omap_wake_suspend(struct platform_device *pdev, pm_message_t state)
>> +{
>> +     struct omap_wake_platform_data *pdata = pdev->dev.platform_data;
>> +     struct gpio_wake *gw;
>> +     int i, ret;
>> +
>> +     for (i = 0; i < pdata->gpio_wake_num; i++) {
>> +             gw = pdata->qpio_wakes + i;
>> +
>> +             ret = request_irq(gpio_to_irq(gw->gpio), omap_wake_detect_gpio,
>> +                             gw->irqflag, gw->name, (void *)gw->name);
>> +             if (ret) {
>> +                     dev_err(&pdev->dev, "can't get IRQ%d, return %d\n",
>> +                                     gpio_to_irq(gw->gpio), ret);
>> +                     goto failed_free_irq;
>> +             }
>> +     }
>> +
>> +     return 0;
>> +
>> +failed_free_irq:
>> +     for (i--; i >= 0; i--) {
>> +             gw = pdata->qpio_wakes + i;
>> +             free_irq(gpio_to_irq(gw->gpio),  (void *)gw->name);
>> +     }
>> +
>> +     return ret;
>> +}
>> +
>> +static int omap_wake_resume_early(struct platform_device *pdev)
>> +{
>> +     omap_wake_detect_wakeup();
>> +
>> +     return 0;
>> +}
>> +
>> +static int omap_wake_resume(struct platform_device *pdev)
>> +{
>> +     struct omap_wake_platform_data *pdata = pdev->dev.platform_data;
>> +     struct gpio_wake *gw;
>> +     int i;
>> +
>> +     for (i = 0; i < pdata->gpio_wake_num; i++) {
>> +             gw = pdata->qpio_wakes + i;
>> +
>> +             free_irq(gpio_to_irq(gw->gpio), (void *)gw->name);
>> +     }
>> +
>> +     printk(KERN_INFO "Wake-up event: %s\n", wakeup_event);
>> +
>> +     return 0;
>> +}
>> +
>> +static struct platform_driver omap_wake_driver = {
>> +     .probe          = omap_wake_probe,
>> +     .remove         = __devexit_p(omap_wake_remove),
>> +     .suspend        = omap_wake_suspend,
>> +     .resume_early   = omap_wake_resume_early,
>> +     .resume         = omap_wake_resume,
>> +     .driver         = {
>> +             .name   = "omap-wake",
>> +             .owner  = THIS_MODULE,
>> +     },
>> +};
>> +
>> +static int __init omap_wake_init(void)
>> +{
>> +     return platform_driver_register(&omap_wake_driver);
>> +}
>> +
>> +module_init(omap_wake_init);
>> +
>> +static void __exit omap_wake_exit(void)
>> +{
>> +     platform_driver_unregister(&omap_wake_driver);
>> +}
>> +module_exit(omap_wake_exit);
>> +
>> +MODULE_AUTHOR("Kim Kyuwon <q1.kim@samsung.com>");
>> +MODULE_DESCRIPTION("OMAP34xx wakeup driver");
>> +MODULE_LICENSE("GPL v2");
>> diff --git a/arch/arm/plat-omap/Kconfig b/arch/arm/plat-omap/Kconfig
>> index 407bd32..7a73c95 100644
>> --- a/arch/arm/plat-omap/Kconfig
>> +++ b/arch/arm/plat-omap/Kconfig
>> @@ -176,6 +176,15 @@ config OMAP_MBOX_FWK
>>         Say Y here if you want to use OMAP Mailbox framework support for
>>         DSP, IVA1.0 and IVA2 in OMAP1/2/3.
>>
>> +config OMAP_WAKE
>> +     tristate "OMAP34xx wakeup source support"
>> +     depends on ARCH_OMAP34XX && PM
>> +     default n
>> +     help
>> +       Select this option if you want to know what kind of wake-up event
>> +       wakes up your board from the low power mode. And this option
>> +       provides the unified GPIO wake-up source configuration.
>> +
>>  choice
>>          prompt "System timer"
>>       default OMAP_MPU_TIMER
>> diff --git a/arch/arm/plat-omap/include/mach/irqs.h
>> b/arch/arm/plat-omap/include/mach/irqs.h
>> index d12c39f..656cc04 100644
>> --- a/arch/arm/plat-omap/include/mach/irqs.h
>> +++ b/arch/arm/plat-omap/include/mach/irqs.h
>> @@ -385,6 +385,7 @@
>>  #ifndef __ASSEMBLY__
>>  extern void omap_init_irq(void);
>>  extern int omap_irq_pending(void);
>> +extern int omap_get_pending_mpu_irq(unsigned int irq_start);
>>  #endif
>>
>>  #include <mach/hardware.h>
>> diff --git a/arch/arm/plat-omap/include/mach/wake.h
>> b/arch/arm/plat-omap/include/mach/wake.h
>> new file mode 100644
>> index 0000000..213b263
>> --- /dev/null
>> +++ b/arch/arm/plat-omap/include/mach/wake.h
>> @@ -0,0 +1,30 @@
>> +/*
>> + * wake.h
>> + *
>> + * Copyright (c) 2009 Samsung Eletronics
>> + *
>> + * Author: Kim Kyuwon <q1.kim@samsung.com>
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + *
>> + */
>> +
>> +#ifndef _WAKE_H_
>> +#define _WAKE_H_
>> +
>> +struct gpio_wake {
>> +     unsigned int    gpio;
>> +     unsigned long   irqflag;
>> +     const char      *name;
>> +     int             request;
>> +};
>> +
>> +struct omap_wake_platform_data{
>> +     struct gpio_wake        *qpio_wakes;
>> +     int                     gpio_wake_num;
>> +};
>> +
>> +#endif /* _WAKE_H_ */
>> +
>> --
>> 1.5.2.5
>>
>>
>> --
>> Q1
>
> Kevin
>



-- 
Kim Kyuwon

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2009-03-19  4:19 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2009-03-03 10:37 [PATCH 1/1] OMAP3: PM: Add the wakeup source driver Kim Kyuwon
2009-03-17 21:47 ` Kevin Hilman
2009-03-19  4:19   ` Kim Kyuwon

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).