1/*
2 * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6/* The code in this file was partially extracted from David Pollack's summer
7 * project, ssh://hg//data/hg_root/summer_students/davidp/sel4vga.
8 */
9
10#include <assert.h>
11#include <string.h>
12#include <stdlib.h>
13#include <stdint.h>
14#include <bga/bga.h>
15
16struct bga {
17    void *framebuffer;
18
19    /* IO port functions. */
20    uint16_t (*read)(uint16_t port);
21    void (*write)(uint16_t port, uint16_t value);
22
23    /* Current configuration. */
24    unsigned int width;
25    unsigned int height;
26    unsigned int bpp;
27};
28
29/* The BGA device is controlled by operating on two IO ports, first the index
30 * port to indicate what register you are trying to read/write and then the
31 * data port to read or write the actual register.
32 */
33static const uint16_t INDEX = 0x1ce;
34static const uint16_t DATA  = 0x1cf;
35static void write_data(bga_p device, uint16_t index, uint16_t data)
36{
37    assert(device != NULL);
38    device->write(INDEX, index);
39    device->write(DATA, data);
40}
41static uint16_t read_data(bga_p device, uint16_t index)
42{
43    assert(device != NULL);
44    device->write(INDEX, index);
45    return device->read(DATA);
46}
47
48/* After writing INDEX_ENABLED to the index port, you can write either 0x0000
49 * or 0x0001 to the data port to disable or enable the device respectively.
50 */
51static const uint16_t INDEX_ENABLED = 0x0004;
52static void disable(bga_p device)
53{
54    const uint16_t data_disable = 0x0000;
55    write_data(device, INDEX_ENABLED, data_disable);
56}
57static void enable(bga_p device)
58{
59    /* Note that we unconditionally use a linear frame buffer and clear the
60    * screen. Not ideal, but the emphasis is on simplicity in this driver.
61    */
62    const uint16_t data_enable = 0x0001;
63    const uint16_t lfb = 0x0040;
64    write_data(device, INDEX_ENABLED, data_enable | lfb);
65}
66
67uint16_t bga_version(bga_p device)
68{
69    assert(device != NULL);
70    const uint16_t index_id = 0x0000; /* Index to read version information. */
71    const uint16_t mask = (uint16_t)~0xb0c0;
72    uint16_t result = read_data(device, index_id);
73    return result & mask;
74}
75
76bga_p bga_init(void *framebuffer,
77               void (*ioport_write)(uint16_t port, uint16_t value),
78               uint16_t (*ioport_read)(uint16_t port))
79{
80    bga_p device = (bga_p)malloc(sizeof(struct bga));
81    if (device == NULL) {
82        return device;
83    }
84
85    memset(device, 0, sizeof(*device));
86    device->framebuffer = framebuffer;
87    device->write = ioport_write;
88    device->read = ioport_read;
89
90    return device;
91}
92
93int bga_destroy(bga_p device)
94{
95    free(device);
96    return 0;
97}
98
99int bga_set_mode(bga_p device, unsigned int width, unsigned int height, unsigned int bpp)
100{
101    /* We need to disable the device to change these parameters. */
102    disable(device);
103
104    /* Relevant indicies to write to. */
105    const uint16_t x_resolution = 0x0001;
106    const uint16_t y_resolution = 0x0002;
107    const uint16_t bits_per_pixel = 0x0003;
108
109    /* Setup the requested settings. */
110    write_data(device, x_resolution, width);
111    device->width = width;
112    write_data(device, y_resolution, height);
113    device->height = height;
114    write_data(device, bits_per_pixel, bpp);
115    device->bpp = bpp;
116
117    /* Finally re-enable the device to have the settings take effect. */
118    enable(device);
119
120    return 0;
121}
122
123int bga_set_pixel(bga_p device, unsigned int x, unsigned int y, char *value)
124{
125    char *target;
126    unsigned int coord_factor;
127    size_t len;
128
129    /* XXX: None of these other than 24-bit have been tested and they are most
130     * likely incorrect.
131     */
132
133    switch (device->bpp) {
134    case 8: {
135        coord_factor = 1;
136        len = 1;
137        break;
138    }
139    case 15:
140    case 16: {
141        coord_factor = 2;
142        len = 2;
143        break;
144    }
145    case 24: {
146        coord_factor = 3;
147        len = 3;
148        break;
149    }
150    case 32: {
151        coord_factor = 4;
152        len = 3;
153        break;
154    }
155    default: {
156        /* Unsupported BPP. */
157        return -1;
158    }
159    }
160
161    /* Determine where we need to write and copy the pixel data over. */
162    target = ((char *)device->framebuffer) + (y * device->width + x) * coord_factor;
163    (void)memcpy(target, value, len);
164
165    return 0;
166}
167
168void *bga_get_framebuffer(bga_p device)
169{
170    return device->framebuffer;
171}
172