xref: /openbsd-src/sys/dev/fdt/psci.c (revision ae3cb403620ab940fbaabb3055fac045a63d56b7)
1 /*	$OpenBSD: psci.c,v 1.4 2018/01/17 10:17:33 kettenis Exp $	*/
2 
3 /*
4  * Copyright (c) 2016 Jonathan Gray <jsg@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/param.h>
20 #include <sys/device.h>
21 #include <sys/systm.h>
22 
23 #include <machine/bus.h>
24 #include <machine/fdt.h>
25 
26 #include <dev/ofw/openfirm.h>
27 #include <dev/ofw/fdt.h>
28 
29 #include <dev/fdt/pscivar.h>
30 
31 extern void (*cpuresetfn)(void);
32 extern void (*powerdownfn)(void);
33 extern int (*cpu_on_fn)(register_t, register_t);
34 
35 #define PSCI_VERSION	0x84000000
36 #define SYSTEM_OFF	0x84000008
37 #define SYSTEM_RESET	0x84000009
38 #ifdef __LP64__
39 #define CPU_ON		0xc4000003
40 #else
41 #define CPU_ON		0x84000003
42 #endif
43 
44 struct psci_softc {
45 	struct device	 sc_dev;
46 	register_t	 (*sc_callfn)(register_t, register_t, register_t,
47 			     register_t);
48 	int		 sc_psci_version;
49 	int		 sc_system_off;
50 	int		 sc_system_reset;
51 	int		 sc_cpu_on;
52 };
53 
54 struct psci_softc *psci_sc;
55 
56 int	psci_match(struct device *, void *, void *);
57 void	psci_attach(struct device *, struct device *, void *);
58 void	psci_reset(void);
59 void	psci_powerdown(void);
60 int	psci_cpu_on(register_t, register_t);
61 
62 extern register_t hvc_call(register_t, register_t, register_t, register_t);
63 extern register_t smc_call(register_t, register_t, register_t, register_t);
64 
65 struct cfattach psci_ca = {
66 	sizeof(struct psci_softc), psci_match, psci_attach
67 };
68 
69 struct cfdriver psci_cd = {
70 	NULL, "psci", DV_DULL
71 };
72 
73 int
74 psci_match(struct device *parent, void *match, void *aux)
75 {
76 	struct fdt_attach_args *faa = aux;
77 
78 	return OF_is_compatible(faa->fa_node, "arm,psci") ||
79 	    OF_is_compatible(faa->fa_node, "arm,psci-0.2") ||
80 	    OF_is_compatible(faa->fa_node, "arm,psci-1.0");
81 }
82 
83 void
84 psci_attach(struct device *parent, struct device *self, void *aux)
85 {
86 	struct psci_softc *sc = (struct psci_softc *)self;
87 	struct fdt_attach_args *faa = aux;
88 	char method[128];
89 	uint32_t version;
90 
91 	if (OF_getprop(faa->fa_node, "method", method, sizeof(method))) {
92 		if (strcmp(method, "hvc") == 0)
93 			sc->sc_callfn = hvc_call;
94 		else if (strcmp(method, "smc") == 0)
95 			sc->sc_callfn = smc_call;
96 	}
97 
98 	/*
99 	 * The function IDs are only to be parsed for the old specification
100 	 * (as in version 0.1).  All newer implementations are supposed to
101 	 * use the specified values.
102 	 */
103 	if (OF_is_compatible(faa->fa_node, "arm,psci-0.2") ||
104 	    OF_is_compatible(faa->fa_node, "arm,psci-1.0")) {
105 		sc->sc_psci_version = PSCI_VERSION;
106 		sc->sc_system_off = SYSTEM_OFF;
107 		sc->sc_system_reset = SYSTEM_RESET;
108 		sc->sc_cpu_on = CPU_ON;
109 	} else if (OF_is_compatible(faa->fa_node, "arm,psci")) {
110 		sc->sc_system_off = OF_getpropint(faa->fa_node,
111 		    "system_off", 0);
112 		sc->sc_system_reset = OF_getpropint(faa->fa_node,
113 		    "system_reset", 0);
114 		sc->sc_cpu_on = OF_getpropint(faa->fa_node, "cpu_on", 0);
115 	}
116 
117 	psci_sc = sc;
118 
119 	version = psci_version();
120 	printf(": PSCI %d.%d\n", version >> 16, version & 0xffff);
121 
122 	if (sc->sc_system_off != 0)
123 		powerdownfn = psci_powerdown;
124 	if (sc->sc_system_reset != 0)
125 		cpuresetfn = psci_reset;
126 	if (sc->sc_cpu_on != 0)
127 		cpu_on_fn = psci_cpu_on;
128 }
129 
130 uint32_t
131 psci_version(void)
132 {
133 	struct psci_softc *sc = psci_sc;
134 
135 	if (sc && sc->sc_callfn && sc->sc_psci_version != 0)
136 		return (*sc->sc_callfn)(sc->sc_psci_version, 0, 0, 0);
137 
138 	/* No version support; return 0.0. */
139 	return 0;
140 }
141 
142 void
143 psci_reset(void)
144 {
145 	struct psci_softc *sc = psci_sc;
146 	if (sc->sc_callfn)
147 		(*sc->sc_callfn)(sc->sc_system_reset, 0, 0, 0);
148 }
149 
150 void
151 psci_powerdown(void)
152 {
153 	struct psci_softc *sc = psci_sc;
154 	if (sc->sc_callfn)
155 		(*sc->sc_callfn)(sc->sc_system_off, 0, 0, 0);
156 }
157 
158 int
159 psci_cpu_on(register_t mpidr, register_t pc)
160 {
161 	struct psci_softc *sc = psci_sc;
162 	if (sc->sc_callfn)
163 		return (*sc->sc_callfn)(sc->sc_cpu_on, mpidr, pc, 0);
164 	return -1;
165 }
166