drm/bridge: anx7625: add HDMI audio function
Add audio HDMI codec function support, enable it through device true flag "analogix,audio-enable". Reviewed-by: Robert Foss <robert.foss@linaro.org> Signed-off-by: Xin Ji <xji@analogixsemi.com> Signed-off-by: Robert Foss <robert.foss@linaro.org> Link: https://patchwork.freedesktop.org/patch/msgid/20211104033857.2634562-1-xji@analogixsemi.com
This commit is contained in:
parent
fd0310b6fe
commit
566fef1226
2 changed files with 231 additions and 0 deletions
|
@ -33,6 +33,7 @@
|
||||||
#include <drm/drm_probe_helper.h>
|
#include <drm/drm_probe_helper.h>
|
||||||
|
|
||||||
#include <media/v4l2-fwnode.h>
|
#include <media/v4l2-fwnode.h>
|
||||||
|
#include <sound/hdmi-codec.h>
|
||||||
#include <video/display_timing.h>
|
#include <video/display_timing.h>
|
||||||
|
|
||||||
#include "anx7625.h"
|
#include "anx7625.h"
|
||||||
|
@ -153,6 +154,20 @@ static int anx7625_write_and(struct anx7625_data *ctx,
|
||||||
return anx7625_reg_write(ctx, client, offset, (val & (mask)));
|
return anx7625_reg_write(ctx, client, offset, (val & (mask)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int anx7625_write_and_or(struct anx7625_data *ctx,
|
||||||
|
struct i2c_client *client,
|
||||||
|
u8 offset, u8 and_mask, u8 or_mask)
|
||||||
|
{
|
||||||
|
int val;
|
||||||
|
|
||||||
|
val = anx7625_reg_read(ctx, client, offset);
|
||||||
|
if (val < 0)
|
||||||
|
return val;
|
||||||
|
|
||||||
|
return anx7625_reg_write(ctx, client,
|
||||||
|
offset, (val & and_mask) | (or_mask));
|
||||||
|
}
|
||||||
|
|
||||||
static int anx7625_config_bit_matrix(struct anx7625_data *ctx)
|
static int anx7625_config_bit_matrix(struct anx7625_data *ctx)
|
||||||
{
|
{
|
||||||
int i, ret;
|
int i, ret;
|
||||||
|
@ -1353,6 +1368,9 @@ static int anx7625_parse_dt(struct device *dev,
|
||||||
else
|
else
|
||||||
DRM_DEV_DEBUG_DRIVER(dev, "found MIPI DSI host node.\n");
|
DRM_DEV_DEBUG_DRIVER(dev, "found MIPI DSI host node.\n");
|
||||||
|
|
||||||
|
if (of_property_read_bool(np, "analogix,audio-enable"))
|
||||||
|
pdata->audio_en = 1;
|
||||||
|
|
||||||
ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, NULL);
|
ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, NULL);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
if (ret == -ENODEV)
|
if (ret == -ENODEV)
|
||||||
|
@ -1423,6 +1441,208 @@ static enum drm_connector_status anx7625_sink_detect(struct anx7625_data *ctx)
|
||||||
connector_status_disconnected;
|
connector_status_disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int anx7625_audio_hw_params(struct device *dev, void *data,
|
||||||
|
struct hdmi_codec_daifmt *fmt,
|
||||||
|
struct hdmi_codec_params *params)
|
||||||
|
{
|
||||||
|
struct anx7625_data *ctx = dev_get_drvdata(dev);
|
||||||
|
int wl, ch, rate;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (fmt->fmt != HDMI_DSP_A) {
|
||||||
|
DRM_DEV_ERROR(dev, "only supports DSP_A\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
DRM_DEV_DEBUG_DRIVER(dev, "setting %d Hz, %d bit, %d channels\n",
|
||||||
|
params->sample_rate, params->sample_width,
|
||||||
|
params->cea.channels);
|
||||||
|
|
||||||
|
ret |= anx7625_write_and_or(ctx, ctx->i2c.tx_p2_client,
|
||||||
|
AUDIO_CHANNEL_STATUS_6,
|
||||||
|
~I2S_SLAVE_MODE,
|
||||||
|
TDM_SLAVE_MODE);
|
||||||
|
|
||||||
|
/* Word length */
|
||||||
|
switch (params->sample_width) {
|
||||||
|
case 16:
|
||||||
|
wl = AUDIO_W_LEN_16_20MAX;
|
||||||
|
break;
|
||||||
|
case 18:
|
||||||
|
wl = AUDIO_W_LEN_18_20MAX;
|
||||||
|
break;
|
||||||
|
case 20:
|
||||||
|
wl = AUDIO_W_LEN_20_20MAX;
|
||||||
|
break;
|
||||||
|
case 24:
|
||||||
|
wl = AUDIO_W_LEN_24_24MAX;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DRM_DEV_DEBUG_DRIVER(dev, "wordlength: %d bit not support",
|
||||||
|
params->sample_width);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
ret |= anx7625_write_and_or(ctx, ctx->i2c.tx_p2_client,
|
||||||
|
AUDIO_CHANNEL_STATUS_5,
|
||||||
|
0xf0, wl);
|
||||||
|
|
||||||
|
/* Channel num */
|
||||||
|
switch (params->cea.channels) {
|
||||||
|
case 2:
|
||||||
|
ch = I2S_CH_2;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
ch = TDM_CH_4;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
ch = TDM_CH_6;
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
ch = TDM_CH_8;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DRM_DEV_DEBUG_DRIVER(dev, "channel number: %d not support",
|
||||||
|
params->cea.channels);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
ret |= anx7625_write_and_or(ctx, ctx->i2c.tx_p2_client,
|
||||||
|
AUDIO_CHANNEL_STATUS_6, 0x1f, ch << 5);
|
||||||
|
if (ch > I2S_CH_2)
|
||||||
|
ret |= anx7625_write_or(ctx, ctx->i2c.tx_p2_client,
|
||||||
|
AUDIO_CHANNEL_STATUS_6, AUDIO_LAYOUT);
|
||||||
|
else
|
||||||
|
ret |= anx7625_write_and(ctx, ctx->i2c.tx_p2_client,
|
||||||
|
AUDIO_CHANNEL_STATUS_6, ~AUDIO_LAYOUT);
|
||||||
|
|
||||||
|
/* FS */
|
||||||
|
switch (params->sample_rate) {
|
||||||
|
case 32000:
|
||||||
|
rate = AUDIO_FS_32K;
|
||||||
|
break;
|
||||||
|
case 44100:
|
||||||
|
rate = AUDIO_FS_441K;
|
||||||
|
break;
|
||||||
|
case 48000:
|
||||||
|
rate = AUDIO_FS_48K;
|
||||||
|
break;
|
||||||
|
case 88200:
|
||||||
|
rate = AUDIO_FS_882K;
|
||||||
|
break;
|
||||||
|
case 96000:
|
||||||
|
rate = AUDIO_FS_96K;
|
||||||
|
break;
|
||||||
|
case 176400:
|
||||||
|
rate = AUDIO_FS_1764K;
|
||||||
|
break;
|
||||||
|
case 192000:
|
||||||
|
rate = AUDIO_FS_192K;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DRM_DEV_DEBUG_DRIVER(dev, "sample rate: %d not support",
|
||||||
|
params->sample_rate);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
ret |= anx7625_write_and_or(ctx, ctx->i2c.tx_p2_client,
|
||||||
|
AUDIO_CHANNEL_STATUS_4,
|
||||||
|
0xf0, rate);
|
||||||
|
ret |= anx7625_write_or(ctx, ctx->i2c.rx_p0_client,
|
||||||
|
AP_AV_STATUS, AP_AUDIO_CHG);
|
||||||
|
if (ret < 0) {
|
||||||
|
DRM_DEV_ERROR(dev, "IO error : config audio.\n");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void anx7625_audio_shutdown(struct device *dev, void *data)
|
||||||
|
{
|
||||||
|
DRM_DEV_DEBUG_DRIVER(dev, "stop audio\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int anx7625_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
|
||||||
|
struct device_node *endpoint)
|
||||||
|
{
|
||||||
|
struct of_endpoint of_ep;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = of_graph_parse_endpoint(endpoint, &of_ep);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HDMI sound should be located at external DPI port
|
||||||
|
* Didn't have good way to check where is internal(DSI)
|
||||||
|
* or external(DPI) bridge
|
||||||
|
*/
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
anx7625_audio_update_connector_status(struct anx7625_data *ctx,
|
||||||
|
enum drm_connector_status status)
|
||||||
|
{
|
||||||
|
if (ctx->plugged_cb && ctx->codec_dev) {
|
||||||
|
ctx->plugged_cb(ctx->codec_dev,
|
||||||
|
status == connector_status_connected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int anx7625_audio_hook_plugged_cb(struct device *dev, void *data,
|
||||||
|
hdmi_codec_plugged_cb fn,
|
||||||
|
struct device *codec_dev)
|
||||||
|
{
|
||||||
|
struct anx7625_data *ctx = data;
|
||||||
|
|
||||||
|
ctx->plugged_cb = fn;
|
||||||
|
ctx->codec_dev = codec_dev;
|
||||||
|
anx7625_audio_update_connector_status(ctx, anx7625_sink_detect(ctx));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct hdmi_codec_ops anx7625_codec_ops = {
|
||||||
|
.hw_params = anx7625_audio_hw_params,
|
||||||
|
.audio_shutdown = anx7625_audio_shutdown,
|
||||||
|
.get_dai_id = anx7625_hdmi_i2s_get_dai_id,
|
||||||
|
.hook_plugged_cb = anx7625_audio_hook_plugged_cb,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void anx7625_unregister_audio(struct anx7625_data *ctx)
|
||||||
|
{
|
||||||
|
struct device *dev = &ctx->client->dev;
|
||||||
|
|
||||||
|
if (ctx->audio_pdev) {
|
||||||
|
platform_device_unregister(ctx->audio_pdev);
|
||||||
|
ctx->audio_pdev = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
DRM_DEV_DEBUG_DRIVER(dev, "unbound to %s", HDMI_CODEC_DRV_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int anx7625_register_audio(struct device *dev, struct anx7625_data *ctx)
|
||||||
|
{
|
||||||
|
struct hdmi_codec_pdata codec_data = {
|
||||||
|
.ops = &anx7625_codec_ops,
|
||||||
|
.max_i2s_channels = 8,
|
||||||
|
.i2s = 1,
|
||||||
|
.data = ctx,
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx->audio_pdev = platform_device_register_data(dev,
|
||||||
|
HDMI_CODEC_DRV_NAME,
|
||||||
|
PLATFORM_DEVID_AUTO,
|
||||||
|
&codec_data,
|
||||||
|
sizeof(codec_data));
|
||||||
|
|
||||||
|
if (IS_ERR(ctx->audio_pdev))
|
||||||
|
return IS_ERR(ctx->audio_pdev);
|
||||||
|
|
||||||
|
DRM_DEV_DEBUG_DRIVER(dev, "bound to %s", HDMI_CODEC_DRV_NAME);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int anx7625_attach_dsi(struct anx7625_data *ctx)
|
static int anx7625_attach_dsi(struct anx7625_data *ctx)
|
||||||
{
|
{
|
||||||
struct mipi_dsi_device *dsi;
|
struct mipi_dsi_device *dsi;
|
||||||
|
@ -1974,6 +2194,9 @@ static int anx7625_i2c_probe(struct i2c_client *client,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (platform->pdata.audio_en)
|
||||||
|
anx7625_register_audio(dev, platform);
|
||||||
|
|
||||||
DRM_DEV_DEBUG_DRIVER(dev, "probe done\n");
|
DRM_DEV_DEBUG_DRIVER(dev, "probe done\n");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -2010,6 +2233,9 @@ static int anx7625_i2c_remove(struct i2c_client *client)
|
||||||
|
|
||||||
anx7625_unregister_i2c_dummy_clients(platform);
|
anx7625_unregister_i2c_dummy_clients(platform);
|
||||||
|
|
||||||
|
if (platform->pdata.audio_en)
|
||||||
|
anx7625_unregister_audio(platform);
|
||||||
|
|
||||||
kfree(platform);
|
kfree(platform);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,6 +111,7 @@
|
||||||
#define AUDIO_CHANNEL_STATUS_6 0xd5
|
#define AUDIO_CHANNEL_STATUS_6 0xd5
|
||||||
#define TDM_SLAVE_MODE 0x10
|
#define TDM_SLAVE_MODE 0x10
|
||||||
#define I2S_SLAVE_MODE 0x08
|
#define I2S_SLAVE_MODE 0x08
|
||||||
|
#define AUDIO_LAYOUT 0x01
|
||||||
|
|
||||||
#define AUDIO_CONTROL_REGISTER 0xe6
|
#define AUDIO_CONTROL_REGISTER 0xe6
|
||||||
#define TDM_TIMING_MODE 0x08
|
#define TDM_TIMING_MODE 0x08
|
||||||
|
@ -367,6 +368,7 @@ struct anx7625_platform_data {
|
||||||
int intp_irq;
|
int intp_irq;
|
||||||
int is_dpi;
|
int is_dpi;
|
||||||
int mipi_lanes;
|
int mipi_lanes;
|
||||||
|
int audio_en;
|
||||||
int dp_lane0_swing_reg_cnt;
|
int dp_lane0_swing_reg_cnt;
|
||||||
int lane0_reg_data[DP_TX_SWING_REG_CNT];
|
int lane0_reg_data[DP_TX_SWING_REG_CNT];
|
||||||
int dp_lane1_swing_reg_cnt;
|
int dp_lane1_swing_reg_cnt;
|
||||||
|
@ -387,6 +389,7 @@ struct anx7625_i2c_client {
|
||||||
|
|
||||||
struct anx7625_data {
|
struct anx7625_data {
|
||||||
struct anx7625_platform_data pdata;
|
struct anx7625_platform_data pdata;
|
||||||
|
struct platform_device *audio_pdev;
|
||||||
int hpd_status;
|
int hpd_status;
|
||||||
int hpd_high_cnt;
|
int hpd_high_cnt;
|
||||||
/* Lock for work queue */
|
/* Lock for work queue */
|
||||||
|
@ -395,6 +398,8 @@ struct anx7625_data {
|
||||||
struct anx7625_i2c_client i2c;
|
struct anx7625_i2c_client i2c;
|
||||||
struct i2c_client *last_client;
|
struct i2c_client *last_client;
|
||||||
struct s_edid_data slimport_edid_p;
|
struct s_edid_data slimport_edid_p;
|
||||||
|
struct device *codec_dev;
|
||||||
|
hdmi_codec_plugged_cb plugged_cb;
|
||||||
struct work_struct work;
|
struct work_struct work;
|
||||||
struct workqueue_struct *workqueue;
|
struct workqueue_struct *workqueue;
|
||||||
char edid_block;
|
char edid_block;
|
||||||
|
|
Loading…
Add table
Reference in a new issue