mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-09-06 13:09:58 +00:00

In some application scenarios, we hope to get the corresponding connector when the bridge's detect hook is invoked. In most cases, we can get the connector by drm_atomic_get_connector_for_encoder if the encoder attached to the bridge is enabled, however there will still be some scenarios where the detect hook of the bridge is called but the corresponding encoder has not been enabled yet. For instance, this occurs when the device is hot plug in for the first time. Since the call to bridge's detect is initiated by the connector, passing down the corresponding connector directly will make things simpler. Signed-off-by: Andy Yan <andy.yan@rock-chips.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Link: https://lore.kernel.org/r/20250703125027.311109-3-andyshrk@163.com [DB: added the chunk to the cdn-dp driver] Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
1119 lines
32 KiB
C
1119 lines
32 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) 2021-2022 Rockchip Electronics Co., Ltd.
|
|
* Copyright (c) 2024 Collabora Ltd.
|
|
*
|
|
* Author: Algea Cao <algea.cao@rock-chips.com>
|
|
* Author: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
|
|
*/
|
|
#include <linux/completion.h>
|
|
#include <linux/hdmi.h>
|
|
#include <linux/export.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/of.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
#include <drm/bridge/dw_hdmi_qp.h>
|
|
#include <drm/display/drm_hdmi_helper.h>
|
|
#include <drm/display/drm_hdmi_state_helper.h>
|
|
#include <drm/drm_atomic.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_bridge.h>
|
|
#include <drm/drm_connector.h>
|
|
#include <drm/drm_edid.h>
|
|
#include <drm/drm_modes.h>
|
|
|
|
#include <sound/hdmi-codec.h>
|
|
|
|
#include "dw-hdmi-qp.h"
|
|
|
|
#define DDC_CI_ADDR 0x37
|
|
#define DDC_SEGMENT_ADDR 0x30
|
|
|
|
#define HDMI14_MAX_TMDSCLK 340000000
|
|
|
|
#define SCRAMB_POLL_DELAY_MS 3000
|
|
|
|
/*
|
|
* Unless otherwise noted, entries in this table are 100% optimization.
|
|
* Values can be obtained from dw_hdmi_qp_compute_n() but that function is
|
|
* slow so we pre-compute values we expect to see.
|
|
*
|
|
* The values for TMDS 25175, 25200, 27000, 54000, 74250 and 148500 kHz are
|
|
* the recommended N values specified in the Audio chapter of the HDMI
|
|
* specification.
|
|
*/
|
|
static const struct dw_hdmi_audio_tmds_n {
|
|
unsigned long tmds;
|
|
unsigned int n_32k;
|
|
unsigned int n_44k1;
|
|
unsigned int n_48k;
|
|
} common_tmds_n_table[] = {
|
|
{ .tmds = 25175000, .n_32k = 4576, .n_44k1 = 7007, .n_48k = 6864, },
|
|
{ .tmds = 25200000, .n_32k = 4096, .n_44k1 = 6272, .n_48k = 6144, },
|
|
{ .tmds = 27000000, .n_32k = 4096, .n_44k1 = 6272, .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 = 6272, .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 = 11648, .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 = 11648, .n_44k1 = 6272, .n_48k = 6144, },
|
|
{ .tmds = 148500000, .n_32k = 4096, .n_44k1 = 6272, .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, },
|
|
};
|
|
|
|
/*
|
|
* These are the CTS values as recommended in the Audio chapter of the HDMI
|
|
* specification.
|
|
*/
|
|
static const struct dw_hdmi_audio_tmds_cts {
|
|
unsigned long tmds;
|
|
unsigned int cts_32k;
|
|
unsigned int cts_44k1;
|
|
unsigned int cts_48k;
|
|
} common_tmds_cts_table[] = {
|
|
{ .tmds = 25175000, .cts_32k = 28125, .cts_44k1 = 31250, .cts_48k = 28125, },
|
|
{ .tmds = 25200000, .cts_32k = 25200, .cts_44k1 = 28000, .cts_48k = 25200, },
|
|
{ .tmds = 27000000, .cts_32k = 27000, .cts_44k1 = 30000, .cts_48k = 27000, },
|
|
{ .tmds = 54000000, .cts_32k = 54000, .cts_44k1 = 60000, .cts_48k = 54000, },
|
|
{ .tmds = 74250000, .cts_32k = 74250, .cts_44k1 = 82500, .cts_48k = 74250, },
|
|
{ .tmds = 148500000, .cts_32k = 148500, .cts_44k1 = 165000, .cts_48k = 148500, },
|
|
|
|
/* End of table */
|
|
{ .tmds = 0, .cts_32k = 0, .cts_44k1 = 0, .cts_48k = 0, },
|
|
};
|
|
|
|
struct dw_hdmi_qp_i2c {
|
|
struct i2c_adapter adap;
|
|
|
|
struct mutex lock; /* used to serialize data transfers */
|
|
struct completion cmp;
|
|
u8 stat;
|
|
|
|
u8 slave_reg;
|
|
bool is_regaddr;
|
|
bool is_segment;
|
|
};
|
|
|
|
struct dw_hdmi_qp {
|
|
struct drm_bridge bridge;
|
|
|
|
struct device *dev;
|
|
struct dw_hdmi_qp_i2c *i2c;
|
|
|
|
struct {
|
|
const struct dw_hdmi_qp_phy_ops *ops;
|
|
void *data;
|
|
} phy;
|
|
|
|
struct regmap *regm;
|
|
|
|
unsigned long tmds_char_rate;
|
|
};
|
|
|
|
static void dw_hdmi_qp_write(struct dw_hdmi_qp *hdmi, unsigned int val,
|
|
int offset)
|
|
{
|
|
regmap_write(hdmi->regm, offset, val);
|
|
}
|
|
|
|
static unsigned int dw_hdmi_qp_read(struct dw_hdmi_qp *hdmi, int offset)
|
|
{
|
|
unsigned int val = 0;
|
|
|
|
regmap_read(hdmi->regm, offset, &val);
|
|
|
|
return val;
|
|
}
|
|
|
|
static void dw_hdmi_qp_mod(struct dw_hdmi_qp *hdmi, unsigned int data,
|
|
unsigned int mask, unsigned int reg)
|
|
{
|
|
regmap_update_bits(hdmi->regm, reg, mask, data);
|
|
}
|
|
|
|
static struct dw_hdmi_qp *dw_hdmi_qp_from_bridge(struct drm_bridge *bridge)
|
|
{
|
|
return container_of(bridge, struct dw_hdmi_qp, bridge);
|
|
}
|
|
|
|
static void dw_hdmi_qp_set_cts_n(struct dw_hdmi_qp *hdmi, unsigned int cts,
|
|
unsigned int n)
|
|
{
|
|
/* Set N */
|
|
dw_hdmi_qp_mod(hdmi, n, AUDPKT_ACR_N_VALUE, AUDPKT_ACR_CONTROL0);
|
|
|
|
/* Set CTS */
|
|
if (cts)
|
|
dw_hdmi_qp_mod(hdmi, AUDPKT_ACR_CTS_OVR_EN, AUDPKT_ACR_CTS_OVR_EN_MSK,
|
|
AUDPKT_ACR_CONTROL1);
|
|
else
|
|
dw_hdmi_qp_mod(hdmi, 0, AUDPKT_ACR_CTS_OVR_EN_MSK,
|
|
AUDPKT_ACR_CONTROL1);
|
|
|
|
dw_hdmi_qp_mod(hdmi, AUDPKT_ACR_CTS_OVR_VAL(cts), AUDPKT_ACR_CTS_OVR_VAL_MSK,
|
|
AUDPKT_ACR_CONTROL1);
|
|
}
|
|
|
|
static int dw_hdmi_qp_match_tmds_n_table(struct dw_hdmi_qp *hdmi,
|
|
unsigned long pixel_clk,
|
|
unsigned long freq)
|
|
{
|
|
const struct dw_hdmi_audio_tmds_n *tmds_n = NULL;
|
|
int i;
|
|
|
|
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)
|
|
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 u32 dw_hdmi_qp_audio_math_diff(unsigned int freq, unsigned int n,
|
|
unsigned int pixel_clk)
|
|
{
|
|
u64 cts = mul_u32_u32(pixel_clk, n);
|
|
|
|
return do_div(cts, 128 * freq);
|
|
}
|
|
|
|
static unsigned int dw_hdmi_qp_compute_n(struct dw_hdmi_qp *hdmi,
|
|
unsigned long pixel_clk,
|
|
unsigned long freq)
|
|
{
|
|
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 (dw_hdmi_qp_audio_math_diff(freq, ideal_n, pixel_clk) == 0)
|
|
return ideal_n;
|
|
|
|
for (n = min_n; n <= max_n; n++) {
|
|
u64 diff = dw_hdmi_qp_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 dw_hdmi_qp_find_n(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk,
|
|
unsigned long sample_rate)
|
|
{
|
|
int n = dw_hdmi_qp_match_tmds_n_table(hdmi, pixel_clk, sample_rate);
|
|
|
|
if (n > 0)
|
|
return n;
|
|
|
|
dev_warn(hdmi->dev, "Rate %lu missing; compute N dynamically\n",
|
|
pixel_clk);
|
|
|
|
return dw_hdmi_qp_compute_n(hdmi, pixel_clk, sample_rate);
|
|
}
|
|
|
|
static unsigned int dw_hdmi_qp_find_cts(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk,
|
|
unsigned long sample_rate)
|
|
{
|
|
const struct dw_hdmi_audio_tmds_cts *tmds_cts = NULL;
|
|
int i;
|
|
|
|
for (i = 0; common_tmds_cts_table[i].tmds != 0; i++) {
|
|
if (pixel_clk == common_tmds_cts_table[i].tmds) {
|
|
tmds_cts = &common_tmds_cts_table[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!tmds_cts)
|
|
return 0;
|
|
|
|
switch (sample_rate) {
|
|
case 32000:
|
|
return tmds_cts->cts_32k;
|
|
case 44100:
|
|
case 88200:
|
|
case 176400:
|
|
return tmds_cts->cts_44k1;
|
|
case 48000:
|
|
case 96000:
|
|
case 192000:
|
|
return tmds_cts->cts_48k;
|
|
default:
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
|
|
static void dw_hdmi_qp_set_audio_interface(struct dw_hdmi_qp *hdmi,
|
|
struct hdmi_codec_daifmt *fmt,
|
|
struct hdmi_codec_params *hparms)
|
|
{
|
|
u32 conf0 = 0;
|
|
|
|
/* Reset the audio data path of the AVP */
|
|
dw_hdmi_qp_write(hdmi, AVP_DATAPATH_PACKET_AUDIO_SWINIT_P, GLOBAL_SWRESET_REQUEST);
|
|
|
|
/* Disable AUDS, ACR, AUDI */
|
|
dw_hdmi_qp_mod(hdmi, 0,
|
|
PKTSCHED_ACR_TX_EN | PKTSCHED_AUDS_TX_EN | PKTSCHED_AUDI_TX_EN,
|
|
PKTSCHED_PKT_EN);
|
|
|
|
/* Clear the audio FIFO */
|
|
dw_hdmi_qp_write(hdmi, AUDIO_FIFO_CLR_P, AUDIO_INTERFACE_CONTROL0);
|
|
|
|
/* Select I2S interface as the audio source */
|
|
dw_hdmi_qp_mod(hdmi, AUD_IF_I2S, AUD_IF_SEL_MSK, AUDIO_INTERFACE_CONFIG0);
|
|
|
|
/* Enable the active i2s lanes */
|
|
switch (hparms->channels) {
|
|
case 7 ... 8:
|
|
conf0 |= I2S_LINES_EN(3);
|
|
fallthrough;
|
|
case 5 ... 6:
|
|
conf0 |= I2S_LINES_EN(2);
|
|
fallthrough;
|
|
case 3 ... 4:
|
|
conf0 |= I2S_LINES_EN(1);
|
|
fallthrough;
|
|
default:
|
|
conf0 |= I2S_LINES_EN(0);
|
|
break;
|
|
}
|
|
|
|
dw_hdmi_qp_mod(hdmi, conf0, I2S_LINES_EN_MSK, AUDIO_INTERFACE_CONFIG0);
|
|
|
|
/*
|
|
* Enable bpcuv generated internally for L-PCM, or received
|
|
* from stream for NLPCM/HBR.
|
|
*/
|
|
switch (fmt->bit_fmt) {
|
|
case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
|
|
conf0 = (hparms->channels == 8) ? AUD_HBR : AUD_ASP;
|
|
conf0 |= I2S_BPCUV_RCV_EN;
|
|
break;
|
|
default:
|
|
conf0 = AUD_ASP | I2S_BPCUV_RCV_DIS;
|
|
break;
|
|
}
|
|
|
|
dw_hdmi_qp_mod(hdmi, conf0, I2S_BPCUV_RCV_MSK | AUD_FORMAT_MSK,
|
|
AUDIO_INTERFACE_CONFIG0);
|
|
|
|
/* Enable audio FIFO auto clear when overflow */
|
|
dw_hdmi_qp_mod(hdmi, AUD_FIFO_INIT_ON_OVF_EN, AUD_FIFO_INIT_ON_OVF_MSK,
|
|
AUDIO_INTERFACE_CONFIG0);
|
|
}
|
|
|
|
/*
|
|
* When transmitting IEC60958 linear PCM audio, these registers allow to
|
|
* configure the channel status information of all the channel status
|
|
* bits in the IEC60958 frame. For the moment this configuration is only
|
|
* used when the I2S audio interface, General Purpose Audio (GPA),
|
|
* or AHB audio DMA (AHBAUDDMA) interface is active
|
|
* (for S/PDIF interface this information comes from the stream).
|
|
*/
|
|
static void dw_hdmi_qp_set_channel_status(struct dw_hdmi_qp *hdmi,
|
|
u8 *channel_status, bool ref2stream)
|
|
{
|
|
/*
|
|
* AUDPKT_CHSTATUS_OVR0: { RSV, RSV, CS1, CS0 }
|
|
* AUDPKT_CHSTATUS_OVR1: { CS6, CS5, CS4, CS3 }
|
|
*
|
|
* | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|
|
* CS0: | Mode | d | c | b | a |
|
|
* CS1: | Category Code |
|
|
* CS2: | Channel Number | Source Number |
|
|
* CS3: | Clock Accuracy | Sample Freq |
|
|
* CS4: | Ori Sample Freq | Word Length |
|
|
* CS5: | | CGMS-A |
|
|
* CS6~CS23: Reserved
|
|
*
|
|
* a: use of channel status block
|
|
* b: linear PCM identification: 0 for lpcm, 1 for nlpcm
|
|
* c: copyright information
|
|
* d: additional format information
|
|
*/
|
|
|
|
if (ref2stream)
|
|
channel_status[0] |= IEC958_AES0_NONAUDIO;
|
|
|
|
if ((dw_hdmi_qp_read(hdmi, AUDIO_INTERFACE_CONFIG0) & GENMASK(25, 24)) == AUD_HBR) {
|
|
/* fixup cs for HBR */
|
|
channel_status[3] = (channel_status[3] & 0xf0) | IEC958_AES3_CON_FS_768000;
|
|
channel_status[4] = (channel_status[4] & 0x0f) | IEC958_AES4_CON_ORIGFS_NOTID;
|
|
}
|
|
|
|
dw_hdmi_qp_write(hdmi, channel_status[0] | (channel_status[1] << 8),
|
|
AUDPKT_CHSTATUS_OVR0);
|
|
|
|
regmap_bulk_write(hdmi->regm, AUDPKT_CHSTATUS_OVR1, &channel_status[3], 1);
|
|
|
|
if (ref2stream)
|
|
dw_hdmi_qp_mod(hdmi, 0,
|
|
AUDPKT_PBIT_FORCE_EN_MASK | AUDPKT_CHSTATUS_OVR_EN_MASK,
|
|
AUDPKT_CONTROL0);
|
|
else
|
|
dw_hdmi_qp_mod(hdmi, AUDPKT_PBIT_FORCE_EN | AUDPKT_CHSTATUS_OVR_EN,
|
|
AUDPKT_PBIT_FORCE_EN_MASK | AUDPKT_CHSTATUS_OVR_EN_MASK,
|
|
AUDPKT_CONTROL0);
|
|
}
|
|
|
|
static void dw_hdmi_qp_set_sample_rate(struct dw_hdmi_qp *hdmi, unsigned long long tmds_char_rate,
|
|
unsigned int sample_rate)
|
|
{
|
|
unsigned int n, cts;
|
|
|
|
n = dw_hdmi_qp_find_n(hdmi, tmds_char_rate, sample_rate);
|
|
cts = dw_hdmi_qp_find_cts(hdmi, tmds_char_rate, sample_rate);
|
|
|
|
dw_hdmi_qp_set_cts_n(hdmi, cts, n);
|
|
}
|
|
|
|
static int dw_hdmi_qp_audio_enable(struct drm_bridge *bridge,
|
|
struct drm_connector *connector)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge);
|
|
|
|
if (hdmi->tmds_char_rate)
|
|
dw_hdmi_qp_mod(hdmi, 0, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dw_hdmi_qp_audio_prepare(struct drm_bridge *bridge,
|
|
struct drm_connector *connector,
|
|
struct hdmi_codec_daifmt *fmt,
|
|
struct hdmi_codec_params *hparms)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge);
|
|
bool ref2stream = false;
|
|
|
|
if (!hdmi->tmds_char_rate)
|
|
return -ENODEV;
|
|
|
|
if (fmt->bit_clk_provider | fmt->frame_clk_provider) {
|
|
dev_err(hdmi->dev, "unsupported clock settings\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (fmt->bit_fmt == SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE)
|
|
ref2stream = true;
|
|
|
|
dw_hdmi_qp_set_audio_interface(hdmi, fmt, hparms);
|
|
dw_hdmi_qp_set_sample_rate(hdmi, hdmi->tmds_char_rate, hparms->sample_rate);
|
|
dw_hdmi_qp_set_channel_status(hdmi, hparms->iec.status, ref2stream);
|
|
drm_atomic_helper_connector_hdmi_update_audio_infoframe(connector, &hparms->cea);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dw_hdmi_qp_audio_disable_regs(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
/*
|
|
* Keep ACR, AUDI, AUDS packet always on to make SINK device
|
|
* active for better compatibility and user experience.
|
|
*
|
|
* This also fix POP sound on some SINK devices which wakeup
|
|
* from suspend to active.
|
|
*/
|
|
dw_hdmi_qp_mod(hdmi, I2S_BPCUV_RCV_DIS, I2S_BPCUV_RCV_MSK,
|
|
AUDIO_INTERFACE_CONFIG0);
|
|
dw_hdmi_qp_mod(hdmi, AUDPKT_PBIT_FORCE_EN | AUDPKT_CHSTATUS_OVR_EN,
|
|
AUDPKT_PBIT_FORCE_EN_MASK | AUDPKT_CHSTATUS_OVR_EN_MASK,
|
|
AUDPKT_CONTROL0);
|
|
|
|
dw_hdmi_qp_mod(hdmi, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE,
|
|
AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE);
|
|
}
|
|
|
|
static void dw_hdmi_qp_audio_disable(struct drm_bridge *bridge,
|
|
struct drm_connector *connector)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = dw_hdmi_qp_from_bridge(bridge);
|
|
|
|
drm_atomic_helper_connector_hdmi_clear_audio_infoframe(connector);
|
|
|
|
if (hdmi->tmds_char_rate)
|
|
dw_hdmi_qp_audio_disable_regs(hdmi);
|
|
}
|
|
|
|
static int dw_hdmi_qp_i2c_read(struct dw_hdmi_qp *hdmi,
|
|
unsigned char *buf, unsigned int length)
|
|
{
|
|
struct dw_hdmi_qp_i2c *i2c = hdmi->i2c;
|
|
int stat;
|
|
|
|
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);
|
|
|
|
dw_hdmi_qp_mod(hdmi, i2c->slave_reg++ << 12, I2CM_ADDR,
|
|
I2CM_INTERFACE_CONTROL0);
|
|
|
|
if (i2c->is_segment)
|
|
dw_hdmi_qp_mod(hdmi, I2CM_EXT_READ, I2CM_WR_MASK,
|
|
I2CM_INTERFACE_CONTROL0);
|
|
else
|
|
dw_hdmi_qp_mod(hdmi, I2CM_FM_READ, I2CM_WR_MASK,
|
|
I2CM_INTERFACE_CONTROL0);
|
|
|
|
stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
|
|
if (!stat) {
|
|
dev_err(hdmi->dev, "i2c read timed out\n");
|
|
dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/* Check for error condition on the bus */
|
|
if (i2c->stat & I2CM_NACK_RCVD_IRQ) {
|
|
dev_err(hdmi->dev, "i2c read error\n");
|
|
dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0);
|
|
return -EIO;
|
|
}
|
|
|
|
*buf++ = dw_hdmi_qp_read(hdmi, I2CM_INTERFACE_RDDATA_0_3) & 0xff;
|
|
dw_hdmi_qp_mod(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0);
|
|
}
|
|
|
|
i2c->is_segment = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dw_hdmi_qp_i2c_write(struct dw_hdmi_qp *hdmi,
|
|
unsigned char *buf, unsigned int length)
|
|
{
|
|
struct dw_hdmi_qp_i2c *i2c = hdmi->i2c;
|
|
int stat;
|
|
|
|
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);
|
|
|
|
dw_hdmi_qp_write(hdmi, *buf++, I2CM_INTERFACE_WRDATA_0_3);
|
|
dw_hdmi_qp_mod(hdmi, i2c->slave_reg++ << 12, I2CM_ADDR,
|
|
I2CM_INTERFACE_CONTROL0);
|
|
dw_hdmi_qp_mod(hdmi, I2CM_FM_WRITE, I2CM_WR_MASK,
|
|
I2CM_INTERFACE_CONTROL0);
|
|
|
|
stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
|
|
if (!stat) {
|
|
dev_err(hdmi->dev, "i2c write time out!\n");
|
|
dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/* Check for error condition on the bus */
|
|
if (i2c->stat & I2CM_NACK_RCVD_IRQ) {
|
|
dev_err(hdmi->dev, "i2c write nack!\n");
|
|
dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0);
|
|
return -EIO;
|
|
}
|
|
|
|
dw_hdmi_qp_mod(hdmi, 0, I2CM_WR_MASK, I2CM_INTERFACE_CONTROL0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dw_hdmi_qp_i2c_xfer(struct i2c_adapter *adap,
|
|
struct i2c_msg *msgs, int num)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = i2c_get_adapdata(adap);
|
|
struct dw_hdmi_qp_i2c *i2c = hdmi->i2c;
|
|
u8 addr = msgs[0].addr;
|
|
int i, ret = 0;
|
|
|
|
if (addr == DDC_CI_ADDR)
|
|
/*
|
|
* The internal I2C controller does not support the multi-byte
|
|
* read and write operations needed for DDC/CI.
|
|
* FIXME: Blacklist the DDC/CI address until we filter out
|
|
* unsupported I2C operations.
|
|
*/
|
|
return -EOPNOTSUPP;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
if (msgs[i].len == 0) {
|
|
dev_err(hdmi->dev,
|
|
"unsupported transfer %d/%d, no data\n",
|
|
i + 1, num);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
guard(mutex)(&i2c->lock);
|
|
|
|
/* Unmute DONE and ERROR interrupts */
|
|
dw_hdmi_qp_mod(hdmi, I2CM_NACK_RCVD_MASK_N | I2CM_OP_DONE_MASK_N,
|
|
I2CM_NACK_RCVD_MASK_N | I2CM_OP_DONE_MASK_N,
|
|
MAINUNIT_1_INT_MASK_N);
|
|
|
|
/* Set slave device address taken from the first I2C message */
|
|
if (addr == DDC_SEGMENT_ADDR && msgs[0].len == 1)
|
|
addr = DDC_ADDR;
|
|
|
|
dw_hdmi_qp_mod(hdmi, addr << 5, I2CM_SLVADDR, I2CM_INTERFACE_CONTROL0);
|
|
|
|
/* 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++) {
|
|
if (msgs[i].addr == DDC_SEGMENT_ADDR && msgs[i].len == 1) {
|
|
i2c->is_segment = true;
|
|
dw_hdmi_qp_mod(hdmi, DDC_SEGMENT_ADDR, I2CM_SEG_ADDR,
|
|
I2CM_INTERFACE_CONTROL1);
|
|
dw_hdmi_qp_mod(hdmi, *msgs[i].buf << 7, I2CM_SEG_PTR,
|
|
I2CM_INTERFACE_CONTROL1);
|
|
} else {
|
|
if (msgs[i].flags & I2C_M_RD)
|
|
ret = dw_hdmi_qp_i2c_read(hdmi, msgs[i].buf,
|
|
msgs[i].len);
|
|
else
|
|
ret = dw_hdmi_qp_i2c_write(hdmi, msgs[i].buf,
|
|
msgs[i].len);
|
|
}
|
|
if (ret < 0)
|
|
break;
|
|
}
|
|
|
|
if (!ret)
|
|
ret = num;
|
|
|
|
/* Mute DONE and ERROR interrupts */
|
|
dw_hdmi_qp_mod(hdmi, 0, I2CM_OP_DONE_MASK_N | I2CM_NACK_RCVD_MASK_N,
|
|
MAINUNIT_1_INT_MASK_N);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static u32 dw_hdmi_qp_i2c_func(struct i2c_adapter *adapter)
|
|
{
|
|
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
|
|
}
|
|
|
|
static const struct i2c_algorithm dw_hdmi_qp_algorithm = {
|
|
.master_xfer = dw_hdmi_qp_i2c_xfer,
|
|
.functionality = dw_hdmi_qp_i2c_func,
|
|
};
|
|
|
|
static struct i2c_adapter *dw_hdmi_qp_i2c_adapter(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
struct dw_hdmi_qp_i2c *i2c;
|
|
struct i2c_adapter *adap;
|
|
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->owner = THIS_MODULE;
|
|
adap->dev.parent = hdmi->dev;
|
|
adap->algo = &dw_hdmi_qp_algorithm;
|
|
strscpy(adap->name, "DesignWare HDMI QP", sizeof(adap->name));
|
|
|
|
i2c_set_adapdata(adap, hdmi);
|
|
|
|
ret = devm_i2c_add_adapter(hdmi->dev, 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 int dw_hdmi_qp_config_avi_infoframe(struct dw_hdmi_qp *hdmi,
|
|
const u8 *buffer, size_t len)
|
|
{
|
|
u32 val, i, j;
|
|
|
|
if (len != HDMI_INFOFRAME_SIZE(AVI)) {
|
|
dev_err(hdmi->dev, "failed to configure avi infoframe\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* DW HDMI QP IP uses a different byte format from standard AVI info
|
|
* frames, though generally the bits are in the correct bytes.
|
|
*/
|
|
val = buffer[1] << 8 | buffer[2] << 16;
|
|
dw_hdmi_qp_write(hdmi, val, PKT_AVI_CONTENTS0);
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
for (j = 0; j < 4; j++) {
|
|
if (i * 4 + j >= 14)
|
|
break;
|
|
if (!j)
|
|
val = buffer[i * 4 + j + 3];
|
|
val |= buffer[i * 4 + j + 3] << (8 * j);
|
|
}
|
|
|
|
dw_hdmi_qp_write(hdmi, val, PKT_AVI_CONTENTS1 + i * 4);
|
|
}
|
|
|
|
dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_AVI_FIELDRATE, PKTSCHED_PKT_CONFIG1);
|
|
|
|
dw_hdmi_qp_mod(hdmi, PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN,
|
|
PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN, PKTSCHED_PKT_EN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dw_hdmi_qp_config_drm_infoframe(struct dw_hdmi_qp *hdmi,
|
|
const u8 *buffer, size_t len)
|
|
{
|
|
u32 val, i;
|
|
|
|
if (len != HDMI_INFOFRAME_SIZE(DRM)) {
|
|
dev_err(hdmi->dev, "failed to configure drm infoframe\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_DRMI_TX_EN, PKTSCHED_PKT_EN);
|
|
|
|
val = buffer[1] << 8 | buffer[2] << 16;
|
|
dw_hdmi_qp_write(hdmi, val, PKT_DRMI_CONTENTS0);
|
|
|
|
for (i = 0; i <= buffer[2]; i++) {
|
|
if (i % 4 == 0)
|
|
val = buffer[3 + i];
|
|
val |= buffer[3 + i] << ((i % 4) * 8);
|
|
|
|
if ((i % 4 == 3) || i == buffer[2])
|
|
dw_hdmi_qp_write(hdmi, val,
|
|
PKT_DRMI_CONTENTS1 + ((i / 4) * 4));
|
|
}
|
|
|
|
dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_DRMI_FIELDRATE, PKTSCHED_PKT_CONFIG1);
|
|
dw_hdmi_qp_mod(hdmi, PKTSCHED_DRMI_TX_EN, PKTSCHED_DRMI_TX_EN,
|
|
PKTSCHED_PKT_EN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Static values documented in the TRM
|
|
* Different values are only used for debug purposes
|
|
*/
|
|
#define DW_HDMI_QP_AUDIO_INFOFRAME_HB1 0x1
|
|
#define DW_HDMI_QP_AUDIO_INFOFRAME_HB2 0xa
|
|
|
|
static int dw_hdmi_qp_config_audio_infoframe(struct dw_hdmi_qp *hdmi,
|
|
const u8 *buffer, size_t len)
|
|
{
|
|
/*
|
|
* AUDI_CONTENTS0: { RSV, HB2, HB1, RSV }
|
|
* AUDI_CONTENTS1: { PB3, PB2, PB1, PB0 }
|
|
* AUDI_CONTENTS2: { PB7, PB6, PB5, PB4 }
|
|
*
|
|
* PB0: CheckSum
|
|
* PB1: | CT3 | CT2 | CT1 | CT0 | F13 | CC2 | CC1 | CC0 |
|
|
* PB2: | F27 | F26 | F25 | SF2 | SF1 | SF0 | SS1 | SS0 |
|
|
* PB3: | F37 | F36 | F35 | F34 | F33 | F32 | F31 | F30 |
|
|
* PB4: | CA7 | CA6 | CA5 | CA4 | CA3 | CA2 | CA1 | CA0 |
|
|
* PB5: | DM_INH | LSV3 | LSV2 | LSV1 | LSV0 | F52 | F51 | F50 |
|
|
* PB6~PB10: Reserved
|
|
*
|
|
* AUDI_CONTENTS0 default value defined by HDMI specification,
|
|
* and shall only be changed for debug purposes.
|
|
*/
|
|
u32 header_bytes = (DW_HDMI_QP_AUDIO_INFOFRAME_HB1 << 8) |
|
|
(DW_HDMI_QP_AUDIO_INFOFRAME_HB2 << 16);
|
|
|
|
regmap_bulk_write(hdmi->regm, PKT_AUDI_CONTENTS0, &header_bytes, 1);
|
|
regmap_bulk_write(hdmi->regm, PKT_AUDI_CONTENTS1, &buffer[3], 1);
|
|
regmap_bulk_write(hdmi->regm, PKT_AUDI_CONTENTS2, &buffer[4], 1);
|
|
|
|
/* Enable ACR, AUDI, AMD */
|
|
dw_hdmi_qp_mod(hdmi,
|
|
PKTSCHED_ACR_TX_EN | PKTSCHED_AUDI_TX_EN | PKTSCHED_AMD_TX_EN,
|
|
PKTSCHED_ACR_TX_EN | PKTSCHED_AUDI_TX_EN | PKTSCHED_AMD_TX_EN,
|
|
PKTSCHED_PKT_EN);
|
|
|
|
/* Enable AUDS */
|
|
dw_hdmi_qp_mod(hdmi, PKTSCHED_AUDS_TX_EN, PKTSCHED_AUDS_TX_EN, PKTSCHED_PKT_EN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dw_hdmi_qp_bridge_atomic_enable(struct drm_bridge *bridge,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = bridge->driver_private;
|
|
struct drm_connector_state *conn_state;
|
|
struct drm_connector *connector;
|
|
unsigned int op_mode;
|
|
|
|
connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
|
|
if (WARN_ON(!connector))
|
|
return;
|
|
|
|
conn_state = drm_atomic_get_new_connector_state(state, connector);
|
|
if (WARN_ON(!conn_state))
|
|
return;
|
|
|
|
if (connector->display_info.is_hdmi) {
|
|
dev_dbg(hdmi->dev, "%s mode=HDMI rate=%llu\n",
|
|
__func__, conn_state->hdmi.tmds_char_rate);
|
|
op_mode = 0;
|
|
hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate;
|
|
} else {
|
|
dev_dbg(hdmi->dev, "%s mode=DVI\n", __func__);
|
|
op_mode = OPMODE_DVI;
|
|
}
|
|
|
|
hdmi->phy.ops->init(hdmi, hdmi->phy.data);
|
|
|
|
dw_hdmi_qp_mod(hdmi, HDCP2_BYPASS, HDCP2_BYPASS, HDCP2LOGIC_CONFIG0);
|
|
dw_hdmi_qp_mod(hdmi, op_mode, OPMODE_DVI, LINK_CONFIG0);
|
|
|
|
drm_atomic_helper_connector_hdmi_update_infoframes(connector, state);
|
|
}
|
|
|
|
static void dw_hdmi_qp_bridge_atomic_disable(struct drm_bridge *bridge,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = bridge->driver_private;
|
|
|
|
hdmi->tmds_char_rate = 0;
|
|
|
|
hdmi->phy.ops->disable(hdmi, hdmi->phy.data);
|
|
}
|
|
|
|
static enum drm_connector_status
|
|
dw_hdmi_qp_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = bridge->driver_private;
|
|
|
|
return hdmi->phy.ops->read_hpd(hdmi, hdmi->phy.data);
|
|
}
|
|
|
|
static const struct drm_edid *
|
|
dw_hdmi_qp_bridge_edid_read(struct drm_bridge *bridge,
|
|
struct drm_connector *connector)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = bridge->driver_private;
|
|
const struct drm_edid *drm_edid;
|
|
|
|
drm_edid = drm_edid_read_ddc(connector, bridge->ddc);
|
|
if (!drm_edid)
|
|
dev_dbg(hdmi->dev, "failed to get edid\n");
|
|
|
|
return drm_edid;
|
|
}
|
|
|
|
static enum drm_mode_status
|
|
dw_hdmi_qp_bridge_tmds_char_rate_valid(const struct drm_bridge *bridge,
|
|
const struct drm_display_mode *mode,
|
|
unsigned long long rate)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = bridge->driver_private;
|
|
|
|
if (rate > HDMI14_MAX_TMDSCLK) {
|
|
dev_dbg(hdmi->dev, "Unsupported TMDS char rate: %lld\n", rate);
|
|
return MODE_CLOCK_HIGH;
|
|
}
|
|
|
|
return MODE_OK;
|
|
}
|
|
|
|
static int dw_hdmi_qp_bridge_clear_infoframe(struct drm_bridge *bridge,
|
|
enum hdmi_infoframe_type type)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = bridge->driver_private;
|
|
|
|
switch (type) {
|
|
case HDMI_INFOFRAME_TYPE_AVI:
|
|
dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_AVI_TX_EN | PKTSCHED_GCP_TX_EN,
|
|
PKTSCHED_PKT_EN);
|
|
break;
|
|
|
|
case HDMI_INFOFRAME_TYPE_DRM:
|
|
dw_hdmi_qp_mod(hdmi, 0, PKTSCHED_DRMI_TX_EN, PKTSCHED_PKT_EN);
|
|
break;
|
|
|
|
case HDMI_INFOFRAME_TYPE_AUDIO:
|
|
dw_hdmi_qp_mod(hdmi, 0,
|
|
PKTSCHED_ACR_TX_EN |
|
|
PKTSCHED_AUDS_TX_EN |
|
|
PKTSCHED_AUDI_TX_EN,
|
|
PKTSCHED_PKT_EN);
|
|
break;
|
|
default:
|
|
dev_dbg(hdmi->dev, "Unsupported infoframe type %x\n", type);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dw_hdmi_qp_bridge_write_infoframe(struct drm_bridge *bridge,
|
|
enum hdmi_infoframe_type type,
|
|
const u8 *buffer, size_t len)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = bridge->driver_private;
|
|
|
|
dw_hdmi_qp_bridge_clear_infoframe(bridge, type);
|
|
|
|
switch (type) {
|
|
case HDMI_INFOFRAME_TYPE_AVI:
|
|
return dw_hdmi_qp_config_avi_infoframe(hdmi, buffer, len);
|
|
|
|
case HDMI_INFOFRAME_TYPE_DRM:
|
|
return dw_hdmi_qp_config_drm_infoframe(hdmi, buffer, len);
|
|
|
|
case HDMI_INFOFRAME_TYPE_AUDIO:
|
|
return dw_hdmi_qp_config_audio_infoframe(hdmi, buffer, len);
|
|
|
|
default:
|
|
dev_dbg(hdmi->dev, "Unsupported infoframe type %x\n", type);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = {
|
|
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
|
|
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
|
|
.atomic_reset = drm_atomic_helper_bridge_reset,
|
|
.atomic_enable = dw_hdmi_qp_bridge_atomic_enable,
|
|
.atomic_disable = dw_hdmi_qp_bridge_atomic_disable,
|
|
.detect = dw_hdmi_qp_bridge_detect,
|
|
.edid_read = dw_hdmi_qp_bridge_edid_read,
|
|
.hdmi_tmds_char_rate_valid = dw_hdmi_qp_bridge_tmds_char_rate_valid,
|
|
.hdmi_clear_infoframe = dw_hdmi_qp_bridge_clear_infoframe,
|
|
.hdmi_write_infoframe = dw_hdmi_qp_bridge_write_infoframe,
|
|
.hdmi_audio_startup = dw_hdmi_qp_audio_enable,
|
|
.hdmi_audio_shutdown = dw_hdmi_qp_audio_disable,
|
|
.hdmi_audio_prepare = dw_hdmi_qp_audio_prepare,
|
|
};
|
|
|
|
static irqreturn_t dw_hdmi_qp_main_hardirq(int irq, void *dev_id)
|
|
{
|
|
struct dw_hdmi_qp *hdmi = dev_id;
|
|
struct dw_hdmi_qp_i2c *i2c = hdmi->i2c;
|
|
u32 stat;
|
|
|
|
stat = dw_hdmi_qp_read(hdmi, MAINUNIT_1_INT_STATUS);
|
|
|
|
i2c->stat = stat & (I2CM_OP_DONE_IRQ | I2CM_READ_REQUEST_IRQ |
|
|
I2CM_NACK_RCVD_IRQ);
|
|
|
|
if (i2c->stat) {
|
|
dw_hdmi_qp_write(hdmi, i2c->stat, MAINUNIT_1_INT_CLEAR);
|
|
complete(&i2c->cmp);
|
|
}
|
|
|
|
if (stat)
|
|
return IRQ_HANDLED;
|
|
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
static const struct regmap_config dw_hdmi_qp_regmap_config = {
|
|
.reg_bits = 32,
|
|
.val_bits = 32,
|
|
.reg_stride = 4,
|
|
.max_register = EARCRX_1_INT_FORCE,
|
|
};
|
|
|
|
static void dw_hdmi_qp_init_hw(struct dw_hdmi_qp *hdmi)
|
|
{
|
|
dw_hdmi_qp_write(hdmi, 0, MAINUNIT_0_INT_MASK_N);
|
|
dw_hdmi_qp_write(hdmi, 0, MAINUNIT_1_INT_MASK_N);
|
|
dw_hdmi_qp_write(hdmi, 428571429, TIMER_BASE_CONFIG0);
|
|
|
|
/* Software reset */
|
|
dw_hdmi_qp_write(hdmi, 0x01, I2CM_CONTROL0);
|
|
|
|
dw_hdmi_qp_write(hdmi, 0x085c085c, I2CM_FM_SCL_CONFIG0);
|
|
|
|
dw_hdmi_qp_mod(hdmi, 0, I2CM_FM_EN, I2CM_INTERFACE_CONTROL0);
|
|
|
|
/* Clear DONE and ERROR interrupts */
|
|
dw_hdmi_qp_write(hdmi, I2CM_OP_DONE_CLEAR | I2CM_NACK_RCVD_CLEAR,
|
|
MAINUNIT_1_INT_CLEAR);
|
|
|
|
if (hdmi->phy.ops->setup_hpd)
|
|
hdmi->phy.ops->setup_hpd(hdmi, hdmi->phy.data);
|
|
}
|
|
|
|
struct dw_hdmi_qp *dw_hdmi_qp_bind(struct platform_device *pdev,
|
|
struct drm_encoder *encoder,
|
|
const struct dw_hdmi_qp_plat_data *plat_data)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct dw_hdmi_qp *hdmi;
|
|
void __iomem *regs;
|
|
int ret;
|
|
|
|
if (!plat_data->phy_ops || !plat_data->phy_ops->init ||
|
|
!plat_data->phy_ops->disable || !plat_data->phy_ops->read_hpd) {
|
|
dev_err(dev, "Missing platform PHY ops\n");
|
|
return ERR_PTR(-ENODEV);
|
|
}
|
|
|
|
hdmi = devm_drm_bridge_alloc(dev, struct dw_hdmi_qp, bridge,
|
|
&dw_hdmi_qp_bridge_funcs);
|
|
if (IS_ERR(hdmi))
|
|
return ERR_CAST(hdmi);
|
|
|
|
hdmi->dev = dev;
|
|
|
|
regs = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(regs))
|
|
return ERR_CAST(regs);
|
|
|
|
hdmi->regm = devm_regmap_init_mmio(dev, regs, &dw_hdmi_qp_regmap_config);
|
|
if (IS_ERR(hdmi->regm)) {
|
|
dev_err(dev, "Failed to configure regmap\n");
|
|
return ERR_CAST(hdmi->regm);
|
|
}
|
|
|
|
hdmi->phy.ops = plat_data->phy_ops;
|
|
hdmi->phy.data = plat_data->phy_data;
|
|
|
|
dw_hdmi_qp_init_hw(hdmi);
|
|
|
|
ret = devm_request_threaded_irq(dev, plat_data->main_irq,
|
|
dw_hdmi_qp_main_hardirq, NULL,
|
|
IRQF_SHARED, dev_name(dev), hdmi);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
hdmi->bridge.driver_private = hdmi;
|
|
hdmi->bridge.ops = DRM_BRIDGE_OP_DETECT |
|
|
DRM_BRIDGE_OP_EDID |
|
|
DRM_BRIDGE_OP_HDMI |
|
|
DRM_BRIDGE_OP_HDMI_AUDIO |
|
|
DRM_BRIDGE_OP_HPD;
|
|
hdmi->bridge.of_node = pdev->dev.of_node;
|
|
hdmi->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
|
|
hdmi->bridge.vendor = "Synopsys";
|
|
hdmi->bridge.product = "DW HDMI QP TX";
|
|
|
|
hdmi->bridge.ddc = dw_hdmi_qp_i2c_adapter(hdmi);
|
|
if (IS_ERR(hdmi->bridge.ddc))
|
|
return ERR_CAST(hdmi->bridge.ddc);
|
|
|
|
hdmi->bridge.hdmi_audio_max_i2s_playback_channels = 8;
|
|
hdmi->bridge.hdmi_audio_dev = dev;
|
|
hdmi->bridge.hdmi_audio_dai_port = 1;
|
|
|
|
ret = devm_drm_bridge_add(dev, &hdmi->bridge);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
ret = drm_bridge_attach(encoder, &hdmi->bridge, NULL,
|
|
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
|
|
if (ret)
|
|
return ERR_PTR(ret);
|
|
|
|
return hdmi;
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_bind);
|
|
|
|
void dw_hdmi_qp_resume(struct device *dev, struct dw_hdmi_qp *hdmi)
|
|
{
|
|
dw_hdmi_qp_init_hw(hdmi);
|
|
}
|
|
EXPORT_SYMBOL_GPL(dw_hdmi_qp_resume);
|
|
|
|
MODULE_AUTHOR("Algea Cao <algea.cao@rock-chips.com>");
|
|
MODULE_AUTHOR("Cristian Ciocaltea <cristian.ciocaltea@collabora.com>");
|
|
MODULE_DESCRIPTION("DW HDMI QP transmitter library");
|
|
MODULE_LICENSE("GPL");
|