/*
 * Copyright (c) Sept 2005 Sentex Communications
 * All rights reserved.
 *
 * Developer: Sentex Communications (Gabor Egressy <gabor@sentex.net> Mike Tancsa <mike@sentex.net>)
 *
 * Based on Daryl Hawkins' ichwd module
 * Copyright (c) 2004 Texas A&M University
 * 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.
 *
 * itxwd.c,v 1.1.1.1 2005/09/01 17:05:31 sentex Exp
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <machine/bus.h>
#include <sys/rman.h>
#include <machine/resource.h>
#include <dev/pci/pcivar.h>
#include <sys/watchdog.h>
#include <sys/types.h>

#include "itxwd.h"

#define DEBUG 1 
#undef DEBUG

static devclass_t itxwd_devclass;

struct itxwd_softc {
    device_t         dev;
    int              armed;
    eventhandler_tag wd_tag;
};

/*
    {VENDORID_WINBOND, DEVICEID_ITX,   "Winbond something watchdog device"},
    {VENDORID_WINBOND, DEVICEID_ITX2,  "Winbond something other watchdog device"},
*/
static struct itxwd_device itxwd_devices[] = {
    {VENDORID_WINBOND, DEVICEID_83697, "Winbond 83697 watchdog device"},
    {0, 0, NULL},
};

static void
itxwd_wdt_ctrl(unsigned timeout)
{
#if DEBUG
    printf("wd_ctrl %d\n", timeout);
#endif
		
    /* enter config mode */
    outb(IO_INDEX_PORT, UNLOCK_DATA);
    outb(IO_INDEX_PORT, UNLOCK_DATA);

    /* select device 8 */
    outb(IO_INDEX_PORT, DEVICE_REGISTER);
    outb(IO_DATA_PORT, 8);

    /* set device active */
    outb(IO_INDEX_PORT, 0x30);
    outb(IO_DATA_PORT, 0x01);

    outb(IO_INDEX_PORT, WDT_CNTRL0);
    outb(IO_DATA_PORT, timeout);

    /* exit config mode */
    outb(IO_INDEX_PORT, LOCK_DATA);
}

static int
itxwd(void *asc, u_int cmd, int *error)
{
    unsigned timeout = cmd & WD_INTERVAL;
    unsigned active = cmd & WD_ACTIVE;
    struct itxwd_softc *sc = asc;


    if (active && timeout) {
        if (!sc->armed) {
            device_printf(sc->dev, "Watchdog armed\n");
            sc->armed = 1;
        }
        if (timeout < 31)
            itxwd_wdt_ctrl(2);
        else if (timeout > 37)
            itxwd_wdt_ctrl(255);
        else
            itxwd_wdt_ctrl(1 << (timeout - 30));
    }
    else {
        if (sc->armed)
            device_printf(sc->dev, "Watchdog disarmed\n");
        sc->armed = 0;
        itxwd_wdt_ctrl(0);
    }

    *error = 0;

    return 0;
}

static int
itxwd_probe(device_t dev)
{
#if DEBUG
    device_printf(dev, "itxwd_probe\n");
#endif

    return 0;
}

static void 
itxwd_identify(driver_t *driver, device_t parent)
{
    struct itxwd_device *id;
    device_t itx = NULL;
    device_t dev;

#if DEBUG
    device_printf(parent, "itxwd_identify\n");
#endif
    for(id = itxwd_devices; id->desc != NULL; ++id) {
        if((itx = pci_find_device(id->vendor, id->device)) != NULL) {
#if DEBUG
            device_printf(itx, "Found!\n");
#endif 
            break;
        }
    }

    if (itx == NULL) {
        device_printf(itx, "Suitable device not found\n");
        return;
    }

    if((dev = device_find_child(parent, driver->name, 0)) == NULL)
        dev = BUS_ADD_CHILD(parent, 0, driver->name, 0);

    if(dev != NULL) 
        device_set_desc_copy(dev, id->desc);

#if DEBUG
    device_printf(itx, "vendor %04x device %04x\n",
            id->vendor, id->device);
#endif 
}

static int
itxwd_attach(device_t dev)
{
    struct itxwd_device *id;
    device_t itx = NULL;
    struct itxwd_softc *sc;


#if DEBUG
    device_printf(dev, "itxwd_attach\n");
#endif

    sc = (struct itxwd_softc *)device_get_softc(dev);
    for(id = itxwd_devices; id->desc != NULL; ++id) {
        if((itx = pci_find_device(id->vendor, id->device)) != NULL) {
            sc->dev = dev;
            sc->armed = 0;
            sc->wd_tag = EVENTHANDLER_REGISTER(watchdog_list, itxwd, sc, 0);
            device_printf(dev, "Watchdog ready...\n");
            break;
        }
    }

    return 0;
}

static int
itxwd_detach(device_t dev)
{
    struct itxwd_softc *sc;


#if DEBUG
    device_printf(dev, "itxwd_detach\n");
#endif

    /* enter config mode, need to do it twice */
    outb(IO_INDEX_PORT, UNLOCK_DATA);
    outb(IO_INDEX_PORT, UNLOCK_DATA);

    /* select device 8 */
    outb(IO_INDEX_PORT, DEVICE_REGISTER);
    outb(IO_DATA_PORT, 8);

    /* write data */
    outb(IO_INDEX_PORT, 0x30); /* reg */
    outb(IO_DATA_PORT, 0x01); /* data */

    /* disable the watchdog counter */
    outb(IO_INDEX_PORT, WDT_CNTRL0);
    outb(IO_DATA_PORT, 0);

    /* exit config mode */
    outb(IO_INDEX_PORT, LOCK_DATA);

    sc = (struct itxwd_softc*)device_get_softc(dev);
    if (sc->armed) {
        device_printf(dev, "Watchdog disarmed\n");
        sc->armed = 0;
    }
    if (sc->wd_tag)
        EVENTHANDLER_DEREGISTER(watchdog_list, sc->wd_tag);

    return 0;
}

static device_method_t itxwd_methods[] = {
    DEVMETHOD(device_identify, itxwd_identify),
    DEVMETHOD(device_probe,    itxwd_probe),
    DEVMETHOD(device_attach,   itxwd_attach),
    DEVMETHOD(device_detach,   itxwd_detach),
    {0, 0}
};

static driver_t itxwd_driver = {
    "itxwd",
    itxwd_methods,
    sizeof(struct itxwd_softc)
};

static int
itxwd_modevent(module_t mode, int type, void *data)
{
    int error = 0;

#if DEBUG
    printf("modevent %d\n", type);
#endif

    switch(type) {
        case MOD_LOAD:
            printf("Loading itxwd module.\n");
            break;
        case MOD_UNLOAD:
            printf("Unloading itxwd module.\n");
            break;
        case MOD_SHUTDOWN:
            printf("Shutting down itxwd module.\n");
            break;
    }
    return error;
}       

DRIVER_MODULE(itxwd,nexus,itxwd_driver,itxwd_devclass,itxwd_modevent,NULL);
