Files
86Box/src/video/vid_ddc.c
2024-03-10 18:17:32 -03:00

233 lines
11 KiB
C

/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* DDC monitor emulation.
*
*
*
* Authors: RichardG, <richardg867@gmail.com>
*
* Copyright 2020 RichardG.
*/
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <wchar.h>
#include <math.h>
#include <86box/86box.h>
#include <86box/i2c.h>
#define PIXEL_MM(px) (((px) * 25.4) / 96.0)
#define STANDARD_TIMING(slot, width, aspect_ratio, refresh) \
do { \
edid->slot.horiz_pixels = ((width) >> 3) - 31; \
edid->slot.aspect_ratio_refresh_rate = ((aspect_ratio) << 6) | ((refresh) -60); \
} while (0)
#define DETAILED_TIMING(slot, clk, width, height, hblank, vblank, hfp, hsp, vfp, vsp) \
do { \
edid->slot.pixel_clock_lsb = ((clk) / 10) & 0xff; \
edid->slot.pixel_clock_msb = ((clk) / 10) >> 8; \
edid->slot.h_active_lsb = (width) &0xff; \
edid->slot.h_blank_lsb = (hblank) &0xff; \
edid->slot.h_active_blank_msb = (((width) >> 4) & 0xf0) | (((hblank) >> 8) & 0x0f); \
edid->slot.v_active_lsb = (height) &0xff; \
edid->slot.v_blank_lsb = (vblank) &0xff; \
edid->slot.v_active_blank_msb = (((height) >> 4) & 0xf0) | (((vblank) >> 8) & 0x0f); \
edid->slot.h_front_porch_lsb = (hfp) &0xff; \
edid->slot.h_sync_pulse_lsb = (hsp) &0xff; \
edid->slot.v_front_porch_sync_pulse_lsb = (((vfp) &0x0f) << 4) | ((vsp) &0x0f); \
edid->slot.hv_front_porch_sync_pulse_msb = (((hfp) >> 2) & 0xc0) | (((hsp) >> 4) & 0x30) | (((vfp) >> 2) & 0x0c) | (((vsp) >> 4) & 0x03); \
edid->slot.h_size_lsb = (uint8_t) horiz_mm; \
edid->slot.v_size_lsb = (uint8_t) vert_mm; \
edid->slot.hv_size_msb = ((((uint16_t) horiz_mm) >> 4) & 0xf0) | ((((uint16_t) vert_mm) >> 8) & 0x0f); \
} while (0)
enum {
STD_ASPECT_16_10 = 0x0,
STD_ASPECT_4_3,
STD_ASPECT_5_4,
STD_ASPECT_16_9
};
typedef struct {
uint8_t horiz_pixels, aspect_ratio_refresh_rate;
} edid_standard_timing_t;
typedef struct {
uint8_t pixel_clock_lsb, pixel_clock_msb, h_active_lsb, h_blank_lsb,
h_active_blank_msb, v_active_lsb, v_blank_lsb, v_active_blank_msb,
h_front_porch_lsb, h_sync_pulse_lsb, v_front_porch_sync_pulse_lsb,
hv_front_porch_sync_pulse_msb, h_size_lsb, v_size_lsb, hv_size_msb,
h_border, v_border, features;
} edid_detailed_timing_t;
typedef struct {
uint8_t magic[2], reserved, tag, range_limit_offsets;
union {
char ascii[13];
struct {
uint8_t min_v_field, max_v_field, min_h_line, max_h_line, max_pixel_clock,
timing_type;
union {
uint8_t padding[7];
struct {
uint8_t reserved, gtf_start_freq, gtf_c, gtf_m_lsb, gtf_m_msb,
gtf_k, gtf_j;
};
struct {
uint8_t cvt_version, add_clock_precision, max_active_pixels,
aspect_ratios, aspect_ratio_pref, scaling_support,
refresh_pref;
};
};
} range_limits;
struct {
edid_standard_timing_t timings[6];
uint8_t padding;
} ext_standard_timings;
struct {
uint8_t version;
struct {
uint8_t lines_lsb, lines_msb_aspect_ratio, refresh_rate;
} timings[4];
} cvt_timings;
struct {
uint8_t version, timings[6], reserved[6];
} established_timings3;
};
} edid_descriptor_t;
typedef struct {
uint8_t magic[8], mfg[2], mfg_product[2], serial[4], mfg_week, mfg_year,
edid_version, edid_rev;
uint8_t input_params, horiz_size, vert_size, gamma, features;
uint8_t red_green_lsb, blue_white_lsb, red_x_msb, red_y_msb, green_x_msb,
green_y_msb, blue_x_msb, blue_y_msb, white_x_msb, white_y_msb;
uint8_t established_timings[3];
edid_standard_timing_t standard_timings[8];
union {
edid_detailed_timing_t detailed_timings[4];
edid_descriptor_t descriptors[4];
};
uint8_t extensions, checksum;
uint8_t ext_tag, ext_rev, ext_dtd_offset, ext_native_dtds;
union {
edid_detailed_timing_t ext_detailed_timings[6];
edid_descriptor_t ext_descriptors[6];
};
uint8_t padding[15], checksum2;
} edid_t;
void *
ddc_init(void *i2c)
{
edid_t *edid = malloc(sizeof(edid_t));
memset(edid, 0, sizeof(edid_t));
uint8_t *edid_bytes = (uint8_t *) edid;
double horiz_mm = PIXEL_MM(800);
double vert_mm = PIXEL_MM(600);
memset(&edid->magic[1], 0xff, sizeof(edid->magic) - 2);
edid->mfg[0] = 0x09; /* manufacturer "BOX" (currently unassigned by UEFI) */
edid->mfg[1] = 0xf8;
edid->mfg_week = 48;
edid->mfg_year = 2020 - 1990;
edid->edid_version = 0x01;
edid->edid_rev = 0x04; /* EDID 1.4, required for Xorg on newer Linux to use the preferred mode timing instead of maxing out */
edid->input_params = 0x0e; /* analog input; separate sync; composite sync; sync on green */
edid->horiz_size = round(horiz_mm / 10.0);
edid->vert_size = round(vert_mm / 10.0);
edid->features = 0xeb; /* DPMS standby/suspend/active-off; RGB color; first timing is preferred; GTF/CVT */
edid->red_green_lsb = 0x81;
edid->blue_white_lsb = 0xf1;
edid->red_x_msb = 0xa3;
edid->red_y_msb = 0x57;
edid->green_x_msb = 0x53;
edid->green_y_msb = 0x9f;
edid->blue_x_msb = 0x27;
edid->blue_y_msb = 0x0a;
edid->white_x_msb = 0x50;
edid->white_y_msb = 0x00;
memset(&edid->established_timings, 0xff, sizeof(edid->established_timings)); /* all enabled */
/* 60 Hz timings */
STANDARD_TIMING(standard_timings[0], 1280, STD_ASPECT_16_9, 60); /* 1280x720 */
STANDARD_TIMING(standard_timings[1], 1280, STD_ASPECT_16_10, 60); /* 1280x800 */
STANDARD_TIMING(standard_timings[2], 1366, STD_ASPECT_16_9, 60); /* 1360x768 (closest to 1366x768) */
STANDARD_TIMING(standard_timings[3], 1440, STD_ASPECT_16_10, 60); /* 1440x900 */
STANDARD_TIMING(standard_timings[4], 1600, STD_ASPECT_16_9, 60); /* 1600x900 */
STANDARD_TIMING(standard_timings[5], 1600, STD_ASPECT_4_3, 60); /* 1600x1200 */
STANDARD_TIMING(standard_timings[6], 1920, STD_ASPECT_16_9, 60); /* 1920x1080 */
STANDARD_TIMING(standard_timings[7], 2048, STD_ASPECT_4_3, 60); /* 2048x1536 */
/* Detailed timing for the preferred mode of 800x600 @ 60 Hz */
DETAILED_TIMING(detailed_timings[0], 40000, 800, 600, 256, 28, 40, 128, 1, 4);
edid->descriptors[1].tag = 0xf7; /* established timings 3 */
edid->descriptors[1].established_timings3.version = 0x0a;
memset(&edid->descriptors[1].established_timings3.timings, 0xff, sizeof(edid->descriptors[1].established_timings3.timings)); /* all enabled */
edid->descriptors[1].established_timings3.timings[5] &= 0xf0; /* reserved bits */
edid->descriptors[2].tag = 0xfd; /* range limits */
edid->descriptors[2].range_limits.min_v_field = 45;
edid->descriptors[2].range_limits.max_v_field = 125;
edid->descriptors[2].range_limits.min_h_line = 30; /* 640x480 = ~31.5 KHz */
edid->descriptors[2].range_limits.max_h_line = 115; /* 1920x1440 = 112.5 KHz */
edid->descriptors[2].range_limits.max_pixel_clock = 30; /* 1920x1440 = 297 MHz */
edid->descriptors[2].range_limits.timing_type = 0x00; /* default GTF */
edid->descriptors[2].range_limits.padding[0] = 0x0a;
memset(&edid->descriptors[2].range_limits.padding[1], 0x20, sizeof(edid->descriptors[2].range_limits.padding) - 1);
edid->descriptors[3].tag = 0xfc; /* display name */
memcpy(&edid->descriptors[3].ascii, "86Box Monitor", 13); /* exactly 13 characters (would otherwise require LF termination and space padding) */
edid->extensions = 1;
for (uint8_t c = 0; c < 127; c++)
edid->checksum += edid_bytes[c];
edid->checksum = 256 - edid->checksum;
edid->ext_tag = 0x02;
edid->ext_rev = 0x03;
edid->ext_native_dtds = 0x80; /* underscans IT; no native extended modes */
edid->ext_dtd_offset = 0x04;
/* Detailed timing for 1366x768 */
DETAILED_TIMING(ext_detailed_timings[0], 85500, 1366, 768, 426, 30, 70, 143, 3, 3);
/* High refresh rate timings (within the standard 85 Hz VGA limit) */
edid->ext_descriptors[1].tag = 0xfa; /* standard timing identifiers */
#define ext_standard_timings0 ext_descriptors[1].ext_standard_timings.timings
STANDARD_TIMING(ext_standard_timings0[0], 640, STD_ASPECT_4_3, 85); /* 640x480 @ 85 Hz */
STANDARD_TIMING(ext_standard_timings0[1], 800, STD_ASPECT_4_3, 85); /* 800x600 @ 85 Hz */
STANDARD_TIMING(ext_standard_timings0[2], 1024, STD_ASPECT_4_3, 85); /* 1024x768 @ 85 Hz */
STANDARD_TIMING(ext_standard_timings0[3], 1280, STD_ASPECT_5_4, 85); /* 1280x1024 @ 85 Hz */
STANDARD_TIMING(ext_standard_timings0[4], 1600, STD_ASPECT_4_3, 85); /* 1600x1200 @ 85 Hz */
STANDARD_TIMING(ext_standard_timings0[5], 1680, STD_ASPECT_16_10, 60); /* 1680x1050 @ 60 Hz (previously in standard timings) */
edid->ext_descriptors[1].ext_standard_timings.padding = 0x0a;
for (uint8_t c = 128; c < 255; c++)
edid->checksum2 += edid_bytes[c];
edid->checksum2 = 256 - edid->checksum2;
return i2c_eeprom_init(i2c, 0x50, edid_bytes, sizeof(edid_t), 0);
}
void
ddc_close(void *eeprom)
{
i2c_eeprom_close(eeprom);
}