M7350/kernel/drivers/video/msm/mdss/mdss_hdmi_util.c
2024-09-09 08:57:42 +00:00

1739 lines
42 KiB
C

/* Copyright (c) 2010-2015, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/io.h>
#include <linux/delay.h>
#include "mdss_hdmi_util.h"
#define RESOLUTION_NAME_STR_LEN 30
#define HDMI_SEC_TO_MS 1000
#define HDMI_MS_TO_US 1000
#define HDMI_SEC_TO_US (HDMI_SEC_TO_MS * HDMI_MS_TO_US)
#define HDMI_KHZ_TO_HZ 1000
#define HDMI_BUSY_WAIT_DELAY_US 100
#define HDMI_SCDC_UNKNOWN_REGISTER "Unknown register"
static char res_buf[RESOLUTION_NAME_STR_LEN];
enum trigger_mode {
TRIGGER_WRITE,
TRIGGER_READ
};
int hdmi_utils_get_timeout_in_hysnc(struct msm_hdmi_mode_timing_info *timing,
u32 timeout_ms)
{
u32 fps, v_total;
u32 time_taken_by_one_line_us, lines_needed_for_given_time;
if (!timing || !timeout_ms) {
pr_err("invalid input\n");
return -EINVAL;
}
fps = timing->refresh_rate / HDMI_KHZ_TO_HZ;
v_total = hdmi_tx_get_v_total(timing);
/*
* pixel clock = h_total * v_total * fps
* 1 sec = pixel clock number of pixels are transmitted.
* time taken by one line (h_total) = 1 / (v_total * fps).
*/
time_taken_by_one_line_us = HDMI_SEC_TO_US / (v_total * fps);
lines_needed_for_given_time = (timeout_ms * HDMI_MS_TO_US) /
time_taken_by_one_line_us;
return lines_needed_for_given_time;
}
static int hdmi_ddc_clear_irq(struct hdmi_tx_ddc_ctrl *ddc_ctrl,
char *what)
{
u32 ddc_int_ctrl, ddc_status, in_use, timeout;
u32 sw_done_mask = BIT(2);
u32 sw_done_ack = BIT(1);
u32 hw_done_mask = BIT(6);
u32 hw_done_ack = BIT(5);
u32 in_use_by_sw = BIT(0);
u32 in_use_by_hw = BIT(1);
if (!ddc_ctrl || !ddc_ctrl->io) {
pr_err("invalid input\n");
return -EINVAL;
}
/* clear and enable interrutps */
ddc_int_ctrl = sw_done_mask | sw_done_ack | hw_done_mask | hw_done_ack;
DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_INT_CTRL, ddc_int_ctrl);
/* wait until DDC HW is free */
timeout = 100;
do {
ddc_status = DSS_REG_R_ND(ddc_ctrl->io, HDMI_DDC_HW_STATUS);
in_use = ddc_status & (in_use_by_sw | in_use_by_hw);
if (in_use) {
pr_debug("ddc is in use by %s, timeout(%d)\n",
ddc_status & in_use_by_sw ? "sw" : "hw",
timeout);
udelay(100);
}
} while (in_use && --timeout);
if (!timeout) {
pr_err("%s: timedout\n", what);
return -ETIMEDOUT;
}
return 0;
}
static void hdmi_scrambler_ddc_reset(struct hdmi_tx_ddc_ctrl *ctrl)
{
u32 reg_val;
if (!ctrl) {
pr_err("Invalid parameters\n");
return;
}
/* clear ack and disable interrupts */
reg_val = BIT(14) | BIT(9) | BIT(5) | BIT(1);
DSS_REG_W(ctrl->io, HDMI_DDC_INT_CTRL2, reg_val);
/* Reset DDC timers */
reg_val = BIT(0) | DSS_REG_R(ctrl->io, HDMI_SCRAMBLER_STATUS_DDC_CTRL);
DSS_REG_W(ctrl->io, HDMI_SCRAMBLER_STATUS_DDC_CTRL, reg_val);
reg_val = DSS_REG_R(ctrl->io, HDMI_SCRAMBLER_STATUS_DDC_CTRL);
reg_val &= ~BIT(0);
DSS_REG_W(ctrl->io, HDMI_SCRAMBLER_STATUS_DDC_CTRL, reg_val);
}
void hdmi_scrambler_ddc_disable(struct hdmi_tx_ddc_ctrl *ctrl)
{
u32 reg_val;
if (!ctrl) {
pr_err("Invalid parameters\n");
return;
}
hdmi_scrambler_ddc_reset(ctrl);
/* Disable HW DDC access to RxStatus register */
reg_val = DSS_REG_R(ctrl->io, HDMI_HW_DDC_CTRL);
reg_val &= ~(BIT(8) | BIT(9));
DSS_REG_W(ctrl->io, HDMI_HW_DDC_CTRL, reg_val);
}
static int hdmi_scrambler_ddc_check_status(struct hdmi_tx_ddc_ctrl *ctrl)
{
int rc = 0;
u32 reg_val;
if (!ctrl) {
pr_err("invalid ddc ctrl\n");
return -EINVAL;
}
/* check for errors and clear status */
reg_val = DSS_REG_R(ctrl->io, HDMI_SCRAMBLER_STATUS_DDC_STATUS);
if (reg_val & BIT(4)) {
pr_err("ddc aborted\n");
reg_val |= BIT(5);
rc = -ECONNABORTED;
}
if (reg_val & BIT(8)) {
pr_err("timed out\n");
reg_val |= BIT(9);
rc = -ETIMEDOUT;
}
if (reg_val & BIT(12)) {
pr_err("NACK0\n");
reg_val |= BIT(13);
rc = -EIO;
}
if (reg_val & BIT(14)) {
pr_err("NACK1\n");
reg_val |= BIT(15);
rc = -EIO;
}
DSS_REG_W(ctrl->io, HDMI_SCRAMBLER_STATUS_DDC_STATUS, reg_val);
return rc;
}
static int hdmi_scrambler_status_timer_setup(struct hdmi_tx_ddc_ctrl *ctrl,
u32 timeout_hsync)
{
u32 reg_val;
int rc;
struct dss_io_data *io = NULL;
if (!ctrl || !ctrl->io) {
pr_err("invalid input\n");
return -EINVAL;
}
io = ctrl->io;
hdmi_ddc_clear_irq(ctrl, "scrambler");
DSS_REG_W(io, HDMI_SCRAMBLER_STATUS_DDC_TIMER_CTRL, timeout_hsync);
DSS_REG_W(io, HDMI_SCRAMBLER_STATUS_DDC_TIMER_CTRL2, timeout_hsync);
reg_val = DSS_REG_R(io, HDMI_DDC_INT_CTRL5);
reg_val |= BIT(10);
DSS_REG_W(io, HDMI_DDC_INT_CTRL5, reg_val);
reg_val = DSS_REG_R(io, HDMI_DDC_INT_CTRL2);
/* Trigger interrupt if scrambler status is 0 or DDC failure */
reg_val |= BIT(10);
reg_val &= ~(BIT(15) | BIT(16));
reg_val |= BIT(16);
DSS_REG_W(io, HDMI_DDC_INT_CTRL2, reg_val);
/* Enable DDC access */
reg_val = DSS_REG_R(io, HDMI_HW_DDC_CTRL);
reg_val &= ~(BIT(8) | BIT(9));
reg_val |= BIT(8);
DSS_REG_W(io, HDMI_HW_DDC_CTRL, reg_val);
/* WAIT for 200ms as per HDMI 2.0 standard for sink to respond */
msleep(200);
/* clear the scrambler status */
rc = hdmi_scrambler_ddc_check_status(ctrl);
if (rc)
pr_err("scrambling ddc error %d\n", rc);
hdmi_scrambler_ddc_disable(ctrl);
return rc;
}
static inline char *hdmi_scdc_reg2string(u32 type)
{
switch (type) {
case HDMI_TX_SCDC_SCRAMBLING_STATUS:
return "HDMI_TX_SCDC_SCRAMBLING_STATUS";
case HDMI_TX_SCDC_SCRAMBLING_ENABLE:
return "HDMI_TX_SCDC_SCRAMBLING_ENABLE";
case HDMI_TX_SCDC_TMDS_BIT_CLOCK_RATIO_UPDATE:
return "HDMI_TX_SCDC_TMDS_BIT_CLOCK_RATIO_UPDATE";
case HDMI_TX_SCDC_CLOCK_DET_STATUS:
return "HDMI_TX_SCDC_CLOCK_DET_STATUS";
case HDMI_TX_SCDC_CH0_LOCK_STATUS:
return "HDMI_TX_SCDC_CH0_LOCK_STATUS";
case HDMI_TX_SCDC_CH1_LOCK_STATUS:
return "HDMI_TX_SCDC_CH1_LOCK_STATUS";
case HDMI_TX_SCDC_CH2_LOCK_STATUS:
return "HDMI_TX_SCDC_CH2_LOCK_STATUS";
case HDMI_TX_SCDC_CH0_ERROR_COUNT:
return "HDMI_TX_SCDC_CH0_ERROR_COUNT";
case HDMI_TX_SCDC_CH1_ERROR_COUNT:
return "HDMI_TX_SCDC_CH1_ERROR_COUNT";
case HDMI_TX_SCDC_CH2_ERROR_COUNT:
return "HDMI_TX_SCDC_CH2_ERROR_COUNT";
case HDMI_TX_SCDC_READ_ENABLE:
return"HDMI_TX_SCDC_READ_ENABLE";
default:
return HDMI_SCDC_UNKNOWN_REGISTER;
}
}
static struct msm_hdmi_mode_timing_info hdmi_resv_timings[
RESERVE_VFRMT_END - HDMI_VFRMT_RESERVE1 + 1];
static int hdmi_get_resv_timing_info(
struct msm_hdmi_mode_timing_info *mode, int id)
{
int i;
for (i = 0; i < ARRAY_SIZE(hdmi_resv_timings); i++) {
struct msm_hdmi_mode_timing_info *info = &hdmi_resv_timings[i];
if (info->video_format == id) {
*mode = *info;
return 0;
}
}
return -EINVAL;
}
int hdmi_set_resv_timing_info(struct msm_hdmi_mode_timing_info *mode)
{
int i;
for (i = 0; i < ARRAY_SIZE(hdmi_resv_timings); i++) {
struct msm_hdmi_mode_timing_info *info = &hdmi_resv_timings[i];
if (info->video_format == 0) {
*info = *mode;
info->video_format = HDMI_VFRMT_RESERVE1 + i;
return info->video_format;
}
}
return -ENOMEM;
}
bool hdmi_is_valid_resv_timing(int mode)
{
struct msm_hdmi_mode_timing_info *info;
if (mode < HDMI_VFRMT_RESERVE1 || mode > RESERVE_VFRMT_END) {
pr_err("invalid mode %d\n", mode);
return false;
}
info = &hdmi_resv_timings[mode - HDMI_VFRMT_RESERVE1];
return info->video_format >= HDMI_VFRMT_RESERVE1 &&
info->video_format <= RESERVE_VFRMT_END;
}
void hdmi_reset_resv_timing_info(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(hdmi_resv_timings); i++) {
struct msm_hdmi_mode_timing_info *info = &hdmi_resv_timings[i];
info->video_format = 0;
}
}
int msm_hdmi_get_timing_info(
struct msm_hdmi_mode_timing_info *mode, int id)
{
int ret = 0;
switch (id) {
case HDMI_VFRMT_640x480p60_4_3:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_640x480p60_4_3);
break;
case HDMI_VFRMT_720x480p60_4_3:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_720x480p60_4_3);
break;
case HDMI_VFRMT_720x480p60_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_720x480p60_16_9);
break;
case HDMI_VFRMT_1280x720p60_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1280x720p60_16_9);
break;
case HDMI_VFRMT_1920x1080i60_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1920x1080i60_16_9);
break;
case HDMI_VFRMT_1440x480i60_4_3:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1440x480i60_4_3);
break;
case HDMI_VFRMT_1440x480i60_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1440x480i60_16_9);
break;
case HDMI_VFRMT_1920x1080p60_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1920x1080p60_16_9);
break;
case HDMI_VFRMT_720x576p50_4_3:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_720x576p50_4_3);
break;
case HDMI_VFRMT_720x576p50_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_720x576p50_16_9);
break;
case HDMI_VFRMT_1280x720p50_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1280x720p50_16_9);
break;
case HDMI_VFRMT_1440x576i50_4_3:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1440x576i50_4_3);
break;
case HDMI_VFRMT_1440x576i50_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1440x576i50_16_9);
break;
case HDMI_VFRMT_1920x1080p50_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1920x1080p50_16_9);
break;
case HDMI_VFRMT_1920x1080p24_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1920x1080p24_16_9);
break;
case HDMI_VFRMT_1920x1080p25_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1920x1080p25_16_9);
break;
case HDMI_VFRMT_1920x1080p30_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1920x1080p30_16_9);
break;
case HDMI_EVFRMT_3840x2160p30_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_EVFRMT_3840x2160p30_16_9);
break;
case HDMI_EVFRMT_3840x2160p25_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_EVFRMT_3840x2160p25_16_9);
break;
case HDMI_EVFRMT_3840x2160p24_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_EVFRMT_3840x2160p24_16_9);
break;
case HDMI_EVFRMT_4096x2160p24_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_EVFRMT_4096x2160p24_16_9);
break;
case HDMI_VFRMT_1024x768p60_4_3:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1024x768p60_4_3);
break;
case HDMI_VFRMT_1280x1024p60_5_4:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1280x1024p60_5_4);
break;
case HDMI_VFRMT_2560x1600p60_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_2560x1600p60_16_9);
break;
case HDMI_VFRMT_800x600p60_4_3:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_800x600p60_4_3);
break;
case HDMI_VFRMT_848x480p60_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_848x480p60_16_9);
break;
case HDMI_VFRMT_1280x960p60_4_3:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1280x960p60_4_3);
break;
case HDMI_VFRMT_1360x768p60_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1360x768p60_16_9);
break;
case HDMI_VFRMT_1440x900p60_16_10:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1440x900p60_16_10);
break;
case HDMI_VFRMT_1400x1050p60_4_3:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1400x1050p60_4_3);
break;
case HDMI_VFRMT_1680x1050p60_16_10:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1680x1050p60_16_10);
break;
case HDMI_VFRMT_1600x1200p60_4_3:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1600x1200p60_4_3);
break;
case HDMI_VFRMT_1920x1200p60_16_10:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1920x1200p60_16_10);
break;
case HDMI_VFRMT_1366x768p60_16_10:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1366x768p60_16_10);
break;
case HDMI_VFRMT_1280x800p60_16_10:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_1280x800p60_16_10);
break;
case HDMI_VFRMT_3840x2160p24_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_3840x2160p24_16_9);
break;
case HDMI_VFRMT_3840x2160p25_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_3840x2160p25_16_9);
break;
case HDMI_VFRMT_3840x2160p30_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_3840x2160p30_16_9);
break;
case HDMI_VFRMT_3840x2160p50_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_3840x2160p50_16_9);
break;
case HDMI_VFRMT_3840x2160p60_16_9:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_3840x2160p60_16_9);
break;
case HDMI_VFRMT_4096x2160p24_256_135:
MSM_HDMI_MODES_GET_DETAILS(mode,
HDMI_VFRMT_4096x2160p24_256_135);
break;
case HDMI_VFRMT_4096x2160p25_256_135:
MSM_HDMI_MODES_GET_DETAILS(mode,
HDMI_VFRMT_4096x2160p25_256_135);
break;
case HDMI_VFRMT_4096x2160p30_256_135:
MSM_HDMI_MODES_GET_DETAILS(mode,
HDMI_VFRMT_4096x2160p30_256_135);
break;
case HDMI_VFRMT_4096x2160p50_256_135:
MSM_HDMI_MODES_GET_DETAILS(mode,
HDMI_VFRMT_4096x2160p50_256_135);
break;
case HDMI_VFRMT_4096x2160p60_256_135:
MSM_HDMI_MODES_GET_DETAILS(mode,
HDMI_VFRMT_4096x2160p60_256_135);
break;
case HDMI_VFRMT_3840x2160p24_64_27:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_3840x2160p24_64_27);
break;
case HDMI_VFRMT_3840x2160p25_64_27:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_3840x2160p25_64_27);
break;
case HDMI_VFRMT_3840x2160p30_64_27:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_3840x2160p30_64_27);
break;
case HDMI_VFRMT_3840x2160p50_64_27:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_3840x2160p50_64_27);
break;
case HDMI_VFRMT_3840x2160p60_64_27:
MSM_HDMI_MODES_GET_DETAILS(mode, HDMI_VFRMT_3840x2160p60_64_27);
break;
default:
ret = hdmi_get_resv_timing_info(mode, id);
}
return ret;
}
int hdmi_get_supported_mode(struct msm_hdmi_mode_timing_info *info,
struct hdmi_util_ds_data *ds_data, u32 mode)
{
int ret;
if (!info)
return -EINVAL;
if (mode >= HDMI_VFRMT_MAX)
return -EINVAL;
ret = msm_hdmi_get_timing_info(info, mode);
if (!ret && ds_data && ds_data->ds_registered && ds_data->ds_max_clk) {
if (info->pixel_freq > ds_data->ds_max_clk)
info->supported = false;
}
return ret;
} /* hdmi_get_supported_mode */
const char *msm_hdmi_mode_2string(u32 mode)
{
static struct msm_hdmi_mode_timing_info ri = {0};
char *aspect_ratio;
if (mode >= HDMI_VFRMT_MAX)
return "???";
if (hdmi_get_supported_mode(&ri, NULL, mode))
return "???";
memset(res_buf, 0, sizeof(res_buf));
if (!ri.supported) {
snprintf(res_buf, RESOLUTION_NAME_STR_LEN, "%d", mode);
return res_buf;
}
switch (ri.ar) {
case HDMI_RES_AR_4_3:
aspect_ratio = "4/3";
break;
case HDMI_RES_AR_5_4:
aspect_ratio = "5/4";
break;
case HDMI_RES_AR_16_9:
aspect_ratio = "16/9";
break;
case HDMI_RES_AR_16_10:
aspect_ratio = "16/10";
break;
default:
aspect_ratio = "???";
};
snprintf(res_buf, RESOLUTION_NAME_STR_LEN, "%dx%d %s%dHz %s",
ri.active_h, ri.active_v, ri.interlaced ? "i" : "p",
ri.refresh_rate / 1000, aspect_ratio);
return res_buf;
}
int hdmi_get_video_id_code(struct msm_hdmi_mode_timing_info *timing_in,
struct hdmi_util_ds_data *ds_data)
{
int i, vic = -1;
struct msm_hdmi_mode_timing_info supported_timing = {0};
u32 ret;
if (!timing_in) {
pr_err("invalid input\n");
goto exit;
}
/* active_low_h, active_low_v and interlaced are not checked against */
for (i = 0; i < HDMI_VFRMT_MAX; i++) {
ret = hdmi_get_supported_mode(&supported_timing, ds_data, i);
if (ret || !supported_timing.supported)
continue;
if (timing_in->active_h != supported_timing.active_h)
continue;
if (timing_in->front_porch_h != supported_timing.front_porch_h)
continue;
if (timing_in->pulse_width_h != supported_timing.pulse_width_h)
continue;
if (timing_in->back_porch_h != supported_timing.back_porch_h)
continue;
if (timing_in->active_v != supported_timing.active_v)
continue;
if (timing_in->front_porch_v != supported_timing.front_porch_v)
continue;
if (timing_in->pulse_width_v != supported_timing.pulse_width_v)
continue;
if (timing_in->back_porch_v != supported_timing.back_porch_v)
continue;
if (timing_in->pixel_freq != supported_timing.pixel_freq)
continue;
if (timing_in->refresh_rate != supported_timing.refresh_rate)
continue;
vic = (int)supported_timing.video_format;
break;
}
if (vic < 0) {
for (i = 0; i < HDMI_VFRMT_MAX; i++) {
ret = hdmi_get_supported_mode(&supported_timing,
ds_data, i);
if (ret || !supported_timing.supported)
continue;
if (timing_in->active_h != supported_timing.active_h)
continue;
if (timing_in->active_v != supported_timing.active_v)
continue;
vic = (int)supported_timing.video_format;
break;
}
}
if (vic < 0) {
pr_err("timing is not supported h=%d v=%d\n",
timing_in->active_h, timing_in->active_v);
}
exit:
pr_debug("vic = %d timing = %s\n", vic,
msm_hdmi_mode_2string((u32)vic));
return vic;
} /* hdmi_get_video_id_code */
static const char *hdmi_get_single_video_3d_fmt_2string(u32 format)
{
switch (format) {
case TOP_AND_BOTTOM: return "TAB";
case FRAME_PACKING: return "FP";
case SIDE_BY_SIDE_HALF: return "SSH";
}
return "";
} /* hdmi_get_single_video_3d_fmt_2string */
ssize_t hdmi_get_video_3d_fmt_2string(u32 format, char *buf, u32 size)
{
ssize_t ret, len = 0;
ret = scnprintf(buf, size, "%s",
hdmi_get_single_video_3d_fmt_2string(
format & FRAME_PACKING));
len += ret;
if (len && (format & TOP_AND_BOTTOM))
ret = scnprintf(buf + len, size - len, ":%s",
hdmi_get_single_video_3d_fmt_2string(
format & TOP_AND_BOTTOM));
else
ret = scnprintf(buf + len, size - len, "%s",
hdmi_get_single_video_3d_fmt_2string(
format & TOP_AND_BOTTOM));
len += ret;
if (len && (format & SIDE_BY_SIDE_HALF))
ret = scnprintf(buf + len, size - len, ":%s",
hdmi_get_single_video_3d_fmt_2string(
format & SIDE_BY_SIDE_HALF));
else
ret = scnprintf(buf + len, size - len, "%s",
hdmi_get_single_video_3d_fmt_2string(
format & SIDE_BY_SIDE_HALF));
len += ret;
return len;
} /* hdmi_get_video_3d_fmt_2string */
static void hdmi_ddc_trigger(struct hdmi_tx_ddc_ctrl *ddc_ctrl,
enum trigger_mode mode, bool seg)
{
struct hdmi_tx_ddc_data *ddc_data = &ddc_ctrl->ddc_data;
struct dss_io_data *io = ddc_ctrl->io;
u32 const seg_addr = 0x60, seg_num = 0x01;
u32 ddc_ctrl_reg_val;
ddc_data->dev_addr &= 0xFE;
if (mode == TRIGGER_READ && seg) {
DSS_REG_W_ND(io, HDMI_DDC_DATA, BIT(31) | (seg_addr << 8));
DSS_REG_W_ND(io, HDMI_DDC_DATA, seg_num << 8);
}
/* handle portion #1 */
DSS_REG_W_ND(io, HDMI_DDC_DATA, BIT(31) | (ddc_data->dev_addr << 8));
/* handle portion #2 */
DSS_REG_W_ND(io, HDMI_DDC_DATA, ddc_data->offset << 8);
if (mode == TRIGGER_READ) {
/* handle portion #3 */
DSS_REG_W_ND(io, HDMI_DDC_DATA,
(ddc_data->dev_addr | BIT(0)) << 8);
/* HDMI_I2C_TRANSACTION0 */
DSS_REG_W_ND(io, HDMI_DDC_TRANS0, BIT(12) | BIT(16));
/* Write to HDMI_I2C_TRANSACTION1 */
if (seg) {
DSS_REG_W_ND(io, HDMI_DDC_TRANS1, BIT(12) | BIT(16));
DSS_REG_W_ND(io, HDMI_DDC_TRANS2,
BIT(0) | BIT(12) | BIT(13) |
(ddc_data->request_len << 16));
ddc_ctrl_reg_val = BIT(0) | BIT(21);
} else {
DSS_REG_W_ND(io, HDMI_DDC_TRANS1,
BIT(0) | BIT(12) | BIT(13) |
(ddc_data->request_len << 16));
ddc_ctrl_reg_val = BIT(0) | BIT(20);
}
} else {
int ndx;
/* write buffer */
for (ndx = 0; ndx < ddc_data->data_len; ++ndx)
DSS_REG_W_ND(io, HDMI_DDC_DATA,
((u32)ddc_data->data_buf[ndx]) << 8);
DSS_REG_W_ND(io, HDMI_DDC_TRANS0,
(ddc_data->data_len + 1) << 16 | BIT(12) | BIT(13));
ddc_ctrl_reg_val = BIT(0);
}
/* Trigger the I2C transfer */
DSS_REG_W_ND(io, HDMI_DDC_CTRL, ddc_ctrl_reg_val);
}
static int hdmi_ddc_check_status(struct hdmi_tx_ddc_ctrl *ddc_ctrl)
{
u32 reg_val;
int rc = 0;
/* Read DDC status */
reg_val = DSS_REG_R(ddc_ctrl->io, HDMI_DDC_SW_STATUS);
reg_val &= BIT(12) | BIT(13) | BIT(14) | BIT(15);
/* Check if any NACK occurred */
if (reg_val) {
pr_debug("%s: NACK: HDMI_DDC_SW_STATUS 0x%x\n",
ddc_ctrl->ddc_data.what, reg_val);
/* SW_STATUS_RESET, SOFT_RESET */
reg_val = BIT(3) | BIT(1);
DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_CTRL, reg_val);
rc = -ECOMM;
}
return rc;
}
static int hdmi_ddc_read_retry(struct hdmi_tx_ddc_ctrl *ddc_ctrl)
{
u32 reg_val, ndx, time_out_count, wait_time;
struct hdmi_tx_ddc_data *ddc_data;
int status, rc;
int busy_wait_us;
if (!ddc_ctrl || !ddc_ctrl->io) {
pr_err("invalid input\n");
return -EINVAL;
}
ddc_data = &ddc_ctrl->ddc_data;
if (!ddc_data->data_buf) {
status = -EINVAL;
pr_err("%s: invalid buf\n", ddc_data->what);
goto error;
}
if (ddc_data->retry < 0) {
pr_err("invalid no. of retries %d\n", ddc_data->retry);
status = -EINVAL;
goto error;
}
do {
status = hdmi_ddc_clear_irq(ddc_ctrl, ddc_data->what);
if (status)
continue;
if (ddc_data->hard_timeout) {
pr_debug("using hard_timeout %dms\n",
ddc_data->hard_timeout);
busy_wait_us = ddc_data->hard_timeout * HDMI_MS_TO_US;
atomic_set(&ddc_ctrl->read_busy_wait_done, 0);
} else {
reinit_completion(&ddc_ctrl->ddc_sw_done);
wait_time = HZ / 2;
}
hdmi_ddc_trigger(ddc_ctrl, TRIGGER_READ, false);
if (ddc_data->hard_timeout) {
while (busy_wait_us > 0 &&
!atomic_read(&ddc_ctrl->read_busy_wait_done)) {
udelay(HDMI_BUSY_WAIT_DELAY_US);
busy_wait_us -= HDMI_BUSY_WAIT_DELAY_US;
};
if (busy_wait_us < 0)
busy_wait_us = 0;
time_out_count = busy_wait_us / HDMI_MS_TO_US;
ddc_data->timeout_left = time_out_count;
} else {
time_out_count = wait_for_completion_timeout(
&ddc_ctrl->ddc_sw_done, wait_time);
ddc_data->timeout_left =
jiffies_to_msecs(time_out_count);
}
pr_debug("ddc read done at %dms\n", jiffies_to_msecs(jiffies));
if (!time_out_count) {
pr_debug("%s: timedout\n", ddc_data->what);
status = -ETIMEDOUT;
}
rc = hdmi_ddc_check_status(ddc_ctrl);
if (!status)
status = rc;
} while (status && ddc_data->retry--);
if (status)
goto error;
/* Write data to DDC buffer */
DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_DATA,
BIT(0) | (3 << 16) | BIT(31));
/* Discard first byte */
DSS_REG_R_ND(ddc_ctrl->io, HDMI_DDC_DATA);
for (ndx = 0; ndx < ddc_data->data_len; ++ndx) {
reg_val = DSS_REG_R_ND(ddc_ctrl->io, HDMI_DDC_DATA);
ddc_data->data_buf[ndx] = (u8)((reg_val & 0x0000FF00) >> 8);
}
pr_debug("%s: success\n", ddc_data->what);
error:
return status;
} /* hdmi_ddc_read_retry */
void hdmi_ddc_config(struct hdmi_tx_ddc_ctrl *ddc_ctrl)
{
if (!ddc_ctrl || !ddc_ctrl->io) {
pr_err("invalid input\n");
return;
}
/* Configure Pre-Scale multiplier & Threshold */
DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_SPEED, (10 << 16) | (2 << 0));
/*
* Setting 31:24 bits : Time units to wait before timeout
* when clock is being stalled by external sink device
*/
DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_SETUP, 0xFF000000);
/* Enable reference timer to 19 micro-seconds */
DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_REF, (1 << 16) | (19 << 0));
} /* hdmi_ddc_config */
int hdmi_hdcp2p2_ddc_check_status(struct hdmi_tx_ddc_ctrl *ctrl)
{
int rc = 0;
u32 reg_val;
if (!ctrl) {
pr_err("invalid ddc ctrl\n");
return -EINVAL;
}
/* check for errors and clear status */
reg_val = DSS_REG_R(ctrl->io, HDMI_HDCP2P2_DDC_STATUS);
if (reg_val & BIT(4)) {
pr_err("ddc aborted\n");
reg_val |= BIT(5);
rc = -ECONNABORTED;
}
if (reg_val & BIT(8)) {
pr_err("timed out\n");
reg_val |= BIT(9);
rc = -ETIMEDOUT;
}
if (reg_val & BIT(12)) {
pr_err("NACK0\n");
reg_val |= BIT(13);
rc = -EIO;
}
if (reg_val & BIT(14)) {
pr_err("NACK1\n");
reg_val |= BIT(15);
rc = -EIO;
}
DSS_REG_W(ctrl->io, HDMI_HDCP2P2_DDC_STATUS, reg_val);
return rc;
}
static int hdmi_ddc_hdcp2p2_isr(struct hdmi_tx_ddc_ctrl *ddc_ctrl)
{
struct dss_io_data *io = NULL;
struct hdmi_tx_hdcp2p2_ddc_data *data;
u32 intr0, intr2, intr5;
u32 msg_size;
int rc = 0;
if (!ddc_ctrl || !ddc_ctrl->io) {
pr_err("invalid input\n");
return -EINVAL;
}
io = ddc_ctrl->io;
data = &ddc_ctrl->hdcp2p2_ddc_data;
intr0 = DSS_REG_R(io, HDMI_DDC_INT_CTRL0);
intr2 = DSS_REG_R(io, HDMI_HDCP_INT_CTRL2);
intr5 = DSS_REG_R_ND(io, HDMI_DDC_INT_CTRL5);
pr_debug("intr0: 0x%x, intr2: 0x%x, intr5: 0x%x\n",
intr0, intr2, intr5);
/* check if encryption is enabled */
if (intr2 & BIT(0)) {
/*
* ack encryption ready interrupt.
* disable encryption ready interrupt.
* enable encryption not ready interrupt.
*/
intr2 &= ~BIT(2);
intr2 |= BIT(1) | BIT(6);
pr_debug("HDCP 2.2 Encryption enabled\n");
data->encryption_ready = true;
}
/* check if encryption is disabled */
if (intr2 & BIT(4)) {
/*
* ack encryption not ready interrupt.
* disable encryption not ready interrupt.
* enable encryption ready interrupt.
*/
intr2 &= ~BIT(6);
intr2 |= BIT(5) | BIT(2);
pr_debug("HDCP 2.2 Encryption disabled\n");
data->encryption_ready = false;
}
DSS_REG_W_ND(io, HDMI_HDCP_INT_CTRL2, intr2);
/* get the message size bits 29:20 */
msg_size = (intr0 & (0x3FF << 20)) >> 20;
if (msg_size) {
/* ack and disable message size interrupt */
intr0 |= BIT(30);
intr0 &= ~BIT(31);
data->message_size = msg_size;
}
/* check and disable ready interrupt */
if (intr0 & BIT(16)) {
/* ack ready/not ready interrupt */
intr0 |= BIT(17);
intr0 &= ~BIT(18);
data->ready = true;
}
/* check and disable not ready interrupt */
if (intr0 & BIT(15)) {
/* ack ready/not ready interrupt */
intr0 |= BIT(17);
intr0 &= ~BIT(19);
data->ready = false;
}
/* check for reauth req interrupt */
if (intr0 & BIT(12)) {
/* ack and disable reauth req interrupt */
intr0 |= BIT(13);
intr0 &= ~BIT(14);
data->reauth_req = true;
}
/* check for ddc fail interrupt */
if (intr0 & BIT(8)) {
/* ack ddc fail interrupt */
intr0 |= BIT(9);
data->ddc_max_retries_fail = true;
}
/* check for ddc done interrupt */
if (intr0 & BIT(4)) {
/* ack ddc done interrupt */
intr0 |= BIT(5);
data->ddc_done = true;
}
/* check for ddc read req interrupt */
if (intr0 & BIT(0)) {
/* ack read req interrupt */
intr0 |= BIT(1);
data->ddc_read_req = true;
}
DSS_REG_W_ND(io, HDMI_DDC_INT_CTRL0, intr0);
if (intr5 & BIT(0)) {
pr_err("RXSTATUS_DDC_REQ_TIMEOUT\n");
/* ack and disable timeout interrupt */
intr5 |= BIT(1);
intr5 &= ~BIT(2);
data->ddc_timeout = true;
}
DSS_REG_W_ND(io, HDMI_DDC_INT_CTRL5, intr5);
if (data->message_size || data->ready || data->reauth_req) {
if (data->wait) {
atomic_set(&ddc_ctrl->rxstatus_busy_wait_done, 1);
} else if (data->link_cb && data->link_data) {
data->link_cb(data->link_data);
} else {
pr_err("new msg/reauth not handled\n");
rc = -EINVAL;
}
}
return rc;
}
static int hdmi_ddc_scrambling_isr(struct hdmi_tx_ddc_ctrl *ddc_ctrl)
{
struct dss_io_data *io;
bool scrambler_timer_off = false;
u32 intr2, intr5;
if (!ddc_ctrl || !ddc_ctrl->io) {
pr_err("invalid input\n");
return -EINVAL;
}
io = ddc_ctrl->io;
intr2 = DSS_REG_R_ND(io, HDMI_DDC_INT_CTRL2);
intr5 = DSS_REG_R_ND(io, HDMI_DDC_INT_CTRL5);
pr_debug("intr2: 0x%x, intr5: 0x%x\n", intr2, intr5);
if (intr2 & BIT(12)) {
pr_err("SCRAMBLER_STATUS_NOT\n");
intr2 |= BIT(14);
scrambler_timer_off = true;
}
if (intr2 & BIT(8)) {
pr_err("SCRAMBLER_STATUS_DDC_FAILED\n");
intr2 |= BIT(9);
scrambler_timer_off = true;
}
DSS_REG_W_ND(io, HDMI_DDC_INT_CTRL2, intr2);
if (intr5 & BIT(8)) {
pr_err("SCRAMBLER_STATUS_DDC_REQ_TIMEOUT\n");
intr5 |= BIT(9);
intr5 &= ~BIT(10);
scrambler_timer_off = true;
}
DSS_REG_W_ND(io, HDMI_DDC_INT_CTRL5, intr5);
if (scrambler_timer_off)
hdmi_scrambler_ddc_disable(ddc_ctrl);
return 0;
}
int hdmi_ddc_isr(struct hdmi_tx_ddc_ctrl *ddc_ctrl, u32 version)
{
u32 ddc_int_ctrl, ret = 0;
if (!ddc_ctrl || !ddc_ctrl->io) {
pr_err("invalid input\n");
return -EINVAL;
}
ddc_int_ctrl = DSS_REG_R_ND(ddc_ctrl->io, HDMI_DDC_INT_CTRL);
pr_debug("intr: 0x%x\n", ddc_int_ctrl);
if (ddc_int_ctrl & BIT(0)) {
pr_debug("sw done\n");
ddc_int_ctrl |= BIT(1);
if (ddc_ctrl->ddc_data.hard_timeout) {
atomic_set(&ddc_ctrl->read_busy_wait_done, 1);
atomic_set(&ddc_ctrl->write_busy_wait_done, 1);
} else {
complete(&ddc_ctrl->ddc_sw_done);
}
}
if (ddc_int_ctrl & BIT(4)) {
pr_debug("hw done\n");
ddc_int_ctrl |= BIT(5);
}
DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_INT_CTRL, ddc_int_ctrl);
if (version >= HDMI_TX_SCRAMBLER_MIN_TX_VERSION) {
ret = hdmi_ddc_scrambling_isr(ddc_ctrl);
if (ret)
pr_err("err in scrambling isr\n");
}
ret = hdmi_ddc_hdcp2p2_isr(ddc_ctrl);
if (ret)
pr_err("err in hdcp2p2 isr\n");
return ret;
} /* hdmi_ddc_isr */
int hdmi_ddc_read(struct hdmi_tx_ddc_ctrl *ddc_ctrl)
{
int rc = 0;
int retry;
struct hdmi_tx_ddc_data *ddc_data;
if (!ddc_ctrl) {
pr_err("invalid ddc ctrl\n");
return -EINVAL;
}
ddc_data = &ddc_ctrl->ddc_data;
retry = ddc_data->retry;
rc = hdmi_ddc_read_retry(ddc_ctrl);
if (!rc)
return rc;
if (ddc_data->retry_align) {
ddc_data->retry = retry;
ddc_data->request_len = 32 * ((ddc_data->data_len + 31) / 32);
rc = hdmi_ddc_read_retry(ddc_ctrl);
}
return rc;
} /* hdmi_ddc_read */
int hdmi_ddc_read_seg(struct hdmi_tx_ddc_ctrl *ddc_ctrl)
{
int status, rc;
u32 reg_val, ndx, time_out_count;
struct hdmi_tx_ddc_data *ddc_data;
if (!ddc_ctrl || !ddc_ctrl->io) {
pr_err("invalid input\n");
return -EINVAL;
}
ddc_data = &ddc_ctrl->ddc_data;
if (!ddc_data->data_buf) {
status = -EINVAL;
pr_err("%s: invalid buf\n", ddc_data->what);
goto error;
}
if (ddc_data->retry < 0) {
pr_err("invalid no. of retries %d\n", ddc_data->retry);
status = -EINVAL;
goto error;
}
do {
status = hdmi_ddc_clear_irq(ddc_ctrl, ddc_data->what);
if (status)
continue;
reinit_completion(&ddc_ctrl->ddc_sw_done);
hdmi_ddc_trigger(ddc_ctrl, TRIGGER_READ, true);
time_out_count = wait_for_completion_timeout(
&ddc_ctrl->ddc_sw_done, HZ / 2);
if (!time_out_count) {
pr_debug("%s: timedout\n", ddc_data->what);
status = -ETIMEDOUT;
}
rc = hdmi_ddc_check_status(ddc_ctrl);
if (!status)
status = rc;
} while (status && ddc_data->retry--);
if (status)
goto error;
/* Write data to DDC buffer */
DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_DATA,
BIT(0) | (5 << 16) | BIT(31));
/* Discard first byte */
DSS_REG_R_ND(ddc_ctrl->io, HDMI_DDC_DATA);
for (ndx = 0; ndx < ddc_data->data_len; ++ndx) {
reg_val = DSS_REG_R_ND(ddc_ctrl->io, HDMI_DDC_DATA);
ddc_data->data_buf[ndx] = (u8) ((reg_val & 0x0000FF00) >> 8);
}
pr_debug("%s: success\n", ddc_data->what);
error:
return status;
} /* hdmi_ddc_read_seg */
int hdmi_ddc_write(struct hdmi_tx_ddc_ctrl *ddc_ctrl)
{
int status, rc;
u32 time_out_count;
struct hdmi_tx_ddc_data *ddc_data;
u32 wait_time;
int busy_wait_us;
if (!ddc_ctrl || !ddc_ctrl->io) {
pr_err("invalid input\n");
return -EINVAL;
}
ddc_data = &ddc_ctrl->ddc_data;
if (!ddc_data->data_buf) {
status = -EINVAL;
pr_err("%s: invalid buf\n", ddc_data->what);
goto error;
}
if (ddc_data->retry < 0) {
pr_err("invalid no. of retries %d\n", ddc_data->retry);
status = -EINVAL;
goto error;
}
do {
status = hdmi_ddc_clear_irq(ddc_ctrl, ddc_data->what);
if (status)
continue;
if (ddc_data->hard_timeout) {
pr_debug("using hard_timeout %dms\n",
ddc_data->hard_timeout);
busy_wait_us = ddc_data->hard_timeout * HDMI_MS_TO_US;
atomic_set(&ddc_ctrl->write_busy_wait_done, 0);
} else {
reinit_completion(&ddc_ctrl->ddc_sw_done);
wait_time = HZ / 2;
}
hdmi_ddc_trigger(ddc_ctrl, TRIGGER_WRITE, false);
if (ddc_data->hard_timeout) {
while (busy_wait_us > 0 &&
!atomic_read(&ddc_ctrl->write_busy_wait_done)) {
udelay(HDMI_BUSY_WAIT_DELAY_US);
busy_wait_us -= HDMI_BUSY_WAIT_DELAY_US;
};
if (busy_wait_us < 0)
busy_wait_us = 0;
time_out_count = busy_wait_us / HDMI_MS_TO_US;
ddc_data->timeout_left = time_out_count;
} else {
time_out_count = wait_for_completion_timeout(
&ddc_ctrl->ddc_sw_done, wait_time);
ddc_data->timeout_left =
jiffies_to_msecs(time_out_count);
}
pr_debug("DDC write done at %dms\n", jiffies_to_msecs(jiffies));
if (!time_out_count) {
pr_debug("%s timout\n", ddc_data->what);
status = -ETIMEDOUT;
}
rc = hdmi_ddc_check_status(ddc_ctrl);
if (!status)
status = rc;
} while (status && ddc_data->retry--);
if (status)
goto error;
pr_debug("%s: success\n", ddc_data->what);
error:
return status;
} /* hdmi_ddc_write */
int hdmi_ddc_abort_transaction(struct hdmi_tx_ddc_ctrl *ddc_ctrl)
{
int status;
struct hdmi_tx_ddc_data *ddc_data;
if (!ddc_ctrl || !ddc_ctrl->io) {
pr_err("invalid input\n");
return -EINVAL;
}
ddc_data = &ddc_ctrl->ddc_data;
status = hdmi_ddc_clear_irq(ddc_ctrl, ddc_data->what);
if (status)
goto error;
DSS_REG_W_ND(ddc_ctrl->io, HDMI_DDC_ARBITRATION, BIT(12)|BIT(8));
error:
return status;
}
int hdmi_scdc_read(struct hdmi_tx_ddc_ctrl *ctrl, u32 data_type, u32 *val)
{
struct hdmi_tx_ddc_data data = {0};
int rc = 0;
u8 data_buf[2] = {0};
if (!ctrl || !ctrl->io || !val) {
pr_err("Bad Parameters\n");
return -EINVAL;
}
if (data_type >= HDMI_TX_SCDC_MAX) {
pr_err("Unsupported data type\n");
return -EINVAL;
}
data.what = hdmi_scdc_reg2string(data_type);
data.dev_addr = 0xA8;
data.retry = 1;
data.data_buf = data_buf;
switch (data_type) {
case HDMI_TX_SCDC_SCRAMBLING_STATUS:
data.data_len = 1;
data.request_len = 1;
data.offset = HDMI_SCDC_SCRAMBLER_STATUS;
break;
case HDMI_TX_SCDC_SCRAMBLING_ENABLE:
case HDMI_TX_SCDC_TMDS_BIT_CLOCK_RATIO_UPDATE:
data.data_len = 1;
data.request_len = 1;
data.offset = HDMI_SCDC_TMDS_CONFIG;
break;
case HDMI_TX_SCDC_CLOCK_DET_STATUS:
case HDMI_TX_SCDC_CH0_LOCK_STATUS:
case HDMI_TX_SCDC_CH1_LOCK_STATUS:
case HDMI_TX_SCDC_CH2_LOCK_STATUS:
data.data_len = 1;
data.request_len = 1;
data.offset = HDMI_SCDC_STATUS_FLAGS_0;
break;
case HDMI_TX_SCDC_CH0_ERROR_COUNT:
data.data_len = 2;
data.request_len = 2;
data.offset = HDMI_SCDC_ERR_DET_0_L;
break;
case HDMI_TX_SCDC_CH1_ERROR_COUNT:
data.data_len = 2;
data.request_len = 2;
data.offset = HDMI_SCDC_ERR_DET_1_L;
break;
case HDMI_TX_SCDC_CH2_ERROR_COUNT:
data.data_len = 2;
data.request_len = 2;
data.offset = HDMI_SCDC_ERR_DET_2_L;
break;
case HDMI_TX_SCDC_READ_ENABLE:
data.data_len = 1;
data.request_len = 1;
data.offset = HDMI_SCDC_CONFIG_0;
break;
default:
break;
}
ctrl->ddc_data = data;
rc = hdmi_ddc_read(ctrl);
if (rc) {
pr_err("DDC Read failed for %s\n", data.what);
return rc;
}
switch (data_type) {
case HDMI_TX_SCDC_SCRAMBLING_STATUS:
*val = (data_buf[0] & BIT(0)) ? 1 : 0;
break;
case HDMI_TX_SCDC_SCRAMBLING_ENABLE:
*val = (data_buf[0] & BIT(0)) ? 1 : 0;
break;
case HDMI_TX_SCDC_TMDS_BIT_CLOCK_RATIO_UPDATE:
*val = (data_buf[0] & BIT(1)) ? 1 : 0;
break;
case HDMI_TX_SCDC_CLOCK_DET_STATUS:
*val = (data_buf[0] & BIT(0)) ? 1 : 0;
break;
case HDMI_TX_SCDC_CH0_LOCK_STATUS:
*val = (data_buf[0] & BIT(1)) ? 1 : 0;
break;
case HDMI_TX_SCDC_CH1_LOCK_STATUS:
*val = (data_buf[0] & BIT(2)) ? 1 : 0;
break;
case HDMI_TX_SCDC_CH2_LOCK_STATUS:
*val = (data_buf[0] & BIT(3)) ? 1 : 0;
break;
case HDMI_TX_SCDC_CH0_ERROR_COUNT:
case HDMI_TX_SCDC_CH1_ERROR_COUNT:
case HDMI_TX_SCDC_CH2_ERROR_COUNT:
if (data_buf[1] & BIT(7))
*val = (data_buf[0] | ((data_buf[1] & 0x7F) << 8));
else
*val = 0;
break;
case HDMI_TX_SCDC_READ_ENABLE:
*val = (data_buf[0] & BIT(0)) ? 1 : 0;
break;
default:
break;
}
return 0;
}
int hdmi_scdc_write(struct hdmi_tx_ddc_ctrl *ctrl, u32 data_type, u32 val)
{
struct hdmi_tx_ddc_data data = {0};
struct hdmi_tx_ddc_data rdata = {0};
int rc = 0;
u8 data_buf[2] = {0};
u8 read_val = 0;
if (!ctrl || !ctrl->io) {
pr_err("Bad Parameters\n");
return -EINVAL;
}
if (data_type >= HDMI_TX_SCDC_MAX) {
pr_err("Unsupported data type\n");
return -EINVAL;
}
data.what = hdmi_scdc_reg2string(data_type);
data.dev_addr = 0xA8;
data.retry = 1;
data.data_buf = data_buf;
switch (data_type) {
case HDMI_TX_SCDC_SCRAMBLING_ENABLE:
case HDMI_TX_SCDC_TMDS_BIT_CLOCK_RATIO_UPDATE:
rdata.what = "TMDS CONFIG";
rdata.dev_addr = 0xA8;
rdata.retry = 2;
rdata.data_buf = &read_val;
rdata.data_len = 1;
rdata.offset = HDMI_SCDC_TMDS_CONFIG;
rdata.request_len = 1;
ctrl->ddc_data = rdata;
rc = hdmi_ddc_read(ctrl);
if (rc) {
pr_err("scdc read failed\n");
return rc;
}
if (data_type == HDMI_TX_SCDC_SCRAMBLING_ENABLE) {
data_buf[0] = ((((u8)(read_val & 0xFF)) & (~BIT(0))) |
((u8)(val & BIT(0))));
} else {
data_buf[0] = ((((u8)(read_val & 0xFF)) & (~BIT(1))) |
(((u8)(val & BIT(0))) << 1));
}
data.data_len = 1;
data.request_len = 1;
data.offset = HDMI_SCDC_TMDS_CONFIG;
break;
case HDMI_TX_SCDC_READ_ENABLE:
data.data_len = 1;
data.request_len = 1;
data.offset = HDMI_SCDC_CONFIG_0;
data_buf[0] = (u8)(val & 0x1);
break;
default:
pr_err("Cannot write to read only reg (%d)\n",
data_type);
return -EINVAL;
}
ctrl->ddc_data = data;
rc = hdmi_ddc_write(ctrl);
if (rc) {
pr_err("DDC Read failed for %s\n", data.what);
return rc;
}
return 0;
}
int hdmi_setup_ddc_timers(struct hdmi_tx_ddc_ctrl *ctrl,
u32 type, u32 to_in_num_lines)
{
if (!ctrl) {
pr_err("Invalid parameters\n");
return -EINVAL;
}
if (type >= HDMI_TX_DDC_TIMER_MAX) {
pr_err("Invalid timer type %d\n", type);
return -EINVAL;
}
switch (type) {
case HDMI_TX_DDC_TIMER_SCRAMBLER_STATUS:
hdmi_scrambler_status_timer_setup(ctrl, to_in_num_lines);
break;
default:
pr_err("%d type not supported\n", type);
return -EINVAL;
}
return 0;
}
static void hdmi_hdcp2p2_ddc_reset(struct hdmi_tx_ddc_ctrl *ctrl)
{
u32 reg_val;
if (!ctrl) {
pr_err("Invalid parameters\n");
return;
}
/*
* Clear acks for DDC_REQ, DDC_DONE, DDC_FAILED, RXSTATUS_READY,
* RXSTATUS_MSG_SIZE
*/
reg_val = BIT(30) | BIT(17) | BIT(13) | BIT(9) | BIT(5) | BIT(1);
DSS_REG_W(ctrl->io, HDMI_DDC_INT_CTRL0, reg_val);
/* Reset DDC timers */
reg_val = BIT(0) | DSS_REG_R(ctrl->io, HDMI_HDCP2P2_DDC_CTRL);
DSS_REG_W(ctrl->io, HDMI_HDCP2P2_DDC_CTRL, reg_val);
reg_val = DSS_REG_R(ctrl->io, HDMI_HDCP2P2_DDC_CTRL);
reg_val &= ~BIT(0);
DSS_REG_W(ctrl->io, HDMI_HDCP2P2_DDC_CTRL, reg_val);
}
void hdmi_hdcp2p2_ddc_disable(struct hdmi_tx_ddc_ctrl *ctrl)
{
u32 reg_val;
if (!ctrl) {
pr_err("Invalid parameters\n");
return;
}
hdmi_hdcp2p2_ddc_reset(ctrl);
/* Disable HW DDC access to RxStatus register */
reg_val = DSS_REG_R(ctrl->io, HDMI_HW_DDC_CTRL);
reg_val &= ~(BIT(1) | BIT(0));
DSS_REG_W(ctrl->io, HDMI_HW_DDC_CTRL, reg_val);
}
int hdmi_hdcp2p2_ddc_read_rxstatus(struct hdmi_tx_ddc_ctrl *ctrl)
{
u32 reg_val;
u32 intr_en_mask;
u32 timeout;
u32 timer;
int rc = 0;
struct hdmi_tx_hdcp2p2_ddc_data *data;
int busy_wait_us;
if (!ctrl) {
pr_err("Invalid ctrl data\n");
return -EINVAL;
}
data = &ctrl->hdcp2p2_ddc_data;
if (!data) {
pr_err("Invalid ddc data\n");
return -EINVAL;
}
rc = hdmi_ddc_clear_irq(ctrl, "rxstatus");
if (rc)
return rc;
intr_en_mask = data->intr_mask;
intr_en_mask |= BIT(HDCP2P2_RXSTATUS_DDC_FAILED_INTR_MASK);
intr_en_mask |= BIT(HDCP2P2_RXSTATUS_DDC_DONE);
/* Disable short read for now, sinks don't support it */
reg_val = DSS_REG_R(ctrl->io, HDMI_HDCP2P2_DDC_CTRL);
reg_val |= BIT(4);
DSS_REG_W(ctrl->io, HDMI_HDCP2P2_DDC_CTRL, reg_val);
/*
* Setup the DDC timers for HDMI_HDCP2P2_DDC_TIMER_CTRL1 and
* HDMI_HDCP2P2_DDC_TIMER_CTRL2.
* Following are the timers:
* 1. DDC_REQUEST_TIMER: Timeout in hsyncs in which to wait for the
* HDCP 2.2 sink to respond to an RxStatus request
* 2. DDC_URGENT_TIMER: Time period in hsyncs to issue an urgent flag
* when an RxStatus DDC request is made but not accepted by I2C
* engine
* 3. DDC_TIMEOUT_TIMER: Timeout in hsyncs which starts counting when
* a request is made and stops when it is accepted by DDC arbiter
*/
timeout = data->timeout_hsync;
timer = data->periodic_timer_hsync;
pr_debug("timeout: %d hsyncs, timer %d hsync\n", timeout, timer);
DSS_REG_W(ctrl->io, HDMI_HDCP2P2_DDC_TIMER_CTRL, timer);
/* Set both urgent and hw-timeout fields to the same value */
DSS_REG_W(ctrl->io, HDMI_HDCP2P2_DDC_TIMER_CTRL2,
(timeout << 16 | timeout));
/* enable interrupts */
reg_val = intr_en_mask;
/* Clear interrupt status bits */
reg_val |= intr_en_mask >> 1;
pr_debug("writng HDMI_DDC_INT_CTRL0 0x%x\n", reg_val);
DSS_REG_W(ctrl->io, HDMI_DDC_INT_CTRL0, reg_val);
reg_val = DSS_REG_R(ctrl->io, HDMI_DDC_INT_CTRL5);
/* clear and enable RxStatus read timeout */
reg_val |= BIT(2) | BIT(1);
DSS_REG_W(ctrl->io, HDMI_DDC_INT_CTRL5, reg_val);
/*
* Enable hardware DDC access to RxStatus register
*
* HDMI_HW_DDC_CTRL:Bits 1:0 (RXSTATUS_DDC_ENABLE) read like this:
*
* 0 = disable HW controlled DDC access to RxStatus
* 1 = automatic on when HDCP 2.2 is authenticated and loop based on
* request timer (i.e. the hardware will loop automatically)
* 2 = force on and loop based on request timer (hardware will loop)
* 3 = enable by sw trigger and loop until interrupt is generated for
* RxStatus.reauth_req, RxStatus.ready or RxStatus.message_Size.
*
* Depending on the value of ddc_data::poll_sink, we make the decision
* to use either SW_TRIGGER(3) (poll_sink = false) which means that the
* hardware will poll sink and generate interrupt when sink responds,
* or use AUTOMATIC_LOOP(1) (poll_sink = true) which will poll the sink
* based on request timer
*/
reg_val = DSS_REG_R(ctrl->io, HDMI_HW_DDC_CTRL);
reg_val &= ~(BIT(1) | BIT(0));
busy_wait_us = data->timeout_ms * HDMI_MS_TO_US;
atomic_set(&ctrl->rxstatus_busy_wait_done, 0);
/* read method: HDCP2P2_RXSTATUS_HW_DDC_SW_TRIGGER */
reg_val |= BIT(1) | BIT(0);
DSS_REG_W(ctrl->io, HDMI_HW_DDC_CTRL, reg_val);
DSS_REG_W(ctrl->io, HDMI_HDCP2P2_DDC_SW_TRIGGER, 1);
if (data->wait) {
while (busy_wait_us > 0 &&
!atomic_read(&ctrl->rxstatus_busy_wait_done)) {
udelay(HDMI_BUSY_WAIT_DELAY_US);
busy_wait_us -= HDMI_BUSY_WAIT_DELAY_US;
};
if (busy_wait_us < 0)
busy_wait_us = 0;
data->timeout_left = busy_wait_us / HDMI_MS_TO_US;
if (!data->timeout_left) {
pr_err("sw ddc rxstatus timeout\n");
rc = -ETIMEDOUT;
}
rc = hdmi_hdcp2p2_ddc_check_status(ctrl);
hdmi_hdcp2p2_ddc_disable(ctrl);
}
return rc;
}