1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2020 Alstom Group. 5 * Copyright (c) 2020 Semihalf. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 /* 30 * Driver for TI TCA64XX I2C GPIO expander module. 31 * 32 * This driver only supports basic functionality 33 * (interrupt handling and polarity inversion were omitted). 34 */ 35 36 #include <sys/cdefs.h> 37 #include <sys/param.h> 38 #include <sys/bus.h> 39 #include <sys/gpio.h> 40 #include <sys/kernel.h> 41 #include <sys/module.h> 42 #include <sys/proc.h> 43 #include <sys/systm.h> 44 #include <sys/sysctl.h> 45 46 #include <machine/bus.h> 47 48 #include <dev/ofw/openfirm.h> 49 #include <dev/ofw/ofw_bus.h> 50 #include <dev/ofw/ofw_bus_subr.h> 51 52 #include <dev/iicbus/iicbus.h> 53 #include <dev/iicbus/iiconf.h> 54 55 #include <dev/gpio/gpiobusvar.h> 56 57 #include "gpio_if.h" 58 59 /* Base addresses of registers. LSB omitted. */ 60 61 #define TCA64XX_PINS_PER_REG 8 62 63 #define TCA64XX_BIT_FROM_PIN(pin) (pin % TCA64XX_PINS_PER_REG) 64 #define TCA64XX_REG_ADDR(pin, baseaddr) (baseaddr | (pin / \ 65 TCA64XX_PINS_PER_REG)) 66 #define TCA64XX_PIN_CAPS (GPIO_PIN_OUTPUT | GPIO_PIN_INPUT \ 67 | GPIO_PIN_PUSHPULL | GPIO_PIN_INVIN) 68 69 #define TCA6416_IN_PORT_REG 0x0 70 #define TCA6416_OUT_PORT_REG 0x2 71 #define TCA6416_POLARITY_INV_REG 0x4 72 #define TCA6416_CONF_REG 0x6 73 #define TCA6416_NUM_PINS 16 74 75 #define TCA6408_IN_PORT_REG 0x0 76 #define TCA6408_OUT_PORT_REG 0x1 77 #define TCA6408_POLARITY_INV_REG 0x2 78 #define TCA6408_CONF_REG 0x3 79 #define TCA6408_NUM_PINS 8 80 81 #ifdef DEBUG 82 #define dbg_dev_printf(dev, fmt, args...) \ 83 device_printf(dev, fmt, ##args) 84 #else 85 #define dbg_dev_printf(dev, fmt, args...) 86 #endif 87 88 enum chip_type{ 89 TCA6416_TYPE = 1, 90 TCA6408_TYPE 91 }; 92 93 struct tca64xx_softc { 94 device_t dev; 95 device_t busdev; 96 enum chip_type chip; 97 struct mtx mtx; 98 uint32_t addr; 99 uint8_t num_pins; 100 uint8_t in_port_reg; 101 uint8_t out_port_reg; 102 uint8_t polarity_inv_reg; 103 uint8_t conf_reg; 104 uint8_t pin_caps; 105 }; 106 107 static int tca64xx_read(device_t, uint8_t, uint8_t *); 108 static int tca64xx_write(device_t, uint8_t, uint8_t); 109 static int tca64xx_probe(device_t); 110 static int tca64xx_attach(device_t); 111 static int tca64xx_detach(device_t); 112 static device_t tca64xx_get_bus(device_t); 113 static int tca64xx_pin_max(device_t, int *); 114 static int tca64xx_pin_getcaps(device_t, uint32_t, uint32_t *); 115 static int tca64xx_pin_getflags(device_t, uint32_t, uint32_t *); 116 static int tca64xx_pin_setflags(device_t, uint32_t, uint32_t); 117 static int tca64xx_pin_getname(device_t, uint32_t, char *); 118 static int tca64xx_pin_get(device_t, uint32_t, unsigned int *); 119 static int tca64xx_pin_set(device_t, uint32_t, unsigned int); 120 static int tca64xx_pin_toggle(device_t, uint32_t); 121 #ifdef DEBUG 122 static void tca6408_regdump_setup(device_t dev); 123 static void tca6416_regdump_setup(device_t dev); 124 static int tca64xx_regdump_sysctl(SYSCTL_HANDLER_ARGS); 125 #endif 126 127 static device_method_t tca64xx_methods[] = { 128 DEVMETHOD(device_probe, tca64xx_probe), 129 DEVMETHOD(device_attach, tca64xx_attach), 130 DEVMETHOD(device_detach, tca64xx_detach), 131 132 /* GPIO methods */ 133 DEVMETHOD(gpio_get_bus, tca64xx_get_bus), 134 DEVMETHOD(gpio_pin_max, tca64xx_pin_max), 135 DEVMETHOD(gpio_pin_getcaps, tca64xx_pin_getcaps), 136 DEVMETHOD(gpio_pin_getflags, tca64xx_pin_getflags), 137 DEVMETHOD(gpio_pin_setflags, tca64xx_pin_setflags), 138 DEVMETHOD(gpio_pin_getname, tca64xx_pin_getname), 139 DEVMETHOD(gpio_pin_get, tca64xx_pin_get), 140 DEVMETHOD(gpio_pin_set, tca64xx_pin_set), 141 DEVMETHOD(gpio_pin_toggle, tca64xx_pin_toggle), 142 143 DEVMETHOD_END 144 }; 145 146 static driver_t tca64xx_driver = { 147 "gpio", 148 tca64xx_methods, 149 sizeof(struct tca64xx_softc) 150 }; 151 152 DRIVER_MODULE(tca64xx, iicbus, tca64xx_driver, 0, 0); 153 MODULE_VERSION(tca64xx, 1); 154 155 static struct ofw_compat_data compat_data[] = { 156 {"nxp,pca9555", TCA6416_TYPE}, 157 {"ti,tca6408", TCA6408_TYPE}, 158 {"ti,tca6416", TCA6416_TYPE}, 159 {"ti,tca9539", TCA6416_TYPE}, 160 {0,0} 161 }; 162 163 static int 164 tca64xx_read(device_t dev, uint8_t reg, uint8_t *data) 165 { 166 struct iic_msg msgs[2]; 167 struct tca64xx_softc *sc; 168 int error; 169 170 sc = device_get_softc(dev); 171 if (data == NULL) 172 return (EINVAL); 173 174 msgs[0].slave = sc->addr; 175 msgs[0].flags = IIC_M_WR | IIC_M_NOSTOP; 176 msgs[0].len = 1; 177 msgs[0].buf = ® 178 179 msgs[1].slave = sc->addr; 180 msgs[1].flags = IIC_M_RD; 181 msgs[1].len = 1; 182 msgs[1].buf = data; 183 184 error = iicbus_transfer_excl(dev, msgs, 2, IIC_WAIT); 185 return (iic2errno(error)); 186 } 187 188 static int 189 tca64xx_write(device_t dev, uint8_t reg, uint8_t val) 190 { 191 struct iic_msg msg; 192 struct tca64xx_softc *sc; 193 int error; 194 uint8_t buffer[2] = {reg, val}; 195 196 sc = device_get_softc(dev); 197 198 msg.slave = sc->addr; 199 msg.flags = IIC_M_WR; 200 msg.len = 2; 201 msg.buf = buffer; 202 203 error = iicbus_transfer_excl(dev, &msg, 1, IIC_WAIT); 204 return (iic2errno(error)); 205 } 206 207 static int 208 tca64xx_probe(device_t dev) 209 { 210 const struct ofw_compat_data *compat_ptr; 211 212 if (!ofw_bus_status_okay(dev)) 213 return (ENXIO); 214 215 compat_ptr = ofw_bus_search_compatible(dev, compat_data); 216 217 switch (compat_ptr->ocd_data) { 218 case TCA6416_TYPE: 219 device_set_desc(dev, "TCA6416 I/O expander"); 220 break; 221 case TCA6408_TYPE: 222 device_set_desc(dev, "TCA6408 I/O expander"); 223 break; 224 default: 225 return (ENXIO); 226 } 227 228 return (BUS_PROBE_DEFAULT); 229 } 230 231 static int 232 tca64xx_attach(device_t dev) 233 { 234 struct tca64xx_softc *sc; 235 const struct ofw_compat_data *compat_ptr; 236 237 sc = device_get_softc(dev); 238 compat_ptr = ofw_bus_search_compatible(dev, compat_data); 239 240 switch (compat_ptr->ocd_data) { 241 case TCA6416_TYPE: 242 sc->in_port_reg = TCA6416_IN_PORT_REG; 243 sc->out_port_reg = TCA6416_OUT_PORT_REG; 244 sc->polarity_inv_reg = TCA6416_POLARITY_INV_REG; 245 sc->conf_reg = TCA6416_CONF_REG; 246 sc->num_pins = TCA6416_NUM_PINS; 247 break; 248 case TCA6408_TYPE: 249 sc->in_port_reg = TCA6408_IN_PORT_REG; 250 sc->out_port_reg = TCA6408_OUT_PORT_REG; 251 sc->polarity_inv_reg = TCA6408_POLARITY_INV_REG; 252 sc->conf_reg = TCA6408_CONF_REG; 253 sc->num_pins = TCA6408_NUM_PINS; 254 break; 255 default: 256 __assert_unreachable(); 257 } 258 259 sc->pin_caps = TCA64XX_PIN_CAPS; 260 sc->chip = compat_ptr->ocd_data; 261 sc->dev = dev; 262 sc->addr = iicbus_get_addr(dev); 263 264 mtx_init(&sc->mtx, "tca64xx gpio", "gpio", MTX_DEF); 265 sc->busdev = gpiobus_attach_bus(dev); 266 if (sc->busdev == NULL) { 267 device_printf(dev, "Could not create busdev child\n"); 268 return (ENXIO); 269 } 270 271 OF_device_register_xref(OF_xref_from_node(ofw_bus_get_node(dev)), dev); 272 273 #ifdef DEBUG 274 switch (sc->chip) { 275 case TCA6416_TYPE: 276 tca6416_regdump_setup(dev); 277 break; 278 case TCA6408_TYPE: 279 tca6408_regdump_setup(dev); 280 break; 281 default: 282 __assert_unreachable(); 283 } 284 #endif 285 286 return (0); 287 } 288 289 static int 290 tca64xx_detach(device_t dev) 291 { 292 struct tca64xx_softc *sc; 293 294 sc = device_get_softc(dev); 295 296 if (sc->busdev != NULL) 297 gpiobus_detach_bus(sc->busdev); 298 299 mtx_destroy(&sc->mtx); 300 301 return (0); 302 } 303 304 static device_t 305 tca64xx_get_bus(device_t dev) 306 { 307 struct tca64xx_softc *sc; 308 309 sc = device_get_softc(dev); 310 311 return (sc->busdev); 312 } 313 314 static int 315 tca64xx_pin_max(device_t dev __unused, int *maxpin) 316 { 317 struct tca64xx_softc *sc; 318 319 sc = device_get_softc(dev); 320 321 if (maxpin == NULL) 322 return (EINVAL); 323 324 *maxpin = sc->num_pins-1; 325 326 return (0); 327 } 328 329 static int 330 tca64xx_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) 331 { 332 struct tca64xx_softc *sc; 333 334 sc = device_get_softc(dev); 335 336 if (pin >= sc->num_pins || caps == NULL) 337 return (EINVAL); 338 *caps = sc->pin_caps; 339 340 return (0); 341 } 342 343 static int 344 tca64xx_pin_getflags(device_t dev, uint32_t pin, uint32_t *pflags) 345 { 346 int error; 347 uint8_t bit, val, addr; 348 struct tca64xx_softc *sc; 349 350 sc = device_get_softc(dev); 351 352 bit = TCA64XX_BIT_FROM_PIN(pin); 353 354 if (pin >= sc->num_pins || pflags == NULL) 355 return (EINVAL); 356 357 addr = TCA64XX_REG_ADDR(pin, sc->conf_reg); 358 error = tca64xx_read(dev, addr, &val); 359 if (error != 0) 360 return (error); 361 362 *pflags = (val & (1 << bit)) ? GPIO_PIN_INPUT : GPIO_PIN_OUTPUT; 363 364 addr = TCA64XX_REG_ADDR(pin, sc->polarity_inv_reg); 365 error = tca64xx_read(dev, addr, &val); 366 if (error != 0) 367 return (error); 368 369 if (val & (1 << bit)) 370 *pflags |= GPIO_PIN_INVIN; 371 372 return (0); 373 } 374 375 static int 376 tca64xx_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) 377 { 378 uint8_t bit, val, addr, pins, inv_val; 379 int error; 380 struct tca64xx_softc *sc; 381 382 sc = device_get_softc(dev); 383 384 pins = sc->num_pins; 385 bit = TCA64XX_BIT_FROM_PIN(pin); 386 387 if (pin >= pins) 388 return (EINVAL); 389 mtx_lock(&sc->mtx); 390 391 addr = TCA64XX_REG_ADDR(pin, sc->conf_reg); 392 error = tca64xx_read(dev, addr, &val); 393 if (error != 0) 394 goto fail; 395 396 addr = TCA64XX_REG_ADDR(pin, sc->polarity_inv_reg); 397 error = tca64xx_read(dev, addr, &inv_val); 398 if (error != 0) 399 goto fail; 400 401 if (flags & GPIO_PIN_INPUT) 402 val |= (1 << bit); 403 else if (flags & GPIO_PIN_OUTPUT) 404 val &= ~(1 << bit); 405 406 if (flags & GPIO_PIN_INVIN) 407 inv_val |= (1 << bit); 408 else 409 inv_val &= ~(1 << bit); 410 411 addr = TCA64XX_REG_ADDR(pin, sc->conf_reg); 412 error = tca64xx_write(dev, addr, val); 413 if (error != 0) 414 goto fail; 415 416 addr = TCA64XX_REG_ADDR(pin, sc->polarity_inv_reg); 417 error = tca64xx_write(dev, addr, inv_val); 418 419 fail: 420 mtx_unlock(&sc->mtx); 421 return (error); 422 } 423 424 static int 425 tca64xx_pin_getname(device_t dev, uint32_t pin, char *name) 426 { 427 struct tca64xx_softc *sc; 428 429 sc = device_get_softc(dev); 430 431 if (pin >= sc->num_pins || name == NULL) 432 return (EINVAL); 433 434 snprintf(name, GPIOMAXNAME, "gpio_P%d%d", pin / TCA64XX_PINS_PER_REG, 435 pin % TCA64XX_PINS_PER_REG); 436 437 return (0); 438 } 439 440 static int 441 tca64xx_pin_get(device_t dev, uint32_t pin, unsigned int *pval) 442 { 443 uint8_t bit, addr, pins, reg_pvalue; 444 int error; 445 struct tca64xx_softc *sc; 446 447 sc = device_get_softc(dev); 448 449 pins = sc->num_pins; 450 addr = TCA64XX_REG_ADDR(pin, sc->in_port_reg); 451 bit = TCA64XX_BIT_FROM_PIN(pin); 452 453 if (pin >= pins || pval == NULL) 454 return (EINVAL); 455 456 dbg_dev_printf(dev, "Reading pin %u pvalue.", pin); 457 458 error = tca64xx_read(dev, addr, ®_pvalue); 459 if (error != 0) 460 return (error); 461 *pval = reg_pvalue & (1 << bit) ? 1 : 0; 462 463 return (0); 464 } 465 466 static int 467 tca64xx_pin_set(device_t dev, uint32_t pin, unsigned int val) 468 { 469 uint8_t bit, addr, pins, value; 470 int error; 471 struct tca64xx_softc *sc; 472 473 sc = device_get_softc(dev); 474 475 pins = sc->num_pins; 476 addr = TCA64XX_REG_ADDR(pin, sc->out_port_reg); 477 bit = TCA64XX_BIT_FROM_PIN(pin); 478 479 if (pin >= pins) 480 return (EINVAL); 481 482 dbg_dev_printf(dev, "Setting pin: %u to %u\n", pin, val); 483 484 mtx_lock(&sc->mtx); 485 486 error = tca64xx_read(dev, addr, &value); 487 if (error != 0) { 488 mtx_unlock(&sc->mtx); 489 dbg_dev_printf(dev, "Failed to read from register.\n"); 490 return (error); 491 } 492 493 if (val != 0) 494 value |= (1 << bit); 495 else 496 value &= ~(1 << bit); 497 498 error = tca64xx_write(dev, addr, value); 499 if (error != 0) { 500 mtx_unlock(&sc->mtx); 501 dbg_dev_printf(dev, "Could not write to register.\n"); 502 return (error); 503 } 504 505 mtx_unlock(&sc->mtx); 506 507 return (0); 508 } 509 510 static int 511 tca64xx_pin_toggle(device_t dev, uint32_t pin) 512 { 513 int error; 514 uint8_t bit, addr, pins, value; 515 struct tca64xx_softc *sc; 516 517 sc = device_get_softc(dev); 518 519 pins = sc->num_pins; 520 addr = TCA64XX_REG_ADDR(pin, sc->out_port_reg); 521 bit = TCA64XX_BIT_FROM_PIN(pin); 522 523 if (pin >= pins) 524 return (EINVAL); 525 526 dbg_dev_printf(dev, "Toggling pin: %d\n", pin); 527 528 mtx_lock(&sc->mtx); 529 530 error = tca64xx_read(dev, addr, &value); 531 if (error != 0) { 532 mtx_unlock(&sc->mtx); 533 dbg_dev_printf(dev, "Cannot read from register.\n"); 534 return (error); 535 } 536 537 value ^= (1 << bit); 538 539 error = tca64xx_write(dev, addr, value); 540 if (error != 0) { 541 mtx_unlock(&sc->mtx); 542 dbg_dev_printf(dev, "Cannot write to register.\n"); 543 return (error); 544 } 545 546 mtx_unlock(&sc->mtx); 547 548 return (0); 549 } 550 551 #ifdef DEBUG 552 static void 553 tca6416_regdump_setup(device_t dev) 554 { 555 struct sysctl_ctx_list *ctx; 556 struct sysctl_oid *node; 557 558 ctx = device_get_sysctl_ctx(dev); 559 node = device_get_sysctl_tree(dev); 560 561 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "in_reg_1", 562 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 563 TCA6416_IN_PORT_REG, tca64xx_regdump_sysctl, "A", "Input port 1"); 564 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "in_reg_2", 565 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 566 TCA6416_IN_PORT_REG | 1, tca64xx_regdump_sysctl, "A", 567 "Input port 2"); 568 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "out_reg_1", 569 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 570 TCA6416_OUT_PORT_REG, tca64xx_regdump_sysctl, "A", 571 "Output port 1"); 572 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "out_reg_2", 573 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 574 TCA6416_OUT_PORT_REG | 1, tca64xx_regdump_sysctl, "A", 575 "Output port 2"); 576 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "pol_inv_1", 577 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 578 TCA6416_POLARITY_INV_REG, tca64xx_regdump_sysctl, "A", 579 "Polarity inv 1"); 580 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "pol_inv_2", 581 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 582 TCA6416_POLARITY_INV_REG | 1, tca64xx_regdump_sysctl, "A", 583 "Polarity inv 2"); 584 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "conf_reg_1", 585 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 586 TCA6416_CONF_REG, tca64xx_regdump_sysctl, "A", "Configuration 1"); 587 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "conf_reg_2", 588 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 589 TCA6416_CONF_REG | 1, tca64xx_regdump_sysctl, "A", 590 "Configuration 2"); 591 } 592 593 static void 594 tca6408_regdump_setup(device_t dev) 595 { 596 struct sysctl_ctx_list *ctx; 597 struct sysctl_oid *node; 598 599 ctx = device_get_sysctl_ctx(dev); 600 node = device_get_sysctl_tree(dev); 601 602 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "in_reg_1", 603 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 604 TCA6408_IN_PORT_REG, tca64xx_regdump_sysctl, "A", "Input port 1"); 605 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "out_reg_1", 606 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 607 TCA6408_OUT_PORT_REG, tca64xx_regdump_sysctl, "A", 608 "Output port 1"); 609 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "pol_inv_1", 610 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 611 TCA6408_POLARITY_INV_REG, tca64xx_regdump_sysctl, 612 "A", "Polarity inv 1"); 613 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "conf_reg_1", 614 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 615 TCA6408_CONF_REG, tca64xx_regdump_sysctl, "A", "Configuration 1"); 616 } 617 618 static int 619 tca64xx_regdump_sysctl(SYSCTL_HANDLER_ARGS) 620 { 621 device_t dev; 622 char buf[5]; 623 int len, error; 624 uint8_t reg, regval; 625 626 dev = (device_t)arg1; 627 reg = (uint8_t)arg2; 628 629 error = tca64xx_read(dev, reg, ®val); 630 if (error != 0) { 631 return (error); 632 } 633 634 len = snprintf(buf, 5, "0x%02x", regval); 635 636 error = sysctl_handle_string(oidp, buf, len, req); 637 638 return (error); 639 } 640 #endif 641