Home | History | Annotate | Line # | Download | only in i2c
      1 /*	$NetBSD: sc16is7xxi2c.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: sc16is7xxi2c.c,v 1.3 2025/12/01 14:56:02 brad Exp $");
     21 
     22 /*
     23  * I2C 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 struct sc16is7xx_i2c_softc {
     46 	struct sc16is7xx_sc sc_sc16is7xx;
     47 	i2c_tag_t sc_tag;
     48 	i2c_addr_t sc_addr;
     49 };
     50 
     51 #define	SC16IS7XX_TO_I2C(sc)	\
     52 	container_of((sc), struct sc16is7xx_i2c_softc, sc_sc16is7xx)
     53 
     54 static int sc16is7xxi2c_poke(i2c_tag_t, i2c_addr_t, bool);
     55 static int sc16is7xxi2c_match(device_t, cfdata_t, void *);
     56 static void sc16is7xxi2c_attach(device_t, device_t, void *);
     57 static int sc16is7xxi2c_detach(device_t, int);
     58 
     59 CFATTACH_DECL_NEW(sc16is7xxi2c, sizeof(struct sc16is7xx_i2c_softc),
     60     sc16is7xxi2c_match, sc16is7xxi2c_attach, sc16is7xxi2c_detach, NULL);
     61 
     62 static int
     63 sc16is7xxi2c_read_register_direct(i2c_tag_t tag, i2c_addr_t addr, uint8_t reg,
     64     int channel, uint8_t *buf, size_t blen)
     65 {
     66 	int error;
     67 	uint8_t xreg;
     68 
     69 	xreg = (reg << 3) | (channel << 1);
     70 
     71 	error = iic_acquire_bus(tag, 0);
     72 	if (error == 0) {
     73 		error = iic_exec(tag, I2C_OP_READ_WITH_STOP, addr, &xreg, 1,
     74 		    buf, blen, 0);
     75 	}
     76 	iic_release_bus(tag, 0);
     77 
     78 	return error;
     79 }
     80 
     81 static int
     82 sc16is7xxi2c_write_register_direct(i2c_tag_t tag, i2c_addr_t addr, uint8_t reg,
     83     int channel, uint8_t *buf, size_t blen)
     84 {
     85 	int error;
     86 	uint8_t xreg;
     87 
     88 	xreg = (reg << 3) | (channel << 1);
     89 
     90 	error = iic_acquire_bus(tag, 0);
     91 	if (error == 0) {
     92 		error = iic_exec(tag, I2C_OP_WRITE_WITH_STOP, addr, &xreg, 1,
     93 		    buf, blen, 0);
     94 	}
     95 	iic_release_bus(tag, 0);
     96 
     97 	return error;
     98 }
     99 /* Use these after the hand off to the general driver happens */
    100 
    101 static int
    102 sc16is7xxi2c_read_register(struct sc16is7xx_sc *sc, uint8_t reg, int channel,
    103     uint8_t *buf, size_t blen)
    104 {
    105 	struct sc16is7xx_i2c_softc *isc = SC16IS7XX_TO_I2C(sc);
    106 	int error;
    107 
    108 	KASSERT(blen > 0);
    109 
    110 	error = sc16is7xxi2c_read_register_direct(isc->sc_tag, isc->sc_addr,
    111 	    reg, channel, buf, blen);
    112 
    113 	return error;
    114 }
    115 
    116 static int
    117 sc16is7xxi2c_write_register(struct sc16is7xx_sc *sc, uint8_t reg, int channel,
    118     uint8_t *buf, size_t blen)
    119 {
    120 	struct sc16is7xx_i2c_softc *isc = SC16IS7XX_TO_I2C(sc);
    121 	int error;
    122 
    123 	KASSERT(blen > 0);
    124 
    125 	error = sc16is7xxi2c_write_register_direct(isc->sc_tag, isc->sc_addr,
    126 	    reg, channel, buf, blen);
    127 
    128 	return error;
    129 }
    130 
    131 static void
    132 sc16is7xxi2c_copy_handles(struct sc16is7xx_sc *sc, struct com_regs *regs)
    133 {
    134 	regs->cr_cookie = sc;
    135 }
    136 
    137 static const struct sc16is7xx_accessfuncs sc16is7xx_i2c_accessfuncs = {
    138 	.read_reg = sc16is7xxi2c_read_register,
    139 	.write_reg = sc16is7xxi2c_write_register,
    140 	.copy_handles = sc16is7xxi2c_copy_handles,
    141 };
    142 /* These will be used by dev/ic/com.c, conform to what is expected */
    143 
    144 static uint8_t
    145 sc16is7xx_i2c_com_read_1(struct com_regs *regs, u_int reg)
    146 {
    147 	struct sc16is7xx_sc *sc = (struct sc16is7xx_sc *)regs->cr_cookie;
    148 	struct sc16is7xx_i2c_softc *isc = SC16IS7XX_TO_I2C(sc);
    149 	uint8_t buf;
    150 	int error;
    151 
    152 	if (regs->cr_has_errored)
    153 		return 0;
    154 
    155 	error = sc16is7xxi2c_read_register_direct(isc->sc_tag, isc->sc_addr,
    156 	    reg, regs->cr_channel, &buf, 1);
    157 
    158 	if (!error)
    159 		return buf;
    160 
    161 	if (error) {
    162 		device_printf(sc->sc_dev, "sc16is7xx_i2c_com_read_1: error=%d\n",error);
    163 		regs->cr_has_errored = true;
    164 		mutex_enter(&sc->sc_thread_mutex);
    165 		if (sc->sc_thread_state != SC16IS7XX_THREAD_GPIO) {
    166 			sc->sc_thread_state = SC16IS7XX_THREAD_STALLED;
    167 			cv_signal(&sc->sc_threadvar);
    168 		}
    169 		mutex_exit(&sc->sc_thread_mutex);
    170 	}
    171 
    172 	return 0;
    173 }
    174 
    175 static void
    176 sc16is7xx_i2c_com_write_1(struct com_regs *regs, u_int reg, uint8_t val)
    177 {
    178 	struct sc16is7xx_sc *sc = (struct sc16is7xx_sc *)regs->cr_cookie;
    179 	struct sc16is7xx_i2c_softc *isc = SC16IS7XX_TO_I2C(sc);
    180 	int error = 0;
    181 
    182 	if (regs->cr_has_errored)
    183 		return;
    184 
    185 	error = sc16is7xxi2c_write_register_direct(isc->sc_tag, isc->sc_addr,
    186 	    reg, regs->cr_channel, &val, 1);
    187 
    188 	if (error) {
    189 		device_printf(sc->sc_dev, "sc16is7xx_i2c_com_write_1: error=%d\n",error);
    190 		regs->cr_has_errored = true;
    191 		mutex_enter(&sc->sc_thread_mutex);
    192 		if (sc->sc_thread_state != SC16IS7XX_THREAD_GPIO) {
    193 			sc->sc_thread_state = SC16IS7XX_THREAD_STALLED;
    194 			cv_signal(&sc->sc_threadvar);
    195 		}
    196 		mutex_exit(&sc->sc_thread_mutex);
    197 	}
    198 }
    199 
    200 static void
    201 sc16is7xx_i2c_com_write_multi_1(struct com_regs *regs, u_int reg, const uint8_t *datap,
    202     bus_size_t count)
    203 {
    204 	struct sc16is7xx_sc *sc = (struct sc16is7xx_sc *)regs->cr_cookie;
    205 	struct sc16is7xx_i2c_softc *isc = SC16IS7XX_TO_I2C(sc);
    206 	int error = 0;
    207 
    208 	if (regs->cr_has_errored)
    209 		return;
    210 
    211 	error = sc16is7xxi2c_write_register_direct(isc->sc_tag, isc->sc_addr,
    212 	    reg, regs->cr_channel, __UNCONST(datap), count);
    213 
    214 	if (error) {
    215 		device_printf(sc->sc_dev, "sc16is7xx_i2c_com_write_multi_1: error=%d\n",error);
    216 		regs->cr_has_errored = true;
    217 		mutex_enter(&sc->sc_thread_mutex);
    218 		if (sc->sc_thread_state != SC16IS7XX_THREAD_GPIO) {
    219 			sc->sc_thread_state = SC16IS7XX_THREAD_STALLED;
    220 			cv_signal(&sc->sc_threadvar);
    221 		}
    222 		mutex_exit(&sc->sc_thread_mutex);
    223 	}
    224 }
    225 
    226 static const struct sc16is7xx_accessfuncs sc16is7xx_i2c_com_accessfuncs = {
    227 	.com_read_1 = sc16is7xx_i2c_com_read_1,
    228 	.com_write_1 = sc16is7xx_i2c_com_write_1,
    229 	.com_write_multi_1 = sc16is7xx_i2c_com_write_multi_1,
    230 };
    231 
    232 static int
    233 sc16is7xxi2c_poke(i2c_tag_t tag, i2c_addr_t addr, bool matchdebug)
    234 {
    235 	uint8_t reg = SC16IS7XX_REGISTER_SPR;
    236 	uint8_t buf[1];
    237 	int error;
    238 
    239 	error = sc16is7xxi2c_read_register_direct(tag, addr, reg, 0, buf, 1);
    240 	if (matchdebug) {
    241 		printf("poke addr=%02x, error=%d\n", addr, error);
    242 	}
    243 	return error;
    244 }
    245 
    246 static int
    247 sc16is7xxi2c_match(device_t parent, cfdata_t cf, void *aux)
    248 {
    249 	struct i2c_attach_args *ia = aux;
    250 	int error, match_result;
    251 	const bool matchdebug = false;
    252 	bool indirect_found = false;
    253 	int i;
    254 
    255 	if (iic_use_direct_match(ia, cf, sc16is7xx_compat_data, &match_result)) {
    256 		return match_result;
    257 	}
    258 	for (i = SC16IS7XX_LOW_I2C_ADDR; i <= SC16IS7XX_HIGH_I2C_ADDR && indirect_found == false; i++) {
    259 		if (ia->ia_addr == i) {
    260 			if (matchdebug) {
    261 				printf("sc16is7xxi2c_match possible indirect: ia_addr=%02x, i=%02x\n", ia->ia_addr, i);
    262 			}
    263 			indirect_found = true;
    264 		}
    265 	}
    266 
    267 	if (!indirect_found)
    268 		return 0;
    269 
    270 	/* Check to see if something is really at this i2c address. This will
    271 	 * keep phantom devices from appearing */
    272 
    273 	error = sc16is7xxi2c_poke(ia->ia_tag, ia->ia_addr, matchdebug);
    274 
    275 	return error == 0 ? I2C_MATCH_ADDRESS_AND_PROBE : 0;
    276 }
    277 
    278 static void
    279 sc16is7xxi2c_attach(device_t parent, device_t self, void *aux)
    280 {
    281 	struct sc16is7xx_i2c_softc *isc = device_private(self);
    282 	struct sc16is7xx_sc *sc = &isc->sc_sc16is7xx;
    283 	struct i2c_attach_args *ia = aux;
    284 
    285 	sc->sc_dev = self;
    286 	sc->sc_funcs = &sc16is7xx_i2c_accessfuncs;
    287 	sc->sc_com_funcs = &sc16is7xx_i2c_com_accessfuncs;
    288 
    289 	isc->sc_tag = ia->ia_tag;
    290 	isc->sc_addr = ia->ia_addr;
    291 
    292 	sc16is7xx_attach(sc);
    293 }
    294 
    295 static int
    296 sc16is7xxi2c_detach(device_t self, int flags)
    297 {
    298 	struct sc16is7xx_i2c_softc *isc = device_private(self);
    299 
    300 	return sc16is7xx_detach(&isc->sc_sc16is7xx, flags);
    301 }
    302