/*      $OpenBSD: amdpcib.c,v 1.2 2010/04/20 22:05:43 tedu Exp $	*/

/*
 * Copyright (c) 2007 Michael Shalayeff
 * All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/*
 * AMD8111 series LPC bridge also containing HPET and watchdog
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/timetc.h>

#include <machine/bus.h>

#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pcidevs.h>

#define	AMD8111_HPET	0xa0	/* PCI config space */
#define	AMD8111_HPET_ENA	0x00000001
#define	AMD8111_HPET_BASE	0xfffffc00
#define	AMD8111_HPET_SIZE	0x00000400

#define	AMD8111_HPET_ID		0x000
#define	AMD8111_HPET_WIDTH	0x00002000
#define	AMD8111_HPET_REV	0x000000ff
#define	AMD8111_HPET_PERIOD	0x004
#define	AMD8111_HPET_CFG	0x010
#define	AMD8111_HPET_CFG_GIEN	0x00000001
#define	AMD8111_HPET_ISTAT	0x020
#define	AMD8111_HPET_MAIN	0x0f0
#define	AMD8111_HPET_T0CFG	0x100
#define	AMD8111_HPET_T0CMP	0x108
#define	AMD8111_HPET_T1CFG	0x120
#define	AMD8111_HPET_T1CMP	0x128
#define	AMD8111_HPET_T2CFG	0x140
#define	AMD8111_HPET_T2CMP	0x148

#define	AMD8111_WDOG	0xa8	/* PCI config space */
#define	AMD8111_WDOG_ENA	0x00000001
#define	AMD8111_WDOG_HALT	0x00000002
#define	AMD8111_WDOG_SILENT	0x00000004
#define	AMD8111_WDOG_BASE	0xffffffe0

#define	AMD8111_WDOG_CTRL	0x00
#define	AMD8111_WDOG_RSTOP	0x0001
#define	AMD8111_WDOG_WFIR	0x0002
#define	AMD8111_WDOG_WACT	0x0004
#define	AMD8111_WDOG_WDALIAS	0x0008
#define	AMD8111_WDOG_WTRIG	0x0080
#define	AMD8111_WDOG_COUNT	0x08
#define	AMD8111_WDOG_MASK	0xffff

struct amdpcib_softc {
	struct device sc_dev;

	bus_space_tag_t sc_hpet_iot;
	bus_space_handle_t sc_hpet_ioh;
	struct timecounter sc_hpet_timecounter;
};

struct cfdriver amdpcib_cd = {
	NULL, "amdpcib", DV_DULL
};

int	amdpcib_match(struct device *, void *, void *);
void	amdpcib_attach(struct device *, struct device *, void *);

struct cfattach amdpcib_ca = {
	sizeof(struct amdpcib_softc), amdpcib_match, amdpcib_attach
};

/* from arch/<*>/pci/pcib.c */
void	pcibattach(struct device *parent, struct device *self, void *aux);

u_int	amdpcib_get_timecount(struct timecounter *tc);

const struct pci_matchid amdpcib_devices[] = {
	{ PCI_VENDOR_AMD,	PCI_PRODUCT_AMD_PBC8111_LPC }
	/* also available on 590 and 690 chipsets */
};

int
amdpcib_match(struct device *parent, void *match, void *aux)
{ 
	if (pci_matchbyid((struct pci_attach_args *)aux, amdpcib_devices,
	    sizeof(amdpcib_devices) / sizeof(amdpcib_devices[0])))
		return 2;

	return 0;
}

void
amdpcib_attach(struct device *parent, struct device *self, void *aux)
{
        struct amdpcib_softc *sc = (struct amdpcib_softc *)self;
	struct pci_attach_args *pa = aux;
	struct timecounter *tc = &sc->sc_hpet_timecounter;
	pcireg_t reg;
	u_int32_t v;


	sc->sc_hpet_iot = pa->pa_memt;
	reg = pci_conf_read(pa->pa_pc, pa->pa_tag, AMD8111_HPET);
	if (reg & AMD8111_HPET_ENA &&
	    bus_space_map(sc->sc_hpet_iot, reg & AMD8111_HPET_BASE,
	     AMD8111_HPET_SIZE, 0, &sc->sc_hpet_ioh) == 0) {

		tc->tc_get_timecount = amdpcib_get_timecount;
		/* XXX 64-bit counter is not supported! */
		tc->tc_counter_mask = 0xffffffff;

		v = bus_space_read_4(sc->sc_hpet_iot, sc->sc_hpet_ioh,
		    AMD8111_HPET_PERIOD);
		/* femtosecs -> Hz */
		tc->tc_frequency = 1000000000000000ULL / v;

		tc->tc_name = "AMD8111";
		tc->tc_quality = 2000;
		tc->tc_priv = sc;
		tc_init(tc);

		/* enable counting */
		bus_space_write_4(sc->sc_hpet_iot, sc->sc_hpet_ioh,
		    AMD8111_HPET_CFG, AMD8111_HPET_CFG_GIEN);

		v = bus_space_read_4(sc->sc_hpet_iot, sc->sc_hpet_ioh,
		    AMD8111_HPET_ID);
		printf(": %d-bit %lluHz timer rev %d",
		    (v & AMD8111_HPET_WIDTH? 64 : 32), tc->tc_frequency,
		    v & AMD8111_HPET_REV);
	}

	pcibattach(parent, self, aux);
}

u_int
amdpcib_get_timecount(struct timecounter *tc)
{
        struct amdpcib_softc *sc = tc->tc_priv;

	/* XXX 64-bit counter is not supported! */
	return bus_space_read_4(sc->sc_hpet_iot, sc->sc_hpet_ioh,
	    AMD8111_HPET_MAIN);
}
