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