/*      $NetBSD: mcp23xxxgpio.c,v 1.2 2022/01/17 19:38:14 thorpej Exp $ */

/*-
* Copyright (c) 2014, 2022 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Frank Kardel, and by Jason R. Thorpe.
*
* 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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>
__KERNEL_RCSID(0, "$NetBSD: mcp23xxxgpio.c,v 1.2 2022/01/17 19:38:14 thorpej Exp $");

/*
* Driver for Microchip serial I/O expansers:
*
*      MCP23008        8-bit, I2C interface
*      MCP23S08        8-bit, SPI interface
*      MCP23017        16-bit, I2C interface
*      MCP23S17        16-bit, SPI interface
*      MCP23018        16-bit (open-drain outputs), I2C interface
*      MCP23S18        16-bit (open-drain outputs), SPI interface
*
* Data sheet:
*
*      https://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf
*/

#include "gpio.h"

#include <sys/types.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/kmem.h>

#include <dev/ic/mcp23xxxgpioreg.h>
#include <dev/ic/mcp23xxxgpiovar.h>

#define PIN_BANK(p)     ((p) / MCPGPIO_PINS_PER_BANK)
#define PIN_EPIN(p)     ((p) % MCPGPIO_PINS_PER_BANK)

static uint8_t
mcpgpio_regaddr(struct mcpgpio_softc *sc, uint8_t bank, uint8_t reg)
{

       if (sc->sc_variant->type == MCPGPIO_TYPE_23x08) {
               return reg;
       }
       if (sc->sc_iocon & IOCON_BANK) {
               return REGADDR_BANK1(bank & 1, reg);
       }
       return REGADDR_BANK0(bank & 1, reg);
}

static const char *
mcpgpio_regname(uint8_t reg)
{
       static const char * const regnames[] = {
               [REG_IODIR]     =       "IODIR",
               [REG_IPOL]      =       "IPOL",
               [REG_GPINTEN]   =       "GPINTEN",
               [REG_DEFVAL]    =       "DEFVAL",
               [REG_INTCON]    =       "INTCON",
               [REG_IOCON]     =       "IOCON",
               [REG_GPPU]      =       "GPPU",
               [REG_INTF]      =       "INTF",
               [REG_INTCAP]    =       "INTCAP",
               [REG_GPIO]      =       "GPIO",
               [REG_OLAT]      =       "OLAT",
       };
       KASSERT(reg <= REG_OLAT);
       return regnames[reg];
}

static const char *
mcpgpio_bankname(struct mcpgpio_softc *sc, uint8_t bank)
{
       static const char * const banknames[] = { "A", "B" };

       if (sc->sc_variant->type == MCPGPIO_TYPE_23x08) {
               return "";
       }
       return banknames[bank & 1];
}

static int
mcpgpio__lock(struct mcpgpio_softc *sc, const char *fn)
{
       int error;

       error = sc->sc_accessops->lock(sc);
       if (__predict_false(error != 0)) {
               aprint_error_dev(sc->sc_dev,
                   "%s: unable to lock device, error=%d\n", fn, error);
       }
       return error;
}

#define mcpgpio_lock(sc)                                                \
       mcpgpio__lock((sc), __func__)

static void
mcpgpio_unlock(struct mcpgpio_softc *sc)
{
       sc->sc_accessops->unlock(sc);
}

static int
mcpgpio__read(struct mcpgpio_softc *sc, const char *fn,
   uint8_t bank, uint8_t reg, uint8_t *valp)
{
       int error;
       uint8_t regaddr = mcpgpio_regaddr(sc, bank, reg);

       error = sc->sc_accessops->read(sc, bank, regaddr, valp);
       if (__predict_false(error != 0)) {
               aprint_error_dev(sc->sc_dev,
                   "%s: unable to read %s%s[0x%02x], error=%d\n", fn,
                   mcpgpio_regname(reg), mcpgpio_bankname(sc, bank),
                   regaddr, error);
       }
       return error;
}

#define mcpgpio_read(sc, b, r, v)                                       \
       mcpgpio__read((sc), __func__, (b), (r), (v))

static int
mcpgpio__write(struct mcpgpio_softc *sc, const char *fn,
   uint8_t bank, uint8_t reg, uint8_t val)
{
       int error;
       uint8_t regaddr = mcpgpio_regaddr(sc, bank, reg);

       error = sc->sc_accessops->write(sc, bank, regaddr, val);
       if (__predict_false(error != 0)) {
               aprint_error_dev(sc->sc_dev,
                   "%s: unable to write %s%s[0x%02x], error=%d\n", fn,
                   mcpgpio_regname(reg), mcpgpio_bankname(sc, bank),
                   regaddr, error);
       }
       return error;
}

#define mcpgpio_write(sc, b, r, v)                                      \
       mcpgpio__write((sc), __func__, (b), (r), (v))

#if NGPIO > 0
/* GPIO support functions */
static int
mcpgpio_gpio_pin_read(void *arg, int pin)
{
       struct mcpgpio_softc *sc = arg;
       uint8_t data;
       int val;
       int error;

       KASSERT(pin >= 0 && pin < sc->sc_npins);

       const uint8_t bank = PIN_BANK(pin);
       const uint8_t epin = PIN_EPIN(pin);

       error = mcpgpio_lock(sc);
       if (__predict_false(error != 0)) {
               return GPIO_PIN_LOW;
       }
       error = mcpgpio_read(sc, bank, REG_GPIO, &data);
       if (error) {
               data = 0;
       }
       mcpgpio_unlock(sc);

       val = data & __BIT(epin) ? GPIO_PIN_HIGH : GPIO_PIN_LOW;

       return val;
}

static void
mcpgpio_gpio_pin_write(void *arg, int pin, int value)
{
       struct mcpgpio_softc *sc = arg;
       uint8_t data;
       int error;

       KASSERT(pin >= 0 && pin < sc->sc_npins);

       const uint8_t bank = PIN_BANK(pin);
       const uint8_t epin = PIN_EPIN(pin);

       error = mcpgpio_lock(sc);
       if (__predict_false(error != 0)) {
               return;
       }

       error = mcpgpio_read(sc, bank, REG_OLAT, &data);
       if (__predict_true(error == 0)) {
               if (value == GPIO_PIN_HIGH) {
                       data |= __BIT(epin);
               } else {
                       data &= ~__BIT(epin);
               }
               (void) mcpgpio_write(sc, bank, REG_OLAT, data);
       }

       mcpgpio_unlock(sc);
}

static void
mcpgpio_gpio_pin_ctl(void *arg, int pin, int flags)
{
       struct mcpgpio_softc *sc = arg;
       uint8_t iodir, ipol, gppu;
       int error;

       KASSERT(pin >= 0 && pin < sc->sc_npins);

       const uint8_t bank = PIN_BANK(pin);
       const uint8_t epin = PIN_EPIN(pin);
       const uint8_t bit = __BIT(epin);

       error = mcpgpio_lock(sc);
       if (__predict_false(error != 0)) {
               return;
       }

       if ((error = mcpgpio_read(sc, bank, REG_IODIR, &iodir)) != 0 ||
           (error = mcpgpio_read(sc, bank, REG_IPOL, &ipol)) != 0 ||
           (error = mcpgpio_read(sc, bank, REG_GPPU, &gppu)) != 0) {
               return;
       }

       if (flags & (GPIO_PIN_OUTPUT|GPIO_PIN_INPUT)) {
               if ((flags & GPIO_PIN_INPUT) || !(flags & GPIO_PIN_OUTPUT)) {
                       /* for safety INPUT will override output */
                       iodir |= bit;
               } else {
                       iodir &= ~bit;
               }
       }

       if (flags & GPIO_PIN_INVIN) {
               ipol |= bit;
       } else {
               ipol &= ~bit;
       }

       if (flags & GPIO_PIN_PULLUP) {
               gppu |= bit;
       } else {
               gppu &= ~bit;
       }

       (void) mcpgpio_write(sc, bank, REG_IODIR, iodir);
       (void) mcpgpio_write(sc, bank, REG_IPOL, ipol);
       (void) mcpgpio_write(sc, bank, REG_GPPU, gppu);

       mcpgpio_unlock(sc);
}
#endif /* NGPIO > 0 */

void
mcpgpio_attach(struct mcpgpio_softc *sc)
{
       int error;

       KASSERT(sc->sc_variant != NULL);

       /*
        * The SPI front-end provides the logical pin count to
        * deal with muliple chips on one chip select.
        */
       if (sc->sc_npins == 0) {
               sc->sc_npins = sc->sc_variant->type == MCPGPIO_TYPE_23x08
                   ? MCP23x08_GPIO_NPINS : MCP23x17_GPIO_NPINS;
       }
       sc->sc_gpio_pins =
           kmem_zalloc(sc->sc_npins * sizeof(*sc->sc_gpio_pins), KM_SLEEP);

       /*
        * Perform the basic setup of the device.  We program the IOCON
        * register once for each bank, even though the data sheet is
        * not clear that this is strictly necessary.
        */
       if (mcpgpio_lock(sc) != 0) {
               return;
       }
       error = mcpgpio_write(sc, 0, REG_IOCON, sc->sc_iocon);
       if (error == 0 && sc->sc_variant->type != MCPGPIO_TYPE_23x08) {
               error = mcpgpio_write(sc, 1, REG_IOCON, sc->sc_iocon);
       }
       mcpgpio_unlock(sc);
       if (error) {
               return;
       }

       /* XXX FDT glue. */

#if NGPIO > 0
       struct gpiobus_attach_args gba;
       int pin_output_caps;
       int i;

       pin_output_caps = sc->sc_variant->type == MCPGPIO_TYPE_23x18
           ? GPIO_PIN_OPENDRAIN : GPIO_PIN_PUSHPULL;

       for (i = 0; i < sc->sc_npins; i++) {
               sc->sc_gpio_pins[i].pin_num = i;
               sc->sc_gpio_pins[i].pin_caps = GPIO_PIN_INPUT |
                       GPIO_PIN_OUTPUT |
                       pin_output_caps |
                       GPIO_PIN_PULLUP |
                       GPIO_PIN_INVIN;

               /* read initial state */
               sc->sc_gpio_pins[i].pin_state =
                       mcpgpio_gpio_pin_read(sc, i);
       }

       /* create controller tag */
       sc->sc_gpio_gc.gp_cookie = sc;
       sc->sc_gpio_gc.gp_pin_read = mcpgpio_gpio_pin_read;
       sc->sc_gpio_gc.gp_pin_write = mcpgpio_gpio_pin_write;
       sc->sc_gpio_gc.gp_pin_ctl = mcpgpio_gpio_pin_ctl;

       gba.gba_gc = &sc->sc_gpio_gc;
       gba.gba_pins = sc->sc_gpio_pins;
       gba.gba_npins = sc->sc_npins;

       config_found(sc->sc_dev, &gba, gpiobus_print,
           CFARGS(.devhandle = device_handle(sc->sc_dev)));
#else
       aprint_normal_dev(sc->sc_dev, "no GPIO configured in kernel");
#endif
}