1 1.8 thorpej /* $NetBSD: mcp23xxxgpio_spi.c,v 1.8 2025/09/13 14:10:44 thorpej Exp $ */ 2 1.1 thorpej 3 1.1 thorpej /*- 4 1.1 thorpej * Copyright (c) 2014, 2022 The NetBSD Foundation, Inc. 5 1.1 thorpej * All rights reserved. 6 1.1 thorpej * 7 1.1 thorpej * This code is derived from software contributed to The NetBSD Foundation 8 1.1 thorpej * by Frank Kardel, and by Jason R. Thorpe. 9 1.1 thorpej * 10 1.1 thorpej * Redistribution and use in source and binary forms, with or without 11 1.1 thorpej * modification, are permitted provided that the following conditions 12 1.1 thorpej * are met: 13 1.1 thorpej * 1. Redistributions of source code must retain the above copyright 14 1.1 thorpej * notice, this list of conditions and the following disclaimer. 15 1.1 thorpej * 2. Redistributions in binary form must reproduce the above copyright 16 1.1 thorpej * notice, this list of conditions and the following disclaimer in the 17 1.1 thorpej * documentation and/or other materials provided with the distribution. 18 1.1 thorpej * 19 1.1 thorpej * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 1.1 thorpej * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 1.1 thorpej * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 1.1 thorpej * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 1.1 thorpej * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 1.1 thorpej * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 1.1 thorpej * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 1.1 thorpej * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 1.1 thorpej * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 1.1 thorpej * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 1.1 thorpej * POSSIBILITY OF SUCH DAMAGE. 30 1.1 thorpej */ 31 1.1 thorpej 32 1.1 thorpej #include <sys/cdefs.h> 33 1.8 thorpej __KERNEL_RCSID(0, "$NetBSD: mcp23xxxgpio_spi.c,v 1.8 2025/09/13 14:10:44 thorpej Exp $"); 34 1.1 thorpej 35 1.1 thorpej /* 36 1.1 thorpej * Driver for Microchip serial I/O expanders: 37 1.1 thorpej * 38 1.1 thorpej * MCP23S08 8-bit, SPI interface 39 1.1 thorpej * MCP23S17 16-bit, SPI interface 40 1.1 thorpej * MCP23S18 16-bit (open-drain outputs), SPI interface 41 1.1 thorpej * 42 1.1 thorpej * Data sheet: 43 1.1 thorpej * 44 1.1 thorpej * https://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf 45 1.1 thorpej */ 46 1.1 thorpej 47 1.7 thorpej #include "opt_fdt.h" 48 1.7 thorpej 49 1.1 thorpej #include <sys/types.h> 50 1.1 thorpej #include <sys/bitops.h> 51 1.1 thorpej #include <sys/device.h> 52 1.1 thorpej #include <sys/kernel.h> 53 1.1 thorpej #include <sys/mutex.h> 54 1.1 thorpej 55 1.1 thorpej #include <dev/ic/mcp23xxxgpioreg.h> 56 1.1 thorpej #include <dev/ic/mcp23xxxgpiovar.h> 57 1.1 thorpej 58 1.1 thorpej #include <dev/spi/spivar.h> 59 1.1 thorpej 60 1.7 thorpej #ifdef FDT 61 1.7 thorpej #include <dev/fdt/fdtvar.h> 62 1.7 thorpej #endif 63 1.7 thorpej 64 1.1 thorpej /* 65 1.1 thorpej * Multi-chip-on-select configurations appear to the upper layers like 66 1.1 thorpej * additional GPIO banks; mixing different chip types on the same chip 67 1.1 thorpej * select is not allowed. 68 1.1 thorpej * 69 1.1 thorpej * Some chips have 2 banks per chip, and we have up to 8 chips per chip 70 1.1 thorpej * select, it's a total of 16 banks per chip select / driver instance. 71 1.1 thorpej */ 72 1.1 thorpej #define MCPGPIO_SPI_MAXBANKS 16 73 1.1 thorpej 74 1.1 thorpej struct mcpgpio_spi_softc { 75 1.1 thorpej struct mcpgpio_softc sc_mcpgpio; 76 1.1 thorpej 77 1.1 thorpej kmutex_t sc_mutex; 78 1.8 thorpej spi_handle_t sc_sh; 79 1.1 thorpej uint8_t sc_ha[MCPGPIO_SPI_MAXBANKS]; 80 1.1 thorpej }; 81 1.1 thorpej 82 1.1 thorpej /* 83 1.1 thorpej * SPI-specific commands (the serial interface on the I2C flavor of 84 1.1 thorpej * the chip uses the I2C protocol to infer this information). Careful 85 1.1 thorpej * readers will note that this ends up being exactly the same bits 86 1.1 thorpej * on the serial interface that the I2C flavor of the chip uses. 87 1.1 thorpej * 88 1.1 thorpej * The SPI version can have up to 4 (or 8) chips per chip-select, demuxed 89 1.1 thorpej * using the hardware address (selected by tying the 2 or 3 HA pins high/low 90 1.1 thorpej * as desired). 91 1.1 thorpej */ 92 1.1 thorpej #define OP_READ(ha) (0x41 | ((ha) << 1)) 93 1.1 thorpej #define OP_WRITE(ha) (0x40 | ((ha) << 1)) 94 1.1 thorpej 95 1.1 thorpej #define MCPGPIO_TO_SPI(sc) \ 96 1.1 thorpej container_of((sc), struct mcpgpio_spi_softc, sc_mcpgpio) 97 1.1 thorpej 98 1.1 thorpej static const struct mcpgpio_variant mcp23s08 = { 99 1.1 thorpej .name = "MCP23S08", 100 1.1 thorpej .type = MCPGPIO_TYPE_23x08, 101 1.1 thorpej }; 102 1.1 thorpej 103 1.1 thorpej static const struct mcpgpio_variant mcp23s17 = { 104 1.1 thorpej .name = "MCP23S17", 105 1.1 thorpej .type = MCPGPIO_TYPE_23x17, 106 1.1 thorpej }; 107 1.1 thorpej 108 1.1 thorpej static const struct mcpgpio_variant mcp23s18 = { 109 1.1 thorpej .name = "MCP23S18", 110 1.1 thorpej .type = MCPGPIO_TYPE_23x18, 111 1.1 thorpej }; 112 1.1 thorpej 113 1.1 thorpej static const struct device_compatible_entry compat_data[] = { 114 1.1 thorpej { .compat = "microchip,mcp23s08", .data = &mcp23s08 }, 115 1.1 thorpej { .compat = "microchip,mcp23s17", .data = &mcp23s17 }, 116 1.1 thorpej { .compat = "microchip,mcp23s18", .data = &mcp23s18 }, 117 1.1 thorpej DEVICE_COMPAT_EOL 118 1.1 thorpej }; 119 1.1 thorpej 120 1.1 thorpej static int 121 1.1 thorpej mcpgpio_spi_lock(struct mcpgpio_softc *sc) 122 1.1 thorpej { 123 1.1 thorpej struct mcpgpio_spi_softc *ssc = MCPGPIO_TO_SPI(sc); 124 1.1 thorpej 125 1.1 thorpej mutex_enter(&ssc->sc_mutex); 126 1.1 thorpej return 0; 127 1.1 thorpej } 128 1.1 thorpej 129 1.1 thorpej static void 130 1.1 thorpej mcpgpio_spi_unlock(struct mcpgpio_softc *sc) 131 1.1 thorpej { 132 1.1 thorpej struct mcpgpio_spi_softc *ssc = MCPGPIO_TO_SPI(sc); 133 1.1 thorpej 134 1.1 thorpej mutex_exit(&ssc->sc_mutex); 135 1.1 thorpej } 136 1.1 thorpej 137 1.1 thorpej static int 138 1.1 thorpej mcpgpio_spi_read(struct mcpgpio_softc *sc, unsigned int bank, 139 1.1 thorpej uint8_t reg, uint8_t *valp) 140 1.1 thorpej { 141 1.1 thorpej struct mcpgpio_spi_softc *ssc = MCPGPIO_TO_SPI(sc); 142 1.1 thorpej uint8_t buf[2]; 143 1.1 thorpej 144 1.1 thorpej KASSERT(bank < (sc->sc_npins >> 3)); 145 1.1 thorpej 146 1.1 thorpej buf[0] = OP_READ(ssc->sc_ha[bank]); 147 1.1 thorpej buf[1] = reg; 148 1.1 thorpej 149 1.1 thorpej return spi_send_recv(ssc->sc_sh, 2, buf, 1, valp); 150 1.1 thorpej } 151 1.1 thorpej 152 1.1 thorpej static int 153 1.1 thorpej mcpgpio_spi_write(struct mcpgpio_softc *sc, unsigned int bank, 154 1.1 thorpej uint8_t reg, uint8_t val) 155 1.1 thorpej { 156 1.1 thorpej struct mcpgpio_spi_softc *ssc = MCPGPIO_TO_SPI(sc); 157 1.1 thorpej uint8_t buf[3]; 158 1.1 thorpej 159 1.1 thorpej KASSERT(bank < (sc->sc_npins >> 3)); 160 1.1 thorpej 161 1.1 thorpej buf[0] = OP_WRITE(ssc->sc_ha[bank]); 162 1.1 thorpej buf[1] = reg; 163 1.1 thorpej buf[2] = val; 164 1.1 thorpej 165 1.1 thorpej return spi_send(ssc->sc_sh, 3, buf); 166 1.1 thorpej } 167 1.1 thorpej 168 1.1 thorpej static const struct mcpgpio_accessops mcpgpio_spi_accessops = { 169 1.1 thorpej .lock = mcpgpio_spi_lock, 170 1.1 thorpej .unlock = mcpgpio_spi_unlock, 171 1.1 thorpej .read = mcpgpio_spi_read, 172 1.1 thorpej .write = mcpgpio_spi_write, 173 1.1 thorpej }; 174 1.1 thorpej 175 1.7 thorpej static const struct mcpgpio_variant * 176 1.7 thorpej mcpgpio_spi_lookup(const struct spi_attach_args *sa) 177 1.7 thorpej { 178 1.7 thorpej const struct device_compatible_entry *dce; 179 1.7 thorpej 180 1.7 thorpej dce = spi_compatible_lookup(sa, compat_data); 181 1.7 thorpej if (dce == NULL) { 182 1.7 thorpej /* 183 1.7 thorpej * Legacy indirect-config-only version of the driver 184 1.7 thorpej * only supported MCP23S17, so that's what we go with. 185 1.7 thorpej * If you want to use something else, then use a proper 186 1.7 thorpej * device tree. 187 1.7 thorpej */ 188 1.7 thorpej return &mcp23s17; 189 1.7 thorpej } 190 1.7 thorpej return dce->data; 191 1.7 thorpej } 192 1.7 thorpej 193 1.1 thorpej static int 194 1.1 thorpej mcpgpio_spi_match(device_t parent, cfdata_t cf, void *aux) 195 1.1 thorpej { 196 1.7 thorpej struct spi_attach_args *sa = aux; 197 1.7 thorpej int match_result; 198 1.7 thorpej 199 1.7 thorpej if (spi_use_direct_match(sa, compat_data, &match_result)) { 200 1.7 thorpej return match_result; 201 1.7 thorpej } 202 1.7 thorpej 203 1.6 thorpej return SPI_MATCH_DEFAULT; 204 1.1 thorpej } 205 1.1 thorpej 206 1.7 thorpej #ifdef FDT 207 1.7 thorpej static uint32_t 208 1.7 thorpej mcpgpio_spi_present_mask_fdt(struct mcpgpio_softc *sc) 209 1.7 thorpej { 210 1.7 thorpej devhandle_t devhandle = device_handle(sc->sc_dev); 211 1.7 thorpej int phandle = devhandle_to_of(devhandle); 212 1.7 thorpej uint32_t val; 213 1.7 thorpej 214 1.7 thorpej /* 215 1.7 thorpej * The number of devices sharing this chip select, along 216 1.7 thorpej * with their assigned addresses, is encoded in the 217 1.7 thorpej * "microchip,spi-present-mask" property. Note that this 218 1.7 thorpej * device tree binding means that we will just have a 219 1.7 thorpej * single driver instance for however many chips are on 220 1.7 thorpej * this chip select. We treat them logically as banks. 221 1.7 thorpej */ 222 1.7 thorpej if (of_getprop_uint32(phandle, "microchip,spi-present-mask", 223 1.7 thorpej &val) != 0 || 224 1.7 thorpej of_getprop_uint32(phandle, "mcp,spi-present-mask", &val) != 0) { 225 1.7 thorpej aprint_error_dev(sc->sc_dev, 226 1.7 thorpej "missing \"microchip,spi-present-mask\" property\n"); 227 1.7 thorpej return 0; 228 1.7 thorpej } 229 1.7 thorpej return val; 230 1.7 thorpej } 231 1.7 thorpej #endif /* FDT */ 232 1.7 thorpej 233 1.1 thorpej static void 234 1.1 thorpej mcpgpio_spi_attach(device_t parent, device_t self, void *aux) 235 1.1 thorpej { 236 1.1 thorpej struct mcpgpio_spi_softc *ssc = device_private(self); 237 1.1 thorpej struct mcpgpio_softc *sc = &ssc->sc_mcpgpio; 238 1.7 thorpej devhandle_t devhandle = device_handle(self); 239 1.1 thorpej struct spi_attach_args *sa = aux; 240 1.1 thorpej uint32_t spi_present_mask; 241 1.1 thorpej int bank, nchips, error, ha; 242 1.1 thorpej 243 1.1 thorpej mutex_init(&ssc->sc_mutex, MUTEX_DEFAULT, IPL_NONE); 244 1.1 thorpej ssc->sc_sh = sa->sa_handle; 245 1.1 thorpej 246 1.1 thorpej sc->sc_dev = self; 247 1.7 thorpej sc->sc_variant = mcpgpio_spi_lookup(sa); 248 1.1 thorpej sc->sc_iocon = IOCON_HAEN | IOCON_SEQOP; 249 1.1 thorpej sc->sc_accessops = &mcpgpio_spi_accessops; 250 1.1 thorpej 251 1.1 thorpej aprint_naive("\n"); 252 1.1 thorpej aprint_normal(": %s I/O Expander\n", sc->sc_variant->name); 253 1.1 thorpej 254 1.3 thorpej /* run at 10MHz */ 255 1.5 thorpej error = spi_configure(self, sa->sa_handle, SPI_MODE_0, 256 1.5 thorpej SPI_FREQ_MHz(10)); 257 1.3 thorpej if (error) { 258 1.3 thorpej return; 259 1.3 thorpej } 260 1.3 thorpej 261 1.1 thorpej /* 262 1.1 thorpej * Before we decode the topology information, ensure each 263 1.1 thorpej * chip has IOCON.HAEN set so that it will actually decode 264 1.1 thorpej * the address bits. 265 1.1 thorpej * 266 1.1 thorpej * XXX Going on blind faith that IOCON.BANK is already 0. 267 1.1 thorpej */ 268 1.1 thorpej if (sc->sc_variant->type == MCPGPIO_TYPE_23x08) { 269 1.1 thorpej error = mcpgpio_spi_write(sc, 0, REG_IOCON, sc->sc_iocon); 270 1.1 thorpej } else { 271 1.1 thorpej error = mcpgpio_spi_write(sc, 0, REGADDR_BANK0(0, REG_IOCON), 272 1.1 thorpej sc->sc_iocon); 273 1.1 thorpej if (error == 0) { 274 1.1 thorpej error = mcpgpio_spi_write(sc, 1, 275 1.1 thorpej REGADDR_BANK0(1, REG_IOCON), sc->sc_iocon); 276 1.1 thorpej } 277 1.1 thorpej } 278 1.1 thorpej if (error) { 279 1.1 thorpej aprint_error_dev(self, 280 1.1 thorpej "unable to initialize IOCON, error=%d\n", error); 281 1.1 thorpej return; 282 1.1 thorpej } 283 1.1 thorpej 284 1.7 thorpej switch (devhandle_type(devhandle)) { 285 1.7 thorpej #ifdef FDT 286 1.7 thorpej case DEVHANDLE_TYPE_OF: 287 1.7 thorpej spi_present_mask = mcpgpio_spi_present_mask_fdt(sc); 288 1.7 thorpej if (spi_present_mask == 0) { 289 1.7 thorpej /* Error already displayed. */ 290 1.7 thorpej return; 291 1.7 thorpej } 292 1.7 thorpej break; 293 1.7 thorpej #endif /* FDT */ 294 1.7 thorpej default: 295 1.7 thorpej /* 296 1.7 thorpej * Legacy indirect-config-only only supported a single 297 1.7 thorpej * chip on the chip-select. 298 1.7 thorpej */ 299 1.7 thorpej spi_present_mask = __BIT(device_cfdata(self)->cf_flags & 0x7); 300 1.1 thorpej } 301 1.1 thorpej 302 1.1 thorpej /* 303 1.1 thorpej * The 23S08 has 2 address pins (4 devices per chip select), 304 1.1 thorpej * and the others have 3 (8 devices per chip select). 305 1.1 thorpej */ 306 1.1 thorpej if (spi_present_mask == 0 || 307 1.1 thorpej (sc->sc_variant->type == MCPGPIO_TYPE_23x08 && 308 1.1 thorpej spi_present_mask >= __BIT(4)) || 309 1.1 thorpej (sc->sc_variant->type != MCPGPIO_TYPE_23x08 && 310 1.1 thorpej spi_present_mask >= __BIT(8))) { 311 1.1 thorpej aprint_error_dev(self, 312 1.1 thorpej "invalid \"microchip,spi-present-mask\" value: 0x%08x\n", 313 1.1 thorpej spi_present_mask); 314 1.1 thorpej return; 315 1.1 thorpej } 316 1.1 thorpej nchips = popcount32(spi_present_mask); 317 1.1 thorpej sc->sc_npins = nchips * 318 1.1 thorpej (sc->sc_variant->type == MCPGPIO_TYPE_23x08 ? MCP23x08_GPIO_NPINS 319 1.1 thorpej : MCP23x17_GPIO_NPINS); 320 1.1 thorpej 321 1.1 thorpej /* Record the hardware addresses for each logical bank of 8 pins. */ 322 1.1 thorpej for (bank = 0; spi_present_mask != 0; spi_present_mask &= ~__BIT(ha)) { 323 1.1 thorpej int ha_first, ha_last; 324 1.1 thorpej 325 1.1 thorpej ha = ffs32(spi_present_mask) - 1; 326 1.1 thorpej ha_first = bank * MCPGPIO_PINS_PER_BANK; 327 1.1 thorpej ssc->sc_ha[bank++] = ha; 328 1.1 thorpej if (sc->sc_variant->type != MCPGPIO_TYPE_23x08) { 329 1.1 thorpej ssc->sc_ha[bank++] = ha; 330 1.1 thorpej } 331 1.1 thorpej ha_last = (bank * MCPGPIO_PINS_PER_BANK) - 1; 332 1.1 thorpej aprint_verbose_dev(self, "pins %d..%d at HA %d\n", 333 1.1 thorpej ha_first, ha_last, ha); 334 1.1 thorpej } 335 1.1 thorpej KASSERT((bank * MCPGPIO_PINS_PER_BANK) == sc->sc_npins); 336 1.1 thorpej 337 1.1 thorpej mcpgpio_attach(sc); 338 1.1 thorpej } 339 1.1 thorpej 340 1.1 thorpej CFATTACH_DECL_NEW(mcpgpio_spi, sizeof(struct mcpgpio_spi_softc), 341 1.1 thorpej mcpgpio_spi_match, mcpgpio_spi_attach, NULL, NULL); 342