freebsd-dev/sys/arm/lpc/lpc_gpio.c
Luiz Otavio O Souza 8839e0e9f3 Make the GPIO children attach to the first unit available and not only to
unit 0.

It seems that this 'simplification' was copied to all GPIO drivers in tree.

This fix a bug where a GPIO controller could fail to attach its children
(gpioc and gpiobus) if another GPIO driver attach first.
2014-10-28 18:33:59 +00:00

557 lines
14 KiB
C

/*-
* Copyright (c) 2011 Jakub Wojciech Klama <jceel@FreeBSD.org>
* 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.
*
*/
/*
* GPIO on LPC32x0 consist of 4 ports:
* - Port0 with 8 input/output pins
* - Port1 with 24 input/output pins
* - Port2 with 13 input/output pins
* - Port3 with:
* - 26 input pins (GPI_00..GPI_09 + GPI_15..GPI_23 + GPI_25 + GPI_27..GPI_28)
* - 24 output pins (GPO_00..GPO_23)
* - 6 input/output pins (GPIO_00..GPIO_05)
*
* Pins are mapped to logical pin number as follows:
* [0..9] -> GPI_00..GPI_09 (port 3)
* [10..18] -> GPI_15..GPI_23 (port 3)
* [19] -> GPI_25 (port 3)
* [20..21] -> GPI_27..GPI_28 (port 3)
* [22..45] -> GPO_00..GPO_23 (port 3)
* [46..51] -> GPIO_00..GPIO_05 (port 3)
* [52..64] -> P2.0..P2.12 (port 2)
* [65..88] -> P1.0..P1.23 (port 1)
* [89..96] -> P0.0..P0.7 (port 0)
*
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bio.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/endian.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/queue.h>
#include <sys/resource.h>
#include <sys/rman.h>
#include <sys/time.h>
#include <sys/timetc.h>
#include <sys/watchdog.h>
#include <sys/gpio.h>
#include <machine/bus.h>
#include <machine/cpu.h>
#include <machine/cpufunc.h>
#include <machine/resource.h>
#include <machine/intr.h>
#include <machine/fdt.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <arm/lpc/lpcreg.h>
#include <arm/lpc/lpcvar.h>
#include "gpio_if.h"
struct lpc_gpio_softc
{
device_t lg_dev;
struct resource * lg_res;
bus_space_tag_t lg_bst;
bus_space_handle_t lg_bsh;
};
struct lpc_gpio_pinmap
{
int lp_start_idx;
int lp_pin_count;
int lp_port;
int lp_start_bit;
int lp_flags;
};
static const struct lpc_gpio_pinmap lpc_gpio_pins[] = {
{ 0, 10, 3, 0, GPIO_PIN_INPUT },
{ 10, 9, 3, 15, GPIO_PIN_INPUT },
{ 19, 1, 3, 25, GPIO_PIN_INPUT },
{ 20, 2, 3, 27, GPIO_PIN_INPUT },
{ 22, 24, 3, 0, GPIO_PIN_OUTPUT },
/*
* -1 below is to mark special case for Port3 GPIO pins, as they
* have other bits in Port 3 registers as inputs and as outputs
*/
{ 46, 6, 3, -1, GPIO_PIN_INPUT | GPIO_PIN_OUTPUT },
{ 52, 13, 2, 0, GPIO_PIN_INPUT | GPIO_PIN_OUTPUT },
{ 65, 24, 1, 0, GPIO_PIN_INPUT | GPIO_PIN_OUTPUT },
{ 89, 8, 0, 0, GPIO_PIN_INPUT | GPIO_PIN_OUTPUT },
{ -1, -1, -1, -1, -1 },
};
#define LPC_GPIO_NPINS \
(LPC_GPIO_P0_COUNT + LPC_GPIO_P1_COUNT + \
LPC_GPIO_P2_COUNT + LPC_GPIO_P3_COUNT)
#define LPC_GPIO_PIN_IDX(_map, _idx) \
(_idx - _map->lp_start_idx)
#define LPC_GPIO_PIN_BIT(_map, _idx) \
(_map->lp_start_bit + LPC_GPIO_PIN_IDX(_map, _idx))
static int lpc_gpio_probe(device_t);
static int lpc_gpio_attach(device_t);
static int lpc_gpio_detach(device_t);
static int lpc_gpio_pin_max(device_t, int *);
static int lpc_gpio_pin_getcaps(device_t, uint32_t, uint32_t *);
static int lpc_gpio_pin_getflags(device_t, uint32_t, uint32_t *);
static int lpc_gpio_pin_setflags(device_t, uint32_t, uint32_t);
static int lpc_gpio_pin_getname(device_t, uint32_t, char *);
static int lpc_gpio_pin_get(device_t, uint32_t, uint32_t *);
static int lpc_gpio_pin_set(device_t, uint32_t, uint32_t);
static int lpc_gpio_pin_toggle(device_t, uint32_t);
static const struct lpc_gpio_pinmap *lpc_gpio_get_pinmap(int);
static struct lpc_gpio_softc *lpc_gpio_sc = NULL;
#define lpc_gpio_read_4(_sc, _reg) \
bus_space_read_4(_sc->lg_bst, _sc->lg_bsh, _reg)
#define lpc_gpio_write_4(_sc, _reg, _val) \
bus_space_write_4(_sc->lg_bst, _sc->lg_bsh, _reg, _val)
#define lpc_gpio_get_4(_sc, _test, _reg1, _reg2) \
lpc_gpio_read_4(_sc, ((_test) ? _reg1 : _reg2))
#define lpc_gpio_set_4(_sc, _test, _reg1, _reg2, _val) \
lpc_gpio_write_4(_sc, ((_test) ? _reg1 : _reg2), _val)
static int
lpc_gpio_probe(device_t dev)
{
if (!ofw_bus_status_okay(dev))
return (ENXIO);
if (!ofw_bus_is_compatible(dev, "lpc,gpio"))
return (ENXIO);
device_set_desc(dev, "LPC32x0 GPIO");
return (BUS_PROBE_DEFAULT);
}
static int
lpc_gpio_attach(device_t dev)
{
struct lpc_gpio_softc *sc = device_get_softc(dev);
int rid;
sc->lg_dev = dev;
rid = 0;
sc->lg_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
RF_ACTIVE);
if (!sc->lg_res) {
device_printf(dev, "cannot allocate memory window\n");
return (ENXIO);
}
sc->lg_bst = rman_get_bustag(sc->lg_res);
sc->lg_bsh = rman_get_bushandle(sc->lg_res);
lpc_gpio_sc = sc;
device_add_child(dev, "gpioc", -1);
device_add_child(dev, "gpiobus", -1);
return (bus_generic_attach(dev));
}
static int
lpc_gpio_detach(device_t dev)
{
return (EBUSY);
}
static int
lpc_gpio_pin_max(device_t dev, int *npins)
{
*npins = LPC_GPIO_NPINS - 1;
return (0);
}
static int
lpc_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps)
{
const struct lpc_gpio_pinmap *map;
if (pin > LPC_GPIO_NPINS)
return (ENODEV);
map = lpc_gpio_get_pinmap(pin);
*caps = map->lp_flags;
return (0);
}
static int
lpc_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags)
{
struct lpc_gpio_softc *sc = device_get_softc(dev);
const struct lpc_gpio_pinmap *map;
uint32_t state;
int dir;
if (pin > LPC_GPIO_NPINS)
return (ENODEV);
map = lpc_gpio_get_pinmap(pin);
/* Check whether it's bidirectional pin */
if ((map->lp_flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) !=
(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) {
*flags = map->lp_flags;
return (0);
}
switch (map->lp_port) {
case 0:
state = lpc_gpio_read_4(sc, LPC_GPIO_P0_DIR_STATE);
dir = (state & (1 << LPC_GPIO_PIN_BIT(map, pin)));
break;
case 1:
state = lpc_gpio_read_4(sc, LPC_GPIO_P1_DIR_STATE);
dir = (state & (1 << LPC_GPIO_PIN_BIT(map, pin)));
break;
case 2:
state = lpc_gpio_read_4(sc, LPC_GPIO_P2_DIR_STATE);
dir = (state & (1 << LPC_GPIO_PIN_BIT(map, pin)));
break;
case 3:
state = lpc_gpio_read_4(sc, LPC_GPIO_P2_DIR_STATE);
dir = (state & (1 << (25 + LPC_GPIO_PIN_IDX(map, pin))));
break;
default:
panic("unknown GPIO port");
}
*flags = dir ? GPIO_PIN_OUTPUT : GPIO_PIN_INPUT;
return (0);
}
static int
lpc_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags)
{
struct lpc_gpio_softc *sc = device_get_softc(dev);
const struct lpc_gpio_pinmap *map;
uint32_t dir, state;
if (pin > LPC_GPIO_NPINS)
return (ENODEV);
map = lpc_gpio_get_pinmap(pin);
/* Check whether it's bidirectional pin */
if ((map->lp_flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) !=
(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT))
return (ENOTSUP);
if (flags & GPIO_PIN_INPUT)
dir = 0;
if (flags & GPIO_PIN_OUTPUT)
dir = 1;
switch (map->lp_port) {
case 0:
state = (1 << LPC_GPIO_PIN_IDX(map, pin));
lpc_gpio_set_4(sc, dir, LPC_GPIO_P0_DIR_SET,
LPC_GPIO_P0_DIR_CLR, state);
break;
case 1:
state = (1 << LPC_GPIO_PIN_IDX(map, pin));
lpc_gpio_set_4(sc, dir, LPC_GPIO_P1_DIR_SET,
LPC_GPIO_P0_DIR_CLR, state);
break;
case 2:
state = (1 << LPC_GPIO_PIN_IDX(map, pin));
lpc_gpio_set_4(sc, dir, LPC_GPIO_P2_DIR_SET,
LPC_GPIO_P0_DIR_CLR, state);
break;
case 3:
state = (1 << (25 + (pin - map->lp_start_idx)));
lpc_gpio_set_4(sc, dir, LPC_GPIO_P2_DIR_SET,
LPC_GPIO_P0_DIR_CLR, state);
break;
}
return (0);
}
static int
lpc_gpio_pin_getname(device_t dev, uint32_t pin, char *name)
{
const struct lpc_gpio_pinmap *map;
int idx;
map = lpc_gpio_get_pinmap(pin);
idx = LPC_GPIO_PIN_IDX(map, pin);
switch (map->lp_port) {
case 0:
case 1:
case 2:
snprintf(name, GPIOMAXNAME - 1, "P%d.%d", map->lp_port,
map->lp_start_bit + LPC_GPIO_PIN_IDX(map, pin));
break;
case 3:
if (map->lp_start_bit == -1) {
snprintf(name, GPIOMAXNAME - 1, "GPIO_%02d", idx);
break;
}
snprintf(name, GPIOMAXNAME - 1, "GP%c_%02d",
(map->lp_flags & GPIO_PIN_INPUT) ? 'I' : 'O',
map->lp_start_bit + idx);
break;
}
return (0);
}
static int
lpc_gpio_pin_get(device_t dev, uint32_t pin, uint32_t *value)
{
struct lpc_gpio_softc *sc = device_get_softc(dev);
const struct lpc_gpio_pinmap *map;
uint32_t state, flags;
int dir;
map = lpc_gpio_get_pinmap(pin);
if (lpc_gpio_pin_getflags(dev, pin, &flags))
return (ENXIO);
if (flags & GPIO_PIN_OUTPUT)
dir = 1;
if (flags & GPIO_PIN_INPUT)
dir = 0;
switch (map->lp_port) {
case 0:
state = lpc_gpio_get_4(sc, dir, LPC_GPIO_P0_OUTP_STATE,
LPC_GPIO_P0_INP_STATE);
*value = !!(state & (1 << LPC_GPIO_PIN_BIT(map, pin)));
case 1:
state = lpc_gpio_get_4(sc, dir, LPC_GPIO_P1_OUTP_STATE,
LPC_GPIO_P1_INP_STATE);
*value = !!(state & (1 << LPC_GPIO_PIN_BIT(map, pin)));
case 2:
state = lpc_gpio_read_4(sc, LPC_GPIO_P2_INP_STATE);
*value = !!(state & (1 << LPC_GPIO_PIN_BIT(map, pin)));
case 3:
state = lpc_gpio_get_4(sc, dir, LPC_GPIO_P3_OUTP_STATE,
LPC_GPIO_P3_INP_STATE);
if (map->lp_start_bit == -1) {
if (dir)
*value = !!(state & (1 << (25 +
LPC_GPIO_PIN_IDX(map, pin))));
else
*value = !!(state & (1 << (10 +
LPC_GPIO_PIN_IDX(map, pin))));
}
*value = !!(state & (1 << LPC_GPIO_PIN_BIT(map, pin)));
}
return (0);
}
static int
lpc_gpio_pin_set(device_t dev, uint32_t pin, uint32_t value)
{
struct lpc_gpio_softc *sc = device_get_softc(dev);
const struct lpc_gpio_pinmap *map;
uint32_t state, flags;
map = lpc_gpio_get_pinmap(pin);
if (lpc_gpio_pin_getflags(dev, pin, &flags))
return (ENXIO);
if ((flags & GPIO_PIN_OUTPUT) == 0)
return (EINVAL);
state = (1 << LPC_GPIO_PIN_BIT(map, pin));
switch (map->lp_port) {
case 0:
lpc_gpio_set_4(sc, value, LPC_GPIO_P0_OUTP_SET,
LPC_GPIO_P0_OUTP_CLR, state);
break;
case 1:
lpc_gpio_set_4(sc, value, LPC_GPIO_P1_OUTP_SET,
LPC_GPIO_P1_OUTP_CLR, state);
break;
case 2:
lpc_gpio_set_4(sc, value, LPC_GPIO_P2_OUTP_SET,
LPC_GPIO_P2_OUTP_CLR, state);
break;
case 3:
if (map->lp_start_bit == -1)
state = (1 << (25 + LPC_GPIO_PIN_IDX(map, pin)));
lpc_gpio_set_4(sc, value, LPC_GPIO_P3_OUTP_SET,
LPC_GPIO_P3_OUTP_CLR, state);
break;
}
return (0);
}
static int
lpc_gpio_pin_toggle(device_t dev, uint32_t pin)
{
const struct lpc_gpio_pinmap *map;
uint32_t flags;
map = lpc_gpio_get_pinmap(pin);
if (lpc_gpio_pin_getflags(dev, pin, &flags))
return (ENXIO);
if ((flags & GPIO_PIN_OUTPUT) == 0)
return (EINVAL);
panic("not implemented yet");
return (0);
}
static const struct lpc_gpio_pinmap *
lpc_gpio_get_pinmap(int pin)
{
const struct lpc_gpio_pinmap *map;
for (map = &lpc_gpio_pins[0]; map->lp_start_idx != -1; map++) {
if (pin >= map->lp_start_idx &&
pin < map->lp_start_idx + map->lp_pin_count)
return map;
}
panic("pin number %d out of range", pin);
}
int
lpc_gpio_set_flags(device_t dev, int pin, int flags)
{
if (lpc_gpio_sc == NULL)
return (ENXIO);
return lpc_gpio_pin_setflags(lpc_gpio_sc->lg_dev, pin, flags);
}
int
lpc_gpio_set_state(device_t dev, int pin, int state)
{
if (lpc_gpio_sc == NULL)
return (ENXIO);
return lpc_gpio_pin_set(lpc_gpio_sc->lg_dev, pin, state);
}
int
lpc_gpio_get_state(device_t dev, int pin, int *state)
{
if (lpc_gpio_sc == NULL)
return (ENXIO);
return lpc_gpio_pin_get(lpc_gpio_sc->lg_dev, pin, state);
}
void
lpc_gpio_init()
{
bus_space_tag_t bst;
bus_space_handle_t bsh;
bst = fdtbus_bs_tag;
/* Preset SPI devices CS pins to one */
bus_space_map(bst, LPC_GPIO_PHYS_BASE, LPC_GPIO_SIZE, 0, &bsh);
bus_space_write_4(bst, bsh, LPC_GPIO_P3_OUTP_SET,
1 << (SSD1289_CS_PIN - LPC_GPIO_GPO_00(0)) |
1 << (SSD1289_DC_PIN - LPC_GPIO_GPO_00(0)) |
1 << (ADS7846_CS_PIN - LPC_GPIO_GPO_00(0)));
bus_space_unmap(bst, bsh, LPC_GPIO_SIZE);
}
static device_method_t lpc_gpio_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, lpc_gpio_probe),
DEVMETHOD(device_attach, lpc_gpio_attach),
DEVMETHOD(device_detach, lpc_gpio_detach),
/* GPIO interface */
DEVMETHOD(gpio_pin_max, lpc_gpio_pin_max),
DEVMETHOD(gpio_pin_getcaps, lpc_gpio_pin_getcaps),
DEVMETHOD(gpio_pin_getflags, lpc_gpio_pin_getflags),
DEVMETHOD(gpio_pin_setflags, lpc_gpio_pin_setflags),
DEVMETHOD(gpio_pin_getname, lpc_gpio_pin_getname),
DEVMETHOD(gpio_pin_set, lpc_gpio_pin_set),
DEVMETHOD(gpio_pin_get, lpc_gpio_pin_get),
DEVMETHOD(gpio_pin_toggle, lpc_gpio_pin_toggle),
{ 0, 0 }
};
static devclass_t lpc_gpio_devclass;
static driver_t lpc_gpio_driver = {
"lpcgpio",
lpc_gpio_methods,
sizeof(struct lpc_gpio_softc),
};
extern devclass_t gpiobus_devclass, gpioc_devclass;
extern driver_t gpiobus_driver, gpioc_driver;
DRIVER_MODULE(lpcgpio, simplebus, lpc_gpio_driver, lpc_gpio_devclass, 0, 0);
DRIVER_MODULE(gpiobus, lpcgpio, gpiobus_driver, gpiobus_devclass, 0, 0);
DRIVER_MODULE(gpioc, lpcgpio, gpioc_driver, gpioc_devclass, 0, 0);
MODULE_VERSION(lpcgpio, 1);