1/* $NetBSD: umcpmio_iic.c,v 1.1 2025/11/29 18:39:14 brad Exp $ */ 2 3/* 4 * Copyright (c) 2024, 2025 Brad Spencer <brad@anduin.eldar.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19#include <sys/cdefs.h> 20__KERNEL_RCSID(0, "$NetBSD: umcpmio_iic.c,v 1.1 2025/11/29 18:39:14 brad Exp $"); 21 22#ifdef _KERNEL_OPT 23#include "opt_usb.h" 24#endif 25 26#include <sys/param.h> 27#include <sys/types.h> 28 29#include <dev/i2c/i2cvar.h> 30 31#include <dev/usb/umcpmio.h> 32#include <dev/usb/umcpmio_hid_reports.h> 33#include <dev/usb/umcpmio_transport.h> 34#include <dev/usb/umcpmio_iic.h> 35#include <dev/usb/umcpmio_subr.h> 36 37#define UMCPMIO_DEBUG 1 38#ifdef UMCPMIO_DEBUG 39#define DPRINTF(x) do { if (umcpmiodebug) printf x; } while (0) 40#define DPRINTFN(n, x) do { if (umcpmiodebug > (n)) printf x; } while (0) 41extern int umcpmiodebug; 42#else 43#define DPRINTF(x) __nothing 44#define DPRINTFN(n, x) __nothing 45#endif 46 47/* Stuff required for the I2C part of the MCP2221 / MCP2221A */ 48 49/* Clear status of the I2C engine */ 50 51static void 52mcp2221_set_i2c_speed(struct mcp2221_status_req *req, int flags) 53{ 54 int i2cbaud = MCP2221_DEFAULT_I2C_SPEED; 55 56 if (flags & I2C_F_SPEED) 57 i2cbaud = 400000; 58 59 req->set_i2c_speed = MCP2221_I2C_SET_SPEED; 60 if (i2cbaud <= 0) 61 i2cbaud = MCP2221_DEFAULT_I2C_SPEED; 62 63 /* Everyone and their brother seems to store the I2C divider like this, 64 * so do likewise */ 65 req->i2c_clock_divider = (MCP2221_INTERNAL_CLOCK / i2cbaud) - 3; 66} 67 68static int 69mcp2221_set_i2c_speed_one(struct umcpmio_softc *sc, int flags) 70{ 71 int err = 0; 72 struct mcp2221_status_req req; 73 struct mcp2221_status_res res; 74 75 KASSERT(mutex_owned(&sc->sc_action_mutex)); 76 77 memset(&req, 0, MCP2221_REQ_BUFFER_SIZE); 78 mcp2221_set_i2c_speed(&req, flags); 79 err = mcp2221_put_status(sc, &req, &res); 80 if (err) 81 goto out; 82 if (res.set_i2c_speed == MCP2221_I2C_SPEED_BUSY) 83 err = EBUSY; 84out: 85 return err; 86} 87 88static int 89mcp2221_i2c_clear(struct umcpmio_softc *sc) 90{ 91 int err = 0; 92 struct mcp2221_status_req status_req; 93 struct mcp2221_status_res status_res; 94 95 KASSERT(mutex_owned(&sc->sc_action_mutex)); 96 97 memset(&status_req, 0, MCP2221_REQ_BUFFER_SIZE); 98 status_req.cmd = MCP2221_CMD_STATUS; 99 status_req.cancel_transfer = MCP2221_I2C_DO_CANCEL; 100 101 err = umcpmio_send_report(sc, 102 (uint8_t *) & status_req, MCP2221_REQ_BUFFER_SIZE, 103 (uint8_t *) & status_res, sc->sc_cv_wait); 104 105 if (err) { 106 err = EIO; 107 goto out; 108 } 109 umcpmio_dump_buffer(sc->sc_dumpbuffer, 110 (uint8_t *) & status_res, MCP2221_RES_BUFFER_SIZE, 111 "mcp2221_i2c_clear buffer copy"); 112 113 if (status_res.cmd != MCP2221_CMD_STATUS && 114 status_res.completion != MCP2221_CMD_COMPLETE_OK) { 115 err = EIO; 116 goto out; 117 } 118 umcpmio_dump_buffer(true, 119 (uint8_t *) & status_res, MCP2221_RES_BUFFER_SIZE, 120 "mcp2221_i2c_clear res buffer"); 121 122out: 123 return err; 124} 125 126/* 127 * There isn't much required to acquire or release the I2C bus, but the man 128 * pages says these are needed 129 */ 130 131static int 132mcp2221_acquire_bus(void *v, int flags) 133{ 134 return 0; 135} 136 137static void 138mcp2221_release_bus(void *v, int flags) 139{ 140 return; 141} 142 143/* 144 * The I2C write and I2C read functions mostly use an algorithm that Adafruit 145 * came up with in their Python based driver. A lot of other people have used 146 * this same algorithm to good effect. If changes are made to the I2C read and 147 * write functions, it is HIGHLY advisable that a MCP2221 or MCP2221A be on 148 * hand to test them. 149 */ 150 151/* This is what is considered a fatal return from the engine. */ 152 153static bool 154mcp2221_i2c_fatal(uint8_t state) 155{ 156 int r = false; 157 158 if (state == MCP2221_ENGINE_ADDRNACK || 159 state == MCP2221_ENGINE_STARTTIMEOUT || 160 state == MCP2221_ENGINE_REPSTARTTIMEOUT || 161 state == MCP2221_ENGINE_STOPTIMEOUT || 162 state == MCP2221_ENGINE_READTIMEOUT || 163 state == MCP2221_ENGINE_WRITETIMEOUT || 164 state == MCP2221_ENGINE_ADDRTIMEOUT) 165 r = true; 166 return r; 167} 168 169static int 170mcp2221_i2c_write(struct umcpmio_softc *sc, i2c_op_t op, i2c_addr_t addr, 171 const void *cmdbuf, size_t cmdlen, void *databuf, size_t datalen, 172 int flags) 173{ 174 struct mcp2221_i2c_req i2c_req; 175 struct mcp2221_i2c_res i2c_res; 176 struct mcp2221_status_res status_res; 177 int remaining; 178 int err = 0; 179 uint8_t cmd; 180 size_t totallen = 0; 181 int wretry = sc->sc_retry_busy_write; 182 int wsretry = sc->sc_retry_busy_write; 183 184 mutex_enter(&sc->sc_action_mutex); 185 err = mcp2221_get_status(sc, &status_res); 186 mutex_exit(&sc->sc_action_mutex); 187 if (err) 188 goto out; 189 if (status_res.internal_i2c_state != 0) { 190 DPRINTF(("mcp2221_i2c_write: internal state not zero," 191 " clearing. internal_i2c_state=%02x\n", 192 status_res.internal_i2c_state)); 193 mutex_enter(&sc->sc_action_mutex); 194 err = mcp2221_i2c_clear(sc); 195 mutex_exit(&sc->sc_action_mutex); 196 } 197 if (err) 198 goto out; 199 200 if (cmdbuf != NULL) 201 totallen += cmdlen; 202 if (databuf != NULL) 203 totallen += datalen; 204 205again: 206 memset(&i2c_req, 0, MCP2221_REQ_BUFFER_SIZE); 207 cmd = MCP2221_I2C_WRITE_DATA_NS; 208 if (I2C_OP_STOP_P(op)) 209 cmd = MCP2221_I2C_WRITE_DATA; 210 i2c_req.cmd = cmd; 211 i2c_req.lsblen = totallen; 212 i2c_req.msblen = 0; 213 i2c_req.slaveaddr = addr << 1; 214 215 remaining = 0; 216 if (cmdbuf != NULL) { 217 memcpy(&i2c_req.data[0], cmdbuf, cmdlen); 218 remaining = cmdlen; 219 } 220 if (databuf != NULL) 221 memcpy(&i2c_req.data[remaining], databuf, datalen); 222 223 DPRINTF(("mcp2221_i2c_write: I2C WRITE: cmd: %02x\n", cmd)); 224 umcpmio_dump_buffer(sc->sc_dumpbuffer, 225 (uint8_t *) & i2c_req, MCP2221_REQ_BUFFER_SIZE, 226 "mcp2221_i2c_write: write req buffer copy"); 227 228 mutex_enter(&sc->sc_action_mutex); 229 err = umcpmio_send_report(sc, 230 (uint8_t *) & i2c_req, MCP2221_REQ_BUFFER_SIZE, 231 (uint8_t *) & i2c_res, sc->sc_cv_wait); 232 mutex_exit(&sc->sc_action_mutex); 233 if (err) { 234 err = EIO; 235 goto out; 236 } 237 umcpmio_dump_buffer(sc->sc_dumpbuffer, 238 (uint8_t *) & i2c_res, MCP2221_RES_BUFFER_SIZE, 239 "mcp2221_i2c_write: write res buffer copy"); 240 if (i2c_res.cmd == cmd && 241 i2c_res.completion == MCP2221_CMD_COMPLETE_OK) { 242 /* Adafruit does a read back of the status at this point. We 243 * choose not to do that. That is done later anyway, and it 244 * seemed to be redundent. */ 245 } else if (i2c_res.cmd == cmd && 246 i2c_res.completion == MCP2221_I2C_ENGINE_BUSY) { 247 DPRINTF(("mcp2221_i2c_write:" 248 " I2C engine busy\n")); 249 250 if (mcp2221_i2c_fatal(i2c_res.internal_i2c_state)) { 251 err = EIO; 252 goto out; 253 } 254 wretry--; 255 if (wretry > 0) { 256 WAITMS(sc->sc_busy_delay); 257 goto again; 258 } else { 259 err = EBUSY; 260 goto out; 261 } 262 } else { 263 err = EIO; 264 goto out; 265 } 266 267 while (wsretry > 0) { 268 wsretry--; 269 270 DPRINTF(("mcp2221_i2c_write: checking status loop:" 271 " wcretry=%d\n", wsretry)); 272 273 mutex_enter(&sc->sc_action_mutex); 274 err = mcp2221_get_status(sc, &status_res); 275 mutex_exit(&sc->sc_action_mutex); 276 if (err) { 277 err = EIO; 278 break; 279 } 280 umcpmio_dump_buffer(sc->sc_dumpbuffer, 281 (uint8_t *) & status_res, 282 MCP2221_RES_BUFFER_SIZE, 283 "mcp2221_i2c_write post check status"); 284 /* Since there isn't any documentation on what some of the 285 * internal state means, it isn't clear that this is any 286 * different than than MCP2221_ENGINE_ADDRNACK in the other 287 * state register. */ 288 289 if (status_res.internal_i2c_state20 & 290 MCP2221_ENGINE_T1_MASK_NACK) { 291 DPRINTF(("mcp2221_i2c_write post check:" 292 " engine internal state T1 says NACK\n")); 293 err = EIO; 294 break; 295 } 296 if (status_res.internal_i2c_state == 0) { 297 DPRINTF(("mcp2221_i2c_write post check:" 298 " engine internal state is ZERO\n")); 299 err = 0; 300 break; 301 } 302 if (status_res.internal_i2c_state == 303 MCP2221_ENGINE_WRITINGNOSTOP && 304 cmd == MCP2221_I2C_WRITE_DATA_NS) { 305 DPRINTF(("mcp2221_i2c_write post check:" 306 " engine internal state is WRITINGNOSTOP\n")); 307 err = 0; 308 break; 309 } 310 if (mcp2221_i2c_fatal(status_res.internal_i2c_state)) { 311 DPRINTF(("mcp2221_i2c_write post check:" 312 " engine internal state is fatal: %02x\n", 313 status_res.internal_i2c_state)); 314 err = EIO; 315 break; 316 } 317 WAITMS(sc->sc_busy_delay); 318 } 319 320out: 321 return err; 322} 323 324/* 325 * This one deviates a bit from Adafruit in that is supports a straight 326 * read and a write + read. That is, write a register to read from and 327 * then do the read. 328 */ 329 330static int 331mcp2221_i2c_read(struct umcpmio_softc *sc, i2c_op_t op, i2c_addr_t addr, 332 const void *cmdbuf, size_t cmdlen, void *databuf, size_t datalen, int 333 flags) 334{ 335 struct mcp2221_i2c_req i2c_req; 336 struct mcp2221_i2c_res i2c_res; 337 struct mcp2221_i2c_fetch_req i2c_fetch_req; 338 struct mcp2221_i2c_fetch_res i2c_fetch_res; 339 struct mcp2221_status_res status_res; 340 int err = 0; 341 uint8_t cmd; 342 int rretry = sc->sc_retry_busy_read; 343 344 if (cmdbuf != NULL) { 345 DPRINTF(("mcp2221_i2c_read: has a cmdbuf, doing write first:" 346 " addr=%02x\n", addr)); 347 err = mcp2221_i2c_write(sc, I2C_OP_WRITE, addr, cmdbuf, cmdlen, 348 NULL, 0, flags); 349 } 350 if (err) 351 goto out; 352 353 mutex_enter(&sc->sc_action_mutex); 354 err = mcp2221_get_status(sc, &status_res); 355 mutex_exit(&sc->sc_action_mutex); 356 if (err) 357 goto out; 358 359 if (status_res.internal_i2c_state != 0 && 360 status_res.internal_i2c_state != MCP2221_ENGINE_WRITINGNOSTOP) { 361 DPRINTF(("mcp2221_i2c_read:" 362 " internal state not zero and not WRITINGNOSTOP," 363 " clearing. internal_i2c_state=%02x\n", 364 status_res.internal_i2c_state)); 365 mutex_enter(&sc->sc_action_mutex); 366 err = mcp2221_i2c_clear(sc); 367 mutex_exit(&sc->sc_action_mutex); 368 } 369 if (err) 370 goto out; 371 372 memset(&i2c_req, 0, MCP2221_REQ_BUFFER_SIZE); 373 if (cmdbuf == NULL && 374 status_res.internal_i2c_state != MCP2221_ENGINE_WRITINGNOSTOP) { 375 cmd = MCP2221_I2C_READ_DATA; 376 } else { 377 cmd = MCP2221_I2C_READ_DATA_RS; 378 } 379 380 /* The chip apparently can't do a READ without a STOP operation. Report 381 * that, and try treating it like a READ with a STOP. This won't work 382 * for a lot of devices. */ 383 384 if (!I2C_OP_STOP_P(op) && sc->sc_reportreadnostop) { 385 device_printf(sc->sc_dev, 386 "mcp2221_i2c_read: ************ called with READ" 387 " without STOP ***************\n"); 388 } 389 i2c_req.cmd = cmd; 390 i2c_req.lsblen = datalen; 391 i2c_req.msblen = 0; 392 i2c_req.slaveaddr = (addr << 1) | 0x01; 393 394 DPRINTF(("mcp2221_i2c_read: I2C READ normal read:" 395 " cmd=%02x, addr=%02x\n", cmd, addr)); 396 397 umcpmio_dump_buffer(sc->sc_dumpbuffer, 398 (uint8_t *) & i2c_req, MCP2221_RES_BUFFER_SIZE, 399 "mcp2221_i2c_read normal read req buffer copy"); 400 401 mutex_enter(&sc->sc_action_mutex); 402 err = umcpmio_send_report(sc, 403 (uint8_t *) & i2c_req, MCP2221_REQ_BUFFER_SIZE, 404 (uint8_t *) & i2c_res, sc->sc_cv_wait); 405 mutex_exit(&sc->sc_action_mutex); 406 407 if (err) { 408 err = EIO; 409 goto out; 410 } 411 umcpmio_dump_buffer(sc->sc_dumpbuffer, 412 (uint8_t *) & i2c_res, MCP2221_RES_BUFFER_SIZE, 413 "mcp2221_i2c_read read-request response buffer copy"); 414 415 while (rretry > 0) { 416 rretry--; 417 DPRINTF(("mcp2221_i2c_read: fetch loop: rretry=%d\n", rretry)); 418 err = 0; 419 memset(&i2c_fetch_req, 0, MCP2221_REQ_BUFFER_SIZE); 420 i2c_fetch_req.cmd = MCP2221_CMD_I2C_FETCH_READ_DATA; 421 mutex_enter(&sc->sc_action_mutex); 422 err = umcpmio_send_report(sc, 423 (uint8_t *) & i2c_fetch_req, MCP2221_REQ_BUFFER_SIZE, 424 (uint8_t *) & i2c_fetch_res, sc->sc_cv_wait); 425 mutex_exit(&sc->sc_action_mutex); 426 umcpmio_dump_buffer(sc->sc_dumpbuffer, 427 (uint8_t *) & i2c_fetch_req, MCP2221_RES_BUFFER_SIZE, 428 "mcp2221_i2c_read fetch res buffer copy"); 429 430 if (i2c_fetch_res.cmd != MCP2221_CMD_I2C_FETCH_READ_DATA) { 431 err = EIO; 432 break; 433 } 434 if (i2c_fetch_res.completion == 435 MCP2221_FETCH_READ_PARTIALDATA || 436 i2c_fetch_res.fetchlen == MCP2221_FETCH_READERROR) { 437 DPRINTF(("mcp2221_i2c_read: fetch loop:" 438 " partial data or read error:" 439 " completion=%02x, fetchlen=%02x\n", 440 i2c_fetch_res.completion, 441 i2c_fetch_res.fetchlen)); 442 WAITMS(sc->sc_busy_delay); 443 err = EAGAIN; 444 continue; 445 } 446 if (i2c_fetch_res.internal_i2c_state == 447 MCP2221_ENGINE_ADDRNACK) { 448 DPRINTF(("mcp2221_i2c_read:" 449 " fetch loop: engine NACK\n")); 450 err = EIO; 451 break; 452 } 453 if (i2c_fetch_res.internal_i2c_state == 0 && 454 i2c_fetch_res.fetchlen == 0) { 455 DPRINTF(("mcp2221_i2c_read: fetch loop:" 456 " internal state and fetch len are ZERO\n")); 457 err = 0; 458 break; 459 } 460 if (i2c_fetch_res.internal_i2c_state == 461 MCP2221_ENGINE_READPARTIAL || 462 i2c_fetch_res.internal_i2c_state == 463 MCP2221_ENGINE_READCOMPLETE) { 464 DPRINTF(("mcp2221_i2c_read:" 465 " fetch loop: read partial or" 466 " read complete: internal_i2c_state=%02x\n", 467 i2c_fetch_res.internal_i2c_state)); 468 err = 0; 469 break; 470 } 471 } 472 if (err == EAGAIN) 473 err = ETIMEDOUT; 474 if (err) 475 goto out; 476 477 if (databuf == NULL || 478 i2c_fetch_res.fetchlen == MCP2221_FETCH_READERROR) { 479 DPRINTF(("mcp2221_i2c_read: copy data:" 480 " databuf is NULL\n")); 481 goto out; 482 } 483 const int size = uimin(i2c_fetch_res.fetchlen, datalen); 484 DPRINTF(("mcp2221_i2c_read: copy data: size=%d, fetchlen=%d\n", 485 size, i2c_fetch_res.fetchlen)); 486 if (size > 0) { 487 memcpy(databuf, &i2c_fetch_res.data[0], size); 488 } 489out: 490 return err; 491} 492 493static int 494mcp2221_i2c_exec(void *v, i2c_op_t op, i2c_addr_t addr, const void *cmdbuf, 495 size_t cmdlen, void *databuf, size_t datalen, int flags) 496{ 497 struct umcpmio_softc *sc = v; 498 size_t totallen = 0; 499 int err = 0; 500 501 if (sc->sc_dying) 502 return EIO; 503 504 if (addr > 0x7f) 505 return ENOTSUP; 506 507 if (cmdbuf != NULL) 508 totallen += cmdlen; 509 if (databuf != NULL) 510 totallen += datalen; 511 512 /* There is a way to do a transfer that is larger than 60 bytes, but it 513 * requires that your break the transfer up into pieces and send them 514 * in 60 byte chunks. We just won't support that right now. It would 515 * be somewhat unusual for there to be a transfer that big, unless you 516 * are trying to do block transfers and that isn't natively supported 517 * by the chip anyway... so those have to be broken up and sent as 518 * bytes. */ 519 520 if (totallen > 60) 521 return ENOTSUP; 522 523 if (I2C_OP_WRITE_P(op)) { 524 err = mcp2221_i2c_write(sc, op, addr, cmdbuf, cmdlen, 525 databuf, datalen, flags); 526 527 DPRINTF(("umcpmio_exec: I2C WRITE: err=%d\n", err)); 528 } else { 529 err = mcp2221_i2c_read(sc, op, addr, cmdbuf, cmdlen, 530 databuf, datalen, flags); 531 532 DPRINTF(("umcpmio_exec: I2C READ: err=%d\n", err)); 533 } 534 535 return err; 536} 537 538int 539umcpmio_i2c_attach(struct umcpmio_softc *sc) 540{ 541 sc->sc_reportreadnostop = true; 542 sc->sc_busy_delay = 1; 543 sc->sc_retry_busy_read = 50; 544 sc->sc_retry_busy_write = 50; 545 546 int err = 0; 547 548 /* The datasheet suggests that it is possble for this to fail if the 549 * I2C port is currently being used. However... since you just plugged 550 * in the chip, the I2C port should not really be in use at that 551 * moment. In any case, try hard to set this and don't make it fatal if 552 * it did not get set. */ 553 int i2cspeed; 554 for (i2cspeed = 0; i2cspeed < 3; i2cspeed++) { 555 mutex_enter(&sc->sc_action_mutex); 556 err = mcp2221_set_i2c_speed_one(sc, I2C_SPEED_SM); 557 mutex_exit(&sc->sc_action_mutex); 558 if (err) { 559 aprint_error_dev(sc->sc_dev, "umcpmio_i2c_attach:" 560 " set I2C speed: err=%d\n", 561 err); 562 delay(300); 563 } 564 break; 565 } 566 567 iic_tag_init(&sc->sc_i2c_tag); 568 sc->sc_i2c_tag.ic_cookie = sc; 569 sc->sc_i2c_tag.ic_acquire_bus = mcp2221_acquire_bus; 570 sc->sc_i2c_tag.ic_release_bus = mcp2221_release_bus; 571 sc->sc_i2c_tag.ic_exec = mcp2221_i2c_exec; 572 573 sc->sc_i2c_dev = iicbus_attach(sc->sc_dev, &sc->sc_i2c_tag); 574 575 return err; 576} 577