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