1 /* $NetBSD: ssdfb_i2c.c,v 1.9 2021/01/28 14:42:45 thorpej Exp $ */ 2 3 /* 4 * Copyright (c) 2019 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Tobias Nygren. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 __KERNEL_RCSID(0, "$NetBSD: ssdfb_i2c.c,v 1.9 2021/01/28 14:42:45 thorpej Exp $"); 34 35 #include <sys/param.h> 36 #include <sys/device.h> 37 #include <dev/wscons/wsdisplayvar.h> 38 #include <dev/rasops/rasops.h> 39 #include <dev/i2c/i2cvar.h> 40 #include <dev/ic/ssdfbvar.h> 41 42 struct ssdfb_i2c_softc { 43 struct ssdfb_softc sc; 44 i2c_tag_t sc_i2c_tag; 45 i2c_addr_t sc_i2c_addr; 46 size_t sc_transfer_size; 47 }; 48 49 static int ssdfb_i2c_match(device_t, cfdata_t, void *); 50 static void ssdfb_i2c_attach(device_t, device_t, void *); 51 static int ssdfb_i2c_detach(device_t, int); 52 53 static int ssdfb_i2c_probe_transfer_size(struct ssdfb_i2c_softc *, bool); 54 static int ssdfb_i2c_transfer(struct ssdfb_i2c_softc *, uint8_t, uint8_t *, 55 size_t, int); 56 static int ssdfb_i2c_cmd(void *, uint8_t *, size_t, bool); 57 static int ssdfb_i2c_transfer_rect(void *, uint8_t, uint8_t, uint8_t, 58 uint8_t, uint8_t *, size_t, bool); 59 static int ssdfb_i2c_transfer_rect_ssd1306(void *, uint8_t, uint8_t, 60 uint8_t, uint8_t, uint8_t *, size_t, bool); 61 static int ssdfb_i2c_transfer_rect_sh1106(void *, uint8_t, uint8_t, 62 uint8_t, uint8_t, uint8_t *, size_t, bool); 63 static int ssdfb_smbus_transfer_rect(void *, uint8_t, uint8_t, uint8_t, 64 uint8_t, uint8_t *, size_t, bool); 65 66 CFATTACH_DECL_NEW(ssdfb_iic, sizeof(struct ssdfb_i2c_softc), 67 ssdfb_i2c_match, ssdfb_i2c_attach, ssdfb_i2c_detach, NULL); 68 69 static const struct device_compatible_entry compat_data[] = { 70 { .compat = "solomon,ssd1306fb-i2c", 71 .value = SSDFB_PRODUCT_SSD1306_GENERIC }, 72 73 { .compat = "sino,sh1106fb-i2c", 74 .value = SSDFB_PRODUCT_SH1106_GENERIC }, 75 76 DEVICE_COMPAT_EOL 77 }; 78 79 static int 80 ssdfb_i2c_match(device_t parent, cfdata_t match, void *aux) 81 { 82 struct i2c_attach_args *ia = aux; 83 int match_result; 84 85 if (iic_use_direct_match(ia, match, compat_data, &match_result)) 86 return match_result; 87 88 switch (ia->ia_addr) { 89 case SSDFB_I2C_DEFAULT_ADDR: 90 case SSDFB_I2C_ALTERNATIVE_ADDR: 91 return I2C_MATCH_ADDRESS_ONLY; 92 } 93 94 return 0; 95 } 96 97 static void 98 ssdfb_i2c_attach(device_t parent, device_t self, void *aux) 99 { 100 struct ssdfb_i2c_softc *sc = device_private(self); 101 struct cfdata *cf = device_cfdata(self); 102 struct i2c_attach_args *ia = aux; 103 int flags = cf->cf_flags; 104 105 if ((flags & SSDFB_ATTACH_FLAG_PRODUCT_MASK) == SSDFB_PRODUCT_UNKNOWN) { 106 const struct device_compatible_entry *dce = 107 iic_compatible_lookup(ia, compat_data); 108 if (dce != NULL) { 109 flags |= (int)dce->value; 110 } 111 } 112 if ((flags & SSDFB_ATTACH_FLAG_PRODUCT_MASK) == SSDFB_PRODUCT_UNKNOWN) 113 flags |= SSDFB_PRODUCT_SSD1306_GENERIC; 114 115 flags |= SSDFB_ATTACH_FLAG_MPSAFE; 116 sc->sc.sc_dev = self; 117 sc->sc_i2c_tag = ia->ia_tag; 118 sc->sc_i2c_addr = ia->ia_addr; 119 sc->sc.sc_cookie = (void *)sc; 120 sc->sc.sc_cmd = ssdfb_i2c_cmd; 121 sc->sc.sc_transfer_rect = ssdfb_i2c_transfer_rect; 122 123 ssdfb_attach(&sc->sc, flags); 124 } 125 126 static int 127 ssdfb_i2c_detach(device_t self, int flags) 128 { 129 struct ssdfb_i2c_softc *sc = device_private(self); 130 131 return ssdfb_detach(&sc->sc); 132 } 133 134 static int 135 ssdfb_i2c_probe_transfer_size(struct ssdfb_i2c_softc *sc, bool usepoll) 136 { 137 int flags = usepoll ? I2C_F_POLL : 0; 138 uint8_t cb = SSDFB_I2C_CTRL_BYTE_DATA_MASK; 139 int error; 140 uint8_t buf[128]; 141 size_t len; 142 143 error = iic_acquire_bus(sc->sc_i2c_tag, flags); 144 if (error) 145 return error; 146 len = sizeof(buf); 147 memset(buf, 0, len); 148 while (len > 0) { 149 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 150 sc->sc_i2c_addr, &cb, sizeof(cb), buf, len, flags); 151 if (!error) { 152 break; 153 } 154 len >>= 1; 155 } 156 if (!error && len < 2) { 157 error = E2BIG; 158 } else { 159 sc->sc_transfer_size = len; 160 } 161 (void) iic_release_bus(sc->sc_i2c_tag, flags); 162 163 return error; 164 } 165 166 static int 167 ssdfb_i2c_transfer(struct ssdfb_i2c_softc *sc, uint8_t cb, uint8_t *data, 168 size_t len, int flags) 169 { 170 int error; 171 size_t xfer_size = sc->sc_transfer_size; 172 173 while (len >= xfer_size) { 174 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 175 sc->sc_i2c_addr, &cb, sizeof(cb), data, xfer_size, flags); 176 if (error) 177 return error; 178 len -= xfer_size; 179 data += xfer_size; 180 } 181 if (len > 0) { 182 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 183 sc->sc_i2c_addr, &cb, sizeof(cb), data, len, flags); 184 } 185 186 return error; 187 } 188 189 static int 190 ssdfb_i2c_cmd(void *cookie, uint8_t *cmd, size_t len, bool usepoll) 191 { 192 struct ssdfb_i2c_softc *sc = (struct ssdfb_i2c_softc *)cookie; 193 int flags = usepoll ? I2C_F_POLL : 0; 194 uint8_t cb = 0; 195 int error; 196 197 error = iic_acquire_bus(sc->sc_i2c_tag, flags); 198 if (error) 199 return error; 200 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 201 sc->sc_i2c_addr, &cb, sizeof(cb), cmd, len, flags); 202 (void) iic_release_bus(sc->sc_i2c_tag, flags); 203 204 return error; 205 } 206 207 static int 208 ssdfb_i2c_transfer_rect(void *cookie, uint8_t fromcol, uint8_t tocol, 209 uint8_t frompage, uint8_t topage, uint8_t *p, size_t stride, bool usepoll) 210 { 211 struct ssdfb_i2c_softc *sc = (struct ssdfb_i2c_softc *)cookie; 212 uint8_t cmd[2]; 213 int error; 214 215 /* 216 * Test if large transfers are supported by the parent i2c bus and 217 * pick the fastest transfer routine for subsequent invocations. 218 */ 219 switch (sc->sc.sc_p->p_controller_id) { 220 case SSDFB_CONTROLLER_SSD1306: 221 sc->sc.sc_transfer_rect = ssdfb_i2c_transfer_rect_ssd1306; 222 break; 223 case SSDFB_CONTROLLER_SH1106: 224 sc->sc.sc_transfer_rect = ssdfb_i2c_transfer_rect_sh1106; 225 break; 226 default: 227 sc->sc.sc_transfer_rect = ssdfb_smbus_transfer_rect; 228 break; 229 } 230 231 if (sc->sc.sc_transfer_rect != ssdfb_smbus_transfer_rect) { 232 error = ssdfb_i2c_probe_transfer_size(sc, usepoll); 233 if (error) 234 return error; 235 aprint_verbose_dev(sc->sc.sc_dev, "%zd-byte transfers\n", 236 sc->sc_transfer_size); 237 if (sc->sc_transfer_size == 2) { 238 sc->sc.sc_transfer_rect = ssdfb_smbus_transfer_rect; 239 } 240 } 241 242 /* 243 * Set addressing mode for SSD1306. 244 */ 245 if (sc->sc.sc_p->p_controller_id == SSDFB_CONTROLLER_SSD1306) { 246 cmd[0] = SSD1306_CMD_SET_MEMORY_ADDRESSING_MODE; 247 cmd[1] = sc->sc.sc_transfer_rect 248 == ssdfb_i2c_transfer_rect_ssd1306 249 ? SSD1306_MEMORY_ADDRESSING_MODE_HORIZONTAL 250 : SSD1306_MEMORY_ADDRESSING_MODE_PAGE; 251 error = ssdfb_i2c_cmd(cookie, cmd, sizeof(cmd), usepoll); 252 if (error) 253 return error; 254 } 255 256 return sc->sc.sc_transfer_rect(cookie, fromcol, tocol, frompage, topage, 257 p, stride, usepoll); 258 } 259 260 261 static int 262 ssdfb_i2c_transfer_rect_ssd1306(void *cookie, uint8_t fromcol, uint8_t tocol, 263 uint8_t frompage, uint8_t topage, uint8_t *p, size_t stride, bool usepoll) 264 { 265 struct ssdfb_i2c_softc *sc = (struct ssdfb_i2c_softc *)cookie; 266 int flags = usepoll ? I2C_F_POLL : 0; 267 uint8_t cc = 0; 268 uint8_t cb = SSDFB_I2C_CTRL_BYTE_DATA_MASK; 269 size_t len = tocol + 1 - fromcol; 270 int error; 271 /* 272 * SSD1306 does not implement the Continuation bit correctly. 273 * The SH1106 protocol defines that a control byte WITH Co 274 * set must be inserted between each command. But SSD1306 275 * fails to parse the commands if we do that. 276 */ 277 uint8_t cmds[] = { 278 SSD1306_CMD_SET_COLUMN_ADDRESS, 279 fromcol, tocol, 280 SSD1306_CMD_SET_PAGE_ADDRESS, 281 frompage, topage 282 }; 283 284 error = iic_acquire_bus(sc->sc_i2c_tag, flags); 285 if (error) 286 return error; 287 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 288 sc->sc_i2c_addr, &cc, sizeof(cc), cmds, sizeof(cmds), flags); 289 if (error) 290 goto out; 291 while (frompage <= topage) { 292 error = ssdfb_i2c_transfer(sc, cb, p, len, flags); 293 if (error) 294 goto out; 295 frompage++; 296 p += stride; 297 } 298 out: 299 (void) iic_release_bus(sc->sc_i2c_tag, flags); 300 301 return error; 302 } 303 304 static int 305 ssdfb_i2c_transfer_rect_sh1106(void *cookie, uint8_t fromcol, uint8_t tocol, 306 uint8_t frompage, uint8_t topage, uint8_t *p, size_t stride, bool usepoll) 307 { 308 struct ssdfb_i2c_softc *sc = (struct ssdfb_i2c_softc *)cookie; 309 int flags = usepoll ? I2C_F_POLL : 0; 310 uint8_t cb = SSDFB_I2C_CTRL_BYTE_DATA_MASK; 311 uint8_t cc = SSDFB_I2C_CTRL_BYTE_CONTINUATION_MASK; 312 size_t len = tocol + 1 - fromcol; 313 int error; 314 uint8_t cmds[] = { 315 SSDFB_CMD_SET_PAGE_START_ADDRESS_BASE + frompage, 316 SSDFB_I2C_CTRL_BYTE_CONTINUATION_MASK, 317 SSDFB_CMD_SET_HIGHER_COLUMN_START_ADDRESS_BASE + (fromcol >> 4), 318 SSDFB_I2C_CTRL_BYTE_CONTINUATION_MASK, 319 SSDFB_CMD_SET_LOWER_COLUMN_START_ADDRESS_BASE + (fromcol & 0xf) 320 }; 321 322 error = iic_acquire_bus(sc->sc_i2c_tag, flags); 323 if (error) 324 return error; 325 while (frompage <= topage) { 326 cmds[0] = SSDFB_CMD_SET_PAGE_START_ADDRESS_BASE + frompage; 327 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 328 sc->sc_i2c_addr, &cc, sizeof(cc), cmds, sizeof(cmds), flags); 329 if (error) 330 goto out; 331 error = ssdfb_i2c_transfer(sc, cb, p, len, flags); 332 if (error) 333 goto out; 334 frompage++; 335 p += stride; 336 } 337 out: 338 (void) iic_release_bus(sc->sc_i2c_tag, flags); 339 340 return error; 341 } 342 343 /* 344 * If the parent is an SMBus, then we can only send 2 bytes 345 * of payload per txn. The SSD1306 triple byte commands are 346 * not available so we have to use PAGE addressing mode 347 * and split data into multiple txns. 348 * This is ugly and slow but it's the best we can do. 349 */ 350 static int 351 ssdfb_smbus_transfer_rect(void *cookie, uint8_t fromcol, uint8_t tocol, 352 uint8_t frompage, uint8_t topage, uint8_t *p, size_t stride, bool usepoll) 353 { 354 struct ssdfb_i2c_softc *sc = (struct ssdfb_i2c_softc *)cookie; 355 int flags = usepoll ? I2C_F_POLL : 0; 356 uint8_t cb = SSDFB_I2C_CTRL_BYTE_DATA_MASK; 357 uint8_t cc = 0; 358 size_t len = tocol + 1 - fromcol; 359 uint8_t cmd_higher_col = 360 SSDFB_CMD_SET_HIGHER_COLUMN_START_ADDRESS_BASE + (fromcol >> 4); 361 uint8_t cmd_lower_col = 362 SSDFB_CMD_SET_LOWER_COLUMN_START_ADDRESS_BASE + (fromcol & 0xf); 363 uint8_t cmd_page; 364 uint8_t data[2]; 365 uint8_t *colp; 366 uint8_t *endp; 367 int error; 368 369 error = iic_acquire_bus(sc->sc_i2c_tag, flags); 370 if (error) 371 return error; 372 while (frompage <= topage) { 373 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 374 sc->sc_i2c_addr, &cc, sizeof(cc), 375 &cmd_higher_col, sizeof(cmd_higher_col), flags); 376 if (error) 377 goto out; 378 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 379 sc->sc_i2c_addr, &cc, sizeof(cc), 380 &cmd_lower_col, sizeof(cmd_lower_col), flags); 381 if (error) 382 goto out; 383 cmd_page = SSDFB_CMD_SET_PAGE_START_ADDRESS_BASE + frompage; 384 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 385 sc->sc_i2c_addr, &cc, sizeof(cc), 386 &cmd_page, sizeof(cmd_page), flags); 387 if (error) 388 goto out; 389 colp = p; 390 endp = colp + len; 391 if (len & 1) { 392 data[0] = *colp++; 393 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 394 sc->sc_i2c_addr, &cb, sizeof(cb), data, 1, flags); 395 if (error) 396 goto out; 397 } 398 while (colp < endp) { 399 /* 400 * Send two bytes at a time. We can't use colp directly 401 * because i2c controllers sometimes have data alignment 402 * requirements. 403 */ 404 data[0] = *colp++; 405 data[1] = *colp++; 406 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 407 sc->sc_i2c_addr, &cb, sizeof(cb), data, 2, flags); 408 if (error) 409 goto out; 410 } 411 frompage++; 412 p += stride; 413 } 414 out: 415 (void) iic_release_bus(sc->sc_i2c_tag, flags); 416 417 return error; 418 } 419