/*-
 * Copyright (c) 2013 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Matt Thomas of 3am Software Foundry.
 *
 * 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 "locators.h"
#include "gpio.h"

#include "opt_arm_debug.h"
#include "opt_allwinner.h"

#include <sys/cdefs.h>

__KERNEL_RCSID(1, "$NetBSD: awin_gpio.c,v 1.22 2016/05/26 07:45:51 bouyer Exp $");

#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/intr.h>
#include <sys/systm.h>
#include <sys/kmem.h>

#include <sys/gpio.h>

#include <dev/gpio/gpiovar.h>

#include <arm/allwinner/awin_reg.h>
#include <arm/allwinner/awin_var.h>

static int awin_gpio_match(device_t, cfdata_t, void *);
static void awin_gpio_attach(device_t, device_t, void *);

static int awin_gpio_pin_read(void *, int);
static void awin_gpio_pin_write(void *, int, int);
static void awin_gpio_pin_ctl(void *, int, int);
static void awin_gpio_pin_irqen(void *, int, bool);

struct awin_gpio_pin_cfg {
	uint32_t cfg[4];
	uint32_t drv[2];
	uint32_t pul[2];
};

static struct awin_gpio_pin_group {
	bus_addr_t grp_offset;
	uint32_t grp_pin_mask;
	uint32_t grp_pin_intr_mask;
	uint32_t grp_pin_inuse_mask;
	bus_space_handle_t grp_bsh;
	struct awin_gpio_pin_cfg grp_cfg;
	struct gpio_chipset_tag grp_gc_tag;
	const int grp_index;
	const char grp_nc_name[6];
	device_t grp_gpio_dev;
} pin_groups[] = {
	[0] = {
		.grp_offset = AWIN_PIO_OFFSET + 0 * AWIN_PIO_GRP_SIZE,
		.grp_pin_mask = __BIT(AWIN_PIO_PA_PINS) - 1,
		.grp_pin_intr_mask = 0,
		.grp_gc_tag = {
			.gp_cookie = &pin_groups[0],
			.gp_pin_read = awin_gpio_pin_read,
			.gp_pin_write = awin_gpio_pin_write,
			.gp_pin_ctl = awin_gpio_pin_ctl,
		},
		.grp_index = 0,
		.grp_nc_name = "nc-pa",
	},
	[1] = {
		.grp_offset = AWIN_PIO_OFFSET + 1 * AWIN_PIO_GRP_SIZE,
		.grp_pin_mask = __BIT(AWIN_PIO_PB_PINS) - 1,
		.grp_pin_intr_mask = 0,
		.grp_gc_tag = {
			.gp_cookie = &pin_groups[1],
			.gp_pin_read = awin_gpio_pin_read,
			.gp_pin_write = awin_gpio_pin_write,
			.gp_pin_ctl = awin_gpio_pin_ctl,
		},
		.grp_index = 1,
		.grp_nc_name = "nc-pb",
	},
	[2] = {
		.grp_offset = AWIN_PIO_OFFSET + 2 * AWIN_PIO_GRP_SIZE,
		.grp_pin_mask = __BIT(AWIN_PIO_PC_PINS) - 1,
		.grp_pin_intr_mask = 0,
		.grp_gc_tag = {
			.gp_cookie = &pin_groups[2],
			.gp_pin_read = awin_gpio_pin_read,
			.gp_pin_write = awin_gpio_pin_write,
			.gp_pin_ctl = awin_gpio_pin_ctl,
		},
		.grp_index = 2,
		.grp_nc_name = "nc-pc",
	},
	[3] = {
		.grp_offset = AWIN_PIO_OFFSET + 3 * AWIN_PIO_GRP_SIZE,
		.grp_pin_mask = __BIT(AWIN_PIO_PD_PINS) - 1,
		.grp_pin_intr_mask = 0,
		.grp_gc_tag = {
			.gp_cookie = &pin_groups[3],
			.gp_pin_read = awin_gpio_pin_read,
			.gp_pin_write = awin_gpio_pin_write,
			.gp_pin_ctl = awin_gpio_pin_ctl,
		},
		.grp_index = 3,
		.grp_nc_name = "nc-pd",
	},
	[4] = {
		.grp_offset = AWIN_PIO_OFFSET + 4 * AWIN_PIO_GRP_SIZE,
		.grp_pin_mask = __BIT(AWIN_PIO_PE_PINS) - 1,
		.grp_pin_intr_mask = 0,
		.grp_gc_tag = {
			.gp_cookie = &pin_groups[4],
			.gp_pin_read = awin_gpio_pin_read,
			.gp_pin_write = awin_gpio_pin_write,
			.gp_pin_ctl = awin_gpio_pin_ctl,
		},
		.grp_index = 4,
		.grp_nc_name = "nc-pe",
	},
	[5] = {
		.grp_offset = AWIN_PIO_OFFSET + 5 * AWIN_PIO_GRP_SIZE,
		.grp_pin_mask = __BIT(AWIN_PIO_PF_PINS) - 1,
		.grp_pin_intr_mask = 0,
		.grp_gc_tag = {
			.gp_cookie = &pin_groups[5],
			.gp_pin_read = awin_gpio_pin_read,
			.gp_pin_write = awin_gpio_pin_write,
			.gp_pin_ctl = awin_gpio_pin_ctl,
		},
		.grp_index = 5,
		.grp_nc_name = "nc-pf",
	},
	[6] = {
		.grp_offset = AWIN_PIO_OFFSET + 6 * AWIN_PIO_GRP_SIZE,
		.grp_pin_mask = __BIT(AWIN_PIO_PG_PINS) - 1,
		.grp_pin_intr_mask = 0,
		.grp_gc_tag = {
			.gp_cookie = &pin_groups[6],
			.gp_pin_read = awin_gpio_pin_read,
			.gp_pin_write = awin_gpio_pin_write,
			.gp_pin_ctl = awin_gpio_pin_ctl,
		},
		.grp_index = 6,
		.grp_nc_name = "nc-pg",
	},
	[7] = {
		.grp_offset = AWIN_PIO_OFFSET + 7 * AWIN_PIO_GRP_SIZE,
		.grp_pin_mask = __BIT(AWIN_PIO_PH_PINS) - 1,
		.grp_pin_intr_mask = AWIN_PIO_PH_EINT_PINS,
		.grp_gc_tag = {
			.gp_cookie = &pin_groups[7],
			.gp_pin_read = awin_gpio_pin_read,
			.gp_pin_write = awin_gpio_pin_write,
			.gp_pin_ctl = awin_gpio_pin_ctl,
			.gp_pin_irqen = awin_gpio_pin_irqen,
		},
		.grp_index = 7,
		.grp_nc_name = "nc-ph",
	},
	[8] = {
		.grp_offset = AWIN_PIO_OFFSET + 8 * AWIN_PIO_GRP_SIZE,
		.grp_pin_mask = __BIT(AWIN_PIO_PI_PINS) - 1,
		.grp_pin_intr_mask = 0,
		.grp_gc_tag = {
			.gp_cookie = &pin_groups[8],
			.gp_pin_read = awin_gpio_pin_read,
			.gp_pin_write = awin_gpio_pin_write,
			.gp_pin_ctl = awin_gpio_pin_ctl,
		},
		.grp_index = 8,
		.grp_nc_name = "nc-pi",
	},
	[9] = {
		.grp_offset = 0,
		.grp_gc_tag = {
			.gp_cookie = &pin_groups[9],
			.gp_pin_read = awin_gpio_pin_read,
			.gp_pin_write = awin_gpio_pin_write,
			.gp_pin_ctl = awin_gpio_pin_ctl,
		},
		.grp_pin_mask = 0,
		.grp_pin_intr_mask = 0,
		.grp_index = 9,
		.grp_nc_name = "nc-pj",
	},
	[10] = {
		.grp_offset = 0,
		.grp_gc_tag = {
			.gp_cookie = &pin_groups[10],
			.gp_pin_read = awin_gpio_pin_read,
			.gp_pin_write = awin_gpio_pin_write,
			.gp_pin_ctl = awin_gpio_pin_ctl,
		},
		.grp_pin_mask = 0,
		.grp_pin_intr_mask = 0,
		.grp_index = 10,
		.grp_nc_name = "nc-pk",
	},
	[11] = {
		.grp_offset = 0,
		.grp_gc_tag = {
			.gp_cookie = &pin_groups[11],
			.gp_pin_read = awin_gpio_pin_read,
			.gp_pin_write = awin_gpio_pin_write,
			.gp_pin_ctl = awin_gpio_pin_ctl,
		},
		.grp_pin_mask = 0,
		.grp_pin_intr_mask = 0,
		.grp_index = 11,
		.grp_nc_name = "nc-pl",
	},
	[12] = {
		.grp_offset = 0,
		.grp_gc_tag = {
			.gp_cookie = &pin_groups[12],
			.gp_pin_read = awin_gpio_pin_read,
			.gp_pin_write = awin_gpio_pin_write,
			.gp_pin_ctl = awin_gpio_pin_ctl,
		},
		.grp_pin_mask = 0,
		.grp_pin_intr_mask = 0,
		.grp_index = 12,
		.grp_nc_name = "nc-pm",
	},
	[13] = {
		.grp_offset = 0,
		.grp_gc_tag = {
			.gp_cookie = &pin_groups[13],
			.gp_pin_read = awin_gpio_pin_read,
			.gp_pin_write = awin_gpio_pin_write,
			.gp_pin_ctl = awin_gpio_pin_ctl,
		},
		.grp_pin_mask = 0,
		.grp_pin_intr_mask = 0,
		.grp_nc_name = "nc-pn",
	},
};


static struct awin_gpio_softc {
	device_t sc_dev;
	bus_space_tag_t sc_bst;
	bus_space_handle_t sc_bsh;
	bus_space_handle_t sc_eint_bsh;
	void *sc_ih;
	kmutex_t sc_intr_lock;
	uint32_t sc_ecfg[4];
	uint32_t sc_eintr_configured;
} awin_gpio_sc = {
	.sc_bst = &armv7_generic_bs_tag,
};

#define INT_OFFSET(x) ((x) - AWIN_PIO_INT_CFG0_REG)

CFATTACH_DECL_NEW(awin_gpio, sizeof(struct awin_gpio_softc),
	awin_gpio_match, awin_gpio_attach, NULL, NULL);

static int awin_gpio_intr(void *);

static int
awin_gpio_match(device_t parent, cfdata_t cf, void *aux)
{
	struct awinio_attach_args * const aio __diagused = aux;
	const struct awin_locators * const loc __diagused = &aio->aio_loc;

	KASSERT(!strcmp(cf->cf_name, loc->loc_name));
	KASSERT(loc->loc_port == AWINIOCF_PORT_DEFAULT);
	KASSERT(cf->cf_loc[AWINIOCF_PORT] == AWINIOCF_PORT_DEFAULT);

	if (awin_gpio_sc.sc_dev != NULL)
		return 0;

	return 1;
}

#if NGPIO > 0
static int
awin_gpio_cfprint(void *priv, const char *pnp)
{
	struct gpiobus_attach_args *gba = priv;
	struct awin_gpio_pin_group *grp = gba->gba_gc->gp_cookie;

	if (pnp)
		aprint_normal("gpiobus at %s", pnp);

	aprint_normal(" port %c", 'A' + grp->grp_index);

	return UNCONF;
}

static void
awin_gpio_config_pins(device_t self)
{
	struct awin_gpio_softc * const sc = &awin_gpio_sc;

	/*
	 * First find out how many pins we can offer.
	 */
	size_t pin_count = 0;
	for (u_int i = 0; i < __arraycount(pin_groups); i++) {
		struct awin_gpio_pin_group * const grp = &pin_groups[i];

		pin_count +=
		    popcount32(grp->grp_pin_mask & ~grp->grp_pin_inuse_mask);
	}

	/*
	 * Allocate the pin data.
	 */
	gpio_pin_t * const pins = kmem_zalloc(sizeof(gpio_pin_t) * pin_count,
	    KM_SLEEP);
	KASSERT(pins != NULL);

	const int pincaps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT
		| GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN;

	gpio_pin_t *pin = pins;
	for (u_int i = 0; i < __arraycount(pin_groups); i++) {
		struct awin_gpio_pin_group * const grp = &pin_groups[i];
		uint32_t mask = grp->grp_pin_mask & ~grp->grp_pin_inuse_mask;

		/* 
		 * If this group has no bits to provide, skip it.
		 */
		if (mask == 0)
			continue;

		struct gpiobus_attach_args gba = {
			.gba_gc = &grp->grp_gc_tag,
			.gba_pins = pin,
		};

		uint32_t data = bus_space_read_4(sc->sc_bst, grp->grp_bsh,
		    AWIN_PIO_DAT_REG);
		for (int num = 0; mask != 0; mask >>= 1, data >>= 1, num++) {
			if (mask & 1) {
				pin->pin_num = num + (i << 5);
				pin->pin_caps = pincaps;
				pin->pin_flags = pincaps;
				if ((grp->grp_pin_intr_mask & (1 << num)) != 0){
					pin->pin_caps |=
					    GPIO_PIN_EVENTS |
					    GPIO_PIN_LEVEL |
					    GPIO_PIN_FALLING;
				}
				pin->pin_state = (data & 1) != 0;
				pin++;
			}
		}

		gba.gba_npins = pin - gba.gba_pins;
		grp->grp_gpio_dev =
		    config_found_ia(self, "gpiobus", &gba, awin_gpio_cfprint);
	}
}
#endif /* NGPIO > 0 */

static void
awin_gpio_attach(device_t parent, device_t self, void *aux)
{
	struct awin_gpio_softc * const sc = &awin_gpio_sc;
	struct awinio_attach_args * const aio = aux;
	const struct awin_locators * const loc = &aio->aio_loc;
	prop_dictionary_t dict = device_properties(self);

	sc->sc_dev = self;

	sc->sc_bst = aio->aio_core_bst;
	bus_space_subregion(sc->sc_bst, aio->aio_core_bsh,
	    loc->loc_offset, loc->loc_size, &sc->sc_bsh);

	aprint_naive("\n");
	aprint_normal("\n");
	sc->sc_ih = intr_establish(loc->loc_intr,
	    IPL_VM, IST_LEVEL | IST_MPSAFE,
	    awin_gpio_intr, &awin_gpio_sc);
	if (sc->sc_ih == NULL) {
		aprint_error_dev(self, "failed to establish interrupt %d\n",
		    loc->loc_intr);
		return;
	}
	aprint_normal_dev(self, "interrupting on irq %d\n", loc->loc_intr);

	for (u_int i = 0; i < __arraycount(pin_groups); i++) {
		struct awin_gpio_pin_group * const grp = &pin_groups[i];

		/*
		 * See if this group has any unconnected pins and make sure
		 * we won't use them.
		 */
		uint32_t nc;
		if (prop_dictionary_get_uint32(dict, grp->grp_nc_name, &nc)) {
			KASSERT((~grp->grp_pin_mask & nc) == 0);
			KASSERT((grp->grp_pin_inuse_mask & ~nc) == 0);
			grp->grp_pin_mask &= ~nc;
		}
	}

#if NGPIO > 0
	config_defer(self, awin_gpio_config_pins);
#endif
}

static int
awin_gpio_intr(void *a)
{
	struct awin_gpio_softc *sc = a;
	uint32_t enabled;
	uint32_t pending;
	struct awin_gpio_pin_group * const grp = &pin_groups[7]; /* XXX */
	
	mutex_enter(&sc->sc_intr_lock);
	enabled = bus_space_read_4(sc->sc_bst, sc->sc_eint_bsh,
	    INT_OFFSET(AWIN_PIO_INT_CTL_REG));
	pending = bus_space_read_4(sc->sc_bst, sc->sc_eint_bsh,
	    INT_OFFSET(AWIN_PIO_INT_STA_REG));
	/* keep only enabled interrupts */
	pending &= enabled;
	/* mask and ack pending interrupts */
	enabled &= ~pending;
	bus_space_write_4(sc->sc_bst, sc->sc_eint_bsh,
	    INT_OFFSET(AWIN_PIO_INT_CTL_REG), enabled);
	bus_space_write_4(sc->sc_bst, sc->sc_eint_bsh,
	    INT_OFFSET(AWIN_PIO_INT_STA_REG), pending);
	mutex_exit(&sc->sc_intr_lock);
	gpio_intr(grp->grp_gpio_dev, pending);
	return 1;
}

static u_int
awin_gpio_get_pin_func(const struct awin_gpio_pin_cfg *cfg, u_int pin)
{
	const u_int shift = (pin & 7) << 2;
	const u_int i = (pin >> 3) & 3;

	return (cfg->cfg[i] >> shift) & 0x0f;
}

static void
awin_gpio_set_pin_func(struct awin_gpio_pin_cfg *cfg, u_int pin, u_int func)
{
	const u_int shift = (pin & 7) << 2;
	const u_int i = (pin >> 3) & 3;
	
	cfg->cfg[i] &= ~(0x0f << shift);
	cfg->cfg[i] |= func << shift;
}

static void
awin_gpio_set_pin_pull(struct awin_gpio_pin_cfg *cfg, u_int pin, u_int pull)
{
	const u_int shift = (pin & 15) << 1;
	const u_int i = (pin >> 4) & 1;
	
	cfg->pul[i] &= ~(0x03 << shift);
	cfg->pul[i] |= pull << shift;
}

static void
awin_gpio_set_pin_drv(struct awin_gpio_pin_cfg *cfg, u_int pin, u_int drv)
{
	const u_int shift = (pin & 15) << 1;
	const u_int i = (pin >> 4) & 1;
	
	cfg->drv[i] &= ~(0x03 << shift);
	cfg->drv[i] |= drv << shift;
}

static void
awin_gpio_update_cfg_regs(bus_space_tag_t bst, struct awin_gpio_pin_group *grp,
    const struct awin_gpio_pin_cfg *ncfg)
{
	for (u_int i = 0; i < 4; i++) {
		if (grp->grp_cfg.cfg[i] != ncfg->cfg[i]) {
			bus_space_write_4(bst, grp->grp_bsh,
			    AWIN_PIO_CFG0_REG + 4 * i, ncfg->cfg[i]);	
			grp->grp_cfg.cfg[i] = ncfg->cfg[i];
		}
	}
	for (u_int i = 0; i < 2; i++) {
		if (grp->grp_cfg.drv[i] != ncfg->drv[i]) {
			bus_space_write_4(bst, grp->grp_bsh,
			    AWIN_PIO_DRV0_REG + 4 * i, ncfg->drv[i]);	
			grp->grp_cfg.drv[i] = ncfg->drv[i];
		}
		if (grp->grp_cfg.pul[i] != ncfg->pul[i]) {
			bus_space_write_4(bst, grp->grp_bsh,
			    AWIN_PIO_PUL0_REG + 4 * i, ncfg->pul[i]);	
			grp->grp_cfg.pul[i] = ncfg->pul[i];
		}
	}
}

static void
awin_gpio_set_pin_eint(struct awin_gpio_softc *sc, int pin, uint32_t m)
{
	int g = (pin >> 3);
	int s = ((pin & 7) * 4);
	KASSERT(g < 4);
	sc->sc_ecfg[g] &= ~(0xf << s);
	sc->sc_ecfg[g] |= ((m & 0xf) << s);
}

static void
awin_gpio_update_eint_regs(struct awin_gpio_softc *sc)
{
	int i;

	KASSERT(mutex_owned(&sc->sc_intr_lock));
	for (i = 0; i < 4; i++) {
		bus_space_write_4(sc->sc_bst, sc->sc_eint_bsh, i * 4,
		    sc->sc_ecfg[i]);
	}
}

static void
awin_gpio_clear_eint(struct awin_gpio_softc *sc, int pin)
{
		bus_space_write_4(sc->sc_bst, sc->sc_eint_bsh,
		    INT_OFFSET(AWIN_PIO_INT_STA_REG), (1U <<pin));
}

static void
awin_gpio_update_eint_mask(struct awin_gpio_softc *sc)
{
	uint32_t enabled;
	KASSERT(mutex_owned(&sc->sc_intr_lock));
	enabled = bus_space_read_4(sc->sc_bst, sc->sc_eint_bsh,
		    INT_OFFSET(AWIN_PIO_INT_CTL_REG));
	enabled &= sc->sc_eintr_configured;
	bus_space_write_4(sc->sc_bst, sc->sc_eint_bsh,
		    INT_OFFSET(AWIN_PIO_INT_CTL_REG), enabled);
}

void
awin_gpio_init(void)
{
	struct awin_gpio_softc * const sc = &awin_gpio_sc;

#ifdef VERBOSE_INIT_ARM
	printf(" free");
#endif
	if (awin_chip_id() == AWIN_CHIP_ID_A31) {
		pin_groups[0].grp_pin_mask = __BIT(AWIN_A31_PIO_PA_PINS) - 1;
		pin_groups[1].grp_pin_mask = __BIT(AWIN_A31_PIO_PB_PINS) - 1;
		pin_groups[2].grp_pin_mask = __BIT(AWIN_A31_PIO_PC_PINS) - 1;
		pin_groups[3].grp_pin_mask = __BIT(AWIN_A31_PIO_PD_PINS) - 1;
		pin_groups[4].grp_pin_mask = __BIT(AWIN_A31_PIO_PE_PINS) - 1;
		pin_groups[5].grp_pin_mask = __BIT(AWIN_A31_PIO_PF_PINS) - 1;
		pin_groups[6].grp_pin_mask = __BIT(AWIN_A31_PIO_PG_PINS) - 1;
		pin_groups[7].grp_pin_mask = __BIT(AWIN_A31_PIO_PH_PINS) - 1;
		pin_groups[8].grp_offset = 0;		/* PI */
		pin_groups[8].grp_pin_mask = 0;		/* PI */
		pin_groups[9].grp_offset = 0;		/* PJ */
		pin_groups[9].grp_pin_mask = 0;		/* PJ */
		pin_groups[10].grp_offset = 0;		/* PK */
		pin_groups[10].grp_pin_mask = 0;	/* PK */
		pin_groups[11].grp_offset = AWIN_A31_CPUPIO_OFFSET +
					    0 * AWIN_PIO_GRP_SIZE;
		pin_groups[11].grp_pin_mask = __BIT(AWIN_A31_PIO_PL_PINS) - 1;
		pin_groups[12].grp_offset = AWIN_A31_CPUPIO_OFFSET +
					    1 * AWIN_PIO_GRP_SIZE;
		pin_groups[12].grp_pin_mask = __BIT(AWIN_A31_PIO_PM_PINS) - 1;
		pin_groups[13].grp_offset = 0;		/* PN */
		pin_groups[13].grp_pin_mask = 0;	/* PN */
	} else if (awin_chip_id() == AWIN_CHIP_ID_A80) {
		pin_groups[0].grp_pin_mask = __BIT(AWIN_A80_PIO_PA_PINS) - 1;
		pin_groups[0].grp_offset = AWIN_A80_PIO_OFFSET + 
					   0 * AWIN_PIO_GRP_SIZE;
		pin_groups[1].grp_pin_mask = __BIT(AWIN_A80_PIO_PB_PINS) - 1;
		pin_groups[1].grp_offset = AWIN_A80_PIO_OFFSET + 
					   1 * AWIN_PIO_GRP_SIZE;
		pin_groups[2].grp_pin_mask = __BIT(AWIN_A80_PIO_PC_PINS) - 1;
		pin_groups[2].grp_offset = AWIN_A80_PIO_OFFSET + 
					   2 * AWIN_PIO_GRP_SIZE;
		pin_groups[3].grp_pin_mask = __BIT(AWIN_A80_PIO_PD_PINS) - 1;
		pin_groups[3].grp_offset = AWIN_A80_PIO_OFFSET + 
					   3 * AWIN_PIO_GRP_SIZE;
		pin_groups[4].grp_pin_mask = __BIT(AWIN_A80_PIO_PE_PINS) - 1;
		pin_groups[4].grp_offset = AWIN_A80_PIO_OFFSET + 
					   4 * AWIN_PIO_GRP_SIZE;
		pin_groups[5].grp_pin_mask = __BIT(AWIN_A80_PIO_PF_PINS) - 1;
		pin_groups[5].grp_offset = AWIN_A80_PIO_OFFSET + 
					   5 * AWIN_PIO_GRP_SIZE;
		pin_groups[6].grp_pin_mask = __BIT(AWIN_A80_PIO_PG_PINS) - 1;
		pin_groups[6].grp_offset = AWIN_A80_PIO_OFFSET + 
					   6 * AWIN_PIO_GRP_SIZE;
		pin_groups[7].grp_pin_mask = __BIT(AWIN_A80_PIO_PH_PINS) - 1;
		pin_groups[7].grp_offset = AWIN_A80_PIO_OFFSET + 
					   7 * AWIN_PIO_GRP_SIZE;
		pin_groups[8].grp_offset = 0;		/* PI */
		pin_groups[8].grp_pin_mask = 0;		/* PI */
		pin_groups[9].grp_offset = 0;		/* PJ */
		pin_groups[9].grp_pin_mask = 0;		/* PJ */
		pin_groups[10].grp_offset = 0;		/* PK */
		pin_groups[10].grp_pin_mask = 0;	/* PK */
		pin_groups[11].grp_offset = AWIN_A80_RPIO_OFFSET +
					    0 * AWIN_PIO_GRP_SIZE;
		pin_groups[11].grp_pin_mask = __BIT(AWIN_A80_PIO_PL_PINS) - 1;
		pin_groups[12].grp_offset = AWIN_A80_RPIO_OFFSET +
					    1 * AWIN_PIO_GRP_SIZE;
		pin_groups[12].grp_pin_mask = __BIT(AWIN_A80_PIO_PM_PINS) - 1;
		pin_groups[13].grp_offset = AWIN_A80_RPIO_OFFSET +
					    2 * AWIN_PIO_GRP_SIZE;
		pin_groups[13].grp_pin_mask = __BIT(AWIN_A80_PIO_PN_PINS) - 1;
	}

	for (u_int i = 0; i < __arraycount(pin_groups); i++) {
		struct awin_gpio_pin_group * const grp = &pin_groups[i];

		if (grp->grp_offset == 0)
			continue;

#if defined(ALLWINNER_A80)
		if (i >= 11) {
			bus_space_subregion(sc->sc_bst, awin_rcpus_bsh,
			    grp->grp_offset, AWIN_PIO_GRP_SIZE, &grp->grp_bsh);
		} else
#endif
		{
			bus_space_subregion(sc->sc_bst, awin_core_bsh,
			    grp->grp_offset, AWIN_PIO_GRP_SIZE, &grp->grp_bsh);
		}

		for (u_int j = 0; j < 4; j++) {
			grp->grp_cfg.cfg[j] = bus_space_read_4(sc->sc_bst,
			    grp->grp_bsh, AWIN_PIO_CFG0_REG + j * 4);
		}
		grp->grp_cfg.drv[0] = bus_space_read_4(sc->sc_bst,
		    grp->grp_bsh, AWIN_PIO_DRV0_REG);
		grp->grp_cfg.drv[1] = bus_space_read_4(sc->sc_bst,
		    grp->grp_bsh, AWIN_PIO_DRV1_REG);
		grp->grp_cfg.pul[0] = bus_space_read_4(sc->sc_bst,
		    grp->grp_bsh, AWIN_PIO_PUL0_REG);
		grp->grp_cfg.pul[1] = bus_space_read_4(sc->sc_bst,
		    grp->grp_bsh, AWIN_PIO_PUL1_REG);

#if !defined(AWIN_GPIO_IGNORE_FW)
		for (uint32_t j = 0, mask = 1;
		     (mask & grp->grp_pin_mask) != 0;
		     j++, mask <<= 1) {
			u_int func = awin_gpio_get_pin_func(&grp->grp_cfg, j);
			if (func > AWIN_PIO_FUNC_OUTPUT) {
				grp->grp_pin_inuse_mask |= mask;
			}
		}
#endif

#ifdef VERBOSE_INIT_ARM
		printf(" P%c=%d", 'A' + i,
		    popcount32(grp->grp_pin_mask & ~grp->grp_pin_inuse_mask));
#endif
	}

	mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_VM);
	bus_space_subregion(sc->sc_bst, awin_core_bsh,
	    AWIN_PIO_OFFSET + AWIN_PIO_INT_CFG0_REG,
	    AWIN_PIO_INT_SIZE, &sc->sc_eint_bsh);
	for (u_int i = 0; i < 3; i++) {
		sc->sc_ecfg[i] = 0;
	}
	sc->sc_eintr_configured = 0;
}

bool
awin_gpio_pinset_available(const struct awin_gpio_pinset *req)
{
	KASSERT(req != NULL);

	if (!req->pinset_group)
		return false;

#ifdef DIAGNOSTIC
	if (awin_chip_id() == AWIN_CHIP_ID_A31) {
		KASSERT(
		    ('A' <= req->pinset_group && req->pinset_group <= 'I') ||
		    ('L' <= req->pinset_group && req->pinset_group <= 'M'));
	} else if (awin_chip_id() == AWIN_CHIP_ID_A80) {
		KASSERT(
		    ('A' <= req->pinset_group && req->pinset_group <= 'I') ||
		    ('L' <= req->pinset_group && req->pinset_group <= 'N'));
	} else {
		KASSERT('A' <= req->pinset_group && req->pinset_group <= 'I');
	}
#endif

	struct awin_gpio_pin_group * const grp =
	    &pin_groups[req->pinset_group - 'A'];

	/*
	 * If there are unconnected pins, then they've been remove from
	 * the groups pin mask.  If we want pins that are unconnected,
	 * fail the request.
	 */
	if (req->pinset_mask & ~grp->grp_pin_mask)
		return false;

	/*
	 * If none of the pins are in use, they must be available.
	 */
	if (req->pinset_mask & ~grp->grp_pin_inuse_mask)
		return true;

	/*
	 * Check to see if the pins are already setup for this function. 
	 */
	for (uint32_t j = 0, inuse = req->pinset_mask & grp->grp_pin_inuse_mask;
	     inuse != 0;
	     j++, inuse >>= 1) {
		const u_int n = ffs(inuse) - 1;
		j += n;
		inuse >>= n;
		/*
		 * If this pin is in use but it's for a different
		 * function, fail the request.
		 */
		if (awin_gpio_get_pin_func(&grp->grp_cfg, j) != req->pinset_func)
			return false;
	}

	/*
	 * Nothing incompatible encountered so the pins must be available.
	 */
	return true;
}

void
awin_gpio_pinset_acquire(const struct awin_gpio_pinset *req)
{
	KASSERT(awin_gpio_pinset_available(req));

	struct awin_gpio_pin_group * const grp =
	    &pin_groups[req->pinset_group - 'A'];


	/*
	 * If all the pins already have right function, just return.
	 */
	if ((req->pinset_mask & ~grp->grp_pin_inuse_mask) == 0) {
		return;
	}

	/*
	 * Copy the current config.
	 */
	struct awin_gpio_pin_cfg ncfg = grp->grp_cfg;

	/*
	 * For each pin not inuse, update the cloned config's function for it.
	 */
	for (uint32_t j = 0, todo = req->pinset_mask & ~grp->grp_pin_inuse_mask;
	     todo != 0;
	     j++, todo >>= 1) {
		const u_int n = ffs(todo) - 1;
		j += n;
		todo >>= n;
		/*
		 * Change the function of this pin.
		 */
		awin_gpio_set_pin_func(&ncfg, j, req->pinset_func);

		if (req->pinset_flags & GPIO_PIN_PULLDOWN)
			awin_gpio_set_pin_pull(&ncfg, j, AWIN_PIO_PULL_DOWN);
		else if (req->pinset_flags & GPIO_PIN_PULLUP)
			awin_gpio_set_pin_pull(&ncfg, j, AWIN_PIO_PULL_UP);

		if (req->pinset_drv)
			awin_gpio_set_pin_drv(&ncfg, j, req->pinset_drv);
	}

	/*
	 * Now update any config register that changed.
	 */
	awin_gpio_update_cfg_regs(&armv7_generic_bs_tag, grp, &ncfg);

	/*
	 * Mark all these pins as in use.
	 */
	grp->grp_pin_inuse_mask |= req->pinset_mask;
}

void
awin_gpio_pinset_release(const struct awin_gpio_pinset *req)
{
	KASSERT(awin_gpio_pinset_available(req));

	struct awin_gpio_pin_group * const grp =
	    &pin_groups[req->pinset_group - 'A'];

#if 0
	/*
	 * Copy the current config.
	 */
	struct awin_gpio_pin_cfg ncfg = grp->grp_cfg;

	/*
	 * For each pin not inuse, update the cloned config's function for it.
	 */
	for (uint32_t j = 0, todo = req->pinset_mask;
	     todo != 0;
	     j++, todo >>= 1) {
		const u_int n = ffs(todo) - 1;
		j += n;
		todo >>= n;
		/*
		 * Change the function of this pin.
		 */
		awin_gpio_set_pin_func(&ncfg, AWIN_PIO_FUNC_INPUT);
	}

	/*
	 * Now update any config register that changed.
	 */
	awin_gpio_update_cfg_regs(sc->sc_bst, grp, &ncfg);
#endif

	/*
	 * Clear these pins as being in use.
	 */
	grp->grp_pin_inuse_mask &= ~req->pinset_mask;
}

static int
awin_gpio_pin_read(void *cookie, int pin)
{
	struct awin_gpio_pin_group * const grp = cookie;

	KASSERT(pin < 32);

	return (bus_space_read_4(awin_gpio_sc.sc_bst, grp->grp_bsh,
	    AWIN_PIO_DAT_REG) >> pin) & 1;
}

static void
awin_gpio_pin_write(void *cookie, int pin, int value)
{
	struct awin_gpio_pin_group * const grp = cookie;

	KASSERT(pin < 32);

	awin_reg_set_clear(awin_gpio_sc.sc_bst, grp->grp_bsh,
	    AWIN_PIO_DAT_REG, value ? __BIT(pin) : 0, __BIT(pin));
}

static void
awin_gpio_pin_ctl(void *cookie, int pin, int flags)
{
	struct awin_gpio_pin_group * const grp = cookie;
	struct awin_gpio_pin_cfg ncfg = grp->grp_cfg;
	struct awin_gpio_softc *sc = &awin_gpio_sc;

	u_int pull_value = AWIN_PIO_PULL_NONE;
	if (flags & GPIO_PIN_PULLUP) {
		pull_value = AWIN_PIO_PULL_UP;
	} else if (flags & GPIO_PIN_PULLDOWN) {
		pull_value = AWIN_PIO_PULL_DOWN;
	}
	awin_gpio_set_pin_pull(&ncfg, pin, pull_value);

	mutex_enter(&sc->sc_intr_lock);
	if (flags & GPIO_PIN_EVENTS) {
		KASSERT(awin_chip_id() == AWIN_CHIP_ID_A20);
		KASSERT(grp->grp_index == 7);
		KASSERT(pin <= 21);
		KASSERT((grp->grp_pin_intr_mask & (1 << pin)) != 0);
		awin_gpio_set_pin_func(&ncfg, pin, AWIN_PIO_PH_EINT_FUNC);
		if (flags & GPIO_PIN_LEVEL) {
			if (flags & GPIO_PIN_FALLING) {
				awin_gpio_set_pin_eint(sc, pin,
				    AWIN_PIO_EINT_LOW_LEVEL);
			} else {
				awin_gpio_set_pin_eint(sc, pin,
				    AWIN_PIO_EINT_HIGH_LEVEL);
			}
		} else {
			if (flags & GPIO_PIN_FALLING) {
				awin_gpio_set_pin_eint(sc, pin,
				    AWIN_PIO_EINT_NEGATIVE_EDGE);
			} else {
				awin_gpio_set_pin_eint(sc, pin,
				    AWIN_PIO_EINT_POSITIVE_EDGE);
			}
		}
		awin_gpio_update_eint_regs(sc);
		sc->sc_eintr_configured |= 1 << pin;
		awin_gpio_clear_eint(sc, pin);
	} else { 
		if ((grp->grp_pin_intr_mask & (1 << pin)) != 0) {
			sc->sc_eintr_configured &= ~(1 << pin);
			awin_gpio_update_eint_mask(sc);
			awin_gpio_clear_eint(sc, pin);
		}
		if (flags & GPIO_PIN_INPUT) {
			awin_gpio_set_pin_func(&ncfg, pin,
			    AWIN_PIO_FUNC_INPUT);
		} else if (flags & GPIO_PIN_OUTPUT) {
			awin_gpio_set_pin_func(&ncfg, pin,
			    AWIN_PIO_FUNC_OUTPUT);
		}
	}

	/*
	 * Now update any config register that changed.
	 */
	awin_gpio_update_cfg_regs(&armv7_generic_bs_tag, grp, &ncfg);
	mutex_exit(&sc->sc_intr_lock);
}

static void
awin_gpio_pin_irqen(void *cookie, int pin, bool enable)
{
	uint32_t enabled;
	struct awin_gpio_softc *sc = &awin_gpio_sc;

	mutex_enter(&sc->sc_intr_lock);
	enabled = bus_space_read_4(sc->sc_bst, sc->sc_eint_bsh,
	    INT_OFFSET(AWIN_PIO_INT_CTL_REG));
	if (enable)
		enabled |= (1 << pin);
	else
		enabled &= ~(1 << pin);
	enabled &= sc->sc_eintr_configured;
	bus_space_write_4(sc->sc_bst, sc->sc_eint_bsh,
	    INT_OFFSET(AWIN_PIO_INT_CTL_REG), enabled);
	mutex_exit(&sc->sc_intr_lock);
}

bool
awin_gpio_pin_reserve(const char *name, struct awin_gpio_pindata *pd)
{
	struct awin_gpio_softc * const sc = &awin_gpio_sc;
	prop_dictionary_t dict = device_properties(sc->sc_dev);
	const char *pin_data;

	if (!prop_dictionary_get_cstring_nocopy(dict, name, &pin_data))
		return false;

	KASSERT(pin_data[0] == '>' || pin_data[0] == '<');
	KASSERT(pin_data[1] == 'P');

#ifdef DIAGNOSTIC
	if (awin_chip_id() == AWIN_CHIP_ID_A31) {
		KASSERT(('A' <= pin_data[2] && pin_data[2] <= 'I') ||
			('L' <= pin_data[2] && pin_data[2] <= 'M'));
	} else if (awin_chip_id() == AWIN_CHIP_ID_A80) {
		KASSERT(('A' <= pin_data[2] && pin_data[2] <= 'I') ||
			('L' <= pin_data[2] && pin_data[2] <= 'N'));
	} else {
		KASSERT('A' <= pin_data[2] && pin_data[2] <= 'I');
	}
#endif
	struct awin_gpio_pin_group * const grp = &pin_groups[pin_data[2] - 'A'];

	u_int pin = pin_data[3] - '0';
	KASSERT(pin < 10);
	if (pin_data[4] != 0) {
		KASSERT(pin_data[5] == 0);
		pin = pin * 10 + pin_data[4] - '0';
	}

	KASSERT(pin < 32);
	KASSERT(grp->grp_offset != 0);
	KASSERT(grp->grp_pin_mask & __BIT(pin));
	KASSERT((grp->grp_pin_inuse_mask & __BIT(pin)) == 0);

	struct awin_gpio_pin_cfg ncfg = grp->grp_cfg;
	awin_gpio_set_pin_func(&ncfg, pin,
	   pin_data[0] == '<' ? AWIN_PIO_FUNC_INPUT : AWIN_PIO_FUNC_OUTPUT);

	/*
	 * Now update any config register that changed.
	 */
	awin_gpio_update_cfg_regs(sc->sc_bst, grp, &ncfg);

	grp->grp_pin_inuse_mask &= ~__BIT(pin);

	pd->pd_gc = &grp->grp_gc_tag;
	pd->pd_pin = pin;
	return true;
}
