Add gpio methods to read/write/configure up to 32 pins simultaneously.

Sometimes it is necessary to combine several gpio pins into an ad-hoc bus
and manipulate the pins as a group. In such cases manipulating the pins
individualy is not an option, because the value on the "bus" assumes
potentially-invalid intermediate values as each pin is changed in turn. Note
that the "bus" may be something as simple as a bi-color LED where changing
colors requires changing both gpio pins at once, or something as complex as
a bitbanged multiplexed address/data bus connected to a microcontroller.

In addition to the absolute requirement of simultaneously changing the
output values of driven pins, a desirable feature of these new methods is to
provide a higher-performance mechanism for reading and writing multiple
pins, especially from userland where pin-at-a-time access incurs a noticible
syscall time penalty.

These new interfaces are NOT intended to abstract away all the ugly details
of how gpio is implemented on any given platform. In fact, to use these
properly you absolutely must know something about how the gpio hardware is
organized. Typically there are "banks" of gpio pins controlled by registers
which group several pins together. A bank may be as small as 2 pins or as
big as "all the pins on the device, hundreds of them." In the latter case, a
driver might support this interface by allowing access to any 32 adjacent
pins within the overall collection. Or, more likely, any 32 adjacent pins
starting at any multiple of 32. Whatever the hardware restrictions may be,
you would need to understand them to use this interface.

In additional to defining the interfaces, two example implementations are
included here, for imx5/6, and allwinner. These represent the two primary
types of gpio hardware drivers. imx6 has multiple gpio devices, each
implementing a single bank of 32 pins. Allwinner implements a single large
gpio number space from 1-n pins, and the driver internally translates that
linear number space to a bank+pin scheme based on how the pins are grouped
into control registers. The allwinner implementation imposes the restriction
that the first_pin argument to the new functions must always be pin 0 of a
bank.

Differential Revision:	https://reviews.freebsd.org/D11810
This commit is contained in:
Ian Lepore 2017-09-10 18:08:25 +00:00
parent d027ed2e7a
commit e1275c6805
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=323392
5 changed files with 281 additions and 10 deletions

View File

@ -195,6 +195,9 @@ struct a10_gpio_softc {
#define A10_GPIO_GP_INT_STA 0x214
#define A10_GPIO_GP_INT_DEB 0x218
static int a10_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *value);
static int a10_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value);
#define A10_GPIO_WRITE(_sc, _off, _val) \
bus_space_write_4(_sc->sc_bst, _sc->sc_bsh, _off, _val)
#define A10_GPIO_READ(_sc, _off) \
@ -316,29 +319,44 @@ a10_gpio_set_drv(struct a10_gpio_softc *sc, uint32_t pin, uint32_t drive)
static int
a10_gpio_pin_configure(struct a10_gpio_softc *sc, uint32_t pin, uint32_t flags)
{
u_int val;
int err = 0;
/* Must be called with lock held. */
A10_GPIO_LOCK_ASSERT(sc);
if (pin > sc->padconf->npins)
return (EINVAL);
/* Manage input/output. */
if (flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) {
if (flags & GPIO_PIN_OUTPUT)
err = a10_gpio_set_function(sc, pin, A10_GPIO_OUTPUT);
else
if (flags & GPIO_PIN_INPUT) {
err = a10_gpio_set_function(sc, pin, A10_GPIO_INPUT);
} else if (flags & GPIO_PIN_OUTPUT) {
if (flags & GPIO_PIN_PRESET_LOW) {
a10_gpio_pin_set(sc->sc_dev, pin, 0);
} else if (flags & GPIO_PIN_PRESET_HIGH) {
a10_gpio_pin_set(sc->sc_dev, pin, 1);
} else {
/* Read the pin and preset output to current state. */
err = a10_gpio_set_function(sc, pin, A10_GPIO_INPUT);
if (err == 0) {
a10_gpio_pin_get(sc->sc_dev, pin, &val);
a10_gpio_pin_set(sc->sc_dev, pin, val);
}
}
if (err == 0)
err = a10_gpio_set_function(sc, pin, A10_GPIO_OUTPUT);
}
if (err)
return (err);
/* Manage Pull-up/pull-down. */
if (flags & (GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN)) {
if (flags & GPIO_PIN_PULLUP)
a10_gpio_set_pud(sc, pin, A10_GPIO_PULLUP);
else
a10_gpio_set_pud(sc, pin, A10_GPIO_PULLDOWN);
} else
if (flags & GPIO_PIN_PULLUP)
a10_gpio_set_pud(sc, pin, A10_GPIO_PULLUP);
else if (flags & GPIO_PIN_PULLDOWN)
a10_gpio_set_pud(sc, pin, A10_GPIO_PULLDOWN);
else
a10_gpio_set_pud(sc, pin, A10_GPIO_NONE);
return (0);
@ -525,6 +543,73 @@ a10_gpio_pin_toggle(device_t dev, uint32_t pin)
return (0);
}
static int
a10_gpio_pin_access_32(device_t dev, uint32_t first_pin, uint32_t clear_pins,
uint32_t change_pins, uint32_t *orig_pins)
{
struct a10_gpio_softc *sc;
uint32_t bank, data, pin;
sc = device_get_softc(dev);
if (first_pin > sc->padconf->npins)
return (EINVAL);
/*
* We require that first_pin refers to the first pin in a bank, because
* this API is not about convenience, it's for making a set of pins
* change simultaneously (required) with reasonably high performance
* (desired); we need to do a read-modify-write on a single register.
*/
bank = sc->padconf->pins[first_pin].port;
pin = sc->padconf->pins[first_pin].pin;
if (pin != 0)
return (EINVAL);
A10_GPIO_LOCK(sc);
data = A10_GPIO_READ(sc, A10_GPIO_GP_DAT(bank));
if ((clear_pins | change_pins) != 0)
A10_GPIO_WRITE(sc, A10_GPIO_GP_DAT(bank),
(data & ~clear_pins) ^ change_pins);
A10_GPIO_UNLOCK(sc);
if (orig_pins != NULL)
*orig_pins = data;
return (0);
}
static int
a10_gpio_pin_config_32(device_t dev, uint32_t first_pin, uint32_t num_pins,
uint32_t *pin_flags)
{
struct a10_gpio_softc *sc;
uint32_t bank, pin;
int err;
sc = device_get_softc(dev);
if (first_pin > sc->padconf->npins)
return (EINVAL);
bank = sc->padconf->pins[first_pin].port;
if (sc->padconf->pins[first_pin].pin != 0)
return (EINVAL);
/*
* The configuration for a bank of pins is scattered among several
* registers; we cannot g'tee to simultaneously change the state of all
* the pins in the flags array. So just loop through the array
* configuring each pin for now. If there was a strong need, it might
* be possible to support some limited simultaneous config, such as
* adjacent groups of 8 pins that line up the same as the config regs.
*/
for (err = 0, pin = first_pin; err == 0 && pin < num_pins; ++pin) {
if (pin_flags[pin] & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT))
err = a10_gpio_pin_configure(sc, pin, pin_flags[pin]);
}
return (err);
}
static int
aw_find_pinnum_by_name(struct a10_gpio_softc *sc, const char *pinname)
{
@ -780,6 +865,8 @@ static device_method_t a10_gpio_methods[] = {
DEVMETHOD(gpio_pin_get, a10_gpio_pin_get),
DEVMETHOD(gpio_pin_set, a10_gpio_pin_set),
DEVMETHOD(gpio_pin_toggle, a10_gpio_pin_toggle),
DEVMETHOD(gpio_pin_access_32, a10_gpio_pin_access_32),
DEVMETHOD(gpio_pin_config_32, a10_gpio_pin_config_32),
DEVMETHOD(gpio_map_gpios, a10_gpio_map_gpios),
/* ofw_bus interface */

View File

@ -667,6 +667,72 @@ imx51_gpio_pin_toggle(device_t dev, uint32_t pin)
return (0);
}
static int
imx51_gpio_pin_access_32(device_t dev, uint32_t first_pin, uint32_t clear_pins,
uint32_t change_pins, uint32_t *orig_pins)
{
struct imx51_gpio_softc *sc;
if (first_pin != 0)
return (EINVAL);
sc = device_get_softc(dev);
if (orig_pins != NULL)
*orig_pins = READ4(sc, IMX_GPIO_PSR_REG);
if ((clear_pins | change_pins) != 0) {
mtx_lock_spin(&sc->sc_mtx);
WRITE4(sc, IMX_GPIO_DR_REG,
(READ4(sc, IMX_GPIO_DR_REG) & ~clear_pins) ^ change_pins);
mtx_unlock_spin(&sc->sc_mtx);
}
return (0);
}
static int
imx51_gpio_pin_config_32(device_t dev, uint32_t first_pin, uint32_t num_pins,
uint32_t *pin_flags)
{
struct imx51_gpio_softc *sc;
u_int i;
uint32_t bit, drclr, drset, flags, oeclr, oeset, pads;
sc = device_get_softc(dev);
if (first_pin != 0 || num_pins > sc->gpio_npins)
return (EINVAL);
drclr = drset = oeclr = oeset = 0;
pads = READ4(sc, IMX_GPIO_PSR_REG);
for (i = 0; i < num_pins; ++i) {
bit = 1u << i;
flags = pin_flags[i];
if (flags & GPIO_PIN_INPUT) {
oeclr |= bit;
} else if (flags & GPIO_PIN_OUTPUT) {
oeset |= bit;
if (flags & GPIO_PIN_PRESET_LOW)
drclr |= bit;
else if (flags & GPIO_PIN_PRESET_HIGH)
drset |= bit;
else /* Drive whatever it's now pulled to. */
drset |= pads & bit;
}
}
mtx_lock_spin(&sc->sc_mtx);
WRITE4(sc, IMX_GPIO_DR_REG,
(READ4(sc, IMX_GPIO_DR_REG) & ~drclr) | drset);
WRITE4(sc, IMX_GPIO_OE_REG,
(READ4(sc, IMX_GPIO_OE_REG) & ~oeclr) | oeset);
mtx_unlock_spin(&sc->sc_mtx);
return (0);
}
static int
imx51_gpio_probe(device_t dev)
{
@ -790,6 +856,8 @@ static device_method_t imx51_gpio_methods[] = {
DEVMETHOD(gpio_pin_get, imx51_gpio_pin_get),
DEVMETHOD(gpio_pin_set, imx51_gpio_pin_set),
DEVMETHOD(gpio_pin_toggle, imx51_gpio_pin_toggle),
DEVMETHOD(gpio_pin_access_32, imx51_gpio_pin_access_32),
DEVMETHOD(gpio_pin_config_32, imx51_gpio_pin_config_32),
{0, 0},
};

View File

@ -39,6 +39,13 @@ CODE {
return (NULL);
}
static int
gpio_default_nosupport(void)
{
return (EOPNOTSUPP);
}
static int
gpio_default_map_gpios(device_t bus, phandle_t dev,
phandle_t gparent, int gcells, pcell_t *gpios, uint32_t *pin,
@ -151,3 +158,31 @@ METHOD int map_gpios {
uint32_t *pin;
uint32_t *flags;
} DEFAULT gpio_default_map_gpios;
#
# Simultaneously read and/or change up to 32 adjacent pins.
# If the device cannot change the pins simultaneously, returns EOPNOTSUPP.
#
# More details about using this interface can be found in sys/gpio.h
#
METHOD int pin_access_32 {
device_t dev;
uint32_t first_pin;
uint32_t clear_pins;
uint32_t change_pins;
uint32_t *orig_pins;
} DEFAULT gpio_default_nosupport;
#
# Simultaneously configure up to 32 adjacent pins.
# This is intended to change the configuration of all the pins simultaneously,
# but unlike pin_access_32, this will not fail if the hardware can't do so.
#
# More details about using this interface can be found in sys/gpio.h
#
METHOD int pin_config_32 {
device_t dev;
uint32_t first_pin;
uint32_t num_pins;
uint32_t *pin_flags;
} DEFAULT gpio_default_nosupport;

View File

@ -125,6 +125,8 @@ gpioc_ioctl(struct cdev *cdev, u_long cmd, caddr_t arg, int fflag,
struct gpioc_softc *sc = cdev->si_drv1;
struct gpio_pin pin;
struct gpio_req req;
struct gpio_access_32 *a32;
struct gpio_config_32 *c32;
uint32_t caps;
bus = GPIO_GET_BUS(sc->sc_pdev);
@ -185,6 +187,16 @@ gpioc_ioctl(struct cdev *cdev, u_long cmd, caddr_t arg, int fflag,
res = GPIOBUS_PIN_SETNAME(bus, pin.gp_pin,
pin.gp_name);
break;
case GPIOACCESS32:
a32 = (struct gpio_access_32 *)arg;
res = GPIO_PIN_ACCESS_32(sc->sc_pdev, a32->first_pin,
a32->clear_pins, a32->orig_pins, &a32->orig_pins);
break;
case GPIOCONFIG32:
c32 = (struct gpio_config_32 *)arg;
res = GPIO_PIN_CONFIG_32(sc->sc_pdev, c32->first_pin,
c32->num_pins, c32->pin_flags);
break;
default:
return (ENOTTY);
break;

View File

@ -70,6 +70,8 @@
#define GPIO_PIN_INVIN 0x00000080 /* invert input */
#define GPIO_PIN_INVOUT 0x00000100 /* invert output */
#define GPIO_PIN_PULSATE 0x00000200 /* pulsate in hardware */
#define GPIO_PIN_PRESET_LOW 0x00000400 /* preset pin to high or */
#define GPIO_PIN_PRESET_HIGH 0x00000800 /* low before enabling output */
/* GPIO interrupt capabilities */
#define GPIO_INTR_NONE 0x00000000 /* no interrupt support */
#define GPIO_INTR_LEVEL_LOW 0x00010000 /* level trigger, low */
@ -94,6 +96,71 @@ struct gpio_req {
uint32_t gp_value; /* value */
};
/*
* gpio_access_32 / GPIOACCESS32
*
* Simultaneously read and/or change up to 32 adjacent pins.
* If the device cannot change the pins simultaneously, returns EOPNOTSUPP.
*
* This accesses an adjacent set of up to 32 pins starting at first_pin within
* the device's collection of pins. How the hardware pins are mapped to the 32
* bits in the arguments is device-specific. It is expected that lower-numbered
* pins in the device's number space map linearly to lower-ordered bits within
* the 32-bit words (i.e., bit 0 is first_pin, bit 1 is first_pin+1, etc).
* Other mappings are possible; know your device.
*
* Some devices may limit the value of first_pin to 0, or to multiples of 16 or
* 32 or some other hardware-specific number; to access pin 2 would require
* first_pin to be zero and then manipulate bit (1 << 2) in the 32-bit word.
* Invalid values in first_pin result in an EINVAL error return.
*
* The starting state of the pins is captured and stored in orig_pins, then the
* pins are set to ((starting_state & ~clear_pins) ^ change_pins).
*
* Clear Change Hardware pin after call
* 0 0 No change
* 0 1 Opposite of current value
* 1 0 Cleared
* 1 1 Set
*/
struct gpio_access_32 {
uint32_t first_pin; /* First pin in group of 32 adjacent */
uint32_t clear_pins; /* Pins are changed using: */
uint32_t change_pins; /* ((hwstate & ~clear_pins) ^ change_pins) */
uint32_t orig_pins; /* Returned hwstate of pins before change. */
};
/*
* gpio_config_32 / GPIOCONFIG32
*
* Simultaneously configure up to 32 adjacent pins. This is intended to change
* the configuration of all the pins simultaneously, such that pins configured
* for output all begin to drive the configured values simultaneously, but not
* all hardware can do that, so the driver "does the best it can" in this
* regard. Notably unlike pin_access_32(), this does NOT fail if the pins
* cannot be atomically configured; it is expected that callers understand the
* hardware and have decided to live with any such limitations it may have.
*
* The pin_flags argument is an array of GPIO_PIN_xxxx flags. If the array
* contains any GPIO_PIN_OUTPUT flags, the driver will manipulate the hardware
* such that all output pins become driven with the proper initial values
* simultaneously if it can. The elements in the array map to pins in the same
* way that bits are mapped by pin_acces_32(), and the same restrictions may
* apply. For example, to configure pins 2 and 3 it may be necessary to set
* first_pin to zero and only populate pin_flags[2] and pin_flags[3]. If a
* given array entry doesn't contain GPIO_PIN_INPUT or GPIO_PIN_OUTPUT then no
* configuration is done for that pin.
*
* Some devices may limit the value of first_pin to 0, or to multiples of 16 or
* 32 or some other hardware-specific number. Invalid values in first_pin or
* num_pins result in an error return with errno set to EINVAL.
*/
struct gpio_config_32 {
uint32_t first_pin;
uint32_t num_pins;
uint32_t pin_flags[32];
};
/*
* ioctls
*/
@ -104,5 +171,7 @@ struct gpio_req {
#define GPIOSET _IOW('G', 4, struct gpio_req)
#define GPIOTOGGLE _IOWR('G', 5, struct gpio_req)
#define GPIOSETNAME _IOW('G', 6, struct gpio_pin)
#define GPIOACCESS32 _IOWR('G', 7, struct gpio_access_32)
#define GPIOCONFIG32 _IOW('G', 8, struct gpio_config_32)
#endif /* __GPIO_H__ */