1 /* $NetBSD: ssdfb_i2c.c,v 1.5 2019/11/05 19:59:35 tnn 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.5 2019/11/05 19:59:35 tnn 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 { "solomon,ssd1306fb-i2c", 0 }, 71 { "sino,sh1106fb-i2c", 0 }, 72 { NULL, 0 } 73 }; 74 75 static int 76 ssdfb_i2c_match(device_t parent, cfdata_t match, void *aux) 77 { 78 struct i2c_attach_args *ia = aux; 79 int match_result; 80 81 if (iic_use_direct_match(ia, match, compat_data, &match_result)) 82 return match_result; 83 84 switch (ia->ia_addr) { 85 case SSDFB_I2C_DEFAULT_ADDR: 86 case SSDFB_I2C_ALTERNATIVE_ADDR: 87 return I2C_MATCH_ADDRESS_ONLY; 88 } 89 90 return 0; 91 } 92 93 static void 94 ssdfb_i2c_attach(device_t parent, device_t self, void *aux) 95 { 96 struct ssdfb_i2c_softc *sc = device_private(self); 97 struct cfdata *cf = device_cfdata(self); 98 struct i2c_attach_args *ia = aux; 99 int flags = cf->cf_flags; 100 int i; 101 102 if ((flags & SSDFB_ATTACH_FLAG_PRODUCT_MASK) == SSDFB_PRODUCT_UNKNOWN) { 103 for (i = 0; i < ia->ia_ncompat; i++) { 104 if (strncmp("solomon,ssd1306", ia->ia_compat[i], 15) 105 == 0) { 106 flags |= SSDFB_PRODUCT_SSD1306_GENERIC; 107 break; 108 } 109 else if (strncmp("sino,sh1106", ia->ia_compat[i], 11) 110 == 0) { 111 flags |= SSDFB_PRODUCT_SH1106_GENERIC; 112 break; 113 } 114 } 115 } 116 if ((flags & SSDFB_ATTACH_FLAG_PRODUCT_MASK) == SSDFB_PRODUCT_UNKNOWN) 117 flags |= SSDFB_PRODUCT_SSD1306_GENERIC; 118 119 flags |= SSDFB_ATTACH_FLAG_MPSAFE; 120 sc->sc.sc_dev = self; 121 sc->sc_i2c_tag = ia->ia_tag; 122 sc->sc_i2c_addr = ia->ia_addr; 123 sc->sc.sc_cookie = (void *)sc; 124 sc->sc.sc_cmd = ssdfb_i2c_cmd; 125 sc->sc.sc_transfer_rect = ssdfb_i2c_transfer_rect; 126 127 ssdfb_attach(&sc->sc, flags); 128 } 129 130 static int 131 ssdfb_i2c_detach(device_t self, int flags) 132 { 133 struct ssdfb_i2c_softc *sc = device_private(self); 134 135 return ssdfb_detach(&sc->sc); 136 } 137 138 static int 139 ssdfb_i2c_probe_transfer_size(struct ssdfb_i2c_softc *sc, bool usepoll) 140 { 141 int flags = usepoll ? I2C_F_POLL : 0; 142 uint8_t cb = SSDFB_I2C_CTRL_BYTE_DATA_MASK; 143 int error; 144 uint8_t buf[128]; 145 size_t len; 146 147 error = iic_acquire_bus(sc->sc_i2c_tag, flags); 148 if (error) 149 return error; 150 len = sizeof(buf); 151 memset(buf, 0, len); 152 while (len > 0) { 153 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 154 sc->sc_i2c_addr, &cb, sizeof(cb), buf, len, flags); 155 if (!error) { 156 break; 157 } 158 len >>= 1; 159 } 160 if (!error && len < 2) { 161 error = E2BIG; 162 } else { 163 sc->sc_transfer_size = len; 164 } 165 (void) iic_release_bus(sc->sc_i2c_tag, flags); 166 167 return error; 168 } 169 170 static int 171 ssdfb_i2c_transfer(struct ssdfb_i2c_softc *sc, uint8_t cb, uint8_t *data, 172 size_t len, int flags) 173 { 174 int error; 175 size_t xfer_size = sc->sc_transfer_size; 176 177 while (len >= xfer_size) { 178 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 179 sc->sc_i2c_addr, &cb, sizeof(cb), data, xfer_size, flags); 180 if (error) 181 return error; 182 len -= xfer_size; 183 data += xfer_size; 184 } 185 if (len > 0) { 186 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 187 sc->sc_i2c_addr, &cb, sizeof(cb), data, len, flags); 188 } 189 190 return error; 191 } 192 193 static int 194 ssdfb_i2c_cmd(void *cookie, uint8_t *cmd, size_t len, bool usepoll) 195 { 196 struct ssdfb_i2c_softc *sc = (struct ssdfb_i2c_softc *)cookie; 197 int flags = usepoll ? I2C_F_POLL : 0; 198 uint8_t cb = 0; 199 int error; 200 201 error = iic_acquire_bus(sc->sc_i2c_tag, flags); 202 if (error) 203 return error; 204 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 205 sc->sc_i2c_addr, &cb, sizeof(cb), cmd, len, flags); 206 (void) iic_release_bus(sc->sc_i2c_tag, flags); 207 208 return error; 209 } 210 211 static int 212 ssdfb_i2c_transfer_rect(void *cookie, uint8_t fromcol, uint8_t tocol, 213 uint8_t frompage, uint8_t topage, uint8_t *p, size_t stride, bool usepoll) 214 { 215 struct ssdfb_i2c_softc *sc = (struct ssdfb_i2c_softc *)cookie; 216 uint8_t cmd[2]; 217 int error; 218 219 /* 220 * Test if large transfers are supported by the parent i2c bus and 221 * pick the fastest transfer routine for subsequent invocations. 222 */ 223 switch (sc->sc.sc_p->p_controller_id) { 224 case SSDFB_CONTROLLER_SSD1306: 225 sc->sc.sc_transfer_rect = ssdfb_i2c_transfer_rect_ssd1306; 226 break; 227 case SSDFB_CONTROLLER_SH1106: 228 sc->sc.sc_transfer_rect = ssdfb_i2c_transfer_rect_sh1106; 229 break; 230 default: 231 sc->sc.sc_transfer_rect = ssdfb_smbus_transfer_rect; 232 break; 233 } 234 235 if (sc->sc.sc_transfer_rect != ssdfb_smbus_transfer_rect) { 236 error = ssdfb_i2c_probe_transfer_size(sc, usepoll); 237 if (error) 238 return error; 239 aprint_verbose_dev(sc->sc.sc_dev, "%zd-byte transfers\n", 240 sc->sc_transfer_size); 241 if (sc->sc_transfer_size == 2) { 242 sc->sc.sc_transfer_rect = ssdfb_smbus_transfer_rect; 243 } 244 } 245 246 /* 247 * Set addressing mode for SSD1306. 248 */ 249 if (sc->sc.sc_p->p_controller_id == SSDFB_CONTROLLER_SSD1306) { 250 cmd[0] = SSD1306_CMD_SET_MEMORY_ADDRESSING_MODE; 251 cmd[1] = sc->sc.sc_transfer_rect 252 == ssdfb_i2c_transfer_rect_ssd1306 253 ? SSD1306_MEMORY_ADDRESSING_MODE_HORIZONTAL 254 : SSD1306_MEMORY_ADDRESSING_MODE_PAGE; 255 error = ssdfb_i2c_cmd(cookie, cmd, sizeof(cmd), usepoll); 256 if (error) 257 return error; 258 } 259 260 return sc->sc.sc_transfer_rect(cookie, fromcol, tocol, frompage, topage, 261 p, stride, usepoll); 262 } 263 264 265 static int 266 ssdfb_i2c_transfer_rect_ssd1306(void *cookie, uint8_t fromcol, uint8_t tocol, 267 uint8_t frompage, uint8_t topage, uint8_t *p, size_t stride, bool usepoll) 268 { 269 struct ssdfb_i2c_softc *sc = (struct ssdfb_i2c_softc *)cookie; 270 int flags = usepoll ? I2C_F_POLL : 0; 271 uint8_t cc = 0; 272 uint8_t cb = SSDFB_I2C_CTRL_BYTE_DATA_MASK; 273 size_t len = tocol + 1 - fromcol; 274 int error; 275 /* 276 * SSD1306 does not implement the Continuation bit correctly. 277 * The SH1106 protocol defines that a control byte WITH Co 278 * set must be inserted between each command. But SSD1306 279 * fails to parse the commands if we do that. 280 */ 281 uint8_t cmds[] = { 282 SSD1306_CMD_SET_COLUMN_ADDRESS, 283 fromcol, tocol, 284 SSD1306_CMD_SET_PAGE_ADDRESS, 285 frompage, topage 286 }; 287 288 error = iic_acquire_bus(sc->sc_i2c_tag, flags); 289 if (error) 290 return error; 291 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 292 sc->sc_i2c_addr, &cc, sizeof(cc), cmds, sizeof(cmds), flags); 293 if (error) 294 goto out; 295 while (frompage <= topage) { 296 error = ssdfb_i2c_transfer(sc, cb, p, len, flags); 297 if (error) 298 goto out; 299 frompage++; 300 p += stride; 301 } 302 out: 303 (void) iic_release_bus(sc->sc_i2c_tag, flags); 304 305 return error; 306 } 307 308 static int 309 ssdfb_i2c_transfer_rect_sh1106(void *cookie, uint8_t fromcol, uint8_t tocol, 310 uint8_t frompage, uint8_t topage, uint8_t *p, size_t stride, bool usepoll) 311 { 312 struct ssdfb_i2c_softc *sc = (struct ssdfb_i2c_softc *)cookie; 313 int flags = usepoll ? I2C_F_POLL : 0; 314 uint8_t cb = SSDFB_I2C_CTRL_BYTE_DATA_MASK; 315 uint8_t cc = SSDFB_I2C_CTRL_BYTE_CONTINUATION_MASK; 316 size_t len = tocol + 1 - fromcol; 317 int error; 318 uint8_t cmds[] = { 319 SSDFB_CMD_SET_PAGE_START_ADDRESS_BASE + frompage, 320 SSDFB_I2C_CTRL_BYTE_CONTINUATION_MASK, 321 SSDFB_CMD_SET_HIGHER_COLUMN_START_ADDRESS_BASE + (fromcol >> 4), 322 SSDFB_I2C_CTRL_BYTE_CONTINUATION_MASK, 323 SSDFB_CMD_SET_LOWER_COLUMN_START_ADDRESS_BASE + (fromcol & 0xf) 324 }; 325 326 error = iic_acquire_bus(sc->sc_i2c_tag, flags); 327 if (error) 328 return error; 329 while (frompage <= topage) { 330 cmds[0] = SSDFB_CMD_SET_PAGE_START_ADDRESS_BASE + frompage; 331 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 332 sc->sc_i2c_addr, &cc, sizeof(cc), cmds, sizeof(cmds), flags); 333 if (error) 334 goto out; 335 error = ssdfb_i2c_transfer(sc, cb, p, len, flags); 336 if (error) 337 goto out; 338 frompage++; 339 p += stride; 340 } 341 out: 342 (void) iic_release_bus(sc->sc_i2c_tag, flags); 343 344 return error; 345 } 346 347 /* 348 * If the parent is an SMBus, then we can only send 2 bytes 349 * of payload per txn. The SSD1306 triple byte commands are 350 * not available so we have to use PAGE addressing mode 351 * and split data into multiple txns. 352 * This is ugly and slow but it's the best we can do. 353 */ 354 static int 355 ssdfb_smbus_transfer_rect(void *cookie, uint8_t fromcol, uint8_t tocol, 356 uint8_t frompage, uint8_t topage, uint8_t *p, size_t stride, bool usepoll) 357 { 358 struct ssdfb_i2c_softc *sc = (struct ssdfb_i2c_softc *)cookie; 359 int flags = usepoll ? I2C_F_POLL : 0; 360 uint8_t cb = SSDFB_I2C_CTRL_BYTE_DATA_MASK; 361 uint8_t cc = 0; 362 size_t len = tocol + 1 - fromcol; 363 uint8_t cmd_higher_col = 364 SSDFB_CMD_SET_HIGHER_COLUMN_START_ADDRESS_BASE + (fromcol >> 4); 365 uint8_t cmd_lower_col = 366 SSDFB_CMD_SET_LOWER_COLUMN_START_ADDRESS_BASE + (fromcol & 0xf); 367 uint8_t cmd_page; 368 uint8_t data[2]; 369 uint8_t *colp; 370 uint8_t *endp; 371 int error; 372 373 error = iic_acquire_bus(sc->sc_i2c_tag, flags); 374 if (error) 375 return error; 376 while (frompage <= topage) { 377 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 378 sc->sc_i2c_addr, &cc, sizeof(cc), 379 &cmd_higher_col, sizeof(cmd_higher_col), flags); 380 if (error) 381 goto out; 382 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 383 sc->sc_i2c_addr, &cc, sizeof(cc), 384 &cmd_lower_col, sizeof(cmd_lower_col), flags); 385 if (error) 386 goto out; 387 cmd_page = SSDFB_CMD_SET_PAGE_START_ADDRESS_BASE + frompage; 388 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 389 sc->sc_i2c_addr, &cc, sizeof(cc), 390 &cmd_page, sizeof(cmd_page), flags); 391 if (error) 392 goto out; 393 colp = p; 394 endp = colp + len; 395 if (len & 1) { 396 data[0] = *colp++; 397 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 398 sc->sc_i2c_addr, &cb, sizeof(cb), data, 1, flags); 399 if (error) 400 goto out; 401 } 402 while (colp < endp) { 403 /* 404 * Send two bytes at a time. We can't use colp directly 405 * because i2c controllers sometimes have data alignment 406 * requirements. 407 */ 408 data[0] = *colp++; 409 data[1] = *colp++; 410 error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP, 411 sc->sc_i2c_addr, &cb, sizeof(cb), data, 2, flags); 412 if (error) 413 goto out; 414 } 415 frompage++; 416 p += stride; 417 } 418 out: 419 (void) iic_release_bus(sc->sc_i2c_tag, flags); 420 421 return error; 422 } 423