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