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