blob: 37f70c360564c52dd690d8c5d16fb5987efdf32c [file] [log] [blame]
// 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/display.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));
}
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 void get_vic(vim2_display_t* display)
{
uint32_t i;
ZX_DEBUG_ASSERT(display);
disp_timing_t* disp_timing = &display->std_disp_timing;
ZX_DEBUG_ASSERT(disp_timing);
struct hdmi_param** supportedFormats = get_supported_formats();
ZX_DEBUG_ASSERT(supportedFormats);
for (i = 0; supportedFormats[i] != NULL; i++) {
if (supportedFormats[i]->timings.hactive != disp_timing->HActive) {
continue;
}
if (supportedFormats[i]->timings.vactive != disp_timing->VActive) {
continue;
}
// FIXME: for some reason 1920x1200 doesn't work. go to a lower resolution
if (disp_timing->HActive == 1920 && disp_timing->VActive == 1200) {
DISP_ERROR("Preferred Resolution of 1920x1200-60Hz is not supported\n");
DISP_ERROR("Switching to 1024x768-60Hz instead\n");
display->p = &hdmi_1024x768p60Hz_vft;
return;
}
display->p = supportedFormats[i];
return;
}
DISP_ERROR("Display preferred resolution not supported (%d x %d%c [flag = 0x%x])\n",
disp_timing->HActive, disp_timing->VActive, disp_timing->Flags & (0x80) ? 'i' : 'p',
disp_timing->Flags);
DISP_ERROR("Use default: 640x480p60Hz\n");
display->p = &hdmi_640x480p60Hz_vft;
return;
}
static void dump_raw_edid(const uint8_t* edid_buf, uint16_t edid_buf_size)
{
ZX_DEBUG_ASSERT(edid_buf);
for (int i = 0; i < edid_buf_size; i++) {
DISP_ERROR("0x%x\n", edid_buf[i]);
}
}
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);
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
get_vic(display);
// 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;
}
return ZX_OK;
}