blob: ae61cb6c0cdaadf92fe993c11ad981af72e14496 [file] [log] [blame]
/*
* DesignWare High-Definition Multimedia Interface (HDMI) driver
*
* Copyright (C) 2013-2015 Mentor Graphics Inc.
* Copyright (C) 2011-2013 Freescale Semiconductor, Inc.
* 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 as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Designware High-Definition Multimedia Interface (HDMI) driver
*
*/
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/hdmi.h>
#include <linux/irq.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_device.h>
#include <linux/pinctrl/consumer.h>
#include <drm/drm_of.h>
#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_encoder_slave.h>
#include <drm/bridge/dw_hdmi.h>
#include "dw_hdmi.h"
#define HDMI_EDID_LEN 512
#define DDC_SEGMENT_ADDR 0x30
#define RGB 0
#define YCBCR444 1
#define YCBCR422_16BITS 2
#define YCBCR422_8BITS 3
#define XVYCC444 4
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 dw_hdmi_hdcp_state {
DW_HDMI_HDCP_STATE_DISABLED,
DW_HDMI_HDCP_STATE_AUTH_START,
DW_HDMI_HDCP_STATE_AUTH_DONE,
DW_HDMI_HDCP_STATE_ENCRY_EN,
};
/*
* Unless otherwise noted, entries in this table are 100% optimization.
* Values can be obtained from hdmi_compute_n() but that function is
* slow so we pre-compute values we expect to see.
*
* All 32k and 48k values are expected to be the same (due to the way
* the math works) for any rate that's an exact kHz.
*/
static const struct dw_hdmi_audio_tmds_n common_tmds_n_table[] = {
{ .tmds = 25175000, .n_32k = 4096, .n_44k1 = 12854, .n_48k = 6144, },
{ .tmds = 25200000, .n_32k = 4096, .n_44k1 = 5656, .n_48k = 6144, },
{ .tmds = 27000000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
{ .tmds = 28320000, .n_32k = 4096, .n_44k1 = 5586, .n_48k = 6144, },
{ .tmds = 30240000, .n_32k = 4096, .n_44k1 = 5642, .n_48k = 6144, },
{ .tmds = 31500000, .n_32k = 4096, .n_44k1 = 5600, .n_48k = 6144, },
{ .tmds = 32000000, .n_32k = 4096, .n_44k1 = 5733, .n_48k = 6144, },
{ .tmds = 33750000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, },
{ .tmds = 36000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
{ .tmds = 40000000, .n_32k = 4096, .n_44k1 = 5733, .n_48k = 6144, },
{ .tmds = 49500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
{ .tmds = 50000000, .n_32k = 4096, .n_44k1 = 5292, .n_48k = 6144, },
{ .tmds = 54000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
{ .tmds = 65000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, },
{ .tmds = 68250000, .n_32k = 4096, .n_44k1 = 5376, .n_48k = 6144, },
{ .tmds = 71000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, },
{ .tmds = 72000000, .n_32k = 4096, .n_44k1 = 5635, .n_48k = 6144, },
{ .tmds = 73250000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, },
{ .tmds = 74250000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, },
{ .tmds = 75000000, .n_32k = 4096, .n_44k1 = 5880, .n_48k = 6144, },
{ .tmds = 78750000, .n_32k = 4096, .n_44k1 = 5600, .n_48k = 6144, },
{ .tmds = 78800000, .n_32k = 4096, .n_44k1 = 5292, .n_48k = 6144, },
{ .tmds = 79500000, .n_32k = 4096, .n_44k1 = 4704, .n_48k = 6144, },
{ .tmds = 83500000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, },
{ .tmds = 85500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
{ .tmds = 88750000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, },
{ .tmds = 97750000, .n_32k = 4096, .n_44k1 = 14112, .n_48k = 6144, },
{ .tmds = 101000000, .n_32k = 4096, .n_44k1 = 7056, .n_48k = 6144, },
{ .tmds = 106500000, .n_32k = 4096, .n_44k1 = 4704, .n_48k = 6144, },
{ .tmds = 108000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
{ .tmds = 115500000, .n_32k = 4096, .n_44k1 = 5712, .n_48k = 6144, },
{ .tmds = 119000000, .n_32k = 4096, .n_44k1 = 5544, .n_48k = 6144, },
{ .tmds = 135000000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
{ .tmds = 146250000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, },
{ .tmds = 148500000, .n_32k = 4096, .n_44k1 = 5488, .n_48k = 6144, },
{ .tmds = 154000000, .n_32k = 4096, .n_44k1 = 5544, .n_48k = 6144, },
{ .tmds = 162000000, .n_32k = 4096, .n_44k1 = 5684, .n_48k = 6144, },
/* For 297 MHz+ HDMI spec have some other rule for setting N */
{ .tmds = 297000000, .n_32k = 3073, .n_44k1 = 4704, .n_48k = 5120, },
{ .tmds = 594000000, .n_32k = 3073, .n_44k1 = 9408, .n_48k = 10240, },
/* End of table */
{ .tmds = 0, .n_32k = 0, .n_44k1 = 0, .n_48k = 0, },
};
static const u16 csc_coeff_default[3][4] = {
{ 0x2000, 0x0000, 0x0000, 0x0000 },
{ 0x0000, 0x2000, 0x0000, 0x0000 },
{ 0x0000, 0x0000, 0x2000, 0x0000 }
};
static const u16 csc_coeff_rgb_out_eitu601[3][4] = {
{ 0x2000, 0x6926, 0x74fd, 0x010e },
{ 0x2000, 0x2cdd, 0x0000, 0x7e9a },
{ 0x2000, 0x0000, 0x38b4, 0x7e3b }
};
static const u16 csc_coeff_rgb_out_eitu709[3][4] = {
{ 0x2000, 0x7106, 0x7a02, 0x00a7 },
{ 0x2000, 0x3264, 0x0000, 0x7e6d },
{ 0x2000, 0x0000, 0x3b61, 0x7e25 }
};
static const u16 csc_coeff_rgb_in_eitu601[3][4] = {
{ 0x2591, 0x1322, 0x074b, 0x0000 },
{ 0x6535, 0x2000, 0x7acc, 0x0200 },
{ 0x6acd, 0x7534, 0x2000, 0x0200 }
};
static const u16 csc_coeff_rgb_in_eitu709[3][4] = {
{ 0x2dc5, 0x0d9b, 0x049e, 0x0000 },
{ 0x62f0, 0x2000, 0x7d11, 0x0200 },
{ 0x6756, 0x78ab, 0x2000, 0x0200 }
};
struct hdmi_id {
u8 design;
u8 revision;
bool prepen;
bool audspdif;
bool audi2s;
bool hdmi14;
bool csc;
bool hdcp;
bool hdmi20;
bool confapb;
bool ahbauddma;
bool gpaud;
u8 phy_type;
};
struct hdmi_vmode {
bool mdvi;
bool has_audio;
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;
struct hdmi_vmode video_mode;
};
struct dw_hdmi_i2c {
struct i2c_adapter adap;
struct mutex lock;
struct completion cmp;
u8 stat;
u8 slave_reg;
bool is_regaddr;
bool is_segment;
};
struct dw_hdmi {
struct drm_connector connector;
struct drm_encoder *encoder;
struct drm_bridge *bridge;
struct platform_device *audio_pdev;
dw_hdmi_audio_plugged_fn audio_plugged_fn;
enum dw_hdmi_devtype dev_type;
struct device *dev;
struct clk *isfr_clk;
struct clk *iahb_clk;
struct dw_hdmi_i2c *i2c;
struct hdmi_id id;
struct hdmi_data_info hdmi_data;
const struct dw_hdmi_plat_data *plat_data;
int vic;
bool hpd_ignore;
u8 edid[HDMI_EDID_LEN];
bool cable_plugin;
bool phy_enabled;
struct drm_display_mode previous_mode;
struct regmap *regmap;
struct i2c_adapter *ddc;
struct pinctrl *pinctrl;
void __iomem *regs;
struct pinctrl_state *default_state;
struct pinctrl_state *unwedge_state;
unsigned int sample_rate;
/* this spinlock is used for audio clock control */
spinlock_t audio_lock;
bool audio_enable;
/* this mutex protects HPD config; used in threaded IRQ handler */
struct mutex hpd_mutex;
bool plugged;
/* this mutex is used for sync hdcp key content */
struct mutex hdcp_key_mutex;
struct dw_hdmi_hdcp_key_1x hdcp_key;
bool is_hdcp_key_present;
/* Protected by i2c mutex; see below */
struct mutex hdcp_state_mutex;
enum dw_hdmi_hdcp_state hdcp_state;
spinlock_t reg_lock;
void (*write)(struct dw_hdmi *hdmi, u8 val, int offset);
u8 (*read)(struct dw_hdmi *hdmi, int offset);
};
static void dw_hdmi_writel(struct dw_hdmi *hdmi, u8 val, int offset)
{
writel(val, hdmi->regs + (offset << 2));
}
static u8 dw_hdmi_readl(struct dw_hdmi *hdmi, int offset)
{
return readl(hdmi->regs + (offset << 2));
}
static void dw_hdmi_writeb(struct dw_hdmi *hdmi, u8 val, int offset)
{
writeb(val, hdmi->regs + offset);
}
static u8 dw_hdmi_readb(struct dw_hdmi *hdmi, int offset)
{
return readb(hdmi->regs + offset);
}
static inline void hdmi_writeb(struct dw_hdmi *hdmi, u8 val, int offset)
{
unsigned long flags;
spin_lock_irqsave(&hdmi->reg_lock, flags);
hdmi->write(hdmi, val, offset);
spin_unlock_irqrestore(&hdmi->reg_lock, flags);
}
static inline u8 hdmi_readb(struct dw_hdmi *hdmi, int offset)
{
return hdmi->read(hdmi, offset);
}
static void hdmi_modb(struct dw_hdmi *hdmi, u8 data, u8 mask, unsigned reg)
{
unsigned long flags;
u8 val;
spin_lock_irqsave(&hdmi->reg_lock, flags);
val = hdmi_readb(hdmi, reg) & ~mask;
val |= data & mask;
hdmi->write(hdmi, val, reg);
spin_unlock_irqrestore(&hdmi->reg_lock, flags);
}
static void hdmi_mask_writeb(struct dw_hdmi *hdmi, u8 data, unsigned int reg,
u8 shift, u8 mask)
{
hdmi_modb(hdmi, data << shift, mask, reg);
}
static void dw_hdmi_i2c_init(struct dw_hdmi *hdmi)
{
/* Software reset */
hdmi_writeb(hdmi, 0x00, HDMI_I2CM_SOFTRSTZ);
/* Set Standard Mode speed */
hdmi_modb(hdmi, HDMI_I2CM_DIV_STD_MODE,
HDMI_I2CM_DIV_FAST_STD_MODE, HDMI_I2CM_DIV);
/* Set done, not acknowledged and arbitration interrupt polarities */
hdmi_writeb(hdmi, HDMI_I2CM_INT_DONE_POL, HDMI_I2CM_INT);
hdmi_writeb(hdmi, HDMI_I2CM_CTLINT_NAC_POL | HDMI_I2CM_CTLINT_ARB_POL,
HDMI_I2CM_CTLINT);
/* Clear DONE and ERROR interrupts */
hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE,
HDMI_IH_I2CM_STAT0);
/* Mute DONE and ERROR interrupts */
hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE,
HDMI_IH_MUTE_I2CM_STAT0);
}
static bool dw_hdmi_i2c_unwedge(struct dw_hdmi *hdmi)
{
/* If no unwedge state then give up */
if (IS_ERR(hdmi->unwedge_state))
return false;
dev_info(hdmi->dev, "Attempting to unwedge stuck i2c bus\n");
/*
* This is a huge hack to workaround a problem where the dw_hdmi i2c
* bus could sometimes get wedged. Once wedged there doesn't appear
* to be any way to unwedge it (including the HDMI_I2CM_SOFTRSTZ)
* other than pulsing the SDA line.
*
* We appear to be able to pulse the SDA line (in the eyes of dw_hdmi)
* by:
* 1. Remux the pin as a GPIO output, driven low.
* 2. Wait a little while. 1 ms seems to work, but we'll do 10.
* 3. Immediately jump to remux the pin as dw_hdmi i2c again.
*
* At the moment of remuxing, the line will still be low due to its
* recent stint as an output, but then it will be pulled high by the
* (presumed) external pullup. dw_hdmi seems to see this as a rising
* edge and that seems to get it out of its jam.
*
* This wedging was only ever seen on one TV, and only on one of
* its HDMI ports. It happened when the TV was powered on while the
* device was plugged in. A scope trace shows the TV bringing both SDA
* and SCL low, then bringing them both back up at roughly the same
* time. Presumably this confuses dw_hdmi because it saw activity but
* no real STOP (maybe it thinks there's another master on the bus?).
* Giving it a clean rising edge of SDA while SCL is already high
* presumably makes dw_hdmi see a STOP which seems to bring dw_hdmi out
* of its stupor.
*
* Note that after coming back alive, transfers seem to immediately
* resume, so if we unwedge due to a timeout we should wait a little
* longer for our transfer to finish, since it might have just started
* now.
*/
pinctrl_select_state(hdmi->pinctrl, hdmi->unwedge_state);
msleep(10);
pinctrl_select_state(hdmi->pinctrl, hdmi->default_state);
return true;
}
static int dw_hdmi_i2c_wait(struct dw_hdmi *hdmi)
{
struct dw_hdmi_i2c *i2c = hdmi->i2c;
int stat;
stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
if (!stat) {
/* If we can't unwedge, return timeout */
if (!dw_hdmi_i2c_unwedge(hdmi))
return -EAGAIN;
/* We tried to unwedge; give it another chance */
stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
if (!stat)
return -EAGAIN;
}
/* Check for error condition on the bus */
if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR)
return -EIO;
return 0;
}
static int dw_hdmi_i2c_read(struct dw_hdmi *hdmi,
unsigned char *buf, unsigned int length)
{
struct dw_hdmi_i2c *i2c = hdmi->i2c;
int ret;
if (!i2c->is_regaddr) {
dev_dbg(hdmi->dev, "set read register address to 0\n");
i2c->slave_reg = 0x00;
i2c->is_regaddr = true;
}
while (length--) {
reinit_completion(&i2c->cmp);
hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS);
if (i2c->is_segment)
hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_READ_EXT,
HDMI_I2CM_OPERATION);
else
hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_READ,
HDMI_I2CM_OPERATION);
ret = dw_hdmi_i2c_wait(hdmi);
if (ret)
return ret;
*buf++ = hdmi_readb(hdmi, HDMI_I2CM_DATAI);
}
i2c->is_segment = false;
return 0;
}
static int dw_hdmi_i2c_write(struct dw_hdmi *hdmi,
unsigned char *buf, unsigned int length)
{
struct dw_hdmi_i2c *i2c = hdmi->i2c;
int ret;
if (!i2c->is_regaddr) {
/* Use the first write byte as register address */
i2c->slave_reg = buf[0];
length--;
buf++;
i2c->is_regaddr = true;
}
while (length--) {
reinit_completion(&i2c->cmp);
hdmi_writeb(hdmi, *buf++, HDMI_I2CM_DATAO);
hdmi_writeb(hdmi, i2c->slave_reg++, HDMI_I2CM_ADDRESS);
hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_WRITE,
HDMI_I2CM_OPERATION);
ret = dw_hdmi_i2c_wait(hdmi);
if (ret)
return ret;
}
return 0;
}
static int dw_hdmi_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
struct dw_hdmi *hdmi = i2c_get_adapdata(adap);
struct dw_hdmi_i2c *i2c = hdmi->i2c;
u8 addr = msgs[0].addr;
int i, ret = 0;
dev_dbg(hdmi->dev, "xfer: num: %d, addr: %#x\n", num, addr);
for (i = 0; i < num; i++) {
if (msgs[i].len == 0) {
dev_dbg(hdmi->dev,
"unsupported transfer %d/%d, no data\n",
i + 1, num);
return -EOPNOTSUPP;
}
}
mutex_lock(&i2c->lock);
hdmi_writeb(hdmi, 0x00, HDMI_IH_MUTE_I2CM_STAT0);
/* Set slave device address taken from the first I2C message */
hdmi_writeb(hdmi, addr, HDMI_I2CM_SLAVE);
/* Set slave device register address on transfer */
i2c->is_regaddr = false;
/* Set segment pointer for I2C extended read mode operation */
i2c->is_segment = false;
for (i = 0; i < num; i++) {
dev_dbg(hdmi->dev, "xfer: num: %d/%d, len: %d, flags: %#x\n",
i + 1, num, msgs[i].len, msgs[i].flags);
if (msgs[i].addr == DDC_SEGMENT_ADDR && msgs[i].len == 1) {
i2c->is_segment = true;
hdmi_writeb(hdmi, DDC_SEGMENT_ADDR, HDMI_I2CM_SEGADDR);
hdmi_writeb(hdmi, *msgs[i].buf, HDMI_I2CM_SEGPTR);
} else {
if (msgs[i].flags & I2C_M_RD)
ret = dw_hdmi_i2c_read(hdmi, msgs[i].buf,
msgs[i].len);
else
ret = dw_hdmi_i2c_write(hdmi, msgs[i].buf,
msgs[i].len);
}
if (ret < 0)
break;
}
if (!ret)
ret = num;
/* Mute DONE and ERROR interrupts */
hdmi_writeb(hdmi, HDMI_IH_I2CM_STAT0_ERROR | HDMI_IH_I2CM_STAT0_DONE,
HDMI_IH_MUTE_I2CM_STAT0);
mutex_unlock(&i2c->lock);
return ret;
}
static u32 dw_hdmi_i2c_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}
static const struct i2c_algorithm dw_hdmi_algorithm = {
.master_xfer = dw_hdmi_i2c_xfer,
.functionality = dw_hdmi_i2c_func,
};
static struct i2c_adapter *dw_hdmi_i2c_adapter(struct dw_hdmi *hdmi)
{
struct i2c_adapter *adap;
struct dw_hdmi_i2c *i2c;
int ret;
i2c = devm_kzalloc(hdmi->dev, sizeof(*i2c), GFP_KERNEL);
if (!i2c)
return ERR_PTR(-ENOMEM);
mutex_init(&i2c->lock);
init_completion(&i2c->cmp);
adap = &i2c->adap;
adap->class = I2C_CLASS_DDC;
adap->owner = THIS_MODULE;
adap->dev.parent = hdmi->dev;
adap->dev.of_node = hdmi->dev->of_node;
adap->algo = &dw_hdmi_algorithm;
strlcpy(adap->name, "DesignWare HDMI", sizeof(adap->name));
i2c_set_adapdata(adap, hdmi);
ret = i2c_add_adapter(adap);
if (ret) {
dev_warn(hdmi->dev, "cannot add %s I2C adapter\n", adap->name);
devm_kfree(hdmi->dev, i2c);
return ERR_PTR(ret);
}
hdmi->i2c = i2c;
dev_info(hdmi->dev, "registered %s I2C bus driver\n", adap->name);
return adap;
}
static void hdmi_set_schnl(struct dw_hdmi *hdmi)
{
u8 aud_schnl_samplerate;
u8 aud_schnl_8;
if (hdmi->id.design != 0x20)
return;
switch (hdmi->sample_rate) {
case 32000:
aud_schnl_samplerate = HDMI_FC_AUDSCHNLS7_SMPRATE_32K;
break;
case 44100:
aud_schnl_samplerate = HDMI_FC_AUDSCHNLS7_SMPRATE_44K1;
break;
case 48000:
aud_schnl_samplerate = HDMI_FC_AUDSCHNLS7_SMPRATE_48K;
break;
case 88200:
aud_schnl_samplerate = HDMI_FC_AUDSCHNLS7_SMPRATE_88K2;
break;
case 96000:
aud_schnl_samplerate = HDMI_FC_AUDSCHNLS7_SMPRATE_96K;
break;
case 176400:
aud_schnl_samplerate = HDMI_FC_AUDSCHNLS7_SMPRATE_176K4;
break;
case 192000:
aud_schnl_samplerate = HDMI_FC_AUDSCHNLS7_SMPRATE_192K;
break;
case 768000:
aud_schnl_samplerate = HDMI_FC_AUDSCHNLS7_SMPRATE_768K;
break;
default:
dev_warn(hdmi->dev, "Unsupported audio sample rate (%u)\n",
hdmi->sample_rate);
return;
}
/* set channel status register */
hdmi_modb(hdmi, aud_schnl_samplerate, HDMI_FC_AUDSCHNLS7_SMPRATE_MASK,
HDMI_FC_AUDSCHNLS7);
aud_schnl_8 = (~aud_schnl_samplerate) <<
HDMI_FC_AUDSCHNLS8_ORIGSAMPFREQ_OFFSET;
aud_schnl_8 |= 2 << HDMI_FC_AUDSCHNLS8_WORDLEGNTH_OFFSET;
hdmi_writeb(hdmi, aud_schnl_8, HDMI_FC_AUDSCHNLS8);
}
static void hdmi_set_clock_regenerator(struct dw_hdmi *hdmi,
unsigned int n, unsigned int cts)
{
u8 n3 = 0;
u8 cts3 = 0;
/* First set NCTS_ATOMIC_WRITE (if present) */
if (hdmi->id.design == 0x20) {
n3 = HDMI_AUD_N3_NCTS_ATOMIC_WRITE;
hdmi_writeb(hdmi, n3, HDMI_AUD_N3);
}
/* set CTS_MANUAL (if present) */
if (hdmi->id.design == 0x20)
cts3 = HDMI_AUD_CTS3_CTS_MANUAL;
cts3 |= HDMI_AUD_CTS3_N_SHIFT_1 << HDMI_AUD_CTS3_N_SHIFT_OFFSET;
cts3 |= (cts >> 16) & HDMI_AUD_CTS3_AUDCTS19_16_MASK;
/* write CTS values; CTS3 must be written first */
hdmi_writeb(hdmi, cts3, HDMI_AUD_CTS3);
hdmi_writeb(hdmi, (cts >> 8) & 0xff, HDMI_AUD_CTS2);
hdmi_writeb(hdmi, cts & 0xff, HDMI_AUD_CTS1);
/* write N values; N1 must be written last */
n3 |= (n >> 16) & HDMI_AUD_N3_AUDN19_16_MASK;
hdmi_writeb(hdmi, n3, HDMI_AUD_N3);
hdmi_writeb(hdmi, (n >> 8) & 0xff, HDMI_AUD_N2);
hdmi_writeb(hdmi, n & 0xff, HDMI_AUD_N1);
}
static int hdmi_match_tmds_n_table(struct dw_hdmi *hdmi,
unsigned long pixel_clk)
{
const struct dw_hdmi_plat_data *plat_data = hdmi->plat_data;
unsigned int freq = hdmi->sample_rate;
const struct dw_hdmi_audio_tmds_n *tmds_n = NULL;
int i;
if (plat_data->tmds_n_table) {
for (i = 0; plat_data->tmds_n_table[i].tmds != 0; i++) {
if (pixel_clk == plat_data->tmds_n_table[i].tmds) {
tmds_n = &plat_data->tmds_n_table[i];
break;
}
}
}
if (tmds_n == NULL) {
for (i = 0; common_tmds_n_table[i].tmds != 0; i++) {
if (pixel_clk == common_tmds_n_table[i].tmds) {
tmds_n = &common_tmds_n_table[i];
break;
}
}
}
if (tmds_n == NULL)
return -ENOENT;
switch (freq) {
case 32000:
return tmds_n->n_32k;
case 44100:
case 88200:
case 176400:
return (freq / 44100) * tmds_n->n_44k1;
case 48000:
case 96000:
case 192000:
return (freq / 48000) * tmds_n->n_48k;
default:
return -ENOENT;
}
}
static u64 hdmi_audio_math_diff(unsigned int freq, unsigned int n,
unsigned int pixel_clk)
{
u64 final, diff;
u64 cts;
final = (u64)pixel_clk * n;
cts = final;
do_div(cts, 128 * freq);
diff = final - (u64)cts * (128 * freq);
return diff;
}
static unsigned int hdmi_compute_n(struct dw_hdmi *hdmi,
unsigned long pixel_clk)
{
unsigned int freq = hdmi->sample_rate;
unsigned int min_n = DIV_ROUND_UP((128 * freq), 1500);
unsigned int max_n = (128 * freq) / 300;
unsigned int ideal_n = (128 * freq) / 1000;
unsigned int best_n_distance = ideal_n;
unsigned int best_n = 0;
u64 best_diff = U64_MAX;
int n;
/* If the ideal N could satisfy the audio math, then just take it */
if (hdmi_audio_math_diff(freq, ideal_n, pixel_clk) == 0)
return ideal_n;
for (n = min_n; n <= max_n; n++) {
u64 diff = hdmi_audio_math_diff(freq, n, pixel_clk);
if (diff < best_diff || (diff == best_diff &&
abs(n - ideal_n) < best_n_distance)) {
best_n = n;
best_diff = diff;
best_n_distance = abs(best_n - ideal_n);
}
/*
* The best N already satisfy the audio math, and also be
* the closest value to ideal N, so just cut the loop.
*/
if ((best_diff == 0) && (abs(n - ideal_n) > best_n_distance))
break;
}
return best_n;
}
static unsigned int hdmi_find_n(struct dw_hdmi *hdmi, unsigned long pixel_clk)
{
int n;
n = hdmi_match_tmds_n_table(hdmi, pixel_clk);
if (n > 0)
return n;
dev_warn(hdmi->dev, "Rate %lu missing; compute N dynamically\n",
pixel_clk);
return hdmi_compute_n(hdmi, pixel_clk);
}
static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi,
unsigned long pixel_clk)
{
unsigned long ftdms = pixel_clk;
unsigned int clk_n, clk_cts;
u64 tmp;
clk_n = hdmi_find_n(hdmi, pixel_clk);
/*
* Compute the CTS value from the N value. Note that CTS and N
* can be up to 20 bits in total, so we need 64-bit math. Also
* note that our TDMS clock is not fully accurate; it is accurate
* to kHz. This can introduce an unnecessary remainder in the
* calculation below, so we don't try to warn about that.
*/
tmp = (u64)ftdms * clk_n;
do_div(tmp, 128 * hdmi->sample_rate);
clk_cts = tmp;
dev_dbg(hdmi->dev, "%s: fs=%uHz ftdms=%lu.%03luMHz N=%d cts=%d\n",
__func__, hdmi->sample_rate, ftdms / 1000000,
(ftdms / 1000) % 1000, clk_n, clk_cts);
hdmi_set_clock_regenerator(hdmi, clk_n, clk_cts);
hdmi_set_schnl(hdmi);
}
static void hdmi_init_clk_regenerator(struct dw_hdmi *hdmi)
{
hdmi_set_clk_regenerator(hdmi, 74250000);
}
static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi *hdmi)
{
hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mpixelclock);
}
static void hdmi_set_sample_rate(struct dw_hdmi *hdmi, unsigned int sample_rate)
{
hdmi->sample_rate = sample_rate;
hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mpixelclock);
}
/*
* 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 dw_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(hdmi, 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(hdmi, val, HDMI_TX_INSTUFFING);
hdmi_writeb(hdmi, 0x0, HDMI_TX_GYDATA0);
hdmi_writeb(hdmi, 0x0, HDMI_TX_GYDATA1);
hdmi_writeb(hdmi, 0x0, HDMI_TX_RCRDATA0);
hdmi_writeb(hdmi, 0x0, HDMI_TX_RCRDATA1);
hdmi_writeb(hdmi, 0x0, HDMI_TX_BCBDATA0);
hdmi_writeb(hdmi, 0x0, HDMI_TX_BCBDATA1);
}
static int is_color_space_conversion(struct dw_hdmi *hdmi)
{
return hdmi->hdmi_data.enc_in_format != hdmi->hdmi_data.enc_out_format;
}
static int is_color_space_decimation(struct dw_hdmi *hdmi)
{
if (hdmi->hdmi_data.enc_out_format != YCBCR422_8BITS)
return 0;
if (hdmi->hdmi_data.enc_in_format == RGB ||
hdmi->hdmi_data.enc_in_format == YCBCR444)
return 1;
return 0;
}
static int is_color_space_interpolation(struct dw_hdmi *hdmi)
{
if (hdmi->hdmi_data.enc_in_format != YCBCR422_8BITS)
return 0;
if (hdmi->hdmi_data.enc_out_format == RGB ||
hdmi->hdmi_data.enc_out_format == YCBCR444)
return 1;
return 0;
}
static void dw_hdmi_update_csc_coeffs(struct dw_hdmi *hdmi)
{
const u16 (*csc_coeff)[3][4] = &csc_coeff_default;
unsigned i;
u32 csc_scale = 1;
if (is_color_space_conversion(hdmi)) {
if (hdmi->hdmi_data.enc_out_format == RGB) {
if (hdmi->hdmi_data.colorimetry ==
HDMI_COLORIMETRY_ITU_601)
csc_coeff = &csc_coeff_rgb_out_eitu601;
else
csc_coeff = &csc_coeff_rgb_out_eitu709;
} else if (hdmi->hdmi_data.enc_in_format == RGB) {
if (hdmi->hdmi_data.colorimetry ==
HDMI_COLORIMETRY_ITU_601)
csc_coeff = &csc_coeff_rgb_in_eitu601;
else
csc_coeff = &csc_coeff_rgb_in_eitu709;
csc_scale = 0;
}
}
/* The CSC registers are sequential, alternating MSB then LSB */
for (i = 0; i < ARRAY_SIZE(csc_coeff_default[0]); i++) {
u16 coeff_a = (*csc_coeff)[0][i];
u16 coeff_b = (*csc_coeff)[1][i];
u16 coeff_c = (*csc_coeff)[2][i];
hdmi_writeb(hdmi, coeff_a & 0xff, HDMI_CSC_COEF_A1_LSB + i * 2);
hdmi_writeb(hdmi, coeff_a >> 8, HDMI_CSC_COEF_A1_MSB + i * 2);
hdmi_writeb(hdmi, coeff_b & 0xff, HDMI_CSC_COEF_B1_LSB + i * 2);
hdmi_writeb(hdmi, coeff_b >> 8, HDMI_CSC_COEF_B1_MSB + i * 2);
hdmi_writeb(hdmi, coeff_c & 0xff, HDMI_CSC_COEF_C1_LSB + i * 2);
hdmi_writeb(hdmi, coeff_c >> 8, HDMI_CSC_COEF_C1_MSB + i * 2);
}
hdmi_modb(hdmi, csc_scale, HDMI_CSC_SCALE_CSCSCALE_MASK,
HDMI_CSC_SCALE);
}
static void hdmi_video_csc(struct dw_hdmi *hdmi)
{
int color_depth = 0;
int interpolation = HDMI_CSC_CFG_INTMODE_DISABLE;
int decimation = 0;
/* YCC422 interpolation to 444 mode */
if (is_color_space_interpolation(hdmi))
interpolation = HDMI_CSC_CFG_INTMODE_CHROMA_INT_FORMULA1;
else if (is_color_space_decimation(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(hdmi, interpolation | decimation, HDMI_CSC_CFG);
hdmi_modb(hdmi, color_depth, HDMI_CSC_SCALE_CSC_COLORDE_PTH_MASK,
HDMI_CSC_SCALE);
dw_hdmi_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 dw_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, vp_conf;
if (hdmi_data->enc_out_format == RGB ||
hdmi_data->enc_out_format == YCBCR444) {
if (!hdmi_data->enc_color_depth) {
output_select = HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS;
} else if (hdmi_data->enc_color_depth == 8) {
color_depth = 0;
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 ||
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;
}
/* 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(hdmi, val, HDMI_VP_PR_CD);
hdmi_modb(hdmi, HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE,
HDMI_VP_STUFF_PR_STUFFING_MASK, HDMI_VP_STUFF);
/* Data from pixel repeater block */
if (hdmi_data->pix_repet_factor > 1) {
vp_conf = HDMI_VP_CONF_PR_EN_ENABLE |
HDMI_VP_CONF_BYPASS_SELECT_PIX_REPEATER;
} else { /* data from packetizer block */
vp_conf = HDMI_VP_CONF_PR_EN_DISABLE |
HDMI_VP_CONF_BYPASS_SELECT_VID_PACKETIZER;
}
hdmi_modb(hdmi, vp_conf,
HDMI_VP_CONF_PR_EN_MASK |
HDMI_VP_CONF_BYPASS_SELECT_MASK, HDMI_VP_CONF);
hdmi_modb(hdmi, 1 << HDMI_VP_STUFF_IDEFAULT_PHASE_OFFSET,
HDMI_VP_STUFF_IDEFAULT_PHASE_MASK, HDMI_VP_STUFF);
hdmi_writeb(hdmi, remap_size, HDMI_VP_REMAP);
if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_PP) {
vp_conf = HDMI_VP_CONF_BYPASS_EN_DISABLE |
HDMI_VP_CONF_PP_EN_ENABLE |
HDMI_VP_CONF_YCC422_EN_DISABLE;
} else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_YCC422) {
vp_conf = HDMI_VP_CONF_BYPASS_EN_DISABLE |
HDMI_VP_CONF_PP_EN_DISABLE |
HDMI_VP_CONF_YCC422_EN_ENABLE;
} else if (output_select == HDMI_VP_CONF_OUTPUT_SELECTOR_BYPASS) {
vp_conf = HDMI_VP_CONF_BYPASS_EN_ENABLE |
HDMI_VP_CONF_PP_EN_DISABLE |
HDMI_VP_CONF_YCC422_EN_DISABLE;
} else {
return;
}
hdmi_modb(hdmi, vp_conf,
HDMI_VP_CONF_BYPASS_EN_MASK | HDMI_VP_CONF_PP_EN_ENMASK |
HDMI_VP_CONF_YCC422_EN_MASK, HDMI_VP_CONF);
hdmi_modb(hdmi, HDMI_VP_STUFF_PP_STUFFING_STUFFING_MODE |
HDMI_VP_STUFF_YCC422_STUFFING_STUFFING_MODE,
HDMI_VP_STUFF_PP_STUFFING_MASK |
HDMI_VP_STUFF_YCC422_STUFFING_MASK, HDMI_VP_STUFF);
hdmi_modb(hdmi, output_select, HDMI_VP_CONF_OUTPUT_SELECTOR_MASK,
HDMI_VP_CONF);
}
static inline void hdmi_phy_test_clear(struct dw_hdmi *hdmi,
unsigned char bit)
{
hdmi_modb(hdmi, bit << HDMI_PHY_TST0_TSTCLR_OFFSET,
HDMI_PHY_TST0_TSTCLR_MASK, HDMI_PHY_TST0);
}
static inline void hdmi_phy_test_enable(struct dw_hdmi *hdmi,
unsigned char bit)
{
hdmi_modb(hdmi, bit << HDMI_PHY_TST0_TSTEN_OFFSET,
HDMI_PHY_TST0_TSTEN_MASK, HDMI_PHY_TST0);
}
static inline void hdmi_phy_test_clock(struct dw_hdmi *hdmi,
unsigned char bit)
{
hdmi_modb(hdmi, bit << HDMI_PHY_TST0_TSTCLK_OFFSET,
HDMI_PHY_TST0_TSTCLK_MASK, HDMI_PHY_TST0);
}
static inline void hdmi_phy_test_din(struct dw_hdmi *hdmi,
unsigned char bit)
{
hdmi_writeb(hdmi, bit, HDMI_PHY_TST1);
}
static inline void hdmi_phy_test_dout(struct dw_hdmi *hdmi,
unsigned char bit)
{
hdmi_writeb(hdmi, bit, HDMI_PHY_TST2);
}
static bool hdmi_phy_wait_i2c_done(struct dw_hdmi *hdmi, int msec)
{
u32 val;
while ((val = hdmi_readb(hdmi, HDMI_IH_I2CMPHY_STAT0) & 0x3) == 0) {
if (msec-- == 0)
return false;
udelay(1000);
}
hdmi_writeb(hdmi, val, HDMI_IH_I2CMPHY_STAT0);
return true;
}
static void __hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data,
unsigned char addr)
{
hdmi_writeb(hdmi, 0xFF, HDMI_IH_I2CMPHY_STAT0);
hdmi_writeb(hdmi, addr, HDMI_PHY_I2CM_ADDRESS_ADDR);
hdmi_writeb(hdmi, (unsigned char)(data >> 8),
HDMI_PHY_I2CM_DATAO_1_ADDR);
hdmi_writeb(hdmi, (unsigned char)(data >> 0),
HDMI_PHY_I2CM_DATAO_0_ADDR);
hdmi_writeb(hdmi, HDMI_PHY_I2CM_OPERATION_ADDR_WRITE,
HDMI_PHY_I2CM_OPERATION_ADDR);
hdmi_phy_wait_i2c_done(hdmi, 1000);
}
static int hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data,
unsigned char addr)
{
__hdmi_phy_i2c_write(hdmi, data, addr);
return 0;
}
static void dw_hdmi_phy_enable_power(struct dw_hdmi *hdmi, u8 enable)
{
hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0,
HDMI_PHY_CONF0_PDZ_OFFSET,
HDMI_PHY_CONF0_PDZ_MASK);
}
static void dw_hdmi_phy_enable_tmds(struct dw_hdmi *hdmi, u8 enable)
{
hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0,
HDMI_PHY_CONF0_ENTMDS_OFFSET,
HDMI_PHY_CONF0_ENTMDS_MASK);
}
static void dw_hdmi_phy_enable_spare(struct dw_hdmi *hdmi, u8 enable)
{
hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0,
HDMI_PHY_CONF0_SPARECTRL_OFFSET,
HDMI_PHY_CONF0_SPARECTRL_MASK);
}
static void dw_hdmi_phy_gen2_pddq(struct dw_hdmi *hdmi, u8 enable)
{
hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0,
HDMI_PHY_CONF0_GEN2_PDDQ_OFFSET,
HDMI_PHY_CONF0_GEN2_PDDQ_MASK);
}
static void dw_hdmi_phy_gen2_txpwron(struct dw_hdmi *hdmi, u8 enable)
{
hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0,
HDMI_PHY_CONF0_GEN2_TXPWRON_OFFSET,
HDMI_PHY_CONF0_GEN2_TXPWRON_MASK);
}
static void dw_hdmi_phy_sel_data_en_pol(struct dw_hdmi *hdmi, u8 enable)
{
hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0,
HDMI_PHY_CONF0_SELDATAENPOL_OFFSET,
HDMI_PHY_CONF0_SELDATAENPOL_MASK);
}
static void dw_hdmi_phy_sel_interface_control(struct dw_hdmi *hdmi, u8 enable)
{
hdmi_mask_writeb(hdmi, enable, HDMI_PHY_CONF0,
HDMI_PHY_CONF0_SELDIPIF_OFFSET,
HDMI_PHY_CONF0_SELDIPIF_MASK);
}
static int hdmi_phy_configure(struct dw_hdmi *hdmi, unsigned char prep,
unsigned char res, int cscon)
{
unsigned res_idx, i;
u8 val, msec;
const struct dw_hdmi_plat_data *plat_data = hdmi->plat_data;
const struct dw_hdmi_mpll_config *mpll_config = plat_data->mpll_cfg;
const struct dw_hdmi_curr_ctrl *curr_ctrl = plat_data->cur_ctr;
const struct dw_hdmi_phy_config *phy_config = plat_data->phy_config;
if (prep)
return -EINVAL;
switch (res) {
case 0: /* color resolution 0 is 8 bit colour depth */
case 8:
res_idx = DW_HDMI_RES_8;
break;
case 10:
res_idx = DW_HDMI_RES_10;
break;
case 12:
res_idx = DW_HDMI_RES_12;
break;
default:
return -EINVAL;
}
/* 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(hdmi, val, HDMI_MC_FLOWCTRL);
/* gen2 tx power off */
dw_hdmi_phy_gen2_txpwron(hdmi, 0);
/* gen2 pddq */
dw_hdmi_phy_gen2_pddq(hdmi, 1);
/* PHY reset */
hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_DEASSERT, HDMI_MC_PHYRSTZ);
hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_ASSERT, HDMI_MC_PHYRSTZ);
hdmi_writeb(hdmi, HDMI_MC_HEACPHY_RST_ASSERT, HDMI_MC_HEACPHY_RST);
hdmi_phy_test_clear(hdmi, 1);
hdmi_writeb(hdmi, HDMI_PHY_I2CM_SLAVE_ADDR_PHY_GEN2,
HDMI_PHY_I2CM_SLAVE_ADDR);
hdmi_phy_test_clear(hdmi, 0);
/* PLL/MPLL Cfg - always match on final entry */
for (i = 0; mpll_config[i].mpixelclock != (~0UL); i++)
if (hdmi->hdmi_data.video_mode.mpixelclock <=
mpll_config[i].mpixelclock)
break;
hdmi_phy_i2c_write(hdmi, mpll_config[i].res[res_idx].cpce, 0x06);
hdmi_phy_i2c_write(hdmi, mpll_config[i].res[res_idx].gmp, 0x15);
for (i = 0; curr_ctrl[i].mpixelclock != (~0UL); i++)
if (hdmi->hdmi_data.video_mode.mpixelclock <=
curr_ctrl[i].mpixelclock)
break;
if (curr_ctrl[i].mpixelclock == (~0UL)) {
dev_err(hdmi->dev, "Pixel clock %d - unsupported by HDMI\n",
hdmi->hdmi_data.video_mode.mpixelclock);
return -EINVAL;
}
/* CURRCTRL */
hdmi_phy_i2c_write(hdmi, curr_ctrl[i].curr[res_idx], 0x10);
hdmi_phy_i2c_write(hdmi, 0x0000, 0x13); /* PLLPHBYCTRL */
hdmi_phy_i2c_write(hdmi, 0x0006, 0x17);
for (i = 0; phy_config[i].mpixelclock != (~0UL); i++)
if (hdmi->hdmi_data.video_mode.mpixelclock <=
phy_config[i].mpixelclock)
break;
/* RESISTANCE TERM 133Ohm Cfg */
hdmi_phy_i2c_write(hdmi, phy_config[i].term, 0x19); /* TXTERM */
/* PREEMP Cgf 0.00 */
hdmi_phy_i2c_write(hdmi, phy_config[i].sym_ctr, 0x09); /* CKSYMTXCTRL */
/* TX/CK LVL 10 */
hdmi_phy_i2c_write(hdmi, phy_config[i].vlev_ctr, 0x0E); /* VLEVCTRL */
/* REMOVE CLK TERM */
hdmi_phy_i2c_write(hdmi, 0x8000, 0x05); /* CKCALCTRL */
dw_hdmi_phy_enable_power(hdmi, 1);
/* toggle TMDS enable */
dw_hdmi_phy_enable_tmds(hdmi, 0);
dw_hdmi_phy_enable_tmds(hdmi, 1);
/* gen2 tx power on */
dw_hdmi_phy_gen2_txpwron(hdmi, 1);
dw_hdmi_phy_gen2_pddq(hdmi, 0);
if (hdmi->dev_type == RK3288_HDMI)
dw_hdmi_phy_enable_spare(hdmi, 1);
/*Wait for PHY PLL lock */
msec = 5;
do {
val = hdmi_readb(hdmi, HDMI_PHY_STAT0) & HDMI_PHY_TX_PHY_LOCK;
if (!val)
break;
if (msec == 0) {
dev_err(hdmi->dev, "PHY PLL not locked\n");
return -ETIMEDOUT;
}
udelay(1000);
msec--;
} while (1);
return 0;
}
static int dw_hdmi_phy_init(struct dw_hdmi *hdmi)
{
int i, ret;
bool cscon = false;
/*check csc whether needed activated in HDMI mode */
cscon = (is_color_space_conversion(hdmi) &&
!hdmi->hdmi_data.video_mode.mdvi);
/* HDMI Phy spec says to do the phy initialization sequence twice */
for (i = 0; i < 2; i++) {
dw_hdmi_phy_sel_data_en_pol(hdmi, 1);
dw_hdmi_phy_sel_interface_control(hdmi, 0);
dw_hdmi_phy_enable_tmds(hdmi, 0);
dw_hdmi_phy_enable_power(hdmi, 0);
/* Enable CSC */
ret = hdmi_phy_configure(hdmi, 0, 8, cscon);
if (ret)
return ret;
}
hdmi->phy_enabled = true;
return 0;
}
static int hdmi_hdcp_wait_rmsts_ok(struct dw_hdmi *hdmi)
{
unsigned long timeout;
timeout = jiffies + msecs_to_jiffies(100);
while ((hdmi_readb(hdmi, HDMI_HDCPREG_RMSTS) & DPK_WR_OK_STS) == 0) {
if (time_after(jiffies, timeout))
return -EBUSY;
usleep_range(10, 15);
}
return 0;
}
static int _hdmi_write_hdcp_key(struct dw_hdmi *hdmi)
{
struct dw_hdmi_hdcp_key_1x *key = &hdmi->hdcp_key;
int i, j;
int ret;
/* Disable decryption logic */
hdmi_writeb(hdmi, 0, HDMI_HDCPREG_RMCTL);
ret = hdmi_hdcp_wait_rmsts_ok(hdmi);
if (ret)
return ret;
/* The useful data in ksv should be 5 byte */
for (i = 4; i >= 0; i--)
hdmi_writeb(hdmi, key->ksv[i], HDMI_HDCPREG_DPK0 + i);
ret = hdmi_hdcp_wait_rmsts_ok(hdmi);
if (ret)
return ret;
/* Enable decryption logic */
hdmi_writeb(hdmi, 1, HDMI_HDCPREG_RMCTL);
hdmi_writeb(hdmi, key->seed[1], HDMI_HDCPREG_SEED1);
hdmi_writeb(hdmi, key->seed[0], HDMI_HDCPREG_SEED0);
/* Write encrypt device private key */
for (i = 0; i < DW_HDMI_HDCP_DPK_LEN - 6; i += 7) {
for (j = 6; j >= 0; j--)
hdmi_writeb(hdmi, key->device_key[i + j],
HDMI_HDCPREG_DPK0 + j);
ret = hdmi_hdcp_wait_rmsts_ok(hdmi);
if (ret)
return ret;
}
return 0;
}
static int hdmi_write_hdcp_key(struct dw_hdmi *hdmi)
{
int ret;
mutex_lock(&hdmi->hdcp_key_mutex);
if (!hdmi->is_hdcp_key_present) {
mutex_unlock(&hdmi->hdcp_key_mutex);
return -EINVAL;
}
ret = _hdmi_write_hdcp_key(hdmi);
if (ret)
dev_err(hdmi->dev, "Failed to write key to HDCP module.\n");
mutex_unlock(&hdmi->hdcp_key_mutex);
return ret;
}
static void _hdmi_close_hdcp_auth(struct dw_hdmi *hdmi)
{
WARN_ON(!mutex_is_locked(&hdmi->hdcp_state_mutex));
/* Disable hdcp encryption */
hdmi_modb(hdmi, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE,
HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK, HDMI_A_HDCPCFG1);
/* Disable the RX detect */
hdmi_modb(hdmi, HDMI_A_HDCPCFG0_RXDETECT_DISABLE,
HDMI_A_HDCPCFG0_RXDETECT_MASK, HDMI_A_HDCPCFG0);
hdmi->hdcp_state = DW_HDMI_HDCP_STATE_DISABLED;
}
static void _hdmi_start_hdcp_auth(struct dw_hdmi *hdmi)
{
WARN_ON(!mutex_is_locked(&hdmi->hdcp_state_mutex));
/* Make sure RX detect is off before we start */
_hdmi_close_hdcp_auth(hdmi);
/* Enable the RX detect; expect an interrupt after this */
hdmi_modb(hdmi, HDMI_A_HDCPCFG0_RXDETECT_ENABLE,
HDMI_A_HDCPCFG0_RXDETECT_MASK, HDMI_A_HDCPCFG0);
hdmi->hdcp_state = DW_HDMI_HDCP_STATE_AUTH_START;
}
static void _hdmi_enable_hdcp_encry(struct dw_hdmi *hdmi)
{
WARN_ON(!mutex_is_locked(&hdmi->hdcp_state_mutex));
WARN_ON(hdmi->hdcp_state != DW_HDMI_HDCP_STATE_AUTH_DONE);
/* Enable hdcp encryption */
hdmi_modb(hdmi, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_ENABLE,
HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK, HDMI_A_HDCPCFG1);
hdmi->hdcp_state = DW_HDMI_HDCP_STATE_ENCRY_EN;
}
static void hdmi_disable_hdcp_encry(struct dw_hdmi *hdmi)
{
mutex_lock(&hdmi->hdcp_state_mutex);
/* Disable hdcp encryption */
hdmi_modb(hdmi, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE,
HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK, HDMI_A_HDCPCFG1);
/*
* Leaving things authed, so keep track of state; note that if we turn
* off auth here we blink the screen.
*/
if (hdmi->hdcp_state == DW_HDMI_HDCP_STATE_ENCRY_EN)
hdmi->hdcp_state = DW_HDMI_HDCP_STATE_AUTH_DONE;
mutex_unlock(&hdmi->hdcp_state_mutex);
}
static int _hdmi_set_hdcp_drm_property(struct dw_hdmi *hdmi, uint64_t val)
{
struct drm_connector *connector = &hdmi->connector;
struct drm_mode_config *mc = &connector->dev->mode_config;
WARN_ON(!mutex_is_locked(&mc->mutex));
return drm_object_property_set_value(&connector->base,
&connector->propvals,
mc->content_protection_property,
val, NULL);
}
static void hdmi_tx_hdcp_config(struct dw_hdmi *hdmi)
{
struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode;
u8 vsync_pol, hsync_pol, data_pol, hdmi_dvi;
/* Reset HDCP Engine */
hdmi_modb(hdmi, HDMI_A_HDCPCFG1_SWRESET_ASSERT,
HDMI_A_HDCPCFG1_SWRESET_MASK, HDMI_A_HDCPCFG1);
/* config the signal polarity */
vsync_pol = vmode->mvsyncpolarity ?
HDMI_A_VIDPOLCFG_VSYNCPOL_ACTIVE_HIGH :
HDMI_A_VIDPOLCFG_VSYNCPOL_ACTIVE_LOW;
hsync_pol = vmode->mhsyncpolarity ?
HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_HIGH :
HDMI_A_VIDPOLCFG_HSYNCPOL_ACTIVE_LOW;
data_pol = vmode->mdataenablepolarity ?
HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_HIGH :
HDMI_A_VIDPOLCFG_DATAENPOL_ACTIVE_LOW;
hdmi_modb(hdmi, vsync_pol | hsync_pol | data_pol,
HDMI_A_VIDPOLCFG_VSYNCPOL_MASK |
HDMI_A_VIDPOLCFG_HSYNCPOL_MASK |
HDMI_A_VIDPOLCFG_DATAENPOL_MASK,
HDMI_A_VIDPOLCFG);
/* config the display mode */
hdmi_dvi = vmode->mdvi ? HDMI_A_HDCPCFG0_HDMIDVI_DVI :
HDMI_A_HDCPCFG0_HDMIDVI_HDMI;
hdmi_modb(hdmi, hdmi_dvi, HDMI_A_HDCPCFG0_HDMIDVI_MASK,
HDMI_A_HDCPCFG0);
/* vendor suggest that a_oesswcfg should write this magic number */
hdmi_writeb(hdmi, 0x40, HDMI_A_OESSWCFG);
hdmi_modb(hdmi, HDMI_A_HDCPCFG0_BYPENCRYPTION_DISABLE |
HDMI_A_HDCPCFG0_EN11FEATURE_DISABLE |
HDMI_A_HDCPCFG0_SYNCRICHECK_ENABLE,
HDMI_A_HDCPCFG0_BYPENCRYPTION_MASK |
HDMI_A_HDCPCFG0_EN11FEATURE_MASK |
HDMI_A_HDCPCFG0_SYNCRICHECK_MASK, HDMI_A_HDCPCFG0);
hdmi_modb(hdmi, HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_DISABLE |
HDMI_A_HDCPCFG1_PH2UPSHFTENC_ENABLE,
HDMI_A_HDCPCFG1_ENCRYPTIONDISABLE_MASK |
HDMI_A_HDCPCFG1_PH2UPSHFTENC_MASK, HDMI_A_HDCPCFG1);
hdmi_modb(hdmi, HDMI_FC_INVIDCONF_HDCP_KEEPOUT_ACTIVE,
HDMI_FC_INVIDCONF_HDCP_KEEPOUT_MASK, HDMI_FC_INVIDCONF);
/* enable hdcp clock */
hdmi_modb(hdmi, 0x00, HDMI_MC_CLKDIS_HDCPCLK_DISABLE, HDMI_MC_CLKDIS);
mutex_lock(&hdmi->hdcp_state_mutex);
if (hdmi->hdcp_state == DW_HDMI_HDCP_STATE_DISABLED)
_hdmi_close_hdcp_auth(hdmi);
else
_hdmi_start_hdcp_auth(hdmi);
mutex_unlock(&hdmi->hdcp_state_mutex);
}
static void hdmi_config_AVI(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
{
u8 val, pix_fmt, under_scan;
u8 act_ratio, coded_ratio, colorimetry, ext_colorimetry;
struct hdmi_avi_infoframe frame;
bool aspect_16_9;
drm_hdmi_avi_infoframe_from_display_mode(&frame, mode);
/* Parse the aspect ratio from drm display mode */
if (frame.picture_aspect == HDMI_PICTURE_ASPECT_16_9)
aspect_16_9 = true;
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;
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(hdmi, val, HDMI_FC_AVICONF0);
/* AVI Data Byte 2 -Set the Aspect Ratio */
if (aspect_16_9) {
coded_ratio = HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_16_9;
} else {
coded_ratio = HDMI_FC_AVICONF1_CODED_ASPECT_RATIO_4_3;
}
act_ratio = HDMI_FC_AVICONF1_ACTIVE_ASPECT_RATIO_USE_CODED;
/* Set up colorimetry */
if (hdmi->hdmi_data.enc_out_format == XVYCC444) {
colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_EXTENDED_INFO;
if (hdmi->hdmi_data.colorimetry == HDMI_COLORIMETRY_ITU_601)
ext_colorimetry =
HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC601;
else /*hdmi->hdmi_data.colorimetry == HDMI_COLORIMETRY_ITU_709*/
ext_colorimetry =
HDMI_FC_AVICONF2_EXT_COLORIMETRY_XVYCC709;
} else if (hdmi->hdmi_data.enc_out_format != RGB) {
if (hdmi->hdmi_data.colorimetry == HDMI_COLORIMETRY_ITU_601)
colorimetry = HDMI_FC_AVICONF1_COLORIMETRY_SMPTE;
else /*hdmi->hdmi_data.colorimetry == HDMI_COLORIMETRY_ITU_709*/
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(hdmi, 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(hdmi, val, HDMI_FC_AVICONF2);
/* AVI Data Byte 4 */
hdmi_writeb(hdmi, 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(hdmi, 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(hdmi, val, HDMI_FC_AVICONF3);
/* AVI Data Bytes 6-13 */
hdmi_writeb(hdmi, 0, HDMI_FC_AVIETB0);
hdmi_writeb(hdmi, 0, HDMI_FC_AVIETB1);
hdmi_writeb(hdmi, 0, HDMI_FC_AVISBB0);
hdmi_writeb(hdmi, 0, HDMI_FC_AVISBB1);
hdmi_writeb(hdmi, 0, HDMI_FC_AVIELB0);
hdmi_writeb(hdmi, 0, HDMI_FC_AVIELB1);
hdmi_writeb(hdmi, 0, HDMI_FC_AVISRB0);
hdmi_writeb(hdmi, 0, HDMI_FC_AVISRB1);
}
static void hdmi_av_composer(struct dw_hdmi *hdmi,
const struct drm_display_mode *mode)
{
u8 inv_val = 0;
struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode;
int hblank, vblank, h_de_hs, v_de_vs, hsync_len, vsync_len;
vmode->mhsyncpolarity = !!(mode->flags & DRM_MODE_FLAG_PHSYNC);
vmode->mvsyncpolarity = !!(mode->flags & DRM_MODE_FLAG_PVSYNC);
vmode->minterlaced = !!(mode->flags & DRM_MODE_FLAG_INTERLACE);
vmode->mpixelclock = mode->clock * 1000;
dev_dbg(hdmi->dev, "final pixclk = %d\n", vmode->mpixelclock);
/* Set up HDMI_FC_INVIDCONF */
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(hdmi, inv_val, HDMI_FC_INVIDCONF);
/* Set up horizontal active pixel width */
hdmi_writeb(hdmi, mode->hdisplay >> 8, HDMI_FC_INHACTV1);
hdmi_writeb(hdmi, mode->hdisplay, HDMI_FC_INHACTV0);
/* Set up vertical active lines */
hdmi_writeb(hdmi, mode->vdisplay >> 8, HDMI_FC_INVACTV1);
hdmi_writeb(hdmi, mode->vdisplay, HDMI_FC_INVACTV0);
/* Set up horizontal blanking pixel region width */
hblank = mode->htotal - mode->hdisplay;
hdmi_writeb(hdmi, hblank >> 8, HDMI_FC_INHBLANK1);
hdmi_writeb(hdmi, hblank, HDMI_FC_INHBLANK0);
/* Set up vertical blanking pixel region width */
vblank = mode->vtotal - mode->vdisplay;
hdmi_writeb(hdmi, vblank, HDMI_FC_INVBLANK);
/* Set up HSYNC active edge delay width (in pixel clks) */
h_de_hs = mode->hsync_start - mode->hdisplay;
hdmi_writeb(hdmi, h_de_hs >> 8, HDMI_FC_HSYNCINDELAY1);
hdmi_writeb(hdmi, h_de_hs, HDMI_FC_HSYNCINDELAY0);
/* Set up VSYNC active edge delay (in lines) */
v_de_vs = mode->vsync_start - mode->vdisplay;
hdmi_writeb(hdmi, v_de_vs, HDMI_FC_VSYNCINDELAY);
/* Set up HSYNC active pulse width (in pixel clks) */
hsync_len = mode->hsync_end - mode->hsync_start;
hdmi_writeb(hdmi, hsync_len >> 8, HDMI_FC_HSYNCINWIDTH1);
hdmi_writeb(hdmi, hsync_len, HDMI_FC_HSYNCINWIDTH0);
/* Set up VSYNC active edge delay (in lines) */
vsync_len = mode->vsync_end - mode->vsync_start;
hdmi_writeb(hdmi, vsync_len, HDMI_FC_VSYNCINWIDTH);
}
static void dw_hdmi_phy_disable(struct dw_hdmi *hdmi)
{
if (!hdmi->phy_enabled)
return;
dw_hdmi_phy_enable_tmds(hdmi, 0);
dw_hdmi_phy_enable_power(hdmi, 0);
hdmi->phy_enabled = false;
}
static void dw_hdmi_audio_set_plugged_callback(struct dw_hdmi *hdmi,
dw_hdmi_audio_plugged_fn fn)
{
mutex_lock(&hdmi->hpd_mutex);
/* Set it once and call to sync up state (while holding mutex) */
hdmi->audio_plugged_fn = fn;
fn(hdmi->audio_pdev, hdmi->plugged);
mutex_unlock(&hdmi->hpd_mutex);
}
static void dw_hdmi_audio_clk_enable(struct dw_hdmi *hdmi)
{
hdmi_modb(hdmi, 0, HDMI_MC_CLKDIS_AUDCLK_DISABLE, HDMI_MC_CLKDIS);
}
static void dw_hdmi_audio_clk_disable(struct dw_hdmi *hdmi)
{
hdmi_modb(hdmi, HDMI_MC_CLKDIS_AUDCLK_DISABLE,
HDMI_MC_CLKDIS_AUDCLK_DISABLE, HDMI_MC_CLKDIS);
}
static void dw_hdmi_audio_restore(struct dw_hdmi *hdmi)
{
unsigned long flags;
spin_lock_irqsave(&hdmi->audio_lock, flags);
if (hdmi->audio_enable)
dw_hdmi_audio_clk_enable(hdmi);
else
dw_hdmi_audio_clk_disable(hdmi);
spin_unlock_irqrestore(&hdmi->audio_lock, flags);
}
/* NOTE: may be called with IRQs disabled */
static void dw_hdmi_audio_enable(struct dw_hdmi *hdmi)
{
unsigned long flags;
spin_lock_irqsave(&hdmi->audio_lock, flags);
if (!hdmi->audio_enable) {
hdmi->audio_enable = true;
dw_hdmi_audio_clk_enable(hdmi);
}
spin_unlock_irqrestore(&hdmi->audio_lock, flags);
}
/* NOTE: may be called with IRQs disabled */
static void dw_hdmi_audio_disable(struct dw_hdmi *hdmi)
{
unsigned long flags;
spin_lock_irqsave(&hdmi->audio_lock, flags);
if (hdmi->audio_enable) {
hdmi->audio_enable = false;
dw_hdmi_audio_clk_disable(hdmi);
}
spin_unlock_irqrestore(&hdmi->audio_lock, flags);
}
/* HDMI Initialization Step B.4 */
static void dw_hdmi_enable_video_path(struct dw_hdmi *hdmi)
{
u8 clkdis;
/* control period minimum duration */
hdmi_writeb(hdmi, 12, HDMI_FC_CTRLDUR);
hdmi_writeb(hdmi, 32, HDMI_FC_EXCTRLDUR);
hdmi_writeb(hdmi, 1, HDMI_FC_EXCTRLSPAC);
/* Set to fill TMDS data channels */
hdmi_writeb(hdmi, 0x0B, HDMI_FC_CH0PREAM);
hdmi_writeb(hdmi, 0x16, HDMI_FC_CH1PREAM);
hdmi_writeb(hdmi, 0x21, HDMI_FC_CH2PREAM);
/* Enable pixel clock and tmds data path */
clkdis = 0x7F;
clkdis &= ~HDMI_MC_CLKDIS_PIXELCLK_DISABLE;
hdmi_writeb(hdmi, clkdis, HDMI_MC_CLKDIS);
clkdis &= ~HDMI_MC_CLKDIS_TMDSCLK_DISABLE;
hdmi_writeb(hdmi, clkdis, HDMI_MC_CLKDIS);
/* Enable csc path */
if (is_color_space_conversion(hdmi)) {
clkdis &= ~HDMI_MC_CLKDIS_CSCCLK_DISABLE;
hdmi_writeb(hdmi, clkdis, HDMI_MC_CLKDIS);
}
}
/* Workaround to clear the overflow condition */
static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi)
{
int count;
u8 val;
/* TMDS software reset */
hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ, HDMI_MC_SWRSTZ);
val = hdmi_readb(hdmi, HDMI_FC_INVIDCONF);
if (hdmi->dev_type == IMX6DL_HDMI) {
hdmi_writeb(hdmi, val, HDMI_FC_INVIDCONF);
return;
}
for (count = 0; count < 4; count++)
hdmi_writeb(hdmi, val, HDMI_FC_INVIDCONF);
}
static void hdmi_enable_overflow_interrupts(struct dw_hdmi *hdmi)
{
hdmi_writeb(hdmi, 0, HDMI_FC_MASK2);
hdmi_writeb(hdmi, 0, HDMI_IH_MUTE_FC_STAT2);
}
static void hdmi_disable_overflow_interrupts(struct dw_hdmi *hdmi)
{
hdmi_writeb(hdmi, HDMI_IH_MUTE_FC_STAT2_OVERFLOW_MASK,
HDMI_IH_MUTE_FC_STAT2);
}
static void hdmi_set_avmute(struct dw_hdmi *hdmi)
{
hdmi_writeb(hdmi, HDMI_FC_GCP_SET_AVMUTE, HDMI_FC_GCP);
}
static void hdmi_clear_avmute(struct dw_hdmi *hdmi)
{
hdmi_writeb(hdmi, HDMI_FC_GCP_CLEAR_AVMUTE, HDMI_FC_GCP);
}
static void hdmi_audio_fifo_reset(struct dw_hdmi *hdmi)
{
hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_II2SSWRST_REQ, HDMI_MC_SWRSTZ);
hdmi_modb(hdmi, HDMI_AUD_CONF0_SW_AUDIO_FIFO_RST,
HDMI_AUD_CONF0_SW_AUDIO_FIFO_RST, HDMI_AUD_CONF0);
hdmi_writeb(hdmi, 0x00, HDMI_AUD_INT);
hdmi_writeb(hdmi, 0x00, HDMI_AUD_INT1);
}
static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
{
int ret;
hdmi_set_avmute(hdmi);
hdmi_disable_overflow_interrupts(hdmi);
hdmi->vic = drm_match_cea_mode(mode);
if (!hdmi->vic)
dev_dbg(hdmi->dev, "Non-CEA mode used in HDMI\n");
else
dev_dbg(hdmi->dev, "CEA mode used vic=%d\n", hdmi->vic);
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 = HDMI_COLORIMETRY_ITU_601;
else
hdmi->hdmi_data.colorimetry = HDMI_COLORIMETRY_ITU_709;
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 interface) */
hdmi->hdmi_data.enc_in_format = RGB;
hdmi->hdmi_data.enc_out_format = RGB;
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, mode);
/* HDMI Initializateion Step B.2 */
ret = dw_hdmi_phy_init(hdmi);
if (ret)
return ret;
/* HDMI Initialization Step B.3 */
dw_hdmi_enable_video_path(hdmi);
/* not for DVI mode */
if (!hdmi->hdmi_data.video_mode.has_audio) {
dev_info(hdmi->dev, "monitor does not support audio\n");
} else {
dev_dbg(hdmi->dev, "monitor support audio\n");
hdmi_audio_fifo_reset(hdmi);
/* TODO: Keep audio clock enable before setting N/CTS resigters
* can resolve TV no sound when switching different resolutions.
* Though we can fix by this way, but we can not give an
* acceptable reason, so untile now we mark this an hack way.
*/
dw_hdmi_audio_restore(hdmi);
/* HDMI Initialization Step E - Configure audio */
hdmi_clk_regenerator_update_pixel_clock(hdmi);
/* HDMI Initialization Step F - Configure AVI InfoFrame */
hdmi_config_AVI(hdmi, mode);
}
hdmi_video_packetize(hdmi);
hdmi_video_csc(hdmi);
hdmi_video_sample(hdmi);
hdmi_tx_hdcp_config(hdmi);
dw_hdmi_clear_overflow(hdmi);
if (hdmi->cable_plugin && !hdmi->hdmi_data.video_mode.mdvi)
hdmi_enable_overflow_interrupts(hdmi);
hdmi_clear_avmute(hdmi);
return 0;
}
static void hdmi_parse_id(struct dw_hdmi *hdmi)
{
u8 config0_id, config1_id, config2_id, config3_id;
config0_id = hdmi_readb(hdmi, HDMI_CONFIG0_ID);
config1_id = hdmi_readb(hdmi, HDMI_CONFIG1_ID);
config2_id = hdmi_readb(hdmi, HDMI_CONFIG2_ID);
config3_id = hdmi_readb(hdmi, HDMI_CONFIG3_ID);
hdmi->id.prepen = config0_id & HDMI_CONFIG0_ID_PREPEN ? true : false;
hdmi->id.audi2s = config0_id & HDMI_CONFIG0_ID_AUDI2S ? true : false;
hdmi->id.hdmi14 = config0_id & HDMI_CONFIG0_ID_HDMI14 ? true : false;
hdmi->id.hdcp = config0_id & HDMI_CONFIG0_ID_HDCP ? true : false;
hdmi->id.csc = config0_id & HDMI_CONFIG0_ID_CSC ? true : false;
hdmi->id.audspdif = config0_id & HDMI_CONFIG0_ID_AUDSPDIF ?
true : false;
hdmi->id.confapb = config1_id & HDMI_CONFIG1_ID_CONFAPB ? true : false;
hdmi->id.hdmi20 = config1_id & HDMI_CONFIG1_ID_HDMI20 ? true : false;
hdmi->id.phy_type = config2_id;
hdmi->id.gpaud = config3_id & HDMI_CONFIG3_ID_GPAUD ? true : false;
hdmi->id.ahbauddma = config3_id & HDMI_CONFIG3_ID_AHBAUDDMA ?
true : false;
hdmi->id.design = hdmi_readb(hdmi, HDMI_DESIGN_ID);
hdmi->id.revision = hdmi_readb(hdmi, HDMI_REVISION_ID);
}
static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi)
{
u8 ih_mute;
/*
* Boot up defaults are:
* HDMI_IH_MUTE = 0x03 (disabled)
* HDMI_IH_MUTE_* = 0x00 (enabled)
*
* Disable top level interrupt bits in HDMI block
*/
ih_mute = hdmi_readb(hdmi, HDMI_IH_MUTE) |
HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT |
HDMI_IH_MUTE_MUTE_ALL_INTERRUPT;
hdmi_writeb(hdmi, ih_mute, HDMI_IH_MUTE);
/* by default mask all interrupts */
hdmi_writeb(hdmi, 0xff, HDMI_VP_MASK);
hdmi_writeb(hdmi, 0xff, HDMI_FC_MASK0);
hdmi_writeb(hdmi, 0xff, HDMI_FC_MASK1);
hdmi_writeb(hdmi, 0xff, HDMI_FC_MASK2);
hdmi_writeb(hdmi, 0xff, HDMI_PHY_MASK0);
hdmi_writeb(hdmi, 0xff, HDMI_PHY_I2CM_INT_ADDR);
hdmi_writeb(hdmi, 0xff, HDMI_PHY_I2CM_CTLINT_ADDR);
hdmi_writeb(hdmi, 0xff, HDMI_AUD_INT);
hdmi_writeb(hdmi, 0xff, HDMI_AUD_SPDIFINT);
hdmi_writeb(hdmi, 0xff, HDMI_AUD_HBR_MASK);
hdmi_writeb(hdmi, 0xff, HDMI_GP_MASK);
hdmi_writeb(hdmi, 0xff, HDMI_A_APIINTMSK);
hdmi_writeb(hdmi, 0xff, HDMI_CEC_MASK);
hdmi_writeb(hdmi, 0xff, HDMI_I2CM_INT);
hdmi_writeb(hdmi, 0xff, HDMI_I2CM_CTLINT);
/* Disable interrupts in the IH_MUTE_* registers */
hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_FC_STAT0);
hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_FC_STAT1);
hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_FC_STAT2);
hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_AS_STAT0);
hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_PHY_STAT0);
hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_I2CM_STAT0);
hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_CEC_STAT0);
hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_VP_STAT0);
hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_I2CMPHY_STAT0);
hdmi_writeb(hdmi, 0xff, HDMI_IH_MUTE_AHBDMAAUD_STAT0);
/* Enable top level interrupt bits in HDMI block */
ih_mute &= ~(HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT |
HDMI_IH_MUTE_MUTE_ALL_INTERRUPT);
hdmi_writeb(hdmi, ih_mute, HDMI_IH_MUTE);
}
static void hdmi_mute_interrupts(struct dw_hdmi *hdmi)
{
u8 ih_mute;
/* Disable all interrupts */
hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);
/* Disable top level interrupt bits in HDMI block */
ih_mute = hdmi_readb(hdmi, HDMI_IH_MUTE) |
HDMI_IH_MUTE_MUTE_WAKEUP_INTERRUPT |
HDMI_IH_MUTE_MUTE_ALL_INTERRUPT;
hdmi_writeb(hdmi, ih_mute, HDMI_IH_MUTE);
}
static void hdmi_init_interrupts(struct dw_hdmi *hdmi)
{
hdmi_writeb(hdmi, HDMI_PHY_I2CM_INT_ADDR_DONE_POL,
HDMI_PHY_I2CM_INT_ADDR);
hdmi_writeb(hdmi, HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL |
HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL,
HDMI_PHY_I2CM_CTLINT_ADDR);
mutex_lock(&hdmi->hpd_mutex);
/* Make sure HPD polarity matches how we think the cable is inserted */
if (!hdmi->plugged)
hdmi_writeb(hdmi, HDMI_PHY_HPD, HDMI_PHY_POL0);
else
hdmi_writeb(hdmi, 0, HDMI_PHY_POL0);
/* Unmask HPD, clear transitory interrupts, then unmute */
hdmi_writeb(hdmi, (u8)~HDMI_PHY_HPD, HDMI_PHY_MASK0);
hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD, HDMI_IH_PHY_STAT0);
hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0);
mutex_unlock(&hdmi->hpd_mutex);
/* Unmask HDCP engaged interrupt */
hdmi_writeb(hdmi, (u8)~HDMI_A_APIINTSTAT_HDCP_ENGAGED,
HDMI_A_APIINTMSK);
}
static void dw_hdmi_poweron(struct dw_hdmi *hdmi)
{
dw_hdmi_setup(hdmi, &hdmi->previous_mode);
}
static void dw_hdmi_poweroff(struct dw_hdmi *hdmi)
{
dw_hdmi_phy_disable(hdmi);
}
static void dw_hdmi_bridge_mode_set(struct drm_bridge *bridge,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct dw_hdmi *hdmi = bridge->driver_private;
dw_hdmi_setup(hdmi, adjusted_mode);
/* Store the display mode for plugin/DKMS poweron events */
memcpy(&hdmi->previous_mode, adjusted_mode, sizeof(*adjusted_mode));
}
static bool dw_hdmi_bridge_mode_fixup(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
static void dw_hdmi_bridge_disable(struct drm_bridge *bridge)
{
struct dw_hdmi *hdmi = bridge->driver_private;
dw_hdmi_poweroff(hdmi);
mutex_lock(&hdmi->hdcp_state_mutex);
if (hdmi->hdcp_state != DW_HDMI_HDCP_STATE_DISABLED) {
/*
* We can ensure each time HDMI unplug/suspend, bridge
* disable would be called by the drm framework with mode
* config locked, so we just need to maintain the HDCP
* desiredness here.
*/
if (hdmi->hdcp_state == DW_HDMI_HDCP_STATE_ENCRY_EN)
_hdmi_set_hdcp_drm_property(hdmi,
DRM_MODE_CONTENT_PROTECTION_DESIRED);
_hdmi_close_hdcp_auth(hdmi);
}
mutex_unlock(&hdmi->hdcp_state_mutex);
}
static void dw_hdmi_bridge_enable(struct drm_bridge *bridge)
{
struct dw_hdmi *hdmi = bridge->driver_private;
dw_hdmi_poweron(hdmi);
}
static void dw_hdmi_bridge_destroy(struct drm_bridge *bridge)
{
drm_bridge_cleanup(bridge);
kfree(bridge);
}
static void dw_hdmi_bridge_nope(struct drm_bridge *bridge)
{
/* do nothing */
}
static enum drm_connector_status
dw_hdmi_connector_detect(struct drm_connector *connector, bool force)
{
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
connector);
enum drm_connector_status ret;
if (hdmi->hpd_ignore)
return connector_status_disconnected;
/*
* We'll return hdmi->plugged instead of raw HDMI_PHY_STAT0 so that
* everything is consistent with our internal state. If it's been
* unplugged or plugged in the meantime, we'll get another IRQ.
*
* We want to have the mutex so we know the IRQ isn't currently
* modifying this. Thus we know that if the IRQ changes it then DRM
* will definitely get told again.
*/
mutex_lock(&hdmi->hpd_mutex);
ret = hdmi->plugged ?
connector_status_connected : connector_status_disconnected;
mutex_unlock(&hdmi->hpd_mutex);
return ret;
}
static int dw_hdmi_connector_get_modes(struct drm_connector *connector)
{
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
connector);
struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode;
struct edid *edid;
int ret = 0;
if (!hdmi->ddc)
return 0;
edid = drm_get_edid(connector, hdmi->ddc);
if (edid) {
dev_dbg(hdmi->dev, "got edid: width[%d] x height[%d]\n",
edid->width_cm, edid->height_cm);
vmode->mdvi = !drm_detect_hdmi_monitor(edid);
vmode->has_audio = drm_detect_monitor_audio(edid);
drm_mode_connector_update_edid_property(connector, edid);
ret = drm_add_edid_modes(connector, edid);
kfree(edid);
} else {
dev_dbg(hdmi->dev, "failed to get edid\n");
/* We'll go with the safest mode */
vmode->mdvi = true;
vmode->has_audio = false;
}
return ret;
}
static enum drm_mode_status
dw_hdmi_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
struct dw_hdmi *hdmi = container_of(connector,
struct dw_hdmi, connector);
struct hdmi_vmode *vmode = &hdmi->hdmi_data.video_mode;
enum drm_mode_status mode_status = MODE_OK;
if (mode->clock > 165000 && vmode->mdvi)
return MODE_BAD;
if (hdmi->plat_data->mode_valid)
mode_status = hdmi->plat_data->mode_valid(connector, mode);
return mode_status;
}
static struct drm_encoder *dw_hdmi_connector_best_encoder(struct drm_connector
*connector)
{
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
connector);
return hdmi->encoder;
}
static void dw_hdmi_connector_destroy(struct drm_connector *connector)
{
drm_connector_unregister(connector);
drm_connector_cleanup(connector);
}
static void dw_hdmi_set_content_protection(struct dw_hdmi *hdmi,
struct drm_mode_object *obj,
struct drm_property *property,
uint64_t val)
{
/*
* For enable: we start authentication. When that finishes we'll
* get an interrupt and turn on encryption.
*
* For disable: we just turn off encryption. There's no need to
* undo the HDCP auth.
*/
switch (val) {
case DRM_MODE_CONTENT_PROTECTION_UNDESIRED:
hdmi_disable_hdcp_encry(hdmi);
break;
case DRM_MODE_CONTENT_PROTECTION_DESIRED:
/*
* Start auth over even if at DW_HDMI_HDCP_STATE_AUTH_DONE
* since we need the interrupt where we'll set
* DRM_MODE_CONTENT_PROTECTION_ENABLED. We don't wan to set
* it here because we'd need to grab the mode_config mutex and
* that could lead to deadlock.
*/
mutex_lock(&hdmi->hdcp_state_mutex);
_hdmi_start_hdcp_auth(hdmi);
mutex_unlock(&hdmi->hdcp_state_mutex);
break;
case DRM_MODE_CONTENT_PROTECTION_ENABLED:
/*
* Don't expect to get here. Userspace will just set to
* desired and expects us to set to enabled. When we set
* to enabled we don't end up here.
*/
WARN_ON(1);
break;
default:
WARN_ON(1);
}
}
static int dw_hdmi_connector_set_property(struct drm_connector *connector,
struct drm_atomic_state *state,
struct drm_property *property,
uint64_t val, void *blob_data)
{
struct dw_hdmi *hdmi = container_of(connector, struct dw_hdmi,
connector);
DRM_DEBUG_KMS("[PROPERTY:%s] = %llu\n", property->name, val);
if (strcmp(property->name, "Content Protection") == 0)
dw_hdmi_set_content_protection(hdmi, &connector->base,
property, val);
return 0;
}
static struct drm_connector_funcs dw_hdmi_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.fill_modes = drm_helper_probe_single_connector_modes,
.detect = dw_hdmi_connector_detect,
.destroy = dw_hdmi_connector_destroy,
.set_property = dw_hdmi_connector_set_property,
};
static struct drm_connector_helper_funcs dw_hdmi_connector_helper_funcs = {
.get_modes = dw_hdmi_connector_get_modes,
.mode_valid = dw_hdmi_connector_mode_valid,
.best_encoder = dw_hdmi_connector_best_encoder,
};
struct drm_bridge_funcs dw_hdmi_bridge_funcs = {
.enable = dw_hdmi_bridge_enable,
.disable = dw_hdmi_bridge_disable,
.pre_enable = dw_hdmi_bridge_nope,
.post_disable = dw_hdmi_bridge_nope,
.mode_set = dw_hdmi_bridge_mode_set,
.mode_fixup = dw_hdmi_bridge_mode_fixup,
.destroy = dw_hdmi_bridge_destroy,
};
static irqreturn_t dw_hdmi_i2c_irq(struct dw_hdmi *hdmi)
{
struct dw_hdmi_i2c *i2c = hdmi->i2c;
unsigned int stat;
stat = hdmi_readb(hdmi, HDMI_IH_I2CM_STAT0);
if (!stat)
return IRQ_NONE;
hdmi_writeb(hdmi, stat, HDMI_IH_I2CM_STAT0);
i2c->stat = stat;
complete(&i2c->cmp);
return IRQ_HANDLED;
}
static irqreturn_t dw_hdmi_hardirq(int irq, void *dev_id)
{
struct dw_hdmi *hdmi = dev_id;
u8 intr_stat, hdcp_stat;
irqreturn_t ret = IRQ_NONE;
if (hdmi->i2c)
ret = dw_hdmi_i2c_irq(hdmi);
/*
* Handle things that will wake the thread below by:
* 1. Mask them (they'll be unmasked by the thread)
* 2. Make sure we return IRQ_WAKE_THREAD. Even if we handled an
* I2C interrupt we need to override here.
*
* We always process all things that might wake the thread. We don't
* worry about locking around writing the mask/unmask registers
* because (at the moment) we always enable "all the interrupts we
* care about" or "none of the interrupts". With an 8-bit write this
* should always be atomic. Whenever we write we'll return
* IRQ_WAKE_THREAD so we're guaranteed to run our thread. If
* someone else unmutes, worst case is that we'll end up right back
* here and mute again (and run our thread an extra time).
*/
hdcp_stat = hdmi_readb(hdmi, HDMI_A_APIINTSTAT);
if (hdcp_stat & HDMI_A_APIINTSTAT_HDCP_ENGAGED) {
hdmi_writeb(hdmi, ~0, HDMI_A_APIINTMSK);
ret = IRQ_WAKE_THREAD;
}
intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0);
if (intr_stat) {
hdmi_writeb(hdmi, ~0, HDMI_IH_MUTE_PHY_STAT0);
ret = IRQ_WAKE_THREAD;
}
return ret;
}
static irqreturn_t dw_hdmi_irq(int irq, void *dev_id)
{
struct dw_hdmi *hdmi = dev_id;
u8 intr_stat, hdcp_stat;
u8 phy_int_pol;
intr_stat = hdmi_readb(hdmi, HDMI_IH_PHY_STAT0);
/*
* Handle HPD signal. HPD is level-sensitive and latched, so:
* - If we were looking for plugs and there was a plug/unplug, we'll
* see an interrupt (for the plug). If we change polarity and try
* to clear the interrupt it will come right back.
* - If we were looking for plugs and there was a plug (and no unplug)
* and we try to clear the interrupt before changing the polarity, it
* will just come back.
*
* To make sure we don't lose anything, we always clear the interrupt
* right after changing the polarity to catch the other edge but before
* processing it.
*/
if (intr_stat & HDMI_IH_PHY_STAT0_HPD) {
/*
* We always want to keep HPD polarity and "plugged" concept in
* sync so grab the mutex around this whole chunk.
*/
mutex_lock(&hdmi->hpd_mutex);
phy_int_pol = hdmi_readb(hdmi, HDMI_PHY_POL0);
if (phy_int_pol & HDMI_PHY_HPD) {
dev_dbg(hdmi->dev, "EVENT=plugin\n");
hdmi_modb(hdmi, 0, HDMI_PHY_HPD, HDMI_PHY_POL0);
hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD,
HDMI_IH_PHY_STAT0);
hdmi->plugged = true;
if (hdmi->audio_plugged_fn)
hdmi->audio_plugged_fn(hdmi->audio_pdev, true);
mutex_unlock(&hdmi->hpd_mutex);
dw_hdmi_poweron(hdmi);
} else {
dev_dbg(hdmi->dev, "EVENT=plugout\n");
hdmi_modb(hdmi, HDMI_PHY_HPD, HDMI_PHY_HPD,
HDMI_PHY_POL0);
hdmi_writeb(hdmi, HDMI_IH_PHY_STAT0_HPD,
HDMI_IH_PHY_STAT0);
hdmi->plugged = false;
if (hdmi->audio_plugged_fn)
hdmi->audio_plugged_fn(hdmi->audio_pdev, false);
mutex_unlock(&hdmi->hpd_mutex);
dw_hdmi_poweroff(hdmi);
}
drm_helper_hpd_irq_event(hdmi->connector.dev);
intr_stat &= ~HDMI_IH_PHY_STAT0_HPD;
}
/*
* Clear any other PHY_STAT0 interrupts just to be nice.
* They should always be muted anyway, though.
*/
if (intr_stat)
hdmi_writeb(hdmi, intr_stat, HDMI_IH_PHY_STAT0);
/*
* If HDCP auth successfully, then set "Content protection"
* to "Enabled"
*/
hdcp_stat = hdmi_readb(hdmi, HDMI_A_APIINTSTAT);
if (hdcp_stat & HDMI_A_APIINTSTAT_HDCP_ENGAGED) {
hdmi_writeb(hdmi, HDMI_A_APIINTSTAT_HDCP_ENGAGED,
HDMI_A_APIINTCLR);
hdcp_stat &= ~HDMI_A_APIINTSTAT_HDCP_ENGAGED;
/* Always grab mode config mutex before state mutex */
mutex_lock(&hdmi->connector.dev->mode_config.mutex);
mutex_lock(&hdmi->hdcp_state_mutex);
hdmi->hdcp_state = DW_HDMI_HDCP_STATE_AUTH_DONE;
_hdmi_enable_hdcp_encry(hdmi);
_hdmi_set_hdcp_drm_property(hdmi,
DRM_MODE_CONTENT_PROTECTION_ENABLED);
mutex_unlock(&hdmi->hdcp_state_mutex);
mutex_unlock(&hdmi->connector.dev->mode_config.mutex);
}
if (hdcp_stat) {
dev_dbg(hdmi->dev, "Unexpected HDCP irq %#x\n", hdcp_stat);
hdmi_writeb(hdmi, hdcp_stat, HDMI_A_APIINTCLR);
}
/* Unmute/unmask interrupts */
hdmi_writeb(hdmi, ~HDMI_IH_PHY_STAT0_HPD, HDMI_IH_MUTE_PHY_STAT0);
hdmi_writeb(hdmi, (u8)~HDMI_A_APIINTSTAT_HDCP_ENGAGED,
HDMI_A_APIINTMSK);
return IRQ_HANDLED;
}
static int dw_hdmi_register(struct drm_device *drm, struct dw_hdmi *hdmi)
{
struct drm_encoder *encoder = hdmi->encoder;
struct drm_mode_config *mode_config;
struct drm_bridge *bridge;
char name[48];
int ret;
bridge = devm_kzalloc(drm->dev, sizeof(*bridge), GFP_KERNEL);
if (!bridge) {
DRM_ERROR("Failed to allocate drm bridge\n");
return -ENOMEM;
}
hdmi->bridge = bridge;
bridge->driver_private = hdmi;
ret = drm_bridge_init(drm, bridge, &dw_hdmi_bridge_funcs);
if (ret) {
DRM_ERROR("Failed to initialize bridge with drm\n");
return -EINVAL;
}
encoder->bridge = bridge;
hdmi->connector.polled = DRM_CONNECTOR_POLL_HPD;
drm_connector_helper_add(&hdmi->connector,
&dw_hdmi_connector_helper_funcs);
drm_connector_init(drm, &hdmi->connector, &dw_hdmi_connector_funcs,
DRM_MODE_CONNECTOR_HDMIA);
ret = drm_connector_register(&hdmi->connector);
if (ret) {
DRM_ERROR("Failed to register connector\n");
drm_connector_cleanup(&hdmi->connector);
return ret;
}
mode_config = &hdmi->connector.dev->mode_config;
drm_object_attach_property(&hdmi->connector.base,
mode_config->content_protection_property,
DRM_MODE_CONTENT_PROTECTION_UNDESIRED);
hdmi->connector.encoder = encoder;
drm_mode_connector_attach_encoder(&hdmi->connector, encoder);
snprintf(name, sizeof(name), "i2c-%d", hdmi->ddc->nr);
ret = sysfs_create_link(&hdmi->connector.kdev->kobj, &hdmi->ddc->dev.kobj, name);
if (ret)
DRM_ERROR("Cannot create sysfs symlink (%d)\n", ret);
return 0;
}
int dw_hdmi_config_hdcp_key(struct device *dev,
const struct dw_hdmi_hdcp_key_1x *keys)
{
struct dw_hdmi *hdmi = dev_get_drvdata(dev);
int ret;
if (!keys)
return -EINVAL;
mutex_lock(&hdmi->hdcp_key_mutex);
if (hdmi->is_hdcp_key_present) {
mutex_unlock(&hdmi->hdcp_key_mutex);
return -EBUSY;
}
hdmi->is_hdcp_key_present = true;
hdmi->hdcp_key = *keys;
ret = _hdmi_write_hdcp_key(hdmi);
mutex_unlock(&hdmi->hdcp_key_mutex);
if (ret) {
dev_err(dev, "Failed to write HDCP key to HDMI IP\n");
return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(dw_hdmi_config_hdcp_key);
int dw_hdmi_bind(struct device *dev, struct device *master,
void *data, struct drm_encoder *encoder,
struct resource *iores, int irq,
const struct dw_hdmi_plat_data *plat_data)
{
struct platform_device_info pdevinfo;
struct dw_hdmi_audio_data audio;
struct drm_device *drm = data;
struct device_node *np = dev->of_node;
struct device_node *ddc_node;
struct dw_hdmi *hdmi;
int ret;
u32 val = 1;
hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
if (!hdmi)
return -ENOMEM;
hdmi->plat_data = plat_data;
hdmi->dev = dev;
hdmi->dev_type = plat_data->dev_type;
hdmi->sample_rate = 48000;
hdmi->encoder = encoder;
hdmi->audio_enable = false;
mutex_init(&hdmi->hpd_mutex);
mutex_init(&hdmi->hdcp_key_mutex);
mutex_init(&hdmi->hdcp_state_mutex);
spin_lock_init(&hdmi->audio_lock);
spin_lock_init(&hdmi->reg_lock);
of_property_read_u32(np, "reg-io-width", &val);
switch (val) {
case 4:
hdmi->write = dw_hdmi_writel;
hdmi->read = dw_hdmi_readl;
break;
case 1:
hdmi->write = dw_hdmi_writeb;
hdmi->read = dw_hdmi_readb;
break;
default:
dev_err(dev, "reg-io-width must be 1 or 4\n");
return -EINVAL;
}
ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0);
if (ddc_node) {
hdmi->ddc = of_find_i2c_adapter_by_node(ddc_node);
of_node_put(ddc_node);
if (!hdmi->ddc) {
dev_dbg(hdmi->dev, "failed to read ddc node\n");
return -EPROBE_DEFER;
}
} else {
dev_dbg(hdmi->dev, "no ddc property found\n");
}
if (of_find_property(np, "hpd-ignore", NULL)) {
dev_info(hdmi->dev, "Ignoring HPD\n");
hdmi->hpd_ignore = true;
}
ret = devm_request_threaded_irq(dev, irq, dw_hdmi_hardirq,
dw_hdmi_irq, 0,
dev_name(dev), hdmi);
if (ret)
return ret;
hdmi->regs = devm_ioremap_resource(dev, iores);
if (IS_ERR(hdmi->regs))
return PTR_ERR(hdmi->regs);
hdmi->isfr_clk = devm_clk_get(hdmi->dev, "isfr");
if (IS_ERR(hdmi->isfr_clk)) {
ret = PTR_ERR(hdmi->isfr_clk);
dev_err(hdmi->dev, "Unable to get HDMI isfr clk: %d\n", ret);
return ret;
}
ret = clk_prepare_enable(hdmi->isfr_clk);
if (ret) {
dev_err(hdmi->dev, "Cannot enable HDMI isfr clock: %d\n", ret);
return ret;
}
hdmi->iahb_clk = devm_clk_get(hdmi->dev, "iahb");
if (IS_ERR(hdmi->iahb_clk)) {
ret = PTR_ERR(hdmi->iahb_clk);
dev_err(hdmi->dev, "Unable to get HDMI iahb clk: %d\n", ret);
goto err_isfr;
}
ret = clk_prepare_enable(hdmi->iahb_clk);
if (ret) {
dev_err(hdmi->dev, "Cannot enable HDMI iahb clock: %d\n", ret);
goto err_isfr;
}
/* Product and revision IDs */
dev_info(dev,
"Detected HDMI controller 0x%x:0x%x:0x%x:0x%x\n",
hdmi_readb(hdmi, HDMI_DESIGN_ID),
hdmi_readb(hdmi, HDMI_REVISION_ID),
hdmi_readb(hdmi, HDMI_PRODUCT_ID0),
hdmi_readb(hdmi, HDMI_PRODUCT_ID1));
/* Config IDs */
dev_info(dev,
"Detected HDMI config_id 0x%x:0x%x:0x%x:0x%x\n",
hdmi_readb(hdmi, HDMI_CONFIG0_ID),
hdmi_readb(hdmi, HDMI_CONFIG1_ID),
hdmi_readb(hdmi, HDMI_CONFIG2_ID),
hdmi_readb(hdmi, HDMI_CONFIG3_ID));
hdmi_parse_id(hdmi);
initialize_hdmi_ih_mutes(hdmi);
/*
* To prevent overflows in HDMI_IH_FC_STAT2, set the clk regenerator
* N and cts values before enabling phy
*/
hdmi_init_clk_regenerator(hdmi);
/* If DDC bus is not specified, try to register HDMI I2C bus */
if (!hdmi->ddc) {
/* Look for (optional) stuff related to unwedging */
hdmi->pinctrl = devm_pinctrl_get(dev);
if (!IS_ERR(hdmi->pinctrl)) {
hdmi->unwedge_state =
pinctrl_lookup_state(hdmi->pinctrl, "unwedge");
hdmi->default_state =
pinctrl_lookup_state(hdmi->pinctrl, "default");
if (IS_ERR(hdmi->default_state) &&
!IS_ERR(hdmi->unwedge_state)) {
dev_warn(dev,
"Unwedge requires default pinctrl\n");
hdmi->unwedge_state = ERR_PTR(-ENODEV);
}
}
hdmi->ddc = dw_hdmi_i2c_adapter(hdmi);
if (IS_ERR(hdmi->ddc))
hdmi->ddc = NULL;
}
ret = dw_hdmi_register(drm, hdmi);
if (ret)
goto err_iahb;
hdmi_init_interrupts(hdmi);
/* Unmute I2CM interrupts and reset HDMI DDC I2C master controller */
if (hdmi->i2c)
dw_hdmi_i2c_init(hdmi);
dev_set_drvdata(dev, hdmi);
memset(&pdevinfo, 0, sizeof(pdevinfo));
pdevinfo.parent = dev;
pdevinfo.id = PLATFORM_DEVID_NONE;
audio.set_plugged_callback = dw_hdmi_audio_set_plugged_callback;
audio.dw = hdmi;
audio.mod = hdmi_modb;
audio.read = hdmi_readb;
audio.write = hdmi_writeb;
audio.enable = dw_hdmi_audio_enable;
audio.disable = dw_hdmi_audio_disable;
audio.set_sample_rate = hdmi_set_sample_rate;
pdevinfo.name = "dw-hdmi-audio";
pdevinfo.data = &audio;
pdevinfo.size_data = sizeof(audio);
pdevinfo.dma_mask = DMA_BIT_MASK(32);
hdmi->audio_pdev = platform_device_register_full(&pdevinfo);
return 0;
err_iahb:
if (hdmi->i2c)
i2c_del_adapter(&hdmi->i2c->adap);
clk_disable_unprepare(hdmi->iahb_clk);
err_isfr:
clk_disable_unprepare(hdmi->isfr_clk);
return ret;
}
EXPORT_SYMBOL_GPL(dw_hdmi_bind);
void dw_hdmi_unbind(struct device *dev, struct device *master, void *data)
{
struct dw_hdmi *hdmi = dev_get_drvdata(dev);
if (!IS_ERR(hdmi->audio_pdev))
platform_device_unregister(hdmi->audio_pdev);
hdmi_mute_interrupts(hdmi);
hdmi->connector.funcs->destroy(&hdmi->connector);
hdmi->encoder->funcs->destroy(hdmi->encoder);
clk_disable_unprepare(hdmi->iahb_clk);
clk_disable_unprepare(hdmi->isfr_clk);
if (hdmi->i2c)
i2c_del_adapter(&hdmi->i2c->adap);
else
i2c_put_adapter(hdmi->ddc);
}
EXPORT_SYMBOL_GPL(dw_hdmi_unbind);
int dw_hdmi_suspend(struct device *dev)
{
struct dw_hdmi *hdmi = dev_get_drvdata(dev);
/*
* drvdata may not be set yet if suspend/resume is called before
* dw_hdmi_bind() has completed successfully.
*/
if (!hdmi)
return 0;
hdmi_mute_interrupts(hdmi);
return 0;
}
EXPORT_SYMBOL_GPL(dw_hdmi_suspend);
int dw_hdmi_resume(struct device *dev)
{
struct dw_hdmi *hdmi = dev_get_drvdata(dev);
/*
* drvdata may not be set yet if suspend/resume is called before
* dw_hdmi_bind() has completed successfully.
*/
if (!hdmi)
return 0;
initialize_hdmi_ih_mutes(hdmi);
/* Unmute I2CM interrupts and reset HDMI DDC I2C master controller */
if (hdmi->i2c)
dw_hdmi_i2c_init(hdmi);
/*
* After the suspend/resume, powerdomain would operate the PD_VIO,
* this would caused the HDMI controller reset, that would caused
* interrupt registers reset default to mute, and HDCP key be
* cleared, so we need to need to reconfigure the interrupt and
* re-write HDCP key.
*
* We must write the key before the interrupt since the interrupt
* might result in us needing the HDCP key.
*/
hdmi_write_hdcp_key(hdmi);
hdmi_init_interrupts(hdmi);
return 0;
}
EXPORT_SYMBOL_GPL(dw_hdmi_resume);
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
MODULE_AUTHOR("Vladimir Zapolskiy <vladimir_zapolskiy@mentor.com>");
MODULE_DESCRIPTION("DW HDMI transmitter driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:dw-hdmi");