/* Copyright 2016 Castle Technology Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/* edidsupport.c */

/*
 * EDID support.
 */

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <math.h>
#include <stdbool.h>

#include "kernel.h"
#include "swis.h"
#include "Global/RISCOS.h"
#include "Global/GraphicsV.h"
#include "Global/Services.h"
#include "Global/VIDCList.h"
#include "Interface/HighFSI.h"

#include "edidmemory.h"
#include "errors.h"
#include "monitors.h"
#include "modex.h"
#include "edidsupport.h"
#include "tables.h"

/* By default we only use DMT timings; we will check for additional support later */
static int timing_support = EDID_USE_DMT;

/* Check if a new mode is present already.
 * If it has, we won't be adding it to the chain.
 * Return 1 if successful, and 0 if we have rejected it for any reason
 */
static int add_proposed_mode(MonitorDescriptionRef monitor_definition, ModeDescriptionRef proposed_mode)
{
    proposed_mode->next = monitor_definition->modelist;
    monitor_definition->modelist = proposed_mode;
    return 1;
}

#if DODEBUG
/* Produce debug output showing the
 * mode descriptor being passed for a particular mode
 */
static void display_mode_parameters(ModeDescriptionRef mode_desc)
{
    printf("XRes: %i YRes: %i ",
           mode_desc->definition.xres,
           mode_desc->definition.yres);
    if (mode_desc->definition.interlaced == 1)
    {
        printf("(interlaced)");
    }
    else
    {
        printf("(non-interlaced)");
    }

    printf(" PixRate: %i\nHSync:%i Hbpch:%i Hlbdr:%i Hdisp:%i Hrbdr:%i Hfpch:%i\nVSync:%i Vbpch:%i Vtbdr:%i Vdispl:%i Vbbdr:%i Vfpch:%i\n\n",
           mode_desc->definition.pixel_khz,
           mode_desc->definition.hpar[FR_SYNC],
           mode_desc->definition.hpar[FR_BPCH],
           mode_desc->definition.hpar[FR_BDR1],
           mode_desc->definition.hpar[FR_DISP],
           mode_desc->definition.hpar[FR_BDR2],
           mode_desc->definition.hpar[FR_FPCH],
           mode_desc->definition.vpar[FR_SYNC],
           mode_desc->definition.vpar[FR_BPCH],
           mode_desc->definition.vpar[FR_BDR1],
           mode_desc->definition.vpar[FR_DISP],
           mode_desc->definition.vpar[FR_BDR2],
           mode_desc->definition.vpar[FR_FPCH]);
}
#endif

/* Copy a builtin mode and add it to the monitor description
 */
static _kernel_oserror *add_builtin_mode(MonitorDescriptionRef monitor, const ModeDefinition *mode, uint8_t priority)
{
    ModeDescriptionRef mode_desc = (ModeDescriptionRef) malloc(sizeof(ModeDescription));
    if (mode_desc == NULL)
    {
        return error(ERR_NOSPACE, 0, 0, 0);
    }

    mode_desc->definition = *mode;
    compute_modedescription(mode_desc);
    mode_desc->frame_hz = mode->freq;
    mode_desc->priority = priority;

    if (add_proposed_mode(monitor, mode_desc) == 0)
    {
        free(mode_desc);
        return NULL;
    }

#if DODEBUG
    display_mode_parameters(mode_desc);
#endif
    return NULL;
}

/* generate_dmt_mode creates a mode using standardised timing parameters.
 * These come from the Display Monitor Timing Document (VESA).
 * They are part of EDID 1.0 to 1.3 and should be used where a DMT
 * standard exists (refer Appendix B of EDID 1.4 spec and the DMT document).
 */
static _kernel_oserror *generate_dmt_mode(char dmt, MonitorDescriptionRef monitor, uint8_t priority)
{
    /* If there is no established timing, ignore (for now).
     * Later we will generate a mode here and add
     */
    if (dmt > 0)
    {
        dmt--; /* Convert DMT back to array number */
        IFDEBUG printf("Established Modes Display Monitor Timing :\n");

        return add_builtin_mode(monitor, &display_monitor_timings[dmt], priority);
    }
#if DODEBUG
    else
    {
        printf("Ignoring established timing(not a standard mode).\n\n");
    }
#endif

    return 0;
}

static _kernel_oserror *Check_EDID_Checksum(EDIDBlockRef edidblock, bool checkall)
{
    _kernel_oserror *res = NULL;
    size_t byte, block, blocks_to_check = 0;

    for (block = 0; block <= blocks_to_check; block++) {
        uint8_t count = 0;
        uint8_t *ediddata = (uint8_t *)&edidblock[block];
        for (byte = 0; byte < sizeof(EDIDBlock); byte++) {
            count = count + ediddata[byte];
        }
        if (count != 0)
        {
            char linebuff1[4];
            char linebuff2[4];
            sprintf(linebuff1, "%u", block);
            sprintf(linebuff2, "%u", blocks_to_check);
            res = error(ERR_BADCHECKSUM, linebuff1, linebuff2, 0);
        }
        else
        {
            if ((block == 0) && checkall)
            {
                blocks_to_check = edidblock->extension_block_count;
            }
            IFDEBUG printf("Checksum: Block %i of %i OK\n", block, blocks_to_check);
        }
    }

    return res;
}

/* Takes a block of text from an 18-byte EDID data block
 * And fills a supplied character buffer with a usable 0-terminated string
 * Buffer must be at least 14 bytes in length.
 */
static void get_dtd_ascii(EDIDBlockRef edidblockref, int block_number, char *data)
{
    int i;
    for (i = 0; i < 13; ++i)
    {
        data[i] = edidblockref->data_block[block_number][i+5];
        if (data[i] == 0x0A)
        {
            break;
        }
        else if ((data[i] < 32) || (data[i] == 127))
        {
            /* Strip out any control characters for safety */
            data[i] = ' ';
        }
    }
    data[i] = 0;
}

static void generate_mode_using_gtf(double h_pixels, double v_lines, double ip_freq_rqd, ModeDescriptionRef mode_desc, MonitorDescriptionRef monitor)
{
    const int margins_rqd = 0; /* Set to 1 if margins are wanted. */
    const double margin_per = 1.8; /* Percentage size of margin (0 to 100) if reqd */
    double v_lines_rnd = 0; /* Number of desired visible lines rounded down to */
                            /* The nearest character cell */
    double v_sync_rqd = 3; /* The width of the v sync in lines */
    double h_sync_percent = 8; /* The width of the H sync as percentage of the total line period */
    double cell_gran_rnd = 8; /* Character cell width in pixels. May be able to confirm this value in hardware but at present hardcode this to 8 (usual value), */
    const int int_rqd = 0; /* int_rqd specifies whether the mode should be interlaced. Most modes used are not. */
    double v_field_rate_reqd = 0; /* The actual vertical field rate after interlacing is taking into consideration */
    double interlace = 0; /* If interlacing is used, this will be set to 0.5 */

    const double min_vsync_and_bp = 550; /* The minimum time of vertical sync and back porch interval(us) */
    const double min_porch = 1; /* Minimum front porch in lines (vertical) and character cells (horizontal) */
    const double c = 40; /* The blanking formula offset */
    const double m = 600; /* The blanking formula gradient */
    const double k = 128; /* The blanking formula scaling factor */
    const double j = 20; /* The blanking formula scaling factor weighting */

    /* coefficient calculations. These should be constants in standard GTF */
    double C = ((c-j) * k / 256) + j;
    double M = k / 256 * m;

    /* Find the refresh rate required (Hz) */

    /* If interlaced, the number of vertical lines assumed by the
     * calculation must be halved, as the computation calculates the
     * number of vertical lines per field. In either case, the number
     * of lines is rounded down to the nearest integer.
     */

    if (int_rqd == 1)
    {
        v_lines_rnd = round(v_lines / 2);
        v_field_rate_reqd = ip_freq_rqd * 2;
        interlace = 0.5;
    }
    else
    {
        v_lines_rnd = round(v_lines);
        v_field_rate_reqd = ip_freq_rqd;
        /* Interlace is automatically set to 0 */
    }

    /* Round the number of pixels to the nearest character */
    /* cell boundary */
    double h_pixels_rnd = (round(h_pixels/cell_gran_rnd)) * cell_gran_rnd;

    /* Determine the width of the left and right borders */
    double left_margin = 0; /* Number of pixels in the left hand margin, rounded */
                          /* down to the nearest character cell. If no margins */
                          /* required this is set to 0. */
    double right_margin = 0; /* see left_margin */
    double top_margin = 0; /* No of lines rounded down to nearest line. */
    double bot_margin = 0; /* See top, left, right */

    if (margins_rqd == 1)
    {
        left_margin = round(h_pixels_rnd * margin_per / 100 / cell_gran_rnd) *cell_gran_rnd;
        right_margin = left_margin;
        top_margin = round(margin_per / 100 * v_lines_rnd);
        bot_margin = top_margin;
    }

    /* For the calculations below, refer to the VESA GTF v1.1 spec */

    /* Estimate the horizontal period */
    double h_period_est = ((1 / v_field_rate_reqd) - min_vsync_and_bp/1000000)
             / (v_lines_rnd + (2 * top_margin) + min_porch + interlace)
             * 1000000;

    /* Find the number of lines in V sync + B Porch */
    double vsync_and_bp = round(min_vsync_and_bp / h_period_est);

    /* Find number of lines in back porch alone */
    double v_back_porch = vsync_and_bp - v_sync_rqd;

    /* Find the total number of lines in the vertical field period */
    double total_v_lines = v_lines_rnd + top_margin + bot_margin + vsync_and_bp + interlace + min_porch;

    /* Estimate the vertical field frequency */
    double v_field_rate_est = 1/h_period_est/total_v_lines * 1000000;

    /* Find the actual horizontal period */
    double h_period = h_period_est / (v_field_rate_reqd / v_field_rate_est);

    /* Find the actual vertical field frequency */
    double v_field_rate = 1/h_period/total_v_lines * 1000000;

    /* Find the vertical frame frequency */
    if (int_rqd == 1)
    {
        v_field_rate = v_field_rate / 2;
    }

    /* GTF calculates margins using a percentage here. We use actual pixels */
    /* (and 0 always at present) */

    double total_active_pixels = h_pixels_rnd + left_margin + right_margin;

    /* Find the ideal blanking duty cycle from the blanking duty cycle equation */
    double ideal_duty_cycle = C - (M * h_period / 1000);
    if (ideal_duty_cycle < 0)
    {
        IFDEBUG printf("Error - negative duty cycle\n");
        /* If this happens we should just ignore the mode */
        free(mode_desc);
        return;
    }

    /* Find the number of pixels in the blanking time to the nearest double */
    /* character cell */
    double h_blank_pixels = (round(total_active_pixels * ideal_duty_cycle / (100 - ideal_duty_cycle) / (2 * cell_gran_rnd))) * (2 * cell_gran_rnd);

    /* Find the total number of pixels */
    double total_pixels = total_active_pixels + h_blank_pixels;

    /* Find pixel clock frequency */
    double pixel_freq = total_pixels / h_period;

    /* Find horizontal frequency */
    /* double h_freq = 1000 / h_period; */

    /* From GTF spec - Using Stage 1 parameters to derive stage 2 parameters */

    /* Find addressable lines per frame */
    double addr_lines_per_frame;
    if (int_rqd == 1)
    {
        addr_lines_per_frame = v_lines_rnd * 2;
    }
    else
    {
        addr_lines_per_frame = v_lines_rnd;
    }

    /* Find the total number of lines in a frame: */
    double total_lines_per_frame = v_lines_rnd + top_margin + bot_margin + vsync_and_bp + interlace + min_porch;
    if (int_rqd == 1)
    {
        total_lines_per_frame = total_lines_per_frame * 2;
    }

    /* Find the number of pixels in the horizontal sync period */
    double h_sync_pixels = (round(h_sync_percent/100 * total_pixels
      / cell_gran_rnd)) * cell_gran_rnd;

    /* Find the number of pixels in the horizontal front porch period */
    double h_front_porch = (h_blank_pixels / 2) - h_sync_pixels;

    double h_back_porch = h_front_porch + h_sync_pixels;

    /* Find the odd front porch period (lines) (36) */
    double v_fporch = min_porch + interlace;

    mode_desc->definition.xres = (int)h_pixels;
    mode_desc->definition.yres = (int)v_lines;
    mode_desc->definition.hpar[FR_SYNC] = (int) h_sync_pixels;
    mode_desc->definition.hpar[FR_BPCH] = (int) h_back_porch;
    mode_desc->definition.hpar[FR_BDR1] = (int) left_margin;
    mode_desc->definition.hpar[FR_DISP] = (int) h_pixels;
    mode_desc->definition.hpar[FR_BDR2] = (int) right_margin;
    mode_desc->definition.hpar[FR_FPCH] = (int) h_front_porch;
    mode_desc->definition.vpar[FR_SYNC] = (int) v_sync_rqd;
    mode_desc->definition.vpar[FR_BPCH] = (int) v_back_porch;
    mode_desc->definition.vpar[FR_BDR1] = (int) top_margin;

    if (int_rqd == 1)
    {
        mode_desc->definition.vpar[FR_DISP] = (int) v_lines / 2;
    }
    else
    {
        mode_desc->definition.vpar[FR_DISP] = (int) v_lines;
    }

    mode_desc->definition.vpar[FR_BDR2] = (int) bot_margin;
    mode_desc->definition.vpar[FR_FPCH] = (int) v_fporch;
    mode_desc->definition.pixel_khz = (int) round(pixel_freq * 1000);
    mode_desc->definition.external_clock = -1;
    if (timing_support == EDID_USE_GTF)
    {
        mode_desc->definition.syncpol = HSync_Negative+VSync_Positive; /* Default GTF */
    }
    else
    {
        mode_desc->definition.syncpol = HSync_Positive+VSync_Negative; /* Secondary GTF */
    }
    mode_desc->definition.interlaced = int_rqd;

    sprintf(mode_desc->definition.name, "%d x %d", (int)h_pixels, (int)v_lines);
    compute_modedescription(mode_desc);

    if (add_proposed_mode(monitor, mode_desc) == 0)
    {
        free(mode_desc);
    }
#if DODEBUG
    display_mode_parameters(mode_desc);
#endif
}

static void generate_mode_using_cvt_rb(double h_pixels, double v_lines, double ip_freq_rqd, ModeDescriptionRef mode_desc, MonitorDescriptionRef monitor)
{
    const int int_rqd = 0; /* int_rqd specifies whether the mode should be interlaced. Most modes used are not. */
    const int margins_rqd = 0; /* Set to 1 if margins are wanted. */
    const double margin_per = 1.8; /* Percentage size of margin(0 to 100) if reqd */
    double cell_gran_rnd = 8; /* Character cell width in pixels. May be able to confirm this value in hardware but at present hardcode this to 8 (usual value), */
    double v_field_rate_reqd = 0; /* The actual vertical field rate after interlacing is taking into consideration */
    double v_lines_rnd = 0; /* Number of desired visible lines rounded down to the nearest character cell */
    double interlace = 0; /* If interlacing is used, this will be set to 0.5 */
    int v_sync_rnd = 0;

    /* Determine the aspect ratio and set the v_sync_rnd variable
     * This is a slightly messy lookup table - VESA recommends the lookup
     * approach. We match on X, but in some cases we need to also check Y if
     * there are other modes with different heights.
     */
    if ((h_pixels == 640)  || (h_pixels == 800)  ||
        (h_pixels == 1024) || (h_pixels == 1400) ||
        (h_pixels == 1600) || (h_pixels == 1920) ||
        (h_pixels == 2048) || (h_pixels == 2560) ||
        (h_pixels == 3200) || (h_pixels == 3840))
    {
        v_sync_rnd = 4;
    }

    if ((h_pixels == 1280) && ((v_lines == 1024) || (v_lines == 768)))
    {
        v_sync_rnd = 7;
    }

    if ((h_pixels == 848) ||
        (h_pixels == 1064) ||
        ((h_pixels == 1280) && (v_lines == 720)) ||
        (h_pixels == 1360) ||
        (h_pixels == 1704) ||
        (h_pixels == 1864) ||
        ((h_pixels == 1920) && (v_lines == 1080)) ||
        (h_pixels == 2128) ||
        (h_pixels == 2560) ||
        (h_pixels == 2728) ||
        (h_pixels == 3408) ||
        (h_pixels == 4264))
    {
        v_sync_rnd = 5;
    }

    if ((int) (h_pixels / v_lines * 10) == 16)
    {
        v_sync_rnd = 6;
    }

    if (v_sync_rnd == 0)
    {
        IFDEBUG printf("Error - Cannot handle this aspect ratio\n");
        /* If this happens we should just ignore the mode */
        free(mode_desc);
        return;
    }

    /* The variable names used below are those in the VESA Coordinated Timings
     * Standard. I've kept this code as close to the original as possible for
     * clarity at the cost of some speed. When this works 110%, this can be
     * optimised out.
     */

    /* Find the refresh rate required (Hz) */

    /* If interlaced, the number of vertical lines assumed by the
     * calculation must be halved, as the computation calculates the
     * number of vertical lines per field. In either case, the number
     * of lines is rounded down to the nearest integer.
     */
    if (int_rqd == 1)
    {
        v_lines_rnd = floor(v_lines / 2);
        v_field_rate_reqd = ip_freq_rqd * 2;
        interlace = 0.5;
    }
    else
    {
        v_lines_rnd = floor(v_lines);
        v_field_rate_reqd = ip_freq_rqd;
        /* Interlace is automatically set to 0 */
    }

    /* Round the number of pixels to the nearest character cell boundary */
    double h_pixels_rnd = (floor(h_pixels/cell_gran_rnd)) * cell_gran_rnd;

    /* Determine the width of the left and right borders */
    double left_margin = 0; /* Number of pixels in the left hand margin, rounded down to the nearest character cell. If no margins required this is set to 0. */
    double right_margin = 0; /* see left_margin */
    double top_margin = 0; /* No of lines rounded down to nearest line. */
    double bot_margin = 0; /* See top, left, right */

    if (margins_rqd == 1)
    {
        left_margin = floor(h_pixels_rnd * margin_per / 100 / cell_gran_rnd) *cell_gran_rnd;
        right_margin = left_margin;
        top_margin = floor(margin_per / 100 * v_lines_rnd);
        bot_margin = top_margin;
    }

    /* Minimum vertical back porch (used in both sets of calcs). */
    const double min_v_bporch = 6;

    /* The total number of active pixels is equal to the rounded
     * horizontal pixels and the margins:
     */
    double total_active_pixels = h_pixels_rnd + left_margin + right_margin;

    /* Pixel clock resolution - see CVT spec S3.2 */
    const double clock_step = 0.25;

    /* Define back porch (used in our final block) */
    int v_back_porch;

    /* Define actual pixel frequency (for final block) */
    double act_pixel_freq;

    /* H blanking period (for final block) */
    int h_blank;

    /* H sync (for final block) */
    int h_sync;

    /* V front porch (NB in CVT this is min_v_porch_rnd and in CVT-RB this is
     * rb_v_fporch but as they are both constants declare them.
     */
    double v_front_porch = 3;

    if (timing_support == EDID_USE_CVT)
    {
        /* Computation of "CRT" (ie non-RB) CVT timings */
        /* Minimum timing for vertical blanking for 'CRT' timings */
        const double min_vsync_bp = 550;
        /* Standard 'CRT' timing vertical front porch */

        /* Estimate the horizontal period (kHz) */
        double h_period_est = ((1 / v_field_rate_reqd) - min_vsync_bp / 1000000) / (v_lines_rnd + (2 * top_margin) + v_front_porch + interlace) * 1000000;
        /* Find the number of lines in V sync + back porch */
        double v_sync_bp = floor(min_vsync_bp / h_period_est) + 1;
        if (v_sync_bp < v_sync_rnd + min_v_bporch)
        {
            v_sync_bp = v_sync_rnd + min_v_bporch;
        }

        /* Find the number of lines in V back porch */
        v_back_porch = (int) (v_sync_bp - v_sync_rnd);

        /* Find total number of lines in vertical field period */
        double total_v_lines = v_lines_rnd + top_margin + bot_margin + v_sync_bp + interlace + v_front_porch;

        /* C_PRIME = ((C-J) * K / 256 + J (from CVT spec) */
        const double c_prime = 30;

        /* M_PRIME = K / 256 * M (from CVT spec) */
        const double m_prime = 300;

        /* Find the ideal blanking duty cycle from the blanking duty cycle equation (%): */
        double ideal_duty_cycle = c_prime - (m_prime * h_period_est / 1000);

        /* Find the number of pixels in the horizontal blanking time to the nearest double character cell
         * (limit horizontal blanking so that it is >= 20% of the horizontal total).
         */
        if (ideal_duty_cycle < 20)
        {
            h_blank = (int) (floor(total_active_pixels * 20 / (100-20) / (2 * cell_gran_rnd)) * (2 * cell_gran_rnd));
        }
        else
        {
            h_blank = (int) (floor(total_active_pixels * ideal_duty_cycle / (100 - ideal_duty_cycle) / (2 * cell_gran_rnd)) * (2 * cell_gran_rnd));
        }

        /* Find the total number of pixels in a line */
        double total_pixels = total_active_pixels + h_blank;

        /* Find pixel clock frequency (MHz): */
        act_pixel_freq = clock_step * floor((total_pixels / h_period_est) / clock_step);

        /* Find actual horizontal frequency (kHz) */
        double act_h_freq = 1000 * act_pixel_freq / total_pixels;

        /* Find actual field rate (Hz) */
        double act_field_rate = 1000 * act_h_freq / total_v_lines;

        /* Find actual refresh rate (Hz) */
        double act_frame_rate = act_field_rate;
        if (int_rqd == 1)
        {
            act_frame_rate = act_field_rate / 2;
        }

        /* H sync per is the percentage of horizontal total period that
         * defines horizontal sync width
         */
        const double h_sync_per = 8;

        /* Calculate H sync */
        h_sync = (int) (floor(h_sync_per / 100 * total_pixels / cell_gran_rnd) * cell_gran_rnd);
    }
    else
    {
        /* Computation of Reduced Blanking timing parameters. */

        /* Estimate the horizontal period (kHz) */
        const double rb_min_v_blank = 460; /* Min V blank for reduced timings */
        double h_period_est = ((1000000/v_field_rate_reqd) - rb_min_v_blank) / (v_lines_rnd + top_margin + bot_margin);

        double vbi_lines = floor(rb_min_v_blank / h_period_est) + 1;

        /* Vertical front porch is fixed in reduced blanking */
        v_front_porch = 3;

        double rb_min_vbi = v_front_porch + v_sync_rnd + min_v_bporch;

        double act_vbi_lines; /* Actual number of vertical blanking lines */
        if (vbi_lines < rb_min_vbi)
        {
            act_vbi_lines = rb_min_vbi;
        }
        else
        {
            act_vbi_lines = vbi_lines;
        }

        double total_v_lines = act_vbi_lines + v_lines_rnd + top_margin + bot_margin + interlace;

        h_sync = 32;   /* H sync is fixed in reduced blanking */
        h_blank = 160; /* H Blank is fixed in reduced blanking */

        /* Find total number of pixel clocks per line */
        double total_pixels = h_blank + total_active_pixels;

        const double clock_step = 0.25;

        /* Calculate the pixel clock frequency to nearest 0.25MHz */
        act_pixel_freq = clock_step * floor((v_field_rate_reqd * total_v_lines * total_pixels / 1000000) / clock_step);

        /* Find the number of lines in V_sync + back porch: */
        v_back_porch = (int) (act_vbi_lines - v_front_porch - v_sync_rnd);
    }

    /* Calculate front and back porch */
    int h_back_porch = h_blank / 2;
    int h_front_porch = h_blank - h_back_porch - h_sync;

    /* Now populate the mode definition block */
    mode_desc->definition.xres = (int)h_pixels;
    mode_desc->definition.yres = (int)v_lines;
    mode_desc->definition.hpar[FR_SYNC] = h_sync;
    mode_desc->definition.hpar[FR_BPCH] = h_back_porch;
    mode_desc->definition.hpar[FR_BDR1] = (int) left_margin;
    mode_desc->definition.hpar[FR_DISP] = (int) h_pixels;
    mode_desc->definition.hpar[FR_BDR2] = (int) right_margin;
    mode_desc->definition.hpar[FR_FPCH] = h_front_porch;
    mode_desc->definition.vpar[FR_SYNC] = (int) v_sync_rnd;
    mode_desc->definition.vpar[FR_BPCH] = v_back_porch;
    mode_desc->definition.vpar[FR_BDR1] = (int) top_margin;
    if (int_rqd == 1)
    {
        mode_desc->definition.vpar[FR_DISP] = (int) v_lines / 2;
    }
    else
    {
        mode_desc->definition.vpar[FR_DISP] = (int) v_lines;
    }
    mode_desc->definition.vpar[FR_BDR2] = (int) bot_margin;
    mode_desc->definition.vpar[FR_FPCH] = (int) v_front_porch;
    mode_desc->definition.pixel_khz = (int) (act_pixel_freq * 1000);
    mode_desc->definition.external_clock = -1;
    if (timing_support == EDID_USE_CVT)
    {
        mode_desc->definition.syncpol = HSync_Negative+VSync_Positive;
    }
    else
    {
        /* EDID_USE_CVTRB */
        mode_desc->definition.syncpol = HSync_Positive+VSync_Negative;
    }
    mode_desc->definition.interlaced = int_rqd;

    sprintf(mode_desc->definition.name, "%d x %d", (int)h_pixels, (int)v_lines);
    compute_modedescription(mode_desc);

    if (add_proposed_mode(monitor, mode_desc) == 0)
    {
        free(mode_desc);
    }
#if DODEBUG
    display_mode_parameters(mode_desc);
#endif
}

static _kernel_oserror *generate_standard_timing(char std1, char std2, MonitorDescriptionRef monitor)
{
    ModeDescriptionRef mode_desc = NULL;
    /* If this mode is defined as a DMT standard, use this by checking the
     * bytes directly with the lookup table
     */
    int i = 0;

    IFDEBUG printf("Standard Timing(%x %x) ", std1, std2);

    while (std_timings[i].stdcode[0] || std_timings[i].stdcode[1])
    {
        if ((std1 == std_timings[i].stdcode[0]) &&
            (std2 == std_timings[i].stdcode[1]))
        {
            mode_desc = (ModeDescriptionRef) malloc(sizeof(ModeDescription));
            if (mode_desc == NULL)
            {
                return error(ERR_NOSPACE, 0, 0, 0);
            }
            mode_desc->definition = display_monitor_timings[std_timings[i].dmt-1];
            compute_modedescription(mode_desc);
            mode_desc->frame_hz = display_monitor_timings[std_timings[i].dmt-1].freq;
            mode_desc->priority = 5;

#if DODEBUG
            printf("(derived from DMT standard):\n");
            display_mode_parameters(mode_desc);
#endif

            if (add_proposed_mode(monitor, mode_desc) == 0)
            {
                free(mode_desc);
            }
            return NULL;
        }
        i++;
    }

    /* If it's not in the DMT lookups and we can generate a timing, do so */
    if (timing_support != EDID_USE_DMT)
    {
        int yres = 0;
        int xres = (std1 + 31) * 8;
        char pixel_ratio_flags = (std2 & 0xC0);
        if (pixel_ratio_flags == 0)
          { /* 00 = 16:10 */
            yres = (int) ((xres * 10) / 16);
        }
        if (pixel_ratio_flags == (1<<6))
        {   /* 01 = 4:3 */
            yres = (int) ((xres * 3) / 4);
        }
        if (pixel_ratio_flags == (1<<7))
        {   /* 10 = 5:4 */
            yres = (int) ((xres * 4) / 5);
        }
        if (pixel_ratio_flags == ((1<<7) + (1<<6)))
        {   /* 11 = 16:9 */
            yres = (int) ((xres * 9) / 16);
        }

        int freq = (std2 & 0x3f) + 60;

        mode_desc = (ModeDescriptionRef) malloc(sizeof(ModeDescription));
        if (mode_desc == NULL)
        {
            return error(ERR_NOSPACE, 0, 0, 0);
        }
        IFDEBUG printf("(calculated):\n");

        if ((timing_support == EDID_USE_GTF) ||
            (timing_support == EDID_USE_GTF2))
        {
            generate_mode_using_gtf(xres, yres, freq, mode_desc, monitor);
        }
        if ((timing_support == EDID_USE_CVT) ||
            (timing_support == EDID_USE_CVTRB))
        {
            generate_mode_using_cvt_rb(xres, yres, freq, mode_desc, monitor);
        }

    }
    return NULL;
}

/* Generates a mode from a 3-byte CVT code
 * NB this is UNTESTED as I haven't got an appropriate EDID to test from
 */
static _kernel_oserror *generate_cvt3_timing(char cvt1, char cvt2, char cvt3, MonitorDescriptionRef monitor)
{
    ModeDescriptionRef mode_desc = NULL;
    /* If this mode is defined as a DMT standard, use this by checking the
     * bytes directly with the lookup table
     */
    IFDEBUG printf("CVT 3-byte Timing(%x %x %x) ", cvt1, cvt2, cvt3);

    /* Check the reserved bits are 00. If not, let this mode fail silently */
    if ((cvt2 & 0x03) != 0)
    {
        IFDEBUG printf("has unknown values in the reserved bits of byte 2 - skipped.\n");
        return NULL;
    }

#ifdef DERIVE_CVT3_FROM_DMT
    int i = 0;
    while (cvt_timings[i].cvtcode[0] || cvt_timings[i].cvtcode[1] || cvt_timings[i].cvtcode[2])
    {
        if ((cvt1 == cvt_timings[i].cvtcode[0]) &&
            (cvt2 == cvt_timings[i].cvtcode[1]) &&
            (cvt3 == cvt_timings[i].cvtcode[2]))
        {
            mode_desc = (ModeDescriptionRef) malloc(sizeof(ModeDescription));
            if (mode_desc == NULL)
            {
                return error(ERR_NOSPACE, 0, 0, 0);
            }
            mode_desc->definition = display_monitor_timings[cvt_timings[i].dmt-1];
            compute_modedescription(mode_desc);
            mode_desc->frame_hz = display_monitor_timings[cvt_timings[i].dmt-1].freq;
            mode_desc->priority = 4; /* CVT timing */

#if DODEBUG
            printf("(derived from DMT standard):\n");
            display_mode_parameters(mode_desc);
#endif
            if (add_proposed_mode(monitor, mode_desc) == 0)
            {
                free(mode_desc);
            }
            return NULL;
        }
        i++;
    }
#endif

    /* If it's not in the DMT lookups and we can generate a timing, do so */
    if ((mode_desc == NULL) && (timing_support != EDID_USE_DMT))
    {
        int vsize = cvt1 + ((cvt2 & 0xf0) << 4);
        int yres = (vsize + 1) * 2;
        int xres = 0;
        int freq = 0;

        /* Use the vertical line count to generate the horizontal addressable
         * Resolution (HAdd). Use 8 x {RoundDown[(VAdd x Aspect ratio) / 8]}
         * Per the EDID guidance (p48).
         */
        char pixel_ratio_flags = (cvt2 & 0x0C) >> 2;
        switch (pixel_ratio_flags)
        {
            case 0:
                xres = (int) (8 * floor((((double) yres * 4) / 3) / 8));
                break;
            case 1:
                xres = (int) (8 * floor((((double) yres * 16) / 9) / 8));
                break;
            case 2:
                xres = (int) (8 * floor((((double) yres * 16) / 10) / 8));
                break;
            case 3:
                xres = (int) (8 * floor((((double) yres * 15) / 9) / 8));
                break;
        }

        /* First generate a descriptor using the display-preferred frequency
         * (bits 5 and 6 of cvt3)
         */
        switch ((cvt3 & 0x60) >> 5)
        {
            case 0:
                freq = 50;
                break;
            case 1:
                freq = 60;
                break;
            case 2:
                freq = 75;
                break;
            case 3:
                freq = 85;
                break;
        }
        if (freq != 0)
        {
            mode_desc = (ModeDescriptionRef) malloc(sizeof(ModeDescription));
            if (mode_desc == NULL)
            {
                return error(ERR_NOSPACE, 0, 0, 0);
            }
            IFDEBUG printf("\nAt %x Hz(calculated):\n", freq);
            generate_mode_using_cvt_rb(xres, yres, freq, mode_desc, monitor);
            if (add_proposed_mode(monitor, mode_desc) == 0)
            {
                free(mode_desc);
            }
        }
        int preferred_freq = freq;

        /* Now we add any other frequencies it supports */

        /* Use timing_support to switch the timing method we use in the
         * CVT calculations and restore it at the end.
         */
        int timing_support_preferred = timing_support;

        for (int supported_freq=0; supported_freq<5; supported_freq++)
        {
            if ((cvt3 & (1<<supported_freq)) == (1<<supported_freq))
            {
                switch (supported_freq)
                {
                    case 0:
                        freq = 60;
                        timing_support = EDID_USE_CVTRB;
                    case 1:
                        freq = 85;
                        timing_support = EDID_USE_CVT;
                    case 2:
                        freq = 75;
                        timing_support = EDID_USE_CVT;
                    case 3:
                        freq = 60;
                        timing_support = EDID_USE_CVT;
                    case 4:
                        freq = 50;
                        timing_support = EDID_USE_CVT;
                }

                if (freq != preferred_freq)
                {
                    mode_desc = (ModeDescriptionRef) malloc(sizeof(ModeDescription));
                    if (mode_desc == NULL)
                    {
                        return error(ERR_NOSPACE, 0, 0, 0);
                    }
                    IFDEBUG printf("\nAt %x Hz(calculated):\n", freq);
                    generate_mode_using_cvt_rb(xres, yres, freq, mode_desc, monitor);
                    if (add_proposed_mode(monitor, mode_desc) == 0)
                    {
                        free(mode_desc);
                    }
                }
            }
        }
        timing_support = timing_support_preferred;
    }
    return NULL;
}

/* Converts a detailed timing descriptor block into a mode descriptor
 * block. The defines above help it pick out the data which is split
 * across nibbles into the parameters.
 * dtd_data on entry should be a pointer to the first byte of the 18 byte
 * dtd block.
 */
static bool dtd_block_to_modedesc(char* dtd_data, ModeDescriptionRef mode_desc)
{
    IFDEBUG printf("Detailed timing descriptor:\n");
    mode_desc->definition.xres          = dtd_data[2] + ((dtd_data[4] & 0xf0) << 4);
    mode_desc->definition.yres          = dtd_data[5] + ((dtd_data[7] & 0xf0) << 4);
    mode_desc->definition.interlaced    = ((dtd_data[17]>>7) & 1);
    mode_desc->definition.hpar[FR_SYNC] = dtd_data[9] + ((dtd_data[11] & 0x30) << 4);
    mode_desc->definition.hpar[FR_BDR1] = dtd_data[15];
    mode_desc->definition.hpar[FR_DISP] = mode_desc->definition.xres;
    mode_desc->definition.hpar[FR_BDR2] = dtd_data[15];
    mode_desc->definition.hpar[FR_FPCH] = dtd_data[8] + ((dtd_data[11] & 0xc0) << 2);
    mode_desc->definition.hpar[FR_BPCH] = dtd_data[3] + ((dtd_data[4] & 0x0f) << 8) -
                                          mode_desc->definition.hpar[FR_SYNC] -
                                          mode_desc->definition.hpar[FR_FPCH] -
                                          mode_desc->definition.hpar[FR_BDR1] -
                                          mode_desc->definition.hpar[FR_BDR2];
    mode_desc->definition.vpar[FR_SYNC] = (dtd_data[10] & 0xf) + ((dtd_data[11] & 0x3) << 4);
    mode_desc->definition.vpar[FR_BDR1] = dtd_data[16];
    mode_desc->definition.vpar[FR_DISP] = mode_desc->definition.yres;
    mode_desc->definition.vpar[FR_BDR2] = dtd_data[16];
    mode_desc->definition.vpar[FR_FPCH] = (dtd_data[10] >> 4) + ((dtd_data[11] & 0xc) << 2);
    mode_desc->definition.vpar[FR_BPCH] = dtd_data[6] + ((dtd_data[7] & 0x0f) << 8) -
                                          mode_desc->definition.vpar[FR_SYNC] -
                                          mode_desc->definition.vpar[FR_FPCH] -
                                          mode_desc->definition.vpar[FR_BDR1] -
                                          mode_desc->definition.vpar[FR_BDR2];
    mode_desc->definition.pixel_khz     = (dtd_data[0] + (dtd_data[1]<<8)) * 10;
    mode_desc->definition.external_clock = -1;

    /* Only accept non-stereoscopic modes
     * Technically we should check for sync here (since we only really support
     * digital separate syncs), but for now we'll assume that misrepresenting
     * the sync isn't going to cause major problems, as it will allow more
     * modes to work (e.g. BenQ FP241W HDMI port advertises 1080p as being
     * serrated sync-on-RGB, even though the monitor is perfectly happy with
     * digital syncs)
     */
    if ((dtd_data[17] & 0x60) != 0x00)
    {
        IFDEBUG printf("Rejecting DTD due to unsupported frame format\n");
        return false;
    }

    /* -----00- is negative-negative, start with that and then invert if necessary */
    mode_desc->definition.syncpol = HSync_Negative+VSync_Negative;
    if (dtd_data[17] & 4)
    {
        mode_desc->definition.syncpol ^= VSync_Negative^VSync_Positive;
    }
    if (dtd_data[17] & 2)
    {
        mode_desc->definition.syncpol ^= HSync_Negative^HSync_Positive;
    }

    /* If we are interlaced, we need to double the number of vertical pixels */
    if (mode_desc->definition.interlaced == 1)
    {
        mode_desc->definition.yres = mode_desc->definition.yres * 2;
    }

    sprintf(mode_desc->definition.name, "%d x %d", mode_desc->definition.xres, mode_desc->definition.yres);
    compute_modedescription(mode_desc);
#if DODEBUG
    display_mode_parameters(mode_desc);
#endif
    return true;
}

/* Checks the 18-byte data blocks for their type.
 * The type returned is then either 10 (empty), 0 (detailed mode descriptor)
 * or other type numbers per the EDID specification
 */
static int get_extd_type(EDIDBlockRef edidblockref, int block_no)
{
    if (edidblockref->data_block[block_no][0] +
        edidblockref->data_block[block_no][1] +
        edidblockref->data_block[block_no][2] == 0)
    {
        return edidblockref->data_block[block_no][3];
    }
    return -1; /* -1 = display descriptor */
}

/* Add to our list of audio formats */
static _kernel_oserror *add_audio_format(uint8_t byte1, uint8_t byte2, uint8_t byte3, MonitorDescriptionRef new_monitor)
{
    AudioFormat newformat;
    newformat.format_code = (byte1 >> 3) & 0x7f;
    newformat.max_channels = (byte1 & 7) + 1;
    newformat.sample_rates = byte2;
    newformat.format_specific = byte3;
    /* Discard if bogus */
    if ((!newformat.sample_rates) || ((newformat.format_code == 1) && !(newformat.format_specific)))
    {
        return NULL;
    }
    /* Work out where to merge it into our list
     * CEA 861-D doesn't state what we should do if multiple descriptors are
     * found for the same format code, so we should be conservative with how
     * we merge blocks together
     */
    int i;
    for (i=0;i<new_monitor->audio_format_count;i++)
    {
        AudioFormat *candidate = &new_monitor->audio_formats[i];
        if (candidate->format_code > newformat.format_code)
        {
            break;
        }
        else if (candidate->format_code < newformat.format_code)
        {
            continue;
        }
        /* Try and merge with this entry
         * note - currently not doing any fancy merging of the format-specific
         * byte (could be a bit tricky for LPCM depending on what future meaning
         * is given to the reserved bits)
         */
        if (newformat.format_specific == candidate->format_specific)
        {
            if (newformat.max_channels == candidate->max_channels)
            {
                /* Merge our list of sample rates into the candidate and call
                 * it a day.
                 */
                candidate->sample_rates |= newformat.sample_rates;
                return NULL;
            }
            else if (newformat.max_channels < candidate->max_channels)
            {
                /* Candidate supports more channels than us with same
                 * format-specific settings, so ignore any sample rates which
                 * the candidate supports.
                 */
                newformat.sample_rates &= ~candidate->sample_rates;
                if (!newformat.sample_rates)
                {
                    return NULL;
                }
            }
            else if (newformat.max_channels > candidate->max_channels)
            {
                /* We support more channels than the candidate, so remove
                 * sample rates from the candidate.
                 */
                candidate->sample_rates &= ~newformat.sample_rates;
                if (!candidate->sample_rates)
                {
                    /* Candidate can be removed completely */
                    new_monitor->audio_format_count--;
                    memmove(candidate, candidate+1, sizeof(AudioFormat)*(new_monitor->audio_format_count-i));
                    void *new = realloc(new_monitor->audio_formats, new_monitor->audio_format_count*sizeof(AudioFormat));
                    if (new || !new_monitor->audio_format_count)
                    {
                        new_monitor->audio_formats = (AudioFormat*) new;
                    }
                    /* Everything's been shuffled down, so process this entry
                     * again.
                     */
                    i--;
                    continue;
                }
            }
        }
    }
    /* Need to insert a new entry */
    void *new = realloc(new_monitor->audio_formats, (new_monitor->audio_format_count+1)*sizeof(AudioFormat));
    if (!new)
    {
        return error(ERR_NOSPACE, 0, 0, 0);
    }
    new_monitor->audio_formats = (AudioFormat*) new;

    /* Shuffle following entries up */
    memmove(new_monitor->audio_formats+i+1, new_monitor->audio_formats+i, sizeof(AudioFormat)*(new_monitor->audio_format_count-i));

    new_monitor->audio_format_count++;
    new_monitor->audio_formats[i] = newformat;

    return NULL;
}

/* Process an audio data block from a CEA extension block */
static _kernel_oserror *process_cea_audio_data_block(EDIDExtensionBlockRef ext_block, int length, const uint8_t *block, MonitorDescriptionRef new_monitor)
{
    while (length >= 3)
    {
        IFDEBUG printf("CEA Short Audio Descriptor %02x %02x %02x\n",block[0],block[1],block[2]);

        /* Ignore if any reserved bits in the first couple of bytes are set */
        if (!(block[0] & 128) && !(block[1] & 128))
        {
            _kernel_oserror *err = add_audio_format(block[0], block[1], block[2], new_monitor);
            if (err)
            {
                return err;
            }
        }
        block += 3;
        length -= 3;
    }
    return NULL;
}

/* Process a video data block from a CEA extension block */
static _kernel_oserror *process_cea_video_data_block(EDIDExtensionBlockRef ext_block, int length, const uint8_t *block, MonitorDescriptionRef new_monitor)
{
    /* Parse SVDs and add to mode list */
    while (length--)
    {
        uint8_t num = block[0] & 0x7f;

        IFDEBUG printf("CEA SVD %02x -> CEA mode %d%s\n", block[0], num, (block[0] & 128) ? " (native)" : "");
        block++;
        if ((num == 0) || (num > MAXCEAMODENUM))
        {
            continue;
        }
        /* Ignore modes that require pixel repetition (currently we have no way of indicating that requirement to the driver) */
        const ModeDefinition *mode = &cea_modes[num-1];
        if ((mode->xres != mode->hpar[FR_DISP]) || (mode->xres == 2880))
        {
            continue;
        }
        _kernel_oserror *err = add_builtin_mode(new_monitor, mode, 3); /* SVDs should be considered the same priority as DTDs */
        if (err)
        {
            return err;
        }
    }
    return NULL;
}

/* Process a speaker allocation block from a CEA extension block */
static _kernel_oserror *process_cea_speaker_allocation_data_block(EDIDExtensionBlockRef ext_block, int length, const uint8_t *block, MonitorDescriptionRef new_monitor)
{
    if (length == 3)
    {
        IFDEBUG printf("CEA Speaker Allocation %02x %02x %02x\n",block[0],block[1],block[2]);
        new_monitor->speaker_mask = block[0] & 0x7f;
        new_monitor->speaker_mask_provided = true;
    }
    return NULL;
}

/* Process a data block from a CEA extension block */
static _kernel_oserror *process_cea_data_block(EDIDExtensionBlockRef ext_block, int tag_code, int length, const uint8_t *block, MonitorDescriptionRef new_monitor)
{
    if (tag_code == 7)
    {
        /* Extended tag */
        tag_code = *block++;
        length--;
        IFDEBUG printf("CEA extended data block code %d data length %d\n",tag_code,length);

        /* TODO: Process any interesting ones */
        return NULL;
    }
    IFDEBUG printf("CEA data block code %d data length %d\n",tag_code,length);
    switch (tag_code)
    {
        case 1:  return process_cea_audio_data_block(ext_block, length, block, new_monitor);
        case 2:  return process_cea_video_data_block(ext_block, length, block, new_monitor);
        case 4:  return process_cea_speaker_allocation_data_block(ext_block, length, block, new_monitor);
        default: return NULL;
    }
}

/* Process a CEA extension block */
static _kernel_oserror *process_cea_extension(EDIDExtensionBlockRef ext_block, MonitorDescriptionRef new_monitor)
{
    /* Offset from the start of the extension block to the
     * first dtd in the extension block. Uses the offset from block+2.
     */
    const uint8_t *extdata = (uint8_t *) ext_block;
    int dtd_offset = extdata[2];

    if (dtd_offset > (126 - 18))
    {
        /* Bogus DTD offset, ignore block */
        return NULL;
    }

    bool basic_audio = (ext_block->revision >= 2) && (extdata[3] & 0x40);
    if (basic_audio)
    {
        IFDEBUG printf("Basic audio supported\n");
        /* 2-ch LPCM at 32kHz, 44.1kHz, 48kHz
         * Assume 16/20/24bit supported (spec is a bit vague, but in reality it
         * shouldn't matter that much because they all get packaged the same)
         */
        _kernel_oserror *err = add_audio_format(0x9, 0x7, 0x7, new_monitor);
        if (err)
        {
            return err;
        }
    }

    if ((ext_block->revision >= 3) && (dtd_offset > 4))
    {
        /* CEA Data Block Collection present */
        int block_offset = 4;
        while (block_offset < dtd_offset)
        {
            int tag_code = extdata[block_offset] >> 5;
            int length = extdata[block_offset] & 0x1f;
            block_offset++;
            if (!tag_code || !length || (length > (dtd_offset-block_offset)))
            {
                /* Bad block length or invalid tag code. TODO - Should probably throw away everything we've learnt so far from this extension block. */
                return NULL;
            }
            _kernel_oserror *err = process_cea_data_block(ext_block, tag_code, length, extdata+block_offset, new_monitor);
            if (err)
            {
                return err;
            }
            block_offset += length;
        }
    }

    if (dtd_offset < 4)
    {
        /* No DTD's provided in this block */
        return NULL;
    }

    while ((dtd_offset <= (126 - 18)) &&
           (extdata[dtd_offset] != 0) && (extdata[dtd_offset+1] != 0))
    {
        ModeDescriptionRef mp;
        mp = (ModeDescriptionRef) malloc(sizeof(ModeDescription));
        if (mp == NULL)
        {
            return error(ERR_NOSPACE, 0, 0, 0);
        }

        if (!dtd_block_to_modedesc((char *) &(extdata[dtd_offset]), mp))
        {
            free(mp);
        }
        else
        {
            mp->priority = 3;
            if (add_proposed_mode(new_monitor, mp) == 0)
            {
                free(mp);
            }
        }
        dtd_offset += 18; /* 18 is the size of a DTD block */
    }

    return NULL;
}

/* Process a VTB extension block */
static _kernel_oserror *process_vtb_extension_block(EDIDExtensionBlockRef ext_block, MonitorDescriptionRef new_monitor)
{
    _kernel_oserror *res = NULL;
    const uint8_t *extdata = (uint8_t *) ext_block;

    /* 'w', 'y' and 'z' originate from the VTB specification */
    int w = extdata[2]; /* 'w' is the number of detailed timing blocks (DTB) */
    int y = extdata[3]; /* 'y' is the number of CVT descriptions */
    int z = extdata[4]; /* 'z' is the number of standard timing descriptions */

    /* There's only space for 122 bytes of timing data. If the block looks like it needs more than that then it's a bad block. */
    if (w*0x12 + y*0x3 + z*0x2 > 122)
    {
        return NULL;
    }
    /* Unused bytes should be zero */
    for (int byte = 0x5 + w*0x12 + y*0x3 + z*0x2; byte < 127; byte++) {
        if (extdata[byte])
        {
            return NULL;
        }
    }

    for (int dtb_blockno = 0; dtb_blockno < w; dtb_blockno++)
    {
        ModeDescriptionRef mp;
        mp = (ModeDescriptionRef) malloc(sizeof(ModeDescription));
        if (mp == NULL)
        {
            return error(ERR_NOSPACE, 0, 0, 0);
        }

        if (!dtd_block_to_modedesc((char *) &(extdata[0x5 + dtb_blockno*0x12]), mp))
        {
            free(mp);
        }
        else
        {
            mp->priority = 3;
            if (add_proposed_mode(new_monitor, mp) == 0)
            {
                free(mp);
            }
        }
    }

    for (int cvt_blockno = 0; cvt_blockno < y; cvt_blockno++)
    {
        ModeDescriptionRef mp;
        mp = (ModeDescriptionRef) malloc(sizeof(ModeDescription));
        if (mp == NULL)
        {
            return error(ERR_NOSPACE, 0, 0, 0);
        }
        int cvt_byte = 0x5 + w*0x12 + cvt_blockno*0x3;
        if (!((extdata[cvt_byte] == 0) &&
            (extdata[cvt_byte+1] == 0) &&
            (extdata[cvt_byte+2] == 0))) {

            res = generate_cvt3_timing(extdata[cvt_byte], extdata[cvt_byte+1], extdata[cvt_byte+2], new_monitor);
            if (res)
            {
                return res;
            }
        }
    }

    for (int std_blockno = 0; std_blockno < z; std_blockno++)
    {
        ModeDescriptionRef mp;
        mp = (ModeDescriptionRef) malloc(sizeof(ModeDescription));
        if (mp == NULL)
        {
            return error(ERR_NOSPACE, 0, 0, 0);
        }
        int std_byte = 0x5 + w*0x12 + y*0x3 + std_blockno*0x2;
        if (!((extdata[std_byte] == 0x1) &&
            (extdata[std_byte+1] == 0x1))) {

            res = generate_standard_timing(extdata[std_byte], extdata[std_byte+1], new_monitor);
            if (res)
            {
                return res;
            }
        }
    }

    return NULL;
}

static _kernel_oserror *parseedid(char *ediddata, const char *file)
{
    _kernel_oserror *res = NULL;
    FILE *f = NULL;
    MonitorDescriptionRef new_monitor;
    ModeDescriptionRef new_preferred_mode = NULL; /* No preferred mode */
    EDIDBlockRef edidblockref = (EDIDBlockRef) ediddata;
    int RangeBlock = -1;                          /* edid block containing timimg limits */

    /* OK, commit to reading a monitor description - go allocate space */
    new_monitor = (MonitorDescriptionRef) malloc(sizeof(MonitorDescription));
    if (new_monitor == NULL)
    {
        IFDEBUG printf("Can't allocate space for monitor list\n");
        return error(ERR_NOSPACE, 0, 0, 0);
    }
    memset(new_monitor, 0, sizeof(MonitorDescription));

    strcpy(new_monitor->name,"Display");

    new_monitor->dpms_state = -1; /* DPMS -1 = Indicates not available */
    new_monitor->lcd_support = 0; /* 'CRT by default' - what does this do???*/
    new_monitor->output_format = -1; /* Output format */
    new_monitor->external_clock = -1; /* External clock not present */
    new_monitor->modelist = NULL; /* Make sure the mode list is initially nil. */

    /* If we are EDID 1.4 compliant or above GTF and CVT support should be
     * present. Use CVT in preference, fallback to DMT only.
     */
    timing_support = EDID_USE_DMT; /* Default at DMT */
    if ((edidblockref->edid_version >= 1) && (edidblockref->edid_revision >= 4))
    {
        IFDEBUG printf("Use CVT; fallback to DMT if unavailable\n");
        timing_support = EDID_USE_CVT;
    }

    /* If we are EDID 1.4 compliant or above GTF support should be
     * present. Use GTF in preference, fallback to DMT only.
     */
    if ((edidblockref->edid_version == 1) && ((edidblockref->edid_revision == 2) || (edidblockref->edid_revision == 3)))
    {
        IFDEBUG printf("Use GTF; fallback to DMT if unavailable\n");
        timing_support = EDID_USE_GTF;
    }
#if DODEBUG
    if (timing_support == EDID_USE_DMT)
    {
        printf("Use DMT only\n");
    }
    else{
        if ((edidblockref->feature_support & 1) == 1)
        {
            printf("This display supports continuous frequency modes(but we don't use this yet\n");
        }
        else
        {
            printf("This display supports only non-continuous frequency modes - use only the resolutions defined\n");
        }
    }
#endif

#ifdef FORCE_TIMINGS
    /* Now if we have set force_timings as a debug option, override the
     * timing_support setting for testing calculations
     */
    timing_support = FORCE_TIMINGS;
#endif

    /* Next do each of the 2-byte 'standard timings' (Priority 5). */
    /* NB GTF support must be functional for these */
    for (int std_timing_code=0; std_timing_code<8;std_timing_code++)
    {
        if (!((edidblockref->standard_timings[std_timing_code*2] == 0x01) && (edidblockref->standard_timings[std_timing_code*2+1] == 0x01)))
        {
            res = generate_standard_timing(edidblockref->standard_timings[std_timing_code*2], edidblockref->standard_timings[std_timing_code*2+1], new_monitor);
            if (res)
            {
                return res;
            }
        }
    }

    /* Sort the data blocks - store information strings and detailed
     * timing descriptors. Defer CVT codes and Established timings III
     * The first block is the 'preferred timing mode' and has highest
     * priority to go in the MDF. Any other 'detailed timing modes
     * have second priority so they will go in next.
     */
    for (int blockno = 0; blockno < 4; blockno++)
    {
        switch (get_extd_type(edidblockref, blockno))
        {
            case -1: /* Normal descriptor block */
            {
                IFDEBUG printf("Data block\n");
                ModeDescriptionRef mp;
                mp = (ModeDescriptionRef) malloc(sizeof(ModeDescription));
                if (mp == NULL)
                {
                    return error(ERR_NOSPACE, 0, 0, 0);
                }

                if (!dtd_block_to_modedesc((char *) &(edidblockref->data_block[blockno][0]), mp))
                {
                    free(mp);
                }
                else
                {
                    IFDEBUG printf("Detailed Mode: %s, X: %i Y: %i\n", mp->definition.name,mp->definition.xres, mp->definition.yres);
                    if (add_proposed_mode(new_monitor, mp) == 0)
                    {
                        free(mp);
                    }
                    else
                    {
                        /* The first block should define the preferred mode */
                        if (blockno == 0)
                        {
                            new_preferred_mode = mp;
                            mp->priority = 0x01;
                        }
                        else
                        {
                            mp->priority = 0x02;
                        }
                    }
                }

                break;
            }
            case DATA_TYPE_RANGELIMITS:
            {
                IFDEBUG printf("Display range limits\n");
                RangeBlock = blockno; /* remember */

                /* Not 100% sure about this but think this is how we pick up CVT-RB support */
                if (timing_support == EDID_USE_CVT)
                {
                    char cvt_blanking = edidblockref->data_block[blockno][15];
                    if ((cvt_blanking & 0x10) == 0x10)
                    {
                        timing_support = EDID_USE_CVTRB;
                    }
                }
                break;
            }
            case DATA_TYPE_MONITORNAME:
            {
                get_dtd_ascii(edidblockref, blockno, new_monitor->name);
                IFDEBUG printf("Model name: %s\n", new_monitor->name);
                break;
            }
            case DATA_TYPE_ESTTIMINGS3:
            {
                IFDEBUG printf("Established timings III");
                for (int timings_byte=0; timings_byte<6; timings_byte++)
                {
                    for (int timings_bit=0; timings_bit<8; timings_bit++)
                    {
                        if ((edidblockref->data_block[blockno][6+timings_byte] & (1<<(timings_bit%8))) == (1<<(timings_bit%8)))
                        {
                            generate_dmt_mode(established_timings3[(timings_byte * 8) + (7 - timings_bit)],
                                              new_monitor, 6);
                        }
                    }
                }
                break;
            }
            case DATA_TYPE_CVT3BYTE:
            {
                IFDEBUG printf("CVT 3 Byte timing codes, priority 4\n");
                for (int cvt_byte = 6;cvt_byte < 18; cvt_byte+=3)
                {
                    if (!((edidblockref->data_block[blockno][cvt_byte] == 0) &&
                          (edidblockref->data_block[blockno][cvt_byte+1] == 0) &&
                          (edidblockref->data_block[blockno][cvt_byte+2] == 0))) {
                        res = generate_cvt3_timing(
                            edidblockref->data_block[blockno][cvt_byte],
                            edidblockref->data_block[blockno][cvt_byte+1],
                            edidblockref->data_block[blockno][cvt_byte+2],
                            new_monitor);
                        if (res)
                        {
                            return res;
                        }
                    }
                }
                break;
            }
#if DODEBUG
            case DATA_TYPE_MONITORSERIALNUM:
            {
                char serial_number[15];

                get_dtd_ascii(edidblockref, blockno, serial_number);
                printf("Product serial number %s\n", serial_number);
                break;
            }
            case DATA_TYPE_COLOURPOINT:
                printf("Colour point data\n");
                break;
            case DATA_TYPE_STDTIMING:
                printf("Standard timing identifications\n");
                break;
            case DATA_TYPE_DCM:
                printf("Display colour management (DCM) data\n");
                break;
            case DATA_TYPE_DUMMY:
                printf("Empty\n");
                break;
#endif
        }
    }

#if DODEBUG
    printf("Timings bitfield 0 = %02x\n",edidblockref->established_timings[0]);
    printf("Timings bitfield 1 = %02x\n",edidblockref->established_timings[1]);
    printf("Timings bitfield 2 = %02x\n",edidblockref->established_timings[2]);
#endif

    /* Add Established timings I and II  (priority 6) */
    for (int timings_byte=0; timings_byte<2; timings_byte++)
    {
        for (int timings_bit=0; timings_bit<8; timings_bit++)
        {
            if ((edidblockref->established_timings[timings_byte] & (1<<(timings_bit%8))) == (1<<(timings_bit%8)))
            {
                IFDEBUG printf("Timings bitfield byte %d bit %d:\n", timings_byte, timings_bit);
                int dmt_mode = established_timings[(timings_byte * 8)+(7 - timings_bit)];
                if (dmt_mode > 0)
                {
                    generate_dmt_mode(dmt_mode, new_monitor, 6);
                }
                else
                {
                    /* Established timings for 720x400 @ 70Hz, derived from:
                     * http://www.javiervalcarce.eu/html/vga-signal-format-timming-specs-en.html
                     * http://www.epanorama.net/documents/pc/vga_timing.html
                     */
                    static const ModeDefinition established_timing_7 =
                    {
                        /* 720 x 400 @ 70Hz */
                        "720 x 400", 720, 400, 108, 54, 0,  720, 0, 18,
                                               2,   34, 0,  400, 0, 13,
                                               28322, -1, NP, 0, 70, 0
                    };

                    /* There are 3 cases which do not have defined DMT codes.
                     * These are byte 0 bits 7, 6 and 4 which we will calculate.
                     */
                    int xres = 0;
                    int yres = 0;
                    int freq = 0;
                    switch (timings_bit | (timings_byte << 3))
                    {
                      case 7: /* 720 X 400 @ 70Hz */
                        /* Use some fixed timings for better compatibility */
                        add_builtin_mode(new_monitor, &established_timing_7, 6);
                        break;
                      case 6: /* 720 X 400 @ 88Hz */
                        xres = 720;
                        yres = 400;
                        freq = 88;
                        break;
                      case 4: /* 640 X 480 @ 67Hz */
                        xres = 640;
                        yres = 480;
                        freq = 67;
                        break;
                    }

                    if (xres > 0)
                    {
                        ModeDescriptionRef mp;
                        mp = (ModeDescriptionRef) malloc(sizeof(ModeDescription));
                        if (mp == NULL)
                        {
                            return error(ERR_NOSPACE, 0, 0, 0);
                        }

                        if ((timing_support == EDID_USE_GTF) ||
                            (timing_support == EDID_USE_GTF2)) {
                            generate_mode_using_gtf(xres, yres, freq, mp, new_monitor);
                        }
                        if ((timing_support == EDID_USE_CVT) ||
                            (timing_support == EDID_USE_CVTRB)) {
                            generate_mode_using_cvt_rb(xres, yres, freq, mp, new_monitor);
                        }
                    }
                }
            }
        }
    }

    /* Now check extension blocks for modes. Detailed timing modes found here have 3rd priority */
    if (edidblockref->extension_block_count > 0)
    {
        IFDEBUG printf("%i Extension block(s) found\n", edidblockref->extension_block_count);

        for (int ext_block_id = 1; ext_block_id <= edidblockref->extension_block_count; ext_block_id++)
        {
            EDIDExtensionBlockRef ext_block = (EDIDExtensionBlockRef)&ediddata[sizeof(EDIDBlock) * ext_block_id];

            /* Different block types to be handled here */

            /* CEA Extension - uses Tag 0x02 */
            if (ext_block->tag == 0x02)
            {
                IFDEBUG printf("Block %i: CEA Extension block found\n", ext_block_id);
                IFDEBUG printf("Version %i\n", ext_block->revision);

                if (ext_block->revision != 0) /* Revision 0 doesn't exist. All othe revisions(including future ones we don't know about) should be at least partially parseable. */
                {
                    res = process_cea_extension(ext_block, new_monitor);
                    if (res)
                    {
                        return res;
                    }
                }
            }

            /* VTB Extension block - Uses Tag 0x10, revision 0x01 */
            if ((ext_block->tag == 0x10) && (ext_block->revision == 0x01))
            {
                res = process_vtb_extension_block(ext_block, new_monitor);
                if (res)
                {
                    return res;
                }
            }
        }
    }

    /* Then sort the modes */
    if (new_monitor->modelist == NULL)
    {
        return error(ERR_NOMODES, "(EDID)", 0, 0);
    }
    else
    {
        sort_modelist(&new_monitor->modelist);
    }

    IFDEBUG printf("Modes sorted\n");
    if (file)
    {
        /* open file for new mode file if required */
        /* file is a string terminated in 0x0D */
        char *ptr,*fbuf;
        int length = strcspn(file,"\r");
        fbuf = malloc(length+MAXMONITORNAME+2);
        /* get around the CR terminated file name */
        if (fbuf)
        {
            memcpy(fbuf,file,length);
            fbuf[length] = '.';
            strcpy(fbuf+length+1,new_monitor->name);
            while (ptr=strpbrk(fbuf+length+1," \"#$%&*.:@\\^|\x7f<>"),ptr)
            {
               *ptr='_';
            }

            f = fopen(fbuf,"w");
            free(fbuf);
        }
        if (!f)
        {
            return _kernel_last_oserror();
        }
    }

    if (f)
    {
        fprintf(f,"# Monitor description file for %s\n",new_monitor->name);
        fprintf(f,"# (EDID specified modes only, no calculated modes)\n\n");
        fprintf(f,"# Max Viewable H %d cm \n",edidblockref->horizontal_screen_size);
        fprintf(f,"# Max Viewable V %d cm\n",edidblockref->vertical_screen_size);
        if (RangeBlock != -1)
        {
            uint8_t flags = edidblockref->data_block[RangeBlock][4];
            int vmin = 0, vmax = 0, hmin = 0, hmax = 0, pixmax = 0;
            if ((flags & 0x3) == 0x2) { vmax = 255;}
            if ((flags & 0x3) == 0x2) { vmax = 255; vmin = 255;}
            if ((flags & 0xc) == 0x8) { hmax = 255;}
            if ((flags & 0xc) == 0xc) { hmax = 255; hmin = 255;}
            vmin  += edidblockref->data_block[RangeBlock][5];
            vmax  += edidblockref->data_block[RangeBlock][6];
            hmin  += edidblockref->data_block[RangeBlock][7];
            hmax  += edidblockref->data_block[RangeBlock][8];
            pixmax = edidblockref->data_block[RangeBlock][9];
            fprintf(f,"# Line rate:     %2d - %2dkHz\n",hmin,hmax);
            fprintf(f,"# Frame rate:    %2d - %2dHz\n",vmin,vmax);
            fprintf(f,"# Max Dot rate: %3dMHz(rounded down)\n",pixmax*10);
        }
        fprintf(f,"# Uses %s frequency pixel clocks\n", ((edidblockref->feature_support & 1) == 1)?"Continuous":"Specific");

        char *rules="unknown";
        switch (timing_support) {
            case EDID_USE_CVT:rules="CVT";break;
            case EDID_USE_DMT:rules="DMT";break;
            case EDID_USE_CVTRB:rules="CVTRB";break;
            case EDID_USE_GTF:rules="GTF";break;
            case EDID_USE_GTF2:rules="GTF2";break;
        }
        fprintf(f,"# Use %s timing rules\n#\n",rules);

        fprintf(f,"file_format:1\nmonitor_title:%s\n",new_monitor->name);
        if (new_monitor->dpms_state!=-1)
        {
            fprintf(f,"DPMS_state:%d\n",new_monitor->dpms_state);
        }
        ModeDescriptionRef this = new_monitor->modelist;
        do {
            fprintf(f,"\n# Mode:  %d x %d @ %dHz",this->definition.xres,this->definition.yres,this->frame_hz);
            fprintf(f,"\n# Bounds: H  %3.2fkHz, V %3.2f, DClock %3.2fMHz",((float)(this->line_hz))/1000.0f,((float)(this->frame_mhz))/1000.0f,((float)(this->definition.pixel_khz))/1000.0f);
            fprintf(f,"\nstartmode");
            fprintf(f,"\n mode_name:%d x %d",this->definition.xres,this->definition.yres);
            fprintf(f,"\n x_res:%d",this->definition.xres);
            fprintf(f,"\n y_res:%d",this->definition.yres);
            fprintf(f,"\n pixel_rate:%d",this->definition.pixel_khz);
            fprintf(f,"\n h_timings:%d,%d,%d,%d,%d,%d",this->definition.hpar[0],this->definition.hpar[1],this->definition.hpar[2],this->definition.hpar[3],this->definition.hpar[4],this->definition.hpar[5]);
            fprintf(f,"\n v_timings:%d,%d,%d,%d,%d,%d",this->definition.vpar[0],this->definition.vpar[1],this->definition.vpar[2],this->definition.vpar[3],this->definition.vpar[4],this->definition.vpar[5]);
            fprintf(f,"\n sync_pol:%d",this->definition.syncpol);
            if (this->definition.interlaced == 1)
            {
              fprintf(f,"\n interlaced");
            }
            fprintf(f,"\nEndMode\n");
            this = this->next;  /* will be NULL at list end*/
        } while (this);
        /* now fo hex dump of EDID block */
        fprintf(f,"# EDID block dump\n#\n");
        for (int i=0; i<(edidblockref->extension_block_count+1)*0x80;i+=16) {
            fprintf(f,"# %02x %02x %02x %02x  %02x %02x %02x %02x",
                    ediddata[i],ediddata[i+1],ediddata[i+2],ediddata[i+3],
                    ediddata[i+4],ediddata[i+5],ediddata[i+6],ediddata[i+7]);
            fprintf(f,"  %02x %02x %02x %02x  %02x %02x %02x %02x\n",
                    ediddata[i+8],ediddata[i+9],ediddata[i+10],ediddata[i+11],
                    ediddata[i+12],ediddata[i+13],ediddata[i+14],ediddata[i+15]);
        }
        fprintf(f,"#\n#End\n");
        fclose(f);
    }
    else
    {
       /* Below taken from the loadmodefile code
        * If we haven't got a file loaded at present, then
        * read current monitortype, to restore on module shutdown
        */
       if (old_monitortype == -1)
       {
           old_monitortype = read_monitortype();
       }

       res = set_monitortype(MONITOR_FILE);
       if (res != NULL)
       {
           _kernel_oserror *res2;
           IFDEBUG printf("setting of monitor type to type `FILE' failed\n");
           res2 = restore_monitortype(); /* restore old value */
           if (res2 != NULL)
           {
               IFDEBUG printf("couldn't reset monitor type to CMOS default!\n");
           }
       }
       else
       {
           _kernel_swi_regs regs;
           release_currentmonitor();
           current_monitor = new_monitor;

           /* Set up the mode specifier */
           if (preferred_mode && new_preferred_mode)
           {
               preferred_mode->bit0 = 1;
               preferred_mode->format = 0;
               preferred_mode->xresol = new_preferred_mode->definition.xres;
               preferred_mode->yresol = new_preferred_mode->definition.yres;
               preferred_mode->depth = 5;
               preferred_mode->framerate = new_preferred_mode->frame_hz;
               preferred_mode->param[0].index = -1;
           }
           else if (preferred_mode)
           {
               preferred_mode->bit0 = 0;
           }

           /* Set the preferred sync type from video input definition bit 3. */
           /* (ref EDID spec table 3.11) */
           if ((edidblockref->video_input_definition & 0x8) == 0x8)
           {
               preferred_sync_type = 0;
           }
           else
           {
               preferred_sync_type = 1;
           }

           IFDEBUG printf("Monitor type changed\n");

           /* Newly defined monitor, announce it */
           regs.r[1] = Service_ModeFileChanged;
           _kernel_swi(OS_ServiceCall, &regs, &regs);
           IFDEBUG printf("Service_ModeFileChanged issued\n");
       }
    }

    return res;
}

static _kernel_oserror *readedidblock(int displaynum, EDIDBlockRef edidblock, int offset, int count)
{
    _kernel_oserror *res;
    int iic_code = (0xa1 << 16) | (0x80*offset << 0);
    int op_code = (displaynum << 24) | (0 << 16) | (GraphicsV_IICOp << 0);

    res = _swix(OS_CallAVector, _INR(0,2) | _IN(4) | _IN(9) | _OUT(0) | _OUT(4), iic_code, edidblock+offset, 0x80*count, op_code, GraphicsV, &iic_code, &op_code);

    /* If GraphicsV 14 was not claimed, R4 (op_code) should return
     * unchanged in which case we need to alert the user that the
     * hardware doesn't like EDID :-(
     */
    if (iic_code != 0)
    {
        res = error(ERR_IICOPFAIL, 0, 0, 0);
    }

    /* An 'EDID read not supported' error will trump an IIC failure. */
    if (op_code != 0)
    {
        res = error(ERR_CANTREADEDID, 0, 0, 0);
    }
    return res;
}

_kernel_oserror *loadedid(const char *file)
{
    _kernel_oserror *res;
    /* Load the EDID data into a data block. */

    /* Disable automatic EDID reading when an EDID file is in use */
    EDIDEnabled = 0;

    int file_length, object_found;

    res = _swix(OS_File, _INR(0,1) | _OUT(0) | _OUT(4),
                OSFile_ReadWithTypeNoPath, file, &object_found, &file_length);
    if (!res && (object_found == object_file))
    {
        char *edidblock = (char *) malloc(file_length + 4);

        if (edidblock == NULL)
        {
            return error(ERR_NOSPACE, 0, 0, 0);
        }
        res = _swix(OS_File, _INR(0,3), OSFile_Load, file, edidblock, 0);

        /* Check the block is valid */
        if (!res)
        {
            res = Check_EDID_Checksum((EDIDBlockRef)edidblock, true);
        }
        if (!res)
        {
            res = parseedid(edidblock,NULL);
        }
        free(edidblock);
    }
    return res;
}

_kernel_oserror *readedid(int displaynum, const char *file)
{
    _kernel_oserror *res;
    EDIDBlockRef edidblock = (EDIDBlockRef)malloc(sizeof(EDIDBlock));
    if (edidblock == NULL)
    {
        return error(ERR_NOSPACE, 0, 0, 0);
    }
    IFDEBUG printf("ReadEDID called\n");

    res = readedidblock(displaynum, edidblock, 0, 1);

    /* Check the block is valid */
    if (!res)
    {
        res = Check_EDID_Checksum(edidblock, false);
   }

    /* Now use a separate pointer to the block for extending it and
     * setting up extensions (because if it fails we use the original
     * pointer to free up the memory block and exit).
     */
    char *ediddata = (char *)edidblock;

    /* If it is, resize to accomodate extension blocks */
    if (!res)
    {
        IFDEBUG printf("%i EDID extension blocks found\n", edidblock->extension_block_count);

        if (edidblock->extension_block_count > 0)
        {
            ediddata = realloc(edidblock, sizeof(EDIDBlock) * (edidblock->extension_block_count + 1));
            if (ediddata == NULL)
            {
                res = error(ERR_NOSPACE, 0, 0, 0);
            }
            else
            {
                edidblock = (EDIDBlockRef) ediddata;

                res = readedidblock(displaynum, edidblock, 1, edidblock->extension_block_count);

                /* Check the block is valid */
                if (!res)
                {
                    res = Check_EDID_Checksum(edidblock, true);
                }
            }
        }
    }

    if (!res)
    {
        IFDEBUG printf("Parsing EDID block\n");
        EDIDEnabled = 1;
        res = parseedid(ediddata,file);
    }

    free(edidblock);
    return res;
}

/* EOF edidsupport.c */