tegrakernel/kernel/nvidia/drivers/video/tegra/dc/hdmi2.0.c

3947 lines
102 KiB
C
Raw Permalink Normal View History

2022-02-16 09:13:02 -06:00
/*
* hdmi2.0.c: hdmi2.0 driver.
*
* Copyright (c) 2014-2021, NVIDIA CORPORATION, All rights reserved.
* Author: Animesh Kishore <ankishore@nvidia.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/device.h>
#include <linux/clk/tegra.h>
#include <linux/nvhost.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/debugfs.h>
#include <linux/unistd.h>
#include <linux/extcon/extcon-disp.h>
#include <linux/extcon.h>
#ifdef CONFIG_SWITCH
#include <linux/switch.h>
#endif
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <soc/tegra/tegra_powergate.h>
#include <uapi/video/tegra_dc_ext.h>
#include "dc.h"
#include "dc_reg.h"
#include "dc_priv.h"
#include "sor.h"
#include "sor_regs.h"
#include "edid.h"
#include "hdmi2.0.h"
#include "hdcp/hdmihdcp.h"
#include "dpaux.h"
#include "hda_dc.h"
#include "hdmivrr.h"
#include <linux/tegra_prod.h>
#include "../../../../arch/arm/mach-tegra/iomap.h"
#include "bridge/hdmi2fpd_ds90uh949.h"
#include "bridge/max929x_hdmi2gmsl.h"
#include "bridge/hdmi2dsi_tc358870.h"
static struct tmds_prod_pair tmds_config_modes[] = {
{ /* 54 MHz */
.clk = 54000000,
.name = "prod_c_54M"
},
{ /* 75 MHz */
.clk = 75000000,
.name = "prod_c_75M"
},
{ /* 150 MHz */
.clk = 150000000,
.name = "prod_c_150M"
},
{ /* 200 MHz */
.clk = 200000000,
.name = "prod_c_200M"
},
{ /* 300 MHz */
.clk = 300000000,
.name = "prod_c_300M"
},
{ /* HDMI 2.0 */
.clk = 600000000,
.name = "prod_c_600M"
},
{ /* end mark */
.clk = 0,
}
};
static int hdmi_instance;
static int tegra_hdmi_controller_enable(struct tegra_hdmi *hdmi);
static void tegra_hdmi_config_clk(struct tegra_hdmi *hdmi, u32 clk_type);
static long tegra_dc_hdmi_setup_clk(struct tegra_dc *dc, struct clk *clk);
static void tegra_hdmi_scdc_worker(struct work_struct *work);
static void tegra_hdmi_debugfs_init(struct tegra_hdmi *hdmi);
static void tegra_hdmi_debugfs_remove(struct tegra_hdmi *hdmi);
static void tegra_hdmi_hdr_worker(struct work_struct *work);
static int tegra_hdmi_v2_x_mon_config(struct tegra_hdmi *hdmi, bool enable);
static void tegra_hdmi_v2_x_host_config(struct tegra_hdmi *hdmi, bool enable);
static inline bool tegra_hdmi_is_connected(struct tegra_hdmi *hdmi)
{
return (hdmi->mon_spec.misc & FB_MISC_HDMI) ||
(hdmi->mon_spec.misc & FB_MISC_HDMI_FORUM);
}
static inline void __maybe_unused
tegra_hdmi_irq_enable(struct tegra_hdmi *hdmi)
{
enable_irq(hdmi->irq);
}
static inline void __maybe_unused
tegra_hdmi_irq_disable(struct tegra_hdmi *hdmi)
{
disable_irq(hdmi->irq);
}
static inline bool __maybe_unused
tegra_hdmi_hpd_asserted(struct tegra_hdmi *hdmi)
{
return tegra_dc_hpd(hdmi->dc);
}
/*
* Calculates the effective SOR link rate based on the DC pclk and the selected
* output mode for native support:
* - RGB444/YUV444 12bpc requires a 2:3 pclk:orclk ratio
* - YUV420 8bpc requires a 2:1 pclk:orclk ratio
*/
static inline unsigned long tegra_sor_get_link_rate(struct tegra_dc *dc)
{
int yuv_flag = dc->mode.vmode & FB_VMODE_YUV_MASK;
unsigned long rate = dc->mode.pclk;
if (tegra_dc_is_t21x())
return rate;
if (!(dc->mode.vmode & FB_VMODE_BYPASS)) {
if ((IS_RGB(yuv_flag) && (yuv_flag == FB_VMODE_Y36)) ||
(yuv_flag == (FB_VMODE_Y444 | FB_VMODE_Y36))) {
rate = rate >> 1;
rate = rate * 3;
} else if (tegra_dc_is_yuv420_8bpc(&dc->mode)) {
rate = rate >> 1;
}
}
return rate;
}
static inline void _tegra_hdmi_ddc_enable(struct tegra_hdmi *hdmi)
{
mutex_lock(&hdmi->ddc_refcount_lock);
if (hdmi->ddc_refcount++)
goto fail;
tegra_hdmi_get(hdmi->dc);
mutex_lock(&hdmi->dpaux->lock);
tegra_dpaux_get(hdmi->dpaux);
mutex_unlock(&hdmi->dpaux->lock);
/*
* hdmi uses i2c lane muxed on dpaux1 pad.
* Enable dpaux1 pads and configure the mux.
*/
tegra_dpaux_config_pad_mode(hdmi->dpaux, TEGRA_DPAUX_PAD_MODE_I2C);
fail:
mutex_unlock(&hdmi->ddc_refcount_lock);
}
static inline void _tegra_hdmi_ddc_disable(struct tegra_hdmi *hdmi)
{
mutex_lock(&hdmi->ddc_refcount_lock);
if (WARN_ONCE(hdmi->ddc_refcount <= 0, "ddc refcount imbalance"))
goto fail;
if (--hdmi->ddc_refcount != 0)
goto fail;
/*
* hdmi uses i2c lane muxed on dpaux1 pad.
* Disable dpaux1 pads.
*/
tegra_dpaux_pad_power(hdmi->dpaux, false);
mutex_lock(&hdmi->dpaux->lock);
tegra_dpaux_put(hdmi->dpaux);
mutex_unlock(&hdmi->dpaux->lock);
tegra_hdmi_put(hdmi->dc);
fail:
mutex_unlock(&hdmi->ddc_refcount_lock);
}
static int tegra_hdmi_ddc_i2c_xfer(struct tegra_dc *dc,
struct i2c_msg *msgs, int num)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
int ret;
/* No physical panel and/or emulator is attached in simulation. */
if (tegra_platform_is_sim())
return -EINVAL;
_tegra_hdmi_ddc_enable(hdmi);
ret = i2c_transfer(hdmi->ddc_i2c_client->adapter, msgs, num);
_tegra_hdmi_ddc_disable(hdmi);
return ret;
}
static int tegra_hdmi_ddc_init(struct tegra_hdmi *hdmi)
{
struct tegra_dc *dc = hdmi->dc;
struct i2c_adapter *i2c_adap;
int err = 0;
struct i2c_board_info i2c_dev_info = {
.type = "tegra_hdmi2.0",
.addr = 0x50,
};
if (hdmi->edid_src == EDID_SRC_PANEL)
hdmi->edid = tegra_edid_create(dc, tegra_hdmi_ddc_i2c_xfer);
else if (hdmi->edid_src == EDID_SRC_DT)
hdmi->edid = tegra_edid_create(dc, tegra_dc_edid_blob);
if (IS_ERR_OR_NULL(hdmi->edid)) {
dev_err(&dc->ndev->dev, "hdmi: can't create edid\n");
return PTR_ERR(hdmi->edid);
}
tegra_dc_set_edid(dc, hdmi->edid);
if (tegra_platform_is_sim())
return 0;
i2c_adap = i2c_get_adapter(dc->out->ddc_bus);
if (i2c_adap) {
hdmi->ddc_i2c_original_rate =
i2c_get_adapter_bus_clk_rate(i2c_adap);
hdmi->ddc_i2c_client = i2c_new_device(i2c_adap, &i2c_dev_info);
i2c_put_adapter(i2c_adap);
if (!hdmi->ddc_i2c_client) {
dev_err(&dc->ndev->dev, "hdmi: can't create new i2c device\n");
err = -EBUSY;
goto fail_edid_free;
}
} else if (hdmi->edid_src != EDID_SRC_DT) {
dev_err(&dc->ndev->dev,
"hdmi: can't get adpater for ddc bus %d\n",
dc->out->ddc_bus);
err = -EBUSY;
goto fail_edid_free;
}
return 0;
fail_edid_free:
tegra_edid_destroy(hdmi->edid);
return err;
}
static int tegra_hdmi_scdc_i2c_xfer(struct tegra_dc *dc,
struct i2c_msg *msgs, int num)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
return i2c_transfer(hdmi->scdc_i2c_client->adapter, msgs, num);
}
static int tegra_hdmi_scdc_init(struct tegra_hdmi *hdmi)
{
struct tegra_dc *dc = hdmi->dc;
struct i2c_adapter *i2c_adap;
int err = 0;
struct i2c_board_info i2c_dev_info = {
.type = "tegra_hdmi_scdc",
.addr = 0x54,
};
if (tegra_platform_is_sim())
return 0;
i2c_adap = i2c_get_adapter(dc->out->ddc_bus);
if (!i2c_adap) {
dev_err(&dc->ndev->dev,
"hdmi: can't get adpater for scdc bus %d\n",
dc->out->ddc_bus);
err = -EBUSY;
goto fail;
}
hdmi->scdc_i2c_client = i2c_new_device(i2c_adap, &i2c_dev_info);
i2c_put_adapter(i2c_adap);
if (!hdmi->scdc_i2c_client) {
dev_err(&dc->ndev->dev,
"hdmi: can't create scdc i2c device\n");
err = -EBUSY;
goto fail;
}
INIT_DELAYED_WORK(&hdmi->scdc_work, tegra_hdmi_scdc_worker);
return 0;
fail:
return err;
}
/* does not return precise tmds character rate */
static u32 tegra_hdmi_mode_min_tmds_rate(const struct fb_videomode *mode)
{
u32 tmds_csc_8bpc_khz = PICOS2KHZ(mode->pixclock);
if (mode->vmode & (FB_VMODE_Y420 | FB_VMODE_Y420_ONLY))
tmds_csc_8bpc_khz /= 2;
return tmds_csc_8bpc_khz;
}
static bool tegra_hdmi_fb_mode_filter(const struct tegra_dc *dc,
struct fb_videomode *mode)
{
struct tegra_hdmi *hdmi = dc->out_data;
if (!mode->pixclock)
return false;
if (mode->xres > 4096 || mode->yres > 2160)
return false;
#if defined(CONFIG_TEGRA_YUV_BYPASS_MODE_FILTER)
if (tegra_dc_is_t21x()) {
/* No support for YUV modes on T210 hardware. Filter them out */
if (mode->vmode & FB_VMODE_YUV_MASK)
return false;
} else {
/* T186 hardware supports only YUV422.
* Filter out YUV420 modes.
*/
if ((mode->vmode & FB_VMODE_Y420_ONLY) ||
(mode->vmode & FB_VMODE_Y420))
return false;
}
#endif
if (mode->vmode & FB_VMODE_INTERLACED)
return false;
/* some non-compliant edids list 420vdb modes in vdb */
if ((mode->vmode & FB_VMODE_Y420) &&
!(mode->flag & FB_MODE_IS_FROM_VAR) &&
!(tegra_edid_is_hfvsdb_present(hdmi->edid) &&
tegra_edid_is_scdc_present(hdmi->edid)) &&
tegra_edid_is_420db_present(hdmi->edid)) {
mode->vmode &= ~FB_VMODE_Y420;
mode->vmode |= FB_VMODE_Y420_ONLY;
}
if ((mode->vmode & FB_VMODE_YUV_MASK) &&
!(mode->flag & FB_MODE_IS_FROM_VAR) &&
(tegra_edid_get_quirks(hdmi->edid) & TEGRA_EDID_QUIRK_NO_YUV))
return false;
if (!(mode->vmode & FB_VMODE_IS_CEA) &&
!(mode->flag & FB_MODE_IS_FROM_VAR) &&
(tegra_edid_get_quirks(hdmi->edid) & TEGRA_EDID_QUIRK_ONLY_CEA))
return false;
/*
* There are currently many TVs in the market that actually do NOT support
* 4k@60fps 4:4:4 (594 MHz), (especially on the HDCP 2.2 ports), but
* advertise it in the DTD block in their EDIDs. The workaround for this port
* is to disable the 594 MHz mode if no HF-VSDB is present or if no SCDC
* support is indicated
*/
if (((tegra_hdmi_mode_min_tmds_rate(mode) / 1000 >= 340) &&
!(mode->flag & FB_MODE_IS_FROM_VAR)) &&
(!tegra_edid_is_hfvsdb_present(hdmi->edid) ||
!tegra_edid_is_scdc_present(hdmi->edid)))
return false;
/* Check if the mode's pixel clock is more than the max rate*/
if (!tegra_dc_valid_pixclock(dc, mode))
return false;
/*
* Workaround for modes that fail the constraint:
* V_FRONT_PORCH >= V_REF_TO_SYNC + 1
*
* This constraint does not apply to nvdisplay.
*/
if (!tegra_dc_is_nvdisplay() && mode->lower_margin == 1) {
mode->lower_margin++;
mode->upper_margin--;
mode->vmode |= FB_VMODE_ADJUSTED;
}
if (!check_fb_videomode_timings(dc, mode)) {
#if defined(CONFIG_TEGRA_DC_TRACE_PRINTK)
trace_printk("check_fb_videomode_timings: false\n"
"%u x %u @ %u Hz\n",
mode->xres, mode->yres, mode->pixclock);
#endif
return false;
}
return true;
}
static int tegra_hdmi_get_mon_spec(struct tegra_hdmi *hdmi)
{
#define MAX_RETRY 10
#define MIN_RETRY_DELAY_US 200
#define MAX_RETRY_DELAY_US (MIN_RETRY_DELAY_US + 200)
size_t attempt_cnt = 0;
int err = 0;
struct i2c_adapter *i2c_adap = i2c_get_adapter(hdmi->dc->out->ddc_bus);
if (IS_ERR_OR_NULL(hdmi->edid)) {
dev_err(&hdmi->dc->ndev->dev, "hdmi: edid not initialized\n");
return PTR_ERR(hdmi->edid);
}
tegra_edid_i2c_adap_change_rate(i2c_adap, hdmi->ddc_i2c_original_rate);
hdmi->mon_spec_valid = false;
if (hdmi->mon_spec_valid)
fb_destroy_modedb(hdmi->mon_spec.modedb);
memset(&hdmi->mon_spec, 0, sizeof(hdmi->mon_spec));
do {
err = tegra_edid_get_monspecs(hdmi->edid, &hdmi->mon_spec);
if (err < 0)
usleep_range(MIN_RETRY_DELAY_US, MAX_RETRY_DELAY_US);
else
break;
} while (++attempt_cnt < MAX_RETRY);
if (err < 0) {
dev_err(&hdmi->dc->ndev->dev, "hdmi: edid read failed\n");
/* Try to load and parse the fallback edid */
hdmi->edid->errors = EDID_ERRORS_READ_FAILED;
err = tegra_edid_get_monspecs(hdmi->edid, &hdmi->mon_spec);
if (err < 0) {
dev_err(&hdmi->dc->ndev->dev,
"hdmi: parsing fallback edid failed\n");
return err;
}
dev_info(&hdmi->dc->ndev->dev, "hdmi: using fallback edid\n");
}
hdmi->mon_spec_valid = true;
return 0;
#undef MAX_RETRY_DELAY_US
#undef MIN_RETRY_DELAY_US
#undef MAX_RETRY
}
static inline int tegra_hdmi_edid_read(struct tegra_hdmi *hdmi)
{
int err;
err = tegra_hdmi_get_mon_spec(hdmi);
return err;
}
static int tegra_hdmi_get_eld(struct tegra_hdmi *hdmi)
{
int err;
hdmi->eld_valid = false;
memset(&hdmi->eld, 0, sizeof(hdmi->eld));
err = tegra_edid_get_eld(hdmi->edid, &hdmi->eld);
if (err < 0) {
dev_err(&hdmi->dc->ndev->dev, "hdmi: eld not available\n");
return err;
}
hdmi->eld_valid = true;
return 0;
}
static inline int tegra_hdmi_eld_read(struct tegra_hdmi *hdmi)
{
return tegra_hdmi_get_eld(hdmi);
}
static void tegra_hdmi_edid_config(struct tegra_hdmi *hdmi)
{
#define CM_TO_MM(x) (x * 10)
struct tegra_dc *dc = hdmi->dc;
if (!hdmi->mon_spec_valid)
return;
dc->out->h_size = CM_TO_MM(hdmi->mon_spec.max_x);
dc->out->v_size = CM_TO_MM(hdmi->mon_spec.max_y);
hdmi->dvi = !tegra_hdmi_is_connected(hdmi);
#undef CM_TO_MM
}
static void tegra_hdmi_hotplug_notify(struct tegra_hdmi *hdmi,
bool is_asserted)
{
struct tegra_dc *dc = hdmi->dc;
struct fb_monspecs *mon_spec;
int n_display_timings, idx;
if (is_asserted)
mon_spec = &hdmi->mon_spec;
else
mon_spec = NULL;
n_display_timings = 0;
/*
* If display timing with non-zero pclk is specified in DT,
* skip parsing EDID from monitor. Except if vedid is active.
* In that case force parsing the EDID
*/
for (idx = 0; idx < dc->out->n_modes; idx++) {
if (0 != dc->out->modes->pclk) {
n_display_timings++;
break;
}
}
if (dc->vedid) {
n_display_timings = 0;
}
if (dc->fb && 0 == n_display_timings) {
tegra_fb_update_monspecs(hdmi->dc->fb, mon_spec,
tegra_hdmi_fb_mode_filter);
tegra_fb_update_fix(hdmi->dc->fb, mon_spec);
}
dc->connected = is_asserted;
tegra_dc_ext_process_hotplug(dc->ndev->id);
tegra_dc_extcon_hpd_notify(dc);
#ifdef CONFIG_SWITCH
switch_set_state(&hdmi->hpd_switch, is_asserted ? 1 : 0);
#endif
}
static int tegra_hdmi_edid_eld_setup(struct tegra_hdmi *hdmi)
{
int err;
tegra_unpowergate_partition(hdmi->sor->powergate_id);
err = tegra_hdmi_edid_read(hdmi);
if (err < 0)
goto fail;
err = tegra_hdmi_eld_read(hdmi);
if (err < 0)
goto fail;
err = tegra_hdmivrr_setup(hdmi);
if (err && err != -ENODEV)
dev_err(&hdmi->dc->ndev->dev, "vrr_setup failed\n");
/*
* Try to write ELD data to SOR (needed only for boot
* doesn't do anything during hotplug)
*/
tegra_hdmi_setup_hda_presence(hdmi->sor->dev_id);
tegra_powergate_partition(hdmi->sor->powergate_id);
tegra_hdmi_edid_config(hdmi);
/*
* eld is configured when audio needs it
* via tegra_hdmi_edid_config()
*/
tegra_hdmi_hotplug_notify(hdmi, true);
return 0;
fail:
tegra_powergate_partition(hdmi->sor->powergate_id);
return err;
}
static int tegra_hdmi_controller_disable(struct tegra_hdmi *hdmi)
{
struct tegra_dc_sor_data *sor = hdmi->sor;
struct tegra_dc *dc = hdmi->dc;
int ret = 0;
tegra_dc_get(dc);
/* disable hdcp */
if (hdmi->edid_src == EDID_SRC_PANEL && !hdmi->dc->vedid)
tegra_nvhdcp_set_plug(hdmi->nvhdcp, false);
cancel_delayed_work_sync(&hdmi->scdc_work);
if (tegra_sor_get_link_rate(dc) > 340000000) {
tegra_hdmi_v2_x_mon_config(hdmi, false);
tegra_hdmi_v2_x_host_config(hdmi, false);
}
tegra_dc_sor_detach(sor);
if (dc->out->vrr_hotplug_state == TEGRA_HPD_STATE_FORCE_DEASSERT)
tegra_dc_disable_disp_ctrl_mode(dc);
tegra_sor_clk_switch_setup(sor, false);
tegra_hdmi_config_clk(hdmi, TEGRA_HDMI_SAFE_CLK);
tegra_sor_power_lanes(sor, 4, false);
tegra_sor_hdmi_pad_power_down(sor);
tegra_sor_reset(hdmi->sor);
tegra_hdmi_put(dc);
if (tegra_dc_is_nvdisplay()) {
ret = clk_set_parent(sor->ref_clk, dc->parent_clk_safe);
if (ret)
dev_err(&dc->ndev->dev,
"can't set parent_clk_safe for sor->ref_clk\n");
}
cancel_delayed_work_sync(&hdmi->hdr_worker);
tegra_dc_put(dc);
return ret;
}
static int tegra_hdmi_disable(struct tegra_hdmi *hdmi)
{
struct tegra_dc *dc = hdmi->dc;
if (!hdmi->enabled) {
dc->connected = false;
tegra_dc_ext_process_hotplug(dc->ndev->id);
tegra_dc_extcon_hpd_notify(dc);
#ifdef CONFIG_SWITCH
switch_set_state(&hdmi->hpd_switch, 0);
#endif
return 0;
}
hdmi->enabled = false;
hdmi->eld_valid = false;
hdmi->mon_spec_valid = false;
tegra_hdmivrr_disable(hdmi);
tegra_dc_disable(hdmi->dc);
tegra_hdmi_hotplug_notify(hdmi, false);
return 0;
}
static int (*tegra_hdmi_state_func[])(struct tegra_hdmi *) = {
tegra_hdmi_disable,
tegra_hdmi_edid_eld_setup,
};
enum tegra_hdmi_plug_states {
TEGRA_HDMI_MONITOR_DISABLE,
TEGRA_HDMI_MONITOR_ENABLE,
};
static int hdmi_hpd_process_edid_match(struct tegra_hdmi *hdmi, int match)
{
int ret = 0;
if (match) {
if (!tegra_dc_ext_is_userspace_active()) {
/* No userspace running. Enable DC with cached mode */
dev_info(&hdmi->dc->ndev->dev,
"hdmi: No EDID change. No userspace active. Using "
"cached mode to initialize dc!\n");
hdmi->dc->use_cached_mode = true;
hdmi->plug_state = TEGRA_HDMI_MONITOR_ENABLE;
} else {
if ((hdmi->edid_src == EDID_SRC_PANEL)
&& !hdmi->dc->vedid && hdmi->enabled) {
tegra_nvhdcp_set_plug(hdmi->nvhdcp, false);
tegra_nvhdcp_set_plug(hdmi->nvhdcp, true);
}
/*
* Userspace is active. No EDID change. Userspace will
* issue unblank call to enable DC later.
*/
dev_info(&hdmi->dc->ndev->dev, "hdmi: No EDID change "
"after HPD bounce, taking no action\n");
ret = -EINVAL;
}
} else {
dev_info(&hdmi->dc->ndev->dev,
"hdmi: EDID change after HPD bounce, resetting\n");
hdmi->plug_state = TEGRA_HDMI_MONITOR_DISABLE;
/*
* In dc resume context DC has to be in disable state if EDID
* is changed, setting dc->reenable_on_resume to false makes
* sure DC does not get enabled.
*/
if (hdmi->dc->suspended)
hdmi->dc->reenable_on_resume = false;
}
return ret;
}
static int read_edid_into_buffer(struct tegra_hdmi *hdmi,
u8 *edid_data, size_t edid_data_len)
{
int err, i;
int extension_blocks;
int max_ext_blocks = (edid_data_len / 128) - 1;
err = tegra_edid_read_block(hdmi->edid, 0, edid_data);
if (err) {
dev_info(&hdmi->dc->ndev->dev, "hdmi: tegra_edid_read_block(0) returned err %d\n",
err);
return err;
}
extension_blocks = edid_data[0x7e];
dev_info(&hdmi->dc->ndev->dev, "%s: extension_blocks = %d, max_ext_blocks = %d\n",
__func__, extension_blocks, max_ext_blocks);
if (extension_blocks > max_ext_blocks)
extension_blocks = max_ext_blocks;
for (i = 1; i <= extension_blocks; i++) {
err = tegra_edid_read_block(hdmi->edid, i, edid_data + i * 128);
if (err) {
dev_info(&hdmi->dc->ndev->dev, "hdmi: tegra_edid_read_block(%d) returned err %d\n",
i, err);
return err;
}
}
return i * 128;
}
static int hdmi_recheck_edid(struct tegra_hdmi *hdmi, int *match)
{
int ret;
u8 tmp[HDMI_EDID_MAX_LENGTH] = {0};
if (tegra_platform_is_sim())
return 0;
if (hdmi->dc->vedid) {
/* Use virtual EDID if it is present. */
memcpy(tmp, hdmi->dc->vedid_data, EDID_BYTES_PER_BLOCK);
ret = EDID_BYTES_PER_BLOCK;
} else {
ret = read_edid_into_buffer(hdmi, tmp, sizeof(tmp));
}
dev_info(&hdmi->dc->ndev->dev, "%s: read_edid_into_buffer() returned %d\n",
__func__, ret);
if (ret > 0) {
struct tegra_dc_edid *data = tegra_edid_get_data(hdmi->edid);
dev_info(&hdmi->dc->ndev->dev, "old edid len = %ld\n",
(long int)data->len);
*match = ((ret == data->len) &&
!memcmp(tmp, data->buf, data->len));
if (*match == 0) {
print_hex_dump(KERN_INFO, "tmp :", DUMP_PREFIX_ADDRESS,
16, 4, tmp, ret, true);
print_hex_dump(KERN_INFO, "data:", DUMP_PREFIX_ADDRESS,
16, 4, data->buf, data->len, true);
}
tegra_edid_put_data(data);
ret = 0;
}
return ret;
}
static void tegra_hdmi_hpd_worker(struct work_struct *work)
{
struct tegra_hdmi *hdmi = container_of(to_delayed_work(work),
struct tegra_hdmi, hpd_worker);
int err;
bool connected;
enum tegra_hdmi_plug_states orig_state;
int match = 0;
mutex_lock(&hdmi->hpd_lock);
connected = tegra_dc_hpd(hdmi->dc);
orig_state = hdmi->plug_state;
if (hdmi->dc->out->hotplug_state == TEGRA_HPD_STATE_NORMAL &&
hdmi->dc->out->prev_hotplug_state == TEGRA_HPD_STATE_NORMAL)
tegra_nvhdcp_clear_fallback(hdmi->nvhdcp);
if (connected) {
switch (orig_state) {
case TEGRA_HDMI_MONITOR_ENABLE:
if (hdmi_recheck_edid(hdmi, &match)) {
dev_info(&hdmi->dc->ndev->dev, "hdmi: unable to read EDID\n");
goto fail;
} else {
err = hdmi_hpd_process_edid_match(hdmi, match);
if (err < 0)
goto fail;
}
break;
case TEGRA_HDMI_MONITOR_DISABLE:
hdmi->plug_state = TEGRA_HDMI_MONITOR_ENABLE;
break;
default:
break;
};
} else {
switch (orig_state) {
case TEGRA_HDMI_MONITOR_ENABLE:
hdmi->plug_state = TEGRA_HDMI_MONITOR_DISABLE;
break;
case TEGRA_HDMI_MONITOR_DISABLE:
goto fail;
default:
break;
};
}
err = tegra_hdmi_state_func[hdmi->plug_state](hdmi);
if (err < 0) {
dev_info(&hdmi->dc->ndev->dev,
"hdmi state %d failed during %splug\n",
hdmi->plug_state, connected ? "" : "un");
hdmi->plug_state = orig_state;
goto fail;
} else {
dev_info(&hdmi->dc->ndev->dev, "hdmi: %splugged\n",
connected ? "" : "un");
}
if (connected && hdmi->plug_state == TEGRA_HDMI_MONITOR_DISABLE)
goto reschedule_worker;
fail:
mutex_unlock(&hdmi->hpd_lock);
complete(&hdmi->dc->hpd_complete);
return;
reschedule_worker:
mutex_unlock(&hdmi->hpd_lock);
cancel_delayed_work(&hdmi->hpd_worker);
schedule_delayed_work(&hdmi->hpd_worker, 0);
return;
}
static irqreturn_t tegra_hdmi_hpd_irq_handler(int irq, void *ptr)
{
struct tegra_dc *dc = ptr;
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
unsigned int hpd_debounce = HDMI_HPD_DEBOUNCE_DELAY_MS;
if (dc->out->type == TEGRA_DC_OUT_FAKE_DP)
return IRQ_HANDLED;
if (atomic_read(&hdmi->suspended))
return IRQ_HANDLED;
tegra_nvhdcp_clear_fallback(hdmi->nvhdcp);
cancel_delayed_work(&hdmi->hpd_worker);
if (tegra_edid_get_quirks(hdmi->edid) &
TEGRA_EDID_QUIRK_HPD_BOUNCE) {
hpd_debounce = HDMI_HPD_DEBOUNCE_WAR_DELAY_MS;
}
schedule_delayed_work(&hdmi->hpd_worker,
msecs_to_jiffies(hpd_debounce));
return IRQ_HANDLED;
}
static int tegra_dc_hdmi_hpd_init(struct tegra_dc *dc)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
struct device_node *sor_np = NULL;
enum of_gpio_flags flags;
int hotplug_gpio = dc->out->hotplug_gpio;
int hotplug_gpio_np;
int hotplug_state;
int hotplug_irq;
int err;
if (tegra_platform_is_sim())
goto skip_gpio_irq_settings;
hotplug_state = hdmi->dc->out->hotplug_state;
sor_np = tegra_dc_get_conn_np(hdmi->dc);
if (!sor_np) {
dev_err(&dc->ndev->dev, "%s: error getting connector np\n",
__func__);
return -ENODEV;
}
hotplug_gpio_np = of_get_named_gpio_flags(sor_np,
"nvidia,hpd-gpio", 0, &flags);
if (hotplug_gpio_np == -ENOENT &&
hotplug_state == TEGRA_HPD_STATE_FORCE_ASSERT) {
dev_info(&dc->ndev->dev,
"hdmi: No hotplug gpio is assigned\n");
goto skip_gpio_irq_settings;
}
if (!gpio_is_valid(hotplug_gpio)) {
dev_err(&dc->ndev->dev, "hdmi: invalid hotplug gpio\n");
return -EINVAL;
}
hotplug_irq = gpio_to_irq(hotplug_gpio);
if (hotplug_irq < 0) {
dev_err(&dc->ndev->dev,
"hdmi: hotplug gpio to irq map failed\n");
return -EINVAL;
}
err = gpio_request(hotplug_gpio, "hdmi2.0_hpd");
if (err < 0)
dev_err(&dc->ndev->dev,
"hdmi: hpd gpio_request failed %d\n", err);
gpio_direction_input(hotplug_gpio);
err = request_threaded_irq(hotplug_irq,
NULL, tegra_hdmi_hpd_irq_handler,
(IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
IRQF_ONESHOT),
dev_name(&dc->ndev->dev), dc);
if (err) {
dev_err(&dc->ndev->dev,
"hdmi: request_threaded_irq failed: %d\n", err);
goto fail;
}
hdmi->irq = hotplug_irq;
if (hotplug_state != TEGRA_HPD_STATE_NORMAL) {
dev_info(&dc->ndev->dev,
"hdmi: keeping hotplug irq disabled\n");
disable_irq(hotplug_irq);
}
skip_gpio_irq_settings:
INIT_DELAYED_WORK(&hdmi->hpd_worker, tegra_hdmi_hpd_worker);
mutex_init(&hdmi->hpd_lock);
return 0;
fail:
gpio_free(hotplug_gpio);
return err;
}
#define MAX_TMDS_FREQ 600000000
#define NAME_PROD_LIST_SOC "prod_list_hdmi_soc"
#define NAME_PROD_LIST_PKG "prod_list_hdmi_package"
#define NAME_PROD_LIST_BOARD "prod_list_hdmi_board"
struct tmds_range_info {
struct list_head list;
int lowerHz;
int upperHz;
const char *pch_prod;
};
/*
* Read a prod-setting list from DT
* o Inputs:
* - hdmi: pointer to hdmi info
* - np_prod: pointer to prod-settings node
* - pch_prod_list: name of the DT property of prod-setting list
* - optional: boolean to indicate optional list or not
* - hd_range: pointer to linked list head for range info read
* o Outputs:
* - return: pointer to a temporary memory block allocated
* caller should free this memory block after use of the linked
* list *hd_range
* NULL to indicate failure
* - *hd_range: linked list of struct tmds_range_info read
* the list is sorted with lower boundary value
*/
static void *tegra_tmds_range_read(struct tegra_hdmi *hdmi,
struct device_node *np_prod, char *pch_prod_list,
bool optional, struct list_head *hd_range)
{
int err = 0;
int num_str;
int i, j, k, n;
int upper, lower;
const char *pch_prod;
struct device_node *np;
LIST_HEAD(head);
struct tmds_range_info *ranges = NULL;
struct tmds_range_info *pos, *lowest;
LIST_HEAD(head_sorted);
#define LEN_NAME 128
char buf_name[LEN_NAME];
/* sanity check */
if (!np_prod)
goto fail_sanity;
num_str = of_property_count_strings(np_prod, pch_prod_list);
if (num_str <= 0) {
if (!optional)
dev_info(&hdmi->dc->ndev->dev,
"hdmi: invalid prod list %s\n",
pch_prod_list);
goto fail_sanity;
}
/* allocate single space for info array */
ranges = kcalloc(num_str, sizeof(*ranges), GFP_KERNEL);
if (!ranges)
goto fail_alloc;
/* read into the linked list */
for (i = j = 0; i < num_str; i++) {
err = of_property_read_string_index(np_prod,
pch_prod_list, i, &pch_prod);
if (err) {
dev_warn(&hdmi->dc->ndev->dev,
"hdmi: %s: string %d read failed, err:%d\n",
pch_prod_list, i, err);
continue;
}
if ('\0' == *pch_prod)
continue;
np = of_get_child_by_name(np_prod, pch_prod);
if (np) {
of_node_put(np);
} else {
dev_warn(&hdmi->dc->ndev->dev,
"hdmi: %s: prod-setting %s is not found\n",
pch_prod_list, pch_prod);
continue;
}
ranges[j].pch_prod = pch_prod;
strlcpy(buf_name, pch_prod, LEN_NAME);
for (k = 0; k < strlen(buf_name); k++) {
if ('A' <= buf_name[k] && buf_name[k] <= 'Z')
buf_name[k] += 'a' - 'A';
else if ('-' == buf_name[k])
buf_name[k] = '_';
}
if (2 == sscanf(buf_name, "prod_c_hdmi_%dm_%dm%n",
&lower, &upper, &n)) {
if (!(lower < upper) || strlen(pch_prod) != n) {
dev_warn(&hdmi->dc->ndev->dev,
"hdmi: %s: invalid range in %s\n",
pch_prod_list, pch_prod);
continue;
}
} else {
dev_warn(&hdmi->dc->ndev->dev,
"hdmi: %s: missing boundary in %s\n",
pch_prod_list, pch_prod);
continue;
}
ranges[j].lowerHz = lower * 1000000;
ranges[j].upperHz = upper * 1000000;
list_add_tail(&ranges[j].list, &head);
j++;
}
/* sort ranges */
while (!list_empty(&head)) {
lowest = list_first_entry(&head, typeof(*lowest), list);
list_for_each_entry(pos, &head, list) {
if (pos->lowerHz < lowest->lowerHz)
lowest = pos;
}
list_move_tail(&lowest->list, &head_sorted);
}
if (!list_empty(&head_sorted))
list_replace(&head_sorted, hd_range);
fail_alloc:
fail_sanity:
return ranges;
#undef LEN_NAME
}
/*
* Construct the HDMI TMDS range prod-setting table, hdmi->tmds_range, from
* the range list read from DeviceTree.
*
* o inputs:
* - hdmi: HDMI info
* - phead: head of the range list
* o outputs:
* - return: 0: no error
* !0: error return
* - hdmi->tmds_range: HDMI TMDS prod-setting range table
*/
static int tegra_tmds_range_construct(struct tegra_hdmi *hdmi,
struct list_head *phead)
{
int i, l;
struct tmds_range_info *pos;
struct tmds_prod_pair *tmds_ranges = NULL;
char *pch_name;
/* allocate space for the table & names */
i = l = 0;
list_for_each_entry(pos, phead, list) {
l += strlen(pos->pch_prod) + 1;
i++;
}
l += sizeof(*tmds_ranges) * (i + 1);
tmds_ranges = kzalloc(l, GFP_KERNEL);
if (!tmds_ranges)
return -ENOMEM;
/* construct the TMDS range table from the list */
pch_name = (char *)&tmds_ranges[i + 1];
i = 0;
list_for_each_entry(pos, phead, list) {
tmds_ranges[i].clk = pos->upperHz;
strcpy(pch_name, pos->pch_prod);
tmds_ranges[i].name = pch_name;
pch_name += strlen(pos->pch_prod) + 1;
dev_dbg(&hdmi->dc->ndev->dev,
"hdmi: range %dM-%dM:%s\n",
pos->lowerHz / 1000000,
pos->upperHz / 1000000,
pos->pch_prod);
i++;
}
tmds_ranges[i].clk = 0; /* end mark */
hdmi->tmds_range = tmds_ranges;
return 0;
}
/*
* Combine two HDMI range lists into the first list, and the second list has
* the priority over the first for an overlapped range. This function returns
* a temporary memory block allocated for combining. This must be freed by
* the caller.
*
* o inputs:
* - hd_f: head of the first range list sorted in range freq.
* - hd_s: head of the second range list sorted in range freq.
* o outputs:
* - return: !0:temporary memory block to be freed with successful return
* 0:error return
* - *hd_f: combined result list
* - *hd_s: empty list
*/
static void *tegra_tmds_range_combine(struct tegra_hdmi *hdmi,
struct list_head *hd_f, struct list_head *hd_s)
{
int idx;
struct tmds_range_info *pos_f, *tmp_f;
struct tmds_range_info *pos_s, *tmp_s;
struct tmds_range_info *spares;
int num_spare;
if (!hd_f || !hd_s)
return NULL;
/* allocate some spares for split ranges */
num_spare = 0;
list_for_each_entry(pos_s, hd_s, list)
num_spare++;
spares = kcalloc(num_spare, sizeof(*spares), GFP_KERNEL);
if (!spares)
return NULL;
/* combine ranges */
idx = 0;
pos_f = list_first_entry(hd_f, typeof(*pos_f), list);
list_for_each_entry_safe(pos_s, tmp_s, hd_s, list) {
list_for_each_entry_safe_from(pos_f, tmp_f, hd_f, list) {
if (pos_s->upperHz <= pos_f->lowerHz) {
break;
} else if (pos_s->lowerHz <= pos_f->lowerHz &&
pos_f->lowerHz < pos_s->upperHz &&
pos_s->upperHz < pos_f->upperHz) {
pos_f->lowerHz = pos_s->upperHz;
break;
} else if (pos_s->lowerHz <= pos_f->lowerHz &&
pos_f->upperHz <= pos_s->upperHz) {
list_del(&pos_f->list);
} else if (pos_f->lowerHz < pos_s->lowerHz &&
pos_s->upperHz < pos_f->upperHz) {
if (idx < num_spare) {
spares[idx].lowerHz = pos_f->lowerHz;
spares[idx].upperHz = pos_s->lowerHz;
spares[idx].pch_prod = pos_f->pch_prod;
list_add_tail(&spares[idx].list,
&pos_f->list);
idx++;
pos_f->lowerHz = pos_s->upperHz;
} else {
dev_err(&hdmi->dc->ndev->dev,
"hdmi: %s: out of spare!\n",
__func__);
}
break;
} else if (pos_f->lowerHz < pos_s->lowerHz &&
pos_s->lowerHz < pos_f->upperHz &&
pos_f->upperHz <= pos_s->upperHz) {
pos_f->upperHz = pos_s->lowerHz;
}
}
list_move_tail(&pos_s->list, &pos_f->list);
}
return spares;
}
/*
* Check the prod-setting list covers the full TMDS spectrum or not
*
* o inputs:
* - phead: head of the prod-setting list
* - max_freq: maximum frequency of the TMDS spectrum in Hz
* o outputs:
* - return: true: covers the full spectrum
* false: does not cover the full spectrum
*/
static bool tegra_tmds_range_check_full_spectrum(
struct list_head *phead, int max_freq)
{
int i;
bool has_gap;
struct tmds_range_info *pos;
/* check the level covers the full range or not */
has_gap = false;
i = 0;
list_for_each_entry(pos, phead, list) {
if (i < pos->lowerHz) {
has_gap = true;
break;
}
i = pos->upperHz;
}
if (!has_gap && i < max_freq)
has_gap = true;
return has_gap ? false : true;
}
/*
* Read the HDMI TMDS range list in all levels from the DeviceTree
* and construct the HDMI TMDS range table, hdmi->tmds_range.
*
* o inputs:
* - hdmi: HDMI info
* - np_prod: valid DT node of this HDMI interface prod-setting
* - phead_board: head of the board level list read
* o outputs:
* - return:
* . 0: succeeded
* . !0: failed
* - hdmi->tmds_range: constructed TMDS prod-setting range table
*/
static int tegra_tmds_range_read_all(struct tegra_hdmi *hdmi,
struct device_node *np_prod,
struct list_head *phead_board)
{
int err = 0;
LIST_HEAD(head_range_soc);
LIST_HEAD(head_range_pkg);
struct list_head *phead_range_board;
void *pmem_soc = NULL, *pmem_pkg = NULL;
void *pmem_pkg2 = NULL, *pmem_board2 = NULL;
/* bring the board level list read */
phead_range_board = phead_board;
/* read the SoC level list */
pmem_soc = tegra_tmds_range_read(hdmi, np_prod,
NAME_PROD_LIST_SOC, false, &head_range_soc);
if (!pmem_soc) {
dev_warn(&hdmi->dc->ndev->dev,
"hdmi: tegra_hdmi_tmds_range_read(soc) failed\n");
err = -EINVAL;
goto fail_range_read_soc;
}
/* check the SoC level covers the full spectrum */
if (!tegra_tmds_range_check_full_spectrum(&head_range_soc,
MAX_TMDS_FREQ))
dev_warn(&hdmi->dc->ndev->dev,
"hdmi: SoC prod-setting covers partial!\n");
/* read the optional SoC-Package level */
pmem_pkg = tegra_tmds_range_read(hdmi, np_prod,
NAME_PROD_LIST_PKG, true, &head_range_pkg);
/* combine the SoC and the SoC-Package level list */
if (pmem_pkg) {
pmem_pkg2 = tegra_tmds_range_combine(hdmi,
&head_range_soc, &head_range_pkg);
if (!pmem_pkg2) {
dev_warn(&hdmi->dc->ndev->dev,
"hdmi: combining SoC & Pkg level failed\n");
err = -ENOMEM;
goto fall_combine_pkg;
}
}
/* combine the board level as well */
pmem_board2 = tegra_tmds_range_combine(hdmi,
&head_range_soc, phead_range_board);
if (!pmem_board2) {
dev_warn(&hdmi->dc->ndev->dev,
"hdmi: combining SoC & Board level failed\n");
err = -ENOMEM;
goto fall_combine_board;
}
/* construct TMDS ranges from the combined list */
err = tegra_tmds_range_construct(hdmi, &head_range_soc);
if (err) {
dev_warn(&hdmi->dc->ndev->dev,
"hdmi: tegra_hdmi_tmds_range_construct() failed\n");
goto fail_construct;
}
err = 0;
fail_construct:
kfree(pmem_board2);
fall_combine_board:
kfree(pmem_pkg2);
fall_combine_pkg:
kfree(pmem_pkg);
kfree(pmem_soc);
fail_range_read_soc:
return err;
}
/*
* Read the HDMI TMDS range list in the board level from the DeviceTree
* property, prod_list_hdmi_board, and construct the HDMI TMDS range table,
* hdmi->tmds_range. If the board level range list does not cover the full
* spectrum, then return with error to combine with lower levels. And, the
* read info from DT will be saved for a reuse.
*
* o inputs:
* - hdmi: HDMI info
* - np_prod: valid DT node of this HDMI interface prod-setting
* - phead_board: empty list head for board level list
* - ppmem_board: double pointer to save the temporary memory allocation
* o outputs:
* - return:
* . 0: succeeded
* . !0: failed
* - hdmi->tmds_range: constructed TMDS prod-setting range table
* - *phead_board: head of the board level list read
* - *ppmem_board: temporary memory block to be freed
*/
static int tegra_tmds_range_read_board(struct tegra_hdmi *hdmi,
struct device_node *np_prod,
struct list_head *phead_board,
void **ppmem_board)
{
int err = 0;
void *ptmp = NULL;
/* read the board level list */
ptmp = tegra_tmds_range_read(hdmi, np_prod,
NAME_PROD_LIST_BOARD, false, phead_board);
if (!ptmp) {
dev_info(&hdmi->dc->ndev->dev,
"hdmi: tegra_hdmi_tmds_range_read(bd) failed\n");
err = -EINVAL;
return err;
}
*ppmem_board = ptmp;
/* check the board level covers the full spectrum or not */
if (!tegra_tmds_range_check_full_spectrum(phead_board,
MAX_TMDS_FREQ)) {
/* partial only */
dev_info(&hdmi->dc->ndev->dev,
"hdmi: board prod-setting covers partial!\n");
return -ENODATA;
}
/* construct TMDS ranges from the list read */
err = tegra_tmds_range_construct(hdmi, phead_board);
if (err) {
dev_warn(&hdmi->dc->ndev->dev,
"hdmi: tegra_hdmi_tmds_range_construct(bd) failed\n");
return err;
}
return 0;
}
/*
* Initialize the HDMI TMDS range from the DeviceTree prod-settings.
* HDMI has 3 levels of prod-setting list in SoC, SoC-package and board level,
* while the board level has the highest priority and the SoC-Package level is
* optional, DeviceTree properties prod_list_hdmi_soc, prod_list_hdmi_package
* and prod_list_hdmi_board represent each level of prod-setting list. These
* lists hold DeviceTree node names of prod-setting data for each range. Each
* range's upper and lower boundary frequencies will be decoded from the
* prod-setting data node name, prod_c_hdmi_LLm_UUm, in the list.
* If the board level list covers the whole spectrum then there is no need to
* combine ranges with lower levels. If the board level covers partial only
* then the board level info read will be saved for a reuse.
*
* o inputs:
* - hdmi: HDMI info
* - np_prod: DT node of prod-setting for this HDMI interface
* o outputs:
* - return: error code
* - hdmi->tmds_range: pointer to TMDS range table constructed
* NULL then the default fall-back table will be used
*/
static int tegra_hdmi_tmds_range_init(struct tegra_hdmi *hdmi,
struct device_node *np_prod)
{
int err = 0;
LIST_HEAD(head_board);
void *pmem_board = NULL;
/* sanity check */
if (!np_prod)
return -EINVAL;
/* build ranges from the board level list first
* if it fails then build ranges by combining all 3 levels */
hdmi->tmds_range = NULL;
err = tegra_tmds_range_read_board(hdmi, np_prod,
&head_board, &pmem_board);
if (err)
err = tegra_tmds_range_read_all(hdmi, np_prod, &head_board);
/* clean-up */
kfree(pmem_board);
return err;
}
#undef MAX_TMDS_FREQ
#undef NAME_PROD_LIST_SOC
#undef NAME_PROD_LIST_PKG
#undef NAME_PROD_LIST_BOARD
static int tegra_hdmi_tmds_init(struct tegra_hdmi *hdmi)
{
int retval = 0;
struct tegra_dc *dc = hdmi->dc;
struct device_node *np_sor;
struct device_node *np_ps;
if (tegra_platform_is_sim())
return 0;
np_sor = tegra_dc_get_conn_np(dc);
if (!np_sor) {
dev_warn(&hdmi->dc->ndev->dev,
"hdmi: error getting connector np\n");
retval = -EINVAL;
goto fail;
}
hdmi->prod_list = devm_tegra_prod_get_from_node(&hdmi->dc->ndev->dev,
np_sor);
if (IS_ERR(hdmi->prod_list)) {
hdmi->prod_list = NULL;
dev_warn(&hdmi->dc->ndev->dev,
"hdmi: prod list init failed with error %ld\n",
PTR_ERR(hdmi->prod_list));
retval = -EINVAL;
goto fail;
}
/* construct the HDMI TMDS range */
np_ps = of_get_child_by_name(np_sor, "prod-settings");
retval = tegra_hdmi_tmds_range_init(hdmi, np_ps);
if (retval) {
dev_warn(&hdmi->dc->ndev->dev,
"hdmi: TMDS range init failed with error %d\n",
retval);
if (np_ps)
of_node_put(np_ps);
retval = -EINVAL;
goto fail;
}
if (np_ps)
of_node_put(np_ps);
fail:
return retval;
}
static int tegra_hdmi_config_tmds(struct tegra_hdmi *hdmi)
{
int i;
int err = 0;
struct tmds_prod_pair *ranges;
unsigned long tmds_rate = tegra_sor_get_link_rate(hdmi->dc);
if (tegra_platform_is_sim())
return 0;
/* Apply the range where tmds rate belongs to */
ranges = hdmi->tmds_range ? : tmds_config_modes;
for (i = 0; ranges[i].clk; i++) {
if (ranges[i].clk < tmds_rate)
continue;
dev_info(&hdmi->dc->ndev->dev,
"hdmi: tmds rate:%luK prod-setting:%s\n",
tmds_rate / 1000, ranges[i].name);
err = tegra_prod_set_by_name(&hdmi->sor->base,
ranges[i].name, hdmi->prod_list);
/* Return if matching range found */
if (!err)
return 0;
}
/* apply highest range if no covered range found */
if (i)
tegra_prod_set_by_name(&hdmi->sor->base,
ranges[i - 1].name, hdmi->prod_list);
dev_warn(&hdmi->dc->ndev->dev,
"hdmi: tmds prod set for tmds rate:%lu failed\n",
tmds_rate);
return -EINVAL;
}
static int tegra_hdmi_hdr_init(struct tegra_hdmi *hdmi)
{
INIT_DELAYED_WORK(&hdmi->hdr_worker, tegra_hdmi_hdr_worker);
return 0;
}
static int tegra_dc_hdmi_init(struct tegra_dc *dc)
{
int err;
struct device_node *sor_np, *panel_np;
struct tegra_hdmi *hdmi;
sor_np = tegra_dc_get_conn_np(dc);
if (!sor_np) {
dev_err(&dc->ndev->dev, "%s: error getting connector np\n",
__func__);
return -ENODEV;
}
panel_np = tegra_dc_get_panel_np(dc);
if (!panel_np) {
dev_err(&dc->ndev->dev, "%s: error getting panel np\n",
__func__);
return -ENODEV;
}
hdmi = devm_kzalloc(&dc->ndev->dev, sizeof(*hdmi), GFP_KERNEL);
if (!hdmi) {
err = -ENOMEM;
goto fail_sor_np;
}
hdmi->hpd_switch_name = devm_kzalloc(&dc->ndev->dev,
CHAR_BUF_SIZE_MAX, GFP_KERNEL);
if (!hdmi->hpd_switch_name) {
err = -ENOMEM;
goto fail_hdmi;
}
hdmi->audio_switch_name = devm_kzalloc(&dc->ndev->dev,
CHAR_BUF_SIZE_MAX, GFP_KERNEL);
if (!hdmi->audio_switch_name) {
err = -ENOMEM;
goto fail_hpd_switch;
}
hdmi->dc = dc;
hdmi->edid_src = EDID_SRC_PANEL;
hdmi->plug_state = TEGRA_HDMI_MONITOR_DISABLE;
if (of_property_read_bool(panel_np, "nvidia,edid"))
hdmi->edid_src = EDID_SRC_DT;
hdmi->dpaux = tegra_dpaux_init_data(dc, sor_np);
if (IS_ERR_OR_NULL(hdmi->dpaux)) {
if ((hdmi->dpaux == NULL) &&
(hdmi->dc->pdata->default_out->ddc_bus < 0) &&
(hdmi->edid_src == EDID_SRC_DT)) {
/* This is not an error since there is a case using */
/* HDMI DDC as generic I2C with these configuration */
dev_info(&hdmi->dc->ndev->dev,
"hdmi: DDC is not used for HDMI.\n");
} else {
err = PTR_ERR(hdmi->dpaux);
goto fail_audio_switch;
}
}
hdmi->sor = tegra_dc_sor_init(dc, NULL);
if (IS_ERR_OR_NULL(hdmi->sor)) {
err = PTR_ERR(hdmi->sor);
goto fail_audio_switch;
}
hdmi->pdata = dc->pdata->default_out->hdmi_out;
hdmi->ddc_refcount = 0; /* assumes this is disabled when starting */
mutex_init(&hdmi->ddc_refcount_lock);
hdmi->nvhdcp = NULL;
hdmi->mon_spec_valid = false;
hdmi->eld_valid = false;
hdmi->device_shutdown = false;
if (hdmi_instance) {
snprintf(hdmi->hpd_switch_name, CHAR_BUF_SIZE_MAX,
"hdmi%d", hdmi_instance);
snprintf(hdmi->audio_switch_name, CHAR_BUF_SIZE_MAX,
"hdmi%d_audio", hdmi_instance);
} else {
snprintf(hdmi->hpd_switch_name, CHAR_BUF_SIZE_MAX, "hdmi");
snprintf(hdmi->audio_switch_name, CHAR_BUF_SIZE_MAX,
"hdmi_audio");
}
#ifdef CONFIG_SWITCH
hdmi->hpd_switch.name = hdmi->hpd_switch_name;
hdmi->audio_switch.name = hdmi->audio_switch_name;
#endif
if (0) {
/* TODO: seamless boot mode needs initialize the state */
} else {
hdmi->enabled = false;
atomic_set(&hdmi->clock_refcount, 0);
}
atomic_set(&hdmi->suspended, 0);
if (!tegra_platform_is_sim()) {
hdmi->nvhdcp = tegra_nvhdcp_create(hdmi, dc->ndev->id,
dc->out->ddc_bus);
if (IS_ERR(hdmi->nvhdcp)) {
err = PTR_ERR(hdmi->nvhdcp);
dev_err(&dc->ndev->dev,
"hdmi hdcp creation failed with err %d\n", err);
} else {
tegra_nvhdcp_debugfs_init(hdmi->nvhdcp);
}
}
tegra_hdmi_ddc_init(hdmi);
tegra_hdmi_scdc_init(hdmi);
err = tegra_hdmi_vrr_init(hdmi);
if (err && err != -ENODEV)
dev_err(&dc->ndev->dev, "vrr_init failed\n");
tegra_hdmi_debugfs_init(hdmi);
tegra_hdmi_tmds_init(hdmi);
tegra_dc_set_outdata(dc, hdmi);
tegra_hdmi_hdr_init(hdmi);
if (hdmi->pdata->hdmi2fpd_bridge_enable) {
hdmi2fpd_init(dc);
hdmi2fpd_enable(dc);
}
#ifdef CONFIG_TEGRA_HDMI2GMSL_MAX929x
if (hdmi->pdata->hdmi2gmsl_bridge_enable) {
hdmi->out_ops = &tegra_hdmi2gmsl_ops;
hdmi->out_ops->init(hdmi);
}
#endif /* CONFIG_TEGRA_HDMI2GMSL_MAX929x */
if (hdmi->pdata->hdmi2dsi_bridge_enable) {
hdmi->out_ops = &tegra_hdmi2dsi_ops;
if (hdmi->out_ops && hdmi->out_ops->init)
hdmi->out_ops->init(hdmi);
}
/* NOTE: Below code is applicable to L4T or embedded systems and is
* protected accordingly. This section chooses a mode to early
* enable DC.
*/
if ((IS_ENABLED(CONFIG_FRAMEBUFFER_CONSOLE) ||
((dc->pdata->flags & TEGRA_DC_FLAG_ENABLED) &&
(dc->pdata->flags & TEGRA_DC_FLAG_SET_EARLY_MODE))) &&
dc->out && (dc->out->type == TEGRA_DC_OUT_HDMI)) {
/* In case of seamless display, dc mode would already be set */
if (!dc->initialized)
tegra_dc_set_fbcon_boot_mode(dc, hdmi->edid);
}
#ifdef CONFIG_SWITCH
err = switch_dev_register(&hdmi->hpd_switch);
if (err)
dev_err(&dc->ndev->dev,
"%s: failed to register hpd switch, err=%d\n",
__func__, err);
err = switch_dev_register(&hdmi->audio_switch);
if (err)
dev_err(&dc->ndev->dev,
"%s: failed to register audio switch, err=%d\n",
__func__, err);
#endif
#ifdef CONFIG_TEGRA_HDA_DC
tegra_hda_init(dc, hdmi);
#endif
hdmi_instance++;
return 0;
fail_audio_switch:
devm_kfree(&dc->ndev->dev, hdmi->audio_switch_name);
fail_hpd_switch:
devm_kfree(&dc->ndev->dev, hdmi->hpd_switch_name);
fail_hdmi:
kfree(hdmi->tmds_range);
hdmi->tmds_range = NULL;
devm_kfree(&dc->ndev->dev, hdmi);
fail_sor_np:
return err;
}
static void tegra_dc_hdmi_destroy(struct tegra_dc *dc)
{
struct tegra_hdmi *hdmi = NULL;
if (!dc->current_topology.valid)
return;
hdmi = tegra_dc_get_outdata(dc);
if (hdmi->pdata->hdmi2fpd_bridge_enable)
hdmi2fpd_destroy(dc);
if (hdmi->out_ops && hdmi->out_ops->destroy)
hdmi->out_ops->destroy(hdmi);
#ifdef CONFIG_TEGRA_HDA_DC
tegra_hda_destroy(hdmi->hda_handle);
#endif
tegra_nvhdcp_destroy(hdmi->nvhdcp);
if (hdmi->dpaux)
tegra_dpaux_destroy_data(hdmi->dpaux);
if (hdmi->ddc_i2c_client)
i2c_unregister_device(hdmi->ddc_i2c_client);
if (hdmi->scdc_i2c_client)
i2c_unregister_device(hdmi->scdc_i2c_client);
if (hdmi->ddcci_i2c_client)
i2c_unregister_device(hdmi->ddcci_i2c_client);
tegra_dc_sor_destroy(hdmi->sor);
tegra_edid_destroy(hdmi->edid);
kfree(hdmi->tmds_range);
hdmi->tmds_range = NULL;
hdmi->prod_list = NULL;
free_irq(gpio_to_irq(dc->out->hotplug_gpio), dc);
gpio_free(dc->out->hotplug_gpio);
tegra_dc_out_destroy(dc);
#ifdef CONFIG_SWITCH
switch_dev_unregister(&hdmi->hpd_switch);
switch_dev_unregister(&hdmi->audio_switch);
#endif
devm_kfree(&dc->ndev->dev, hdmi->hpd_switch_name);
devm_kfree(&dc->ndev->dev, hdmi->audio_switch_name);
tegra_hdmi_debugfs_remove(hdmi);
devm_kfree(&dc->ndev->dev, hdmi);
dc->current_topology.valid = false;
}
static void tegra_hdmi_config(struct tegra_hdmi *hdmi)
{
struct tegra_dc_sor_data *sor = hdmi->sor;
struct tegra_dc *dc = hdmi->dc;
u32 h_pulse_start, h_pulse_end;
u32 hblank, max_ac, rekey;
unsigned long val;
u32 arm_video_range;
if ((dc->mode.vmode & FB_VMODE_BYPASS) ||
!(dc->mode.vmode & FB_VMODE_LIMITED_RANGE))
arm_video_range = NV_SOR_INPUT_CONTROL_ARM_VIDEO_RANGE_FULL;
else
arm_video_range = NV_SOR_INPUT_CONTROL_ARM_VIDEO_RANGE_LIMITED;
tegra_sor_write_field(sor, NV_SOR_INPUT_CONTROL,
NV_SOR_INPUT_CONTROL_ARM_VIDEO_RANGE_LIMITED,
arm_video_range);
if (tegra_dc_is_t21x())
tegra_sor_write_field(sor, NV_SOR_INPUT_CONTROL,
NV_SOR_INPUT_CONTROL_HDMI_SRC_SELECT_DISPLAYB,
NV_SOR_INPUT_CONTROL_HDMI_SRC_SELECT_DISPLAYB);
/*
* The rekey register and corresponding eq want to operate
* on "-2" of the desired rekey value
*/
rekey = NV_SOR_HDMI_CTRL_REKEY_DEFAULT - 2;
hblank = dc->mode.h_sync_width + dc->mode.h_back_porch +
dc->mode.h_front_porch;
max_ac = (hblank - rekey - 18) / 32;
val = 0;
/* no DVI when the display mode belongs to HDMI 2.0 */
val |= (hdmi->dvi && tegra_sor_get_link_rate(hdmi->dc) <= 340000000)
? 0x0 : NV_SOR_HDMI_CTRL_ENABLE;
val |= NV_SOR_HDMI_CTRL_REKEY(rekey);
val |= NV_SOR_HDMI_CTRL_MAX_AC_PACKET(max_ac);
val |= NV_SOR_HDMI_CTRL_AUDIO_LAYOUT_SELECT;
tegra_sor_writel(sor, NV_SOR_HDMI_CTRL, val);
if (tegra_dc_is_t21x()) {
tegra_dc_writel(dc, 0x180, DC_DISP_H_PULSE2_CONTROL);
h_pulse_start = dc->mode.h_ref_to_sync +
dc->mode.h_sync_width +
dc->mode.h_back_porch - 10;
h_pulse_end = h_pulse_start + 8;
tegra_dc_writel(dc, PULSE_START(h_pulse_start) |
PULSE_END(h_pulse_end),
DC_DISP_H_PULSE2_POSITION_A);
val = tegra_dc_readl(dc, DC_DISP_DISP_SIGNAL_OPTIONS0);
val |= H_PULSE_2_ENABLE;
tegra_dc_writel(dc, val, DC_DISP_DISP_SIGNAL_OPTIONS0);
}
}
void tegra_hdmi_infoframe_pkt_write(struct tegra_hdmi *hdmi,
u32 header_reg, u8 pkt_type,
u8 pkt_vs, u8 pkt_len,
void *reg_payload,
u32 reg_payload_len,
bool sw_checksum)
{
struct tegra_dc_sor_data *sor = hdmi->sor;
u32 val;
u32 *data = reg_payload;
u32 data_reg = header_reg + 1;
val = NV_SOR_HDMI_INFOFRAME_HEADER_TYPE(pkt_type) |
NV_SOR_HDMI_INFOFRAME_HEADER_VERSION(pkt_vs) |
NV_SOR_HDMI_INFOFRAME_HEADER_LEN(pkt_len);
tegra_sor_writel(sor, header_reg, val);
if (sw_checksum) {
u8 checksum = pkt_type + pkt_vs + pkt_len;
for (val = 1; val < reg_payload_len; val++)
checksum += ((u8 *)reg_payload)[val];
/* The first byte of the payload must always be the checksum
* that we are going to calculate in SW */
((u8 *)reg_payload)[0] = (256 - checksum);
}
for (val = 0; val < reg_payload_len; val += 4, data_reg++, data++)
tegra_sor_writel(sor, data_reg, *data);
}
u32 tegra_hdmi_get_cea_modedb_size(struct tegra_hdmi *hdmi)
{
if (!tegra_hdmi_is_connected(hdmi) || !hdmi->mon_spec_valid)
return 0;
return (hdmi->mon_spec.misc & FB_MISC_HDMI_FORUM) ?
CEA_861_F_MODEDB_SIZE : CEA_861_D_MODEDB_SIZE;
}
static void tegra_hdmi_get_cea_fb_videomode(struct fb_videomode *m,
struct tegra_hdmi *hdmi)
{
struct tegra_dc *dc = hdmi->dc;
struct tegra_dc_mode dc_mode;
bool yuv_bypass_vmode = false;
memcpy(&dc_mode, &dc->mode, sizeof(dc->mode));
/* get CEA video timings */
yuv_bypass_vmode = (dc_mode.vmode & FB_VMODE_YUV_MASK) &&
(dc_mode.vmode & FB_VMODE_BYPASS);
/*
* On T21x, the bypass flag is exclusively sent as part of the flip, and
* not as part of the modeset.
*/
if (yuv_bypass_vmode || tegra_dc_is_t21x()) {
if (tegra_dc_is_yuv420_8bpc(&dc_mode)) {
dc_mode.h_back_porch *= 2;
dc_mode.h_front_porch *= 2;
dc_mode.h_sync_width *= 2;
dc_mode.h_active *= 2;
dc_mode.pclk *= 2;
} else if (tegra_dc_is_yuv420_10bpc(&dc_mode)) {
dc_mode.h_back_porch = (dc_mode.h_back_porch * 8) / 5;
dc_mode.h_front_porch = (dc_mode.h_front_porch * 8) / 5;
dc_mode.h_sync_width = (dc_mode.h_sync_width * 8) / 5;
dc_mode.h_active = (dc_mode.h_active * 8) / 5;
dc_mode.pclk = (dc_mode.pclk / 5) * 8;
}
}
tegra_dc_to_fb_videomode(m, &dc_mode);
/* only interlaced required for VIC identification */
m->vmode &= FB_VMODE_INTERLACED;
}
__maybe_unused
static int tegra_hdmi_find_cea_vic(struct tegra_hdmi *hdmi)
{
struct fb_videomode m;
struct tegra_dc *dc = hdmi->dc;
unsigned i;
unsigned best = 0;
u32 modedb_size = tegra_hdmi_get_cea_modedb_size(hdmi);
if (dc->initialized) {
u32 vic = tegra_sor_readl(hdmi->sor,
NV_SOR_HDMI_AVI_INFOFRAME_SUBPACK0_HIGH) & 0xff;
if (!vic)
dev_warn(&dc->ndev->dev, "hdmi: BL set VIC 0\n");
return vic;
}
tegra_hdmi_get_cea_fb_videomode(&m, hdmi);
m.flag &= ~FB_FLAG_RATIO_MASK;
m.flag |= tegra_dc_get_aspect_ratio(dc);
for (i = 1; i < modedb_size; i++) {
const struct fb_videomode *curr = &cea_modes[i];
if (!fb_mode_is_equal_tolerance(curr, &m,
FB_MODE_TOLERANCE_DEFAULT))
continue;
if (!best)
best = i;
/* if either flag is set, then match is required */
if (curr->flag &
(FB_FLAG_RATIO_4_3 | FB_FLAG_RATIO_16_9 |
FB_FLAG_RATIO_64_27 | FB_FLAG_RATIO_256_135)) {
if (m.flag & curr->flag & FB_FLAG_RATIO_4_3)
best = i;
else if (m.flag & curr->flag & FB_FLAG_RATIO_16_9)
best = i;
else if (m.flag & curr->flag & FB_FLAG_RATIO_64_27)
best = i;
else if (m.flag & curr->flag & FB_FLAG_RATIO_256_135)
best = i;
} else {
best = i;
}
}
return best;
}
static u32 tegra_hdmi_get_aspect_ratio(struct tegra_hdmi *hdmi)
{
u32 aspect_ratio;
switch (hdmi->dc->mode.avi_m) {
case TEGRA_DC_MODE_AVI_M_4_3:
aspect_ratio = HDMI_AVI_ASPECT_RATIO_4_3;
break;
case TEGRA_DC_MODE_AVI_M_16_9:
aspect_ratio = HDMI_AVI_ASPECT_RATIO_16_9;
break;
/*
* no avi_m field for picture aspect ratio 64:27 and 256:135.
* sink detects via VIC, avi_m is 0.
*/
case TEGRA_DC_MODE_AVI_M_64_27: /* fall through */
case TEGRA_DC_MODE_AVI_M_256_135: /* fall through */
default:
aspect_ratio = HDMI_AVI_ASPECT_RATIO_NO_DATA;
}
/* For seamless HDMI, read aspect ratio parameters from bootloader
* set AVI Infoframe parameters
*/
if ((aspect_ratio == HDMI_AVI_ASPECT_RATIO_NO_DATA) &&
(hdmi->dc->initialized)) {
u32 temp = 0;
temp = tegra_sor_readl(hdmi->sor,
NV_SOR_HDMI_AVI_INFOFRAME_SUBPACK0_LOW);
temp = (temp >> 20) & 0x3;
switch (temp) {
case 1:
aspect_ratio = HDMI_AVI_ASPECT_RATIO_4_3;
break;
case 2:
aspect_ratio = HDMI_AVI_ASPECT_RATIO_16_9;
break;
default:
aspect_ratio = HDMI_AVI_ASPECT_RATIO_NO_DATA;
}
}
return aspect_ratio;
}
static u32 tegra_hdmi_get_rgb_ycc(struct tegra_hdmi *hdmi)
{
int yuv_flag = hdmi->dc->mode.vmode & FB_VMODE_YUV_MASK;
/*
* For seamless HDMI, read YUV flag parameters from bootloader
* set AVI Infoframe parameters
*/
if (hdmi->dc->initialized) {
u32 temp = 0;
temp = tegra_sor_readl(hdmi->sor,
NV_SOR_HDMI_AVI_INFOFRAME_SUBPACK0_LOW);
temp = (temp >> 13) & 0x3;
switch (temp) {
case HDMI_AVI_RGB:
return HDMI_AVI_RGB;
case HDMI_AVI_YCC_420:
return HDMI_AVI_YCC_420;
default:
dev_warn(&hdmi->dc->ndev->dev, "hdmi: BL didn't set RGB/YUV indicator flag\n");
break;
}
}
if (yuv_flag & (FB_VMODE_Y420 | FB_VMODE_Y420_ONLY))
return HDMI_AVI_YCC_420;
else if (yuv_flag & FB_VMODE_Y422)
return HDMI_AVI_YCC_422;
else if (yuv_flag & FB_VMODE_Y444)
return HDMI_AVI_YCC_444;
return HDMI_AVI_RGB;
}
static bool tegra_hdmi_is_ex_colorimetry(struct tegra_hdmi *hdmi)
{
return !!(hdmi->dc->mode.vmode & FB_VMODE_EC_ENABLE);
}
static u32 tegra_hdmi_get_ex_colorimetry(struct tegra_hdmi *hdmi)
{
u32 vmode = hdmi->dc->mode.vmode;
return tegra_hdmi_is_ex_colorimetry(hdmi) ?
((vmode & FB_VMODE_EC_MASK) >> FB_VMODE_EC_SHIFT) :
HDMI_AVI_EXT_COLORIMETRY_INVALID;
}
static u32 tegra_hdmi_get_rgb_quant(struct tegra_hdmi *hdmi)
{
u32 vmode = hdmi->dc->mode.vmode;
/*
* For seamless HDMI, read Q0/Q1 from bootloader
* set AVI Infoframe packet contents of the HDMI SPEC
*/
if (hdmi->dc->initialized) {
u32 temp = 0;
dev_info(&hdmi->dc->ndev->dev, "hdmi: get RGB quant from REG programmed by BL.\n");
temp = tegra_sor_readl(hdmi->sor,
NV_SOR_HDMI_AVI_INFOFRAME_SUBPACK0_LOW);
temp = (temp >> 26) & 0x3;
switch (temp) {
case HDMI_AVI_RGB_QUANT_DEFAULT:
case HDMI_AVI_RGB_QUANT_LIMITED:
case HDMI_AVI_RGB_QUANT_FULL:
return temp;
default:
dev_warn(&hdmi->dc->ndev->dev,
"hdmi: BL didn't set quantization range\n");
return HDMI_AVI_RGB_QUANT_DEFAULT;
}
}
return (vmode & FB_VMODE_LIMITED_RANGE) ? HDMI_AVI_RGB_QUANT_LIMITED :
HDMI_AVI_RGB_QUANT_FULL;
}
static u32 tegra_hdmi_get_ycc_quant(struct tegra_hdmi *hdmi)
{
u32 vmode = hdmi->dc->mode.vmode;
u32 hdmi_quant = HDMI_AVI_YCC_QUANT_NONE;
/*
* For seamless HDMI, read YQ1/YQ1 from bootloader
* set AVI Infoframe packet contents of the HDMI SPEC
*/
if (hdmi->dc->initialized) {
u32 temp = 0;
dev_info(&hdmi->dc->ndev->dev, "hdmi: get YCC quant from REG programmed by BL.\n");
temp = tegra_sor_readl(hdmi->sor,
NV_SOR_HDMI_AVI_INFOFRAME_SUBPACK0_HIGH);
temp = (temp >> 14) & 0x3;
switch (temp) {
case HDMI_AVI_YCC_QUANT_LIMITED:
case HDMI_AVI_YCC_QUANT_FULL:
return temp;
default:
dev_warn(&hdmi->dc->ndev->dev,
"hdmi: BL didn't set ycc quantization range\n");
return HDMI_AVI_YCC_QUANT_NONE;
}
}
dev_info(&hdmi->dc->ndev->dev, "hdmi: get YCC quant from EDID.\n");
if (tegra_edid_is_yuv_quantization_selectable(hdmi->edid)) {
if (vmode & FB_VMODE_LIMITED_RANGE)
hdmi_quant = HDMI_AVI_YCC_QUANT_LIMITED;
else
hdmi_quant = HDMI_AVI_YCC_QUANT_FULL;
}
return hdmi_quant;
}
static void tegra_hdmi_avi_infoframe_update(struct tegra_hdmi *hdmi)
{
struct hdmi_avi_infoframe *avi = &hdmi->avi;
memset(&hdmi->avi, 0, sizeof(hdmi->avi));
avi->scan = HDMI_AVI_UNDERSCAN;
avi->bar_valid = HDMI_AVI_BAR_INVALID;
avi->act_fmt_valid = HDMI_AVI_ACTIVE_FORMAT_INVALID;
avi->rgb_ycc = tegra_hdmi_get_rgb_ycc(hdmi);
avi->act_format = HDMI_AVI_ACTIVE_FORMAT_SAME;
avi->aspect_ratio = tegra_hdmi_get_aspect_ratio(hdmi);
avi->colorimetry = tegra_hdmi_is_ex_colorimetry(hdmi) ?
HDMI_AVI_COLORIMETRY_EXTENDED_VALID :
HDMI_AVI_COLORIMETRY_DEFAULT;
avi->scaling = HDMI_AVI_SCALING_UNKNOWN;
avi->rgb_quant = tegra_hdmi_get_rgb_quant(hdmi);
avi->ext_colorimetry = tegra_hdmi_get_ex_colorimetry(hdmi);
switch (hdmi->avi_colorimetry) {
case TEGRA_DC_EXT_AVI_COLORIMETRY_BT2020_YCC_RGB:
avi->colorimetry = HDMI_AVI_COLORIMETRY_EXTENDED_VALID;
avi->ext_colorimetry = HDMI_AVI_EXT_COLORIMETRY_BT2020_YCC_RGB;
break;
case TEGRA_DC_EXT_AVI_COLORIMETRY_xvYCC709:
avi->colorimetry = HDMI_AVI_COLORIMETRY_EXTENDED_VALID;
avi->ext_colorimetry = HDMI_AVI_EXT_COLORIMETRY_xvYCC709;
break;
default:
/* Let default value as it is.*/
break;
}
avi->it_content = HDMI_AVI_IT_CONTENT_FALSE;
/* set correct vic if video format is cea defined else set 0 */
avi->video_format = tegra_hdmi_find_cea_vic(hdmi);
/* set aspect ratio to match CEA VIC */
if (avi->video_format) {
/* Only 4:3 & 16:9 are valid picture aspect ratios for avi_m */
if (cea_modes[avi->video_format].flag & FB_FLAG_RATIO_4_3)
avi->aspect_ratio = HDMI_AVI_ASPECT_RATIO_4_3;
else if (cea_modes[avi->video_format].flag & FB_FLAG_RATIO_16_9)
avi->aspect_ratio = HDMI_AVI_ASPECT_RATIO_16_9;
}
avi->pix_rep = HDMI_AVI_NO_PIX_REPEAT;
avi->it_content_type = HDMI_AVI_IT_CONTENT_NONE;
avi->ycc_quant = tegra_hdmi_get_ycc_quant(hdmi);
avi->top_bar_end_line_low_byte = 0;
avi->top_bar_end_line_high_byte = 0;
avi->bot_bar_start_line_low_byte = 0;
avi->bot_bar_start_line_high_byte = 0;
avi->left_bar_end_pixel_low_byte = 0;
avi->left_bar_end_pixel_high_byte = 0;
avi->right_bar_start_pixel_low_byte = 0;
avi->right_bar_start_pixel_high_byte = 0;
}
static void tegra_hdmi_avi_infoframe(struct tegra_hdmi *hdmi)
{
struct tegra_dc_sor_data *sor = hdmi->sor;
if (hdmi->dvi)
return;
/* disable avi infoframe before configuring except for seamless case */
if (!hdmi->dc->initialized)
tegra_sor_writel(sor, NV_SOR_HDMI_AVI_INFOFRAME_CTRL, 0);
tegra_hdmi_avi_infoframe_update(hdmi);
tegra_hdmi_infoframe_pkt_write(hdmi, NV_SOR_HDMI_AVI_INFOFRAME_HEADER,
HDMI_INFOFRAME_TYPE_AVI,
HDMI_INFOFRAME_VS_AVI,
HDMI_INFOFRAME_LEN_AVI,
&hdmi->avi, sizeof(hdmi->avi),
false);
/* Send infoframe every frame, checksum hw generated */
tegra_sor_writel(sor, NV_SOR_HDMI_AVI_INFOFRAME_CTRL,
NV_SOR_HDMI_AVI_INFOFRAME_CTRL_ENABLE_YES |
NV_SOR_HDMI_AVI_INFOFRAME_CTRL_OTHER_DISABLE |
NV_SOR_HDMI_AVI_INFOFRAME_CTRL_SINGLE_DISABLE |
NV_SOR_HDMI_AVI_INFOFRAME_CTRL_CHECKSUM_ENABLE);
}
static int tegra_dc_hdmi_set_avi(struct tegra_dc *dc, struct tegra_dc_ext_avi *avi)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
hdmi->avi_colorimetry = avi->avi_colorimetry;
/* Setting AVI infoframe externally */
tegra_hdmi_avi_infoframe(hdmi);
return 0;
}
static int tegra_hdmi_get_extended_vic(const struct tegra_dc_mode *mode)
{
struct fb_videomode m;
struct tegra_dc_mode mode_fixed;
unsigned i;
mode_fixed = *mode;
if (mode_fixed.vmode & FB_VMODE_1000DIV1001) {
mode_fixed.pclk = (u64)mode_fixed.pclk * 1001 / 1000;
mode_fixed.vmode &= ~FB_VMODE_1000DIV1001;
}
tegra_dc_to_fb_videomode(&m, &mode_fixed);
/* only interlaced required for VIC identification */
m.vmode &= FB_VMODE_INTERLACED;
for (i = 1; i < HDMI_EXT_MODEDB_SIZE; i++) {
const struct fb_videomode *curr = &hdmi_ext_modes[i];
if (fb_mode_is_equal_tolerance(curr, &m,
FB_MODE_TOLERANCE_DEFAULT))
return i;
}
return 0;
}
static void tegra_hdmi_vendor_infoframe_update(struct tegra_hdmi *hdmi)
{
struct hdmi_vendor_infoframe *vsi = &hdmi->vsi;
u8 extended_vic;
memset(&hdmi->vsi, 0, sizeof(hdmi->vsi));
vsi->oui = HDMI_LICENSING_LLC_OUI;
extended_vic = tegra_hdmi_get_extended_vic(&hdmi->dc->mode);
if (extended_vic) {
vsi->video_format =
HDMI_VENDOR_VIDEO_FORMAT_EXTENDED;
vsi->extended_vic = extended_vic;
}
}
static void tegra_hdmi_vendor_infoframe(struct tegra_hdmi *hdmi)
{
/* hdmi licensing, LLC vsi playload len as per hdmi1.4b */
#define HDMI_INFOFRAME_LEN_VENDOR_LLC (6)
struct tegra_dc_sor_data *sor = hdmi->sor;
if (hdmi->dvi)
return;
/* disable vsi infoframe before configuring */
tegra_sor_writel(sor, NV_SOR_HDMI_VSI_INFOFRAME_CTRL, 0);
tegra_hdmi_vendor_infoframe_update(hdmi);
tegra_hdmi_infoframe_pkt_write(hdmi, NV_SOR_HDMI_VSI_INFOFRAME_HEADER,
HDMI_INFOFRAME_TYPE_VENDOR,
HDMI_INFOFRAME_VS_VENDOR,
HDMI_INFOFRAME_LEN_VENDOR_LLC,
&hdmi->vsi, sizeof(hdmi->vsi),
false);
/* Send infoframe every frame, checksum hw generated */
tegra_sor_writel(sor, NV_SOR_HDMI_VSI_INFOFRAME_CTRL,
NV_SOR_HDMI_VSI_INFOFRAME_CTRL_ENABLE_YES |
NV_SOR_HDMI_VSI_INFOFRAME_CTRL_OTHER_DISABLE |
NV_SOR_HDMI_VSI_INFOFRAME_CTRL_SINGLE_DISABLE |
NV_SOR_HDMI_VSI_INFOFRAME_CTRL_CHECKSUM_ENABLE);
#undef HDMI_INFOFRAME_LEN_VENDOR_LLC
}
static void tegra_hdmi_hdr_infoframe_update(struct tegra_hdmi *hdmi)
{
struct hdmi_hdr_infoframe *hdr = &hdmi->hdr;
memset(&hdmi->hdr, 0, sizeof(hdmi->hdr));
hdr->eotf = hdmi->dc->hdr.eotf;
hdr->static_metadata_id = hdmi->dc->hdr.static_metadata_id;
/* PB3-14 : Group 1 : Static Metadata*/
hdr->display_primaries_x_0_lsb = hdmi->dc->hdr.static_metadata[0];
hdr->display_primaries_x_0_msb = hdmi->dc->hdr.static_metadata[1];
hdr->display_primaries_y_0_lsb = hdmi->dc->hdr.static_metadata[2];
hdr->display_primaries_y_0_msb = hdmi->dc->hdr.static_metadata[3];
hdr->display_primaries_x_1_lsb = hdmi->dc->hdr.static_metadata[4];
hdr->display_primaries_x_1_msb = hdmi->dc->hdr.static_metadata[5];
hdr->display_primaries_y_1_lsb = hdmi->dc->hdr.static_metadata[6];
hdr->display_primaries_y_1_msb = hdmi->dc->hdr.static_metadata[7];
hdr->display_primaries_x_2_lsb = hdmi->dc->hdr.static_metadata[8];
hdr->display_primaries_x_2_msb = hdmi->dc->hdr.static_metadata[9];
hdr->display_primaries_y_2_lsb = hdmi->dc->hdr.static_metadata[10];
hdr->display_primaries_y_2_msb = hdmi->dc->hdr.static_metadata[11];
/* PB15-18 : Group 2 : Static Metadata*/
hdr->white_point_x_lsb = hdmi->dc->hdr.static_metadata[12];
hdr->white_point_x_msb = hdmi->dc->hdr.static_metadata[13];
hdr->white_point_y_lsb = hdmi->dc->hdr.static_metadata[14];
hdr->white_point_y_msb = hdmi->dc->hdr.static_metadata[15];
/* PB19-20 : Group 3 : Static Metadata*/
hdr->max_display_mastering_luminance_lsb =
hdmi->dc->hdr.static_metadata[16];
hdr->max_display_mastering_luminance_msb =
hdmi->dc->hdr.static_metadata[17];
/* PB21-22 : Group 4 : Static Metadata*/
hdr->min_display_mastering_luminance_lsb =
hdmi->dc->hdr.static_metadata[18];
hdr->min_display_mastering_luminance_msb =
hdmi->dc->hdr.static_metadata[19];
/* PB23-24 : Group 5 : Static Metadata*/
hdr->max_content_light_level_lsb = hdmi->dc->hdr.static_metadata[20];
hdr->max_content_light_level_msb = hdmi->dc->hdr.static_metadata[21];
/* PB25-26 : Group 6 : Static Metadata*/
hdr->max_frame_avg_light_level_lsb = hdmi->dc->hdr.static_metadata[22];
hdr->min_frame_avg_light_level_msb = hdmi->dc->hdr.static_metadata[23];
return;
}
static void tegra_hdmi_hdr_infoframe(struct tegra_hdmi *hdmi)
{
struct tegra_dc_sor_data *sor = hdmi->sor;
/* set_bits = contains all the bits to be set
* for NV_SOR_HDMI_GENERIC_CTRL reg */
u32 set_bits = (NV_SOR_HDMI_GENERIC_CTRL_ENABLE_YES |
NV_SOR_HDMI_GENERIC_CTRL_AUDIO_ENABLE);
/* disable generic infoframe before configuring */
tegra_sor_writel(sor, NV_SOR_HDMI_GENERIC_CTRL, 0);
tegra_hdmi_hdr_infoframe_update(hdmi);
tegra_hdmi_infoframe_pkt_write(hdmi, NV_SOR_HDMI_GENERIC_HEADER,
HDMI_INFOFRAME_TYPE_HDR,
HDMI_INFOFRAME_VS_HDR,
HDMI_INFOFRAME_LEN_HDR,
&hdmi->hdr, sizeof(hdmi->hdr),
true);
/* set the required bits in NV_SOR_HDMI_GENERIC_CTRL*/
tegra_sor_writel(sor, NV_SOR_HDMI_GENERIC_CTRL, set_bits);
return;
}
static void tegra_hdmi_spd_infoframe_update(struct tegra_hdmi *hdmi)
{
struct hdmi_spd_infoframe *spd = &hdmi->spd;
struct spd_infoframe *spd_infoframe =
hdmi->dc->pdata->default_out->hdmi_out->spd_infoframe;
memset(&hdmi->spd, 0, sizeof(hdmi->spd));
if (!spd_infoframe)
return;
/* PB1-8 : Vendor Name */
spd->vendor_name_char_1 = spd_infoframe->vendor_name[0];
spd->vendor_name_char_2 = spd_infoframe->vendor_name[1];
spd->vendor_name_char_3 = spd_infoframe->vendor_name[2];
spd->vendor_name_char_4 = spd_infoframe->vendor_name[3];
spd->vendor_name_char_5 = spd_infoframe->vendor_name[4];
spd->vendor_name_char_6 = spd_infoframe->vendor_name[5];
spd->vendor_name_char_7 = spd_infoframe->vendor_name[6];
spd->vendor_name_char_8 = spd_infoframe->vendor_name[7];
/* PB9-24 : Product Description */
spd->prod_desc_char_1 = spd_infoframe->prod_desc[0];
spd->prod_desc_char_2 = spd_infoframe->prod_desc[1];
spd->prod_desc_char_3 = spd_infoframe->prod_desc[2];
spd->prod_desc_char_4 = spd_infoframe->prod_desc[3];
spd->prod_desc_char_5 = spd_infoframe->prod_desc[4];
spd->prod_desc_char_6 = spd_infoframe->prod_desc[5];
spd->prod_desc_char_7 = spd_infoframe->prod_desc[6];
spd->prod_desc_char_8 = spd_infoframe->prod_desc[7];
spd->prod_desc_char_9 = spd_infoframe->prod_desc[8];
spd->prod_desc_char_10 = spd_infoframe->prod_desc[9];
spd->prod_desc_char_11 = spd_infoframe->prod_desc[10];
spd->prod_desc_char_12 = spd_infoframe->prod_desc[11];
spd->prod_desc_char_13 = spd_infoframe->prod_desc[12];
spd->prod_desc_char_14 = spd_infoframe->prod_desc[13];
spd->prod_desc_char_15 = spd_infoframe->prod_desc[14];
spd->prod_desc_char_16 = spd_infoframe->prod_desc[15];
/* PB25 : Source Information */
spd->source_information = spd_infoframe->source_information;
}
static void tegra_hdmi_spd_infoframe(struct tegra_hdmi *hdmi)
{
struct tegra_dc_sor_data *sor = hdmi->sor;
u32 val, set_bits;
/* If the generic infoframe is not for SPD, return */
if (hdmi->pdata->generic_infoframe_type != HDMI_INFOFRAME_TYPE_SPD)
return;
/* set_bits = contains all the bits to be set
* for NV_SOR_HDMI_GENERIC_CTRL reg
*/
set_bits = (NV_SOR_HDMI_GENERIC_CTRL_ENABLE_YES |
NV_SOR_HDMI_GENERIC_CTRL_OTHER_DISABLE |
NV_SOR_HDMI_GENERIC_CTRL_SINGLE_DISABLE);
/* read the current value to restore some bit values */
val = (tegra_sor_readl(sor, NV_SOR_HDMI_GENERIC_CTRL)
& ~set_bits);
/* disable generic infoframe before configuring */
tegra_sor_writel(sor, NV_SOR_HDMI_GENERIC_CTRL, 0);
tegra_hdmi_spd_infoframe_update(hdmi);
tegra_hdmi_infoframe_pkt_write(hdmi, NV_SOR_HDMI_GENERIC_HEADER,
HDMI_INFOFRAME_TYPE_SPD,
HDMI_INFOFRAME_VS_SPD,
HDMI_INFOFRAME_LEN_SPD,
&hdmi->spd, sizeof(hdmi->spd),
true);
/* set the required bits in NV_SOR_HDMI_GENERIC_CTRL*/
val = val | set_bits;
tegra_sor_writel(sor, NV_SOR_HDMI_GENERIC_CTRL, val);
}
__maybe_unused
static int tegra_hdmi_scdc_read(struct tegra_hdmi *hdmi,
u8 offset_data[][2], u32 n_entries)
{
u32 i;
int ret = 0;
struct i2c_adapter *i2c_adap = i2c_get_adapter(hdmi->dc->out->ddc_bus);
struct i2c_msg msg[] = {
{
.addr = 0x54,
.len = 1,
.buf = NULL,
},
{
.addr = 0x54,
.flags = I2C_M_RD,
.len = 1,
.buf = NULL,
},
};
_tegra_hdmi_ddc_enable(hdmi);
for (i = 0; i < n_entries; i++) {
msg[0].buf = offset_data[i];
msg[1].buf = &offset_data[i][1];
do {
ret = tegra_hdmi_scdc_i2c_xfer(hdmi->dc, msg,
ARRAY_SIZE(msg));
} while ((ret < 0) && !tegra_edid_i2c_divide_rate(hdmi->edid));
}
/* Reset ddc i2c clock rate to original rate */
ret = tegra_edid_i2c_adap_change_rate(i2c_adap,
hdmi->ddc_i2c_original_rate);
if (ret < 0)
dev_info(&hdmi->dc->ndev->dev,
"Failed to reset DDC i2c clock rate for scdc read\n");
_tegra_hdmi_ddc_disable(hdmi);
return 0;
}
static int tegra_hdmi_scdc_write(struct tegra_hdmi *hdmi,
u8 offset_data[][2], u32 n_entries)
{
u32 i;
struct i2c_msg msg[] = {
{
.addr = 0x54,
.len = 2,
.buf = NULL,
},
};
_tegra_hdmi_ddc_enable(hdmi);
for (i = 0; i < n_entries; i++) {
msg[0].buf = offset_data[i];
tegra_hdmi_scdc_i2c_xfer(hdmi->dc, msg, ARRAY_SIZE(msg));
}
_tegra_hdmi_ddc_disable(hdmi);
return 0;
}
static int tegra_hdmi_v2_x_mon_config(struct tegra_hdmi *hdmi, bool enable)
{
u8 tmds_config_en[][2] = {
{
HDMI_SCDC_TMDS_CONFIG_OFFSET,
(HDMI_SCDC_TMDS_CONFIG_BIT_CLK_RATIO_40 |
HDMI_SCDC_TMDS_CONFIG_SCRAMBLING_EN)
},
};
u8 tmds_config_dis[][2] = {
{
HDMI_SCDC_TMDS_CONFIG_OFFSET,
0
},
};
if (hdmi->dc->vedid)
goto skip_scdc_i2c;
tegra_hdmi_scdc_write(hdmi,
enable ? tmds_config_en : tmds_config_dis,
ARRAY_SIZE(tmds_config_en));
skip_scdc_i2c:
return 0;
}
static void tegra_hdmi_v2_x_host_config(struct tegra_hdmi *hdmi, bool enable)
{
u32 val = NV_SOR_HDMI2_CTRL_SCRAMBLE_ENABLE |
NV_SOR_HDMI2_CTRL_SSCP_START |
NV_SOR_HDMI2_CTRL_CLK_MODE_DIV_BY_4;
tegra_sor_write_field(hdmi->sor, NV_SOR_HDMI2_CTRL,
NV_SOR_HDMI2_CTRL_SCRAMBLE_ENABLE |
NV_SOR_HDMI2_CTRL_SSCP_START_MASK |
NV_SOR_HDMI2_CTRL_CLK_MODE_DIV_BY_4,
enable ? val : 0);
}
static int _tegra_hdmi_v2_x_config(struct tegra_hdmi *hdmi)
{
#define SCDC_STABILIZATION_DELAY_MS (20)
/* disable hdmi2.x config on host and monitor only
* if bootloader didn't initialize hdmi
*/
if (!hdmi->dc->initialized) {
tegra_hdmi_v2_x_mon_config(hdmi, false);
tegra_hdmi_v2_x_host_config(hdmi, false);
}
/* enable hdmi2.x config on host and monitor */
tegra_hdmi_v2_x_mon_config(hdmi, true);
msleep(SCDC_STABILIZATION_DELAY_MS);
tegra_hdmi_v2_x_host_config(hdmi, true);
return 0;
#undef SCDC_STABILIZATION_DELAY_MS
}
static int tegra_hdmi_v2_x_config(struct tegra_hdmi *hdmi)
{
_tegra_hdmi_v2_x_config(hdmi);
return 0;
}
static void tegra_hdmi_scdc_worker(struct work_struct *work)
{
struct tegra_hdmi *hdmi = container_of(to_delayed_work(work),
struct tegra_hdmi, scdc_work);
u8 rd_status_flags[][2] = {
{HDMI_SCDC_STATUS_FLAGS, 0x0}
};
unsigned long tmds_rate = tegra_sor_get_link_rate(hdmi->dc);
if (!hdmi->enabled || tmds_rate <= 340000000)
return;
if (hdmi->dc->vedid)
goto skip_scdc_i2c;
if (!tegra_edid_is_scdc_present(hdmi->dc->edid))
return;
tegra_hdmi_scdc_read(hdmi, rd_status_flags, ARRAY_SIZE(rd_status_flags));
if (!rd_status_flags[0][1] && (tmds_rate > 340000000)) {
dev_info(&hdmi->dc->ndev->dev, "hdmi: scdc scrambling status is reset, "
"trying to reconfigure.\n");
_tegra_hdmi_v2_x_config(hdmi);
}
skip_scdc_i2c:
/* reschedule the worker */
cancel_delayed_work(&hdmi->scdc_work);
schedule_delayed_work(&hdmi->scdc_work,
msecs_to_jiffies(HDMI_SCDC_MONITOR_TIMEOUT_MS));
}
static void _tegra_hdmi_clock_enable(struct tegra_hdmi *hdmi)
{
struct tegra_dc_sor_data *sor = hdmi->sor;
tegra_disp_clk_prepare_enable(sor->safe_clk);
tegra_hdmi_config_clk(hdmi, TEGRA_HDMI_SAFE_CLK);
/* Setting various clock to figure out whether bpmp is able to
* handle this
*/
/* Set PLLD2 to preset clock value*/
/* Set PLLD2 to SOR_REF_CLK*/
tegra_sor_clk_enable(sor);
}
static void _tegra_hdmi_clock_disable(struct tegra_hdmi *hdmi)
{
struct tegra_dc_sor_data *sor = hdmi->sor;
tegra_sor_clk_disable(sor);
tegra_disp_clk_disable_unprepare(sor->safe_clk);
}
void tegra_hdmi_get(struct tegra_dc *dc)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
if (atomic_inc_return(&hdmi->clock_refcount) == 1)
_tegra_hdmi_clock_enable(hdmi);
}
void tegra_hdmi_put(struct tegra_dc *dc)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
if (WARN_ONCE(atomic_read(&hdmi->clock_refcount) <= 0,
"hdmi: clock refcount imbalance"))
return;
if (atomic_dec_return(&hdmi->clock_refcount) == 0)
_tegra_hdmi_clock_disable(hdmi);
}
static inline u32 tegra_hdmi_get_bpp(struct tegra_hdmi *hdmi)
{
int yuv_flag = hdmi->dc->mode.vmode & FB_VMODE_YUV_MASK;
/* Special case for YUV422 modes. For all pixel depths, YUV422
* is packed as 24 BPP HDMI video stream
*/
if (yuv_flag & FB_VMODE_Y422)
return 24;
else if (yuv_flag & FB_VMODE_Y24)
return 24;
else if (yuv_flag & FB_VMODE_Y30)
return 30;
else if (yuv_flag & FB_VMODE_Y36)
return 36;
else if (yuv_flag & FB_VMODE_Y48)
return 48;
else
return 0;
}
static u32 tegra_hdmi_gcp_color_depth(struct tegra_hdmi *hdmi)
{
u32 gcp_cd = 0;
switch (tegra_hdmi_get_bpp(hdmi)) {
case 0:
gcp_cd = TEGRA_HDMI_BPP_UNKNOWN;
break;
case 24:
gcp_cd = TEGRA_HDMI_BPP_24;
break;
case 30:
gcp_cd = TEGRA_HDMI_BPP_30;
break;
case 36:
gcp_cd = TEGRA_HDMI_BPP_36;
break;
case 48:
gcp_cd = TEGRA_HDMI_BPP_48;
break;
default:
dev_WARN(&hdmi->dc->ndev->dev,
"hdmi: unknown gcp color depth\n");
};
return gcp_cd;
}
/* return packing phase of last pixel in preceding video data period */
static u32 tegra_hdmi_gcp_packing_phase(struct tegra_hdmi *hdmi)
{
int yuv_flag = hdmi->dc->mode.vmode & FB_VMODE_YUV_MASK;
if (!tegra_hdmi_gcp_color_depth(hdmi))
return 0;
if ((IS_RGB(yuv_flag) && (yuv_flag == FB_VMODE_Y36)) ||
(yuv_flag == (FB_VMODE_Y444 | FB_VMODE_Y36)))
return 2;
else
return 0;
}
static bool tegra_hdmi_gcp_default_phase_en(struct tegra_hdmi *hdmi)
{
int yuv_flag = hdmi->dc->mode.vmode & FB_VMODE_YUV_MASK;
if (!tegra_hdmi_gcp_color_depth(hdmi))
return false;
if (tegra_dc_is_yuv420_10bpc(&hdmi->dc->mode) ||
(yuv_flag == (FB_VMODE_Y444 | FB_VMODE_Y36)) ||
(IS_RGB(yuv_flag) && (yuv_flag == FB_VMODE_Y36)))
return true;
else
return false;
}
/* general control packet */
static void tegra_hdmi_gcp(struct tegra_hdmi *hdmi)
{
#define GCP_SB1_PP_SHIFT 4
struct tegra_dc_sor_data *sor = hdmi->sor;
u8 sb1, sb2;
/* disable gcp before configuring */
tegra_sor_writel(sor, NV_SOR_HDMI_GCP_CTRL, 0);
sb1 = tegra_hdmi_gcp_packing_phase(hdmi) << GCP_SB1_PP_SHIFT |
tegra_hdmi_gcp_color_depth(hdmi);
sb2 = !!tegra_hdmi_gcp_default_phase_en(hdmi);
tegra_sor_writel(sor, NV_SOR_HDMI_GCP_SUBPACK(0),
sb1 << NV_SOR_HDMI_GCP_SUBPACK_SB1_SHIFT |
sb2 << NV_SOR_HDMI_GCP_SUBPACK_SB2_SHIFT);
/* Send gcp every frame */
tegra_sor_writel(sor, NV_SOR_HDMI_GCP_CTRL,
NV_SOR_HDMI_GCP_CTRL_ENABLE |
NV_SOR_HDMI_GCP_CTRL_OTHER_DIS |
NV_SOR_HDMI_GCP_CTRL_SINGLE_DIS);
#undef GCP_SB1_PP_SHIFT
}
static int tegra_hdmi_controller_enable(struct tegra_hdmi *hdmi)
{
struct tegra_dc *dc = hdmi->dc;
struct tegra_dc_sor_data *sor = hdmi->sor;
u32 dispclk_div_8_2 = 0;
int val = NV_SOR_CLK_CNTRL_DP_LINK_SPEED_G2_7;
tegra_dc_get(dc);
tegra_hdmi_get(dc);
if (tegra_platform_is_fpga())
tegra_sor_program_fpga_clk_mux(sor);
if (tegra_dc_is_t21x()) {
dispclk_div_8_2 = (dc->mode.pclk) / 1000000 * 4;
tegra_sor_writel(sor, NV_SOR_REFCLK,
NV_SOR_REFCLK_DIV_INT(dispclk_div_8_2 >> 2) |
NV_SOR_REFCLK_DIV_FRAC(dispclk_div_8_2));
}
/* half rate and double vco */
if (tegra_sor_get_link_rate(dc) > 340000000)
val = NV_SOR_CLK_CNTRL_DP_LINK_SPEED_G5_4;
val |= NV_SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_PCLK;
tegra_sor_writel(hdmi->sor, NV_SOR_CLK_CNTRL, val);
tegra_sor_cal(sor);
tegra_hdmi_config_tmds(hdmi);
tegra_sor_hdmi_pad_power_up(sor);
tegra_sor_power_lanes(sor, 4, true);
tegra_sor_config_xbar(hdmi->sor);
tegra_sor_clk_switch_setup(sor, true);
tegra_hdmi_config_clk(hdmi, TEGRA_HDMI_BRICK_CLK);
tegra_dc_sor_set_internal_panel(sor, false);
tegra_hdmi_config(hdmi);
/* For simulator we still have use cases where
* we can have hotplug without a valid edid. This
* check ensures we don't reference a null edid
* */
if (hdmi->edid) {
tegra_hdmi_avi_infoframe(hdmi);
tegra_hdmi_vendor_infoframe(hdmi);
tegra_hdmi_spd_infoframe(hdmi);
}
if (dc->out->vrr_hotplug_state == TEGRA_HPD_STATE_NORMAL)
tegra_dc_sor_attach(sor);
else
tegra_dc_enable_disp_ctrl_mode(dc);
/* enable hdcp only if valid edid */
if (hdmi->edid_src == EDID_SRC_PANEL && !hdmi->dc->vedid && hdmi->edid &&
(tegra_edid_get_monspecs(hdmi->edid, &hdmi->mon_spec) == 0))
tegra_nvhdcp_set_plug(hdmi->nvhdcp, true);
if (hdmi->dpaux) {
mutex_lock(&hdmi->dpaux->lock);
tegra_dpaux_prod_set(hdmi->dpaux);
mutex_unlock(&hdmi->dpaux->lock);
}
if (tegra_dc_is_t21x()) {
tegra_dc_setup_clk(dc, dc->clk);
tegra_dc_hdmi_setup_clk(dc, hdmi->sor->sor_clk);
}
/* IS THE POWER ENABLE AFTER ATTACH IS VALID*/
/* TODO: Confirm sequence with HW */
tegra_sor_writel(sor, NV_SOR_SEQ_INST(0), 0x8080);
tegra_sor_writel(sor, NV_SOR_PWR, 0x80000001);
if (tegra_sor_get_link_rate(hdmi->dc) > 340000000) {
tegra_hdmi_v2_x_config(hdmi);
schedule_delayed_work(&hdmi->scdc_work,
msecs_to_jiffies(HDMI_SCDC_MONITOR_TIMEOUT_MS));
}
tegra_hdmi_gcp(hdmi);
/* check SOR pad PLL lock status */
/* for newer SOC than T210 */
if (!tegra_dc_is_t21x() &&
!(tegra_sor_readl(sor, nv_sor_pll4()) &
NV_SOR_PLL4_LOCKDET_MASK))
dev_err(&hdmi->dc->ndev->dev,
"hdmi: pad PLL is not locked!\n");
tegra_dc_put(dc);
return 0;
}
static void tegra_dc_hdmi_enable(struct tegra_dc *dc)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
if (hdmi->enabled)
return;
if (NULL != hdmi->out_ops && NULL != hdmi->out_ops->enable)
hdmi->out_ops->enable(hdmi);
tegra_hdmi_controller_enable(hdmi);
hdmi->enabled = true;
#ifdef CONFIG_TEGRA_HDA_DC
tegra_hda_enable(hdmi->hda_handle);
#endif
if (!hdmi->dvi) {
disp_state_extcon_aux_report(hdmi->sor->ctrl_num,
EXTCON_DISP_AUX_STATE_ENABLED);
pr_info("Extcon AUX%d(HDMI) enable\n", hdmi->sor->ctrl_num);
}
#ifdef CONFIG_SWITCH
if (!hdmi->dvi)
switch_set_state(&hdmi->audio_switch, 1);
#endif
}
static inline u32 __maybe_unused
tegra_hdmi_get_shift_clk_div(struct tegra_hdmi *hdmi)
{
/*
* HW does not support deep color yet
* always return 0
*/
return 0;
}
static void tegra_hdmi_config_clk_nvdisplay(struct tegra_hdmi *hdmi,
u32 clk_type)
{
if (clk_type == hdmi->clk_type)
return;
if (clk_type == TEGRA_HDMI_BRICK_CLK) {
struct tegra_dc_sor_data *sor = hdmi->sor;
long rate = clk_get_rate(sor->ref_clk);
if (tegra_sor_get_link_rate(hdmi->dc) > 340000000)
clk_set_rate(sor->ref_clk, (rate >> 1));
clk_set_rate(sor->pad_clk, rate);
clk_set_parent(sor->sor_clk, sor->pad_clk);
hdmi->clk_type = TEGRA_HDMI_BRICK_CLK;
} else if (clk_type == TEGRA_HDMI_SAFE_CLK) {
if (!hdmi->dc->initialized) {
clk_set_parent(hdmi->sor->sor_clk, hdmi->sor->safe_clk);
hdmi->clk_type = TEGRA_HDMI_SAFE_CLK;
}
} else {
dev_err(&hdmi->dc->ndev->dev,
"hdmi: incorrect clk type configured\n");
}
}
static void tegra_hdmi_config_clk_t21x(struct tegra_hdmi *hdmi, u32 clk_type)
{
if (clk_type == hdmi->clk_type)
return;
if (clk_type == TEGRA_HDMI_BRICK_CLK) {
u32 val;
struct tegra_dc_sor_data *sor = hdmi->sor;
int div = (tegra_sor_get_link_rate(hdmi->dc) < 340000000) ?
1 : 2;
unsigned long rate = clk_get_rate(sor->ref_clk);
unsigned long parent_rate =
clk_get_rate(clk_get_parent(sor->ref_clk));
/* Set sor divider */
if (rate != DIV_ROUND_UP(parent_rate, div)) {
rate = DIV_ROUND_UP(parent_rate, div);
clk_set_rate(sor->ref_clk, rate);
}
val = (tegra_sor_get_link_rate(hdmi->dc) < 340000000) ?
NV_SOR_CLK_CNTRL_DP_LINK_SPEED_G2_7 :
NV_SOR_CLK_CNTRL_DP_LINK_SPEED_G5_4;
val |= NV_SOR_CLK_CNTRL_DP_CLK_SEL_SINGLE_PCLK;
/*
* Report brick configuration and rate, so that SOR clock tree
* is properly updated. No h/w changes by clock api calls below,
* just sync s/w state with brick h/w.
*/
rate = rate/NV_SOR_HDMI_BRICK_DIV*NV_SOR_HDMI_BRICK_MUL(val);
if (clk_get_parent(sor->pad_clk) != sor->ref_clk)
clk_set_parent(sor->pad_clk, sor->ref_clk);
clk_set_rate(sor->pad_clk, rate);
#ifdef CONFIG_TEGRA_CORE_DVFS
/*
* Select primary -- HDMI -- DVFS table for SOR clock (if SOR
* clock has single DVFS table for all modes, nothing changes).
*/
tegra_dvfs_use_alt_freqs_on_clk(sor->sor_clk, false);
#endif
/* Select sor clock muxes */
#ifdef CONFIG_TEGRA_CLK_FRAMEWORK
tegra_clk_cfg_ex(sor->sor_clk, TEGRA_CLK_SOR_CLK_SEL, 3);
#else
clk_set_parent(sor->sor_clk, sor->pad_clk);
#endif
tegra_dc_writel(hdmi->dc, PIXEL_CLK_DIVIDER_PCD1 |
SHIFT_CLK_DIVIDER(tegra_hdmi_get_shift_clk_div(hdmi)),
DC_DISP_DISP_CLOCK_CONTROL);
hdmi->clk_type = TEGRA_HDMI_BRICK_CLK;
} else if (clk_type == TEGRA_HDMI_SAFE_CLK) {
if (!hdmi->dc->initialized) {
/* Select sor clock muxes */
#ifdef CONFIG_TEGRA_CLK_FRAMEWORK
tegra_clk_cfg_ex(hdmi->sor->sor_clk,
TEGRA_CLK_SOR_CLK_SEL, 0);
#else
clk_set_parent(hdmi->sor->sor_clk, hdmi->sor->safe_clk);
#endif
hdmi->clk_type = TEGRA_HDMI_SAFE_CLK;
}
} else {
dev_err(&hdmi->dc->ndev->dev, "hdmi: incorrect clk type configured\n");
}
}
static void tegra_hdmi_config_clk(struct tegra_hdmi *hdmi, u32 clk_type)
{
if (tegra_dc_is_nvdisplay())
return tegra_hdmi_config_clk_nvdisplay(hdmi, clk_type);
else
return tegra_hdmi_config_clk_t21x(hdmi, clk_type);
}
/* returns exact pixel clock in Hz */
static long __maybe_unused tegra_hdmi_get_pclk(struct tegra_dc_mode *mode)
{
long h_total, v_total;
long refresh, pclk;
h_total = mode->h_active + mode->h_front_porch + mode->h_back_porch +
mode->h_sync_width;
v_total = mode->v_active + mode->v_front_porch + mode->v_back_porch +
mode->v_sync_width;
refresh = tegra_dc_calc_refresh(mode);
refresh = DIV_ROUND_CLOSEST(refresh, 1000);
pclk = h_total * v_total * refresh;
if (mode->vmode & FB_VMODE_1000DIV1001)
pclk = pclk * 1000 / 1001;
return pclk;
}
static inline void tegra_sor_set_ref_clk_rate(struct tegra_dc_sor_data *sor)
{
unsigned long rate = tegra_sor_get_link_rate(sor->dc);
clk_set_rate(sor->ref_clk, rate);
}
static long tegra_dc_hdmi_setup_clk_nvdisplay(struct tegra_dc *dc,
struct clk *clk)
{
#define MIN_PARENT_CLK (27000000)
#define DIVIDER_CAP (128)
struct clk *parent_clk;
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
struct tegra_dc_sor_data *sor = hdmi->sor;
long parent_clk_rate;
int yuv_flag = hdmi->dc->mode.vmode & FB_VMODE_YUV_MASK;
if (!dc->out->parent_clk) {
dev_err(&dc->ndev->dev,
"hdmi: failed, no clock name for this node.\n");
return -EINVAL;
}
/* GET THE PARENT */
parent_clk = tegra_disp_clk_get(&dc->ndev->dev, dc->out->parent_clk);
if (IS_ERR_OR_NULL(parent_clk)) {
dev_err(&dc->ndev->dev, "hdmi: failed to get clock %s\n",
dc->out->parent_clk);
return -EINVAL;
}
if (!dc->mode.pclk_hz_used)
dc->mode.pclk = tegra_hdmi_get_pclk(&dc->mode);
/* Set rate on PARENT */
if (!dc->initialized) {
/*
* For RGB/YUV444 12bpc, the pclk:orclk ratio should be 2:3.
* The SOR refclk vs. pclk dividers should be in a 2:3 ratio if
* TMDS <= 340, and in a 4:3 ratio if TMDS > 340. Use a PCLK_DIV
* of 3 to comply with these constraints.
*/
parent_clk_rate = dc->mode.pclk;
if ((IS_RGB(yuv_flag) && (yuv_flag == FB_VMODE_Y36)) ||
(yuv_flag == (FB_VMODE_Y444 | FB_VMODE_Y36)))
parent_clk_rate *= 3;
/*
* For t18x plldx cannot go below 27MHz.
* Real HW limit is lesser though.
* 27Mz is chosen to have a safe margin.
*/
if (parent_clk_rate < (MIN_PARENT_CLK/DIVIDER_CAP)) {
dev_err(&dc->ndev->dev, "hdmi: unsupported parent clock rate (%ld).\n",
parent_clk_rate);
return -EINVAL;
}
while (parent_clk_rate < MIN_PARENT_CLK)
parent_clk_rate += parent_clk_rate;
clk_set_rate(parent_clk, parent_clk_rate);
if (clk == dc->clk)
clk_set_rate(clk, dc->mode.pclk);
}
tegra_sor_safe_clk_enable(sor);
clk_set_parent(sor->ref_clk, parent_clk);
if (!dc->initialized)
tegra_sor_set_ref_clk_rate(sor);
if (atomic_inc_return(&hdmi->clock_refcount) == 1)
tegra_sor_clk_enable(sor);
if (!dc->initialized)
clk_set_parent(sor->sor_clk, sor->safe_clk);
tegra_disp_clk_prepare_enable(sor->pad_clk);
tegra_disp_clk_prepare_enable(sor->sor_clk);
return tegra_dc_pclk_round_rate(dc, dc->mode.pclk);
}
static void tegra_dc_hdmi_configure_ss(struct tegra_dc *dc, struct clk *clk)
{
#if defined(CONFIG_ARCH_TEGRA_210_SOC)
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
if (!dc->pdata->plld2_ss_enable)
return;
if (dc->mode.pclk == 148500000 &&
dc->mode.h_active == 1920 &&
dc->mode.v_active == 1080 &&
(tegra_hdmi_find_cea_vic(hdmi) == 31)) {
tegra210_plld2_configure_ss(true);
} else {
tegra210_plld2_configure_ss(false);
}
#endif
}
static long tegra_dc_hdmi_setup_clk_t21x(struct tegra_dc *dc, struct clk *clk)
{
struct clk *parent_clk;
parent_clk = clk_get(NULL, "pll_d2_out0");
if (!dc->mode.pclk_hz_used)
dc->mode.pclk = tegra_hdmi_get_pclk(&dc->mode);
if (IS_ERR_OR_NULL(parent_clk)) {
dev_err(&dc->ndev->dev, "hdmi: parent clk get failed\n");
return 0;
}
if (!tegra_platform_is_silicon())
return dc->mode.pclk;
if (clk == dc->clk) {
if (clk_get_parent(clk) != parent_clk) {
if (clk_set_parent(clk, parent_clk)) {
dev_err(&dc->ndev->dev,
"hdmi: set dc parent failed\n");
return 0;
}
}
} else {
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
struct tegra_dc_sor_data *sor = hdmi->sor;
if (clk_get_parent(sor->ref_clk) != parent_clk) {
if (clk_set_parent(sor->ref_clk, parent_clk)) {
dev_err(&dc->ndev->dev,
"hdmi: set src switch parent failed\n");
return 0;
}
}
tegra_dc_hdmi_configure_ss(dc, clk);
}
if (dc->initialized)
goto skip_setup;
if (clk_get_rate(parent_clk) != dc->mode.pclk)
clk_set_rate(parent_clk, dc->mode.pclk);
skip_setup:
#ifdef CONFIG_TEGRA_CORE_DVFS
/*
* DC clock divider is controlled by DC driver transparently to clock
* framework -- hence, direct call to DVFS with target mode rate. SOR
* clock rate in clock tree is properly updated, and can be used for
* DVFS update.
*
* TODO: tegra_hdmi_controller_enable() procedure 1st configures SOR
* clock via tegra_hdmi_config_clk(), and then calls this function
* that may re-lock parent PLL. That needs to be double-checked:
* in general re-locking PLL while the downstream module is already
* sourced from it is not recommended. If/when the order of enabling
* HDMI controller is changed, we can remove direct DVFS call for SOR
* (but for DC it should be kept, anyway).
*/
if (clk == dc->clk)
tegra_dvfs_set_rate(clk, dc->mode.pclk);
else
tegra_dvfs_set_rate(clk, clk_get_rate(clk));
#endif
return tegra_dc_pclk_round_rate(dc, dc->mode.pclk);
}
static long tegra_dc_hdmi_setup_clk(struct tegra_dc *dc, struct clk *clk)
{
if (tegra_dc_is_nvdisplay())
return tegra_dc_hdmi_setup_clk_nvdisplay(dc, clk);
else
return tegra_dc_hdmi_setup_clk_t21x(dc, clk);
}
static void tegra_dc_hdmi_shutdown(struct tegra_dc *dc)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
_tegra_hdmivrr_activate(hdmi, false);
hdmi->device_shutdown = true;
tegra_nvhdcp_shutdown(hdmi->nvhdcp);
return;
}
static void tegra_dc_hdmi_disable(struct tegra_dc *dc)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
_tegra_hdmivrr_activate(hdmi, false);
if (NULL != hdmi->out_ops && NULL != hdmi->out_ops->disable)
hdmi->out_ops->disable(hdmi);
hdmi->enabled = false;
disp_state_extcon_aux_report(hdmi->sor->ctrl_num,
EXTCON_DISP_AUX_STATE_DISABLED);
pr_info("Extcon AUX%d(HDMI) disable\n", hdmi->sor->ctrl_num);
#ifdef CONFIG_SWITCH
switch_set_state(&hdmi->audio_switch, 0);
#endif
tegra_hdmi_controller_disable(hdmi);
#ifdef CONFIG_TEGRA_HDA_DC
tegra_hda_disable(hdmi->hda_handle);
#endif
return;
}
static bool tegra_dc_hdmi_detect(struct tegra_dc *dc)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
unsigned long delay = msecs_to_jiffies(HDMI_HPD_DEBOUNCE_DELAY_MS);
if (dc->out->hotplug_state != TEGRA_HPD_STATE_NORMAL)
delay = 0;
if ((tegra_platform_is_sim() || tegra_platform_is_fpga()) &&
(dc->out->hotplug_state == TEGRA_HPD_STATE_NORMAL)) {
complete(&dc->hpd_complete);
return true;
}
cancel_delayed_work(&hdmi->hpd_worker);
schedule_delayed_work(&hdmi->hpd_worker, delay);
return tegra_dc_hpd(dc);
}
static void tegra_dc_hdmi_suspend(struct tegra_dc *dc)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
_tegra_hdmivrr_activate(hdmi, false);
if (hdmi->pdata->hdmi2fpd_bridge_enable)
hdmi2fpd_suspend(dc);
if (hdmi->out_ops && hdmi->out_ops->suspend)
hdmi->out_ops->suspend(hdmi);
if (dc->out->flags & TEGRA_DC_OUT_HOTPLUG_WAKE_LP0) {
int wake_irq = gpio_to_irq(dc->out->hotplug_gpio);
int ret;
ret = enable_irq_wake(wake_irq);
if (ret < 0) {
dev_err(&dc->ndev->dev,
"%s: Couldn't enable HDMI wakeup, irq=%d, error=%d\n",
__func__, wake_irq, ret);
}
}
atomic_set(&hdmi->suspended, 1);
}
static void tegra_dc_hdmi_resume(struct tegra_dc *dc)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
atomic_set(&hdmi->suspended, 0);
if (dc->out->flags & TEGRA_DC_OUT_HOTPLUG_WAKE_LP0)
disable_irq_wake(gpio_to_irq(dc->out->hotplug_gpio));
if (hdmi->pdata->hdmi2fpd_bridge_enable)
hdmi2fpd_resume(dc);
if (hdmi->out_ops && hdmi->out_ops->resume)
hdmi->out_ops->resume(hdmi);
if (tegra_platform_is_sim() &&
(dc->out->hotplug_state == TEGRA_HPD_STATE_NORMAL))
return;
cancel_delayed_work(&hdmi->hpd_worker);
reinit_completion(&dc->hpd_complete);
/* If resume happens with a non-VRR monitor, the HPD
worker will correct the mode based on the new EDID */
_tegra_hdmivrr_activate(hdmi, true);
schedule_delayed_work(&hdmi->hpd_worker,
msecs_to_jiffies(HDMI_HPD_DEBOUNCE_DELAY_MS));
wait_for_completion(&dc->hpd_complete);
}
static int tegra_dc_hdmi_set_hdr(struct tegra_dc *dc)
{
u16 ret = 0;
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
/* If the generic infoframe is not for HDR, return */
if (hdmi->pdata->generic_infoframe_type != HDMI_INFOFRAME_TYPE_HDR)
return 0;
/* If the sink isn't HDR capable, return */
ret = tegra_edid_get_ex_hdr_cap(hdmi->edid);
if (!ret)
return 0;
/* Cancel any pending hdr-exit work */
if (dc->hdr.enabled)
cancel_delayed_work_sync(&hdmi->hdr_worker);
tegra_hdmi_hdr_infoframe(hdmi);
/*
*If hdr is disabled then send HDR infoframe for
*2 secs with SDR eotf and then stop
*/
if (dc->hdr.enabled)
return 0;
schedule_delayed_work(&hdmi->hdr_worker,
msecs_to_jiffies(HDMI_HDR_INFOFRAME_STOP_TIMEOUT_MS));
return 0;
}
static void tegra_hdmi_hdr_worker(struct work_struct *work)
{
u32 val = 0;
struct tegra_hdmi *hdmi = container_of(to_delayed_work(work),
struct tegra_hdmi, hdr_worker);
if (hdmi && hdmi->enabled) {
/* If hdr re-enabled within 2s, return.
* Note this an extra safety check since
* we should have already cancelled this work */
if (hdmi->dc->hdr.enabled)
return;
/* Read the current regsiter value to restore the bits */
val = tegra_sor_readl(hdmi->sor, NV_SOR_HDMI_GENERIC_CTRL);
/* Set val to disable generic infoframe */
val &= ~NV_SOR_HDMI_GENERIC_CTRL_ENABLE_YES;
tegra_sor_writel(hdmi->sor, NV_SOR_HDMI_GENERIC_CTRL, val);
}
return;
}
static int tegra_dc_hdmi_ddc_enable(struct tegra_dc *dc)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
_tegra_hdmi_ddc_enable(hdmi);
return 0;
}
static int tegra_dc_hdmi_ddc_disable(struct tegra_dc *dc)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
_tegra_hdmi_ddc_disable(hdmi);
return 0;
}
static void tegra_dc_hdmi_modeset_notifier(struct tegra_dc *dc)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
/* In case of seamless display, kernel carries forward BL config */
if (dc->initialized)
return;
tegra_hdmi_get(dc);
tegra_dc_io_start(dc);
/* disable hdmi2.x config on host and monitor */
if (tegra_sor_get_link_rate(dc) > 340000000) {
if (tegra_edid_is_scdc_present(dc->edid))
tegra_hdmi_v2_x_mon_config(hdmi, true);
tegra_hdmi_v2_x_host_config(hdmi, true);
} else {
if (tegra_edid_is_scdc_present(dc->edid))
tegra_hdmi_v2_x_mon_config(hdmi, false);
tegra_hdmi_v2_x_host_config(hdmi, false);
}
if (tegra_dc_is_t21x()) {
if (!(dc->mode.vmode & FB_VMODE_SET_YUV_MASK))
_tegra_dc_cmu_enable(dc,
dc->mode.vmode & FB_VMODE_LIMITED_RANGE);
}
tegra_dc_io_end(dc);
tegra_hdmi_put(dc);
}
int tegra_hdmi_get_hotplug_state(struct tegra_hdmi *hdmi)
{
rmb();
return hdmi->dc->out->hotplug_state;
}
void tegra_hdmi_set_hotplug_state(struct tegra_hdmi *hdmi, int new_hpd_state)
{
struct tegra_dc *dc = hdmi->dc;
int hotplug_state;
rmb();
hotplug_state = dc->out->hotplug_state;
dc->out->prev_hotplug_state = hotplug_state;
if (hotplug_state == TEGRA_HPD_STATE_NORMAL &&
new_hpd_state != TEGRA_HPD_STATE_NORMAL &&
dc->hotplug_supported) {
disable_irq(gpio_to_irq(dc->out->hotplug_gpio));
} else if (hotplug_state != TEGRA_HPD_STATE_NORMAL &&
new_hpd_state == TEGRA_HPD_STATE_NORMAL &&
dc->hotplug_supported) {
enable_irq(gpio_to_irq(dc->out->hotplug_gpio));
}
dc->out->hotplug_state = new_hpd_state;
wmb();
/*
* sw controlled plug/unplug.
* wait for any already executing hpd worker thread.
* No debounce delay, schedule immedately
*/
reinit_completion(&dc->hpd_complete);
cancel_delayed_work_sync(&hdmi->hpd_worker);
schedule_delayed_work(&hdmi->hpd_worker, 0);
wait_for_completion(&dc->hpd_complete);
}
#ifdef CONFIG_DEBUG_FS
/* show current hpd state */
static int tegra_hdmi_hotplug_dbg_show(struct seq_file *m, void *unused)
{
struct tegra_hdmi *hdmi = m->private;
struct tegra_dc *dc = hdmi->dc;
if (WARN_ON(!hdmi || !dc || !dc->out))
return -EINVAL;
/* make sure we see updated hotplug_state value */
rmb();
seq_printf(m, "hdmi hpd state: %d\n", dc->out->hotplug_state);
return 0;
}
/*
* sw control for hpd.
* 0 is normal state, hw drives hpd.
* -1 is force deassert, sw drives hpd.
* 1 is force assert, sw drives hpd.
* before releasing to hw, sw must ensure hpd state is normal i.e. 0
*/
static ssize_t tegra_hdmi_hotplug_dbg_write(struct file *file,
const char __user *addr,
size_t len, loff_t *pos)
{
struct seq_file *m = file->private_data;
struct tegra_hdmi *hdmi = m->private;
struct tegra_dc *dc = hdmi->dc;
long new_hpd_state;
int ret;
if (WARN_ON(!hdmi || !dc || !dc->out))
return -EINVAL;
ret = kstrtol_from_user(addr, len, 10, &new_hpd_state);
if (ret < 0)
return ret;
tegra_hdmi_set_hotplug_state(hdmi, new_hpd_state);
return len;
}
static int tegra_hdmi_hotplug_dbg_open(struct inode *inode, struct file *file)
{
return single_open(file, tegra_hdmi_hotplug_dbg_show, inode->i_private);
}
/* show current hpd/vhotplug state */
static int tegra_hdmi_vhotplug_dbg_show(struct seq_file *m, void *unused)
{
struct tegra_hdmi *hdmi = m->private;
struct tegra_dc *dc;
if (WARN_ON(!hdmi))
return -EINVAL;
dc = hdmi->dc;
if (WARN_ON(!dc || !dc->out))
return -EINVAL;
/* make sure we see updated hotplug_state value */
rmb();
seq_printf(m, "hdmi vhotplug state: %d\n", dc->out->vrr_hotplug_state);
return 0;
}
/*
* sw control for hpd/vhotplug.
* 0 is normal state, hw drives hpd/vhotplug.
* -1 is force deassert, sw drives hpd/vhotplug.
* 1 is force assert, sw drives hpd/vhotplug.
* before releasing to hw, sw must ensure hpd/vhotplug state is normal i.e. 0
*/
static ssize_t tegra_hdmi_vhotplug_dbg_write(struct file *file,
const char __user *addr,
size_t len, loff_t *pos)
{
struct seq_file *m = file->private_data;
struct tegra_hdmi *hdmi = m->private;
struct tegra_dc *dc;
long new_hpd_state;
int ret;
static bool free_vrr;
if (WARN_ON(!hdmi))
return -EINVAL;
dc = hdmi->dc;
if (WARN_ON(!dc || !dc->out))
return -EINVAL;
ret = kstrtol_from_user(addr, len, 10, &new_hpd_state);
if (ret < 0)
return ret;
if (new_hpd_state == TEGRA_HPD_STATE_FORCE_ASSERT) {
if (!dc->out->vrr) {
dc->out->vrr = devm_kzalloc(&dc->ndev->dev,
sizeof(struct tegra_vrr),
GFP_KERNEL);
if (!dc->out->vrr) {
dev_err(&dc->ndev->dev, "not enough memory\n");
return -ENOMEM;
}
free_vrr = true;
}
dc->out->vrr->capability = 1;
} else if (new_hpd_state == TEGRA_HPD_STATE_FORCE_DEASSERT) {
if (free_vrr && dc->out->vrr) {
devm_kfree(&dc->ndev->dev, dc->out->vrr);
dc->out->vrr = NULL;
}
if (dc->out->vrr)
dc->out->vrr->capability = 0;
}
dc->out->vrr_hotplug_state = new_hpd_state;
tegra_hdmi_set_hotplug_state(hdmi, new_hpd_state);
return len;
}
static int tegra_hdmi_vhotplug_dbg_open(struct inode *inode,
struct file *file)
{
return single_open(file, tegra_hdmi_vhotplug_dbg_show,
inode->i_private);
}
static const struct file_operations tegra_hdmi_hotplug_dbg_ops = {
.open = tegra_hdmi_hotplug_dbg_open,
.read = seq_read,
.write = tegra_hdmi_hotplug_dbg_write,
.llseek = seq_lseek,
.release = single_release,
};
static const struct file_operations tegra_hdmi_vhotplug_dbg_ops = {
.open = tegra_hdmi_vhotplug_dbg_open,
.read = seq_read,
.write = tegra_hdmi_vhotplug_dbg_write,
.llseek = seq_lseek,
.release = single_release,
};
static void tegra_hdmi_ddc_power_toggle(struct tegra_hdmi *dc_hdmi, int value)
{
if (!dc_hdmi || !dc_hdmi->sor)
return;
if (value == 0)
_tegra_hdmi_ddc_disable(dc_hdmi);
else if (value == 1)
_tegra_hdmi_ddc_enable(dc_hdmi);
}
static int tegra_hdmi_ddc_power_toggle_dbg_show(struct seq_file *m,
void *unused)
{
struct tegra_hdmi *hdmi = m->private;
struct tegra_dc *dc = hdmi->dc;
if (WARN_ON(!hdmi || !dc || !dc->out))
return -EINVAL;
/* make sure we see updated ddc_refcount value */
rmb();
seq_printf(m, "ddc_refcount: %d\n", hdmi->ddc_refcount);
return 0;
}
static ssize_t tegra_hdmi_ddc_power_toggle_dbg_write(struct file *file,
const char __user *addr,
size_t len, loff_t *pos)
{
struct seq_file *m = file->private_data;
struct tegra_hdmi *hdmi = m->private;
struct tegra_dc *dc = hdmi->dc;
long value;
int ret;
if (WARN_ON(!hdmi || !dc || !dc->out))
return -EINVAL;
ret = kstrtol_from_user(addr, len, 10, &value);
if (ret < 0)
return ret;
tegra_hdmi_ddc_power_toggle(hdmi, value);
return len;
}
static int tegra_hdmi_ddc_power_toggle_dbg_open(struct inode *inode,
struct file *file)
{
return single_open(file, tegra_hdmi_ddc_power_toggle_dbg_show,
inode->i_private);
}
static const struct file_operations tegra_hdmi_ddc_power_toggle_dbg_ops = {
.open = tegra_hdmi_ddc_power_toggle_dbg_open,
.read = seq_read,
.write = tegra_hdmi_ddc_power_toggle_dbg_write,
.llseek = seq_lseek,
.release = single_release,
};
static int tegra_hdmi_status_dbg_show(struct seq_file *m, void *unused)
{
struct tegra_hdmi *hdmi = m->private;
struct tegra_dc *dc;
if (WARN_ON(!hdmi))
return -EINVAL;
dc = hdmi->dc;
if (WARN_ON(!dc || !dc->out))
return -EINVAL;
seq_printf(m, "hotplug state: %d\n", tegra_dc_hpd(dc));
seq_printf(m, "SCDC present: %d\n",
tegra_edid_is_scdc_present(hdmi->edid));
seq_printf(m, "Forum VSDB present: %d\n",
tegra_edid_is_hfvsdb_present(hdmi->edid));
seq_printf(m, "YCbCr4:2:0 VDB present: %d\n",
tegra_edid_is_420db_present(hdmi->edid));
return 0;
}
static int tegra_hdmi_status_dbg_open(struct inode *inode, struct file *file)
{
return single_open(file, tegra_hdmi_status_dbg_show,
inode->i_private);
}
static const struct file_operations tegra_hdmi_status_dbg_ops = {
.open = tegra_hdmi_status_dbg_open,
.read = seq_read,
.release = single_release,
};
static void tegra_hdmi_debugfs_init(struct tegra_hdmi *hdmi)
{
struct dentry *ret;
char debug_dirname[CHAR_BUF_SIZE_MAX] = "tegra_hdmi";
if (hdmi_instance) {
snprintf(debug_dirname, sizeof(debug_dirname),
"tegra_hdmi%d", hdmi_instance);
}
hdmi->debugdir = debugfs_create_dir(debug_dirname, NULL);
if (IS_ERR_OR_NULL(hdmi->debugdir)) {
dev_err(&hdmi->dc->ndev->dev, "could not create hdmi%d debugfs\n",
hdmi_instance);
return;
}
ret = debugfs_create_file("hotplug", 0444, hdmi->debugdir,
hdmi, &tegra_hdmi_hotplug_dbg_ops);
if (IS_ERR_OR_NULL(ret))
goto fail;
ret = debugfs_create_file("hdmi_status", 0444, hdmi->debugdir,
hdmi, &tegra_hdmi_status_dbg_ops);
if (IS_ERR_OR_NULL(ret))
goto fail;
ret = debugfs_create_file("ddc_power_toggle", 0444, hdmi->debugdir,
hdmi, &tegra_hdmi_ddc_power_toggle_dbg_ops);
if (IS_ERR_OR_NULL(ret))
goto fail;
ret = debugfs_create_file("vhotplug", 0444, hdmi->debugdir,
hdmi, &tegra_hdmi_vhotplug_dbg_ops);
if (IS_ERR_OR_NULL(ret))
goto fail;
return;
fail:
dev_err(&hdmi->dc->ndev->dev, "could not create hdmi%d debugfs\n",
hdmi_instance);
tegra_hdmi_debugfs_remove(hdmi);
return;
}
static void tegra_hdmi_debugfs_remove(struct tegra_hdmi *hdmi)
{
debugfs_remove_recursive(hdmi->debugdir);
hdmi->debugdir = NULL;
}
#else
static void tegra_hdmi_debugfs_init(struct tegra_hdmi *hdmi)
{
return;
}
static void tegra_hdmi_debugfs_remove(struct tegra_hdmi *hdmi)
{
return;
}
#endif
static bool tegra_dc_hdmi_hpd_state(struct tegra_dc *dc)
{
int sense;
int level;
bool hpd;
if (WARN_ON(!dc || !dc->out))
return false;
if (tegra_platform_is_sim())
return true;
level = gpio_get_value_cansleep(dc->out->hotplug_gpio);
sense = dc->out->flags & TEGRA_DC_OUT_HOTPLUG_MASK;
hpd = (sense == TEGRA_DC_OUT_HOTPLUG_HIGH && level) ||
(sense == TEGRA_DC_OUT_HOTPLUG_LOW && !level);
return hpd;
}
static void tegra_dc_hdmi_vrr_enable(struct tegra_dc *dc, bool enable)
{
struct tegra_vrr *vrr = dc->out->vrr;
if (!vrr || !vrr->capability)
return;
if (!(dc->mode.vmode & FB_VMODE_VRR)) {
WARN(enable, "VRR enable request in non-VRR mode\n");
return;
}
vrr->enable = enable;
}
static void tegra_dc_hdmi_postpoweron(struct tegra_dc *dc)
{
_tegra_hdmivrr_activate(tegra_dc_get_outdata(dc), true);
}
static void tegra_dc_hdmi_sor_sleep(struct tegra_dc *dc)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
if (hdmi->sor->sor_state == SOR_ATTACHED)
tegra_dc_sor_sleep(hdmi->sor);
}
static u32 tegra_dc_hdmi_sor_crc_check(struct tegra_dc *dc)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
return tegra_dc_sor_debugfs_get_crc(hdmi->sor, NULL);
}
static void tegra_dc_hdmi_sor_crc_toggle(struct tegra_dc *dc,
u32 val)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
tegra_dc_sor_toggle_crc(hdmi->sor, val);
}
static int tegra_dc_hdmi_get_sor_ctrl_num(struct tegra_dc *dc)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
return (!hdmi) ? -ENODEV : tegra_sor_get_ctrl_num(hdmi->sor);
}
static int tegra_dc_hdmi_sor_crc_en_dis(struct tegra_dc *dc,
struct tegra_dc_ext_crc_or_params *params,
bool en)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
if (params->out_type != TEGRA_DC_EXT_HDMI)
return -EINVAL;
tegra_dc_sor_crc_en_dis(hdmi->sor, params->sor_params, en);
return 0;
}
static int tegra_dc_hdmi_sor_crc_en(struct tegra_dc *dc,
struct tegra_dc_ext_crc_or_params *params)
{
return tegra_dc_hdmi_sor_crc_en_dis(dc, params, true);
}
static int tegra_dc_hdmi_sor_crc_dis(struct tegra_dc *dc,
struct tegra_dc_ext_crc_or_params *params)
{
return tegra_dc_hdmi_sor_crc_en_dis(dc, params, false);
}
static int tegra_dc_hdmi_sor_crc_get(struct tegra_dc *dc, u32 *crc)
{
struct tegra_hdmi *hdmi = tegra_dc_get_outdata(dc);
return tegra_dc_sor_crc_get(hdmi->sor, crc);
}
struct tegra_dc_out_ops tegra_dc_hdmi2_0_ops = {
.init = tegra_dc_hdmi_init,
.hotplug_init = tegra_dc_hdmi_hpd_init,
.destroy = tegra_dc_hdmi_destroy,
.enable = tegra_dc_hdmi_enable,
.disable = tegra_dc_hdmi_disable,
.setup_clk = tegra_dc_hdmi_setup_clk,
.detect = tegra_dc_hdmi_detect,
.shutdown = tegra_dc_hdmi_shutdown,
.suspend = tegra_dc_hdmi_suspend,
.resume = tegra_dc_hdmi_resume,
.ddc_enable = tegra_dc_hdmi_ddc_enable,
.ddc_disable = tegra_dc_hdmi_ddc_disable,
.modeset_notifier = tegra_dc_hdmi_modeset_notifier,
.mode_filter = tegra_hdmi_fb_mode_filter,
.hpd_state = tegra_dc_hdmi_hpd_state,
.vrr_enable = tegra_dc_hdmi_vrr_enable,
.vrr_update_monspecs = tegra_hdmivrr_update_monspecs,
.set_hdr = tegra_dc_hdmi_set_hdr,
.set_avi = tegra_dc_hdmi_set_avi,
.postpoweron = tegra_dc_hdmi_postpoweron,
.shutdown_interface = tegra_dc_hdmi_sor_sleep,
.get_crc = tegra_dc_hdmi_sor_crc_check,
.toggle_crc = tegra_dc_hdmi_sor_crc_toggle,
.get_connector_instance = tegra_dc_hdmi_get_sor_ctrl_num,
.crc_en = tegra_dc_hdmi_sor_crc_en,
.crc_dis = tegra_dc_hdmi_sor_crc_dis,
.crc_get = tegra_dc_hdmi_sor_crc_get,
};