| 1 | /* $NetBSD: i2c_exec.c,v 1.10 2015/03/07 14:16:51 jmcneill 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.10 2015/03/07 14:16:51 jmcneill Exp $" ); |
| 40 | |
| 41 | #include <sys/param.h> |
| 42 | #include <sys/systm.h> |
| 43 | #include <sys/device.h> |
| 44 | #include <sys/module.h> |
| 45 | #include <sys/event.h> |
| 46 | #include <sys/conf.h> |
| 47 | |
| 48 | #define _I2C_PRIVATE |
| 49 | #include <dev/i2c/i2cvar.h> |
| 50 | |
| 51 | static uint8_t iic_smbus_crc8(uint16_t); |
| 52 | static uint8_t iic_smbus_pec(int, uint8_t *, uint8_t *); |
| 53 | |
| 54 | static int i2cexec_modcmd(modcmd_t, void *); |
| 55 | |
| 56 | /* |
| 57 | * iic_exec: |
| 58 | * |
| 59 | * Simplified I2C client interface engine. |
| 60 | * |
| 61 | * This and the SMBus routines are the preferred interface for |
| 62 | * client access to I2C/SMBus, since many automated controllers |
| 63 | * do not provide access to the low-level primitives of the I2C |
| 64 | * bus protocol. |
| 65 | */ |
| 66 | int |
| 67 | iic_exec(i2c_tag_t tag, i2c_op_t op, i2c_addr_t addr, const void *vcmd, |
| 68 | size_t cmdlen, void *vbuf, size_t buflen, int flags) |
| 69 | { |
| 70 | const uint8_t *cmd = vcmd; |
| 71 | uint8_t *buf = vbuf; |
| 72 | int error; |
| 73 | size_t len; |
| 74 | |
| 75 | if ((flags & I2C_F_PEC) && cmdlen > 0 && tag->ic_exec != NULL) { |
| 76 | uint8_t data[33]; /* XXX */ |
| 77 | uint8_t b[3]; |
| 78 | |
| 79 | b[0] = addr << 1; |
| 80 | b[1] = cmd[0]; |
| 81 | |
| 82 | switch (buflen) { |
| 83 | case 0: |
| 84 | data[0] = iic_smbus_pec(2, b, NULL); |
| 85 | buflen++; |
| 86 | break; |
| 87 | case 1: |
| 88 | b[2] = buf[0]; |
| 89 | data[0] = iic_smbus_pec(3, b, NULL); |
| 90 | data[1] = b[2]; |
| 91 | buflen++; |
| 92 | break; |
| 93 | case 2: |
| 94 | break; |
| 95 | default: |
| 96 | KASSERT(buflen+1 < sizeof(data)); |
| 97 | memcpy(data, vbuf, buflen); |
| 98 | data[buflen] = iic_smbus_pec(2, b, data); |
| 99 | buflen++; |
| 100 | break; |
| 101 | } |
| 102 | |
| 103 | return ((*tag->ic_exec)(tag->ic_cookie, op, addr, cmd, |
| 104 | cmdlen, data, buflen, flags)); |
| 105 | } |
| 106 | |
| 107 | /* |
| 108 | * Defer to the controller if it provides an exec function. Use |
| 109 | * it if it does. |
| 110 | */ |
| 111 | if (tag->ic_exec != NULL) |
| 112 | return ((*tag->ic_exec)(tag->ic_cookie, op, addr, cmd, |
| 113 | cmdlen, buf, buflen, flags)); |
| 114 | |
| 115 | if ((len = cmdlen) != 0) { |
| 116 | if ((error = iic_initiate_xfer(tag, addr, flags)) != 0) |
| 117 | goto bad; |
| 118 | while (len--) { |
| 119 | if ((error = iic_write_byte(tag, *cmd++, flags)) != 0) |
| 120 | goto bad; |
| 121 | } |
| 122 | } else if (buflen == 0) { |
| 123 | /* |
| 124 | * This is a quick_read()/quick_write() command with |
| 125 | * neither command nor data bytes |
| 126 | */ |
| 127 | if (I2C_OP_STOP_P(op)) |
| 128 | flags |= I2C_F_STOP; |
| 129 | if (I2C_OP_READ_P(op)) |
| 130 | flags |= I2C_F_READ; |
| 131 | if ((error = iic_initiate_xfer(tag, addr, flags)) != 0) |
| 132 | goto bad; |
| 133 | } |
| 134 | |
| 135 | if (I2C_OP_READ_P(op)) |
| 136 | flags |= I2C_F_READ; |
| 137 | |
| 138 | len = buflen; |
| 139 | while (len--) { |
| 140 | if (len == 0 && I2C_OP_STOP_P(op)) |
| 141 | flags |= I2C_F_STOP; |
| 142 | if (I2C_OP_READ_P(op)) { |
| 143 | /* Send REPEATED START. */ |
| 144 | if ((len + 1) == buflen && |
| 145 | (error = iic_initiate_xfer(tag, addr, flags)) != 0) |
| 146 | goto bad; |
| 147 | /* NACK on last byte. */ |
| 148 | if (len == 0) |
| 149 | flags |= I2C_F_LAST; |
| 150 | if ((error = iic_read_byte(tag, buf++, flags)) != 0) |
| 151 | goto bad; |
| 152 | } else { |
| 153 | /* Maybe send START. */ |
| 154 | if ((len + 1) == buflen && cmdlen == 0 && |
| 155 | (error = iic_initiate_xfer(tag, addr, flags)) != 0) |
| 156 | goto bad; |
| 157 | if ((error = iic_write_byte(tag, *buf++, flags)) != 0) |
| 158 | goto bad; |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | return (0); |
| 163 | bad: |
| 164 | iic_send_stop(tag, flags); |
| 165 | return (error); |
| 166 | } |
| 167 | |
| 168 | /* |
| 169 | * iic_smbus_write_byte: |
| 170 | * |
| 171 | * Perform an SMBus "write byte" operation. |
| 172 | */ |
| 173 | int |
| 174 | iic_smbus_write_byte(i2c_tag_t tag, i2c_addr_t addr, uint8_t cmd, |
| 175 | uint8_t val, int flags) |
| 176 | { |
| 177 | |
| 178 | return (iic_exec(tag, I2C_OP_WRITE_WITH_STOP, addr, &cmd, 1, |
| 179 | &val, 1, flags)); |
| 180 | } |
| 181 | |
| 182 | /* |
| 183 | * iic_smbus_write_word: |
| 184 | * |
| 185 | * Perform an SMBus "write word" operation. |
| 186 | */ |
| 187 | int |
| 188 | iic_smbus_write_word(i2c_tag_t tag, i2c_addr_t addr, uint8_t cmd, |
| 189 | uint16_t val, int flags) |
| 190 | { |
| 191 | uint8_t vbuf[2]; |
| 192 | |
| 193 | vbuf[0] = val & 0xff; |
| 194 | vbuf[1] = (val >> 8) & 0xff; |
| 195 | |
| 196 | return (iic_exec(tag, I2C_OP_WRITE_WITH_STOP, addr, &cmd, 1, |
| 197 | vbuf, 2, flags)); |
| 198 | } |
| 199 | |
| 200 | /* |
| 201 | * iic_smbus_read_byte: |
| 202 | * |
| 203 | * Perform an SMBus "read byte" operation. |
| 204 | */ |
| 205 | int |
| 206 | iic_smbus_read_byte(i2c_tag_t tag, i2c_addr_t addr, uint8_t cmd, |
| 207 | uint8_t *valp, int flags) |
| 208 | { |
| 209 | |
| 210 | return (iic_exec(tag, I2C_OP_READ_WITH_STOP, addr, &cmd, 1, |
| 211 | valp, 1, flags)); |
| 212 | } |
| 213 | |
| 214 | /* |
| 215 | * iic_smbus_read_word: |
| 216 | * |
| 217 | * Perform an SMBus "read word" operation. |
| 218 | */ |
| 219 | int |
| 220 | iic_smbus_read_word(i2c_tag_t tag, i2c_addr_t addr, uint8_t cmd, |
| 221 | uint16_t *valp, int flags) |
| 222 | { |
| 223 | |
| 224 | return (iic_exec(tag, I2C_OP_READ_WITH_STOP, addr, &cmd, 1, |
| 225 | (uint8_t *)valp, 2, flags)); |
| 226 | } |
| 227 | |
| 228 | /* |
| 229 | * iic_smbus_receive_byte: |
| 230 | * |
| 231 | * Perform an SMBus "receive byte" operation. |
| 232 | */ |
| 233 | int |
| 234 | iic_smbus_receive_byte(i2c_tag_t tag, i2c_addr_t addr, uint8_t *valp, |
| 235 | int flags) |
| 236 | { |
| 237 | |
| 238 | return (iic_exec(tag, I2C_OP_READ_WITH_STOP, addr, NULL, 0, |
| 239 | valp, 1, flags)); |
| 240 | } |
| 241 | |
| 242 | /* |
| 243 | * iic_smbus_send_byte: |
| 244 | * |
| 245 | * Perform an SMBus "send byte" operation. |
| 246 | */ |
| 247 | int |
| 248 | iic_smbus_send_byte(i2c_tag_t tag, i2c_addr_t addr, uint8_t val, int flags) |
| 249 | { |
| 250 | |
| 251 | return (iic_exec(tag, I2C_OP_WRITE_WITH_STOP, addr, NULL, 0, |
| 252 | &val, 1, flags)); |
| 253 | } |
| 254 | |
| 255 | /* |
| 256 | * iic_smbus_quick_read: |
| 257 | * |
| 258 | * Perform an SMBus "quick read" operation. |
| 259 | */ |
| 260 | int |
| 261 | iic_smbus_quick_read(i2c_tag_t tag, i2c_addr_t addr, int flags) |
| 262 | { |
| 263 | |
| 264 | return (iic_exec(tag, I2C_OP_READ_WITH_STOP, addr, NULL, 0, |
| 265 | NULL, 0, flags)); |
| 266 | } |
| 267 | |
| 268 | /* |
| 269 | * iic_smbus_quick_write: |
| 270 | * |
| 271 | * Perform an SMBus "quick write" operation. |
| 272 | */ |
| 273 | int |
| 274 | iic_smbus_quick_write(i2c_tag_t tag, i2c_addr_t addr, int flags) |
| 275 | { |
| 276 | |
| 277 | return (iic_exec(tag, I2C_OP_WRITE_WITH_STOP, addr, NULL, 0, |
| 278 | NULL, 0, flags)); |
| 279 | } |
| 280 | |
| 281 | /* |
| 282 | * iic_smbus_block_read: |
| 283 | * |
| 284 | * Perform an SMBus "block read" operation. |
| 285 | */ |
| 286 | int |
| 287 | iic_smbus_block_read(i2c_tag_t tag, i2c_addr_t addr, uint8_t cmd, |
| 288 | uint8_t *vbuf, size_t buflen, int flags) |
| 289 | { |
| 290 | |
| 291 | return (iic_exec(tag, I2C_OP_READ_BLOCK, addr, &cmd, 1, |
| 292 | vbuf, buflen, flags)); |
| 293 | } |
| 294 | |
| 295 | /* |
| 296 | * iic_smbus_block_write: |
| 297 | * |
| 298 | * Perform an SMBus "block write" operation. |
| 299 | */ |
| 300 | int |
| 301 | iic_smbus_block_write(i2c_tag_t tag, i2c_addr_t addr, uint8_t cmd, |
| 302 | uint8_t *vbuf, size_t buflen, int flags) |
| 303 | { |
| 304 | |
| 305 | return (iic_exec(tag, I2C_OP_WRITE_BLOCK, addr, &cmd, 1, |
| 306 | vbuf, buflen, flags)); |
| 307 | } |
| 308 | |
| 309 | /* |
| 310 | * iic_smbus_crc8 |
| 311 | * |
| 312 | * Private helper for calculating packet error checksum |
| 313 | */ |
| 314 | static uint8_t |
| 315 | iic_smbus_crc8(uint16_t data) |
| 316 | { |
| 317 | int i; |
| 318 | |
| 319 | for (i = 0; i < 8; i++) { |
| 320 | if (data & 0x8000) |
| 321 | data = data ^ (0x1070U << 3); |
| 322 | data = data << 1; |
| 323 | } |
| 324 | |
| 325 | return (uint8_t)(data >> 8); |
| 326 | } |
| 327 | |
| 328 | /* |
| 329 | * iic_smbus_pec |
| 330 | * |
| 331 | * Private function for calculating packet error checking on SMBus |
| 332 | * packets. |
| 333 | */ |
| 334 | static uint8_t |
| 335 | iic_smbus_pec(int count, uint8_t *s, uint8_t *r) |
| 336 | { |
| 337 | int i; |
| 338 | uint8_t crc = 0; |
| 339 | |
| 340 | for (i = 0; i < count; i++) |
| 341 | crc = iic_smbus_crc8((crc ^ s[i]) << 8); |
| 342 | if (r != NULL) |
| 343 | for (i = 0; i <= r[0]; i++) |
| 344 | crc = iic_smbus_crc8((crc ^ r[i]) << 8); |
| 345 | |
| 346 | return crc; |
| 347 | } |
| 348 | |
| 349 | MODULE(MODULE_CLASS_MISC, i2cexec, NULL); |
| 350 | |
| 351 | static int |
| 352 | i2cexec_modcmd(modcmd_t cmd, void *opaque) |
| 353 | { |
| 354 | switch (cmd) { |
| 355 | case MODULE_CMD_INIT: |
| 356 | case MODULE_CMD_FINI: |
| 357 | return 0; |
| 358 | break; |
| 359 | default: |
| 360 | return ENOTTY; |
| 361 | } |
| 362 | } |
| 363 | |