diff --git a/sys/dev/w83977/w83977.c b/sys/dev/w83977/w83977.c new file mode 100644 index 0000000..64155e4 --- /dev/null +++ b/sys/dev/w83977/w83977.c @@ -0,0 +1,611 @@ +/* + * Copyright (c) 2009 Andriy Gapon + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS 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 OR CONTRIBUTORS 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. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include /* cdevsw stuff */ +#include /* SYSINIT stuff */ +#include /* malloc region definitions */ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include "isa_if.h" + +#ifdef __cplusplus +} +#endif + +/* + * One of these per allocated device. + */ +struct w83977_softc { + struct mtx mtx; + int rid_ioport; + struct resource* res_ioport; /* Resource for port range. */ + device_t device; + struct cdev *led_dev; + eventhandler_tag wd_ev; +}; + +#define W83977_INB(reg) bus_read_1(scp->res_ioport, reg) +#define W83977_OUTB(reg, val) bus_write_1(scp->res_ioport, reg, val) + +static inline uint8_t +getreg(struct w83977_softc *scp, uint8_t reg) +{ + W83977_OUTB(0, reg); + return W83977_INB(1); +} + +static inline void +setreg(struct w83977_softc *scp, uint8_t reg, uint8_t val) +{ + W83977_OUTB(0, reg); + W83977_OUTB(1, val); +} + +/* + * The softc is automatically allocated by the parent bus using the + * size specified in the driver_t declaration below. + */ +#define DEV2SOFTC(dev) ((struct w83977_softc *) (dev)->si_drv1) +#define DEVICE2SOFTC(dev) ((struct w83977_softc *) device_get_softc(dev)) + +/* + * Device specific misc defines. + */ +#define NUMPORTS 2 +#define ENTER_VAL 0x87 +#define EXIT_VAL 0xaa +#define DEVID_REG 0x20 +#define DEVREV_REG 0x21 +#define DEVSEL_REG 0x07 +#define DEVEN_REG 0x30 + +#define W83977_DEVID 0x52 +#define W83977_REV_MASK 0xf0 +#define W83977_REVID 0xf0 + +/* XXX These shall be tunables. */ +static int led_pin = 103; +static int wd_pin = 57; +static int wd_test_mode = 0; /* XXX */ + +/* Function prototypes (these should all be static). */ +static int w83977_deallocate_resources(device_t device); +static int w83977_allocate_resources(device_t device); +static int w83977_attach(device_t device, struct w83977_softc *scp); +static int w83977_detach(device_t device, struct w83977_softc *scp); + +static void wd_func(void *priv, u_int cmd, int *error); +static void led_func(void *priv, int onoff); +static void devmode(struct w83977_softc *scp, int on); +static void devsel(struct w83977_softc *scp, uint8_t devnum); +static void exit_extmode(struct w83977_softc *scp); +static void enter_extmode(struct w83977_softc *scp); +static void exit_extmode_unl(struct w83977_softc *scp); +static void enter_extmode_unl(struct w83977_softc *scp); + + +static devclass_t w83977_devclass; + +/* + ****************************************** + * ISA Attachment structures and functions. + ****************************************** + */ +static void w83977_isa_identify (driver_t *, device_t); +static int w83977_isa_probe (device_t); +static int w83977_isa_attach (device_t); +static int w83977_isa_detach (device_t); + +static struct isa_pnp_id w83977_ids[] = { + {0, NULL} +}; + +static device_method_t w83977_methods[] = { + DEVMETHOD(device_identify, w83977_isa_identify), + DEVMETHOD(device_probe, w83977_isa_probe), + DEVMETHOD(device_attach, w83977_isa_attach), + DEVMETHOD(device_detach, w83977_isa_detach), + { 0, 0 } +}; + +static driver_t w83977_isa_driver = { + "w83977", + w83977_methods, + sizeof (struct w83977_softc) +}; + +DRIVER_MODULE(w83977, isa, w83977_isa_driver, w83977_devclass, 0, 0); + +/* + * Here list some port addresses we might expect our widget to appear at: + * This list should only be used for cards that have some non-destructive + * (to other cards) way of probing these address. Otherwise the driver + * should not go looking for instances of itself, but instead rely on + * the hints file. Strange failures for people with other cards might + * result. + */ +static struct localhints { + int ioport; + int irq; + int drq; + int mem; +} res[] = { + { 0x3f0, 0, 0, 0 }, + { 0, 0, 0, 0 } +}; + +#define MAXHINTS 10 /* Just an arbitrary safety limit. */ +static void +w83977_isa_identify (driver_t *driver, device_t parent) +{ + u_int32_t ioport; + device_t child; + int i; + + /* + * If we've already got W83977 attached somehow, don't try again. + * Maybe it was in the hints file. or it was loaded before. + */ + if (device_find_child(parent, "w83977", 0)) { + if (bootverbose) + printf("W83977: already attached\n"); + return; + } + + for (i = 0; i < MAXHINTS; i++) { + ioport = res[i].ioport; + if (ioport == 0) + return; /* We've added all our local hints. */ + + child = BUS_ADD_CHILD(parent, ISA_ORDER_SPECULATIVE, "w83977", -1); + bus_set_resource(child, SYS_RES_IOPORT, 0, ioport, NUMPORTS); + } + + return; +} + +static int +w83977_isa_probe (device_t device) +{ + int error; + device_t parent = device_get_parent(device); + struct w83977_softc *scp = DEVICE2SOFTC(device); + u_long port_start; + u_long port_count; + uint8_t devid; + uint8_t revid; + uint8_t val; + + scp->device = device; + + /* + * Check this device for a PNP match in our table. + * There are several possible outcomes. + * error == 0 We match a PNP. + * error == ENXIO, It is a PNP device but not in our table. + * error == ENOENT, It is not a PNP device.. try heuristic probes. + * -- logic from if_ed_isa.c, added info from isa/isa_if.m: + * + * If we had a list of devices that we could handle really well, + * and a list which we could handle only basic functions, then + * we would call this twice, once for each list, + * and return a value of '-2' or something if we could + * only handle basic functions. This would allow a specific + * Widgetplus driver to make a better offer if it knows how to + * do all the extended functions. (See non-pnp part for more info). + */ + error = ISA_PNP_PROBE(parent, device, w83977_ids); + switch (error) { + case 0: + /* + * We found a PNP device. + * Do nothing, as it's all done in attach(). + */ + break; + case ENOENT: + /* + * Find out the values of any resources we + * need for our dumb probe. Also check we have enough ports + * in the request. (could be hints based). + * Should probably do the same for memory regions too. + */ + error = bus_get_resource(device, SYS_RES_IOPORT, 0, &port_start, &port_count); + if (port_count != NUMPORTS) { + bus_set_resource(device, SYS_RES_IOPORT, 0, port_start, NUMPORTS); + } + + /* + * Make a temporary resource reservation. + * If we can't get the resources we need then + * we need to abort. Possibly this indicates + * the resources were used by another device + * in which case the probe would have failed anyhow. + */ + if ((error = (w83977_allocate_resources(device)))) { + error = ENXIO; + goto errexit; + } + + enter_extmode_unl(scp); + devid = getreg(scp, DEVID_REG); + revid = getreg(scp, DEVREV_REG); + exit_extmode_unl(scp); + + if (devid != W83977_DEVID || (revid & W83977_REV_MASK) != W83977_REVID) { + error = ENXIO; + } + else { + /* Verify if actual HW configurations matches + * the driver's configurations. + */ + if (led_pin == 103) { + /* Check if GP14 is configured as output. */ + enter_extmode_unl(scp); + devsel(scp, 0x7); + val = getreg(scp, 0xe4); + exit_extmode_unl(scp); + if (val & 0x1) { + printf("GP14 is not configured as output\n"); + error = ENXIO; + } + else if (val & 0x18) { + printf("GP14 is configured for alternate function\n"); + error = ENXIO; + } + else { + device_set_desc(device, "Winbond W83977EF/EG Super I/O"); + error = 0; + } + } + else { + /* XXX TODO Handler other configurations/wirings. */ + error = ENXIO; + } + } + /* + * Unreserve the resources for now because + * another driver may bid for device too. + * If we lose the bid, but still hold the resources, we will + * effectively have disabled the other driver from getting them + * which will result in neither driver getting the device. + * We will ask for them again in attach if we win. + */ + w83977_deallocate_resources(device); + break; + case ENXIO: + /* It was PNP but not ours, leave immediately. */ + /* FALLTHROUGH */ + default: + error = ENXIO; + } +errexit: + return (error); +} + +/* + * Called if the probe succeeded and our bid won the device. + * We can be destructive here as we know we have the device. + * This is the first place we can be sure we have a softc structure. + * You would do ISA specific attach things here, but generically there aren't + * any (yay new-bus!). + */ +static int +w83977_isa_attach (device_t device) +{ + int error; + struct w83977_softc *scp = DEVICE2SOFTC(device); + + error = w83977_attach(device, scp); + if (error) + w83977_isa_detach(device); + return (error); +} + +/* + * Detach the driver (e.g. module unload), + * call the bus independent version + * and undo anything we did in the ISA attach routine. + */ +static int +w83977_isa_detach (device_t device) +{ + int error; + struct w83977_softc *scp = DEVICE2SOFTC(device); + + error = w83977_detach(device, scp); + return (error); +} + +/* + **************************************** + * Common Attachment sub-functions + **************************************** + */ +static int +w83977_attach(device_t device, struct w83977_softc * scp) +{ + uint8_t val; + + mtx_init(&scp->mtx, device_get_nameunit(device), "w83977", MTX_DEF); + + if (w83977_allocate_resources(device)) + goto errexit; + + /* Setup HW registers. */ + enter_extmode(scp); + devsel(scp, 0x8); + setreg(scp, 0xf2, 0x00); /* disable timer */ + setreg(scp, 0xf3, 0x08); /* flash PLEDO on timeout */ + setreg(scp, 0xf4, 0x40); /* set seconds mode for timer */ + devmode(scp, 1); /* enable device */ + + DELAY(1); + + devsel(scp, 0x7); + /* For pin 57 set GP12 to WDTO function with inverted polarity. */ + if (wd_pin == 57) + setreg(scp, 0xe2, 0x0A); + /* XXX Handle other configurations. */ + + DELAY(1); + + /* Drive pin 57 with GP12(WDTO), if not in test mode. */ + if (wd_pin == 57 && !wd_test_mode) { + val = getreg(scp, 0x2a); + setreg(scp, 0x2a, val | 0x80); + } + /* XXX Handle other configurations. */ + + /* In test mode drive pin 103 with PLEDO signal, otherwise + * drive it with GP14. + */ + if (led_pin == 103) { + if (wd_test_mode && bootverbose) + device_printf(device, "power led may go off now, this is normal for test mode\n"); + + val = getreg(scp, 0x2c); + val &= ~0x03; + if (wd_test_mode) + val |= 0x02; + else + val |= 0x01; + setreg(scp, 0x2c, val); + } + /* XXX Handle other configurations. */ + + exit_extmode(scp); + + + scp->wd_ev = EVENTHANDLER_REGISTER(watchdog_list, wd_func, scp, 0); + if (!wd_test_mode) + scp->led_dev = led_create_state(led_func, scp, "w83977", 1); + + return 0; + +errexit: + /* + * Undo anything we may have done. + */ + w83977_detach(device, scp); + return (ENXIO); +} + +static int +w83977_detach(device_t device, struct w83977_softc *scp) +{ + int dummy; + + if (scp->led_dev != NULL) + led_destroy(scp->led_dev); + + /* Disable watchdog timer. */ + wd_func(scp, 0, &dummy); + if (scp->wd_ev != NULL) + EVENTHANDLER_DEREGISTER(watchdog_list, scp->wd_ev); + + w83977_deallocate_resources(device); + mtx_destroy(&scp->mtx); + + return (0); +} + +static int +w83977_allocate_resources(device_t device) +{ + struct w83977_softc *scp = DEVICE2SOFTC(device); + + scp->res_ioport = bus_alloc_resource(device, SYS_RES_IOPORT, &scp->rid_ioport, 0ul, ~0ul, NUMPORTS, RF_ACTIVE); + if (scp->res_ioport == NULL) + goto errexit; + + return (0); + +errexit: + w83977_deallocate_resources(device); + return (ENXIO); +} + +static int +w83977_deallocate_resources(device_t device) +{ + struct w83977_softc *scp = DEVICE2SOFTC(device); + + if (scp->res_ioport != 0) { + bus_deactivate_resource(device, SYS_RES_IOPORT, scp->rid_ioport, scp->res_ioport); + bus_release_resource(device, SYS_RES_IOPORT, scp->rid_ioport, scp->res_ioport); + scp->res_ioport = 0; + } + return (0); +} + + +/* + **************************************** + * HW access routines + **************************************** + */ +static void +enter_extmode_unl(struct w83977_softc *scp) +{ + /* Yes, twice. */ + W83977_OUTB(0, ENTER_VAL); + W83977_OUTB(0, ENTER_VAL); +} + +static void +enter_extmode(struct w83977_softc *scp) +{ + mtx_lock(&scp->mtx); + enter_extmode_unl(scp); +} + +static void +exit_extmode_unl(struct w83977_softc *scp) +{ + W83977_OUTB(0, EXIT_VAL); +} + + +static void +exit_extmode(struct w83977_softc *scp) +{ + exit_extmode_unl(scp); + mtx_unlock(&scp->mtx); +} + +static void +devsel(struct w83977_softc *scp, uint8_t devnum) +{ + setreg(scp, DEVSEL_REG, devnum); +} + +static void +devmode(struct w83977_softc *scp, int on) +{ + setreg(scp, DEVEN_REG, on ? 1 : 0); +} + +/* + **************************************** + * Userland interface routines + **************************************** + */ +static void +led_func(void *priv, int onoff) +{ + struct w83977_softc *scp = (struct w83977_softc *)priv; + uint8_t val; + + if (led_pin == 103) { + /* If the pin is conifgured to be PLEDO + * for watchdog testing, the we can not control + * the LED, because PLEDO is beyond our reach. + */ + if (wd_test_mode) + return; + + /* XXX It would be more efficient to control + * GP14 output value (via GP1 IO port). + * For now reconfigure GP14 output polarity. + */ + enter_extmode(scp); + devsel(scp, 0x7); + val = getreg(scp, 0xe4); + if (!!(val & 0x2) != onoff) + setreg(scp, 0xe4, onoff ? (val | 0x2) : (val & ~0x2)); + exit_extmode(scp); + } + + /* XXX TODO Handle other wiring configurations. */ + return; +} + +static void +wd_func(void *priv, u_int cmd, int *error) +{ + struct w83977_softc *scp = (struct w83977_softc *)priv; + uint64_t timeout; + uint8_t val; + + cmd &= WD_INTERVAL; + + if (wd_pin == 57) { + if (cmd < WD_TO_1SEC) { + val = 0; + } + else { + /* timeout in seconds */ + timeout = (uint64_t)1 << (cmd - WD_TO_1SEC); + + /* TODO If timeout is greater than maximum value + * that can be specified in seconds, we should + * switch WD timer to minutes mode. + * XXX Silently capping timeout value is very bad. + */ + val = timeout > 255 ? 255 : timeout; + *error = 0; + } + + enter_extmode(scp); + devsel(scp, 0x8); + val = getreg(scp, 0xf4); + val &= ~0x01; /* clear status bit just in case */ + val &= ~0x04; /* clear force bit just in case */ + setreg(scp, 0xf4, val); + setreg(scp, 0xf2, val); + exit_extmode(scp); + + return; + } + + /* XXX TODO Handle other wiring configurations. */ + *error = EOPNOTSUPP; + return; +} + diff --git a/sys/modules/w83977/Makefile b/sys/modules/w83977/Makefile new file mode 100644 index 0000000..76f2ee2 --- /dev/null +++ b/sys/modules/w83977/Makefile @@ -0,0 +1,10 @@ +# W83977 Loadable Kernel Module +# +# $FreeBSD: $ + +.PATH: ${.CURDIR}/../../dev/w83977 +KMOD = w83977 +SRCS = w83977.c +SRCS += device_if.h bus_if.h isa_if.h + +.include