1 /* $NetBSD: mcp23xxxgpio_i2c.c,v 1.2 2022/01/17 19:36:54 thorpej Exp $ */ 2 3 /*- 4 * Copyright (c) 2022 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Jason R. Thorpe. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 __KERNEL_RCSID(0, "$NetBSD: mcp23xxxgpio_i2c.c,v 1.2 2022/01/17 19:36:54 thorpej Exp $"); 34 35 /* 36 * Driver for Microchip serial I/O expanders: 37 * 38 * MCP23008 8-bit, I2C interface 39 * MCP23017 16-bit, I2C interface 40 * MCP23018 16-bit (open-drain outputs), I2C interface 41 * 42 * Data sheet: 43 * 44 * https://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf 45 */ 46 47 #include <sys/types.h> 48 #include <sys/device.h> 49 #include <sys/kernel.h> 50 51 #include <dev/ic/mcp23xxxgpioreg.h> 52 #include <dev/ic/mcp23xxxgpiovar.h> 53 54 #include <dev/i2c/i2cvar.h> 55 56 struct mcpgpio_i2c_softc { 57 struct mcpgpio_softc sc_mcpgpio; 58 59 i2c_tag_t sc_i2c; 60 i2c_addr_t sc_addr; 61 }; 62 63 #define MCPGPIO_TO_I2C(sc) \ 64 container_of((sc), struct mcpgpio_i2c_softc, sc_mcpgpio) 65 66 static const struct mcpgpio_variant mcp23008 = { 67 .name = "MCP23008", 68 .type = MCPGPIO_TYPE_23x08, 69 }; 70 71 static const struct mcpgpio_variant mcp23017 = { 72 .name = "MCP23017", 73 .type = MCPGPIO_TYPE_23x17, 74 }; 75 76 static const struct mcpgpio_variant mcp23018 = { 77 .name = "MCP23018", 78 .type = MCPGPIO_TYPE_23x18, 79 }; 80 81 static const struct device_compatible_entry compat_data[] = { 82 { .compat = "microchip,mcp23008", .data = &mcp23008 }, 83 { .compat = "microchip,mcp23017", .data = &mcp23017 }, 84 { .compat = "microchip,mcp23018", .data = &mcp23018 }, 85 DEVICE_COMPAT_EOL 86 }; 87 88 static int 89 mcpgpio_i2c_lock(struct mcpgpio_softc *sc) 90 { 91 struct mcpgpio_i2c_softc *isc = MCPGPIO_TO_I2C(sc); 92 93 return iic_acquire_bus(isc->sc_i2c, 0); 94 } 95 96 static void 97 mcpgpio_i2c_unlock(struct mcpgpio_softc *sc) 98 { 99 struct mcpgpio_i2c_softc *isc = MCPGPIO_TO_I2C(sc); 100 101 iic_release_bus(isc->sc_i2c, 0); 102 } 103 104 static int 105 mcpgpio_i2c_read(struct mcpgpio_softc *sc, unsigned int bank, 106 uint8_t reg, uint8_t *valp) 107 { 108 struct mcpgpio_i2c_softc *isc = MCPGPIO_TO_I2C(sc); 109 110 /* Only one chip per hardware address in i2c. */ 111 KASSERT((bank & ~1U) == 0); 112 113 return iic_exec(isc->sc_i2c, I2C_OP_READ_WITH_STOP, isc->sc_addr, 114 ®, 1, valp, 1, 0); 115 } 116 117 static int 118 mcpgpio_i2c_write(struct mcpgpio_softc *sc, unsigned int bank, 119 uint8_t reg, uint8_t val) 120 { 121 struct mcpgpio_i2c_softc *isc = MCPGPIO_TO_I2C(sc); 122 123 /* Only one chip per hardware address in i2c. */ 124 KASSERT((bank & ~1U) == 0); 125 126 return iic_exec(isc->sc_i2c, I2C_OP_WRITE_WITH_STOP, isc->sc_addr, 127 ®, 1, &val, 1, 0); 128 } 129 130 static const struct mcpgpio_accessops mcpgpio_i2c_accessops = { 131 .lock = mcpgpio_i2c_lock, 132 .unlock = mcpgpio_i2c_unlock, 133 .read = mcpgpio_i2c_read, 134 .write = mcpgpio_i2c_write, 135 }; 136 137 static int 138 mcpgpio_i2c_match(device_t parent, cfdata_t match, void *aux) 139 { 140 struct i2c_attach_args *ia = aux; 141 int match_result; 142 143 if (iic_use_direct_match(ia, match, compat_data, &match_result)) { 144 return match_result; 145 } 146 return 0; 147 } 148 149 static void 150 mcpgpio_i2c_attach(device_t parent, device_t self, void *aux) 151 { 152 struct mcpgpio_i2c_softc *isc = device_private(self); 153 struct mcpgpio_softc *sc = &isc->sc_mcpgpio; 154 struct i2c_attach_args *ia = aux; 155 const struct device_compatible_entry *dce; 156 157 isc->sc_i2c = ia->ia_tag; 158 isc->sc_addr = ia->ia_addr; 159 160 dce = iic_compatible_lookup(ia, compat_data); 161 KASSERT(dce != NULL); 162 163 sc->sc_dev = self; 164 sc->sc_variant = dce->data; 165 sc->sc_iocon = IOCON_SEQOP; 166 sc->sc_accessops = &mcpgpio_i2c_accessops; 167 168 aprint_naive("\n"); 169 aprint_normal(": %s I/O Expander\n", sc->sc_variant->name); 170 171 mcpgpio_attach(sc); 172 } 173 174 CFATTACH_DECL_NEW(mcpgpio_i2c, sizeof(struct mcpgpio_i2c_softc), 175 mcpgpio_i2c_match, mcpgpio_i2c_attach, NULL, NULL); 176