diff --git a/drivers/gpu/drm/bridge/synopsys/Makefile b/drivers/gpu/drm/bridge/synopsys/Makefile index 6f2340eec44e..ea11fa1d4800 100644 --- a/drivers/gpu/drm/bridge/synopsys/Makefile +++ b/drivers/gpu/drm/bridge/synopsys/Makefile @@ -2,7 +2,7 @@ obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o dw-hdmi-hdcp.o \ dw-hdmi-qp.o obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o -obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o +obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o dw-hdmi-qp-i2s-audio.o obj-$(CONFIG_DRM_DW_HDMI_CEC) += dw-hdmi-cec.o obj-$(CONFIG_DRM_DW_MIPI_DSI) += dw-mipi-dsi.o diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp-audio.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp-audio.h new file mode 100644 index 000000000000..93f1a42954e7 --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp-audio.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Rockchip Electronics Co. Ltd. + * Author: Sugar Zhang + */ + +#ifndef DW_HDMI_QP_AUDIO_H +#define DW_HDMI_QP_AUDIO_H + +struct dw_hdmi_qp; + +struct dw_hdmi_qp_audio_data { + phys_addr_t phys; + void __iomem *base; + int irq; + struct dw_hdmi_qp *hdmi; + u8 *eld; +}; + +struct dw_hdmi_qp_i2s_audio_data { + struct dw_hdmi_qp *hdmi; + u8 *eld; + + void (*write)(struct dw_hdmi_qp *hdmi, u32 val, int offset); + u32 (*read)(struct dw_hdmi_qp *hdmi, int offset); + void (*mod)(struct dw_hdmi_qp *hdmi, u32 val, u32 mask, u32 reg); +}; + +#endif diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp-i2s-audio.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp-i2s-audio.c new file mode 100644 index 000000000000..7df74654a5e7 --- /dev/null +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp-i2s-audio.c @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dw-hdmi-qp-i2s-audio.c + * + * Copyright (c) 2021 Rockchip Electronics Co. Ltd. + * Author: Sugar Zhang + */ + +#include +#include + +#include +#include + +#include + +#include "dw-hdmi-qp.h" +#include "dw-hdmi-qp-audio.h" + +#define DRIVER_NAME "dw-hdmi-qp-i2s-audio" + +static inline void hdmi_write(struct dw_hdmi_qp_i2s_audio_data *audio, + u32 val, int offset) +{ + struct dw_hdmi_qp *hdmi = audio->hdmi; + + audio->write(hdmi, val, offset); +} + +static inline u32 hdmi_read(struct dw_hdmi_qp_i2s_audio_data *audio, int offset) +{ + struct dw_hdmi_qp *hdmi = audio->hdmi; + + return audio->read(hdmi, offset); +} + +static inline void hdmi_mod(struct dw_hdmi_qp_i2s_audio_data *audio, + u32 data, u32 mask, u32 reg) +{ + struct dw_hdmi_qp *hdmi = audio->hdmi; + + return audio->mod(hdmi, data, mask, reg); +} + +static int dw_hdmi_qp_i2s_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + struct dw_hdmi_qp_i2s_audio_data *audio = data; + struct dw_hdmi_qp *hdmi = audio->hdmi; + u32 conf0 = 0; + + if (fmt->bit_clk_master | fmt->frame_clk_master) { + dev_err(dev, "unsupported clock settings\n"); + return -EINVAL; + } + + /* Reset the audio data path of the AVP */ + hdmi_write(audio, AVP_DATAPATH_PACKET_AUDIO_SWINIT_P, GLOBAL_SWRESET_REQUEST); + + /* Clear the audio FIFO */ + hdmi_write(audio, AUDIO_FIFO_CLR_P, AUDIO_INTERFACE_CONTROL0); + + /* Disable AUDS, ACR, AUDI, AMD */ + hdmi_mod(audio, 0, + PKTSCHED_ACR_TX_EN | PKTSCHED_AUDS_TX_EN | + PKTSCHED_AUDI_TX_EN | PKTSCHED_AMD_TX_EN, + PKTSCHED_PKT_EN); + + /* Select I2S interface as the audio source */ + hdmi_mod(audio, 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; + } + + hdmi_mod(audio, conf0, I2S_LINES_EN_MSK, AUDIO_INTERFACE_CONFIG0); + + /* Enable bpcuv generated internally */ + hdmi_mod(audio, I2S_BPCUV_RCV_DIS, I2S_BPCUV_RCV_MSK, AUDIO_INTERFACE_CONFIG0); + + /* Configure the audio format */ + hdmi_mod(audio, AUD_ASP, AUD_FORMAT_MSK, AUDIO_INTERFACE_CONFIG0); + + /* Enable audio FIFO auto clear when overflow */ + hdmi_mod(audio, AUD_FIFO_INIT_ON_OVF_EN, AUD_FIFO_INIT_ON_OVF_MSK, + AUDIO_INTERFACE_CONFIG0); + + dw_hdmi_qp_set_sample_rate(hdmi, hparms->sample_rate); + dw_hdmi_qp_set_channel_status(hdmi, hparms->iec.status); + dw_hdmi_qp_set_channel_count(hdmi, hparms->channels); + dw_hdmi_qp_set_channel_allocation(hdmi, hparms->cea.channel_allocation); + + /* Enable ACR, AUDI, AMD */ + hdmi_mod(audio, + 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 */ + hdmi_mod(audio, PKTSCHED_AUDS_TX_EN, PKTSCHED_AUDS_TX_EN, PKTSCHED_PKT_EN); + + return 0; +} + +static int dw_hdmi_qp_i2s_audio_startup(struct device *dev, void *data) +{ + struct dw_hdmi_qp_i2s_audio_data *audio = data; + struct dw_hdmi_qp *hdmi = audio->hdmi; + + dw_hdmi_qp_audio_enable(hdmi); + + return 0; +} + +static void dw_hdmi_qp_i2s_audio_shutdown(struct device *dev, void *data) +{ + struct dw_hdmi_qp_i2s_audio_data *audio = data; + struct dw_hdmi_qp *hdmi = audio->hdmi; + + dw_hdmi_qp_audio_disable(hdmi); +} + +static int dw_hdmi_qp_i2s_get_eld(struct device *dev, void *data, uint8_t *buf, + size_t len) +{ + struct dw_hdmi_qp_i2s_audio_data *audio = data; + + memcpy(buf, audio->eld, min_t(size_t, MAX_ELD_BYTES, len)); + + return 0; +} + +static int dw_hdmi_qp_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 as reg = <2> + * Then, it is sound port 0 + */ + if (of_ep.port == 2) + return 0; + + return -EINVAL; +} + +static struct hdmi_codec_ops dw_hdmi_qp_i2s_ops = { + .hw_params = dw_hdmi_qp_i2s_hw_params, + .audio_startup = dw_hdmi_qp_i2s_audio_startup, + .audio_shutdown = dw_hdmi_qp_i2s_audio_shutdown, + .get_eld = dw_hdmi_qp_i2s_get_eld, + .get_dai_id = dw_hdmi_qp_i2s_get_dai_id, +}; + +static int snd_dw_hdmi_qp_probe(struct platform_device *pdev) +{ + struct dw_hdmi_qp_i2s_audio_data *audio = pdev->dev.platform_data; + struct platform_device_info pdevinfo; + struct hdmi_codec_pdata pdata; + struct platform_device *platform; + + pdata.ops = &dw_hdmi_qp_i2s_ops; + pdata.i2s = 1; + pdata.max_i2s_channels = 8; + pdata.data = audio; + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.parent = pdev->dev.parent; + pdevinfo.id = PLATFORM_DEVID_AUTO; + pdevinfo.name = HDMI_CODEC_DRV_NAME; + pdevinfo.data = &pdata; + pdevinfo.size_data = sizeof(pdata); + pdevinfo.dma_mask = DMA_BIT_MASK(32); + + platform = platform_device_register_full(&pdevinfo); + if (IS_ERR(platform)) + return PTR_ERR(platform); + + dev_set_drvdata(&pdev->dev, platform); + + return 0; +} + +static int snd_dw_hdmi_qp_remove(struct platform_device *pdev) +{ + struct platform_device *platform = dev_get_drvdata(&pdev->dev); + + platform_device_unregister(platform); + + return 0; +} + +static struct platform_driver snd_dw_hdmi_qp_driver = { + .probe = snd_dw_hdmi_qp_probe, + .remove = snd_dw_hdmi_qp_remove, + .driver = { + .name = DRIVER_NAME, + }, +}; +module_platform_driver(snd_dw_hdmi_qp_driver); + +MODULE_AUTHOR("Sugar Zhang "); +MODULE_DESCRIPTION("Synopsis Designware HDMI QP I2S ALSA SoC interface"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c index 9b68185d39fb..71fde3b23faa 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.c @@ -34,6 +34,7 @@ #include #include +#include "dw-hdmi-qp-audio.h" #include "dw-hdmi-qp.h" #include @@ -53,6 +54,61 @@ static const unsigned int dw_hdmi_cable[] = { EXTCON_NONE, }; +/* + * Unless otherwise noted, entries in this table are 100% optimization. + * Values can be obtained from hdmi_compute_n() but that function is + * slow so we pre-compute values we expect to see. + * + * All 32k and 48k values are expected to be the same (due to the way + * the math works) for any rate that's an exact kHz. + */ +static const struct dw_hdmi_audio_tmds_n common_tmds_n_table[] = { + { .tmds = 25175000, .n_32k = 4096, .n_44k1 = 12854, .n_48k = 6144, }, + { .tmds = 25200000, .n_32k = 4096, .n_44k1 = 5656, .n_48k = 6144, }, + { .tmds = 27000000, .n_32k = 4096, .n_44k1 = 5488, .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 = 5684, .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 = 4096, .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 = 4096, .n_44k1 = 6272, .n_48k = 6144, }, + { .tmds = 148500000, .n_32k = 4096, .n_44k1 = 5488, .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, }, +}; + static const struct drm_display_mode dw_hdmi_default_modes[] = { /* 2 - 720x480@60Hz 4:3 */ { DRM_MODE("720x480", DRM_MODE_TYPE_DRIVER, 27000, 720, 736, @@ -161,6 +217,7 @@ struct dw_hdmi_qp { struct drm_connector connector; struct drm_bridge bridge; struct platform_device *hdcp_dev; + struct platform_device *audio; struct device *dev; struct dw_hdmi_qp_i2c *i2c; @@ -210,6 +267,15 @@ struct dw_hdmi_qp { u32 flt_intr; u32 earc_intr; + spinlock_t audio_lock; + struct mutex audio_mutex; + unsigned int sample_rate; + unsigned int audio_cts; + unsigned int audio_n; + bool audio_enable; + void (*enable_audio)(struct dw_hdmi_qp *hdmi); + void (*disable_audio)(struct dw_hdmi_qp *hdmi); + struct dentry *debugfs_dir; bool scramble_low_rates; @@ -246,6 +312,245 @@ static void hdmi_modb(struct dw_hdmi_qp *hdmi, u32 data, u32 mask, u32 reg) regmap_update_bits(hdmi->regm, reg, mask, data); } +static void hdmi_set_cts_n(struct dw_hdmi_qp *hdmi, unsigned int cts, + unsigned int n) +{ + /* Set N */ + hdmi_modb(hdmi, n, AUDPKT_ACR_N_VALUE, AUDPKT_ACR_CONTROL0); + + /* Set CTS */ + if (cts) + hdmi_modb(hdmi, AUDPKT_ACR_CTS_OVR_EN, AUDPKT_ACR_CTS_OVR_EN_MSK, + AUDPKT_ACR_CONTROL1); + else + hdmi_modb(hdmi, 0, AUDPKT_ACR_CTS_OVR_EN_MSK, + AUDPKT_ACR_CONTROL1); + + hdmi_modb(hdmi, AUDPKT_ACR_CTS_OVR_VAL(cts), AUDPKT_ACR_CTS_OVR_VAL_MSK, + AUDPKT_ACR_CONTROL1); +} + +static int hdmi_match_tmds_n_table(struct dw_hdmi_qp *hdmi, + unsigned long pixel_clk, + unsigned long freq) +{ + const struct dw_hdmi_plat_data *plat_data = hdmi->plat_data; + const struct dw_hdmi_audio_tmds_n *tmds_n = NULL; + int i; + + if (plat_data->tmds_n_table) { + for (i = 0; plat_data->tmds_n_table[i].tmds != 0; i++) { + if (pixel_clk == plat_data->tmds_n_table[i].tmds) { + tmds_n = &plat_data->tmds_n_table[i]; + break; + } + } + } + + if (tmds_n == NULL) { + 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 == NULL) + 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 u64 hdmi_audio_math_diff(unsigned int freq, unsigned int n, + unsigned int pixel_clk) +{ + u64 final, diff; + u64 cts; + + final = (u64)pixel_clk * n; + + cts = final; + do_div(cts, 128 * freq); + + diff = final - (u64)cts * (128 * freq); + + return diff; +} + +static unsigned int hdmi_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 (hdmi_audio_math_diff(freq, ideal_n, pixel_clk) == 0) + return ideal_n; + + for (n = min_n; n <= max_n; n++) { + u64 diff = hdmi_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 hdmi_find_n(struct dw_hdmi_qp *hdmi, unsigned long pixel_clk, + unsigned long sample_rate) +{ + int n; + + n = hdmi_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 hdmi_compute_n(hdmi, pixel_clk, sample_rate); +} + +/* + * 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). + */ +void dw_hdmi_qp_set_channel_status(struct dw_hdmi_qp *hdmi, + u8 *channel_status) +{ +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_channel_status); + +static void hdmi_set_clk_regenerator(struct dw_hdmi_qp *hdmi, + unsigned long pixel_clk, unsigned int sample_rate) +{ + unsigned int n = 0, cts = 0; + + n = hdmi_find_n(hdmi, pixel_clk, sample_rate); + + spin_lock_irq(&hdmi->audio_lock); + hdmi->audio_n = n; + hdmi->audio_cts = cts; + hdmi_set_cts_n(hdmi, cts, hdmi->audio_enable ? n : 0); + spin_unlock_irq(&hdmi->audio_lock); +} + +static void hdmi_init_clk_regenerator(struct dw_hdmi_qp *hdmi) +{ + mutex_lock(&hdmi->audio_mutex); + hdmi_set_clk_regenerator(hdmi, 74250000, hdmi->sample_rate); + mutex_unlock(&hdmi->audio_mutex); +} + +static void hdmi_clk_regenerator_update_pixel_clock(struct dw_hdmi_qp *hdmi) +{ + mutex_lock(&hdmi->audio_mutex); + hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock, + hdmi->sample_rate); + mutex_unlock(&hdmi->audio_mutex); +} + +void dw_hdmi_qp_set_sample_rate(struct dw_hdmi_qp *hdmi, unsigned int rate) +{ + mutex_lock(&hdmi->audio_mutex); + hdmi->sample_rate = rate; + hdmi_set_clk_regenerator(hdmi, hdmi->hdmi_data.video_mode.mtmdsclock, + hdmi->sample_rate); + mutex_unlock(&hdmi->audio_mutex); +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_sample_rate); + +void dw_hdmi_qp_set_channel_count(struct dw_hdmi_qp *hdmi, unsigned int cnt) +{ +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_channel_count); + +void dw_hdmi_qp_set_channel_allocation(struct dw_hdmi_qp *hdmi, unsigned int ca) +{ +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_set_channel_allocation); + +static void hdmi_enable_audio_clk(struct dw_hdmi_qp *hdmi, bool enable) +{ + if (enable) + hdmi_modb(hdmi, 0, + AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE); + else + hdmi_modb(hdmi, AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, + AVP_DATAPATH_PACKET_AUDIO_SWDISABLE, GLOBAL_SWDISABLE); +} + +static void dw_hdmi_i2s_audio_enable(struct dw_hdmi_qp *hdmi) +{ + hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); + hdmi_enable_audio_clk(hdmi, true); +} + +static void dw_hdmi_i2s_audio_disable(struct dw_hdmi_qp *hdmi) +{ + hdmi_enable_audio_clk(hdmi, false); +} + +void dw_hdmi_qp_audio_enable(struct dw_hdmi_qp *hdmi) +{ + unsigned long flags; + + spin_lock_irqsave(&hdmi->audio_lock, flags); + hdmi->audio_enable = true; + if (hdmi->enable_audio) + hdmi->enable_audio(hdmi); + spin_unlock_irqrestore(&hdmi->audio_lock, flags); +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_audio_enable); + +void dw_hdmi_qp_audio_disable(struct dw_hdmi_qp *hdmi) +{ + unsigned long flags; + + spin_lock_irqsave(&hdmi->audio_lock, flags); + hdmi->audio_enable = false; + if (hdmi->disable_audio) + hdmi->disable_audio(hdmi); + spin_unlock_irqrestore(&hdmi->audio_lock, flags); +} +EXPORT_SYMBOL_GPL(dw_hdmi_qp_audio_disable); + static bool hdmi_bus_fmt_is_rgb(unsigned int bus_format) { switch (bus_format) { @@ -1113,6 +1418,14 @@ static int dw_hdmi_qp_setup(struct dw_hdmi_qp *hdmi, if (ret) return ret; + if (hdmi->sink_has_audio) { + dev_dbg(hdmi->dev, "sink has audio support\n"); + + /* HDMI Initialization Step E - Configure audio */ + hdmi_clk_regenerator_update_pixel_clock(hdmi); + hdmi_enable_audio_clk(hdmi, hdmi->audio_enable); + } + /* not for DVI mode */ if (hdmi->sink_is_hdmi) { dev_info(hdmi->dev, "%s HDMI mode\n", __func__); @@ -1693,6 +2006,8 @@ __dw_hdmi_probe(struct platform_device *pdev, struct device_node *np = dev->of_node; struct device_node *ddc_node; struct dw_hdmi_qp *hdmi; + struct dw_hdmi_qp_i2s_audio_data audio; + struct platform_device_info pdevinfo; struct resource *iores = NULL; int irq; int ret; @@ -1704,10 +2019,13 @@ __dw_hdmi_probe(struct platform_device *pdev, hdmi->connector.stereo_allowed = 1; hdmi->plat_data = plat_data; hdmi->dev = dev; + hdmi->sample_rate = 48000; hdmi->disabled = true; mutex_init(&hdmi->mutex); + mutex_init(&hdmi->audio_mutex); mutex_init(&hdmi->cec_notifier_mutex); + spin_lock_init(&hdmi->audio_lock); ddc_node = of_parse_phandle(np, "ddc-i2c-bus", 0); if (ddc_node) { @@ -1832,6 +2150,8 @@ __dw_hdmi_probe(struct platform_device *pdev, if (ret) goto err_ref; + hdmi_init_clk_regenerator(hdmi); + /* If DDC bus is not specified, try to register HDMI I2C bus */ if (!hdmi->ddc) { hdmi->ddc = dw_hdmi_i2c_adapter(hdmi); @@ -1860,6 +2180,23 @@ __dw_hdmi_probe(struct platform_device *pdev, hdmi->connector.ycbcr_420_allowed = hdmi->plat_data->ycbcr_420_allowed; + audio.hdmi = hdmi; + audio.eld = hdmi->connector.eld; + audio.write = hdmi_writel; + audio.read = hdmi_readl; + audio.mod = hdmi_modb; + hdmi->enable_audio = dw_hdmi_i2s_audio_enable; + hdmi->disable_audio = dw_hdmi_i2s_audio_disable; + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + pdevinfo.parent = dev; + pdevinfo.id = PLATFORM_DEVID_AUTO; + pdevinfo.name = "dw-hdmi-qp-i2s-audio"; + pdevinfo.data = &audio; + pdevinfo.size_data = sizeof(audio); + pdevinfo.dma_mask = DMA_BIT_MASK(32); + hdmi->audio = platform_device_register_full(&pdevinfo); + hdmi->extcon = devm_extcon_dev_allocate(hdmi->dev, dw_hdmi_cable); if (IS_ERR(hdmi->extcon)) { dev_err(hdmi->dev, "allocate extcon failed\n"); @@ -1928,6 +2265,9 @@ static void __dw_hdmi_remove(struct dw_hdmi_qp *hdmi) hdmi->connector.funcs->destroy(&hdmi->connector); + if (hdmi->audio && !IS_ERR(hdmi->audio)) + platform_device_unregister(hdmi->audio); + if (hdmi->bridge.encoder) hdmi->bridge.encoder->funcs->destroy(hdmi->bridge.encoder); diff --git a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h index 959ce59642ce..0634ff8a5843 100644 --- a/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h +++ b/drivers/gpu/drm/bridge/synopsys/dw-hdmi-qp.h @@ -95,12 +95,30 @@ #define VIDEO_PACKING_CONFIG0 0x81c /* Audio Interface Registers */ #define AUDIO_INTERFACE_CONFIG0 0x820 -#define AUD_IF_SEL 0x3 -#define I2S_LINES_EN 0xf0 -#define SPDIF_LINES_EN (0xf << 16) -#define AUD_FORMAT (0x7 << 24) +#define AUD_IF_SEL_MSK 0x3 +#define AUD_IF_SPDIF 0x2 +#define AUD_IF_I2S 0x1 +#define AUD_IF_PAI 0x0 +#define AUD_FIFO_INIT_ON_OVF_MSK BIT(2) +#define AUD_FIFO_INIT_ON_OVF_EN BIT(2) +#define I2S_LINES_EN_MSK GENMASK(7, 4) +#define I2S_LINES_EN(x) BIT(x + 4) +#define I2S_BPCUV_RCV_MSK BIT(12) +#define I2S_BPCUV_RCV_EN BIT(12) +#define I2S_BPCUV_RCV_DIS 0 +#define SPDIF_LINES_EN GENMASK(19, 16) +#define AUD_FORMAT_MSK GENMASK(26, 24) +#define AUD_3DOBA (0x7 << 24) +#define AUD_3DASP (0x6 << 24) +#define AUD_MSOBA (0x5 << 24) +#define AUD_MSASP (0x4 << 24) +#define AUD_HBR (0x3 << 24) +#define AUD_DST (0x2 << 24) +#define AUD_OBA (0x1 << 24) +#define AUD_ASP (0x0 << 24) #define AUDIO_INTERFACE_CONFIG1 0x824 #define AUDIO_INTERFACE_CONTROL0 0x82c +#define AUDIO_FIFO_CLR_P BIT(0) #define AUDIO_INTERFACE_STATUS0 0x834 /* Frame Composer Registers */ #define FRAME_COMPOSER_CONFIG0 0x840 @@ -368,6 +386,9 @@ #define AUDPKT_ACR_CONTROL0 0xe40 #define AUDPKT_ACR_N_VALUE 0xfffff #define AUDPKT_ACR_CONTROL1 0xe44 +#define AUDPKT_ACR_CTS_OVR_VAL_MSK GENMASK(23, 4) +#define AUDPKT_ACR_CTS_OVR_VAL(x) ((x) << 4) +#define AUDPKT_ACR_CTS_OVR_EN_MSK BIT(1) #define AUDPKT_ACR_CTS_OVR_EN BIT(1) #define AUDPKT_ACR_STATUS0 0xe4c #define AUDPKT_CHSTATUS_OVR0 0xe60 diff --git a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c index 8a5af16a7ac4..87349b98ceeb 100644 --- a/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c +++ b/drivers/gpu/drm/rockchip/dw_hdmi-rockchip.c @@ -2921,7 +2921,8 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, if (!hdmi->id) { val = HIWORD_UPDATE(RK3588_SCLIN_MASK, RK3588_SCLIN_MASK) | HIWORD_UPDATE(RK3588_SDAIN_MASK, RK3588_SDAIN_MASK) | - HIWORD_UPDATE(RK3588_MODE_MASK, RK3588_MODE_MASK); + HIWORD_UPDATE(RK3588_MODE_MASK, RK3588_MODE_MASK) | + HIWORD_UPDATE(RK3588_I2S_SEL_MASK, RK3588_I2S_SEL_MASK); regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON3, val); val = HIWORD_UPDATE(RK3588_SET_HPD_PATH_MASK, @@ -2934,7 +2935,8 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master, } else { val = HIWORD_UPDATE(RK3588_SCLIN_MASK, RK3588_SCLIN_MASK) | HIWORD_UPDATE(RK3588_SDAIN_MASK, RK3588_SDAIN_MASK) | - HIWORD_UPDATE(RK3588_MODE_MASK, RK3588_MODE_MASK); + HIWORD_UPDATE(RK3588_MODE_MASK, RK3588_MODE_MASK) | + HIWORD_UPDATE(RK3588_I2S_SEL_MASK, RK3588_I2S_SEL_MASK); regmap_write(hdmi->vo1_regmap, RK3588_GRF_VO1_CON6, val); val = HIWORD_UPDATE(RK3588_SET_HPD_PATH_MASK, diff --git a/include/drm/bridge/dw_hdmi.h b/include/drm/bridge/dw_hdmi.h index c13388a80858..f2949679be89 100644 --- a/include/drm/bridge/dw_hdmi.h +++ b/include/drm/bridge/dw_hdmi.h @@ -286,5 +286,11 @@ void dw_hdmi_qp_resume(struct device *dev, struct dw_hdmi_qp *hdmi); void dw_hdmi_qp_cec_set_hpd(struct dw_hdmi_qp *hdmi, bool plug_in, bool change); void dw_hdmi_qp_set_cec_adap(struct dw_hdmi_qp *hdmi, struct cec_adapter *adap); int dw_hdmi_qp_set_earc(struct dw_hdmi_qp *hdmi); +void dw_hdmi_qp_set_sample_rate(struct dw_hdmi_qp *hdmi, unsigned int rate); +void dw_hdmi_qp_set_channel_count(struct dw_hdmi_qp *hdmi, unsigned int cnt); +void dw_hdmi_qp_set_channel_status(struct dw_hdmi_qp *hdmi, u8 *channel_status); +void dw_hdmi_qp_set_channel_allocation(struct dw_hdmi_qp *hdmi, unsigned int ca); +void dw_hdmi_qp_audio_enable(struct dw_hdmi_qp *hdmi); +void dw_hdmi_qp_audio_disable(struct dw_hdmi_qp *hdmi); #endif /* __IMX_HDMI_H__ */