umcpmio_iic.c revision 1.1
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