Add sysctls for changing GPIO pins function

Submitted by:	Luiz Otavio O Souza
This commit is contained in:
Oleksandr Tymoshenko 2012-12-18 22:18:54 +00:00
parent e1e84e74c1
commit 90576f541b

View File

@ -38,6 +38,7 @@ __FBSDID("$FreeBSD$");
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/gpio.h>
#include <sys/sysctl.h>
#include <machine/bus.h>
#include <machine/cpu.h>
@ -66,6 +67,11 @@ __FBSDID("$FreeBSD$");
#define BCM_GPIO_DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \
GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN)
struct bcm_gpio_sysctl {
struct bcm_gpio_softc *sc;
uint32_t pin;
};
struct bcm_gpio_softc {
device_t sc_dev;
struct mtx sc_mtx;
@ -78,6 +84,7 @@ struct bcm_gpio_softc {
int sc_ro_npins;
int sc_ro_pins[BCM_GPIO_PINS];
struct gpio_pin sc_gpio_pins[BCM_GPIO_PINS];
struct bcm_gpio_sysctl sc_sysctl[BCM_GPIO_PINS];
};
enum bcm_gpio_fsel {
@ -99,6 +106,7 @@ enum bcm_gpio_pud {
#define BCM_GPIO_LOCK(_sc) mtx_lock(&_sc->sc_mtx)
#define BCM_GPIO_UNLOCK(_sc) mtx_unlock(&_sc->sc_mtx)
#define BCM_GPIO_LOCK_ASSERT(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED)
#define BCM_GPIO_GPFSEL(_bank) 0x00 + _bank * 4
#define BCM_GPIO_GPSET(_bank) 0x1c + _bank * 4
@ -126,53 +134,89 @@ bcm_gpio_pin_is_ro(struct bcm_gpio_softc *sc, int pin)
static uint32_t
bcm_gpio_get_function(struct bcm_gpio_softc *sc, uint32_t pin)
{
uint32_t bank, data, offset;
uint32_t bank, func, offset;
/* Five banks, 10 pins per bank, 3 bits per pin. */
bank = pin / 10;
offset = (pin - bank * 10) * 3;
BCM_GPIO_LOCK(sc);
data = (BCM_GPIO_READ(sc, BCM_GPIO_GPFSEL(bank)) >> offset) & 7;
func = (BCM_GPIO_READ(sc, BCM_GPIO_GPFSEL(bank)) >> offset) & 7;
BCM_GPIO_UNLOCK(sc);
#ifdef DEBUG
device_printf(sc->sc_dev, "pin %d function: ", pin);
switch (data) {
return (func);
}
static void
bcm_gpio_func_str(uint32_t nfunc, char *buf, int bufsize)
{
switch (nfunc) {
case BCM_GPIO_INPUT:
printf("input\n");
strncpy(buf, "input", bufsize);
break;
case BCM_GPIO_OUTPUT:
printf("output\n");
strncpy(buf, "output", bufsize);
break;
case BCM_GPIO_ALT0:
printf("alt0\n");
strncpy(buf, "alt0", bufsize);
break;
case BCM_GPIO_ALT1:
printf("alt1\n");
strncpy(buf, "alt1", bufsize);
break;
case BCM_GPIO_ALT2:
printf("alt2\n");
strncpy(buf, "alt2", bufsize);
break;
case BCM_GPIO_ALT3:
printf("alt3\n");
strncpy(buf, "alt3", bufsize);
break;
case BCM_GPIO_ALT4:
printf("alt4\n");
strncpy(buf, "alt4", bufsize);
break;
case BCM_GPIO_ALT5:
printf("alt5\n");
strncpy(buf, "alt5", bufsize);
break;
default:
strncpy(buf, "invalid", bufsize);
}
#endif
}
switch (data) {
static int
bcm_gpio_str_func(char *func, uint32_t *nfunc)
{
if (strcasecmp(func, "input") == 0)
*nfunc = BCM_GPIO_INPUT;
else if (strcasecmp(func, "output") == 0)
*nfunc = BCM_GPIO_OUTPUT;
else if (strcasecmp(func, "alt0") == 0)
*nfunc = BCM_GPIO_ALT0;
else if (strcasecmp(func, "alt1") == 0)
*nfunc = BCM_GPIO_ALT1;
else if (strcasecmp(func, "alt2") == 0)
*nfunc = BCM_GPIO_ALT2;
else if (strcasecmp(func, "alt3") == 0)
*nfunc = BCM_GPIO_ALT3;
else if (strcasecmp(func, "alt4") == 0)
*nfunc = BCM_GPIO_ALT4;
else if (strcasecmp(func, "alt5") == 0)
*nfunc = BCM_GPIO_ALT5;
else
return (-1);
return (0);
}
static uint32_t
bcm_gpio_func_flag(uint32_t nfunc)
{
switch (nfunc) {
case BCM_GPIO_INPUT:
return (GPIO_PIN_INPUT);
case BCM_GPIO_OUTPUT:
return (GPIO_PIN_OUTPUT);
}
return (0);
}
@ -181,16 +225,17 @@ bcm_gpio_set_function(struct bcm_gpio_softc *sc, uint32_t pin, uint32_t f)
{
uint32_t bank, data, offset;
/* Must be called with lock held. */
BCM_GPIO_LOCK_ASSERT(sc);
/* Five banks, 10 pins per bank, 3 bits per pin. */
bank = pin / 10;
offset = (pin - bank * 10) * 3;
BCM_GPIO_LOCK(sc);
data = BCM_GPIO_READ(sc, BCM_GPIO_GPFSEL(bank));
data &= ~(7 << offset);
data |= (f << offset);
BCM_GPIO_WRITE(sc, BCM_GPIO_GPFSEL(bank), data);
BCM_GPIO_UNLOCK(sc);
}
static void
@ -198,17 +243,18 @@ bcm_gpio_set_pud(struct bcm_gpio_softc *sc, uint32_t pin, uint32_t state)
{
uint32_t bank, offset;
/* Must be called with lock held. */
BCM_GPIO_LOCK_ASSERT(sc);
bank = pin / 32;
offset = pin - 32 * bank;
BCM_GPIO_LOCK(sc);
BCM_GPIO_WRITE(sc, BCM_GPIO_GPPUD(0), state);
DELAY(10);
BCM_GPIO_WRITE(sc, BCM_GPIO_GPPUDCLK(bank), (1 << offset));
DELAY(10);
BCM_GPIO_WRITE(sc, BCM_GPIO_GPPUD(0), 0);
BCM_GPIO_WRITE(sc, BCM_GPIO_GPPUDCLK(bank), 0);
BCM_GPIO_UNLOCK(sc);
}
static void
@ -216,6 +262,8 @@ bcm_gpio_pin_configure(struct bcm_gpio_softc *sc, struct gpio_pin *pin,
unsigned int flags)
{
BCM_GPIO_LOCK(sc);
/*
* Manage input/output.
*/
@ -244,6 +292,8 @@ bcm_gpio_pin_configure(struct bcm_gpio_softc *sc, struct gpio_pin *pin,
}
} else
bcm_gpio_set_pud(sc, pin->gp_pin, BCM_GPIO_NONE);
BCM_GPIO_UNLOCK(sc);
}
static int
@ -471,13 +521,96 @@ bcm_gpio_get_ro_pins(struct bcm_gpio_softc *sc)
printf(",");
printf("%d", sc->sc_ro_pins[i]);
}
if (i > 0)
if (i > 0)
printf(".");
printf("\n");
return (0);
}
static int
bcm_gpio_func_proc(SYSCTL_HANDLER_ARGS)
{
char buf[16];
struct bcm_gpio_softc *sc;
struct bcm_gpio_sysctl *sc_sysctl;
uint32_t nfunc;
int i, error;
sc_sysctl = arg1;
sc = sc_sysctl->sc;
/* Get the current pin function. */
nfunc = bcm_gpio_get_function(sc, sc_sysctl->pin);
bcm_gpio_func_str(nfunc, buf, sizeof(buf));
error = sysctl_handle_string(oidp, buf, sizeof(buf), req);
if (error != 0 || req->newptr == NULL)
return (error);
/* Parse the user supplied string and check for a valid pin function. */
if (bcm_gpio_str_func(buf, &nfunc) != 0)
return (EINVAL);
BCM_GPIO_LOCK(sc);
/* Disable pull-up or pull-down on pin. */
bcm_gpio_set_pud(sc, sc_sysctl->pin, BCM_GPIO_NONE);
/* And now set the pin function. */
bcm_gpio_set_function(sc, sc_sysctl->pin, nfunc);
/* Update the pin flags. */
for (i = 0; i < sc->sc_gpio_npins; i++) {
if (sc->sc_gpio_pins[i].gp_pin == sc_sysctl->pin)
break;
}
if (i < sc->sc_gpio_npins)
sc->sc_gpio_pins[i].gp_flags = bcm_gpio_func_flag(nfunc);
BCM_GPIO_UNLOCK(sc);
return (0);
}
static void
bcm_gpio_sysctl_init(struct bcm_gpio_softc *sc)
{
char pinbuf[3];
struct bcm_gpio_sysctl *sc_sysctl;
struct sysctl_ctx_list *ctx;
struct sysctl_oid *tree_node, *pin_node, *pinN_node;
struct sysctl_oid_list *tree, *pin_tree, *pinN_tree;
int i;
/*
* Add per-pin sysctl tree/handlers.
*/
ctx = device_get_sysctl_ctx(sc->sc_dev);
tree_node = device_get_sysctl_tree(sc->sc_dev);
tree = SYSCTL_CHILDREN(tree_node);
pin_node = SYSCTL_ADD_NODE(ctx, tree, OID_AUTO, "pin",
CTLFLAG_RW, NULL, "GPIO Pins");
pin_tree = SYSCTL_CHILDREN(pin_node);
for (i = 0; i < sc->sc_gpio_npins; i++) {
snprintf(pinbuf, sizeof(pinbuf), "%d", i);
pinN_node = SYSCTL_ADD_NODE(ctx, pin_tree, OID_AUTO, pinbuf,
CTLFLAG_RD, NULL, "GPIO Pin");
pinN_tree = SYSCTL_CHILDREN(pinN_node);
sc->sc_sysctl[i].sc = sc;
sc_sysctl = &sc->sc_sysctl[i];
sc_sysctl->sc = sc;
sc_sysctl->pin = sc->sc_gpio_pins[i].gp_pin;
SYSCTL_ADD_PROC(ctx, pinN_tree, OID_AUTO, "function",
CTLFLAG_RW | CTLTYPE_STRING, sc_sysctl,
sizeof(struct bcm_gpio_sysctl), bcm_gpio_func_proc,
"A", "Pin Function");
}
}
static int
bcm_gpio_get_reserved_pins(struct bcm_gpio_softc *sc)
{
@ -499,7 +632,7 @@ bcm_gpio_get_reserved_pins(struct bcm_gpio_softc *sc)
*/
reserved = 0;
while ((node != 0) && (reserved == 0)) {
len = OF_getprop(node, "name", name,
len = OF_getprop(node, "name", name,
sizeof(name) - 1);
name[len] = 0;
if (strcmp(name, "reserved") == 0)
@ -532,7 +665,7 @@ bcm_gpio_get_reserved_pins(struct bcm_gpio_softc *sc)
sc->sc_ro_pins[j++ + sc->sc_ro_npins] = fdt32_to_cpu(pins[i]);
}
sc->sc_ro_npins += j;
if (i > 0)
if (i > 0)
printf(".");
printf("\n");
@ -553,6 +686,7 @@ static int
bcm_gpio_attach(device_t dev)
{
struct bcm_gpio_softc *sc = device_get_softc(dev);
uint32_t func;
int i, j, rid;
phandle_t gpio;
@ -600,15 +734,18 @@ bcm_gpio_attach(device_t dev)
continue;
snprintf(sc->sc_gpio_pins[i].gp_name, GPIOMAXNAME,
"pin %d", j);
func = bcm_gpio_get_function(sc, j);
sc->sc_gpio_pins[i].gp_pin = j;
sc->sc_gpio_pins[i].gp_caps = BCM_GPIO_DEFAULT_CAPS;
sc->sc_gpio_pins[i].gp_flags = bcm_gpio_get_function(sc, j);
sc->sc_gpio_pins[i].gp_flags = bcm_gpio_func_flag(func);
i++;
}
sc->sc_gpio_npins = i;
device_add_child(dev, "gpioc", device_get_unit(dev));
device_add_child(dev, "gpiobus", device_get_unit(dev));
bcm_gpio_sysctl_init(sc);
device_add_child(dev, "gpioc", device_get_unit(dev));
device_add_child(dev, "gpiobus", device_get_unit(dev));
return (bus_generic_attach(dev));
fail: