/*      $OpenBSD: glxpcib.c,v 1.14 2014/12/10 12:27:57 mikeb Exp $	*/

/*
 * Copyright (c) 2007 Marc Balmer <mbalmer@openbsd.org>
 * 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.
 */

/*
 * AMD CS5536 series LPC bridge also containing timer, watchdog, and GPIO.
 */

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

#include <machine/bus.h>
#ifdef __i386__
#include <machine/cpufunc.h>
#endif

#include <dev/gpio/gpiovar.h>
#include <dev/i2c/i2cvar.h>

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

#include <dev/pci/glxreg.h>
#include <dev/pci/glxvar.h>

#include "gpio.h"

#define	AMD5536_REV		GLCP_CHIP_REV_ID
#define	AMD5536_REV_MASK	0xff
#define	AMD5536_TMC		PMC_LTMR

#define	MSR_LBAR_ENABLE		0x100000000ULL

/* Multi-Functional General Purpose Timer */
#define	MSR_LBAR_MFGPT		DIVIL_LBAR_MFGPT
#define	MSR_MFGPT_SIZE		0x40
#define	MSR_MFGPT_ADDR_MASK	0xffc0
#define	AMD5536_MFGPT0_CMP1	0x00000000
#define	AMD5536_MFGPT0_CMP2	0x00000002
#define	AMD5536_MFGPT0_CNT	0x00000004
#define	AMD5536_MFGPT0_SETUP	0x00000006
#define	AMD5536_MFGPT_DIV_MASK	0x000f	/* div = 1 << mask */
#define	AMD5536_MFGPT_CLKSEL	0x0010
#define	AMD5536_MFGPT_REV_EN	0x0020
#define	AMD5536_MFGPT_CMP1DIS	0x0000
#define	AMD5536_MFGPT_CMP1EQ	0x0040
#define	AMD5536_MFGPT_CMP1GE	0x0080
#define	AMD5536_MFGPT_CMP1EV	0x00c0
#define	AMD5536_MFGPT_CMP2DIS	0x0000
#define	AMD5536_MFGPT_CMP2EQ	0x0100
#define	AMD5536_MFGPT_CMP2GE	0x0200
#define	AMD5536_MFGPT_CMP2EV	0x0300
#define	AMD5536_MFGPT_STOP_EN	0x0800
#define	AMD5536_MFGPT_SET	0x1000
#define	AMD5536_MFGPT_CMP1	0x2000
#define	AMD5536_MFGPT_CMP2	0x4000
#define	AMD5536_MFGPT_CNT_EN	0x8000
#define	AMD5536_MFGPT_IRQ	MFGPT_IRQ
#define	AMD5536_MFGPT0_C1_IRQM	0x00000001
#define	AMD5536_MFGPT1_C1_IRQM	0x00000002
#define	AMD5536_MFGPT2_C1_IRQM	0x00000004
#define	AMD5536_MFGPT3_C1_IRQM	0x00000008
#define	AMD5536_MFGPT4_C1_IRQM	0x00000010
#define	AMD5536_MFGPT5_C1_IRQM	0x00000020
#define	AMD5536_MFGPT6_C1_IRQM	0x00000040
#define	AMD5536_MFGPT7_C1_IRQM	0x00000080
#define	AMD5536_MFGPT0_C2_IRQM	0x00000100
#define	AMD5536_MFGPT1_C2_IRQM	0x00000200
#define	AMD5536_MFGPT2_C2_IRQM	0x00000400
#define	AMD5536_MFGPT3_C2_IRQM	0x00000800
#define	AMD5536_MFGPT4_C2_IRQM	0x00001000
#define	AMD5536_MFGPT5_C2_IRQM	0x00002000
#define	AMD5536_MFGPT6_C2_IRQM	0x00004000
#define	AMD5536_MFGPT7_C2_IRQM	0x00008000
#define	AMD5536_MFGPT_NR	MFGPT_NR
#define	AMD5536_MFGPT0_C1_NMIM	0x00000001
#define	AMD5536_MFGPT1_C1_NMIM	0x00000002
#define	AMD5536_MFGPT2_C1_NMIM	0x00000004
#define	AMD5536_MFGPT3_C1_NMIM	0x00000008
#define	AMD5536_MFGPT4_C1_NMIM	0x00000010
#define	AMD5536_MFGPT5_C1_NMIM	0x00000020
#define	AMD5536_MFGPT6_C1_NMIM	0x00000040
#define	AMD5536_MFGPT7_C1_NMIM	0x00000080
#define	AMD5536_MFGPT0_C2_NMIM	0x00000100
#define	AMD5536_MFGPT1_C2_NMIM	0x00000200
#define	AMD5536_MFGPT2_C2_NMIM	0x00000400
#define	AMD5536_MFGPT3_C2_NMIM	0x00000800
#define	AMD5536_MFGPT4_C2_NMIM	0x00001000
#define	AMD5536_MFGPT5_C2_NMIM	0x00002000
#define	AMD5536_MFGPT6_C2_NMIM	0x00004000
#define	AMD5536_MFGPT7_C2_NMIM	0x00008000
#define	AMD5536_NMI_LEG		0x00010000
#define	AMD5536_MFGPT0_C2_RSTEN	0x01000000
#define	AMD5536_MFGPT1_C2_RSTEN	0x02000000
#define	AMD5536_MFGPT2_C2_RSTEN	0x04000000
#define	AMD5536_MFGPT3_C2_RSTEN	0x08000000
#define	AMD5536_MFGPT4_C2_RSTEN	0x10000000
#define	AMD5536_MFGPT5_C2_RSTEN	0x20000000
#define	AMD5536_MFGPT_SETUP	MFGPT_SETUP

/* GPIO */
#define	MSR_LBAR_GPIO		DIVIL_LBAR_GPIO
#define	MSR_GPIO_SIZE		0x100
#define	MSR_GPIO_ADDR_MASK	0xff00
#define	AMD5536_GPIO_NPINS	32
#define	AMD5536_GPIOH_OFFSET	0x80	/* high bank register offset */
#define	AMD5536_GPIO_OUT_VAL	0x00	/* output value */
#define	AMD5536_GPIO_OUT_EN	0x04	/* output enable */
#define	AMD5536_GPIO_OD_EN	0x08	/* open-drain enable */
#define AMD5536_GPIO_OUT_INVRT_EN 0x0c	/* invert output */
#define	AMD5536_GPIO_PU_EN	0x18	/* pull-up enable */
#define	AMD5536_GPIO_PD_EN	0x1c	/* pull-down enable */
#define	AMD5536_GPIO_IN_EN	0x20	/* input enable */
#define AMD5536_GPIO_IN_INVRT_EN 0x24	/* invert input */
#define	AMD5536_GPIO_READ_BACK	0x30	/* read back value */

/* SMB */
#define MSR_LBAR_SMB		DIVIL_LBAR_SMB
#define MSR_SMB_SIZE		0x08
#define MSR_SMB_ADDR_MASK	0xfff8
#define AMD5536_SMB_SDA		0x00 /* serial data */
#define AMD5536_SMB_STS		0x01 /* status */
#define AMD5536_SMB_STS_SLVSTOP	0x80 /* slave stop */
#define AMD5536_SMB_STS_SDAST	0x40 /* smb data status */
#define AMD5536_SMB_STS_BER	0x20 /* bus error */
#define AMD5536_SMB_STS_NEGACK	0x10 /* negative acknowledge */
#define AMD5536_SMB_STS_STASTR	0x08 /* stall after start */
#define AMD5536_SMB_STS_MASTER	0x02 /* master */
#define AMD5536_SMB_STS_XMIT	0x01 /* transmit or receive */
#define AMD5536_SMB_CST		0x02 /* control status */
#define AMD5536_SMB_CST_MATCH	0x04 /* address match */
#define AMD5536_SMB_CST_BB	0x02 /* bus busy */
#define AMD5536_SMB_CST_BUSY	0x01 /* busy */
#define AMD5536_SMB_CTL1	0x03 /* control 1 */
#define AMD5536_SMB_CTL1_STASTRE 0x80 /* stall after start enable */
#define AMD5536_SMB_CTL1_ACK	0x10 /* receive acknowledge */
#define AMD5536_SMB_CTL1_INTEN	0x04 /* interrupt enable  */
#define AMD5536_SMB_CTL1_STOP	0x02 /* stop */
#define AMD5536_SMB_CTL1_START	0x01 /* start */
#define AMD5536_SMB_ADDR	0x04 /* serial address */
#define AMD5536_SMB_ADDR_SAEN	0x80 /* slave enable */
#define AMD5536_SMB_CTL2	0x05 /* control 2 */
#define AMD5536_SMB_CTL2_EN	0x01 /* enable clock */
#define AMD5536_SMB_CTL2_FREQ	0x78 /* 100 kHz */
#define AMD5536_SMB_CTL3	0x06 /* control 3 */

/* PMS */
#define	MSR_LBAR_PMS		DIVIL_LBAR_PMS
#define	MSR_PMS_SIZE		0x80
#define	MSR_PMS_ADDR_MASK	0xff80
#define	AMD5536_PMS_SSC		0x54
#define	AMD5536_PMS_SSC_PI	0x00040000
#define	AMD5536_PMS_SSC_CLR_PI	0x00020000
#define	AMD5536_PMS_SSC_SET_PI	0x00010000

/*
 * MSR registers we want to preserve accross suspend/resume
 */
const uint32_t glxpcib_msrlist[] = {
	GLIU_PAE,
	GLCP_GLD_MSR_PM,
	DIVIL_BALL_OPTS
};

struct glxpcib_softc {
	struct device		sc_dev;

	struct timecounter	sc_timecounter;
	bus_space_tag_t		sc_iot;
	bus_space_handle_t	sc_ioh;

	uint64_t 		sc_msrsave[nitems(glxpcib_msrlist)];

#ifndef SMALL_KERNEL
#if NGPIO > 0
	/* GPIO interface */
	bus_space_tag_t		sc_gpio_iot;
	bus_space_handle_t	sc_gpio_ioh;
	struct gpio_chipset_tag	sc_gpio_gc;
	gpio_pin_t		sc_gpio_pins[AMD5536_GPIO_NPINS];
#endif
	/* I2C interface */
	bus_space_tag_t		sc_smb_iot;
	bus_space_handle_t	sc_smb_ioh;
	struct i2c_controller	sc_smb_ic;
	struct rwlock		sc_smb_lck;

	/* Watchdog */
	int			sc_wdog;
	int			sc_wdog_period;
#endif
};

struct cfdriver glxpcib_cd = {
	NULL, "glxpcib", DV_DULL
};

int	glxpcib_match(struct device *, void *, void *);
void	glxpcib_attach(struct device *, struct device *, void *);
int	glxpcib_activate(struct device *, int);
int	glxpcib_search(struct device *, void *, void *);
int	glxpcib_print(void *, const char *);

struct cfattach glxpcib_ca = {
	sizeof(struct glxpcib_softc), glxpcib_match, glxpcib_attach,
	NULL, glxpcib_activate
};

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

u_int	glxpcib_get_timecount(struct timecounter *tc);

#ifndef SMALL_KERNEL
int     glxpcib_wdogctl_cb(void *, int);
#if NGPIO > 0
void	glxpcib_gpio_pin_ctl(void *, int, int);
int	glxpcib_gpio_pin_read(void *, int);
void	glxpcib_gpio_pin_write(void *, int, int);
#endif
int	glxpcib_smb_acquire_bus(void *, int);
void	glxpcib_smb_release_bus(void *, int);
int	glxpcib_smb_send_start(void *, int);
int	glxpcib_smb_send_stop(void *, int);
void	glxpcib_smb_send_ack(void *, int);
int	glxpcib_smb_initiate_xfer(void *, i2c_addr_t, int);
int	glxpcib_smb_read_byte(void *, uint8_t *, int);
int	glxpcib_smb_write_byte(void *, uint8_t, int);
void	glxpcib_smb_reset(struct glxpcib_softc *);
int	glxpcib_smb_wait(struct glxpcib_softc *, int, int);
#endif

const struct pci_matchid glxpcib_devices[] = {
	{ PCI_VENDOR_AMD,	PCI_PRODUCT_AMD_CS5536_PCIB }
};

int
glxpcib_match(struct device *parent, void *match, void *aux)
{ 
	if (pci_matchbyid((struct pci_attach_args *)aux, glxpcib_devices,
	    nitems(glxpcib_devices))) {
		/* needs to win over pcib */
		return 2;
	}

	return 0;
}

void
glxpcib_attach(struct device *parent, struct device *self, void *aux)
{
	struct glxpcib_softc *sc = (struct glxpcib_softc *)self;
	struct timecounter *tc = &sc->sc_timecounter;
#ifndef SMALL_KERNEL
	struct pci_attach_args *pa = (struct pci_attach_args *)aux;
	u_int64_t wa;
#if NGPIO > 0
	u_int64_t ga;
	struct gpiobus_attach_args gba;
	int i, gpio = 0;
#endif
	u_int64_t sa;
	struct i2cbus_attach_args iba;
	int i2c = 0;
	bus_space_handle_t tmpioh;
#endif
	tc->tc_get_timecount = glxpcib_get_timecount;
	tc->tc_counter_mask = 0xffffffff;
	tc->tc_frequency = 3579545;
	tc->tc_name = "CS5536";
	tc->tc_quality = 1000;
	tc->tc_priv = sc;
	tc_init(tc);

	printf(": rev %d, 32-bit %lluHz timer",
	    (int)rdmsr(AMD5536_REV) & AMD5536_REV_MASK,
	    tc->tc_frequency);

#ifndef SMALL_KERNEL
	/* Attach the watchdog timer */
	sc->sc_iot = pa->pa_iot;
	wa = rdmsr(MSR_LBAR_MFGPT);
	if (wa & MSR_LBAR_ENABLE &&
	    !bus_space_map(sc->sc_iot, wa & MSR_MFGPT_ADDR_MASK,
	    MSR_MFGPT_SIZE, 0, &sc->sc_ioh)) {
		/* count in seconds (as upper level desires) */
		bus_space_write_2(sc->sc_iot, sc->sc_ioh, AMD5536_MFGPT0_SETUP,
		    AMD5536_MFGPT_CNT_EN | AMD5536_MFGPT_CMP2EV |
		    AMD5536_MFGPT_CMP2 | AMD5536_MFGPT_DIV_MASK |
		    AMD5536_MFGPT_STOP_EN);
		wdog_register(glxpcib_wdogctl_cb, sc);
		sc->sc_wdog = 1;
		printf(", watchdog");
	}

#if NGPIO > 0
	/* map GPIO I/O space */
	sc->sc_gpio_iot = pa->pa_iot;
	ga = rdmsr(MSR_LBAR_GPIO);
	if (ga & MSR_LBAR_ENABLE &&
	    !bus_space_map(sc->sc_gpio_iot, ga & MSR_GPIO_ADDR_MASK,
	    MSR_GPIO_SIZE, 0, &sc->sc_gpio_ioh)) {
		printf(", gpio");

		/* initialize pin array */
		for (i = 0; i < AMD5536_GPIO_NPINS; i++) {
			sc->sc_gpio_pins[i].pin_num = i;
			sc->sc_gpio_pins[i].pin_caps = GPIO_PIN_INPUT |
			    GPIO_PIN_OUTPUT | GPIO_PIN_OPENDRAIN |
			    GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN |
			    GPIO_PIN_INVIN | GPIO_PIN_INVOUT;

			/* read initial state */
			sc->sc_gpio_pins[i].pin_state =
			    glxpcib_gpio_pin_read(sc, i);
		}

		/* create controller tag */
		sc->sc_gpio_gc.gp_cookie = sc;
		sc->sc_gpio_gc.gp_pin_read = glxpcib_gpio_pin_read;
		sc->sc_gpio_gc.gp_pin_write = glxpcib_gpio_pin_write;
		sc->sc_gpio_gc.gp_pin_ctl = glxpcib_gpio_pin_ctl;

		gba.gba_name = "gpio";
		gba.gba_gc = &sc->sc_gpio_gc;
		gba.gba_pins = sc->sc_gpio_pins;
		gba.gba_npins = AMD5536_GPIO_NPINS;
		gpio = 1;

	}
#endif /* NGPIO */

	/* Map SMB I/O space */
	sc->sc_smb_iot = pa->pa_iot;
	sa = rdmsr(MSR_LBAR_SMB);
	if (sa & MSR_LBAR_ENABLE &&
	    !bus_space_map(sc->sc_smb_iot, sa & MSR_SMB_ADDR_MASK,
	    MSR_SMB_SIZE, 0, &sc->sc_smb_ioh)) {
		printf(", i2c");

		/* Enable controller */
		bus_space_write_1(sc->sc_smb_iot, sc->sc_smb_ioh,
		    AMD5536_SMB_CTL2, AMD5536_SMB_CTL2_EN |
		    AMD5536_SMB_CTL2_FREQ);

		/* Disable interrupts */
		bus_space_write_1(sc->sc_smb_iot, sc->sc_smb_ioh,
		    AMD5536_SMB_CTL1, 0);

		/* Disable slave address */
		bus_space_write_1(sc->sc_smb_iot, sc->sc_smb_ioh,
		    AMD5536_SMB_ADDR, 0);

		/* Stall the bus after start */
		bus_space_write_1(sc->sc_smb_iot, sc->sc_smb_ioh,
		    AMD5536_SMB_CTL1, AMD5536_SMB_CTL1_STASTRE);

		/* Attach I2C framework */
		sc->sc_smb_ic.ic_cookie = sc;
		sc->sc_smb_ic.ic_acquire_bus = glxpcib_smb_acquire_bus;
		sc->sc_smb_ic.ic_release_bus = glxpcib_smb_release_bus;
		sc->sc_smb_ic.ic_send_start = glxpcib_smb_send_start;
		sc->sc_smb_ic.ic_send_stop = glxpcib_smb_send_stop;
		sc->sc_smb_ic.ic_initiate_xfer = glxpcib_smb_initiate_xfer;
		sc->sc_smb_ic.ic_read_byte = glxpcib_smb_read_byte;
		sc->sc_smb_ic.ic_write_byte = glxpcib_smb_write_byte;

		rw_init(&sc->sc_smb_lck, "iiclk");

		bzero(&iba, sizeof(iba));
		iba.iba_name = "iic";
		iba.iba_tag = &sc->sc_smb_ic;
		i2c = 1;
	}

	/* Map PMS I/O space and enable the ``Power Immediate'' feature */
	sa = rdmsr(MSR_LBAR_PMS);
	if (sa & MSR_LBAR_ENABLE &&
	    !bus_space_map(pa->pa_iot, sa & MSR_PMS_ADDR_MASK,
	    MSR_PMS_SIZE, 0, &tmpioh)) {
		bus_space_write_4(pa->pa_iot, tmpioh, AMD5536_PMS_SSC,
		    AMD5536_PMS_SSC_SET_PI);
		bus_space_barrier(pa->pa_iot, tmpioh, AMD5536_PMS_SSC, 4,
		    BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE);
		bus_space_unmap(pa->pa_iot, tmpioh, MSR_PMS_SIZE);
	}
#endif /* SMALL_KERNEL */
	pcibattach(parent, self, aux);

#ifndef SMALL_KERNEL
#if NGPIO > 0
	if (gpio)
		config_found(&sc->sc_dev, &gba, gpiobus_print);
#endif
	if (i2c)
		config_found(&sc->sc_dev, &iba, iicbus_print);

	config_search(glxpcib_search, self, pa);
#endif
}

int
glxpcib_activate(struct device *self, int act)
{
#ifndef SMALL_KERNEL
	struct glxpcib_softc *sc = (struct glxpcib_softc *)self;
	uint i;
#endif
	int rv = 0;

	switch (act) {
	case DVACT_SUSPEND:
#ifndef SMALL_KERNEL
		if (sc->sc_wdog) {
			sc->sc_wdog_period = bus_space_read_2(sc->sc_iot,
			    sc->sc_ioh, AMD5536_MFGPT0_CMP2);
			glxpcib_wdogctl_cb(sc, 0);
		}
#endif
		rv = config_activate_children(self, act);
#ifndef SMALL_KERNEL
		for (i = 0; i < nitems(glxpcib_msrlist); i++)
			sc->sc_msrsave[i] = rdmsr(glxpcib_msrlist[i]);
#endif

		break;
	case DVACT_RESUME:
#ifndef SMALL_KERNEL
		if (sc->sc_wdog)
			glxpcib_wdogctl_cb(sc, sc->sc_wdog_period);
		for (i = 0; i < nitems(glxpcib_msrlist); i++)
			wrmsr(glxpcib_msrlist[i], sc->sc_msrsave[i]);
#endif
		rv = config_activate_children(self, act);
		break;
	case DVACT_POWERDOWN:
#ifndef SMALL_KERNEL
		if (sc->sc_wdog)
			wdog_shutdown(self);
#endif
		rv = config_activate_children(self, act);
		break;
	default:
		rv = config_activate_children(self, act);
		break;
	}
	return (rv);
}

u_int
glxpcib_get_timecount(struct timecounter *tc)
{
        return rdmsr(AMD5536_TMC);
}

#ifndef SMALL_KERNEL
int
glxpcib_wdogctl_cb(void *v, int period)
{
	struct glxpcib_softc *sc = v;

	if (period > 0xffff)
		period = 0xffff;

	bus_space_write_2(sc->sc_iot, sc->sc_ioh, AMD5536_MFGPT0_SETUP,
	    AMD5536_MFGPT_CNT_EN | AMD5536_MFGPT_CMP2);
	bus_space_write_2(sc->sc_iot, sc->sc_ioh, AMD5536_MFGPT0_CNT, 0);
	bus_space_write_2(sc->sc_iot, sc->sc_ioh, AMD5536_MFGPT0_CMP2, period);

	if (period)
		wrmsr(AMD5536_MFGPT_NR,
		    rdmsr(AMD5536_MFGPT_NR) | AMD5536_MFGPT0_C2_RSTEN);
	else
		wrmsr(AMD5536_MFGPT_NR,
		    rdmsr(AMD5536_MFGPT_NR) & ~AMD5536_MFGPT0_C2_RSTEN);

	return period;
}

#if NGPIO > 0
int
glxpcib_gpio_pin_read(void *arg, int pin)
{
	struct glxpcib_softc *sc = arg;
	u_int32_t data;
	int reg, off = 0;

	reg = AMD5536_GPIO_IN_EN;
	if (pin > 15) {
		pin &= 0x0f;
		off = AMD5536_GPIOH_OFFSET;
	}
	reg += off;
	data = bus_space_read_4(sc->sc_gpio_iot, sc->sc_gpio_ioh, reg);

	if (data & (1 << pin))
		reg = AMD5536_GPIO_READ_BACK + off;
	else
		reg = AMD5536_GPIO_OUT_VAL + off;

	data = bus_space_read_4(sc->sc_gpio_iot, sc->sc_gpio_ioh, reg);

	return data & 1 << pin ? GPIO_PIN_HIGH : GPIO_PIN_LOW;
}

void
glxpcib_gpio_pin_write(void *arg, int pin, int value)
{
	struct glxpcib_softc *sc = arg;
	u_int32_t data;
	int reg;

	reg = AMD5536_GPIO_OUT_VAL;
	if (pin > 15) {
		pin &= 0x0f;
		reg += AMD5536_GPIOH_OFFSET;
	}
	if (value == 1)
		data = 1 << pin;
	else
		data = 1 << (pin + 16);

	bus_space_write_4(sc->sc_gpio_iot, sc->sc_gpio_ioh, reg, data);
}

void
glxpcib_gpio_pin_ctl(void *arg, int pin, int flags)
{
	struct glxpcib_softc *sc = arg;
	int n, reg[7], val[7], nreg = 0, off = 0;

	if (pin > 15) {
		pin &= 0x0f;
		off = AMD5536_GPIOH_OFFSET;
	}

	reg[nreg] = AMD5536_GPIO_IN_EN + off;
	if (flags & GPIO_PIN_INPUT)
		val[nreg++] = 1 << pin;
	else
		val[nreg++] = 1 << (pin + 16);

	reg[nreg] = AMD5536_GPIO_OUT_EN + off;
	if (flags & GPIO_PIN_OUTPUT)
		val[nreg++] = 1 << pin;
	else
		val[nreg++] = 1 << (pin + 16);

	reg[nreg] = AMD5536_GPIO_OD_EN + off;
	if (flags & GPIO_PIN_OPENDRAIN)
		val[nreg++] = 1 << pin;
	else
		val[nreg++] = 1 << (pin + 16);

	reg[nreg] = AMD5536_GPIO_PU_EN + off;
	if (flags & GPIO_PIN_PULLUP)
		val[nreg++] = 1 << pin;
	else
		val[nreg++] = 1 << (pin + 16);

	reg[nreg] = AMD5536_GPIO_PD_EN + off;
	if (flags & GPIO_PIN_PULLDOWN)
		val[nreg++] = 1 << pin;
	else
		val[nreg++] = 1 << (pin + 16);

	reg[nreg] = AMD5536_GPIO_IN_INVRT_EN + off;
	if (flags & GPIO_PIN_INVIN)
		val[nreg++] = 1 << pin;
	else
		val[nreg++] = 1 << (pin + 16);

	reg[nreg] = AMD5536_GPIO_OUT_INVRT_EN + off;
	if (flags & GPIO_PIN_INVOUT)
		val[nreg++] = 1 << pin;
	else
		val[nreg++] = 1 << (pin + 16);

	/* set flags */
	for (n = 0; n < nreg; n++)
		bus_space_write_4(sc->sc_gpio_iot, sc->sc_gpio_ioh, reg[n],
		    val[n]);
}
#endif /* GPIO */

int
glxpcib_smb_acquire_bus(void *arg, int flags)
{
	struct glxpcib_softc *sc = arg;

	if (cold || flags & I2C_F_POLL)
		return (0);

	return (rw_enter(&sc->sc_smb_lck, RW_WRITE | RW_INTR));
}

void
glxpcib_smb_release_bus(void *arg, int flags)
{
	struct glxpcib_softc *sc = arg;

	if (cold || flags & I2C_F_POLL)
		return;

	rw_exit(&sc->sc_smb_lck);
}

int
glxpcib_smb_send_start(void *arg, int flags)
{
	struct glxpcib_softc *sc = arg;
	u_int8_t ctl;

	ctl = bus_space_read_1(sc->sc_smb_iot, sc->sc_smb_ioh,
	    AMD5536_SMB_CTL1);
	bus_space_write_1(sc->sc_smb_iot, sc->sc_smb_ioh, AMD5536_SMB_CTL1,
	    ctl | AMD5536_SMB_CTL1_START);

	return (0);
}

int
glxpcib_smb_send_stop(void *arg, int flags)
{
	struct glxpcib_softc *sc = arg;
	u_int8_t ctl;

	ctl = bus_space_read_1(sc->sc_smb_iot, sc->sc_smb_ioh,
	    AMD5536_SMB_CTL1);
	bus_space_write_1(sc->sc_smb_iot, sc->sc_smb_ioh, AMD5536_SMB_CTL1,
	    ctl | AMD5536_SMB_CTL1_STOP);

	return (0);
}

void
glxpcib_smb_send_ack(void *arg, int flags)
{
	struct glxpcib_softc *sc = arg;
	u_int8_t ctl;

	ctl = bus_space_read_1(sc->sc_smb_iot, sc->sc_smb_ioh,
	    AMD5536_SMB_CTL1);
	bus_space_write_1(sc->sc_smb_iot, sc->sc_smb_ioh, AMD5536_SMB_CTL1,
	    ctl | AMD5536_SMB_CTL1_ACK);
}

int
glxpcib_smb_initiate_xfer(void *arg, i2c_addr_t addr, int flags)
{
	struct glxpcib_softc *sc = arg;
	int error, dir;

	/* Issue start condition */
	glxpcib_smb_send_start(sc, flags);

	/* Wait for bus mastership */
	if ((error = glxpcib_smb_wait(sc, AMD5536_SMB_STS_MASTER |
	    AMD5536_SMB_STS_SDAST, flags)) != 0)
		return (error);

	/* Send address byte */
	dir = (flags & I2C_F_READ ? 1 : 0);
	bus_space_write_1(sc->sc_smb_iot, sc->sc_smb_ioh, AMD5536_SMB_SDA,
	    (addr << 1) | dir);

	return (0);
}

int
glxpcib_smb_read_byte(void *arg, uint8_t *bytep, int flags)
{
	struct glxpcib_softc *sc = arg;
	int error;

	/* Wait for the bus to be ready */
	if ((error = glxpcib_smb_wait(sc, AMD5536_SMB_STS_SDAST, flags)))
		return (error);

	/* Acknowledge the last byte */
	if (flags & I2C_F_LAST)
		glxpcib_smb_send_ack(sc, 0);

	/* Read data byte */
	*bytep = bus_space_read_1(sc->sc_smb_iot, sc->sc_smb_ioh,
	    AMD5536_SMB_SDA);

	return (0);
}

int
glxpcib_print(void *args, const char *parentname)
{
	struct glxpcib_attach_args *gaa = (struct glxpcib_attach_args *)args;

	if (parentname != NULL)
		printf("%s at %s", gaa->gaa_name, parentname);

	return UNCONF;
}

int
glxpcib_search(struct device *parent, void *gcf, void *args)
{
	struct glxpcib_softc *sc = (struct glxpcib_softc *)parent;
	struct cfdata *cf = (struct cfdata *)gcf;
	struct pci_attach_args *pa = (struct pci_attach_args *)args;
	struct glxpcib_attach_args gaa;

	gaa.gaa_name = cf->cf_driver->cd_name;
	gaa.gaa_pa = pa;
	gaa.gaa_iot = sc->sc_iot;
	gaa.gaa_ioh = sc->sc_ioh;

	/*
	 * These devices are attached directly, either from
	 * glxpcib_attach() or later in time from pcib_callback().
	 */
	if (strcmp(cf->cf_driver->cd_name, "gpio") == 0 ||
	    strcmp(cf->cf_driver->cd_name, "iic") == 0 ||
	    strcmp(cf->cf_driver->cd_name, "isa") == 0)
		return 0;

	if (cf->cf_attach->ca_match(parent, cf, &gaa) == 0)
		return 0;

	config_attach(parent, cf, &gaa, glxpcib_print);
	return 1;
}

int
glxpcib_smb_write_byte(void *arg, uint8_t byte, int flags)
{
	struct glxpcib_softc *sc = arg;
	int error;

	/* Wait for the bus to be ready */
	if ((error = glxpcib_smb_wait(sc, AMD5536_SMB_STS_SDAST, flags)))
		return (error);

	/* Send stop after the last byte */
	if (flags & I2C_F_STOP)
		glxpcib_smb_send_stop(sc, 0);

	/* Write data byte */
	bus_space_write_1(sc->sc_smb_iot, sc->sc_smb_ioh, AMD5536_SMB_SDA,
	    byte);

	return (0);
}

void
glxpcib_smb_reset(struct glxpcib_softc *sc)
{
	u_int8_t st;

	/* Clear MASTER, NEGACK and BER */
	st = bus_space_read_1(sc->sc_smb_iot, sc->sc_smb_ioh, AMD5536_SMB_STS);
	bus_space_write_1(sc->sc_smb_iot, sc->sc_smb_ioh, AMD5536_SMB_STS, st |
	    AMD5536_SMB_STS_MASTER | AMD5536_SMB_STS_NEGACK |
	    AMD5536_SMB_STS_BER);

	/* Disable and re-enable controller */
	bus_space_write_1(sc->sc_smb_iot, sc->sc_smb_ioh, AMD5536_SMB_CTL2, 0);
	bus_space_write_1(sc->sc_smb_iot, sc->sc_smb_ioh, AMD5536_SMB_CTL2,
	    AMD5536_SMB_CTL2_EN | AMD5536_SMB_CTL2_FREQ);

	/* Send stop */
	glxpcib_smb_send_stop(sc, 0);
}

int
glxpcib_smb_wait(struct glxpcib_softc *sc, int bits, int flags)
{
	u_int8_t st;
	int i;

	for (i = 0; i < 100; i++) {
		st = bus_space_read_1(sc->sc_smb_iot, sc->sc_smb_ioh,
		    AMD5536_SMB_STS);
		if (st & AMD5536_SMB_STS_BER) {
			printf("%s: bus error, bits=%#x st=%#x\n",
			    sc->sc_dev.dv_xname, bits, st);
			glxpcib_smb_reset(sc);
			return (EIO);
		}
		if ((bits & AMD5536_SMB_STS_MASTER) == 0 &&
		    (st & AMD5536_SMB_STS_NEGACK)) {
			glxpcib_smb_reset(sc);
			return (EIO);
		}
		if (st & AMD5536_SMB_STS_STASTR)
			bus_space_write_1(sc->sc_smb_iot, sc->sc_smb_ioh,
			    AMD5536_SMB_STS, AMD5536_SMB_STS_STASTR);
		if ((st & bits) == bits)
			break;
		delay(2);
	}
	if ((st & bits) != bits) {
		glxpcib_smb_reset(sc);
		return (ETIMEDOUT);
	}
	return (0);
}
#endif /* SMALL_KERNEL */
