xref: /netbsd-src/sys/dev/ic/mcp23xxxgpio.c (revision 6690c9f22f3caebb101ec0e4aec2b09ee28fa80f)
1*6690c9f2Sthorpej /*      $NetBSD: mcp23xxxgpio.c,v 1.2 2022/01/17 19:38:14 thorpej Exp $	*/
25eb438adSthorpej 
35eb438adSthorpej /*-
45eb438adSthorpej  * Copyright (c) 2014, 2022 The NetBSD Foundation, Inc.
55eb438adSthorpej  * All rights reserved.
65eb438adSthorpej  *
75eb438adSthorpej  * This code is derived from software contributed to The NetBSD Foundation
85eb438adSthorpej  * by Frank Kardel, and by Jason R. Thorpe.
95eb438adSthorpej  *
105eb438adSthorpej  * Redistribution and use in source and binary forms, with or without
115eb438adSthorpej  * modification, are permitted provided that the following conditions
125eb438adSthorpej  * are met:
135eb438adSthorpej  * 1. Redistributions of source code must retain the above copyright
145eb438adSthorpej  *    notice, this list of conditions and the following disclaimer.
155eb438adSthorpej  * 2. Redistributions in binary form must reproduce the above copyright
165eb438adSthorpej  *    notice, this list of conditions and the following disclaimer in the
175eb438adSthorpej  *    documentation and/or other materials provided with the distribution.
185eb438adSthorpej  *
195eb438adSthorpej  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
205eb438adSthorpej  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
215eb438adSthorpej  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
225eb438adSthorpej  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
235eb438adSthorpej  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
245eb438adSthorpej  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
255eb438adSthorpej  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
265eb438adSthorpej  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
275eb438adSthorpej  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
285eb438adSthorpej  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
295eb438adSthorpej  * POSSIBILITY OF SUCH DAMAGE.
305eb438adSthorpej  */
315eb438adSthorpej 
325eb438adSthorpej #include <sys/cdefs.h>
33*6690c9f2Sthorpej __KERNEL_RCSID(0, "$NetBSD: mcp23xxxgpio.c,v 1.2 2022/01/17 19:38:14 thorpej Exp $");
345eb438adSthorpej 
355eb438adSthorpej /*
365eb438adSthorpej  * Driver for Microchip serial I/O expansers:
375eb438adSthorpej  *
385eb438adSthorpej  *	MCP23008	8-bit, I2C interface
395eb438adSthorpej  *	MCP23S08	8-bit, SPI interface
405eb438adSthorpej  *	MCP23017	16-bit, I2C interface
415eb438adSthorpej  *	MCP23S17	16-bit, SPI interface
425eb438adSthorpej  *	MCP23018	16-bit (open-drain outputs), I2C interface
435eb438adSthorpej  *	MCP23S18	16-bit (open-drain outputs), SPI interface
445eb438adSthorpej  *
455eb438adSthorpej  * Data sheet:
465eb438adSthorpej  *
475eb438adSthorpej  *	https://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf
485eb438adSthorpej  */
495eb438adSthorpej 
505eb438adSthorpej #include "gpio.h"
515eb438adSthorpej 
525eb438adSthorpej #include <sys/types.h>
535eb438adSthorpej #include <sys/systm.h>
545eb438adSthorpej #include <sys/device.h>
555eb438adSthorpej #include <sys/kernel.h>
565eb438adSthorpej #include <sys/kmem.h>
575eb438adSthorpej 
585eb438adSthorpej #include <dev/ic/mcp23xxxgpioreg.h>
595eb438adSthorpej #include <dev/ic/mcp23xxxgpiovar.h>
605eb438adSthorpej 
615eb438adSthorpej #define	PIN_BANK(p)	((p) / MCPGPIO_PINS_PER_BANK)
625eb438adSthorpej #define	PIN_EPIN(p)	((p) % MCPGPIO_PINS_PER_BANK)
635eb438adSthorpej 
645eb438adSthorpej static uint8_t
mcpgpio_regaddr(struct mcpgpio_softc * sc,uint8_t bank,uint8_t reg)655eb438adSthorpej mcpgpio_regaddr(struct mcpgpio_softc *sc, uint8_t bank, uint8_t reg)
665eb438adSthorpej {
675eb438adSthorpej 
685eb438adSthorpej 	if (sc->sc_variant->type == MCPGPIO_TYPE_23x08) {
695eb438adSthorpej 		return reg;
705eb438adSthorpej 	}
715eb438adSthorpej 	if (sc->sc_iocon & IOCON_BANK) {
725eb438adSthorpej 		return REGADDR_BANK1(bank & 1, reg);
735eb438adSthorpej 	}
745eb438adSthorpej 	return REGADDR_BANK0(bank & 1, reg);
755eb438adSthorpej }
765eb438adSthorpej 
775eb438adSthorpej static const char *
mcpgpio_regname(uint8_t reg)785eb438adSthorpej mcpgpio_regname(uint8_t reg)
795eb438adSthorpej {
805eb438adSthorpej 	static const char * const regnames[] = {
815eb438adSthorpej 		[REG_IODIR]	=	"IODIR",
825eb438adSthorpej 		[REG_IPOL]	=	"IPOL",
835eb438adSthorpej 		[REG_GPINTEN]	=	"GPINTEN",
845eb438adSthorpej 		[REG_DEFVAL]	=	"DEFVAL",
855eb438adSthorpej 		[REG_INTCON]	=	"INTCON",
865eb438adSthorpej 		[REG_IOCON]	=	"IOCON",
875eb438adSthorpej 		[REG_GPPU]	=	"GPPU",
885eb438adSthorpej 		[REG_INTF]	=	"INTF",
895eb438adSthorpej 		[REG_INTCAP]	=	"INTCAP",
905eb438adSthorpej 		[REG_GPIO]	=	"GPIO",
915eb438adSthorpej 		[REG_OLAT]	=	"OLAT",
925eb438adSthorpej 	};
935eb438adSthorpej 	KASSERT(reg <= REG_OLAT);
945eb438adSthorpej 	return regnames[reg];
955eb438adSthorpej }
965eb438adSthorpej 
975eb438adSthorpej static const char *
mcpgpio_bankname(struct mcpgpio_softc * sc,uint8_t bank)985eb438adSthorpej mcpgpio_bankname(struct mcpgpio_softc *sc, uint8_t bank)
995eb438adSthorpej {
1005eb438adSthorpej 	static const char * const banknames[] = { "A", "B" };
1015eb438adSthorpej 
1025eb438adSthorpej 	if (sc->sc_variant->type == MCPGPIO_TYPE_23x08) {
1035eb438adSthorpej 		return "";
1045eb438adSthorpej 	}
1055eb438adSthorpej 	return banknames[bank & 1];
1065eb438adSthorpej }
1075eb438adSthorpej 
1085eb438adSthorpej static int
mcpgpio__lock(struct mcpgpio_softc * sc,const char * fn)1095eb438adSthorpej mcpgpio__lock(struct mcpgpio_softc *sc, const char *fn)
1105eb438adSthorpej {
1115eb438adSthorpej 	int error;
1125eb438adSthorpej 
1135eb438adSthorpej 	error = sc->sc_accessops->lock(sc);
1145eb438adSthorpej 	if (__predict_false(error != 0)) {
1155eb438adSthorpej 		aprint_error_dev(sc->sc_dev,
1165eb438adSthorpej 		    "%s: unable to lock device, error=%d\n", fn, error);
1175eb438adSthorpej 	}
1185eb438adSthorpej 	return error;
1195eb438adSthorpej }
1205eb438adSthorpej 
1215eb438adSthorpej #define	mcpgpio_lock(sc)						\
1225eb438adSthorpej 	mcpgpio__lock((sc), __func__)
1235eb438adSthorpej 
1245eb438adSthorpej static void
mcpgpio_unlock(struct mcpgpio_softc * sc)1255eb438adSthorpej mcpgpio_unlock(struct mcpgpio_softc *sc)
1265eb438adSthorpej {
1275eb438adSthorpej 	sc->sc_accessops->unlock(sc);
1285eb438adSthorpej }
1295eb438adSthorpej 
1305eb438adSthorpej static int
mcpgpio__read(struct mcpgpio_softc * sc,const char * fn,uint8_t bank,uint8_t reg,uint8_t * valp)1315eb438adSthorpej mcpgpio__read(struct mcpgpio_softc *sc, const char *fn,
1325eb438adSthorpej     uint8_t bank, uint8_t reg, uint8_t *valp)
1335eb438adSthorpej {
1345eb438adSthorpej 	int error;
1355eb438adSthorpej 	uint8_t regaddr = mcpgpio_regaddr(sc, bank, reg);
1365eb438adSthorpej 
1375eb438adSthorpej 	error = sc->sc_accessops->read(sc, bank, regaddr, valp);
1385eb438adSthorpej 	if (__predict_false(error != 0)) {
1395eb438adSthorpej 		aprint_error_dev(sc->sc_dev,
1405eb438adSthorpej 		    "%s: unable to read %s%s[0x%02x], error=%d\n", fn,
1415eb438adSthorpej 		    mcpgpio_regname(reg), mcpgpio_bankname(sc, bank),
1425eb438adSthorpej 		    regaddr, error);
1435eb438adSthorpej 	}
1445eb438adSthorpej 	return error;
1455eb438adSthorpej }
1465eb438adSthorpej 
1475eb438adSthorpej #define	mcpgpio_read(sc, b, r, v)					\
1485eb438adSthorpej 	mcpgpio__read((sc), __func__, (b), (r), (v))
1495eb438adSthorpej 
1505eb438adSthorpej static int
mcpgpio__write(struct mcpgpio_softc * sc,const char * fn,uint8_t bank,uint8_t reg,uint8_t val)1515eb438adSthorpej mcpgpio__write(struct mcpgpio_softc *sc, const char *fn,
1525eb438adSthorpej     uint8_t bank, uint8_t reg, uint8_t val)
1535eb438adSthorpej {
1545eb438adSthorpej 	int error;
1555eb438adSthorpej 	uint8_t regaddr = mcpgpio_regaddr(sc, bank, reg);
1565eb438adSthorpej 
1575eb438adSthorpej 	error = sc->sc_accessops->write(sc, bank, regaddr, val);
1585eb438adSthorpej 	if (__predict_false(error != 0)) {
1595eb438adSthorpej 		aprint_error_dev(sc->sc_dev,
1605eb438adSthorpej 		    "%s: unable to write %s%s[0x%02x], error=%d\n", fn,
1615eb438adSthorpej 		    mcpgpio_regname(reg), mcpgpio_bankname(sc, bank),
1625eb438adSthorpej 		    regaddr, error);
1635eb438adSthorpej 	}
1645eb438adSthorpej 	return error;
1655eb438adSthorpej }
1665eb438adSthorpej 
1675eb438adSthorpej #define	mcpgpio_write(sc, b, r, v)					\
1685eb438adSthorpej 	mcpgpio__write((sc), __func__, (b), (r), (v))
1695eb438adSthorpej 
1705eb438adSthorpej #if NGPIO > 0
1715eb438adSthorpej /* GPIO support functions */
1725eb438adSthorpej static int
mcpgpio_gpio_pin_read(void * arg,int pin)1735eb438adSthorpej mcpgpio_gpio_pin_read(void *arg, int pin)
1745eb438adSthorpej {
1755eb438adSthorpej 	struct mcpgpio_softc *sc = arg;
1765eb438adSthorpej 	uint8_t data;
1775eb438adSthorpej 	int val;
1785eb438adSthorpej 	int error;
1795eb438adSthorpej 
1805eb438adSthorpej 	KASSERT(pin >= 0 && pin < sc->sc_npins);
1815eb438adSthorpej 
1825eb438adSthorpej 	const uint8_t bank = PIN_BANK(pin);
1835eb438adSthorpej 	const uint8_t epin = PIN_EPIN(pin);
1845eb438adSthorpej 
1855eb438adSthorpej 	error = mcpgpio_lock(sc);
1865eb438adSthorpej 	if (__predict_false(error != 0)) {
1875eb438adSthorpej 		return GPIO_PIN_LOW;
1885eb438adSthorpej 	}
1895eb438adSthorpej 	error = mcpgpio_read(sc, bank, REG_GPIO, &data);
1905eb438adSthorpej 	if (error) {
1915eb438adSthorpej 		data = 0;
1925eb438adSthorpej 	}
1935eb438adSthorpej 	mcpgpio_unlock(sc);
1945eb438adSthorpej 
1955eb438adSthorpej 	val = data & __BIT(epin) ? GPIO_PIN_HIGH : GPIO_PIN_LOW;
1965eb438adSthorpej 
1975eb438adSthorpej 	return val;
1985eb438adSthorpej }
1995eb438adSthorpej 
2005eb438adSthorpej static void
mcpgpio_gpio_pin_write(void * arg,int pin,int value)2015eb438adSthorpej mcpgpio_gpio_pin_write(void *arg, int pin, int value)
2025eb438adSthorpej {
2035eb438adSthorpej 	struct mcpgpio_softc *sc = arg;
2045eb438adSthorpej 	uint8_t data;
2055eb438adSthorpej 	int error;
2065eb438adSthorpej 
2075eb438adSthorpej 	KASSERT(pin >= 0 && pin < sc->sc_npins);
2085eb438adSthorpej 
2095eb438adSthorpej 	const uint8_t bank = PIN_BANK(pin);
2105eb438adSthorpej 	const uint8_t epin = PIN_EPIN(pin);
2115eb438adSthorpej 
2125eb438adSthorpej 	error = mcpgpio_lock(sc);
2135eb438adSthorpej 	if (__predict_false(error != 0)) {
2145eb438adSthorpej 		return;
2155eb438adSthorpej 	}
2165eb438adSthorpej 
2175eb438adSthorpej 	error = mcpgpio_read(sc, bank, REG_OLAT, &data);
2185eb438adSthorpej 	if (__predict_true(error == 0)) {
2195eb438adSthorpej 		if (value == GPIO_PIN_HIGH) {
2205eb438adSthorpej 			data |= __BIT(epin);
2215eb438adSthorpej 		} else {
2225eb438adSthorpej 			data &= ~__BIT(epin);
2235eb438adSthorpej 		}
2245eb438adSthorpej 		(void) mcpgpio_write(sc, bank, REG_OLAT, data);
2255eb438adSthorpej 	}
2265eb438adSthorpej 
2275eb438adSthorpej 	mcpgpio_unlock(sc);
2285eb438adSthorpej }
2295eb438adSthorpej 
2305eb438adSthorpej static void
mcpgpio_gpio_pin_ctl(void * arg,int pin,int flags)2315eb438adSthorpej mcpgpio_gpio_pin_ctl(void *arg, int pin, int flags)
2325eb438adSthorpej {
2335eb438adSthorpej 	struct mcpgpio_softc *sc = arg;
2345eb438adSthorpej 	uint8_t iodir, ipol, gppu;
2355eb438adSthorpej 	int error;
2365eb438adSthorpej 
2375eb438adSthorpej 	KASSERT(pin >= 0 && pin < sc->sc_npins);
2385eb438adSthorpej 
2395eb438adSthorpej 	const uint8_t bank = PIN_BANK(pin);
2405eb438adSthorpej 	const uint8_t epin = PIN_EPIN(pin);
2415eb438adSthorpej 	const uint8_t bit = __BIT(epin);
2425eb438adSthorpej 
2435eb438adSthorpej 	error = mcpgpio_lock(sc);
2445eb438adSthorpej 	if (__predict_false(error != 0)) {
2455eb438adSthorpej 		return;
2465eb438adSthorpej 	}
2475eb438adSthorpej 
2485eb438adSthorpej 	if ((error = mcpgpio_read(sc, bank, REG_IODIR, &iodir)) != 0 ||
2495eb438adSthorpej 	    (error = mcpgpio_read(sc, bank, REG_IPOL, &ipol)) != 0 ||
2505eb438adSthorpej 	    (error = mcpgpio_read(sc, bank, REG_GPPU, &gppu)) != 0) {
2515eb438adSthorpej 		return;
2525eb438adSthorpej 	}
2535eb438adSthorpej 
2545eb438adSthorpej 	if (flags & (GPIO_PIN_OUTPUT|GPIO_PIN_INPUT)) {
2555eb438adSthorpej 		if ((flags & GPIO_PIN_INPUT) || !(flags & GPIO_PIN_OUTPUT)) {
2565eb438adSthorpej 			/* for safety INPUT will override output */
2575eb438adSthorpej 			iodir |= bit;
2585eb438adSthorpej 		} else {
2595eb438adSthorpej 			iodir &= ~bit;
2605eb438adSthorpej 		}
2615eb438adSthorpej 	}
2625eb438adSthorpej 
2635eb438adSthorpej 	if (flags & GPIO_PIN_INVIN) {
2645eb438adSthorpej 		ipol |= bit;
2655eb438adSthorpej 	} else {
2665eb438adSthorpej 		ipol &= ~bit;
2675eb438adSthorpej 	}
2685eb438adSthorpej 
2695eb438adSthorpej 	if (flags & GPIO_PIN_PULLUP) {
2705eb438adSthorpej 		gppu |= bit;
2715eb438adSthorpej 	} else {
2725eb438adSthorpej 		gppu &= ~bit;
2735eb438adSthorpej 	}
2745eb438adSthorpej 
2755eb438adSthorpej 	(void) mcpgpio_write(sc, bank, REG_IODIR, iodir);
2765eb438adSthorpej 	(void) mcpgpio_write(sc, bank, REG_IPOL, ipol);
2775eb438adSthorpej 	(void) mcpgpio_write(sc, bank, REG_GPPU, gppu);
2785eb438adSthorpej 
2795eb438adSthorpej 	mcpgpio_unlock(sc);
2805eb438adSthorpej }
2815eb438adSthorpej #endif /* NGPIO > 0 */
2825eb438adSthorpej 
2835eb438adSthorpej void
mcpgpio_attach(struct mcpgpio_softc * sc)2845eb438adSthorpej mcpgpio_attach(struct mcpgpio_softc *sc)
2855eb438adSthorpej {
2865eb438adSthorpej 	int error;
2875eb438adSthorpej 
2885eb438adSthorpej 	KASSERT(sc->sc_variant != NULL);
2895eb438adSthorpej 
2905eb438adSthorpej 	/*
2915eb438adSthorpej 	 * The SPI front-end provides the logical pin count to
2925eb438adSthorpej 	 * deal with muliple chips on one chip select.
2935eb438adSthorpej 	 */
2945eb438adSthorpej 	if (sc->sc_npins == 0) {
2955eb438adSthorpej 		sc->sc_npins = sc->sc_variant->type == MCPGPIO_TYPE_23x08
2965eb438adSthorpej 		    ? MCP23x08_GPIO_NPINS : MCP23x17_GPIO_NPINS;
2975eb438adSthorpej 	}
2985eb438adSthorpej 	sc->sc_gpio_pins =
2995eb438adSthorpej 	    kmem_zalloc(sc->sc_npins * sizeof(*sc->sc_gpio_pins), KM_SLEEP);
3005eb438adSthorpej 
3015eb438adSthorpej 	/*
3025eb438adSthorpej 	 * Perform the basic setup of the device.  We program the IOCON
3035eb438adSthorpej 	 * register once for each bank, even though the data sheet is
3045eb438adSthorpej 	 * not clear that this is strictly necessary.
3055eb438adSthorpej 	 */
3065eb438adSthorpej 	if (mcpgpio_lock(sc) != 0) {
3075eb438adSthorpej 		return;
3085eb438adSthorpej 	}
3095eb438adSthorpej 	error = mcpgpio_write(sc, 0, REG_IOCON, sc->sc_iocon);
3105eb438adSthorpej 	if (error == 0 && sc->sc_variant->type != MCPGPIO_TYPE_23x08) {
3115eb438adSthorpej 		error = mcpgpio_write(sc, 1, REG_IOCON, sc->sc_iocon);
3125eb438adSthorpej 	}
3135eb438adSthorpej 	mcpgpio_unlock(sc);
3145eb438adSthorpej 	if (error) {
3155eb438adSthorpej 		return;
3165eb438adSthorpej 	}
3175eb438adSthorpej 
3185eb438adSthorpej 	/* XXX FDT glue. */
3195eb438adSthorpej 
3205eb438adSthorpej #if NGPIO > 0
3215eb438adSthorpej 	struct gpiobus_attach_args gba;
3225eb438adSthorpej 	int pin_output_caps;
3235eb438adSthorpej 	int i;
3245eb438adSthorpej 
3255eb438adSthorpej 	pin_output_caps = sc->sc_variant->type == MCPGPIO_TYPE_23x18
3265eb438adSthorpej 	    ? GPIO_PIN_OPENDRAIN : GPIO_PIN_PUSHPULL;
3275eb438adSthorpej 
3285eb438adSthorpej 	for (i = 0; i < sc->sc_npins; i++) {
3295eb438adSthorpej 		sc->sc_gpio_pins[i].pin_num = i;
3305eb438adSthorpej 		sc->sc_gpio_pins[i].pin_caps = GPIO_PIN_INPUT |
3315eb438adSthorpej 			GPIO_PIN_OUTPUT |
3325eb438adSthorpej 			pin_output_caps |
3335eb438adSthorpej 			GPIO_PIN_PULLUP |
3345eb438adSthorpej 			GPIO_PIN_INVIN;
3355eb438adSthorpej 
3365eb438adSthorpej 		/* read initial state */
3375eb438adSthorpej 		sc->sc_gpio_pins[i].pin_state =
3385eb438adSthorpej 			mcpgpio_gpio_pin_read(sc, i);
3395eb438adSthorpej 	}
3405eb438adSthorpej 
3415eb438adSthorpej 	/* create controller tag */
3425eb438adSthorpej 	sc->sc_gpio_gc.gp_cookie = sc;
3435eb438adSthorpej 	sc->sc_gpio_gc.gp_pin_read = mcpgpio_gpio_pin_read;
3445eb438adSthorpej 	sc->sc_gpio_gc.gp_pin_write = mcpgpio_gpio_pin_write;
3455eb438adSthorpej 	sc->sc_gpio_gc.gp_pin_ctl = mcpgpio_gpio_pin_ctl;
3465eb438adSthorpej 
3475eb438adSthorpej 	gba.gba_gc = &sc->sc_gpio_gc;
3485eb438adSthorpej 	gba.gba_pins = sc->sc_gpio_pins;
3495eb438adSthorpej 	gba.gba_npins = sc->sc_npins;
3505eb438adSthorpej 
351*6690c9f2Sthorpej 	config_found(sc->sc_dev, &gba, gpiobus_print,
352*6690c9f2Sthorpej 	    CFARGS(.devhandle = device_handle(sc->sc_dev)));
3535eb438adSthorpej #else
3545eb438adSthorpej 	aprint_normal_dev(sc->sc_dev, "no GPIO configured in kernel");
3555eb438adSthorpej #endif
3565eb438adSthorpej }
357