• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * dw-hdmi-qp-i2s-audio.c
4  *
5  * Copyright (c) 2021 Rockchip Electronics Co. Ltd.
6  * Author: Sugar Zhang <sugar.zhang@rock-chips.com>
7  */
8 
9 #include <linux/dma-mapping.h>
10 #include <linux/module.h>
11 
12 #include <drm/bridge/dw_hdmi.h>
13 #include <drm/drm_crtc.h>
14 
15 #include <sound/hdmi-codec.h>
16 
17 #include "dw-hdmi-qp.h"
18 #include "dw-hdmi-qp-audio.h"
19 
20 #define DRIVER_NAME "dw-hdmi-qp-i2s-audio"
21 
hdmi_write(struct dw_hdmi_qp_i2s_audio_data * audio,u32 val,int offset)22 static inline void hdmi_write(struct dw_hdmi_qp_i2s_audio_data *audio,
23 			      u32 val, int offset)
24 {
25 	struct dw_hdmi_qp *hdmi = audio->hdmi;
26 
27 	audio->write(hdmi, val, offset);
28 }
29 
hdmi_read(struct dw_hdmi_qp_i2s_audio_data * audio,int offset)30 static inline u32 hdmi_read(struct dw_hdmi_qp_i2s_audio_data *audio, int offset)
31 {
32 	struct dw_hdmi_qp *hdmi = audio->hdmi;
33 
34 	return audio->read(hdmi, offset);
35 }
36 
hdmi_mod(struct dw_hdmi_qp_i2s_audio_data * audio,u32 data,u32 mask,u32 reg)37 static inline void hdmi_mod(struct dw_hdmi_qp_i2s_audio_data *audio,
38 			    u32 data, u32 mask, u32 reg)
39 {
40 	struct dw_hdmi_qp *hdmi = audio->hdmi;
41 
42 	return audio->mod(hdmi, data, mask, reg);
43 }
44 
is_dw_hdmi_qp_clk_off(struct dw_hdmi_qp_i2s_audio_data * audio)45 static inline bool is_dw_hdmi_qp_clk_off(struct dw_hdmi_qp_i2s_audio_data *audio)
46 {
47 	u32 sta = hdmi_read(audio, CMU_STATUS);
48 
49 	return (sta & (AUDCLK_OFF | LINKQPCLK_OFF | VIDQPCLK_OFF));
50 }
51 
dw_hdmi_qp_i2s_hw_params(struct device * dev,void * data,struct hdmi_codec_daifmt * fmt,struct hdmi_codec_params * hparms)52 static int dw_hdmi_qp_i2s_hw_params(struct device *dev, void *data,
53 				    struct hdmi_codec_daifmt *fmt,
54 				    struct hdmi_codec_params *hparms)
55 {
56 	struct dw_hdmi_qp_i2s_audio_data *audio = data;
57 	struct dw_hdmi_qp *hdmi = audio->hdmi;
58 	u32 conf0 = 0;
59 
60 	if (is_dw_hdmi_qp_clk_off(audio))
61 		return 0;
62 
63 	if (fmt->bit_clk_master | fmt->frame_clk_master) {
64 		dev_err(dev, "unsupported clock settings\n");
65 		return -EINVAL;
66 	}
67 
68 	/* Reset the audio data path of the AVP */
69 	hdmi_write(audio, AVP_DATAPATH_PACKET_AUDIO_SWINIT_P, GLOBAL_SWRESET_REQUEST);
70 
71 	/* Clear the audio FIFO */
72 	hdmi_write(audio, AUDIO_FIFO_CLR_P, AUDIO_INTERFACE_CONTROL0);
73 
74 	/* Disable AUDS, ACR, AUDI, AMD */
75 	hdmi_mod(audio, 0,
76 		 PKTSCHED_ACR_TX_EN | PKTSCHED_AUDS_TX_EN |
77 		 PKTSCHED_AUDI_TX_EN | PKTSCHED_AMD_TX_EN,
78 		 PKTSCHED_PKT_EN);
79 
80 	/* Select I2S interface as the audio source */
81 	hdmi_mod(audio, AUD_IF_I2S, AUD_IF_SEL_MSK, AUDIO_INTERFACE_CONFIG0);
82 
83 	/* Enable the active i2s lanes */
84 	switch (hparms->channels) {
85 	case 7 ... 8:
86 		conf0 |= I2S_LINES_EN(3);
87 		fallthrough;
88 	case 5 ... 6:
89 		conf0 |= I2S_LINES_EN(2);
90 		fallthrough;
91 	case 3 ... 4:
92 		conf0 |= I2S_LINES_EN(1);
93 		fallthrough;
94 	default:
95 		conf0 |= I2S_LINES_EN(0);
96 		break;
97 	}
98 
99 	hdmi_mod(audio, conf0, I2S_LINES_EN_MSK, AUDIO_INTERFACE_CONFIG0);
100 
101 	/*
102 	 * Enable bpcuv generated internally for L-PCM, or received
103 	 * from stream for NLPCM/HBR.
104 	 */
105 	switch (fmt->bit_fmt) {
106 	case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
107 		conf0 = (hparms->channels == 8) ? AUD_HBR : AUD_ASP;
108 		conf0 |= I2S_BPCUV_RCV_EN;
109 		break;
110 	default:
111 		conf0 = AUD_ASP | I2S_BPCUV_RCV_DIS;
112 		break;
113 	}
114 
115 	hdmi_mod(audio, conf0, I2S_BPCUV_RCV_MSK | AUD_FORMAT_MSK,
116 		 AUDIO_INTERFACE_CONFIG0);
117 
118 	/* Enable audio FIFO auto clear when overflow */
119 	hdmi_mod(audio, AUD_FIFO_INIT_ON_OVF_EN, AUD_FIFO_INIT_ON_OVF_MSK,
120 		 AUDIO_INTERFACE_CONFIG0);
121 
122 	dw_hdmi_qp_set_sample_rate(hdmi, hparms->sample_rate);
123 	dw_hdmi_qp_set_channel_status(hdmi, hparms->iec.status);
124 	dw_hdmi_qp_set_channel_count(hdmi, hparms->channels);
125 	dw_hdmi_qp_set_channel_allocation(hdmi, hparms->cea.channel_allocation);
126 
127 	/* Enable ACR, AUDI, AMD */
128 	hdmi_mod(audio,
129 		 PKTSCHED_ACR_TX_EN | PKTSCHED_AUDI_TX_EN | PKTSCHED_AMD_TX_EN,
130 		 PKTSCHED_ACR_TX_EN | PKTSCHED_AUDI_TX_EN | PKTSCHED_AMD_TX_EN,
131 		 PKTSCHED_PKT_EN);
132 
133 	/* Enable AUDS */
134 	hdmi_mod(audio, PKTSCHED_AUDS_TX_EN, PKTSCHED_AUDS_TX_EN, PKTSCHED_PKT_EN);
135 
136 	return 0;
137 }
138 
dw_hdmi_qp_i2s_audio_startup(struct device * dev,void * data)139 static int dw_hdmi_qp_i2s_audio_startup(struct device *dev, void *data)
140 {
141 	struct dw_hdmi_qp_i2s_audio_data *audio = data;
142 	struct dw_hdmi_qp *hdmi = audio->hdmi;
143 
144 	if (is_dw_hdmi_qp_clk_off(audio))
145 		return 0;
146 
147 	dw_hdmi_qp_audio_enable(hdmi);
148 
149 	return 0;
150 }
151 
dw_hdmi_qp_i2s_audio_shutdown(struct device * dev,void * data)152 static void dw_hdmi_qp_i2s_audio_shutdown(struct device *dev, void *data)
153 {
154 	struct dw_hdmi_qp_i2s_audio_data *audio = data;
155 	struct dw_hdmi_qp *hdmi = audio->hdmi;
156 
157 	if (is_dw_hdmi_qp_clk_off(audio))
158 		return;
159 
160 	dw_hdmi_qp_audio_disable(hdmi);
161 }
162 
dw_hdmi_qp_i2s_get_eld(struct device * dev,void * data,uint8_t * buf,size_t len)163 static int dw_hdmi_qp_i2s_get_eld(struct device *dev, void *data, uint8_t *buf,
164 				  size_t len)
165 {
166 	struct dw_hdmi_qp_i2s_audio_data *audio = data;
167 
168 	memcpy(buf, audio->eld, min_t(size_t, MAX_ELD_BYTES, len));
169 
170 	return 0;
171 }
172 
dw_hdmi_qp_i2s_get_dai_id(struct snd_soc_component * component,struct device_node * endpoint)173 static int dw_hdmi_qp_i2s_get_dai_id(struct snd_soc_component *component,
174 				     struct device_node *endpoint)
175 {
176 	struct of_endpoint of_ep;
177 	int ret;
178 
179 	ret = of_graph_parse_endpoint(endpoint, &of_ep);
180 	if (ret < 0)
181 		return ret;
182 
183 	/*
184 	 * HDMI sound should be located as reg = <2>
185 	 * Then, it is sound port 0
186 	 */
187 	if (of_ep.port == 2)
188 		return 0;
189 
190 	return -EINVAL;
191 }
192 
dw_hdmi_qp_i2s_hook_plugged_cb(struct device * dev,void * data,hdmi_codec_plugged_cb fn,struct device * codec_dev)193 static int dw_hdmi_qp_i2s_hook_plugged_cb(struct device *dev, void *data,
194 					  hdmi_codec_plugged_cb fn,
195 					  struct device *codec_dev)
196 {
197 	struct dw_hdmi_qp_i2s_audio_data *audio = data;
198 	struct dw_hdmi_qp *hdmi = audio->hdmi;
199 
200 	return dw_hdmi_qp_set_plugged_cb(hdmi, fn, codec_dev);
201 }
202 
203 static struct hdmi_codec_ops dw_hdmi_qp_i2s_ops = {
204 	.hw_params	= dw_hdmi_qp_i2s_hw_params,
205 	.audio_startup  = dw_hdmi_qp_i2s_audio_startup,
206 	.audio_shutdown	= dw_hdmi_qp_i2s_audio_shutdown,
207 	.get_eld	= dw_hdmi_qp_i2s_get_eld,
208 	.get_dai_id	= dw_hdmi_qp_i2s_get_dai_id,
209 	.hook_plugged_cb = dw_hdmi_qp_i2s_hook_plugged_cb,
210 };
211 
snd_dw_hdmi_qp_probe(struct platform_device * pdev)212 static int snd_dw_hdmi_qp_probe(struct platform_device *pdev)
213 {
214 	struct dw_hdmi_qp_i2s_audio_data *audio = pdev->dev.platform_data;
215 	struct platform_device_info pdevinfo;
216 	struct hdmi_codec_pdata pdata;
217 	struct platform_device *platform;
218 
219 	pdata.ops		= &dw_hdmi_qp_i2s_ops;
220 	pdata.i2s		= 1;
221 	pdata.max_i2s_channels	= 8;
222 	pdata.data		= audio;
223 
224 	memset(&pdevinfo, 0, sizeof(pdevinfo));
225 	pdevinfo.parent		= pdev->dev.parent;
226 	pdevinfo.id		= PLATFORM_DEVID_AUTO;
227 	pdevinfo.name		= HDMI_CODEC_DRV_NAME;
228 	pdevinfo.data		= &pdata;
229 	pdevinfo.size_data	= sizeof(pdata);
230 	pdevinfo.dma_mask	= DMA_BIT_MASK(32);
231 
232 	platform = platform_device_register_full(&pdevinfo);
233 	if (IS_ERR(platform))
234 		return PTR_ERR(platform);
235 
236 	dev_set_drvdata(&pdev->dev, platform);
237 
238 	return 0;
239 }
240 
snd_dw_hdmi_qp_remove(struct platform_device * pdev)241 static int snd_dw_hdmi_qp_remove(struct platform_device *pdev)
242 {
243 	struct platform_device *platform = dev_get_drvdata(&pdev->dev);
244 
245 	platform_device_unregister(platform);
246 
247 	return 0;
248 }
249 
250 static struct platform_driver snd_dw_hdmi_qp_driver = {
251 	.probe	= snd_dw_hdmi_qp_probe,
252 	.remove	= snd_dw_hdmi_qp_remove,
253 	.driver	= {
254 		.name = DRIVER_NAME,
255 	},
256 };
257 module_platform_driver(snd_dw_hdmi_qp_driver);
258 
259 MODULE_AUTHOR("Sugar Zhang <sugar.zhang@rock-chips.com>");
260 MODULE_DESCRIPTION("Synopsis Designware HDMI QP I2S ALSA SoC interface");
261 MODULE_LICENSE("GPL v2");
262 MODULE_ALIAS("platform:" DRIVER_NAME);
263