Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 10 Sep 2017 18:08:25 +0000 (UTC)
From:      Ian Lepore <ian@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r323392 - in head/sys: arm/allwinner arm/freescale/imx dev/gpio sys
Message-ID:  <201709101808.v8AI8Pm9039095@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: ian
Date: Sun Sep 10 18:08:25 2017
New Revision: 323392
URL: https://svnweb.freebsd.org/changeset/base/323392

Log:
  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

Modified:
  head/sys/arm/allwinner/a10_gpio.c
  head/sys/arm/freescale/imx/imx_gpio.c
  head/sys/dev/gpio/gpio_if.m
  head/sys/dev/gpio/gpioc.c
  head/sys/sys/gpio.h

Modified: head/sys/arm/allwinner/a10_gpio.c
==============================================================================
--- head/sys/arm/allwinner/a10_gpio.c	Sun Sep 10 17:46:03 2017	(r323391)
+++ head/sys/arm/allwinner/a10_gpio.c	Sun Sep 10 18:08:25 2017	(r323392)
@@ -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 p
 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);
@@ -526,6 +544,73 @@ a10_gpio_pin_toggle(device_t dev, uint32_t pin)
 }
 
 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)
 {
 	int i;
@@ -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 */

Modified: head/sys/arm/freescale/imx/imx_gpio.c
==============================================================================
--- head/sys/arm/freescale/imx/imx_gpio.c	Sun Sep 10 17:46:03 2017	(r323391)
+++ head/sys/arm/freescale/imx/imx_gpio.c	Sun Sep 10 18:08:25 2017	(r323392)
@@ -668,6 +668,72 @@ imx51_gpio_pin_toggle(device_t dev, uint32_t pin)
 }
 
 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},
 };
 

Modified: head/sys/dev/gpio/gpio_if.m
==============================================================================
--- head/sys/dev/gpio/gpio_if.m	Sun Sep 10 17:46:03 2017	(r323391)
+++ head/sys/dev/gpio/gpio_if.m	Sun Sep 10 18:08:25 2017	(r323392)
@@ -40,6 +40,13 @@ CODE {
 	}
 
 	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,
 	    uint32_t *flags)
@@ -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;

Modified: head/sys/dev/gpio/gpioc.c
==============================================================================
--- head/sys/dev/gpio/gpioc.c	Sun Sep 10 17:46:03 2017	(r323391)
+++ head/sys/dev/gpio/gpioc.c	Sun Sep 10 18:08:25 2017	(r323392)
@@ -125,6 +125,8 @@ gpioc_ioctl(struct cdev *cdev, u_long cmd, caddr_t arg
 	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);
@@ -184,6 +186,16 @@ gpioc_ioctl(struct cdev *cdev, u_long cmd, caddr_t arg
 			dprintf("set name on pin %d\n", pin.gp_pin);
 			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);

Modified: head/sys/sys/gpio.h
==============================================================================
--- head/sys/sys/gpio.h	Sun Sep 10 17:46:03 2017	(r323391)
+++ head/sys/sys/gpio.h	Sun Sep 10 18:08:25 2017	(r323392)
@@ -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 */
@@ -95,6 +97,71 @@ struct gpio_req {
 };
 
 /*
+ * 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
  */
 #define GPIOMAXPIN		_IOR('G', 0, int)
@@ -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__ */



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201709101808.v8AI8Pm9039095>