iBoot/drivers/samsung/hdmi8947/hdmi.c

1849 lines
54 KiB
C

/*
* Copyright (C) 2012-2014 Apple Inc. All rights reserved.
*
* This document is the property of Apple Inc.
* It is considered confidential and proprietary.
*
* This document may not be reproduced or transmitted in any form,
* in whole or in part, without the express written permission of
* Apple Inc.
*/
#include <debug.h>
#include <arch.h>
#include <platform.h>
#include <platform/int.h>
#include <platform/clocks.h>
#include <platform/gpiodef.h>
#include <platform/soc/hwclocks.h>
#include <platform/soc/hwisr.h>
#include <platform/soc/hwregbase.h>
#include <platform/soc/pmgr.h>
#include <sys.h>
#include <sys/task.h>
#include <sys/callout.h>
#include <lib/devicetree.h>
#include <drivers/display.h>
#include <drivers/hdmi.h>
#include "hdmi.h"
#include "regs.h"
#include <drivers/process_edid.h>
#include <drivers/iic.h>
/////////////////////////////////////////
////////// debug support
#define HDMI_DEBUG_MASK ( \
HDMI_DEBUG_INIT | \
HDMI_DEBUG_ERROR | \
HDMI_DEBUG_INFO | \
HDMI_DEBUG_INT | \
HDMI_DEBUG_VIDEO | \
HDMI_DEBUG_PLL | \
HDMI_DEBUG_REG | \
0)
#undef HDMI_DEBUG_MASK
#define HDMI_DEBUG_MASK (HDMI_DEBUG_INIT | HDMI_DEBUG_ERROR)
#define HDMI_DEBUG_INIT (1<<16) // initialisation
#define HDMI_DEBUG_ERROR (1<<17) // errors
#define HDMI_DEBUG_INFO (1<<18) // info
#define HDMI_DEBUG_INT (1<<19) // interrupts
#define HDMI_DEBUG_VIDEO (1<<20) // video
#define HDMI_DEBUG_REG (1<<22) // register
#define HDMI_DEBUG_I2C (1<<23) // i2c read/write
#define HDMI_DEBUG_PLL (1<<24) // PLL
#define HDMI_DEBUG_ALWAYS (1<<31) // unconditional output
#define debug(_fac, _fmt, _args...) \
do { \
if ((HDMI_DEBUG_ ## _fac) & (HDMI_DEBUG_MASK | HDMI_DEBUG_ALWAYS)) \
dprintf(DEBUG_CRITICAL, "HDMI: %s, %d: " _fmt, __FUNCTION__, __LINE__, ##_args);\
} while(0)
/////////////////////////////////////////
////////// consts, macros
#define HDMI_DTPATH "arm-io/hdmi"
// Crude screensaver: Turn off hdmi after 3 minutes.
#define kScreenBurnTimeout (3 * 60 * 1000000)
#define kTMDSMinimumPowerOffIntervalMS 100
#define kHDMICoreResetIntervalMS 1 // Core reset asserted time: Arbitrary large value
#define kPHYResetIntervalMS 1 // PHY reset signal high time: Minimum 100 microseconds per HDMI 1.4 PHY manual
#define kHDMIStackSize 8192
#define require(assertion, exception_label) \
do { \
if (__builtin_expect(!(assertion), 0)) \
{ \
goto exception_label; \
} \
} while (0)
#define require_action(assertion, exception_label, action) \
do { \
if (__builtin_expect(!(assertion), 0)) \
{ \
{ \
action; \
} \
goto exception_label; \
} \
} while (0)
#define require_noerr(error_code, exception_label) \
do { \
if (__builtin_expect(0 != (error_code), 0)) \
{ \
goto exception_label; \
} \
} while (0)
#define require_noerr_action(error_code, exception_label, action) \
do { \
if (__builtin_expect(0 != (error_code), 0)) \
{ \
{ \
action; \
} \
goto exception_label; \
} \
} while (0)
/////////////////////////////////////////
////////// typedefs, enums, structs
uint32_t _registers[] = {
0x00000000,
0x00010000,
0x00030000,
0x00040000,
0x00050000,
0x00060000,
0x00070000,
0x00017000,
0x00080000,
};
uint32_t _phyConfigRegs[kPHYConfigRegCount/4 + 1] = {
HDMI_BASE_ADDR + HDMIPHYCON0,
HDMI_BASE_ADDR + HDMIPHYCON1,
HDMI_BASE_ADDR + HDMIPHYCON2,
HDMI_BASE_ADDR + HDMIPHYCON3,
HDMI_BASE_ADDR + HDMIPHYCON4,
HDMI_BASE_ADDR + HDMIPHYCON5,
HDMI_BASE_ADDR + HDMIPHYCON6,
HDMI_BASE_ADDR + HDMIPHYCON7,
HDMI_BASE_ADDR + HDMIPHYCON8,
};
enum Module {
kCtrlModule = 0, // CTRL_BASE: Controller
kHDMIModule = 1, // HDMI_CORE_BASE: HDMI Core
kSPDIFModule = 2, // SPDIF_BASE: SPDIF Receiver
kI2SModule = 3, // I2S_BASE: I2S Receiver
kTGModule = 4, // TG_BASE: Timing Generator
kEFuseModule = 5, // EFUSE_BASE: E-Fuse
kCECModule = 6, // CEC_BASE: HDMI CEC
kHDCPModule = 7, // HDCP Registers
kI2CModule = 8, // I2C_BASE: I2C to HDMI PHY
kModuleCount = 9
};
/////////////////////////////////////////
////////// local variables
static const char hdmi_dt_path[] = HDMI_DTPATH;
static addr_t __base_address;
static AppleSamsungHDMITXPHYConfigTableEntry *hdmi_port_phy_cfg_table;
size_t hdmi_port_phy_cfg_table_size;
const HDMITXPHYConfig *phyConfig;
uint32_t frame_interval_MS;
struct video_link_data _current_video_link;
static bool hdmi_started;
static bool hdmi_video_started;
static u_int8_t hdmi_controller_type;
static u_int8_t hdmi_controller_mode;
static struct task_event hdmi_controller_task_event;
static struct callout hdmi_controller_screensaver_callout;
static bool hdmi_interrupt_occurred;
static uint32_t ctrl_int_status;
static uint32_t iic_int_status;
/////////////////////////////////////////
////////// local functions declaration
static int hdmi_controller_task(void *arg);
static int hdmi_phy_regs_task(void *arg);
static int configure_video(struct video_link_data *data, uint32_t pixel_clock);
static void init_video(bool master);
static void configure_video_mute(struct video_link_data *data, bool mute);
static int configure_video_color(const struct video_link_data * data);
static int configure_video_mode(struct video_link_data *data, uint32_t pixel_clock);
static void configure_video_bist(struct video_link_data *data);
static void hdmi_interrupt_filter(void *arg);
static void handle_interrupt(void);
static void uninit_video();
static int reset(void);
static void power_down_phy(bool fullPowerDown);
static int validate_video_link(struct video_link_data * data, uint32_t pixel_clock, const HDMITXPHYConfig ** pPHYConfig);
static int set_tx_output_enabled(bool enabled);
static int set_tx_output_mode(int mode);
static void handle_screensaver_callout(struct callout *co, void *args);
void set_av_mute_enabled(bool enabled);
static uint8_t read_reg(uint8_t module, u_int32_t offset);
static uint32_t read_reg32(uint8_t module, u_int32_t offset);
static void write_reg(uint8_t module, u_int32_t offset, uint8_t val);
static void write_reg32(uint8_t module, u_int32_t offset, uint32_t val);
static void and_reg(uint8_t module, u_int32_t offset, uint8_t val);
static void or_reg(uint8_t module, u_int32_t offset, uint8_t val);
static void set_bits_in_reg(uint8_t module, u_int32_t offset, uint8_t pos, uint8_t mask, uint8_t value);
static uint32_t get_bits_in_reg(uint8_t module, uint32_t reg, uint32_t pos, uint32_t mask);
static uint8_t read_phy_reg(uint8_t offset);
static int write_phy_reg(u_int32_t offset, const uint8_t data);
static void or_phy_reg(u_int32_t offset, uint8_t data);
static void and_phy_reg(u_int32_t offset, uint8_t data);
void write_regs(uint8_t module, uint32_t basereg, uint32_t, unsigned int length);
void write_regs_with_data(uint8_t module, uint32_t basereg, uint8_t * data, unsigned int length);
static int write_phy_config_reg(PHYConfigReg reg, uint32_t data);
static int stop_video_link();
static int complete_video_link();
static void set_TMDS_power_enabled(bool enabled);
static int bypass_wait = 0;
struct hdmi_controller_config {
addr_t base_address;
u_int32_t clock;
u_int32_t irq;
const char *dt_path;
};
static const struct hdmi_controller_config hdmi_config[] = {
#if WITH_HW_DISPLAY_HDMI
{ HDMI_BASE_ADDR, CLK_HDMI, INT_HDMI_LINK, hdmi_dt_path },
#else
{ 0, 0, 0, 0 },
#endif // WITH_HW_DISPLAY_HDMI
};
struct display_infoframe aviInfoFrame = {
kDisplayInfoFrameTypeAVI,
0x02, //Version
0x0d, //length
0xf3, //checksum
{0x10, 0x68, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00 } //data
};
extern AppleSamsungHDMITXPHYConfigTableEntry hdmi_static_config[];
extern size_t hdmi_static_config_size;
/////////////////////////////////////////
////////// controller global functions
int hdmi_controller_start(u_int8_t type, u_int8_t mode)
{
u_int32_t hpdstate;
if (hdmi_started)
return 0;
hdmi_controller_type = type;
hdmi_controller_mode = mode;
__base_address = hdmi_config[hdmi_controller_type].base_address;
// register interrupt handler
install_int_handler(hdmi_config[hdmi_controller_type].irq, hdmi_interrupt_filter, NULL);
event_init(&hdmi_controller_task_event, EVENT_FLAG_AUTO_UNSIGNAL, false);
hdmi_port_phy_cfg_table = (AppleSamsungHDMITXPHYConfigTableEntry *)hdmi_static_config;
hdmi_port_phy_cfg_table_size = hdmi_static_config_size;
clock_gate(hdmi_config[hdmi_controller_type].clock, true);
unmask_int(hdmi_config[hdmi_controller_type].irq);
reset();
//power down HDMI PHY, keeping HPD enabled
power_down_phy(false);
hdmi_started = true;
task_start(task_create("hdmi", &hdmi_controller_task, NULL, kHDMIStackSize));
hpdstate = read_reg(kCtrlModule, CTRL_HPD_STATUS);
debug(INIT,"hpdstate=0x%08x\n", hpdstate);
if (hpdstate == 0x00000001) {
hdmi_device_start();
}
// clear any pending HPD interrupts (Note: status bits are write-one-to-clear)
write_reg(kCtrlModule, CTRL_INTC_FLAG, CTRL_INTC_FLAG_HPD_CHG);
// unmask HPD
or_reg(kCtrlModule, CTRL_INTC_CON, CTRL_INTC_CON_EN_GLOBAL|CTRL_INTC_CON_EN_HPD_CHG);
write_reg(kCtrlModule, CTRL_INTC_FLAG, CTRL_INTC_FLAG_HPD_CHG);
return 0;
}
void hdmi_controller_stop()
{
if (!hdmi_started) {
debug(INT, "hdmi not started. Nothing to do\n");
return;
}
hdmi_controller_stop_video();
// disable interrupt source
mask_int(hdmi_config[hdmi_controller_type].irq);
// Stop background EDID polling.
abort_edid();
hdmi_video_started = false;
hdmi_device_stop();
if (hdmi_controller_type == kHDMIControllerType_HDMI) {
callout_dequeue(&hdmi_controller_screensaver_callout);
}
hdmi_started = false;
}
int hdmi_controller_validate_video(struct video_timing_data *timings)
{
uint64_t pixel_clock;
// Assume basic settings.
struct video_link_data data;
bzero(&data, sizeof(data));
bcopy(timings, &data.timing, sizeof(*timings));
data.mirror_mode = false;
data.test_mode = 0;
data.color.depth = 8;
data.color.space = kDisplayColorSpacesRGB;
data.color.range = kDisplayColorDynamicRangeVESA;
data.color.coefficient = kDisplayColorCoefficientITU601;
pixel_clock = data.timing.axis[kDisplayAxisTypeVertical].total *
data.timing.axis[kDisplayAxisTypeHorizontal].total;
pixel_clock *= data.timing.axis[kDisplayAxisTypeVertical].sync_rate; // vTotal * hTotal * vSyncRate
pixel_clock >>= 16;
debug(VIDEO, "stream clock=%lld\n", pixel_clock);
return validate_video_link(&data, pixel_clock, NULL);
}
int hdmi_controller_read_bytes_i2c(u_int32_t device_addr, u_int8_t addr, u_int8_t *data, u_int32_t length)
{
uint8_t offset[1];
offset[0] = addr;
return (iic_read(HDMI_DDC_IIC_BUS, device_addr, offset, 1, data, length, IIC_COMBINED));
}
int hdmi_controller_write_bytes_i2c(u_int32_t device_addr, u_int32_t addr, u_int8_t *data, u_int32_t length)
{
return -1;
}
int hdmi_controller_start_video(struct video_link_data *data, uint32_t pixel_clock)
{
struct video_link_data edid_data;
struct video_link_data *timings = NULL;
if ( !hdmi_started )
return -1;
debug(INIT,"Pixel clock %d\n", pixel_clock);
// Overwriting timings with EDID preference.
bcopy(data, &edid_data, sizeof(edid_data));
if (get_edid_timings(&edid_data.timing) == 0) timings = &edid_data;
if (timings == NULL) timings = data;
return configure_video(timings, pixel_clock);
}
int hdmi_controller_stop_video()
{
if ( !hdmi_video_started )
return -1;
stop_video_link();
complete_video_link();
// unmask HPD
or_reg(kCtrlModule, CTRL_INTC_CON, CTRL_INTC_CON_EN_GLOBAL|CTRL_INTC_CON_EN_HPD_CHG);
write_reg(kCtrlModule, CTRL_INTC_FLAG, CTRL_INTC_FLAG_HPD_CHG);
hdmi_video_started = false;
return 0;
}
bool hdmi_controller_video_configured()
{
return hdmi_video_started;
}
uint8_t hdmi_read_reg(uint32_t offset)
{
return( read_reg(0, offset) );
}
void hdmi_write_reg(uint32_t offset, uint8_t value)
{
write_reg(0, offset, value);
}
/////////////////////////////////////////
////////// controller local functions
static void handle_screensaver_callout(struct callout *co, void *args)
{
debug(ALWAYS, "HDMI Screensaver\n");
// set_TMDS_power_enabled calls write_phy_reg which endup calling event_wait_timeout. Such call is not safe when performed in the handling of the callout.
bypass_wait = 1;
set_TMDS_power_enabled(false);
bypass_wait = 0;
}
static int hdmi_controller_task(void *arg)
{
if (hdmi_controller_type == kHDMIControllerType_HDMI) {
callout_enqueue(&hdmi_controller_screensaver_callout, kScreenBurnTimeout, handle_screensaver_callout, NULL);
}
// Perform this on the hdmi task to prevent blocking startup.
for (;;) {
event_wait(&hdmi_controller_task_event);
// if controller was stopped before hdmi task got chance to run, exit
if (hdmi_started == false) {
debug(ERROR, "controller task stopped since hdmi stopped\n");
return 0;
}
mask_int(hdmi_config[hdmi_controller_type].irq);
if (hdmi_interrupt_occurred) {
hdmi_interrupt_occurred = false;
if (hdmi_controller_type == kHDMIControllerType_HDMI) {
callout_reset(&hdmi_controller_screensaver_callout, 0);
}
// Invoke bottom half.
handle_interrupt();
}
unmask_int(hdmi_config[hdmi_controller_type].irq);
}
return 0;
}
static bool get_hot_plug_detect()
{
return read_reg(kCtrlModule, CTRL_HPD_STATUS) & CTRL_HPD_STATUS_PLUGGED;
}
static void hdmi_interrupt_filter(void *arg)
{
ctrl_int_status = read_reg(kCtrlModule, CTRL_INTC_FLAG);
write_reg(kCtrlModule, CTRL_INTC_FLAG, ctrl_int_status);
iic_int_status = read_reg32(kI2CModule, IICINT);
write_reg32(kI2CModule, IICINT, iic_int_status);
debug(INT, "Interrupt received. ctrl_int_status=0x%08x\n", ctrl_int_status);
debug(INT, "Interrupt received. ctrl_int_status=0x%08x iic_int_status=0x%08x\n", ctrl_int_status, iic_int_status);
hdmi_interrupt_occurred = true;
event_signal(&hdmi_controller_task_event);
}
static void handle_interrupt(void)
{
// Handle unplug event
if ( (ctrl_int_status & CTRL_INTC_FLAG_HPD_UNPLUG) ) {
debug(INT, "HPD_UNPLUG\n");
debug(INT, "MUST HANDLE TEAR DOWN\n");
hdmi_device_stop();
}
// Handle plug event
if ( (ctrl_int_status & CTRL_INTC_FLAG_HPD_PLUG) ) {
debug(INT, "HPD_PLUG\n");
if ( get_hot_plug_detect() ) {
debug(INT, "MUST HANDLE PLUG IN \n");
hdmi_device_start();
} else {
debug(INT, "MUST HANDLE UNPLUG\n");
hdmi_device_stop();
}
}
// Handle HDCP interrupts
if ((ctrl_int_status & CTRL_INTC_FLAG_HDCP)) {
uint32_t hdcp_int_status;
debug(INT, "HDCP REALLY!!!\n");
hdcp_int_status = read_reg(kHDMIModule, HDMI_STATUS);
// Clear active HDCP interrupts (Note: bit 7 is read-only and not an interrupt source)
write_reg(kHDMIModule, HDMI_STATUS, hdcp_int_status);
}
}
static void reset_HDMI_core(void)
{
// From H4I Power Manager (PMGR) Microarchitecture Specification
// section 7.2 Block Resets (see H4i_UM):
//
// NOTE: Some of the blocks may have CSR bits defined as reset bits, however some of these have
// been disabled. The only valid supported block reset mechanism in H4I is the reset bit in the
// block's power state register.
//Reset device
clock_reset_device(hdmi_config[hdmi_controller_type].clock);
}
static void reset_PHY(void)
{
// Ensure that PHY is powered on (if the PHY power off signal is connected)
and_reg(kCtrlModule, CTRL_PHY_CON_0, ~CTRL_PHY_CON_0_PHY_PWR_OFF);
// Reset PHY
or_reg(kCtrlModule, CTRL_PHY_RSTOUT, CTRL_HDMI_PHY_RSTOUT_RESET);
task_sleep(kPHYResetIntervalMS);
and_reg(kCtrlModule, CTRL_PHY_RSTOUT, ~(CTRL_HDMI_PHY_RSTOUT_RESET));
}
static void set_TMDS_power_enabled(bool enabled)
{
uint8_t reg;
uint8_t mask;
static bool tmds_enabled = 0;
if (tmds_enabled == enabled)
return;
tmds_enabled = enabled;
reg = PHY_CONFIG_REG1D;
mask = PHY_V1P4_CONFIG_REG1D_I2C_PDEN | PHY_V1P4_CONFIG_REG1D_TX_DRV_PD;
if (enabled ) {
and_phy_reg(reg, ~mask);
} else {
or_phy_reg(reg, mask);
}
}
static void power_down_phy(bool fullPowerDown)
{
or_phy_reg(PHY_CONFIG_REG1D, PHY_V1P4_CONFIG_REG1D_I2C_PDEN | (fullPowerDown ? PHY_V1P4_CONFIG_REG1D_FULL_PD : PHY_V1P4_CONFIG_REG1D_PD_WITH_HPD));
debug(INIT,"PHY %s power down\n", fullPowerDown ? "full" : "partial");
if ( fullPowerDown ) {
// Power down PHY (if the PHY power off signal is connected)
or_reg(kCtrlModule, CTRL_PHY_CON_0, CTRL_PHY_CON_0_PHY_PWR_OFF);
}
}
bool get_is_PHY_ready(void)
{
return ((read_reg(kCtrlModule, CTRL_PHY_STATUS_0) & CTRL_PHY_STATUS_0_PHY_READY) ? true : false) ;
}
static int reset(void)
{
debug(INIT,"PHY_CON_0=0x%08x\n", read_reg(kCtrlModule, CTRL_PHY_CON_0));
// Reset HDMI core and PHY
debug(INIT,"PHYReady=%u\n", get_is_PHY_ready());
// Reset HDMI core (Note: core reset disables PHY power in case of 1.4 TX)
reset_HDMI_core();
// Reset HDMI PHY
reset_PHY();
// Set TMDS bit order
set_bits_in_reg(kHDMIModule, HDMI_CON_0, 0, HDMI_CON_0_ENCODING_RETAIN_BIT_ORDER, 0);
debug(INIT,"PHYReady=%u\n", get_is_PHY_ready());
debug(INIT,"PHY_CON_0=0x%08x\n", read_reg(kCtrlModule, CTRL_PHY_CON_0));
// Initialize the I2C bridge
//
// - Clear pending interrupts
// - Set configuration options from device tree
// - Unmask Stop interrupt
// - Enable ACK generation
// - Set mode to master transmitter and enable I2C output
write_reg32(kI2CModule, SW_RESET, kIICSwReset);
do {
write_reg32(kI2CModule, HDMIPHY_ID, PHY_I2C_ADDRESS);
} while (read_reg32(kI2CModule, HDMIPHY_ID) != PHY_I2C_ADDRESS);
write_reg32(kI2CModule, IICINT, kIICIntAll); // Clear pending interrupts
write_reg32(kI2CModule, IICCON, (IICCONFIG_IICCON | kIICConAckGen));
write_reg32(kI2CModule, FIFOCON, IICCONFIG_FIFOCON);
write_reg32(kI2CModule, IICBUSCON, IICCONFIG_IICBUSCON);
write_reg32(kI2CModule, TIMEOUT_CON, IICCONFIG_TIMEOUTCON);
write_reg32(kI2CModule, IICSTAT, (kIICStatMaster | kIICStatTx | kIICStatSOE));
return 0;
}
static int configure_video(struct video_link_data *data, uint32_t pixel_clock)
{
debug(INIT,"starting to configure video\n");
if (hdmi_video_started) return 0;
// Uncomment to show the timings we finally decide to use.
debug(ALWAYS, "configure_video with:\n");
debug(ALWAYS, "mirror_mode: %d\n", data->mirror_mode);
debug(ALWAYS, "test_mode: 0x%08x\n", data->test_mode);
debug(ALWAYS, "color:\n");
debug(ALWAYS, " depth: %u\n", data->color.depth);
debug(ALWAYS, " space: %u\n", data->color.space);
debug(ALWAYS, " range: %u\n", data->color.range);
debug(ALWAYS, " coefficient: %u\n", data->color.coefficient);
debug(ALWAYS, "timing:\n");
debug(ALWAYS, " interlaced: %d\n", data->timing.interlaced);
{
int i;
for (i = 0; i < kDisplayAxisCount; ++i) {
debug(ALWAYS, " Axis %d:\n", i);
debug(ALWAYS, " total: %u\n", data->timing.axis[i].total);
debug(ALWAYS, " active: %u\n", data->timing.axis[i].active);
debug(ALWAYS, " sync_width: %u\n", data->timing.axis[i].sync_width);
debug(ALWAYS, " back_porch: %u\n", data->timing.axis[i].back_porch);
debug(ALWAYS, " front_porch: %u\n", data->timing.axis[i].front_porch);
debug(ALWAYS, " sync_rate: %u (%uHz)\n",
data->timing.axis[i].sync_rate, data->timing.axis[i].sync_rate >> 16);
debug(ALWAYS, " sync_polarity: %u\n", data->timing.axis[i].sync_polarity);
}
}
init_video((data->mirror_mode ? false : true));
if ( configure_video_mode(data, pixel_clock) != 0 ) {
debug(ERROR,"failed configure_video_mode\n");
goto exit;
}
if ( configure_video_color(data) != 0 )
goto exit;
configure_video_bist(data);
configure_video_mute(data, false);
hdmi_video_started = true;
debug(VIDEO, "finished configuring video\n");
return 0;
exit:
debug(ERROR, "failed to configure video\n");
hdmi_video_started = false;
return -1;
}
static void init_video(bool master)
{
debug(INIT,"%s:nothing to do yet\n", __FUNCTION__);
}
static HDMITXPHYConfigDepth phy_config_depth(uint32_t depth)
{
HDMITXPHYConfigDepth depthIndex;
switch ( depth ) {
case 12:
depthIndex = kHDMITXPHYConfigDepth12;
break;
case 10:
depthIndex = kHDMITXPHYConfigDepth10;
break;
default:
depthIndex = kHDMITXPHYConfigDepth8;
break;
}
return depthIndex;
}
const HDMITXPHYConfig * find_PHY_config(uint32_t pixelClockHz, uint32_t pixelClockMinHz, uint32_t pixelClockMaxHz, uint32_t colorDepth)
{
const HDMITXPHYConfig * bestConfig = NULL;
const HDMITXPHYConfigDepth depthIndex = phy_config_depth(colorDepth);
if ( hdmi_port_phy_cfg_table ) {
const AppleSamsungHDMITXPHYConfigTableEntry * table;
unsigned int rowCount;
uint32_t bestError;
// Search for an entry with a matching pixel clock
//
// Note: The table must be sorted by pixel clock in ascending order.
//
table = (const AppleSamsungHDMITXPHYConfigTableEntry *)hdmi_port_phy_cfg_table;
rowCount = hdmi_port_phy_cfg_table_size / sizeof table[0];
bestError = UINT32_MAX;
for (unsigned int i = 0; i < rowCount; i++) {
uint32_t configPixelClockHz = table[i].pixelClockHz;
uint32_t error;
debug(INFO,"pixelClockMaxHz %d pixel_clock %d configPixelClockHz %d\n", pixelClockMaxHz, pixelClockHz, configPixelClockHz);
if (configPixelClockHz < pixelClockMinHz) {
goto next;
}
if (configPixelClockHz > pixelClockMaxHz) {
goto exit;
}
if (table[i].depth[depthIndex].invalid) {
goto next;
}
if ( configPixelClockHz < pixelClockHz ) {
error = pixelClockHz - configPixelClockHz;
} else {
error = configPixelClockHz - pixelClockHz;
}
if ( error < bestError ) {
bestError = error;
bestConfig = &table[i].depth[depthIndex].data;
}
next:
continue;
}
}
exit:
return bestConfig;
}
static void configure_video_mute(struct video_link_data *data, bool mute)
{
set_av_mute_enabled(mute);
}
static int validate_video_color(const struct video_color_data * color)
{
int ret = 0;
if (color->depth < 8 || color->depth > 12) {
ret = -1;
debug(ERROR,"Color depth not supported\n");
goto exit;
}
if (color->space == kDisplayColorSpacesYCbCr422) {
// It is invalid to have a depth other than 8 with YCbCr4:2:2 [see HDMI v1.3 section 6.5].
if (color->depth != 8) {
ret = -1;
debug(ERROR,"YCbCr4:2:2 requires depth = 8 bits\n");
goto exit;
}
}
exit:
return ret;
}
static int validate_video_timing(const struct video_timing_axis * h, const struct video_timing_axis * v)
{
int ret = 0;
if (h->total <= 0 || h->total > kHorizontalTotalLimit) {
ret = -1;
goto exit;
}
if (v->total <= 0 && v->total > kVerticalTotalLimit) {
ret = -1;
goto exit;
}
if (h->active > kHorizontalActiveLimit) {
ret = -1;
goto exit;
}
if (v->active > kVerticalActiveLimit) {
ret = -1;
goto exit;
}
if ((h->total < h->active) || ((h->total - h->active) > kHorizontalBlankLimit)) {
ret = -1;
goto exit;
}
if ((v->total < v->active) || ((v->total - v->active) > kVerticalBlankLimit)) {
ret = -1;
goto exit;
}
exit:
return ret;
}
#define kVideoPixelClkTolleranceNumerator 5
#define kVideoPixelClkTolleranceDenomiator 1000
static inline u_int32_t
ulmin(u_int32_t a, u_int32_t b)
{
return (a < b ? a : b);
}
uint32_t _pixelClockLimit = UINT32_MAX;
static int validate_video_link(struct video_link_data * data, uint32_t pixel_clock, const HDMITXPHYConfig ** pPHYConfig)
{
const struct video_timing_axis * h = &data->timing.axis[kDisplayAxisTypeHorizontal];
const struct video_timing_axis * v = &data->timing.axis[kDisplayAxisTypeVertical];
const struct video_color_data * color = &data->color;
const HDMITXPHYConfig * phyConfig = NULL;
uint32_t pixelClockToleranceHz, pixelClockMinHz, pixelClockMaxHz;
int64_t pixelClockToleranceFixed = pixel_clock;
int ret = 0;
pixelClockToleranceFixed *= kVideoPixelClkTolleranceNumerator;
pixelClockToleranceFixed /= kVideoPixelClkTolleranceDenomiator;
pixelClockToleranceHz = (uint32_t) pixelClockToleranceFixed;
// Uncomment to show the timings we finally decide to use.
debug(VIDEO,"Validating link\n");
debug(VIDEO,"mirror_mode: %d\n", data->mirror_mode);
debug(VIDEO,"test_mode: 0x%08x\n", data->test_mode);
debug(VIDEO,"color:\n");
debug(VIDEO," depth: %u\n", data->color.depth);
debug(VIDEO," space: %u\n", data->color.space);
debug(VIDEO," range: %u\n", data->color.range);
debug(VIDEO," coefficient: %u\n", data->color.coefficient);
debug(VIDEO,"timing:\n");
debug(VIDEO," interlaced: %d\n", data->timing.interlaced);
{
int i;
for (i = 0; i < kDisplayAxisCount; ++i) {
debug(VIDEO," Axis %d:\n", i);
debug(VIDEO," total: %u\n", data->timing.axis[i].total);
debug(VIDEO," active: %u\n", data->timing.axis[i].active);
debug(VIDEO," sync_width: %u\n", data->timing.axis[i].sync_width);
debug(VIDEO," back_porch: %u\n", data->timing.axis[i].back_porch);
debug(VIDEO," front_porch: %u\n", data->timing.axis[i].front_porch);
debug(VIDEO," sync_rate: %u (%uHz)\n",
data->timing.axis[i].sync_rate, data->timing.axis[i].sync_rate >> 16);
debug(VIDEO," sync_polarity: %u\n", data->timing.axis[i].sync_polarity);
}
}
// Interlaced video not supported
if (data->timing.interlaced) {
ret = -1;
debug(VIDEO,"interlaced mode not supported\n");
goto exit;
}
// Mirror mode is not supported
if (data->mirror_mode) {
ret = -1;
debug(VIDEO,"Mirror mode not supported\n");
goto exit;
}
// Verify that color mode is supported
ret = validate_video_color(color);
if (ret < 0) {
goto exit;
}
// Calculate target pixel clock range, clamping max value to _pixelClockLimit
pixelClockMinHz = pixel_clock - pixelClockToleranceHz;
pixelClockMaxHz = ulmin(pixel_clock + pixelClockToleranceHz, _pixelClockLimit);
// Verify that the target pixel clock is within the valid range
if (pixel_clock > _pixelClockLimit) {
ret = -1;
debug(VIDEO,"pixel clock value is too high, %lu\n", (unsigned long)pixel_clock);
goto exit;
}
// Verify that there is a PHY configuration for the pixel clock
phyConfig = find_PHY_config(pixel_clock, pixelClockMinHz, pixelClockMaxHz, data->color.depth);
if (phyConfig == NULL) {
ret = -1;
debug(VIDEO,"No PHY configuration for required pixel clock: %lu\n", (unsigned long)pixel_clock);
goto exit;
}
// Return the PHY configuration if requested
if ( pPHYConfig ) {
*pPHYConfig = phyConfig;
}
// Verify that timing generator parameters are supported
ret = validate_video_timing(h, v);
if (ret < 0) {
debug(VIDEO,"Timing not supported\n");
goto exit;
}
exit:
debug(ERROR,"ret=0x%08x\n", ret);
return ret;
}
void set_horizontal_blanking(uint32_t value)
{
// H_BLANK_0:1
write_regs(kHDMIModule, HDMI_H_BLANK_0, value, 2);
}
void set_vertical_blanking(uint32_t blanking, uint32_t total)
{
// V1_BLANK_0:1
write_regs(kHDMIModule, HDMI_V1_BLANK_0, blanking, 2);
// V2_BLANK_0:1
write_regs(kHDMIModule, HDMI_V2_BLANK_0, total, 2);
}
void set_line_lengths(uint32_t h_line, uint32_t v_line)
{
debug(VIDEO,"h_line %d, v_line %d\n", h_line, v_line);
// V_LINE_0:1
write_regs(kHDMIModule, HDMI_V_LINE_0, v_line, 2);
// H_LINE_0:1
write_regs(kHDMIModule, HDMI_H_LINE_0, h_line, 2);
}
void set_bottom_field_extents(uint32_t start, uint32_t end)
{
// V_BLANK_F0_0:1
write_regs(kHDMIModule, HDMI_V_BLANK_F0_0, start, 2);
// V_BLANK_F1_0:1
write_regs(kHDMIModule, HDMI_V_BLANK_F1_0, end, 2);
}
void set_horizontal_sync_shape(bool active_high, uint32_t start, uint32_t end)
{
// HSYNC_POL
write_reg(kHDMIModule, HDMI_HSYNC_POL, !active_high);
// H_SYNC_START_0:1
write_regs(kHDMIModule, HDMI_H_SYNC_START_0, start, 2);
// H_SYNC_END_0:1
write_regs(kHDMIModule, HDMI_H_SYNC_END_0, end, 2);
}
void set_vertical_sync_shape(bool active_high, uint32_t vsync1_start, uint32_t vsync1_end, uint32_t vsync2_start, uint32_t vsync2_end)
{
// VSYNC_POL
write_reg(kHDMIModule, HDMI_VSYNC_POL, !active_high);
// V_SYNC_LINE_BEF_1_0:1 - top field vsync start
write_regs(kHDMIModule, HDMI_V_SYNC_LINE_BEF_1_0, vsync1_start, 2);
// V_SYNC_LINE_BEF_2_0:1 - top field vsync end
write_regs(kHDMIModule, HDMI_V_SYNC_LINE_BEF_2_0, vsync1_end, 2);
// V_SYNC_LINE_AFT_1_0:1 - bottom field vsync start
write_regs(kHDMIModule, HDMI_V_SYNC_LINE_AFT_1_0, vsync2_start, 2);
// V_SYNC_LINE_AFT_2_0:1 - bottom field vsync end
write_regs(kHDMIModule, HDMI_V_SYNC_LINE_AFT_2_0, vsync2_end, 2);
}
static void set_bottom_field_transition_point(uint32_t start, uint32_t end)
{
// V_SYNC_LINE_AFT_PXL_1_0:1
write_regs(kHDMIModule, HDMI_V_SYNC_LINE_AFT_PXL_1_0, start, 2);
// V_SYNC_LINE_AFT_PXL_2_0:1
write_regs(kHDMIModule, HDMI_V_SYNC_LINE_AFT_PXL_2_0, end, 2);
}
static int configure_timing_generator(const struct video_link_data * data)
{
const struct video_timing_axis * h = &data->timing.axis[kDisplayAxisTypeHorizontal];
const struct video_timing_axis * v = &data->timing.axis[kDisplayAxisTypeVertical];
uint32_t h_blanking = h->total - h->active;
uint32_t v_blanking = v->total - v->active;
uint32_t v_bot_start = v->total + v_blanking + 1; // interlaced only
// Make "line 1" coincident with the first VSYNC, as in CEA formats
uint32_t vsync1_lineno = 1;
uint32_t vsync2_lineno = vsync1_lineno + v->total; // interlaced only
// Configure Timing Generator related registers
// TG_CMD
write_reg(kTGModule, TG_CMD, data->timing.interlaced ? TG_CMD_FIELD_EN : 0);
// TG_CBGEN
// Pixel repititon is not supported <rdar://problem/10712907>
// TG_H_FSZ_L:TG_H_FSZ_H
write_regs(kTGModule, TG_H_FSZ_L, h->total, 2);
// TG_HACT_ST_L:TG_HACT_ST_H
write_regs(kTGModule, TG_HACT_ST_L, h_blanking, 2);
// TG_HACT_SZ_L:TG_HACT_SZ_H
write_regs(kTGModule, TG_HACT_SZ_L, h->active, 2);
// TG_V_FSZ_L:TG_V_FSZ_H
write_regs(kTGModule, TG_V_FSZ_L, v->total, 2);
// TG_VSYNC_L:TG_VSYNC_H
write_regs(kTGModule, TG_VSYNC_L, vsync1_lineno, 2);
// TG_VSYNC2_L:TG_VSYNC2_H (interlaced only)
write_regs(kTGModule, TG_VSYNC2_L, vsync2_lineno, 2);
// TG_VACT_ST_L:TG_VACT_ST_H
write_regs(kTGModule, TG_VACT_ST_L, v_blanking, 2);
// TG_VACT_SZ_L:TG_VACT_SZ_H
write_regs(kTGModule, TG_VACT_SZ_L, v->active, 2);
// TG_FIELD_CHG_L:TG_FIELD_CHG_H (interlaced only)
write_regs(kTGModule, TG_FIELD_CHG_L, vsync2_lineno, 2);
// TG_VACT_ST2_L:TG_VACT_ST2_H (interlaced only)
write_regs(kTGModule, TG_VACT_ST2_L, v_bot_start, 2);
// Note: 3D (HDMI 1.4 TX only) not supported
// TG_VSYNC_TOP_HDMI_L:TG_VSYNC_TOP_HDMI_H
write_regs(kTGModule, TG_VSYNC_TOP_HDMI_L, vsync1_lineno, 2); // same as TG_VSYNC_L:TG_VSYNC_H
// TG_VSYNC_BOT_HDMI_L:TG_VSYNC_BOT_HDMI_H (interlaced only)
write_regs(kTGModule, TG_VSYNC_BOT_HDMI_L, vsync2_lineno, 2); // same as TG_VSYNC2_L:TG_VSYNC2_H
// TG_FIELD_TOP_HDMI_L:TG_FIELD_TOP_HDMI_H
write_regs(kTGModule, TG_FIELD_TOP_HDMI_L, vsync1_lineno, 2); // same as TG_VSYNC_L:TG_VSYNC_H
// TG_FIELD_BOT_HDMI_L:TG_FIELD_BOT_HDMI_H
write_regs(kTGModule, TG_FIELD_BOT_HDMI_L, vsync2_lineno, 2); // same as TG_VSYNC2_L:TG_VSYNC2_H
// TG_HDMI_TPGEN
// Pixel repititon is not supported <rdar://problem/10712907>
return 0;
}
static int configure_video_format(struct video_link_data * data)
{
const struct video_timing_axis * h = &data->timing.axis[kDisplayAxisTypeHorizontal];
const struct video_timing_axis * v = &data->timing.axis[kDisplayAxisTypeVertical];
uint32_t h_blanking = h->total - h->active;
uint32_t v_blanking = v->total - v->active;
uint32_t v_line = data->timing.interlaced ? (v->total * 2 + 1) : v->total;
uint32_t v_bot_start = v->total + v_blanking + 1; // interlaced only
uint32_t hsync_start = h->front_porch - 2; // There is apparently an implicit +2 added to HSYNC_START (compare manual recommended values to CEA-861-D formats)
uint32_t hsync_end = hsync_start + h->sync_width;
uint32_t vsync1_start = v->front_porch;
uint32_t vsync1_end = vsync1_start + v->sync_width;
uint32_t vsync2_start = v->total + vsync1_start; // interlaced only
uint32_t vsync2_end = v->total + vsync1_end; // interlaced only
// The +0.5 vertical lines offset, not included in vsync2_start/end
uint32_t vsync2_h_start = (h->total / 2) + h->front_porch; // interlaced only
uint32_t vsync2_h_end = vsync2_h_start; // interlaced only
uint64_t const64 = 1000;
// Calculate frame interval rounded (half up) to nearest millisecond
frame_interval_MS = (((const64 << 32) / v->sync_rate) + 0x8000) >> 16;
// Configure registers related to video format
set_horizontal_blanking(h_blanking);
set_vertical_blanking(v_blanking, v->total);
set_line_lengths(h->total, v_line);
// INT_PRO_MODE
write_reg(kHDMIModule, HDMI_INT_PRO_MODE, data->timing.interlaced);
set_bottom_field_extents(v_bot_start, v_line); // interlaced only
set_horizontal_sync_shape(h->sync_polarity, hsync_start, hsync_end);
set_vertical_sync_shape(v->sync_polarity, vsync1_start, vsync1_end, vsync2_start, vsync2_end);
set_bottom_field_transition_point(vsync2_h_start, vsync2_h_end);
// HDMI_CON_1[Pxl_rep_num]
// Pixel repititon is not supported <rdar://problem/10712907>
return 0;
}
static void set_mute_pixel(struct pixel pixel)
{
// BLUE_SCREEN_[RGB]_0 - upper half of the least significant 8 bits
write_reg(kHDMIModule, HDMI_BLUE_SCREEN_R_0, (pixel.r >> 4) & 0x0F);
write_reg(kHDMIModule, HDMI_BLUE_SCREEN_G_0, (pixel.g >> 4) & 0x0F);
write_reg(kHDMIModule, HDMI_BLUE_SCREEN_B_0, (pixel.b >> 4) & 0x0F);
// BLUE_SCREEN_[RGB]_1 - the most significant 8 bits
write_reg(kHDMIModule, HDMI_BLUE_SCREEN_R_1, pixel.r >> 8);
write_reg(kHDMIModule, HDMI_BLUE_SCREEN_G_1, pixel.g >> 8);
write_reg(kHDMIModule, HDMI_BLUE_SCREEN_B_1, pixel.b >> 8);
}
static void set_color_space(int space)
{
// Enable or disable YCbCr4:2:2 mode (RGB4:4:4/YCbCr4:4:4 are handled identically)
set_bits_in_reg(kHDMIModule, HDMI_CON_0, 0, HDMI_CON_0_YCBCR422_SEL, space == kDisplayColorSpacesYCbCr422 ? HDMI_CON_0_YCBCR422_SEL : 0);
}
static int configure_video_pattern(const struct video_link_data * data)
{
// enable video pattern generation
if ( data->test_mode ) {
or_reg(kHDMIModule, HDMI_VIDEO_PATTERN_GEN, HDMI_VIDEO_PATTERN_GEN_PATTERN_ENABLE);
debug(VIDEO,"Video pattern generation enabled.\n");
} else {
and_reg(kHDMIModule, HDMI_VIDEO_PATTERN_GEN, ~(HDMI_VIDEO_PATTERN_GEN_PATTERN_ENABLE));
}
return 0;
}
static int set_tx_output_mode(int mode)
{
uint32_t dvi_mask = HDMI_CON_2_VID_PERIOD_EN | HDMI_CON_2_DVI_BAND_EN;
uint8_t transtaled_mode = (mode == kHDMI_tx_mode_HDMI) ? HDMI_MODE_SEL_MODE_HDMI : HDMI_MODE_SEL_MODE_DVI;
debug(VIDEO,"%s %d:, Mode %s\n", __FUNCTION__, __LINE__, (mode == kHDMI_tx_mode_HDMI) ? "HDMI" : "DVI");
// MODE_SEL[1:0] = mode
set_bits_in_reg(kHDMIModule, HDMI_MODE_SEL, HDMI_MODE_SEL_MODE_SHIFT, HDMI_MODE_SEL_MODE_MASK, transtaled_mode);
// Configure video preamble and guard band
// HDMI_CON_2[Vid_Period_En] = 1 if DVI mode, 0 otherwise
// HDMI_CON_2[Dvi_Band_En] = 1 if DVI mode, 0 otherwise
set_bits_in_reg(kHDMIModule, HDMI_CON_2, 0, dvi_mask, (mode == kHDMI_tx_mode_DVI) ? dvi_mask : 0);
return 0;
}
static inline int set_PHY_config_done(bool done)
{
write_phy_config_reg(kPHYConfigRegModeSet, done ? PHY_CONFIG_MODE_SET_DONE : PHY_CONFIG_MODE_SET_IN_PROGRESS);
return 0;
}
static int configure_PHY(const HDMITXPHYConfig * config)
{
uint8_t buf[kPHYConfigRegCount];
int ret = 0;
int reg;
// Sanity check (if this fails, it is a programming error)
if (sizeof (*config) != (sizeof (buf) - 2)) {
ret = -1;
goto exit;
}
// Copy config into data buffer; initialize first & last bytes from registers
buf[kPHYConfigReg00] = read_phy_reg(kPHYConfigReg00);
buf[kPHYConfigReg07] = read_phy_reg(kPHYConfigReg07);
memcpy((uint8_t *)buf + 1, config, sizeof (*config));
debug(VIDEO,"PHY_STATUS=0x%08x\n", read_reg(kCtrlModule, CTRL_PHY_STATUS_0));
// Configure the PHY
ret = set_PHY_config_done(false);
if (ret < 0) {
debug(ERROR,"set_PHY_config_done: ret %d\n", ret);
goto exit;
}
for (reg = kPHYConfigReg00; reg != kPHYConfigRegCount; reg++) {
ret = write_phy_config_reg(reg, buf[reg]);
if (ret < 0) {
debug(ERROR,"write_phy_config_reg: ret %d\n", ret);
goto exit;
}
}
ret = set_PHY_config_done(true);
if (ret < 0) {
debug(ERROR,"set_PHY_config_done: ret %d\n", ret);
goto exit;
}
debug(VIDEO,"PHY_STATUS=0x%08x\n", read_reg(kCtrlModule, CTRL_PHY_STATUS_0));
exit:
return ret;
}
static int wait_for_PHY_ready()
{
//TODO
while (get_is_PHY_ready() == false) {
task_sleep(kPHYReadyPollIntervalMS);
}
return 0;
}
static int set_timing_generator_enabled(bool enabled)
{
// TG_CMD[TG_EN] = enabled
set_bits_in_reg(kTGModule, TG_CMD, 0, TG_CMD_TG_EN, enabled ? TG_CMD_TG_EN : 0);
return 0;
}
static int configure_video_color(const struct video_link_data * data)
{
static const struct pixel kBlackPixelRGB =
{
.r = 0,
.g = 0,
.b = 0
};
static const struct pixel kBlackPixelYCbCr =
{
.g = 16 << 8, // Y
.b = 128 << 8, // Cb
.r = 128 << 8 // Cr
};
const struct video_color_data * color = &data->color;
uint32_t limit_mode;
uint32_t deep_color_mode;
// Configure the color space
set_color_space(color->space);
// Configure "blue screen" (actually black, not blue) pixel value
set_mute_pixel((color->space == kDisplayColorSpacesRGB) ? kBlackPixelRGB : kBlackPixelYCbCr);
// Configure Color space and Pixel limitation
//
// Note: This allows a full-range YCbCr configuration, though HDMI v1.3
// section 6.6 says "YCBCR components shall always be Limited Range"
//
if ( color->range == kDisplayColorDynamicRangeFull ) {
limit_mode = HDMI_CON_1_PXL_LMT_CTRL_BYPASS_MODE;
} else {
write_reg(kHDMIModule, HDMI_YMIN, kVideoColorLimitedRangeYMin);
write_reg(kHDMIModule, HDMI_YMAX, kVideoColorLimitedRangeYMax);
if ( color->space == kDisplayColorSpacesRGB ) {
limit_mode = HDMI_CON_1_PXL_LMT_CTRL_RGB_MODE;
} else {
limit_mode = HDMI_CON_1_PXL_LMT_CTRL_YCBCR_MODE;
write_reg(kHDMIModule, HDMI_CMIN, kVideoColorLimitedRangeCMin);
write_reg(kHDMIModule, HDMI_CMAX, kVideoColorLimitedRangeCMax);
}
}
set_bits_in_reg(kHDMIModule, HDMI_CON_1, HDMI_CON_1_PXL_LMT_CTRL_SHIFT, HDMI_CON_1_PXL_LMT_CTRL_MASK, limit_mode);
// Configure Color depth
switch ( color->depth ) {
case 12:
deep_color_mode = HDMI_DC_CONTROL_DEEP_COLOR_MODE_12_BPC;
break;
case 10:
deep_color_mode = HDMI_DC_CONTROL_DEEP_COLOR_MODE_10_BPC;
break;
default:
deep_color_mode = HDMI_DC_CONTROL_DEEP_COLOR_MODE_8_BPC;
break;
}
set_bits_in_reg(kHDMIModule, HDMI_DC_CONTROL, HDMI_DC_CONTROL_DEEP_COLOR_MODE_SHIFT, HDMI_DC_CONTROL_DEEP_COLOR_MODE_MASK, deep_color_mode);
return 0;
}
static int prepare_video_link(struct video_link_data *data, uint32_t pixel_clock)
{
const HDMITXPHYConfig * phyConfig;
int tx_mode = kHDMI_tx_mode_HDMI;
int ret;
ret = validate_video_link(data, pixel_clock, &phyConfig);
require_noerr(ret, exit);
// Use DVI mode if downstream port type is not HDMI
tx_mode = get_edid_downstream_type();
ret = configure_video_format(data);
require_noerr(ret, exit);
ret = configure_timing_generator(data);
require_noerr_action(ret, exit, debug(ERROR,"configure_timing_generator: ret %d\n", ret));
ret = configure_video_color(data);
require_noerr_action(ret, exit, debug(ERROR,"configure_timing_generator: ret %d\n", ret));
ret = configure_video_pattern(data);
require_noerr_action(ret, exit, debug(ERROR,"configure_video_pattern: ret %d\n", ret));
ret = set_tx_output_mode(tx_mode);
require_noerr_action(ret, exit, debug(ERROR,"set_tx_output_mode: ret %d\n", ret));
// Note: PHY configuration includes enabling TMDS power.
ret = configure_PHY(phyConfig);
require_noerr_action(ret, exit, debug(ERROR,"configure_PHY: ret %d\n", ret));
ret = wait_for_PHY_ready();
require_noerr_action(ret, exit, debug(ERROR,"wait_for_PHY_ready: ret %d\n", ret));
set_TMDS_power_enabled(false);
// Enable timing generator
ret = set_timing_generator_enabled(true);
require_noerr_action(ret, exit, debug(ERROR,"set_timing_generator_enabled: ret %d\n", ret));
return 0;
exit:
debug(ERROR,"error\n");
return -1;
}
static void start_info_frame(struct display_infoframe *infoFrame)
{
// Configure the InfoFrame header, if needed.
uint8_t header[] = {infoFrame->type, infoFrame->version, infoFrame->length} ;
write_regs_with_data(kHDMIModule, HDMI_AVI_HEADER0, header, sizeof header);
// Set the InfoFrame checksum
write_reg(kHDMIModule, HDMI_AVI_CHECK_SUM, infoFrame->checksum);
// Set the InfoFrame data
write_regs_with_data(kHDMIModule, HDMI_AVI_BYTE01, infoFrame->data, infoFrame->length);
// Configure InfoFrame to be sent at every VSYNC
set_bits_in_reg(kHDMIModule, HDMI_AVI_CON, HDMI_PKT_CON_PKT_TX_CON_SHIFT, HDMI_PKT_CON_PKT_TX_CON_MASK, HDMI_PKT_CON_PKT_TX_CON_SEND_ALWAYS);
}
void stop_info_frame(struct display_infoframe * infoFrame)
{
// Disable transmission of the InfoFrame
set_bits_in_reg(kHDMIModule, HDMI_AVI_CON, HDMI_PKT_CON_PKT_TX_CON_SHIFT, HDMI_PKT_CON_PKT_TX_CON_MASK, HDMI_PKT_CON_PKT_TX_CON_DISABLED);
}
static int get_tx_output_mode()
{
uint32_t reg;
int mode;
reg = get_bits_in_reg(kHDMIModule, HDMI_MODE_SEL, HDMI_MODE_SEL_MODE_SHIFT, HDMI_MODE_SEL_MODE_MASK);
mode = reg == HDMI_MODE_SEL_MODE_HDMI ? kHDMI_tx_mode_HDMI : kHDMI_tx_mode_DVI;
debug(VIDEO,"%s %d:, Mode %s\n", __FUNCTION__, __LINE__, (mode == kHDMI_tx_mode_HDMI) ? "HDMI" : "DVI");
return mode;
}
static int start_general_control_packet(const struct video_link_data * data)
{
const struct video_color_data * color = &data->color;
uint32_t gcp_cd;
bool setSupportsDeepColor = false;
// Clear GCP data registers
write_regs(kHDMIModule, HDMI_GCP_BYTE1, (uint32_t)0, 3);
// Note: HDMI v1.3, section 6.5.3 says:
//
// Sources shall only send GCPs with non-zero CD to Sinks that indicate
// support for Deep Color...
//
// Once a Source sends a GCP with non-zero CD to a sink, it should continue
// sending GCPs with non-zero CD at least once per video field even if
// reverting to 24-bit color, as long as the Sink continues to support
// Deep Color.
// Set color depth (CD) field of General Control Packet
switch ( color->depth ) {
case 12:
gcp_cd = GCP_SB1_CD_36_BPP;
setSupportsDeepColor = true;
break;
case 10:
gcp_cd = GCP_SB1_CD_30_BPP;
setSupportsDeepColor = true;
break;
default:
gcp_cd = GCP_SB1_CD_24_BPP;
break;
}
// Set Color Depth
set_bits_in_reg(kHDMIModule, HDMI_GCP_BYTE2, GCP_SB1_CD_SHIFT, GCP_SB1_CD_MASK,
setSupportsDeepColor ? gcp_cd : GCP_SB1_CD_NONE);
// Set Transmission Frequency
// Always set both VSYNC enables <rdar://problem/11672667>
set_bits_in_reg(kHDMIModule, HDMI_GCP_CON, HDMI_GCP_CON_VSYNC_SHIFT, HDMI_GCP_CON_VSYNC_MASK, HDMI_GCP_CON_VSYNC_BOTH_FIELDS);
// Configure GCP to be sent at every VSYNC
// GCP_CON[GCP_CON] = (every VSYNC)
set_bits_in_reg(kHDMIModule, HDMI_GCP_CON, HDMI_PKT_CON_PKT_TX_CON_SHIFT, HDMI_PKT_CON_PKT_TX_CON_MASK, HDMI_PKT_CON_PKT_TX_CON_SEND_ALWAYS);
return 0;
}
void stop_general_control_packet()
{
set_bits_in_reg(kHDMIModule, HDMI_GCP_CON, HDMI_PKT_CON_PKT_TX_CON_SHIFT, HDMI_PKT_CON_PKT_TX_CON_MASK, HDMI_PKT_CON_PKT_TX_CON_DISABLED);
// Clear GCP data registers
write_regs(kHDMIModule, HDMI_GCP_BYTE1, (uint32_t)0, 3);
}
void set_av_mute_enabled(bool enabled)
{
set_bits_in_reg(kHDMIModule, HDMI_GCP_BYTE1, 0, GCP_SB0_CLR_AVMUTE|GCP_SB0_SET_AVMUTE, enabled ? GCP_SB0_SET_AVMUTE : GCP_SB0_CLR_AVMUTE);
// Allow time for updated GCP to be transmitted
task_sleep(frame_interval_MS + 1);
debug(VIDEO,"AV MUTE %s after %u ms\n", enabled ? "enabled" : "disabled", (unsigned int)(frame_interval_MS + 1));
}
static int start_video_link(struct video_link_data *data)
{
int tx_mode = get_tx_output_mode();
int ret;
if ( tx_mode != kHDMI_tx_mode_DVI ) {
// start the AVI InfoFrame
start_info_frame(&aviInfoFrame);
// start the general control packet (GCP)
ret = start_general_control_packet(data);
require_noerr(ret, exit);
// Enable Set_AVMUTE before enabling HDMI TX output
set_av_mute_enabled(true);
}
// Enable HDMI core output (to PHY)
ret = set_tx_output_enabled(true);
require_noerr(ret, exit);
task_sleep(kTMDSMinimumPowerOffIntervalMS);
set_TMDS_power_enabled(true);
ret = wait_for_PHY_ready();
require_noerr(ret, exit);
if ( tx_mode != kHDMI_tx_mode_DVI ) {
// Allow time for video to stabilize before unmute
task_sleep(kVideoStabilizationIntervalMS);
// Send Clear_AVMUTE
set_av_mute_enabled(false);
}
debug(VIDEO,"Finished Configuring Video\n");
_current_video_link = *data;
exit:
return ret;
}
static int complete_video_link()
{
int ret;
// Disable timing generator
ret = set_timing_generator_enabled(false);
require_noerr(ret, exit);
// power down HDMI PHY, keeping HPD enabled
power_down_phy(false);
// Reset TX mode
ret = set_tx_output_mode(kHDMI_tx_mode_None);
require_noerr(ret, exit);
exit:
return ret;
}
static int stop_video_link()
{
int tx_mode = get_tx_output_mode();
int ret = 0;
if ( tx_mode != kHDMI_tx_mode_DVI ) {
// Enable AVMUTE
set_av_mute_enabled(true);
}
bzero(&_current_video_link, sizeof(_current_video_link));
// Disable PHY TMDS power (see POWER DOWN SEQUENCE in HDMI TX V1.4 manual)
set_TMDS_power_enabled(false);
task_sleep(kTMDSMinimumPowerOffIntervalMS);
if ( tx_mode != kHDMI_tx_mode_DVI ) {
// Clear TX internal AVMUTE state
set_av_mute_enabled(false);
}
ret = set_tx_output_enabled(false);
require_noerr_action(ret, exit, debug(ERROR,"for set_tx_output_enabled: ret %d\n", ret));
if ( tx_mode != kHDMI_tx_mode_DVI ) {
struct display_infoframe local_aviInfoFrame; // dummy
// stop the general control packet (GCP)
stop_general_control_packet();
// Stop info frame
stop_info_frame(&local_aviInfoFrame);
}
exit:
return ret;
}
static int set_tx_output_enabled(bool enabled)
{
// HDMI_CON_0[SYSTEM_EN] = enabled
set_bits_in_reg(kHDMIModule, HDMI_CON_0, 0, HDMI_CON_0_SYSTEM_EN, enabled ? HDMI_CON_0_SYSTEM_EN : 0);
return 0;
}
static int configure_video_mode(struct video_link_data *data, uint32_t pixel_clock)
{
int ret;
ret = prepare_video_link(data, pixel_clock);
if (ret < 0) {
debug(ERROR,"failed to prepare_video_link\n");
return -1;
}
ret = start_video_link(data);
if (ret < 0) {
debug(ERROR,"failed to start_video_link\n");
return -1;
}
return 0;
}
static void configure_video_bist(struct video_link_data *data)
{
// enable bist mode
if ( data->test_mode ) {
or_reg(kHDMIModule, HDMI_VIDEO_PATTERN_GEN, HDMI_VIDEO_PATTERN_GEN_PATTERN_ENABLE);
debug(VIDEO, "Finished Video BIST\n");
} else {
and_reg(kHDMIModule, HDMI_VIDEO_PATTERN_GEN, ~(HDMI_VIDEO_PATTERN_GEN_PATTERN_ENABLE));
}
}
#define HDMI_REG_OFFSET(size, m, o) \
*(volatile size *)(__base_address +_registers[m] + o)
static uint8_t read_reg(uint8_t module, uint32_t offset)
{
uint8_t val;
uint32_t addr;
addr = __base_address + _registers[module] + offset;
val = HDMI_REG_OFFSET(uint8_t, module, offset);
debug(REG, "module=%d reg=0x%08x addr=0x%08x value=0x%08x\n", module, offset, addr, val);
return(val);
}
static uint32_t read_reg32(uint8_t module, u_int32_t offset)
{
uint32_t val;
uint32_t addr;
addr = __base_address + _registers[module] + offset;
val = HDMI_REG_OFFSET(uint32_t, module, offset);
debug(REG,"module=%d addr=0x%08x reg=0x%08x value=0x%08x\n", module, addr, offset, val);
return(val);
}
static void write_reg(uint8_t module, uint32_t offset, uint8_t value)
{
uint32_t addr;
addr = __base_address + _registers[module] + offset;
HDMI_REG_OFFSET(uint8_t, module, offset) = value;
debug(REG,"module=%d reg=0x%08x addr=0x%08x value=0x%08x\n", module, offset, addr, value);
}
static void write_reg32(uint8_t module, u_int32_t offset, uint32_t value)
{
uint32_t addr;
addr = __base_address + _registers[module] + offset;
HDMI_REG_OFFSET(uint32_t, module, offset) = value;
debug(REG,"module=%d reg=0x%08x addr=0x%08x value=0x%08x\n", module, offset, addr, value);
}
static void and_reg(uint8_t module, uint32_t offset, uint8_t value)
{
uint32_t addr;
uint8_t reg;
addr = __base_address + _registers[module] + offset;
reg = read_reg(module, offset);
reg &= value;
write_reg(module, offset, reg);
}
static void or_reg(uint8_t module, uint32_t offset, uint8_t value)
{
uint32_t addr;
uint8_t reg;
addr = __base_address + _registers[module] + offset;
reg = read_reg(module, offset);
reg |= value;
write_reg(module, offset, reg);
}
static void set_bits_in_reg(uint8_t module, uint32_t offset, uint8_t pos, uint8_t mask, uint8_t value)
{
uint8_t set = read_reg(module, offset);
set &= ~mask;
set |= (value << pos);
write_reg(module, offset, set);
}
static uint32_t get_bits_in_reg(uint8_t module, uint32_t reg, uint32_t pos, uint32_t mask)
{
return (read_reg(module, reg) & mask) >> pos;
}
static uint8_t read_phy_reg(uint8_t reg)
{
uint32_t Register;
uint8_t data;
uint8_t offset;
uint8_t shift;
offset = reg / 4;
shift = reg % 4;
Register = *(volatile uint32_t *)((uintptr_t)_phyConfigRegs[offset]);
//Now normalize its value
data = Register >> shift;
debug(REG, "read_phy_reg: hdmiphycon%x := 0x%08x addr 0x%08x\n", offset, data, _phyConfigRegs[offset]);
return data;
}
#define HDMI_IO_RETRIES 20
bool get_is_PHY_write_in_progress(void)
{
uint32_t status;
int i;
for (i = 0; i < HDMI_IO_RETRIES; i++) {
task_sleep(1); //MS
status = read_reg(kI2CModule, HDMIPHYCON_STAT);
if (!(status & HDMIPHYCON_STAT_IN_PROGRESS))
break;
debug(REG, "Retry: %d HDMIPHYCON_STAT 0x%08x\n", i, status);
}
return (status & HDMIPHYCON_STAT_IN_PROGRESS);
}
#define HDMI_IO_TIMEOUT_US (10 * 1000)
static int wait_for_PHY_write_completion()
{
return (get_is_PHY_write_in_progress() ? -1 : 0);
}
int write_phy_reg(uint32_t reg, const uint8_t data)
{
int ret = 0;
uint8_t offset;
uint8_t shift;
require_action((reg < PHY_CONFIG_REG_COUNT), exit, ret = -1);
require_action(!get_is_PHY_write_in_progress(), exit, ret = -1);
if (reg == kPHYConfigRegModeSet) {
offset = kPHYConfigRegModeSet;
shift = 0;
} else {
offset = reg / 4;
shift = (reg % 4) * 8;
}
//Put it in position
*(volatile uint32_t *)((uintptr_t)_phyConfigRegs[offset]) = ((*(volatile uint32_t *)((uintptr_t)_phyConfigRegs[offset])) & ~(0xFF << shift)) | (data << shift);
if (!bypass_wait && wait_for_PHY_write_completion()) {
debug(INT, "%s %d: done waiting for stop event\n", __FUNCTION__, __LINE__);
}
debug(REG, "%s: reg 0x%08x := 0x%08x\n", __FUNCTION__, reg, *(volatile uint32_t *)((uintptr_t)_phyConfigRegs[offset]));
exit:
return ret;
}
static int write_phy_config_reg(PHYConfigReg reg, uint32_t data)
{
int ret = 0;
uint32_t Register;
if (get_is_PHY_write_in_progress()) {
debug(ERROR, "%s: Failed cuz write is in progress\n", __FUNCTION__);
ret = -1;
goto exit;
}
uint8_t offset;
uint8_t shift;
offset = reg / 4;
shift = (reg % 4) * 8;
//Put it in position
Register = *(volatile uint32_t *)((uintptr_t)_phyConfigRegs[offset]);
Register &= ~(0xFF << shift);
Register |= (data << shift);
*(volatile uint32_t *)((uintptr_t)_phyConfigRegs[offset]) = Register;
debug(REG, "HDMIPHYCON%u := 0x%08x\n", offset, Register);
debug(REG, "reg 0x%08x addr 0x%p := 0x%08x\n", reg, (volatile uint32_t *)((uintptr_t)_phyConfigRegs[offset]), Register);
if (wait_for_PHY_write_completion()) {
debug(REG, "%s %d: done waiting for stop event\n", __FUNCTION__, __LINE__);
}
exit:
return ret;
}
static void and_phy_reg(uint32_t offset, uint8_t data)
{
uint8_t value;
value = read_phy_reg(offset);
value &= data;
write_phy_reg(offset, value);
}
static void or_phy_reg(uint32_t offset, uint8_t data)
{
uint8_t value;
value = read_phy_reg(offset);
value |= data;
write_phy_reg(offset, value);
}
void write_regs_with_data(uint8_t module, uint32_t basereg, uint8_t * data, unsigned int length)
{
for (unsigned int i = 0; i < length; i++) {
write_reg(module, basereg + (4 * i), data[i]);
}
}
void write_regs(uint8_t module, uint32_t basereg, uint32_t value, unsigned int length)
{
for (unsigned int i = 0; i < length; i++) {
write_reg(module, basereg + (4 * i), value & 0xff);
value >>= 8;
}
}