From patchwork Fri Apr 3 10:43:42 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: kim kyuwon X-Patchwork-Id: 16088 X-Patchwork-Delegate: khilman@deeprootsystems.com Received: from vger.kernel.org (vger.kernel.org [209.132.176.167]) by demeter.kernel.org (8.14.2/8.14.2) with ESMTP id n33Ai5jo011915 for ; Fri, 3 Apr 2009 10:44:06 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1759401AbZDCKoD (ORCPT ); Fri, 3 Apr 2009 06:44:03 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1757356AbZDCKoD (ORCPT ); Fri, 3 Apr 2009 06:44:03 -0400 Received: from wf-out-1314.google.com ([209.85.200.172]:18761 "EHLO wf-out-1314.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754592AbZDCKn7 (ORCPT ); Fri, 3 Apr 2009 06:43:59 -0400 Received: by wf-out-1314.google.com with SMTP id 29so1142883wff.4 for ; Fri, 03 Apr 2009 03:43:57 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:mime-version:date:received:message-id:subject :from:to:cc:content-type:content-transfer-encoding; bh=LLlPrGr9WcKOOMk1SRjkVOM9nUoL9eBC5bYDPZSzyt0=; b=Yp9BcciinN67UJo020fEGZ4eI/6mv+AzUCpSOHFkMvrbAGPoFifooEV1dFdWQb3mMJ iHlf1vsMIM9JOJSU4P5lAL55/Z/vLQABeq2VO4m5OA188EWsZSWzollY6GRnznAdIy1y 8E58IBS0HbgSOl68hQfoOIre2F5iP1FfbbwEs= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=mime-version:date:message-id:subject:from:to:cc:content-type :content-transfer-encoding; b=LcFst4daqp+crhuKJq0cmL+yUtnnKiyrA16EJ4Hi2BHSoDlNmlTjDRkUIPTxOCPagI qoEBCFVVJ68UfQ/1OBNAbofgvUC9Guv7G0zOjbrlaZ0+iEo3wGyyo83uEkL1cwr1K/B0 St5w++DbPclli5s6IwHbCseKfOG3xl8fqtteQ= MIME-Version: 1.0 Date: Fri, 3 Apr 2009 19:43:42 +0900 Received: by 10.142.135.16 with SMTP id i16mr302375wfd.192.1238755437234; Fri, 03 Apr 2009 03:43:57 -0700 (PDT) Message-ID: <4d34a0a70904030343h4f1562b5y3dbc9467aac9967@mail.gmail.com> Subject: [PATCH] OMAP3: PM: Add the wakeup source driver, v3 From: Kim Kyuwon To: Kevin Hilman Cc: OMAP , Tony Lindgren , =?EUC-KR?B?udqw5rnO?= , riverful@gmail.com Sender: linux-omap-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-omap@vger.kernel.org Sometimes, it is necessary to find out "what does wake up my board from suspend?". 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 from Suspend-to-RAM state to minimize power consumption. Note that this driver can't inform wake-up events from idle state. This driver uses sysfs interface to give information to user mode applications like: cat /sys/power/omap_resume_irq cat /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, "BT_WAKEUP", 1}, { 24, IRQF_TRIGGER_RISING, "USB_DETECT", 1}, }; static struct omap_wake_platform_data boardname_wake_data = { .gpio_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, }, }; The patch adds Kconfig options "OMAP34xx wakeup source support" under "System type"->"TI OMAP implementations" menu. Signed-off-by: Kim Kyuwon --- arch/arm/mach-omap2/Makefile | 1 + arch/arm/mach-omap2/irq.c | 21 +- arch/arm/mach-omap2/prcm-common.h | 4 + arch/arm/mach-omap2/prm-regbits-34xx.h | 5 + arch/arm/mach-omap2/wake34xx.c | 681 ++++++++++++++++++++++++++++++++ arch/arm/plat-omap/Kconfig | 9 + arch/arm/plat-omap/include/mach/irqs.h | 4 + arch/arm/plat-omap/include/mach/wake.h | 30 ++ 8 files changed, 752 insertions(+), 3 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 e693efd..4d7dbca 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 cpuidle34xx.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 700fc3d..18ac725 100644 --- a/arch/arm/mach-omap2/irq.c +++ b/arch/arm/mach-omap2/irq.c @@ -33,9 +33,6 @@ #define INTC_MIR_SET0 0x008c #define INTC_PENDING_IRQ0 0x0098 -/* Number of IRQ state bits in each MIR register */ -#define IRQ_BITS_PER_REG 32 - /* * OMAP2 has a number of different interrupt controllers, each interrupt * controller is identified as its own "bank". Register definitions are @@ -193,6 +190,24 @@ int omap_irq_pending(void) return 0; } +void omap_get_pending_irqs(u32 *pending_irqs, unsigned len) +{ + int i, j = 0; + + for (i = 0; i < ARRAY_SIZE(irq_banks); i++) { + struct omap_irq_bank *bank = irq_banks + i; + int irq; + + for (irq = 0; irq < bank->nr_irqs && j < len; + irq += IRQ_BITS_PER_REG) { + int offset = irq & (~(IRQ_BITS_PER_REG - 1)); + + pending_irqs[j++] = intc_bank_read_reg(bank, + (INTC_PENDING_IRQ0 + offset)); + } + } +} + 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 06fee29..f0a6395 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 @@ -432,6 +434,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..86aac4f --- /dev/null +++ b/arch/arm/mach-omap2/wake34xx.c @@ -0,0 +1,681 @@ +/* + * wake34xx.c + * + * Copyright (c) 2009 Samsung Eletronics + * + * Author: Kim Kyuwon + * + * 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 +#include +#include +#include + +#include +#include +#include + +#include "prm-regbits-34xx.h" + +/* + * Sometimes, it is necessary to find out "what does wake up my board from + * suspend?". 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 from + * Suspend-to-RAM state to minimize power consumption. Note that this + * driver can't inform wake-up events from idle state. This driver uses + * sysfs interface to give information to user mode applications. + */ + +#define DOMAIN_IS_WKUP (1 << 0) +#define DOMAIN_IS_PER (1 << 1) +#define DOMAIN_IS_CORE1 (1 << 2) +#define DOMAIN_IS_CORE3 (1 << 3) +#define DOMAIN_IS_USBHOST (1 << 4) + +#define WAKE_STR_LEN 64 +#define WAKE_BUF_LEN 32 + +static char wakeup_gpio[WAKE_STR_LEN]; + +struct pm_wakeup_status { + u32 wkup; + u32 per; + u32 core1; + u32 core3; + u32 usbhost; +}; + +struct omap_wake { + u32 pending_irqs[INTCPS_NR_MIR_REGS]; + + struct pm_wakeup_status pm_wkst; +}; + +struct wake_event { + const u32 mask; + const u32 domain; + const char *name; + + /* OMAP chip types that this wakeup status is valid on */ + const struct omap_chip_id omap_chip; +}; + + +/* Note: Allowed to use Only in the wakeup_source_show() function */ +static struct omap_wake *g_wake; + +static struct wake_event omap3_wake_events[] = { + { /* WKUP */ + .mask = OMAP3430_ST_IO_CHAIN, + .domain = DOMAIN_IS_WKUP, + .name = "ST_IO_CHAIN", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_IO, + .domain = DOMAIN_IS_WKUP, + .name = "ST_IO", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_SR2_MASK, + .domain = DOMAIN_IS_WKUP, + .name = "ST_SR2", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_SR1_MASK, + .domain = DOMAIN_IS_WKUP, + .name = "ST_SR1", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_GPIO1_MASK, + .domain = DOMAIN_IS_WKUP, + .name = "ST_GPIO1", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_GPT12_MASK, + .domain = DOMAIN_IS_WKUP, + .name = "ST_GPT12", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_GPT1_MASK, + .domain = DOMAIN_IS_WKUP, + .name = "ST_GPT1", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { /* PER */ + .mask = OMAP3430_ST_GPIO6_MASK, + .domain = DOMAIN_IS_PER, + .name = "ST_GPIO6", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_GPIO5_MASK, + .domain = DOMAIN_IS_PER, + .name = "ST_GPIO5", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_GPIO4_MASK, + .domain = DOMAIN_IS_PER, + .name = "ST_GPIO4", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_GPIO3_MASK, + .domain = DOMAIN_IS_PER, + .name = "ST_GPIO3", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_GPIO2_MASK, + .domain = DOMAIN_IS_PER, + .name = "ST_GPIO2", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_UART3_MASK, + .domain = DOMAIN_IS_PER, + .name = "ST_UART3", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_GPT9_MASK, + .domain = DOMAIN_IS_PER, + .name = "ST_GPT9", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_GPT8_MASK, + .domain = DOMAIN_IS_PER, + .name = "ST_GPT8", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_GPT7_MASK, + .domain = DOMAIN_IS_PER, + .name = "ST_GPT7", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_GPT6_MASK, + .domain = DOMAIN_IS_PER, + .name = "ST_GPT6", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_GPT5_MASK, + .domain = DOMAIN_IS_PER, + .name = "ST_GPT5", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_GPT4_MASK, + .domain = DOMAIN_IS_PER, + .name = "ST_GPT4", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_GPT3_MASK, + .domain = DOMAIN_IS_PER, + .name = "ST_GPT3", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_GPT2_MASK, + .domain = DOMAIN_IS_PER, + .name = "ST_GPT2", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_EN_MCBSP4, + .domain = DOMAIN_IS_PER, + .name = "EN_MCBSP4", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_EN_MCBSP3, + .domain = DOMAIN_IS_PER, + .name = "EN_MCBSP3", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_EN_MCBSP2, + .domain = DOMAIN_IS_PER, + .name = "EN_MCBSP2", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { /* CORE1 */ + .mask = OMAP3430_ST_MMC3_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_MMC3", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_MMC2_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_MMC2", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_MMC1_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_MMC1", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_MCSPI4_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_MCSPI4", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_MCSPI3_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_MCSPI3", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_MCSPI2_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_MCSPI2", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_MCSPI1_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_MCSPI1", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_I2C3_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_I2C3", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_I2C2_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_I2C2", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_I2C1_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_I2C1", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_UART1_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_UART1", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_GPT11_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_GPT11", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_GPT10_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_GPT10", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_MCBSP5_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_MCBSP5", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430_ST_MCBSP1_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_MCBSP1", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { + .mask = OMAP3430ES1_ST_FSHOSTUSB_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_FSHOSTUSB", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430ES1), + }, { + .mask = OMAP3430ES1_ST_HSOTGUSB_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_HSOTGUSB", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430ES1), + }, { + .mask = OMAP3430_ST_D2D_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_D2D", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430ES1), + }, { + .mask = OMAP3430ES2_ST_HSOTGUSB_STDBY_MASK, + .domain = DOMAIN_IS_CORE1, + .name = "ST_HSOTGUSB", + .omap_chip = OMAP_CHIP_INIT(CHIP_GE_OMAP3430ES2), + }, { /* CORE3 */ + .mask = OMAP3430_ST_USBTLL_MASK, + .domain = DOMAIN_IS_CORE3, + .name = "ST_USBTLL", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430), + }, { /* USBHOST */ + .mask = OMAP3430_ST_USBHOST, + .domain = DOMAIN_IS_USBHOST, + .name = "ST_USBHOST", + .omap_chip = OMAP_CHIP_INIT(CHIP_IS_OMAP3430ES1), + }, +}; + +static ssize_t wakeup_source_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); + +/* + * Get the first pending MPU IRQ number from 'irq_start'. + * If none, return -EINVAL. + */ +static int omap3_wake_get_pending_irq(struct omap_wake *wake, + unsigned int irq_start) +{ + int i, bits_skip, idx_start; + + if (irq_start >= INTCPS_NR_IRQS) + return -EINVAL; + + bits_skip = irq_start % IRQ_BITS_PER_REG; + idx_start = irq_start / IRQ_BITS_PER_REG; + + for (i = idx_start; i < ARRAY_SIZE(wake->pending_irqs); i++) { + unsigned long val, bit; + + val = wake->pending_irqs[i]; + if (!val) + continue; + + if (idx_start == i) + bit = find_next_bit(&val, IRQ_BITS_PER_REG, bits_skip); + else + bit = find_first_bit(&val, IRQ_BITS_PER_REG); + + if (bit < IRQ_BITS_PER_REG) + return i * IRQ_BITS_PER_REG + bit; + } + + return -EINVAL; +} + +static void omap3_wake_strncat(char *dest, char *src, size_t count) +{ + int len; + + if (!src[0]) + return; + + if (dest[0]) + len = strlen(dest) + strlen(src) + 2; + else + len = strlen(dest) + strlen(src); + + if (len > count) { + pr_err("Can't strncat: %s\n", src); + return; + } + + if (dest[0]) + strcat(dest, ", "); + strcat(dest, src); +} + +static u32 omap3_wake_get_domain_wkst(struct omap_wake *wake, u32 domain) +{ + struct pm_wakeup_status *pm_wkst = &wake->pm_wkst; + + switch (domain) { + case DOMAIN_IS_WKUP: + return pm_wkst->wkup; + case DOMAIN_IS_PER: + return pm_wkst->per; + case DOMAIN_IS_CORE1: + return pm_wkst->core1; + case DOMAIN_IS_CORE3: + return pm_wkst->core3; + case DOMAIN_IS_USBHOST: + return pm_wkst->usbhost; + default: + pr_err("Invalid domain ID: %d\n", domain); + break; + } + + return -EINVAL; +} + +static void omap3_wake_lookup_event(struct omap_wake *wake, char *event, + char *wake_event, size_t count) +{ + struct wake_event *events = omap3_wake_events; + int i, len = ARRAY_SIZE(omap3_wake_events); + u32 wkst; + + for (i = 0; i < len; i++) { + if (!omap_chip_is(events[i].omap_chip)) + continue; + + wkst = omap3_wake_get_domain_wkst(wake, events[i].domain); + if (wkst <= 0) + continue; + + if (wkst & events[i].mask) { + omap3_wake_strncat(wake_event, + (char *)events[i].name, count); + } + } +} + +/* Detect wake-up events */ +static void omap3_wake_dump_wakeup(struct omap_wake *wake, + char *wake_irq, char *wake_event, + size_t irq_size, size_t event_size) +{ + char buf[WAKE_BUF_LEN] = {0, }; + int irq, len, gpio_irq = 0, prcm_irq = 0; + + /* IRQ */ + irq = omap3_wake_get_pending_irq(wake, 0); + while (irq >= 0) { + if (irq == INT_34XX_SYS_NIRQ) + omap3_wake_strncat(wake_event, "sys_nirq", + event_size - 1); + else if (irq == INT_34XX_PRCM_MPU_IRQ) + prcm_irq = 1; + else if (irq >= INT_34XX_GPIO_BANK1 && + irq <= INT_34XX_GPIO_BANK6) + gpio_irq = 1; + + len = strlen(wake_irq) + + snprintf(buf, WAKE_BUF_LEN, "%d", irq); + if (len > irq_size - 1) + break; + + strcat(wake_irq, buf); + + irq = omap3_wake_get_pending_irq(wake, irq + 1); + if (irq >= 0) { + len = strlen(wake_irq) + 2; + if (len > irq_size - 1) + break; + + strcat(wake_irq, ", "); + } + } + if (gpio_irq) + omap3_wake_strncat(wake_event, wakeup_gpio, event_size - 1); + + if (prcm_irq) + omap3_wake_lookup_event(wake, buf, wake_event, event_size - 1); + + if (!wake_irq[0]) + strncpy(wake_irq, "Unknown", irq_size - 1); + + if (!wake_event[0]) + strncpy(wake_event, "Unknown", event_size - 1); +} + +static struct kobj_attribute wakeup_irq_attr = + __ATTR(omap_resume_irq, 0644, wakeup_source_show, NULL); + +static struct kobj_attribute wakeup_event_attr = + __ATTR(omap_resume_event, 0644, wakeup_source_show, NULL); + +static ssize_t wakeup_source_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + char wakeup_irq[WAKE_STR_LEN] = {0, }; + char wakeup_event[WAKE_STR_LEN] = {0, }; + + if (!g_wake) + return -EINVAL; + + omap3_wake_dump_wakeup(g_wake, wakeup_irq, wakeup_event, + sizeof(wakeup_irq), sizeof(wakeup_event)); + + 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 omap3_wake_gpio_interrupt(int irq, void *dev_id) +{ + omap3_wake_strncat(wakeup_gpio, dev_id, sizeof(wakeup_gpio) - 1); + + return IRQ_HANDLED; +} + +static int __devinit omap3_wake_probe(struct platform_device *pdev) +{ + struct omap_wake *wake; + struct omap_wake_platform_data *pdata = pdev->dev.platform_data; + struct gpio_wake *gw; + int i, ret; + + wake = kzalloc(sizeof(struct omap_wake), GFP_KERNEL); + if (wake == NULL) { + dev_err(&pdev->dev, "failed to allocate driver data\n"); + return -ENOMEM; + } + + platform_set_drvdata(pdev, wake); + + /* + * 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->gpio_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)); + } + + /* + * In wakeup_source_show(), we can't access platform_device + * or omap_wake structure without a global variable. so 'g_wake' is + * needed, but please use it carefully. + */ + g_wake = wake; + + 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->gpio_wakes + i; + + if (gw->request) + gpio_free(gw->gpio); + } + kfree(wake); + + return ret; +} + +static int __devexit omap3_wake_remove(struct platform_device *pdev) +{ + struct omap_wake *wake = platform_get_drvdata(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->gpio_wakes + i; + + if (gw->request) + gpio_free(gw->gpio); + + disable_irq_wake(gpio_to_irq(gw->gpio)); + } + kfree(wake); + + return 0; +} + +static int omap3_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->gpio_wakes + i; + + ret = request_irq(gpio_to_irq(gw->gpio), + omap3_wake_gpio_interrupt, + gw->irqflag | IRQF_SHARED, + 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; + } + } + + memset(wakeup_gpio, 0x0, WAKE_STR_LEN); + + return 0; + +failed_free_irq: + for (i--; i >= 0; i--) { + gw = pdata->gpio_wakes + i; + free_irq(gpio_to_irq(gw->gpio), (void *)gw->name); + } + + return ret; +} + +static int omap3_wake_resume_early(struct platform_device *pdev) +{ + struct omap_wake *wake = platform_get_drvdata(pdev); + struct pm_wakeup_status *pm_wkst = &wake->pm_wkst; + + omap_get_pending_irqs(wake->pending_irqs, + ARRAY_SIZE(wake->pending_irqs)); + + /* If PRCM interrupt generates this system wake-up event, */ + if (wake->pending_irqs[0] & (0x1 << INT_34XX_PRCM_MPU_IRQ)) { + pm_wkst->wkup = prm_read_mod_reg(WKUP_MOD, PM_WKST); + pm_wkst->core1 = prm_read_mod_reg(CORE_MOD, PM_WKST1); + pm_wkst->core3 = + prm_read_mod_reg(CORE_MOD, OMAP3430ES2_PM_WKST3); + pm_wkst->per = prm_read_mod_reg(OMAP3430_PER_MOD, PM_WKST); + pm_wkst->usbhost = + prm_read_mod_reg(OMAP3430ES2_USBHOST_MOD, PM_WKST); + } + + return 0; +} + +static int omap3_wake_resume(struct platform_device *pdev) +{ + struct omap_wake_platform_data *pdata = pdev->dev.platform_data; + struct gpio_wake *gw; + int i; + +#ifdef CONFIG_PM_DEBUG + struct omap_wake *wake = platform_get_drvdata(pdev); + char wakeup_irq[WAKE_STR_LEN] = {0, }; + char wakeup_event[WAKE_STR_LEN] = {0, }; + + omap3_wake_dump_wakeup(wake, wakeup_irq, wakeup_event, + sizeof(wakeup_irq), sizeof(wakeup_event)); + pr_info("OMAP resume IRQ: %s\n", wakeup_irq); + pr_info("OMAP resume event: %s\n", wakeup_event); +#endif + + for (i = 0; i < pdata->gpio_wake_num; i++) { + gw = pdata->gpio_wakes + i; + free_irq(gpio_to_irq(gw->gpio), (void *)gw->name); + } + + return 0; +} + +static struct platform_driver omap3_wake_driver = { + .probe = omap3_wake_probe, + .remove = __devexit_p(omap3_wake_remove), + .suspend = omap3_wake_suspend, + .resume_early = omap3_wake_resume_early, + .resume = omap3_wake_resume, + .driver = { + .name = "omap-wake", + .owner = THIS_MODULE, + }, +}; + +static int __init omap3_wake_init(void) +{ + return platform_driver_register(&omap3_wake_driver); +} + +module_init(omap3_wake_init); + +static void __exit omap3_wake_exit(void) +{ + platform_driver_unregister(&omap3_wake_driver); +} +module_exit(omap3_wake_exit); + +MODULE_AUTHOR("Kim Kyuwon "); +MODULE_DESCRIPTION("OMAP34xx wakeup driver"); +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/plat-omap/Kconfig b/arch/arm/plat-omap/Kconfig index c5e8239..ddeeae2 100644 --- a/arch/arm/plat-omap/Kconfig +++ b/arch/arm/plat-omap/Kconfig @@ -211,6 +211,15 @@ config OMAP_IOMMU Say Y here if you want to use OMAP IOMMU support for IVA2 and Camera in OMAP3. +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 suspend. 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 c9a5b19..ee15402 100644 --- a/arch/arm/plat-omap/include/mach/irqs.h +++ b/arch/arm/plat-omap/include/mach/irqs.h @@ -385,9 +385,13 @@ #define INTCPS_NR_MIR_REGS 3 #define INTCPS_NR_IRQS 96 +/* Number of IRQ state bits in each MIR register */ +#define IRQ_BITS_PER_REG 32 + #ifndef __ASSEMBLY__ extern void omap_init_irq(void); extern int omap_irq_pending(void); +extern void omap_get_pending_irqs(u32 *pending_irqs, unsigned len); void omap3_intc_save_context(void); void omap3_intc_restore_context(void); #endif 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..7da8ec8 --- /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 + * + * 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 *gpio_wakes; + int gpio_wake_num; +}; + +#endif /* _WAKE_H_ */ +