summaryrefslogtreecommitdiff
path: root/arch/arm/mach-bcm2835/bcm2835.c
blob: 40686d7ef500223765a1a08f6beccfad9a2847f6 (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
/*
 * Copyright (C) 2010 Broadcom
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/delay.h>
#include <linux/init.h>
#include <linux/irqchip/bcm2835.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/clk/bcm2835.h>
#include <linux/clocksource.h>

#include <asm/mach/arch.h>
#include <asm/mach/map.h>

#define PM_RSTC				0x1c
#define PM_RSTS				0x20
#define PM_WDOG				0x24

#define PM_PASSWORD			0x5a000000
#define PM_RSTC_WRCFG_MASK		0x00000030
#define PM_RSTC_WRCFG_FULL_RESET	0x00000020
#define PM_RSTS_HADWRH_SET		0x00000040

#define BCM2835_PERIPH_PHYS	0x20000000
#define BCM2835_PERIPH_VIRT	0xf0000000
#define BCM2835_PERIPH_SIZE	SZ_16M

static void __iomem *wdt_regs;

/*
 * The machine restart method can be called from an atomic context so we won't
 * be able to ioremap the regs then.
 */
static void bcm2835_setup_restart(void)
{
	struct device_node *np = of_find_compatible_node(NULL, NULL,
						"brcm,bcm2835-pm-wdt");
	if (WARN(!np, "unable to setup watchdog restart"))
		return;

	wdt_regs = of_iomap(np, 0);
	WARN(!wdt_regs, "failed to remap watchdog regs");
}

static void bcm2835_restart(enum reboot_mode mode, const char *cmd)
{
	u32 val;

	if (!wdt_regs)
		return;

	/* use a timeout of 10 ticks (~150us) */
	writel_relaxed(10 | PM_PASSWORD, wdt_regs + PM_WDOG);
	val = readl_relaxed(wdt_regs + PM_RSTC);
	val &= ~PM_RSTC_WRCFG_MASK;
	val |= PM_PASSWORD | PM_RSTC_WRCFG_FULL_RESET;
	writel_relaxed(val, wdt_regs + PM_RSTC);

	/* No sleeping, possibly atomic. */
	mdelay(1);
}

/*
 * We can't really power off, but if we do the normal reset scheme, and
 * indicate to bootcode.bin not to reboot, then most of the chip will be
 * powered off.
 */
static void bcm2835_power_off(void)
{
	u32 val;

	/*
	 * We set the watchdog hard reset bit here to distinguish this reset
	 * from the normal (full) reset. bootcode.bin will not reboot after a
	 * hard reset.
	 */
	val = readl_relaxed(wdt_regs + PM_RSTS);
	val &= ~PM_RSTC_WRCFG_MASK;
	val |= PM_PASSWORD | PM_RSTS_HADWRH_SET;
	writel_relaxed(val, wdt_regs + PM_RSTS);

	/* Continue with normal reset mechanism */
	bcm2835_restart(REBOOT_HARD, "");
}

static struct map_desc io_map __initdata = {
	.virtual = BCM2835_PERIPH_VIRT,
	.pfn = __phys_to_pfn(BCM2835_PERIPH_PHYS),
	.length = BCM2835_PERIPH_SIZE,
	.type = MT_DEVICE
};

static void __init bcm2835_map_io(void)
{
	iotable_init(&io_map, 1);
}

static void __init bcm2835_init(void)
{
	int ret;

	bcm2835_setup_restart();
	if (wdt_regs)
		pm_power_off = bcm2835_power_off;

	bcm2835_init_clocks();

	ret = of_platform_populate(NULL, of_default_bus_match_table, NULL,
				   NULL);
	if (ret) {
		pr_err("of_platform_populate failed: %d\n", ret);
		BUG();
	}
}

static const char * const bcm2835_compat[] = {
	"brcm,bcm2835",
	NULL
};

DT_MACHINE_START(BCM2835, "BCM2835")
	.map_io = bcm2835_map_io,
	.init_irq = bcm2835_init_irq,
	.handle_irq = bcm2835_handle_irq,
	.init_machine = bcm2835_init,
	.init_time = clocksource_of_init,
	.restart = bcm2835_restart,
	.dt_compat = bcm2835_compat
MACHINE_END