/* $NetBSD: obio.c,v 1.41 2014/11/11 23:08:37 macallan Exp $ */ /*- * Copyright (C) 1998 Internet Research Institute, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by * Internet Research Institute, Inc. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __KERNEL_RCSID(0, "$NetBSD: obio.c,v 1.41 2014/11/11 23:08:37 macallan Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_obio.h" #ifdef OBIO_DEBUG # define DPRINTF printf #else # define DPRINTF while (0) printf #endif static void obio_attach(device_t, device_t, void *); static int obio_match(device_t, cfdata_t, void *); static int obio_print(void *, const char *); struct obio_softc { device_t sc_dev; bus_space_tag_t sc_tag; bus_space_handle_t sc_bh; int sc_node; #ifdef OBIO_SPEED_CONTROL int sc_voltage; int sc_busspeed; int sc_spd_hi, sc_spd_lo; struct cpufreq sc_cf; #endif }; static struct obio_softc *obio0 = NULL; #ifdef OBIO_SPEED_CONTROL static void obio_setup_gpios(struct obio_softc *, int); static void obio_set_cpu_speed(struct obio_softc *, int); static int obio_get_cpu_speed(struct obio_softc *); static int sysctl_cpuspeed_temp(SYSCTLFN_ARGS); static int sysctl_cpuspeed_cur(SYSCTLFN_ARGS); static int sysctl_cpuspeed_available(SYSCTLFN_ARGS); static void obio_get_freq(void *, void *); static void obio_set_freq(void *, void *); static const char *keylargo[] = {"Keylargo", "AAPL,Keylargo", NULL}; #endif CFATTACH_DECL_NEW(obio, sizeof(struct obio_softc), obio_match, obio_attach, NULL, NULL); int obio_match(device_t parent, cfdata_t cf, void *aux) { struct pci_attach_args *pa = aux; if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_APPLE) switch (PCI_PRODUCT(pa->pa_id)) { case PCI_PRODUCT_APPLE_GC: case PCI_PRODUCT_APPLE_OHARE: case PCI_PRODUCT_APPLE_HEATHROW: case PCI_PRODUCT_APPLE_PADDINGTON: case PCI_PRODUCT_APPLE_KEYLARGO: case PCI_PRODUCT_APPLE_PANGEA_MACIO: case PCI_PRODUCT_APPLE_INTREPID: case PCI_PRODUCT_APPLE_K2: case PCI_PRODUCT_APPLE_SHASTA: return 1; } return 0; } /* * Attach all the sub-devices we can find */ void obio_attach(device_t parent, device_t self, void *aux) { struct obio_softc *sc = device_private(self); struct pci_attach_args *pa = aux; struct confargs ca; bus_space_handle_t bsh; int node, child, namelen, error; u_int reg[20]; int intr[6], parent_intr = 0, parent_nintr = 0; int map_size = 0x1000; char name[32]; char compat[32]; sc->sc_dev = self; #ifdef OBIO_SPEED_CONTROL sc->sc_voltage = -1; sc->sc_busspeed = -1; sc->sc_spd_lo = 600; sc->sc_spd_hi = 800; #endif switch (PCI_PRODUCT(pa->pa_id)) { case PCI_PRODUCT_APPLE_GC: case PCI_PRODUCT_APPLE_OHARE: case PCI_PRODUCT_APPLE_HEATHROW: case PCI_PRODUCT_APPLE_PADDINGTON: case PCI_PRODUCT_APPLE_KEYLARGO: case PCI_PRODUCT_APPLE_PANGEA_MACIO: case PCI_PRODUCT_APPLE_INTREPID: node = pcidev_to_ofdev(pa->pa_pc, pa->pa_tag); if (node == -1) node = OF_finddevice("mac-io"); if (node == -1) node = OF_finddevice("/pci/mac-io"); break; case PCI_PRODUCT_APPLE_K2: case PCI_PRODUCT_APPLE_SHASTA: node = OF_finddevice("mac-io"); map_size = 0x10000; break; default: node = -1; break; } if (node == -1) panic("macio not found or unknown"); sc->sc_node = node; #if defined (PMAC_G5) if (OF_getprop(node, "assigned-addresses", reg, sizeof(reg)) < 20) { return; } #else if (OF_getprop(node, "assigned-addresses", reg, sizeof(reg)) < 12) return; #endif /* PMAC_G5 */ /* * XXX * This relies on the primary obio always attaching first which is * true on the PowerBook 3400c and similar machines but may or may * not work on others. We can't rely on the node name since Apple * didn't follow anything remotely resembling a consistent naming * scheme. */ if (obio0 == NULL) obio0 = sc; ca.ca_baseaddr = reg[2]; ca.ca_tag = pa->pa_memt; sc->sc_tag = pa->pa_memt; error = bus_space_map (pa->pa_memt, ca.ca_baseaddr, map_size, 0, &bsh); if (error) panic(": failed to map mac-io %#x", ca.ca_baseaddr); sc->sc_bh = bsh; printf(": addr 0x%x\n", ca.ca_baseaddr); /* Enable internal modem (KeyLargo) */ if (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_APPLE_KEYLARGO) { aprint_normal("%s: enabling KeyLargo internal modem\n", device_xname(self)); bus_space_write_4(ca.ca_tag, bsh, 0x40, bus_space_read_4(ca.ca_tag, bsh, 0x40) & ~(1<<25)); } /* Enable internal modem (Pangea) */ if (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_APPLE_PANGEA_MACIO) { /* set reset */ bus_space_write_1(ca.ca_tag, bsh, 0x006a + 0x03, 0x04); /* power modem on */ bus_space_write_1(ca.ca_tag, bsh, 0x006a + 0x02, 0x04); /* unset reset */ bus_space_write_1(ca.ca_tag, bsh, 0x006a + 0x03, 0x05); } /* Gatwick and Paddington use same product ID */ namelen = OF_getprop(node, "compatible", compat, sizeof(compat)); if (strcmp(compat, "gatwick") == 0) { parent_nintr = OF_getprop(node, "AAPL,interrupts", intr, sizeof(intr)); parent_intr = intr[0]; } else { /* Enable CD and microphone sound input. */ if (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_APPLE_PADDINGTON) bus_space_write_1(ca.ca_tag, bsh, 0x37, 0x03); } for (child = OF_child(node); child; child = OF_peer(child)) { namelen = OF_getprop(child, "name", name, sizeof(name)); if (namelen < 0) continue; if (namelen >= sizeof(name)) continue; #ifdef OBIO_SPEED_CONTROL if (strcmp(name, "gpio") == 0) { obio_setup_gpios(sc, child); continue; } #endif name[namelen] = 0; ca.ca_name = name; ca.ca_node = child; ca.ca_tag = pa->pa_memt; ca.ca_nreg = OF_getprop(child, "reg", reg, sizeof(reg)); if (strcmp(compat, "gatwick") != 0) { ca.ca_nintr = OF_getprop(child, "AAPL,interrupts", intr, sizeof(intr)); if (ca.ca_nintr == -1) ca.ca_nintr = OF_getprop(child, "interrupts", intr, sizeof(intr)); } else { intr[0] = parent_intr; ca.ca_nintr = parent_nintr; } ca.ca_reg = reg; ca.ca_intr = intr; config_found(self, &ca, obio_print); } } static const char * const skiplist[] = { "interrupt-controller", "gpio", "escc-legacy", "timer", "i2c", "power-mgt", "escc", "battery", "backlight" }; #define N_LIST (sizeof(skiplist) / sizeof(skiplist[0])) int obio_print(void *aux, const char *obio) { struct confargs *ca = aux; int i; for (i = 0; i < N_LIST; i++) if (strcmp(ca->ca_name, skiplist[i]) == 0) return QUIET; if (obio) aprint_normal("%s at %s", ca->ca_name, obio); if (ca->ca_nreg > 0) aprint_normal(" offset 0x%x", ca->ca_reg[0]); return UNCONF; } void obio_write_4(int offset, uint32_t value) { if (obio0 == NULL) return; bus_space_write_4(obio0->sc_tag, obio0->sc_bh, offset, value); } void obio_write_1(int offset, uint8_t value) { if (obio0 == NULL) return; bus_space_write_1(obio0->sc_tag, obio0->sc_bh, offset, value); } uint32_t obio_read_4(int offset) { if (obio0 == NULL) return 0xffffffff; return bus_space_read_4(obio0->sc_tag, obio0->sc_bh, offset); } uint8_t obio_read_1(int offset) { if (obio0 == NULL) return 0xff; return bus_space_read_1(obio0->sc_tag, obio0->sc_bh, offset); } #ifdef OBIO_SPEED_CONTROL static void obio_setup_cpufreq(device_t dev) { struct obio_softc *sc = device_private(dev); int ret; ret = cpufreq_register(&sc->sc_cf); if (ret != 0) aprint_error_dev(sc->sc_dev, "cpufreq_register() failed, error %d\n", ret); } static void obio_setup_gpios(struct obio_softc *sc, int node) { uint32_t gpio_base, reg[6]; const struct sysctlnode *sysctl_node, *me, *freq; struct cpufreq *cf = &sc->sc_cf; char name[32]; int child, use_dfs, cpunode, hiclock; if (of_compatible(sc->sc_node, keylargo) == -1) return; if (OF_getprop(node, "reg", reg, sizeof(reg)) < 4) return; gpio_base = reg[0]; DPRINTF("gpio_base: %02x\n", gpio_base); /* now look for voltage and bus speed gpios */ use_dfs = 0; for (child = OF_child(node); child; child = OF_peer(child)) { if (OF_getprop(child, "name", name, sizeof(name)) < 1) continue; if (OF_getprop(child, "reg", reg, sizeof(reg)) < 4) continue; /* * These register offsets either have to be added to the obio * base address or to the gpio base address. This differs * even in the same OF-tree! So we guess the offset is * based on obio when it is larger than the gpio_base. */ if (reg[0] >= gpio_base) reg[0] -= gpio_base; if (strcmp(name, "frequency-gpio") == 0) { DPRINTF("found frequency_gpio at %02x\n", reg[0]); sc->sc_busspeed = gpio_base + reg[0]; } if (strcmp(name, "voltage-gpio") == 0) { DPRINTF("found voltage_gpio at %02x\n", reg[0]); sc->sc_voltage = gpio_base + reg[0]; } if (strcmp(name, "cpu-vcore-select") == 0) { DPRINTF("found cpu-vcore-select at %02x\n", reg[0]); sc->sc_voltage = gpio_base + reg[0]; /* frequency gpio is not needed, we use cpu's DFS */ use_dfs = 1; } } if ((sc->sc_voltage < 0) || (sc->sc_busspeed < 0 && !use_dfs)) return; printf("%s: enabling Intrepid CPU speed control\n", device_xname(sc->sc_dev)); sc->sc_spd_lo = curcpu()->ci_khz / 1000; hiclock = 0; cpunode = OF_finddevice("/cpus/@0"); OF_getprop(cpunode, "clock-frequency", &hiclock, 4); if (hiclock != 0) sc->sc_spd_hi = (hiclock + 500000) / 1000000; printf("hiclock: %d\n", sc->sc_spd_hi); sysctl_node = NULL; if (sysctl_createv(NULL, 0, NULL, &me, CTLFLAG_READWRITE, CTLTYPE_NODE, "intrepid", NULL, NULL, 0, NULL, 0, CTL_MACHDEP, CTL_CREATE, CTL_EOL) != 0) printf("couldn't create 'intrepid' node\n"); if (sysctl_createv(NULL, 0, NULL, &freq, CTLFLAG_READWRITE, CTLTYPE_NODE, "frequency", NULL, NULL, 0, NULL, 0, CTL_MACHDEP, me->sysctl_num, CTL_CREATE, CTL_EOL) != 0) printf("couldn't create 'frequency' node\n"); if (sysctl_createv(NULL, 0, NULL, &sysctl_node, CTLFLAG_READWRITE | CTLFLAG_OWNDESC, CTLTYPE_INT, "target", "CPU speed", sysctl_cpuspeed_temp, 0, (void *)sc, 0, CTL_MACHDEP, me->sysctl_num, freq->sysctl_num, CTL_CREATE, CTL_EOL) == 0) { } else printf("couldn't create 'target' node\n"); if (sysctl_createv(NULL, 0, NULL, &sysctl_node, CTLFLAG_READWRITE, CTLTYPE_INT, "current", NULL, sysctl_cpuspeed_cur, 1, (void *)sc, 0, CTL_MACHDEP, me->sysctl_num, freq->sysctl_num, CTL_CREATE, CTL_EOL) == 0) { } else printf("couldn't create 'current' node\n"); if (sysctl_createv(NULL, 0, NULL, &sysctl_node, CTLFLAG_READWRITE, CTLTYPE_STRING, "available", NULL, sysctl_cpuspeed_available, 2, (void *)sc, 0, CTL_MACHDEP, me->sysctl_num, freq->sysctl_num, CTL_CREATE, CTL_EOL) == 0) { } else printf("couldn't create 'available' node\n"); printf("speed: %d\n", curcpu()->ci_khz); /* support cpufreq */ snprintf(cf->cf_name, CPUFREQ_NAME_MAX, "Intrepid"); cf->cf_state[0].cfs_freq = sc->sc_spd_hi; cf->cf_state[1].cfs_freq = sc->sc_spd_lo; cf->cf_state_count = 2; cf->cf_mp = FALSE; cf->cf_cookie = sc; cf->cf_get_freq = obio_get_freq; cf->cf_set_freq = obio_set_freq; /* * XXX * cpufreq_register() calls xc_broadcast() which relies on kthreads * running so we need to postpone it */ config_interrupts(sc->sc_dev, obio_setup_cpufreq); } static void obio_set_cpu_speed(struct obio_softc *sc, int fast) { if (sc->sc_voltage < 0) return; if (sc->sc_busspeed >= 0) { /* set voltage and speed via gpio */ if (fast) { bus_space_write_1(sc->sc_tag, sc->sc_bh, sc->sc_voltage, 5); bus_space_write_1(sc->sc_tag, sc->sc_bh, sc->sc_busspeed, 5); } else { bus_space_write_1(sc->sc_tag, sc->sc_bh, sc->sc_busspeed, 4); bus_space_write_1(sc->sc_tag, sc->sc_bh, sc->sc_voltage, 4); } } else { /* set voltage via gpio and speed via the 7447A's DFS bit */ if (fast) { bus_space_write_1(sc->sc_tag, sc->sc_bh, sc->sc_voltage, 5); DELAY(1000); } /* set DFS for all cpus */ cpu_set_dfs(fast ? 1 : 2); DELAY(100); if (!fast) { bus_space_write_1(sc->sc_tag, sc->sc_bh, sc->sc_voltage, 4); DELAY(1000); } } } static int obio_get_cpu_speed(struct obio_softc *sc) { if (sc->sc_voltage < 0) return 0; if (sc->sc_busspeed >= 0) { if (bus_space_read_1(sc->sc_tag, sc->sc_bh, sc->sc_busspeed) & 1) return 1; } else return cpu_get_dfs() == 1; return 0; } static void obio_get_freq(void *cookie, void *spd) { struct obio_softc *sc = cookie; uint32_t *freq; freq = spd; if (obio_get_cpu_speed(sc) == 0) { *freq = sc->sc_spd_lo; } else *freq = sc->sc_spd_hi; } static void obio_set_freq(void *cookie, void *spd) { struct obio_softc *sc = cookie; uint32_t *freq; freq = spd; if (*freq == sc->sc_spd_lo) { obio_set_cpu_speed(sc, 0); } else if (*freq == sc->sc_spd_hi) { obio_set_cpu_speed(sc, 1); } else aprint_error_dev(sc->sc_dev, "%s(%d) bogus CPU speed\n", __func__, *freq); } static int sysctl_cpuspeed_temp(SYSCTLFN_ARGS) { struct sysctlnode node = *rnode; struct obio_softc *sc = node.sysctl_data; int speed, mhz; speed = obio_get_cpu_speed(sc); switch (speed) { case 0: mhz = sc->sc_spd_lo; break; case 1: mhz = sc->sc_spd_hi; break; default: speed = -1; } node.sysctl_data = &mhz; if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) { int new_reg; new_reg = *(int *)node.sysctl_data; if (new_reg == sc->sc_spd_lo) { obio_set_cpu_speed(sc, 0); } else if (new_reg == sc->sc_spd_hi) { obio_set_cpu_speed(sc, 1); } else { printf("%s: new_reg %d\n", __func__, new_reg); return EINVAL; } return 0; } return EINVAL; } static int sysctl_cpuspeed_cur(SYSCTLFN_ARGS) { struct sysctlnode node = *rnode; struct obio_softc *sc = node.sysctl_data; int speed, mhz; speed = obio_get_cpu_speed(sc); switch (speed) { case 0: mhz = sc->sc_spd_lo; break; case 1: mhz = sc->sc_spd_hi; break; default: speed = -1; } node.sysctl_data = &mhz; return sysctl_lookup(SYSCTLFN_CALL(&node)); } static int sysctl_cpuspeed_available(SYSCTLFN_ARGS) { struct sysctlnode node = *rnode; struct obio_softc *sc = node.sysctl_data; char buf[128]; snprintf(buf, 128, "%d %d", sc->sc_spd_lo, sc->sc_spd_hi); node.sysctl_data = buf; return(sysctl_lookup(SYSCTLFN_CALL(&node))); } SYSCTL_SETUP(sysctl_ams_setup, "sysctl obio subtree setup") { sysctl_createv(NULL, 0, NULL, NULL, CTLFLAG_PERMANENT, CTLTYPE_NODE, "machdep", NULL, NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL); } #endif /* OBIO_SPEEDCONTROL */