Training courses

Kernel and Embedded Linux

Bootlin training courses

Embedded Linux, kernel,
Yocto Project, Buildroot, real-time,
graphics, boot time, debugging...

Bootlin logo

Elixir Cross Referencer

/*      $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
}