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