1 /* $NetBSD: sc16is7xxspi.c,v 1.3 2025/12/01 14:56:02 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.3 2025/12/01 14:56:02 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_spi_softc), 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 regs->cr_cookie = sc; 132 } 133 134 static const struct sc16is7xx_accessfuncs sc16is7xx_spi_accessfuncs = { 135 .read_reg = sc16is7xxspi_read_register, 136 .write_reg = sc16is7xxspi_write_register, 137 .copy_handles = sc16is7xxspi_copy_handles, 138 }; 139 /* These will be used by dev/ic/com.c, conform to what is expected */ 140 141 static uint8_t 142 sc16is7xx_spi_com_read_1(struct com_regs *regs, u_int reg) 143 { 144 struct sc16is7xx_sc *sc = (struct sc16is7xx_sc *)regs->cr_cookie; 145 struct sc16is7xx_spi_softc *isc = SC16IS7XX_TO_SPI(sc); 146 uint8_t buf; 147 int error; 148 149 if (regs->cr_has_errored) 150 return 0; 151 152 error = sc16is7xxspi_read_register_direct(isc->sc_sh, 153 reg, regs->cr_channel, &buf, 1); 154 155 if (!error) 156 return buf; 157 158 if (error) { 159 device_printf(sc->sc_dev, "sc16is7xx_spi_com_read_1: error=%d\n",error); 160 regs->cr_has_errored = true; 161 mutex_enter(&sc->sc_thread_mutex); 162 if (sc->sc_thread_state != SC16IS7XX_THREAD_GPIO) { 163 sc->sc_thread_state = SC16IS7XX_THREAD_STALLED; 164 cv_signal(&sc->sc_threadvar); 165 } 166 mutex_exit(&sc->sc_thread_mutex); 167 } 168 169 return 0; 170 } 171 172 static void 173 sc16is7xx_spi_com_write_1(struct com_regs *regs, u_int reg, uint8_t val) 174 { 175 struct sc16is7xx_sc *sc = (struct sc16is7xx_sc *)regs->cr_cookie; 176 struct sc16is7xx_spi_softc *isc = SC16IS7XX_TO_SPI(sc); 177 int error = 0; 178 179 if (regs->cr_has_errored) 180 return; 181 182 error = sc16is7xxspi_write_register_direct(isc->sc_sh, 183 reg, regs->cr_channel, &val, 1); 184 185 if (error) { 186 device_printf(sc->sc_dev, "sc16is7xx_spi_com_write_1: error=%d\n",error); 187 regs->cr_has_errored = true; 188 mutex_enter(&sc->sc_thread_mutex); 189 if (sc->sc_thread_state != SC16IS7XX_THREAD_GPIO) { 190 sc->sc_thread_state = SC16IS7XX_THREAD_STALLED; 191 cv_signal(&sc->sc_threadvar); 192 } 193 mutex_exit(&sc->sc_thread_mutex); 194 } 195 } 196 197 static void 198 sc16is7xx_spi_com_write_multi_1(struct com_regs *regs, u_int reg, const uint8_t *datap, 199 bus_size_t count) 200 { 201 struct sc16is7xx_sc *sc = (struct sc16is7xx_sc *)regs->cr_cookie; 202 struct sc16is7xx_spi_softc *isc = SC16IS7XX_TO_SPI(sc); 203 int error = 0; 204 205 if (regs->cr_has_errored) 206 return; 207 208 error = sc16is7xxspi_write_register_direct(isc->sc_sh, 209 reg, regs->cr_channel, __UNCONST(datap), count); 210 if (error) { 211 device_printf(sc->sc_dev, "sc16is7xx_spi_com_write_multi_1: error=%d\n",error); 212 regs->cr_has_errored = true; 213 mutex_enter(&sc->sc_thread_mutex); 214 if (sc->sc_thread_state != SC16IS7XX_THREAD_GPIO) { 215 sc->sc_thread_state = SC16IS7XX_THREAD_STALLED; 216 cv_signal(&sc->sc_threadvar); 217 } 218 mutex_exit(&sc->sc_thread_mutex); 219 } 220 } 221 222 static const struct sc16is7xx_accessfuncs sc16is7xx_spi_com_accessfuncs = { 223 .com_read_1 = sc16is7xx_spi_com_read_1, 224 .com_write_1 = sc16is7xx_spi_com_write_1, 225 .com_write_multi_1 = sc16is7xx_spi_com_write_multi_1, 226 }; 227 228 static int 229 sc16is7xxspi_match(device_t parent, cfdata_t match, void *aux) 230 { 231 struct spi_attach_args *sa = aux; 232 int match_result; 233 234 if (spi_use_direct_match(sa, sc16is7xx_compat_data, &match_result)) { 235 return match_result; 236 } 237 return SPI_MATCH_DEFAULT; 238 } 239 240 #ifndef SC16IS7XX_SPI_FREQUENCY 241 #define SC16IS7XX_SPI_FREQUENCY 1 242 #endif 243 244 static void 245 sc16is7xxspi_attach(device_t parent, device_t self, void *aux) 246 { 247 struct sc16is7xx_spi_softc *ssc = device_private(self); 248 struct sc16is7xx_sc *sc = &ssc->sc_sc16is7xx; 249 struct spi_attach_args *sa = aux; 250 int error; 251 252 sc->sc_dev = self; 253 sc->sc_funcs = &sc16is7xx_spi_accessfuncs; 254 sc->sc_com_funcs = &sc16is7xx_spi_com_accessfuncs; 255 256 ssc->sc_sh = sa->sa_handle; 257 258 aprint_normal("\n"); 259 aprint_normal_dev(sc->sc_dev, "SPI frequency %dMhz", SC16IS7XX_SPI_FREQUENCY); 260 261 /* Configure for SPI mode 0 according to the data sheet. The chip will 262 * do up to 4Mhz or 15Mhz depending on the varient and does support 263 * other modes. */ 264 error = spi_configure(self, sa->sa_handle, SPI_MODE_0, SPI_FREQ_MHz(SC16IS7XX_SPI_FREQUENCY)); 265 if (error) { 266 return; 267 } 268 sc16is7xx_attach(sc); 269 } 270 271 static int 272 sc16is7xxspi_detach(device_t self, int flags) 273 { 274 struct sc16is7xx_spi_softc *ssc = device_private(self); 275 276 return sc16is7xx_detach(&ssc->sc_sc16is7xx, flags); 277 } 278