xref: /openbmc/qemu/hw/display/ssd0323.c (revision a719a27c)
1 /*
2  * SSD0323 OLED controller with OSRAM Pictiva 128x64 display.
3  *
4  * Copyright (c) 2006-2007 CodeSourcery.
5  * Written by Paul Brook
6  *
7  * This code is licensed under the GPL.
8  */
9 
10 /* The controller can support a variety of different displays, but we only
11    implement one.  Most of the commends relating to brightness and geometry
12    setup are ignored. */
13 #include "hw/ssi.h"
14 #include "ui/console.h"
15 
16 //#define DEBUG_SSD0323 1
17 
18 #ifdef DEBUG_SSD0323
19 #define DPRINTF(fmt, ...) \
20 do { printf("ssd0323: " fmt , ## __VA_ARGS__); } while (0)
21 #define BADF(fmt, ...) \
22 do { \
23     fprintf(stderr, "ssd0323: error: " fmt , ## __VA_ARGS__); abort(); \
24 } while (0)
25 #else
26 #define DPRINTF(fmt, ...) do {} while(0)
27 #define BADF(fmt, ...) \
28 do { fprintf(stderr, "ssd0323: error: " fmt , ## __VA_ARGS__);} while (0)
29 #endif
30 
31 /* Scaling factor for pixels.  */
32 #define MAGNIFY 4
33 
34 #define REMAP_SWAP_COLUMN 0x01
35 #define REMAP_SWAP_NYBBLE 0x02
36 #define REMAP_VERTICAL    0x04
37 #define REMAP_SWAP_COM    0x10
38 #define REMAP_SPLIT_COM   0x40
39 
40 enum ssd0323_mode
41 {
42     SSD0323_CMD,
43     SSD0323_DATA
44 };
45 
46 typedef struct {
47     SSISlave ssidev;
48     QemuConsole *con;
49 
50     int cmd_len;
51     int cmd;
52     int cmd_data[8];
53     int row;
54     int row_start;
55     int row_end;
56     int col;
57     int col_start;
58     int col_end;
59     int redraw;
60     int remap;
61     enum ssd0323_mode mode;
62     uint8_t framebuffer[128 * 80 / 2];
63 } ssd0323_state;
64 
65 static uint32_t ssd0323_transfer(SSISlave *dev, uint32_t data)
66 {
67     ssd0323_state *s = FROM_SSI_SLAVE(ssd0323_state, dev);
68 
69     switch (s->mode) {
70     case SSD0323_DATA:
71         DPRINTF("data 0x%02x\n", data);
72         s->framebuffer[s->col + s->row * 64] = data;
73         if (s->remap & REMAP_VERTICAL) {
74             s->row++;
75             if (s->row > s->row_end) {
76                 s->row = s->row_start;
77                 s->col++;
78             }
79             if (s->col > s->col_end) {
80                 s->col = s->col_start;
81             }
82         } else {
83             s->col++;
84             if (s->col > s->col_end) {
85                 s->row++;
86                 s->col = s->col_start;
87             }
88             if (s->row > s->row_end) {
89                 s->row = s->row_start;
90             }
91         }
92         s->redraw = 1;
93         break;
94     case SSD0323_CMD:
95         DPRINTF("cmd 0x%02x\n", data);
96         if (s->cmd_len == 0) {
97             s->cmd = data;
98         } else {
99             s->cmd_data[s->cmd_len - 1] = data;
100         }
101         s->cmd_len++;
102         switch (s->cmd) {
103 #define DATA(x) if (s->cmd_len <= (x)) return 0
104         case 0x15: /* Set column.  */
105             DATA(2);
106             s->col = s->col_start = s->cmd_data[0] % 64;
107             s->col_end = s->cmd_data[1] % 64;
108             break;
109         case 0x75: /* Set row.  */
110             DATA(2);
111             s->row = s->row_start = s->cmd_data[0] % 80;
112             s->row_end = s->cmd_data[1] % 80;
113             break;
114         case 0x81: /* Set contrast */
115             DATA(1);
116             break;
117         case 0x84: case 0x85: case 0x86: /* Max current.  */
118             DATA(0);
119             break;
120         case 0xa0: /* Set remapping.  */
121             /* FIXME: Implement this.  */
122             DATA(1);
123             s->remap = s->cmd_data[0];
124             break;
125         case 0xa1: /* Set display start line.  */
126         case 0xa2: /* Set display offset.  */
127             /* FIXME: Implement these.  */
128             DATA(1);
129             break;
130         case 0xa4: /* Normal mode.  */
131         case 0xa5: /* All on.  */
132         case 0xa6: /* All off.  */
133         case 0xa7: /* Inverse.  */
134             /* FIXME: Implement these.  */
135             DATA(0);
136             break;
137         case 0xa8: /* Set multiplex ratio.  */
138         case 0xad: /* Set DC-DC converter.  */
139             DATA(1);
140             /* Ignored.  Don't care.  */
141             break;
142         case 0xae: /* Display off.  */
143         case 0xaf: /* Display on.  */
144             DATA(0);
145             /* TODO: Implement power control.  */
146             break;
147         case 0xb1: /* Set phase length.  */
148         case 0xb2: /* Set row period.  */
149         case 0xb3: /* Set clock rate.  */
150         case 0xbc: /* Set precharge.  */
151         case 0xbe: /* Set VCOMH.  */
152         case 0xbf: /* Set segment low.  */
153             DATA(1);
154             /* Ignored.  Don't care.  */
155             break;
156         case 0xb8: /* Set grey scale table.  */
157             /* FIXME: Implement this.  */
158             DATA(8);
159             break;
160         case 0xe3: /* NOP.  */
161             DATA(0);
162             break;
163         case 0xff: /* Nasty hack because we don't handle chip selects
164                       properly.  */
165             break;
166         default:
167             BADF("Unknown command: 0x%x\n", data);
168         }
169         s->cmd_len = 0;
170         return 0;
171     }
172     return 0;
173 }
174 
175 static void ssd0323_update_display(void *opaque)
176 {
177     ssd0323_state *s = (ssd0323_state *)opaque;
178     DisplaySurface *surface = qemu_console_surface(s->con);
179     uint8_t *dest;
180     uint8_t *src;
181     int x;
182     int y;
183     int i;
184     int line;
185     char *colors[16];
186     char colortab[MAGNIFY * 64];
187     char *p;
188     int dest_width;
189 
190     if (!s->redraw)
191         return;
192 
193     switch (surface_bits_per_pixel(surface)) {
194     case 0:
195         return;
196     case 15:
197         dest_width = 2;
198         break;
199     case 16:
200         dest_width = 2;
201         break;
202     case 24:
203         dest_width = 3;
204         break;
205     case 32:
206         dest_width = 4;
207         break;
208     default:
209         BADF("Bad color depth\n");
210         return;
211     }
212     p = colortab;
213     for (i = 0; i < 16; i++) {
214         int n;
215         colors[i] = p;
216         switch (surface_bits_per_pixel(surface)) {
217         case 15:
218             n = i * 2 + (i >> 3);
219             p[0] = n | (n << 5);
220             p[1] = (n << 2) | (n >> 3);
221             break;
222         case 16:
223             n = i * 2 + (i >> 3);
224             p[0] = n | (n << 6) | ((n << 1) & 0x20);
225             p[1] = (n << 3) | (n >> 2);
226             break;
227         case 24:
228         case 32:
229             n = (i << 4) | i;
230             p[0] = p[1] = p[2] = n;
231             break;
232         default:
233             BADF("Bad color depth\n");
234             return;
235         }
236         p += dest_width;
237     }
238     /* TODO: Implement row/column remapping.  */
239     dest = surface_data(surface);
240     for (y = 0; y < 64; y++) {
241         line = y;
242         src = s->framebuffer + 64 * line;
243         for (x = 0; x < 64; x++) {
244             int val;
245             val = *src >> 4;
246             for (i = 0; i < MAGNIFY; i++) {
247                 memcpy(dest, colors[val], dest_width);
248                 dest += dest_width;
249             }
250             val = *src & 0xf;
251             for (i = 0; i < MAGNIFY; i++) {
252                 memcpy(dest, colors[val], dest_width);
253                 dest += dest_width;
254             }
255             src++;
256         }
257         for (i = 1; i < MAGNIFY; i++) {
258             memcpy(dest, dest - dest_width * MAGNIFY * 128,
259                    dest_width * 128 * MAGNIFY);
260             dest += dest_width * 128 * MAGNIFY;
261         }
262     }
263     s->redraw = 0;
264     dpy_gfx_update(s->con, 0, 0, 128 * MAGNIFY, 64 * MAGNIFY);
265 }
266 
267 static void ssd0323_invalidate_display(void * opaque)
268 {
269     ssd0323_state *s = (ssd0323_state *)opaque;
270     s->redraw = 1;
271 }
272 
273 /* Command/data input.  */
274 static void ssd0323_cd(void *opaque, int n, int level)
275 {
276     ssd0323_state *s = (ssd0323_state *)opaque;
277     DPRINTF("%s mode\n", level ? "Data" : "Command");
278     s->mode = level ? SSD0323_DATA : SSD0323_CMD;
279 }
280 
281 static void ssd0323_save(QEMUFile *f, void *opaque)
282 {
283     SSISlave *ss = SSI_SLAVE(opaque);
284     ssd0323_state *s = (ssd0323_state *)opaque;
285     int i;
286 
287     qemu_put_be32(f, s->cmd_len);
288     qemu_put_be32(f, s->cmd);
289     for (i = 0; i < 8; i++)
290         qemu_put_be32(f, s->cmd_data[i]);
291     qemu_put_be32(f, s->row);
292     qemu_put_be32(f, s->row_start);
293     qemu_put_be32(f, s->row_end);
294     qemu_put_be32(f, s->col);
295     qemu_put_be32(f, s->col_start);
296     qemu_put_be32(f, s->col_end);
297     qemu_put_be32(f, s->redraw);
298     qemu_put_be32(f, s->remap);
299     qemu_put_be32(f, s->mode);
300     qemu_put_buffer(f, s->framebuffer, sizeof(s->framebuffer));
301 
302     qemu_put_be32(f, ss->cs);
303 }
304 
305 static int ssd0323_load(QEMUFile *f, void *opaque, int version_id)
306 {
307     SSISlave *ss = SSI_SLAVE(opaque);
308     ssd0323_state *s = (ssd0323_state *)opaque;
309     int i;
310 
311     if (version_id != 1)
312         return -EINVAL;
313 
314     s->cmd_len = qemu_get_be32(f);
315     if (s->cmd_len < 0 || s->cmd_len > ARRAY_SIZE(s->cmd_data)) {
316         return -EINVAL;
317     }
318     s->cmd = qemu_get_be32(f);
319     for (i = 0; i < 8; i++)
320         s->cmd_data[i] = qemu_get_be32(f);
321     s->row = qemu_get_be32(f);
322     if (s->row < 0 || s->row >= 80) {
323         return -EINVAL;
324     }
325     s->row_start = qemu_get_be32(f);
326     if (s->row_start < 0 || s->row_start >= 80) {
327         return -EINVAL;
328     }
329     s->row_end = qemu_get_be32(f);
330     if (s->row_end < 0 || s->row_end >= 80) {
331         return -EINVAL;
332     }
333     s->col = qemu_get_be32(f);
334     if (s->col < 0 || s->col >= 64) {
335         return -EINVAL;
336     }
337     s->col_start = qemu_get_be32(f);
338     if (s->col_start < 0 || s->col_start >= 64) {
339         return -EINVAL;
340     }
341     s->col_end = qemu_get_be32(f);
342     if (s->col_end < 0 || s->col_end >= 64) {
343         return -EINVAL;
344     }
345     s->redraw = qemu_get_be32(f);
346     s->remap = qemu_get_be32(f);
347     s->mode = qemu_get_be32(f);
348     if (s->mode != SSD0323_CMD && s->mode != SSD0323_DATA) {
349         return -EINVAL;
350     }
351     qemu_get_buffer(f, s->framebuffer, sizeof(s->framebuffer));
352 
353     ss->cs = qemu_get_be32(f);
354 
355     return 0;
356 }
357 
358 static const GraphicHwOps ssd0323_ops = {
359     .invalidate  = ssd0323_invalidate_display,
360     .gfx_update  = ssd0323_update_display,
361 };
362 
363 static int ssd0323_init(SSISlave *d)
364 {
365     DeviceState *dev = DEVICE(d);
366     ssd0323_state *s = FROM_SSI_SLAVE(ssd0323_state, d);
367 
368     s->col_end = 63;
369     s->row_end = 79;
370     s->con = graphic_console_init(dev, 0, &ssd0323_ops, s);
371     qemu_console_resize(s->con, 128 * MAGNIFY, 64 * MAGNIFY);
372 
373     qdev_init_gpio_in(dev, ssd0323_cd, 1);
374 
375     register_savevm(dev, "ssd0323_oled", -1, 1,
376                     ssd0323_save, ssd0323_load, s);
377     return 0;
378 }
379 
380 static void ssd0323_class_init(ObjectClass *klass, void *data)
381 {
382     SSISlaveClass *k = SSI_SLAVE_CLASS(klass);
383 
384     k->init = ssd0323_init;
385     k->transfer = ssd0323_transfer;
386     k->cs_polarity = SSI_CS_HIGH;
387 }
388 
389 static const TypeInfo ssd0323_info = {
390     .name          = "ssd0323",
391     .parent        = TYPE_SSI_SLAVE,
392     .instance_size = sizeof(ssd0323_state),
393     .class_init    = ssd0323_class_init,
394 };
395 
396 static void ssd03232_register_types(void)
397 {
398     type_register_static(&ssd0323_info);
399 }
400 
401 type_init(ssd03232_register_types)
402