summaryrefslogtreecommitdiff
path: root/arch/powerpc/sysdev/fsl_pmc.c
blob: 81d75a6168f9fd3ceb77ef455eb8cc0d2932d782 (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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/*
 * Suspend/resume support
 *
 * Copyright 2009  MontaVista Software, Inc.
 * Copyright 2010-2012 Freescale Semiconductor Inc.
 *
 * Author: Anton Vorontsov <avorontsov@ru.mvista.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#include <linux/init.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/suspend.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/pm.h>
#include <asm/switch_to.h>
#include <asm/cacheflush.h>

#include <sysdev/fsl_soc.h>

struct pmc_regs {
	__be32 devdisr;
	__be32 devdisr2;
	__be32 res1;
	__be32 res2;
	__be32 powmgtcsr;
#define POWMGTCSR_SLP		0x00020000
#define POWMGTCSR_DPSLP		0x00100000
#define POWMGTCSR_LOSSLESS	0x00400000
	__be32 res3[2];
	__be32 pmcdr;
};

static struct pmc_regs __iomem *pmc_regs;
static unsigned int pmc_flag;

#define PMC_SLEEP	0x1
#define PMC_DEEP_SLEEP	0x2
#define PMC_LOSSLESS	0x4

/**
 * mpc85xx_pmc_set_wake - enable devices as wakeup event source
 * @dev: a device affected
 * @enable: True to enable event generation; false to disable
 *
 * This enables the device as a wakeup event source, or disables it.
 *
 * RETURN VALUE:
 * 0 is returned on success.
 * -EINVAL is returned if device is not supposed to wake up the system.
 * -ENODEV is returned if PMC is unavailable.
 * Error code depending on the platform is returned if both the platform and
 * the native mechanism fail to enable the generation of wake-up events
 */
int mpc85xx_pmc_set_wake(struct device *dev, bool enable)
{
	int ret = 0;
	struct device_node *clk_np;
	const u32 *prop;
	u32 pmcdr_mask;

	if (!pmc_regs) {
		dev_err(dev, "%s: PMC is unavailable\n", __func__);
		return -ENODEV;
	}

	if (enable && !device_may_wakeup(dev))
		return -EINVAL;

	clk_np = of_parse_phandle(dev->of_node, "fsl,pmc-handle", 0);
	if (!clk_np)
		return -EINVAL;

	prop = of_get_property(clk_np, "fsl,pmcdr-mask", NULL);
	if (!prop) {
		ret = -EINVAL;
		goto out;
	}
	pmcdr_mask = be32_to_cpup(prop);

	if (enable)
		/* clear to enable clock in low power mode */
		clrbits32(&pmc_regs->pmcdr, pmcdr_mask);
	else
		setbits32(&pmc_regs->pmcdr, pmcdr_mask);

out:
	of_node_put(clk_np);
	return ret;
}
EXPORT_SYMBOL_GPL(mpc85xx_pmc_set_wake);

/**
 * mpc85xx_pmc_set_lossless_ethernet - enable lossless ethernet
 * in (deep) sleep mode
 * @enable: True to enable event generation; false to disable
 */
void mpc85xx_pmc_set_lossless_ethernet(int enable)
{
	if (pmc_flag & PMC_LOSSLESS) {
		if (enable)
			setbits32(&pmc_regs->powmgtcsr,	POWMGTCSR_LOSSLESS);
		else
			clrbits32(&pmc_regs->powmgtcsr, POWMGTCSR_LOSSLESS);
	}
}
EXPORT_SYMBOL_GPL(mpc85xx_pmc_set_lossless_ethernet);

static int pmc_suspend_enter(suspend_state_t state)
{
	int ret = 0;
	int result;

	switch (state) {
#ifdef CONFIG_PPC_85xx
	case PM_SUSPEND_MEM:
#ifdef CONFIG_SPE
		enable_kernel_spe();
#endif
		enable_kernel_fp();

		pr_debug("%s: Entering deep sleep\n", __func__);

		local_irq_disable();
		mpc85xx_enter_deep_sleep(get_immrbase(), POWMGTCSR_DPSLP);

		pr_debug("%s: Resumed from deep sleep\n", __func__);
		break;
#endif

	case PM_SUSPEND_STANDBY:
		local_irq_disable();
		flush_dcache_L1();

		setbits32(&pmc_regs->powmgtcsr, POWMGTCSR_SLP);
		/* At this point, the CPU is asleep. */

		/* Upon resume, wait for SLP bit to be clear. */
		result = spin_event_timeout(
			(in_be32(&pmc_regs->powmgtcsr) & POWMGTCSR_SLP) == 0,
			10000, 10);
		if (!result) {
			pr_err("%s: timeout waiting for SLP bit "
				"to be cleared\n", __func__);
			ret = -ETIMEDOUT;
		}
		break;

	default:
		ret = -EINVAL;

	}
	return ret;
}

static int pmc_suspend_valid(suspend_state_t state)
{
	if (((pmc_flag & PMC_SLEEP) && (state == PM_SUSPEND_STANDBY)) ||
	    ((pmc_flag & PMC_DEEP_SLEEP) && (state == PM_SUSPEND_MEM)))
		return 1;
	else
		return 0;
}

static const struct platform_suspend_ops pmc_suspend_ops = {
	.valid = pmc_suspend_valid,
	.enter = pmc_suspend_enter,
};

static int pmc_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;

	pmc_regs = of_iomap(np, 0);
	if (!pmc_regs)
		return -ENOMEM;

	pmc_flag = PMC_SLEEP;
	if (of_device_is_compatible(np, "fsl,mpc8536-pmc"))
		pmc_flag |= PMC_DEEP_SLEEP;

	if (of_device_is_compatible(np, "fsl,p1022-pmc"))
		pmc_flag |= PMC_DEEP_SLEEP | PMC_LOSSLESS;

	suspend_set_ops(&pmc_suspend_ops);

	pr_info("Freescale PMC driver\n");
	return 0;
}

static const struct of_device_id pmc_ids[] = {
	{ .compatible = "fsl,mpc8548-pmc", },
	{ .compatible = "fsl,mpc8641d-pmc", },
	{ },
};

static struct platform_driver pmc_driver = {
	.driver = {
		.name = "fsl-pmc",
		.owner = THIS_MODULE,
		.of_match_table = pmc_ids,
	},
	.probe = pmc_probe,
};

static int __init pmc_init(void)
{
	return platform_driver_register(&pmc_driver);
}
device_initcall(pmc_init);