149ab747fSPaolo Bonzini /*
249ab747fSPaolo Bonzini * SSD0303 OLED controller with OSRAM Pictiva 96x16 display.
349ab747fSPaolo Bonzini *
449ab747fSPaolo Bonzini * Copyright (c) 2006-2007 CodeSourcery.
549ab747fSPaolo Bonzini * Written by Paul Brook
649ab747fSPaolo Bonzini *
749ab747fSPaolo Bonzini * This code is licensed under the GPL.
849ab747fSPaolo Bonzini */
949ab747fSPaolo Bonzini
1049ab747fSPaolo Bonzini /* The controller can support a variety of different displays, but we only
1133a52307SMichael Tokarev implement one. Most of the commands relating to brightness and geometry
1249ab747fSPaolo Bonzini setup are ignored. */
130b8fa32fSMarkus Armbruster
1447df5154SPeter Maydell #include "qemu/osdep.h"
1549ab747fSPaolo Bonzini #include "hw/i2c/i2c.h"
16d6454270SMarkus Armbruster #include "migration/vmstate.h"
170b8fa32fSMarkus Armbruster #include "qemu/module.h"
1849ab747fSPaolo Bonzini #include "ui/console.h"
19db1015e9SEduardo Habkost #include "qom/object.h"
2049ab747fSPaolo Bonzini
2149ab747fSPaolo Bonzini //#define DEBUG_SSD0303 1
2249ab747fSPaolo Bonzini
2349ab747fSPaolo Bonzini #ifdef DEBUG_SSD0303
2449ab747fSPaolo Bonzini #define DPRINTF(fmt, ...) \
2549ab747fSPaolo Bonzini do { printf("ssd0303: " fmt , ## __VA_ARGS__); } while (0)
2649ab747fSPaolo Bonzini #define BADF(fmt, ...) \
2749ab747fSPaolo Bonzini do { fprintf(stderr, "ssd0303: error: " fmt , ## __VA_ARGS__); exit(1);} while (0)
2849ab747fSPaolo Bonzini #else
2949ab747fSPaolo Bonzini #define DPRINTF(fmt, ...) do {} while(0)
3049ab747fSPaolo Bonzini #define BADF(fmt, ...) \
3149ab747fSPaolo Bonzini do { fprintf(stderr, "ssd0303: error: " fmt , ## __VA_ARGS__);} while (0)
3249ab747fSPaolo Bonzini #endif
3349ab747fSPaolo Bonzini
3449ab747fSPaolo Bonzini /* Scaling factor for pixels. */
3549ab747fSPaolo Bonzini #define MAGNIFY 4
3649ab747fSPaolo Bonzini
3749ab747fSPaolo Bonzini enum ssd0303_mode
3849ab747fSPaolo Bonzini {
3949ab747fSPaolo Bonzini SSD0303_IDLE,
4049ab747fSPaolo Bonzini SSD0303_DATA,
4149ab747fSPaolo Bonzini SSD0303_CMD
4249ab747fSPaolo Bonzini };
4349ab747fSPaolo Bonzini
4449ab747fSPaolo Bonzini enum ssd0303_cmd {
4549ab747fSPaolo Bonzini SSD0303_CMD_NONE,
4649ab747fSPaolo Bonzini SSD0303_CMD_SKIP1
4749ab747fSPaolo Bonzini };
4849ab747fSPaolo Bonzini
49b1be4515SAndreas Färber #define TYPE_SSD0303 "ssd0303"
508063396bSEduardo Habkost OBJECT_DECLARE_SIMPLE_TYPE(ssd0303_state, SSD0303)
51b1be4515SAndreas Färber
52db1015e9SEduardo Habkost struct ssd0303_state {
53b1be4515SAndreas Färber I2CSlave parent_obj;
54b1be4515SAndreas Färber
5549ab747fSPaolo Bonzini QemuConsole *con;
5649ab747fSPaolo Bonzini int row;
5749ab747fSPaolo Bonzini int col;
5849ab747fSPaolo Bonzini int start_line;
5949ab747fSPaolo Bonzini int mirror;
6049ab747fSPaolo Bonzini int flash;
6149ab747fSPaolo Bonzini int enabled;
6249ab747fSPaolo Bonzini int inverse;
6349ab747fSPaolo Bonzini int redraw;
6449ab747fSPaolo Bonzini enum ssd0303_mode mode;
6549ab747fSPaolo Bonzini enum ssd0303_cmd cmd_state;
6649ab747fSPaolo Bonzini uint8_t framebuffer[132*8];
67db1015e9SEduardo Habkost };
6849ab747fSPaolo Bonzini
ssd0303_recv(I2CSlave * i2c)692ac4c5f4SCorey Minyard static uint8_t ssd0303_recv(I2CSlave *i2c)
7049ab747fSPaolo Bonzini {
7149ab747fSPaolo Bonzini BADF("Reads not implemented\n");
722ac4c5f4SCorey Minyard return 0xff;
7349ab747fSPaolo Bonzini }
7449ab747fSPaolo Bonzini
ssd0303_send(I2CSlave * i2c,uint8_t data)7549ab747fSPaolo Bonzini static int ssd0303_send(I2CSlave *i2c, uint8_t data)
7649ab747fSPaolo Bonzini {
77b1be4515SAndreas Färber ssd0303_state *s = SSD0303(i2c);
7849ab747fSPaolo Bonzini enum ssd0303_cmd old_cmd_state;
79b1be4515SAndreas Färber
8049ab747fSPaolo Bonzini switch (s->mode) {
8149ab747fSPaolo Bonzini case SSD0303_IDLE:
8249ab747fSPaolo Bonzini DPRINTF("byte 0x%02x\n", data);
8349ab747fSPaolo Bonzini if (data == 0x80)
8449ab747fSPaolo Bonzini s->mode = SSD0303_CMD;
8549ab747fSPaolo Bonzini else if (data == 0x40)
8649ab747fSPaolo Bonzini s->mode = SSD0303_DATA;
8749ab747fSPaolo Bonzini else
8849ab747fSPaolo Bonzini BADF("Unexpected byte 0x%x\n", data);
8949ab747fSPaolo Bonzini break;
9049ab747fSPaolo Bonzini case SSD0303_DATA:
9149ab747fSPaolo Bonzini DPRINTF("data 0x%02x\n", data);
9249ab747fSPaolo Bonzini if (s->col < 132) {
9349ab747fSPaolo Bonzini s->framebuffer[s->col + s->row * 132] = data;
9449ab747fSPaolo Bonzini s->col++;
9549ab747fSPaolo Bonzini s->redraw = 1;
9649ab747fSPaolo Bonzini }
9749ab747fSPaolo Bonzini break;
9849ab747fSPaolo Bonzini case SSD0303_CMD:
9949ab747fSPaolo Bonzini old_cmd_state = s->cmd_state;
10049ab747fSPaolo Bonzini s->cmd_state = SSD0303_CMD_NONE;
10149ab747fSPaolo Bonzini switch (old_cmd_state) {
10249ab747fSPaolo Bonzini case SSD0303_CMD_NONE:
10349ab747fSPaolo Bonzini DPRINTF("cmd 0x%02x\n", data);
10449ab747fSPaolo Bonzini s->mode = SSD0303_IDLE;
10549ab747fSPaolo Bonzini switch (data) {
10649ab747fSPaolo Bonzini case 0x00 ... 0x0f: /* Set lower column address. */
10749ab747fSPaolo Bonzini s->col = (s->col & 0xf0) | (data & 0xf);
10849ab747fSPaolo Bonzini break;
10949ab747fSPaolo Bonzini case 0x10 ... 0x20: /* Set higher column address. */
11049ab747fSPaolo Bonzini s->col = (s->col & 0x0f) | ((data & 0xf) << 4);
11149ab747fSPaolo Bonzini break;
11249ab747fSPaolo Bonzini case 0x40 ... 0x7f: /* Set start line. */
11349ab747fSPaolo Bonzini s->start_line = 0;
11449ab747fSPaolo Bonzini break;
11549ab747fSPaolo Bonzini case 0x81: /* Set contrast (Ignored). */
11649ab747fSPaolo Bonzini s->cmd_state = SSD0303_CMD_SKIP1;
11749ab747fSPaolo Bonzini break;
11849ab747fSPaolo Bonzini case 0xa0: /* Mirror off. */
11949ab747fSPaolo Bonzini s->mirror = 0;
12049ab747fSPaolo Bonzini break;
12149ab747fSPaolo Bonzini case 0xa1: /* Mirror off. */
12249ab747fSPaolo Bonzini s->mirror = 1;
12349ab747fSPaolo Bonzini break;
12449ab747fSPaolo Bonzini case 0xa4: /* Entire display off. */
12549ab747fSPaolo Bonzini s->flash = 0;
12649ab747fSPaolo Bonzini break;
12749ab747fSPaolo Bonzini case 0xa5: /* Entire display on. */
12849ab747fSPaolo Bonzini s->flash = 1;
12949ab747fSPaolo Bonzini break;
13049ab747fSPaolo Bonzini case 0xa6: /* Inverse off. */
13149ab747fSPaolo Bonzini s->inverse = 0;
13249ab747fSPaolo Bonzini break;
13349ab747fSPaolo Bonzini case 0xa7: /* Inverse on. */
13449ab747fSPaolo Bonzini s->inverse = 1;
13549ab747fSPaolo Bonzini break;
13649ab747fSPaolo Bonzini case 0xa8: /* Set multiplied ratio (Ignored). */
13749ab747fSPaolo Bonzini s->cmd_state = SSD0303_CMD_SKIP1;
13849ab747fSPaolo Bonzini break;
13949ab747fSPaolo Bonzini case 0xad: /* DC-DC power control. */
14049ab747fSPaolo Bonzini s->cmd_state = SSD0303_CMD_SKIP1;
14149ab747fSPaolo Bonzini break;
14249ab747fSPaolo Bonzini case 0xae: /* Display off. */
14349ab747fSPaolo Bonzini s->enabled = 0;
14449ab747fSPaolo Bonzini break;
14549ab747fSPaolo Bonzini case 0xaf: /* Display on. */
14649ab747fSPaolo Bonzini s->enabled = 1;
14749ab747fSPaolo Bonzini break;
14849ab747fSPaolo Bonzini case 0xb0 ... 0xbf: /* Set Page address. */
14949ab747fSPaolo Bonzini s->row = data & 7;
15049ab747fSPaolo Bonzini break;
15149ab747fSPaolo Bonzini case 0xc0 ... 0xc8: /* Set COM output direction (Ignored). */
15249ab747fSPaolo Bonzini break;
15349ab747fSPaolo Bonzini case 0xd3: /* Set display offset (Ignored). */
15449ab747fSPaolo Bonzini s->cmd_state = SSD0303_CMD_SKIP1;
15549ab747fSPaolo Bonzini break;
15649ab747fSPaolo Bonzini case 0xd5: /* Set display clock (Ignored). */
15749ab747fSPaolo Bonzini s->cmd_state = SSD0303_CMD_SKIP1;
15849ab747fSPaolo Bonzini break;
15949ab747fSPaolo Bonzini case 0xd8: /* Set color and power mode (Ignored). */
16049ab747fSPaolo Bonzini s->cmd_state = SSD0303_CMD_SKIP1;
16149ab747fSPaolo Bonzini break;
16249ab747fSPaolo Bonzini case 0xd9: /* Set pre-charge period (Ignored). */
16349ab747fSPaolo Bonzini s->cmd_state = SSD0303_CMD_SKIP1;
16449ab747fSPaolo Bonzini break;
16549ab747fSPaolo Bonzini case 0xda: /* Set COM pin configuration (Ignored). */
16649ab747fSPaolo Bonzini s->cmd_state = SSD0303_CMD_SKIP1;
16749ab747fSPaolo Bonzini break;
16849ab747fSPaolo Bonzini case 0xdb: /* Set VCOM dselect level (Ignored). */
16949ab747fSPaolo Bonzini s->cmd_state = SSD0303_CMD_SKIP1;
17049ab747fSPaolo Bonzini break;
17149ab747fSPaolo Bonzini case 0xe3: /* no-op. */
17249ab747fSPaolo Bonzini break;
17349ab747fSPaolo Bonzini default:
17449ab747fSPaolo Bonzini BADF("Unknown command: 0x%x\n", data);
17549ab747fSPaolo Bonzini }
17649ab747fSPaolo Bonzini break;
17749ab747fSPaolo Bonzini case SSD0303_CMD_SKIP1:
17849ab747fSPaolo Bonzini DPRINTF("skip 0x%02x\n", data);
17949ab747fSPaolo Bonzini break;
18049ab747fSPaolo Bonzini }
18149ab747fSPaolo Bonzini break;
18249ab747fSPaolo Bonzini }
18349ab747fSPaolo Bonzini return 0;
18449ab747fSPaolo Bonzini }
18549ab747fSPaolo Bonzini
ssd0303_event(I2CSlave * i2c,enum i2c_event event)186d307c28cSCorey Minyard static int ssd0303_event(I2CSlave *i2c, enum i2c_event event)
18749ab747fSPaolo Bonzini {
188b1be4515SAndreas Färber ssd0303_state *s = SSD0303(i2c);
189b1be4515SAndreas Färber
19049ab747fSPaolo Bonzini switch (event) {
19149ab747fSPaolo Bonzini case I2C_FINISH:
19249ab747fSPaolo Bonzini s->mode = SSD0303_IDLE;
19349ab747fSPaolo Bonzini break;
19449ab747fSPaolo Bonzini case I2C_START_RECV:
19549ab747fSPaolo Bonzini case I2C_START_SEND:
19649ab747fSPaolo Bonzini case I2C_NACK:
19749ab747fSPaolo Bonzini /* Nothing to do. */
19849ab747fSPaolo Bonzini break;
199a78e9839SKlaus Jensen default:
200a78e9839SKlaus Jensen return -1;
20149ab747fSPaolo Bonzini }
202d307c28cSCorey Minyard
203d307c28cSCorey Minyard return 0;
20449ab747fSPaolo Bonzini }
20549ab747fSPaolo Bonzini
ssd0303_update_display(void * opaque)20649ab747fSPaolo Bonzini static void ssd0303_update_display(void *opaque)
20749ab747fSPaolo Bonzini {
20849ab747fSPaolo Bonzini ssd0303_state *s = (ssd0303_state *)opaque;
20949ab747fSPaolo Bonzini DisplaySurface *surface = qemu_console_surface(s->con);
21049ab747fSPaolo Bonzini uint8_t *dest;
21149ab747fSPaolo Bonzini uint8_t *src;
21249ab747fSPaolo Bonzini int x;
21349ab747fSPaolo Bonzini int y;
21449ab747fSPaolo Bonzini int line;
21549ab747fSPaolo Bonzini char *colors[2];
21649ab747fSPaolo Bonzini char colortab[MAGNIFY * 8];
21749ab747fSPaolo Bonzini int dest_width;
21849ab747fSPaolo Bonzini uint8_t mask;
21949ab747fSPaolo Bonzini
22049ab747fSPaolo Bonzini if (!s->redraw)
22149ab747fSPaolo Bonzini return;
22249ab747fSPaolo Bonzini
22349ab747fSPaolo Bonzini switch (surface_bits_per_pixel(surface)) {
22449ab747fSPaolo Bonzini case 0:
22549ab747fSPaolo Bonzini return;
22649ab747fSPaolo Bonzini case 15:
22749ab747fSPaolo Bonzini dest_width = 2;
22849ab747fSPaolo Bonzini break;
22949ab747fSPaolo Bonzini case 16:
23049ab747fSPaolo Bonzini dest_width = 2;
23149ab747fSPaolo Bonzini break;
23249ab747fSPaolo Bonzini case 24:
23349ab747fSPaolo Bonzini dest_width = 3;
23449ab747fSPaolo Bonzini break;
23549ab747fSPaolo Bonzini case 32:
23649ab747fSPaolo Bonzini dest_width = 4;
23749ab747fSPaolo Bonzini break;
23849ab747fSPaolo Bonzini default:
23949ab747fSPaolo Bonzini BADF("Bad color depth\n");
24049ab747fSPaolo Bonzini return;
24149ab747fSPaolo Bonzini }
24249ab747fSPaolo Bonzini dest_width *= MAGNIFY;
24349ab747fSPaolo Bonzini memset(colortab, 0xff, dest_width);
24449ab747fSPaolo Bonzini memset(colortab + dest_width, 0, dest_width);
24549ab747fSPaolo Bonzini if (s->flash) {
24649ab747fSPaolo Bonzini colors[0] = colortab;
24749ab747fSPaolo Bonzini colors[1] = colortab;
24849ab747fSPaolo Bonzini } else if (s->inverse) {
24949ab747fSPaolo Bonzini colors[0] = colortab;
25049ab747fSPaolo Bonzini colors[1] = colortab + dest_width;
25149ab747fSPaolo Bonzini } else {
25249ab747fSPaolo Bonzini colors[0] = colortab + dest_width;
25349ab747fSPaolo Bonzini colors[1] = colortab;
25449ab747fSPaolo Bonzini }
25549ab747fSPaolo Bonzini dest = surface_data(surface);
25649ab747fSPaolo Bonzini for (y = 0; y < 16; y++) {
25749ab747fSPaolo Bonzini line = (y + s->start_line) & 63;
25849ab747fSPaolo Bonzini src = s->framebuffer + 132 * (line >> 3) + 36;
25949ab747fSPaolo Bonzini mask = 1 << (line & 7);
26049ab747fSPaolo Bonzini for (x = 0; x < 96; x++) {
26149ab747fSPaolo Bonzini memcpy(dest, colors[(*src & mask) != 0], dest_width);
26249ab747fSPaolo Bonzini dest += dest_width;
26349ab747fSPaolo Bonzini src++;
26449ab747fSPaolo Bonzini }
26549ab747fSPaolo Bonzini for (x = 1; x < MAGNIFY; x++) {
26649ab747fSPaolo Bonzini memcpy(dest, dest - dest_width * 96, dest_width * 96);
26749ab747fSPaolo Bonzini dest += dest_width * 96;
26849ab747fSPaolo Bonzini }
26949ab747fSPaolo Bonzini }
27049ab747fSPaolo Bonzini s->redraw = 0;
27149ab747fSPaolo Bonzini dpy_gfx_update(s->con, 0, 0, 96 * MAGNIFY, 16 * MAGNIFY);
27249ab747fSPaolo Bonzini }
27349ab747fSPaolo Bonzini
ssd0303_invalidate_display(void * opaque)27449ab747fSPaolo Bonzini static void ssd0303_invalidate_display(void * opaque)
27549ab747fSPaolo Bonzini {
27649ab747fSPaolo Bonzini ssd0303_state *s = (ssd0303_state *)opaque;
27749ab747fSPaolo Bonzini s->redraw = 1;
27849ab747fSPaolo Bonzini }
27949ab747fSPaolo Bonzini
28049ab747fSPaolo Bonzini static const VMStateDescription vmstate_ssd0303 = {
28149ab747fSPaolo Bonzini .name = "ssd0303_oled",
28249ab747fSPaolo Bonzini .version_id = 1,
28349ab747fSPaolo Bonzini .minimum_version_id = 1,
284*f0613160SRichard Henderson .fields = (const VMStateField[]) {
28549ab747fSPaolo Bonzini VMSTATE_INT32(row, ssd0303_state),
28649ab747fSPaolo Bonzini VMSTATE_INT32(col, ssd0303_state),
28749ab747fSPaolo Bonzini VMSTATE_INT32(start_line, ssd0303_state),
28849ab747fSPaolo Bonzini VMSTATE_INT32(mirror, ssd0303_state),
28949ab747fSPaolo Bonzini VMSTATE_INT32(flash, ssd0303_state),
29049ab747fSPaolo Bonzini VMSTATE_INT32(enabled, ssd0303_state),
29149ab747fSPaolo Bonzini VMSTATE_INT32(inverse, ssd0303_state),
29249ab747fSPaolo Bonzini VMSTATE_INT32(redraw, ssd0303_state),
29349ab747fSPaolo Bonzini VMSTATE_UINT32(mode, ssd0303_state),
29449ab747fSPaolo Bonzini VMSTATE_UINT32(cmd_state, ssd0303_state),
29549ab747fSPaolo Bonzini VMSTATE_BUFFER(framebuffer, ssd0303_state),
296b1be4515SAndreas Färber VMSTATE_I2C_SLAVE(parent_obj, ssd0303_state),
29749ab747fSPaolo Bonzini VMSTATE_END_OF_LIST()
29849ab747fSPaolo Bonzini }
29949ab747fSPaolo Bonzini };
30049ab747fSPaolo Bonzini
301380cd056SGerd Hoffmann static const GraphicHwOps ssd0303_ops = {
302380cd056SGerd Hoffmann .invalidate = ssd0303_invalidate_display,
303380cd056SGerd Hoffmann .gfx_update = ssd0303_update_display,
304380cd056SGerd Hoffmann };
305380cd056SGerd Hoffmann
ssd0303_realize(DeviceState * dev,Error ** errp)306c8c9e103SPhilippe Mathieu-Daudé static void ssd0303_realize(DeviceState *dev, Error **errp)
30749ab747fSPaolo Bonzini {
308c8c9e103SPhilippe Mathieu-Daudé ssd0303_state *s = SSD0303(dev);
30949ab747fSPaolo Bonzini
310c8c9e103SPhilippe Mathieu-Daudé s->con = graphic_console_init(dev, 0, &ssd0303_ops, s);
31149ab747fSPaolo Bonzini qemu_console_resize(s->con, 96 * MAGNIFY, 16 * MAGNIFY);
31249ab747fSPaolo Bonzini }
31349ab747fSPaolo Bonzini
ssd0303_class_init(ObjectClass * klass,void * data)31449ab747fSPaolo Bonzini static void ssd0303_class_init(ObjectClass *klass, void *data)
31549ab747fSPaolo Bonzini {
31649ab747fSPaolo Bonzini DeviceClass *dc = DEVICE_CLASS(klass);
31749ab747fSPaolo Bonzini I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);
31849ab747fSPaolo Bonzini
319c8c9e103SPhilippe Mathieu-Daudé dc->realize = ssd0303_realize;
32049ab747fSPaolo Bonzini k->event = ssd0303_event;
32149ab747fSPaolo Bonzini k->recv = ssd0303_recv;
32249ab747fSPaolo Bonzini k->send = ssd0303_send;
32349ab747fSPaolo Bonzini dc->vmsd = &vmstate_ssd0303;
32449ab747fSPaolo Bonzini }
32549ab747fSPaolo Bonzini
32649ab747fSPaolo Bonzini static const TypeInfo ssd0303_info = {
327b1be4515SAndreas Färber .name = TYPE_SSD0303,
32849ab747fSPaolo Bonzini .parent = TYPE_I2C_SLAVE,
32949ab747fSPaolo Bonzini .instance_size = sizeof(ssd0303_state),
33049ab747fSPaolo Bonzini .class_init = ssd0303_class_init,
33149ab747fSPaolo Bonzini };
33249ab747fSPaolo Bonzini
ssd0303_register_types(void)33349ab747fSPaolo Bonzini static void ssd0303_register_types(void)
33449ab747fSPaolo Bonzini {
33549ab747fSPaolo Bonzini type_register_static(&ssd0303_info);
33649ab747fSPaolo Bonzini }
33749ab747fSPaolo Bonzini
33849ab747fSPaolo Bonzini type_init(ssd0303_register_types)
339