2024-09-09 08:57:42 +00:00
|
|
|
/* Copyright (c) 2010-2015, The Linux Foundation. All rights reserved.
|
2024-09-09 08:52:07 +00:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
|
|
* only version 2 as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/bitops.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/mutex.h>
|
|
|
|
#include <linux/iopoll.h>
|
|
|
|
#include <linux/of_address.h>
|
|
|
|
#include <linux/of_gpio.h>
|
|
|
|
#include <linux/of_platform.h>
|
|
|
|
#include <linux/types.h>
|
2024-09-09 08:57:42 +00:00
|
|
|
#include <linux/msm_hdmi.h>
|
|
|
|
#include <linux/hdcp_qseecom.h>
|
|
|
|
#include <linux/clk.h>
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
#define REG_DUMP 0
|
|
|
|
|
|
|
|
#include "mdss_debug.h"
|
|
|
|
#include "mdss_fb.h"
|
|
|
|
#include "mdss_hdmi_cec.h"
|
|
|
|
#include "mdss_hdmi_edid.h"
|
|
|
|
#include "mdss_hdmi_hdcp.h"
|
|
|
|
#include "mdss_hdmi_tx.h"
|
|
|
|
#include "mdss.h"
|
|
|
|
#include "mdss_panel.h"
|
|
|
|
#include "mdss_hdmi_mhl.h"
|
|
|
|
|
|
|
|
#define DRV_NAME "hdmi-tx"
|
|
|
|
#define COMPATIBLE_NAME "qcom,hdmi-tx"
|
|
|
|
|
|
|
|
#define DEFAULT_VIDEO_RESOLUTION HDMI_VFRMT_640x480p60_4_3
|
2024-09-09 08:57:42 +00:00
|
|
|
#define DEFAULT_HDMI_PRIMARY_RESOLUTION HDMI_VFRMT_1920x1080p60_16_9
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
/* HDMI PHY/PLL bit field macros */
|
|
|
|
#define SW_RESET BIT(2)
|
|
|
|
#define SW_RESET_PLL BIT(0)
|
|
|
|
|
|
|
|
#define HPD_DISCONNECT_POLARITY 0
|
|
|
|
#define HPD_CONNECT_POLARITY 1
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
#define AUDIO_ACK_SET_ENABLE BIT(5)
|
|
|
|
#define AUDIO_ACK_ENABLE BIT(4)
|
|
|
|
#define AUDIO_ACK_CONNECT BIT(0)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Audio engine may take 1 to 3 sec to shutdown
|
|
|
|
* in normal cases. To handle worst cases, making
|
|
|
|
* timeout for audio engine shutdown as 5 sec.
|
|
|
|
*/
|
|
|
|
#define AUDIO_POLL_SLEEP_US (5 * 1000)
|
|
|
|
#define AUDIO_POLL_TIMEOUT_US (AUDIO_POLL_SLEEP_US * 1000)
|
|
|
|
|
|
|
|
#define LPA_DMA_IDLE_MAX 200
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
#define IFRAME_CHECKSUM_32(d) \
|
|
|
|
((d & 0xff) + ((d >> 8) & 0xff) + \
|
|
|
|
((d >> 16) & 0xff) + ((d >> 24) & 0xff))
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Pixel Clock to TMDS Character Rate Ratios.
|
|
|
|
*/
|
|
|
|
#define HDMI_TX_YUV420_24BPP_PCLK_TMDS_CH_RATE_RATIO 2
|
|
|
|
#define HDMI_TX_YUV422_24BPP_PCLK_TMDS_CH_RATE_RATIO 1
|
|
|
|
#define HDMI_TX_RGB_24BPP_PCLK_TMDS_CH_RATE_RATIO 1
|
|
|
|
|
|
|
|
#define HDMI_TX_SCRAMBLER_THRESHOLD_RATE_KHZ 340000
|
|
|
|
#define HDMI_TX_SCRAMBLER_TIMEOUT_MSEC 200
|
|
|
|
|
|
|
|
#define HDMI_TX_KHZ_TO_HZ 1000
|
|
|
|
#define HDMI_TX_MHZ_TO_HZ 1000000
|
|
|
|
|
|
|
|
/* Maximum pixel clock rates for hdmi tx */
|
|
|
|
#define HDMI_DEFAULT_MAX_PCLK_RATE 148500
|
|
|
|
#define HDMI_TX_3_MAX_PCLK_RATE 297000
|
|
|
|
#define HDMI_TX_4_MAX_PCLK_RATE 600000
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
/* Enable HDCP by default */
|
|
|
|
static bool hdcp_feature_on = true;
|
|
|
|
|
|
|
|
/* Supported HDMI Audio channels */
|
|
|
|
#define MSM_HDMI_AUDIO_CHANNEL_2 2
|
2024-09-09 08:57:42 +00:00
|
|
|
#define MSM_HDMI_AUDIO_CHANNEL_3 3
|
2024-09-09 08:52:07 +00:00
|
|
|
#define MSM_HDMI_AUDIO_CHANNEL_4 4
|
2024-09-09 08:57:42 +00:00
|
|
|
#define MSM_HDMI_AUDIO_CHANNEL_5 5
|
2024-09-09 08:52:07 +00:00
|
|
|
#define MSM_HDMI_AUDIO_CHANNEL_6 6
|
2024-09-09 08:57:42 +00:00
|
|
|
#define MSM_HDMI_AUDIO_CHANNEL_7 7
|
2024-09-09 08:52:07 +00:00
|
|
|
#define MSM_HDMI_AUDIO_CHANNEL_8 8
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/* AVI INFOFRAME DATA */
|
|
|
|
#define NUM_MODES_AVI 20
|
|
|
|
#define AVI_MAX_DATA_BYTES 13
|
|
|
|
|
|
|
|
/* Line numbers at which AVI Infoframe and Vendor Infoframe will be sent */
|
|
|
|
#define AVI_IFRAME_LINE_NUMBER 1
|
|
|
|
#define VENDOR_IFRAME_LINE_NUMBER 3
|
|
|
|
#define MAX_EDID_READ_RETRY 5
|
|
|
|
|
|
|
|
enum {
|
|
|
|
DATA_BYTE_1,
|
|
|
|
DATA_BYTE_2,
|
|
|
|
DATA_BYTE_3,
|
|
|
|
DATA_BYTE_4,
|
|
|
|
DATA_BYTE_5,
|
|
|
|
DATA_BYTE_6,
|
|
|
|
DATA_BYTE_7,
|
|
|
|
DATA_BYTE_8,
|
|
|
|
DATA_BYTE_9,
|
|
|
|
DATA_BYTE_10,
|
|
|
|
DATA_BYTE_11,
|
|
|
|
DATA_BYTE_12,
|
|
|
|
DATA_BYTE_13,
|
|
|
|
};
|
|
|
|
|
|
|
|
#define IFRAME_PACKET_OFFSET 0x80
|
|
|
|
/*
|
|
|
|
* InfoFrame Type Code:
|
|
|
|
* 0x0 - Reserved
|
|
|
|
* 0x1 - Vendor Specific
|
|
|
|
* 0x2 - Auxiliary Video Information
|
|
|
|
* 0x3 - Source Product Description
|
|
|
|
* 0x4 - AUDIO
|
|
|
|
* 0x5 - MPEG Source
|
|
|
|
* 0x6 - NTSC VBI
|
|
|
|
* 0x7 - 0xFF - Reserved
|
|
|
|
*/
|
|
|
|
#define AVI_IFRAME_TYPE 0x2
|
|
|
|
#define AVI_IFRAME_VERSION 0x2
|
|
|
|
#define LEFT_SHIFT_BYTE(x) ((x) << 8)
|
|
|
|
#define LEFT_SHIFT_WORD(x) ((x) << 16)
|
|
|
|
#define LEFT_SHIFT_24BITS(x) ((x) << 24)
|
|
|
|
|
|
|
|
/* AVI Infoframe data byte 3, bit 7 (msb) represents ITC bit */
|
|
|
|
#define SET_ITC_BIT(byte) (byte = (byte | BIT(7)))
|
|
|
|
#define CLR_ITC_BIT(byte) (byte = (byte & ~BIT(7)))
|
|
|
|
|
|
|
|
/*
|
|
|
|
* CN represents IT content type, if ITC bit in infoframe data byte 3
|
|
|
|
* is set, CN bits will represent content type as below:
|
|
|
|
* 0b00 Graphics
|
|
|
|
* 0b01 Photo
|
|
|
|
* 0b10 Cinema
|
|
|
|
* 0b11 Game
|
|
|
|
*/
|
|
|
|
#define CONFIG_CN_BITS(bits, byte) \
|
|
|
|
(byte = (byte & ~(BIT(4) | BIT(5))) |\
|
|
|
|
((bits & (BIT(0) | BIT(1))) << 4))
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
enum msm_hdmi_supported_audio_sample_rates {
|
|
|
|
AUDIO_SAMPLE_RATE_32KHZ,
|
|
|
|
AUDIO_SAMPLE_RATE_44_1KHZ,
|
|
|
|
AUDIO_SAMPLE_RATE_48KHZ,
|
|
|
|
AUDIO_SAMPLE_RATE_88_2KHZ,
|
|
|
|
AUDIO_SAMPLE_RATE_96KHZ,
|
|
|
|
AUDIO_SAMPLE_RATE_176_4KHZ,
|
|
|
|
AUDIO_SAMPLE_RATE_192KHZ,
|
|
|
|
AUDIO_SAMPLE_RATE_MAX
|
|
|
|
};
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
enum hdmi_tx_hpd_states {
|
|
|
|
HPD_OFF,
|
|
|
|
HPD_ON,
|
|
|
|
HPD_ON_CONDITIONAL_MTP,
|
|
|
|
HPD_DISABLE,
|
|
|
|
HPD_ENABLE
|
|
|
|
};
|
|
|
|
|
|
|
|
enum hdmi_tx_res_states {
|
|
|
|
RESOLUTION_UNCHANGED,
|
|
|
|
RESOLUTION_CHANGED
|
|
|
|
};
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
/* parameters for clock regeneration */
|
|
|
|
struct hdmi_tx_audio_acr {
|
|
|
|
u32 n;
|
|
|
|
u32 cts;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct hdmi_tx_audio_acr_arry {
|
|
|
|
u32 pclk;
|
|
|
|
struct hdmi_tx_audio_acr lut[AUDIO_SAMPLE_RATE_MAX];
|
|
|
|
};
|
|
|
|
|
|
|
|
static int hdmi_tx_set_mhl_hpd(struct platform_device *pdev, uint8_t on);
|
|
|
|
static int hdmi_tx_sysfs_enable_hpd(struct hdmi_tx_ctrl *hdmi_ctrl, int on);
|
|
|
|
static irqreturn_t hdmi_tx_isr(int irq, void *data);
|
|
|
|
static void hdmi_tx_hpd_off(struct hdmi_tx_ctrl *hdmi_ctrl);
|
|
|
|
static int hdmi_tx_enable_power(struct hdmi_tx_ctrl *hdmi_ctrl,
|
|
|
|
enum hdmi_tx_power_module_type module, int enable);
|
2024-09-09 08:57:42 +00:00
|
|
|
static int hdmi_tx_audio_setup(struct hdmi_tx_ctrl *hdmi_ctrl);
|
|
|
|
static int hdmi_tx_setup_tmds_clk_rate(struct hdmi_tx_ctrl *hdmi_ctrl);
|
|
|
|
static void hdmi_tx_set_vendor_specific_infoframe(
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static struct mdss_hw hdmi_tx_hw = {
|
2024-09-09 08:52:07 +00:00
|
|
|
.hw_ndx = MDSS_HW_HDMI,
|
|
|
|
.ptr = NULL,
|
|
|
|
.irq_handler = hdmi_tx_isr,
|
|
|
|
};
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static struct dss_gpio hpd_gpio_config[] = {
|
2024-09-09 08:52:07 +00:00
|
|
|
{0, 1, COMPATIBLE_NAME "-hpd"},
|
|
|
|
{0, 1, COMPATIBLE_NAME "-mux-en"},
|
2024-09-09 08:57:42 +00:00
|
|
|
{0, 0, COMPATIBLE_NAME "-mux-sel"},
|
|
|
|
{0, 1, COMPATIBLE_NAME "-mux-lpm"}
|
2024-09-09 08:52:07 +00:00
|
|
|
};
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static struct dss_gpio ddc_gpio_config[] = {
|
|
|
|
{0, 1, COMPATIBLE_NAME "-ddc-mux-sel"},
|
2024-09-09 08:52:07 +00:00
|
|
|
{0, 1, COMPATIBLE_NAME "-ddc-clk"},
|
|
|
|
{0, 1, COMPATIBLE_NAME "-ddc-data"}
|
|
|
|
};
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static struct dss_gpio core_gpio_config[] = {
|
2024-09-09 08:52:07 +00:00
|
|
|
};
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static struct dss_gpio cec_gpio_config[] = {
|
2024-09-09 08:52:07 +00:00
|
|
|
{0, 1, COMPATIBLE_NAME "-cec"}
|
|
|
|
};
|
|
|
|
|
|
|
|
const char *hdmi_pm_name(enum hdmi_tx_power_module_type module)
|
|
|
|
{
|
|
|
|
switch (module) {
|
|
|
|
case HDMI_TX_HPD_PM: return "HDMI_TX_HPD_PM";
|
|
|
|
case HDMI_TX_DDC_PM: return "HDMI_TX_DDC_PM";
|
|
|
|
case HDMI_TX_CORE_PM: return "HDMI_TX_CORE_PM";
|
|
|
|
case HDMI_TX_CEC_PM: return "HDMI_TX_CEC_PM";
|
|
|
|
default: return "???";
|
|
|
|
}
|
|
|
|
} /* hdmi_pm_name */
|
|
|
|
|
|
|
|
/* Audio constants lookup table for hdmi_tx_audio_acr_setup */
|
|
|
|
/* Valid Pixel-Clock rates: 25.2MHz, 27MHz, 27.03MHz, 74.25MHz, 148.5MHz */
|
|
|
|
static const struct hdmi_tx_audio_acr_arry hdmi_tx_audio_acr_lut[] = {
|
|
|
|
/* 25.200MHz */
|
|
|
|
{25200, {{4096, 25200}, {6272, 28000}, {6144, 25200}, {12544, 28000},
|
|
|
|
{12288, 25200}, {25088, 28000}, {24576, 25200} } },
|
|
|
|
/* 27.000MHz */
|
|
|
|
{27000, {{4096, 27000}, {6272, 30000}, {6144, 27000}, {12544, 30000},
|
|
|
|
{12288, 27000}, {25088, 30000}, {24576, 27000} } },
|
|
|
|
/* 27.027MHz */
|
2024-09-09 08:57:42 +00:00
|
|
|
{27027, {{4096, 27027}, {6272, 30030}, {6144, 27027}, {12544, 30030},
|
2024-09-09 08:52:07 +00:00
|
|
|
{12288, 27027}, {25088, 30030}, {24576, 27027} } },
|
|
|
|
/* 74.250MHz */
|
|
|
|
{74250, {{4096, 74250}, {6272, 82500}, {6144, 74250}, {12544, 82500},
|
|
|
|
{12288, 74250}, {25088, 82500}, {24576, 74250} } },
|
|
|
|
/* 148.500MHz */
|
|
|
|
{148500, {{4096, 148500}, {6272, 165000}, {6144, 148500},
|
|
|
|
{12544, 165000}, {12288, 148500}, {25088, 165000},
|
|
|
|
{24576, 148500} } },
|
|
|
|
/* 297.000MHz */
|
|
|
|
{297000, {{3072, 222750}, {4704, 247500}, {5120, 247500},
|
|
|
|
{9408, 247500}, {10240, 247500}, {18816, 247500},
|
|
|
|
{20480, 247500} } },
|
2024-09-09 08:57:42 +00:00
|
|
|
/* 594.000MHz */
|
|
|
|
{594000, {{3072, 445500}, {9408, 990000}, {6144, 594000},
|
|
|
|
{18816, 990000}, {12288, 594000}, {37632, 990000},
|
|
|
|
{24576, 594000} } },
|
2024-09-09 08:52:07 +00:00
|
|
|
};
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int hdmi_tx_get_version(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
int reg_val;
|
|
|
|
struct dss_io_data *io;
|
|
|
|
|
|
|
|
rc = hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_HPD_PM, true);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: Failed to read HDMI version\n", __func__);
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: core io not inititalized\n", __func__);
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
reg_val = DSS_REG_R(io, HDMI_VERSION);
|
|
|
|
reg_val = (reg_val & 0xF0000000) >> 28;
|
|
|
|
hdmi_ctrl->hdmi_tx_ver = reg_val;
|
|
|
|
|
|
|
|
switch (hdmi_ctrl->hdmi_tx_ver) {
|
|
|
|
case (HDMI_TX_VERSION_3):
|
|
|
|
hdmi_ctrl->max_pclk_khz = HDMI_TX_3_MAX_PCLK_RATE;
|
|
|
|
break;
|
|
|
|
case (HDMI_TX_VERSION_4):
|
|
|
|
hdmi_ctrl->max_pclk_khz = HDMI_TX_4_MAX_PCLK_RATE;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
hdmi_ctrl->max_pclk_khz = HDMI_DEFAULT_MAX_PCLK_RATE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_HPD_PM, false);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: FAILED to disable power\n", __func__);
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
fail:
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
int register_hdmi_cable_notification(struct hdmi_cable_notify *handler)
|
|
|
|
{
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
|
|
|
struct list_head *pos;
|
|
|
|
|
|
|
|
if (!hdmi_tx_hw.ptr) {
|
|
|
|
DEV_WARN("%s: HDMI Tx core not ready\n", __func__);
|
|
|
|
return -EPROBE_DEFER;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!handler) {
|
|
|
|
DEV_ERR("%s: Empty handler\n", __func__);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_ctrl = (struct hdmi_tx_ctrl *) hdmi_tx_hw.ptr;
|
|
|
|
|
|
|
|
mutex_lock(&hdmi_ctrl->cable_notify_mutex);
|
|
|
|
handler->status = hdmi_ctrl->hpd_state;
|
|
|
|
list_for_each(pos, &hdmi_ctrl->cable_notify_handlers);
|
|
|
|
list_add_tail(&handler->link, pos);
|
|
|
|
mutex_unlock(&hdmi_ctrl->cable_notify_mutex);
|
|
|
|
|
|
|
|
return handler->status;
|
|
|
|
} /* register_hdmi_cable_notification */
|
|
|
|
|
|
|
|
int unregister_hdmi_cable_notification(struct hdmi_cable_notify *handler)
|
|
|
|
{
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
|
|
|
|
|
|
|
if (!hdmi_tx_hw.ptr) {
|
|
|
|
DEV_WARN("%s: HDMI Tx core not ready\n", __func__);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!handler) {
|
|
|
|
DEV_ERR("%s: Empty handler\n", __func__);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_ctrl = (struct hdmi_tx_ctrl *) hdmi_tx_hw.ptr;
|
|
|
|
|
|
|
|
mutex_lock(&hdmi_ctrl->cable_notify_mutex);
|
|
|
|
list_del(&handler->link);
|
|
|
|
mutex_unlock(&hdmi_ctrl->cable_notify_mutex);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
} /* unregister_hdmi_cable_notification */
|
|
|
|
|
|
|
|
static void hdmi_tx_cable_notify_work(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
|
|
|
struct hdmi_cable_notify *pos;
|
|
|
|
|
|
|
|
hdmi_ctrl = container_of(work, struct hdmi_tx_ctrl, cable_notify_work);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid hdmi data\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_lock(&hdmi_ctrl->cable_notify_mutex);
|
|
|
|
list_for_each_entry(pos, &hdmi_ctrl->cable_notify_handlers, link) {
|
|
|
|
if (pos->status != hdmi_ctrl->hpd_state) {
|
|
|
|
pos->status = hdmi_ctrl->hpd_state;
|
|
|
|
pos->hpd_notify(pos);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mutex_unlock(&hdmi_ctrl->cable_notify_mutex);
|
|
|
|
} /* hdmi_tx_cable_notify_work */
|
|
|
|
|
|
|
|
static bool hdmi_tx_is_cea_format(int mode)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
bool cea_fmt;
|
|
|
|
|
|
|
|
if ((mode > 0) && (mode <= HDMI_EVFRMT_END))
|
|
|
|
cea_fmt = true;
|
2024-09-09 08:52:07 +00:00
|
|
|
else
|
2024-09-09 08:57:42 +00:00
|
|
|
cea_fmt = false;
|
|
|
|
|
|
|
|
DEV_DBG("%s: %s\n", __func__, cea_fmt ? "Yes" : "No");
|
|
|
|
|
|
|
|
return cea_fmt;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool hdmi_tx_is_hdcp_enabled(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
return hdmi_ctrl->hdcp_feature_on &&
|
|
|
|
(hdmi_ctrl->hdcp14_present || hdmi_ctrl->hdcp22_present) &&
|
|
|
|
hdmi_ctrl->hdcp_ops;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static const char *hdmi_tx_pm_name(enum hdmi_tx_power_module_type module)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
|
|
|
switch (module) {
|
|
|
|
case HDMI_TX_HPD_PM: return "HDMI_TX_HPD_PM";
|
|
|
|
case HDMI_TX_DDC_PM: return "HDMI_TX_DDC_PM";
|
|
|
|
case HDMI_TX_CORE_PM: return "HDMI_TX_CORE_PM";
|
|
|
|
case HDMI_TX_CEC_PM: return "HDMI_TX_CEC_PM";
|
|
|
|
default: return "???";
|
|
|
|
}
|
|
|
|
} /* hdmi_tx_pm_name */
|
|
|
|
|
|
|
|
static const char *hdmi_tx_io_name(u32 type)
|
|
|
|
{
|
|
|
|
switch (type) {
|
|
|
|
case HDMI_TX_CORE_IO: return "core_physical";
|
|
|
|
case HDMI_TX_QFPROM_IO: return "qfprom_physical";
|
2024-09-09 08:57:42 +00:00
|
|
|
case HDMI_TX_HDCP_IO: return "hdcp_physical";
|
2024-09-09 08:52:07 +00:00
|
|
|
default: return NULL;
|
|
|
|
}
|
|
|
|
} /* hdmi_tx_io_name */
|
|
|
|
|
|
|
|
static int hdmi_tx_get_vic_from_panel_info(struct hdmi_tx_ctrl *hdmi_ctrl,
|
|
|
|
struct mdss_panel_info *pinfo)
|
|
|
|
{
|
|
|
|
int new_vic = -1;
|
|
|
|
u32 h_total, v_total;
|
|
|
|
struct msm_hdmi_mode_timing_info timing;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl || !pinfo) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pinfo->vic) {
|
2024-09-09 08:57:42 +00:00
|
|
|
struct msm_hdmi_mode_timing_info info = {0};
|
|
|
|
u32 ret = hdmi_get_supported_mode(&info,
|
|
|
|
&hdmi_ctrl->ds_data, pinfo->vic);
|
|
|
|
u32 supported = info.supported;
|
|
|
|
|
|
|
|
if (!ret && supported) {
|
2024-09-09 08:52:07 +00:00
|
|
|
new_vic = pinfo->vic;
|
|
|
|
DEV_DBG("%s: %s is supported\n", __func__,
|
|
|
|
msm_hdmi_mode_2string(new_vic));
|
|
|
|
} else {
|
|
|
|
DEV_ERR("%s: invalid or not supported vic %d\n",
|
|
|
|
__func__, pinfo->vic);
|
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
timing.active_h = pinfo->xres;
|
|
|
|
timing.back_porch_h = pinfo->lcdc.h_back_porch;
|
|
|
|
timing.front_porch_h = pinfo->lcdc.h_front_porch;
|
|
|
|
timing.pulse_width_h = pinfo->lcdc.h_pulse_width;
|
|
|
|
h_total = timing.active_h + timing.back_porch_h +
|
|
|
|
timing.front_porch_h + timing.pulse_width_h;
|
|
|
|
DEV_DBG("%s: ah=%d bph=%d fph=%d pwh=%d ht=%d\n", __func__,
|
|
|
|
timing.active_h, timing.back_porch_h,
|
|
|
|
timing.front_porch_h, timing.pulse_width_h, h_total);
|
|
|
|
|
|
|
|
timing.active_v = pinfo->yres;
|
|
|
|
timing.back_porch_v = pinfo->lcdc.v_back_porch;
|
|
|
|
timing.front_porch_v = pinfo->lcdc.v_front_porch;
|
|
|
|
timing.pulse_width_v = pinfo->lcdc.v_pulse_width;
|
|
|
|
v_total = timing.active_v + timing.back_porch_v +
|
|
|
|
timing.front_porch_v + timing.pulse_width_v;
|
|
|
|
DEV_DBG("%s: av=%d bpv=%d fpv=%d pwv=%d vt=%d\n", __func__,
|
|
|
|
timing.active_v, timing.back_porch_v,
|
|
|
|
timing.front_porch_v, timing.pulse_width_v, v_total);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
timing.pixel_freq = ((unsigned long int)pinfo->clk_rate / 1000);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (h_total && v_total) {
|
|
|
|
timing.refresh_rate = ((timing.pixel_freq * 1000) /
|
|
|
|
(h_total * v_total)) * 1000;
|
|
|
|
} else {
|
|
|
|
DEV_ERR("%s: cannot cal refresh rate\n", __func__);
|
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
DEV_DBG("%s: pixel_freq=%d refresh_rate=%d\n", __func__,
|
|
|
|
timing.pixel_freq, timing.refresh_rate);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
new_vic = hdmi_get_video_id_code(&timing, &hdmi_ctrl->ds_data);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return new_vic;
|
|
|
|
} /* hdmi_tx_get_vic_from_panel_info */
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static inline u32 hdmi_tx_is_dvi_mode(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
return hdmi_edid_get_sink_mode(
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID]) ? 0 : 1;
|
|
|
|
} /* hdmi_tx_is_dvi_mode */
|
|
|
|
|
|
|
|
static inline bool hdmi_tx_is_panel_on(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
return hdmi_ctrl->hpd_state && hdmi_ctrl->panel_power_on;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void hdmi_tx_send_cable_notification(
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl, int val)
|
|
|
|
{
|
|
|
|
int state = 0;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
state = hdmi_ctrl->sdev.state;
|
|
|
|
|
|
|
|
switch_set_state(&hdmi_ctrl->sdev, val);
|
|
|
|
|
|
|
|
DEV_INFO("%s: cable state %s %d\n", __func__,
|
|
|
|
hdmi_ctrl->sdev.state == state ?
|
|
|
|
"is same" : "switched to",
|
|
|
|
hdmi_ctrl->sdev.state);
|
|
|
|
|
|
|
|
/* Notify all registered modules of cable connection status */
|
|
|
|
schedule_work(&hdmi_ctrl->cable_notify_work);
|
|
|
|
} /* hdmi_tx_send_cable_notification */
|
|
|
|
|
|
|
|
static inline void hdmi_tx_set_audio_switch_node(
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl, int val)
|
|
|
|
{
|
|
|
|
int state = 0;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
state = hdmi_ctrl->audio_sdev.state;
|
|
|
|
|
|
|
|
if (hdmi_ctrl->audio_ack_enabled &&
|
|
|
|
atomic_read(&hdmi_ctrl->audio_ack_pending)) {
|
|
|
|
DEV_ERR("%s: %s ack pending, not notifying %s\n", __func__,
|
|
|
|
state ? "connect" : "disconnect",
|
|
|
|
val ? "connect" : "disconnect");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hdmi_tx_is_dvi_mode(hdmi_ctrl) &&
|
|
|
|
hdmi_tx_is_cea_format(hdmi_ctrl->vid_cfg.vic)) {
|
|
|
|
bool switched;
|
|
|
|
|
|
|
|
switch_set_state(&hdmi_ctrl->audio_sdev, val);
|
|
|
|
switched = hdmi_ctrl->audio_sdev.state != state;
|
|
|
|
|
|
|
|
if (hdmi_ctrl->audio_ack_enabled && switched)
|
|
|
|
atomic_set(&hdmi_ctrl->audio_ack_pending, 1);
|
|
|
|
|
|
|
|
DEV_INFO("%s: audio state %s %d\n", __func__,
|
|
|
|
switched ? "switched to" : "is same",
|
|
|
|
hdmi_ctrl->audio_sdev.state);
|
|
|
|
}
|
|
|
|
} /* hdmi_tx_set_audio_switch_node */
|
|
|
|
|
|
|
|
static void hdmi_tx_wait_for_audio_engine(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
u64 status = 0;
|
|
|
|
u32 wait_for_vote = 50;
|
|
|
|
struct dss_io_data *io = NULL;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: core io not inititalized\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* wait for 5 sec max for audio engine to acknowledge if hdmi tx core
|
|
|
|
* can be safely turned off. Sleep for a reasonable time to make sure
|
|
|
|
* vote_hdmi_core_on variable is updated properly by audio.
|
|
|
|
*/
|
|
|
|
while (hdmi_ctrl->vote_hdmi_core_on && --wait_for_vote)
|
|
|
|
msleep(100);
|
|
|
|
|
|
|
|
|
|
|
|
if (!wait_for_vote)
|
|
|
|
DEV_ERR("%s: HDMI core still voted for power on\n", __func__);
|
|
|
|
|
|
|
|
if (readl_poll_timeout(io->base + HDMI_AUDIO_PKT_CTRL, status,
|
|
|
|
(status & BIT(0)) == 0, AUDIO_POLL_SLEEP_US,
|
|
|
|
AUDIO_POLL_TIMEOUT_US))
|
|
|
|
DEV_ERR("%s: Error turning off audio packet transmission.\n",
|
|
|
|
__func__);
|
|
|
|
|
|
|
|
if (readl_poll_timeout(io->base + HDMI_AUDIO_CFG, status,
|
|
|
|
(status & BIT(0)) == 0, AUDIO_POLL_SLEEP_US,
|
|
|
|
AUDIO_POLL_TIMEOUT_US))
|
|
|
|
DEV_ERR("%s: Error turning off audio engine.\n", __func__);
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
static struct hdmi_tx_ctrl *hdmi_tx_get_drvdata_from_panel_data(
|
|
|
|
struct mdss_panel_data *mpd)
|
|
|
|
{
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
|
|
|
|
|
|
|
if (mpd) {
|
|
|
|
hdmi_ctrl = container_of(mpd, struct hdmi_tx_ctrl, panel_data);
|
|
|
|
if (!hdmi_ctrl)
|
|
|
|
DEV_ERR("%s: hdmi_ctrl = NULL\n", __func__);
|
|
|
|
} else {
|
|
|
|
DEV_ERR("%s: mdss_panel_data = NULL\n", __func__);
|
|
|
|
}
|
|
|
|
return hdmi_ctrl;
|
|
|
|
} /* hdmi_tx_get_drvdata_from_panel_data */
|
|
|
|
|
|
|
|
static struct hdmi_tx_ctrl *hdmi_tx_get_drvdata_from_sysfs_dev(
|
|
|
|
struct device *device)
|
|
|
|
{
|
|
|
|
struct msm_fb_data_type *mfd = NULL;
|
|
|
|
struct mdss_panel_data *panel_data = NULL;
|
|
|
|
struct fb_info *fbi = dev_get_drvdata(device);
|
|
|
|
|
|
|
|
if (fbi) {
|
|
|
|
mfd = (struct msm_fb_data_type *)fbi->par;
|
|
|
|
panel_data = dev_get_platdata(&mfd->pdev->dev);
|
|
|
|
|
|
|
|
return hdmi_tx_get_drvdata_from_panel_data(panel_data);
|
|
|
|
} else {
|
|
|
|
DEV_ERR("%s: fbi = NULL\n", __func__);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
} /* hdmi_tx_get_drvdata_from_sysfs_dev */
|
|
|
|
|
|
|
|
/* todo: Fix this. Right now this is declared in hdmi_util.h */
|
|
|
|
void *hdmi_get_featuredata_from_sysfs_dev(struct device *device,
|
|
|
|
u32 feature_type)
|
|
|
|
{
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!device || feature_type >= HDMI_TX_FEAT_MAX) {
|
2024-09-09 08:52:07 +00:00
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_ctrl = hdmi_tx_get_drvdata_from_sysfs_dev(device);
|
|
|
|
if (hdmi_ctrl)
|
|
|
|
return hdmi_ctrl->feature_data[feature_type];
|
|
|
|
else
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
} /* hdmi_tx_get_featuredata_from_sysfs_dev */
|
|
|
|
EXPORT_SYMBOL(hdmi_get_featuredata_from_sysfs_dev);
|
|
|
|
|
|
|
|
static ssize_t hdmi_tx_sysfs_rda_connected(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
ssize_t ret;
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl =
|
|
|
|
hdmi_tx_get_drvdata_from_sysfs_dev(dev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_lock(&hdmi_ctrl->mutex);
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "%d\n", hdmi_ctrl->hpd_state);
|
|
|
|
DEV_DBG("%s: '%d'\n", __func__, hdmi_ctrl->hpd_state);
|
|
|
|
mutex_unlock(&hdmi_ctrl->mutex);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
} /* hdmi_tx_sysfs_rda_connected */
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static ssize_t hdmi_tx_sysfs_wta_edid(struct device *dev,
|
|
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
ssize_t ret = strnlen(buf, PAGE_SIZE);
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
|
|
|
int i = 0;
|
|
|
|
const char *buf_t = buf;
|
|
|
|
const int char_to_nib = 2;
|
|
|
|
int edid_size = count / char_to_nib;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl = hdmi_tx_get_drvdata_from_sysfs_dev(dev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl || !hdmi_ctrl->edid_buf) {
|
|
|
|
DEV_ERR("%s: invalid data\n", __func__);
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
memset(hdmi_ctrl->edid_buf, 0, hdmi_ctrl->edid_buf_size);
|
|
|
|
|
|
|
|
while (edid_size--) {
|
|
|
|
char t[char_to_nib + 1];
|
|
|
|
int d, rc;
|
|
|
|
|
|
|
|
memcpy(t, buf_t, sizeof(char) * char_to_nib);
|
|
|
|
t[char_to_nib] = '\0';
|
|
|
|
|
|
|
|
rc = kstrtoint(t, 16, &d);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("kstrtoint error %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(hdmi_ctrl->edid_buf + i++, &d,
|
|
|
|
sizeof(*hdmi_ctrl->edid_buf));
|
|
|
|
|
|
|
|
buf_t += char_to_nib;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_ctrl->custom_edid = true;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
return ret;
|
2024-09-09 08:57:42 +00:00
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static ssize_t hdmi_tx_sysfs_rda_edid(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
|
|
|
u32 size;
|
|
|
|
u32 cea_blks;
|
|
|
|
|
|
|
|
hdmi_ctrl = hdmi_tx_get_drvdata_from_sysfs_dev(dev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl || !hdmi_ctrl->edid_buf) {
|
|
|
|
DEV_ERR("%s: invalid data\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
cea_blks = hdmi_ctrl->edid_buf[EDID_BLOCK_SIZE - 2];
|
|
|
|
size = (cea_blks + 1) * EDID_BLOCK_SIZE;
|
|
|
|
size = min_t(u32, size, PAGE_SIZE);
|
|
|
|
|
|
|
|
DEV_DBG("%s: edid size %d\n", __func__, size);
|
|
|
|
|
|
|
|
memcpy(buf, hdmi_ctrl->edid_buf, size);
|
|
|
|
|
|
|
|
print_hex_dump(KERN_DEBUG, "HDMI EDID: ", DUMP_PREFIX_NONE,
|
|
|
|
16, 1, buf, size, false);
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t hdmi_tx_sysfs_wta_audio_cb(struct device *dev,
|
2024-09-09 08:52:07 +00:00
|
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
int ack, rc = 0;
|
|
|
|
int ack_hpd;
|
2024-09-09 08:52:07 +00:00
|
|
|
ssize_t ret = strnlen(buf, PAGE_SIZE);
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
|
|
|
|
|
|
|
hdmi_ctrl = hdmi_tx_get_drvdata_from_sysfs_dev(dev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
rc = kstrtoint(buf, 10, &ack);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: kstrtoint failed. rc=%d\n", __func__, rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (ack & AUDIO_ACK_SET_ENABLE) {
|
|
|
|
hdmi_ctrl->audio_ack_enabled = ack & AUDIO_ACK_ENABLE ?
|
|
|
|
true : false;
|
|
|
|
|
|
|
|
DEV_INFO("%s: audio ack feature %s\n", __func__,
|
|
|
|
hdmi_ctrl->audio_ack_enabled ? "enabled" : "disabled");
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!hdmi_ctrl->audio_ack_enabled)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
atomic_set(&hdmi_ctrl->audio_ack_pending, 0);
|
|
|
|
|
|
|
|
ack_hpd = ack & AUDIO_ACK_CONNECT;
|
|
|
|
|
|
|
|
if (ack_hpd != hdmi_ctrl->hpd_state) {
|
|
|
|
DEV_INFO("%s: unbalanced audio state, ack %d, hpd %d\n",
|
|
|
|
__func__, ack_hpd, hdmi_ctrl->hpd_state);
|
|
|
|
|
|
|
|
hdmi_tx_set_audio_switch_node(hdmi_ctrl, hdmi_ctrl->hpd_state);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2024-09-09 08:57:42 +00:00
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static ssize_t hdmi_tx_sysfs_rda_video_mode(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
|
|
|
ssize_t ret;
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl =
|
|
|
|
hdmi_tx_get_drvdata_from_sysfs_dev(dev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_lock(&hdmi_ctrl->mutex);
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "%d\n", hdmi_ctrl->vid_cfg.vic);
|
|
|
|
DEV_DBG("%s: '%d'\n", __func__, hdmi_ctrl->vid_cfg.vic);
|
|
|
|
mutex_unlock(&hdmi_ctrl->mutex);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
return ret;
|
2024-09-09 08:57:42 +00:00
|
|
|
} /* hdmi_tx_sysfs_rda_video_mode */
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static ssize_t hdmi_tx_sysfs_rda_hpd(struct device *dev,
|
2024-09-09 08:52:07 +00:00
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
ssize_t ret;
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl =
|
|
|
|
hdmi_tx_get_drvdata_from_sysfs_dev(dev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = snprintf(buf, PAGE_SIZE, "%d\n", hdmi_ctrl->hpd_feature_on);
|
|
|
|
DEV_DBG("%s: '%d'\n", __func__, hdmi_ctrl->hpd_feature_on);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
} /* hdmi_tx_sysfs_rda_hpd */
|
|
|
|
|
|
|
|
static ssize_t hdmi_tx_sysfs_wta_hpd(struct device *dev,
|
|
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
int hpd, rc = 0;
|
|
|
|
ssize_t ret = strnlen(buf, PAGE_SIZE);
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
|
|
|
|
|
|
|
DEV_DBG("%s:\n", __func__);
|
|
|
|
hdmi_ctrl = hdmi_tx_get_drvdata_from_sysfs_dev(dev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = kstrtoint(buf, 10, &hpd);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: kstrtoint failed. rc=%d\n", __func__, rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hdmi_ctrl->ds_registered && hpd &&
|
|
|
|
(!hdmi_ctrl->mhl_hpd_on || hdmi_ctrl->hpd_feature_on))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
switch (hpd) {
|
|
|
|
case HPD_OFF:
|
|
|
|
case HPD_DISABLE:
|
|
|
|
if (hpd == HPD_DISABLE)
|
|
|
|
hdmi_ctrl->hpd_disabled = true;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl->hpd_feature_on) {
|
|
|
|
DEV_DBG("%s: HPD is already off\n", __func__);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = hdmi_tx_sysfs_enable_hpd(hdmi_ctrl, false);
|
|
|
|
|
|
|
|
mutex_lock(&hdmi_ctrl->power_mutex);
|
|
|
|
if (hdmi_tx_is_panel_on(hdmi_ctrl)) {
|
|
|
|
mutex_unlock(&hdmi_ctrl->power_mutex);
|
|
|
|
hdmi_tx_set_audio_switch_node(hdmi_ctrl, 0);
|
|
|
|
hdmi_tx_wait_for_audio_engine(hdmi_ctrl);
|
|
|
|
} else {
|
|
|
|
mutex_unlock(&hdmi_ctrl->power_mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_tx_send_cable_notification(hdmi_ctrl, 0);
|
|
|
|
break;
|
|
|
|
case HPD_ON:
|
|
|
|
if (hdmi_ctrl->hpd_disabled == true) {
|
|
|
|
DEV_ERR("%s: hpd is disabled, state %d not allowed\n",
|
|
|
|
__func__, hpd);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hdmi_ctrl->pdata.cond_power_on) {
|
|
|
|
DEV_ERR("%s: hpd state %d not allowed w/ cond. hpd\n",
|
|
|
|
__func__, hpd);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hdmi_ctrl->hpd_feature_on) {
|
|
|
|
DEV_DBG("%s: HPD is already on\n", __func__);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = hdmi_tx_sysfs_enable_hpd(hdmi_ctrl, true);
|
|
|
|
break;
|
|
|
|
case HPD_ON_CONDITIONAL_MTP:
|
|
|
|
if (hdmi_ctrl->hpd_disabled == true) {
|
|
|
|
DEV_ERR("%s: hpd is disabled, state %d not allowed\n",
|
|
|
|
__func__, hpd);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hdmi_ctrl->pdata.cond_power_on) {
|
|
|
|
DEV_ERR("%s: hpd state %d not allowed w/o cond. hpd\n",
|
|
|
|
__func__, hpd);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hdmi_ctrl->hpd_feature_on) {
|
|
|
|
DEV_DBG("%s: HPD is already on\n", __func__);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = hdmi_tx_sysfs_enable_hpd(hdmi_ctrl, true);
|
|
|
|
break;
|
|
|
|
case HPD_ENABLE:
|
|
|
|
hdmi_ctrl->hpd_disabled = false;
|
|
|
|
|
|
|
|
rc = hdmi_tx_sysfs_enable_hpd(hdmi_ctrl, true);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
DEV_ERR("%s: Invalid HPD state requested\n", __func__);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!rc) {
|
|
|
|
hdmi_ctrl->hpd_feature_on =
|
|
|
|
(~hdmi_ctrl->hpd_feature_on) & BIT(0);
|
|
|
|
DEV_DBG("%s: '%d'\n", __func__, hdmi_ctrl->hpd_feature_on);
|
|
|
|
} else {
|
|
|
|
DEV_ERR("%s: failed to '%s' hpd. rc = %d\n", __func__,
|
|
|
|
hpd ? "enable" : "disable", rc);
|
|
|
|
ret = rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
} /* hdmi_tx_sysfs_wta_hpd */
|
|
|
|
|
|
|
|
static ssize_t hdmi_tx_sysfs_wta_vendor_name(struct device *dev,
|
|
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
ssize_t ret, sz;
|
|
|
|
u8 *s = (u8 *) buf;
|
|
|
|
u8 *d = NULL;
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl =
|
|
|
|
hdmi_tx_get_drvdata_from_sysfs_dev(dev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
d = hdmi_ctrl->spd_vendor_name;
|
|
|
|
ret = strnlen(buf, PAGE_SIZE);
|
|
|
|
ret = (ret > 8) ? 8 : ret;
|
|
|
|
|
|
|
|
sz = sizeof(hdmi_ctrl->spd_vendor_name);
|
|
|
|
memset(hdmi_ctrl->spd_vendor_name, 0, sz);
|
|
|
|
while (*s) {
|
|
|
|
if (*s & 0x60 && *s ^ 0x7f) {
|
|
|
|
*d = *s;
|
|
|
|
} else {
|
|
|
|
/* stop copying if control character found */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (++s > (u8 *) (buf + ret))
|
|
|
|
break;
|
|
|
|
|
|
|
|
d++;
|
|
|
|
}
|
|
|
|
hdmi_ctrl->spd_vendor_name[sz - 1] = 0;
|
|
|
|
|
|
|
|
DEV_DBG("%s: '%s'\n", __func__, hdmi_ctrl->spd_vendor_name);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
} /* hdmi_tx_sysfs_wta_vendor_name */
|
|
|
|
|
|
|
|
static ssize_t hdmi_tx_sysfs_rda_vendor_name(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
ssize_t ret;
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl =
|
|
|
|
hdmi_tx_get_drvdata_from_sysfs_dev(dev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "%s\n", hdmi_ctrl->spd_vendor_name);
|
|
|
|
DEV_DBG("%s: '%s'\n", __func__, hdmi_ctrl->spd_vendor_name);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
} /* hdmi_tx_sysfs_rda_vendor_name */
|
|
|
|
|
|
|
|
static ssize_t hdmi_tx_sysfs_wta_product_description(struct device *dev,
|
|
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
ssize_t ret, sz;
|
2024-09-09 08:52:07 +00:00
|
|
|
u8 *s = (u8 *) buf;
|
|
|
|
u8 *d = NULL;
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl =
|
|
|
|
hdmi_tx_get_drvdata_from_sysfs_dev(dev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
d = hdmi_ctrl->spd_product_description;
|
|
|
|
ret = strnlen(buf, PAGE_SIZE);
|
|
|
|
ret = (ret > 16) ? 16 : ret;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
sz = sizeof(hdmi_ctrl->spd_product_description);
|
|
|
|
memset(hdmi_ctrl->spd_product_description, 0, sz);
|
2024-09-09 08:52:07 +00:00
|
|
|
while (*s) {
|
|
|
|
if (*s & 0x60 && *s ^ 0x7f) {
|
|
|
|
*d = *s;
|
|
|
|
} else {
|
|
|
|
/* stop copying if control character found */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (++s > (u8 *) (buf + ret))
|
|
|
|
break;
|
|
|
|
|
|
|
|
d++;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->spd_product_description[sz - 1] = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
DEV_DBG("%s: '%s'\n", __func__, hdmi_ctrl->spd_product_description);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
} /* hdmi_tx_sysfs_wta_product_description */
|
|
|
|
|
|
|
|
static ssize_t hdmi_tx_sysfs_rda_product_description(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
ssize_t ret;
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl =
|
|
|
|
hdmi_tx_get_drvdata_from_sysfs_dev(dev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "%s\n",
|
|
|
|
hdmi_ctrl->spd_product_description);
|
|
|
|
DEV_DBG("%s: '%s'\n", __func__, hdmi_ctrl->spd_product_description);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
} /* hdmi_tx_sysfs_rda_product_description */
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static ssize_t hdmi_tx_sysfs_wta_avi_itc(struct device *dev,
|
|
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
ssize_t ret = strnlen(buf, PAGE_SIZE);
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
|
|
|
int itc = 0, rc = 0;
|
|
|
|
|
|
|
|
hdmi_ctrl = hdmi_tx_get_drvdata_from_sysfs_dev(dev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = kstrtoint(buf, 10, &itc);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: kstrtoint failed. rc =%d\n", __func__, rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (itc < 0 || itc > 1) {
|
|
|
|
DEV_ERR("%s: Invalid ITC %d\n", __func__, itc);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mutex_lock_interruptible(&hdmi_ctrl->lut_lock))
|
|
|
|
return -ERESTARTSYS;
|
|
|
|
|
|
|
|
hdmi_ctrl->vid_cfg.avi_iframe.is_it_content =
|
|
|
|
itc ? true : false;
|
|
|
|
|
|
|
|
mutex_unlock(&hdmi_ctrl->lut_lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
} /* hdmi_tx_sysfs_wta_avi_itc */
|
|
|
|
|
|
|
|
static ssize_t hdmi_tx_sysfs_wta_avi_cn_bits(struct device *dev,
|
|
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
ssize_t ret = strnlen(buf, PAGE_SIZE);
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
|
|
|
int cn_bits = 0, rc = 0;
|
|
|
|
|
|
|
|
hdmi_ctrl = hdmi_tx_get_drvdata_from_sysfs_dev(dev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = kstrtoint(buf, 10, &cn_bits);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: kstrtoint failed. rc=%d\n", __func__, rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* As per CEA-861-E, CN is a positive number and can be max 3 */
|
|
|
|
if (cn_bits < 0 || cn_bits > 3) {
|
|
|
|
DEV_ERR("%s: Invalid CN %d\n", __func__, cn_bits);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mutex_lock_interruptible(&hdmi_ctrl->lut_lock))
|
|
|
|
return -ERESTARTSYS;
|
|
|
|
|
|
|
|
hdmi_ctrl->vid_cfg.avi_iframe.content_type = cn_bits;
|
|
|
|
|
|
|
|
mutex_unlock(&hdmi_ctrl->lut_lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
} /* hdmi_tx_sysfs_wta_cn_bits */
|
|
|
|
|
|
|
|
static ssize_t hdmi_tx_sysfs_wta_s3d_mode(struct device *dev,
|
|
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
int rc, s3d_mode;
|
|
|
|
ssize_t ret = strnlen(buf, PAGE_SIZE);
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
|
|
|
|
|
|
|
hdmi_ctrl = hdmi_tx_get_drvdata_from_sysfs_dev(dev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = kstrtoint(buf, 10, &s3d_mode);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: kstrtoint failed. rc=%d\n", __func__, rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s3d_mode < HDMI_S3D_NONE || s3d_mode >= HDMI_S3D_MAX) {
|
|
|
|
DEV_ERR("%s: invalid s3d mode = %d\n", __func__, s3d_mode);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s3d_mode > HDMI_S3D_NONE &&
|
|
|
|
!hdmi_edid_is_s3d_mode_supported(
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID],
|
|
|
|
hdmi_ctrl->vid_cfg.vic,
|
|
|
|
s3d_mode)) {
|
|
|
|
DEV_ERR("%s: s3d mode not supported in current video mode\n",
|
|
|
|
__func__);
|
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_ctrl->s3d_mode = s3d_mode;
|
|
|
|
hdmi_tx_set_vendor_specific_infoframe(hdmi_ctrl);
|
|
|
|
|
|
|
|
DEV_DBG("%s: %d\n", __func__, hdmi_ctrl->s3d_mode);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t hdmi_tx_sysfs_rda_s3d_mode(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
|
|
|
ssize_t ret;
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl =
|
|
|
|
hdmi_tx_get_drvdata_from_sysfs_dev(dev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = snprintf(buf, PAGE_SIZE, "%d\n", hdmi_ctrl->s3d_mode);
|
|
|
|
DEV_DBG("%s: '%d'\n", __func__, hdmi_ctrl->s3d_mode);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t hdmi_tx_sysfs_wta_5v(struct device *dev,
|
|
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
int read, rc = 0;
|
|
|
|
ssize_t ret = strnlen(buf, PAGE_SIZE);
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
|
|
|
struct dss_module_power *pd = NULL;
|
|
|
|
|
|
|
|
hdmi_ctrl = hdmi_tx_get_drvdata_from_sysfs_dev(dev);
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
pd = &hdmi_ctrl->pdata.power_data[HDMI_TX_HPD_PM];
|
|
|
|
if (!pd) {
|
|
|
|
DEV_ERR("%s: Error: invalid power data\n", __func__);
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = kstrtoint(buf, 10, &read);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: kstrtoint failed. rc=%d\n", __func__, rc);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
read = ~(!!read ^ pd->gpio_config->value) & BIT(0);
|
|
|
|
|
|
|
|
DEV_DBG("%s: writing %d to 5v gpio\n", __func__, read);
|
|
|
|
gpio_set_value(pd->gpio_config->gpio, read);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
error:
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
static DEVICE_ATTR(connected, S_IRUGO, hdmi_tx_sysfs_rda_connected, NULL);
|
2024-09-09 08:57:42 +00:00
|
|
|
static DEVICE_ATTR(hdmi_audio_cb, S_IWUSR, NULL, hdmi_tx_sysfs_wta_audio_cb);
|
|
|
|
static DEVICE_ATTR(edid, S_IRUGO | S_IWUSR, hdmi_tx_sysfs_rda_edid,
|
|
|
|
hdmi_tx_sysfs_wta_edid);
|
|
|
|
static DEVICE_ATTR(video_mode, S_IRUGO, hdmi_tx_sysfs_rda_video_mode, NULL);
|
2024-09-09 08:52:07 +00:00
|
|
|
static DEVICE_ATTR(hpd, S_IRUGO | S_IWUSR, hdmi_tx_sysfs_rda_hpd,
|
|
|
|
hdmi_tx_sysfs_wta_hpd);
|
|
|
|
static DEVICE_ATTR(vendor_name, S_IRUGO | S_IWUSR,
|
|
|
|
hdmi_tx_sysfs_rda_vendor_name, hdmi_tx_sysfs_wta_vendor_name);
|
|
|
|
static DEVICE_ATTR(product_description, S_IRUGO | S_IWUSR,
|
|
|
|
hdmi_tx_sysfs_rda_product_description,
|
|
|
|
hdmi_tx_sysfs_wta_product_description);
|
2024-09-09 08:57:42 +00:00
|
|
|
static DEVICE_ATTR(avi_itc, S_IWUSR, NULL, hdmi_tx_sysfs_wta_avi_itc);
|
|
|
|
static DEVICE_ATTR(avi_cn0_1, S_IWUSR, NULL, hdmi_tx_sysfs_wta_avi_cn_bits);
|
|
|
|
static DEVICE_ATTR(s3d_mode, S_IRUGO | S_IWUSR, hdmi_tx_sysfs_rda_s3d_mode,
|
|
|
|
hdmi_tx_sysfs_wta_s3d_mode);
|
|
|
|
static DEVICE_ATTR(5v, S_IWUSR, NULL, hdmi_tx_sysfs_wta_5v);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
static struct attribute *hdmi_tx_fs_attrs[] = {
|
|
|
|
&dev_attr_connected.attr,
|
2024-09-09 08:57:42 +00:00
|
|
|
&dev_attr_hdmi_audio_cb.attr,
|
|
|
|
&dev_attr_edid.attr,
|
|
|
|
&dev_attr_video_mode.attr,
|
2024-09-09 08:52:07 +00:00
|
|
|
&dev_attr_hpd.attr,
|
|
|
|
&dev_attr_vendor_name.attr,
|
|
|
|
&dev_attr_product_description.attr,
|
2024-09-09 08:57:42 +00:00
|
|
|
&dev_attr_avi_itc.attr,
|
|
|
|
&dev_attr_avi_cn0_1.attr,
|
|
|
|
&dev_attr_s3d_mode.attr,
|
|
|
|
&dev_attr_5v.attr,
|
2024-09-09 08:52:07 +00:00
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
static struct attribute_group hdmi_tx_fs_attrs_group = {
|
|
|
|
.attrs = hdmi_tx_fs_attrs,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int hdmi_tx_sysfs_create(struct hdmi_tx_ctrl *hdmi_ctrl,
|
|
|
|
struct fb_info *fbi)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl || !fbi) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = sysfs_create_group(&fbi->dev->kobj,
|
|
|
|
&hdmi_tx_fs_attrs_group);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: failed, rc=%d\n", __func__, rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
hdmi_ctrl->kobj = &fbi->dev->kobj;
|
|
|
|
DEV_DBG("%s: sysfs group %p\n", __func__, hdmi_ctrl->kobj);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
} /* hdmi_tx_sysfs_create */
|
|
|
|
|
|
|
|
static void hdmi_tx_sysfs_remove(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (hdmi_ctrl->kobj)
|
|
|
|
sysfs_remove_group(hdmi_ctrl->kobj, &hdmi_tx_fs_attrs_group);
|
|
|
|
hdmi_ctrl->kobj = NULL;
|
|
|
|
} /* hdmi_tx_sysfs_remove */
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int hdmi_tx_config_avmute(struct hdmi_tx_ctrl *hdmi_ctrl, bool set)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
struct dss_io_data *io;
|
|
|
|
u32 av_mute_status;
|
|
|
|
bool av_pkt_en = false;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
return -EINVAL;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: Core io is not initialized\n", __func__);
|
|
|
|
return -EINVAL;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
av_mute_status = DSS_REG_R(io, HDMI_GC);
|
|
|
|
|
|
|
|
if (set) {
|
|
|
|
if (!(av_mute_status & BIT(0))) {
|
|
|
|
DSS_REG_W(io, HDMI_GC, av_mute_status | BIT(0));
|
|
|
|
av_pkt_en = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (av_mute_status & BIT(0)) {
|
|
|
|
DSS_REG_W(io, HDMI_GC, av_mute_status & ~BIT(0));
|
|
|
|
av_pkt_en = true;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/* Enable AV Mute tranmission here */
|
|
|
|
if (av_pkt_en)
|
|
|
|
DSS_REG_W(io, HDMI_VBI_PKT_CTRL,
|
|
|
|
DSS_REG_R(io, HDMI_VBI_PKT_CTRL) | (BIT(4) & BIT(5)));
|
|
|
|
|
|
|
|
DEV_DBG("%s: AVMUTE %s\n", __func__, set ? "set" : "cleared");
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
} /* hdmi_tx_config_avmute */
|
|
|
|
|
|
|
|
static bool hdmi_tx_is_encryption_set(struct hdmi_tx_ctrl *hdmi_ctrl)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
|
|
|
struct dss_io_data *io;
|
2024-09-09 08:57:42 +00:00
|
|
|
bool enc_en = true;
|
|
|
|
u32 reg_val;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
goto end;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: Core io is not initialized\n", __func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
goto end;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
reg_val = DSS_REG_R_ND(io, HDMI_HDCP_CTRL2);
|
|
|
|
if ((reg_val & BIT(0)) && (reg_val & BIT(1)))
|
|
|
|
goto end;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (DSS_REG_R_ND(io, HDMI_CTRL) & BIT(2))
|
|
|
|
goto end;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
return false;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
end:
|
|
|
|
return enc_en;
|
|
|
|
} /* hdmi_tx_is_encryption_set */
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static void hdmi_tx_hdcp_cb(void *ptr, enum hdmi_hdcp_state status)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = (struct hdmi_tx_ctrl *)ptr;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->hdcp_status = status;
|
|
|
|
|
|
|
|
queue_delayed_work(hdmi_ctrl->workq, &hdmi_ctrl->hdcp_cb_work, HZ/4);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool hdmi_tx_is_stream_shareable(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
bool ret;
|
|
|
|
|
|
|
|
switch (hdmi_ctrl->enc_lvl) {
|
|
|
|
case HDCP_STATE_AUTH_ENC_NONE:
|
|
|
|
ret = true;
|
|
|
|
break;
|
|
|
|
case HDCP_STATE_AUTH_ENC_1X:
|
|
|
|
ret = hdmi_tx_is_hdcp_enabled(hdmi_ctrl) &&
|
|
|
|
hdmi_ctrl->auth_state;
|
|
|
|
break;
|
|
|
|
case HDCP_STATE_AUTH_ENC_2P2:
|
|
|
|
ret = hdmi_ctrl->hdcp_feature_on &&
|
|
|
|
hdmi_ctrl->hdcp22_present &&
|
|
|
|
hdmi_ctrl->auth_state;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ret = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void hdmi_tx_hdcp_cb_work(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
|
|
|
struct delayed_work *dw = to_delayed_work(work);
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
hdmi_ctrl = container_of(dw, struct hdmi_tx_ctrl, hdcp_cb_work);
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_DBG("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
DEV_DBG("%s: HDCP status=%s hpd_state=%d\n", __func__,
|
2024-09-09 08:57:42 +00:00
|
|
|
hdcp_state_name(hdmi_ctrl->hdcp_status), hdmi_ctrl->hpd_state);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
switch (hdmi_ctrl->hdcp_status) {
|
2024-09-09 08:52:07 +00:00
|
|
|
case HDCP_STATE_AUTHENTICATED:
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->auth_state = true;
|
|
|
|
|
|
|
|
if (hdmi_tx_is_panel_on(hdmi_ctrl) &&
|
|
|
|
hdmi_tx_is_stream_shareable(hdmi_ctrl)) {
|
|
|
|
rc = hdmi_tx_config_avmute(hdmi_ctrl, false);
|
|
|
|
hdmi_tx_set_audio_switch_node(hdmi_ctrl, 1);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
if (hdmi_ctrl->hdcp1_use_sw_keys && hdmi_ctrl->hdcp14_present)
|
|
|
|
hdcp1_set_enc(true);
|
2024-09-09 08:52:07 +00:00
|
|
|
break;
|
|
|
|
case HDCP_STATE_AUTH_FAIL:
|
2024-09-09 08:57:42 +00:00
|
|
|
if (hdmi_ctrl->hdcp1_use_sw_keys && hdmi_ctrl->hdcp14_present) {
|
|
|
|
if (hdmi_ctrl->auth_state)
|
|
|
|
hdcp1_set_enc(false);
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->auth_state = false;
|
|
|
|
|
|
|
|
if (hdmi_tx_is_encryption_set(hdmi_ctrl) ||
|
|
|
|
!hdmi_tx_is_stream_shareable(hdmi_ctrl)) {
|
|
|
|
hdmi_tx_set_audio_switch_node(hdmi_ctrl, 0);
|
|
|
|
rc = hdmi_tx_config_avmute(hdmi_ctrl, true);
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (hdmi_tx_is_panel_on(hdmi_ctrl)) {
|
2024-09-09 08:52:07 +00:00
|
|
|
DEV_DBG("%s: Reauthenticating\n", __func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
rc = hdmi_ctrl->hdcp_ops->hdmi_hdcp_reauthenticate(
|
|
|
|
hdmi_ctrl->hdcp_data);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (rc)
|
|
|
|
DEV_ERR("%s: HDCP reauth failed. rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
} else {
|
|
|
|
DEV_DBG("%s: Not reauthenticating. Cable not conn\n",
|
|
|
|
__func__);
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
break;
|
|
|
|
case HDCP_STATE_AUTH_ENC_NONE:
|
|
|
|
hdmi_ctrl->enc_lvl = HDCP_STATE_AUTH_ENC_NONE;
|
|
|
|
|
|
|
|
if (hdmi_tx_is_panel_on(hdmi_ctrl)) {
|
|
|
|
rc = hdmi_tx_config_avmute(hdmi_ctrl, false);
|
|
|
|
hdmi_tx_set_audio_switch_node(hdmi_ctrl, 1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case HDCP_STATE_AUTH_ENC_1X:
|
|
|
|
case HDCP_STATE_AUTH_ENC_2P2:
|
|
|
|
hdmi_ctrl->enc_lvl = hdmi_ctrl->hdcp_status;
|
|
|
|
|
|
|
|
if (hdmi_tx_is_panel_on(hdmi_ctrl) &&
|
|
|
|
hdmi_tx_is_stream_shareable(hdmi_ctrl)) {
|
|
|
|
rc = hdmi_tx_config_avmute(hdmi_ctrl, false);
|
|
|
|
hdmi_tx_set_audio_switch_node(hdmi_ctrl, 1);
|
|
|
|
} else {
|
|
|
|
hdmi_tx_set_audio_switch_node(hdmi_ctrl, 0);
|
|
|
|
rc = hdmi_tx_config_avmute(hdmi_ctrl, true);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
/* do nothing */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static u32 hdmi_tx_ddc_read(struct hdmi_tx_ddc_ctrl *ddc_ctrl,
|
|
|
|
u32 block, u8 *edid_buf)
|
|
|
|
{
|
|
|
|
u32 block_size = EDID_BLOCK_SIZE;
|
|
|
|
struct hdmi_tx_ddc_data ddc_data;
|
|
|
|
u32 status = 0, retry_cnt = 0, i;
|
|
|
|
|
|
|
|
if (!ddc_ctrl || !edid_buf) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
DEV_DBG("EDID: reading block(%d) with block-size=%d\n",
|
|
|
|
block, block_size);
|
|
|
|
|
|
|
|
for (i = 0; i < EDID_BLOCK_SIZE; i += block_size) {
|
|
|
|
memset(&ddc_data, 0, sizeof(ddc_data));
|
|
|
|
|
|
|
|
ddc_data.dev_addr = EDID_BLOCK_ADDR;
|
|
|
|
ddc_data.offset = block * EDID_BLOCK_SIZE + i;
|
|
|
|
ddc_data.data_buf = edid_buf + i;
|
|
|
|
ddc_data.data_len = block_size;
|
|
|
|
ddc_data.request_len = block_size;
|
|
|
|
ddc_data.retry = 1;
|
|
|
|
ddc_data.what = "EDID";
|
|
|
|
ddc_data.retry_align = true;
|
|
|
|
|
|
|
|
ddc_ctrl->ddc_data = ddc_data;
|
|
|
|
|
|
|
|
/* Read EDID twice with 32bit alighnment too */
|
|
|
|
if (block < 2)
|
|
|
|
status = hdmi_ddc_read(ddc_ctrl);
|
|
|
|
else
|
|
|
|
status = hdmi_ddc_read_seg(ddc_ctrl);
|
|
|
|
|
|
|
|
if (status)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (retry_cnt++ >= MAX_EDID_READ_RETRY)
|
|
|
|
block_size /= 2;
|
|
|
|
|
|
|
|
} while (status && (block_size >= 16));
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hdmi_tx_read_edid_retry(struct hdmi_tx_ctrl *hdmi_ctrl, u8 block)
|
|
|
|
{
|
|
|
|
u32 checksum_retry = 0;
|
|
|
|
u8 *ebuf;
|
|
|
|
int ret = 0;
|
|
|
|
struct hdmi_tx_ddc_ctrl *ddc_ctrl;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ebuf = hdmi_ctrl->edid_buf;
|
|
|
|
if (!ebuf) {
|
|
|
|
DEV_ERR("%s: invalid edid buf\n", __func__);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ddc_ctrl = &hdmi_ctrl->ddc_ctrl;
|
|
|
|
|
|
|
|
while (checksum_retry++ < MAX_EDID_READ_RETRY) {
|
|
|
|
ret = hdmi_tx_ddc_read(ddc_ctrl, block,
|
|
|
|
ebuf + (block * EDID_BLOCK_SIZE));
|
|
|
|
if (ret)
|
|
|
|
continue;
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
end:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hdmi_tx_read_edid(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
int ndx, check_sum;
|
|
|
|
int cea_blks = 0, block = 0, total_blocks = 0;
|
|
|
|
int ret = 0;
|
|
|
|
u8 *ebuf;
|
|
|
|
struct hdmi_tx_ddc_ctrl *ddc_ctrl;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ebuf = hdmi_ctrl->edid_buf;
|
|
|
|
if (!ebuf) {
|
|
|
|
DEV_ERR("%s: invalid edid buf\n", __func__);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(ebuf, 0, hdmi_ctrl->edid_buf_size);
|
|
|
|
|
|
|
|
ddc_ctrl = &hdmi_ctrl->ddc_ctrl;
|
|
|
|
|
|
|
|
do {
|
|
|
|
if (block * EDID_BLOCK_SIZE > hdmi_ctrl->edid_buf_size) {
|
|
|
|
DEV_ERR("%s: no mem for block %d, max mem %d\n",
|
|
|
|
__func__, block, hdmi_ctrl->edid_buf_size);
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = hdmi_tx_read_edid_retry(hdmi_ctrl, block);
|
|
|
|
if (ret) {
|
|
|
|
DEV_ERR("%s: edid read failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* verify checksum to validate edid block */
|
|
|
|
check_sum = 0;
|
|
|
|
for (ndx = 0; ndx < EDID_BLOCK_SIZE; ++ndx)
|
|
|
|
check_sum += ebuf[ndx];
|
|
|
|
|
|
|
|
if (check_sum & 0xFF) {
|
|
|
|
DEV_ERR("%s: checksome mismatch\n", __func__);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* get number of cea extension blocks as given in block 0*/
|
|
|
|
if (block == 0) {
|
|
|
|
cea_blks = ebuf[EDID_BLOCK_SIZE - 2];
|
|
|
|
if (cea_blks < 0 || cea_blks >= MAX_EDID_BLOCKS) {
|
|
|
|
cea_blks = 0;
|
|
|
|
DEV_ERR("%s: invalid cea blocks %d\n",
|
|
|
|
__func__, cea_blks);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
total_blocks = cea_blks + 1;
|
|
|
|
}
|
|
|
|
} while ((cea_blks-- > 0) && (block++ < MAX_EDID_BLOCKS));
|
|
|
|
end:
|
|
|
|
|
|
|
|
return ret;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Enable HDMI features */
|
2024-09-09 08:57:42 +00:00
|
|
|
static int hdmi_tx_init_features(struct hdmi_tx_ctrl *hdmi_ctrl,
|
|
|
|
struct fb_info *fbi)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
struct hdmi_edid_init_data edid_init_data = {0};
|
|
|
|
struct hdmi_hdcp_init_data hdcp_init_data = {0};
|
|
|
|
struct hdmi_cec_init_data cec_init_data = {0};
|
|
|
|
struct cec_abstract_init_data cec_abst_init_data = {0};
|
|
|
|
struct resource *res = NULL;
|
|
|
|
void *fd = NULL;
|
|
|
|
int ret = 0;
|
|
|
|
void *cec_hw_data, *cec_abst_data;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!hdmi_ctrl || !fbi) {
|
2024-09-09 08:52:07 +00:00
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = -EINVAL;
|
|
|
|
goto end;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Initialize EDID feature */
|
2024-09-09 08:57:42 +00:00
|
|
|
edid_init_data.kobj = hdmi_ctrl->kobj;
|
|
|
|
edid_init_data.ds_data = hdmi_ctrl->ds_data;
|
|
|
|
edid_init_data.max_pclk_khz = hdmi_ctrl->max_pclk_khz;
|
|
|
|
|
|
|
|
fd = hdmi_edid_init(&edid_init_data);
|
|
|
|
if (!fd) {
|
2024-09-09 08:52:07 +00:00
|
|
|
DEV_ERR("%s: hdmi_edid_init failed\n", __func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = -ENODEV;
|
|
|
|
goto end;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
hdmi_ctrl->panel_data.panel_info.edid_data = fd;
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID] = fd;
|
|
|
|
|
|
|
|
/* get edid buffer from edid parser */
|
|
|
|
hdmi_ctrl->edid_buf = edid_init_data.buf;
|
|
|
|
hdmi_ctrl->edid_buf_size = edid_init_data.buf_size;
|
|
|
|
|
|
|
|
hdmi_edid_set_video_resolution(fd, hdmi_ctrl->vid_cfg.vic, true);
|
|
|
|
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID] = fd;
|
|
|
|
|
|
|
|
res = platform_get_resource_byname(hdmi_ctrl->pdev,
|
|
|
|
IORESOURCE_MEM, hdmi_tx_io_name(HDMI_TX_CORE_IO));
|
|
|
|
if (!res) {
|
|
|
|
DEV_ERR("%s: Error getting HDMI tx core resource\n",
|
|
|
|
__func__);
|
|
|
|
ret = -ENODEV;
|
|
|
|
goto err_res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Initialize HDCP features */
|
|
|
|
hdcp_init_data.phy_addr = res->start;
|
|
|
|
hdcp_init_data.core_io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
hdcp_init_data.qfprom_io = &hdmi_ctrl->pdata.io[HDMI_TX_QFPROM_IO];
|
|
|
|
hdcp_init_data.hdcp_io = &hdmi_ctrl->pdata.io[HDMI_TX_HDCP_IO];
|
|
|
|
hdcp_init_data.mutex = &hdmi_ctrl->mutex;
|
|
|
|
hdcp_init_data.sysfs_kobj = hdmi_ctrl->kobj;
|
|
|
|
hdcp_init_data.ddc_ctrl = &hdmi_ctrl->ddc_ctrl;
|
|
|
|
hdcp_init_data.workq = hdmi_ctrl->workq;
|
|
|
|
hdcp_init_data.notify_status = hdmi_tx_hdcp_cb;
|
|
|
|
hdcp_init_data.cb_data = (void *)hdmi_ctrl;
|
|
|
|
hdcp_init_data.hdmi_tx_ver = hdmi_ctrl->hdmi_tx_ver;
|
|
|
|
hdcp_init_data.timing = &hdmi_ctrl->vid_cfg.timing;
|
|
|
|
|
|
|
|
if (hdmi_ctrl->hdcp14_present) {
|
|
|
|
fd = hdmi_hdcp_init(&hdcp_init_data);
|
|
|
|
|
|
|
|
if (IS_ERR_OR_NULL(fd)) {
|
|
|
|
DEV_WARN("%s: hdmi_hdcp_init failed\n", __func__);
|
|
|
|
ret = -ENODEV;
|
|
|
|
goto err_res;
|
|
|
|
} else {
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP] = fd;
|
|
|
|
DEV_DBG("%s: HDCP 1.4 configured\n", __func__);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fd = hdmi_hdcp2p2_init(&hdcp_init_data);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (IS_ERR_OR_NULL(fd)) {
|
|
|
|
DEV_WARN("%s: hdmi_hdcp2p2_init failed\n", __func__);
|
|
|
|
ret = -ENODEV;
|
|
|
|
goto err_hdcp;
|
|
|
|
} else {
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP2P2] = fd;
|
|
|
|
DEV_DBG("%s: HDCP 2.2 configured\n", __func__);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/* initialize cec hw feature and get ops */
|
2024-09-09 08:52:07 +00:00
|
|
|
cec_init_data.io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
cec_init_data.workq = hdmi_ctrl->workq;
|
2024-09-09 08:57:42 +00:00
|
|
|
cec_init_data.pinfo = &hdmi_ctrl->panel_data.panel_info;
|
|
|
|
cec_init_data.ops = &hdmi_ctrl->hdmi_cec_ops;
|
|
|
|
cec_init_data.cbs = &hdmi_ctrl->hdmi_cec_cbs;
|
|
|
|
|
|
|
|
cec_hw_data = hdmi_cec_init(&cec_init_data);
|
|
|
|
if (IS_ERR_OR_NULL(cec_hw_data)) {
|
|
|
|
DEV_ERR("%s: error cec init\n", __func__);
|
|
|
|
goto err_cec_hw;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_ctrl->panel_data.panel_info.is_cec_supported = true;
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW] = cec_hw_data;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/* initialize cec abstract layer and get callbacks */
|
|
|
|
cec_abst_init_data.kobj = hdmi_ctrl->kobj;
|
|
|
|
cec_abst_init_data.ops = &hdmi_ctrl->hdmi_cec_ops;
|
|
|
|
cec_abst_init_data.cbs = &hdmi_ctrl->hdmi_cec_cbs;
|
|
|
|
|
|
|
|
cec_abst_data = cec_abstract_init(&cec_abst_init_data);
|
|
|
|
if (IS_ERR_OR_NULL(cec_abst_data)) {
|
|
|
|
DEV_ERR("%s: error cec init\n", __func__);
|
|
|
|
goto err_cec_abst;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* keep cec abstract data */
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_ABST] = cec_abst_data;
|
|
|
|
hdmi_ctrl->panel_data.panel_info.cec_data = cec_abst_data;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
return 0;
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
err_cec_abst:
|
|
|
|
hdmi_cec_deinit(hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW]);
|
|
|
|
hdmi_ctrl->panel_data.panel_info.is_cec_supported = false;
|
|
|
|
err_cec_hw:
|
|
|
|
if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP2P2]) {
|
|
|
|
hdmi_hdcp2p2_deinit(
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP2P2]);
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP2P2] = NULL;
|
|
|
|
}
|
|
|
|
err_hdcp:
|
|
|
|
if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP]) {
|
|
|
|
hdmi_hdcp_deinit(hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP]);
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP] = NULL;
|
|
|
|
}
|
|
|
|
err_res:
|
|
|
|
hdmi_edid_deinit(hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID]);
|
|
|
|
end:
|
|
|
|
return ret;
|
2024-09-09 08:52:07 +00:00
|
|
|
} /* hdmi_tx_init_features */
|
|
|
|
|
|
|
|
static inline u32 hdmi_tx_is_controller_on(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
struct dss_io_data *io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
return DSS_REG_R_ND(io, HDMI_CTRL) & BIT(0);
|
|
|
|
} /* hdmi_tx_is_controller_on */
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int hdmi_tx_init_panel_info(struct hdmi_tx_ctrl *hdmi_ctrl)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
struct mdss_panel_info *pinfo;
|
|
|
|
struct msm_hdmi_mode_timing_info timing = {0};
|
|
|
|
u32 ret;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = hdmi_get_supported_mode(&timing, &hdmi_ctrl->ds_data,
|
|
|
|
hdmi_ctrl->vid_cfg.vic);
|
|
|
|
pinfo = &hdmi_ctrl->panel_data.panel_info;
|
|
|
|
|
|
|
|
if (ret || !timing.supported || !pinfo) {
|
|
|
|
DEV_ERR("%s: invalid timing data\n", __func__);
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
pinfo->xres = timing.active_h;
|
|
|
|
pinfo->yres = timing.active_v;
|
|
|
|
pinfo->clk_rate = timing.pixel_freq * 1000;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
pinfo->lcdc.h_back_porch = timing.back_porch_h;
|
|
|
|
pinfo->lcdc.h_front_porch = timing.front_porch_h;
|
|
|
|
pinfo->lcdc.h_pulse_width = timing.pulse_width_h;
|
|
|
|
pinfo->lcdc.v_back_porch = timing.back_porch_v;
|
|
|
|
pinfo->lcdc.v_front_porch = timing.front_porch_v;
|
|
|
|
pinfo->lcdc.v_pulse_width = timing.pulse_width_v;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
pinfo->type = DTV_PANEL;
|
2024-09-09 08:57:42 +00:00
|
|
|
pinfo->pdest = DISPLAY_3;
|
2024-09-09 08:52:07 +00:00
|
|
|
pinfo->wait_cycle = 0;
|
|
|
|
pinfo->bpp = 24;
|
|
|
|
pinfo->fb_num = 1;
|
|
|
|
|
|
|
|
pinfo->lcdc.border_clr = 0; /* blk */
|
|
|
|
pinfo->lcdc.underflow_clr = 0xff; /* blue */
|
|
|
|
pinfo->lcdc.hsync_skew = 0;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
pinfo->cont_splash_enabled = hdmi_ctrl->pdata.cont_splash_enabled;
|
|
|
|
pinfo->is_pluggable = hdmi_ctrl->pdata.pluggable;
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
return 0;
|
|
|
|
} /* hdmi_tx_init_panel_info */
|
|
|
|
|
|
|
|
static int hdmi_tx_read_sink_info(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hdmi_tx_is_controller_on(hdmi_ctrl)) {
|
|
|
|
DEV_ERR("%s: failed: HDMI controller is off", __func__);
|
|
|
|
status = -ENXIO;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!hdmi_ctrl->custom_edid) {
|
|
|
|
hdmi_ddc_config(&hdmi_ctrl->ddc_ctrl);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
status = hdmi_tx_read_edid(hdmi_ctrl);
|
|
|
|
if (status) {
|
|
|
|
DEV_ERR("%s: error reading edid\n", __func__);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
hdmi_ctrl->custom_edid = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
status = hdmi_edid_parser(hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID]);
|
|
|
|
if (status)
|
|
|
|
DEV_ERR("%s: edid parse failed\n", __func__);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
error:
|
|
|
|
return status;
|
|
|
|
} /* hdmi_tx_read_sink_info */
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static void hdmi_tx_update_hdcp_info(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
void *fd = NULL;
|
|
|
|
struct hdmi_hdcp_ops *ops = NULL;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check first if hdcp2p2 is supported */
|
|
|
|
fd = hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP2P2];
|
|
|
|
if (fd)
|
|
|
|
ops = hdmi_hdcp2p2_start(fd);
|
|
|
|
|
|
|
|
if (ops && ops->feature_supported)
|
|
|
|
hdmi_ctrl->hdcp22_present = ops->feature_supported(fd);
|
|
|
|
else
|
|
|
|
hdmi_ctrl->hdcp22_present = false;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl->hdcp22_present) {
|
|
|
|
if (hdmi_ctrl->hdcp1_use_sw_keys)
|
|
|
|
hdmi_ctrl->hdcp14_present =
|
|
|
|
hdcp1_check_if_supported_load_app();
|
|
|
|
|
|
|
|
if (hdmi_ctrl->hdcp14_present) {
|
|
|
|
fd = hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP];
|
|
|
|
ops = hdmi_hdcp_start(fd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* update internal data about hdcp */
|
|
|
|
hdmi_ctrl->hdcp_data = fd;
|
|
|
|
hdmi_ctrl->hdcp_ops = ops;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
static void hdmi_tx_hpd_int_work(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
2024-09-09 08:57:42 +00:00
|
|
|
struct dss_io_data *io;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
hdmi_ctrl = container_of(work, struct hdmi_tx_ctrl, hpd_int_work);
|
|
|
|
if (!hdmi_ctrl || !hdmi_ctrl->hpd_initialized) {
|
|
|
|
DEV_DBG("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
DEV_DBG("%s: Got HPD %s interrupt\n", __func__,
|
|
|
|
hdmi_ctrl->hpd_state ? "CONNECT" : "DISCONNECT");
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (hdmi_ctrl->hpd_state) {
|
|
|
|
if (hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_DDC_PM, true)) {
|
|
|
|
DEV_ERR("%s: Failed to enable ddc power\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/* Enable SW DDC before EDID read */
|
|
|
|
DSS_REG_W_ND(io, HDMI_DDC_ARBITRATION ,
|
|
|
|
DSS_REG_R(io, HDMI_DDC_ARBITRATION) & ~(BIT(4)));
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
hdmi_tx_read_sink_info(hdmi_ctrl);
|
2024-09-09 08:57:42 +00:00
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
if (hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_DDC_PM, false))
|
2024-09-09 08:57:42 +00:00
|
|
|
DEV_ERR("%s: Failed to disable ddc power\n", __func__);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_tx_send_cable_notification(hdmi_ctrl, true);
|
|
|
|
} else {
|
|
|
|
hdmi_tx_set_audio_switch_node(hdmi_ctrl, 0);
|
|
|
|
hdmi_tx_wait_for_audio_engine(hdmi_ctrl);
|
|
|
|
|
|
|
|
hdmi_tx_send_cable_notification(hdmi_ctrl, false);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!completion_done(&hdmi_ctrl->hpd_int_done))
|
|
|
|
complete_all(&hdmi_ctrl->hpd_int_done);
|
2024-09-09 08:52:07 +00:00
|
|
|
} /* hdmi_tx_hpd_int_work */
|
|
|
|
|
|
|
|
static int hdmi_tx_check_capability(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
u32 hdmi_disabled, hdcp_disabled, reg_val;
|
2024-09-09 08:52:07 +00:00
|
|
|
struct dss_io_data *io = NULL;
|
2024-09-09 08:57:42 +00:00
|
|
|
int ret = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = -EINVAL;
|
|
|
|
goto end;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_QFPROM_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: QFPROM io is not initialized\n", __func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = -EINVAL;
|
|
|
|
goto end;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/* check if hdmi and hdcp are disabled */
|
|
|
|
if (hdmi_ctrl->hdmi_tx_ver < HDMI_TX_VERSION_4) {
|
|
|
|
hdcp_disabled = DSS_REG_R_ND(io,
|
|
|
|
QFPROM_RAW_FEAT_CONFIG_ROW0_LSB) & BIT(31);
|
|
|
|
|
|
|
|
hdmi_disabled = DSS_REG_R_ND(io,
|
|
|
|
QFPROM_RAW_FEAT_CONFIG_ROW0_MSB) & BIT(0);
|
|
|
|
} else {
|
|
|
|
reg_val = DSS_REG_R_ND(io,
|
|
|
|
QFPROM_RAW_FEAT_CONFIG_ROW0_LSB + QFPROM_RAW_VERSION_4);
|
|
|
|
hdcp_disabled = reg_val & BIT(12);
|
|
|
|
hdmi_disabled = reg_val & BIT(13);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
reg_val = DSS_REG_R_ND(io, SEC_CTRL_HW_VERSION);
|
|
|
|
/*
|
|
|
|
* With HDCP enabled on capable hardware, check if HW
|
|
|
|
* or SW keys should be used.
|
|
|
|
*/
|
|
|
|
if (!hdcp_disabled && (reg_val >= HDCP_SEL_MIN_SEC_VERSION)) {
|
|
|
|
reg_val = DSS_REG_R_ND(io,
|
|
|
|
QFPROM_RAW_FEAT_CONFIG_ROW0_MSB +
|
|
|
|
QFPROM_RAW_VERSION_4);
|
|
|
|
if (!(reg_val & BIT(23)))
|
|
|
|
hdmi_ctrl->hdcp1_use_sw_keys = true;
|
|
|
|
}
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
DEV_DBG("%s: Features <HDMI:%s, HDCP:%s>\n", __func__,
|
|
|
|
hdmi_disabled ? "OFF" : "ON", hdcp_disabled ? "OFF" : "ON");
|
|
|
|
|
|
|
|
if (hdmi_disabled) {
|
|
|
|
DEV_ERR("%s: HDMI disabled\n", __func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
ret = -ENODEV;
|
|
|
|
goto end;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->hdcp14_present = !hdcp_disabled;
|
|
|
|
end:
|
|
|
|
return ret;
|
2024-09-09 08:52:07 +00:00
|
|
|
} /* hdmi_tx_check_capability */
|
|
|
|
|
|
|
|
static int hdmi_tx_set_video_fmt(struct hdmi_tx_ctrl *hdmi_ctrl,
|
|
|
|
struct mdss_panel_info *pinfo)
|
|
|
|
{
|
|
|
|
int new_vic = -1;
|
2024-09-09 08:57:42 +00:00
|
|
|
int res_changed = RESOLUTION_UNCHANGED;
|
|
|
|
struct hdmi_video_config *vid_cfg = NULL;
|
|
|
|
u32 ret;
|
|
|
|
u32 div = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!hdmi_ctrl || !pinfo) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
vid_cfg = &hdmi_ctrl->vid_cfg;
|
2024-09-09 08:52:07 +00:00
|
|
|
new_vic = hdmi_tx_get_vic_from_panel_info(hdmi_ctrl, pinfo);
|
|
|
|
if ((new_vic < 0) || (new_vic > HDMI_VFRMT_MAX)) {
|
|
|
|
DEV_ERR("%s: invalid or not supported vic\n", __func__);
|
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (vid_cfg->vic != new_vic) {
|
|
|
|
res_changed = RESOLUTION_CHANGED;
|
|
|
|
DEV_DBG("%s: switching from %s => %s", __func__,
|
|
|
|
msm_hdmi_mode_2string(vid_cfg->vic),
|
|
|
|
msm_hdmi_mode_2string(new_vic));
|
|
|
|
}
|
|
|
|
|
|
|
|
vid_cfg->vic = (u32)new_vic;
|
|
|
|
|
|
|
|
ret = hdmi_get_supported_mode(&vid_cfg->timing, &hdmi_ctrl->ds_data,
|
|
|
|
vid_cfg->vic);
|
|
|
|
|
|
|
|
if (ret || !vid_cfg->timing.supported) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Setup AVI Infoframe content */
|
|
|
|
vid_cfg->vic = new_vic;
|
|
|
|
vid_cfg->avi_iframe.pixel_format = pinfo->out_format;
|
|
|
|
vid_cfg->avi_iframe.scan_info = hdmi_edid_get_sink_scaninfo(
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID],
|
|
|
|
hdmi_ctrl->vid_cfg.vic);
|
|
|
|
|
|
|
|
vid_cfg->avi_iframe.bar_info.end_of_top_bar = 0x0;
|
|
|
|
vid_cfg->avi_iframe.bar_info.start_of_bottom_bar =
|
|
|
|
vid_cfg->timing.active_v + 1;
|
|
|
|
vid_cfg->avi_iframe.bar_info.end_of_left_bar = 0;
|
|
|
|
vid_cfg->avi_iframe.bar_info.start_of_right_bar =
|
|
|
|
vid_cfg->timing.active_h + 1;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
vid_cfg->avi_iframe.act_fmt_info_present = true;
|
|
|
|
vid_cfg->avi_iframe.rgb_quantization_range = HDMI_QUANTIZATION_DEFAULT;
|
|
|
|
vid_cfg->avi_iframe.yuv_quantization_range = HDMI_QUANTIZATION_DEFAULT;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
vid_cfg->avi_iframe.scaling_info = HDMI_SCALING_NONE;
|
|
|
|
|
|
|
|
vid_cfg->avi_iframe.colorimetry_info = 0;
|
|
|
|
vid_cfg->avi_iframe.ext_colorimetry_info = 0;
|
|
|
|
|
|
|
|
vid_cfg->avi_iframe.pixel_rpt_factor = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If output format is yuv420, pixel clock rate should be half of the
|
|
|
|
* rate that is used for rgb888. MDP timing engine is programmed at half
|
|
|
|
* rate because the bits per pixel for yuv420 is only half that of
|
|
|
|
* rgb888
|
|
|
|
*/
|
|
|
|
if (pinfo->out_format == MDP_Y_CBCR_H2V2)
|
|
|
|
div = 1;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
hdmi_ctrl->pdata.power_data[HDMI_TX_CORE_PM].clk_config[0].rate =
|
2024-09-09 08:57:42 +00:00
|
|
|
(vid_cfg->timing.pixel_freq * 1000) >> div;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
hdmi_edid_set_video_resolution(
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID],
|
2024-09-09 08:57:42 +00:00
|
|
|
vid_cfg->vic, false);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
return res_changed;
|
2024-09-09 08:52:07 +00:00
|
|
|
} /* hdmi_tx_set_video_fmt */
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int hdmi_tx_video_setup(struct hdmi_tx_ctrl *hdmi_ctrl)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
|
|
|
u32 total_v = 0;
|
|
|
|
u32 total_h = 0;
|
|
|
|
u32 start_h = 0;
|
|
|
|
u32 end_h = 0;
|
|
|
|
u32 start_v = 0;
|
|
|
|
u32 end_v = 0;
|
2024-09-09 08:57:42 +00:00
|
|
|
u32 div = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
struct dss_io_data *io = NULL;
|
2024-09-09 08:57:42 +00:00
|
|
|
struct msm_hdmi_mode_timing_info *timing = NULL;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
timing = &hdmi_ctrl->vid_cfg.timing;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (timing == NULL) {
|
|
|
|
DEV_ERR("%s: video format not supported: %d\n", __func__,
|
|
|
|
hdmi_ctrl->vid_cfg.vic);
|
|
|
|
return -EPERM;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: Core io is not initialized\n", __func__);
|
|
|
|
return -EPERM;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
/*
|
|
|
|
* In case of YUV420 output, Horizontal timing parameters should be
|
|
|
|
* reduced by half
|
|
|
|
*/
|
|
|
|
if (hdmi_ctrl->vid_cfg.avi_iframe.pixel_format == MDP_Y_CBCR_H2V2)
|
|
|
|
div = 1;
|
|
|
|
|
|
|
|
total_h = (hdmi_tx_get_h_total(timing) >> div) - 1;
|
|
|
|
total_v = hdmi_tx_get_v_total(timing) - 1;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (((total_v << 16) & 0xE0000000) || (total_h & 0xFFFFE000)) {
|
|
|
|
DEV_ERR("%s: total v=%d or h=%d is larger than supported\n",
|
|
|
|
__func__, total_v, total_h);
|
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
DSS_REG_W(io, HDMI_TOTAL, (total_v << 16) | (total_h << 0));
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
start_h = (timing->back_porch_h >> div) +
|
|
|
|
(timing->pulse_width_h >> div);
|
|
|
|
end_h = (total_h + 1) - (timing->front_porch_h >> div);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (((end_h << 16) & 0xE0000000) || (start_h & 0xFFFFE000)) {
|
|
|
|
DEV_ERR("%s: end_h=%d or start_h=%d is larger than supported\n",
|
|
|
|
__func__, end_h, start_h);
|
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
DSS_REG_W(io, HDMI_ACTIVE_H, (end_h << 16) | (start_h << 0));
|
|
|
|
|
|
|
|
start_v = timing->back_porch_v + timing->pulse_width_v - 1;
|
|
|
|
end_v = total_v - timing->front_porch_v;
|
|
|
|
if (((end_v << 16) & 0xE0000000) || (start_v & 0xFFFFE000)) {
|
|
|
|
DEV_ERR("%s: end_v=%d or start_v=%d is larger than supported\n",
|
|
|
|
__func__, end_v, start_v);
|
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
DSS_REG_W(io, HDMI_ACTIVE_V, (end_v << 16) | (start_v << 0));
|
|
|
|
|
|
|
|
if (timing->interlaced) {
|
|
|
|
DSS_REG_W(io, HDMI_V_TOTAL_F2, (total_v + 1) << 0);
|
|
|
|
DSS_REG_W(io, HDMI_ACTIVE_V_F2,
|
|
|
|
((end_v + 1) << 16) | ((start_v + 1) << 0));
|
|
|
|
} else {
|
|
|
|
DSS_REG_W(io, HDMI_V_TOTAL_F2, 0);
|
|
|
|
DSS_REG_W(io, HDMI_ACTIVE_V_F2, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
DSS_REG_W(io, HDMI_FRAME_CTRL,
|
|
|
|
((timing->interlaced << 31) & 0x80000000) |
|
|
|
|
((timing->active_low_h << 29) & 0x20000000) |
|
|
|
|
((timing->active_low_v << 28) & 0x10000000));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
} /* hdmi_tx_video_setup */
|
|
|
|
|
|
|
|
static void hdmi_tx_set_avi_infoframe(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
int i;
|
|
|
|
u8 avi_iframe[AVI_MAX_DATA_BYTES] = {0};
|
2024-09-09 08:52:07 +00:00
|
|
|
u8 checksum;
|
2024-09-09 08:57:42 +00:00
|
|
|
u32 sum, reg_val;
|
2024-09-09 08:52:07 +00:00
|
|
|
struct dss_io_data *io = NULL;
|
2024-09-09 08:57:42 +00:00
|
|
|
struct hdmi_avi_infoframe_config *avi_info;
|
|
|
|
struct msm_hdmi_mode_timing_info *timing;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
2024-09-09 08:57:42 +00:00
|
|
|
avi_info = &hdmi_ctrl->vid_cfg.avi_iframe;
|
|
|
|
timing = &hdmi_ctrl->vid_cfg.timing;
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: Core io is not initialized\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/*
|
|
|
|
* BYTE - 1:
|
|
|
|
* 0:1 - Scan Information
|
|
|
|
* 2:3 - Bar Info
|
|
|
|
* 4 - Active Format Info present
|
|
|
|
* 5:6 - Pixel format type;
|
|
|
|
* 7 - Reserved;
|
|
|
|
*/
|
|
|
|
avi_iframe[0] = (avi_info->scan_info & 0x3) |
|
|
|
|
(avi_info->bar_info.vert_binfo_present ? BIT(2) : 0) |
|
|
|
|
(avi_info->bar_info.horz_binfo_present ? BIT(3) : 0) |
|
|
|
|
(avi_info->act_fmt_info_present ? BIT(4) : 0);
|
|
|
|
if (avi_info->pixel_format == MDP_Y_CBCR_H2V2)
|
|
|
|
avi_iframe[0] |= (0x3 << 5);
|
|
|
|
else if (avi_info->pixel_format == MDP_Y_CBCR_H2V1)
|
|
|
|
avi_iframe[0] |= (0x1 << 5);
|
|
|
|
else if (avi_info->pixel_format == MDP_Y_CBCR_H1V1)
|
|
|
|
avi_iframe[0] |= (0x2 << 5);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* BYTE - 2:
|
|
|
|
* 0:3 - Active format info
|
|
|
|
* 4:5 - Picture aspect ratio
|
|
|
|
* 6:7 - Colorimetry info
|
|
|
|
*/
|
|
|
|
avi_iframe[1] |= 0x08;
|
|
|
|
if (timing->ar == HDMI_RES_AR_4_3)
|
|
|
|
avi_iframe[1] |= (0x1 << 4);
|
|
|
|
else if (timing->ar == HDMI_RES_AR_16_9)
|
|
|
|
avi_iframe[1] |= (0x2 << 4);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
avi_iframe[1] |= (avi_info->colorimetry_info & 0x3) << 6;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/*
|
|
|
|
* BYTE - 3:
|
|
|
|
* 0:1 - Scaling info
|
|
|
|
* 2:3 - Quantization range
|
|
|
|
* 4:6 - Extended Colorimetry
|
|
|
|
* 7 - IT content
|
|
|
|
*/
|
|
|
|
avi_iframe[2] |= (avi_info->scaling_info & 0x3) |
|
|
|
|
((avi_info->rgb_quantization_range & 0x3) << 2) |
|
|
|
|
((avi_info->ext_colorimetry_info & 0x7) << 4) |
|
|
|
|
((avi_info->is_it_content ? 0x1 : 0x0) << 7);
|
|
|
|
/*
|
|
|
|
* BYTE - 4:
|
|
|
|
* 0:7 - VIC
|
|
|
|
*/
|
|
|
|
if (timing->video_format < HDMI_VFRMT_END)
|
|
|
|
avi_iframe[3] = timing->video_format;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
/*
|
|
|
|
* BYTE - 5:
|
|
|
|
* 0:3 - Pixel Repeat factor
|
|
|
|
* 4:5 - Content type
|
|
|
|
* 6:7 - YCC Quantization range
|
|
|
|
*/
|
|
|
|
avi_iframe[4] = (avi_info->pixel_rpt_factor & 0xF) |
|
|
|
|
((avi_info->content_type & 0x3) << 4) |
|
|
|
|
((avi_info->yuv_quantization_range & 0x3) << 6);
|
|
|
|
|
|
|
|
/* BYTE - 6,7: End of top bar */
|
|
|
|
avi_iframe[5] = avi_info->bar_info.end_of_top_bar & 0xFF;
|
|
|
|
avi_iframe[6] = ((avi_info->bar_info.end_of_top_bar & 0xFF00) >> 8);
|
|
|
|
|
|
|
|
/* BYTE - 8,9: Start of bottom bar */
|
|
|
|
avi_iframe[7] = avi_info->bar_info.start_of_bottom_bar & 0xFF;
|
|
|
|
avi_iframe[8] = ((avi_info->bar_info.start_of_bottom_bar & 0xFF00) >>
|
|
|
|
8);
|
|
|
|
|
|
|
|
/* BYTE - 10,11: Endof of left bar */
|
|
|
|
avi_iframe[9] = avi_info->bar_info.end_of_left_bar & 0xFF;
|
|
|
|
avi_iframe[10] = ((avi_info->bar_info.end_of_left_bar & 0xFF00) >> 8);
|
|
|
|
|
|
|
|
/* BYTE - 12,13: Start of right bar */
|
|
|
|
avi_iframe[11] = avi_info->bar_info.start_of_right_bar & 0xFF;
|
|
|
|
avi_iframe[12] = ((avi_info->bar_info.start_of_right_bar & 0xFF00) >>
|
|
|
|
8);
|
|
|
|
|
|
|
|
sum = IFRAME_PACKET_OFFSET + AVI_IFRAME_TYPE +
|
|
|
|
AVI_IFRAME_VERSION + AVI_MAX_DATA_BYTES;
|
|
|
|
|
|
|
|
for (i = 0; i < AVI_MAX_DATA_BYTES; i++)
|
2024-09-09 08:52:07 +00:00
|
|
|
sum += avi_iframe[i];
|
|
|
|
sum &= 0xFF;
|
|
|
|
sum = 256 - sum;
|
|
|
|
checksum = (u8) sum;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
reg_val = checksum |
|
|
|
|
LEFT_SHIFT_BYTE(avi_iframe[DATA_BYTE_1]) |
|
|
|
|
LEFT_SHIFT_WORD(avi_iframe[DATA_BYTE_2]) |
|
|
|
|
LEFT_SHIFT_24BITS(avi_iframe[DATA_BYTE_3]);
|
|
|
|
DSS_REG_W(io, HDMI_AVI_INFO0, reg_val);
|
|
|
|
|
|
|
|
reg_val = avi_iframe[DATA_BYTE_4] |
|
|
|
|
LEFT_SHIFT_BYTE(avi_iframe[DATA_BYTE_5]) |
|
|
|
|
LEFT_SHIFT_WORD(avi_iframe[DATA_BYTE_6]) |
|
|
|
|
LEFT_SHIFT_24BITS(avi_iframe[DATA_BYTE_7]);
|
|
|
|
DSS_REG_W(io, HDMI_AVI_INFO1, reg_val);
|
|
|
|
|
|
|
|
reg_val = avi_iframe[DATA_BYTE_8] |
|
|
|
|
LEFT_SHIFT_BYTE(avi_iframe[DATA_BYTE_9]) |
|
|
|
|
LEFT_SHIFT_WORD(avi_iframe[DATA_BYTE_10]) |
|
|
|
|
LEFT_SHIFT_24BITS(avi_iframe[DATA_BYTE_11]);
|
|
|
|
DSS_REG_W(io, HDMI_AVI_INFO2, reg_val);
|
|
|
|
|
|
|
|
reg_val = avi_iframe[DATA_BYTE_12] |
|
|
|
|
LEFT_SHIFT_BYTE(avi_iframe[DATA_BYTE_13]) |
|
|
|
|
LEFT_SHIFT_24BITS(AVI_IFRAME_VERSION);
|
|
|
|
DSS_REG_W(io, HDMI_AVI_INFO3, reg_val);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
/* AVI InfFrame enable (every frame) */
|
|
|
|
DSS_REG_W(io, HDMI_INFOFRAME_CTRL0,
|
|
|
|
DSS_REG_R(io, HDMI_INFOFRAME_CTRL0) | BIT(1) | BIT(0));
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
reg_val = DSS_REG_R(io, HDMI_INFOFRAME_CTRL1);
|
|
|
|
reg_val &= ~0x3F;
|
|
|
|
reg_val |= AVI_IFRAME_LINE_NUMBER;
|
|
|
|
DSS_REG_W(io, HDMI_INFOFRAME_CTRL1, reg_val);
|
2024-09-09 08:52:07 +00:00
|
|
|
} /* hdmi_tx_set_avi_infoframe */
|
|
|
|
|
|
|
|
static void hdmi_tx_set_vendor_specific_infoframe(
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
u8 vs_iframe[9]; /* two header + length + 6 data */
|
|
|
|
u32 sum, reg_val;
|
2024-09-09 08:57:42 +00:00
|
|
|
u32 hdmi_vic, hdmi_video_format, s3d_struct = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
struct dss_io_data *io = NULL;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: Core io is not initialized\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* HDMI Spec 1.4a Table 8-10 */
|
|
|
|
vs_iframe[0] = 0x81; /* type */
|
|
|
|
vs_iframe[1] = 0x1; /* version */
|
|
|
|
vs_iframe[2] = 0x8; /* length */
|
|
|
|
|
|
|
|
vs_iframe[3] = 0x0; /* PB0: checksum */
|
|
|
|
|
|
|
|
/* PB1..PB3: 24 Bit IEEE Registration Code 00_0C_03 */
|
|
|
|
vs_iframe[4] = 0x03;
|
|
|
|
vs_iframe[5] = 0x0C;
|
|
|
|
vs_iframe[6] = 0x00;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if ((hdmi_ctrl->s3d_mode != HDMI_S3D_NONE) &&
|
|
|
|
hdmi_edid_is_s3d_mode_supported(
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID],
|
|
|
|
hdmi_ctrl->vid_cfg.vic,
|
|
|
|
hdmi_ctrl->s3d_mode)) {
|
|
|
|
switch (hdmi_ctrl->s3d_mode) {
|
|
|
|
case HDMI_S3D_SIDE_BY_SIDE:
|
|
|
|
s3d_struct = 0x8;
|
|
|
|
break;
|
|
|
|
case HDMI_S3D_TOP_AND_BOTTOM:
|
|
|
|
s3d_struct = 0x6;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
s3d_struct = 0;
|
|
|
|
}
|
|
|
|
hdmi_video_format = 0x2;
|
|
|
|
hdmi_vic = 0;
|
|
|
|
/* PB5: 3D_Structure[7:4], Reserved[3:0] */
|
|
|
|
vs_iframe[8] = s3d_struct << 4;
|
|
|
|
} else {
|
|
|
|
hdmi_video_format = 0x1;
|
|
|
|
switch (hdmi_ctrl->vid_cfg.vic) {
|
|
|
|
case HDMI_EVFRMT_3840x2160p30_16_9:
|
|
|
|
hdmi_vic = 0x1;
|
|
|
|
break;
|
|
|
|
case HDMI_EVFRMT_3840x2160p25_16_9:
|
|
|
|
hdmi_vic = 0x2;
|
|
|
|
break;
|
|
|
|
case HDMI_EVFRMT_3840x2160p24_16_9:
|
|
|
|
hdmi_vic = 0x3;
|
|
|
|
break;
|
|
|
|
case HDMI_EVFRMT_4096x2160p24_16_9:
|
|
|
|
hdmi_vic = 0x4;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
hdmi_video_format = 0x0;
|
|
|
|
hdmi_vic = 0x0;
|
|
|
|
}
|
|
|
|
/* PB5: HDMI_VIC */
|
|
|
|
vs_iframe[8] = hdmi_vic;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
/* PB4: HDMI Video Format[7:5], Reserved[4:0] */
|
|
|
|
vs_iframe[7] = (hdmi_video_format << 5) & 0xE0;
|
|
|
|
|
|
|
|
/* compute checksum */
|
|
|
|
sum = 0;
|
|
|
|
for (i = 0; i < 9; i++)
|
|
|
|
sum += vs_iframe[i];
|
|
|
|
|
|
|
|
sum &= 0xFF;
|
|
|
|
sum = 256 - sum;
|
|
|
|
vs_iframe[3] = (u8)sum;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
reg_val = (s3d_struct << 24) | (hdmi_vic << 16) | (vs_iframe[3] << 8) |
|
2024-09-09 08:52:07 +00:00
|
|
|
(hdmi_video_format << 5) | vs_iframe[2];
|
|
|
|
DSS_REG_W(io, HDMI_VENSPEC_INFO0, reg_val);
|
|
|
|
|
|
|
|
/* vendor specific info-frame enable (every frame) */
|
|
|
|
DSS_REG_W(io, HDMI_INFOFRAME_CTRL0,
|
|
|
|
DSS_REG_R(io, HDMI_INFOFRAME_CTRL0) | BIT(13) | BIT(12));
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
reg_val = DSS_REG_R(io, HDMI_INFOFRAME_CTRL1);
|
|
|
|
reg_val &= ~0x3F000000;
|
|
|
|
reg_val |= (VENDOR_IFRAME_LINE_NUMBER << 24);
|
|
|
|
DSS_REG_W(io, HDMI_INFOFRAME_CTRL1, reg_val);
|
2024-09-09 08:52:07 +00:00
|
|
|
} /* hdmi_tx_set_vendor_specific_infoframe */
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
void hdmi_tx_set_spd_infoframe(struct hdmi_tx_ctrl *hdmi_ctrl)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
|
|
|
u32 packet_header = 0;
|
|
|
|
u32 check_sum = 0;
|
|
|
|
u32 packet_payload = 0;
|
|
|
|
u32 packet_control = 0;
|
|
|
|
|
|
|
|
u8 *vendor_name = NULL;
|
|
|
|
u8 *product_description = NULL;
|
|
|
|
struct dss_io_data *io = NULL;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: Core io is not initialized\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
vendor_name = hdmi_ctrl->spd_vendor_name;
|
|
|
|
product_description = hdmi_ctrl->spd_product_description;
|
|
|
|
|
|
|
|
/* Setup Packet header and payload */
|
|
|
|
/*
|
|
|
|
* 0x83 InfoFrame Type Code
|
|
|
|
* 0x01 InfoFrame Version Number
|
|
|
|
* 0x19 Length of Source Product Description InfoFrame
|
|
|
|
*/
|
|
|
|
packet_header = 0x83 | (0x01 << 8) | (0x19 << 16);
|
|
|
|
DSS_REG_W(io, HDMI_GENERIC1_HDR, packet_header);
|
|
|
|
check_sum += IFRAME_CHECKSUM_32(packet_header);
|
|
|
|
|
|
|
|
packet_payload = (vendor_name[3] & 0x7f)
|
|
|
|
| ((vendor_name[4] & 0x7f) << 8)
|
|
|
|
| ((vendor_name[5] & 0x7f) << 16)
|
|
|
|
| ((vendor_name[6] & 0x7f) << 24);
|
|
|
|
DSS_REG_W(io, HDMI_GENERIC1_1, packet_payload);
|
|
|
|
check_sum += IFRAME_CHECKSUM_32(packet_payload);
|
|
|
|
|
|
|
|
/* Product Description (7-bit ASCII code) */
|
|
|
|
packet_payload = (vendor_name[7] & 0x7f)
|
|
|
|
| ((product_description[0] & 0x7f) << 8)
|
|
|
|
| ((product_description[1] & 0x7f) << 16)
|
|
|
|
| ((product_description[2] & 0x7f) << 24);
|
|
|
|
DSS_REG_W(io, HDMI_GENERIC1_2, packet_payload);
|
|
|
|
check_sum += IFRAME_CHECKSUM_32(packet_payload);
|
|
|
|
|
|
|
|
packet_payload = (product_description[3] & 0x7f)
|
|
|
|
| ((product_description[4] & 0x7f) << 8)
|
|
|
|
| ((product_description[5] & 0x7f) << 16)
|
|
|
|
| ((product_description[6] & 0x7f) << 24);
|
|
|
|
DSS_REG_W(io, HDMI_GENERIC1_3, packet_payload);
|
|
|
|
check_sum += IFRAME_CHECKSUM_32(packet_payload);
|
|
|
|
|
|
|
|
packet_payload = (product_description[7] & 0x7f)
|
|
|
|
| ((product_description[8] & 0x7f) << 8)
|
|
|
|
| ((product_description[9] & 0x7f) << 16)
|
|
|
|
| ((product_description[10] & 0x7f) << 24);
|
|
|
|
DSS_REG_W(io, HDMI_GENERIC1_4, packet_payload);
|
|
|
|
check_sum += IFRAME_CHECKSUM_32(packet_payload);
|
|
|
|
|
|
|
|
packet_payload = (product_description[11] & 0x7f)
|
|
|
|
| ((product_description[12] & 0x7f) << 8)
|
|
|
|
| ((product_description[13] & 0x7f) << 16)
|
|
|
|
| ((product_description[14] & 0x7f) << 24);
|
|
|
|
DSS_REG_W(io, HDMI_GENERIC1_5, packet_payload);
|
|
|
|
check_sum += IFRAME_CHECKSUM_32(packet_payload);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Source Device Information
|
|
|
|
* 00h unknown
|
|
|
|
* 01h Digital STB
|
|
|
|
* 02h DVD
|
|
|
|
* 03h D-VHS
|
|
|
|
* 04h HDD Video
|
|
|
|
* 05h DVC
|
|
|
|
* 06h DSC
|
|
|
|
* 07h Video CD
|
|
|
|
* 08h Game
|
|
|
|
* 09h PC general
|
|
|
|
*/
|
|
|
|
packet_payload = (product_description[15] & 0x7f) | 0x00 << 8;
|
|
|
|
DSS_REG_W(io, HDMI_GENERIC1_6, packet_payload);
|
|
|
|
check_sum += IFRAME_CHECKSUM_32(packet_payload);
|
|
|
|
|
|
|
|
/* Vendor Name (7bit ASCII code) */
|
|
|
|
packet_payload = ((vendor_name[0] & 0x7f) << 8)
|
|
|
|
| ((vendor_name[1] & 0x7f) << 16)
|
|
|
|
| ((vendor_name[2] & 0x7f) << 24);
|
|
|
|
check_sum += IFRAME_CHECKSUM_32(packet_payload);
|
|
|
|
packet_payload |= ((0x100 - (0xff & check_sum)) & 0xff);
|
|
|
|
DSS_REG_W(io, HDMI_GENERIC1_0, packet_payload);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* GENERIC1_LINE | GENERIC1_CONT | GENERIC1_SEND
|
|
|
|
* Setup HDMI TX generic packet control
|
|
|
|
* Enable this packet to transmit every frame
|
|
|
|
* Enable HDMI TX engine to transmit Generic packet 1
|
|
|
|
*/
|
|
|
|
packet_control = DSS_REG_R_ND(io, HDMI_GEN_PKT_CTRL);
|
|
|
|
packet_control |= ((0x1 << 24) | (1 << 5) | (1 << 4));
|
|
|
|
DSS_REG_W(io, HDMI_GEN_PKT_CTRL, packet_control);
|
|
|
|
} /* hdmi_tx_set_spd_infoframe */
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static void hdmi_tx_set_mode(struct hdmi_tx_ctrl *hdmi_ctrl, u32 power_on)
|
|
|
|
{
|
|
|
|
struct dss_io_data *io = NULL;
|
|
|
|
/* Defaults: Disable block, HDMI mode */
|
|
|
|
u32 reg_val = BIT(1);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: Core io is not initialized\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_lock(&hdmi_ctrl->mutex);
|
|
|
|
if (power_on) {
|
|
|
|
/* Enable the block */
|
|
|
|
reg_val |= BIT(0);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* HDMI Encryption, if HDCP is enabled
|
|
|
|
* The ENC_REQUIRED bit is only available on HDMI Tx major
|
|
|
|
* version less than 4. From 4 onwards, this bit is controlled
|
|
|
|
* by TZ
|
|
|
|
*/
|
|
|
|
if (hdmi_ctrl->hdmi_tx_ver < 4 &&
|
|
|
|
hdmi_tx_is_hdcp_enabled(hdmi_ctrl) &&
|
|
|
|
!hdmi_ctrl->pdata.primary)
|
|
|
|
reg_val |= BIT(2);
|
|
|
|
|
|
|
|
/* Set transmission mode to DVI based in EDID info */
|
|
|
|
if (hdmi_edid_get_sink_mode(
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID]) == 0)
|
|
|
|
reg_val &= ~BIT(1); /* DVI mode */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Use DATAPATH_MODE as 1 always, the new mode that also
|
|
|
|
* supports scrambler and HDCP 2.2. The legacy mode should no
|
|
|
|
* longer be used
|
|
|
|
*/
|
|
|
|
reg_val |= BIT(31);
|
|
|
|
}
|
|
|
|
|
|
|
|
DSS_REG_W(io, HDMI_CTRL, reg_val);
|
|
|
|
mutex_unlock(&hdmi_ctrl->mutex);
|
|
|
|
|
|
|
|
DEV_DBG("HDMI Core: %s, HDMI_CTRL=0x%08x\n",
|
|
|
|
power_on ? "Enable" : "Disable", reg_val);
|
|
|
|
} /* hdmi_tx_set_mode */
|
|
|
|
|
|
|
|
static int hdmi_tx_pinctrl_set_state(struct hdmi_tx_ctrl *hdmi_ctrl,
|
|
|
|
enum hdmi_tx_power_module_type module, bool active)
|
|
|
|
{
|
|
|
|
struct pinctrl_state *pin_state = NULL;
|
|
|
|
int rc = -EFAULT;
|
|
|
|
struct dss_module_power *power_data = NULL;
|
|
|
|
u64 cur_pin_states;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IS_ERR_OR_NULL(hdmi_ctrl->pin_res.pinctrl))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
power_data = &hdmi_ctrl->pdata.power_data[module];
|
|
|
|
|
|
|
|
cur_pin_states = active ? (hdmi_ctrl->pdata.pin_states | BIT(module))
|
|
|
|
: (hdmi_ctrl->pdata.pin_states & ~BIT(module));
|
|
|
|
|
|
|
|
if (cur_pin_states & BIT(HDMI_TX_HPD_PM)) {
|
|
|
|
if (cur_pin_states & BIT(HDMI_TX_DDC_PM)) {
|
|
|
|
if (cur_pin_states & BIT(HDMI_TX_CEC_PM))
|
|
|
|
pin_state = hdmi_ctrl->pin_res.state_active;
|
|
|
|
else
|
|
|
|
pin_state =
|
|
|
|
hdmi_ctrl->pin_res.state_ddc_active;
|
|
|
|
} else if (cur_pin_states & BIT(HDMI_TX_CEC_PM)) {
|
|
|
|
pin_state = hdmi_ctrl->pin_res.state_cec_active;
|
|
|
|
} else {
|
|
|
|
pin_state = hdmi_ctrl->pin_res.state_hpd_active;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pin_state = hdmi_ctrl->pin_res.state_suspend;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!IS_ERR_OR_NULL(pin_state)) {
|
|
|
|
rc = pinctrl_select_state(hdmi_ctrl->pin_res.pinctrl,
|
|
|
|
pin_state);
|
|
|
|
if (rc)
|
|
|
|
pr_err("%s: cannot set pins\n", __func__);
|
|
|
|
else
|
|
|
|
hdmi_ctrl->pdata.pin_states = cur_pin_states;
|
|
|
|
} else {
|
|
|
|
pr_err("%s: pinstate not found\n", __func__);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hdmi_tx_pinctrl_init(struct platform_device *pdev)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl = platform_get_drvdata(pdev);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
return -ENODEV;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
hdmi_ctrl->pin_res.pinctrl = devm_pinctrl_get(&pdev->dev);
|
|
|
|
if (IS_ERR_OR_NULL(hdmi_ctrl->pin_res.pinctrl)) {
|
|
|
|
pr_err("%s: failed to get pinctrl\n", __func__);
|
|
|
|
return PTR_ERR(hdmi_ctrl->pin_res.pinctrl);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->pin_res.state_active =
|
|
|
|
pinctrl_lookup_state(hdmi_ctrl->pin_res.pinctrl, "hdmi_active");
|
|
|
|
if (IS_ERR_OR_NULL(hdmi_ctrl->pin_res.state_active))
|
|
|
|
pr_debug("%s: cannot get active pinstate\n", __func__);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->pin_res.state_hpd_active =
|
|
|
|
pinctrl_lookup_state(hdmi_ctrl->pin_res.pinctrl,
|
|
|
|
"hdmi_hpd_active");
|
|
|
|
if (IS_ERR_OR_NULL(hdmi_ctrl->pin_res.state_hpd_active))
|
|
|
|
pr_debug("%s: cannot get hpd active pinstate\n", __func__);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->pin_res.state_cec_active =
|
|
|
|
pinctrl_lookup_state(hdmi_ctrl->pin_res.pinctrl,
|
|
|
|
"hdmi_cec_active");
|
|
|
|
if (IS_ERR_OR_NULL(hdmi_ctrl->pin_res.state_cec_active))
|
|
|
|
pr_debug("%s: cannot get cec active pinstate\n", __func__);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->pin_res.state_ddc_active =
|
|
|
|
pinctrl_lookup_state(hdmi_ctrl->pin_res.pinctrl,
|
|
|
|
"hdmi_ddc_active");
|
|
|
|
if (IS_ERR_OR_NULL(hdmi_ctrl->pin_res.state_ddc_active))
|
|
|
|
pr_debug("%s: cannot get ddc active pinstate\n", __func__);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->pin_res.state_suspend =
|
|
|
|
pinctrl_lookup_state(hdmi_ctrl->pin_res.pinctrl, "hdmi_sleep");
|
|
|
|
if (IS_ERR_OR_NULL(hdmi_ctrl->pin_res.state_suspend))
|
|
|
|
pr_debug("%s: cannot get sleep pinstate\n", __func__);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
static int hdmi_tx_config_power(struct hdmi_tx_ctrl *hdmi_ctrl,
|
|
|
|
enum hdmi_tx_power_module_type module, int config)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
struct dss_module_power *power_data = NULL;
|
2024-09-09 08:57:42 +00:00
|
|
|
char name[MAX_CLIENT_NAME_LEN];
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!hdmi_ctrl || module >= HDMI_TX_MAX_PM) {
|
|
|
|
DEV_ERR("%s: Error: invalid input\n", __func__);
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
power_data = &hdmi_ctrl->pdata.power_data[module];
|
|
|
|
if (!power_data) {
|
|
|
|
DEV_ERR("%s: Error: invalid power data\n", __func__);
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config) {
|
|
|
|
rc = msm_dss_config_vreg(&hdmi_ctrl->pdev->dev,
|
|
|
|
power_data->vreg_config, power_data->num_vreg, 1);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: Failed to config %s vreg. Err=%d\n",
|
|
|
|
__func__, hdmi_tx_pm_name(module), rc);
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
snprintf(name, MAX_CLIENT_NAME_LEN, "hdmi:%u", module);
|
|
|
|
hdmi_ctrl->pdata.reg_bus_clt[module] =
|
|
|
|
mdss_reg_bus_vote_client_create(name);
|
|
|
|
if (IS_ERR_OR_NULL(hdmi_ctrl->pdata.reg_bus_clt[module])) {
|
|
|
|
pr_err("reg bus client create failed\n");
|
|
|
|
msm_dss_config_vreg(&hdmi_ctrl->pdev->dev,
|
|
|
|
power_data->vreg_config, power_data->num_vreg, 0);
|
|
|
|
rc = PTR_ERR(hdmi_ctrl->pdata.reg_bus_clt[module]);
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
rc = msm_dss_get_clk(&hdmi_ctrl->pdev->dev,
|
|
|
|
power_data->clk_config, power_data->num_clk);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: Failed to get %s clk. Err=%d\n",
|
|
|
|
__func__, hdmi_tx_pm_name(module), rc);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
mdss_reg_bus_vote_client_destroy(
|
|
|
|
hdmi_ctrl->pdata.reg_bus_clt[module]);
|
|
|
|
hdmi_ctrl->pdata.reg_bus_clt[module] = NULL;
|
2024-09-09 08:52:07 +00:00
|
|
|
msm_dss_config_vreg(&hdmi_ctrl->pdev->dev,
|
|
|
|
power_data->vreg_config, power_data->num_vreg, 0);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
msm_dss_put_clk(power_data->clk_config, power_data->num_clk);
|
2024-09-09 08:57:42 +00:00
|
|
|
mdss_reg_bus_vote_client_destroy(
|
|
|
|
hdmi_ctrl->pdata.reg_bus_clt[module]);
|
|
|
|
hdmi_ctrl->pdata.reg_bus_clt[module] = NULL;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
rc = msm_dss_config_vreg(&hdmi_ctrl->pdev->dev,
|
|
|
|
power_data->vreg_config, power_data->num_vreg, 0);
|
|
|
|
if (rc)
|
|
|
|
DEV_ERR("%s: Fail to deconfig %s vreg. Err=%d\n",
|
|
|
|
__func__, hdmi_tx_pm_name(module), rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
exit:
|
|
|
|
return rc;
|
|
|
|
} /* hdmi_tx_config_power */
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int hdmi_tx_check_clk_state(struct hdmi_tx_ctrl *hdmi_ctrl,
|
|
|
|
enum hdmi_tx_power_module_type module)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
int rc = 0;
|
|
|
|
struct dss_module_power *pd = NULL;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl || module >= HDMI_TX_MAX_PM) {
|
|
|
|
DEV_ERR("%s: Error: invalid input\n", __func__);
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
pd = &hdmi_ctrl->pdata.power_data[module];
|
|
|
|
if (!pd) {
|
|
|
|
DEV_ERR("%s: Error: invalid power data\n", __func__);
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < pd->num_clk; i++) {
|
|
|
|
struct clk *clk = pd->clk_config[i].clk;
|
|
|
|
|
|
|
|
if (clk) {
|
|
|
|
u32 rate = clk_get_rate(clk);
|
|
|
|
|
|
|
|
DEV_DBG("%s: clk %s: rate %d\n", __func__,
|
|
|
|
pd->clk_config[i].clk_name, rate);
|
|
|
|
|
|
|
|
if (!rate) {
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
DEV_ERR("%s: clk %s: not configured\n", __func__,
|
|
|
|
pd->clk_config[i].clk_name);
|
|
|
|
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
error:
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
static int hdmi_tx_enable_power(struct hdmi_tx_ctrl *hdmi_ctrl,
|
|
|
|
enum hdmi_tx_power_module_type module, int enable)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
struct dss_module_power *power_data = NULL;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl || module >= HDMI_TX_MAX_PM) {
|
|
|
|
DEV_ERR("%s: Error: invalid input\n", __func__);
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
power_data = &hdmi_ctrl->pdata.power_data[module];
|
|
|
|
if (!power_data) {
|
|
|
|
DEV_ERR("%s: Error: invalid power data\n", __func__);
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (enable) {
|
2024-09-09 08:57:42 +00:00
|
|
|
if (hdmi_ctrl->panel_data.panel_info.cont_splash_enabled) {
|
|
|
|
DEV_DBG("%s: %s already eanbled by splash\n",
|
|
|
|
__func__, hdmi_pm_name(module));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
rc = msm_dss_enable_vreg(power_data->vreg_config,
|
|
|
|
power_data->num_vreg, 1);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: Failed to enable %s vreg. Error=%d\n",
|
|
|
|
__func__, hdmi_tx_pm_name(module), rc);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
rc = hdmi_tx_pinctrl_set_state(hdmi_ctrl, module, enable);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: Failed to set %s pinctrl state\n",
|
|
|
|
__func__, hdmi_tx_pm_name(module));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
rc = msm_dss_enable_gpio(power_data->gpio_config,
|
|
|
|
power_data->num_gpio, 1);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: Failed to enable %s gpio. Error=%d\n",
|
|
|
|
__func__, hdmi_tx_pm_name(module), rc);
|
|
|
|
goto disable_vreg;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
mdss_update_reg_bus_vote(hdmi_ctrl->pdata.reg_bus_clt[module],
|
|
|
|
VOTE_INDEX_19_MHZ);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
rc = msm_dss_clk_set_rate(power_data->clk_config,
|
|
|
|
power_data->num_clk);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: failed to set clks rate for %s. err=%d\n",
|
|
|
|
__func__, hdmi_tx_pm_name(module), rc);
|
|
|
|
goto disable_gpio;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = msm_dss_enable_clk(power_data->clk_config,
|
|
|
|
power_data->num_clk, 1);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: Failed to enable clks for %s. Error=%d\n",
|
|
|
|
__func__, hdmi_tx_pm_name(module), rc);
|
|
|
|
goto disable_gpio;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
msm_dss_enable_clk(power_data->clk_config,
|
|
|
|
power_data->num_clk, 0);
|
2024-09-09 08:57:42 +00:00
|
|
|
mdss_update_reg_bus_vote(hdmi_ctrl->pdata.reg_bus_clt[module],
|
|
|
|
VOTE_INDEX_DISABLE);
|
2024-09-09 08:52:07 +00:00
|
|
|
msm_dss_enable_gpio(power_data->gpio_config,
|
|
|
|
power_data->num_gpio, 0);
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_tx_pinctrl_set_state(hdmi_ctrl, module, 0);
|
2024-09-09 08:52:07 +00:00
|
|
|
msm_dss_enable_vreg(power_data->vreg_config,
|
|
|
|
power_data->num_vreg, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
disable_gpio:
|
2024-09-09 08:57:42 +00:00
|
|
|
mdss_update_reg_bus_vote(hdmi_ctrl->pdata.reg_bus_clt[module],
|
|
|
|
VOTE_INDEX_DISABLE);
|
2024-09-09 08:52:07 +00:00
|
|
|
msm_dss_enable_gpio(power_data->gpio_config, power_data->num_gpio, 0);
|
|
|
|
disable_vreg:
|
|
|
|
msm_dss_enable_vreg(power_data->vreg_config, power_data->num_vreg, 0);
|
|
|
|
error:
|
|
|
|
return rc;
|
|
|
|
} /* hdmi_tx_enable_power */
|
|
|
|
|
|
|
|
static void hdmi_tx_core_off(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_CEC_PM, 0);
|
|
|
|
hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_CORE_PM, 0);
|
|
|
|
} /* hdmi_tx_core_off */
|
|
|
|
|
|
|
|
static int hdmi_tx_core_on(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_CORE_PM, 1);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: core hdmi_msm_enable_power failed rc = %d\n",
|
|
|
|
__func__, rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
rc = hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_CEC_PM, 1);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: cec hdmi_msm_enable_power failed rc = %d\n",
|
|
|
|
__func__, rc);
|
|
|
|
goto disable_core_power;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
disable_core_power:
|
|
|
|
hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_CORE_PM, 0);
|
|
|
|
return rc;
|
|
|
|
} /* hdmi_tx_core_on */
|
|
|
|
|
|
|
|
static void hdmi_tx_phy_reset(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
unsigned int phy_reset_polarity = 0x0;
|
|
|
|
unsigned int pll_reset_polarity = 0x0;
|
|
|
|
unsigned int val;
|
|
|
|
struct dss_io_data *io = NULL;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: core io not inititalized\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
val = DSS_REG_R_ND(io, HDMI_PHY_CTRL);
|
|
|
|
|
|
|
|
phy_reset_polarity = val >> 3 & 0x1;
|
|
|
|
pll_reset_polarity = val >> 1 & 0x1;
|
|
|
|
|
|
|
|
if (phy_reset_polarity == 0)
|
|
|
|
DSS_REG_W_ND(io, HDMI_PHY_CTRL, val | SW_RESET);
|
|
|
|
else
|
|
|
|
DSS_REG_W_ND(io, HDMI_PHY_CTRL, val & (~SW_RESET));
|
|
|
|
|
|
|
|
if (pll_reset_polarity == 0)
|
|
|
|
DSS_REG_W_ND(io, HDMI_PHY_CTRL, val | SW_RESET_PLL);
|
|
|
|
else
|
|
|
|
DSS_REG_W_ND(io, HDMI_PHY_CTRL, val & (~SW_RESET_PLL));
|
|
|
|
|
|
|
|
if (phy_reset_polarity == 0)
|
|
|
|
DSS_REG_W_ND(io, HDMI_PHY_CTRL, val & (~SW_RESET));
|
|
|
|
else
|
|
|
|
DSS_REG_W_ND(io, HDMI_PHY_CTRL, val | SW_RESET);
|
|
|
|
|
|
|
|
if (pll_reset_polarity == 0)
|
|
|
|
DSS_REG_W_ND(io, HDMI_PHY_CTRL, val & (~SW_RESET_PLL));
|
|
|
|
else
|
|
|
|
DSS_REG_W_ND(io, HDMI_PHY_CTRL, val | SW_RESET_PLL);
|
|
|
|
} /* hdmi_tx_phy_reset */
|
|
|
|
|
|
|
|
static int hdmi_tx_audio_acr_setup(struct hdmi_tx_ctrl *hdmi_ctrl,
|
2024-09-09 08:57:42 +00:00
|
|
|
bool enabled)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
|
|
|
/* Read first before writing */
|
|
|
|
u32 acr_pck_ctrl_reg;
|
2024-09-09 08:57:42 +00:00
|
|
|
u32 sample_rate_hz;
|
|
|
|
u32 pixel_freq;
|
2024-09-09 08:52:07 +00:00
|
|
|
struct dss_io_data *io = NULL;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: Invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
sample_rate_hz = hdmi_ctrl->audio_data.sample_rate_hz;
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: core io not inititalized\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
acr_pck_ctrl_reg = DSS_REG_R(io, HDMI_ACR_PKT_CTRL);
|
|
|
|
|
|
|
|
if (enabled) {
|
2024-09-09 08:57:42 +00:00
|
|
|
struct msm_hdmi_mode_timing_info *timing =
|
|
|
|
&hdmi_ctrl->vid_cfg.timing;
|
2024-09-09 08:52:07 +00:00
|
|
|
const struct hdmi_tx_audio_acr_arry *audio_acr =
|
|
|
|
&hdmi_tx_audio_acr_lut[0];
|
|
|
|
const int lut_size = sizeof(hdmi_tx_audio_acr_lut)
|
|
|
|
/ sizeof(*hdmi_tx_audio_acr_lut);
|
|
|
|
u32 i, n, cts, layout, multiplier, aud_pck_ctrl_2_reg;
|
|
|
|
|
|
|
|
if (timing == NULL) {
|
|
|
|
DEV_WARN("%s: video format %d not supported\n",
|
2024-09-09 08:57:42 +00:00
|
|
|
__func__, hdmi_ctrl->vid_cfg.vic);
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EPERM;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
pixel_freq = hdmi_tx_setup_tmds_clk_rate(hdmi_ctrl);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
for (i = 0; i < lut_size;
|
|
|
|
audio_acr = &hdmi_tx_audio_acr_lut[++i]) {
|
2024-09-09 08:57:42 +00:00
|
|
|
if (audio_acr->pclk == pixel_freq)
|
2024-09-09 08:52:07 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (i >= lut_size) {
|
|
|
|
DEV_WARN("%s: pixel clk %d not supported\n", __func__,
|
2024-09-09 08:57:42 +00:00
|
|
|
pixel_freq);
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
n = audio_acr->lut[sample_rate_hz].n;
|
|
|
|
cts = audio_acr->lut[sample_rate_hz].cts;
|
|
|
|
layout = (MSM_HDMI_AUDIO_CHANNEL_2 ==
|
|
|
|
hdmi_ctrl->audio_data.num_of_channels) ? 0 : 1;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (
|
2024-09-09 08:57:42 +00:00
|
|
|
(AUDIO_SAMPLE_RATE_192KHZ == sample_rate_hz) ||
|
|
|
|
(AUDIO_SAMPLE_RATE_176_4KHZ == sample_rate_hz)) {
|
2024-09-09 08:52:07 +00:00
|
|
|
multiplier = 4;
|
|
|
|
n >>= 2; /* divide N by 4 and use multiplier */
|
|
|
|
} else if (
|
2024-09-09 08:57:42 +00:00
|
|
|
(AUDIO_SAMPLE_RATE_96KHZ == sample_rate_hz) ||
|
|
|
|
(AUDIO_SAMPLE_RATE_88_2KHZ == sample_rate_hz)) {
|
2024-09-09 08:52:07 +00:00
|
|
|
multiplier = 2;
|
|
|
|
n >>= 1; /* divide N by 2 and use multiplier */
|
|
|
|
} else {
|
|
|
|
multiplier = 1;
|
|
|
|
}
|
|
|
|
DEV_DBG("%s: n=%u, cts=%u, layout=%u\n", __func__, n, cts,
|
|
|
|
layout);
|
|
|
|
|
|
|
|
/* AUDIO_PRIORITY | SOURCE */
|
|
|
|
acr_pck_ctrl_reg |= 0x80000100;
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
/* Reset multiplier bits */
|
|
|
|
acr_pck_ctrl_reg &= ~(7 << 16);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
/* N_MULTIPLE(multiplier) */
|
|
|
|
acr_pck_ctrl_reg |= (multiplier & 7) << 16;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if ((AUDIO_SAMPLE_RATE_48KHZ == sample_rate_hz) ||
|
|
|
|
(AUDIO_SAMPLE_RATE_96KHZ == sample_rate_hz) ||
|
|
|
|
(AUDIO_SAMPLE_RATE_192KHZ == sample_rate_hz)) {
|
2024-09-09 08:52:07 +00:00
|
|
|
/* SELECT(3) */
|
|
|
|
acr_pck_ctrl_reg |= 3 << 4;
|
|
|
|
/* CTS_48 */
|
|
|
|
cts <<= 12;
|
|
|
|
|
|
|
|
/* CTS: need to determine how many fractional bits */
|
|
|
|
DSS_REG_W(io, HDMI_ACR_48_0, cts);
|
|
|
|
/* N */
|
|
|
|
DSS_REG_W(io, HDMI_ACR_48_1, n);
|
|
|
|
} else if (
|
2024-09-09 08:57:42 +00:00
|
|
|
(AUDIO_SAMPLE_RATE_44_1KHZ == sample_rate_hz) ||
|
|
|
|
(AUDIO_SAMPLE_RATE_88_2KHZ == sample_rate_hz) ||
|
|
|
|
(AUDIO_SAMPLE_RATE_176_4KHZ == sample_rate_hz)) {
|
2024-09-09 08:52:07 +00:00
|
|
|
/* SELECT(2) */
|
|
|
|
acr_pck_ctrl_reg |= 2 << 4;
|
|
|
|
/* CTS_44 */
|
|
|
|
cts <<= 12;
|
|
|
|
|
|
|
|
/* CTS: need to determine how many fractional bits */
|
|
|
|
DSS_REG_W(io, HDMI_ACR_44_0, cts);
|
|
|
|
/* N */
|
|
|
|
DSS_REG_W(io, HDMI_ACR_44_1, n);
|
|
|
|
} else { /* default to 32k */
|
|
|
|
/* SELECT(1) */
|
|
|
|
acr_pck_ctrl_reg |= 1 << 4;
|
|
|
|
/* CTS_32 */
|
|
|
|
cts <<= 12;
|
|
|
|
|
|
|
|
/* CTS: need to determine how many fractional bits */
|
|
|
|
DSS_REG_W(io, HDMI_ACR_32_0, cts);
|
|
|
|
/* N */
|
|
|
|
DSS_REG_W(io, HDMI_ACR_32_1, n);
|
|
|
|
}
|
|
|
|
/* Payload layout depends on number of audio channels */
|
|
|
|
/* LAYOUT_SEL(layout) */
|
|
|
|
aud_pck_ctrl_2_reg = 1 | (layout << 1);
|
|
|
|
/* override | layout */
|
|
|
|
DSS_REG_W(io, HDMI_AUDIO_PKT_CTRL2, aud_pck_ctrl_2_reg);
|
|
|
|
|
|
|
|
/* SEND | CONT */
|
|
|
|
acr_pck_ctrl_reg |= 0x00000003;
|
|
|
|
} else {
|
|
|
|
/* ~(SEND | CONT) */
|
|
|
|
acr_pck_ctrl_reg &= ~0x00000003;
|
|
|
|
}
|
|
|
|
DSS_REG_W(io, HDMI_ACR_PKT_CTRL, acr_pck_ctrl_reg);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
} /* hdmi_tx_audio_acr_setup */
|
|
|
|
|
|
|
|
static int hdmi_tx_audio_iframe_setup(struct hdmi_tx_ctrl *hdmi_ctrl,
|
2024-09-09 08:57:42 +00:00
|
|
|
bool enabled)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
|
|
|
struct dss_io_data *io = NULL;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
u32 hdmi_debug_reg = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
u32 channel_count = 1; /* Def to 2 channels -> Table 17 in CEA-D */
|
2024-09-09 08:57:42 +00:00
|
|
|
u32 num_of_channels;
|
|
|
|
u32 channel_allocation;
|
|
|
|
u32 level_shift;
|
|
|
|
u32 down_mix;
|
2024-09-09 08:52:07 +00:00
|
|
|
u32 check_sum, audio_info_0_reg, audio_info_1_reg;
|
|
|
|
u32 audio_info_ctrl_reg;
|
|
|
|
u32 aud_pck_ctrl_2_reg;
|
|
|
|
u32 layout;
|
2024-09-09 08:57:42 +00:00
|
|
|
u32 sample_present;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
num_of_channels = hdmi_ctrl->audio_data.num_of_channels;
|
|
|
|
channel_allocation = hdmi_ctrl->audio_data.channel_allocation;
|
|
|
|
level_shift = hdmi_ctrl->audio_data.level_shift;
|
|
|
|
down_mix = hdmi_ctrl->audio_data.down_mix;
|
|
|
|
sample_present = hdmi_ctrl->audio_data.sample_present;
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: core io not inititalized\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
layout = (MSM_HDMI_AUDIO_CHANNEL_2 == num_of_channels) ? 0 : 1;
|
|
|
|
aud_pck_ctrl_2_reg = 1 | (layout << 1);
|
|
|
|
DSS_REG_W(io, HDMI_AUDIO_PKT_CTRL2, aud_pck_ctrl_2_reg);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Please see table 20 Audio InfoFrame in HDMI spec
|
|
|
|
* FL = front left
|
|
|
|
* FC = front Center
|
|
|
|
* FR = front right
|
|
|
|
* FLC = front left center
|
|
|
|
* FRC = front right center
|
|
|
|
* RL = rear left
|
|
|
|
* RC = rear center
|
|
|
|
* RR = rear right
|
|
|
|
* RLC = rear left center
|
|
|
|
* RRC = rear right center
|
|
|
|
* LFE = low frequency effect
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Read first then write because it is bundled with other controls */
|
|
|
|
audio_info_ctrl_reg = DSS_REG_R(io, HDMI_INFOFRAME_CTRL0);
|
|
|
|
|
|
|
|
if (enabled) {
|
|
|
|
switch (num_of_channels) {
|
|
|
|
case MSM_HDMI_AUDIO_CHANNEL_2:
|
|
|
|
break;
|
2024-09-09 08:57:42 +00:00
|
|
|
case MSM_HDMI_AUDIO_CHANNEL_3:
|
|
|
|
channel_count = 2;
|
|
|
|
break;
|
2024-09-09 08:52:07 +00:00
|
|
|
case MSM_HDMI_AUDIO_CHANNEL_4:
|
|
|
|
channel_count = 3;
|
|
|
|
break;
|
2024-09-09 08:57:42 +00:00
|
|
|
case MSM_HDMI_AUDIO_CHANNEL_5:
|
|
|
|
channel_count = 4;
|
|
|
|
break;
|
2024-09-09 08:52:07 +00:00
|
|
|
case MSM_HDMI_AUDIO_CHANNEL_6:
|
|
|
|
channel_count = 5;
|
|
|
|
break;
|
2024-09-09 08:57:42 +00:00
|
|
|
case MSM_HDMI_AUDIO_CHANNEL_7:
|
|
|
|
channel_count = 6;
|
|
|
|
break;
|
2024-09-09 08:52:07 +00:00
|
|
|
case MSM_HDMI_AUDIO_CHANNEL_8:
|
|
|
|
channel_count = 7;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
DEV_ERR("%s: Unsupported num_of_channels = %u\n",
|
|
|
|
__func__, num_of_channels);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Program the Channel-Speaker allocation */
|
|
|
|
audio_info_1_reg = 0;
|
|
|
|
/* CA(channel_allocation) */
|
|
|
|
audio_info_1_reg |= channel_allocation & 0xff;
|
|
|
|
/* Program the Level shifter */
|
|
|
|
audio_info_1_reg |= (level_shift << 11) & 0x00007800;
|
|
|
|
/* Program the Down-mix Inhibit Flag */
|
|
|
|
audio_info_1_reg |= (down_mix << 15) & 0x00008000;
|
|
|
|
|
|
|
|
DSS_REG_W(io, HDMI_AUDIO_INFO1, audio_info_1_reg);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Calculate CheckSum: Sum of all the bytes in the
|
|
|
|
* Audio Info Packet (See table 8.4 in HDMI spec)
|
|
|
|
*/
|
|
|
|
check_sum = 0;
|
|
|
|
/* HDMI_AUDIO_INFO_FRAME_PACKET_HEADER_TYPE[0x84] */
|
|
|
|
check_sum += 0x84;
|
|
|
|
/* HDMI_AUDIO_INFO_FRAME_PACKET_HEADER_VERSION[0x01] */
|
|
|
|
check_sum += 1;
|
|
|
|
/* HDMI_AUDIO_INFO_FRAME_PACKET_LENGTH[0x0A] */
|
|
|
|
check_sum += 0x0A;
|
|
|
|
check_sum += channel_count;
|
|
|
|
check_sum += channel_allocation;
|
|
|
|
/* See Table 8.5 in HDMI spec */
|
|
|
|
check_sum += (level_shift & 0xF) << 3 | (down_mix & 0x1) << 7;
|
|
|
|
check_sum &= 0xFF;
|
|
|
|
check_sum = (u8) (256 - check_sum);
|
|
|
|
|
|
|
|
audio_info_0_reg = 0;
|
|
|
|
/* CHECKSUM(check_sum) */
|
|
|
|
audio_info_0_reg |= check_sum & 0xff;
|
|
|
|
/* CC(channel_count) */
|
|
|
|
audio_info_0_reg |= (channel_count << 8) & 0x00000700;
|
|
|
|
|
|
|
|
DSS_REG_W(io, HDMI_AUDIO_INFO0, audio_info_0_reg);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set these flags
|
|
|
|
* AUDIO_INFO_UPDATE |
|
|
|
|
* AUDIO_INFO_SOURCE |
|
|
|
|
* AUDIO_INFO_CONT |
|
|
|
|
* AUDIO_INFO_SEND
|
|
|
|
*/
|
|
|
|
audio_info_ctrl_reg |= 0x000000F0;
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Program the Sample Present into the debug register so that
|
|
|
|
* the HDMI transmitter core can add the sample present to
|
|
|
|
* Audio Sample Packet once tranmission starts.
|
|
|
|
*/
|
|
|
|
if (layout) {
|
|
|
|
/* Set the Layout bit */
|
|
|
|
hdmi_debug_reg |= BIT(4);
|
|
|
|
/* Set the Sample Present bits */
|
|
|
|
hdmi_debug_reg |= sample_present & 0xF;
|
|
|
|
DSS_REG_W(io, HDMI_DEBUG, hdmi_debug_reg);
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
} else {
|
|
|
|
/*Clear these flags
|
|
|
|
* ~(AUDIO_INFO_UPDATE |
|
|
|
|
* AUDIO_INFO_SOURCE |
|
|
|
|
* AUDIO_INFO_CONT |
|
|
|
|
* AUDIO_INFO_SEND)
|
|
|
|
*/
|
|
|
|
audio_info_ctrl_reg &= ~0x000000F0;
|
|
|
|
}
|
|
|
|
DSS_REG_W(io, HDMI_INFOFRAME_CTRL0, audio_info_ctrl_reg);
|
|
|
|
|
|
|
|
dss_reg_dump(io->base, io->len,
|
|
|
|
enabled ? "HDMI-AUDIO-ON: " : "HDMI-AUDIO-OFF: ", REG_DUMP);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
} /* hdmi_tx_audio_iframe_setup */
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int hdmi_tx_get_audio_sample_rate(u32 *sample_rate_hz)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
u32 rate = *sample_rate_hz;
|
|
|
|
|
|
|
|
switch (rate) {
|
|
|
|
case 32000:
|
|
|
|
*sample_rate_hz = AUDIO_SAMPLE_RATE_32KHZ;
|
|
|
|
break;
|
|
|
|
case 44100:
|
|
|
|
*sample_rate_hz = AUDIO_SAMPLE_RATE_44_1KHZ;
|
|
|
|
break;
|
|
|
|
case 48000:
|
|
|
|
*sample_rate_hz = AUDIO_SAMPLE_RATE_48KHZ;
|
|
|
|
break;
|
|
|
|
case 88200:
|
|
|
|
*sample_rate_hz = AUDIO_SAMPLE_RATE_88_2KHZ;
|
|
|
|
break;
|
|
|
|
case 96000:
|
|
|
|
*sample_rate_hz = AUDIO_SAMPLE_RATE_96KHZ;
|
|
|
|
break;
|
|
|
|
case 176400:
|
|
|
|
*sample_rate_hz = AUDIO_SAMPLE_RATE_176_4KHZ;
|
|
|
|
break;
|
|
|
|
case 192000:
|
|
|
|
*sample_rate_hz = AUDIO_SAMPLE_RATE_192KHZ;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ret = -EINVAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
} /* hdmi_tx_get_audio_sample_rate */
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
static int hdmi_tx_audio_info_setup(struct platform_device *pdev,
|
2024-09-09 08:57:42 +00:00
|
|
|
struct msm_hdmi_audio_setup_params *params)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev);
|
2024-09-09 08:57:42 +00:00
|
|
|
u32 is_mode_dvi;
|
|
|
|
u32 *sample_rate_hz;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!hdmi_ctrl || !params) {
|
2024-09-09 08:52:07 +00:00
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
is_mode_dvi = hdmi_tx_is_dvi_mode(hdmi_ctrl);
|
|
|
|
|
|
|
|
mutex_lock(&hdmi_ctrl->power_mutex);
|
|
|
|
if (!is_mode_dvi && hdmi_tx_is_panel_on(hdmi_ctrl)) {
|
|
|
|
mutex_unlock(&hdmi_ctrl->power_mutex);
|
|
|
|
memcpy(&hdmi_ctrl->audio_data, params,
|
|
|
|
sizeof(struct msm_hdmi_audio_setup_params));
|
|
|
|
|
|
|
|
sample_rate_hz = &hdmi_ctrl->audio_data.sample_rate_hz;
|
|
|
|
rc = hdmi_tx_get_audio_sample_rate(sample_rate_hz);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: invalid sample rate = %d\n",
|
|
|
|
__func__, hdmi_ctrl->audio_data.sample_rate_hz);
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = hdmi_tx_audio_setup(hdmi_ctrl);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (rc)
|
|
|
|
DEV_ERR("%s: hdmi_tx_audio_iframe_setup failed.rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
} else {
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_unlock(&hdmi_ctrl->power_mutex);
|
2024-09-09 08:52:07 +00:00
|
|
|
rc = -EPERM;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (rc)
|
|
|
|
dev_err_ratelimited(&hdmi_ctrl->pdev->dev,
|
|
|
|
"%s: hpd %d, ack %d, switch %d, mode %s, power %d\n",
|
|
|
|
__func__, hdmi_ctrl->hpd_state,
|
|
|
|
atomic_read(&hdmi_ctrl->audio_ack_pending),
|
|
|
|
hdmi_ctrl->audio_sdev.state,
|
|
|
|
is_mode_dvi ? "dvi" : "hdmi",
|
|
|
|
hdmi_ctrl->panel_power_on);
|
|
|
|
|
|
|
|
exit:
|
2024-09-09 08:52:07 +00:00
|
|
|
return rc;
|
|
|
|
} /* hdmi_tx_audio_info_setup */
|
|
|
|
|
|
|
|
static int hdmi_tx_get_audio_edid_blk(struct platform_device *pdev,
|
|
|
|
struct msm_hdmi_audio_edid_blk *blk)
|
|
|
|
{
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!hdmi_ctrl->audio_sdev.state)
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EPERM;
|
|
|
|
|
|
|
|
return hdmi_edid_get_audio_blk(
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID], blk);
|
|
|
|
} /* hdmi_tx_get_audio_edid_blk */
|
|
|
|
|
|
|
|
static u8 hdmi_tx_tmds_enabled(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* status of tmds */
|
|
|
|
return (hdmi_ctrl->timing_gen_on == true);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hdmi_tx_set_mhl_max_pclk(struct platform_device *pdev, u32 max_val)
|
|
|
|
{
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
|
|
|
|
|
|
|
hdmi_ctrl = platform_get_drvdata(pdev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
if (max_val) {
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->ds_data.ds_max_clk = max_val;
|
|
|
|
hdmi_ctrl->ds_data.ds_registered = true;
|
2024-09-09 08:52:07 +00:00
|
|
|
} else {
|
|
|
|
DEV_ERR("%s: invalid max pclk val\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int msm_hdmi_register_mhl(struct platform_device *pdev,
|
|
|
|
struct msm_hdmi_mhl_ops *ops, void *data)
|
|
|
|
{
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid pdev\n", __func__);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ops) {
|
|
|
|
DEV_ERR("%s: invalid ops\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ops->tmds_enabled = hdmi_tx_tmds_enabled;
|
|
|
|
ops->set_mhl_max_pclk = hdmi_tx_set_mhl_max_pclk;
|
|
|
|
ops->set_upstream_hpd = hdmi_tx_set_mhl_hpd;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->ds_registered = true;
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int hdmi_tx_get_cable_status(struct platform_device *pdev, u32 vote)
|
|
|
|
{
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev);
|
|
|
|
unsigned long flags;
|
|
|
|
u32 hpd;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock_irqsave(&hdmi_ctrl->hpd_state_lock, flags);
|
|
|
|
hpd = hdmi_tx_is_panel_on(hdmi_ctrl);
|
|
|
|
spin_unlock_irqrestore(&hdmi_ctrl->hpd_state_lock, flags);
|
|
|
|
|
|
|
|
hdmi_ctrl->vote_hdmi_core_on = false;
|
|
|
|
|
|
|
|
if (vote && hpd)
|
|
|
|
hdmi_ctrl->vote_hdmi_core_on = true;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* if cable is not connected and audio calls this function,
|
|
|
|
* consider this as an error as it will result in whole
|
|
|
|
* audio path to fail.
|
|
|
|
*/
|
|
|
|
if (!hpd)
|
|
|
|
dev_err_ratelimited(&hdmi_ctrl->pdev->dev,
|
|
|
|
"%s: hpd %d, ack %d, switch %d, power %d\n",
|
|
|
|
__func__, hdmi_ctrl->hpd_state,
|
|
|
|
atomic_read(&hdmi_ctrl->audio_ack_pending),
|
|
|
|
hdmi_ctrl->audio_sdev.state,
|
|
|
|
hdmi_ctrl->panel_power_on);
|
|
|
|
|
|
|
|
return hpd;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
int msm_hdmi_register_audio_codec(struct platform_device *pdev,
|
|
|
|
struct msm_hdmi_audio_codec_ops *ops)
|
|
|
|
{
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl || !ops) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
ops->audio_info_setup = hdmi_tx_audio_info_setup;
|
|
|
|
ops->get_audio_edid_blk = hdmi_tx_get_audio_edid_blk;
|
2024-09-09 08:57:42 +00:00
|
|
|
ops->hdmi_cable_status = hdmi_tx_get_cable_status;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
} /* hdmi_tx_audio_register */
|
|
|
|
EXPORT_SYMBOL(msm_hdmi_register_audio_codec);
|
|
|
|
|
|
|
|
static int hdmi_tx_audio_setup(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
struct dss_io_data *io = NULL;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: core io not inititalized\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
rc = hdmi_tx_audio_acr_setup(hdmi_ctrl, true);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: hdmi_tx_audio_acr_setup failed. rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
rc = hdmi_tx_audio_iframe_setup(hdmi_ctrl, true);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: hdmi_tx_audio_iframe_setup failed. rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEV_INFO("HDMI Audio: Enabled\n");
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
} /* hdmi_tx_audio_setup */
|
|
|
|
|
|
|
|
static void hdmi_tx_audio_off(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
struct dss_io_data *io = NULL;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: core io not inititalized\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (hdmi_tx_audio_iframe_setup(hdmi_ctrl, false))
|
|
|
|
DEV_ERR("%s: hdmi_tx_audio_iframe_setup failed.\n", __func__);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (hdmi_tx_audio_acr_setup(hdmi_ctrl, false))
|
|
|
|
DEV_ERR("%s: hdmi_tx_audio_acr_setup failed.\n", __func__);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->audio_data.sample_rate_hz = AUDIO_SAMPLE_RATE_48KHZ;
|
|
|
|
hdmi_ctrl->audio_data.num_of_channels = MSM_HDMI_AUDIO_CHANNEL_2;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
DEV_INFO("HDMI Audio: Disabled\n");
|
|
|
|
} /* hdmi_tx_audio_off */
|
|
|
|
|
|
|
|
static int hdmi_tx_setup_tmds_clk_rate(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
u32 rate = 0;
|
|
|
|
struct msm_hdmi_mode_timing_info *timing = NULL;
|
|
|
|
u32 rate_ratio;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: Bad input parameters\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
timing = &hdmi_ctrl->vid_cfg.timing;
|
|
|
|
if (!timing) {
|
|
|
|
DEV_ERR("%s: Invalid timing info\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (hdmi_ctrl->vid_cfg.avi_iframe.pixel_format) {
|
|
|
|
case MDP_Y_CBCR_H2V2:
|
|
|
|
rate_ratio = HDMI_TX_YUV420_24BPP_PCLK_TMDS_CH_RATE_RATIO;
|
|
|
|
break;
|
|
|
|
case MDP_Y_CBCR_H2V1:
|
|
|
|
rate_ratio = HDMI_TX_YUV422_24BPP_PCLK_TMDS_CH_RATE_RATIO;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
rate_ratio = HDMI_TX_RGB_24BPP_PCLK_TMDS_CH_RATE_RATIO;
|
2024-09-09 08:52:07 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
rate = timing->pixel_freq / rate_ratio;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
end:
|
|
|
|
return rate;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
int hdmi_tx_setup_scrambler(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
u32 rate = 0;
|
|
|
|
u32 reg_val = 0;
|
|
|
|
u32 tmds_clock_ratio = 0;
|
|
|
|
bool scrambler_on = false;
|
|
|
|
struct dss_io_data *io = NULL;
|
|
|
|
struct msm_hdmi_mode_timing_info *timing = NULL;
|
|
|
|
void *edid_data = NULL;
|
|
|
|
int timeout_hsync;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: Bad input parameters\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: core io is not initialized\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
edid_data = hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID];
|
|
|
|
|
|
|
|
timing = &hdmi_ctrl->vid_cfg.timing;
|
|
|
|
if (!timing) {
|
|
|
|
DEV_ERR("%s: Invalid timing info\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Scrambling is supported from HDMI TX 4.0 */
|
|
|
|
if (hdmi_ctrl->hdmi_tx_ver < HDMI_TX_SCRAMBLER_MIN_TX_VERSION) {
|
|
|
|
DEV_DBG("%s: HDMI TX does not support scrambling\n", __func__);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
rate = hdmi_tx_setup_tmds_clk_rate(hdmi_ctrl);
|
|
|
|
|
|
|
|
if (rate > HDMI_TX_SCRAMBLER_THRESHOLD_RATE_KHZ) {
|
|
|
|
scrambler_on = true;
|
|
|
|
tmds_clock_ratio = 1;
|
|
|
|
} else {
|
|
|
|
if (hdmi_edid_get_sink_scrambler_support(edid_data))
|
|
|
|
scrambler_on = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (scrambler_on) {
|
|
|
|
rc = hdmi_scdc_write(&hdmi_ctrl->ddc_ctrl,
|
|
|
|
HDMI_TX_SCDC_TMDS_BIT_CLOCK_RATIO_UPDATE,
|
|
|
|
tmds_clock_ratio);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: TMDS CLK RATIO ERR\n", __func__);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
reg_val = DSS_REG_R(io, HDMI_CTRL);
|
|
|
|
reg_val |= BIT(31); /* Enable Update DATAPATH_MODE */
|
|
|
|
reg_val |= BIT(28); /* Set SCRAMBLER_EN bit */
|
|
|
|
|
|
|
|
DSS_REG_W(io, HDMI_CTRL, reg_val);
|
|
|
|
|
|
|
|
rc = hdmi_scdc_write(&hdmi_ctrl->ddc_ctrl,
|
|
|
|
HDMI_TX_SCDC_SCRAMBLING_ENABLE, 0x1);
|
|
|
|
if (!rc) {
|
|
|
|
hdmi_ctrl->scrambler_enabled = true;
|
|
|
|
} else {
|
|
|
|
DEV_ERR("%s: failed to enable scrambling\n",
|
|
|
|
__func__);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Setup hardware to periodically check for scrambler
|
|
|
|
* status bit on the sink. Sink should set this bit
|
|
|
|
* with in 200ms after scrambler is enabled.
|
|
|
|
*/
|
|
|
|
timeout_hsync = hdmi_utils_get_timeout_in_hysnc(
|
|
|
|
&hdmi_ctrl->vid_cfg.timing,
|
|
|
|
HDMI_TX_SCRAMBLER_TIMEOUT_MSEC);
|
|
|
|
|
|
|
|
if (timeout_hsync <= 0) {
|
|
|
|
DEV_ERR("%s: err in timeout hsync calc\n", __func__);
|
|
|
|
timeout_hsync = HDMI_DEFAULT_TIMEOUT_HSYNC;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_debug("timeout for scrambling en: %d hsyncs\n",
|
|
|
|
timeout_hsync);
|
|
|
|
|
|
|
|
rc = hdmi_setup_ddc_timers(&hdmi_ctrl->ddc_ctrl,
|
|
|
|
HDMI_TX_DDC_TIMER_SCRAMBLER_STATUS, timeout_hsync);
|
|
|
|
} else {
|
|
|
|
hdmi_scdc_write(&hdmi_ctrl->ddc_ctrl,
|
|
|
|
HDMI_TX_SCDC_SCRAMBLING_ENABLE, 0x0);
|
|
|
|
|
|
|
|
hdmi_ctrl->scrambler_enabled = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
static int hdmi_tx_start(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
struct dss_io_data *io = NULL;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: core io is not initialized\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_tx_set_mode(hdmi_ctrl, false);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
DSS_REG_W(io, HDMI_USEC_REFTIMER, 0x0001001B);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
rc = hdmi_tx_video_setup(hdmi_ctrl);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: hdmi_tx_video_setup failed. rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hdmi_tx_is_dvi_mode(hdmi_ctrl) &&
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_tx_is_cea_format(hdmi_ctrl->vid_cfg.vic)) {
|
|
|
|
hdmi_tx_audio_setup(hdmi_ctrl);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!hdmi_tx_is_encryption_set(hdmi_ctrl) &&
|
|
|
|
hdmi_tx_is_stream_shareable(hdmi_ctrl)) {
|
|
|
|
hdmi_tx_set_audio_switch_node(hdmi_ctrl, 1);
|
|
|
|
hdmi_tx_config_avmute(hdmi_ctrl, false);
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
hdmi_tx_set_avi_infoframe(hdmi_ctrl);
|
|
|
|
hdmi_tx_set_vendor_specific_infoframe(hdmi_ctrl);
|
|
|
|
hdmi_tx_set_spd_infoframe(hdmi_ctrl);
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_tx_set_mode(hdmi_ctrl, true);
|
|
|
|
|
|
|
|
if (hdmi_tx_setup_scrambler(hdmi_ctrl))
|
|
|
|
DEV_WARN("%s: Scrambler setup failed\n", __func__);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
DEV_INFO("%s: HDMI Core: Initialized\n", __func__);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
} /* hdmi_tx_start */
|
|
|
|
|
|
|
|
static void hdmi_tx_hpd_polarity_setup(struct hdmi_tx_ctrl *hdmi_ctrl,
|
|
|
|
bool polarity)
|
|
|
|
{
|
|
|
|
struct dss_io_data *io = NULL;
|
|
|
|
u32 cable_sense;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: core io is not initialized\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (polarity)
|
|
|
|
DSS_REG_W(io, HDMI_HPD_INT_CTRL, BIT(2) | BIT(1));
|
|
|
|
else
|
|
|
|
DSS_REG_W(io, HDMI_HPD_INT_CTRL, BIT(2));
|
|
|
|
|
|
|
|
cable_sense = (DSS_REG_R(io, HDMI_HPD_INT_STATUS) & BIT(1)) >> 1;
|
|
|
|
DEV_DBG("%s: listen = %s, sense = %s\n", __func__,
|
|
|
|
polarity ? "connect" : "disconnect",
|
|
|
|
cable_sense ? "connect" : "disconnect");
|
|
|
|
|
|
|
|
if (cable_sense == polarity) {
|
|
|
|
u32 reg_val = DSS_REG_R(io, HDMI_HPD_CTRL);
|
|
|
|
|
|
|
|
/* Toggle HPD circuit to trigger HPD sense */
|
|
|
|
DSS_REG_W(io, HDMI_HPD_CTRL, reg_val & ~BIT(28));
|
|
|
|
DSS_REG_W(io, HDMI_HPD_CTRL, reg_val | BIT(28));
|
|
|
|
}
|
|
|
|
} /* hdmi_tx_hpd_polarity_setup */
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int hdmi_tx_power_off(struct mdss_panel_data *panel_data)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
|
|
|
struct dss_io_data *io = NULL;
|
2024-09-09 08:57:42 +00:00
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl =
|
|
|
|
hdmi_tx_get_drvdata_from_panel_data(panel_data);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_lock(&hdmi_ctrl->power_mutex);
|
|
|
|
if (!hdmi_ctrl ||
|
|
|
|
(!panel_data->panel_info.cont_splash_enabled &&
|
|
|
|
!hdmi_ctrl->panel_power_on)) {
|
|
|
|
mutex_unlock(&hdmi_ctrl->power_mutex);
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
} else {
|
|
|
|
mutex_unlock(&hdmi_ctrl->power_mutex);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: Core io is not initialized\n", __func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
return -EINVAL;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!hdmi_tx_is_dvi_mode(hdmi_ctrl))
|
|
|
|
hdmi_tx_audio_off(hdmi_ctrl);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
hdmi_tx_core_off(hdmi_ctrl);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_lock(&hdmi_ctrl->power_mutex);
|
|
|
|
hdmi_ctrl->panel_power_on = false;
|
|
|
|
mutex_unlock(&hdmi_ctrl->power_mutex);
|
|
|
|
|
|
|
|
mutex_lock(&hdmi_ctrl->mutex);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (hdmi_ctrl->hpd_off_pending) {
|
|
|
|
hdmi_ctrl->hpd_off_pending = false;
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_unlock(&hdmi_ctrl->mutex);
|
|
|
|
if (!hdmi_ctrl->hpd_state)
|
|
|
|
hdmi_tx_hpd_off(hdmi_ctrl);
|
|
|
|
} else {
|
|
|
|
mutex_unlock(&hdmi_ctrl->mutex);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (hdmi_ctrl->hdmi_tx_hpd_done)
|
|
|
|
hdmi_ctrl->hdmi_tx_hpd_done(
|
|
|
|
hdmi_ctrl->downstream_data);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
DEV_INFO("%s: HDMI Core: OFF\n", __func__);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
} /* hdmi_tx_power_off */
|
|
|
|
|
|
|
|
static int hdmi_tx_power_on(struct mdss_panel_data *panel_data)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
2024-09-09 08:57:42 +00:00
|
|
|
int res_changed = RESOLUTION_UNCHANGED;
|
2024-09-09 08:52:07 +00:00
|
|
|
struct dss_io_data *io = NULL;
|
2024-09-09 08:57:42 +00:00
|
|
|
struct mdss_panel_info *panel_info = NULL;
|
2024-09-09 08:52:07 +00:00
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl =
|
|
|
|
hdmi_tx_get_drvdata_from_panel_data(panel_data);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: core io is not initialized\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hdmi_ctrl->hpd_initialized) {
|
2024-09-09 08:57:42 +00:00
|
|
|
DEV_ERR("%s: hpd not initialized\n", __func__);
|
2024-09-09 08:52:07 +00:00
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
rc = hdmi_tx_check_clk_state(hdmi_ctrl, HDMI_TX_HPD_PM);
|
|
|
|
if (rc)
|
2024-09-09 08:52:07 +00:00
|
|
|
return rc;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
panel_info = &panel_data->panel_info;
|
2024-09-09 08:52:07 +00:00
|
|
|
hdmi_ctrl->hdcp_feature_on = hdcp_feature_on;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
res_changed = hdmi_tx_set_video_fmt(hdmi_ctrl, panel_info);
|
|
|
|
|
|
|
|
DEV_DBG("%s: %dx%d%s\n", __func__,
|
|
|
|
panel_info->xres, panel_info->yres,
|
|
|
|
panel_info->cont_splash_enabled ? " (handoff underway)" : "");
|
|
|
|
|
|
|
|
if (hdmi_ctrl->pdata.cont_splash_enabled) {
|
|
|
|
hdmi_ctrl->pdata.cont_splash_enabled = false;
|
|
|
|
panel_data->panel_info.cont_splash_enabled = false;
|
|
|
|
|
|
|
|
if (res_changed == RESOLUTION_UNCHANGED) {
|
|
|
|
mutex_lock(&hdmi_ctrl->power_mutex);
|
|
|
|
hdmi_ctrl->panel_power_on = true;
|
|
|
|
mutex_unlock(&hdmi_ctrl->power_mutex);
|
|
|
|
|
|
|
|
hdmi_tx_set_vendor_specific_infoframe(hdmi_ctrl);
|
|
|
|
hdmi_tx_set_spd_infoframe(hdmi_ctrl);
|
|
|
|
|
|
|
|
if (!hdmi_tx_is_hdcp_enabled(hdmi_ctrl))
|
|
|
|
hdmi_tx_set_audio_switch_node(hdmi_ctrl, 1);
|
|
|
|
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
rc = hdmi_tx_core_on(hdmi_ctrl);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: hdmi_msm_core_on failed\n", __func__);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hdmi_ctrl->hpd_state) {
|
|
|
|
rc = hdmi_tx_start(hdmi_ctrl);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: hdmi_tx_start failed. rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
hdmi_tx_power_off(panel_data);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_lock(&hdmi_ctrl->power_mutex);
|
|
|
|
hdmi_ctrl->panel_power_on = true;
|
|
|
|
mutex_unlock(&hdmi_ctrl->power_mutex);
|
|
|
|
end:
|
2024-09-09 08:52:07 +00:00
|
|
|
dss_reg_dump(io->base, io->len, "HDMI-ON: ", REG_DUMP);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
DEV_DBG("%s: Tx: %s (%s mode)\n", __func__,
|
2024-09-09 08:52:07 +00:00
|
|
|
hdmi_tx_is_controller_on(hdmi_ctrl) ? "ON" : "OFF" ,
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_tx_is_dvi_mode(hdmi_ctrl) ? "DVI" : "HDMI");
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
hdmi_tx_hpd_polarity_setup(hdmi_ctrl, HPD_DISCONNECT_POLARITY);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (hdmi_ctrl->hdmi_tx_hpd_done)
|
|
|
|
hdmi_ctrl->hdmi_tx_hpd_done(hdmi_ctrl->downstream_data);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
return 0;
|
|
|
|
} /* hdmi_tx_power_on */
|
|
|
|
|
|
|
|
static void hdmi_tx_hpd_off(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
struct dss_io_data *io = NULL;
|
2024-09-09 08:57:42 +00:00
|
|
|
unsigned long flags;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!hdmi_ctrl->hpd_initialized) {
|
|
|
|
DEV_DBG("%s: HPD is already OFF, returning\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: core io not inititalized\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* finish the ongoing hpd work if any */
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!hdmi_ctrl->panel_suspend)
|
|
|
|
flush_work(&hdmi_ctrl->hpd_int_work);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
/* Turn off HPD interrupts */
|
|
|
|
DSS_REG_W(io, HDMI_HPD_INT_CTRL, 0);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->mdss_util->disable_irq(&hdmi_tx_hw);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
hdmi_tx_set_mode(hdmi_ctrl, false);
|
|
|
|
|
|
|
|
rc = hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_HPD_PM, 0);
|
|
|
|
if (rc)
|
|
|
|
DEV_INFO("%s: Failed to disable hpd power. Error=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
spin_lock_irqsave(&hdmi_ctrl->hpd_state_lock, flags);
|
2024-09-09 08:52:07 +00:00
|
|
|
hdmi_ctrl->hpd_state = false;
|
2024-09-09 08:57:42 +00:00
|
|
|
spin_unlock_irqrestore(&hdmi_ctrl->hpd_state_lock, flags);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
hdmi_ctrl->hpd_initialized = false;
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
if (!completion_done(&hdmi_ctrl->hpd_off_done))
|
|
|
|
complete_all(&hdmi_ctrl->hpd_off_done);
|
|
|
|
|
|
|
|
DEV_DBG("%s: HPD is now OFF\n", __func__);
|
2024-09-09 08:52:07 +00:00
|
|
|
} /* hdmi_tx_hpd_off */
|
|
|
|
|
|
|
|
static int hdmi_tx_hpd_on(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
u32 reg_val;
|
|
|
|
int rc = 0;
|
|
|
|
struct dss_io_data *io = NULL;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: core io not inititalized\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hdmi_ctrl->hpd_initialized) {
|
|
|
|
DEV_DBG("%s: HPD is already ON\n", __func__);
|
|
|
|
} else {
|
|
|
|
rc = hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_HPD_PM, true);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: Failed to enable hpd power. rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
dss_reg_dump(io->base, io->len, "HDMI-INIT: ", REG_DUMP);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!hdmi_ctrl->panel_data.panel_info.cont_splash_enabled) {
|
|
|
|
hdmi_tx_set_mode(hdmi_ctrl, false);
|
|
|
|
hdmi_tx_phy_reset(hdmi_ctrl);
|
|
|
|
hdmi_tx_set_mode(hdmi_ctrl, true);
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
DSS_REG_W(io, HDMI_USEC_REFTIMER, 0x0001001B);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->mdss_util->enable_irq(&hdmi_tx_hw);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
hdmi_ctrl->hpd_initialized = true;
|
|
|
|
|
|
|
|
DEV_INFO("%s: HDMI HW version = 0x%x\n", __func__,
|
|
|
|
DSS_REG_R_ND(&hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO],
|
|
|
|
HDMI_VERSION));
|
|
|
|
|
|
|
|
/* set timeout to 4.1ms (max) for hardware debounce */
|
|
|
|
reg_val = DSS_REG_R(io, HDMI_HPD_CTRL) | 0x1FFF;
|
|
|
|
|
|
|
|
/* Turn on HPD HW circuit */
|
|
|
|
DSS_REG_W(io, HDMI_HPD_CTRL, reg_val | BIT(28));
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
atomic_set(&hdmi_ctrl->audio_ack_pending, 0);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
hdmi_tx_hpd_polarity_setup(hdmi_ctrl, HPD_CONNECT_POLARITY);
|
2024-09-09 08:57:42 +00:00
|
|
|
DEV_DBG("%s: HPD is now ON\n", __func__);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
} /* hdmi_tx_hpd_on */
|
|
|
|
|
|
|
|
static int hdmi_tx_sysfs_enable_hpd(struct hdmi_tx_ctrl *hdmi_ctrl, int on)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEV_INFO("%s: %d\n", __func__, on);
|
|
|
|
if (on) {
|
2024-09-09 08:57:42 +00:00
|
|
|
if (hdmi_ctrl->hpd_off_pending) {
|
|
|
|
u32 timeout;
|
|
|
|
|
|
|
|
reinit_completion(&hdmi_ctrl->hpd_off_done);
|
|
|
|
timeout = wait_for_completion_timeout(
|
|
|
|
&hdmi_ctrl->hpd_off_done, HZ);
|
|
|
|
if (!timeout) {
|
|
|
|
hdmi_ctrl->hpd_off_pending = false;
|
|
|
|
DEV_ERR("%s: hpd off still pending\n",
|
|
|
|
__func__);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
rc = hdmi_tx_hpd_on(hdmi_ctrl);
|
|
|
|
} else {
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_lock(&hdmi_ctrl->power_mutex);
|
|
|
|
if (!hdmi_ctrl->panel_power_on &&
|
|
|
|
!hdmi_ctrl->hpd_off_pending) {
|
|
|
|
mutex_unlock(&hdmi_ctrl->power_mutex);
|
2024-09-09 08:52:07 +00:00
|
|
|
hdmi_tx_hpd_off(hdmi_ctrl);
|
2024-09-09 08:57:42 +00:00
|
|
|
} else {
|
|
|
|
mutex_unlock(&hdmi_ctrl->power_mutex);
|
2024-09-09 08:52:07 +00:00
|
|
|
hdmi_ctrl->hpd_off_pending = true;
|
2024-09-09 08:57:42 +00:00
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
} /* hdmi_tx_sysfs_enable_hpd */
|
|
|
|
|
|
|
|
static int hdmi_tx_set_mhl_hpd(struct platform_device *pdev, uint8_t on)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
|
|
|
|
|
|
|
hdmi_ctrl = platform_get_drvdata(pdev);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* mhl status should override */
|
|
|
|
hdmi_ctrl->mhl_hpd_on = on;
|
|
|
|
|
|
|
|
if (!on && hdmi_ctrl->hpd_feature_on) {
|
|
|
|
rc = hdmi_tx_sysfs_enable_hpd(hdmi_ctrl, false);
|
|
|
|
} else if (on && !hdmi_ctrl->hpd_feature_on) {
|
|
|
|
rc = hdmi_tx_sysfs_enable_hpd(hdmi_ctrl, true);
|
|
|
|
} else {
|
|
|
|
DEV_DBG("%s: hpd is already '%s'. return\n", __func__,
|
|
|
|
hdmi_ctrl->hpd_feature_on ? "enabled" : "disabled");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!rc) {
|
|
|
|
hdmi_ctrl->hpd_feature_on =
|
|
|
|
(~hdmi_ctrl->hpd_feature_on) & BIT(0);
|
|
|
|
DEV_DBG("%s: '%d'\n", __func__, hdmi_ctrl->hpd_feature_on);
|
|
|
|
} else {
|
|
|
|
DEV_ERR("%s: failed to '%s' hpd. rc = %d\n", __func__,
|
|
|
|
on ? "enable" : "disable", rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t hdmi_tx_isr(int irq, void *data)
|
|
|
|
{
|
|
|
|
struct dss_io_data *io = NULL;
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = (struct hdmi_tx_ctrl *)data;
|
2024-09-09 08:57:42 +00:00
|
|
|
unsigned long flags;
|
|
|
|
u32 hpd_current_state;
|
|
|
|
u32 reg_val = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_WARN("%s: invalid input data, ISR ignored\n", __func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
goto end;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_WARN("%s: core io not initialized, ISR ignored\n",
|
|
|
|
__func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
goto end;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (DSS_REG_R(io, HDMI_HPD_INT_STATUS) & BIT(0)) {
|
2024-09-09 08:57:42 +00:00
|
|
|
spin_lock_irqsave(&hdmi_ctrl->hpd_state_lock, flags);
|
|
|
|
hpd_current_state = hdmi_ctrl->hpd_state;
|
2024-09-09 08:52:07 +00:00
|
|
|
hdmi_ctrl->hpd_state =
|
|
|
|
(DSS_REG_R(io, HDMI_HPD_INT_STATUS) & BIT(1)) >> 1;
|
2024-09-09 08:57:42 +00:00
|
|
|
spin_unlock_irqrestore(&hdmi_ctrl->hpd_state_lock, flags);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* check if this is a spurious interrupt, if yes, reset
|
|
|
|
* interrupts and return
|
|
|
|
*/
|
|
|
|
if (hpd_current_state == hdmi_ctrl->hpd_state) {
|
|
|
|
DEV_DBG("%s: spurious interrupt %d\n", __func__,
|
|
|
|
hpd_current_state);
|
|
|
|
|
|
|
|
/* enable interrupts */
|
|
|
|
reg_val |= BIT(2);
|
|
|
|
|
|
|
|
/* set polarity, reverse of current state */
|
|
|
|
reg_val |= (~hpd_current_state << 1) & BIT(1);
|
|
|
|
|
|
|
|
/* ack interrupt */
|
|
|
|
reg_val |= BIT(0);
|
|
|
|
|
|
|
|
DSS_REG_W(io, HDMI_HPD_INT_CTRL, reg_val);
|
|
|
|
goto end;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Ack the current hpd interrupt and stop listening to
|
|
|
|
* new hpd interrupt.
|
|
|
|
*/
|
|
|
|
DSS_REG_W(io, HDMI_HPD_INT_CTRL, BIT(0));
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If suspend has already triggered, don't start the hpd work
|
|
|
|
* to avoid a possible deadlock during suspend where hpd off
|
|
|
|
* waits for hpd interrupt to finish. Suspend thread will
|
|
|
|
* eventually reset the HPD module.
|
|
|
|
*/
|
|
|
|
if (hdmi_ctrl->panel_suspend)
|
|
|
|
hdmi_ctrl->hpd_state = 0;
|
|
|
|
else
|
|
|
|
queue_work(hdmi_ctrl->workq, &hdmi_ctrl->hpd_int_work);
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (hdmi_ddc_isr(&hdmi_ctrl->ddc_ctrl,
|
|
|
|
hdmi_ctrl->hdmi_tx_ver))
|
2024-09-09 08:52:07 +00:00
|
|
|
DEV_ERR("%s: hdmi_ddc_isr failed\n", __func__);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW])
|
|
|
|
if (hdmi_cec_isr(hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW]))
|
2024-09-09 08:52:07 +00:00
|
|
|
DEV_ERR("%s: hdmi_cec_isr failed\n", __func__);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (hdmi_ctrl->hdcp_ops && hdmi_ctrl->hdcp_data) {
|
|
|
|
if (hdmi_ctrl->hdcp_ops->hdmi_hdcp_isr) {
|
|
|
|
if (hdmi_ctrl->hdcp_ops->hdmi_hdcp_isr(
|
|
|
|
hdmi_ctrl->hdcp_data))
|
|
|
|
DEV_ERR("%s: hdmi_hdcp_isr failed\n",
|
|
|
|
__func__);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
end:
|
2024-09-09 08:52:07 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
} /* hdmi_tx_isr */
|
|
|
|
|
|
|
|
static void hdmi_tx_dev_deinit(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_ABST]) {
|
|
|
|
cec_abstract_deinit(
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_ABST]);
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_ABST] = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW]) {
|
|
|
|
hdmi_cec_deinit(hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW]);
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_CEC_HW] = NULL;
|
|
|
|
hdmi_ctrl->panel_data.panel_info.is_cec_supported = false;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP]) {
|
|
|
|
hdmi_hdcp_deinit(hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP]);
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP] = NULL;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP2P2]) {
|
|
|
|
hdmi_hdcp2p2_deinit(
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP2P2]);
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_HDCP2P2] = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_ctrl->hdcp_ops = NULL;
|
|
|
|
hdmi_ctrl->hdcp_data = NULL;
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
if (hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID]) {
|
|
|
|
hdmi_edid_deinit(hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID]);
|
|
|
|
hdmi_ctrl->feature_data[HDMI_TX_FEAT_EDID] = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch_dev_unregister(&hdmi_ctrl->audio_sdev);
|
|
|
|
switch_dev_unregister(&hdmi_ctrl->sdev);
|
|
|
|
if (hdmi_ctrl->workq)
|
|
|
|
destroy_workqueue(hdmi_ctrl->workq);
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_destroy(&hdmi_ctrl->lut_lock);
|
|
|
|
mutex_destroy(&hdmi_ctrl->power_mutex);
|
|
|
|
mutex_destroy(&hdmi_ctrl->cable_notify_mutex);
|
2024-09-09 08:52:07 +00:00
|
|
|
mutex_destroy(&hdmi_ctrl->mutex);
|
|
|
|
|
|
|
|
hdmi_tx_hw.ptr = NULL;
|
|
|
|
} /* hdmi_tx_dev_deinit */
|
|
|
|
|
|
|
|
static int hdmi_tx_dev_init(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
struct hdmi_tx_platform_data *pdata = NULL;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
pdata = &hdmi_ctrl->pdata;
|
|
|
|
|
|
|
|
rc = hdmi_tx_check_capability(hdmi_ctrl);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: no HDMI device\n", __func__);
|
|
|
|
goto fail_no_hdmi;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* irq enable/disable will be handled in hpd on/off */
|
|
|
|
hdmi_tx_hw.ptr = (void *)hdmi_ctrl;
|
|
|
|
|
|
|
|
mutex_init(&hdmi_ctrl->mutex);
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_init(&hdmi_ctrl->lut_lock);
|
|
|
|
mutex_init(&hdmi_ctrl->cable_notify_mutex);
|
|
|
|
mutex_init(&hdmi_ctrl->power_mutex);
|
|
|
|
|
|
|
|
INIT_LIST_HEAD(&hdmi_ctrl->cable_notify_handlers);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
hdmi_ctrl->workq = create_workqueue("hdmi_tx_workq");
|
|
|
|
if (!hdmi_ctrl->workq) {
|
|
|
|
DEV_ERR("%s: hdmi_tx_workq creation failed.\n", __func__);
|
|
|
|
rc = -EPERM;
|
|
|
|
goto fail_create_workq;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_ctrl->ddc_ctrl.io = &pdata->io[HDMI_TX_CORE_IO];
|
|
|
|
init_completion(&hdmi_ctrl->ddc_ctrl.ddc_sw_done);
|
|
|
|
|
|
|
|
hdmi_ctrl->panel_power_on = false;
|
|
|
|
hdmi_ctrl->panel_suspend = false;
|
|
|
|
|
|
|
|
hdmi_ctrl->hpd_state = false;
|
|
|
|
hdmi_ctrl->hpd_initialized = false;
|
|
|
|
hdmi_ctrl->hpd_off_pending = false;
|
2024-09-09 08:57:42 +00:00
|
|
|
init_completion(&hdmi_ctrl->hpd_int_done);
|
|
|
|
init_completion(&hdmi_ctrl->hpd_off_done);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
INIT_WORK(&hdmi_ctrl->hpd_int_work, hdmi_tx_hpd_int_work);
|
|
|
|
INIT_WORK(&hdmi_ctrl->cable_notify_work, hdmi_tx_cable_notify_work);
|
|
|
|
INIT_DELAYED_WORK(&hdmi_ctrl->hdcp_cb_work, hdmi_tx_hdcp_cb_work);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
spin_lock_init(&hdmi_ctrl->hpd_state_lock);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->audio_data.sample_rate_hz = AUDIO_SAMPLE_RATE_48KHZ;
|
|
|
|
hdmi_ctrl->audio_data.num_of_channels = MSM_HDMI_AUDIO_CHANNEL_2;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
fail_create_workq:
|
|
|
|
if (hdmi_ctrl->workq)
|
|
|
|
destroy_workqueue(hdmi_ctrl->workq);
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_destroy(&hdmi_ctrl->lut_lock);
|
2024-09-09 08:52:07 +00:00
|
|
|
mutex_destroy(&hdmi_ctrl->mutex);
|
|
|
|
fail_no_hdmi:
|
|
|
|
return rc;
|
|
|
|
} /* hdmi_tx_dev_init */
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int hdmi_tx_start_hdcp(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hdmi_ctrl->panel_data.panel_info.cont_splash_enabled ||
|
|
|
|
!hdmi_tx_is_hdcp_enabled(hdmi_ctrl))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (hdmi_tx_is_encryption_set(hdmi_ctrl))
|
|
|
|
hdmi_tx_config_avmute(hdmi_ctrl, true);
|
|
|
|
|
|
|
|
if (hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_DDC_PM, true)) {
|
|
|
|
DEV_ERR("%s: Failed to enable ddc power\n", __func__);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = hdmi_ctrl->hdcp_ops->hdmi_hdcp_authenticate(hdmi_ctrl->hdcp_data);
|
|
|
|
if (rc)
|
|
|
|
DEV_ERR("%s: hdcp auth failed. rc=%d\n", __func__, rc);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hdmi_tx_init_switch_dev(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
int rc = -EINVAL;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_ctrl->sdev.name = "hdmi";
|
|
|
|
rc = switch_dev_register(&hdmi_ctrl->sdev);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: display switch registration failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_ctrl->audio_sdev.name = "hdmi_audio";
|
|
|
|
rc = switch_dev_register(&hdmi_ctrl->audio_sdev);
|
|
|
|
if (rc)
|
|
|
|
DEV_ERR("%s: audio switch registration failed\n", __func__);
|
|
|
|
|
|
|
|
end:
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
static int hdmi_tx_panel_event_handler(struct mdss_panel_data *panel_data,
|
|
|
|
int event, void *arg)
|
|
|
|
{
|
|
|
|
int rc = 0, new_vic = -1;
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl =
|
|
|
|
hdmi_tx_get_drvdata_from_panel_data(panel_data);
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEV_DBG("%s: event = %d suspend=%d, hpd_feature=%d\n", __func__,
|
|
|
|
event, hdmi_ctrl->panel_suspend, hdmi_ctrl->hpd_feature_on);
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
case MDSS_EVENT_FB_REGISTERED:
|
|
|
|
rc = hdmi_tx_sysfs_create(hdmi_ctrl, arg);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: hdmi_tx_sysfs_create failed.rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
return rc;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
rc = hdmi_tx_init_features(hdmi_ctrl, arg);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: init_features failed.rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
hdmi_tx_sysfs_remove(hdmi_ctrl);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
rc = hdmi_tx_init_switch_dev(hdmi_ctrl);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: init switch dev failed.rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
hdmi_tx_sysfs_remove(hdmi_ctrl);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hdmi_ctrl->pdata.primary || !hdmi_ctrl->pdata.pluggable) {
|
|
|
|
reinit_completion(&hdmi_ctrl->hpd_int_done);
|
2024-09-09 08:52:07 +00:00
|
|
|
rc = hdmi_tx_sysfs_enable_hpd(hdmi_ctrl, true);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: hpd_enable failed. rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
hdmi_tx_sysfs_remove(hdmi_ctrl);
|
|
|
|
return rc;
|
|
|
|
} else {
|
|
|
|
hdmi_ctrl->hpd_feature_on = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MDSS_EVENT_CHECK_PARAMS:
|
|
|
|
new_vic = hdmi_tx_get_vic_from_panel_info(hdmi_ctrl,
|
|
|
|
(struct mdss_panel_info *)arg);
|
|
|
|
if ((new_vic < 0) || (new_vic > HDMI_VFRMT_MAX)) {
|
|
|
|
DEV_ERR("%s: invalid or not supported vic\n", __func__);
|
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* return value of 1 lets mdss know that panel
|
|
|
|
* needs a reconfig due to new resolution and
|
|
|
|
* it will issue close and open subsequently.
|
|
|
|
*/
|
2024-09-09 08:57:42 +00:00
|
|
|
if (new_vic != hdmi_ctrl->vid_cfg.vic)
|
2024-09-09 08:52:07 +00:00
|
|
|
rc = 1;
|
|
|
|
else
|
|
|
|
DEV_DBG("%s: no res change.\n", __func__);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MDSS_EVENT_RESUME:
|
|
|
|
if (hdmi_ctrl->hpd_feature_on) {
|
2024-09-09 08:57:42 +00:00
|
|
|
reinit_completion(&hdmi_ctrl->hpd_int_done);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
rc = hdmi_tx_hpd_on(hdmi_ctrl);
|
|
|
|
if (rc)
|
|
|
|
DEV_ERR("%s: hdmi_tx_hpd_on failed. rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MDSS_EVENT_RESET:
|
2024-09-09 08:57:42 +00:00
|
|
|
if (hdmi_ctrl->hpd_initialized) {
|
|
|
|
hdmi_tx_set_mode(hdmi_ctrl, false);
|
|
|
|
hdmi_tx_phy_reset(hdmi_ctrl);
|
|
|
|
hdmi_tx_set_mode(hdmi_ctrl, true);
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
if (hdmi_ctrl->panel_suspend) {
|
|
|
|
u32 timeout;
|
|
|
|
hdmi_ctrl->panel_suspend = false;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
timeout = wait_for_completion_timeout(
|
|
|
|
&hdmi_ctrl->hpd_int_done, HZ/10);
|
|
|
|
if (!timeout && !hdmi_ctrl->hpd_state) {
|
2024-09-09 08:52:07 +00:00
|
|
|
DEV_INFO("%s: cable removed during suspend\n",
|
|
|
|
__func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_tx_send_cable_notification(hdmi_ctrl,
|
|
|
|
false);
|
2024-09-09 08:52:07 +00:00
|
|
|
rc = -EPERM;
|
|
|
|
} else {
|
|
|
|
DEV_DBG("%s: cable present after resume\n",
|
|
|
|
__func__);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MDSS_EVENT_UNBLANK:
|
|
|
|
rc = hdmi_tx_power_on(panel_data);
|
|
|
|
if (rc)
|
|
|
|
DEV_ERR("%s: hdmi_tx_power_on failed. rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MDSS_EVENT_PANEL_ON:
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_tx_update_hdcp_info(hdmi_ctrl);
|
|
|
|
|
|
|
|
rc = hdmi_tx_start_hdcp(hdmi_ctrl);
|
|
|
|
if (rc)
|
|
|
|
DEV_ERR("%s: hdcp start failed rc=%d\n", __func__, rc);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
hdmi_ctrl->timing_gen_on = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MDSS_EVENT_SUSPEND:
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_lock(&hdmi_ctrl->power_mutex);
|
|
|
|
if (!hdmi_ctrl->panel_power_on &&
|
|
|
|
!hdmi_ctrl->hpd_off_pending && !hdmi_ctrl->hpd_state) {
|
|
|
|
mutex_unlock(&hdmi_ctrl->power_mutex);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (hdmi_ctrl->hpd_feature_on)
|
|
|
|
hdmi_tx_hpd_off(hdmi_ctrl);
|
|
|
|
|
|
|
|
hdmi_ctrl->panel_suspend = false;
|
|
|
|
} else {
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->hpd_state = 0;
|
|
|
|
mutex_unlock(&hdmi_ctrl->power_mutex);
|
2024-09-09 08:52:07 +00:00
|
|
|
hdmi_ctrl->hpd_off_pending = true;
|
|
|
|
hdmi_ctrl->panel_suspend = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MDSS_EVENT_BLANK:
|
2024-09-09 08:57:42 +00:00
|
|
|
if (hdmi_tx_is_hdcp_enabled(hdmi_ctrl)) {
|
|
|
|
flush_delayed_work(&hdmi_ctrl->hdcp_cb_work);
|
|
|
|
|
|
|
|
DEV_DBG("%s: Turning off HDCP\n", __func__);
|
|
|
|
hdmi_ctrl->hdcp_ops->hdmi_hdcp_off(
|
|
|
|
hdmi_ctrl->hdcp_data);
|
|
|
|
|
|
|
|
hdmi_ctrl->hdcp_ops = NULL;
|
|
|
|
|
|
|
|
rc = hdmi_tx_enable_power(hdmi_ctrl, HDMI_TX_DDC_PM,
|
|
|
|
false);
|
|
|
|
if (rc)
|
|
|
|
DEV_ERR("%s: Failed to disable ddc power\n",
|
|
|
|
__func__);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MDSS_EVENT_PANEL_OFF:
|
|
|
|
mutex_lock(&hdmi_ctrl->power_mutex);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (hdmi_ctrl->panel_power_on) {
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_unlock(&hdmi_ctrl->power_mutex);
|
|
|
|
hdmi_tx_config_avmute(hdmi_ctrl, 1);
|
2024-09-09 08:52:07 +00:00
|
|
|
rc = hdmi_tx_power_off(panel_data);
|
|
|
|
if (rc)
|
|
|
|
DEV_ERR("%s: hdmi_tx_power_off failed.rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
} else {
|
2024-09-09 08:57:42 +00:00
|
|
|
mutex_unlock(&hdmi_ctrl->power_mutex);
|
2024-09-09 08:52:07 +00:00
|
|
|
DEV_DBG("%s: hdmi is already powered off\n", __func__);
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_ctrl->timing_gen_on = false;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MDSS_EVENT_CLOSE:
|
2024-09-09 08:57:42 +00:00
|
|
|
if (panel_data->panel_info.cont_splash_enabled) {
|
|
|
|
hdmi_tx_power_off(panel_data);
|
|
|
|
panel_data->panel_info.cont_splash_enabled = false;
|
|
|
|
} else {
|
|
|
|
if (hdmi_ctrl->hpd_feature_on &&
|
|
|
|
hdmi_ctrl->hpd_initialized &&
|
|
|
|
!hdmi_ctrl->hpd_state)
|
|
|
|
hdmi_tx_hpd_polarity_setup(hdmi_ctrl,
|
|
|
|
HPD_CONNECT_POLARITY);
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
} /* hdmi_tx_panel_event_handler */
|
|
|
|
|
|
|
|
static int hdmi_tx_register_panel(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_ctrl->panel_data.event_handler = hdmi_tx_panel_event_handler;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!hdmi_ctrl->pdata.primary)
|
|
|
|
hdmi_ctrl->vid_cfg.vic = DEFAULT_VIDEO_RESOLUTION;
|
|
|
|
|
|
|
|
rc = hdmi_tx_init_panel_info(hdmi_ctrl);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: hdmi_init_panel_info failed\n", __func__);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = mdss_register_panel(hdmi_ctrl->pdev, &hdmi_ctrl->panel_data);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: FAILED: to register HDMI panel\n", __func__);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
rc = hdmi_ctrl->mdss_util->register_irq(&hdmi_tx_hw);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (rc)
|
|
|
|
DEV_ERR("%s: mdss_register_irq failed.\n", __func__);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
} /* hdmi_tx_register_panel */
|
|
|
|
|
|
|
|
static void hdmi_tx_deinit_resource(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* VREG & CLK */
|
|
|
|
for (i = HDMI_TX_MAX_PM - 1; i >= 0; i--) {
|
|
|
|
if (hdmi_tx_config_power(hdmi_ctrl, i, 0))
|
|
|
|
DEV_ERR("%s: '%s' power deconfig fail\n",
|
|
|
|
__func__, hdmi_tx_pm_name(i));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* IO */
|
2024-09-09 08:57:42 +00:00
|
|
|
for (i = HDMI_TX_MAX_IO - 1; i >= 0; i--) {
|
|
|
|
if (hdmi_ctrl->pdata.io[i].base)
|
|
|
|
msm_dss_iounmap(&hdmi_ctrl->pdata.io[i]);
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
} /* hdmi_tx_deinit_resource */
|
|
|
|
|
|
|
|
static int hdmi_tx_init_resource(struct hdmi_tx_ctrl *hdmi_ctrl)
|
|
|
|
{
|
|
|
|
int i, rc = 0;
|
|
|
|
struct hdmi_tx_platform_data *pdata = NULL;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
pdata = &hdmi_ctrl->pdata;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_tx_pinctrl_init(hdmi_ctrl->pdev);
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
/* IO */
|
|
|
|
for (i = 0; i < HDMI_TX_MAX_IO; i++) {
|
|
|
|
rc = msm_dss_ioremap_byname(hdmi_ctrl->pdev, &pdata->io[i],
|
|
|
|
hdmi_tx_io_name(i));
|
|
|
|
if (rc) {
|
2024-09-09 08:57:42 +00:00
|
|
|
DEV_DBG("%s: '%s' remap failed or not available\n",
|
|
|
|
__func__, hdmi_tx_io_name(i));
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
DEV_INFO("%s: '%s': start = 0x%p, len=0x%x\n", __func__,
|
|
|
|
hdmi_tx_io_name(i), pdata->io[i].base,
|
2024-09-09 08:52:07 +00:00
|
|
|
pdata->io[i].len);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* VREG & CLK */
|
|
|
|
for (i = 0; i < HDMI_TX_MAX_PM; i++) {
|
|
|
|
rc = hdmi_tx_config_power(hdmi_ctrl, i, 1);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: '%s' power config failed.rc=%d\n",
|
|
|
|
__func__, hdmi_tx_pm_name(i), rc);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
error:
|
|
|
|
hdmi_tx_deinit_resource(hdmi_ctrl);
|
|
|
|
return rc;
|
|
|
|
} /* hdmi_tx_init_resource */
|
|
|
|
|
|
|
|
static void hdmi_tx_put_dt_clk_data(struct device *dev,
|
|
|
|
struct dss_module_power *module_power)
|
|
|
|
{
|
|
|
|
if (!module_power) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (module_power->clk_config) {
|
|
|
|
devm_kfree(dev, module_power->clk_config);
|
|
|
|
module_power->clk_config = NULL;
|
|
|
|
}
|
|
|
|
module_power->num_clk = 0;
|
|
|
|
} /* hdmi_tx_put_dt_clk_data */
|
|
|
|
|
|
|
|
/* todo: once clk are moved to device tree then change this implementation */
|
|
|
|
static int hdmi_tx_get_dt_clk_data(struct device *dev,
|
|
|
|
struct dss_module_power *mp, u32 module_type)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
if (!dev || !mp) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
return -EINVAL;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEV_DBG("%s: module: '%s'\n", __func__, hdmi_tx_pm_name(module_type));
|
|
|
|
|
|
|
|
switch (module_type) {
|
|
|
|
case HDMI_TX_HPD_PM:
|
2024-09-09 08:57:42 +00:00
|
|
|
mp->num_clk = 4;
|
2024-09-09 08:52:07 +00:00
|
|
|
mp->clk_config = devm_kzalloc(dev, sizeof(struct dss_clk) *
|
|
|
|
mp->num_clk, GFP_KERNEL);
|
|
|
|
if (!mp->clk_config) {
|
|
|
|
DEV_ERR("%s: can't alloc '%s' clk mem\n", __func__,
|
|
|
|
hdmi_tx_pm_name(module_type));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintf(mp->clk_config[0].clk_name, 32, "%s", "iface_clk");
|
|
|
|
mp->clk_config[0].type = DSS_CLK_AHB;
|
|
|
|
mp->clk_config[0].rate = 0;
|
|
|
|
|
|
|
|
snprintf(mp->clk_config[1].clk_name, 32, "%s", "core_clk");
|
|
|
|
mp->clk_config[1].type = DSS_CLK_OTHER;
|
|
|
|
mp->clk_config[1].rate = 19200000;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This clock is required to clock MDSS interrupt registers
|
|
|
|
* when HDMI is the only block turned on within MDSS. Since
|
|
|
|
* rate for this clock is controlled by MDP driver, treat this
|
|
|
|
* similar to AHB clock and do not set rate for it.
|
|
|
|
*/
|
|
|
|
snprintf(mp->clk_config[2].clk_name, 32, "%s", "mdp_core_clk");
|
|
|
|
mp->clk_config[2].type = DSS_CLK_AHB;
|
|
|
|
mp->clk_config[2].rate = 0;
|
2024-09-09 08:57:42 +00:00
|
|
|
|
|
|
|
snprintf(mp->clk_config[3].clk_name, 32, "%s", "alt_iface_clk");
|
|
|
|
mp->clk_config[3].type = DSS_CLK_AHB;
|
|
|
|
mp->clk_config[3].rate = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case HDMI_TX_CORE_PM:
|
2024-09-09 08:57:42 +00:00
|
|
|
mp->num_clk = 1;
|
2024-09-09 08:52:07 +00:00
|
|
|
mp->clk_config = devm_kzalloc(dev, sizeof(struct dss_clk) *
|
|
|
|
mp->num_clk, GFP_KERNEL);
|
|
|
|
if (!mp->clk_config) {
|
|
|
|
DEV_ERR("%s: can't alloc '%s' clk mem\n", __func__,
|
|
|
|
hdmi_tx_pm_name(module_type));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintf(mp->clk_config[0].clk_name, 32, "%s", "extp_clk");
|
|
|
|
mp->clk_config[0].type = DSS_CLK_PCLK;
|
|
|
|
/* This rate will be overwritten when core is powered on */
|
|
|
|
mp->clk_config[0].rate = 148500000;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case HDMI_TX_DDC_PM:
|
|
|
|
case HDMI_TX_CEC_PM:
|
|
|
|
mp->num_clk = 0;
|
|
|
|
DEV_DBG("%s: no clk\n", __func__);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
DEV_ERR("%s: invalid module type=%d\n", __func__,
|
|
|
|
module_type);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
error:
|
|
|
|
if (mp->clk_config) {
|
|
|
|
devm_kfree(dev, mp->clk_config);
|
|
|
|
mp->clk_config = NULL;
|
|
|
|
}
|
|
|
|
mp->num_clk = 0;
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
} /* hdmi_tx_get_dt_clk_data */
|
|
|
|
|
|
|
|
static void hdmi_tx_put_dt_vreg_data(struct device *dev,
|
|
|
|
struct dss_module_power *module_power)
|
|
|
|
{
|
|
|
|
if (!module_power) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (module_power->vreg_config) {
|
|
|
|
devm_kfree(dev, module_power->vreg_config);
|
|
|
|
module_power->vreg_config = NULL;
|
|
|
|
}
|
|
|
|
module_power->num_vreg = 0;
|
|
|
|
} /* hdmi_tx_put_dt_vreg_data */
|
|
|
|
|
|
|
|
static int hdmi_tx_get_dt_vreg_data(struct device *dev,
|
|
|
|
struct dss_module_power *mp, u32 module_type)
|
|
|
|
{
|
|
|
|
int i, j, rc = 0;
|
|
|
|
int dt_vreg_total = 0, mod_vreg_total = 0;
|
|
|
|
u32 ndx_mask = 0;
|
|
|
|
u32 *val_array = NULL;
|
|
|
|
const char *mod_name = NULL;
|
|
|
|
struct device_node *of_node = NULL;
|
|
|
|
|
|
|
|
if (!dev || !mp) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
2024-09-09 08:57:42 +00:00
|
|
|
return -EINVAL;
|
2024-09-09 08:52:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch (module_type) {
|
|
|
|
case HDMI_TX_HPD_PM:
|
|
|
|
mod_name = "hpd";
|
|
|
|
break;
|
|
|
|
case HDMI_TX_DDC_PM:
|
|
|
|
mod_name = "ddc";
|
|
|
|
break;
|
|
|
|
case HDMI_TX_CORE_PM:
|
|
|
|
mod_name = "core";
|
|
|
|
break;
|
|
|
|
case HDMI_TX_CEC_PM:
|
|
|
|
mod_name = "cec";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
DEV_ERR("%s: invalid module type=%d\n", __func__,
|
|
|
|
module_type);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEV_DBG("%s: module: '%s'\n", __func__, hdmi_tx_pm_name(module_type));
|
|
|
|
|
|
|
|
of_node = dev->of_node;
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
dt_vreg_total = of_property_count_strings(of_node, "qcom,supply-names");
|
2024-09-09 08:52:07 +00:00
|
|
|
if (dt_vreg_total < 0) {
|
|
|
|
DEV_ERR("%s: vreg not found. rc=%d\n", __func__,
|
|
|
|
dt_vreg_total);
|
|
|
|
rc = dt_vreg_total;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* count how many vreg for particular hdmi module */
|
|
|
|
for (i = 0; i < dt_vreg_total; i++) {
|
|
|
|
const char *st = NULL;
|
|
|
|
rc = of_property_read_string_index(of_node,
|
2024-09-09 08:57:42 +00:00
|
|
|
"qcom,supply-names", i, &st);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: error reading name. i=%d, rc=%d\n",
|
|
|
|
__func__, i, rc);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strnstr(st, mod_name, strlen(st))) {
|
|
|
|
ndx_mask |= BIT(i);
|
|
|
|
mod_vreg_total++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mod_vreg_total > 0) {
|
|
|
|
mp->num_vreg = mod_vreg_total;
|
|
|
|
mp->vreg_config = devm_kzalloc(dev, sizeof(struct dss_vreg) *
|
|
|
|
mod_vreg_total, GFP_KERNEL);
|
|
|
|
if (!mp->vreg_config) {
|
|
|
|
DEV_ERR("%s: can't alloc '%s' vreg mem\n", __func__,
|
|
|
|
hdmi_tx_pm_name(module_type));
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
DEV_DBG("%s: no vreg\n", __func__);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
val_array = devm_kzalloc(dev, sizeof(u32) * dt_vreg_total, GFP_KERNEL);
|
|
|
|
if (!val_array) {
|
|
|
|
DEV_ERR("%s: can't allocate vreg scratch mem\n", __func__);
|
|
|
|
rc = -ENOMEM;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0, j = 0; (i < dt_vreg_total) && (j < mod_vreg_total); i++) {
|
|
|
|
const char *st = NULL;
|
|
|
|
|
|
|
|
if (!(ndx_mask & BIT(0))) {
|
|
|
|
ndx_mask >>= 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* vreg-name */
|
|
|
|
rc = of_property_read_string_index(of_node,
|
2024-09-09 08:57:42 +00:00
|
|
|
"qcom,supply-names", i, &st);
|
2024-09-09 08:52:07 +00:00
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: error reading name. i=%d, rc=%d\n",
|
|
|
|
__func__, i, rc);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
snprintf(mp->vreg_config[j].vreg_name, 32, "%s", st);
|
|
|
|
|
|
|
|
/* vreg-min-voltage */
|
|
|
|
memset(val_array, 0, sizeof(u32) * dt_vreg_total);
|
|
|
|
rc = of_property_read_u32_array(of_node,
|
2024-09-09 08:57:42 +00:00
|
|
|
"qcom,min-voltage-level", val_array,
|
2024-09-09 08:52:07 +00:00
|
|
|
dt_vreg_total);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: error read '%s' min volt. rc=%d\n",
|
|
|
|
__func__, hdmi_tx_pm_name(module_type), rc);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
mp->vreg_config[j].min_voltage = val_array[i];
|
|
|
|
|
|
|
|
/* vreg-max-voltage */
|
|
|
|
memset(val_array, 0, sizeof(u32) * dt_vreg_total);
|
|
|
|
rc = of_property_read_u32_array(of_node,
|
2024-09-09 08:57:42 +00:00
|
|
|
"qcom,max-voltage-level", val_array,
|
2024-09-09 08:52:07 +00:00
|
|
|
dt_vreg_total);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: error read '%s' max volt. rc=%d\n",
|
|
|
|
__func__, hdmi_tx_pm_name(module_type), rc);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
mp->vreg_config[j].max_voltage = val_array[i];
|
|
|
|
|
|
|
|
/* vreg-op-mode */
|
|
|
|
memset(val_array, 0, sizeof(u32) * dt_vreg_total);
|
|
|
|
rc = of_property_read_u32_array(of_node,
|
2024-09-09 08:57:42 +00:00
|
|
|
"qcom,enable-load", val_array,
|
|
|
|
dt_vreg_total);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: error read '%s' enable load. rc=%d\n",
|
|
|
|
__func__, hdmi_tx_pm_name(module_type), rc);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
mp->vreg_config[j].enable_load = val_array[i];
|
|
|
|
|
|
|
|
memset(val_array, 0, sizeof(u32) * dt_vreg_total);
|
|
|
|
rc = of_property_read_u32_array(of_node,
|
|
|
|
"qcom,disable-load", val_array,
|
2024-09-09 08:52:07 +00:00
|
|
|
dt_vreg_total);
|
|
|
|
if (rc) {
|
2024-09-09 08:57:42 +00:00
|
|
|
DEV_ERR("%s: error read '%s' disable load. rc=%d\n",
|
2024-09-09 08:52:07 +00:00
|
|
|
__func__, hdmi_tx_pm_name(module_type), rc);
|
|
|
|
goto error;
|
|
|
|
}
|
2024-09-09 08:57:42 +00:00
|
|
|
mp->vreg_config[j].disable_load = val_array[i];
|
2024-09-09 08:52:07 +00:00
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
DEV_DBG("%s: %s min=%d, max=%d, enable=%d disable=%d\n",
|
|
|
|
__func__,
|
2024-09-09 08:52:07 +00:00
|
|
|
mp->vreg_config[j].vreg_name,
|
|
|
|
mp->vreg_config[j].min_voltage,
|
|
|
|
mp->vreg_config[j].max_voltage,
|
2024-09-09 08:57:42 +00:00
|
|
|
mp->vreg_config[j].enable_load,
|
|
|
|
mp->vreg_config[j].disable_load);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
ndx_mask >>= 1;
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
|
|
|
|
devm_kfree(dev, val_array);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
error:
|
|
|
|
if (mp->vreg_config) {
|
|
|
|
devm_kfree(dev, mp->vreg_config);
|
|
|
|
mp->vreg_config = NULL;
|
|
|
|
}
|
|
|
|
mp->num_vreg = 0;
|
|
|
|
|
|
|
|
if (val_array)
|
|
|
|
devm_kfree(dev, val_array);
|
|
|
|
return rc;
|
|
|
|
} /* hdmi_tx_get_dt_vreg_data */
|
|
|
|
|
|
|
|
static void hdmi_tx_put_dt_gpio_data(struct device *dev,
|
|
|
|
struct dss_module_power *module_power)
|
|
|
|
{
|
|
|
|
if (!module_power) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (module_power->gpio_config) {
|
|
|
|
devm_kfree(dev, module_power->gpio_config);
|
|
|
|
module_power->gpio_config = NULL;
|
|
|
|
}
|
|
|
|
module_power->num_gpio = 0;
|
|
|
|
} /* hdmi_tx_put_dt_gpio_data */
|
|
|
|
|
|
|
|
static int hdmi_tx_get_dt_gpio_data(struct device *dev,
|
|
|
|
struct dss_module_power *mp, u32 module_type)
|
|
|
|
{
|
|
|
|
int i, j;
|
|
|
|
int mp_gpio_cnt = 0, gpio_list_size = 0;
|
|
|
|
struct dss_gpio *gpio_list = NULL;
|
|
|
|
struct device_node *of_node = NULL;
|
|
|
|
|
|
|
|
DEV_DBG("%s: module: '%s'\n", __func__, hdmi_tx_pm_name(module_type));
|
|
|
|
|
|
|
|
if (!dev || !mp) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
of_node = dev->of_node;
|
|
|
|
|
|
|
|
switch (module_type) {
|
|
|
|
case HDMI_TX_HPD_PM:
|
|
|
|
gpio_list_size = ARRAY_SIZE(hpd_gpio_config);
|
|
|
|
gpio_list = hpd_gpio_config;
|
|
|
|
break;
|
|
|
|
case HDMI_TX_DDC_PM:
|
|
|
|
gpio_list_size = ARRAY_SIZE(ddc_gpio_config);
|
|
|
|
gpio_list = ddc_gpio_config;
|
|
|
|
break;
|
|
|
|
case HDMI_TX_CORE_PM:
|
|
|
|
gpio_list_size = ARRAY_SIZE(core_gpio_config);
|
|
|
|
gpio_list = core_gpio_config;
|
|
|
|
break;
|
|
|
|
case HDMI_TX_CEC_PM:
|
|
|
|
gpio_list_size = ARRAY_SIZE(cec_gpio_config);
|
|
|
|
gpio_list = cec_gpio_config;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
DEV_ERR("%s: invalid module type=%d\n", __func__,
|
|
|
|
module_type);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < gpio_list_size; i++)
|
|
|
|
if (of_find_property(of_node, gpio_list[i].gpio_name, NULL))
|
|
|
|
mp_gpio_cnt++;
|
|
|
|
|
|
|
|
if (!mp_gpio_cnt) {
|
|
|
|
DEV_DBG("%s: no gpio\n", __func__);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEV_DBG("%s: mp_gpio_cnt = %d\n", __func__, mp_gpio_cnt);
|
|
|
|
mp->num_gpio = mp_gpio_cnt;
|
|
|
|
|
|
|
|
mp->gpio_config = devm_kzalloc(dev, sizeof(struct dss_gpio) *
|
|
|
|
mp_gpio_cnt, GFP_KERNEL);
|
|
|
|
if (!mp->gpio_config) {
|
|
|
|
DEV_ERR("%s: can't alloc '%s' gpio mem\n", __func__,
|
|
|
|
hdmi_tx_pm_name(module_type));
|
|
|
|
|
|
|
|
mp->num_gpio = 0;
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0, j = 0; i < gpio_list_size; i++) {
|
|
|
|
int gpio = of_get_named_gpio(of_node,
|
|
|
|
gpio_list[i].gpio_name, 0);
|
|
|
|
if (gpio < 0) {
|
|
|
|
DEV_DBG("%s: no gpio named %s\n", __func__,
|
|
|
|
gpio_list[i].gpio_name);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
memcpy(&mp->gpio_config[j], &gpio_list[i],
|
|
|
|
sizeof(struct dss_gpio));
|
|
|
|
|
|
|
|
mp->gpio_config[j].gpio = (unsigned)gpio;
|
|
|
|
|
|
|
|
DEV_DBG("%s: gpio num=%d, name=%s, value=%d\n",
|
|
|
|
__func__, mp->gpio_config[j].gpio,
|
|
|
|
mp->gpio_config[j].gpio_name,
|
|
|
|
mp->gpio_config[j].value);
|
|
|
|
j++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
} /* hdmi_tx_get_dt_gpio_data */
|
|
|
|
|
|
|
|
static void hdmi_tx_put_dt_data(struct device *dev,
|
|
|
|
struct hdmi_tx_platform_data *pdata)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
if (!dev || !pdata) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = HDMI_TX_MAX_PM - 1; i >= 0; i--)
|
|
|
|
hdmi_tx_put_dt_clk_data(dev, &pdata->power_data[i]);
|
|
|
|
|
|
|
|
for (i = HDMI_TX_MAX_PM - 1; i >= 0; i--)
|
|
|
|
hdmi_tx_put_dt_vreg_data(dev, &pdata->power_data[i]);
|
|
|
|
|
|
|
|
for (i = HDMI_TX_MAX_PM - 1; i >= 0; i--)
|
|
|
|
hdmi_tx_put_dt_gpio_data(dev, &pdata->power_data[i]);
|
|
|
|
} /* hdmi_tx_put_dt_data */
|
|
|
|
|
|
|
|
static int hdmi_tx_get_dt_data(struct platform_device *pdev,
|
|
|
|
struct hdmi_tx_platform_data *pdata)
|
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
int i, rc = 0, len = 0;
|
2024-09-09 08:52:07 +00:00
|
|
|
struct device_node *of_node = NULL;
|
2024-09-09 08:57:42 +00:00
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev);
|
|
|
|
const char *data;
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
if (!pdev || !pdata) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
of_node = pdev->dev.of_node;
|
|
|
|
|
|
|
|
rc = of_property_read_u32(of_node, "cell-index", &pdev->id);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: dev id from dt not found.rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
DEV_DBG("%s: id=%d\n", __func__, pdev->id);
|
|
|
|
|
|
|
|
/* GPIO */
|
|
|
|
for (i = 0; i < HDMI_TX_MAX_PM; i++) {
|
|
|
|
rc = hdmi_tx_get_dt_gpio_data(&pdev->dev,
|
|
|
|
&pdata->power_data[i], i);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: '%s' get_dt_gpio_data failed.rc=%d\n",
|
|
|
|
__func__, hdmi_tx_pm_name(i), rc);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* VREG */
|
|
|
|
for (i = 0; i < HDMI_TX_MAX_PM; i++) {
|
|
|
|
rc = hdmi_tx_get_dt_vreg_data(&pdev->dev,
|
|
|
|
&pdata->power_data[i], i);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: '%s' get_dt_vreg_data failed.rc=%d\n",
|
|
|
|
__func__, hdmi_tx_pm_name(i), rc);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* CLK */
|
|
|
|
for (i = 0; i < HDMI_TX_MAX_PM; i++) {
|
|
|
|
rc = hdmi_tx_get_dt_clk_data(&pdev->dev,
|
|
|
|
&pdata->power_data[i], i);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: '%s' get_dt_clk_data failed.rc=%d\n",
|
|
|
|
__func__, hdmi_tx_pm_name(i), rc);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (!hdmi_ctrl->pdata.primary)
|
|
|
|
hdmi_ctrl->pdata.primary = of_property_read_bool(
|
|
|
|
pdev->dev.of_node, "qcom,primary_panel");
|
|
|
|
|
|
|
|
pdata->cond_power_on = of_property_read_bool(pdev->dev.of_node,
|
|
|
|
"qcom,conditional-power-on");
|
|
|
|
|
|
|
|
if (!pdata->cont_splash_enabled)
|
|
|
|
pdata->cont_splash_enabled =
|
|
|
|
hdmi_ctrl->mdss_util->panel_intf_status(DISPLAY_3,
|
|
|
|
MDSS_PANEL_INTF_HDMI) ? true : false;
|
|
|
|
|
|
|
|
pdata->pluggable = of_property_read_bool(pdev->dev.of_node,
|
|
|
|
"qcom,pluggable");
|
|
|
|
|
|
|
|
data = of_get_property(pdev->dev.of_node, "qcom,display-id", &len);
|
|
|
|
if (!data || len <= 0)
|
|
|
|
pr_err("%s:%d Unable to read qcom,display-id, data=%p,len=%d\n",
|
|
|
|
__func__, __LINE__, data, len);
|
|
|
|
else
|
|
|
|
snprintf(hdmi_ctrl->panel_data.panel_info.display_id,
|
|
|
|
MDSS_DISPLAY_ID_MAX_LEN, "%s", data);
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
error:
|
|
|
|
hdmi_tx_put_dt_data(&pdev->dev, pdata);
|
|
|
|
return rc;
|
|
|
|
} /* hdmi_tx_get_dt_data */
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static void hdmi_tx_audio_tear_down(struct hdmi_tx_ctrl *hdmi_ctrl)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
2024-09-09 08:57:42 +00:00
|
|
|
struct dss_io_data *io;
|
|
|
|
u32 audio_pkt_ctrl;
|
|
|
|
u32 audio_eng_cfg;
|
|
|
|
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: invalid input\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
io = &hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO];
|
|
|
|
if (!io->base) {
|
|
|
|
DEV_ERR("%s: Core io is not initialized\n", __func__);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
audio_pkt_ctrl = DSS_REG_R(io, HDMI_AUDIO_PKT_CTRL);
|
|
|
|
audio_eng_cfg = DSS_REG_R(io, HDMI_AUDIO_CFG);
|
|
|
|
|
|
|
|
if ((audio_pkt_ctrl & BIT(0)) || (audio_eng_cfg & BIT(0))) {
|
|
|
|
u32 lpa_dma, i = 0;
|
|
|
|
|
|
|
|
void __iomem *lpa_base = ioremap(LPASS_LPAIF_RDDMA_CTL0, 0xFF);
|
|
|
|
|
|
|
|
lpa_dma = readl_relaxed(lpa_base + LPASS_LPAIF_RDDMA_PER_CNT0);
|
|
|
|
|
|
|
|
/* Disable audio packet transmission */
|
|
|
|
DSS_REG_W(io, HDMI_AUDIO_PKT_CTRL,
|
|
|
|
DSS_REG_R(io, HDMI_AUDIO_PKT_CTRL) & ~BIT(0));
|
|
|
|
|
|
|
|
/* Wait for LPA DMA Engine to be idle */
|
|
|
|
while (i < LPA_DMA_IDLE_MAX) {
|
|
|
|
u32 val;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* sleep for minimum HW recommended time
|
|
|
|
* for HW status to update.
|
|
|
|
*/
|
|
|
|
msleep(20);
|
|
|
|
|
|
|
|
val = readl_relaxed(lpa_base +
|
|
|
|
LPASS_LPAIF_RDDMA_PER_CNT0);
|
|
|
|
if (val == lpa_dma)
|
|
|
|
break;
|
|
|
|
|
|
|
|
lpa_dma = val;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEV_DBG("%s: LPA DMA idle after %d ms\n", __func__, i * 20);
|
|
|
|
|
|
|
|
/* Disable audio engine */
|
|
|
|
DSS_REG_W(io, HDMI_AUDIO_CFG,
|
|
|
|
DSS_REG_R(io, HDMI_AUDIO_CFG) & ~BIT(0));
|
|
|
|
|
|
|
|
/* Disable LPA DMA Engine */
|
|
|
|
writel_relaxed(readl_relaxed(lpa_base) & ~BIT(0), lpa_base);
|
|
|
|
|
|
|
|
iounmap(lpa_base);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int hdmi_tx_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
int rc = 0, i;
|
2024-09-09 08:52:07 +00:00
|
|
|
struct device_node *of_node = pdev->dev.of_node;
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = NULL;
|
2024-09-09 08:57:42 +00:00
|
|
|
struct mdss_panel_cfg *pan_cfg = NULL;
|
2024-09-09 08:52:07 +00:00
|
|
|
if (!of_node) {
|
|
|
|
DEV_ERR("%s: FAILED: of_node not found\n", __func__);
|
|
|
|
rc = -ENODEV;
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_ctrl = devm_kzalloc(&pdev->dev, sizeof(*hdmi_ctrl), GFP_KERNEL);
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: FAILED: cannot alloc hdmi tx ctrl\n", __func__);
|
|
|
|
rc = -ENOMEM;
|
|
|
|
goto failed_no_mem;
|
|
|
|
}
|
|
|
|
|
|
|
|
platform_set_drvdata(pdev, hdmi_ctrl);
|
|
|
|
hdmi_ctrl->pdev = pdev;
|
2024-09-09 08:57:42 +00:00
|
|
|
hdmi_ctrl->enc_lvl = HDCP_STATE_AUTH_ENC_NONE;
|
|
|
|
|
|
|
|
pan_cfg = mdss_panel_intf_type(MDSS_PANEL_INTF_HDMI);
|
|
|
|
if (IS_ERR(pan_cfg)) {
|
|
|
|
return PTR_ERR(pan_cfg);
|
|
|
|
} else if (pan_cfg) {
|
|
|
|
int vic;
|
|
|
|
|
|
|
|
if (kstrtoint(pan_cfg->arg_cfg, 10, &vic) ||
|
|
|
|
vic <= HDMI_VFRMT_UNKNOWN || vic >= HDMI_VFRMT_MAX)
|
|
|
|
vic = DEFAULT_HDMI_PRIMARY_RESOLUTION;
|
|
|
|
|
|
|
|
hdmi_ctrl->pdata.primary = true;
|
|
|
|
hdmi_ctrl->vid_cfg.vic = vic;
|
|
|
|
hdmi_ctrl->panel_data.panel_info.is_prim_panel = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_ctrl->mdss_util = mdss_get_util_intf();
|
|
|
|
if (hdmi_ctrl->mdss_util == NULL) {
|
|
|
|
pr_err("Failed to get mdss utility functions\n");
|
|
|
|
rc = -ENODEV;
|
|
|
|
goto failed_res_init;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_tx_hw.irq_info = mdss_intr_line();
|
|
|
|
if (hdmi_tx_hw.irq_info == NULL) {
|
|
|
|
pr_err("Failed to get mdss irq information\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
2024-09-09 08:52:07 +00:00
|
|
|
|
|
|
|
rc = hdmi_tx_get_dt_data(pdev, &hdmi_ctrl->pdata);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: FAILED: parsing device tree data. rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
goto failed_dt_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = hdmi_tx_init_resource(hdmi_ctrl);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: FAILED: resource init. rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
goto failed_res_init;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
rc = hdmi_tx_get_version(hdmi_ctrl);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: FAILED: hdmi_tx_get_version. rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
goto failed_reg_panel;
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
rc = hdmi_tx_dev_init(hdmi_ctrl);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: FAILED: hdmi_tx_dev_init. rc=%d\n", __func__, rc);
|
|
|
|
goto failed_dev_init;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = hdmi_tx_register_panel(hdmi_ctrl);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: FAILED: register_panel. rc=%d\n", __func__, rc);
|
|
|
|
goto failed_reg_panel;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = of_platform_populate(of_node, NULL, NULL, &pdev->dev);
|
|
|
|
if (rc) {
|
|
|
|
DEV_ERR("%s: Failed to add child devices. rc=%d\n",
|
|
|
|
__func__, rc);
|
|
|
|
goto failed_reg_panel;
|
|
|
|
} else {
|
|
|
|
DEV_DBG("%s: Add child devices.\n", __func__);
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (mdss_debug_register_io("hdmi",
|
|
|
|
&hdmi_ctrl->pdata.io[HDMI_TX_CORE_IO], NULL))
|
2024-09-09 08:52:07 +00:00
|
|
|
DEV_WARN("%s: hdmi_tx debugfs register failed\n", __func__);
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
if (hdmi_ctrl->panel_data.panel_info.cont_splash_enabled) {
|
|
|
|
for (i = 0; i < HDMI_TX_MAX_PM; i++) {
|
|
|
|
msm_dss_enable_vreg(
|
|
|
|
hdmi_ctrl->pdata.power_data[i].vreg_config,
|
|
|
|
hdmi_ctrl->pdata.power_data[i].num_vreg, 1);
|
|
|
|
|
|
|
|
msm_dss_enable_gpio(
|
|
|
|
hdmi_ctrl->pdata.power_data[i].gpio_config,
|
|
|
|
hdmi_ctrl->pdata.power_data[i].num_gpio, 1);
|
|
|
|
|
|
|
|
msm_dss_enable_clk(
|
|
|
|
hdmi_ctrl->pdata.power_data[i].clk_config,
|
|
|
|
hdmi_ctrl->pdata.power_data[i].num_clk, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_tx_audio_tear_down(hdmi_ctrl);
|
|
|
|
}
|
|
|
|
|
2024-09-09 08:52:07 +00:00
|
|
|
return rc;
|
|
|
|
|
|
|
|
failed_reg_panel:
|
|
|
|
hdmi_tx_dev_deinit(hdmi_ctrl);
|
|
|
|
failed_dev_init:
|
|
|
|
hdmi_tx_deinit_resource(hdmi_ctrl);
|
|
|
|
failed_res_init:
|
|
|
|
hdmi_tx_put_dt_data(&pdev->dev, &hdmi_ctrl->pdata);
|
|
|
|
failed_dt_data:
|
|
|
|
devm_kfree(&pdev->dev, hdmi_ctrl);
|
|
|
|
failed_no_mem:
|
|
|
|
return rc;
|
|
|
|
} /* hdmi_tx_probe */
|
|
|
|
|
2024-09-09 08:57:42 +00:00
|
|
|
static int hdmi_tx_remove(struct platform_device *pdev)
|
2024-09-09 08:52:07 +00:00
|
|
|
{
|
|
|
|
struct hdmi_tx_ctrl *hdmi_ctrl = platform_get_drvdata(pdev);
|
|
|
|
if (!hdmi_ctrl) {
|
|
|
|
DEV_ERR("%s: no driver data\n", __func__);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdmi_tx_sysfs_remove(hdmi_ctrl);
|
|
|
|
hdmi_tx_dev_deinit(hdmi_ctrl);
|
|
|
|
hdmi_tx_deinit_resource(hdmi_ctrl);
|
|
|
|
hdmi_tx_put_dt_data(&pdev->dev, &hdmi_ctrl->pdata);
|
|
|
|
devm_kfree(&hdmi_ctrl->pdev->dev, hdmi_ctrl);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
} /* hdmi_tx_remove */
|
|
|
|
|
|
|
|
static const struct of_device_id hdmi_tx_dt_match[] = {
|
|
|
|
{.compatible = COMPATIBLE_NAME,},
|
2024-09-09 08:57:42 +00:00
|
|
|
{ /* Sentinel */ },
|
2024-09-09 08:52:07 +00:00
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, hdmi_tx_dt_match);
|
|
|
|
|
|
|
|
static struct platform_driver this_driver = {
|
|
|
|
.probe = hdmi_tx_probe,
|
|
|
|
.remove = hdmi_tx_remove,
|
|
|
|
.driver = {
|
|
|
|
.name = DRV_NAME,
|
|
|
|
.of_match_table = hdmi_tx_dt_match,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __init hdmi_tx_drv_init(void)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
rc = platform_driver_register(&this_driver);
|
|
|
|
if (rc)
|
|
|
|
DEV_ERR("%s: FAILED: rc=%d\n", __func__, rc);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
} /* hdmi_tx_drv_init */
|
|
|
|
|
|
|
|
static void __exit hdmi_tx_drv_exit(void)
|
|
|
|
{
|
|
|
|
platform_driver_unregister(&this_driver);
|
|
|
|
} /* hdmi_tx_drv_exit */
|
|
|
|
|
|
|
|
static int set_hdcp_feature_on(const char *val, const struct kernel_param *kp)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
rc = param_set_bool(val, kp);
|
|
|
|
if (!rc)
|
|
|
|
pr_debug("%s: HDCP feature = %d\n", __func__, hdcp_feature_on);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct kernel_param_ops hdcp_feature_on_param_ops = {
|
|
|
|
.set = set_hdcp_feature_on,
|
|
|
|
.get = param_get_bool,
|
|
|
|
};
|
|
|
|
|
|
|
|
module_param_cb(hdcp, &hdcp_feature_on_param_ops, &hdcp_feature_on,
|
|
|
|
S_IRUGO | S_IWUSR);
|
|
|
|
MODULE_PARM_DESC(hdcp, "Enable or Disable HDCP");
|
|
|
|
|
|
|
|
module_init(hdmi_tx_drv_init);
|
|
|
|
module_exit(hdmi_tx_drv_exit);
|
|
|
|
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
|
|
MODULE_VERSION("0.3");
|
|
|
|
MODULE_DESCRIPTION("HDMI MSM TX driver");
|