1 /* $NetBSD: elan520.c,v 1.4 2002/10/02 05:47:15 thorpej Exp $ */ 2 3 /*- 4 * Copyright (c) 2002 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Jason R. Thorpe. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. All advertising materials mentioning features or use of this software 19 * must display the following acknowledgement: 20 * This product includes software developed by the NetBSD 21 * Foundation, Inc. and its contributors. 22 * 4. Neither the name of The NetBSD Foundation nor the names of its 23 * contributors may be used to endorse or promote products derived 24 * from this software without specific prior written permission. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 36 * POSSIBILITY OF SUCH DAMAGE. 37 */ 38 39 /* 40 * Device driver for the AMD Elan SC520 System Controller. This attaches 41 * where the "pchb" driver might normally attach, and provides support for 42 * extra features on the SC520, such as the watchdog timer and GPIO. 43 * 44 * Information about the GP bus echo bug work-around is from code posted 45 * to the "soekris-tech" mailing list by Jasper Wallace. 46 */ 47 48 #include <sys/cdefs.h> 49 50 __KERNEL_RCSID(0, "$NetBSD: elan520.c,v 1.4 2002/10/02 05:47:15 thorpej Exp $"); 51 52 #include <sys/param.h> 53 #include <sys/systm.h> 54 #include <sys/device.h> 55 #include <sys/wdog.h> 56 57 #include <machine/bus.h> 58 59 #include <dev/pci/pcivar.h> 60 61 #include <dev/pci/pcidevs.h> 62 63 #include <arch/i386/pci/elan520reg.h> 64 65 #include <dev/sysmon/sysmonvar.h> 66 67 struct elansc_softc { 68 struct device sc_dev; 69 bus_space_tag_t sc_memt; 70 bus_space_handle_t sc_memh; 71 int sc_echobug; 72 73 struct sysmon_wdog sc_smw; 74 }; 75 76 static void 77 elansc_wdogctl_write(struct elansc_softc *sc, uint16_t val) 78 { 79 int s; 80 uint8_t echo_mode; 81 82 s = splhigh(); 83 84 /* Switch off GP bus echo mode if we need to. */ 85 if (sc->sc_echobug) { 86 echo_mode = bus_space_read_1(sc->sc_memt, sc->sc_memh, 87 MMCR_GPECHO); 88 bus_space_write_1(sc->sc_memt, sc->sc_memh, 89 MMCR_GPECHO, echo_mode & ~GPECHO_GP_ECHO_ENB); 90 } 91 92 /* Unlock the register. */ 93 bus_space_write_2(sc->sc_memt, sc->sc_memh, MMCR_WDTMRCTL, 94 WDTMRCTL_UNLOCK1); 95 bus_space_write_2(sc->sc_memt, sc->sc_memh, MMCR_WDTMRCTL, 96 WDTMRCTL_UNLOCK2); 97 98 /* Write the value. */ 99 bus_space_write_2(sc->sc_memt, sc->sc_memh, MMCR_WDTMRCTL, val); 100 101 /* Switch GP bus echo mode back. */ 102 if (sc->sc_echobug) 103 bus_space_write_1(sc->sc_memt, sc->sc_memh, MMCR_GPECHO, 104 echo_mode); 105 106 splx(s); 107 } 108 109 static void 110 elansc_wdogctl_reset(struct elansc_softc *sc) 111 { 112 int s; 113 uint8_t echo_mode; 114 115 s = splhigh(); 116 117 /* Switch off GP bus echo mode if we need to. */ 118 if (sc->sc_echobug) { 119 echo_mode = bus_space_read_1(sc->sc_memt, sc->sc_memh, 120 MMCR_GPECHO); 121 bus_space_write_1(sc->sc_memt, sc->sc_memh, 122 MMCR_GPECHO, echo_mode & ~GPECHO_GP_ECHO_ENB); 123 } 124 125 /* Reset the watchdog. */ 126 bus_space_write_2(sc->sc_memt, sc->sc_memh, MMCR_WDTMRCTL, 127 WDTMRCTL_RESET1); 128 bus_space_write_2(sc->sc_memt, sc->sc_memh, MMCR_WDTMRCTL, 129 WDTMRCTL_RESET2); 130 131 /* Switch GP bus echo mode back. */ 132 if (sc->sc_echobug) 133 bus_space_write_1(sc->sc_memt, sc->sc_memh, MMCR_GPECHO, 134 echo_mode); 135 136 splx(s); 137 } 138 139 static const struct { 140 int period; /* whole seconds */ 141 uint16_t exp; /* exponent select */ 142 } elansc_wdog_periods[] = { 143 { 1, WDTMRCTL_EXP_SEL25 }, 144 { 2, WDTMRCTL_EXP_SEL26 }, 145 { 4, WDTMRCTL_EXP_SEL27 }, 146 { 8, WDTMRCTL_EXP_SEL28 }, 147 { 16, WDTMRCTL_EXP_SEL29 }, 148 { 32, WDTMRCTL_EXP_SEL30 }, 149 { 0, 0 }, 150 }; 151 152 static int 153 elansc_wdog_setmode(struct sysmon_wdog *smw) 154 { 155 struct elansc_softc *sc = smw->smw_cookie; 156 int i; 157 uint16_t exp_sel; 158 159 if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) { 160 elansc_wdogctl_write(sc, 161 WDTMRCTL_WRST_ENB | WDTMRCTL_EXP_SEL30); 162 } else { 163 if (smw->smw_period == WDOG_PERIOD_DEFAULT) { 164 smw->smw_period = 32; 165 exp_sel = WDTMRCTL_EXP_SEL30; 166 } else { 167 for (i = 0; elansc_wdog_periods[i].period != 0; i++) { 168 if (elansc_wdog_periods[i].period == 169 smw->smw_period) { 170 exp_sel = elansc_wdog_periods[i].exp; 171 break; 172 } 173 } 174 if (elansc_wdog_periods[i].period == 0) 175 return (EINVAL); 176 } 177 elansc_wdogctl_write(sc, WDTMRCTL_ENB | 178 WDTMRCTL_WRST_ENB | exp_sel); 179 elansc_wdogctl_reset(sc); 180 } 181 return (0); 182 } 183 184 static int 185 elansc_wdog_tickle(struct sysmon_wdog *smw) 186 { 187 struct elansc_softc *sc = smw->smw_cookie; 188 189 elansc_wdogctl_reset(sc); 190 return (0); 191 } 192 193 static int 194 elansc_match(struct device *parent, struct cfdata *match, void *aux) 195 { 196 struct pci_attach_args *pa = aux; 197 198 if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_AMD && 199 PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_AMD_SC520_SC) 200 return (10); /* beat pchb */ 201 202 return (0); 203 } 204 205 static const char *elansc_speeds[] = { 206 "(reserved 00)", 207 "100MHz", 208 "133MHz", 209 "(reserved 11)", 210 }; 211 212 static void 213 elansc_attach(struct device *parent, struct device *self, void *aux) 214 { 215 struct elansc_softc *sc = (void *) self; 216 struct pci_attach_args *pa = aux; 217 uint16_t rev; 218 uint8_t ressta, cpuctl; 219 220 printf(": AMD Elan SC520 System Controller\n"); 221 222 sc->sc_memt = pa->pa_memt; 223 if (bus_space_map(sc->sc_memt, MMCR_BASE_ADDR, NBPG, 0, 224 &sc->sc_memh) != 0) { 225 printf("%s: unable to map registers\n", sc->sc_dev.dv_xname); 226 return; 227 } 228 229 rev = bus_space_read_2(sc->sc_memt, sc->sc_memh, MMCR_REVID); 230 cpuctl = bus_space_read_1(sc->sc_memt, sc->sc_memh, MMCR_CPUCTL); 231 232 printf("%s: product %d stepping %d.%d, CPU clock %s\n", 233 sc->sc_dev.dv_xname, 234 (rev & REVID_PRODID) >> REVID_PRODID_SHIFT, 235 (rev & REVID_MAJSTEP) >> REVID_MAJSTEP_SHIFT, 236 (rev & REVID_MINSTEP), 237 elansc_speeds[cpuctl & CPUCTL_CPU_CLK_SPD_MASK]); 238 239 /* 240 * SC520 rev A1 has a bug that affects the watchdog timer. If 241 * the GP bus echo mode is enabled, writing to the watchdog control 242 * register is blocked. 243 * 244 * The BIOS in some systems (e.g. the Soekris net4501) enables 245 * GP bus echo for various reasons, so we need to switch it off 246 * when we talk to the watchdog timer. 247 * 248 * XXX The step 1.1 (B1?) in my Soekris net4501 also has this 249 * XXX problem, so we'll just enable it for all Elan SC520s 250 * XXX for now. --thorpej@netbsd.org 251 */ 252 if (1 || rev == ((PRODID_ELAN_SC520 << REVID_PRODID_SHIFT) | 253 (0 << REVID_MAJSTEP_SHIFT) | (1))) 254 sc->sc_echobug = 1; 255 256 /* 257 * Determine cause of the last reset, and issue a warning if it 258 * was due to watchdog expiry. 259 */ 260 ressta = bus_space_read_1(sc->sc_memt, sc->sc_memh, MMCR_RESSTA); 261 if (ressta & RESSTA_WDT_RST_DET) 262 printf("%s: WARNING: LAST RESET DUE TO WATCHDOG EXPIRATION!\n", 263 sc->sc_dev.dv_xname); 264 bus_space_write_1(sc->sc_memt, sc->sc_memh, MMCR_RESSTA, ressta); 265 266 /* 267 * Hook up the watchdog timer. 268 */ 269 sc->sc_smw.smw_name = sc->sc_dev.dv_xname; 270 sc->sc_smw.smw_cookie = sc; 271 sc->sc_smw.smw_setmode = elansc_wdog_setmode; 272 sc->sc_smw.smw_tickle = elansc_wdog_tickle; 273 sc->sc_smw.smw_period = 32; /* actually 32.54 */ 274 if (sysmon_wdog_register(&sc->sc_smw) != 0) 275 printf("%s: unable to register watchdog with sysmon\n", 276 sc->sc_dev.dv_xname); 277 278 /* Set up the watchdog registers with some defaults. */ 279 elansc_wdogctl_write(sc, WDTMRCTL_WRST_ENB | WDTMRCTL_EXP_SEL30); 280 281 /* ...and clear it. */ 282 elansc_wdogctl_reset(sc); 283 } 284 285 CFATTACH_DECL(elansc, sizeof(struct elansc_softc), 286 elansc_match, elansc_attach, NULL, NULL); 287