Skip to content

Instantly share code, notes, and snippets.

@notro
Created March 1, 2021 21:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save notro/c1b32cea591f84d3d1c94f30812c1ba6 to your computer and use it in GitHub Desktop.
Save notro/c1b32cea591f84d3d1c94f30812c1ba6 to your computer and use it in GitHub Desktop.
/*
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <string.h>
#include "gud.h"
#define GUD_LOG1
#define GUD_LOG2
#define min(a,b) (((a) < (b)) ? (a) : (b))
#define div_round_up(a,b) (((a) + ((b) / 2)) / (b))
struct gud_set_buffer_req _set_buf;
#define GUD_MAX_PROPERTIES 8
// struct with room for properties, since no malloc.
struct gud_state_req_with_property_array {
struct gud_display_mode_req mode;
uint8_t format;
uint8_t connector;
struct gud_property_req properties[GUD_MAX_PROPERTIES];
} __attribute__((packed));
static struct gud_state_req_with_property_array _state;
static uint8_t _state_num_properties;
static int gud_req_get_descriptor(const struct gud_display *disp, void *data, size_t size)
{
struct gud_display_descriptor_req desc;
// If ever a setup/init function arises, move this check:
if ((disp->num_properties + disp->num_connector_properties) > GUD_MAX_PROPERTIES)
return -GUD_STATUS_ERROR;
if (!size)
return -GUD_STATUS_PROTOCOL_ERROR;
desc.magic = GUD_DISPLAY_MAGIC;
desc.version = 1;
desc.max_buffer_size = 0;
desc.flags = disp->flags;
desc.compression = disp->compression;
desc.min_width = disp->width;
desc.max_width = disp->width;
desc.min_height = disp->height;
desc.max_height = disp->height;
size = min(size, sizeof(desc));
memcpy(data, &desc, size);
return size;
}
static int gud_req_get_formats(const struct gud_display *disp, void *data, size_t size)
{
size = min(size, disp->num_formats);
memcpy(data, disp->formats, size);
return size;
}
static int gud_req_get_properties(const struct gud_display *disp,
void *data, size_t size)
{
size = size - (size % sizeof(*disp->properties));
if (!size)
return -GUD_STATUS_PROTOCOL_ERROR;
size = min(size, disp->num_properties * sizeof(*disp->properties));
memcpy(data, disp->properties, size);
return size;
}
static int gud_req_get_connectors(const struct gud_display *disp, struct gud_connector_descriptor_req *desc, size_t size)
{
if (size < sizeof(*desc))
return -GUD_STATUS_PROTOCOL_ERROR;
desc->connector_type = GUD_CONNECTOR_TYPE_PANEL;
desc->flags = 0;
return sizeof(*desc);
}
static int gud_req_get_connector_properties(const struct gud_display *disp,
void *data, size_t size)
{
size = size - (size % sizeof(*disp->connector_properties));
if (!size)
return -GUD_STATUS_PROTOCOL_ERROR;
size = min(size, disp->num_connector_properties * sizeof(*disp->connector_properties));
memcpy(data, disp->connector_properties, size);
return size;
}
static int gud_req_get_connector_status(const struct gud_display *disp, uint8_t *status, size_t size)
{
if (!size)
return -GUD_STATUS_PROTOCOL_ERROR;
*status = GUD_CONNECTOR_STATUS_CONNECTED;
return sizeof(*status);
}
static int gud_req_get_connector_modes(const struct gud_display *disp, struct gud_display_mode_req *mode, size_t size)
{
// if (disp->edid)
// return 0;
if (size < sizeof(*mode))
return -GUD_STATUS_PROTOCOL_ERROR;
mode->clock = 1;
mode->hdisplay = disp->width;
mode->hsync_start = mode->hdisplay;
mode->hsync_end = mode->hdisplay;
mode->htotal = mode->hdisplay;
mode->vdisplay = disp->height;
mode->vsync_start = mode->vdisplay;
mode->vsync_end = mode->vdisplay;
mode->vtotal = mode->vdisplay;
mode->flags = 0;
return sizeof(*mode);
}
static int gud_req_get_connector_edid(const struct gud_display *disp,
uint8_t *edid, size_t size)
{
// Caller might cap wLength to save on buffer size so don't return an error
if (size < 128)
return 0;
if (!disp->edid ||
!disp->edid->name || strlen(disp->edid->name) > 13 ||
!disp->edid->pnp || strlen(disp->edid->pnp) != 3)
return 0;
// Header
edid[0] = 0x00; edid[1] = 0xff; edid[2] = 0xff; edid[3] = 0xff;
edid[4] = 0xff; edid[5] = 0xff; edid[6] = 0xff; edid[7] = 0x00;
// Plug'n Play Id
uint8_t pnp[3];
for (uint8_t i = 0; i < 3; i++)
pnp[i] = disp->edid->pnp[i] - 'A' + 1;
edid[8] = pnp[0] << 2 | pnp[1] >> 3;
edid[9] = pnp[1] << 5 | pnp[2];
// Manufacturer product code. 16 bits, little-endian.
edid[10] = disp->edid->product_code;
edid[11] = disp->edid->product_code >> 8;
// Serial number. 32 bits, little-endian.
uint32_t serial = 0;
if (disp->edid->get_serial_number)
serial = disp->edid->get_serial_number();
for (uint8_t i = 12; i < 16; i++) {
edid[i] = serial & 0xff;
serial >>= 8;
}
// Manufacture
edid[16] = 1; // week
if (disp->edid->year > 1990)
edid[17] = disp->edid->year - 1990;
else
edid[17] = 0;
// EDID version 1.3
edid[18] = 1; edid[19] = 3;
// Basic display parameters
edid[20] = 0x80; // Digital input: 1, bit depth: undefined, interface: undefined
edid[21] = div_round_up(disp->edid->width_mm, 10); // width in cm
edid[22] = div_round_up(disp->edid->height_mm, 10); // height in cm
edid[23] = 0; // gamma
edid[24] = 0x0a; // RGB-color, preferred timing in DTD 1
// chroma
memset(edid + 25, 0, 10);
// Established timings (not used)
memset(edid + 35, 0, 3);
// Standard timing information, filled with unused values
memset(edid + 38, 0x01, 16);
// Descriptor 1 (54-71): Detailed Timing Descriptor
uint32_t framerate = 60;
uint32_t clock_khz = disp->width * disp->height * framerate / 1000;
// Pixel clock in 10 kHz units (0.01–655.35 MHz, little-endian).
edid[54] = div_round_up(clock_khz, 10); edid[55] = div_round_up(clock_khz, 10) >> 8;
edid[56] = disp->width & 0xff; // Horizontal active pixels 8 lsbits (0–4095)
edid[57] = 0x00; // Horizontal blanking pixels 8 lsbits (0–4095) End of active to start of next active.
edid[58] = (disp->width >> 8) << 4; // Horizontal active pixels 4 msbits << 4 | Horizontal blanking pixels 4 msbits
edid[59] = disp->height & 0xff; // 480=0x1e0 Vertical active lines 8 lsbits (0–4095)
edid[60] = 0x00; // Vertical blanking lines 8 lsbits (0–4095)
edid[61] = (disp->height >> 8) << 4; // Vertical active lines 4 msbits << 4 | Vertical blanking lines 4 msbits
edid[62] = 0x00; // Horizontal front porch (sync offset) pixels 8 lsbits (0–1023) From blanking start
// DRM chokes on zero pulse width, so use 1:
edid[63] = 0x01; // Horizontal sync pulse width pixels 8 lsbits (0–1023)
edid[64] = 0x01; // Vertical front porch (sync offset) lines 4 lsbits (0–63) << 4 | Vertical sync pulse width lines 4 lsbits (0–63)
edid[65] = 0x00; // msbits
edid[66] = disp->edid->width_mm & 0xff; // Horizontal image size, mm, 8 lsbits (0–4095 mm)
edid[67] = disp->edid->height_mm & 0xff; // Vertical image size, mm, 8 lsbits (0–4095 mm)
edid[68] = (disp->edid->width_mm >> 8) << 4 |
disp->edid->height_mm >> 8; // Horizontal image size, mm, 4 msbits << 4 | Vertical image size, mm, 4 msbits
edid[69] = 0x00; // Horizontal border pixels (one side; total is twice this)
edid[70] = 0x00; // Vertical border lines (one side; total is twice this)
edid[71] = 0x1e; // Features bitmap: Non-Interlaced, Normal Display – No Stereo, Digital Separate Sync, +vsync, +hsync
// Descriptor 2 (72-89): Display name
edid[72] = 0x00; edid[73] = 0x00; edid[74] = 0x00;
edid[75] = 0xfc;
edid[76] = 0x00;
memset(edid + 77, 0x20, 13); // unused: pad with spaces
uint8_t name_len = strlen(disp->edid->name);
memcpy(edid + 77, disp->edid->name, name_len);
if (name_len < 13)
edid[77 + name_len] = 0x0a; // terminate short string with line feed
// Descriptor 3 (90-107): Unused
memset(edid + 90, 0, 18);
// Descriptor 4 (108-125): Unused
memset(edid + 108, 0, 18);
// Number of extensions to follow. 0 if no extensions.
edid[126] = 0;
// checksum
uint8_t checksum = 0;
for (uint8_t i = 0; i < 127; i++)
checksum += edid[i];
edid[127] = 0xff - checksum + 1;
return 128;
}
int gud_req_get(const struct gud_display *disp, uint8_t request, uint16_t index, void *data, size_t size)
{
int ret;
GUD_LOG2("%s: request=0x%x index=%u size=%zu\n", __func__, request, index, size);
if (index)
return -GUD_STATUS_PROTOCOL_ERROR;
switch (request) {
case GUD_REQ_GET_DESCRIPTOR:
ret = gud_req_get_descriptor(disp, data, size);
break;
case GUD_REQ_GET_FORMATS:
ret = gud_req_get_formats(disp, data, size);
break;
case GUD_REQ_GET_PROPERTIES:
ret = gud_req_get_properties(disp, data, size);
break;
case GUD_REQ_GET_CONNECTORS:
ret = gud_req_get_connectors(disp, data, size);
break;
case GUD_REQ_GET_CONNECTOR_PROPERTIES:
ret = gud_req_get_connector_properties(disp, data, size);
break;
// case GUD_REQ_GET_CONNECTOR_TV_MODE_VALUES:
// ret = gud_req_get_connector_tv_mode_values(disp, data, size);
// break;
case GUD_REQ_GET_CONNECTOR_STATUS:
ret = gud_req_get_connector_status(disp, data, size);
break;
case GUD_REQ_GET_CONNECTOR_MODES:
ret = gud_req_get_connector_modes(disp, data, size);
break;
case GUD_REQ_GET_CONNECTOR_EDID:
ret = gud_req_get_connector_edid(disp, data, size);
break;
default:
ret = -GUD_STATUS_REQUEST_NOT_SUPPORTED;
break;
}
return ret;
}
static int gud_req_set_buffer(const struct gud_display *disp, const struct gud_set_buffer_req *req, size_t size)
{
size_t length;
int ret;
if (size != sizeof(*req))
return -GUD_STATUS_PROTOCOL_ERROR;
GUD_LOG1("%s: x=%u y=%u width=%u height=%u length=%u compression=0x%x compressed_length=%u\n",
__func__, req->x, req->y, req->width, req->height, req->length,
req->compression, req->compressed_length);
if (req->x >= disp->width || req->y >= disp->height ||
(req->x + req->width) > disp->width || (req->y + req->height) > disp->height)
return -GUD_STATUS_INVALID_PARAMETER;
if (req->compression && !req->compressed_length)
return -GUD_STATUS_INVALID_PARAMETER;
switch (_state.format) {
case GUD_PIXEL_FORMAT_R1:
length = req->width * req->height / 8;
break;
case GUD_PIXEL_FORMAT_RGB565:
length = req->width * req->height * 2;
break;
case GUD_PIXEL_FORMAT_XRGB8888:
case GUD_PIXEL_FORMAT_ARGB8888:
length = req->width * req->height * 4;
break;
default:
return -GUD_STATUS_INVALID_PARAMETER;
}
if (req->length != length)
return -GUD_STATUS_INVALID_PARAMETER;
if (disp->set_buffer)
ret = disp->set_buffer(disp, req);
else
ret = 0;
_set_buf = *req;
return ret;
}
static int gud_req_set_state_check(const struct gud_display *disp, const struct gud_state_req *req, size_t size)
{
unsigned int i, num_properties;
GUD_LOG1("%s: mode=%ux%u format=0x%02x connector=%u\n",
__func__, req->mode.hdisplay, req->mode.vdisplay, req->format, req->connector);
if (size < sizeof(*req))
return -GUD_STATUS_PROTOCOL_ERROR;
if ((size - sizeof(*req)) % sizeof(*req->properties))
return -GUD_STATUS_PROTOCOL_ERROR;
num_properties = (size - sizeof(*req)) / sizeof(*req->properties);
if (num_properties > disp->num_properties + disp->num_connector_properties)
return -GUD_STATUS_PROTOCOL_ERROR;
if (req->mode.hdisplay != disp->width || req->mode.vdisplay != disp->height ||
req->connector != 0)
return -GUD_STATUS_INVALID_PARAMETER;
for (i = 0; i < disp->num_formats; i++) {
if (req->format == disp->formats[i])
break;
}
if (i == disp->num_formats)
return -GUD_STATUS_INVALID_PARAMETER;
memcpy(&_state, req, size);
_state_num_properties = num_properties;
return 0;
}
static int gud_req_set_state_commit(const struct gud_display *disp, size_t size)
{
int ret;
GUD_LOG1("%s:\n", __func__);
if (disp->state_commit)
ret = disp->state_commit(disp, (const struct gud_state_req *)&_state, _state_num_properties);
else
ret = 0;
return ret;
}
static int gud_req_set_controller_enable(const struct gud_display *disp, const uint8_t *enable, size_t size)
{
int ret;
if (size != sizeof(*enable))
return -GUD_STATUS_REQUEST_NOT_SUPPORTED;
GUD_LOG1("%s: enable=%u\n", __func__, *enable);
if (disp->controller_enable)
ret = disp->controller_enable(disp, *enable);
else
ret = 0;
return ret;
}
static int gud_req_set_display_enable(const struct gud_display *disp, const uint8_t *enable, size_t size)
{
int ret;
if (size != sizeof(*enable))
return -GUD_STATUS_REQUEST_NOT_SUPPORTED;
GUD_LOG1("%s: enable=%u\n", __func__, *enable);
if (disp->display_enable)
ret = disp->display_enable(disp, *enable);
else
ret = 0;
return ret;
}
int gud_req_set(const struct gud_display *disp, uint8_t request, uint16_t index, const void *data, size_t size)
{
int ret;
GUD_LOG2("%s: request=0x%x index=%u size=%zu\n", __func__, request, index, size);
if (index)
return -GUD_STATUS_PROTOCOL_ERROR;
switch (request) {
case GUD_REQ_SET_CONNECTOR_FORCE_DETECT:
ret = 0;
break;
case GUD_REQ_SET_BUFFER:
ret = gud_req_set_buffer(disp, data, size);
break;
case GUD_REQ_SET_STATE_CHECK:
ret = gud_req_set_state_check(disp, data, size);
break;
case GUD_REQ_SET_STATE_COMMIT:
ret = gud_req_set_state_commit(disp, size);
break;
case GUD_REQ_SET_CONTROLLER_ENABLE:
ret = gud_req_set_controller_enable(disp, data, size);
break;
case GUD_REQ_SET_DISPLAY_ENABLE:
ret = gud_req_set_display_enable(disp, data, size);
break;
default:
ret = -GUD_STATUS_REQUEST_NOT_SUPPORTED;
break;
}
return ret;
}
void gud_write_buffer(const struct gud_display *disp, void *buf)
{
if (disp->write_buffer)
disp->write_buffer(disp, &_set_buf, buf);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment