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