From 5a302a7a25fe5ac6585ab3b6ca225dbdb63c018f Mon Sep 17 00:00:00 2001 From: Xingyu Wu Date: Tue, 11 Oct 2022 10:46:07 +0800 Subject: [PATCH] gpu:drm:Add audio function in inno hdmi Could playback audio through HDMI. Signed-off-by: Xingyu Wu --- drivers/gpu/drm/verisilicon/Makefile | 3 +- drivers/gpu/drm/verisilicon/inno_hdmi.c | 52 +--- drivers/gpu/drm/verisilicon/inno_hdmi.h | 52 ++++ drivers/gpu/drm/verisilicon/starfive_hdmi_audio.c | 293 ++++++++++++++++++++++ 4 files changed, 354 insertions(+), 46 deletions(-) mode change 100755 => 100644 drivers/gpu/drm/verisilicon/inno_hdmi.c mode change 100755 => 100644 drivers/gpu/drm/verisilicon/inno_hdmi.h create mode 100644 drivers/gpu/drm/verisilicon/starfive_hdmi_audio.c diff --git a/drivers/gpu/drm/verisilicon/Makefile b/drivers/gpu/drm/verisilicon/Makefile index 2a67eba..7319de4 100644 --- a/drivers/gpu/drm/verisilicon/Makefile +++ b/drivers/gpu/drm/verisilicon/Makefile @@ -13,7 +13,8 @@ vs_drm-$(CONFIG_VERISILICON_VIRTUAL_DISPLAY) += vs_virtual.o vs_drm-$(CONFIG_VERISILICON_DW_MIPI_DSI) += dw_mipi_dsi.o vs_drm-$(CONFIG_VERISILICON_MMU) += vs_dc_mmu.o vs_drm-$(CONFIG_VERISILICON_DEC) += vs_dc_dec.o -vs_drm-$(CONFIG_STARFIVE_INNO_HDMI) += inno_hdmi.o +vs_drm-$(CONFIG_STARFIVE_INNO_HDMI) += inno_hdmi.o \ + starfive_hdmi_audio.o vs_drm-$(CONFIG_STARFIVE_DSI) += starfive_drm_dsi.o starfive_drm_seeedpanel.o obj-$(CONFIG_DRM_VERISILICON) += vs_drm.o diff --git a/drivers/gpu/drm/verisilicon/inno_hdmi.c b/drivers/gpu/drm/verisilicon/inno_hdmi.c old mode 100755 new mode 100644 index 734f2e8..3c2fccc --- a/drivers/gpu/drm/verisilicon/inno_hdmi.c +++ b/drivers/gpu/drm/verisilicon/inno_hdmi.c @@ -31,15 +31,6 @@ #define to_inno_hdmi(x) container_of(x, struct inno_hdmi, x) -struct hdmi_data_info { - int vic; - bool sink_is_hdmi; - bool sink_has_audio; - unsigned int enc_in_format; - unsigned int enc_out_format; - unsigned int colorimetry; -}; - struct inno_hdmi_i2c { struct i2c_adapter adap; @@ -50,34 +41,6 @@ struct inno_hdmi_i2c { struct completion cmp; }; -struct inno_hdmi { - struct device *dev; - struct drm_device *drm_dev; - - int irq; - struct clk *pclk; - struct clk *sys_clk; - struct clk *mclk; - struct clk *bclk; - struct reset_control *tx_rst; - void __iomem *regs; - - struct drm_connector connector; - struct drm_encoder encoder; - - struct inno_hdmi_i2c *i2c; - struct i2c_adapter *ddc; - - unsigned long tmds_rate; - - struct hdmi_data_info hdmi_data; - struct drm_display_mode previous_mode; - struct regulator *hdmi_1p8; - struct regulator *hdmi_0p9; - const struct pre_pll_config *pre_cfg; - const struct post_pll_config *post_cfg; -}; - enum { CSC_ITU601_16_235_TO_RGB_0_255_8BIT, CSC_ITU601_0_255_TO_RGB_0_255_8BIT, @@ -133,17 +96,17 @@ static const struct post_pll_config post_pll_cfg_table[] = { { /* sentinel */ } }; -static inline u8 hdmi_readb(struct inno_hdmi *hdmi, u16 offset) +inline u8 hdmi_readb(struct inno_hdmi *hdmi, u16 offset) { return readl_relaxed(hdmi->regs + (offset) * 0x04); } -static inline void hdmi_writeb(struct inno_hdmi *hdmi, u16 offset, u32 val) +inline void hdmi_writeb(struct inno_hdmi *hdmi, u16 offset, u32 val) { writel_relaxed(val, hdmi->regs + (offset) * 0x04); } -static inline void hdmi_modb(struct inno_hdmi *hdmi, u16 offset, +inline void hdmi_modb(struct inno_hdmi *hdmi, u16 offset, u32 msk, u32 val) { u8 temp = hdmi_readb(hdmi, offset) & ~msk; @@ -493,11 +456,6 @@ static int inno_hdmi_setup(struct inno_hdmi *hdmi, inno_hdmi_tx_ctrl(hdmi); - hdmi_writeb(hdmi, 0x35, 0x01); - hdmi_writeb(hdmi, 0x38, 0x04); - hdmi_writeb(hdmi, 0x40, 0x18); - hdmi_writeb(hdmi, 0x41, 0x80); - inno_hdmi_tx_phy_power_on(hdmi); inno_hdmi_tmds_driver_on(hdmi); @@ -971,6 +929,10 @@ static int inno_hdmi_bind(struct device *dev, struct device *master, if (ret < 0) goto err_cleanup_hdmi; + ret = starfive_hdmi_audio_init(hdmi); + if (ret) + dev_err(dev, "failed to audio init\n"); + return 0; err_cleanup_hdmi: hdmi->connector.funcs->destroy(&hdmi->connector); diff --git a/drivers/gpu/drm/verisilicon/inno_hdmi.h b/drivers/gpu/drm/verisilicon/inno_hdmi.h old mode 100755 new mode 100644 index f07281a..b790434 --- a/drivers/gpu/drm/verisilicon/inno_hdmi.h +++ b/drivers/gpu/drm/verisilicon/inno_hdmi.h @@ -8,6 +8,14 @@ #ifndef __INNO_HDMI_H__ #define __INNO_HDMI_H__ +#include +#include +#include +#include +#include +#include +#include + #define DDC_SEGMENT_ADDR 0x30 enum PWR_MODE { @@ -543,4 +551,48 @@ typedef struct register_value { u8 value; } reg_value_t; +struct hdmi_data_info { + int vic; + bool sink_is_hdmi; + bool sink_has_audio; + unsigned int enc_in_format; + unsigned int enc_out_format; + unsigned int colorimetry; +}; + +struct inno_hdmi { + struct device *dev; + struct drm_device *drm_dev; + + int irq; + struct clk *pclk; + struct clk *sys_clk; + struct clk *mclk; + struct clk *bclk; + struct clk *phy_clk; + struct reset_control *tx_rst; + void __iomem *regs; + + struct drm_connector connector; + struct drm_encoder encoder; + + struct inno_hdmi_i2c *i2c; + struct i2c_adapter *ddc; + + unsigned long tmds_rate; + + struct hdmi_data_info hdmi_data; + struct drm_display_mode previous_mode; + struct regulator *hdmi_1p8; + struct regulator *hdmi_0p9; + const struct pre_pll_config *pre_cfg; + const struct post_pll_config *post_cfg; +}; + +int starfive_hdmi_audio_init(struct inno_hdmi *hdmi); +inline u8 hdmi_readb(struct inno_hdmi *hdmi, u16 offset); +inline void hdmi_writeb(struct inno_hdmi *hdmi, u16 offset, u32 val); +inline void hdmi_modb(struct inno_hdmi *hdmi, u16 offset, + u32 msk, u32 val); + #endif /* __INNO_HDMI_H__ */ diff --git a/drivers/gpu/drm/verisilicon/starfive_hdmi_audio.c b/drivers/gpu/drm/verisilicon/starfive_hdmi_audio.c new file mode 100644 index 0000000..d197291 --- /dev/null +++ b/drivers/gpu/drm/verisilicon/starfive_hdmi_audio.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * HDMI Audio driver for the StarFive JH7110 SoC + * + * Copyright (C) 2022 StarFive Technology Co., Ltd. + * Author: Xingyu Wu + */ +#include +#include +#include +#include +#include +#include +#include + +#include "inno_hdmi.h" + +#define SF_PCM_RATE_32000_192000 (SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_176400 | \ + SNDRV_PCM_RATE_192000) + +#define DEV_MUTE 0x005 +#define AUDIO_CFG 0x035 +#define SAMPLE_FRE 0x037 +#define PINS_ENA 0x038 +#define CHANNEL_INPUT 0x039 +#define N_VALUE1 0x03F +#define N_VALUE2 0x040 +#define N_VALUE3 0x041 +#define CTS_VALUE1 0x045 +#define CTS_VALUE2 0x046 +#define CTS_VALUE3 0x047 + +/* DEV_MUTE */ +#define AUDIO_MUTE_MASK BIT(1) +#define AUDIO_MUTE BIT(1) +#define AUDIO_NO_MUTE 0x0 + +/* AUDIO_CFG */ +#define MCLK_RATIO_MASK GENMASK(1, 0) +#define MCLK_128FS 0x0 +#define MCLK_256FS 0x1 +#define MCLK_384FS 0x2 +#define MCLK_512FS 0x3 +#define AUDIO_TYPE_SEL_MASK GENMASK(4, 3) +#define AUDIO_SEL_I2S 0x0 +#define AUDIO_SEL_SPDIF BIT(3) +#define CTS_SOURCE_SEL_MASK BIT(7) +#define CTS_INTER 0x0 +#define CTS_EXTER BIT(7) + +/* SAMPLE_FRE */ +#define I2S_SAMP_FREQ_MASK GENMASK(3, 0) +#define FREQ_32K 0x3 +#define FREQ_44K 0x0 +#define FREQ_48K 0x2 +#define FREQ_88K 0x8 +#define FREQ_96K 0xa +#define FREQ_176K 0xc +#define FREQ_192K 0xe + +/* PINS_ENA */ +#define I2S_FORMAT_MASK GENMASK(1, 0) +#define STANDARD_MODE 0x0 +#define RIGHT_JUSTIFIED_MODE 0x1 +#define LEFT_JUSTIFIED_MODE 0x2 +#define I2S_PIN_ENA_MASK GENMASK(5, 2) +#define I2S0_ENA BIT(2) +#define I2S1_ENA BIT(3) +#define I2S2_ENA BIT(4) +#define I2S3_ENA BIT(5) + +/* CHANNEL_INPUT */ +#define CHANNEL0_INPUT_MASK GENMASK(1, 0) +#define CHANNEL0_I2S0 (0x0 << 0) +#define CHANNEL0_I2S3 (0x1 << 0) +#define CHANNEL0_I2S2 (0x2 << 0) +#define CHANNEL0_I2S1 (0x3 << 0) +#define CHANNEL1_INPUT_MASK GENMASK(3, 2) +#define CHANNEL1_I2S1 (0x0 << 2) +#define CHANNEL1_I2S0 (0x1 << 2) +#define CHANNEL1_I2S3 (0x2 << 2) +#define CHANNEL1_I2S2 (0x3 << 2) +#define CHANNEL2_INPUT_MASK GENMASK(5, 4) +#define CHANNEL2_I2S2 (0x0 << 4) +#define CHANNEL2_I2S1 (0x1 << 4) +#define CHANNEL2_I2S0 (0x2 << 4) +#define CHANNEL2_I2S3 (0x3 << 4) +#define CHANNEL3_INPUT_MASK GENMASK(7, 6) +#define CHANNEL3_I2S3 (0x0 << 6) +#define CHANNEL3_I2S2 (0x1 << 6) +#define CHANNEL3_I2S1 (0x2 << 6) +#define CHANNEL3_I2S0 (0x3 << 6) + +static int starfive_hdmi_audio_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct inno_hdmi *priv = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* Audio no mute */ + hdmi_modb(priv, DEV_MUTE, AUDIO_MUTE_MASK, AUDIO_NO_MUTE); + return 0; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* Audio mute */ + hdmi_modb(priv, DEV_MUTE, AUDIO_MUTE_MASK, AUDIO_MUTE); + return 0; + + default: + return -EINVAL; + } +} + +static int starfive_hdmi_audio_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct inno_hdmi *priv = snd_soc_dai_get_drvdata(dai); + unsigned int sample_rate; + unsigned int channels; + unsigned int rate_reg; + unsigned int channels_reg; + unsigned int Nvalue; + unsigned int CTSvalue; + unsigned int TMDS = priv->tmds_rate; + + dev_dbg(priv->dev, "HDMI&AUDIO: tmds rate:%d\n", TMDS); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return 0; + + sample_rate = params_rate(params); + switch (sample_rate) { + case 32000: + rate_reg = FREQ_32K; + break; + case 44100: + rate_reg = FREQ_44K; + break; + case 48000: + rate_reg = FREQ_48K; + break; + case 88200: + rate_reg = FREQ_88K; + break; + case 96000: + rate_reg = FREQ_96K; + break; + case 176400: + rate_reg = FREQ_176K; + break; + case 192000: + rate_reg = FREQ_192K; + break; + default: + dev_err(priv->dev, "HDMI&AUDIO: not support sample rate:%d\n", + sample_rate); + return -EINVAL; + } + + Nvalue = 128 * sample_rate / 1000; + CTSvalue = TMDS / 1000; + + channels = params_channels(params); + switch (channels) { + case 2: + channels_reg = I2S0_ENA; + break; + case 4: + channels_reg = I2S0_ENA | I2S1_ENA; + break; + case 6: + channels_reg = I2S0_ENA | I2S1_ENA | I2S2_ENA; + break; + case 8: + channels_reg = I2S0_ENA | I2S1_ENA | I2S2_ENA | I2S3_ENA; + break; + default: + dev_err(priv->dev, "HDMI&AUDIO: not support channels:%d\n", + channels); + return -EINVAL; + } + + hdmi_modb(priv, AUDIO_CFG, CTS_SOURCE_SEL_MASK, CTS_EXTER); + + hdmi_writeb(priv, SAMPLE_FRE, rate_reg); + + hdmi_modb(priv, PINS_ENA, I2S_PIN_ENA_MASK, channels_reg); + + /* N{reg3f[3:0],reg40[7:0],reg41[7:0]} */ + hdmi_writeb(priv, N_VALUE1, ((Nvalue >> 16) & 0xf)); + hdmi_writeb(priv, N_VALUE2, ((Nvalue >> 8) & 0xff)); + hdmi_writeb(priv, N_VALUE3, ((Nvalue >> 0) & 0xff)); + + /* CTS{reg45[3:0],reg46[7:0],reg47[7:0]} */ + hdmi_writeb(priv, CTS_VALUE1, ((CTSvalue >> 16) & 0xf)); + hdmi_writeb(priv, CTS_VALUE2, ((CTSvalue >> 8) & 0xff)); + hdmi_writeb(priv, CTS_VALUE3, ((CTSvalue >> 0) & 0xff)); + + dev_dbg(priv->dev, "HDMI&AUDIO: AUDIO_CFG :0x%x\n", + hdmi_readb(priv, AUDIO_CFG)); + dev_dbg(priv->dev, "HDMI&AUDIO: CHANNEL_INPUT :0x%x\n", + hdmi_readb(priv, CHANNEL_INPUT)); + dev_dbg(priv->dev, "HDMI&AUDIO: SAMPLE_FRE :0x%x\n", + hdmi_readb(priv, SAMPLE_FRE)); + dev_dbg(priv->dev, "HDMI&AUDIO: PINS_ENA :0x%x\n", + hdmi_readb(priv, PINS_ENA)); + dev_dbg(priv->dev, "HDMI&AUDIO: N_VALUE :0x%x, 0x%x, 0x%x\n", + hdmi_readb(priv, N_VALUE1), + hdmi_readb(priv, N_VALUE2), + hdmi_readb(priv, N_VALUE3)); + dev_dbg(priv->dev, "HDMI&AUDIO: CTS_VALUE :0x%x,0x%x,0x%x\n", + hdmi_readb(priv, CTS_VALUE1), + hdmi_readb(priv, CTS_VALUE2), + hdmi_readb(priv, CTS_VALUE3)); + + return 0; +} + +static int starfive_hdmi_audio_probe(struct snd_soc_component *component) +{ + struct inno_hdmi *priv = snd_soc_component_get_drvdata(component); + + /* Use external CTS source */ + hdmi_modb(priv, AUDIO_CFG, CTS_SOURCE_SEL_MASK, CTS_EXTER); + + /* select I2S type */ + hdmi_modb(priv, AUDIO_CFG, AUDIO_TYPE_SEL_MASK, AUDIO_SEL_I2S); + + /* MCLK ratio 0:128fs, 1:256fs, 2:384fs, 3:512fs */ + hdmi_modb(priv, AUDIO_CFG, MCLK_RATIO_MASK, MCLK_256FS); + + /* I2S format 0:standard, 1:right-justified, 2:left-justified */ + hdmi_modb(priv, PINS_ENA, I2S_FORMAT_MASK, STANDARD_MODE); + + /* Audio channel input */ + hdmi_writeb(priv, CHANNEL_INPUT, CHANNEL0_I2S0 | CHANNEL1_I2S1 | + CHANNEL2_I2S2 | CHANNEL3_I2S3); + + /* Audio mute */ + hdmi_modb(priv, DEV_MUTE, AUDIO_MUTE_MASK, AUDIO_MUTE); + + return 0; +} + +static const struct snd_soc_dai_ops starfive_hdmi_audio_dai_ops = { + .trigger = starfive_hdmi_audio_trigger, + .hw_params = starfive_hdmi_audio_hw_params, +}; + +static struct snd_soc_dai_driver starfive_hdmi_audio_dai = { + .name = "starfive-hdmi-audio", + .id = 0, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SF_PCM_RATE_32000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &starfive_hdmi_audio_dai_ops, + .symmetric_rate = 1, +}; + +static const struct snd_soc_component_driver starfive_hdmi_audio_component = { + .name = "starfive-hdmi-audio", + .probe = starfive_hdmi_audio_probe, +}; + +int starfive_hdmi_audio_init(struct inno_hdmi *hdmi) +{ + int ret; + + ret = devm_snd_soc_register_component(hdmi->dev, &starfive_hdmi_audio_component, + &starfive_hdmi_audio_dai, 1); + if (ret) { + dev_err(hdmi->dev, "HDMI&AUDIO: not able to register dai\n"); + return ret; + } + dev_info(hdmi->dev, "HDMI&AUDIO register done.\n"); + + return 0; +} -- 2.7.4