1 /* $NetBSD: sc16is7xxspi.c,v 1.1 2025/10/24 23:16:11 brad Exp $ */ 2 3 /* 4 * Copyright (c) 2025 Brad Spencer <brad (at) anduin.eldar.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/cdefs.h> 20 __KERNEL_RCSID(0, "$NetBSD: sc16is7xxspi.c,v 1.1 2025/10/24 23:16:11 brad Exp $"); 21 22 /* 23 * SPI frontend driver for the SC16IS7xx UART bridge. 24 * The heavy lifting is done by the general sc16is7xx(4) 25 * driver and the com(4) backend. 26 */ 27 28 #include <sys/param.h> 29 #include <sys/systm.h> 30 #include <sys/kernel.h> 31 #include <sys/device.h> 32 #include <sys/module.h> 33 #include <sys/conf.h> 34 #include <sys/sysctl.h> 35 #include <sys/mutex.h> 36 #include <sys/condvar.h> 37 #include <sys/pool.h> 38 #include <sys/kmem.h> 39 40 #include <dev/i2c/i2cvar.h> 41 #include <dev/spi/spivar.h> 42 #include <dev/ic/sc16is7xxreg.h> 43 #include <dev/ic/sc16is7xxvar.h> 44 45 #include "opt_sc16is7xx.h" 46 47 struct sc16is7xx_spi_softc { 48 struct sc16is7xx_sc sc_sc16is7xx; 49 spi_handle_t sc_sh; 50 }; 51 52 #define SC16IS7XX_TO_SPI(sc) \ 53 container_of((sc), struct sc16is7xx_spi_softc, sc_sc16is7xx) 54 55 static int sc16is7xxspi_match(device_t, cfdata_t, void *); 56 static void sc16is7xxspi_attach(device_t, device_t, void *); 57 static int sc16is7xxspi_detach(device_t, int); 58 59 CFATTACH_DECL_NEW(sc16is7xxspi, sizeof(struct sc16is7xx_sc), 60 sc16is7xxspi_match, sc16is7xxspi_attach, sc16is7xxspi_detach, NULL); 61 62 static int 63 sc16is7xxspi_read_register_direct(spi_handle_t sh, 64 uint8_t reg, int channel, uint8_t *buf, size_t blen) 65 { 66 int error; 67 uint8_t xreg; 68 69 xreg = ((reg << 3) | (channel << 1)) | 0x80; 70 71 error = spi_send_recv(sh, 1, &xreg, blen, buf); 72 return error; 73 } 74 75 static int 76 sc16is7xxspi_write_register_direct(spi_handle_t sh, 77 uint8_t reg, int channel, uint8_t *buf, size_t blen) 78 { 79 int error; 80 uint8_t xreg; 81 struct iovec iov[2]; 82 83 xreg = (reg << 3) | (channel << 1); 84 85 KASSERTMSG(!(xreg & 0x80), "xreg=%02x", xreg); /* panic if this ends up 86 * trying to be a read */ 87 88 iov[0].iov_len = 1; 89 iov[0].iov_base = &xreg; 90 iov[1].iov_len = blen; 91 iov[1].iov_base = buf; 92 error = spi_sendv(sh, &iov[0], 2); 93 94 return error; 95 } 96 /* Use these after the hand off to the general driver happens */ 97 98 static int 99 sc16is7xxspi_read_register(struct sc16is7xx_sc *sc, uint8_t reg, int channel, 100 uint8_t *buf, size_t blen) 101 { 102 struct sc16is7xx_spi_softc *ssc = SC16IS7XX_TO_SPI(sc); 103 int error; 104 105 KASSERT(blen > 0); 106 107 error = sc16is7xxspi_read_register_direct(ssc->sc_sh, 108 reg, channel, buf, blen); 109 110 return error; 111 } 112 113 static int 114 sc16is7xxspi_write_register(struct sc16is7xx_sc *sc, uint8_t reg, int channel, 115 uint8_t *buf, size_t blen) 116 { 117 struct sc16is7xx_spi_softc *ssc = SC16IS7XX_TO_SPI(sc); 118 int error; 119 120 KASSERT(blen > 0); 121 122 error = sc16is7xxspi_write_register_direct(ssc->sc_sh, 123 reg, channel, buf, blen); 124 125 return error; 126 } 127 128 static void 129 sc16is7xxspi_copy_handles(struct sc16is7xx_sc *sc, struct com_regs *regs) 130 { 131 struct sc16is7xx_spi_softc *ssc = SC16IS7XX_TO_SPI(sc); 132 133 regs->cr_sh = ssc->sc_sh; 134 } 135 136 static const struct sc16is7xx_accessfuncs sc16is7xx_spi_accessfuncs = { 137 .read_reg = sc16is7xxspi_read_register, 138 .write_reg = sc16is7xxspi_write_register, 139 .copy_handles = sc16is7xxspi_copy_handles, 140 }; 141 /* These will be used by dev/ic/com.c, conform to what is expected */ 142 143 static uint8_t 144 sc16is7xx_spi_com_read_1(struct com_regs *regs, u_int reg) 145 { 146 uint8_t buf; 147 int error; 148 149 error = sc16is7xxspi_read_register_direct(regs->cr_sh, 150 reg, regs->cr_channel, &buf, 1); 151 152 if (!error) 153 return buf; 154 155 return 0; 156 } 157 158 static void 159 sc16is7xx_spi_com_write_1(struct com_regs *regs, u_int reg, uint8_t val) 160 { 161 sc16is7xxspi_write_register_direct(regs->cr_sh, 162 reg, regs->cr_channel, &val, 1); 163 } 164 165 static void 166 sc16is7xx_spi_com_write_multi_1(struct com_regs *regs, u_int reg, const uint8_t *datap, 167 bus_size_t count) 168 { 169 sc16is7xxspi_write_register_direct(regs->cr_sh, 170 reg, regs->cr_channel, __UNCONST(datap), count); 171 } 172 173 static const struct sc16is7xx_accessfuncs sc16is7xx_spi_com_accessfuncs = { 174 .com_read_1 = sc16is7xx_spi_com_read_1, 175 .com_write_1 = sc16is7xx_spi_com_write_1, 176 .com_write_multi_1 = sc16is7xx_spi_com_write_multi_1, 177 }; 178 179 static int 180 sc16is7xxspi_match(device_t parent, cfdata_t match, void *aux) 181 { 182 struct spi_attach_args *sa = aux; 183 int match_result; 184 185 if (spi_use_direct_match(sa, sc16is7xx_compat_data, &match_result)) { 186 return match_result; 187 } 188 return SPI_MATCH_DEFAULT; 189 } 190 191 #ifndef SC16IS7XX_SPI_FREQUENCY 192 #define SC16IS7XX_SPI_FREQUENCY 1 193 #endif 194 195 static void 196 sc16is7xxspi_attach(device_t parent, device_t self, void *aux) 197 { 198 struct sc16is7xx_spi_softc *ssc = device_private(self); 199 struct sc16is7xx_sc *sc = &ssc->sc_sc16is7xx; 200 struct spi_attach_args *sa = aux; 201 int error; 202 203 sc->sc_dev = self; 204 sc->sc_funcs = &sc16is7xx_spi_accessfuncs; 205 sc->sc_com_funcs = &sc16is7xx_spi_com_accessfuncs; 206 207 ssc->sc_sh = sa->sa_handle; 208 209 aprint_normal("\n"); 210 aprint_normal_dev(sc->sc_dev, "SPI frequency %dMhz", SC16IS7XX_SPI_FREQUENCY); 211 212 /* Configure for SPI mode 0 according to the data sheet. The chip will 213 * do up to 4Mhz or 15Mhz depending on the varient and does support 214 * other modes. */ 215 error = spi_configure(self, sa->sa_handle, SPI_MODE_0, SPI_FREQ_MHz(SC16IS7XX_SPI_FREQUENCY)); 216 if (error) { 217 return; 218 } 219 sc16is7xx_attach(sc); 220 } 221 222 static int 223 sc16is7xxspi_detach(device_t self, int flags) 224 { 225 struct sc16is7xx_spi_softc *ssc = device_private(self); 226 227 return sc16is7xx_detach(&ssc->sc_sc16is7xx, flags); 228 } 229