view vordog.c @ 13:e4e6727f530f tip

To work with bootstrap code's configuration on the timer
author Thinker K.F. Li <thinker@branda.to>
date Wed, 09 Jul 2008 13:05:43 +0800
parents 6bfd60bcd7b8
children
line wrap: on
line source

/*-
 * Copyright (c) 2008 Kueifong Li <thinker@branda.to>.
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/ioccom.h>
#include <sys/types.h>
#include <sys/malloc.h>
#include <sys/watchdog.h>
#include "vordog.h"

#if 0
#undef __FreeBSD_version
#define __FreeBSD_version 800029
#endif

#define VD_STATUS 0x841
#define VD_INITVAL 0x84a
#define VD_CTR 0x84b
/* trigger PCIRST */
#define VD_TIMER_RST 0xc

typedef struct vd_softc {
    struct cdev *_cdev;
    device_t dev;
    int open_cnt;
    u_int init_val;
    eventhandler_tag wd9_tag;
} *vd_softc_t;

#define CDEV_2_SOFTC(_cdev) ((_cdev)->si_drv1)

static int
vordog_open(struct cdev *dev, int oflags, int devtype, struct thread *td) {
    vd_softc_t sc;

    sc = CDEV_2_SOFTC(dev);
    sc->open_cnt++;

    return 0;
}

static int
vordog_close(struct cdev *dev, int fflag, int devtype, struct thread *td) {
    vd_softc_t sc;

    sc = CDEV_2_SOFTC(dev);
    sc->open_cnt--;

    return 0;
}

static int
vordog_go_detach(void) {
    devclass_t vd_dc;
    device_t vd_dev, nexus_dev;
    int r;

    vd_dc = devclass_find("vordog");
    if(vd_dc == NULL)
	return EINVAL;

    vd_dev = devclass_get_device(vd_dc, 0);
    if(vd_dev == NULL)
	return EINVAL;

    nexus_dev = device_get_parent(vd_dev);
    if(nexus_dev == NULL)
	return EINVAL;

    r = device_delete_child(nexus_dev, vd_dev);
    return r;
}

static void
setup_timer(vordog_cfg_t cfg) {
    int val;

    val = cfg->init_val & 0xff;
    outb(VD_INITVAL, val);
    val = 0x80 | ((cfg->unit & 0x3) << 4) | VD_TIMER_RST;
    outb(VD_CTR, val);
}

static void
reset_timer(void) {
    outb(VD_STATUS, 0xc0);
}

static void
disable_timer(void) {
    outb(VD_CTR, 0);
    outb(VD_STATUS, 0xc0);
}

static int
vordog_ioctl(struct cdev *dev, u_long cmd, caddr_t data,
	     int fflag, struct thread *td) {
    int r = 0;
    vd_softc_t sc;
    vordog_cfg_t cfg;
    static const u_int unit_factors[] = {0, 1, 60, 360};

    sc = CDEV_2_SOFTC(dev);
    switch(cmd) {
    case VDCTL_ENABLE:
	cfg = (vordog_cfg_t)data;
	sc->init_val = (cfg->init_val & 0xff) * unit_factors[cfg->unit];
	setup_timer(cfg);
	break;
    case VDCTL_RESET:
	reset_timer();
	break;
    case VDCTL_DISABLE:
	disable_timer();
	break;
    default:
	r = EINVAL;
    }
    return r;
}

struct cdevsw vordog_cdevsw = {
    .d_version = D_VERSION,
    .d_open = vordog_open,
    .d_close = vordog_close,
    .d_ioctl = vordog_ioctl,
    .d_name = "vordog",
};

/*
 * Implements watchdog(9) compatible driver.
 */
/*! \brief Initialize timer for an interval in specified seconds.
 */
static int
vordog_wd9_init(u_int timeout) {
    int val;

    if(timeout < 0 || timeout > 255)
	return EINVAL;

    outb(VD_INITVAL, timeout & 0xff);
    val = 0x80 | (VDT_1S << 4) | VD_TIMER_RST;
    outb(VD_CTR, val);
    return 0;
}

/*! \brief Reset timer before it is timeout.
 *
 * It make the timer reload it's value from initial value register.
 */
static void
vordog_wd9_reset(void) {
    outb(VD_STATUS, 0xc0);
}

/*! \brief Stop counting of watchdog timer.
 */
static void
vordog_wd9_disable(void) {
    outb(VD_CTR, 0);
    outb(VD_STATUS, 0xc0);
}

/*! \brief event handler for watchdog(9).
 *
 * Timeout interval of watchdog(9) is defined by power of 2 nanoseconds.
 * Watchdog timer of Vortex86 provides only small range of intervals.
 * We use only the timer in seconds to simplize the code.  It provides
 * interval 1~255 seconds.
 *
 * Since, only SFTMR1_STS is work, in my experience, and it is triggered
 * after 4 times of initial value.  So, it only (i * 4) seconds in range
 * of 4 ~ (255 * 4), where i is one of positive integer, are supported.
 *
 * The supported time intervals of watchdog(9) are 2^32~2^39 nanoseconds.
 * vordog used a (i * 4) seconds for 2^n seconds while i * 4 seconds is
 * closest to 2^n nanoseconds.  The variances between i * 4 seconds and
 * 2^n nanoseconds are less than 7% (6.8678%).
 */
static void
vordog_wd9_evh(void *priv_data, u_int cmd, int *error) {
    vd_softc_t sc;
    u_int timeout, u;
    /* 2^32, 2^33, ..., 2^39 nanoseconds */
    static const int timeouts[] = { 1, 2, 4, 9, 17, 34, 69, 137};

    sc = (vd_softc_t)priv_data;

    u = cmd & WD_INTERVAL;
    if(u < 32 || u > 39) {
	/* Out of range! Can not be configured. */
	vordog_wd9_disable();
	sc->init_val = 0;
	return;
    }

    timeout = timeouts[u - 31];
    if(timeout != sc->init_val) {
	vordog_wd9_init(timeout);
	sc->init_val = timeout;
    } else {
	vordog_wd9_reset();
    }
    *error = 0;
}

static void
vordog_wd9_register(vd_softc_t sc) {
    sc->wd9_tag = \
	EVENTHANDLER_REGISTER(watchdog_list, vordog_wd9_evh, sc, 0);
}

static void
vordog_wd9_unregister(vd_softc_t sc) {
    EVENTHANDLER_DEREGISTER(watchdog_list, sc->wd9_tag);
}


/* Following implements a new bus device.
 * It is attached under nexus bus, an on board device as `vordog0'.
 */

static void vordog_identify(driver_t *driver, device_t parent) {
    device_t vordog;
    
    /* make sure vordog device is not in the bus. */
    vordog = device_find_child(parent, "vordog", 0);
    if(vordog != NULL)
	return;
    vordog = BUS_ADD_CHILD(parent, 10, "vordog", 0);
}

static int vordog_probe(device_t dev) {
    int b;

    disable_timer();		/* To work with bootstrap code
				 * that may configure the timer. */

    outb(VD_INITVAL, 0xaa);
    b = inb(VD_INITVAL);
    if(b != 0xaa)
	return EINVAL;
    b = inb(VD_CTR);
    if(b != 0)
	return EINVAL;
    b = inb(VD_STATUS);
    if(b != 0)
	return EINVAL;
    return 0;
}

static int vordog_attach(device_t dev) {
    vd_softc_t sc;
    struct cdev *_cdev;

    disable_timer();

    sc = (vd_softc_t)malloc(sizeof(struct vd_softc), M_TEMP, M_WAITOK);
    if(sc == NULL)
	return ENOMEM;

    _cdev = make_dev(&vordog_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "vordog0");
    if(_cdev == NULL) {
	free(sc, M_TEMP);
	return EINVAL;
    }

    sc->_cdev = _cdev;
    sc->dev = dev;

    device_set_softc(dev, sc);
    CDEV_2_SOFTC(_cdev) = sc;

    sc->open_cnt = 0;
    sc->init_val = 0;
    sc->wd9_tag = NULL;

    device_set_desc(dev, "Watchdog of Vortex86 SoC");
    device_printf(dev, "<Vortex86 SoC watchdog> at port 0x%x\n", VD_STATUS);

    vordog_wd9_register(sc);

    return 0;
}

static int vordog_detach(device_t dev) {
    vd_softc_t sc;
    struct cdev *_cdev;

    sc = device_get_softc(dev);
    if(sc->open_cnt != 0)
	return EBUSY;

    disable_timer();

    vordog_wd9_unregister(sc);
    
    _cdev = sc->_cdev;

    destroy_dev(_cdev);

    free(sc, M_TEMP);

    return 0;
}

static device_method_t vordog_methods[] = {
    DEVMETHOD(device_identify, vordog_identify),
    DEVMETHOD(device_probe, vordog_probe),
    DEVMETHOD(device_attach, vordog_attach),
    DEVMETHOD(device_detach, vordog_detach),
    {0, 0}
};

static driver_t vordog_driver = {
    "vordog",
    vordog_methods,
    0,
};

static int
vordog_evh(module_t mod, int cmd, void *arg) {
    int r = 0;

    switch(cmd) {
    case MOD_LOAD:
	break;
    case MOD_UNLOAD:
	vordog_go_detach();	/* remove device from parent */
	break;
    case MOD_SHUTDOWN:
	disable_timer();	/* prevent system reset when shuting down */
	break;
    default:
	r = EINVAL;
	break;
    }
    return r;
}

static devclass_t vordog_devclass;

DRIVER_MODULE(vordog, nexus, vordog_driver, vordog_devclass,
	      vordog_evh, NULL);