]>
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 | ||
b115bb3f PB |
206 | if (!s->redraw) |
207 | return; | |
208 | ||
0e1f5a0c | 209 | switch (ds_get_bits_per_pixel(s->ds)) { |
b115bb3f PB |
210 | case 0: |
211 | return; | |
212 | case 15: | |
213 | dest_width = 2; | |
214 | break; | |
215 | case 16: | |
216 | dest_width = 2; | |
217 | break; | |
218 | case 24: | |
219 | dest_width = 3; | |
220 | break; | |
221 | case 32: | |
222 | dest_width = 4; | |
223 | break; | |
224 | default: | |
225 | BADF("Bad color depth\n"); | |
226 | return; | |
227 | } | |
228 | dest_width *= MAGNIFY; | |
229 | memset(colortab, 0xff, dest_width); | |
230 | memset(colortab + dest_width, 0, dest_width); | |
231 | if (s->flash) { | |
232 | colors[0] = colortab; | |
233 | colors[1] = colortab; | |
234 | } else if (s->inverse) { | |
235 | colors[0] = colortab; | |
236 | colors[1] = colortab + dest_width; | |
237 | } else { | |
238 | colors[0] = colortab + dest_width; | |
239 | colors[1] = colortab; | |
240 | } | |
0e1f5a0c | 241 | dest = ds_get_data(s->ds); |
b115bb3f PB |
242 | for (y = 0; y < 16; y++) { |
243 | line = (y + s->start_line) & 63; | |
244 | src = s->framebuffer + 132 * (line >> 3) + 36; | |
245 | mask = 1 << (line & 7); | |
246 | for (x = 0; x < 96; x++) { | |
247 | memcpy(dest, colors[(*src & mask) != 0], dest_width); | |
248 | dest += dest_width; | |
249 | src++; | |
9ee6e8bb | 250 | } |
b115bb3f PB |
251 | for (x = 1; x < MAGNIFY; x++) { |
252 | memcpy(dest, dest - dest_width * 96, dest_width * 96); | |
253 | dest += dest_width * 96; | |
9ee6e8bb PB |
254 | } |
255 | } | |
b115bb3f | 256 | s->redraw = 0; |
9ee6e8bb PB |
257 | dpy_update(s->ds, 0, 0, 96 * MAGNIFY, 16 * MAGNIFY); |
258 | } | |
259 | ||
260 | static void ssd0303_invalidate_display(void * opaque) | |
261 | { | |
262 | ssd0303_state *s = (ssd0303_state *)opaque; | |
263 | s->redraw = 1; | |
264 | } | |
265 | ||
23e39294 PB |
266 | static void ssd0303_save(QEMUFile *f, void *opaque) |
267 | { | |
268 | ssd0303_state *s = (ssd0303_state *)opaque; | |
269 | ||
270 | qemu_put_be32(f, s->row); | |
271 | qemu_put_be32(f, s->col); | |
272 | qemu_put_be32(f, s->start_line); | |
273 | qemu_put_be32(f, s->mirror); | |
274 | qemu_put_be32(f, s->flash); | |
275 | qemu_put_be32(f, s->enabled); | |
276 | qemu_put_be32(f, s->inverse); | |
277 | qemu_put_be32(f, s->redraw); | |
278 | qemu_put_be32(f, s->mode); | |
279 | qemu_put_be32(f, s->cmd_state); | |
280 | qemu_put_buffer(f, s->framebuffer, sizeof(s->framebuffer)); | |
281 | ||
282 | i2c_slave_save(f, &s->i2c); | |
283 | } | |
284 | ||
285 | static int ssd0303_load(QEMUFile *f, void *opaque, int version_id) | |
286 | { | |
287 | ssd0303_state *s = (ssd0303_state *)opaque; | |
288 | ||
289 | if (version_id != 1) | |
290 | return -EINVAL; | |
291 | ||
292 | s->row = qemu_get_be32(f); | |
293 | s->col = qemu_get_be32(f); | |
294 | s->start_line = qemu_get_be32(f); | |
295 | s->mirror = qemu_get_be32(f); | |
296 | s->flash = qemu_get_be32(f); | |
297 | s->enabled = qemu_get_be32(f); | |
298 | s->inverse = qemu_get_be32(f); | |
299 | s->redraw = qemu_get_be32(f); | |
300 | s->mode = qemu_get_be32(f); | |
301 | s->cmd_state = qemu_get_be32(f); | |
302 | qemu_get_buffer(f, s->framebuffer, sizeof(s->framebuffer)); | |
303 | ||
304 | i2c_slave_load(f, &s->i2c); | |
305 | ||
306 | return 0; | |
307 | } | |
308 | ||
9ee6e8bb PB |
309 | void ssd0303_init(DisplayState *ds, i2c_bus *bus, int address) |
310 | { | |
311 | ssd0303_state *s; | |
312 | ||
313 | s = (ssd0303_state *)i2c_slave_init(bus, address, sizeof(ssd0303_state)); | |
314 | s->ds = ds; | |
315 | s->i2c.event = ssd0303_event; | |
316 | s->i2c.recv = ssd0303_recv; | |
317 | s->i2c.send = ssd0303_send; | |
c60e08d9 PB |
318 | s->console = graphic_console_init(ds, ssd0303_update_display, |
319 | ssd0303_invalidate_display, | |
320 | NULL, NULL, s); | |
321 | qemu_console_resize(s->console, 96 * MAGNIFY, 16 * MAGNIFY); | |
23e39294 | 322 | register_savevm("ssd0303_oled", -1, 1, ssd0303_save, ssd0303_load, s); |
9ee6e8bb | 323 | } |