1
0
Fork 0

MLK-23250-10: drm: imx: hdmi: Add iMX8MP hdmi driver

Add iMX8MP hdmi driver.
Basci hdmi video function is working.
EDID function is working.
HPD basic working but not stable enough for stress test,
may not work in some TVs.
HDMI GP audio function added.

Signed-off-by: Sandor Yu <Sandor.yu@nxp.com>
Reviewed-by: Robby Cai <robby.cai@nxp.com>
5.4-rM2-2.2.x-imx-squashed
Sandor Yu 2020-01-19 11:23:30 +08:00
parent 54ec768dc7
commit b82d0af62f
4 changed files with 327 additions and 12 deletions

View File

@ -180,6 +180,7 @@ struct dw_hdmi {
spinlock_t audio_lock;
struct mutex audio_mutex;
unsigned int sample_rate;
unsigned int channels;
unsigned int audio_cts;
unsigned int audio_n;
bool audio_enable;
@ -542,6 +543,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk)
n = 4096;
else if (pixel_clk == 74176000 || pixel_clk == 148352000)
n = 11648;
else if (pixel_clk == 297000000)
n = 3072;
else
n = 4096;
n *= mult;
@ -554,6 +557,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk)
n = 17836;
else if (pixel_clk == 148352000)
n = 8918;
else if (pixel_clk == 297000000)
n = 4704;
else
n = 6272;
n *= mult;
@ -568,6 +573,8 @@ static unsigned int hdmi_compute_n(unsigned int freq, unsigned long pixel_clk)
n = 11648;
else if (pixel_clk == 148352000)
n = 5824;
else if (pixel_clk == 297000000)
n = 5120;
else
n = 6144;
n *= mult;
@ -593,7 +600,7 @@ static void hdmi_set_clk_regenerator(struct dw_hdmi *hdmi,
config3 = hdmi_readb(hdmi, HDMI_CONFIG3_ID);
/* Only compute CTS when using internal AHB audio */
if (config3 & HDMI_CONFIG3_AHBAUDDMA) {
if ((config3 & HDMI_CONFIG3_AHBAUDDMA) || (config3 & HDMI_CONFIG3_GPAUD)) {
/*
* 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
@ -650,6 +657,7 @@ void dw_hdmi_set_channel_count(struct dw_hdmi *hdmi, unsigned int cnt)
u8 layout;
mutex_lock(&hdmi->audio_mutex);
hdmi->channels = cnt;
/*
* For >2 channel PCM audio, we need to select layout 1
@ -690,6 +698,84 @@ static void hdmi_enable_audio_clk(struct dw_hdmi *hdmi, bool enable)
hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS);
}
static void dw_hdmi_gp_audio_enable(struct dw_hdmi *hdmi)
{
int sample_freq = 0x2, org_sample_freq = 0xD;
int ch_mask = BIT(hdmi->channels) - 1;
switch (hdmi->sample_rate) {
case 32000:
sample_freq = 0x03;
org_sample_freq = 0x0C;
hdmi_writeb(hdmi, 0x31, HDMI_FC_AUDICONF1);
break;
case 44100:
sample_freq = 0x00;
org_sample_freq = 0x0F;
hdmi_writeb(hdmi, 0x32, HDMI_FC_AUDICONF1);
break;
case 48000:
sample_freq = 0x02;
org_sample_freq = 0x0D;
hdmi_writeb(hdmi, 0x33, HDMI_FC_AUDICONF1);
break;
case 88200:
sample_freq = 0x08;
org_sample_freq = 0x07;
hdmi_writeb(hdmi, 0x34, HDMI_FC_AUDICONF1);
break;
case 96000:
sample_freq = 0x0A;
org_sample_freq = 0x05;
hdmi_writeb(hdmi, 0x35, HDMI_FC_AUDICONF1);
break;
case 176400:
sample_freq = 0x0C;
org_sample_freq = 0x03;
hdmi_writeb(hdmi, 0x36, HDMI_FC_AUDICONF1);
break;
case 192000:
sample_freq = 0x0E;
org_sample_freq = 0x01;
hdmi_writeb(hdmi, 0x37, HDMI_FC_AUDICONF1);
break;
default:
break;
}
hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n);
hdmi_enable_audio_clk(hdmi, true);
hdmi_writeb(hdmi, 0x1, HDMI_FC_AUDSCHNL0);
hdmi_writeb(hdmi, hdmi->channels, HDMI_FC_AUDSCHNL2);
hdmi_writeb(hdmi, 0x22, HDMI_FC_AUDSCHNL3);
hdmi_writeb(hdmi, 0x22, HDMI_FC_AUDSCHNL4);
hdmi_writeb(hdmi, 0x11, HDMI_FC_AUDSCHNL5);
hdmi_writeb(hdmi, 0x11, HDMI_FC_AUDSCHNL6);
hdmi_writeb(hdmi, (0x3 << 4) | sample_freq, HDMI_FC_AUDSCHNL7);
hdmi_writeb(hdmi, (org_sample_freq << 4) | 0xb, HDMI_FC_AUDSCHNL8);
hdmi_writeb(hdmi, ch_mask, HDMI_GP_CONF1);
hdmi_writeb(hdmi, 0x02, HDMI_GP_CONF2);
hdmi_writeb(hdmi, 0x01, HDMI_GP_CONF0);
hdmi_modb(hdmi, 0x3, 0x3, HDMI_FC_DATAUTO3);
if (hdmi->phy.ops->enable_audio)
hdmi->phy.ops->enable_audio(hdmi, hdmi->phy.data, hdmi->channels);
}
static void dw_hdmi_gp_audio_disable(struct dw_hdmi *hdmi)
{
hdmi_set_cts_n(hdmi, hdmi->audio_cts, 0);
hdmi_modb(hdmi, 0, 0x3, HDMI_FC_DATAUTO3);
if (hdmi->phy.ops->disable_audio)
hdmi->phy.ops->disable_audio(hdmi, hdmi->phy.data);
hdmi_enable_audio_clk(hdmi, false);
}
static void dw_hdmi_ahb_audio_enable(struct dw_hdmi *hdmi)
{
hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n);
@ -1069,6 +1155,14 @@ static void hdmi_video_packetize(struct dw_hdmi *hdmi)
HDMI_VP_PR_CD_DESIRED_PR_FACTOR_MASK);
hdmi_writeb(hdmi, val, HDMI_VP_PR_CD);
val = hdmi_readb(hdmi, HDMI_FC_DATAUTO3);
if (color_depth == 4)
/* disable Auto GCP when bpp 24 */
val &= ~0x4;
else
val |= 0x4;
hdmi_writeb(hdmi, val, HDMI_FC_DATAUTO3);
hdmi_modb(hdmi, HDMI_VP_STUFF_PR_STUFFING_STUFFING_MODE,
HDMI_VP_STUFF_PR_STUFFING_MASK, HDMI_VP_STUFF);
@ -1267,12 +1361,20 @@ static void dw_hdmi_phy_sel_interface_control(struct dw_hdmi *hdmi, u8 enable)
}
void dw_hdmi_phy_reset(struct dw_hdmi *hdmi)
{
/* PHY reset. The reset signal is active high on Gen1 PHYs. */
hdmi_writeb(hdmi, 0, HDMI_MC_PHYRSTZ);
hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_PHYRSTZ, HDMI_MC_PHYRSTZ);
}
EXPORT_SYMBOL_GPL(dw_hdmi_phy_reset);
void dw_hdmi_phy_gen2_reset(struct dw_hdmi *hdmi)
{
/* PHY reset. The reset signal is active high on Gen2 PHYs. */
hdmi_writeb(hdmi, HDMI_MC_PHYRSTZ_PHYRSTZ, HDMI_MC_PHYRSTZ);
hdmi_writeb(hdmi, 0, HDMI_MC_PHYRSTZ);
}
EXPORT_SYMBOL_GPL(dw_hdmi_phy_reset);
EXPORT_SYMBOL_GPL(dw_hdmi_phy_gen2_reset);
void dw_hdmi_phy_i2c_set_addr(struct dw_hdmi *hdmi, u8 address)
{
@ -1425,7 +1527,7 @@ static int hdmi_phy_configure(struct dw_hdmi *hdmi)
if (phy->has_svsret)
dw_hdmi_phy_enable_svsret(hdmi, 1);
dw_hdmi_phy_reset(hdmi);
dw_hdmi_phy_gen2_reset(hdmi);
hdmi_writeb(hdmi, HDMI_MC_HEACPHY_RST_ASSERT, HDMI_MC_HEACPHY_RST);
@ -2615,6 +2717,7 @@ __dw_hdmi_probe(struct platform_device *pdev,
hdmi->plat_data = plat_data;
hdmi->dev = dev;
hdmi->sample_rate = 48000;
hdmi->channels = 2;
hdmi->disabled = true;
hdmi->rxsense = true;
hdmi->phy_mask = (u8)~(HDMI_PHY_HPD | HDMI_PHY_RX_SENSE);
@ -2830,6 +2933,24 @@ __dw_hdmi_probe(struct platform_device *pdev,
pdevinfo.size_data = sizeof(audio);
pdevinfo.dma_mask = DMA_BIT_MASK(32);
hdmi->audio = platform_device_register_full(&pdevinfo);
} else if (config3 & HDMI_CONFIG3_GPAUD) {
struct dw_hdmi_audio_data audio;
audio.phys = iores->start;
audio.base = hdmi->regs;
audio.irq = irq;
audio.hdmi = hdmi;
audio.eld = hdmi->connector.eld;
hdmi->enable_audio = dw_hdmi_gp_audio_enable;
hdmi->disable_audio = dw_hdmi_gp_audio_disable;
pdevinfo.name = "dw-hdmi-gp-audio";
pdevinfo.id = PLATFORM_DEVID_NONE;
pdevinfo.data = &audio;
pdevinfo.size_data = sizeof(audio);
pdevinfo.dma_mask = DMA_BIT_MASK(32);
hdmi->audio = platform_device_register_full(&pdevinfo);
}
if (config0 & HDMI_CONFIG0_CEC) {

View File

@ -158,6 +158,17 @@
#define HDMI_FC_SPDDEVICEINF 0x1062
#define HDMI_FC_AUDSCONF 0x1063
#define HDMI_FC_AUDSSTAT 0x1064
#define HDMI_FC_AUDSV 0x1065
#define HDMI_FC_AUDSU 0x1066
#define HDMI_FC_AUDSCHNL0 0x1067
#define HDMI_FC_AUDSCHNL1 0x1068
#define HDMI_FC_AUDSCHNL2 0x1069
#define HDMI_FC_AUDSCHNL3 0x106A
#define HDMI_FC_AUDSCHNL4 0x106B
#define HDMI_FC_AUDSCHNL5 0x106C
#define HDMI_FC_AUDSCHNL6 0x106D
#define HDMI_FC_AUDSCHNL7 0x106E
#define HDMI_FC_AUDSCHNL8 0x106F
#define HDMI_FC_DATACH0FILL 0x1070
#define HDMI_FC_DATACH1FILL 0x1071
#define HDMI_FC_DATACH2FILL 0x1072

View File

@ -9,7 +9,9 @@
#include <linux/mfd/syscon/imx6q-iomuxc-gpr.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <video/imx-ipu-v3.h>
@ -19,13 +21,23 @@
#include <drm/drm_encoder.h>
#include <drm/drm_of.h>
#include "imx8mp-hdmi-pavi.h"
#include "imx-drm.h"
/* GPR reg */
struct imx_hdmi_chip_data {
int reg_offset;
u32 mask_bits;
u32 shift_bit;
};
struct imx_hdmi {
struct device *dev;
struct drm_encoder encoder;
struct dw_hdmi *hdmi;
struct regmap *regmap;
const struct imx_hdmi_chip_data *chip_data;
struct phy *phy;
};
static inline struct imx_hdmi *enc_to_imx_hdmi(struct drm_encoder *e)
@ -91,7 +103,7 @@ static const struct dw_hdmi_curr_ctrl imx_cur_ctr[] = {
* PREEMP config 0.00
* TX/CK level 10
*/
static const struct dw_hdmi_phy_config imx_phy_config[] = {
static const struct dw_hdmi_phy_config imx6_phy_config[] = {
/*pixelclk symbol term vlev */
{ 216000000, 0x800d, 0x0005, 0x01ad},
{ ~0UL, 0x0000, 0x0000, 0x0000}
@ -100,6 +112,7 @@ static const struct dw_hdmi_phy_config imx_phy_config[] = {
static int dw_hdmi_imx_parse_dt(struct imx_hdmi *hdmi)
{
struct device_node *np = hdmi->dev->of_node;
int ret;
hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "gpr");
if (IS_ERR(hdmi->regmap)) {
@ -107,6 +120,14 @@ static int dw_hdmi_imx_parse_dt(struct imx_hdmi *hdmi)
return PTR_ERR(hdmi->regmap);
}
hdmi->phy = devm_phy_optional_get(hdmi->dev, "hdmi");
if (IS_ERR(hdmi->phy)) {
ret = PTR_ERR(hdmi->phy);
if (ret != -EPROBE_DEFER)
dev_err(hdmi->dev, "failed to get phy\n");
return ret;
}
return 0;
}
@ -119,9 +140,11 @@ static void dw_hdmi_imx_encoder_enable(struct drm_encoder *encoder)
struct imx_hdmi *hdmi = enc_to_imx_hdmi(encoder);
int mux = drm_of_encoder_active_port_id(hdmi->dev->of_node, encoder);
regmap_update_bits(hdmi->regmap, IOMUXC_GPR3,
IMX6Q_GPR3_HDMI_MUX_CTL_MASK,
mux << IMX6Q_GPR3_HDMI_MUX_CTL_SHIFT);
if (hdmi->chip_data->reg_offset < 0)
return;
regmap_update_bits(hdmi->regmap, hdmi->chip_data->reg_offset,
hdmi->chip_data->mask_bits, mux << hdmi->chip_data->shift_bit);
}
static int dw_hdmi_imx_atomic_check(struct drm_encoder *encoder,
@ -131,6 +154,7 @@ static int dw_hdmi_imx_atomic_check(struct drm_encoder *encoder,
struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
imx_crtc_state->bus_format = MEDIA_BUS_FMT_RGB888_1X24;
imx_crtc_state->bus_flags = DRM_BUS_FLAG_DE_HIGH;
imx_crtc_state->di_hsync_pin = 2;
imx_crtc_state->di_vsync_pin = 3;
@ -173,18 +197,158 @@ imx6dl_hdmi_mode_valid(struct drm_connector *con,
return MODE_OK;
}
static enum drm_mode_status
imx8mp_hdmi_mode_valid(struct drm_connector *con,
const struct drm_display_mode *mode)
{
if (mode->clock < 13500)
return MODE_CLOCK_LOW;
if (mode->clock > 297000)
return MODE_CLOCK_HIGH;
/* We don't support double-clocked and Interlaced modes */
if (mode->flags & DRM_MODE_FLAG_DBLCLK ||
mode->flags & DRM_MODE_FLAG_INTERLACE)
return MODE_BAD;
return MODE_OK;
}
struct imx_hdmi_chip_data imx6_chip_data = {
.reg_offset = IOMUXC_GPR3,
.mask_bits = IMX6Q_GPR3_HDMI_MUX_CTL_MASK,
.shift_bit = IMX6Q_GPR3_HDMI_MUX_CTL_SHIFT,
};
static struct dw_hdmi_plat_data imx6q_hdmi_drv_data = {
.mpll_cfg = imx_mpll_cfg,
.cur_ctr = imx_cur_ctr,
.phy_config = imx_phy_config,
.phy_config = imx6_phy_config,
.mode_valid = imx6q_hdmi_mode_valid,
.phy_data = &imx6_chip_data,
};
static struct dw_hdmi_plat_data imx6dl_hdmi_drv_data = {
.mpll_cfg = imx_mpll_cfg,
.cur_ctr = imx_cur_ctr,
.phy_config = imx_phy_config,
.phy_config = imx6_phy_config,
.mode_valid = imx6dl_hdmi_mode_valid,
.phy_data = &imx6_chip_data,
};
static int imx8mp_hdmi_phy_init(struct dw_hdmi *dw_hdmi, void *data,
struct drm_display_mode *mode)
{
struct imx_hdmi *hdmi = (struct imx_hdmi *)data;
int val;
dev_dbg(hdmi->dev, "%s\n", __func__);
dw_hdmi_phy_reset(dw_hdmi);
/* enable PVI */
imx8mp_hdmi_pvi_powerup();
imx8mp_hdmi_pvi_enable(mode);
/* HDMI PHY power up */
regmap_read(hdmi->regmap, 0x200, &val);
val &= ~0x8;
regmap_write(hdmi->regmap, 0x200, val);
if (!hdmi->phy)
return 0;
phy_power_on(hdmi->phy);
return 0;
}
static void imx8mp_hdmi_phy_disable(struct dw_hdmi *dw_hdmi, void *data)
{
struct imx_hdmi *hdmi = (struct imx_hdmi *)data;
int val;
dev_dbg(hdmi->dev, "%s\n", __func__);
if (!hdmi->phy)
return;
/* disable PVI */
imx8mp_hdmi_pvi_disable();
imx8mp_hdmi_pvi_powerdown();
/* TODO Power down HDMI PHY */
regmap_read(hdmi->regmap, 0x200, &val);
val |= 0x8;
regmap_write(hdmi->regmap, 0x200, val);
}
static int imx8mp_hdmimix_setup(struct imx_hdmi *hdmi)
{
int ret;
struct clk_bulk_data clocks[] = {
{ .id = "phy_int" },
{ .id = "prep_clk" },
{ .id = "skp_clk" },
{ .id = "sfr_clk" },
{ .id = "pix_clk" },
{ .id = "cec_clk" },
{ .id = "apb_clk" },
{ .id = "hpi_clk" },
{ .id = "fdcc_ref" },
{ .id = "pipe_clk" },
};
if (NULL == imx8mp_hdmi_pavi_init()) {
dev_err(hdmi->dev, "No pavi info found\n");
return -EPROBE_DEFER;
}
ret = device_reset(hdmi->dev);
if (ret == -EPROBE_DEFER)
return ret;
ret = devm_clk_bulk_get(hdmi->dev, ARRAY_SIZE(clocks), clocks);
if (ret < 0) {
dev_err(hdmi->dev, "No hdmimix bulk clk got\n");
return -EPROBE_DEFER;
}
return clk_bulk_prepare_enable(ARRAY_SIZE(clocks), clocks);
}
void imx8mp_hdmi_enable_audio(struct dw_hdmi *dw_hdmi, void *data, int channel)
{
imx8mp_hdmi_pai_powerup();
imx8mp_hdmi_pai_enable(channel);
}
void imx8mp_hdmi_disable_audio(struct dw_hdmi *dw_hdmi, void *data)
{
imx8mp_hdmi_pai_disable();
imx8mp_hdmi_pai_powerdown();
}
static const struct dw_hdmi_phy_ops imx8mp_hdmi_phy_ops = {
.init = imx8mp_hdmi_phy_init,
.disable = imx8mp_hdmi_phy_disable,
.read_hpd = dw_hdmi_phy_read_hpd,
.update_hpd = dw_hdmi_phy_update_hpd,
.setup_hpd = dw_hdmi_phy_setup_hpd,
.enable_audio = imx8mp_hdmi_enable_audio,
.disable_audio = imx8mp_hdmi_disable_audio,
};
struct imx_hdmi_chip_data imx8mp_chip_data = {
.reg_offset = -1,
};
static const struct dw_hdmi_plat_data imx8mp_hdmi_drv_data = {
.mode_valid = imx8mp_hdmi_mode_valid,
.phy_data = &imx8mp_chip_data,
.phy_ops = &imx8mp_hdmi_phy_ops,
.phy_name = "samsung_dw_hdmi_phy2",
.phy_force_vendor = true,
};
static const struct of_device_id dw_hdmi_imx_dt_ids[] = {
@ -193,6 +357,9 @@ static const struct of_device_id dw_hdmi_imx_dt_ids[] = {
}, {
.compatible = "fsl,imx6dl-hdmi",
.data = &imx6dl_hdmi_drv_data
}, {
.compatible = "fsl,imx8mp-hdmi",
.data = &imx8mp_hdmi_drv_data
},
{},
};
@ -202,7 +369,7 @@ static int dw_hdmi_imx_bind(struct device *dev, struct device *master,
void *data)
{
struct platform_device *pdev = to_platform_device(dev);
const struct dw_hdmi_plat_data *plat_data;
struct dw_hdmi_plat_data *plat_data;
const struct of_device_id *match;
struct drm_device *drm = data;
struct drm_encoder *encoder;
@ -217,9 +384,15 @@ static int dw_hdmi_imx_bind(struct device *dev, struct device *master,
return -ENOMEM;
match = of_match_node(dw_hdmi_imx_dt_ids, pdev->dev.of_node);
plat_data = match->data;
plat_data = devm_kmemdup(&pdev->dev, match->data,
sizeof(*plat_data), GFP_KERNEL);
if (!plat_data)
return -ENOMEM;
hdmi->dev = &pdev->dev;
encoder = &hdmi->encoder;
hdmi->chip_data = plat_data->phy_data;
plat_data->phy_data = hdmi;
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
/*
@ -241,6 +414,12 @@ static int dw_hdmi_imx_bind(struct device *dev, struct device *master,
platform_set_drvdata(pdev, hdmi);
if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx8mp-hdmi")) {
ret = imx8mp_hdmimix_setup(hdmi);
if (ret < 0)
return ret;
}
hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data);
/*

View File

@ -118,6 +118,8 @@ struct dw_hdmi_phy_ops {
void (*update_hpd)(struct dw_hdmi *hdmi, void *data,
bool force, bool disabled, bool rxsense);
void (*setup_hpd)(struct dw_hdmi *hdmi, void *data);
void (*enable_audio)(struct dw_hdmi *hdmi, void *data, int channel);
void (*disable_audio)(struct dw_hdmi *hdmi, void *data);
};
struct dw_hdmi_plat_data {
@ -166,9 +168,11 @@ void dw_hdmi_phy_i2c_set_addr(struct dw_hdmi *hdmi, u8 address);
void dw_hdmi_phy_i2c_write(struct dw_hdmi *hdmi, unsigned short data,
unsigned char addr);
void dw_hdmi_phy_reset(struct dw_hdmi *hdmi);
void dw_hdmi_phy_gen2_pddq(struct dw_hdmi *hdmi, u8 enable);
void dw_hdmi_phy_gen2_txpwron(struct dw_hdmi *hdmi, u8 enable);
void dw_hdmi_phy_reset(struct dw_hdmi *hdmi);
void dw_hdmi_phy_gen2_reset(struct dw_hdmi *hdmi);
enum drm_connector_status dw_hdmi_phy_read_hpd(struct dw_hdmi *hdmi,
void *data);