freebsd-skq/sys/arm/xscale/pxa/pxa_gpio.c
benno 980aab4117 Support for the XScale PXA255 SoC as found on the Gumstix Basix and Connex
boards.  This is enough to net-boot to multiuser.

Also supported is the SMSC LAN91C111 parts used on the netCF, netDUO and netMMC
add-on boards.

I'll be putting some instructions on how to boot this on the Gumstix boards
online soon.

This is still fairly rough and will be refined over time but I felt it was
better to get this out there where other people can help out.
2008-06-06 05:08:09 +00:00

359 lines
9.0 KiB
C

/*-
* Copyright (c) 2006 Benno Rice. 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 ``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 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>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/interrupt.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/rman.h>
#include <sys/queue.h>
#include <sys/taskqueue.h>
#include <sys/timetc.h>
#include <machine/bus.h>
#include <machine/intr.h>
#include <arm/xscale/pxa/pxavar.h>
#include <arm/xscale/pxa/pxareg.h>
struct pxa_gpio_softc {
struct resource * pg_res[4];
bus_space_tag_t pg_bst;
bus_space_handle_t pg_bsh;
struct mtx pg_mtx;
uint32_t pg_intr[3];
};
static struct resource_spec pxa_gpio_spec[] = {
{ SYS_RES_MEMORY, 0, RF_ACTIVE },
{ SYS_RES_IRQ, 0, RF_ACTIVE },
{ SYS_RES_IRQ, 1, RF_ACTIVE },
{ SYS_RES_IRQ, 2, RF_ACTIVE },
{ -1, 0 }
};
static struct pxa_gpio_softc *pxa_gpio_softc = NULL;
static int pxa_gpio_probe(device_t);
static int pxa_gpio_attach(device_t);
static driver_filter_t pxa_gpio_intr0;
static driver_filter_t pxa_gpio_intr1;
static driver_filter_t pxa_gpio_intrN;
static int
pxa_gpio_probe(device_t dev)
{
device_set_desc(dev, "GPIO Controller");
return (0);
}
static int
pxa_gpio_attach(device_t dev)
{
int error;
void *ihl;
struct pxa_gpio_softc *sc;
sc = (struct pxa_gpio_softc *)device_get_softc(dev);
if (pxa_gpio_softc != NULL)
return (ENXIO);
pxa_gpio_softc = sc;
error = bus_alloc_resources(dev, pxa_gpio_spec, sc->pg_res);
if (error) {
device_printf(dev, "could not allocate resources\n");
return (ENXIO);
}
sc->pg_bst = rman_get_bustag(sc->pg_res[0]);
sc->pg_bsh = rman_get_bushandle(sc->pg_res[0]);
/* Disable and clear all interrupts. */
bus_space_write_4(sc->pg_bst, sc->pg_bsh, GPIO_GRER0, 0);
bus_space_write_4(sc->pg_bst, sc->pg_bsh, GPIO_GRER1, 0);
bus_space_write_4(sc->pg_bst, sc->pg_bsh, GPIO_GRER2, 0);
bus_space_write_4(sc->pg_bst, sc->pg_bsh, GPIO_GFER0, 0);
bus_space_write_4(sc->pg_bst, sc->pg_bsh, GPIO_GFER1, 0);
bus_space_write_4(sc->pg_bst, sc->pg_bsh, GPIO_GFER2, 0);
bus_space_write_4(sc->pg_bst, sc->pg_bsh, GPIO_GEDR0, ~0);
bus_space_write_4(sc->pg_bst, sc->pg_bsh, GPIO_GEDR1, ~0);
bus_space_write_4(sc->pg_bst, sc->pg_bsh, GPIO_GEDR2, ~0);
mtx_init(&sc->pg_mtx, "GPIO mutex", NULL, MTX_SPIN);
if (bus_setup_intr(dev, sc->pg_res[1], INTR_TYPE_MISC|INTR_MPSAFE,
pxa_gpio_intr0, NULL, sc, &ihl) != 0) {
bus_release_resources(dev, pxa_gpio_spec, sc->pg_res);
device_printf(dev, "could not set up intr0\n");
return (ENXIO);
}
if (bus_setup_intr(dev, sc->pg_res[2], INTR_TYPE_MISC|INTR_MPSAFE,
pxa_gpio_intr1, NULL, sc, &ihl) != 0) {
bus_release_resources(dev, pxa_gpio_spec, sc->pg_res);
device_printf(dev, "could not set up intr1\n");
return (ENXIO);
}
if (bus_setup_intr(dev, sc->pg_res[3], INTR_TYPE_MISC|INTR_MPSAFE,
pxa_gpio_intrN, NULL, sc, &ihl) != 0) {
bus_release_resources(dev, pxa_gpio_spec, sc->pg_res);
device_printf(dev, "could not set up intrN\n");
return (ENXIO);
}
return (0);
}
static int
pxa_gpio_intr0(void *arg)
{
struct pxa_gpio_softc *sc;
sc = (struct pxa_gpio_softc *)arg;
bus_space_write_4(sc->pg_bst, sc->pg_bsh, GPIO_GEDR0, 0x1);
sc->pg_intr[0] |= 1;
return (FILTER_HANDLED);
}
static int
pxa_gpio_intr1(void *arg)
{
struct pxa_gpio_softc *sc;
sc = (struct pxa_gpio_softc *)arg;
bus_space_write_4(sc->pg_bst, sc->pg_bsh, GPIO_GEDR0, 0x2);
sc->pg_intr[1] |= 2;
return (FILTER_HANDLED);
}
static int
pxa_gpio_intrN(void *arg)
{
uint32_t gedr0, gedr1, gedr2;
struct pxa_gpio_softc *sc;
sc = (struct pxa_gpio_softc *)arg;
gedr0 = bus_space_read_4(sc->pg_bst, sc->pg_bsh, GPIO_GEDR0);
gedr0 &= 0xfffffffc;
bus_space_write_4(sc->pg_bst, sc->pg_bsh, GPIO_GEDR0, gedr0);
gedr1 = bus_space_read_4(sc->pg_bst, sc->pg_bsh, GPIO_GEDR1);
bus_space_write_4(sc->pg_bst, sc->pg_bsh, GPIO_GEDR1, gedr1);
gedr2 = bus_space_read_4(sc->pg_bst, sc->pg_bsh, GPIO_GEDR2);
gedr2 &= 0x001fffff;
bus_space_write_4(sc->pg_bst, sc->pg_bsh, GPIO_GEDR2, gedr2);
sc->pg_intr[0] |= gedr0;
sc->pg_intr[1] |= gedr1;
sc->pg_intr[2] |= gedr2;
return (FILTER_HANDLED);
}
static device_method_t pxa_gpio_methods[] = {
DEVMETHOD(device_probe, pxa_gpio_probe),
DEVMETHOD(device_attach, pxa_gpio_attach),
{0, 0}
};
static driver_t pxa_gpio_driver = {
"gpio",
pxa_gpio_methods,
sizeof(struct pxa_gpio_softc),
};
static devclass_t pxa_gpio_devclass;
DRIVER_MODULE(pxagpio, pxa, pxa_gpio_driver, pxa_gpio_devclass, 0, 0);
#define pxagpio_reg_read(softc, reg) \
bus_space_read_4(sc->pg_bst, sc->pg_bsh, reg)
#define pxagpio_reg_write(softc, reg, val) \
bus_space_write_4(sc->pg_bst, sc->pg_bsh, reg, val)
uint32_t
pxa_gpio_get_function(int gpio)
{
struct pxa_gpio_softc *sc;
uint32_t rv, io;
sc = pxa_gpio_softc;
rv = pxagpio_reg_read(sc, GPIO_FN_REG(gpio)) >> GPIO_FN_SHIFT(gpio);
rv = GPIO_FN(rv);
io = pxagpio_reg_read(sc, PXA250_GPIO_REG(GPIO_GPDR0, gpio));
if (io & GPIO_BIT(gpio))
rv |= GPIO_OUT;
io = pxagpio_reg_read(sc, PXA250_GPIO_REG(GPIO_GPLR0, gpio));
if (io & GPIO_BIT(gpio))
rv |= GPIO_SET;
return (rv);
}
uint32_t
pxa_gpio_set_function(int gpio, uint32_t fn)
{
struct pxa_gpio_softc *sc;
uint32_t rv, bit, oldfn;
sc = pxa_gpio_softc;
oldfn = pxa_gpio_get_function(gpio);
if (GPIO_FN(fn) == GPIO_FN(oldfn) &&
GPIO_FN_IS_OUT(fn) == GPIO_FN_IS_OUT(oldfn)) {
/*
* The pin's function is not changing.
* For Alternate Functions and GPIO input, we can just
* return now.
* For GPIO output pins, check the initial state is
* the same.
*
* Return 'fn' instead of 'oldfn' so the caller can
* reliably detect that we didn't change anything.
* (The initial state might be different for non-
* GPIO output pins).
*/
if (!GPIO_IS_GPIO_OUT(fn) ||
GPIO_FN_IS_SET(fn) == GPIO_FN_IS_SET(oldfn))
return (fn);
}
/*
* See section 4.1.3.7 of the PXA2x0 Developer's Manual for
* the correct procedure for changing GPIO pin functions.
*/
bit = GPIO_BIT(gpio);
/*
* 1. Configure the correct set/clear state of the pin
*/
if (GPIO_FN_IS_SET(fn))
pxagpio_reg_write(sc, PXA250_GPIO_REG(GPIO_GPSR0, gpio), bit);
else
pxagpio_reg_write(sc, PXA250_GPIO_REG(GPIO_GPCR0, gpio), bit);
/*
* 2. Configure the pin as an input or output as appropriate
*/
rv = pxagpio_reg_read(sc, PXA250_GPIO_REG(GPIO_GPDR0, gpio)) & ~bit;
if (GPIO_FN_IS_OUT(fn))
rv |= bit;
pxagpio_reg_write(sc, PXA250_GPIO_REG(GPIO_GPDR0, gpio), rv);
/*
* 3. Configure the pin's function
*/
bit = GPIO_FN_MASK << GPIO_FN_SHIFT(gpio);
fn = GPIO_FN(fn) << GPIO_FN_SHIFT(gpio);
rv = pxagpio_reg_read(sc, GPIO_FN_REG(gpio)) & ~bit;
pxagpio_reg_write(sc, GPIO_FN_REG(gpio), rv | fn);
return (oldfn);
}
/*
* GPIO "interrupt" handling.
*/
void
pxa_gpio_mask_irq(int irq)
{
uint32_t val;
struct pxa_gpio_softc *sc;
int gpio;
sc = pxa_gpio_softc;
gpio = IRQ_TO_GPIO(irq);
val = pxagpio_reg_read(sc, PXA250_GPIO_REG(GPIO_GRER0, gpio));
val &= ~GPIO_BIT(gpio);
pxagpio_reg_write(sc, PXA250_GPIO_REG(GPIO_GRER0, gpio), val);
}
void
pxa_gpio_unmask_irq(int irq)
{
uint32_t val;
struct pxa_gpio_softc *sc;
int gpio;
sc = pxa_gpio_softc;
gpio = IRQ_TO_GPIO(irq);
val = pxagpio_reg_read(sc, PXA250_GPIO_REG(GPIO_GRER0, gpio));
val |= GPIO_BIT(gpio);
pxagpio_reg_write(sc, PXA250_GPIO_REG(GPIO_GRER0, gpio), val);
}
int
pxa_gpio_get_next_irq()
{
struct pxa_gpio_softc *sc;
int gpio;
sc = pxa_gpio_softc;
if (sc->pg_intr[0] != 0) {
gpio = ffs(sc->pg_intr[0]) - 1;
sc->pg_intr[0] &= ~(1 << gpio);
return (GPIO_TO_IRQ(gpio));
}
if (sc->pg_intr[1] != 0) {
gpio = ffs(sc->pg_intr[1]) - 1;
sc->pg_intr[1] &= ~(1 << gpio);
return (GPIO_TO_IRQ(gpio + 32));
}
if (sc->pg_intr[2] != 0) {
gpio = ffs(sc->pg_intr[2]) - 1;
sc->pg_intr[2] &= ~(1 << gpio);
return (GPIO_TO_IRQ(gpio + 64));
}
return (-1);
}