Commit 83d71152 authored by Shawn Guo's avatar Shawn Guo

drm: zte: support hdmi audio through spdif

It enables HDMI audio support through SPDIF interface based on generic
hdmi-audio-codec driver.  The HDMI hardware supports more audio
interfaces than SPDIF, like I2S, which may be added later.
Signed-off-by: default avatarShawn Guo <shawn.guo@linaro.org>
parent 1aaaac1f
...@@ -4,6 +4,7 @@ config DRM_ZTE ...@@ -4,6 +4,7 @@ config DRM_ZTE
select DRM_KMS_CMA_HELPER select DRM_KMS_CMA_HELPER
select DRM_KMS_FB_HELPER select DRM_KMS_FB_HELPER
select DRM_KMS_HELPER select DRM_KMS_HELPER
select SND_SOC_HDMI_CODEC if SND_SOC
select VIDEOMODE_HELPERS select VIDEOMODE_HELPERS
help help
Choose this option to enable DRM on ZTE ZX SoCs. Choose this option to enable DRM on ZTE ZX SoCs.
...@@ -25,6 +25,8 @@ ...@@ -25,6 +25,8 @@
#include <drm/drm_of.h> #include <drm/drm_of.h>
#include <drm/drmP.h> #include <drm/drmP.h>
#include <sound/hdmi-codec.h>
#include "zx_hdmi_regs.h" #include "zx_hdmi_regs.h"
#include "zx_vou.h" #include "zx_vou.h"
...@@ -49,6 +51,7 @@ struct zx_hdmi { ...@@ -49,6 +51,7 @@ struct zx_hdmi {
bool sink_is_hdmi; bool sink_is_hdmi;
bool sink_has_audio; bool sink_has_audio;
const struct vou_inf *inf; const struct vou_inf *inf;
struct platform_device *audio_pdev;
}; };
#define to_zx_hdmi(x) container_of(x, struct zx_hdmi, x) #define to_zx_hdmi(x) container_of(x, struct zx_hdmi, x)
...@@ -366,6 +369,142 @@ static irqreturn_t zx_hdmi_irq_handler(int irq, void *dev_id) ...@@ -366,6 +369,142 @@ static irqreturn_t zx_hdmi_irq_handler(int irq, void *dev_id)
return IRQ_NONE; return IRQ_NONE;
} }
static int zx_hdmi_audio_startup(struct device *dev, void *data)
{
struct zx_hdmi *hdmi = dev_get_drvdata(dev);
struct drm_encoder *encoder = &hdmi->encoder;
vou_inf_hdmi_audio_sel(encoder->crtc, VOU_HDMI_AUD_SPDIF);
return 0;
}
static void zx_hdmi_audio_shutdown(struct device *dev, void *data)
{
struct zx_hdmi *hdmi = dev_get_drvdata(dev);
/* Disable audio input */
hdmi_writeb_mask(hdmi, AUD_EN, AUD_IN_EN, 0);
}
static inline int zx_hdmi_audio_get_n(unsigned int fs)
{
unsigned int n;
if (fs && (fs % 44100) == 0)
n = 6272 * (fs / 44100);
else
n = fs * 128 / 1000;
return n;
}
static int zx_hdmi_audio_hw_params(struct device *dev,
void *data,
struct hdmi_codec_daifmt *daifmt,
struct hdmi_codec_params *params)
{
struct zx_hdmi *hdmi = dev_get_drvdata(dev);
struct hdmi_audio_infoframe *cea = &params->cea;
union hdmi_infoframe frame;
int n;
/* We only support spdif for now */
if (daifmt->fmt != HDMI_SPDIF) {
DRM_DEV_ERROR(hdmi->dev, "invalid daifmt %d\n", daifmt->fmt);
return -EINVAL;
}
switch (params->sample_width) {
case 16:
hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, SPDIF_SAMPLE_SIZE_MASK,
SPDIF_SAMPLE_SIZE_16BIT);
break;
case 20:
hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, SPDIF_SAMPLE_SIZE_MASK,
SPDIF_SAMPLE_SIZE_20BIT);
break;
case 24:
hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, SPDIF_SAMPLE_SIZE_MASK,
SPDIF_SAMPLE_SIZE_24BIT);
break;
default:
DRM_DEV_ERROR(hdmi->dev, "invalid sample width %d\n",
params->sample_width);
return -EINVAL;
}
/* CTS is calculated by hardware, and we only need to take care of N */
n = zx_hdmi_audio_get_n(params->sample_rate);
hdmi_writeb(hdmi, N_SVAL1, n & 0xff);
hdmi_writeb(hdmi, N_SVAL2, (n >> 8) & 0xff);
hdmi_writeb(hdmi, N_SVAL3, (n >> 16) & 0xf);
/* Enable spdif mode */
hdmi_writeb_mask(hdmi, AUD_MODE, SPDIF_EN, SPDIF_EN);
/* Enable audio input */
hdmi_writeb_mask(hdmi, AUD_EN, AUD_IN_EN, AUD_IN_EN);
memcpy(&frame.audio, cea, sizeof(*cea));
return zx_hdmi_infoframe_trans(hdmi, &frame, FSEL_AUDIO);
}
static int zx_hdmi_audio_digital_mute(struct device *dev, void *data,
bool enable)
{
struct zx_hdmi *hdmi = dev_get_drvdata(dev);
if (enable)
hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, TPI_AUD_MUTE,
TPI_AUD_MUTE);
else
hdmi_writeb_mask(hdmi, TPI_AUD_CONFIG, TPI_AUD_MUTE, 0);
return 0;
}
static int zx_hdmi_audio_get_eld(struct device *dev, void *data,
uint8_t *buf, size_t len)
{
struct zx_hdmi *hdmi = dev_get_drvdata(dev);
struct drm_connector *connector = &hdmi->connector;
memcpy(buf, connector->eld, min(sizeof(connector->eld), len));
return 0;
}
static const struct hdmi_codec_ops zx_hdmi_codec_ops = {
.audio_startup = zx_hdmi_audio_startup,
.hw_params = zx_hdmi_audio_hw_params,
.audio_shutdown = zx_hdmi_audio_shutdown,
.digital_mute = zx_hdmi_audio_digital_mute,
.get_eld = zx_hdmi_audio_get_eld,
};
static struct hdmi_codec_pdata zx_hdmi_codec_pdata = {
.ops = &zx_hdmi_codec_ops,
.spdif = 1,
};
static int zx_hdmi_audio_register(struct zx_hdmi *hdmi)
{
struct platform_device *pdev;
pdev = platform_device_register_data(hdmi->dev, HDMI_CODEC_DRV_NAME,
PLATFORM_DEVID_AUTO,
&zx_hdmi_codec_pdata,
sizeof(zx_hdmi_codec_pdata));
if (IS_ERR(pdev))
return PTR_ERR(pdev);
hdmi->audio_pdev = pdev;
return 0;
}
static int zx_hdmi_i2c_read(struct zx_hdmi *hdmi, struct i2c_msg *msg) static int zx_hdmi_i2c_read(struct zx_hdmi *hdmi, struct i2c_msg *msg)
{ {
int len = msg->len; int len = msg->len;
...@@ -566,6 +705,12 @@ static int zx_hdmi_bind(struct device *dev, struct device *master, void *data) ...@@ -566,6 +705,12 @@ static int zx_hdmi_bind(struct device *dev, struct device *master, void *data)
return ret; return ret;
} }
ret = zx_hdmi_audio_register(hdmi);
if (ret) {
DRM_DEV_ERROR(dev, "failed to register audio: %d\n", ret);
return ret;
}
ret = zx_hdmi_register(drm, hdmi); ret = zx_hdmi_register(drm, hdmi);
if (ret) { if (ret) {
DRM_DEV_ERROR(dev, "failed to register hdmi: %d\n", ret); DRM_DEV_ERROR(dev, "failed to register hdmi: %d\n", ret);
...@@ -590,6 +735,9 @@ static void zx_hdmi_unbind(struct device *dev, struct device *master, ...@@ -590,6 +735,9 @@ static void zx_hdmi_unbind(struct device *dev, struct device *master,
hdmi->connector.funcs->destroy(&hdmi->connector); hdmi->connector.funcs->destroy(&hdmi->connector);
hdmi->encoder.funcs->destroy(&hdmi->encoder); hdmi->encoder.funcs->destroy(&hdmi->encoder);
if (hdmi->audio_pdev)
platform_device_unregister(hdmi->audio_pdev);
} }
static const struct component_ops zx_hdmi_component_ops = { static const struct component_ops zx_hdmi_component_ops = {
......
...@@ -52,5 +52,19 @@ ...@@ -52,5 +52,19 @@
#define TPI_INFO_TRANS_RPT BIT(6) #define TPI_INFO_TRANS_RPT BIT(6)
#define TPI_DDC_MASTER_EN 0x06f8 #define TPI_DDC_MASTER_EN 0x06f8
#define HW_DDC_MASTER BIT(7) #define HW_DDC_MASTER BIT(7)
#define N_SVAL1 0xa03
#define N_SVAL2 0xa04
#define N_SVAL3 0xa05
#define AUD_EN 0xa13
#define AUD_IN_EN BIT(0)
#define AUD_MODE 0xa14
#define SPDIF_EN BIT(1)
#define TPI_AUD_CONFIG 0xa62
#define SPDIF_SAMPLE_SIZE_SHIFT 6
#define SPDIF_SAMPLE_SIZE_MASK (0x3 << SPDIF_SAMPLE_SIZE_SHIFT)
#define SPDIF_SAMPLE_SIZE_16BIT (0x1 << SPDIF_SAMPLE_SIZE_SHIFT)
#define SPDIF_SAMPLE_SIZE_20BIT (0x2 << SPDIF_SAMPLE_SIZE_SHIFT)
#define SPDIF_SAMPLE_SIZE_24BIT (0x3 << SPDIF_SAMPLE_SIZE_SHIFT)
#define TPI_AUD_MUTE BIT(4)
#endif /* __ZX_HDMI_REGS_H__ */ #endif /* __ZX_HDMI_REGS_H__ */
...@@ -119,6 +119,15 @@ static inline struct zx_vou_hw *crtc_to_vou(struct drm_crtc *crtc) ...@@ -119,6 +119,15 @@ static inline struct zx_vou_hw *crtc_to_vou(struct drm_crtc *crtc)
return zcrtc->vou; return zcrtc->vou;
} }
void vou_inf_hdmi_audio_sel(struct drm_crtc *crtc,
enum vou_inf_hdmi_audio aud)
{
struct zx_crtc *zcrtc = to_zx_crtc(crtc);
struct zx_vou_hw *vou = zcrtc->vou;
zx_writel_mask(vou->vouctl + VOU_INF_HDMI_CTRL, VOU_HDMI_AUD_MASK, aud);
}
void vou_inf_enable(const struct vou_inf *inf, struct drm_crtc *crtc) void vou_inf_enable(const struct vou_inf *inf, struct drm_crtc *crtc)
{ {
struct zx_crtc *zcrtc = to_zx_crtc(crtc); struct zx_crtc *zcrtc = to_zx_crtc(crtc);
......
...@@ -30,6 +30,14 @@ enum vou_inf_data_sel { ...@@ -30,6 +30,14 @@ enum vou_inf_data_sel {
VOU_RGB_666 = 3, VOU_RGB_666 = 3,
}; };
enum vou_inf_hdmi_audio {
VOU_HDMI_AUD_SPDIF = BIT(0),
VOU_HDMI_AUD_I2S = BIT(1),
VOU_HDMI_AUD_DSD = BIT(2),
VOU_HDMI_AUD_HBR = BIT(3),
VOU_HDMI_AUD_PARALLEL = BIT(4),
};
struct vou_inf { struct vou_inf {
enum vou_inf_id id; enum vou_inf_id id;
enum vou_inf_data_sel data_sel; enum vou_inf_data_sel data_sel;
...@@ -37,6 +45,8 @@ struct vou_inf { ...@@ -37,6 +45,8 @@ struct vou_inf {
u32 clocks_sel_bits; u32 clocks_sel_bits;
}; };
void vou_inf_hdmi_audio_sel(struct drm_crtc *crtc,
enum vou_inf_hdmi_audio aud);
void vou_inf_enable(const struct vou_inf *inf, struct drm_crtc *crtc); void vou_inf_enable(const struct vou_inf *inf, struct drm_crtc *crtc);
void vou_inf_disable(const struct vou_inf *inf, struct drm_crtc *crtc); void vou_inf_disable(const struct vou_inf *inf, struct drm_crtc *crtc);
......
...@@ -150,6 +150,8 @@ ...@@ -150,6 +150,8 @@
#define VOU_CLK_GL0_SEL BIT(4) #define VOU_CLK_GL0_SEL BIT(4)
#define VOU_CLK_REQEN 0x20 #define VOU_CLK_REQEN 0x20
#define VOU_CLK_EN 0x24 #define VOU_CLK_EN 0x24
#define VOU_INF_HDMI_CTRL 0x30
#define VOU_HDMI_AUD_MASK 0x1f
/* OTFPPU_CTRL registers */ /* OTFPPU_CTRL registers */
#define OTFPPU_RSZ_DATA_SOURCE 0x04 #define OTFPPU_RSZ_DATA_SOURCE 0x04
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment