Subject: port-i386/31514: PATCH: Driver for hardware watchdog on AMD Geode SC1100
To: None <port-i386-maintainer@netbsd.org, gnats-admin@netbsd.org,>
From: None <john32979@gmail.com>
List: netbsd-bugs
Date: 10/08/2005 19:47:00
>Number:         31514
>Category:       port-i386
>Synopsis:       PATCH: Driver for hardware watchdog on AMD Geode SC1100
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    port-i386-maintainer
>State:          open
>Class:          change-request
>Submitter-Id:   net
>Arrival-Date:   Sat Oct 08 19:47:00 +0000 2005
>Originator:     John Yau
>Release:        
>Organization:
>Environment:
>Description:
It looks like David Young beat me to it by a couple weeks.  I'm submitting the file/patch anyway just in case anyone finds it useful.

There are two things:

geode_wdog.c (submitted in it's entirety)
files.i386 (patch)

>How-To-Repeat:

>Fix:
Index: files.i386
===================================================================
RCS file: /cvsroot/src/sys/arch/i386/conf/files.i386,v
retrieving revision 1.254
diff -u -r1.254 files.i386
--- files.i386	25 Mar 2004 23:32:10 -0000	1.254
+++ files.i386	8 Oct 2005 19:39:31 -0000
@@ -177,6 +177,11 @@
 attach	elansc at pci
 file	arch/i386/pci/elan520.c		elansc
 
+# AMD Geode Watchdog
+device	geode_wdog: sysmon_wdog
+attach	geode_wdog at pci
+file	arch/i386/pci/geode_wdog.c	geode_wdog
+
 # PCI-EISA bridges
 device	pceb: eisabus, isabus
 attach	pceb at pci

-------------------------------------------------------------------
geode_wdog.c (to be placed in /sys/arch/i386/pci)
-------------------------------------------------------------------
/*-
 * A watch dog implementation for the AMD SC1100 "Geode" processor
 * 
 * Snippets of code were taken from and information was gleaned from 
 * Poul-Henning Kamp's geode.c for FreeBSD.
 * 
 * Copyright (c) 2005 John Yau
 * Copyright (c) 2003-2004 Poul-Henning Kamp
 * 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.
 */

#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/wdog.h>

#include <machine/bus.h>

#include <dev/pci/pcivar.h>
#include <dev/pci/pcidevs.h>
#include <dev/sysmon/sysmonvar.h>

#define GEODE_WDTIMEOUT 0x00
#define GEODE_WDCONFIG 0x02
#define GEODE_WDSTATUS 0x04

#define GEODE_VENDOR_ID  0x100b
#define GEODE_DEVICE_ID  0x0515 

static unsigned	cba;
static unsigned	geode_counter;

struct geode_wdog_softc {
	struct device sc_dev;
	struct sysmon_wdog sc_smw;
	int sc_wdog_armed;
	int sc_wdog_period;
};

static int
geode_wdog_tickle(struct sysmon_wdog *smw)
{
	/* Convert seconds to ticks */
	u_int16_t scaler = 0, reg;
	u_int32_t ticks;

	if (smw->smw_period == 0) {
		reg = inw(cba + GEODE_WDCONFIG) & 0xff00;
		/* Turn on the watch do, set the scaler and set the action to
	 	* reset when the watchdog goes off
	 	*/
		outw(cba + GEODE_WDCONFIG, 0xf0 & reg);
		outw(cba + GEODE_WDTIMEOUT, 0);
		return (0);
	}
	/* The timer of the Geode runs at 32 kHz */
	ticks = smw->smw_period * 32000;
	/* We figure out the appropriate scaler and ticks */
	while (scaler < 13 && ticks > 0xffff) {
		ticks >>= 1;
		scaler++;
	}
	if (scaler < 13 ) {
		reg = inw(cba + GEODE_WDCONFIG) & 0xff00;
		/* Turn on the watch dog, set the scaler and set the action to
		 * reset when the watchdog goes off
		 */
		if (ticks == 0)
		{
			scaler--;
			ticks = 0xffff;
		}
		outw(cba + GEODE_WDCONFIG, scaler | 0xf0 | reg);
		reg = (u_int16_t) ticks;
		outw(cba + GEODE_WDTIMEOUT, reg);
	} else {
		reg = inw(cba + GEODE_WDCONFIG) & 0xff00;
		/* Turn on the watch dog, set the scaler and set the action to
	 	 * reset when the watchdog goes off
	 	 */
		outw(cba + GEODE_WDCONFIG, 0xf0 & reg);
		outw(cba + GEODE_WDTIMEOUT, 0);
	}
	return (0);
}

static int
geode_wdog_setmode(struct sysmon_wdog *smw)
{
	struct geode_wdog_softc *sc = smw->smw_cookie;
	u_int reg;
	if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
		if (sc->sc_wdog_armed) {
			sc->sc_wdog_armed = 0;
			reg = inw(cba + GEODE_WDCONFIG) & 0xff00;
			/* Turn off the watch dog */
			outw(cba + GEODE_WDCONFIG, 0 | reg);
			outw(cba + GEODE_WDTIMEOUT, 0);
		}
	} else {
		if (smw->smw_period == WDOG_PERIOD_DEFAULT) {
			smw->smw_period = 30;
		} else {
			smw->smw_period = sc->sc_wdog_period;
		}
		sc->sc_wdog_armed = 1;
		/* Watchdog is armed by tickling it. */
		geode_wdog_tickle(smw);
	}
	return (0);
}

static int
geode_wdog_match(struct device *parent, struct cfdata *match, void *aux)
{
	struct pci_attach_args *pa = aux;

	if (PCI_VENDOR(pa->pa_id) == GEODE_VENDOR_ID &&
		PCI_PRODUCT(pa->pa_id) == GEODE_DEVICE_ID) {
		return (10);
	}
	return (0);
}

static void
geode_wdog_attach(struct device *parent, struct device *self, void *aux)
{
	struct geode_wdog_softc *sc = (void *) self;
	struct pci_attach_args *pa = aux;
	if (PCI_VENDOR(pa->pa_id) == GEODE_VENDOR_ID &&
		PCI_PRODUCT(pa->pa_id) == GEODE_DEVICE_ID) {
		if (geode_counter == 0) {
			/*
			 * The address of the CBA is written to this register
			 * by the bios, see p161 in data sheet.
			 */
			cba = pci_conf_read(pa->pa_pc, pa->pa_tag, 0x64);
			printf("Geode CBA@ 0x%x\n", cba);
			geode_counter = cba + 0x08;
			outl(cba + 0x0d, 2);
			printf("Geode rev: %02x %02x\n",
				inb(cba + 0x3c), inb(cba + 0x3d));
			
			sc->sc_smw.smw_name = sc->sc_dev.dv_xname;
			sc->sc_smw.smw_cookie = sc;
			sc->sc_smw.smw_setmode = geode_wdog_setmode;
			sc->sc_smw.smw_tickle = geode_wdog_tickle;
			sc->sc_smw.smw_period = sc->sc_wdog_period;
		}
	}
	printf("Geode %s: Watchdog timer\n", sc->sc_dev.dv_xname);
	printf("Geode %s: %d second period\n", sc->sc_dev.dv_xname,
		sc->sc_wdog_period);

	if (sysmon_wdog_register(&sc->sc_smw) != 0) {
		printf("Geode %s: unable to register with sysmon\n",
		    sc->sc_dev.dv_xname);
	}
}

CFATTACH_DECL(geode_wdog, sizeof(struct geode_wdog_softc),
    geode_wdog_match, geode_wdog_attach, NULL, NULL);