xref: /freebsd/sys/powerpc/powermac/smu.c (revision 830940567b49bb0c08dfaed40418999e76616909)
1 /*-
2  * Copyright (c) 2009 Nathan Whitehorn
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
21  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  */
27 
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30 
31 #include <sys/param.h>
32 #include <sys/bus.h>
33 #include <sys/systm.h>
34 #include <sys/module.h>
35 #include <sys/conf.h>
36 #include <sys/cpu.h>
37 #include <sys/kernel.h>
38 #include <sys/rman.h>
39 #include <sys/sysctl.h>
40 
41 #include <machine/bus.h>
42 #include <machine/md_var.h>
43 
44 #include <dev/ofw/openfirm.h>
45 #include <dev/ofw/ofw_bus.h>
46 #include <powerpc/powermac/macgpiovar.h>
47 
48 struct smu_cmd {
49 	uint8_t		cmd;
50 	uint8_t		len;
51 	uint8_t		data[254];
52 };
53 
54 struct smu_softc {
55 	device_t	sc_dev;
56 	struct mtx	sc_mtx;
57 
58 	struct resource	*sc_memr;
59 	int		sc_memrid;
60 
61 	bus_dma_tag_t	sc_dmatag;
62 	bus_space_tag_t	sc_bt;
63 	bus_space_handle_t sc_mailbox;
64 
65 	struct smu_cmd	*sc_cmd;
66 	bus_addr_t	sc_cmd_phys;
67 	bus_dmamap_t	sc_cmd_dmamap;
68 };
69 
70 /* regular bus attachment functions */
71 
72 static int	smu_probe(device_t);
73 static int	smu_attach(device_t);
74 
75 /* cpufreq notification hooks */
76 
77 static void	smu_cpufreq_pre_change(device_t, const struct cf_level *level);
78 static void	smu_cpufreq_post_change(device_t, const struct cf_level *level);
79 
80 /* where to find the doorbell GPIO */
81 
82 static device_t	smu_doorbell = NULL;
83 
84 static device_method_t  smu_methods[] = {
85 	/* Device interface */
86 	DEVMETHOD(device_probe,		smu_probe),
87 	DEVMETHOD(device_attach,	smu_attach),
88 	{ 0, 0 },
89 };
90 
91 static driver_t smu_driver = {
92 	"smu",
93 	smu_methods,
94 	sizeof(struct smu_softc)
95 };
96 
97 static devclass_t smu_devclass;
98 
99 DRIVER_MODULE(smu, nexus, smu_driver, smu_devclass, 0, 0);
100 
101 #define SMU_MAILBOX	0x860c
102 
103 /* Command types */
104 #define SMU_POWER	0xaa
105 
106 static int
107 smu_probe(device_t dev)
108 {
109 	const char *name = ofw_bus_get_name(dev);
110 
111 	if (strcmp(name, "smu") != 0)
112 		return (ENXIO);
113 
114 	device_set_desc(dev, "Apple System Management Unit");
115 	return (0);
116 }
117 
118 static void
119 smu_phys_callback(void *xsc, bus_dma_segment_t *segs, int nsegs, int error)
120 {
121 	struct smu_softc *sc = xsc;
122 
123 	sc->sc_cmd_phys = segs[0].ds_addr;
124 }
125 
126 static int
127 smu_attach(device_t dev)
128 {
129 	struct smu_softc *sc;
130 
131 	sc = device_get_softc(dev);
132 
133 	mtx_init(&sc->sc_mtx, "smu", NULL, MTX_DEF);
134 
135 	/*
136 	 * Map the mailbox area. This should be determined from firmware,
137 	 * but I have not found a simple way to do that.
138 	 */
139 	bus_dma_tag_create(NULL, 16, 0, BUS_SPACE_MAXADDR_32BIT,
140 	    BUS_SPACE_MAXADDR, NULL, NULL, PAGE_SIZE, 1, PAGE_SIZE, 0, NULL,
141 	    NULL, &(sc->sc_dmatag));
142 	sc->sc_bt = &bs_be_tag;
143 	bus_space_map(sc->sc_bt, SMU_MAILBOX, 4, 0, &sc->sc_mailbox);
144 
145 	/*
146 	 * Allocate the command buffer. This can be anywhere in the low 4 GB
147 	 * of memory.
148 	 */
149 	bus_dmamem_alloc(sc->sc_dmatag, (void **)&sc->sc_cmd, BUS_DMA_WAITOK |
150 	    BUS_DMA_ZERO, &sc->sc_cmd_dmamap);
151 	bus_dmamap_load(sc->sc_dmatag, sc->sc_cmd_dmamap,
152 	    sc->sc_cmd, PAGE_SIZE, smu_phys_callback, sc, 0);
153 
154 	/*
155 	 * Set up handlers to change CPU voltage when CPU frequency is changed.
156 	 */
157 	EVENTHANDLER_REGISTER(cpufreq_pre_change, smu_cpufreq_pre_change, dev,
158 	    EVENTHANDLER_PRI_ANY);
159 	EVENTHANDLER_REGISTER(cpufreq_post_change, smu_cpufreq_post_change, dev,
160 	    EVENTHANDLER_PRI_ANY);
161 
162 	return (0);
163 }
164 
165 static int
166 smu_run_cmd(device_t dev, struct smu_cmd *cmd)
167 {
168 	struct smu_softc *sc;
169 	int doorbell_ack, result;
170 
171 	sc = device_get_softc(dev);
172 
173 	mtx_lock(&sc->sc_mtx);
174 
175 	/* Copy the command to the mailbox */
176 	memcpy(sc->sc_cmd, cmd, sizeof(*cmd));
177 	bus_dmamap_sync(sc->sc_dmatag, sc->sc_cmd_dmamap, BUS_DMASYNC_PREWRITE);
178 	bus_space_write_4(sc->sc_bt, sc->sc_mailbox, 0, sc->sc_cmd_phys);
179 
180 	/* Invalidate the cacheline it is in -- SMU bypasses the cache */
181 	__asm __volatile("dcbst 0,%0; sync" :: "r"(sc->sc_cmd): "memory");
182 
183 	/* Ring SMU doorbell */
184 	macgpio_write(smu_doorbell, GPIO_DDR_OUTPUT);
185 
186 	/* Wait for the doorbell GPIO to go high, signaling completion */
187 	do {
188 		/* XXX: timeout */
189 		DELAY(50);
190 		doorbell_ack = macgpio_read(smu_doorbell);
191 	} while (!doorbell_ack);
192 
193 	/* Check result. First invalidate the cache again... */
194 	__asm __volatile("dcbf 0,%0; sync" :: "r"(sc->sc_cmd) : "memory");
195 
196 	bus_dmamap_sync(sc->sc_dmatag, sc->sc_cmd_dmamap, BUS_DMASYNC_POSTREAD);
197 
198 	/* SMU acks the command by inverting the command bits */
199 	if (sc->sc_cmd->cmd == ~cmd->cmd)
200 		result = 0;
201 	else
202 		result = EIO;
203 
204 	mtx_unlock(&sc->sc_mtx);
205 
206 	return (result);
207 }
208 
209 static void
210 smu_slew_cpu_voltage(device_t dev, int to)
211 {
212 	struct smu_cmd cmd;
213 
214 	cmd.cmd = SMU_POWER;
215 	cmd.len = 8;
216 	cmd.data[0] = 'V';
217 	cmd.data[1] = 'S';
218 	cmd.data[2] = 'L';
219 	cmd.data[3] = 'E';
220 	cmd.data[4] = 'W';
221 	cmd.data[5] = 0xff;
222 	cmd.data[6] = 1;
223 	cmd.data[7] = to;
224 
225 	smu_run_cmd(dev, &cmd);
226 }
227 
228 static void
229 smu_cpufreq_pre_change(device_t dev, const struct cf_level *level)
230 {
231 	/*
232 	 * Make sure the CPU voltage is raised before we raise
233 	 * the clock.
234 	 */
235 
236 	if (level->rel_set[0].freq == 10000 /* max */)
237 		smu_slew_cpu_voltage(dev, 0);
238 }
239 
240 static void
241 smu_cpufreq_post_change(device_t dev, const struct cf_level *level)
242 {
243 	/* We are safe to reduce CPU voltage after a downward transition */
244 
245 	if (level->rel_set[0].freq < 10000 /* max */)
246 		smu_slew_cpu_voltage(dev, 1); /* XXX: 1/4 voltage for 970MP? */
247 }
248 
249 /* Routines for probing the SMU doorbell GPIO */
250 static int doorbell_probe(device_t dev);
251 static int doorbell_attach(device_t dev);
252 
253 static device_method_t  doorbell_methods[] = {
254 	/* Device interface */
255 	DEVMETHOD(device_probe,		doorbell_probe),
256 	DEVMETHOD(device_attach,	doorbell_attach),
257 	{ 0, 0 },
258 };
259 
260 static driver_t doorbell_driver = {
261 	"smudoorbell",
262 	doorbell_methods,
263 	0
264 };
265 
266 static devclass_t doorbell_devclass;
267 
268 DRIVER_MODULE(smudoorbell, macgpio, doorbell_driver, doorbell_devclass, 0, 0);
269 
270 static int
271 doorbell_probe(device_t dev)
272 {
273 	const char *name = ofw_bus_get_name(dev);
274 
275 	if (strcmp(name, "smu-doorbell") != 0)
276 		return (ENXIO);
277 
278 	device_set_desc(dev, "SMU Doorbell GPIO");
279 	device_quiet(dev);
280 	return (0);
281 }
282 
283 static int
284 doorbell_attach(device_t dev)
285 {
286 	smu_doorbell = dev;
287 	return (0);
288 }
289