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 sc->busdev = gpiobus_attach_bus(dev); 265 if (sc->busdev == NULL) { 266 device_printf(dev, "Could not create busdev child\n"); 267 return (ENXIO); 268 } 269 270 OF_device_register_xref(OF_xref_from_node(ofw_bus_get_node(dev)), dev); 271 272 #ifdef DEBUG 273 switch (sc->chip) { 274 case TCA6416_TYPE: 275 tca6416_regdump_setup(dev); 276 break; 277 case TCA6408_TYPE: 278 tca6408_regdump_setup(dev); 279 break; 280 default: 281 __assert_unreachable(); 282 } 283 #endif 284 285 return (0); 286 } 287 288 static int 289 tca64xx_detach(device_t dev) 290 { 291 struct tca64xx_softc *sc; 292 293 sc = device_get_softc(dev); 294 295 if (sc->busdev != NULL) 296 gpiobus_detach_bus(sc->busdev); 297 298 mtx_destroy(&sc->mtx); 299 300 return (0); 301 } 302 303 static device_t 304 tca64xx_get_bus(device_t dev) 305 { 306 struct tca64xx_softc *sc; 307 308 sc = device_get_softc(dev); 309 310 return (sc->busdev); 311 } 312 313 static int 314 tca64xx_pin_max(device_t dev __unused, int *maxpin) 315 { 316 struct tca64xx_softc *sc; 317 318 sc = device_get_softc(dev); 319 320 if (maxpin == NULL) 321 return (EINVAL); 322 323 *maxpin = sc->num_pins-1; 324 325 return (0); 326 } 327 328 static int 329 tca64xx_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) 330 { 331 struct tca64xx_softc *sc; 332 333 sc = device_get_softc(dev); 334 335 if (pin >= sc->num_pins || caps == NULL) 336 return (EINVAL); 337 *caps = sc->pin_caps; 338 339 return (0); 340 } 341 342 static int 343 tca64xx_pin_getflags(device_t dev, uint32_t pin, uint32_t *pflags) 344 { 345 int error; 346 uint8_t bit, val, addr; 347 struct tca64xx_softc *sc; 348 349 sc = device_get_softc(dev); 350 351 bit = TCA64XX_BIT_FROM_PIN(pin); 352 353 if (pin >= sc->num_pins || pflags == NULL) 354 return (EINVAL); 355 356 addr = TCA64XX_REG_ADDR(pin, sc->conf_reg); 357 error = tca64xx_read(dev, addr, &val); 358 if (error != 0) 359 return (error); 360 361 *pflags = (val & (1 << bit)) ? GPIO_PIN_INPUT : GPIO_PIN_OUTPUT; 362 363 addr = TCA64XX_REG_ADDR(pin, sc->polarity_inv_reg); 364 error = tca64xx_read(dev, addr, &val); 365 if (error != 0) 366 return (error); 367 368 if (val & (1 << bit)) 369 *pflags |= GPIO_PIN_INVIN; 370 371 return (0); 372 } 373 374 static int 375 tca64xx_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) 376 { 377 uint8_t bit, val, addr, pins, inv_val; 378 int error; 379 struct tca64xx_softc *sc; 380 381 sc = device_get_softc(dev); 382 383 pins = sc->num_pins; 384 bit = TCA64XX_BIT_FROM_PIN(pin); 385 386 if (pin >= pins) 387 return (EINVAL); 388 mtx_lock(&sc->mtx); 389 390 addr = TCA64XX_REG_ADDR(pin, sc->conf_reg); 391 error = tca64xx_read(dev, addr, &val); 392 if (error != 0) 393 goto fail; 394 395 addr = TCA64XX_REG_ADDR(pin, sc->polarity_inv_reg); 396 error = tca64xx_read(dev, addr, &inv_val); 397 if (error != 0) 398 goto fail; 399 400 if (flags & GPIO_PIN_INPUT) 401 val |= (1 << bit); 402 else if (flags & GPIO_PIN_OUTPUT) 403 val &= ~(1 << bit); 404 405 if (flags & GPIO_PIN_INVIN) 406 inv_val |= (1 << bit); 407 else 408 inv_val &= ~(1 << bit); 409 410 addr = TCA64XX_REG_ADDR(pin, sc->conf_reg); 411 error = tca64xx_write(dev, addr, val); 412 if (error != 0) 413 goto fail; 414 415 addr = TCA64XX_REG_ADDR(pin, sc->polarity_inv_reg); 416 error = tca64xx_write(dev, addr, inv_val); 417 418 fail: 419 mtx_unlock(&sc->mtx); 420 return (error); 421 } 422 423 static int 424 tca64xx_pin_getname(device_t dev, uint32_t pin, char *name) 425 { 426 struct tca64xx_softc *sc; 427 428 sc = device_get_softc(dev); 429 430 if (pin >= sc->num_pins || name == NULL) 431 return (EINVAL); 432 433 snprintf(name, GPIOMAXNAME, "gpio_P%d%d", pin / TCA64XX_PINS_PER_REG, 434 pin % TCA64XX_PINS_PER_REG); 435 436 return (0); 437 } 438 439 static int 440 tca64xx_pin_get(device_t dev, uint32_t pin, unsigned int *pval) 441 { 442 uint8_t bit, addr, pins, reg_pvalue; 443 int error; 444 struct tca64xx_softc *sc; 445 446 sc = device_get_softc(dev); 447 448 pins = sc->num_pins; 449 addr = TCA64XX_REG_ADDR(pin, sc->in_port_reg); 450 bit = TCA64XX_BIT_FROM_PIN(pin); 451 452 if (pin >= pins || pval == NULL) 453 return (EINVAL); 454 455 dbg_dev_printf(dev, "Reading pin %u pvalue.", pin); 456 457 error = tca64xx_read(dev, addr, ®_pvalue); 458 if (error != 0) 459 return (error); 460 *pval = reg_pvalue & (1 << bit) ? 1 : 0; 461 462 return (0); 463 } 464 465 static int 466 tca64xx_pin_set(device_t dev, uint32_t pin, unsigned int val) 467 { 468 uint8_t bit, addr, pins, value; 469 int error; 470 struct tca64xx_softc *sc; 471 472 sc = device_get_softc(dev); 473 474 pins = sc->num_pins; 475 addr = TCA64XX_REG_ADDR(pin, sc->out_port_reg); 476 bit = TCA64XX_BIT_FROM_PIN(pin); 477 478 if (pin >= pins) 479 return (EINVAL); 480 481 dbg_dev_printf(dev, "Setting pin: %u to %u\n", pin, val); 482 483 mtx_lock(&sc->mtx); 484 485 error = tca64xx_read(dev, addr, &value); 486 if (error != 0) { 487 mtx_unlock(&sc->mtx); 488 dbg_dev_printf(dev, "Failed to read from register.\n"); 489 return (error); 490 } 491 492 if (val != 0) 493 value |= (1 << bit); 494 else 495 value &= ~(1 << bit); 496 497 error = tca64xx_write(dev, addr, value); 498 if (error != 0) { 499 mtx_unlock(&sc->mtx); 500 dbg_dev_printf(dev, "Could not write to register.\n"); 501 return (error); 502 } 503 504 mtx_unlock(&sc->mtx); 505 506 return (0); 507 } 508 509 static int 510 tca64xx_pin_toggle(device_t dev, uint32_t pin) 511 { 512 int error; 513 uint8_t bit, addr, pins, value; 514 struct tca64xx_softc *sc; 515 516 sc = device_get_softc(dev); 517 518 pins = sc->num_pins; 519 addr = TCA64XX_REG_ADDR(pin, sc->out_port_reg); 520 bit = TCA64XX_BIT_FROM_PIN(pin); 521 522 if (pin >= pins) 523 return (EINVAL); 524 525 dbg_dev_printf(dev, "Toggling pin: %d\n", pin); 526 527 mtx_lock(&sc->mtx); 528 529 error = tca64xx_read(dev, addr, &value); 530 if (error != 0) { 531 mtx_unlock(&sc->mtx); 532 dbg_dev_printf(dev, "Cannot read from register.\n"); 533 return (error); 534 } 535 536 value ^= (1 << bit); 537 538 error = tca64xx_write(dev, addr, value); 539 if (error != 0) { 540 mtx_unlock(&sc->mtx); 541 dbg_dev_printf(dev, "Cannot write to register.\n"); 542 return (error); 543 } 544 545 mtx_unlock(&sc->mtx); 546 547 return (0); 548 } 549 550 #ifdef DEBUG 551 static void 552 tca6416_regdump_setup(device_t dev) 553 { 554 struct sysctl_ctx_list *ctx; 555 struct sysctl_oid *node; 556 557 ctx = device_get_sysctl_ctx(dev); 558 node = device_get_sysctl_tree(dev); 559 560 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "in_reg_1", 561 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 562 TCA6416_IN_PORT_REG, tca64xx_regdump_sysctl, "A", "Input port 1"); 563 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "in_reg_2", 564 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 565 TCA6416_IN_PORT_REG | 1, tca64xx_regdump_sysctl, "A", 566 "Input port 2"); 567 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "out_reg_1", 568 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 569 TCA6416_OUT_PORT_REG, tca64xx_regdump_sysctl, "A", 570 "Output port 1"); 571 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "out_reg_2", 572 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 573 TCA6416_OUT_PORT_REG | 1, tca64xx_regdump_sysctl, "A", 574 "Output port 2"); 575 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "pol_inv_1", 576 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 577 TCA6416_POLARITY_INV_REG, tca64xx_regdump_sysctl, "A", 578 "Polarity inv 1"); 579 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "pol_inv_2", 580 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 581 TCA6416_POLARITY_INV_REG | 1, tca64xx_regdump_sysctl, "A", 582 "Polarity inv 2"); 583 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "conf_reg_1", 584 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 585 TCA6416_CONF_REG, tca64xx_regdump_sysctl, "A", "Configuration 1"); 586 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "conf_reg_2", 587 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 588 TCA6416_CONF_REG | 1, tca64xx_regdump_sysctl, "A", 589 "Configuration 2"); 590 } 591 592 static void 593 tca6408_regdump_setup(device_t dev) 594 { 595 struct sysctl_ctx_list *ctx; 596 struct sysctl_oid *node; 597 598 ctx = device_get_sysctl_ctx(dev); 599 node = device_get_sysctl_tree(dev); 600 601 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "in_reg_1", 602 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 603 TCA6408_IN_PORT_REG, tca64xx_regdump_sysctl, "A", "Input port 1"); 604 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "out_reg_1", 605 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 606 TCA6408_OUT_PORT_REG, tca64xx_regdump_sysctl, "A", 607 "Output port 1"); 608 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "pol_inv_1", 609 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 610 TCA6408_POLARITY_INV_REG, tca64xx_regdump_sysctl, 611 "A", "Polarity inv 1"); 612 SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "conf_reg_1", 613 CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, 614 TCA6408_CONF_REG, tca64xx_regdump_sysctl, "A", "Configuration 1"); 615 } 616 617 static int 618 tca64xx_regdump_sysctl(SYSCTL_HANDLER_ARGS) 619 { 620 device_t dev; 621 char buf[5]; 622 int len, error; 623 uint8_t reg, regval; 624 625 dev = (device_t)arg1; 626 reg = (uint8_t)arg2; 627 628 error = tca64xx_read(dev, reg, ®val); 629 if (error != 0) { 630 return (error); 631 } 632 633 len = snprintf(buf, 5, "0x%02x", regval); 634 635 error = sysctl_handle_string(oidp, buf, len, req); 636 637 return (error); 638 } 639 #endif 640