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