Commit | Line | Data |
---|---|---|
199d035b | 1 | // SPDX-License-Identifier: GPL-2.0 |
2761ba6c KM |
2 | /* |
3 | * dw-hdmi-i2s-audio.c | |
4 | * | |
81aa368c KM |
5 | * Copyright (c) 2017 Renesas Solutions Corp. |
6 | * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> | |
2761ba6c | 7 | */ |
428747ae SR |
8 | |
9 | #include <linux/dma-mapping.h> | |
10 | #include <linux/module.h> | |
11 | ||
2761ba6c | 12 | #include <drm/bridge/dw_hdmi.h> |
fc1ca6e0 | 13 | #include <drm/drm_crtc.h> |
2761ba6c KM |
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 | ||
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 | ||
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 | ||
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 inputclkfs = 0; | |
46 | ||
47 | /* it cares I2S only */ | |
9f1c8677 | 48 | if (fmt->bit_clk_provider | fmt->frame_clk_provider) { |
8067f62b | 49 | dev_err(dev, "unsupported clock settings\n"); |
2761ba6c KM |
50 | return -EINVAL; |
51 | } | |
52 | ||
46cecde3 JB |
53 | /* Reset the FIFOs before applying new params */ |
54 | hdmi_write(audio, HDMI_AUD_CONF0_SW_RESET, HDMI_AUD_CONF0); | |
55 | hdmi_write(audio, (u8)~HDMI_MC_SWRSTZ_I2SSWRST_REQ, HDMI_MC_SWRSTZ); | |
56 | ||
2761ba6c | 57 | inputclkfs = HDMI_AUD_INPUTCLKFS_64FS; |
43e88f67 JB |
58 | conf0 = (HDMI_AUD_CONF0_I2S_SELECT | HDMI_AUD_CONF0_I2S_EN0); |
59 | ||
60 | /* Enable the required i2s lanes */ | |
61 | switch (hparms->channels) { | |
62 | case 7 ... 8: | |
63 | conf0 |= HDMI_AUD_CONF0_I2S_EN3; | |
df561f66 | 64 | fallthrough; |
43e88f67 JB |
65 | case 5 ... 6: |
66 | conf0 |= HDMI_AUD_CONF0_I2S_EN2; | |
df561f66 | 67 | fallthrough; |
43e88f67 JB |
68 | case 3 ... 4: |
69 | conf0 |= HDMI_AUD_CONF0_I2S_EN1; | |
70 | /* Fall-thru */ | |
71 | } | |
2761ba6c KM |
72 | |
73 | switch (hparms->sample_width) { | |
74 | case 16: | |
75 | conf1 = HDMI_AUD_CONF1_WIDTH_16; | |
76 | break; | |
77 | case 24: | |
78 | case 32: | |
79 | conf1 = HDMI_AUD_CONF1_WIDTH_24; | |
80 | break; | |
81 | } | |
82 | ||
8067f62b JB |
83 | switch (fmt->fmt) { |
84 | case HDMI_I2S: | |
85 | conf1 |= HDMI_AUD_CONF1_MODE_I2S; | |
86 | break; | |
87 | case HDMI_RIGHT_J: | |
88 | conf1 |= HDMI_AUD_CONF1_MODE_RIGHT_J; | |
89 | break; | |
90 | case HDMI_LEFT_J: | |
91 | conf1 |= HDMI_AUD_CONF1_MODE_LEFT_J; | |
92 | break; | |
93 | case HDMI_DSP_A: | |
94 | conf1 |= HDMI_AUD_CONF1_MODE_BURST_1; | |
95 | break; | |
96 | case HDMI_DSP_B: | |
97 | conf1 |= HDMI_AUD_CONF1_MODE_BURST_2; | |
98 | break; | |
99 | default: | |
100 | dev_err(dev, "unsupported format\n"); | |
101 | return -EINVAL; | |
102 | } | |
103 | ||
2761ba6c | 104 | dw_hdmi_set_sample_rate(hdmi, hparms->sample_rate); |
3250cdf9 | 105 | dw_hdmi_set_channel_status(hdmi, hparms->iec.status); |
17a1e555 | 106 | dw_hdmi_set_channel_count(hdmi, hparms->channels); |
0c609885 | 107 | dw_hdmi_set_channel_allocation(hdmi, hparms->cea.channel_allocation); |
2761ba6c KM |
108 | |
109 | hdmi_write(audio, inputclkfs, HDMI_AUD_INPUTCLKFS); | |
110 | hdmi_write(audio, conf0, HDMI_AUD_CONF0); | |
111 | hdmi_write(audio, conf1, HDMI_AUD_CONF1); | |
112 | ||
c41784b0 CYC |
113 | return 0; |
114 | } | |
115 | ||
116 | static int dw_hdmi_i2s_audio_startup(struct device *dev, void *data) | |
117 | { | |
118 | struct dw_hdmi_i2s_audio_data *audio = data; | |
119 | struct dw_hdmi *hdmi = audio->hdmi; | |
120 | ||
2761ba6c KM |
121 | dw_hdmi_audio_enable(hdmi); |
122 | ||
123 | return 0; | |
124 | } | |
125 | ||
126 | static void dw_hdmi_i2s_audio_shutdown(struct device *dev, void *data) | |
127 | { | |
128 | struct dw_hdmi_i2s_audio_data *audio = data; | |
129 | struct dw_hdmi *hdmi = audio->hdmi; | |
130 | ||
131 | dw_hdmi_audio_disable(hdmi); | |
2761ba6c KM |
132 | } |
133 | ||
fc1ca6e0 JB |
134 | static int dw_hdmi_i2s_get_eld(struct device *dev, void *data, uint8_t *buf, |
135 | size_t len) | |
136 | { | |
137 | struct dw_hdmi_i2s_audio_data *audio = data; | |
3f2532d6 NA |
138 | u8 *eld; |
139 | ||
140 | eld = audio->get_eld(audio->hdmi); | |
141 | if (eld) | |
142 | memcpy(buf, eld, min_t(size_t, MAX_ELD_BYTES, len)); | |
143 | else | |
144 | /* Pass en empty ELD if connector not available */ | |
145 | memset(buf, 0, len); | |
fc1ca6e0 | 146 | |
fc1ca6e0 JB |
147 | return 0; |
148 | } | |
149 | ||
e3839bd6 KM |
150 | static int dw_hdmi_i2s_get_dai_id(struct snd_soc_component *component, |
151 | struct device_node *endpoint) | |
152 | { | |
153 | struct of_endpoint of_ep; | |
154 | int ret; | |
155 | ||
156 | ret = of_graph_parse_endpoint(endpoint, &of_ep); | |
157 | if (ret < 0) | |
158 | return ret; | |
159 | ||
160 | /* | |
161 | * HDMI sound should be located as reg = <2> | |
162 | * Then, it is sound port 0 | |
163 | */ | |
164 | if (of_ep.port == 2) | |
165 | return 0; | |
166 | ||
167 | return -EINVAL; | |
168 | } | |
169 | ||
a9c82d63 CYC |
170 | static int dw_hdmi_i2s_hook_plugged_cb(struct device *dev, void *data, |
171 | hdmi_codec_plugged_cb fn, | |
172 | struct device *codec_dev) | |
173 | { | |
174 | struct dw_hdmi_i2s_audio_data *audio = data; | |
175 | struct dw_hdmi *hdmi = audio->hdmi; | |
176 | ||
177 | return dw_hdmi_set_plugged_cb(hdmi, fn, codec_dev); | |
178 | } | |
179 | ||
f3d52908 | 180 | static const struct hdmi_codec_ops dw_hdmi_i2s_ops = { |
2761ba6c | 181 | .hw_params = dw_hdmi_i2s_hw_params, |
c41784b0 | 182 | .audio_startup = dw_hdmi_i2s_audio_startup, |
2761ba6c | 183 | .audio_shutdown = dw_hdmi_i2s_audio_shutdown, |
fc1ca6e0 | 184 | .get_eld = dw_hdmi_i2s_get_eld, |
e3839bd6 | 185 | .get_dai_id = dw_hdmi_i2s_get_dai_id, |
a9c82d63 | 186 | .hook_plugged_cb = dw_hdmi_i2s_hook_plugged_cb, |
2761ba6c KM |
187 | }; |
188 | ||
189 | static int snd_dw_hdmi_probe(struct platform_device *pdev) | |
190 | { | |
191 | struct dw_hdmi_i2s_audio_data *audio = pdev->dev.platform_data; | |
192 | struct platform_device_info pdevinfo; | |
193 | struct hdmi_codec_pdata pdata; | |
194 | struct platform_device *platform; | |
195 | ||
54650eb1 | 196 | memset(&pdata, 0, sizeof(pdata)); |
2761ba6c KM |
197 | pdata.ops = &dw_hdmi_i2s_ops; |
198 | pdata.i2s = 1; | |
17a1e555 | 199 | pdata.max_i2s_channels = 8; |
2761ba6c KM |
200 | pdata.data = audio; |
201 | ||
202 | memset(&pdevinfo, 0, sizeof(pdevinfo)); | |
203 | pdevinfo.parent = pdev->dev.parent; | |
204 | pdevinfo.id = PLATFORM_DEVID_AUTO; | |
205 | pdevinfo.name = HDMI_CODEC_DRV_NAME; | |
206 | pdevinfo.data = &pdata; | |
207 | pdevinfo.size_data = sizeof(pdata); | |
208 | pdevinfo.dma_mask = DMA_BIT_MASK(32); | |
209 | ||
210 | platform = platform_device_register_full(&pdevinfo); | |
211 | if (IS_ERR(platform)) | |
212 | return PTR_ERR(platform); | |
213 | ||
214 | dev_set_drvdata(&pdev->dev, platform); | |
215 | ||
216 | return 0; | |
217 | } | |
218 | ||
ed58ee12 | 219 | static void snd_dw_hdmi_remove(struct platform_device *pdev) |
2761ba6c KM |
220 | { |
221 | struct platform_device *platform = dev_get_drvdata(&pdev->dev); | |
222 | ||
223 | platform_device_unregister(platform); | |
2761ba6c KM |
224 | } |
225 | ||
226 | static struct platform_driver snd_dw_hdmi_driver = { | |
227 | .probe = snd_dw_hdmi_probe, | |
ed58ee12 | 228 | .remove_new = snd_dw_hdmi_remove, |
2761ba6c KM |
229 | .driver = { |
230 | .name = DRIVER_NAME, | |
2761ba6c KM |
231 | }, |
232 | }; | |
233 | module_platform_driver(snd_dw_hdmi_driver); | |
234 | ||
235 | MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); | |
236 | MODULE_DESCRIPTION("Synopsis Designware HDMI I2S ALSA SoC interface"); | |
237 | MODULE_LICENSE("GPL v2"); | |
238 | MODULE_ALIAS("platform:" DRIVER_NAME); |