#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/gpio.h>
#include <sys/evcount.h>
#include <sys/malloc.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <machine/intr.h>
#include <dev/gpio/gpiovar.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_clock.h>
#include <dev/ofw/ofw_gpio.h>
#include <dev/ofw/ofw_pinctrl.h>
#include <dev/ofw/ofw_regulator.h>
#include <dev/ofw/fdt.h>
#include <dev/fdt/sunxireg.h>
#include <dev/fdt/sxipiovar.h>
#include "gpio.h"
#define SXIPIO_NPORT 9
struct sxipio_softc;
struct sxipio_gpio {
struct sxipio_softc *sc;
int port;
};
struct intrhand {
int (*ih_func)(void *);
void *ih_arg;
int ih_ipl;
int ih_irq;
int ih_gpio;
struct evcount ih_count;
char *ih_name;
};
struct sxipio_softc {
struct device sc_dev;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
int sc_node;
void *sc_ih_h;
void *sc_ih_l;
int sc_max_il;
int sc_min_il;
const struct sxipio_pin *sc_pins;
int sc_npins;
struct gpio_controller sc_gc;
struct sxipio_gpio sc_gpio[SXIPIO_NPORT];
struct gpio_chipset_tag sc_gpio_tag[SXIPIO_NPORT];
gpio_pin_t sc_gpio_pins[SXIPIO_NPORT][32];
struct intrhand *sc_handlers[32];
void (*sc_bias_cfg)(struct sxipio_softc *,
int, uint32_t);
};
#define SXIPIO_CFG(port, pin) 0x00 + ((port) * 0x24) + (((pin) >> 3) * 0x04)
#define SXIPIO_DAT(port) 0x10 + ((port) * 0x24)
#define SXIPIO_DRV(port, pin) 0x14 + ((port) * 0x24) + (((pin) >> 4) * 0x04)
#define SXIPIO_PUL(port, pin) 0x1c + ((port) * 0x24) + (((pin) >> 4) * 0x04)
#define SXIPIO_INT_CFG0(port) 0x0200 + ((port) * 0x04)
#define SXIPIO_INT_CTL 0x0210
#define SXIPIO_INT_STA 0x0214
#define SXIPIO_INT_DEB 0x0218
#define SXIPIO_GRP_CFG(port) 0x0300 + ((port) * 0x04)
#define SXIPIO_IO_BIAS_MASK (0xf << 0)
#define SXIPIO_IO_BIAS_1_8V 0x0
#define SXIPIO_IO_BIAS_2_5V 0x6
#define SXIPIO_IO_BIAS_2_8V 0x9
#define SXIPIO_IO_BIAS_3_0V 0xa
#define SXIPIO_IO_BIAS_3_3V 0xd
#define SXIPIO_GPIO_IN 0
#define SXIPIO_GPIO_OUT 1
#define SXIPIO_DISABLED 7
int sxipio_match(struct device *, void *, void *);
void sxipio_attach(struct device *, struct device *, void *);
const struct cfattach sxipio_ca = {
sizeof (struct sxipio_softc), sxipio_match, sxipio_attach
};
struct cfdriver sxipio_cd = {
NULL, "sxipio", DV_DULL
};
void sxipio_attach_gpio(struct device *);
int sxipio_pinctrl(uint32_t, void *);
void sxipio_config_pin(void *, uint32_t *, int);
int sxipio_get_pin(void *, uint32_t *);
void sxipio_set_pin(void *, uint32_t *, int);
void sxipio_a80_bias_cfg(struct sxipio_softc *, int, uint32_t);
#include "sxipio_pins.h"
struct sxipio_pins {
const char *compat;
const struct sxipio_pin *pins;
int npins;
};
const struct sxipio_pins sxipio_pins[] = {
{
"allwinner,sun4i-a10-pinctrl",
sun4i_a10_pins, nitems(sun4i_a10_pins)
},
{
"allwinner,sun5i-a10s-pinctrl",
sun5i_a10s_pins, nitems(sun5i_a10s_pins)
},
{
"allwinner,sun5i-a13-pinctrl",
sun5i_a13_pins, nitems(sun5i_a13_pins)
},
{
"allwinner,sun5i-gr8-pinctrl",
sun5i_gr8_pins, nitems(sun5i_gr8_pins)
},
{
"allwinner,sun7i-a20-pinctrl",
sun7i_a20_pins, nitems(sun7i_a20_pins)
},
{
"allwinner,sun8i-r40-pinctrl",
sun8i_r40_pins, nitems(sun8i_r40_pins)
},
{
"allwinner,sun8i-a33-pinctrl",
sun8i_a33_pins, nitems(sun8i_a33_pins)
},
{
"allwinner,sun8i-h3-pinctrl",
sun8i_h3_pins, nitems(sun8i_h3_pins)
},
{
"allwinner,sun8i-h3-r-pinctrl",
sun8i_h3_r_pins, nitems(sun8i_h3_r_pins)
},
{
"allwinner,sun8i-v3-pinctrl",
sun8i_v3_pins, nitems(sun8i_v3_pins)
},
{
"allwinner,sun8i-v3s-pinctrl",
sun8i_v3s_pins, nitems(sun8i_v3s_pins)
},
{
"allwinner,sun9i-a80-pinctrl",
sun9i_a80_pins, nitems(sun9i_a80_pins)
},
{
"allwinner,sun9i-a80-r-pinctrl",
sun9i_a80_r_pins, nitems(sun9i_a80_r_pins)
},
{
"allwinner,sun20i-d1-pinctrl",
sun20i_d1_pins, nitems(sun20i_d1_pins)
},
{
"allwinner,sun50i-a64-pinctrl",
sun50i_a64_pins, nitems(sun50i_a64_pins)
},
{
"allwinner,sun50i-a64-r-pinctrl",
sun50i_a64_r_pins, nitems(sun50i_a64_r_pins)
},
{
"allwinner,sun50i-h5-pinctrl",
sun50i_h5_pins, nitems(sun50i_h5_pins)
},
{
"allwinner,sun50i-h6-pinctrl",
sun50i_h6_pins, nitems(sun50i_h6_pins)
},
{
"allwinner,sun50i-h6-r-pinctrl",
sun50i_h6_r_pins, nitems(sun50i_h6_r_pins)
},
{
"allwinner,sun50i-h616-pinctrl",
sun50i_h616_pins, nitems(sun50i_h616_pins)
},
{
"allwinner,sun50i-h616-r-pinctrl",
sun50i_h616_r_pins, nitems(sun50i_h616_r_pins)
},
};
int
sxipio_match(struct device *parent, void *match, void *aux)
{
struct fdt_attach_args *faa = aux;
int i;
for (i = 0; i < nitems(sxipio_pins); i++) {
if (OF_is_compatible(faa->fa_node, sxipio_pins[i].compat))
return 1;
}
return 0;
}
void
sxipio_attach(struct device *parent, struct device *self, void *aux)
{
struct sxipio_softc *sc = (struct sxipio_softc *)self;
struct fdt_attach_args *faa = aux;
int i;
sc->sc_iot = faa->fa_iot;
if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
faa->fa_reg[0].size, 0, &sc->sc_ioh))
panic("%s: bus_space_map failed!", __func__);
sc->sc_node = faa->fa_node;
clock_enable_all(faa->fa_node);
reset_deassert_all(faa->fa_node);
for (i = 0; i < nitems(sxipio_pins); i++) {
if (OF_is_compatible(faa->fa_node, sxipio_pins[i].compat)) {
sc->sc_pins = sxipio_pins[i].pins;
sc->sc_npins = sxipio_pins[i].npins;
break;
}
}
if (OF_is_compatible(faa->fa_node, "allwinner,sun9i-a80-pinctrl") ||
OF_is_compatible(faa->fa_node, "allwinner,sun9i-a80-r-pinctrl"))
sc->sc_bias_cfg = sxipio_a80_bias_cfg;
KASSERT(sc->sc_pins);
pinctrl_register(faa->fa_node, sxipio_pinctrl, sc);
sc->sc_gc.gc_node = faa->fa_node;
sc->sc_gc.gc_cookie = sc;
sc->sc_gc.gc_config_pin = sxipio_config_pin;
sc->sc_gc.gc_get_pin = sxipio_get_pin;
sc->sc_gc.gc_set_pin = sxipio_set_pin;
gpio_controller_register(&sc->sc_gc);
config_defer(self, sxipio_attach_gpio);
printf(": %d pins\n", sc->sc_npins);
}
int
sxipio_drive(int node)
{
int drive;
drive = OF_getpropint(node, "allwinner,drive", -1);
if (drive >= 0)
return drive;
drive = OF_getpropint(node, "drive-strength", 0) - 10;
if (drive >= 0)
return (drive / 10);
return -1;
}
int
sxipio_pull(int node)
{
int pull;
pull = OF_getpropint(node, "allwinner,pull", -1);
if (pull >= 0)
return pull;
if (OF_getproplen(node, "bias-disable") == 0)
return 0;
if (OF_getproplen(node, "bias-pull-up") == 0)
return 1;
if (OF_getproplen(node, "bias-pull-down") == 0)
return 2;
return -1;
}
int
sxipio_pinctrl(uint32_t phandle, void *cookie)
{
struct sxipio_softc *sc = cookie;
char func[32];
char vcc[16];
char *names, *name;
uint32_t supply;
int group, port, pin, off, mask;
int mux, drive, pull;
int node;
int len;
int i, j;
int s;
node = OF_getnodebyphandle(phandle);
if (node == 0)
return -1;
len = OF_getprop(node, "allwinner,function", func, sizeof(func));
if (len <= 0 || len >= sizeof(func)) {
len = OF_getprop(node, "function", func, sizeof(func));
if (len <= 0 || len >= sizeof(func))
return -1;
}
len = OF_getproplen(node, "allwinner,pins");
if (len <= 0) {
len = OF_getproplen(node, "pins");
if (len <= 0)
return -1;
}
names = malloc(len, M_TEMP, M_WAITOK);
if (OF_getprop(node, "allwinner,pins", names, len) <= 0)
OF_getprop(node, "pins", names, len);
drive = sxipio_drive(node);
pull = sxipio_pull(node);
name = names;
while (len > 0) {
for (i = 0; i < sc->sc_npins; i++) {
if (strcmp(name, sc->sc_pins[i].name) == 0)
break;
}
if (i >= sc->sc_npins)
goto err;
for (j = 0; j < nitems(sc->sc_pins[i].funcs); j++) {
if (sc->sc_pins[i].funcs[j].name == NULL)
continue;
if (strcmp(func, sc->sc_pins[i].funcs[j].name) == 0)
break;
}
if (j >= nitems(sc->sc_pins[i].funcs))
goto err;
group = sc->sc_pins[i].name[1] - 'A';
port = sc->sc_pins[i].port;
pin = sc->sc_pins[i].pin;
mux = sc->sc_pins[i].funcs[j].mux;
snprintf(vcc, sizeof(vcc), "vcc-p%c-supply", 'a' + group);
supply = OF_getpropint(sc->sc_node, vcc, 0);
if (supply) {
regulator_enable(supply);
if (sc->sc_bias_cfg)
sc->sc_bias_cfg(sc, port, supply);
}
s = splhigh();
off = (pin & 0x7) << 2, mask = (0x7 << off);
SXICMS4(sc, SXIPIO_CFG(port, pin), mask, mux << off);
off = (pin & 0xf) << 1, mask = (0x3 << off);
if (drive >= 0 && drive < 4)
SXICMS4(sc, SXIPIO_DRV(port, pin), mask, drive << off);
if (pull >= 0 && pull < 3)
SXICMS4(sc, SXIPIO_PUL(port, pin), mask, pull << off);
splx(s);
len -= strlen(name) + 1;
name += strlen(name) + 1;
}
free(names, M_TEMP, len);
return 0;
err:
free(names, M_TEMP, len);
return -1;
}
void
sxipio_config_pin(void *cookie, uint32_t *cells, int config)
{
struct sxipio_softc *sc = cookie;
uint32_t port = cells[0];
uint32_t pin = cells[1];
int mux, off;
if (port > SXIPIO_NPORT || pin >= 32)
return;
mux = (config & GPIO_CONFIG_OUTPUT) ? 1 : 0;
off = (pin & 0x7) << 2;
SXICMS4(sc, SXIPIO_CFG(port, pin), 0x7 << off, mux << off);
}
int
sxipio_get_pin(void *cookie, uint32_t *cells)
{
struct sxipio_softc *sc = cookie;
uint32_t port = cells[0];
uint32_t pin = cells[1];
uint32_t flags = cells[2];
uint32_t reg;
int val;
if (port > SXIPIO_NPORT || pin >= 32)
return 0;
reg = SXIREAD4(sc, SXIPIO_DAT(port));
reg &= (1 << pin);
val = (reg >> pin) & 1;
if (flags & GPIO_ACTIVE_LOW)
val = !val;
return val;
}
void
sxipio_set_pin(void *cookie, uint32_t *cells, int val)
{
struct sxipio_softc *sc = cookie;
uint32_t port = cells[0];
uint32_t pin = cells[1];
uint32_t flags = cells[2];
uint32_t reg;
if (port > SXIPIO_NPORT || pin >= 32)
return;
reg = SXIREAD4(sc, SXIPIO_DAT(port));
if (flags & GPIO_ACTIVE_LOW)
val = !val;
if (val)
reg |= (1 << pin);
else
reg &= ~(1 << pin);
SXIWRITE4(sc, SXIPIO_DAT(port), reg);
}
void
sxipio_a80_bias_cfg(struct sxipio_softc *sc, int port, uint32_t supply)
{
uint32_t voltage;
uint32_t bias;
voltage = regulator_get_voltage(supply);
if (voltage <= 1800000)
bias = SXIPIO_IO_BIAS_1_8V;
else if (voltage <= 2500000)
bias = SXIPIO_IO_BIAS_2_5V;
else if (voltage <= 2800000)
bias = SXIPIO_IO_BIAS_2_8V;
else if (voltage <= 3000000)
bias = SXIPIO_IO_BIAS_3_0V;
else
bias = SXIPIO_IO_BIAS_3_3V;
SXICMS4(sc, SXIPIO_GRP_CFG(port), SXIPIO_IO_BIAS_MASK, bias);
}
int sxipio_pin_read(void *, int);
void sxipio_pin_write(void *, int, int);
void sxipio_pin_ctl(void *, int, int);
static const struct gpio_chipset_tag sxipio_gpio_tag = {
.gp_pin_read = sxipio_pin_read,
.gp_pin_write = sxipio_pin_write,
.gp_pin_ctl = sxipio_pin_ctl
};
int
sxipio_pin_read(void *cookie, int pin)
{
struct sxipio_gpio *gpio = cookie;
uint32_t cells[3];
cells[0] = gpio->port;
cells[1] = pin;
cells[2] = 0;
return sxipio_get_pin(gpio->sc, cells) ? GPIO_PIN_HIGH : GPIO_PIN_LOW;
}
void
sxipio_pin_write(void *cookie, int pin, int val)
{
struct sxipio_gpio *gpio = cookie;
uint32_t cells[3];
cells[0] = gpio->port;
cells[1] = pin;
cells[2] = 0;
sxipio_set_pin(gpio->sc, cells, val);
}
void
sxipio_pin_ctl(void *cookie, int pin, int flags)
{
struct sxipio_gpio *gpio = cookie;
uint32_t cells[3];
cells[0] = gpio->port;
cells[1] = pin;
cells[2] = 0;
if (ISSET(flags, GPIO_PIN_OUTPUT))
sxipio_config_pin(gpio->sc, cells, GPIO_CONFIG_OUTPUT);
else
sxipio_config_pin(gpio->sc, cells, 0);
}
void
sxipio_attach_gpio(struct device *parent)
{
struct sxipio_softc *sc = (struct sxipio_softc *)parent;
struct gpiobus_attach_args gba;
uint32_t reg;
int port, pin;
int off, mux;
int state, flags;
int i;
for (i = 0; i < sc->sc_npins; i++) {
if (strcmp(sc->sc_pins[i].funcs[0].name, "gpio_in") != 0 ||
strcmp(sc->sc_pins[i].funcs[1].name, "gpio_out") != 0)
continue;
port = sc->sc_pins[i].port;
pin = sc->sc_pins[i].pin;
reg = SXIREAD4(sc, SXIPIO_CFG(port, pin));
off = (pin & 0x7) << 2;
mux = (reg >> off) & 0x7;
if (mux != SXIPIO_GPIO_IN && mux != SXIPIO_GPIO_OUT &&
mux != SXIPIO_DISABLED)
continue;
switch (mux) {
case SXIPIO_GPIO_IN:
flags = GPIO_PIN_SET | GPIO_PIN_INPUT;
break;
case SXIPIO_GPIO_OUT:
flags = GPIO_PIN_SET | GPIO_PIN_OUTPUT;
break;
default:
flags = GPIO_PIN_SET;
}
reg = SXIREAD4(sc, SXIPIO_DAT(port));
state = (reg >> pin) & 1;
sc->sc_gpio_pins[port][pin].pin_caps =
GPIO_PIN_INPUT | GPIO_PIN_OUTPUT;
sc->sc_gpio_pins[port][pin].pin_flags = flags;
sc->sc_gpio_pins[port][pin].pin_state = state;
sc->sc_gpio_pins[port][pin].pin_num = pin;
}
for (i = 0; i <= port; i++) {
memcpy(&sc->sc_gpio_tag[i], &sxipio_gpio_tag, sizeof(sxipio_gpio_tag));
sc->sc_gpio_tag[i].gp_cookie = &sc->sc_gpio[i];
sc->sc_gpio[i].sc = sc;
sc->sc_gpio[i].port = i;
gba.gba_name = "gpio";
gba.gba_gc = &sc->sc_gpio_tag[i];
gba.gba_pins = &sc->sc_gpio_pins[i][0];
gba.gba_npins = 32;
#if NGPIO > 0
config_found(&sc->sc_dev, &gba, gpiobus_print);
#endif
}
}