1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * dw-hdmi-i2s-audio.c
4 *
5 * Copyright (c) 2017 Renesas Solutions Corp.
6 * Kuninori Morimoto <kuninori.morimoto.gx@renesas.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.h"
18 #include "dw-hdmi-audio.h"
19
20 #define DRIVER_NAME "dw-hdmi-i2s-audio"
21
hdmi_write(struct dw_hdmi_i2s_audio_data * audio,u8 val,int offset)22 static inline void hdmi_write(struct dw_hdmi_i2s_audio_data *audio,
23 u8 val, int offset)
24 {
25 struct dw_hdmi *hdmi = audio->hdmi;
26
27 audio->write(hdmi, val, offset);
28 }
29
hdmi_read(struct dw_hdmi_i2s_audio_data * audio,int offset)30 static inline u8 hdmi_read(struct dw_hdmi_i2s_audio_data *audio, int offset)
31 {
32 struct dw_hdmi *hdmi = audio->hdmi;
33
34 return audio->read(hdmi, offset);
35 }
36
dw_hdmi_i2s_hw_params(struct device * dev,void * data,struct hdmi_codec_daifmt * fmt,struct hdmi_codec_params * hparms)37 static int dw_hdmi_i2s_hw_params(struct device *dev, void *data,
38 struct hdmi_codec_daifmt *fmt,
39 struct hdmi_codec_params *hparms)
40 {
41 struct dw_hdmi_i2s_audio_data *audio = data;
42 struct dw_hdmi *hdmi = audio->hdmi;
43 u8 conf0 = 0;
44 u8 conf1 = 0;
45 u8 conf2 = 0;
46 u8 inputclkfs = 0;
47
48 /* it cares I2S only */
49 if (fmt->bit_clk_master | fmt->frame_clk_master) {
50 dev_err(dev, "unsupported clock settings\n");
51 return -EINVAL;
52 }
53
54 /* Reset the FIFOs before applying new params */
55 hdmi_write(audio, HDMI_AUD_CONF0_SW_RESET, HDMI_AUD_CONF0);
56 hdmi_write(audio, (u8)~HDMI_MC_SWRSTZ_I2SSWRST_REQ, HDMI_MC_SWRSTZ);
57
58 inputclkfs = HDMI_AUD_INPUTCLKFS_64FS;
59 conf0 = (HDMI_AUD_CONF0_I2S_SELECT | HDMI_AUD_CONF0_I2S_EN0);
60
61 /* Enable the required i2s lanes */
62 switch (hparms->channels) {
63 case 7 ... 8:
64 conf0 |= HDMI_AUD_CONF0_I2S_EN3;
65 fallthrough;
66 case 5 ... 6:
67 conf0 |= HDMI_AUD_CONF0_I2S_EN2;
68 fallthrough;
69 case 3 ... 4:
70 conf0 |= HDMI_AUD_CONF0_I2S_EN1;
71 /* Fall-thru */
72 }
73
74 switch (hparms->sample_width) {
75 case 16:
76 conf1 = HDMI_AUD_CONF1_WIDTH_16;
77 break;
78 case 24:
79 case 32:
80 conf1 = HDMI_AUD_CONF1_WIDTH_24;
81 break;
82 }
83
84 switch (fmt->fmt) {
85 case HDMI_I2S:
86 conf1 |= HDMI_AUD_CONF1_MODE_I2S;
87 break;
88 case HDMI_RIGHT_J:
89 conf1 |= HDMI_AUD_CONF1_MODE_RIGHT_J;
90 break;
91 case HDMI_LEFT_J:
92 conf1 |= HDMI_AUD_CONF1_MODE_LEFT_J;
93 break;
94 case HDMI_DSP_A:
95 conf1 |= HDMI_AUD_CONF1_MODE_BURST_1;
96 break;
97 case HDMI_DSP_B:
98 conf1 |= HDMI_AUD_CONF1_MODE_BURST_2;
99 break;
100 default:
101 dev_err(dev, "unsupported format\n");
102 return -EINVAL;
103 }
104
105 switch (fmt->bit_fmt) {
106 case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
107 conf1 = HDMI_AUD_CONF1_WIDTH_21;
108 conf2 = (hparms->channels == 8) ? HDMI_AUD_CONF2_HBR : HDMI_AUD_CONF2_NLPCM;
109 break;
110 default:
111 /*
112 * dw-hdmi introduced insert_pcuv bit in version 2.10a.
113 * When set (1'b1), this bit enables the insertion of the PCUV
114 * (Parity, Channel Status, User bit and Validity) bits on the
115 * incoming audio stream (support limited to Linear PCM audio)
116 */
117 if (hdmi_read(audio, HDMI_DESIGN_ID) >= 0x21)
118 conf2 = HDMI_AUD_CONF2_INSERT_PCUV;
119 break;
120 }
121
122 dw_hdmi_set_sample_rate(hdmi, hparms->sample_rate);
123 dw_hdmi_set_channel_status(hdmi, hparms->iec.status);
124 dw_hdmi_set_channel_count(hdmi, hparms->channels);
125 dw_hdmi_set_channel_allocation(hdmi, hparms->cea.channel_allocation);
126
127 hdmi_write(audio, inputclkfs, HDMI_AUD_INPUTCLKFS);
128 hdmi_write(audio, conf0, HDMI_AUD_CONF0);
129 hdmi_write(audio, conf1, HDMI_AUD_CONF1);
130 hdmi_write(audio, conf2, HDMI_AUD_CONF2);
131
132 return 0;
133 }
134
dw_hdmi_i2s_audio_startup(struct device * dev,void * data)135 static int dw_hdmi_i2s_audio_startup(struct device *dev, void *data)
136 {
137 struct dw_hdmi_i2s_audio_data *audio = data;
138 struct dw_hdmi *hdmi = audio->hdmi;
139
140 dw_hdmi_audio_enable(hdmi);
141
142 return 0;
143 }
144
dw_hdmi_i2s_audio_shutdown(struct device * dev,void * data)145 static void dw_hdmi_i2s_audio_shutdown(struct device *dev, void *data)
146 {
147 struct dw_hdmi_i2s_audio_data *audio = data;
148 struct dw_hdmi *hdmi = audio->hdmi;
149
150 dw_hdmi_audio_disable(hdmi);
151 }
152
dw_hdmi_i2s_get_eld(struct device * dev,void * data,uint8_t * buf,size_t len)153 static int dw_hdmi_i2s_get_eld(struct device *dev, void *data, uint8_t *buf,
154 size_t len)
155 {
156 struct dw_hdmi_i2s_audio_data *audio = data;
157 u8 *eld;
158
159 eld = audio->get_eld(audio->hdmi);
160 if (eld)
161 memcpy(buf, eld, min_t(size_t, MAX_ELD_BYTES, len));
162 else
163 /* Pass en empty ELD if connector not available */
164 memset(buf, 0, len);
165
166 return 0;
167 }
168
dw_hdmi_i2s_get_dai_id(struct snd_soc_component * component,struct device_node * endpoint)169 static int dw_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
170 struct device_node *endpoint)
171 {
172 struct of_endpoint of_ep;
173 int ret;
174
175 ret = of_graph_parse_endpoint(endpoint, &of_ep);
176 if (ret < 0)
177 return ret;
178
179 /*
180 * HDMI sound should be located as reg = <2>
181 * Then, it is sound port 0
182 */
183 if (of_ep.port == 2)
184 return 0;
185
186 return -EINVAL;
187 }
188
dw_hdmi_i2s_hook_plugged_cb(struct device * dev,void * data,hdmi_codec_plugged_cb fn,struct device * codec_dev)189 static int dw_hdmi_i2s_hook_plugged_cb(struct device *dev, void *data,
190 hdmi_codec_plugged_cb fn,
191 struct device *codec_dev)
192 {
193 struct dw_hdmi_i2s_audio_data *audio = data;
194 struct dw_hdmi *hdmi = audio->hdmi;
195
196 return dw_hdmi_set_plugged_cb(hdmi, fn, codec_dev);
197 }
198
199 static struct hdmi_codec_ops dw_hdmi_i2s_ops = {
200 .hw_params = dw_hdmi_i2s_hw_params,
201 .audio_startup = dw_hdmi_i2s_audio_startup,
202 .audio_shutdown = dw_hdmi_i2s_audio_shutdown,
203 .get_eld = dw_hdmi_i2s_get_eld,
204 .get_dai_id = dw_hdmi_i2s_get_dai_id,
205 .hook_plugged_cb = dw_hdmi_i2s_hook_plugged_cb,
206 };
207
snd_dw_hdmi_probe(struct platform_device * pdev)208 static int snd_dw_hdmi_probe(struct platform_device *pdev)
209 {
210 struct dw_hdmi_i2s_audio_data *audio = pdev->dev.platform_data;
211 struct platform_device_info pdevinfo;
212 struct hdmi_codec_pdata pdata;
213 struct platform_device *platform;
214
215 pdata.ops = &dw_hdmi_i2s_ops;
216 pdata.i2s = 1;
217 pdata.max_i2s_channels = 8;
218 pdata.data = audio;
219
220 memset(&pdevinfo, 0, sizeof(pdevinfo));
221 pdevinfo.parent = pdev->dev.parent;
222 pdevinfo.id = PLATFORM_DEVID_AUTO;
223 pdevinfo.name = HDMI_CODEC_DRV_NAME;
224 pdevinfo.data = &pdata;
225 pdevinfo.size_data = sizeof(pdata);
226 pdevinfo.dma_mask = DMA_BIT_MASK(32);
227
228 platform = platform_device_register_full(&pdevinfo);
229 if (IS_ERR(platform))
230 return PTR_ERR(platform);
231
232 dev_set_drvdata(&pdev->dev, platform);
233
234 return 0;
235 }
236
snd_dw_hdmi_remove(struct platform_device * pdev)237 static int snd_dw_hdmi_remove(struct platform_device *pdev)
238 {
239 struct platform_device *platform = dev_get_drvdata(&pdev->dev);
240
241 platform_device_unregister(platform);
242
243 return 0;
244 }
245
246 static struct platform_driver snd_dw_hdmi_driver = {
247 .probe = snd_dw_hdmi_probe,
248 .remove = snd_dw_hdmi_remove,
249 .driver = {
250 .name = DRIVER_NAME,
251 },
252 };
253 module_platform_driver(snd_dw_hdmi_driver);
254
255 MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
256 MODULE_DESCRIPTION("Synopsis Designware HDMI I2S ALSA SoC interface");
257 MODULE_LICENSE("GPL v2");
258 MODULE_ALIAS("platform:" DRIVER_NAME);
259