i2c_exec.c revision 1.20 1 /* $NetBSD: i2c_exec.c,v 1.20 2025/09/21 14:43:19 thorpej Exp $ */
2
3 /*
4 * Copyright (c) 2003 Wasabi Systems, Inc.
5 * All rights reserved.
6 *
7 * Written by Jason R. Thorpe for Wasabi Systems, Inc.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3. All advertising materials mentioning features or use of this software
18 * must display the following acknowledgement:
19 * This product includes software developed for the NetBSD Project by
20 * Wasabi Systems, Inc.
21 * 4. The name of Wasabi Systems, Inc. may not be used to endorse
22 * or promote products derived from this software without specific prior
23 * written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
27 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC
29 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
36 */
37
38 #include <sys/cdefs.h>
39 __KERNEL_RCSID(0, "$NetBSD: i2c_exec.c,v 1.20 2025/09/21 14:43:19 thorpej Exp $");
40
41 #include <sys/param.h>
42 #include <sys/systm.h>
43 #include <sys/cpu.h>
44 #include <sys/device.h>
45 #include <sys/module.h>
46 #include <sys/event.h>
47 #include <sys/conf.h>
48 #include <sys/kernel.h>
49
50 #define _I2C_PRIVATE
51 #include <dev/i2c/i2cvar.h>
52
53 static uint8_t iic_smbus_crc8(uint16_t);
54 static uint8_t iic_smbus_pec(int, uint8_t *, uint8_t *);
55
56 static int i2cexec_modcmd(modcmd_t, void *);
57
58 static inline int
59 iic_op_flags(int flags)
60 {
61
62 return flags | ((cold || shutting_down) ? I2C_F_POLL : 0);
63 }
64
65 /*
66 * iic_tag_init:
67 *
68 * Perform some basic initialization of the i2c controller tag.
69 */
70 void
71 iic_tag_init(i2c_tag_t tag)
72 {
73
74 memset(tag, 0, sizeof(*tag));
75 mutex_init(&tag->ic_bus_lock, MUTEX_DEFAULT, IPL_NONE);
76 tag->ic_channel = I2C_CHANNEL_DEFAULT;
77 }
78
79 /*
80 * iic_tag_fini:
81 *
82 * Teardown of the i2c controller tag.
83 */
84 void
85 iic_tag_fini(i2c_tag_t tag)
86 {
87
88 mutex_destroy(&tag->ic_bus_lock);
89 }
90
91 /*
92 * iic_acquire_bus:
93 *
94 * Acquire the I2C bus for use by a client.
95 */
96 int
97 iic_acquire_bus(i2c_tag_t tag, int flags)
98 {
99 #if 0 /* XXX Not quite ready for this yet. */
100 KASSERT(!cpu_intr_p());
101 #endif
102
103 flags = iic_op_flags(flags);
104
105 int error = iic_acquire_bus_lock(&tag->ic_bus_lock, flags);
106 if (error) {
107 return error;
108 }
109
110 if (tag->ic_acquire_bus) {
111 error = (*tag->ic_acquire_bus)(tag->ic_cookie, flags);
112 }
113
114 if (__predict_false(error)) {
115 iic_release_bus_lock(&tag->ic_bus_lock);
116 }
117
118 return error;
119 }
120
121 /*
122 * iic_release_bus:
123 *
124 * Release the I2C bus, allowing another client to use it.
125 */
126 void
127 iic_release_bus(i2c_tag_t tag, int flags)
128 {
129 #if 0 /* XXX Not quite ready for this yet. */
130 KASSERT(!cpu_intr_p());
131 #endif
132
133 flags = iic_op_flags(flags);
134
135 if (tag->ic_release_bus) {
136 (*tag->ic_release_bus)(tag->ic_cookie, flags);
137 }
138
139 iic_release_bus_lock(&tag->ic_bus_lock);
140 }
141
142 /*
143 * iic_exec:
144 *
145 * Simplified I2C client interface engine.
146 *
147 * This and the SMBus routines are the preferred interface for
148 * client access to I2C/SMBus, since many automated controllers
149 * do not provide access to the low-level primitives of the I2C
150 * bus protocol.
151 */
152 int
153 iic_exec(i2c_tag_t tag, i2c_op_t op, i2c_addr_t addr, const void *vcmd,
154 size_t cmdlen, void *vbuf, size_t buflen, int flags)
155 {
156 const uint8_t *cmd = vcmd;
157 uint8_t *buf = vbuf;
158 int error;
159 size_t len;
160
161 #if 0 /* XXX Not quite ready for this yet. */
162 KASSERT(!cpu_intr_p());
163 #endif
164
165 flags = iic_op_flags(flags);
166
167 if ((flags & I2C_F_PEC) && cmdlen > 0 && tag->ic_exec != NULL) {
168 uint8_t data[33]; /* XXX */
169 uint8_t b[3];
170
171 b[0] = addr << 1;
172 b[1] = cmd[0];
173
174 switch (buflen) {
175 case 0:
176 data[0] = iic_smbus_pec(2, b, NULL);
177 buflen++;
178 break;
179 case 1:
180 b[2] = buf[0];
181 data[0] = iic_smbus_pec(3, b, NULL);
182 data[1] = b[2];
183 buflen++;
184 break;
185 case 2:
186 break;
187 default:
188 KASSERT(buflen+1 < sizeof(data));
189 memcpy(data, vbuf, buflen);
190 data[buflen] = iic_smbus_pec(2, b, data);
191 buflen++;
192 break;
193 }
194
195 return ((*tag->ic_exec)(tag->ic_cookie, op, addr, cmd,
196 cmdlen, data, buflen, flags));
197 }
198
199 /*
200 * Defer to the controller if it provides an exec function. Use
201 * it if it does.
202 */
203 if (tag->ic_exec != NULL)
204 return ((*tag->ic_exec)(tag->ic_cookie, op, addr, cmd,
205 cmdlen, buf, buflen, flags));
206
207 if ((len = cmdlen) != 0) {
208 if ((error = iic_initiate_xfer(tag, addr, flags)) != 0)
209 goto bad;
210 while (len--) {
211 if ((error = iic_write_byte(tag, *cmd++, flags)) != 0)
212 goto bad;
213 }
214 } else if (buflen == 0) {
215 /*
216 * This is a quick_read()/quick_write() command with
217 * neither command nor data bytes
218 */
219 if (I2C_OP_STOP_P(op))
220 flags |= I2C_F_STOP;
221 if (I2C_OP_READ_P(op))
222 flags |= I2C_F_READ;
223 if ((error = iic_initiate_xfer(tag, addr, flags)) != 0)
224 goto bad;
225 }
226
227 if (I2C_OP_READ_P(op))
228 flags |= I2C_F_READ;
229
230 len = buflen;
231 while (len--) {
232 if (len == 0 && I2C_OP_STOP_P(op))
233 flags |= I2C_F_STOP;
234 if (I2C_OP_READ_P(op)) {
235 /* Send REPEATED START. */
236 if ((len + 1) == buflen &&
237 (error = iic_initiate_xfer(tag, addr, flags)) != 0)
238 goto bad;
239 /* NACK on last byte. */
240 if (len == 0)
241 flags |= I2C_F_LAST;
242 if ((error = iic_read_byte(tag, buf++, flags)) != 0)
243 goto bad;
244 } else {
245 /* Maybe send START. */
246 if ((len + 1) == buflen && cmdlen == 0 &&
247 (error = iic_initiate_xfer(tag, addr, flags)) != 0)
248 goto bad;
249 if ((error = iic_write_byte(tag, *buf++, flags)) != 0)
250 goto bad;
251 }
252 }
253
254 return (0);
255 bad:
256 iic_send_stop(tag, flags);
257 return (error);
258 }
259
260 /*
261 * iic_smbus_write_byte:
262 *
263 * Perform an SMBus "write byte" operation.
264 */
265 int
266 iic_smbus_write_byte(i2c_tag_t tag, i2c_addr_t addr, uint8_t cmd,
267 uint8_t val, int flags)
268 {
269
270 return (iic_exec(tag, I2C_OP_WRITE_WITH_STOP, addr, &cmd, 1,
271 &val, 1, flags));
272 }
273
274 /*
275 * iic_smbus_write_word:
276 *
277 * Perform an SMBus "write word" operation.
278 */
279 int
280 iic_smbus_write_word(i2c_tag_t tag, i2c_addr_t addr, uint8_t cmd,
281 uint16_t val, int flags)
282 {
283 uint8_t vbuf[2];
284
285 vbuf[0] = val & 0xff;
286 vbuf[1] = (val >> 8) & 0xff;
287
288 return (iic_exec(tag, I2C_OP_WRITE_WITH_STOP, addr, &cmd, 1,
289 vbuf, 2, flags));
290 }
291
292 /*
293 * iic_smbus_read_byte:
294 *
295 * Perform an SMBus "read byte" operation.
296 */
297 int
298 iic_smbus_read_byte(i2c_tag_t tag, i2c_addr_t addr, uint8_t cmd,
299 uint8_t *valp, int flags)
300 {
301
302 return (iic_exec(tag, I2C_OP_READ_WITH_STOP, addr, &cmd, 1,
303 valp, 1, flags));
304 }
305
306 /*
307 * iic_smbus_read_word:
308 *
309 * Perform an SMBus "read word" operation.
310 */
311 int
312 iic_smbus_read_word(i2c_tag_t tag, i2c_addr_t addr, uint8_t cmd,
313 uint16_t *valp, int flags)
314 {
315
316 return (iic_exec(tag, I2C_OP_READ_WITH_STOP, addr, &cmd, 1,
317 (uint8_t *)valp, 2, flags));
318 }
319
320 /*
321 * iic_smbus_receive_byte:
322 *
323 * Perform an SMBus "receive byte" operation.
324 */
325 int
326 iic_smbus_receive_byte(i2c_tag_t tag, i2c_addr_t addr, uint8_t *valp,
327 int flags)
328 {
329
330 return (iic_exec(tag, I2C_OP_READ_WITH_STOP, addr, NULL, 0,
331 valp, 1, flags));
332 }
333
334 /*
335 * iic_smbus_send_byte:
336 *
337 * Perform an SMBus "send byte" operation.
338 */
339 int
340 iic_smbus_send_byte(i2c_tag_t tag, i2c_addr_t addr, uint8_t val, int flags)
341 {
342
343 return (iic_exec(tag, I2C_OP_WRITE_WITH_STOP, addr, NULL, 0,
344 &val, 1, flags));
345 }
346
347 /*
348 * iic_smbus_quick_read:
349 *
350 * Perform an SMBus "quick read" operation.
351 */
352 int
353 iic_smbus_quick_read(i2c_tag_t tag, i2c_addr_t addr, int flags)
354 {
355
356 return (iic_exec(tag, I2C_OP_READ_WITH_STOP, addr, NULL, 0,
357 NULL, 0, flags));
358 }
359
360 /*
361 * iic_smbus_quick_write:
362 *
363 * Perform an SMBus "quick write" operation.
364 */
365 int
366 iic_smbus_quick_write(i2c_tag_t tag, i2c_addr_t addr, int flags)
367 {
368
369 return (iic_exec(tag, I2C_OP_WRITE_WITH_STOP, addr, NULL, 0,
370 NULL, 0, flags));
371 }
372
373 /*
374 * iic_smbus_block_read:
375 *
376 * Perform an SMBus "block read" operation.
377 */
378 int
379 iic_smbus_block_read(i2c_tag_t tag, i2c_addr_t addr, uint8_t cmd,
380 uint8_t *vbuf, size_t buflen, int flags)
381 {
382
383 return (iic_exec(tag, I2C_OP_READ_BLOCK, addr, &cmd, 1,
384 vbuf, buflen, flags));
385 }
386
387 /*
388 * iic_smbus_block_write:
389 *
390 * Perform an SMBus "block write" operation.
391 */
392 int
393 iic_smbus_block_write(i2c_tag_t tag, i2c_addr_t addr, uint8_t cmd,
394 uint8_t *vbuf, size_t buflen, int flags)
395 {
396
397 return (iic_exec(tag, I2C_OP_WRITE_BLOCK, addr, &cmd, 1,
398 vbuf, buflen, flags));
399 }
400
401 /*
402 * iic_smbus_crc8
403 *
404 * Private helper for calculating packet error checksum
405 */
406 static uint8_t
407 iic_smbus_crc8(uint16_t data)
408 {
409 int i;
410
411 for (i = 0; i < 8; i++) {
412 if (data & 0x8000)
413 data = data ^ (0x1070U << 3);
414 data = data << 1;
415 }
416
417 return (uint8_t)(data >> 8);
418 }
419
420 /*
421 * iic_smbus_pec
422 *
423 * Private function for calculating packet error checking on SMBus
424 * packets.
425 */
426 static uint8_t
427 iic_smbus_pec(int count, uint8_t *s, uint8_t *r)
428 {
429 int i;
430 uint8_t crc = 0;
431
432 for (i = 0; i < count; i++)
433 crc = iic_smbus_crc8((crc ^ s[i]) << 8);
434 if (r != NULL)
435 for (i = 0; i <= r[0]; i++)
436 crc = iic_smbus_crc8((crc ^ r[i]) << 8);
437
438 return crc;
439 }
440
441 MODULE(MODULE_CLASS_MISC, i2cexec, NULL);
442
443 static int
444 i2cexec_modcmd(modcmd_t cmd, void *opaque)
445 {
446 switch (cmd) {
447 case MODULE_CMD_INIT:
448 case MODULE_CMD_FINI:
449 return 0;
450 break;
451 default:
452 return ENOTTY;
453 }
454 }
455