@@ -11,6 +11,8 @@ config DRM_ROCKCHIP
select DRM_DW_MIPI_DSI if ROCKCHIP_DW_MIPI_DSI
select DRM_RGB if ROCKCHIP_RGB
select SND_SOC_HDMI_CODEC if ROCKCHIP_CDN_DP && SND_SOC
+ select SND_SOC_HDMI_CODEC if ROCKCHIP_RK3066_HDMI && SND_SOC
+ select SND_SOC_ROCKCHIP_I2S if ROCKCHIP_RK3066_HDMI && SND_SOC
help
Choose this option if you have a Rockchip soc chipset.
This driver provides kernel mode setting and buffer
@@ -13,6 +13,8 @@
#include <linux/platform_device.h>
#include <linux/regmap.h>
+#include <sound/hdmi-codec.h>
+
#include "rk3066_hdmi.h"
#include "rockchip_drm_drv.h"
@@ -20,9 +22,16 @@
#define DEFAULT_PLLA_RATE 30000000
+struct audio_info {
+ int sample_rate;
+ int channels;
+ int sample_width;
+};
+
struct hdmi_data_info {
int vic; /* The CEA Video ID (VIC) of the current drm display mode. */
bool sink_is_hdmi;
+ bool sink_has_audio;
unsigned int enc_out_format;
unsigned int colorimetry;
};
@@ -54,12 +63,19 @@ struct rk3066_hdmi {
unsigned int tmdsclk;
+ struct platform_device *audio_pdev;
+ bool audio_enable;
+
struct hdmi_data_info hdmi_data;
+ struct audio_info audio;
struct drm_display_mode previous_mode;
};
#define to_rk3066_hdmi(x) container_of(x, struct rk3066_hdmi, x)
+static int
+rk3066_hdmi_config_audio(struct rk3066_hdmi *hdmi, struct audio_info *audio);
+
static inline u8 hdmi_readb(struct rk3066_hdmi *hdmi, u16 offset)
{
return readl_relaxed(hdmi->regs + offset);
@@ -205,6 +221,20 @@ static int rk3066_hdmi_config_avi(struct rk3066_hdmi *hdmi,
HDMI_INFOFRAME_AVI, 0, 0, 0);
}
+static int rk3066_hdmi_config_aai(struct rk3066_hdmi *hdmi,
+ struct audio_info *audio)
+{
+ struct hdmi_audio_infoframe *faudio;
+ union hdmi_infoframe frame;
+ int rc;
+
+ rc = hdmi_audio_infoframe_init(&frame.audio);
+ faudio = (struct hdmi_audio_infoframe *)&frame;
+
+ return rk3066_hdmi_upload_frame(hdmi, rc, &frame,
+ HDMI_INFOFRAME_AAI, 0, 0, 0);
+}
+
static int rk3066_hdmi_config_video_timing(struct rk3066_hdmi *hdmi,
struct drm_display_mode *mode)
{
@@ -353,6 +383,7 @@ static int rk3066_hdmi_setup(struct rk3066_hdmi *hdmi,
hdmi_modb(hdmi, HDMI_HDCP_CTRL, HDMI_VIDEO_MODE_MASK,
HDMI_VIDEO_MODE_HDMI);
rk3066_hdmi_config_avi(hdmi, mode);
+ rk3066_hdmi_config_audio(hdmi, &hdmi->audio);
} else {
hdmi_modb(hdmi, HDMI_HDCP_CTRL, HDMI_VIDEO_MODE_MASK, 0);
}
@@ -369,9 +400,20 @@ static int rk3066_hdmi_setup(struct rk3066_hdmi *hdmi,
*/
rk3066_hdmi_i2c_init(hdmi);
- /* Unmute video output. */
+ /* Unmute video and audio output. */
hdmi_modb(hdmi, HDMI_VIDEO_CTRL2,
HDMI_VIDEO_AUDIO_DISABLE_MASK, HDMI_AUDIO_DISABLE);
+ if (hdmi->audio_enable) {
+ hdmi_modb(hdmi, HDMI_VIDEO_CTRL2, HDMI_AUDIO_DISABLE, 0);
+ /* Reset audio capture logic. */
+ hdmi_modb(hdmi, HDMI_VIDEO_CTRL2,
+ HDMI_AUDIO_CP_LOGIC_RESET_MASK,
+ HDMI_AUDIO_CP_LOGIC_RESET);
+ usleep_range(900, 1000);
+ hdmi_modb(hdmi, HDMI_VIDEO_CTRL2,
+ HDMI_AUDIO_CP_LOGIC_RESET_MASK, 0);
+ }
+
return 0;
}
@@ -473,9 +515,13 @@ static int rk3066_hdmi_connector_get_modes(struct drm_connector *connector)
edid = drm_get_edid(connector, hdmi->ddc);
if (edid) {
hdmi->hdmi_data.sink_is_hdmi = drm_detect_hdmi_monitor(edid);
+ hdmi->hdmi_data.sink_has_audio = drm_detect_monitor_audio(edid);
drm_connector_update_edid_property(connector, edid);
ret = drm_add_edid_modes(connector, edid);
kfree(edid);
+ } else {
+ hdmi->hdmi_data.sink_is_hdmi = true;
+ hdmi->hdmi_data.sink_has_audio = true;
}
return ret;
@@ -536,6 +582,228 @@ struct drm_connector_helper_funcs rk3066_hdmi_connector_helper_funcs = {
};
static int
+rk3066_hdmi_config_audio(struct rk3066_hdmi *hdmi, struct audio_info *audio)
+{
+ u32 rate, channel, word_length, N, CTS;
+ u64 tmp;
+
+ if (audio->channels < 3)
+ channel = HDMI_AUDIO_I2S_CHANNEL_1_2;
+ else if (audio->channels < 5)
+ channel = HDMI_AUDIO_I2S_CHANNEL_3_4;
+ else if (audio->channels < 7)
+ channel = HDMI_AUDIO_I2S_CHANNEL_5_6;
+ else
+ channel = HDMI_AUDIO_I2S_CHANNEL_7_8;
+
+ switch (audio->sample_rate) {
+ case 32000:
+ rate = HDMI_AUDIO_SAMPLE_FRE_32000;
+ N = N_32K;
+ break;
+ case 44100:
+ rate = HDMI_AUDIO_SAMPLE_FRE_44100;
+ N = N_441K;
+ break;
+ case 48000:
+ rate = HDMI_AUDIO_SAMPLE_FRE_48000;
+ N = N_48K;
+ break;
+ case 88200:
+ rate = HDMI_AUDIO_SAMPLE_FRE_88200;
+ N = N_882K;
+ break;
+ case 96000:
+ rate = HDMI_AUDIO_SAMPLE_FRE_96000;
+ N = N_96K;
+ break;
+ case 176400:
+ rate = HDMI_AUDIO_SAMPLE_FRE_176400;
+ N = N_1764K;
+ break;
+ case 192000:
+ rate = HDMI_AUDIO_SAMPLE_FRE_192000;
+ N = N_192K;
+ break;
+ default:
+ DRM_DEV_ERROR(hdmi->dev, "no support for sample rate %d\n",
+ audio->sample_rate);
+ return -ENOENT;
+ }
+
+ switch (audio->sample_width) {
+ case 16:
+ word_length = 0x02;
+ break;
+ case 20:
+ word_length = 0x0a;
+ break;
+ case 24:
+ word_length = 0x0b;
+ break;
+ default:
+ DRM_DEV_ERROR(hdmi->dev, "no support for word length %d\n",
+ audio->sample_width);
+ return -ENOENT;
+ }
+
+ tmp = (u64)hdmi->tmdsclk * N;
+ do_div(tmp, 128 * audio->sample_rate);
+ CTS = tmp;
+
+ /* Set_audio source I2S. */
+ hdmi_writeb(hdmi, HDMI_AUDIO_CTRL1, 0x00);
+ hdmi_writeb(hdmi, HDMI_AUDIO_CTRL2, 0x40);
+ hdmi_writeb(hdmi, HDMI_I2S_AUDIO_CTRL,
+ HDMI_AUDIO_I2S_FORMAT_STANDARD | channel);
+ hdmi_writeb(hdmi, HDMI_I2S_SWAP, 0x00);
+ hdmi_modb(hdmi, HDMI_AV_CTRL1, HDMI_AUDIO_SAMPLE_FRE_MASK, rate);
+ hdmi_writeb(hdmi, HDMI_AUDIO_SRC_NUM_AND_LENGTH, word_length);
+
+ /* Set N value. */
+ hdmi_modb(hdmi, HDMI_LR_SWAP_N3,
+ HDMI_AUDIO_N_19_16_MASK, (N >> 16) & 0x0F);
+ hdmi_writeb(hdmi, HDMI_N2, (N >> 8) & 0xFF);
+ hdmi_writeb(hdmi, HDMI_N1, N & 0xFF);
+
+ /* Set CTS value. */
+ hdmi_writeb(hdmi, HDMI_CTS_EXT1, CTS & 0xff);
+ hdmi_writeb(hdmi, HDMI_CTS_EXT2, (CTS >> 8) & 0xff);
+ hdmi_writeb(hdmi, HDMI_CTS_EXT3, (CTS >> 16) & 0xff);
+
+ if (audio->channels > 2)
+ hdmi_modb(hdmi, HDMI_LR_SWAP_N3,
+ HDMI_AUDIO_LR_SWAP_MASK,
+ HDMI_AUDIO_LR_SWAP_SUBPACKET1);
+ rate = (~(rate >> 4)) & 0x0f;
+ hdmi_writeb(hdmi, HDMI_AUDIO_STA_BIT_CTRL1, rate);
+ hdmi_writeb(hdmi, HDMI_AUDIO_STA_BIT_CTRL2, 0);
+
+ return rk3066_hdmi_config_aai(hdmi, audio);
+}
+
+static int rk3066_hdmi_audio_hw_params(struct device *dev, void *d,
+ struct hdmi_codec_daifmt *daifmt,
+ struct hdmi_codec_params *params)
+{
+ struct rk3066_hdmi *hdmi = dev_get_drvdata(dev);
+
+ if (!hdmi->hdmi_data.sink_has_audio) {
+ DRM_DEV_ERROR(hdmi->dev, "no audio support\n");
+ return -ENODEV;
+ }
+
+ if (!hdmi->encoder.crtc)
+ return -ENODEV;
+
+ switch (daifmt->fmt) {
+ case HDMI_I2S:
+ break;
+ default:
+ DRM_DEV_ERROR(dev, "invalid format %d\n", daifmt->fmt);
+ return -EINVAL;
+ }
+
+ hdmi->audio.sample_width = params->sample_width;
+ hdmi->audio.sample_rate = params->sample_rate;
+ hdmi->audio.channels = params->channels;
+
+ return rk3066_hdmi_config_audio(hdmi, &hdmi->audio);
+}
+
+static void rk3066_hdmi_audio_shutdown(struct device *dev, void *d)
+{
+ /* do nothing */
+}
+
+static int
+rk3066_hdmi_audio_mute_stream(struct device *dev, void *d,
+ bool mute, int direction)
+{
+ struct rk3066_hdmi *hdmi = dev_get_drvdata(dev);
+
+ if (!hdmi->hdmi_data.sink_has_audio) {
+ DRM_DEV_ERROR(hdmi->dev, "no audio support\n");
+ return -ENODEV;
+ }
+
+ hdmi->audio_enable = !mute;
+
+ if (mute)
+ hdmi_modb(hdmi, HDMI_VIDEO_CTRL2,
+ HDMI_AUDIO_DISABLE, HDMI_AUDIO_DISABLE);
+ else
+ hdmi_modb(hdmi, HDMI_VIDEO_CTRL2, HDMI_AUDIO_DISABLE, 0);
+
+ /*
+ * Under power mode E we need to reset the audio capture logic to
+ * make the audio setting update.
+ */
+ if (rk3066_hdmi_get_power_mode(hdmi) == HDMI_SYS_POWER_MODE_E) {
+ hdmi_modb(hdmi, HDMI_VIDEO_CTRL2,
+ HDMI_AUDIO_CP_LOGIC_RESET_MASK,
+ HDMI_AUDIO_CP_LOGIC_RESET);
+ usleep_range(900, 1000);
+ hdmi_modb(hdmi, HDMI_VIDEO_CTRL2,
+ HDMI_AUDIO_CP_LOGIC_RESET_MASK, 0);
+ }
+
+ return 0;
+}
+
+static int rk3066_hdmi_audio_get_eld(struct device *dev, void *d,
+ u8 *buf, size_t len)
+{
+ struct rk3066_hdmi *hdmi = dev_get_drvdata(dev);
+ struct drm_mode_config *config = &hdmi->encoder.dev->mode_config;
+ struct drm_connector *connector;
+ int ret = -ENODEV;
+
+ mutex_lock(&config->mutex);
+ list_for_each_entry(connector, &config->connector_list, head) {
+ if (&hdmi->encoder == connector->encoder) {
+ memcpy(buf, connector->eld,
+ min(sizeof(connector->eld), len));
+ ret = 0;
+ }
+ }
+ mutex_unlock(&config->mutex);
+
+ return ret;
+}
+
+static const struct hdmi_codec_ops audio_codec_ops = {
+ .hw_params = rk3066_hdmi_audio_hw_params,
+ .audio_shutdown = rk3066_hdmi_audio_shutdown,
+ .mute_stream = rk3066_hdmi_audio_mute_stream,
+ .get_eld = rk3066_hdmi_audio_get_eld,
+ .no_capture_mute = 1,
+};
+
+static int rk3066_hdmi_audio_codec_init(struct rk3066_hdmi *hdmi,
+ struct device *dev)
+{
+ struct hdmi_codec_pdata codec_data = {
+ .i2s = 1,
+ .ops = &audio_codec_ops,
+ .max_i2s_channels = 8,
+ };
+
+ hdmi->audio.channels = 2;
+ hdmi->audio.sample_rate = 48000;
+ hdmi->audio.sample_width = 16;
+ hdmi->audio_enable = false;
+ hdmi->audio_pdev =
+ platform_device_register_data(dev,
+ HDMI_CODEC_DRV_NAME,
+ PLATFORM_DEVID_NONE,
+ &codec_data,
+ sizeof(codec_data));
+
+ return PTR_ERR_OR_ZERO(hdmi->audio_pdev);
+}
+
+static int
rk3066_hdmi_register(struct drm_device *drm, struct rk3066_hdmi *hdmi)
{
struct drm_encoder *encoder = &hdmi->encoder;
@@ -567,6 +835,8 @@ rk3066_hdmi_register(struct drm_device *drm, struct rk3066_hdmi *hdmi)
drm_connector_attach_encoder(&hdmi->connector, encoder);
+ rk3066_hdmi_audio_codec_init(hdmi, dev);
+
return 0;
}