aboutsummaryrefslogtreecommitdiff
path: root/arch/powerpc/platforms/52xx/mpc52xx_pm.c
blob: 76722532bd95e516955397c5a81d651159cddc2e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#include <linux/init.h>
#include <linux/suspend.h>
#include <linux/io.h>
#include <asm/time.h>
#include <asm/cacheflush.h>
#include <asm/mpc52xx.h>

/* these are defined in mpc52xx_sleep.S, and only used here */
extern void mpc52xx_deep_sleep(void __iomem *sram, void __iomem *sdram_regs,
		struct mpc52xx_cdm __iomem *, struct mpc52xx_intr __iomem*);
extern void mpc52xx_ds_sram(void);
extern const long mpc52xx_ds_sram_size;
extern void mpc52xx_ds_cached(void);
extern const long mpc52xx_ds_cached_size;

static void __iomem *mbar;
static void __iomem *sdram;
static struct mpc52xx_cdm __iomem *cdm;
static struct mpc52xx_intr __iomem *intr;
static struct mpc52xx_gpio_wkup __iomem *gpiow;
static void __iomem *sram;
static int sram_size;

struct mpc52xx_suspend mpc52xx_suspend;

static int mpc52xx_pm_valid(suspend_state_t state)
{
	switch (state) {
	case PM_SUSPEND_STANDBY:
		return 1;
	default:
		return 0;
	}
}

int mpc52xx_set_wakeup_gpio(u8 pin, u8 level)
{
	u16 tmp;

	/* enable gpio */
	out_8(&gpiow->wkup_gpioe, in_8(&gpiow->wkup_gpioe) | (1 << pin));
	/* set as input */
	out_8(&gpiow->wkup_ddr, in_8(&gpiow->wkup_ddr) & ~(1 << pin));
	/* enable deep sleep interrupt */
	out_8(&gpiow->wkup_inten, in_8(&gpiow->wkup_inten) | (1 << pin));
	/* low/high level creates wakeup interrupt */
	tmp = in_be16(&gpiow->wkup_itype);
	tmp &= ~(0x3 << (pin * 2));
	tmp |= (!level + 1) << (pin * 2);
	out_be16(&gpiow->wkup_itype, tmp);
	/* master enable */
	out_8(&gpiow->wkup_maste, 1);

	return 0;
}

int mpc52xx_pm_prepare(void)
{
	struct device_node *np;
	const struct of_device_id immr_ids[] = {
		{ .compatible = "fsl,mpc5200-immr", },
		{ .compatible = "fsl,mpc5200b-immr", },
		{ .type = "soc", .compatible = "mpc5200", }, /* lite5200 */
		{ .type = "builtin", .compatible = "mpc5200", }, /* efika */
		{}
	};
	struct resource res;

	/* map the whole register space */
	np = of_find_matching_node(NULL, immr_ids);

	if (of_address_to_resource(np, 0, &res)) {
		pr_err("mpc52xx_pm_prepare(): could not get IMMR address\n");
		of_node_put(np);
		return -ENOSYS;
	}

	mbar = ioremap(res.start, 0xc000); /* we should map whole region including SRAM */

	of_node_put(np);
	if (!mbar) {
		pr_err("mpc52xx_pm_prepare(): could not map registers\n");
		return -ENOSYS;
	}
	/* these offsets are from mpc5200 users manual */
	sdram	= mbar + 0x100;
	cdm	= mbar + 0x200;
	intr	= mbar + 0x500;
	gpiow	= mbar + 0xc00;
	sram	= mbar + 0x8000;	/* Those will be handled by the */
	sram_size = 0x4000;		/* bestcomm driver soon */

	/* call board suspend code, if applicable */
	if (mpc52xx_suspend.board_suspend_prepare)
		mpc52xx_suspend.board_suspend_prepare(mbar);
	else {
		printk(KERN_ALERT "%s: %i don't know how to wake up the board\n",
				__func__, __LINE__);
		goto out_unmap;
	}

	return 0;

 out_unmap:
	iounmap(mbar);
	return -ENOSYS;
}


char saved_sram[0x4000];

int mpc52xx_pm_enter(suspend_state_t state)
{
	u32 clk_enables;
	u32 msr, hid0;
	u32 intr_main_mask;
	void __iomem * irq_0x500 = (void __iomem *)CONFIG_KERNEL_START + 0x500;
	unsigned long irq_0x500_stop = (unsigned long)irq_0x500 + mpc52xx_ds_cached_size;
	char saved_0x500[mpc52xx_ds_cached_size];

	/* disable all interrupts in PIC */
	intr_main_mask = in_be32(&intr->main_mask);
	out_be32(&intr->main_mask, intr_main_mask | 0x1ffff);

	/* don't let DEC expire any time soon */
	mtspr(SPRN_DEC, 0x7fffffff);

	/* save SRAM */
	memcpy(saved_sram, sram, sram_size);

	/* copy low level suspend code to sram */
	memcpy(sram, mpc52xx_ds_sram, mpc52xx_ds_sram_size);

	out_8(&cdm->ccs_sleep_enable, 1);
	out_8(&cdm->osc_sleep_enable, 1);
	out_8(&cdm->ccs_qreq_test, 1);

	/* disable all but SDRAM and bestcomm (SRAM) clocks */
	clk_enables = in_be32(&cdm->clk_enables);
	out_be32(&cdm->clk_enables, clk_enables & 0x00088000);

	/* disable power management */
	msr = mfmsr();
	mtmsr(msr & ~MSR_POW);

	/* enable sleep mode, disable others */
	hid0 = mfspr(SPRN_HID0);
	mtspr(SPRN_HID0, (hid0 & ~(HID0_DOZE | HID0_NAP | HID0_DPM)) | HID0_SLEEP);

	/* save original, copy our irq handler, flush from dcache and invalidate icache */
	memcpy(saved_0x500, irq_0x500, mpc52xx_ds_cached_size);
	memcpy(irq_0x500, mpc52xx_ds_cached, mpc52xx_ds_cached_size);
	flush_icache_range((unsigned long)irq_0x500, irq_0x500_stop);

	/* call low-level sleep code */
	mpc52xx_deep_sleep(sram, sdram, cdm, intr);

	/* restore original irq handler */
	memcpy(irq_0x500, saved_0x500, mpc52xx_ds_cached_size);
	flush_icache_range((unsigned long)irq_0x500, irq_0x500_stop);

	/* restore old power mode */
	mtmsr(msr & ~MSR_POW);
	mtspr(SPRN_HID0, hid0);
	mtmsr(msr);

	out_be32(&cdm->clk_enables, clk_enables);
	out_8(&cdm->ccs_sleep_enable, 0);
	out_8(&cdm->osc_sleep_enable, 0);

	/* restore SRAM */
	memcpy(sram, saved_sram, sram_size);

	/* restart jiffies */
	wakeup_decrementer();

	/* reenable interrupts in PIC */
	out_be32(&intr->main_mask, intr_main_mask);

	return 0;
}

void mpc52xx_pm_finish(void)
{
	/* call board resume code */
	if (mpc52xx_suspend.board_resume_finish)
		mpc52xx_suspend.board_resume_finish(mbar);

	iounmap(mbar);
}

static struct platform_suspend_ops mpc52xx_pm_ops = {
	.valid		= mpc52xx_pm_valid,
	.prepare	= mpc52xx_pm_prepare,
	.enter		= mpc52xx_pm_enter,
	.finish		= mpc52xx_pm_finish,
};

int __init mpc52xx_pm_init(void)
{
	suspend_set_ops(&mpc52xx_pm_ops);
	return 0;
}