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