blob: 6bb39f9795d1683d1616ddce528b265bf998d7a2 [file] [log] [blame]
/*
* Copyright (C) 2011-2016 Freescale Semiconductor, Inc.
*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations:
*
* http://www.opensource.org/licenses/gpl-license.html
* http://www.gnu.org/copyleft/gpl.html
*/
/*
* SH-Mobile High-Definition Multimedia Interface (HDMI) driver
* for SLISHDMI13T and SLIPHDMIT IP cores
*
* Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/io.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/uaccess.h>
#include <linux/cpufreq.h>
#include <linux/firmware.h>
#include <linux/kthread.h>
#include <linux/regulator/driver.h>
#include <linux/fsl_devices.h>
#include <linux/ipu.h>
#include <linux/regmap.h>
#include <linux/pinctrl/consumer.h>
#include <linux/of_device.h>
#include <linux/console.h>
#include <linux/types.h>
#include "../edid.h"
#include <video/mxc_edid.h>
#include <video/mxc_hdmi.h>
#include "mxc_dispdrv.h"
#include <linux/mfd/mxc-hdmi-core.h>
#define DISPDRV_HDMI "hdmi"
#define HDMI_EDID_LEN 512
/* status codes for reading edid */
#define HDMI_EDID_SUCCESS 0
#define HDMI_EDID_FAIL -1
#define HDMI_EDID_SAME -2
#define HDMI_EDID_NO_MODES -3
#define NUM_CEA_VIDEO_MODES 64
#define DEFAULT_VIDEO_MODE 16 /* 1080P */
#define RGB 0
#define YCBCR444 1
#define YCBCR422_16BITS 2
#define YCBCR422_8BITS 3
#define XVYCC444 4
/*
* We follow a flowchart which is in the "Synopsys DesignWare Courses
* HDMI Transmitter Controller User Guide, 1.30a", section 3.1
* (dwc_hdmi_tx_user.pdf)
*
* Below are notes that say "HDMI Initialization Step X"
* These correspond to the flowchart.
*/
/*
* We are required to configure VGA mode before reading edid
* in HDMI Initialization Step B
*/
static const struct fb_videomode vga_mode = {
/* 640x480 @ 60 Hz, 31.5 kHz hsync */
NULL, 60, 640, 480, 39721, 48, 16, 33, 10, 96, 2, 0,
FB_VMODE_NONINTERLACED | FB_VMODE_ASPECT_4_3, FB_MODE_IS_VESA,
};
enum hdmi_datamap {
RGB444_8B = 0x01,
RGB444_10B = 0x03,
RGB444_12B = 0x05,
RGB444_16B = 0x07,
YCbCr444_8B = 0x09,
YCbCr444_10B = 0x0B,
YCbCr444_12B = 0x0D,
YCbCr444_16B = 0x0F,
YCbCr422_8B = 0x16,
YCbCr422_10B = 0x14,
YCbCr422_12B = 0x12,
};
enum hdmi_colorimetry {
eITU601,
eITU709,
};
struct hdmi_vmode {
bool mDVI;
bool mHSyncPolarity;
bool mVSyncPolarity;
bool mInterlaced;
bool mDataEnablePolarity;
unsigned int mPixelClock;
unsigned int mPixelRepetitionInput;
unsigned int mPixelRepetitionOutput;
};
struct hdmi_data_info {
unsigned int enc_in_format;
unsigned int enc_out_format;
unsigned int enc_color_depth;
unsigned int colorimetry;
unsigned int pix_repet_factor;
unsigned int hdcp_enable;
unsigned int rgb_out_enable;
struct hdmi_vmode video_mode;
};
struct hdmi_phy_reg_config {
/* HDMI PHY register config for pass HCT */
u16 reg_vlev;
u16 reg_cksymtx;
};
struct mxc_hdmi {
struct platform_device *pdev;
struct platform_device *core_pdev;
struct mxc_dispdrv_handle *disp_mxc_hdmi;
struct fb_info *fbi;
struct clk *hdmi_isfr_clk;
struct clk *hdmi_iahb_clk;
struct clk *mipi_core_clk;
struct delayed_work hotplug_work;
struct delayed_work hdcp_hdp_work;
struct notifier_block nb;
struct hdmi_data_info hdmi_data;
int vic;
struct mxc_edid_cfg edid_cfg;
u8 edid[HDMI_EDID_LEN];
bool fb_reg;
bool cable_plugin;
u8 blank;
bool dft_mode_set;
char *dft_mode_str;
int default_bpp;
u8 latest_intr_stat;
bool irq_enabled;
spinlock_t irq_lock;
bool phy_enabled;
struct fb_videomode default_mode;
struct fb_videomode previous_non_vga_mode;
bool requesting_vga_for_initialization;
int *gpr_base;
int *gpr_hdmi_base;
int *gpr_sdma_base;
int cpu_type;
int cpu_version;
struct hdmi_phy_reg_config phy_config;
struct pinctrl *pinctrl;
};
static int hdmi_major;
static struct class *hdmi_class;
struct i2c_client *hdmi_i2c;
struct mxc_hdmi *g_hdmi;
static bool hdmi_inited;
static bool hdcp_init;
static struct regulator *hdmi_regulator;
extern const struct fb_videomode mxc_cea_mode[64];
extern void mxc_hdmi_cec_handle(u16 cec_stat);
static void mxc_hdmi_setup(struct mxc_hdmi *hdmi, unsigned long event);
static void mxc_hdmi_phy_disable(struct mxc_hdmi *hdmi);
static void hdmi_enable_overflow_interrupts(void);
static void hdmi_disable_overflow_interrupts(void);
static struct platform_device_id imx_hdmi_devtype[] = {
{
.name = "hdmi-imx6DL",
.driver_data = IMX6DL_HDMI,
}, {
.name = "hdmi-imx6Q",
.driver_data = IMX6Q_HDMI,
}, {
/* sentinel */
}
};
MODULE_DEVICE_TABLE(platform, imx_hdmi_devtype);
static const struct of_device_id imx_hdmi_dt_ids[] = {
{ .compatible = "fsl,imx6dl-hdmi-video", .data = &imx_hdmi_devtype[IMX6DL_HDMI], },
{ .compatible = "fsl,imx6q-hdmi-video", .data = &imx_hdmi_devtype[IMX6Q_HDMI], },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_hdmi_dt_ids);
static inline int cpu_is_imx6dl(struct mxc_hdmi *hdmi)
{
return hdmi->cpu_type == IMX6DL_HDMI;
}
#ifdef DEBUG
static void dump_fb_videomode(const struct fb_videomode *m)
{
pr_debug("fb_videomode = %d %d %d %d %d %d %d %d %d %d %d %d %d\n",
m->refresh, m->xres, m->yres, m->pixclock, m->left_margin,
m->right_margin, m->upper_margin, m->lower_margin,
m->hsync_len, m->vsync_len, m->sync, m->vmode, m->flag);
}
#else
static void dump_fb_videomode(const struct fb_videomode *m)
{}
#endif
static ssize_t mxc_hdmi_show_name(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mxc_hdmi *hdmi = dev_get_drvdata(dev);
strcpy(buf, hdmi->fbi->fix.id);
sprintf(buf+strlen(buf), "\n");
return strlen(buf);
}
static DEVICE_ATTR(fb_name, S_IRUGO, mxc_hdmi_show_name, NULL);
static ssize_t mxc_hdmi_show_state(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mxc_hdmi *hdmi = dev_get_drvdata(dev);
if (hdmi->cable_plugin == false)
strcpy(buf, "plugout\n");
else
strcpy(buf, "plugin\n");
return strlen(buf);
}
static DEVICE_ATTR(cable_state, S_IRUGO, mxc_hdmi_show_state, NULL);
static ssize_t mxc_hdmi_show_edid(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mxc_hdmi *hdmi = dev_get_drvdata(dev);
int i, j, len = 0;
for (j = 0; j < HDMI_EDID_LEN/16; j++) {
for (i = 0; i < 16; i++)
len += sprintf(buf+len, "0x%02X ",
hdmi->edid[j*16 + i]);
len += sprintf(buf+len, "\n");
}
return len;
}
static DEVICE_ATTR(edid, S_IRUGO, mxc_hdmi_show_edid, NULL);
static ssize_t mxc_hdmi_show_rgb_out_enable(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mxc_hdmi *hdmi = dev_get_drvdata(dev);
if (hdmi->hdmi_data.rgb_out_enable == true)
strcpy(buf, "RGB out\n");
else
strcpy(buf, "YCbCr out\n");
return strlen(buf);
}
static ssize_t mxc_hdmi_store_rgb_out_enable(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct mxc_hdmi *hdmi = dev_get_drvdata(dev);
unsigned long value;
int ret;
ret = kstrtoul(buf, 10, &value);
if (ret)
return ret;
hdmi->hdmi_data.rgb_out_enable = value;
/* Reconfig HDMI for output color space change */
mxc_hdmi_setup(hdmi, 0);
return count;
}
static DEVICE_ATTR(rgb_out_enable, S_IRUGO | S_IWUSR,
mxc_hdmi_show_rgb_out_enable,
mxc_hdmi_store_rgb_out_enable);
static ssize_t mxc_hdmi_show_hdcp_enable(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mxc_hdmi *hdmi = dev_get_drvdata(dev);
if (hdmi->hdmi_data.hdcp_enable == false)
strcpy(buf, "hdcp disable\n");
else
strcpy(buf, "hdcp enable\n");
return strlen(buf);
}
static ssize_t mxc_hdmi_store_hdcp_enable(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct mxc_hdmi *hdmi = dev_get_drvdata(dev);
char event_string[32];
char *envp[] = { event_string, NULL };
unsigned long value;
u8 clkdis;
int ret;
ret = kstrtoul(buf, 10, &value);
if (ret)
return ret;
hdmi->hdmi_data.hdcp_enable = value;
/* Disable All HDMI clock and HDMI PHY */
clkdis = hdmi_readb(HDMI_MC_CLKDIS);
clkdis |= 0x5f;
hdmi_writeb(clkdis, HDMI_MC_CLKDIS);
mxc_hdmi_phy_disable(hdmi);
hdmi_disable_overflow_interrupts();
/* Reconfig HDMI for HDCP */
mxc_hdmi_setup(hdmi, 0);
if (hdmi->hdmi_data.hdcp_enable == false) {
sprintf(event_string, "EVENT=hdcpdisable");
kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp);
} else {
sprintf(event_string, "EVENT=hdcpenable");
kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp);
}
return count;
}
static DEVICE_ATTR(hdcp_enable, S_IRUGO | S_IWUSR,
mxc_hdmi_show_hdcp_enable, mxc_hdmi_store_hdcp_enable);
/*!
* this submodule is responsible for the video data synchronization.
* for example, for RGB 4:4:4 input, the data map is defined as
* pin{47~40} <==> R[7:0]
* pin{31~24} <==> G[7:0]
* pin{15~8} <==> B[7:0]
*/
static void hdmi_video_sample(struct mxc_hdmi *hdmi)
{
int color_format = 0;
u8 val;
if (hdmi->hdmi_data.enc_in_format == RGB) {
if (hdmi->hdmi_data.enc_color_depth == 8)
color_format = 0x01;
else if (hdmi->hdmi_data.enc_color_depth == 10)
color_format = 0x03;
else if (hdmi->hdmi_data.enc_color_depth == 12)
color_format = 0x05;
else if (hdmi->hdmi_data.enc_color_depth == 16)
color_format = 0x07;
else
return;
} else if (hdmi->hdmi_data.enc_in_format == YCBCR444) {
if (hdmi->hdmi_data.enc_color_depth == 8)
color_format = 0x09;
else if (hdmi->hdmi_data.enc_color_depth == 10)
color_format = 0x0B;
else if (hdmi->hdmi_data.enc_color_depth == 12)
color_format = 0x0D;
else if (hdmi->hdmi_data.enc_color_depth == 16)
color_format = 0x0F;
else
return;
} else if (hdmi->hdmi_data.enc_in_format == YCBCR422_8BITS) {
if (hdmi->hdmi_data.enc_color_depth == 8)
color_format = 0x16;
else if (hdmi->hdmi_data.enc_color_depth == 10)
color_format = 0x14;
else if (hdmi->hdmi_data.enc_color_depth == 12)
color_format = 0x12;
else
return;
}
val = HDMI_TX_INVID0_INTERNAL_DE_GENERATOR_DISABLE |
((color_format << HDMI_TX_INVID0_VIDEO_MAPPING_OFFSET) &
HDMI_TX_INVID0_VIDEO_MAPPING_MASK);
hdmi_writeb(val, HDMI_TX_INVID0);
/* Enable TX stuffing: When DE is inactive, fix the output data to 0 */
val = HDMI_TX_INSTUFFING_BDBDATA_STUFFING_ENABLE |
HDMI_TX_INSTUFFING_RCRDATA_STUFFING_ENABLE |
HDMI_TX_INSTUFFING_GYDATA_STUFFING_ENABLE;
hdmi_writeb(val, HDMI_TX_INSTUFFING);
hdmi_writeb(0x0, HDMI_TX_GYDATA0);
hdmi_writeb(0x0, HDMI_TX_GYDATA1);
hdmi_writeb(0x0, HDMI_TX_RCRDATA0);
hdmi_writeb(0x0, HDMI_TX_RCRDATA1);
hdmi_writeb(0x0, HDMI_TX_BCBDATA0);
hdmi_writeb(0x0, HDMI_TX_BCBDATA1);
}
static int isColorSpaceConversion(struct mxc_hdmi *hdmi)
{
return (hdmi->hdmi_data.enc_in_format !=
hdmi->hdmi_data.enc_out_format);
}
static int isColorSpaceDecimation(struct mxc_hdmi *hdmi)
{
return ((hdmi->hdmi_data.enc_out_format == YCBCR422_8BITS) &&
(hdmi->hdmi_data.enc_in_format == RGB ||
hdmi->hdmi_data.enc_in_format == YCBCR444));
}
static int isColorSpaceInterpolation(struct mxc_hdmi *hdmi)
{
return ((hdmi->hdmi_data.enc_in_format == YCBCR422_8BITS) &&
(hdmi->hdmi_data.enc_out_format == RGB
|| hdmi->hdmi_data.enc_out_format == YCBCR444));
}
/*!
* update the color space conversion coefficients.
*/
static void update_csc_coeffs(struct mxc_hdmi *hdmi)
{
unsigned short csc_coeff[3][4];
unsigned int csc_scale = 1;
u8 val;
bool coeff_selected = false;
if (isColorSpaceConversion(hdmi)) { /* csc needed */
if (hdmi->hdmi_data.enc_out_format == RGB) {
if (hdmi->hdmi_data.colorimetry == eITU601) {
csc_coeff[0][0] = 0x2000;
csc_coeff[0][1] = 0x6926;
csc_coeff[0][2] = 0x74fd;
csc_coeff[0][3] = 0x010e;
csc_coeff[1][0] = 0x2000;
csc_coeff[1][1] = 0x2cdd;
csc_coeff[1][2] = 0x0000;
csc_coeff[1][3] = 0x7e9a;
csc_coeff[2][0] = 0x2000;
csc_coeff[2][1] = 0x0000;
csc_coeff[2][2] = 0x38b4;
csc_coeff[2][3] = 0x7e3b;
csc_scale = 1;
coeff_selected = true;
} else if (hdmi->hdmi_data.colorimetry == eITU709) {
csc_coeff[0][0] = 0x2000;
csc_coeff[0][1] = 0x7106;
csc_coeff[0][2] = 0x7a02;
csc_coeff[0][3] = 0x00a7;
csc_coeff[1][0] = 0x2000;
csc_coeff[1][1] = 0x3264;
csc_coeff[1][2] = 0x0000;
csc_coeff[1][3] = 0x7e6d;
csc_coeff[2][0] = 0x2000;
csc_coeff[2][1] = 0x0000;
csc_coeff[2][2] = 0x3b61;
csc_coeff[2][3] = 0x7e25;
csc_scale = 1;
coeff_selected = true;
}
} else if (hdmi->hdmi_data.enc_in_format == RGB) {
if (hdmi->hdmi_data.colorimetry == eITU601) {
csc_coeff[0][0] = 0x2591;
csc_coeff[0][1] = 0x1322;
csc_coeff[0][2] = 0x074b;
csc_coeff[0][3] = 0x0000;
csc_coeff[1][0] = 0x6535;
csc_coeff[1][1] = 0x2000;
csc_coeff[1][2] = 0x7acc;
csc_coeff[1][3] = 0x0200;
csc_coeff[2][0] = 0x6acd;
csc_coeff[2][1] = 0x7534;
csc_coeff[2][2] = 0x2000;
csc_coeff[2][3] = 0x0200;
csc_scale = 0;
coeff_selected = true;
} else if (hdmi->hdmi_data.colorimetry == eITU709) {
csc_coeff[0][0] = 0x2dc5;
csc_coeff[0][1] = 0x0d9b;
csc_coeff[0][2] = 0x049e;
csc_coeff[0][3] = 0x0000;
csc_coeff[1][0] = 0x62f0;
csc_coeff[1][1] = 0x2000;
csc_coeff[1][2] = 0x7d11;
csc_coeff[1][3] = 0x0200;
csc_coeff[2][0] = 0x6756;
csc_coeff[2][1] = 0x78ab;
csc_coeff[2][2] = 0x2000;
csc_coeff[2][3] = 0x0200;
csc_scale = 0;
coeff_selected = true;
}
}
}
if (!coeff_selected) {
csc_coeff[0][0] = 0x2000;
csc_coeff[0][1] = 0x0000;
csc_coeff[0][2] = 0x0000;
csc_coeff[0][3] = 0x0000;
csc_coeff[1][0] = 0x0000;
csc_coeff[1][1] = 0x2000;
csc_coeff[1][2] = 0x0000;
csc_coeff[1][3] = 0x0000;
csc_coeff[2][0] = 0x0000;
csc_coeff[2][1] = 0x0000;
csc_coeff[2][2] = 0x2000;
csc_coeff[2][3] = 0x0000;
csc_scale = 1;
}
/* Update CSC parameters in HDMI CSC registers */
hdmi_writeb((unsigned char)(csc_coeff[0][0] & 0xFF),
HDMI_CSC_COEF_A1_LSB);
hdmi_writeb((unsigned char)(csc_coeff[0][0] >> 8),
HDMI_CSC_COEF_A1_MSB);
hdmi_writeb((unsigned char)(csc_coeff[0][1] & 0xFF),
HDMI_CSC_COEF_A2_LSB);
hdmi_writeb((unsigned char)(csc_coeff[0][1] >> 8),
HDMI_CSC_COEF_A2_MSB);
hdmi_writeb((unsigned char)(csc_coeff[0][2] & 0xFF),
HDMI_CSC_COEF_A3_LSB);
hdmi_writeb((unsigned char)(csc_coeff[0][2] >> 8),
HDMI_CSC_COEF_A3_MSB);
hdmi_writeb((unsigned char)(csc_coeff[0][3] & 0xFF),
HDMI_CSC_COEF_A4_LSB);
hdmi_writeb((unsigned char)(csc_coeff[0][3] >> 8),
HDMI_CSC_COEF_A4_MSB);
hdmi_writeb((unsigned char)(csc_coeff[1][0] & 0xFF),
HDMI_CSC_COEF_B1_LSB);
hdmi_writeb((unsigned char)(csc_coeff[1][0] >> 8),
HDMI_CSC_COEF_B1_MSB);
hdmi_writeb((unsigned char)(csc_coeff[1][1] & 0xFF),
HDMI_CSC_COEF_B2_LSB);
hdmi_writeb((unsigned char)(csc_coeff[1][1] >> 8),
HDMI_CSC_COEF_B2_MSB);
hdmi_writeb((unsigned char)(csc_coeff[1][2] & 0xFF),
HDMI_CSC_COEF_B3_LSB);
hdmi_writeb((unsigned char)(csc_coeff[1][2] >> 8),
HDMI_CSC_COEF_B3_MSB);
hdmi_writeb((unsigned char)(csc_coeff[1][3] & 0xFF),
HDMI_CSC_COEF_B4_LSB);
hdmi_writeb((unsigned char)(csc_coeff[1][3] >> 8),
HDMI_CSC_COEF_B4_MSB);
hdmi_writeb((unsigned char)(csc_coeff[2][0] & 0xFF),
HDMI_CSC_COEF_C1_LSB);
hdmi_writeb((unsigned char)(csc_coeff[2][0] >> 8),
HDMI_CSC_COEF_C1_MSB);
hdmi_writeb((unsigned char)(csc_coeff[2][1] & 0xFF),
HDMI_CSC_COEF_C2_LSB);
hdmi_writeb((unsigned char)(csc_coeff[2][1] >> 8),
HDMI_CSC_COEF_C2_MSB);
hdmi_writeb((unsigned char)(csc_coeff[2][2] & 0xFF),
HDMI_CSC_COEF_C3_LSB);
hdmi_writeb((unsigned char)(csc_coeff[2][2] >> 8),
HDMI_CSC_COEF_C3_MSB);
hdmi_writeb((unsigned char)(csc_coeff[2][3] & 0xFF),
HDMI_CSC_COEF_C4_LSB);
hdmi_writeb((unsigned char)(csc_coeff[2][3] >> 8),
HDMI_CSC_COEF_C4_MSB);
val = hdmi_readb(HDMI_CSC_SCALE);
val &= ~HDMI_CSC_SCALE_CSCSCALE_MASK;
val |= csc_scale & HDMI_CSC_SCALE_CSCSCALE_MASK;
hdmi_writeb(val, HDMI_CSC_SCALE);
}
static void hdmi_video_csc(struct mxc_hdmi *hdmi)
{
int color_depth = 0;
int interpolation = HDMI_CSC_CFG_INTMODE_DISABLE;
int decimation = 0;
u8 val;
/* YCC422 interpolation to 444 mode */
if (isColorSpaceInterpolation(hdmi))
interpolation = HDMI_CSC_CFG_INTMODE_CHROMA_INT_FORMULA1;
else if (isColorSpaceDecimation(hdmi))
decimation = HDMI_CSC_CFG_DECMODE_CHROMA_INT_FORMULA3;
if (hdmi->hdmi_data.enc_color_depth == 8)
color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_24BPP;
else if (hdmi->hdmi_data.enc_color_depth == 10)
color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_30BPP;
else if (hdmi->hdmi_data.enc_color_depth == 12)
color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_36BPP;
else if (hdmi->hdmi_data.enc_color_depth == 16)
color_depth = HDMI_CSC_SCALE_CSC_COLORDE_PTH_48BPP;
else
return;
/*configure the CSC registers */
hdmi_writeb(interpolation | decimation, HDMI_CSC_CFG);
val = hdmi_readb(HDMI_CSC_SCALE);
val &= ~HDMI_CSC_SCALE_CSC_COLORDE_PTH_MASK;
val |= color_depth;
hdmi_writeb(val, HDMI_CSC_SCALE);
update_csc_coeffs(hdmi);
}
/*!
* HDMI video packetizer is used to packetize the data.
* for example, if input is YCC422 mode or repeater is used,
* data should be repacked this module can be bypassed.
*/
static void hdmi_video_packetize(struct mxc_hdmi *hdmi)
{
unsigned int color_depth = 0;
unsigned int remap_size = HDMI_VP_REMAP_YCC422_16bit;
unsigned int output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_PP;
struct hdmi_data_info *hdmi_data = &hdmi->hdmi_data;
u8 val;
if (hdmi_data->enc_out_format == RGB
|| hdmi_data->enc_out_format == YCBCR444) {
if (hdmi_data->enc_color_depth == 0)
output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS;
else if (hdmi_data->enc_color_depth == 8) {
color_depth = 4;
output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS;
} else if (hdmi_data->enc_color_depth == 10)
color_depth = 5;
else if (hdmi_data->enc_color_depth == 12)
color_depth = 6;
else if (hdmi_data->enc_color_depth == 16)
color_depth = 7;
else
return;
} else if (hdmi_data->enc_out_format == YCBCR422_8BITS) {
if (hdmi_data->enc_color_depth == 0 ||
hdmi_data->enc_color_depth == 8)
remap_size = HDMI_VP_REMAP_YCC422_16bit;
else if (hdmi_data->enc_color_depth == 10)
remap_size = HDMI_VP_REMAP_YCC422_20bit;
else if (hdmi_data->enc_color_depth == 12)
remap_size = HDMI_VP_REMAP_YCC422_24bit;
else
return;
output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422;
} else
return;
/* HDMI not support deep color,
* because IPU MAX support color depth is 24bit */
color_depth = 0;
/* set the packetizer registers */
val = ((color_depth << HDMI_VP_PR_CD_COLOR_DEPTH_OFFSET) &
HDMI_VP_PR_CD_COLOR_DEPTH_MASK) |
((hdmi_data->pix_repet_factor <<
HDMI_VP_PR_CD_DESIRED_PR_FACTOR_OFFSET) &
HDMI_VP_PR_CD_DESIRED_PR_FACTOR_MASK);
hdmi_writeb(val, HDMI_VP_PR_CD);
val = hdmi_readb(HDMI_VP_STUFF);
val &= ~HDMI_VP_STUFF_PR_STUFFING_MASK;
val |= HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE;
hdmi_writeb(val, HDMI_VP_STUFF);
/* Data from pixel repeater block */
if (hdmi_data->pix_repet_factor > 1) {
val = hdmi_readb(HDMI_VP_CONF);
val &= ~(HDMI_VP_CONF_PR_EN_MASK |
HDMI_VP_CONF_BYPASS_SELECT_MASK);
val |= HDMI_VP_CONF_PR_EN_ENABLE |
HDMI_VP_CONF_BYPASS_SELECT_PIX_REPEATER;
hdmi_writeb(val, HDMI_VP_CONF);
} else { /* data from packetizer block */
val = hdmi_readb(HDMI_VP_CONF);
val &= ~(HDMI_VP_CONF_PR_EN_MASK |
HDMI_VP_CONF_BYPASS_SELECT_MASK);
val |= HDMI_VP_CONF_PR_EN_DISABLE |
HDMI_VP_CONF_BYPASS_SELECT_VID_PACKETIZER;
hdmi_writeb(val, HDMI_VP_CONF);
}
val = hdmi_readb(HDMI_VP_STUFF);
val &= ~HDMI_VP_STUFF_IDEFAULT_PHASE_MASK;
val |= 1 << HDMI_VP_STUFF_IDEFAULT_PHASE_OFFSET;
hdmi_writeb(val, HDMI_VP_STUFF);
hdmi_writeb(remap_size, HDMI_VP_REMAP);
if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_PP) {
val = hdmi_readb(HDMI_VP_CONF);
val &= ~(HDMI_VP_CONF_BYPASS_EN_MASK |
HDMI_VP_CONF_PP_EN_ENMASK |
HDMI_VP_CONF_YCC422_EN_MASK);
val |= HDMI_VP_CONF_BYPASS_EN_DISABLE |
HDMI_VP_CONF_PP_EN_ENABLE |
HDMI_VP_CONF_YCC422_EN_DISABLE;
hdmi_writeb(val, HDMI_VP_CONF);
} else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422) {
val = hdmi_readb(HDMI_VP_CONF);
val &= ~(HDMI_VP_CONF_BYPASS_EN_MASK |
HDMI_VP_CONF_PP_EN_ENMASK |
HDMI_VP_CONF_YCC422_EN_MASK);
val |= HDMI_VP_CONF_BYPASS_EN_DISABLE |
HDMI_VP_CONF_PP_EN_DISABLE |
HDMI_VP_CONF_YCC422_EN_ENABLE;
hdmi_writeb(val, HDMI_VP_CONF);
} else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS) {
val = hdmi_readb(HDMI_VP_CONF);
val &= ~(HDMI_VP_CONF_BYPASS_EN_MASK |
HDMI_VP_CONF_PP_EN_ENMASK |
HDMI_VP_CONF_YCC422_EN_MASK);
val |= HDMI_VP_CONF_BYPASS_EN_ENABLE |
HDMI_VP_CONF_PP_EN_DISABLE |
HDMI_VP_CONF_YCC422_EN_DISABLE;
hdmi_writeb(val, HDMI_VP_CONF);
} else {
return;
}
val = hdmi_readb(HDMI_VP_STUFF);
val &= ~(HDMI_VP_STUFF_PP_STUFFING_MASK |
HDMI_VP_STUFF_YCC422_STUFFING_MASK);
val |= HDMI_VP_STUFF_PP_STUFFING_STUFFING_MODE |
HDMI_VP_STUFF_YCC422_STUFFING_STUFFING_MODE;
hdmi_writeb(val, HDMI_VP_STUFF);
val = hdmi_readb(HDMI_VP_CONF);
val &= ~HDMI_VP_CONF_OUTPUT_SELECTOR_MASK;
val |= output_select;
hdmi_writeb(val, HDMI_VP_CONF);
}
#if 0
/* Force a fixed color screen */
static void hdmi_video_force_output(struct mxc_hdmi *hdmi, unsigned char force)
{
u8 val;
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
if (force) {
hdmi_writeb(0x00, HDMI_FC_DBGTMDS2); /* R */
hdmi_writeb(0x00, HDMI_FC_DBGTMDS1); /* G */
hdmi_writeb(0xFF, HDMI_FC_DBGTMDS0); /* B */
val = hdmi_readb(HDMI_FC_DBGFORCE);
val |= HDMI_FC_DBGFORCE_FORCEVIDEO;
hdmi_writeb(val, HDMI_FC_DBGFORCE);
} else {
val = hdmi_readb(HDMI_FC_DBGFORCE);
val &= ~HDMI_FC_DBGFORCE_FORCEVIDEO;
hdmi_writeb(val, HDMI_FC_DBGFORCE);
hdmi_writeb(0x00, HDMI_FC_DBGTMDS2); /* R */
hdmi_writeb(0x00, HDMI_FC_DBGTMDS1); /* G */
hdmi_writeb(0x00, HDMI_FC_DBGTMDS0); /* B */
}
}
#endif
static inline void hdmi_phy_test_clear(struct mxc_hdmi *hdmi,
unsigned char bit)
{
u8 val = hdmi_readb(HDMI_PHY_TST0);
val &= ~HDMI_PHY_TST0_TSTCLR_MASK;
val |= (bit << HDMI_PHY_TST0_TSTCLR_OFFSET) &
HDMI_PHY_TST0_TSTCLR_MASK;
hdmi_writeb(val, HDMI_PHY_TST0);
}
static inline void hdmi_phy_test_enable(struct mxc_hdmi *hdmi,
unsigned char bit)
{
u8 val = hdmi_readb(HDMI_PHY_TST0);
val &= ~HDMI_PHY_TST0_TSTEN_MASK;
val |= (bit << HDMI_PHY_TST0_TSTEN_OFFSET) &
HDMI_PHY_TST0_TSTEN_MASK;
hdmi_writeb(val, HDMI_PHY_TST0);
}
static inline void hdmi_phy_test_clock(struct mxc_hdmi *hdmi,
unsigned char bit)
{
u8 val = hdmi_readb(HDMI_PHY_TST0);
val &= ~HDMI_PHY_TST0_TSTCLK_MASK;
val |= (bit << HDMI_PHY_TST0_TSTCLK_OFFSET) &
HDMI_PHY_TST0_TSTCLK_MASK;
hdmi_writeb(val, HDMI_PHY_TST0);
}
static inline void hdmi_phy_test_din(struct mxc_hdmi *hdmi,
unsigned char bit)
{
hdmi_writeb(bit, HDMI_PHY_TST1);
}
static inline void hdmi_phy_test_dout(struct mxc_hdmi *hdmi,
unsigned char bit)
{
hdmi_writeb(bit, HDMI_PHY_TST2);
}
static bool hdmi_phy_wait_i2c_done(struct mxc_hdmi *hdmi, int msec)
{
unsigned char val = 0;
val = hdmi_readb(HDMI_IH_I2CMPHY_STAT0) & 0x3;
while (val == 0) {
udelay(1000);
if (msec-- == 0)
return false;
val = hdmi_readb(HDMI_IH_I2CMPHY_STAT0) & 0x3;
}
return true;
}
static void hdmi_phy_i2c_write(struct mxc_hdmi *hdmi, unsigned short data,
unsigned char addr)
{
hdmi_writeb(0xFF, HDMI_IH_I2CMPHY_STAT0);
hdmi_writeb(addr, HDMI_PHY_I2CM_ADDRESS_ADDR);
hdmi_writeb((unsigned char)(data >> 8),
HDMI_PHY_I2CM_DATAO_1_ADDR);
hdmi_writeb((unsigned char)(data >> 0),
HDMI_PHY_I2CM_DATAO_0_ADDR);
hdmi_writeb(HDMI_PHY_I2CM_OPERATION_ADDR_WRITE,
HDMI_PHY_I2CM_OPERATION_ADDR);
hdmi_phy_wait_i2c_done(hdmi, 1000);
}
#if 0
static unsigned short hdmi_phy_i2c_read(struct mxc_hdmi *hdmi,
unsigned char addr)
{
unsigned short data;
unsigned char msb = 0, lsb = 0;
hdmi_writeb(0xFF, HDMI_IH_I2CMPHY_STAT0);
hdmi_writeb(addr, HDMI_PHY_I2CM_ADDRESS_ADDR);
hdmi_writeb(HDMI_PHY_I2CM_OPERATION_ADDR_READ,
HDMI_PHY_I2CM_OPERATION_ADDR);
hdmi_phy_wait_i2c_done(hdmi, 1000);
msb = hdmi_readb(HDMI_PHY_I2CM_DATAI_1_ADDR);
lsb = hdmi_readb(HDMI_PHY_I2CM_DATAI_0_ADDR);
data = (msb << 8) | lsb;
return data;
}
static int hdmi_phy_i2c_write_verify(struct mxc_hdmi *hdmi, unsigned short data,
unsigned char addr)
{
unsigned short val = 0;
hdmi_phy_i2c_write(hdmi, data, addr);
val = hdmi_phy_i2c_read(hdmi, addr);
return (val == data);
}
#endif
static bool hdmi_edid_wait_i2c_done(struct mxc_hdmi *hdmi, int msec)
{
unsigned char val = 0;
val = hdmi_readb(HDMI_IH_I2CM_STAT0) & 0x2;
while (val == 0) {
udelay(1000);
if (msec-- == 0) {
dev_dbg(&hdmi->pdev->dev,
"HDMI EDID i2c operation time out!!\n");
return false;
}
val = hdmi_readb(HDMI_IH_I2CM_STAT0) & 0x2;
}
return true;
}
static u8 hdmi_edid_i2c_read(struct mxc_hdmi *hdmi,
u8 addr, u8 blockno)
{
u8 spointer = blockno / 2;
u8 edidaddress = ((blockno % 2) * 0x80) + addr;
u8 data;
hdmi_writeb(0xFF, HDMI_IH_I2CM_STAT0);
hdmi_writeb(edidaddress, HDMI_I2CM_ADDRESS);
hdmi_writeb(spointer, HDMI_I2CM_SEGADDR);
if (spointer == 0)
hdmi_writeb(HDMI_I2CM_OPERATION_READ,
HDMI_I2CM_OPERATION);
else
hdmi_writeb(HDMI_I2CM_OPERATION_READ_EXT,
HDMI_I2CM_OPERATION);
hdmi_edid_wait_i2c_done(hdmi, 30);
data = hdmi_readb(HDMI_I2CM_DATAI);
hdmi_writeb(0xFF, HDMI_IH_I2CM_STAT0);
return data;
}
/* "Power-down enable (active low)"
* That mean that power up == 1! */
static void mxc_hdmi_phy_enable_power(u8 enable)
{
hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
HDMI_PHY_CONF0_PDZ_OFFSET,
HDMI_PHY_CONF0_PDZ_MASK);
}
static void mxc_hdmi_phy_enable_tmds(u8 enable)
{
hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
HDMI_PHY_CONF0_ENTMDS_OFFSET,
HDMI_PHY_CONF0_ENTMDS_MASK);
}
static void mxc_hdmi_phy_gen2_pddq(u8 enable)
{
hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET,
HDMI_PHY_CONF0_GEN2_PDDQ_MASK);
}
static void mxc_hdmi_phy_gen2_txpwron(u8 enable)
{
hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET,
HDMI_PHY_CONF0_GEN2_TXPWRON_MASK);
}
#if 0
static void mxc_hdmi_phy_gen2_enhpdrxsense(u8 enable)
{
hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_OFFSET,
HDMI_PHY_CONF0_GEN2_ENHPDRXSENSE_MASK);
}
#endif
static void mxc_hdmi_phy_sel_data_en_pol(u8 enable)
{
hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
HDMI_PHY_CONF0_SELDATAENPOL_OFFSET,
HDMI_PHY_CONF0_SELDATAENPOL_MASK);
}
static void mxc_hdmi_phy_sel_interface_control(u8 enable)
{
hdmi_mask_writeb(enable, HDMI_PHY_CONF0,
HDMI_PHY_CONF0_SELDIPIF_OFFSET,
HDMI_PHY_CONF0_SELDIPIF_MASK);
}
static int hdmi_phy_configure(struct mxc_hdmi *hdmi, unsigned char pRep,
unsigned char cRes, int cscOn)
{
u8 val;
u8 msec;
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
/* color resolution 0 is 8 bit colour depth */
if (cRes == 0)
cRes = 8;
if (pRep != 0)
return false;
else if (cRes != 8 && cRes != 12)
return false;
/* Enable csc path */
if (cscOn)
val = HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_IN_PATH;
else
val = HDMI_MC_FLOWCTRL_FEED_THROUGH_OFF_CSC_BYPASS;
hdmi_writeb(val, HDMI_MC_FLOWCTRL);
/* gen2 tx power off */
mxc_hdmi_phy_gen2_txpwron(0);
/* gen2 pddq */
mxc_hdmi_phy_gen2_pddq(1);
/* PHY reset */
hdmi_writeb(HDMI_MC_PHYRSTZ_DEASSERT, HDMI_MC_PHYRSTZ);
hdmi_writeb(HDMI_MC_PHYRSTZ_ASSERT, HDMI_MC_PHYRSTZ);
hdmi_writeb(HDMI_MC_HEACPHY_RST_ASSERT, HDMI_MC_HEACPHY_RST);
hdmi_phy_test_clear(hdmi, 1);
hdmi_writeb(HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2,
HDMI_PHY_I2CM_SLAVE_ADDR);
hdmi_phy_test_clear(hdmi, 0);
if (hdmi->hdmi_data.video_mode.mPixelClock <= 45250000) {
switch (cRes) {
case 8:
/* PLL/MPLL Cfg */
hdmi_phy_i2c_write(hdmi, 0x01e0, 0x06);
hdmi_phy_i2c_write(hdmi, 0x0000, 0x15); /* GMPCTRL */
break;
case 10:
hdmi_phy_i2c_write(hdmi, 0x21e1, 0x06);
hdmi_phy_i2c_write(hdmi, 0x0000, 0x15);
break;
case 12:
hdmi_phy_i2c_write(hdmi, 0x41e2, 0x06);
hdmi_phy_i2c_write(hdmi, 0x0000, 0x15);
break;
default:
return false;
}
} else if (hdmi->hdmi_data.video_mode.mPixelClock <= 92500000) {
switch (cRes) {
case 8:
hdmi_phy_i2c_write(hdmi, 0x0140, 0x06);
hdmi_phy_i2c_write(hdmi, 0x0005, 0x15);
break;
case 10:
hdmi_phy_i2c_write(hdmi, 0x2141, 0x06);
hdmi_phy_i2c_write(hdmi, 0x0005, 0x15);
break;
case 12:
hdmi_phy_i2c_write(hdmi, 0x4142, 0x06);
hdmi_phy_i2c_write(hdmi, 0x0005, 0x15);
default:
return false;
}
} else if (hdmi->hdmi_data.video_mode.mPixelClock <= 148500000) {
switch (cRes) {
case 8:
hdmi_phy_i2c_write(hdmi, 0x00a0, 0x06);
hdmi_phy_i2c_write(hdmi, 0x000a, 0x15);
break;
case 10:
hdmi_phy_i2c_write(hdmi, 0x20a1, 0x06);
hdmi_phy_i2c_write(hdmi, 0x000a, 0x15);
break;
case 12:
hdmi_phy_i2c_write(hdmi, 0x40a2, 0x06);
hdmi_phy_i2c_write(hdmi, 0x000a, 0x15);
default:
return false;
}
} else {
switch (cRes) {
case 8:
hdmi_phy_i2c_write(hdmi, 0x00a0, 0x06);
hdmi_phy_i2c_write(hdmi, 0x000a, 0x15);
break;
case 10:
hdmi_phy_i2c_write(hdmi, 0x2001, 0x06);
hdmi_phy_i2c_write(hdmi, 0x000f, 0x15);
break;
case 12:
hdmi_phy_i2c_write(hdmi, 0x4002, 0x06);
hdmi_phy_i2c_write(hdmi, 0x000f, 0x15);
default:
return false;
}
}
if (hdmi->hdmi_data.video_mode.mPixelClock <= 54000000) {
switch (cRes) {
case 8:
hdmi_phy_i2c_write(hdmi, 0x091c, 0x10); /* CURRCTRL */
break;
case 10:
hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
break;
case 12:
hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
break;
default:
return false;
}
} else if (hdmi->hdmi_data.video_mode.mPixelClock <= 58400000) {
switch (cRes) {
case 8:
hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
break;
case 10:
hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
break;
case 12:
hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
break;
default:
return false;
}
} else if (hdmi->hdmi_data.video_mode.mPixelClock <= 72000000) {
switch (cRes) {
case 8:
hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
break;
case 10:
hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
break;
case 12:
hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
break;
default:
return false;
}
} else if (hdmi->hdmi_data.video_mode.mPixelClock <= 74250000) {
switch (cRes) {
case 8:
hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
break;
case 10:
hdmi_phy_i2c_write(hdmi, 0x0b5c, 0x10);
break;
case 12:
hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
break;
default:
return false;
}
} else if (hdmi->hdmi_data.video_mode.mPixelClock <= 118800000) {
switch (cRes) {
case 8:
hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
break;
case 10:
hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
break;
case 12:
hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
break;
default:
return false;
}
} else if (hdmi->hdmi_data.video_mode.mPixelClock <= 216000000) {
switch (cRes) {
case 8:
hdmi_phy_i2c_write(hdmi, 0x06dc, 0x10);
break;
case 10:
hdmi_phy_i2c_write(hdmi, 0x0b5c, 0x10);
break;
case 12:
hdmi_phy_i2c_write(hdmi, 0x091c, 0x10);
break;
default:
return false;
}
} else {
dev_err(&hdmi->pdev->dev,
"Pixel clock %d - unsupported by HDMI\n",
hdmi->hdmi_data.video_mode.mPixelClock);
return false;
}
hdmi_phy_i2c_write(hdmi, 0x0000, 0x13); /* PLLPHBYCTRL */
hdmi_phy_i2c_write(hdmi, 0x0006, 0x17);
/* RESISTANCE TERM 133Ohm Cfg */
hdmi_phy_i2c_write(hdmi, 0x0005, 0x19); /* TXTERM */
/* PREEMP Cgf 0.00 */
hdmi_phy_i2c_write(hdmi, 0x800d, 0x09); /* CKSYMTXCTRL */
/* TX/CK LVL 10 */
hdmi_phy_i2c_write(hdmi, 0x01ad, 0x0E); /* VLEVCTRL */
/* Board specific setting for PHY register 0x09, 0x0e to pass HCT */
if (hdmi->phy_config.reg_cksymtx != 0)
hdmi_phy_i2c_write(hdmi, hdmi->phy_config.reg_cksymtx, 0x09);
if (hdmi->phy_config.reg_vlev != 0)
hdmi_phy_i2c_write(hdmi, hdmi->phy_config.reg_vlev, 0x0E);
/* REMOVE CLK TERM */
hdmi_phy_i2c_write(hdmi, 0x8000, 0x05); /* CKCALCTRL */
if (hdmi->hdmi_data.video_mode.mPixelClock > 148500000) {
hdmi_phy_i2c_write(hdmi, 0x800b, 0x09);
hdmi_phy_i2c_write(hdmi, 0x0129, 0x0E);
}
mxc_hdmi_phy_enable_power(1);
/* toggle TMDS enable */
mxc_hdmi_phy_enable_tmds(0);
mxc_hdmi_phy_enable_tmds(1);
/* gen2 tx power on */
mxc_hdmi_phy_gen2_txpwron(1);
mxc_hdmi_phy_gen2_pddq(0);
/*Wait for PHY PLL lock */
msec = 4;
val = hdmi_readb(HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK;
while (val == 0) {
udelay(1000);
if (msec-- == 0) {
dev_dbg(&hdmi->pdev->dev, "PHY PLL not locked\n");
return false;
}
val = hdmi_readb(HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK;
}
return true;
}
static void mxc_hdmi_phy_init(struct mxc_hdmi *hdmi)
{
int i;
bool cscon = false;
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
/* Never do phy init if pixel clock is gated.
* Otherwise HDMI PHY will get messed up and generate an overflow
* interrupt that can't be cleared or detected by accessing the
* status register. */
if (!hdmi->fb_reg || !hdmi->cable_plugin
|| (hdmi->blank != FB_BLANK_UNBLANK))
return;
if (!hdmi->hdmi_data.video_mode.mDVI)
hdmi_enable_overflow_interrupts();
/*check csc whether needed activated in HDMI mode */
cscon = (isColorSpaceConversion(hdmi) &&
!hdmi->hdmi_data.video_mode.mDVI);
/* HDMI Phy spec says to do the phy initialization sequence twice */
for (i = 0 ; i < 2 ; i++) {
mxc_hdmi_phy_sel_data_en_pol(1);
mxc_hdmi_phy_sel_interface_control(0);
mxc_hdmi_phy_enable_tmds(0);
mxc_hdmi_phy_enable_power(0);
/* Enable CSC */
hdmi_phy_configure(hdmi, 0, 8, cscon);
}
hdmi->phy_enabled = true;
}
static void hdmi_config_AVI(struct mxc_hdmi *hdmi)
{
u8 val;
u8 pix_fmt;
u8 under_scan;
u8 act_ratio, coded_ratio, colorimetry, ext_colorimetry;
struct fb_videomode mode;
const struct fb_videomode *edid_mode;
bool aspect_16_9;
dev_dbg(&hdmi->pdev->dev, "set up AVI frame\n");
fb_var_to_videomode(&mode, &hdmi->fbi->var);
/* Use mode from list extracted from EDID to get aspect ratio */
if (!list_empty(&hdmi->fbi->modelist)) {
edid_mode = fb_find_nearest_mode(&mode, &hdmi->fbi->modelist);
if (edid_mode->vmode & FB_VMODE_ASPECT_16_9)
aspect_16_9 = true;
else
aspect_16_9 = false;
} else
aspect_16_9 = false;
/********************************************
* AVI Data Byte 1
********************************************/
if (hdmi->hdmi_data.enc_out_format == YCBCR444)
pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_YCBCR444;
else if (hdmi->hdmi_data.enc_out_format == YCBCR422_8BITS)
pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_YCBCR422;
else
pix_fmt = HDMI_FC_AVICONF0_PIX_FMT_RGB;
if (hdmi->edid_cfg.cea_underscan)
under_scan = HDMI_FC_AVICONF0_SCAN_INFO_UNDERSCAN;
else
under_scan = HDMI_FC_AVICONF0_SCAN_INFO_NODATA;
/*
* Active format identification data is present in the AVI InfoFrame.
* Under scan info, no bar data
*/
val = pix_fmt | under_scan |
HDMI_FC_AVICONF0_ACTIVE_FMT_INFO_PRESENT |
HDMI_FC_AVICONF0_BAR_DATA_NO_DATA;
hdmi_writeb(val, HDMI_FC_AVICONF0);
/********************************************
* AVI Data Byte 2
********************************************/
/* Set the Aspect Ratio */
if (aspect_16_9) {
act_ratio = HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_16_9;
coded_ratio = HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_16_9;
} else {
act_ratio = HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_4_3;
coded_ratio = HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_4_3;
}
/* Set up colorimetry */
if (hdmi->hdmi_data.enc_out_format == XVYCC444) {
colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_EXTENDED_INFO;
if (hdmi->hdmi_data.colorimetry == eITU601)
ext_colorimetry =
HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601;
else /* hdmi->hdmi_data.colorimetry == eITU709 */
ext_colorimetry =
HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC709;
} else if (hdmi->hdmi_data.enc_out_format != RGB) {
if (hdmi->hdmi_data.colorimetry == eITU601)
colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_SMPTE;
else /* hdmi->hdmi_data.colorimetry == eITU709 */
colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_ITUR;
ext_colorimetry = HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601;
} else { /* Carries no data */
colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_NO_DATA;
ext_colorimetry = HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601;
}
val = colorimetry | coded_ratio | act_ratio;
hdmi_writeb(val, HDMI_FC_AVICONF1);
/********************************************
* AVI Data Byte 3
********************************************/
val = HDMI_FC_AVICONF2_IT_CONTENT_NO_DATA | ext_colorimetry |
HDMI_FC_AVICONF2_RGB_QUANT_DEFAULT |
HDMI_FC_AVICONF2_SCALING_NONE;
hdmi_writeb(val, HDMI_FC_AVICONF2);
/********************************************
* AVI Data Byte 4
********************************************/
hdmi_writeb(hdmi->vic, HDMI_FC_AVIVID);
/********************************************
* AVI Data Byte 5
********************************************/
/* Set up input and output pixel repetition */
val = (((hdmi->hdmi_data.video_mode.mPixelRepetitionInput + 1) <<
HDMI_FC_PRCONF_INCOMING_PR_FACTOR_OFFSET) &
HDMI_FC_PRCONF_INCOMING_PR_FACTOR_MASK) |
((hdmi->hdmi_data.video_mode.mPixelRepetitionOutput <<
HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_OFFSET) &
HDMI_FC_PRCONF_OUTPUT_PR_FACTOR_MASK);
hdmi_writeb(val, HDMI_FC_PRCONF);
/* IT Content and quantization range = don't care */
val = HDMI_FC_AVICONF3_IT_CONTENT_TYPE_GRAPHICS |
HDMI_FC_AVICONF3_QUANT_RANGE_LIMITED;
hdmi_writeb(val, HDMI_FC_AVICONF3);
/********************************************
* AVI Data Bytes 6-13
********************************************/
hdmi_writeb(0, HDMI_FC_AVIETB0);
hdmi_writeb(0, HDMI_FC_AVIETB1);
hdmi_writeb(0, HDMI_FC_AVISBB0);
hdmi_writeb(0, HDMI_FC_AVISBB1);
hdmi_writeb(0, HDMI_FC_AVIELB0);
hdmi_writeb(0, HDMI_FC_AVIELB1);
hdmi_writeb(0, HDMI_FC_AVISRB0);
hdmi_writeb(0, HDMI_FC_AVISRB1);
}
/*!
* this submodule is responsible for the video/audio data composition.
*/
static void hdmi_av_composer(struct mxc_hdmi *hdmi)
{
u8 inv_val;
struct fb_info *fbi = hdmi->fbi;
struct fb_videomode fb_mode;
struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode;
int hblank, vblank;
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
fb_var_to_videomode(&fb_mode, &fbi->var);
vmode->mHSyncPolarity = ((fb_mode.sync & FB_SYNC_HOR_HIGH_ACT) != 0);
vmode->mVSyncPolarity = ((fb_mode.sync & FB_SYNC_VERT_HIGH_ACT) != 0);
vmode->mInterlaced = ((fb_mode.vmode & FB_VMODE_INTERLACED) != 0);
vmode->mPixelClock = (fb_mode.xres + fb_mode.left_margin +
fb_mode.right_margin + fb_mode.hsync_len) * (fb_mode.yres +
fb_mode.upper_margin + fb_mode.lower_margin +
fb_mode.vsync_len) * fb_mode.refresh;
dev_dbg(&hdmi->pdev->dev, "final pixclk = %d\n", vmode->mPixelClock);
/* Set up HDMI_FC_INVIDCONF */
inv_val = (hdmi->hdmi_data.hdcp_enable ?
HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE :
HDMI_FC_INVIDCONF_HDCP_KEEPOUT_INACTIVE);
inv_val |= (vmode->mVSyncPolarity ?
HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_HIGH :
HDMI_FC_INVIDCONF_VSYNC_IN_POLARITY_ACTIVE_LOW);
inv_val |= (vmode->mHSyncPolarity ?
HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_HIGH :
HDMI_FC_INVIDCONF_HSYNC_IN_POLARITY_ACTIVE_LOW);
inv_val |= (vmode->mDataEnablePolarity ?
HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_HIGH :
HDMI_FC_INVIDCONF_DE_IN_POLARITY_ACTIVE_LOW);
if (hdmi->vic == 39)
inv_val |= HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH;
else
inv_val |= (vmode->mInterlaced ?
HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_HIGH :
HDMI_FC_INVIDCONF_R_V_BLANK_IN_OSC_ACTIVE_LOW);
inv_val |= (vmode->mInterlaced ?
HDMI_FC_INVIDCONF_IN_I_P_INTERLACED :
HDMI_FC_INVIDCONF_IN_I_P_PROGRESSIVE);
inv_val |= (vmode->mDVI ?
HDMI_FC_INVIDCONF_DVI_MODEZ_DVI_MODE :
HDMI_FC_INVIDCONF_DVI_MODEZ_HDMI_MODE);
hdmi_writeb(inv_val, HDMI_FC_INVIDCONF);
/* Set up horizontal active pixel region width */
hdmi_writeb(fb_mode.xres >> 8, HDMI_FC_INHACTV1);
hdmi_writeb(fb_mode.xres, HDMI_FC_INHACTV0);
/* Set up vertical blanking pixel region width */
hdmi_writeb(fb_mode.yres >> 8, HDMI_FC_INVACTV1);
hdmi_writeb(fb_mode.yres, HDMI_FC_INVACTV0);
/* Set up horizontal blanking pixel region width */
hblank = fb_mode.left_margin + fb_mode.right_margin +
fb_mode.hsync_len;
hdmi_writeb(hblank >> 8, HDMI_FC_INHBLANK1);
hdmi_writeb(hblank, HDMI_FC_INHBLANK0);
/* Set up vertical blanking pixel region width */
vblank = fb_mode.upper_margin + fb_mode.lower_margin +
fb_mode.vsync_len;
hdmi_writeb(vblank, HDMI_FC_INVBLANK);
/* Set up HSYNC active edge delay width (in pixel clks) */
hdmi_writeb(fb_mode.right_margin >> 8, HDMI_FC_HSYNCINDELAY1);
hdmi_writeb(fb_mode.right_margin, HDMI_FC_HSYNCINDELAY0);
/* Set up VSYNC active edge delay (in pixel clks) */
hdmi_writeb(fb_mode.lower_margin, HDMI_FC_VSYNCINDELAY);
/* Set up HSYNC active pulse width (in pixel clks) */
hdmi_writeb(fb_mode.hsync_len >> 8, HDMI_FC_HSYNCINWIDTH1);
hdmi_writeb(fb_mode.hsync_len, HDMI_FC_HSYNCINWIDTH0);
/* Set up VSYNC active edge delay (in pixel clks) */
hdmi_writeb(fb_mode.vsync_len, HDMI_FC_VSYNCINWIDTH);
dev_dbg(&hdmi->pdev->dev, "%s exit\n", __func__);
}
static int mxc_edid_read_internal(struct mxc_hdmi *hdmi, unsigned char *edid,
struct mxc_edid_cfg *cfg, struct fb_info *fbi)
{
int extblknum;
int i, j, ret;
unsigned char *ediddata = edid;
unsigned char tmpedid[EDID_LENGTH];
dev_info(&hdmi->pdev->dev, "%s\n", __func__);
if (!edid || !cfg || !fbi)
return -EINVAL;
/* init HDMI I2CM for read edid*/
hdmi_writeb(0x0, HDMI_I2CM_DIV);
hdmi_writeb(0x00, HDMI_I2CM_SS_SCL_HCNT_1_ADDR);
hdmi_writeb(0x79, HDMI_I2CM_SS_SCL_HCNT_0_ADDR);
hdmi_writeb(0x00, HDMI_I2CM_SS_SCL_LCNT_1_ADDR);
hdmi_writeb(0x91, HDMI_I2CM_SS_SCL_LCNT_0_ADDR);
hdmi_writeb(0x00, HDMI_I2CM_FS_SCL_HCNT_1_ADDR);
hdmi_writeb(0x0F, HDMI_I2CM_FS_SCL_HCNT_0_ADDR);
hdmi_writeb(0x00, HDMI_I2CM_FS_SCL_LCNT_1_ADDR);
hdmi_writeb(0x21, HDMI_I2CM_FS_SCL_LCNT_0_ADDR);
hdmi_writeb(0x50, HDMI_I2CM_SLAVE);
hdmi_writeb(0x30, HDMI_I2CM_SEGADDR);
/* Umask edid interrupt */
hdmi_writeb(HDMI_I2CM_INT_DONE_POL,
HDMI_I2CM_INT);
hdmi_writeb(HDMI_I2CM_CTLINT_NAC_POL |
HDMI_I2CM_CTLINT_ARBITRATION_POL,
HDMI_I2CM_CTLINT);
/* reset edid data zero */
memset(edid, 0, EDID_LENGTH*4);
memset(cfg, 0, sizeof(struct mxc_edid_cfg));
/* Check first three byte of EDID head */
if (!(hdmi_edid_i2c_read(hdmi, 0, 0) == 0x00) ||
!(hdmi_edid_i2c_read(hdmi, 1, 0) == 0xFF) ||
!(hdmi_edid_i2c_read(hdmi, 2, 0) == 0xFF)) {
dev_info(&hdmi->pdev->dev, "EDID head check failed!");
return -ENOENT;
}
for (i = 0; i < 128; i++) {
*ediddata = hdmi_edid_i2c_read(hdmi, i, 0);
ediddata++;
}
extblknum = edid[0x7E];
if (extblknum == 255)
extblknum = 0;
if (extblknum) {
ediddata = edid + EDID_LENGTH;
for (i = 0; i < 128; i++) {
*ediddata = hdmi_edid_i2c_read(hdmi, i, 1);
ediddata++;
}
}
/* edid first block parsing */
memset(&fbi->monspecs, 0, sizeof(fbi->monspecs));
fb_edid_to_monspecs(edid, &fbi->monspecs);
if (extblknum) {
ret = mxc_edid_parse_ext_blk(edid + EDID_LENGTH,
cfg, &fbi->monspecs);
if (ret < 0)
return -ENOENT;
}
/* need read segment block? */
if (extblknum > 1) {
for (j = 2; j <= extblknum; j++) {
for (i = 0; i < 128; i++)
tmpedid[i] = hdmi_edid_i2c_read(hdmi, i, j);
/* edid ext block parsing */
ret = mxc_edid_parse_ext_blk(tmpedid,
cfg, &fbi->monspecs);
if (ret < 0)
return -ENOENT;
}
}
return 0;
}
static int mxc_hdmi_read_edid(struct mxc_hdmi *hdmi)
{
int ret;
u8 edid_old[HDMI_EDID_LEN];
u8 clkdis;
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
/* save old edid */
memcpy(edid_old, hdmi->edid, HDMI_EDID_LEN);
/* Read EDID via HDMI DDC when HDCP Enable */
if (!hdcp_init)
ret = mxc_edid_read(hdmi_i2c->adapter, hdmi_i2c->addr,
hdmi->edid, &hdmi->edid_cfg, hdmi->fbi);
else {
/* Disable HDCP clk */
if (hdmi->hdmi_data.hdcp_enable) {
clkdis = hdmi_readb(HDMI_MC_CLKDIS);
clkdis |= HDMI_MC_CLKDIS_HDCPCLK_DISABLE;
hdmi_writeb(clkdis, HDMI_MC_CLKDIS);
}
ret = mxc_edid_read_internal(hdmi, hdmi->edid,
&hdmi->edid_cfg, hdmi->fbi);
/* Enable HDCP clk */
if (hdmi->hdmi_data.hdcp_enable) {
clkdis = hdmi_readb(HDMI_MC_CLKDIS);
clkdis &= ~HDMI_MC_CLKDIS_HDCPCLK_DISABLE;
hdmi_writeb(clkdis, HDMI_MC_CLKDIS);
}
}
if (ret < 0) {
dev_dbg(&hdmi->pdev->dev, "read failed\n");
return HDMI_EDID_FAIL;
}
/* Save edid cfg for audio driver */
hdmi_set_edid_cfg(&hdmi->edid_cfg);
if (!memcmp(edid_old, hdmi->edid, HDMI_EDID_LEN)) {
dev_info(&hdmi->pdev->dev, "same edid\n");
return HDMI_EDID_SAME;
}
if (hdmi->fbi->monspecs.modedb_len == 0) {
dev_info(&hdmi->pdev->dev, "No modes read from edid\n");
return HDMI_EDID_NO_MODES;
}
return HDMI_EDID_SUCCESS;
}
static void mxc_hdmi_phy_disable(struct mxc_hdmi *hdmi)
{
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
if (!hdmi->phy_enabled)
return;
hdmi_disable_overflow_interrupts();
/* Setting PHY to reset status */
hdmi_writeb(HDMI_MC_PHYRSTZ_DEASSERT, HDMI_MC_PHYRSTZ);
/* Power down PHY */
mxc_hdmi_phy_enable_tmds(0);
mxc_hdmi_phy_enable_power(0);
mxc_hdmi_phy_gen2_txpwron(0);
mxc_hdmi_phy_gen2_pddq(1);
hdmi->phy_enabled = false;
dev_dbg(&hdmi->pdev->dev, "%s - exit\n", __func__);
}
/* HDMI Initialization Step B.4 */
static void mxc_hdmi_enable_video_path(struct mxc_hdmi *hdmi)
{
u8 clkdis;
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
/* control period minimum duration */
hdmi_writeb(12, HDMI_FC_CTRLDUR);
hdmi_writeb(32, HDMI_FC_EXCTRLDUR);
hdmi_writeb(1, HDMI_FC_EXCTRLSPAC);
/* Set to fill TMDS data channels */
hdmi_writeb(0x0B, HDMI_FC_CH0PREAM);
hdmi_writeb(0x16, HDMI_FC_CH1PREAM);
hdmi_writeb(0x21, HDMI_FC_CH2PREAM);
clkdis = hdmi_readb(HDMI_MC_CLKDIS);
/* Enable pixel clock and tmds data path */
clkdis &= ~HDMI_MC_CLKDIS_PIXELCLK_DISABLE;
hdmi_writeb(clkdis, HDMI_MC_CLKDIS);
clkdis &= ~HDMI_MC_CLKDIS_TMDSCLK_DISABLE;
hdmi_writeb(clkdis, HDMI_MC_CLKDIS);
/* Enable csc path */
if (isColorSpaceConversion(hdmi)) {
clkdis &= ~HDMI_MC_CLKDIS_CSCCLK_DISABLE;
hdmi_writeb(clkdis, HDMI_MC_CLKDIS);
}
}
static void hdmi_enable_audio_clk(struct mxc_hdmi *hdmi)
{
u8 clkdis;
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
clkdis = hdmi_readb(HDMI_MC_CLKDIS);
clkdis &= ~HDMI_MC_CLKDIS_AUDCLK_DISABLE;
hdmi_writeb(clkdis, HDMI_MC_CLKDIS);
}
/* Workaround to clear the overflow condition */
static void mxc_hdmi_clear_overflow(struct mxc_hdmi *hdmi)
{
int count;
u8 val;
/* TMDS software reset */
hdmi_writeb((u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ, HDMI_MC_SWRSTZ);
val = hdmi_readb(HDMI_FC_INVIDCONF);
if (cpu_is_imx6dl(hdmi)) {
hdmi_writeb(val, HDMI_FC_INVIDCONF);
return;
}
for (count = 0 ; count < 5 ; count++)
hdmi_writeb(val, HDMI_FC_INVIDCONF);
}
static void hdmi_enable_overflow_interrupts(void)
{
pr_debug("%s\n", __func__);
hdmi_writeb(0, HDMI_FC_MASK2);
hdmi_writeb(0, HDMI_IH_MUTE_FC_STAT2);
}
static void hdmi_disable_overflow_interrupts(void)
{
pr_debug("%s\n", __func__);
hdmi_writeb(HDMI_IH_MUTE_FC_STAT2_OVERFLOW_MASK,
HDMI_IH_MUTE_FC_STAT2);
hdmi_writeb(0xff, HDMI_FC_MASK2);
}
static void mxc_hdmi_notify_fb(struct mxc_hdmi *hdmi)
{
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
/* Don't notify if we aren't registered yet */
WARN_ON(!hdmi->fb_reg);
/* disable the phy before ipu changes mode */
mxc_hdmi_phy_disable(hdmi);
/*
* Note that fb_set_var will block. During this time,
* FB_EVENT_MODE_CHANGE callback will happen.
* So by the end of this function, mxc_hdmi_setup()
* will be done.
*/
hdmi->fbi->var.activate |= FB_ACTIVATE_FORCE;
console_lock();
hdmi->fbi->flags |= FBINFO_MISC_USEREVENT;
fb_set_var(hdmi->fbi, &hdmi->fbi->var);
hdmi->fbi->flags &= ~FBINFO_MISC_USEREVENT;
console_unlock();
dev_dbg(&hdmi->pdev->dev, "%s exit\n", __func__);
}
static void mxc_hdmi_edid_rebuild_modelist(struct mxc_hdmi *hdmi)
{
int i;
struct fb_videomode *mode;
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
console_lock();
fb_destroy_modelist(&hdmi->fbi->modelist);
fb_add_videomode(&vga_mode, &hdmi->fbi->modelist);
for (i = 0; i < hdmi->fbi->monspecs.modedb_len; i++) {
/*
* We might check here if mode is supported by HDMI.
* We do not currently support interlaced modes.
* And add CEA modes in the modelist.
*/
mode = &hdmi->fbi->monspecs.modedb[i];
if (!(mode->vmode & FB_VMODE_INTERLACED) &&
(mxc_edid_mode_to_vic(mode) != 0)) {
dev_dbg(&hdmi->pdev->dev, "Added mode %d:", i);
dev_dbg(&hdmi->pdev->dev,
"xres = %d, yres = %d, freq = %d, vmode = %d, flag = %d\n",
hdmi->fbi->monspecs.modedb[i].xres,
hdmi->fbi->monspecs.modedb[i].yres,
hdmi->fbi->monspecs.modedb[i].refresh,
hdmi->fbi->monspecs.modedb[i].vmode,
hdmi->fbi->monspecs.modedb[i].flag);
fb_add_videomode(mode, &hdmi->fbi->modelist);
}
}
fb_new_modelist(hdmi->fbi);
console_unlock();
}
static void mxc_hdmi_default_edid_cfg(struct mxc_hdmi *hdmi)
{
/* Default setting HDMI working in HDMI mode */
hdmi->edid_cfg.hdmi_cap = true;
}
static void mxc_hdmi_default_modelist(struct mxc_hdmi *hdmi)
{
struct fb_modelist *modelist;
struct fb_videomode *m;
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
/* If no EDID data read, set up default modelist; since we don't know
* the supported modes of the current sink, we will use only one mode in
* this modelist:
* the default_mode set up at init (usually got from cmdline)
*/
dev_info(&hdmi->pdev->dev, "create default modelist\n");
/* If the current modelist is already default, don't re-create it*/
if (list_is_singular(&hdmi->fbi->modelist)) {
modelist = list_entry((&hdmi->fbi->modelist)->next,
struct fb_modelist, list);
m = &modelist->mode;
if (fb_mode_is_equal(m, &hdmi->default_mode)) {
dev_info(&hdmi->pdev->dev,
"Modelist is already default, no need to re-create!\n");
return;
}
}
console_lock();
fb_destroy_modelist(&hdmi->fbi->modelist);
fb_add_videomode(&hdmi->default_mode, &hdmi->fbi->modelist);
fb_new_modelist(hdmi->fbi);
console_unlock();
}
static void mxc_hdmi_set_mode_to_vga_dvi(struct mxc_hdmi *hdmi)
{
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
hdmi_disable_overflow_interrupts();
fb_videomode_to_var(&hdmi->fbi->var, &vga_mode);
hdmi->requesting_vga_for_initialization = true;
mxc_hdmi_notify_fb(hdmi);
hdmi->requesting_vga_for_initialization = false;
}
static void mxc_hdmi_set_mode(struct mxc_hdmi *hdmi)
{
const struct fb_videomode *mode;
struct fb_videomode m;
struct fb_var_screeninfo var;
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
/* Set the default mode only once. */
if (!hdmi->dft_mode_set) {
fb_videomode_to_var(&var, &hdmi->default_mode);
hdmi->dft_mode_set = true;
} else
fb_videomode_to_var(&var, &hdmi->previous_non_vga_mode);
fb_var_to_videomode(&m, &var);
dump_fb_videomode(&m);
mode = fb_find_nearest_mode(&m, &hdmi->fbi->modelist);
if (!mode) {
pr_err("%s: could not find mode in modelist\n", __func__);
return;
}
/* If both video mode and work mode same as previous,
* init HDMI again */
if (fb_mode_is_equal(&hdmi->previous_non_vga_mode, mode) &&
(hdmi->edid_cfg.hdmi_cap != hdmi->hdmi_data.video_mode.mDVI)) {
dev_dbg(&hdmi->pdev->dev,
"%s: Video mode same as previous\n", __func__);
/* update fbi mode in case modelist is updated */
hdmi->fbi->mode = (struct fb_videomode *)mode;
fb_videomode_to_var(&hdmi->fbi->var, mode);
/* update hdmi setting in case EDID data updated */
mxc_hdmi_setup(hdmi, 0);
} else {
dev_dbg(&hdmi->pdev->dev, "%s: New video mode\n", __func__);
mxc_hdmi_set_mode_to_vga_dvi(hdmi);
fb_videomode_to_var(&hdmi->fbi->var, mode);
dump_fb_videomode((struct fb_videomode *)mode);
mxc_hdmi_notify_fb(hdmi);
}
}
static void mxc_hdmi_cable_connected(struct mxc_hdmi *hdmi)
{
int edid_status;
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
hdmi->cable_plugin = true;
/* HDMI Initialization Step C */
edid_status = mxc_hdmi_read_edid(hdmi);
/* Read EDID again if first EDID read failed */
if (edid_status == HDMI_EDID_NO_MODES ||
edid_status == HDMI_EDID_FAIL) {
int retry_status;
dev_info(&hdmi->pdev->dev, "Read EDID again\n");
msleep(200);
retry_status = mxc_hdmi_read_edid(hdmi);
/* If we get NO_MODES on the 1st and SAME on the 2nd attempt we
* want NO_MODES as final result. */
if (retry_status != HDMI_EDID_SAME)
edid_status = retry_status;
}
/* HDMI Initialization Steps D, E, F */
switch (edid_status) {
case HDMI_EDID_SUCCESS:
mxc_hdmi_edid_rebuild_modelist(hdmi);
break;
/* Nothing to do if EDID same */
case HDMI_EDID_SAME:
break;
case HDMI_EDID_FAIL:
mxc_hdmi_default_edid_cfg(hdmi);
/* No break here */
case HDMI_EDID_NO_MODES:
default:
mxc_hdmi_default_modelist(hdmi);
break;
}
/* Setting video mode */
mxc_hdmi_set_mode(hdmi);
dev_dbg(&hdmi->pdev->dev, "%s exit\n", __func__);
}
static int mxc_hdmi_power_on(struct mxc_dispdrv_handle *disp,
struct fb_info *fbi)
{
struct mxc_hdmi *hdmi = mxc_dispdrv_getdata(disp);
mxc_hdmi_phy_init(hdmi);
return 0;
}
static void mxc_hdmi_power_off(struct mxc_dispdrv_handle *disp,
struct fb_info *fbi)
{
struct mxc_hdmi *hdmi = mxc_dispdrv_getdata(disp);
mxc_hdmi_phy_disable(hdmi);
}
static void mxc_hdmi_cable_disconnected(struct mxc_hdmi *hdmi)
{
u8 clkdis;
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
/* Disable All HDMI clock and bypass cec */
clkdis = hdmi_readb(HDMI_MC_CLKDIS);
clkdis |= 0x5f;
hdmi_writeb(clkdis, HDMI_MC_CLKDIS);
mxc_hdmi_phy_disable(hdmi);
hdmi_disable_overflow_interrupts();
hdmi->cable_plugin = false;
}
static void hotplug_worker(struct work_struct *work)
{
struct delayed_work *delay_work = to_delayed_work(work);
struct mxc_hdmi *hdmi =
container_of(delay_work, struct mxc_hdmi, hotplug_work);
u32 phy_int_stat, phy_int_pol, phy_int_mask;
u8 val;
unsigned long flags;
char event_string[32];
char *envp[] = { event_string, NULL };
phy_int_stat = hdmi->latest_intr_stat;
phy_int_pol = hdmi_readb(HDMI_PHY_POL0);
dev_dbg(&hdmi->pdev->dev, "phy_int_stat=0x%x, phy_int_pol=0x%x\n",
phy_int_stat, phy_int_pol);
/* check cable status */
if (phy_int_stat & HDMI_IH_PHY_STAT0_HPD) {
/* cable connection changes */
if (phy_int_pol & HDMI_PHY_HPD) {
/* Plugin event */
dev_dbg(&hdmi->pdev->dev, "EVENT=plugin\n");
mxc_hdmi_cable_connected(hdmi);
/* Make HPD intr active low to capture unplug event */
val = hdmi_readb(HDMI_PHY_POL0);
val &= ~HDMI_PHY_HPD;
hdmi_writeb(val, HDMI_PHY_POL0);
hdmi_set_cable_state(1);
sprintf(event_string, "EVENT=plugin");
kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp);
#ifdef CONFIG_MXC_HDMI_CEC
mxc_hdmi_cec_handle(0x80);
#endif
} else if (!(phy_int_pol & HDMI_PHY_HPD)) {
/* Plugout event */
dev_dbg(&hdmi->pdev->dev, "EVENT=plugout\n");
hdmi_set_cable_state(0);
mxc_hdmi_abort_stream();
mxc_hdmi_cable_disconnected(hdmi);
/* Make HPD intr active high to capture plugin event */
val = hdmi_readb(HDMI_PHY_POL0);
val |= HDMI_PHY_HPD;
hdmi_writeb(val, HDMI_PHY_POL0);
sprintf(event_string, "EVENT=plugout");
kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp);
#ifdef CONFIG_MXC_HDMI_CEC
mxc_hdmi_cec_handle(0x100);
#endif
} else
dev_dbg(&hdmi->pdev->dev, "EVENT=none?\n");
}
/* Lock here to ensure full powerdown sequence
* completed before next interrupt processed */
spin_lock_irqsave(&hdmi->irq_lock, flags);
/* Re-enable HPD interrupts */
phy_int_mask = hdmi_readb(HDMI_PHY_MASK0);
phy_int_mask &= ~HDMI_PHY_HPD;
hdmi_writeb(phy_int_mask, HDMI_PHY_MASK0);
/* Unmute interrupts */
hdmi_writeb(~HDMI_IH_MUTE_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0);
if (hdmi_readb(HDMI_IH_FC_STAT2) & HDMI_IH_FC_STAT2_OVERFLOW_MASK)
mxc_hdmi_clear_overflow(hdmi);
spin_unlock_irqrestore(&hdmi->irq_lock, flags);
}
static void hdcp_hdp_worker(struct work_struct *work)
{
struct delayed_work *delay_work = to_delayed_work(work);
struct mxc_hdmi *hdmi =
container_of(delay_work, struct mxc_hdmi, hdcp_hdp_work);
char event_string[32];
char *envp[] = { event_string, NULL };
/* HDCP interrupt */
sprintf(event_string, "EVENT=hdcpint");
kobject_uevent_env(&hdmi->pdev->dev.kobj, KOBJ_CHANGE, envp);
/* Unmute interrupts in HDCP application*/
}
static irqreturn_t mxc_hdmi_hotplug(int irq, void *data)
{
struct mxc_hdmi *hdmi = data;
u8 val, intr_stat;
unsigned long flags;
spin_lock_irqsave(&hdmi->irq_lock, flags);
/* Check and clean packet overflow interrupt.*/
if (hdmi_readb(HDMI_IH_FC_STAT2) &
HDMI_IH_FC_STAT2_OVERFLOW_MASK) {
mxc_hdmi_clear_overflow(hdmi);
dev_dbg(&hdmi->pdev->dev, "Overflow interrupt received\n");
/* clear irq status */
hdmi_writeb(HDMI_IH_FC_STAT2_OVERFLOW_MASK,
HDMI_IH_FC_STAT2);
}
/*
* We could not disable the irq. Probably the audio driver
* has enabled it. Masking off the HDMI interrupts using
* HDMI registers.
*/
/* Capture status - used in hotplug_worker ISR */
intr_stat = hdmi_readb(HDMI_IH_PHY_STAT0);
if (intr_stat & HDMI_IH_PHY_STAT0_HPD) {
dev_dbg(&hdmi->pdev->dev, "Hotplug interrupt received\n");
hdmi->latest_intr_stat = intr_stat;
/* Mute interrupts until handled */
val = hdmi_readb(HDMI_IH_MUTE_PHY_STAT0);
val |= HDMI_IH_MUTE_PHY_STAT0_HPD;
hdmi_writeb(val, HDMI_IH_MUTE_PHY_STAT0);
val = hdmi_readb(HDMI_PHY_MASK0);
val |= HDMI_PHY_HPD;
hdmi_writeb(val, HDMI_PHY_MASK0);
/* Clear Hotplug interrupts */
hdmi_writeb(HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0);
schedule_delayed_work(&(hdmi->hotplug_work), msecs_to_jiffies(20));
}
/* Check HDCP interrupt state */
if (hdmi->hdmi_data.hdcp_enable) {
val = hdmi_readb(HDMI_A_APIINTSTAT);
if (val != 0) {
/* Mute interrupts until interrupt handled */
val = 0xFF;
hdmi_writeb(val, HDMI_A_APIINTMSK);
schedule_delayed_work(&(hdmi->hdcp_hdp_work), msecs_to_jiffies(50));
}
}
spin_unlock_irqrestore(&hdmi->irq_lock, flags);
return IRQ_HANDLED;
}
static void mxc_hdmi_setup(struct mxc_hdmi *hdmi, unsigned long event)
{
struct fb_videomode m;
const struct fb_videomode *edid_mode;
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
fb_var_to_videomode(&m, &hdmi->fbi->var);
dump_fb_videomode(&m);
dev_dbg(&hdmi->pdev->dev, "%s - video mode changed\n", __func__);
hdmi->vic = 0;
if (!hdmi->requesting_vga_for_initialization) {
/* Save mode if this isn't the result of requesting
* vga default. */
memcpy(&hdmi->previous_non_vga_mode, &m,
sizeof(struct fb_videomode));
if (!list_empty(&hdmi->fbi->modelist)) {
edid_mode = fb_find_nearest_mode(&m, &hdmi->fbi->modelist);
pr_debug("edid mode ");
dump_fb_videomode((struct fb_videomode *)edid_mode);
/* update fbi mode */
hdmi->fbi->mode = (struct fb_videomode *)edid_mode;
hdmi->vic = mxc_edid_mode_to_vic(edid_mode);
}
}
hdmi_disable_overflow_interrupts();
dev_dbg(&hdmi->pdev->dev, "CEA mode used vic=%d\n", hdmi->vic);
if (hdmi->edid_cfg.hdmi_cap)
hdmi->hdmi_data.video_mode.mDVI = false;
else {
dev_dbg(&hdmi->pdev->dev, "CEA mode vic=%d work in DVI\n", hdmi->vic);
hdmi->hdmi_data.video_mode.mDVI = true;
}
if ((hdmi->vic == 6) || (hdmi->vic == 7) ||
(hdmi->vic == 21) || (hdmi->vic == 22) ||
(hdmi->vic == 2) || (hdmi->vic == 3) ||
(hdmi->vic == 17) || (hdmi->vic == 18))
hdmi->hdmi_data.colorimetry = eITU601;
else
hdmi->hdmi_data.colorimetry = eITU709;
if ((hdmi->vic == 10) || (hdmi->vic == 11) ||
(hdmi->vic == 12) || (hdmi->vic == 13) ||
(hdmi->vic == 14) || (hdmi->vic == 15) ||
(hdmi->vic == 25) || (hdmi->vic == 26) ||
(hdmi->vic == 27) || (hdmi->vic == 28) ||
(hdmi->vic == 29) || (hdmi->vic == 30) ||
(hdmi->vic == 35) || (hdmi->vic == 36) ||
(hdmi->vic == 37) || (hdmi->vic == 38))
hdmi->hdmi_data.video_mode.mPixelRepetitionOutput = 1;
else
hdmi->hdmi_data.video_mode.mPixelRepetitionOutput = 0;
hdmi->hdmi_data.video_mode.mPixelRepetitionInput = 0;
/* TODO: Get input format from IPU (via FB driver iface) */
hdmi->hdmi_data.enc_in_format = RGB;
hdmi->hdmi_data.enc_out_format = RGB;
/* YCbCr only enabled in HDMI mode */
if (!hdmi->hdmi_data.video_mode.mDVI &&
!hdmi->hdmi_data.rgb_out_enable) {
if (hdmi->edid_cfg.cea_ycbcr444)
hdmi->hdmi_data.enc_out_format = YCBCR444;
else if (hdmi->edid_cfg.cea_ycbcr422)
hdmi->hdmi_data.enc_out_format = YCBCR422_8BITS;
}
/* IPU not support depth color output */
hdmi->hdmi_data.enc_color_depth = 8;
hdmi->hdmi_data.pix_repet_factor = 0;
hdmi->hdmi_data.video_mode.mDataEnablePolarity = true;
/* HDMI Initialization Step B.1 */
hdmi_av_composer(hdmi);
/* HDMI Initializateion Step B.2 */
mxc_hdmi_phy_init(hdmi);
/* HDMI Initialization Step B.3 */
mxc_hdmi_enable_video_path(hdmi);
/* not for DVI mode */
if (hdmi->hdmi_data.video_mode.mDVI)
dev_dbg(&hdmi->pdev->dev, "%s DVI mode\n", __func__);
else {
dev_dbg(&hdmi->pdev->dev, "%s CEA mode\n", __func__);
/* HDMI Initialization Step E - Configure audio */
hdmi_clk_regenerator_update_pixel_clock(hdmi->fbi->var.pixclock);
hdmi_enable_audio_clk(hdmi);
/* HDMI Initialization Step F - Configure AVI InfoFrame */
hdmi_config_AVI(hdmi);
}
hdmi_video_packetize(hdmi);
hdmi_video_csc(hdmi);
hdmi_video_sample(hdmi);
mxc_hdmi_clear_overflow(hdmi);
dev_dbg(&hdmi->pdev->dev, "%s exit\n\n", __func__);
}
/* Wait until we are registered to enable interrupts */
static void mxc_hdmi_fb_registered(struct mxc_hdmi *hdmi)
{
unsigned long flags;
if (hdmi->fb_reg)
return;
spin_lock_irqsave(&hdmi->irq_lock, flags);
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
hdmi_writeb(HDMI_PHY_I2CM_INT_ADDR_DONE_POL,
HDMI_PHY_I2CM_INT_ADDR);
hdmi_writeb(HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL |
HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL,
HDMI_PHY_I2CM_CTLINT_ADDR);
/* enable cable hot plug irq */
hdmi_writeb((u8)~HDMI_PHY_HPD, HDMI_PHY_MASK0);
/* Clear Hotplug interrupts */
hdmi_writeb(HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0);
/* Unmute interrupts */
hdmi_writeb(~HDMI_IH_MUTE_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0);
hdmi->fb_reg = true;
spin_unlock_irqrestore(&hdmi->irq_lock, flags);
}
static int mxc_hdmi_fb_event(struct notifier_block *nb,
unsigned long val, void *v)
{
struct fb_event *event = v;
struct mxc_hdmi *hdmi = container_of(nb, struct mxc_hdmi, nb);
if (strcmp(event->info->fix.id, hdmi->fbi->fix.id))
return 0;
switch (val) {
case FB_EVENT_FB_REGISTERED:
dev_dbg(&hdmi->pdev->dev, "event=FB_EVENT_FB_REGISTERED\n");
mxc_hdmi_fb_registered(hdmi);
hdmi_set_registered(1);
break;
case FB_EVENT_FB_UNREGISTERED:
dev_dbg(&hdmi->pdev->dev, "event=FB_EVENT_FB_UNREGISTERED\n");
hdmi->fb_reg = false;
hdmi_set_registered(0);
break;
case FB_EVENT_MODE_CHANGE:
dev_dbg(&hdmi->pdev->dev, "event=FB_EVENT_MODE_CHANGE\n");
if (hdmi->fb_reg)
mxc_hdmi_setup(hdmi, val);
break;
case FB_EVENT_BLANK:
if ((*((int *)event->data) == FB_BLANK_UNBLANK) &&
(*((int *)event->data) != hdmi->blank)) {
dev_dbg(&hdmi->pdev->dev,
"event=FB_EVENT_BLANK - UNBLANK\n");
hdmi->blank = *((int *)event->data);
if (hdmi->fb_reg && hdmi->cable_plugin)
mxc_hdmi_setup(hdmi, val);
hdmi_set_blank_state(1);
} else if (*((int *)event->data) != hdmi->blank) {
dev_dbg(&hdmi->pdev->dev,
"event=FB_EVENT_BLANK - BLANK\n");
hdmi_set_blank_state(0);
mxc_hdmi_abort_stream();
mxc_hdmi_phy_disable(hdmi);
hdmi->blank = *((int *)event->data);
} else
dev_dbg(&hdmi->pdev->dev,
"FB BLANK state no changed!\n");
break;
case FB_EVENT_SUSPEND:
dev_dbg(&hdmi->pdev->dev,
"event=FB_EVENT_SUSPEND\n");
if (hdmi->blank == FB_BLANK_UNBLANK) {
mxc_hdmi_phy_disable(hdmi);
clk_disable(hdmi->hdmi_iahb_clk);
clk_disable(hdmi->hdmi_isfr_clk);
clk_disable(hdmi->mipi_core_clk);
}
break;
case FB_EVENT_RESUME:
dev_dbg(&hdmi->pdev->dev,
"event=FB_EVENT_RESUME\n");
if (hdmi->blank == FB_BLANK_UNBLANK) {
clk_enable(hdmi->mipi_core_clk);
clk_enable(hdmi->hdmi_iahb_clk);
clk_enable(hdmi->hdmi_isfr_clk);
mxc_hdmi_phy_init(hdmi);
}
break;
}
return 0;
}
static void hdmi_init_route(struct mxc_hdmi *hdmi)
{
uint32_t hdmi_mux_setting, reg;
int ipu_id, disp_id;
ipu_id = mxc_hdmi_ipu_id;
disp_id = mxc_hdmi_disp_id;
if ((ipu_id > 1) || (ipu_id < 0)) {
pr_err("Invalid IPU select for HDMI: %d. Set to 0\n", ipu_id);
ipu_id = 0;
}
if ((disp_id > 1) || (disp_id < 0)) {
pr_err("Invalid DI select for HDMI: %d. Set to 0\n", disp_id);
disp_id = 0;
}
reg = readl(hdmi->gpr_hdmi_base);
/* Configure the connection between IPU1/2 and HDMI */
hdmi_mux_setting = 2*ipu_id + disp_id;
/* GPR3, bits 2-3 = HDMI_MUX_CTL */
reg &= ~0xd;
reg |= hdmi_mux_setting << 2;
writel(reg, hdmi->gpr_hdmi_base);
/* Set HDMI event as SDMA event2 for HDMI audio */
reg = readl(hdmi->gpr_sdma_base);
reg |= 0x1;
writel(reg, hdmi->gpr_sdma_base);
}
static void hdmi_hdcp_get_property(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
/* Check hdcp enable by dts.*/
hdcp_init = of_property_read_bool(np, "fsl,hdcp");
if (hdcp_init)
dev_dbg(&pdev->dev, "hdcp enable\n");
else
dev_dbg(&pdev->dev, "hdcp disable\n");
}
static void hdmi_get_of_property(struct mxc_hdmi *hdmi)
{
struct platform_device *pdev = hdmi->pdev;
struct device_node *np = pdev->dev.of_node;
const struct of_device_id *of_id =
of_match_device(imx_hdmi_dt_ids, &pdev->dev);
int ret;
u32 phy_reg_vlev = 0, phy_reg_cksymtx = 0;
if (of_id) {
pdev->id_entry = of_id->data;
hdmi->cpu_type = pdev->id_entry->driver_data;
}
/* HDMI PHY register vlev and cksymtx preperty is optional.
* It is for specific board to pass HCT electrical part.
* Default value will been setting in HDMI PHY config function
* if it is not define in device tree.
*/
ret = of_property_read_u32(np, "fsl,phy_reg_vlev", &phy_reg_vlev);
if (ret)
dev_dbg(&pdev->dev, "No board specific HDMI PHY vlev\n");
ret = of_property_read_u32(np, "fsl,phy_reg_cksymtx", &phy_reg_cksymtx);
if (ret)
dev_dbg(&pdev->dev, "No board specific HDMI PHY cksymtx\n");
/* Specific phy config */
hdmi->phy_config.reg_cksymtx = phy_reg_cksymtx;
hdmi->phy_config.reg_vlev = phy_reg_vlev;
}
/* HDMI Initialization Step A */
static int mxc_hdmi_disp_init(struct mxc_dispdrv_handle *disp,
struct mxc_dispdrv_setting *setting)
{
int ret = 0;
u32 i;
const struct fb_videomode *mode;
struct fb_videomode m;
struct mxc_hdmi *hdmi = mxc_dispdrv_getdata(disp);
int irq = platform_get_irq(hdmi->pdev, 0);
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
/* Check hdmi disp init once */
if (hdmi_inited) {
dev_err(&hdmi->pdev->dev,
"Error only one HDMI output support now!\n");
return -1;
}
hdmi_get_of_property(hdmi);
if (irq < 0)
return -ENODEV;
/* Setting HDMI default to blank state */
hdmi->blank = FB_BLANK_POWERDOWN;
ret = ipu_di_to_crtc(&hdmi->pdev->dev, mxc_hdmi_ipu_id,
mxc_hdmi_disp_id, &setting->crtc);
if (ret < 0)
return ret;
setting->if_fmt = IPU_PIX_FMT_RGB24;
hdmi->dft_mode_str = setting->dft_mode_str;
hdmi->default_bpp = setting->default_bpp;
dev_dbg(&hdmi->pdev->dev, "%s - default mode %s bpp=%d\n",
__func__, hdmi->dft_mode_str, hdmi->default_bpp);
hdmi->fbi = setting->fbi;
hdmi_init_route(hdmi);
hdmi->mipi_core_clk = clk_get(&hdmi->pdev->dev, "mipi_core");
if (IS_ERR(hdmi->mipi_core_clk)) {
ret = PTR_ERR(hdmi->mipi_core_clk);
dev_err(&hdmi->pdev->dev,
"Unable to get mipi core clk: %d\n", ret);
goto egetclk;
}
ret = clk_prepare_enable(hdmi->mipi_core_clk);
if (ret < 0) {
dev_err(&hdmi->pdev->dev,
"Cannot enable mipi core clock: %d\n", ret);
goto erate;
}
hdmi->hdmi_isfr_clk = clk_get(&hdmi->pdev->dev, "hdmi_isfr");
if (IS_ERR(hdmi->hdmi_isfr_clk)) {
ret = PTR_ERR(hdmi->hdmi_isfr_clk);
dev_err(&hdmi->pdev->dev,
"Unable to get HDMI clk: %d\n", ret);
goto egetclk1;
}
ret = clk_prepare_enable(hdmi->hdmi_isfr_clk);
if (ret < 0) {
dev_err(&hdmi->pdev->dev,
"Cannot enable HDMI isfr clock: %d\n", ret);
goto erate1;
}
hdmi->hdmi_iahb_clk = clk_get(&hdmi->pdev->dev, "hdmi_iahb");
if (IS_ERR(hdmi->hdmi_iahb_clk)) {
ret = PTR_ERR(hdmi->hdmi_iahb_clk);
dev_err(&hdmi->pdev->dev,
"Unable to get HDMI clk: %d\n", ret);
goto egetclk2;
}
ret = clk_prepare_enable(hdmi->hdmi_iahb_clk);
if (ret < 0) {
dev_err(&hdmi->pdev->dev,
"Cannot enable HDMI iahb clock: %d\n", ret);
goto erate2;
}
dev_dbg(&hdmi->pdev->dev, "Enabled HDMI clocks\n");
/* Init DDC pins for HDCP */
if (hdcp_init) {
hdmi->pinctrl = devm_pinctrl_get_select_default(&hdmi->pdev->dev);
if (IS_ERR(hdmi->pinctrl)) {
dev_err(&hdmi->pdev->dev, "can't get/select DDC pinctrl\n");
goto erate2;
}
}
/* Product and revision IDs */
dev_info(&hdmi->pdev->dev,
"Detected HDMI controller 0x%x:0x%x:0x%x:0x%x\n",
hdmi_readb(HDMI_DESIGN_ID),
hdmi_readb(HDMI_REVISION_ID),
hdmi_readb(HDMI_PRODUCT_ID0),
hdmi_readb(HDMI_PRODUCT_ID1));
/* To prevent overflows in HDMI_IH_FC_STAT2, set the clk regenerator
* N and cts values before enabling phy */
hdmi_init_clk_regenerator();
INIT_LIST_HEAD(&hdmi->fbi->modelist);
spin_lock_init(&hdmi->irq_lock);
/* Set the default mode and modelist when disp init. */
fb_find_mode(&hdmi->fbi->var, hdmi->fbi,
hdmi->dft_mode_str, NULL, 0, NULL,
hdmi->default_bpp);
console_lock();
fb_destroy_modelist(&hdmi->fbi->modelist);
/*Add all no interlaced CEA mode to default modelist */
for (i = 0; i < ARRAY_SIZE(mxc_cea_mode); i++) {
mode = &mxc_cea_mode[i];
if (!(mode->vmode & FB_VMODE_INTERLACED) && (mode->xres != 0))
fb_add_videomode(mode, &hdmi->fbi->modelist);
}
console_unlock();
/* Find a nearest mode in default modelist */
fb_var_to_videomode(&m, &hdmi->fbi->var);
hdmi->dft_mode_set = false;
mode = fb_find_nearest_mode(&m, &hdmi->fbi->modelist);
if (!mode) {
pr_err("%s: could not find mode in modelist\n", __func__);
return -1;
}
dump_fb_videomode(mode);
/* Save default video mode */
memcpy(&hdmi->default_mode, mode, sizeof(struct fb_videomode));
fb_videomode_to_var(&hdmi->fbi->var, mode);
/* update fbi mode */
hdmi->fbi->mode = (struct fb_videomode *)mode;
/* Default setting HDMI working in HDMI mode*/
hdmi->edid_cfg.hdmi_cap = true;
INIT_DELAYED_WORK(&hdmi->hotplug_work, hotplug_worker);
INIT_DELAYED_WORK(&hdmi->hdcp_hdp_work, hdcp_hdp_worker);
/* Configure registers related to HDMI interrupt
* generation before registering IRQ. */
hdmi_writeb(HDMI_PHY_HPD, HDMI_PHY_POL0);
/* Clear Hotplug interrupts */
hdmi_writeb(HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0);
hdmi->nb.notifier_call = mxc_hdmi_fb_event;
ret = fb_register_client(&hdmi->nb);
if (ret < 0)
goto efbclient;
memset(&hdmi->hdmi_data, 0, sizeof(struct hdmi_data_info));
/* Default HDMI working in RGB mode */
hdmi->hdmi_data.rgb_out_enable = true;
ret = devm_request_irq(&hdmi->pdev->dev, irq, mxc_hdmi_hotplug, IRQF_SHARED,
dev_name(&hdmi->pdev->dev), hdmi);
if (ret < 0) {
dev_err(&hdmi->pdev->dev,
"Unable to request irq: %d\n", ret);
goto ereqirq;
}
ret = device_create_file(&hdmi->pdev->dev, &dev_attr_fb_name);
if (ret < 0)
dev_warn(&hdmi->pdev->dev,
"cound not create sys node for fb name\n");
ret = device_create_file(&hdmi->pdev->dev, &dev_attr_cable_state);
if (ret < 0)
dev_warn(&hdmi->pdev->dev,
"cound not create sys node for cable state\n");
ret = device_create_file(&hdmi->pdev->dev, &dev_attr_edid);
if (ret < 0)
dev_warn(&hdmi->pdev->dev,
"cound not create sys node for edid\n");
ret = device_create_file(&hdmi->pdev->dev, &dev_attr_rgb_out_enable);
if (ret < 0)
dev_warn(&hdmi->pdev->dev,
"cound not create sys node for rgb out enable\n");
ret = device_create_file(&hdmi->pdev->dev, &dev_attr_hdcp_enable);
if (ret < 0)
dev_warn(&hdmi->pdev->dev,
"cound not create sys node for hdcp enable\n");
dev_dbg(&hdmi->pdev->dev, "%s exit\n", __func__);
hdmi_inited = true;
return ret;
efbclient:
free_irq(irq, hdmi);
ereqirq:
clk_disable_unprepare(hdmi->hdmi_iahb_clk);
erate2:
clk_put(hdmi->hdmi_iahb_clk);
egetclk2:
clk_disable_unprepare(hdmi->hdmi_isfr_clk);
erate1:
clk_put(hdmi->hdmi_isfr_clk);
egetclk1:
clk_disable_unprepare(hdmi->mipi_core_clk);
erate:
clk_put(hdmi->mipi_core_clk);
egetclk:
dev_dbg(&hdmi->pdev->dev, "%s error exit\n", __func__);
return ret;
}
static void mxc_hdmi_disp_deinit(struct mxc_dispdrv_handle *disp)
{
struct mxc_hdmi *hdmi = mxc_dispdrv_getdata(disp);
dev_dbg(&hdmi->pdev->dev, "%s\n", __func__);
fb_unregister_client(&hdmi->nb);
clk_disable_unprepare(hdmi->hdmi_isfr_clk);
clk_put(hdmi->hdmi_isfr_clk);
clk_disable_unprepare(hdmi->hdmi_iahb_clk);
clk_put(hdmi->hdmi_iahb_clk);
clk_disable_unprepare(hdmi->mipi_core_clk);
clk_put(hdmi->mipi_core_clk);
platform_device_unregister(hdmi->pdev);
hdmi_inited = false;
}
static struct mxc_dispdrv_driver mxc_hdmi_drv = {
.name = DISPDRV_HDMI,
.init = mxc_hdmi_disp_init,
.deinit = mxc_hdmi_disp_deinit,
.enable = mxc_hdmi_power_on,
.disable = mxc_hdmi_power_off,
};
static int mxc_hdmi_open(struct inode *inode, struct file *file)
{
return 0;
}
static long mxc_hdmi_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
int __user *argp = (void __user *)arg;
int ret = 0;
switch (cmd) {
case HDMI_IOC_GET_RESOURCE:
ret = copy_to_user(argp, &g_hdmi->hdmi_data,
sizeof(g_hdmi->hdmi_data)) ? -EFAULT : 0;
break;
case HDMI_IOC_GET_CPU_TYPE:
ret = put_user(g_hdmi->cpu_type, argp);
break;
default:
pr_debug("Unsupport cmd %d\n", cmd);
break;
}
return ret;
}
static int mxc_hdmi_release(struct inode *inode, struct file *file)
{
return 0;
}
static const struct file_operations mxc_hdmi_fops = {
.owner = THIS_MODULE,
.open = mxc_hdmi_open,
.release = mxc_hdmi_release,
.unlocked_ioctl = mxc_hdmi_ioctl,
};
static int mxc_hdmi_probe(struct platform_device *pdev)
{
struct mxc_hdmi *hdmi;
struct device *temp_class;
struct resource *res;
int ret = 0;
/* Check I2C driver is loaded and available
* check hdcp function is enable by dts */
hdmi_hdcp_get_property(pdev);
if (!hdmi_i2c && !hdcp_init)
return -ENODEV;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENOENT;
hdmi = devm_kzalloc(&pdev->dev,
sizeof(struct mxc_hdmi),
GFP_KERNEL);
if (!hdmi) {
dev_err(&pdev->dev, "Cannot allocate device data\n");
ret = -ENOMEM;
goto ealloc;
}
g_hdmi = hdmi;
hdmi_major = register_chrdev(hdmi_major, "mxc_hdmi", &mxc_hdmi_fops);
if (hdmi_major < 0) {
printk(KERN_ERR "HDMI: unable to get a major for HDMI\n");
ret = -EBUSY;
goto ealloc;
}
hdmi_class = class_create(THIS_MODULE, "mxc_hdmi");
if (IS_ERR(hdmi_class)) {
ret = PTR_ERR(hdmi_class);
goto err_out_chrdev;
}
temp_class = device_create(hdmi_class, NULL, MKDEV(hdmi_major, 0),
NULL, "mxc_hdmi");
if (IS_ERR(temp_class)) {
ret = PTR_ERR(temp_class);
goto err_out_class;
}
hdmi->pdev = pdev;
hdmi->core_pdev = platform_device_alloc("mxc_hdmi_core", -1);
if (!hdmi->core_pdev) {
pr_err("%s failed platform_device_alloc for hdmi core\n",
__func__);
ret = -ENOMEM;
goto ecore;
}
hdmi->gpr_base = ioremap(res->start, resource_size(res));
if (!hdmi->gpr_base) {
dev_err(&pdev->dev, "ioremap failed\n");
ret = -ENOMEM;
goto eiomap;
}
hdmi->gpr_hdmi_base = hdmi->gpr_base + 3;
hdmi->gpr_sdma_base = hdmi->gpr_base;
hdmi_inited = false;
hdmi->disp_mxc_hdmi = mxc_dispdrv_register(&mxc_hdmi_drv);
if (IS_ERR(hdmi->disp_mxc_hdmi)) {
dev_err(&pdev->dev, "Failed to register dispdrv - 0x%x\n",
(int)hdmi->disp_mxc_hdmi);
ret = (int)hdmi->disp_mxc_hdmi;
goto edispdrv;
}
mxc_dispdrv_setdata(hdmi->disp_mxc_hdmi, hdmi);
platform_set_drvdata(pdev, hdmi);
hdmi_regulator = devm_regulator_get(&pdev->dev, "HDMI");
if (!IS_ERR(hdmi_regulator)) {
ret = regulator_enable(hdmi_regulator);
if (ret) {
dev_err(&pdev->dev, "enable 5v hdmi regulator failed\n");
goto edispdrv;
}
} else {
hdmi_regulator = NULL;
dev_warn(&pdev->dev, "No hdmi 5v supply\n");
}
return 0;
edispdrv:
iounmap(hdmi->gpr_base);
eiomap:
platform_device_put(hdmi->core_pdev);
ecore:
kfree(hdmi);
err_out_class:
device_destroy(hdmi_class, MKDEV(hdmi_major, 0));
class_destroy(hdmi_class);
err_out_chrdev:
unregister_chrdev(hdmi_major, "mxc_hdmi");
ealloc:
return ret;
}
static int mxc_hdmi_remove(struct platform_device *pdev)
{
struct mxc_hdmi *hdmi = platform_get_drvdata(pdev);
int irq = platform_get_irq(pdev, 0);
fb_unregister_client(&hdmi->nb);
mxc_dispdrv_puthandle(hdmi->disp_mxc_hdmi);
mxc_dispdrv_unregister(hdmi->disp_mxc_hdmi);
iounmap(hdmi->gpr_base);
/* No new work will be scheduled, wait for running ISR */
free_irq(irq, hdmi);
kfree(hdmi);
if (hdmi_regulator)
regulator_disable(hdmi_regulator);
g_hdmi = NULL;
return 0;
}
static struct platform_driver mxc_hdmi_driver = {
.probe = mxc_hdmi_probe,
.remove = mxc_hdmi_remove,
.driver = {
.name = "mxc_hdmi",
.of_match_table = imx_hdmi_dt_ids,
.owner = THIS_MODULE,
},
};
static int __init mxc_hdmi_init(void)
{
return platform_driver_register(&mxc_hdmi_driver);
}
module_init(mxc_hdmi_init);
static void __exit mxc_hdmi_exit(void)
{
if (hdmi_major > 0) {
device_destroy(hdmi_class, MKDEV(hdmi_major, 0));
class_destroy(hdmi_class);
unregister_chrdev(hdmi_major, "mxc_hdmi");
hdmi_major = 0;
}
platform_driver_unregister(&mxc_hdmi_driver);
}
module_exit(mxc_hdmi_exit);
static int mxc_hdmi_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE | I2C_FUNC_I2C))
return -ENODEV;
hdmi_i2c = client;
return 0;
}
static int mxc_hdmi_i2c_remove(struct i2c_client *client)
{
hdmi_i2c = NULL;
return 0;
}
static const struct of_device_id imx_hdmi_i2c_match[] = {
{ .compatible = "fsl,imx6-hdmi-i2c", },
{ /* sentinel */ }
};
static const struct i2c_device_id mxc_hdmi_i2c_id[] = {
{ "mxc_hdmi_i2c", 0 },
{},
};
MODULE_DEVICE_TABLE(i2c, mxc_hdmi_i2c_id);
static struct i2c_driver mxc_hdmi_i2c_driver = {
.driver = {
.name = "mxc_hdmi_i2c",
.of_match_table = imx_hdmi_i2c_match,
},
.probe = mxc_hdmi_i2c_probe,
.remove = mxc_hdmi_i2c_remove,
.id_table = mxc_hdmi_i2c_id,
};
static int __init mxc_hdmi_i2c_init(void)
{
return i2c_add_driver(&mxc_hdmi_i2c_driver);
}
static void __exit mxc_hdmi_i2c_exit(void)
{
i2c_del_driver(&mxc_hdmi_i2c_driver);
}
module_init(mxc_hdmi_i2c_init);
module_exit(mxc_hdmi_i2c_exit);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");