freebsd-nq/sys/dev/p2sb/lewisburg_gpiocm.c

352 lines
8.1 KiB
C

/*-
* Copyright (c) 2018 Stormshield
* 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.
*/
#include <sys/param.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/bus.h>
#include <sys/gpio.h>
#include <machine/bus.h>
#include <sys/rman.h>
#include <machine/resource.h>
#include "gpio_if.h"
#include "lewisburg_gpiocm.h"
#include "p2sb.h"
#define PADBAR 0x00c
#define PADCFG0_GPIORXDIS (1<<9)
#define PADCFG0_GPIOTXDIS (1<<8)
#define PADCFG0_GPIORXSTATE (1<<1)
#define PADCFG0_GPIOTXSTATE (1<<0)
#define MAX_PAD_PER_GROUP 24
#define LBGGPIOCM_READ(sc, reg) p2sb_port_read_4(sc->p2sb, sc->port, reg)
#define LBGGPIOCM_WRITE(sc, reg, val) \
p2sb_port_write_4(sc->p2sb, sc->port, reg, val)
#define LBGGPIOCM_LOCK(sc) p2sb_lock(sc->p2sb)
#define LBGGPIOCM_UNLOCK(sc) p2sb_unlock(sc->p2sb)
struct lbggroup {
int groupid;
int npins;
int pins_off;
device_t dev;
char grpname;
};
struct lbgcommunity {
uint8_t npins;
const char *name;
uint32_t pad_off;
struct lbggroup groups[3];
int ngroups;
const char *grpnames;
};
#define LBG_COMMUNITY(n, np, g) \
{ \
.name = n, \
.npins = np, \
.grpnames = g, \
}
static struct lbgcommunity lbg_communities[] = {
LBG_COMMUNITY("LewisBurg GPIO Community 0", 72, "ABF"),
LBG_COMMUNITY("LewisBurg GPIO Community 1", 61, "CDE"),
LBG_COMMUNITY("LewisBurg GPIO Community 2", 0, ""),
LBG_COMMUNITY("LewisBurg GPIO Community 3", 12, "I"),
LBG_COMMUNITY("LewisBurg GPIO Community 4", 36, "JK"),
LBG_COMMUNITY("LewisBurg GPIO Community 5", 66, "GHL"),
};
struct lbggpiocm_softc
{
int port;
device_t p2sb;
struct lbgcommunity *community;
};
static struct lbggroup *lbggpiocm_get_group(struct lbggpiocm_softc *sc,
device_t child);
static __inline struct lbggroup *
lbggpiocm_get_group(struct lbggpiocm_softc *sc, device_t child)
{
int i;
for (i = 0; i < sc->community->ngroups; ++i)
if (sc->community->groups[i].dev == child)
return (&sc->community->groups[i]);
return (NULL);
}
static __inline uint32_t
lbggpiocm_getpad(struct lbggpiocm_softc *sc, uint32_t pin)
{
if (pin >= sc->community->npins)
return (0);
return (sc->community->pad_off + 2 * 4 * pin);
}
int
lbggpiocm_get_group_npins(device_t dev, device_t child)
{
struct lbggpiocm_softc *sc = device_get_softc(dev);
struct lbggroup *group;
group = lbggpiocm_get_group(sc, child);
if (group != NULL)
return (group->npins);
return (-1);
}
char
lbggpiocm_get_group_name(device_t dev, device_t child)
{
struct lbggpiocm_softc *sc = device_get_softc(dev);
struct lbggroup *group;
group = lbggpiocm_get_group(sc, child);
if (group != NULL)
return (group->grpname);
return ('\0');
}
static int
lbggpiocm_pin2cpin(struct lbggpiocm_softc *sc, device_t child, uint32_t pin)
{
struct lbggroup *group;
group = lbggpiocm_get_group(sc, child);
if (group != NULL)
return (pin + group->pins_off);
return (-1);
}
int
lbggpiocm_pin_setflags(device_t dev, device_t child, uint32_t pin, uint32_t flags)
{
struct lbggpiocm_softc *sc = device_get_softc(dev);
uint32_t padreg, padval;
int rpin;
if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) ==
(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT))
return (EINVAL);
if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) == 0)
return (EINVAL);
rpin = lbggpiocm_pin2cpin(sc, child, pin);
if (rpin < 0)
return (EINVAL);
padreg = lbggpiocm_getpad(sc, rpin);
LBGGPIOCM_LOCK(sc);
padval = LBGGPIOCM_READ(sc, padreg);
if (flags & GPIO_PIN_INPUT) {
padval &= ~PADCFG0_GPIORXDIS;
padval |= PADCFG0_GPIOTXDIS;
} else if (flags & GPIO_PIN_OUTPUT) {
padval &= ~PADCFG0_GPIOTXDIS;
padval |= PADCFG0_GPIORXDIS;
}
LBGGPIOCM_WRITE(sc, padreg, padval);
LBGGPIOCM_UNLOCK(sc);
return (0);
}
int
lbggpiocm_pin_get(device_t dev, device_t child, uint32_t pin, uint32_t *value)
{
struct lbggpiocm_softc *sc = device_get_softc(dev);
uint32_t padreg, val;
int rpin;
if (value == NULL)
return (EINVAL);
rpin = lbggpiocm_pin2cpin(sc, child, pin);
if (rpin < 0)
return (EINVAL);
padreg = lbggpiocm_getpad(sc, rpin);
LBGGPIOCM_LOCK(sc);
val = LBGGPIOCM_READ(sc, padreg);
LBGGPIOCM_UNLOCK(sc);
if (!(val & PADCFG0_GPIOTXDIS))
*value = !!(val & PADCFG0_GPIOTXSTATE);
else
*value = !!(val & PADCFG0_GPIORXSTATE);
return (0);
}
int
lbggpiocm_pin_set(device_t dev, device_t child, uint32_t pin, uint32_t value)
{
struct lbggpiocm_softc *sc = device_get_softc(dev);
uint32_t padreg, padcfg;
int rpin;
rpin = lbggpiocm_pin2cpin(sc, child, pin);
if (rpin < 0)
return (EINVAL);
padreg = lbggpiocm_getpad(sc, rpin);
LBGGPIOCM_LOCK(sc);
padcfg = LBGGPIOCM_READ(sc, padreg);
if (value)
padcfg |= PADCFG0_GPIOTXSTATE;
else
padcfg &= ~PADCFG0_GPIOTXSTATE;
LBGGPIOCM_WRITE(sc, padreg, padcfg);
LBGGPIOCM_UNLOCK(sc);
return (0);
}
int
lbggpiocm_pin_toggle(device_t dev, device_t child, uint32_t pin)
{
struct lbggpiocm_softc *sc = device_get_softc(dev);
uint32_t padreg, padcfg;
int rpin;
rpin = lbggpiocm_pin2cpin(sc, child, pin);
if (rpin < 0)
return (EINVAL);
padreg = lbggpiocm_getpad(sc, rpin);
LBGGPIOCM_LOCK(sc);
padcfg = LBGGPIOCM_READ(sc, padreg);
padcfg ^= PADCFG0_GPIOTXSTATE;
LBGGPIOCM_WRITE(sc, padreg, padcfg);
LBGGPIOCM_UNLOCK(sc);
return (0);
}
static int
lbggpiocm_probe(device_t dev)
{
struct lbggpiocm_softc *sc = device_get_softc(dev);
int unit;
sc->p2sb = device_get_parent(dev);
unit = device_get_unit(dev);
KASSERT(unit < nitems(lbg_communities), ("Wrong number of devices or communities"));
sc->port = p2sb_get_port(sc->p2sb, unit);
sc->community = &lbg_communities[unit];
if (sc->port < 0)
return (ENXIO);
device_set_desc(dev, sc->community->name);
return (BUS_PROBE_DEFAULT);
}
static int
lbggpiocm_attach(device_t dev)
{
uint32_t npins;
struct lbggpiocm_softc *sc;
struct lbggroup *group;
int i;
sc = device_get_softc(dev);
if (sc->community->npins == 0)
return (ENXIO);
LBGGPIOCM_LOCK(sc);
sc->community->pad_off = LBGGPIOCM_READ(sc, PADBAR);
LBGGPIOCM_UNLOCK(sc);
npins = sc->community->npins;
for (i = 0; i < nitems(sc->community->groups) && npins > 0; ++i) {
group = &sc->community->groups[i];
group->groupid = i;
group->grpname = sc->community->grpnames[i];
group->pins_off = i * MAX_PAD_PER_GROUP;
group->npins = npins < MAX_PAD_PER_GROUP ? npins :
MAX_PAD_PER_GROUP;
npins -= group->npins;
group->dev = device_add_child(dev, "gpio", -1);
}
sc->community->ngroups = i;
return (bus_generic_attach(dev));
}
static int
lbggpiocm_detach(device_t dev)
{
int error;
error = device_delete_children(dev);
if (error)
return (error);
return (bus_generic_detach(dev));
}
static device_method_t lbggpiocm_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, lbggpiocm_probe),
DEVMETHOD(device_attach, lbggpiocm_attach),
DEVMETHOD(device_detach, lbggpiocm_detach),
DEVMETHOD_END
};
static driver_t lbggpiocm_driver = {
"lbggpiocm",
lbggpiocm_methods,
sizeof(struct lbggpiocm_softc)
};
DRIVER_MODULE(lbggpiocm, p2sb, lbggpiocm_driver, NULL, NULL);