xref: /openbmc/qemu/hw/display/ssd0303.c (revision 52f91c37)
1 /*
2  * SSD0303 OLED controller with OSRAM Pictiva 96x16 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/i2c/i2c.h"
14 #include "ui/console.h"
15 
16 //#define DEBUG_SSD0303 1
17 
18 #ifdef DEBUG_SSD0303
19 #define DPRINTF(fmt, ...) \
20 do { printf("ssd0303: " fmt , ## __VA_ARGS__); } while (0)
21 #define BADF(fmt, ...) \
22 do { fprintf(stderr, "ssd0303: error: " fmt , ## __VA_ARGS__); exit(1);} while (0)
23 #else
24 #define DPRINTF(fmt, ...) do {} while(0)
25 #define BADF(fmt, ...) \
26 do { fprintf(stderr, "ssd0303: error: " fmt , ## __VA_ARGS__);} while (0)
27 #endif
28 
29 /* Scaling factor for pixels.  */
30 #define MAGNIFY 4
31 
32 enum ssd0303_mode
33 {
34     SSD0303_IDLE,
35     SSD0303_DATA,
36     SSD0303_CMD
37 };
38 
39 enum ssd0303_cmd {
40     SSD0303_CMD_NONE,
41     SSD0303_CMD_SKIP1
42 };
43 
44 #define TYPE_SSD0303 "ssd0303"
45 #define SSD0303(obj) OBJECT_CHECK(ssd0303_state, (obj), TYPE_SSD0303)
46 
47 typedef struct {
48     I2CSlave parent_obj;
49 
50     QemuConsole *con;
51     int row;
52     int col;
53     int start_line;
54     int mirror;
55     int flash;
56     int enabled;
57     int inverse;
58     int redraw;
59     enum ssd0303_mode mode;
60     enum ssd0303_cmd cmd_state;
61     uint8_t framebuffer[132*8];
62 } ssd0303_state;
63 
64 static int ssd0303_recv(I2CSlave *i2c)
65 {
66     BADF("Reads not implemented\n");
67     return -1;
68 }
69 
70 static int ssd0303_send(I2CSlave *i2c, uint8_t data)
71 {
72     ssd0303_state *s = SSD0303(i2c);
73     enum ssd0303_cmd old_cmd_state;
74 
75     switch (s->mode) {
76     case SSD0303_IDLE:
77         DPRINTF("byte 0x%02x\n", data);
78         if (data == 0x80)
79             s->mode = SSD0303_CMD;
80         else if (data == 0x40)
81             s->mode = SSD0303_DATA;
82         else
83             BADF("Unexpected byte 0x%x\n", data);
84         break;
85     case SSD0303_DATA:
86         DPRINTF("data 0x%02x\n", data);
87         if (s->col < 132) {
88             s->framebuffer[s->col + s->row * 132] = data;
89             s->col++;
90             s->redraw = 1;
91         }
92         break;
93     case SSD0303_CMD:
94         old_cmd_state = s->cmd_state;
95         s->cmd_state = SSD0303_CMD_NONE;
96         switch (old_cmd_state) {
97         case SSD0303_CMD_NONE:
98             DPRINTF("cmd 0x%02x\n", data);
99             s->mode = SSD0303_IDLE;
100             switch (data) {
101             case 0x00 ... 0x0f: /* Set lower column address.  */
102                 s->col = (s->col & 0xf0) | (data & 0xf);
103                 break;
104             case 0x10 ... 0x20: /* Set higher column address.  */
105                 s->col = (s->col & 0x0f) | ((data & 0xf) << 4);
106                 break;
107             case 0x40 ... 0x7f: /* Set start line.  */
108                 s->start_line = 0;
109                 break;
110             case 0x81: /* Set contrast (Ignored).  */
111                 s->cmd_state = SSD0303_CMD_SKIP1;
112                 break;
113             case 0xa0: /* Mirror off.  */
114                 s->mirror = 0;
115                 break;
116             case 0xa1: /* Mirror off.  */
117                 s->mirror = 1;
118                 break;
119             case 0xa4: /* Entire display off.  */
120                 s->flash = 0;
121                 break;
122             case 0xa5: /* Entire display on.  */
123                 s->flash = 1;
124                 break;
125             case 0xa6: /* Inverse off.  */
126                 s->inverse = 0;
127                 break;
128             case 0xa7: /* Inverse on.  */
129                 s->inverse = 1;
130                 break;
131             case 0xa8: /* Set multiplied ratio (Ignored).  */
132                 s->cmd_state = SSD0303_CMD_SKIP1;
133                 break;
134             case 0xad: /* DC-DC power control.  */
135                 s->cmd_state = SSD0303_CMD_SKIP1;
136                 break;
137             case 0xae: /* Display off.  */
138                 s->enabled = 0;
139                 break;
140             case 0xaf: /* Display on.  */
141                 s->enabled = 1;
142                 break;
143             case 0xb0 ... 0xbf: /* Set Page address.  */
144                 s->row = data & 7;
145                 break;
146             case 0xc0 ... 0xc8: /* Set COM output direction (Ignored).  */
147                 break;
148             case 0xd3: /* Set display offset (Ignored).  */
149                 s->cmd_state = SSD0303_CMD_SKIP1;
150                 break;
151             case 0xd5: /* Set display clock (Ignored).  */
152                 s->cmd_state = SSD0303_CMD_SKIP1;
153                 break;
154             case 0xd8: /* Set color and power mode (Ignored).  */
155                 s->cmd_state = SSD0303_CMD_SKIP1;
156                 break;
157             case 0xd9: /* Set pre-charge period (Ignored).  */
158                 s->cmd_state = SSD0303_CMD_SKIP1;
159                 break;
160             case 0xda: /* Set COM pin configuration (Ignored).  */
161                 s->cmd_state = SSD0303_CMD_SKIP1;
162                 break;
163             case 0xdb: /* Set VCOM dselect level (Ignored).  */
164                 s->cmd_state = SSD0303_CMD_SKIP1;
165                 break;
166             case 0xe3: /* no-op.  */
167                 break;
168             default:
169                 BADF("Unknown command: 0x%x\n", data);
170             }
171             break;
172         case SSD0303_CMD_SKIP1:
173             DPRINTF("skip 0x%02x\n", data);
174             break;
175         }
176         break;
177     }
178     return 0;
179 }
180 
181 static void ssd0303_event(I2CSlave *i2c, enum i2c_event event)
182 {
183     ssd0303_state *s = SSD0303(i2c);
184 
185     switch (event) {
186     case I2C_FINISH:
187         s->mode = SSD0303_IDLE;
188         break;
189     case I2C_START_RECV:
190     case I2C_START_SEND:
191     case I2C_NACK:
192         /* Nothing to do.  */
193         break;
194     }
195 }
196 
197 static void ssd0303_update_display(void *opaque)
198 {
199     ssd0303_state *s = (ssd0303_state *)opaque;
200     DisplaySurface *surface = qemu_console_surface(s->con);
201     uint8_t *dest;
202     uint8_t *src;
203     int x;
204     int y;
205     int line;
206     char *colors[2];
207     char colortab[MAGNIFY * 8];
208     int dest_width;
209     uint8_t mask;
210 
211     if (!s->redraw)
212         return;
213 
214     switch (surface_bits_per_pixel(surface)) {
215     case 0:
216         return;
217     case 15:
218         dest_width = 2;
219         break;
220     case 16:
221         dest_width = 2;
222         break;
223     case 24:
224         dest_width = 3;
225         break;
226     case 32:
227         dest_width = 4;
228         break;
229     default:
230         BADF("Bad color depth\n");
231         return;
232     }
233     dest_width *= MAGNIFY;
234     memset(colortab, 0xff, dest_width);
235     memset(colortab + dest_width, 0, dest_width);
236     if (s->flash) {
237         colors[0] = colortab;
238         colors[1] = colortab;
239     } else if (s->inverse) {
240         colors[0] = colortab;
241         colors[1] = colortab + dest_width;
242     } else {
243         colors[0] = colortab + dest_width;
244         colors[1] = colortab;
245     }
246     dest = surface_data(surface);
247     for (y = 0; y < 16; y++) {
248         line = (y + s->start_line) & 63;
249         src = s->framebuffer + 132 * (line >> 3) + 36;
250         mask = 1 << (line & 7);
251         for (x = 0; x < 96; x++) {
252             memcpy(dest, colors[(*src & mask) != 0], dest_width);
253             dest += dest_width;
254             src++;
255         }
256         for (x = 1; x < MAGNIFY; x++) {
257             memcpy(dest, dest - dest_width * 96, dest_width * 96);
258             dest += dest_width * 96;
259         }
260     }
261     s->redraw = 0;
262     dpy_gfx_update(s->con, 0, 0, 96 * MAGNIFY, 16 * MAGNIFY);
263 }
264 
265 static void ssd0303_invalidate_display(void * opaque)
266 {
267     ssd0303_state *s = (ssd0303_state *)opaque;
268     s->redraw = 1;
269 }
270 
271 static const VMStateDescription vmstate_ssd0303 = {
272     .name = "ssd0303_oled",
273     .version_id = 1,
274     .minimum_version_id = 1,
275     .minimum_version_id_old = 1,
276     .fields      = (VMStateField []) {
277         VMSTATE_INT32(row, ssd0303_state),
278         VMSTATE_INT32(col, ssd0303_state),
279         VMSTATE_INT32(start_line, ssd0303_state),
280         VMSTATE_INT32(mirror, ssd0303_state),
281         VMSTATE_INT32(flash, ssd0303_state),
282         VMSTATE_INT32(enabled, ssd0303_state),
283         VMSTATE_INT32(inverse, ssd0303_state),
284         VMSTATE_INT32(redraw, ssd0303_state),
285         VMSTATE_UINT32(mode, ssd0303_state),
286         VMSTATE_UINT32(cmd_state, ssd0303_state),
287         VMSTATE_BUFFER(framebuffer, ssd0303_state),
288         VMSTATE_I2C_SLAVE(parent_obj, ssd0303_state),
289         VMSTATE_END_OF_LIST()
290     }
291 };
292 
293 static const GraphicHwOps ssd0303_ops = {
294     .invalidate  = ssd0303_invalidate_display,
295     .gfx_update  = ssd0303_update_display,
296 };
297 
298 static int ssd0303_init(I2CSlave *i2c)
299 {
300     ssd0303_state *s = SSD0303(i2c);
301 
302     s->con = graphic_console_init(DEVICE(i2c), 0, &ssd0303_ops, s);
303     qemu_console_resize(s->con, 96 * MAGNIFY, 16 * MAGNIFY);
304     return 0;
305 }
306 
307 static void ssd0303_class_init(ObjectClass *klass, void *data)
308 {
309     DeviceClass *dc = DEVICE_CLASS(klass);
310     I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
311 
312     k->init = ssd0303_init;
313     k->event = ssd0303_event;
314     k->recv = ssd0303_recv;
315     k->send = ssd0303_send;
316     dc->vmsd = &vmstate_ssd0303;
317 }
318 
319 static const TypeInfo ssd0303_info = {
320     .name          = TYPE_SSD0303,
321     .parent        = TYPE_I2C_SLAVE,
322     .instance_size = sizeof(ssd0303_state),
323     .class_init    = ssd0303_class_init,
324 };
325 
326 static void ssd0303_register_types(void)
327 {
328     type_register_static(&ssd0303_info);
329 }
330 
331 type_init(ssd0303_register_types)
332