freebsd-src/sys/dev/ftgpio/ftgpio.c
Stéphane Rochoy 2c1b8eb29d ftgpio: Fix panic at attach and use better device description
Use ftgpio_group_get_status instead of inlining an imperfect
version of it to get correct register and avoid
panic: ftgpio_group_get_ioreg: invalid register 0 for group 0

Reviewed by: imp
Pull Request: https://github.com/freebsd/freebsd-src/pull/677
2023-03-02 08:17:04 -07:00

605 lines
16 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2016-2023 Stormshield
*
* 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/eventhandler.h>
#include <sys/gpio.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <machine/bus.h>
#include <dev/gpio/gpiobusvar.h>
#include <dev/superio/superio.h>
#include "gpio_if.h"
#define GPIO_LOCK_INIT(_sc) mtx_init(&(_sc)->mtx, \
device_get_nameunit(dev), NULL, MTX_DEF)
#define GPIO_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->mtx)
#define GPIO_LOCK(_sc) mtx_lock(&(_sc)->mtx)
#define GPIO_UNLOCK(_sc) mtx_unlock(&(_sc)->mtx)
#define GPIO_ASSERT_LOCKED(_sc) mtx_assert(&(_sc)->mtx, MA_OWNED)
#define GPIO_ASSERT_UNLOCKED(_sc) mtx_assert(&(_sc)->mtx, MA_NOTOWNED)
/* Global register set */
#define GPIO4_ENABLE 0x28
#define GPIO3_ENABLE 0x29
#define FULL_UR5_UR6 0x2A
#define GPIO1_ENABLE 0x2B
#define GPIO2_ENABLE 0x2C
/* Logical Device Numbers. */
#define FTGPIO_LDN_GPIO 0x06
#define FTGPIO_MAX_GROUP 6
#define FTGPIO_MAX_PIN 52
#define FTGPIO_IS_VALID_PIN(_p) ((_p) >= 0 && (_p) <= FTGPIO_MAX_PIN)
#define FTGPIO_PIN_GETINDEX(_p) ((_p) & 7)
#define FTGPIO_PIN_GETGROUP(_p) ((_p) >> 3)
#define FTGPIO_GPIO_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_PIN_INVIN | \
GPIO_PIN_INVOUT | GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)
#define GET_BIT(_v, _b) (((_v) >> (_b)) & 1)
#define FTGPIO_VERBOSE_PRINTF(dev, ...) \
do { \
if (__predict_false(bootverbose)) \
device_printf(dev, __VA_ARGS__); \
} while (0)
/*
* Note that the values are important.
* They match actual register offsets.
* See p71 and p72 of F81865's datasheet.
*/
#define REG_OUTPUT_ENABLE 0 /* Not for GPIO0 */
#define REG_OUTPUT_DATA 1
#define REG_PIN_STATUS 2
#define REG_DRIVE_ENABLE 3
#define REG_MODE_SELECT_1 4 /* Only for GPIO0 */
#define REG_MODE_SELECT_2 5 /* Only for GPIO0 */
#define REG_PULSE_WIDTH_SELECT_1 6 /* Only for GPIO0 */
#define REG_PULSE_WIDTH_SELECT_2 7 /* Only for GPIO0 */
#define REG_INTERRUPT_ENABLE 8 /* Only for GPIO0 */
#define REG_INTERRUPT_STATUS 9 /* Only for GPIO0 */
struct ftgpio_device {
uint16_t devid;
const char *descr;
} ftgpio_devices[] = {
{
.devid = 0x0704,
.descr = "Fintek F81865",
},
};
struct ftgpio_softc {
device_t dev;
device_t busdev;
struct mtx mtx;
struct gpio_pin pins[FTGPIO_MAX_PIN + 1];
};
static uint8_t
ftgpio_group_get_ioreg(struct ftgpio_softc *sc, uint8_t reg, unsigned group)
{
uint8_t ioreg;
KASSERT((group == 0 && REG_OUTPUT_DATA <= reg && reg <= REG_INTERRUPT_STATUS) || \
(group >= 1 && reg <= REG_DRIVE_ENABLE),
("%s: invalid register %u for group %u", __func__, reg, group));
ioreg = (((0xf - group) << 4) + reg);
return (ioreg);
}
static uint8_t
ftgpio_group_get_output(struct ftgpio_softc *sc, unsigned group)
{
uint8_t ioreg, val;
ioreg = ftgpio_group_get_ioreg(sc, REG_OUTPUT_DATA, group);
val = superio_read(sc->dev, ioreg);
FTGPIO_VERBOSE_PRINTF(sc->dev, "group GPIO%u output is 0x%x (ioreg=0x%x)\n",
group, val, ioreg);
return (val);
}
static void
ftgpio_group_set_output(struct ftgpio_softc *sc, unsigned group, uint8_t group_value)
{
uint8_t ioreg;
ioreg = ftgpio_group_get_ioreg(sc, REG_OUTPUT_DATA, group);
superio_write(sc->dev, ioreg, group_value);
FTGPIO_VERBOSE_PRINTF(sc->dev, "set group GPIO%u output to 0x%x (ioreg=0x%x)\n",
group, group_value, ioreg);
}
static uint8_t
ftgpio_group_get_status(struct ftgpio_softc *sc, unsigned group)
{
uint8_t ioreg;
ioreg = ftgpio_group_get_ioreg(sc, REG_PIN_STATUS, group);
return (superio_read(sc->dev, ioreg));
}
static void
ftgpio_pin_write(struct ftgpio_softc *sc, uint32_t pin_num, bool pin_value)
{
uint32_t pin_flags;
uint8_t val;
unsigned group, index;
GPIO_ASSERT_LOCKED(sc);
index = FTGPIO_PIN_GETINDEX(pin_num);
group = FTGPIO_PIN_GETGROUP(pin_num);
pin_flags = sc->pins[pin_num].gp_flags;
if ((pin_flags & (GPIO_PIN_OUTPUT)) == 0) {
FTGPIO_VERBOSE_PRINTF(sc->dev, "pin %u<GPIO%u%u> is not configured for output\n",
pin_num, group, index);
return;
}
FTGPIO_VERBOSE_PRINTF(sc->dev, "set pin %u<GPIO%u%u> to %s\n",
pin_num, group, index, (pin_value ? "on" : "off"));
val = ftgpio_group_get_output(sc, group);
if (!pin_value != !(pin_flags & GPIO_PIN_INVOUT))
val |= (1 << index);
else
val &= ~(1 << index);
ftgpio_group_set_output(sc, group, val);
}
static bool
ftgpio_pin_read(struct ftgpio_softc *sc, uint32_t pin_num)
{
uint32_t pin_flags;
unsigned group, index;
uint8_t val;
bool pin_value;
GPIO_ASSERT_LOCKED(sc);
group = FTGPIO_PIN_GETGROUP(pin_num);
index = FTGPIO_PIN_GETINDEX(pin_num);
pin_flags = sc->pins[pin_num].gp_flags;
if ((pin_flags & (GPIO_PIN_OUTPUT | GPIO_PIN_INPUT)) == 0) {
FTGPIO_VERBOSE_PRINTF(sc->dev, "pin %u<GPIO%u%u> is not configured for input or output\n",
pin_num, group, index);
return (false);
}
if (pin_flags & GPIO_PIN_OUTPUT)
val = ftgpio_group_get_output(sc, group);
else
val = ftgpio_group_get_status(sc, group);
pin_value = GET_BIT(val, index);
if (((pin_flags & (GPIO_PIN_OUTPUT|GPIO_PIN_INVOUT)) == (GPIO_PIN_OUTPUT|GPIO_PIN_INVOUT)) ||
((pin_flags & (GPIO_PIN_INPUT |GPIO_PIN_INVIN )) == (GPIO_PIN_INPUT |GPIO_PIN_INVIN)))
pin_value = !pin_value;
FTGPIO_VERBOSE_PRINTF(sc->dev, "pin %u<GPIO%u%u> is %s\n",
pin_num, group, index, (pin_value ? "on" : "off"));
return (pin_value);
}
static void
ftgpio_pin_set_drive(struct ftgpio_softc *sc, uint32_t pin_num, bool pin_drive)
{
unsigned group, index;
uint8_t group_drive, ioreg;
index = FTGPIO_PIN_GETINDEX(pin_num);
group = FTGPIO_PIN_GETGROUP(pin_num);
ioreg = ftgpio_group_get_ioreg(sc, REG_DRIVE_ENABLE, group);
group_drive = superio_read(sc->dev, ioreg);
FTGPIO_VERBOSE_PRINTF(sc->dev, "group GPIO%u drive is 0x%x (ioreg=0x%x)\n",
group, group_drive, ioreg);
if (pin_drive)
group_drive |= (1 << index); /* push pull */
else
group_drive &= ~(1 << index); /* open drain */
superio_write(sc->dev, ioreg, group_drive);
}
static bool
ftgpio_pin_is_pushpull(struct ftgpio_softc *sc, uint32_t pin_num)
{
unsigned group, index;
uint8_t group_drive, ioreg;
bool is_pushpull;
index = FTGPIO_PIN_GETINDEX(pin_num);
group = FTGPIO_PIN_GETGROUP(pin_num);
ioreg = ftgpio_group_get_ioreg(sc, REG_DRIVE_ENABLE, group);
group_drive = superio_read(sc->dev, ioreg);
FTGPIO_VERBOSE_PRINTF(sc->dev, "group GPIO%u drive is 0x%x (ioreg=0x%x)\n",
group, group_drive, ioreg);
is_pushpull = group_drive & (1 << index);
FTGPIO_VERBOSE_PRINTF(sc->dev, "pin %u<GPIO%u%u> drive is %s\n",
pin_num, group, index, (is_pushpull ? "pushpull" : "opendrain"));
return (is_pushpull);
}
static void
ftgpio_pin_set_io(struct ftgpio_softc *sc, uint32_t pin_num, bool pin_io)
{
unsigned group, index;
uint8_t group_io, ioreg;
index = FTGPIO_PIN_GETINDEX(pin_num);
group = FTGPIO_PIN_GETGROUP(pin_num);
FTGPIO_VERBOSE_PRINTF(sc->dev, "set pin %u<GPIO%u%u> io to %s\n",
pin_num, group, index, (pin_io ? "output" : "input"));
ioreg = ftgpio_group_get_ioreg(sc, REG_OUTPUT_ENABLE, group);
group_io = superio_read(sc->dev, ioreg);
FTGPIO_VERBOSE_PRINTF(sc->dev, "group GPIO%u io is 0x%x (ioreg=0x%x)\n",
group, group_io, ioreg);
if (pin_io)
group_io |= (1 << index); /* output */
else
group_io &= ~(1 << index); /* input */
superio_write(sc->dev, ioreg, group_io);
FTGPIO_VERBOSE_PRINTF(sc->dev, "set group GPIO%u io to 0x%x (ioreg=0x%x)\n",
group, group_io, ioreg);
}
static bool
ftgpio_pin_is_output(struct ftgpio_softc *sc, uint32_t pin_num)
{
unsigned group, index;
bool is_output;
index = FTGPIO_PIN_GETINDEX(pin_num);
group = FTGPIO_PIN_GETGROUP(pin_num);
is_output = ftgpio_group_get_status(sc, group) & (1 << index);
FTGPIO_VERBOSE_PRINTF(sc->dev, "pin %u<GPIO%u%u> io is %s\n",
pin_num, group, index, (is_output ? "output" : "input"));
return (is_output);
}
static int
ftgpio_pin_setflags(struct ftgpio_softc *sc, uint32_t pin_num, uint32_t pin_flags)
{
/* check flags consistency */
if ((pin_flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) ==
(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT))
return (EINVAL);
if ((pin_flags & (GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)) ==
(GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL))
return (EINVAL);
if (pin_flags & GPIO_PIN_OPENDRAIN)
ftgpio_pin_set_drive(sc, pin_num, 0 /* open drain */);
else if (pin_flags & GPIO_PIN_PUSHPULL)
ftgpio_pin_set_drive(sc, pin_num, 1 /* push pull */);
if (pin_flags & GPIO_PIN_INPUT)
ftgpio_pin_set_io(sc, pin_num, 0 /* input */);
else if (pin_flags & GPIO_PIN_OUTPUT)
ftgpio_pin_set_io(sc, pin_num, 1 /* output */);
sc->pins[pin_num].gp_flags = pin_flags;
return (0);
}
static int
ftgpio_probe(device_t dev)
{
uint16_t devid;
int i;
if (superio_vendor(dev) != SUPERIO_VENDOR_FINTEK)
return (ENXIO);
if (superio_get_type(dev) != SUPERIO_DEV_GPIO)
return (ENXIO);
/*
* There are several GPIO devices, we attach only to one of them
* and use the rest without attaching.
*/
if (superio_get_ldn(dev) != FTGPIO_LDN_GPIO)
return (ENXIO);
devid = superio_devid(dev);
for (i = 0; i < nitems(ftgpio_devices); i++) {
if (devid == ftgpio_devices[i].devid) {
device_set_desc(dev, ftgpio_devices[i].descr);
return (BUS_PROBE_DEFAULT);
}
}
return (ENXIO);
}
static int
ftgpio_attach(device_t dev)
{
struct ftgpio_softc *sc;
int i;
sc = device_get_softc(dev);
sc->dev = dev;
GPIO_LOCK_INIT(sc);
GPIO_LOCK(sc);
for (i = 0; i <= FTGPIO_MAX_PIN; i++) {
struct gpio_pin *pin;
pin = &sc->pins[i];
pin->gp_pin = i;
pin->gp_caps = FTGPIO_GPIO_CAPS;
pin->gp_flags = 0;
if (ftgpio_pin_is_output(sc, i))
pin->gp_flags |= GPIO_PIN_OUTPUT;
else
pin->gp_flags |= GPIO_PIN_INPUT;
if (ftgpio_pin_is_pushpull(sc, i))
pin->gp_flags |= GPIO_PIN_PUSHPULL;
else
pin->gp_flags |= GPIO_PIN_OPENDRAIN;
snprintf(pin->gp_name, GPIOMAXNAME, "GPIO%u%u",
FTGPIO_PIN_GETGROUP(i), FTGPIO_PIN_GETINDEX(i));
}
/* Enable all groups */
superio_write(sc->dev, GPIO1_ENABLE, 0xFF);
superio_write(sc->dev, GPIO2_ENABLE, 0xFF);
superio_write(sc->dev, GPIO3_ENABLE, 0xFF);
superio_write(sc->dev, GPIO4_ENABLE, 0xFF);
superio_write(sc->dev, FULL_UR5_UR6, 0x0A);
FTGPIO_VERBOSE_PRINTF(sc->dev, "groups GPIO1..GPIO6 enabled\n");
GPIO_UNLOCK(sc);
sc->busdev = gpiobus_attach_bus(dev);
if (sc->busdev == NULL) {
GPIO_LOCK_DESTROY(sc);
return (ENXIO);
}
return (0);
}
static int
ftgpio_detach(device_t dev)
{
struct ftgpio_softc *sc;
sc = device_get_softc(dev);
gpiobus_detach_bus(dev);
GPIO_ASSERT_UNLOCKED(sc);
GPIO_LOCK_DESTROY(sc);
return (0);
}
static device_t
ftgpio_gpio_get_bus(device_t dev)
{
struct ftgpio_softc *sc;
sc = device_get_softc(dev);
return (sc->busdev);
}
static int
ftgpio_gpio_pin_max(device_t dev, int *npins)
{
*npins = FTGPIO_MAX_PIN;
return (0);
}
static int
ftgpio_gpio_pin_set(device_t dev, uint32_t pin_num, uint32_t pin_value)
{
struct ftgpio_softc *sc;
if (!FTGPIO_IS_VALID_PIN(pin_num))
return (EINVAL);
sc = device_get_softc(dev);
GPIO_LOCK(sc);
if ((sc->pins[pin_num].gp_flags & GPIO_PIN_OUTPUT) == 0) {
GPIO_UNLOCK(sc);
return (EINVAL);
}
ftgpio_pin_write(sc, pin_num, pin_value);
GPIO_UNLOCK(sc);
return (0);
}
static int
ftgpio_gpio_pin_get(device_t dev, uint32_t pin_num, uint32_t *pin_value)
{
struct ftgpio_softc *sc;
if (!FTGPIO_IS_VALID_PIN(pin_num))
return (EINVAL);
if (pin_value == NULL)
return (EINVAL);
sc = device_get_softc(dev);
GPIO_LOCK(sc);
*pin_value = ftgpio_pin_read(sc, pin_num);
GPIO_UNLOCK(sc);
return (0);
}
static int
ftgpio_gpio_pin_toggle(device_t dev, uint32_t pin_num)
{
struct ftgpio_softc *sc;
bool pin_value;
if (!FTGPIO_IS_VALID_PIN(pin_num))
return (EINVAL);
sc = device_get_softc(dev);
GPIO_LOCK(sc);
pin_value = ftgpio_pin_read(sc, pin_num);
ftgpio_pin_write(sc, pin_num, !pin_value);
GPIO_UNLOCK(sc);
return (0);
}
static int
ftgpio_gpio_pin_getname(device_t dev, uint32_t pin_num, char *pin_name)
{
struct ftgpio_softc *sc;
if (pin_name == NULL)
return (EINVAL);
if (!FTGPIO_IS_VALID_PIN(pin_num))
return (EINVAL);
sc = device_get_softc(dev);
strlcpy(pin_name, sc->pins[pin_num].gp_name, GPIOMAXNAME);
return (0);
}
static int
ftgpio_gpio_pin_getcaps(device_t dev, uint32_t pin_num, uint32_t *pin_caps)
{
struct ftgpio_softc *sc;
if (pin_caps == NULL)
return (EINVAL);
if (!FTGPIO_IS_VALID_PIN(pin_num))
return (EINVAL);
sc = device_get_softc(dev);
*pin_caps = sc->pins[pin_num].gp_caps;
return (0);
}
static int
ftgpio_gpio_pin_getflags(device_t dev, uint32_t pin_num, uint32_t *pin_flags)
{
struct ftgpio_softc *sc;
if (pin_flags == NULL)
return (EINVAL);
if (!FTGPIO_IS_VALID_PIN(pin_num))
return (EINVAL);
sc = device_get_softc(dev);
*pin_flags = sc->pins[pin_num].gp_flags;
return (0);
}
static int
ftgpio_gpio_pin_setflags(device_t dev, uint32_t pin_num, uint32_t pin_flags)
{
struct ftgpio_softc *sc;
int ret;
if (!FTGPIO_IS_VALID_PIN(pin_num)) {
FTGPIO_VERBOSE_PRINTF(dev, "invalid pin number: %u\n", pin_num);
return (EINVAL);
}
sc = device_get_softc(dev);
/* Check for unwanted flags. */
if ((pin_flags & sc->pins[pin_num].gp_caps) != pin_flags) {
FTGPIO_VERBOSE_PRINTF(dev, "invalid pin flags 0x%x, vs caps 0x%x\n",
pin_flags, sc->pins[pin_num].gp_caps);
return (EINVAL);
}
GPIO_LOCK(sc);
ret = ftgpio_pin_setflags(sc, pin_num, pin_flags);
GPIO_UNLOCK(sc);
return (ret);
}
static device_method_t ftgpio_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, ftgpio_probe),
DEVMETHOD(device_attach, ftgpio_attach),
DEVMETHOD(device_detach, ftgpio_detach),
/* GPIO */
DEVMETHOD(gpio_get_bus, ftgpio_gpio_get_bus),
DEVMETHOD(gpio_pin_max, ftgpio_gpio_pin_max),
DEVMETHOD(gpio_pin_set, ftgpio_gpio_pin_set),
DEVMETHOD(gpio_pin_get, ftgpio_gpio_pin_get),
DEVMETHOD(gpio_pin_toggle, ftgpio_gpio_pin_toggle),
DEVMETHOD(gpio_pin_getname, ftgpio_gpio_pin_getname),
DEVMETHOD(gpio_pin_getcaps, ftgpio_gpio_pin_getcaps),
DEVMETHOD(gpio_pin_getflags, ftgpio_gpio_pin_getflags),
DEVMETHOD(gpio_pin_setflags, ftgpio_gpio_pin_setflags),
DEVMETHOD_END
};
static driver_t ftgpio_driver = {
"gpio",
ftgpio_methods,
sizeof(struct ftgpio_softc)
};
DRIVER_MODULE(ftgpio, superio, ftgpio_driver, NULL, NULL);
MODULE_DEPEND(ftgpio, gpiobus, 1, 1, 1);
MODULE_DEPEND(ftgpio, superio, 1, 1, 1);
MODULE_VERSION(ftgpio, 1);