2008-03-13 16:12:03

by Haavard Skinnemoen

[permalink] [raw]
Subject: [PATCH 00/10] avr32: Power Management support

This patch series adds support for "standby" and "mem" power modes on
the AT32AP700x CPUs.

I've also included a couple of fixes that should go in through other
trees in order to ease testing:
#1 fixes an oops on suspend and shutdown. Picked from the linux-mtd
list.
#2 is a quick-n-dirty hack for a build problem when CONFIG_PM=y
#7 is a quick-n-dirty suspend/resume implementation for macb which
prevents it from waking up the system all the time.

This patch series is meant to apply to the avr32-arch branch of the
avr32 tree. It won't apply to 2.6.24, and even if you fix the
conflicts it won't work because of Kconfig changes. I've made the
necessary fixes and pushed it out to

git://git.kernel.org/pub/scm/linux/kernel/git/hskinnemoen/avr32-2.6.git atmel-2.6.24

and the result seems to work fine.

I haven't measured the time it takes to resume the system, but it
feels instantaneous, at least on the ATNGW100. The delay is more
noticeable on ATSTK1000 since it needs to power up the LCD panel
(the ltv350qv panel takes about 100 ms to power up).

Anton Vorontsov (1):
mtd: maps/physmap: fix oops in suspend/resume/shutdown ops

Haavard Skinnemoen (9):
atmel_serial: Fix build on avr32 with CONFIG_PM enabled
avr32: Move setup_platform() into chip-specific file
avr32: Rename at32ap.c -> pdc.c
avr32: Enable SDRAMC clock at startup
avr32: Add simple SRAM allocator
macb: Basic suspend/resume support
avr32: Define TIF_FREEZE
avr32: Add system device for the internal interrupt controller (intc)
avr32: Power Management support ("standby" and "mem" modes)

arch/avr32/Kconfig | 6 +
arch/avr32/mach-at32ap/Makefile | 7 +-
arch/avr32/mach-at32ap/at32ap700x.c | 53 +++++-
arch/avr32/mach-at32ap/intc.c | 80 +++++++++-
arch/avr32/mach-at32ap/{at32ap.c => pdc.c} | 8 -
arch/avr32/mach-at32ap/pm-at32ap700x.S | 108 ++++++++++++
arch/avr32/mach-at32ap/pm.c | 245 ++++++++++++++++++++++++++++
arch/avr32/mach-at32ap/sdramc.h | 76 +++++++++
drivers/mtd/maps/physmap.c | 15 +-
drivers/net/macb.c | 37 ++++
drivers/serial/atmel_serial.c | 11 +-
include/asm-avr32/arch-at32ap/init.h | 4 -
include/asm-avr32/arch-at32ap/pm.h | 3 +
include/asm-avr32/arch-at32ap/sram.h | 30 ++++
include/asm-avr32/thread_info.h | 1 +
15 files changed, 649 insertions(+), 35 deletions(-)
rename arch/avr32/mach-at32ap/{at32ap.c => pdc.c} (90%)
create mode 100644 arch/avr32/mach-at32ap/pm.c
create mode 100644 arch/avr32/mach-at32ap/sdramc.h
create mode 100644 include/asm-avr32/arch-at32ap/sram.h


2008-03-13 14:49:41

by Haavard Skinnemoen

[permalink] [raw]
Subject: [PATCH 02/10] atmel_serial: Fix build on avr32 with CONFIG_PM enabled

AVR32 doesn't have at91_suspend_entering_slow_clock(). Just assume the
clock will keep running for now.

Signed-off-by: Haavard Skinnemoen <[email protected]>
---
drivers/serial/atmel_serial.c | 11 ++++++++++-
1 files changed, 10 insertions(+), 1 deletions(-)

diff --git a/drivers/serial/atmel_serial.c b/drivers/serial/atmel_serial.c
index d57bf3e..2efbdf6 100644
--- a/drivers/serial/atmel_serial.c
+++ b/drivers/serial/atmel_serial.c
@@ -1428,6 +1428,15 @@ static struct uart_driver atmel_uart = {
};

#ifdef CONFIG_PM
+static bool atmel_serial_clk_will_stop(void)
+{
+#ifdef CONFIG_ARCH_AT91
+ return at91_suspend_entering_slow_clock();
+#else
+ return false;
+#endif
+}
+
static int atmel_serial_suspend(struct platform_device *pdev,
pm_message_t state)
{
@@ -1435,7 +1444,7 @@ static int atmel_serial_suspend(struct platform_device *pdev,
struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);

if (device_may_wakeup(&pdev->dev)
- && !at91_suspend_entering_slow_clock())
+ && !atmel_serial_clk_will_stop())
enable_irq_wake(port->irq);
else {
uart_suspend_port(&atmel_uart, port);
--
1.5.4.3

2008-03-13 14:52:59

by Haavard Skinnemoen

[permalink] [raw]
Subject: [PATCH 06/10] avr32: Add simple SRAM allocator

Add SRAM allocator for avr32, which is just a thin wrapper around
genalloc.

Signed-off-by: Haavard Skinnemoen <[email protected]>
---
arch/avr32/Kconfig | 1 +
arch/avr32/mach-at32ap/at32ap700x.c | 26 ++++++++++++++++++++++++++
include/asm-avr32/arch-at32ap/sram.h | 30 ++++++++++++++++++++++++++++++
3 files changed, 57 insertions(+), 0 deletions(-)
create mode 100644 include/asm-avr32/arch-at32ap/sram.h

diff --git a/arch/avr32/Kconfig b/arch/avr32/Kconfig
index 521dc18..078ab31 100644
--- a/arch/avr32/Kconfig
+++ b/arch/avr32/Kconfig
@@ -89,6 +89,7 @@ config PLATFORM_AT32AP
select MMU
select PERFORMANCE_COUNTERS
select HAVE_GPIO_LIB
+ select GENERIC_ALLOCATOR

#
# CPU types
diff --git a/arch/avr32/mach-at32ap/at32ap700x.c b/arch/avr32/mach-at32ap/at32ap700x.c
index ca5ee7a..cd94a1f 100644
--- a/arch/avr32/mach-at32ap/at32ap700x.c
+++ b/arch/avr32/mach-at32ap/at32ap700x.c
@@ -20,6 +20,7 @@
#include <asm/arch/at32ap700x.h>
#include <asm/arch/board.h>
#include <asm/arch/portmux.h>
+#include <asm/arch/sram.h>

#include <video/atmel_lcdc.h>

@@ -2007,3 +2008,28 @@ void __init setup_platform(void)
at32_init_pio(&pio3_device);
at32_init_pio(&pio4_device);
}
+
+struct gen_pool *sram_pool;
+
+static int __init sram_init(void)
+{
+ struct gen_pool *pool;
+
+ /* 1KiB granularity */
+ pool = gen_pool_create(10, -1);
+ if (!pool)
+ goto fail;
+
+ if (gen_pool_add(pool, 0x24000000, 0x8000, -1))
+ goto err_pool_add;
+
+ sram_pool = pool;
+ return 0;
+
+err_pool_add:
+ gen_pool_destroy(pool);
+fail:
+ pr_err("Failed to create SRAM pool\n");
+ return -ENOMEM;
+}
+core_initcall(sram_init);
diff --git a/include/asm-avr32/arch-at32ap/sram.h b/include/asm-avr32/arch-at32ap/sram.h
new file mode 100644
index 0000000..4838dae
--- /dev/null
+++ b/include/asm-avr32/arch-at32ap/sram.h
@@ -0,0 +1,30 @@
+/*
+ * Simple SRAM allocator
+ *
+ * Copyright (C) 2008 Atmel Corporation
+ *
+ * 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 __ASM_AVR32_ARCH_SRAM_H
+#define __ASM_AVR32_ARCH_SRAM_H
+
+#include <linux/genalloc.h>
+
+extern struct gen_pool *sram_pool;
+
+static inline unsigned long sram_alloc(size_t len)
+{
+ if (!sram_pool)
+ return 0UL;
+
+ return gen_pool_alloc(sram_pool, len);
+}
+
+static inline void sram_free(unsigned long addr, size_t len)
+{
+ return gen_pool_free(sram_pool, addr, len);
+}
+
+#endif /* __ASM_AVR32_ARCH_SRAM_H */
--
1.5.4.3

2008-03-13 15:47:18

by Haavard Skinnemoen

[permalink] [raw]
Subject: [PATCH 05/10] avr32: Enable SDRAMC clock at startup

The SDRAM controller needs a clock in order to respond to our
commands, and suspend doesn't work very well without the SDRAM in
self-refresh mode.

Signed-off-by: Haavard Skinnemoen <[email protected]>
---
arch/avr32/mach-at32ap/at32ap700x.c | 9 +++++++++
1 files changed, 9 insertions(+), 0 deletions(-)

diff --git a/arch/avr32/mach-at32ap/at32ap700x.c b/arch/avr32/mach-at32ap/at32ap700x.c
index 1af9c1d..ca5ee7a 100644
--- a/arch/avr32/mach-at32ap/at32ap700x.c
+++ b/arch/avr32/mach-at32ap/at32ap700x.c
@@ -682,6 +682,14 @@ static struct clk hramc_clk = {
.users = 1,
.index = 3,
};
+static struct clk sdramc_clk = {
+ .name = "sdramc_clk",
+ .parent = &pbb_clk,
+ .mode = pbb_clk_mode,
+ .get_rate = pbb_clk_get_rate,
+ .users = 1,
+ .index = 14,
+};

static struct resource smc0_resource[] = {
PBMEM(0xfff03400),
@@ -1887,6 +1895,7 @@ struct clk *at32_clock_list[] = {
&hmatrix_clk,
&ebi_clk,
&hramc_clk,
+ &sdramc_clk,
&smc0_pclk,
&smc0_mck,
&pdc_hclk,
--
1.5.4.3

2008-03-13 15:47:29

by Haavard Skinnemoen

[permalink] [raw]
Subject: [PATCH 07/10] macb: Basic suspend/resume support

---
drivers/net/macb.c | 37 +++++++++++++++++++++++++++++++++++++
1 files changed, 37 insertions(+), 0 deletions(-)

diff --git a/drivers/net/macb.c b/drivers/net/macb.c
index 1d210ed..94bf2ba 100644
--- a/drivers/net/macb.c
+++ b/drivers/net/macb.c
@@ -1275,8 +1275,45 @@ static int __exit macb_remove(struct platform_device *pdev)
return 0;
}

+#ifdef CONFIG_PM
+static int macb_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct net_device *netdev = platform_get_drvdata(pdev);
+ struct macb *bp = netdev_priv(netdev);
+
+ netif_device_detach(netdev);
+
+#ifndef CONFIG_ARCH_AT91
+ clk_disable(bp->hclk);
+#endif
+ clk_disable(bp->pclk);
+
+ return 0;
+}
+
+static int macb_resume(struct platform_device *pdev)
+{
+ struct net_device *netdev = platform_get_drvdata(pdev);
+ struct macb *bp = netdev_priv(netdev);
+
+ clk_enable(bp->pclk);
+#ifndef CONFIG_ARCH_AT91
+ clk_enable(bp->hclk);
+#endif
+
+ netif_device_attach(netdev);
+
+ return 0;
+}
+#else
+#define macb_suspend NULL
+#define macb_resume NULL
+#endif
+
static struct platform_driver macb_driver = {
.remove = __exit_p(macb_remove),
+ .suspend = macb_suspend,
+ .resume = macb_resume,
.driver = {
.name = "macb",
},
--
1.5.4.3

2008-03-13 16:02:21

by Haavard Skinnemoen

[permalink] [raw]
Subject: [PATCH 08/10] avr32: Define TIF_FREEZE

Signed-off-by: Haavard Skinnemoen <[email protected]>
---
include/asm-avr32/thread_info.h | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/include/asm-avr32/thread_info.h b/include/asm-avr32/thread_info.h
index 07049f6..df68631 100644
--- a/include/asm-avr32/thread_info.h
+++ b/include/asm-avr32/thread_info.h
@@ -88,6 +88,7 @@ static inline struct thread_info *current_thread_info(void)
#define TIF_MEMDIE 6
#define TIF_RESTORE_SIGMASK 7 /* restore signal mask in do_signal */
#define TIF_CPU_GOING_TO_SLEEP 8 /* CPU is entering sleep 0 mode */
+#define TIF_FREEZE 29
#define TIF_DEBUG 30 /* debugging enabled */
#define TIF_USERSPACE 31 /* true if FS sets userspace */

--
1.5.4.3

2008-03-13 16:02:33

by Haavard Skinnemoen

[permalink] [raw]
Subject: [PATCH 04/10] avr32: Rename at32ap.c -> pdc.c

The only thing left in at32ap.c is some PDC stuff. Rename the file to
reflect what it actually does.

Signed-off-by: Haavard Skinnemoen <[email protected]>
---
arch/avr32/mach-at32ap/Makefile | 2 +-
arch/avr32/mach-at32ap/{at32ap.c => pdc.c} | 0
2 files changed, 1 insertions(+), 1 deletions(-)
rename arch/avr32/mach-at32ap/{at32ap.c => pdc.c} (100%)

diff --git a/arch/avr32/mach-at32ap/Makefile b/arch/avr32/mach-at32ap/Makefile
index e890094..cb44182 100644
--- a/arch/avr32/mach-at32ap/Makefile
+++ b/arch/avr32/mach-at32ap/Makefile
@@ -1,3 +1,3 @@
-obj-y += at32ap.o clock.o intc.o extint.o pio.o hsmc.o
+obj-y += pdc.o clock.o intc.o extint.o pio.o hsmc.o
obj-$(CONFIG_CPU_AT32AP700X) += at32ap700x.o pm-at32ap700x.o
obj-$(CONFIG_CPU_FREQ_AT32AP) += cpufreq.o
diff --git a/arch/avr32/mach-at32ap/at32ap.c b/arch/avr32/mach-at32ap/pdc.c
similarity index 100%
rename from arch/avr32/mach-at32ap/at32ap.c
rename to arch/avr32/mach-at32ap/pdc.c
--
1.5.4.3

2008-03-13 16:11:34

by Haavard Skinnemoen

[permalink] [raw]
Subject: [PATCH 09/10] avr32: Add system device for the internal interrupt controller (intc)

This makes the intc show up in sysfs (probably not very useful), and
allows us to easily add suspend/resume support later.

Signed-off-by: Haavard Skinnemoen <[email protected]>
---
arch/avr32/mach-at32ap/intc.c | 28 +++++++++++++++++++++++++---
1 files changed, 25 insertions(+), 3 deletions(-)

diff --git a/arch/avr32/mach-at32ap/intc.c b/arch/avr32/mach-at32ap/intc.c
index 097cf4e..644a3fb 100644
--- a/arch/avr32/mach-at32ap/intc.c
+++ b/arch/avr32/mach-at32ap/intc.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006 Atmel Corporation
+ * Copyright (C) 2006, 2008 Atmel Corporation
*
* 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
@@ -12,14 +12,16 @@
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/platform_device.h>
+#include <linux/sysdev.h>

#include <asm/io.h>

#include "intc.h"

struct intc {
- void __iomem *regs;
- struct irq_chip chip;
+ void __iomem *regs;
+ struct irq_chip chip;
+ struct sys_device sysdev;
};

extern struct platform_device at32_intc0_device;
@@ -136,6 +138,26 @@ fail:
panic("Interrupt controller initialization failed!\n");
}

+static struct sysdev_class intc_class = {
+ .name = "intc",
+};
+
+static int __init intc_init_sysdev(void)
+{
+ int ret;
+
+ ret = sysdev_class_register(&intc_class);
+ if (ret)
+ return ret;
+
+ intc0.sysdev.id = 0;
+ intc0.sysdev.cls = &intc_class;
+ ret = sysdev_register(&intc0.sysdev);
+
+ return ret;
+}
+device_initcall(intc_init_sysdev);
+
unsigned long intc_get_pending(unsigned int group)
{
return intc_readl(&intc0, INTREQ0 + 4 * group);
--
1.5.4.3

2008-03-13 16:11:48

by Haavard Skinnemoen

[permalink] [raw]
Subject: [PATCH 03/10] avr32: Move setup_platform() into chip-specific file

Combine at32_clock_init() and at32_portmux_init() into
setup_platform() and remove setup_platform() from at32ap.c. No
functional change since all setup_platform() ever did was call those
two functions.

Signed-off-by: Haavard Skinnemoen <[email protected]>
---
arch/avr32/mach-at32ap/at32ap.c | 8 --------
arch/avr32/mach-at32ap/at32ap700x.c | 18 ++++++++----------
include/asm-avr32/arch-at32ap/init.h | 4 ----
3 files changed, 8 insertions(+), 22 deletions(-)

diff --git a/arch/avr32/mach-at32ap/at32ap.c b/arch/avr32/mach-at32ap/at32ap.c
index 7c4987f..1040bda 100644
--- a/arch/avr32/mach-at32ap/at32ap.c
+++ b/arch/avr32/mach-at32ap/at32ap.c
@@ -11,14 +11,6 @@
#include <linux/init.h>
#include <linux/platform_device.h>

-#include <asm/arch/init.h>
-
-void __init setup_platform(void)
-{
- at32_clock_init();
- at32_portmux_init();
-}
-
static int __init pdc_probe(struct platform_device *pdev)
{
struct clk *pclk, *hclk;
diff --git a/arch/avr32/mach-at32ap/at32ap700x.c b/arch/avr32/mach-at32ap/at32ap700x.c
index 0f24b4f..1af9c1d 100644
--- a/arch/avr32/mach-at32ap/at32ap700x.c
+++ b/arch/avr32/mach-at32ap/at32ap700x.c
@@ -1935,16 +1935,7 @@ struct clk *at32_clock_list[] = {
};
unsigned int at32_nr_clocks = ARRAY_SIZE(at32_clock_list);

-void __init at32_portmux_init(void)
-{
- at32_init_pio(&pio0_device);
- at32_init_pio(&pio1_device);
- at32_init_pio(&pio2_device);
- at32_init_pio(&pio3_device);
- at32_init_pio(&pio4_device);
-}
-
-void __init at32_clock_init(void)
+void __init setup_platform(void)
{
u32 cpu_mask = 0, hsb_mask = 0, pba_mask = 0, pbb_mask = 0;
int i;
@@ -1999,4 +1990,11 @@ void __init at32_clock_init(void)
pm_writel(HSB_MASK, hsb_mask);
pm_writel(PBA_MASK, pba_mask);
pm_writel(PBB_MASK, pbb_mask);
+
+ /* Initialize the port muxes */
+ at32_init_pio(&pio0_device);
+ at32_init_pio(&pio1_device);
+ at32_init_pio(&pio2_device);
+ at32_init_pio(&pio3_device);
+ at32_init_pio(&pio4_device);
}
diff --git a/include/asm-avr32/arch-at32ap/init.h b/include/asm-avr32/arch-at32ap/init.h
index 5e75d85..bc40e3d 100644
--- a/include/asm-avr32/arch-at32ap/init.h
+++ b/include/asm-avr32/arch-at32ap/init.h
@@ -13,10 +13,6 @@
void setup_platform(void);
void setup_board(void);

-/* Called by setup_platform */
-void at32_clock_init(void);
-void at32_portmux_init(void);
-
void at32_setup_serial_console(unsigned int usart_id);

#endif /* __ASM_AVR32_AT32AP_INIT_H__ */
--
1.5.4.3

2008-03-13 16:12:27

by Haavard Skinnemoen

[permalink] [raw]
Subject: [PATCH 01/10] mtd: maps/physmap: fix oops in suspend/resume/shutdown ops

From: Anton Vorontsov <[email protected]>

- - - -
# reboot
...
[ 42.351266] Flash device refused suspend due to active operation (state 0)
[ 42.358195] Unable to handle kernel NULL pointer dereference at virtual address 00000078
[ 42.360060] pgd = c7d9c000
[ 42.362769] [00000078] *pgd=a7d8d031, *pte=00000000, *ppte=00000000
[ 42.372902] Internal error: Oops: 17 [#1]
[ 42.376911] Modules linked in:
[ 42.379980] CPU: 0 Not tainted (2.6.25-rc2-10642-ge8f2594-dirty #73)
[ 42.380000] PC is at physmap_flash_shutdown+0x28/0x54
...
[ 42.380000] Backtrace:
[ 42.380000] [<c0130c1c>] (physmap_flash_shutdown+0x0/0x54) from [<c01207c0>] (platform_drv_shutdown+0x20/0x24)
[ 42.380000] r5:28121969 r4:c0229e08
[ 42.380000] [<c01207a0>] (platform_drv_shutdown+0x0/0x24) from [<c011cd40>] (device_shutdown+0x60/0x88)
[ 42.380000] [<c011cce0>] (device_shutdown+0x0/0x88) from [<c003e8a4>] (kernel_restart_prepare+0x2c/0x3c)
[ 42.380000] r4:00000000
[ 42.380000] [<c003e878>] (kernel_restart_prepare+0x0/0x3c) from [<c003ea00>] (kernel_restart+0x14/0x48)
[ 42.380000] [<c003e9ec>] (kernel_restart+0x0/0x48) from [<c003fdc0>] (sys_reboot+0xe8/0x1f8)
[ 42.380000] r4:01234567
[ 42.380000] [<c003fcd8>] (sys_reboot+0x0/0x1f8) from [<c001aa00>] (ret_fast_syscall+0x0/0x2c)
[ 42.380000] r7:00000058 r6:00000004 r5:00000001 r4:00000000
[ 42.380000] Code: 0a000009 e7953004 e1a00003 e1a0e00f (e593f078)
[ 42.650051] ---[ end trace 6d6c26a0fc3141de ]---
Segmentation fault
INIT: no more processes left in this runlevel
- - - -

While looping for mtd[i]s, we should stop at the mtd[i] == NULL.

This patch also removes unnecessary "if (info)" checks:
suspend/resume/shutdown ops are executed only if probe() is succeeded,
so info is guaranteed to be !NULL.

Signed-off-by: Anton Vorontsov <[email protected]>
---
drivers/mtd/maps/physmap.c | 15 +++++++--------
1 files changed, 7 insertions(+), 8 deletions(-)

diff --git a/drivers/mtd/maps/physmap.c b/drivers/mtd/maps/physmap.c
index f00e04e..bc4649a 100644
--- a/drivers/mtd/maps/physmap.c
+++ b/drivers/mtd/maps/physmap.c
@@ -202,9 +202,8 @@ static int physmap_flash_suspend(struct platform_device *dev, pm_message_t state
int ret = 0;
int i;

- if (info)
- for (i = 0; i < MAX_RESOURCES; i++)
- ret |= info->mtd[i]->suspend(info->mtd[i]);
+ for (i = 0; i < MAX_RESOURCES && info->mtd[i]; i++)
+ ret |= info->mtd[i]->suspend(info->mtd[i]);

return ret;
}
@@ -214,9 +213,9 @@ static int physmap_flash_resume(struct platform_device *dev)
struct physmap_flash_info *info = platform_get_drvdata(dev);
int i;

- if (info)
- for (i = 0; i < MAX_RESOURCES; i++)
- info->mtd[i]->resume(info->mtd[i]);
+ for (i = 0; i < MAX_RESOURCES && info->mtd[i]; i++)
+ info->mtd[i]->resume(info->mtd[i]);
+
return 0;
}

@@ -225,8 +224,8 @@ static void physmap_flash_shutdown(struct platform_device *dev)
struct physmap_flash_info *info = platform_get_drvdata(dev);
int i;

- for (i = 0; i < MAX_RESOURCES; i++)
- if (info && info->mtd[i]->suspend(info->mtd[i]) == 0)
+ for (i = 0; i < MAX_RESOURCES && info->mtd[i]; i++)
+ if (info->mtd[i]->suspend(info->mtd[i]) == 0)
info->mtd[i]->resume(info->mtd[i]);
}
#else
--
1.5.4.3

2008-03-13 16:12:51

by Haavard Skinnemoen

[permalink] [raw]
Subject: [PATCH 10/10] avr32: Power Management support ("standby" and "mem" modes)

Implement Standby support. In this mode, we'll suspend all drivers,
put the SDRAM in self-refresh mode and switch off the HSB bus
("frozen" mode.)

Implement Suspend-to-mem support. In this mode, we suspend all
drivers, put the SDRAM into self-refresh mode and switch off all
internal clocks except the 32 kHz oscillator ("stop" mode.)

The lowest-level suspend code runs from a small portion of SRAM
allocated at startup time. This gets rid of a small potential race
with the SDRAM where we might try to enter self-refresh mode in the
middle of an icache burst. We also relocate all interrupt and
exception handlers to SRAM during the small window when we enter and
exit the low-power modes.

We don't need to do any special tricks to start and stop the PLL. The
main clock is automatically gated by hardware until the PLL is stable.

Signed-off-by: Haavard Skinnemoen <[email protected]>
---
arch/avr32/Kconfig | 5 +
arch/avr32/mach-at32ap/Makefile | 5 +
arch/avr32/mach-at32ap/intc.c | 54 +++++++-
arch/avr32/mach-at32ap/pm-at32ap700x.S | 108 ++++++++++++++
arch/avr32/mach-at32ap/pm.c | 245 ++++++++++++++++++++++++++++++++
arch/avr32/mach-at32ap/sdramc.h | 76 ++++++++++
include/asm-avr32/arch-at32ap/pm.h | 3 +
7 files changed, 495 insertions(+), 1 deletions(-)
create mode 100644 arch/avr32/mach-at32ap/pm.c
create mode 100644 arch/avr32/mach-at32ap/sdramc.h

diff --git a/arch/avr32/Kconfig b/arch/avr32/Kconfig
index 078ab31..a2ee8a7 100644
--- a/arch/avr32/Kconfig
+++ b/arch/avr32/Kconfig
@@ -203,6 +203,11 @@ endmenu

menu "Power management options"

+source "kernel/power/Kconfig"
+
+config ARCH_SUSPEND_POSSIBLE
+ def_bool y
+
menu "CPU Frequency scaling"

source "drivers/cpufreq/Kconfig"
diff --git a/arch/avr32/mach-at32ap/Makefile b/arch/avr32/mach-at32ap/Makefile
index cb44182..d5018e2 100644
--- a/arch/avr32/mach-at32ap/Makefile
+++ b/arch/avr32/mach-at32ap/Makefile
@@ -1,3 +1,8 @@
obj-y += pdc.o clock.o intc.o extint.o pio.o hsmc.o
obj-$(CONFIG_CPU_AT32AP700X) += at32ap700x.o pm-at32ap700x.o
obj-$(CONFIG_CPU_FREQ_AT32AP) += cpufreq.o
+obj-$(CONFIG_PM) += pm.o
+
+ifeq ($(CONFIG_PM_DEBUG),y)
+CFLAGS_pm.o += -DDEBUG
+endif
diff --git a/arch/avr32/mach-at32ap/intc.c b/arch/avr32/mach-at32ap/intc.c
index 644a3fb..994c454 100644
--- a/arch/avr32/mach-at32ap/intc.c
+++ b/arch/avr32/mach-at32ap/intc.c
@@ -22,6 +22,10 @@ struct intc {
void __iomem *regs;
struct irq_chip chip;
struct sys_device sysdev;
+#ifdef CONFIG_PM
+ unsigned long suspend_ipr;
+ unsigned long saved_ipr[64];
+#endif
};

extern struct platform_device at32_intc0_device;
@@ -138,8 +142,56 @@ fail:
panic("Interrupt controller initialization failed!\n");
}

+#ifdef CONFIG_PM
+void intc_set_suspend_handler(unsigned long offset)
+{
+ intc0.suspend_ipr = offset;
+}
+
+static int intc_suspend(struct sys_device *sdev, pm_message_t state)
+{
+ struct intc *intc = container_of(sdev, struct intc, sysdev);
+ int i;
+
+ if (unlikely(!irqs_disabled())) {
+ pr_err("intc_suspend: called with interrupts enabled\n");
+ return -EINVAL;
+ }
+
+ if (unlikely(!intc->suspend_ipr)) {
+ pr_err("intc_suspend: suspend_ipr not initialized\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < 64; i++) {
+ intc->saved_ipr[i] = intc_readl(intc, INTPR0 + 4 * i);
+ intc_writel(intc, INTPR0 + 4 * i, intc->suspend_ipr);
+ }
+
+ return 0;
+}
+
+static int intc_resume(struct sys_device *sdev)
+{
+ struct intc *intc = container_of(sdev, struct intc, sysdev);
+ int i;
+
+ WARN_ON(!irqs_disabled());
+
+ for (i = 0; i < 64; i++)
+ intc_writel(intc, INTPR0 + 4 * i, intc->saved_ipr[i]);
+
+ return 0;
+}
+#else
+#define intc_suspend NULL
+#define intc_resume NULL
+#endif
+
static struct sysdev_class intc_class = {
- .name = "intc",
+ .name = "intc",
+ .suspend = intc_suspend,
+ .resume = intc_resume,
};

static int __init intc_init_sysdev(void)
diff --git a/arch/avr32/mach-at32ap/pm-at32ap700x.S b/arch/avr32/mach-at32ap/pm-at32ap700x.S
index 949e248..0a53ad3 100644
--- a/arch/avr32/mach-at32ap/pm-at32ap700x.S
+++ b/arch/avr32/mach-at32ap/pm-at32ap700x.S
@@ -12,6 +12,12 @@
#include <asm/thread_info.h>
#include <asm/arch/pm.h>

+#include "pm.h"
+#include "sdramc.h"
+
+/* Same as 0xfff00000 but fits in a 21 bit signed immediate */
+#define PM_BASE -0x100000
+
.section .bss, "wa", @nobits
.global disable_idle_sleep
.type disable_idle_sleep, @object
@@ -64,3 +70,105 @@ cpu_idle_skip_sleep:
unmask_interrupts
retal r12
.size cpu_idle_skip_sleep, . - cpu_idle_skip_sleep
+
+#ifdef CONFIG_PM
+ .section .init.text, "ax", @progbits
+
+ .global pm_exception
+ .type pm_exception, @function
+pm_exception:
+ /*
+ * Exceptions are masked when we switch to this handler, so
+ * we'll only get "unrecoverable" exceptions (offset 0.)
+ */
+ sub r12, pc, . - .Lpanic_msg
+ lddpc pc, .Lpanic_addr
+
+ .align 2
+.Lpanic_addr:
+ .long panic
+.Lpanic_msg:
+ .asciz "Unrecoverable exception during suspend\n"
+ .size pm_exception, . - pm_exception
+
+ .global pm_irq0
+ .type pm_irq0, @function
+pm_irq0:
+ /* Disable interrupts and return after the sleep instruction */
+ mfsr r9, SYSREG_RSR_INT0
+ mtsr SYSREG_RAR_INT0, r8
+ sbr r9, SYSREG_GM_OFFSET
+ mtsr SYSREG_RSR_INT0, r9
+ rete
+
+ /*
+ * void cpu_enter_standby(unsigned long sdramc_base)
+ *
+ * Enter PM_SUSPEND_STANDBY mode. At this point, all drivers
+ * are suspended and interrupts are disabled. Interrupts
+ * marked as 'wakeup' event sources may still come along and
+ * get us out of here.
+ *
+ * The SDRAM will be put into self-refresh mode (which does
+ * not require a clock from the CPU), and the CPU will be put
+ * into "frozen" mode (HSB bus stopped). The SDRAM controller
+ * will automatically bring the SDRAM into normal mode on the
+ * first access, and the power manager will automatically
+ * start the HSB and CPU clocks upon a wakeup event.
+ *
+ * This code uses the same "skip sleep" technique as above.
+ * It is very important that we jump directly to
+ * cpu_after_sleep after the sleep instruction since that's
+ * where we'll end up if the interrupt handler decides that we
+ * need to skip the sleep instruction.
+ */
+ .global pm_standby
+ .type pm_standby, @function
+pm_standby:
+ /*
+ * interrupts are already masked at this point, and EVBA
+ * points to pm_exception above.
+ */
+ ld.w r10, r12[SDRAMC_LPR]
+ sub r8, pc, . - 1f /* return address for irq handler */
+ mov r11, SDRAMC_LPR_LPCB_SELF_RFR
+ bfins r10, r11, 0, 2 /* LPCB <- self Refresh */
+ sync 0 /* flush write buffer */
+ st.w r12[SDRAMC_LPR], r11 /* put SDRAM in self-refresh mode */
+ ld.w r11, r12[SDRAMC_LPR]
+ unmask_interrupts
+ sleep CPU_SLEEP_FROZEN
+1: mask_interrupts
+ retal r12
+ .size pm_standby, . - pm_standby
+
+ .global pm_suspend_to_ram
+ .type pm_suspend_to_ram, @function
+pm_suspend_to_ram:
+ /*
+ * interrupts are already masked at this point, and EVBA
+ * points to pm_exception above.
+ */
+ mov r11, 0
+ cache r11[2], 8 /* clean all dcache lines */
+ sync 0 /* flush write buffer */
+ ld.w r10, r12[SDRAMC_LPR]
+ sub r8, pc, . - 1f /* return address for irq handler */
+ mov r11, SDRAMC_LPR_LPCB_SELF_RFR
+ bfins r10, r11, 0, 2 /* LPCB <- self refresh */
+ st.w r12[SDRAMC_LPR], r10 /* put SDRAM in self-refresh mode */
+ ld.w r11, r12[SDRAMC_LPR]
+
+ unmask_interrupts
+ sleep CPU_SLEEP_STOP
+1: mask_interrupts
+
+ retal r12
+ .size pm_suspend_to_ram, . - pm_suspend_to_ram
+
+ .global pm_sram_end
+ .type pm_sram_end, @function
+pm_sram_end:
+ .size pm_sram_end, 0
+
+#endif /* CONFIG_PM */
diff --git a/arch/avr32/mach-at32ap/pm.c b/arch/avr32/mach-at32ap/pm.c
new file mode 100644
index 0000000..0b76432
--- /dev/null
+++ b/arch/avr32/mach-at32ap/pm.c
@@ -0,0 +1,245 @@
+/*
+ * AVR32 AP Power Management
+ *
+ * Copyright (C) 2008 Atmel Corporation
+ *
+ * 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/io.h>
+#include <linux/suspend.h>
+#include <linux/vmalloc.h>
+
+#include <asm/cacheflush.h>
+#include <asm/sysreg.h>
+
+#include <asm/arch/pm.h>
+#include <asm/arch/sram.h>
+
+/* FIXME: This is only valid for AP7000 */
+#define SDRAMC_BASE 0xfff03800
+
+#include "sdramc.h"
+
+#define SRAM_PAGE_FLAGS (SYSREG_BIT(TLBELO_D) | SYSREG_BF(SZ, 1) \
+ | SYSREG_BF(AP, 3) | SYSREG_BIT(G))
+
+
+static unsigned long pm_sram_start;
+static size_t pm_sram_size;
+static struct vm_struct *pm_sram_area;
+
+static void (*avr32_pm_enter_standby)(unsigned long sdramc_base);
+static void (*avr32_pm_enter_str)(unsigned long sdramc_base);
+
+/*
+ * Must be called with interrupts disabled. Exceptions will be masked
+ * on return (i.e. all exceptions will be "unrecoverable".)
+ */
+static void *avr32_pm_map_sram(void)
+{
+ unsigned long vaddr;
+ unsigned long page_addr;
+ u32 tlbehi;
+ u32 mmucr;
+
+ vaddr = (unsigned long)pm_sram_area->addr;
+ page_addr = pm_sram_start & PAGE_MASK;
+
+ /*
+ * Mask exceptions and grab the first TLB entry. We won't be
+ * needing it while sleeping.
+ */
+ asm volatile("ssrf %0" : : "i"(SYSREG_EM_OFFSET) : "memory");
+
+ mmucr = sysreg_read(MMUCR);
+ tlbehi = sysreg_read(TLBEHI);
+ sysreg_write(MMUCR, SYSREG_BFINS(DRP, 0, mmucr));
+
+ tlbehi = SYSREG_BF(ASID, SYSREG_BFEXT(ASID, tlbehi));
+ tlbehi |= vaddr & PAGE_MASK;
+ tlbehi |= SYSREG_BIT(TLBEHI_V);
+
+ sysreg_write(TLBELO, page_addr | SRAM_PAGE_FLAGS);
+ sysreg_write(TLBEHI, tlbehi);
+ __builtin_tlbw();
+
+ return (void *)(vaddr + pm_sram_start - page_addr);
+}
+
+/*
+ * Must be called with interrupts disabled. Exceptions will be
+ * unmasked on return.
+ */
+static void avr32_pm_unmap_sram(void)
+{
+ u32 mmucr;
+ u32 tlbehi;
+ u32 tlbarlo;
+
+ /* Going to update TLB entry at index 0 */
+ mmucr = sysreg_read(MMUCR);
+ tlbehi = sysreg_read(TLBEHI);
+ sysreg_write(MMUCR, SYSREG_BFINS(DRP, 0, mmucr));
+
+ /* Clear the "valid" bit */
+ tlbehi = SYSREG_BF(ASID, SYSREG_BFEXT(ASID, tlbehi));
+ sysreg_write(TLBEHI, tlbehi);
+
+ /* Mark it as "not accessed" */
+ tlbarlo = sysreg_read(TLBARLO);
+ sysreg_write(TLBARLO, tlbarlo | 0x80000000U);
+
+ /* Update the TLB */
+ __builtin_tlbw();
+
+ /* Unmask exceptions */
+ asm volatile("csrf %0" : : "i"(SYSREG_EM_OFFSET) : "memory");
+}
+
+static int avr32_pm_valid_state(suspend_state_t state)
+{
+ switch (state) {
+ case PM_SUSPEND_ON:
+ case PM_SUSPEND_STANDBY:
+ case PM_SUSPEND_MEM:
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+static int avr32_pm_enter(suspend_state_t state)
+{
+ u32 lpr_saved;
+ u32 evba_saved;
+ void *sram;
+
+ switch (state) {
+ case PM_SUSPEND_STANDBY:
+ sram = avr32_pm_map_sram();
+
+ /* Switch to in-sram exception handlers */
+ evba_saved = sysreg_read(EVBA);
+ sysreg_write(EVBA, (unsigned long)sram);
+
+ /*
+ * Save the LPR register so that we can re-enable
+ * SDRAM Low Power mode on resume.
+ */
+ lpr_saved = sdramc_readl(LPR);
+ pr_debug("%s: Entering standby...\n", __func__);
+ avr32_pm_enter_standby(SDRAMC_BASE);
+ sdramc_writel(LPR, lpr_saved);
+
+ /* Switch back to regular exception handlers */
+ sysreg_write(EVBA, evba_saved);
+
+ avr32_pm_unmap_sram();
+ break;
+
+ case PM_SUSPEND_MEM:
+ sram = avr32_pm_map_sram();
+
+ /* Switch to in-sram exception handlers */
+ evba_saved = sysreg_read(EVBA);
+ sysreg_write(EVBA, (unsigned long)sram);
+
+ /*
+ * Save the LPR register so that we can re-enable
+ * SDRAM Low Power mode on resume.
+ */
+ lpr_saved = sdramc_readl(LPR);
+ pr_debug("%s: Entering suspend-to-ram...\n", __func__);
+ avr32_pm_enter_str(SDRAMC_BASE);
+ sdramc_writel(LPR, lpr_saved);
+
+ /* Switch back to regular exception handlers */
+ sysreg_write(EVBA, evba_saved);
+
+ avr32_pm_unmap_sram();
+ break;
+
+ case PM_SUSPEND_ON:
+ pr_debug("%s: Entering idle...\n", __func__);
+ cpu_enter_idle();
+ break;
+
+ default:
+ pr_debug("%s: Invalid suspend state %d\n", __func__, state);
+ goto out;
+ }
+
+ pr_debug("%s: wakeup\n", __func__);
+
+out:
+ return 0;
+}
+
+static struct platform_suspend_ops avr32_pm_ops = {
+ .valid = avr32_pm_valid_state,
+ .enter = avr32_pm_enter,
+};
+
+static unsigned long avr32_pm_offset(void *symbol)
+{
+ extern u8 pm_exception[];
+
+ return (unsigned long)symbol - (unsigned long)pm_exception;
+}
+
+static int __init avr32_pm_init(void)
+{
+ extern u8 pm_exception[];
+ extern u8 pm_irq0[];
+ extern u8 pm_standby[];
+ extern u8 pm_suspend_to_ram[];
+ extern u8 pm_sram_end[];
+ void *dst;
+
+ /*
+ * To keep things simple, we depend on not needing more than a
+ * single page.
+ */
+ pm_sram_size = avr32_pm_offset(pm_sram_end);
+ if (pm_sram_size > PAGE_SIZE)
+ goto err;
+
+ pm_sram_start = sram_alloc(pm_sram_size);
+ if (!pm_sram_start)
+ goto err_alloc_sram;
+
+ /* Grab a virtual area we can use later on. */
+ pm_sram_area = get_vm_area(pm_sram_size, VM_IOREMAP);
+ if (!pm_sram_area)
+ goto err_vm_area;
+ pm_sram_area->phys_addr = pm_sram_start;
+
+ local_irq_disable();
+ dst = avr32_pm_map_sram();
+ memcpy(dst, pm_exception, pm_sram_size);
+ flush_dcache_region(dst, pm_sram_size);
+ invalidate_icache_region(dst, pm_sram_size);
+ avr32_pm_unmap_sram();
+ local_irq_enable();
+
+ avr32_pm_enter_standby = dst + avr32_pm_offset(pm_standby);
+ avr32_pm_enter_str = dst + avr32_pm_offset(pm_suspend_to_ram);
+ intc_set_suspend_handler(avr32_pm_offset(pm_irq0));
+
+ suspend_set_ops(&avr32_pm_ops);
+
+ printk("AVR32 AP Power Management enabled\n");
+
+ return 0;
+
+err_vm_area:
+ sram_free(pm_sram_start, pm_sram_size);
+err_alloc_sram:
+err:
+ pr_err("AVR32 Power Management initialization failed\n");
+ return -ENOMEM;
+}
+arch_initcall(avr32_pm_init);
diff --git a/arch/avr32/mach-at32ap/sdramc.h b/arch/avr32/mach-at32ap/sdramc.h
new file mode 100644
index 0000000..66eeaed
--- /dev/null
+++ b/arch/avr32/mach-at32ap/sdramc.h
@@ -0,0 +1,76 @@
+/*
+ * Register definitions for the AT32AP SDRAM Controller
+ *
+ * Copyright (C) 2008 Atmel Corporation
+ *
+ * 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.
+ */
+
+/* Register offsets */
+#define SDRAMC_MR 0x0000
+#define SDRAMC_TR 0x0004
+#define SDRAMC_CR 0x0008
+#define SDRAMC_HSR 0x000c
+#define SDRAMC_LPR 0x0010
+#define SDRAMC_IER 0x0014
+#define SDRAMC_IDR 0x0018
+#define SDRAMC_IMR 0x001c
+#define SDRAMC_ISR 0x0020
+#define SDRAMC_MDR 0x0024
+
+/* MR - Mode Register */
+#define SDRAMC_MR_MODE_NORMAL ( 0 << 0)
+#define SDRAMC_MR_MODE_NOP ( 1 << 0)
+#define SDRAMC_MR_MODE_BANKS_PRECHARGE ( 2 << 0)
+#define SDRAMC_MR_MODE_LOAD_MODE ( 3 << 0)
+#define SDRAMC_MR_MODE_AUTO_REFRESH ( 4 << 0)
+#define SDRAMC_MR_MODE_EXT_LOAD_MODE ( 5 << 0)
+#define SDRAMC_MR_MODE_POWER_DOWN ( 6 << 0)
+
+/* CR - Configuration Register */
+#define SDRAMC_CR_NC_8_BITS ( 0 << 0)
+#define SDRAMC_CR_NC_9_BITS ( 1 << 0)
+#define SDRAMC_CR_NC_10_BITS ( 2 << 0)
+#define SDRAMC_CR_NC_11_BITS ( 3 << 0)
+#define SDRAMC_CR_NR_11_BITS ( 0 << 2)
+#define SDRAMC_CR_NR_12_BITS ( 1 << 2)
+#define SDRAMC_CR_NR_13_BITS ( 2 << 2)
+#define SDRAMC_CR_NB_2_BANKS ( 0 << 4)
+#define SDRAMC_CR_NB_4_BANKS ( 1 << 4)
+#define SDRAMC_CR_CAS(x) ((x) << 5)
+#define SDRAMC_CR_DBW_32_BITS ( 0 << 7)
+#define SDRAMC_CR_DBW_16_BITS ( 1 << 7)
+#define SDRAMC_CR_TWR(x) ((x) << 8)
+#define SDRAMC_CR_TRC(x) ((x) << 12)
+#define SDRAMC_CR_TRP(x) ((x) << 16)
+#define SDRAMC_CR_TRCD(x) ((x) << 20)
+#define SDRAMC_CR_TRAS(x) ((x) << 24)
+#define SDRAMC_CR_TXSR(x) ((x) << 28)
+
+/* HSR - High Speed Register */
+#define SDRAMC_HSR_DA ( 1 << 0)
+
+/* LPR - Low Power Register */
+#define SDRAMC_LPR_LPCB_INHIBIT ( 0 << 0)
+#define SDRAMC_LPR_LPCB_SELF_RFR ( 1 << 0)
+#define SDRAMC_LPR_LPCB_PDOWN ( 2 << 0)
+#define SDRAMC_LPR_LPCB_DEEP_PDOWN ( 3 << 0)
+#define SDRAMC_LPR_PASR(x) ((x) << 4)
+#define SDRAMC_LPR_TCSR(x) ((x) << 8)
+#define SDRAMC_LPR_DS(x) ((x) << 10)
+#define SDRAMC_LPR_TIMEOUT(x) ((x) << 12)
+
+/* IER/IDR/IMR/ISR - Interrupt Enable/Disable/Mask/Status Register */
+#define SDRAMC_ISR_RES ( 1 << 0)
+
+/* MDR - Memory Device Register */
+#define SDRAMC_MDR_MD_SDRAM ( 0 << 0)
+#define SDRAMC_MDR_MD_LOW_PWR_SDRAM ( 1 << 0)
+
+/* Register access macros */
+#define sdramc_readl(reg) \
+ __raw_readl((void __iomem __force *)SDRAMC_BASE + SDRAMC_##reg)
+#define sdramc_writel(reg, value) \
+ __raw_writel(value, (void __iomem __force *)SDRAMC_BASE + SDRAMC_##reg)
diff --git a/include/asm-avr32/arch-at32ap/pm.h b/include/asm-avr32/arch-at32ap/pm.h
index 356e430..979b355 100644
--- a/include/asm-avr32/arch-at32ap/pm.h
+++ b/include/asm-avr32/arch-at32ap/pm.h
@@ -19,6 +19,7 @@

#ifndef __ASSEMBLY__
extern void cpu_enter_idle(void);
+extern void cpu_enter_standby(unsigned long sdramc_base);

extern bool disable_idle_sleep;

@@ -43,6 +44,8 @@ static inline void cpu_idle_sleep(void)
else
cpu_enter_idle();
}
+
+void intc_set_suspend_handler(unsigned long offset);
#endif

#endif /* __ASM_AVR32_ARCH_PM_H */
--
1.5.4.3

2008-03-13 19:26:22

by David Brownell

[permalink] [raw]
Subject: Re: [PATCH 01/10] mtd: maps/physmap: fix oops in suspend/resume/shutdown ops

On Thursday 13 March 2008, Haavard Skinnemoen wrote:
> ...
> [ 42.351266] Flash device refused suspend due to active operation (state 0)
> [ 42.358195] Unable to handle kernel NULL pointer dereference at virtual address 00000078
> [ 42.360060] pgd = c7d9c000
> [ 42.362769] [00000078] *pgd=a7d8d031, *pte=00000000, *ppte=00000000
> [ 42.372902] Internal error: Oops: 17 [#1]
> [ 42.376911] Modules linked in:
> [ 42.379980] CPU: 0 Not tainted (2.6.25-rc2-10642-ge8f2594-dirty #73)
> [ 42.380000] PC is at physmap_flash_shutdown+0x28/0x54
> ...

I sure hope this regression fix gets merged before 2.6.25-final ...
I've seen it too, came up with a different (quick'n'dirty) fix.

- Dave

2008-03-13 20:19:15

by David Brownell

[permalink] [raw]
Subject: Re: [PATCH 07/10] macb: Basic suspend/resume support

On Thursday 13 March 2008, Haavard Skinnemoen wrote:
> ---
> drivers/net/macb.c | 37 +++++++++++++++++++++++++++++++++++++
> 1 files changed, 37 insertions(+), 0 deletions(-)

Shouldn't this look more like the suspend/resume support
in drivers/net/arm/at91_ether.c ? I don't see calls to
netif_stop_queue()/netif_start_queue(), among other things.

The fact that this driver leaves the peripheral clocked
when it's not open is a minor PM goof; ditto not using
the PHY irq to track connect/disconnect. But that's just
general PM stuff, not specific to suspend/resume support.

- Dave


> diff --git a/drivers/net/macb.c b/drivers/net/macb.c
> index 1d210ed..94bf2ba 100644
> --- a/drivers/net/macb.c
> +++ b/drivers/net/macb.c
> @@ -1275,8 +1275,45 @@ static int __exit macb_remove(struct platform_device *pdev)
> return 0;
> }
>
> +#ifdef CONFIG_PM
> +static int macb_suspend(struct platform_device *pdev, pm_message_t state)
> +{
> + struct net_device *netdev = platform_get_drvdata(pdev);
> + struct macb *bp = netdev_priv(netdev);
> +
> + netif_device_detach(netdev);
> +
> +#ifndef CONFIG_ARCH_AT91
> + clk_disable(bp->hclk);
> +#endif
> + clk_disable(bp->pclk);
> +
> + return 0;
> +}
> +
> +static int macb_resume(struct platform_device *pdev)
> +{
> + struct net_device *netdev = platform_get_drvdata(pdev);
> + struct macb *bp = netdev_priv(netdev);
> +
> + clk_enable(bp->pclk);
> +#ifndef CONFIG_ARCH_AT91
> + clk_enable(bp->hclk);
> +#endif
> +
> + netif_device_attach(netdev);
> +
> + return 0;
> +}
> +#else
> +#define macb_suspend NULL
> +#define macb_resume NULL
> +#endif
> +
> static struct platform_driver macb_driver = {
> .remove = __exit_p(macb_remove),
> + .suspend = macb_suspend,
> + .resume = macb_resume,
> .driver = {
> .name = "macb",
> },
> --
> 1.5.4.3
>

2008-03-13 20:28:15

by Haavard Skinnemoen

[permalink] [raw]
Subject: Re: [PATCH 07/10] macb: Basic suspend/resume support

On Thu, 13 Mar 2008 12:18:56 -0800
David Brownell <[email protected]> wrote:

> On Thursday 13 March 2008, Haavard Skinnemoen wrote:
> > ---
> > drivers/net/macb.c | 37 +++++++++++++++++++++++++++++++++++++
> > 1 files changed, 37 insertions(+), 0 deletions(-)
>
> Shouldn't this look more like the suspend/resume support
> in drivers/net/arm/at91_ether.c ? I don't see calls to
> netif_stop_queue()/netif_start_queue(), among other things.

Could be, although netif_stop_queue()/netif_start_queue() is done by
netif_device_detach() and netif_device_attach() respectively.

We should probably shut things down gracefully as well...not nice to
disable the clock when the MAC is in the middle of transmitting a
frame. Like I said in the introductory mail, this patch is just a quick
hack to prevent the macb from waking the system prematurely.

> The fact that this driver leaves the peripheral clocked
> when it's not open is a minor PM goof; ditto not using
> the PHY irq to track connect/disconnect. But that's just
> general PM stuff, not specific to suspend/resume support.

Right, it's on my todo list.

Haavard

2008-03-13 20:37:56

by David Brownell

[permalink] [raw]
Subject: Re: [PATCH 02/10] atmel_serial: Fix build on avr32 with CONFIG_PM enabled

On Thursday 13 March 2008, Haavard Skinnemoen wrote:
> AVR32 doesn't have at91_suspend_entering_slow_clock(). Just assume the
> clock will keep running for now.

Well, it should acquire the clk_must_disable() or somesuch... :)

By the way, I noticed a goof in this driver's clock management.
It should leave the clock off whenever the device isn't open.
As it is, it wastes power both at runtime and during suspend.

- Dave


> Signed-off-by: Haavard Skinnemoen <[email protected]>
> ---
> drivers/serial/atmel_serial.c | 11 ++++++++++-
> 1 files changed, 10 insertions(+), 1 deletions(-)
>
> diff --git a/drivers/serial/atmel_serial.c b/drivers/serial/atmel_serial.c
> index d57bf3e..2efbdf6 100644
> --- a/drivers/serial/atmel_serial.c
> +++ b/drivers/serial/atmel_serial.c
> @@ -1428,6 +1428,15 @@ static struct uart_driver atmel_uart = {
> };
>
> #ifdef CONFIG_PM
> +static bool atmel_serial_clk_will_stop(void)
> +{
> +#ifdef CONFIG_ARCH_AT91
> + return at91_suspend_entering_slow_clock();
> +#else
> + return false;
> +#endif
> +}
> +
> static int atmel_serial_suspend(struct platform_device *pdev,
> pm_message_t state)
> {
> @@ -1435,7 +1444,7 @@ static int atmel_serial_suspend(struct platform_device *pdev,
> struct atmel_uart_port *atmel_port = to_atmel_uart_port(port);
>
> if (device_may_wakeup(&pdev->dev)
> - && !at91_suspend_entering_slow_clock())
> + && !atmel_serial_clk_will_stop())
> enable_irq_wake(port->irq);
> else {
> uart_suspend_port(&atmel_uart, port);
> --
> 1.5.4.3
>

2008-03-13 20:54:17

by Haavard Skinnemoen

[permalink] [raw]
Subject: Re: [PATCH 02/10] atmel_serial: Fix build on avr32 with CONFIG_PM enabled

On Thu, 13 Mar 2008 12:37:40 -0800
David Brownell <[email protected]> wrote:

> On Thursday 13 March 2008, Haavard Skinnemoen wrote:
> > AVR32 doesn't have at91_suspend_entering_slow_clock(). Just assume the
> > clock will keep running for now.
>
> Well, it should acquire the clk_must_disable() or somesuch... :)

Indeed. But that interface doesn't exist yet...

> By the way, I noticed a goof in this driver's clock management.
> It should leave the clock off whenever the device isn't open.
> As it is, it wastes power both at runtime and during suspend.

Right. I suspect you'll find a similar goofs in lots of drivers if you
go looking. I intend to go through all the drivers I care about and see
if I can tighten up the clock management a bit. There are probably a
few missing or half-assed suspend/resume implementations around too.

Haavard