summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/i2c
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@armlinux.org.uk>2019-02-22 21:53:38 +0100
committerRussell King <rmk+kernel@armlinux.org.uk>2019-06-13 22:54:41 +0200
commita03a915b8387286dfd1e7500705124414802ede7 (patch)
treea7f29dcd96fd6714e0756e3e5d4b9da06d2cc2c9 /drivers/gpu/drm/i2c
parentdrm/i2c: tda998x: improve programming of audio divisor (diff)
downloadlinux-a03a915b8387286dfd1e7500705124414802ede7.tar.xz
linux-a03a915b8387286dfd1e7500705124414802ede7.zip
drm/i2c: tda998x: derive CTS_N value from aclk sample rate ratio
The TDA998x derives the CTS value using the supplied I2S bit clock (ACLK, in TDA998x parlence) rather than 128·fs. TDA998x uses two constants named m and k in the CTS generator such that we have this relationship between the I2S source ACLK and the sink fs: 128·fs_sink = ACLK·m / k Where ACLK = aclk_ratio·fs_source. When audio support was originally added, we supported a fixed ratio of 64·fs, intending to support the Kirkwood I2S on Dove. However, when hdmi-codec support was added, this was changed to scale the ratio with the sample width, which would've broken its use with Kirkwood I2S. We are now starting to see other users whose I2S blocks send at 64·fs for 16-bit samples, so we need to reinstate the support for the fixed ratio I2S bit clock. This commit takes a step towards supporting these configurations by selecting the CTS_N register m and k values based on the bit clock ratio. However, as the driver is not given the bit clock ratio from ALSA, continue deriving this from the sample width. This will be addressed in a later commit. Tested-by: Sven Van Asbroeck <TheSven73@gmail.com> Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
Diffstat (limited to 'drivers/gpu/drm/i2c')
-rw-r--r--drivers/gpu/drm/i2c/tda998x_drv.c94
1 files changed, 74 insertions, 20 deletions
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
index f23aee65846c..9b3e47f408ca 100644
--- a/drivers/gpu/drm/i2c/tda998x_drv.c
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -43,6 +43,7 @@ struct tda998x_audio_port {
struct tda998x_audio_settings {
struct tda998x_audio_params params;
u8 i2s_format;
+ u8 cts_n;
};
struct tda998x_priv {
@@ -891,6 +892,58 @@ static u8 tda998x_get_adiv(struct tda998x_priv *priv, unsigned int fs)
return adiv;
}
+/*
+ * In auto-CTS mode, the TDA998x uses a "measured time stamp" counter to
+ * generate the CTS value. It appears that the "measured time stamp" is
+ * the number of TDMS clock cycles within a number of audio input clock
+ * cycles defined by the k and N parameters defined below, in a similar
+ * way to that which is set out in the CTS generation in the HDMI spec.
+ *
+ * tmdsclk ----> mts -> /m ---> CTS
+ * ^
+ * sclk -> /k -> /N
+ *
+ * CTS = mts / m, where m is 2^M.
+ * /k is a divider based on the K value below, K+1 for K < 4, or 8 for K >= 4
+ * /N is a divider based on the HDMI specified N value.
+ *
+ * This produces the following equation:
+ * CTS = tmds_clock * k * N / (sclk * m)
+ *
+ * When combined with the sink-side equation, and realising that sclk is
+ * bclk_ratio * fs, we end up with:
+ * k = m * bclk_ratio / 128.
+ *
+ * Note: S/PDIF always uses a bclk_ratio of 64.
+ */
+static int tda998x_derive_cts_n(struct tda998x_priv *priv,
+ struct tda998x_audio_settings *settings,
+ unsigned int ratio)
+{
+ switch (ratio) {
+ case 16:
+ settings->cts_n = CTS_N_M(3) | CTS_N_K(0);
+ break;
+ case 32:
+ settings->cts_n = CTS_N_M(3) | CTS_N_K(1);
+ break;
+ case 48:
+ settings->cts_n = CTS_N_M(3) | CTS_N_K(2);
+ break;
+ case 64:
+ settings->cts_n = CTS_N_M(3) | CTS_N_K(3);
+ break;
+ case 128:
+ settings->cts_n = CTS_N_M(0) | CTS_N_K(0);
+ break;
+ default:
+ dev_err(&priv->hdmi->dev, "unsupported bclk ratio %ufs\n",
+ ratio);
+ return -EINVAL;
+ }
+ return 0;
+}
+
static void tda998x_audio_mute(struct tda998x_priv *priv, bool on)
{
if (on) {
@@ -905,7 +958,7 @@ static void tda998x_audio_mute(struct tda998x_priv *priv, bool on)
static int tda998x_configure_audio(struct tda998x_priv *priv,
const struct tda998x_audio_settings *settings)
{
- u8 buf[6], clksel_aip, clksel_fs, cts_n, adiv;
+ u8 buf[6], clksel_aip, clksel_fs, adiv;
u32 n;
adiv = tda998x_get_adiv(priv, settings->params.sample_rate);
@@ -920,7 +973,6 @@ static int tda998x_configure_audio(struct tda998x_priv *priv,
reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_SPDIF);
clksel_aip = AIP_CLKSEL_AIP_SPDIF;
clksel_fs = AIP_CLKSEL_FS_FS64SPDIF;
- cts_n = CTS_N_M(3) | CTS_N_K(3);
break;
case AFMT_I2S:
@@ -928,20 +980,6 @@ static int tda998x_configure_audio(struct tda998x_priv *priv,
reg_write(priv, REG_MUX_AP, MUX_AP_SELECT_I2S);
clksel_aip = AIP_CLKSEL_AIP_I2S;
clksel_fs = AIP_CLKSEL_FS_ACLK;
- switch (settings->params.sample_width) {
- case 16:
- cts_n = CTS_N_M(3) | CTS_N_K(1);
- break;
- case 18:
- case 20:
- case 24:
- cts_n = CTS_N_M(3) | CTS_N_K(2);
- break;
- default:
- case 32:
- cts_n = CTS_N_M(3) | CTS_N_K(3);
- break;
- }
break;
default:
@@ -953,7 +991,7 @@ static int tda998x_configure_audio(struct tda998x_priv *priv,
reg_write(priv, REG_AIP_CLKSEL, clksel_aip);
reg_clear(priv, REG_AIP_CNTRL_0, AIP_CNTRL_0_LAYOUT |
AIP_CNTRL_0_ACR_MAN); /* auto CTS */
- reg_write(priv, REG_CTS_N, cts_n);
+ reg_write(priv, REG_CTS_N, settings->cts_n);
reg_write(priv, REG_AUDIO_DIV, adiv);
/*
@@ -1000,6 +1038,7 @@ static int tda998x_audio_hw_params(struct device *dev, void *data,
struct hdmi_codec_params *params)
{
struct tda998x_priv *priv = dev_get_drvdata(dev);
+ unsigned int bclk_ratio;
bool spdif = daifmt->fmt == HDMI_SPDIF;
int i, ret;
struct tda998x_audio_settings audio = {
@@ -1052,6 +1091,11 @@ static int tda998x_audio_hw_params(struct device *dev, void *data,
return -EINVAL;
}
+ bclk_ratio = spdif ? 64 : params->sample_width * 2;
+ ret = tda998x_derive_cts_n(priv, &audio, bclk_ratio);
+ if (ret < 0)
+ return ret;
+
mutex_lock(&priv->audio_mutex);
if (priv->supports_infoframes && priv->sink_has_audio)
ret = tda998x_configure_audio(priv, &audio);
@@ -1640,8 +1684,8 @@ static int tda998x_get_audio_ports(struct tda998x_priv *priv,
return 0;
}
-static void tda998x_set_config(struct tda998x_priv *priv,
- const struct tda998x_encoder_params *p)
+static int tda998x_set_config(struct tda998x_priv *priv,
+ const struct tda998x_encoder_params *p)
{
priv->vip_cntrl_0 = VIP_CNTRL_0_SWAP_A(p->swap_a) |
(p->mirr_a ? VIP_CNTRL_0_MIRR_A : 0) |
@@ -1657,9 +1701,17 @@ static void tda998x_set_config(struct tda998x_priv *priv,
(p->mirr_f ? VIP_CNTRL_2_MIRR_F : 0);
if (p->audio_params.format != AFMT_UNUSED) {
+ unsigned int ratio;
+ bool spdif = p->audio_params.format == AFMT_SPDIF;
+
priv->audio.params = p->audio_params;
priv->audio.i2s_format = I2S_FORMAT_PHILIPS;
+
+ ratio = spdif ? 64 : p->audio_params.sample_width * 2;
+ return tda998x_derive_cts_n(priv, &priv->audio, ratio);
}
+
+ return 0;
}
static void tda998x_destroy(struct device *dev)
@@ -1861,7 +1913,9 @@ static int tda998x_create(struct device *dev)
if (priv->audio_port[0].format != AFMT_UNUSED)
tda998x_audio_codec_init(priv, &client->dev);
} else if (dev->platform_data) {
- tda998x_set_config(priv, dev->platform_data);
+ ret = tda998x_set_config(priv, dev->platform_data);
+ if (ret)
+ goto fail;
}
priv->bridge.funcs = &tda998x_bridge_funcs;