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