Add the GPIO driver for the North/South bridge in Marvell Armada 37x0.

The A3700 has a different GPIO controller and thus, do not use the old (and
shared) code for Marvell.

The pinctrl driver, also part of the controller, is not supported yet (but
the implementation should be straightforward).

Sponsored by:	Rubicon Communications, LLC (Netgate)
This commit is contained in:
loos 2019-06-10 21:27:21 +00:00
parent ee63b95958
commit 8b044e9dba
3 changed files with 354 additions and 0 deletions

352
sys/arm/mv/a37x0_gpio.c Normal file
View File

@ -0,0 +1,352 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2018-2019, Rubicon Communications, LLC (Netgate)
* 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 ``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 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/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/gpio.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <dev/gpio/gpiobusvar.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include "gpio_if.h"
static struct resource_spec a37x0_gpio_res_spec[] = {
{ SYS_RES_MEMORY, 0, RF_ACTIVE }, /* Pinctl / GPIO */
{ SYS_RES_MEMORY, 1, RF_ACTIVE }, /* Interrupts control */
{ -1, 0, 0 }
};
struct a37x0_gpio_softc {
bus_space_tag_t sc_bst;
bus_space_handle_t sc_bsh;
device_t sc_busdev;
int sc_type;
uint32_t sc_max_pins;
uint32_t sc_npins;
struct resource *sc_mem_res[nitems(a37x0_gpio_res_spec) - 1];
};
/* Memory regions. */
#define A37X0_GPIO 0
#define A37X0_INTR 1
/* North Bridge / South Bridge. */
#define A37X0_NB_GPIO 1
#define A37X0_SB_GPIO 2
#define A37X0_GPIO_WRITE(_sc, _off, _val) \
bus_space_write_4((_sc)->sc_bst, (_sc)->sc_bsh, (_off), (_val))
#define A37X0_GPIO_READ(_sc, _off) \
bus_space_read_4((_sc)->sc_bst, (_sc)->sc_bsh, (_off))
#define A37X0_GPIO_BIT(_p) (1U << ((_p) % 32))
#define A37X0_GPIO_OUT_EN(_p) (0x0 + ((_p) / 32) * 4)
#define A37X0_GPIO_LATCH(_p) (0x8 + ((_p) / 32) * 4)
#define A37X0_GPIO_INPUT(_p) (0x10 + ((_p) / 32) * 4)
#define A37X0_GPIO_OUTPUT(_p) (0x18 + ((_p) / 32) * 4)
#define A37X0_GPIO_SEL 0x30
static struct ofw_compat_data compat_data[] = {
{ "marvell,armada3710-nb-pinctrl", A37X0_NB_GPIO },
{ "marvell,armada3710-sb-pinctrl", A37X0_SB_GPIO },
{ NULL, 0 }
};
static phandle_t
a37x0_gpio_get_node(device_t bus, device_t dev)
{
return (ofw_bus_get_node(bus));
}
static device_t
a37x0_gpio_get_bus(device_t dev)
{
struct a37x0_gpio_softc *sc;
sc = device_get_softc(dev);
return (sc->sc_busdev);
}
static int
a37x0_gpio_pin_max(device_t dev, int *maxpin)
{
struct a37x0_gpio_softc *sc;
sc = device_get_softc(dev);
*maxpin = sc->sc_npins - 1;
return (0);
}
static int
a37x0_gpio_pin_getname(device_t dev, uint32_t pin, char *name)
{
struct a37x0_gpio_softc *sc;
sc = device_get_softc(dev);
if (pin >= sc->sc_npins)
return (EINVAL);
snprintf(name, GPIOMAXNAME, "pin %d", pin);
return (0);
}
static int
a37x0_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps)
{
struct a37x0_gpio_softc *sc;
sc = device_get_softc(dev);
if (pin >= sc->sc_npins)
return (EINVAL);
*caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT;
return (0);
}
static int
a37x0_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags)
{
struct a37x0_gpio_softc *sc;
uint32_t reg;
sc = device_get_softc(dev);
if (pin >= sc->sc_npins)
return (EINVAL);
reg = A37X0_GPIO_READ(sc, A37X0_GPIO_OUT_EN(pin));
if ((reg & A37X0_GPIO_BIT(pin)) != 0)
*flags = GPIO_PIN_OUTPUT;
else
*flags = GPIO_PIN_INPUT;
return (0);
}
static int
a37x0_gpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags)
{
struct a37x0_gpio_softc *sc;
uint32_t reg;
sc = device_get_softc(dev);
if (pin >= sc->sc_npins)
return (EINVAL);
reg = A37X0_GPIO_READ(sc, A37X0_GPIO_OUT_EN(pin));
if (flags & GPIO_PIN_OUTPUT)
reg |= A37X0_GPIO_BIT(pin);
else
reg &= ~A37X0_GPIO_BIT(pin);
A37X0_GPIO_WRITE(sc, A37X0_GPIO_OUT_EN(pin), reg);
return (0);
}
static int
a37x0_gpio_pin_get(device_t dev, uint32_t pin, unsigned int *val)
{
struct a37x0_gpio_softc *sc;
uint32_t reg;
sc = device_get_softc(dev);
if (pin >= sc->sc_npins)
return (EINVAL);
reg = A37X0_GPIO_READ(sc, A37X0_GPIO_OUT_EN(pin));
if ((reg & A37X0_GPIO_BIT(pin)) != 0)
reg = A37X0_GPIO_READ(sc, A37X0_GPIO_OUTPUT(pin));
else
reg = A37X0_GPIO_READ(sc, A37X0_GPIO_INPUT(pin));
*val = ((reg & A37X0_GPIO_BIT(pin)) != 0) ? 1 : 0;
return (0);
}
static int
a37x0_gpio_pin_set(device_t dev, uint32_t pin, unsigned int val)
{
struct a37x0_gpio_softc *sc;
uint32_t reg;
sc = device_get_softc(dev);
if (pin >= sc->sc_npins)
return (EINVAL);
reg = A37X0_GPIO_READ(sc, A37X0_GPIO_OUTPUT(pin));
if (val != 0)
reg |= A37X0_GPIO_BIT(pin);
else
reg &= ~A37X0_GPIO_BIT(pin);
A37X0_GPIO_WRITE(sc, A37X0_GPIO_OUTPUT(pin), reg);
return (0);
}
static int
a37x0_gpio_pin_toggle(device_t dev, uint32_t pin)
{
struct a37x0_gpio_softc *sc;
uint32_t reg;
sc = device_get_softc(dev);
if (pin >= sc->sc_npins)
return (EINVAL);
reg = A37X0_GPIO_READ(sc, A37X0_GPIO_OUT_EN(pin));
if ((reg & A37X0_GPIO_BIT(pin)) == 0)
return (EINVAL);
reg = A37X0_GPIO_READ(sc, A37X0_GPIO_OUTPUT(pin));
reg ^= A37X0_GPIO_BIT(pin);
A37X0_GPIO_WRITE(sc, A37X0_GPIO_OUTPUT(pin), reg);
return (0);
}
static int
a37x0_gpio_probe(device_t dev)
{
const char *desc;
struct a37x0_gpio_softc *sc;
if (!OF_hasprop(ofw_bus_get_node(dev), "gpio-controller"))
return (ENXIO);
sc = device_get_softc(dev);
sc->sc_type = ofw_bus_search_compatible(
device_get_parent(dev), compat_data)->ocd_data;
switch (sc->sc_type) {
case A37X0_NB_GPIO:
sc->sc_max_pins = 36;
desc = "Armada 37x0 North Bridge GPIO Controller";
break;
case A37X0_SB_GPIO:
sc->sc_max_pins = 30;
desc = "Armada 37x0 South Bridge GPIO Controller";
break;
default:
return (ENXIO);
}
device_set_desc(dev, desc);
return (BUS_PROBE_DEFAULT);
}
static int
a37x0_gpio_attach(device_t dev)
{
int err, ncells;
pcell_t *ranges;
struct a37x0_gpio_softc *sc;
sc = device_get_softc(dev);
/* Read and verify the "gpio-ranges" property. */
ncells = OF_getencprop_alloc(ofw_bus_get_node(dev), "gpio-ranges",
(void **)&ranges);
if (ncells == -1)
return (ENXIO);
if (ncells != sizeof(*ranges) * 4 || ranges[1] != 0 || ranges[2] != 0) {
OF_prop_free(ranges);
return (ENXIO);
}
sc->sc_npins = ranges[3];
OF_prop_free(ranges);
/* Check the number of pins in the DTS vs HW capabilities. */
if (sc->sc_npins > sc->sc_max_pins)
return (ENXIO);
err = bus_alloc_resources(dev, a37x0_gpio_res_spec, sc->sc_mem_res);
if (err != 0) {
device_printf(dev, "cannot allocate memory window\n");
return (ENXIO);
}
sc->sc_bst = rman_get_bustag(sc->sc_mem_res[A37X0_GPIO]);
sc->sc_bsh = rman_get_bushandle(sc->sc_mem_res[A37X0_GPIO]);
sc->sc_busdev = gpiobus_attach_bus(dev);
if (sc->sc_busdev == NULL)
return (ENXIO);
return (0);
}
static int
a37x0_gpio_detach(device_t dev)
{
return (EBUSY);
}
static device_method_t a37x0_gpio_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, a37x0_gpio_probe),
DEVMETHOD(device_attach, a37x0_gpio_attach),
DEVMETHOD(device_detach, a37x0_gpio_detach),
/* GPIO interface */
DEVMETHOD(gpio_get_bus, a37x0_gpio_get_bus),
DEVMETHOD(gpio_pin_max, a37x0_gpio_pin_max),
DEVMETHOD(gpio_pin_getname, a37x0_gpio_pin_getname),
DEVMETHOD(gpio_pin_getcaps, a37x0_gpio_pin_getcaps),
DEVMETHOD(gpio_pin_getflags, a37x0_gpio_pin_getflags),
DEVMETHOD(gpio_pin_setflags, a37x0_gpio_pin_setflags),
DEVMETHOD(gpio_pin_get, a37x0_gpio_pin_get),
DEVMETHOD(gpio_pin_set, a37x0_gpio_pin_set),
DEVMETHOD(gpio_pin_toggle, a37x0_gpio_pin_toggle),
/* ofw_bus interface */
DEVMETHOD(ofw_bus_get_node, a37x0_gpio_get_node),
DEVMETHOD_END
};
static devclass_t a37x0_gpio_devclass;
static driver_t a37x0_gpio_driver = {
"gpio",
a37x0_gpio_methods,
sizeof(struct a37x0_gpio_softc),
};
EARLY_DRIVER_MODULE(a37x0_gpio, simple_mfd, a37x0_gpio_driver,
a37x0_gpio_devclass, 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LAST);

View File

@ -223,6 +223,7 @@ device a10_codec
device a31_dmac
# GPIO / PINCTRL
device a37x0_gpio # Marvell Armada 37x0 GPIO controller
device aw_gpio # Allwinner GPIO controller
device gpio
device gpioled

View File

@ -95,6 +95,7 @@ arm/broadcom/bcm2835/bcm2835_vcio.c optional soc_brcm_bcm2837 fdt
arm/broadcom/bcm2835/bcm2835_wdog.c optional soc_brcm_bcm2837 fdt
arm/broadcom/bcm2835/bcm2836.c optional soc_brcm_bcm2837 fdt
arm/broadcom/bcm2835/bcm283x_dwc_fdt.c optional dwcotg fdt soc_brcm_bcm2837
arm/mv/a37x0_gpio.c optional a37x0_gpio gpio fdt
arm/mv/gpio.c optional mv_gpio fdt
arm/mv/mvebu_pinctrl.c optional mvebu_pinctrl fdt
arm/mv/mv_cp110_icu.c optional mv_cp110_icu fdt