]> CyberLeo.Net >> Repos - FreeBSD/releng/8.0.git/blob - sys/powerpc/powermac/smu.c
Adjust to reflect 8.0-RELEASE.
[FreeBSD/releng/8.0.git] / sys / powerpc / powermac / smu.c
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 }