blob: 0e58aa36508f1a3940f9cf727a86785e70087979 [file] [log] [blame] [edit]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/debug.h>
#include <ddk/binding.h>
#include <ddk/io-buffer.h>
#include <ddk/protocol/platform-defs.h>
#include <ddk/protocol/platform-device.h>
#include <zircon/syscalls.h>
#include <zircon/assert.h>
#include <hw/reg.h>
#include "vim-display.h"
#include "hdmitx.h"
#include "edid.h"
bool edid_has_extension(const uint8_t* edid_buf)
{
const edid_t* edid = (edid_t *) edid_buf;
return (edid->ext_flag == 1);
}
bool edid_rgb_disp(const uint8_t* edid_buf)
{
const edid_t* edid = (edid_t *) edid_buf;
return (!!((edid->feature_support & (1 << 2)) >> 2));
}
void edid_get_max_size(const uint8_t* edid_buf, uint8_t* hoz, uint8_t* ver)
{
const edid_t* edid = (edid_t *) edid_buf;
*hoz = edid->max_hoz_img_size;
*ver = edid->max_ver_img_size;
}
static char* get_mfg_id(const uint8_t* edid_buf)
{
char *mfg_str = calloc(1, sizeof(char));;
mfg_str[0] = ((edid_buf[8] & 0x7c) >> 2) + 'A' - 1;
mfg_str[1] = (((edid_buf[8] & 0x03) << 3) | (edid_buf[9] & 0xe0) >> 5) + 'A' - 1;
mfg_str[2] = ((edid_buf[9] & 0x1f)) + 'A' - 1;
mfg_str[3] = '\0';
return mfg_str;
}
static uint16_t get_prod_id(const uint8_t* edid_buf)
{
return ((edid_buf[11] << 8) | (edid_buf[10]));
}
static void edid_dump_disp_timing(const disp_timing_t* d)
{
DISP_INFO("pixel_clk = 0x%x (%d)\n", d->pixel_clk, d->pixel_clk);
DISP_INFO("HActive = 0x%x (%d)\n", d->HActive, d->HActive);
DISP_INFO("HBlanking = 0x%x (%d)\n", d->HBlanking, d->HBlanking);
DISP_INFO("VActive = 0x%x (%d)\n", d->VActive, d->VActive);
DISP_INFO("VBlanking = 0x%x (%d)\n", d->VBlanking, d->VBlanking);
DISP_INFO("HSyncOffset = 0x%x (%d)\n", d->HSyncOffset, d->HSyncOffset);
DISP_INFO("HSyncPulseWidth = 0x%x (%d)\n", d->HSyncPulseWidth, d->HSyncPulseWidth);
DISP_INFO("VSyncOffset = 0x%x (%d)\n", d->VSyncOffset, d->VSyncOffset);
DISP_INFO("VSyncPulseWidth = 0x%x (%d)\n", d->VSyncPulseWidth, d->VSyncPulseWidth);
DISP_INFO("HImageSize = 0x%x (%d)\n", d->HImageSize, d->HImageSize);
DISP_INFO("VImageSize = 0x%x (%d)\n", d->VImageSize, d->VImageSize);
DISP_INFO("HBorder = 0x%x (%d)\n", d->HBorder, d->HBorder);
DISP_INFO("VBorder = 0x%x (%d)\n", d->VBorder, d->VBorder);
DISP_INFO("Flags = 0x%x (%d)\n", d->Flags, d->Flags);
}
static void populate_timings(detailed_timing_t* raw_dtd, disp_timing_t* disp_timing)
{
disp_timing->pixel_clk = raw_dtd->raw_pixel_clk[1] << 8 |
raw_dtd->raw_pixel_clk[0];
disp_timing->HActive = (((raw_dtd->raw_Hact_HBlank & 0xf0)>>4) << 8) |
raw_dtd->raw_Hact;
disp_timing->HBlanking = ((raw_dtd->raw_Hact_HBlank & 0x0f) << 8) |
raw_dtd->raw_HBlank;
disp_timing->VActive = (((raw_dtd->raw_Vact_VBlank & 0xf0)>>4) << 8) |
raw_dtd->raw_Vact;
disp_timing->VBlanking = ((raw_dtd->raw_Vact_VBlank & 0x0f) << 8) |
raw_dtd->raw_VBlank;
disp_timing->HSyncOffset = (((raw_dtd->raw_HSync_VSync_OFF_PW & 0xc0)>>6) << 8) |
raw_dtd->raw_HSyncOff;
disp_timing->HSyncPulseWidth = (((raw_dtd->raw_HSync_VSync_OFF_PW & 0x30)>>4) << 8) |
raw_dtd->raw_HSyncPW;
disp_timing->VSyncOffset = (((raw_dtd->raw_HSync_VSync_OFF_PW & 0x0c)>>2) << 4) |
(raw_dtd->raw_VSyncOff_VSyncPW & 0xf0)>>4;
disp_timing->VSyncPulseWidth = ((raw_dtd->raw_HSync_VSync_OFF_PW & 0x03) << 4) |
(raw_dtd->raw_VSyncOff_VSyncPW & 0x0f);
disp_timing->HImageSize = (((raw_dtd->raw_H_V_ImageSize & 0xf0)>>4)<<8) |
raw_dtd->raw_HImageSize;
disp_timing->VImageSize = ((raw_dtd->raw_H_V_ImageSize & 0x0f)<<8) |
raw_dtd->raw_VImageSize;
disp_timing->HBorder = raw_dtd->raw_HBorder;
disp_timing->VBorder = raw_dtd->raw_VBorder;
disp_timing->Flags = raw_dtd->raw_Flags;
}
/* This function reads the detailed timing found in block0 and block1 (referred to standard
* and preferred
*/
zx_status_t edid_parse_display_timing(const uint8_t* edid_buf, detailed_timing_t* raw_dtd,
disp_timing_t* std_disp_timing,
disp_timing_t* pref_disp_timing)
{
const uint8_t* start_dtd;
const uint8_t* start_ext;
uint8_t* s_r_dtd = (uint8_t*) raw_dtd;
ZX_DEBUG_ASSERT(edid_buf);
ZX_DEBUG_ASSERT(raw_dtd);
ZX_DEBUG_ASSERT(std_disp_timing);
ZX_DEBUG_ASSERT(pref_disp_timing);
start_dtd = &edid_buf[0x36];
// populate raw structure first
memcpy(s_r_dtd, start_dtd, 18);
populate_timings(raw_dtd, std_disp_timing);
if (!edid_has_extension(edid_buf)) {
DISP_INFO("extension flag not found!\n");
// we should exit here but there are cases where there were more blocks eventhough flag was
// not set. So we'll read it anyways
// return ZX_OK;
}
// It has extension. Read from start of DTD until you hit 00 00
start_ext = &edid_buf[128];
if (start_ext[0] != 0x2 ) {
if(!edid_has_extension(edid_buf)) {
// No extension and no valid tag. Not worth reading on.
return ZX_OK;
}
DISP_ERROR("%s: Unknown tag! %d\n", __FUNCTION__, start_ext[0]);
return ZX_ERR_WRONG_TYPE;
}
if (start_ext[2] == 0) {
DISP_ERROR("%s: Invalid DTD pointer! 0x%x\n", __FUNCTION__, start_ext[2]);
return ZX_ERR_WRONG_TYPE;
}
start_dtd = &start_ext[0] + start_ext[2];
// populate raw structure first
memcpy(s_r_dtd, start_dtd, 18);
populate_timings(&raw_dtd[0], pref_disp_timing);
return ZX_OK;
}
static uint8_t getInterlaced(uint8_t flag)
{
return ((flag & (0x80)) >> 7); // TODO: bit defs)
}
static uint8_t getHPol(uint8_t flag)
{
if(((flag & 0x18) >> 3) == 0x3) {
// sync type is 3 (i.e. digitail separate)
return ((flag & (0x02)) >> 1);
}
return 0;
}
static uint8_t getVPol(uint8_t flag)
{
if(((flag & 0x18) >> 3) == 0x3) {
// sync type is 3 (i.e. digitail separate)
return ((flag & (0x04)) >> 2);
}
return 0;
}
static zx_status_t get_vic(vim2_display_t* display)
{
disp_timing_t* disp_timing;
disp_timing= &display->std_disp_timing;
ZX_DEBUG_ASSERT(disp_timing);
// Monitor has its own preferred timings. Use that
display->p->timings.interlace_mode = getInterlaced(disp_timing->Flags);
display->p->timings.pfreq = (disp_timing->pixel_clk * 10); // KHz
//TODO: pixel repetition is 0 for most progressive. We don't support interlaced
display->p->timings.pixel_repeat = 0;
display->p->timings.hactive = disp_timing->HActive;
display->p->timings.hblank = disp_timing->HBlanking;
display->p->timings.hfront = disp_timing->HSyncOffset;
display->p->timings.hsync = disp_timing->HSyncPulseWidth;
display->p->timings.htotal = (display->p->timings.hactive) +
(display->p->timings.hblank);
display->p->timings.hback = (display->p->timings.hblank) -
(display->p->timings.hfront +
display->p->timings.hsync);
display->p->timings.hpol = getHPol(disp_timing->Flags);
display->p->timings.vactive = disp_timing->VActive;
display->p->timings.vblank0 = disp_timing->VBlanking;
display->p->timings.vfront = disp_timing->VSyncOffset;
display->p->timings.vsync = disp_timing->VSyncPulseWidth;
display->p->timings.vtotal = (display->p->timings.vactive) +
(display->p->timings.vblank0);
display->p->timings.vback = (display->p->timings.vblank0) -
(display->p->timings.vfront +
display->p->timings.vsync);
display->p->timings.vpol = getVPol(disp_timing->Flags);
//FIXE: VENC Repeat is undocumented. It seems to be only needed for the following
// resolutions: 1280x720p60, 1280x720p50, 720x480p60, 720x480i60, 720x576p50, 720x576i50
// For now, we will simply not support this feature.
display->p->timings.venc_pixel_repeat = 0;
// Let's make sure we support what we've got so far
if (display->p->timings.interlace_mode) {
DISP_ERROR("ERROR: UNSUPPORTED DISPLAY!!!! Pixel Freq = %d (%s mode)\n",
display->p->timings.pfreq,
display->p->timings.interlace_mode? "Interlaced": "Progressive");
DISP_ERROR("Loading 640x480p as Default\n");
display->p->timings.interlace_mode = 0;
display->p->timings.pfreq = (25175); // KHz
display->p->timings.pixel_repeat = 0;
display->p->timings.hactive = 640;
display->p->timings.hblank = 160;
display->p->timings.hfront = 16;
display->p->timings.hsync = 96;
display->p->timings.htotal = (display->p->timings.hactive) +
(display->p->timings.hblank);
display->p->timings.hback = (display->p->timings.hblank) -
(display->p->timings.hfront +
display->p->timings.hsync);
display->p->timings.hpol = 1;
display->p->timings.vactive = 480;
display->p->timings.vblank0 = 45;
display->p->timings.vfront = 10;
display->p->timings.vsync = 2;
display->p->timings.vtotal = (display->p->timings.vactive) +
(display->p->timings.vblank0);
display->p->timings.vback = (display->p->timings.vblank0) -
(display->p->timings.vfront +
display->p->timings.vsync);
display->p->timings.vpol = 1;
}
if (display->p->timings.vactive == 2160) {
DISP_INFO("4K Monitor Detected.\n");
if (display->p->timings.pfreq == 533250) {
// FIXME: 4K with reduced blanking (533.25MHz) does not work
DISP_INFO("4K @ 30Hz\n");
display->p->timings.interlace_mode = 0;
display->p->timings.pfreq = (297000); // KHz
display->p->timings.pixel_repeat = 0;
display->p->timings.hactive = 3840;
display->p->timings.hblank = 560;
display->p->timings.hfront = 176;
display->p->timings.hsync = 88;
display->p->timings.htotal = (display->p->timings.hactive) +
(display->p->timings.hblank);
display->p->timings.hback = (display->p->timings.hblank) -
(display->p->timings.hfront +
display->p->timings.hsync);
display->p->timings.hpol = 1;
display->p->timings.vactive = 2160;
display->p->timings.vblank0 = 90;
display->p->timings.vfront = 8;
display->p->timings.vsync = 10;
display->p->timings.vtotal = (display->p->timings.vactive) +
(display->p->timings.vblank0);
display->p->timings.vback = (display->p->timings.vblank0) -
(display->p->timings.vfront +
display->p->timings.vsync);
display->p->timings.vpol = 1;
}
}
if (display->p->timings.pfreq > 500000) {
display->p->is4K = true;
} else {
display->p->is4K = false;
}
// Aspect ratio determination. 4:3 otherwise 16:9
uint8_t h, v, tmp;
edid_get_max_size(display->edid_buf, &h, &v);
if ( (h % 4 == 0) && (v % 3) == 0) {
tmp = h / 4;
if ( ((v % tmp) == 0) && ((v / tmp) == 3)) {
display->p->aspect_ratio = HDMI_ASPECT_RATIO_4x3;
} else {
display->p->aspect_ratio = HDMI_ASPECT_RATIO_16x9;
}
} else {
display->p->aspect_ratio = HDMI_ASPECT_RATIO_16x9;
}
display->p->colorimetry = HDMI_COLORIMETRY_ITU601;
if (display->p->timings.pfreq > 500000) {
display->p->phy_mode = 1;
} else if (display->p->timings.pfreq > 200000) {
display->p->phy_mode = 2;
} else if (display->p->timings.pfreq > 100000) {
display->p->phy_mode = 3;
} else {
display->p->phy_mode = 4;
}
//TODO: We probably need a more sophisticated method for calculating
// clocks. This will do for now.
display->p->pll_p_24b.viu_channel = 1;
display->p->pll_p_24b.viu_type = VIU_ENCP;
display->p->pll_p_24b.vid_pll_div = VID_PLL_DIV_5;
display->p->pll_p_24b.vid_clk_div = 2;
display->p->pll_p_24b.hdmi_tx_pixel_div = 1;
display->p->pll_p_24b.encp_div = 1;
display->p->pll_p_24b.od1 = 1;
display->p->pll_p_24b.od2 = 1;
display->p->pll_p_24b.od3 = 1;
display->p->pll_p_24b.hpll_clk_out = (display->p->timings.pfreq * 10);
while (display->p->pll_p_24b.hpll_clk_out < 2900000) {
if (display->p->pll_p_24b.od1 < 4) {
display->p->pll_p_24b.od1 *= 2;
display->p->pll_p_24b.hpll_clk_out *= 2;
} else if (display->p->pll_p_24b.od2 < 4) {
display->p->pll_p_24b.od2 *= 2;
display->p->pll_p_24b.hpll_clk_out *= 2;
} else if (display->p->pll_p_24b.od3 < 4) {
display->p->pll_p_24b.od3 *= 2;
display->p->pll_p_24b.hpll_clk_out *= 2;
} else {
return ZX_ERR_OUT_OF_RANGE;
}
}
if(display->p->pll_p_24b.hpll_clk_out > 6000000) {
DISP_ERROR("Something went wrong in clock calculation (pll_out = %d)\n",
display->p->pll_p_24b.hpll_clk_out);
return ZX_ERR_OUT_OF_RANGE;
}
return ZX_OK;
}
static void dump_raw_edid(const uint8_t* edid_buf, uint16_t edid_buf_size)
{
ZX_DEBUG_ASSERT(edid_buf);
zxlogf(INFO, "\n$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n");
zxlogf(INFO,"$$$$$$$$$$$$ RAW EDID INFO $$$$$$$$$$$$\n");
zxlogf(INFO, "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n\n");
for (int i = 0; i < edid_buf_size; i++) {
zxlogf(INFO, "0x%02x ", edid_buf[i]);
if ( ((i + 1) % 8) == 0) {
zxlogf(INFO, "\n");
}
}
zxlogf(INFO, "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n\n");
}
zx_status_t get_preferred_res(vim2_display_t* display, uint16_t edid_buf_size)
{
uint32_t timeout = 0;
uint32_t addr = 0;
uint32_t i;
zx_status_t status;
ZX_DEBUG_ASSERT(edid_buf_size <= EDID_BUF_SIZE);
ZX_DEBUG_ASSERT(display);
ZX_DEBUG_ASSERT(display->edid_buf);
ZX_DEBUG_ASSERT(display->p);
memset(display->edid_buf, 0, edid_buf_size);
for (addr = 0; addr < edid_buf_size; addr+=8) {
// Program SLAVE/SEGMENT/ADDR
hdmitx_writereg(display, HDMITX_DWC_I2CM_SLAVE, 0x50);
hdmitx_writereg(display, HDMITX_DWC_I2CM_SEGADDR, 0x30);
hdmitx_writereg(display, HDMITX_DWC_I2CM_SEGPTR, 1);
hdmitx_writereg(display, HDMITX_DWC_I2CM_ADDRESS, addr);
hdmitx_writereg(display, HDMITX_DWC_I2CM_OPERATION, 1 << 2);
timeout = 0;
while ((!(hdmitx_readreg(display, HDMITX_DWC_IH_I2CM_STAT0) & (1 << 1))) && (timeout < 5)) {
usleep(1000);
timeout ++;
}
if (timeout == 5) {
DISP_ERROR("HDMI DDC TimeOut\n");
return ZX_ERR_TIMED_OUT;
}
usleep(1000);
hdmitx_writereg(display, HDMITX_DWC_IH_I2CM_STAT0, 1 << 1); // clear INT
for (i = 0; i < 8; i ++) {
display->edid_buf[addr+i] = hdmitx_readreg(display, HDMITX_DWC_I2CM_READ_BUFF0 + i);
}
}
if ( (status = edid_parse_display_timing(display->edid_buf, &display->std_raw_dtd,
&display->std_disp_timing, &display->pref_disp_timing)) != ZX_OK) {
DISP_ERROR("Something went wrong in EDID Parsing (%d)\n", status);
return status;
}
// Find out whether we support the preferred format or not
status = get_vic(display);
if (status != ZX_OK) {
DISP_ERROR("Could not get a proper display timing\n");
return status;
}
// See if we need to change output color to RGB
if (edid_rgb_disp(display->edid_buf)) {
display->output_color_format = HDMI_COLOR_FORMAT_RGB;
} else {
display->output_color_format = HDMI_COLOR_FORMAT_444;
}
dump_raw_edid(display->edid_buf, edid_buf_size);
return ZX_OK;
}