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 | |
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 */ |
48 | if (fmt->bit_clk_provider | fmt->frame_clk_provider) { |
49 | dev_err(dev, "unsupported clock settings\n" ); |
50 | return -EINVAL; |
51 | } |
52 | |
53 | /* Reset the FIFOs before applying new params */ |
54 | hdmi_write(audio, val: HDMI_AUD_CONF0_SW_RESET, HDMI_AUD_CONF0); |
55 | hdmi_write(audio, val: (u8)~HDMI_MC_SWRSTZ_I2SSWRST_REQ, HDMI_MC_SWRSTZ); |
56 | |
57 | inputclkfs = HDMI_AUD_INPUTCLKFS_64FS; |
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; |
64 | fallthrough; |
65 | case 5 ... 6: |
66 | conf0 |= HDMI_AUD_CONF0_I2S_EN2; |
67 | fallthrough; |
68 | case 3 ... 4: |
69 | conf0 |= HDMI_AUD_CONF0_I2S_EN1; |
70 | /* Fall-thru */ |
71 | } |
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 | |
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 | |
104 | dw_hdmi_set_sample_rate(hdmi, rate: hparms->sample_rate); |
105 | dw_hdmi_set_channel_status(hdmi, channel_status: hparms->iec.status); |
106 | dw_hdmi_set_channel_count(hdmi, cnt: hparms->channels); |
107 | dw_hdmi_set_channel_allocation(hdmi, ca: hparms->cea.channel_allocation); |
108 | |
109 | hdmi_write(audio, val: inputclkfs, HDMI_AUD_INPUTCLKFS); |
110 | hdmi_write(audio, val: conf0, HDMI_AUD_CONF0); |
111 | hdmi_write(audio, val: conf1, HDMI_AUD_CONF1); |
112 | |
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 | |
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); |
132 | } |
133 | |
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; |
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); |
146 | |
147 | return 0; |
148 | } |
149 | |
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(node: 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 | |
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 | |
180 | static const struct hdmi_codec_ops dw_hdmi_i2s_ops = { |
181 | .hw_params = dw_hdmi_i2s_hw_params, |
182 | .audio_startup = dw_hdmi_i2s_audio_startup, |
183 | .audio_shutdown = dw_hdmi_i2s_audio_shutdown, |
184 | .get_eld = dw_hdmi_i2s_get_eld, |
185 | .get_dai_id = dw_hdmi_i2s_get_dai_id, |
186 | .hook_plugged_cb = dw_hdmi_i2s_hook_plugged_cb, |
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 | |
196 | memset(&pdata, 0, sizeof(pdata)); |
197 | pdata.ops = &dw_hdmi_i2s_ops; |
198 | pdata.i2s = 1; |
199 | pdata.max_i2s_channels = 8; |
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: &pdevinfo); |
211 | if (IS_ERR(ptr: platform)) |
212 | return PTR_ERR(ptr: platform); |
213 | |
214 | dev_set_drvdata(dev: &pdev->dev, data: platform); |
215 | |
216 | return 0; |
217 | } |
218 | |
219 | static void snd_dw_hdmi_remove(struct platform_device *pdev) |
220 | { |
221 | struct platform_device *platform = dev_get_drvdata(dev: &pdev->dev); |
222 | |
223 | platform_device_unregister(platform); |
224 | } |
225 | |
226 | static struct platform_driver snd_dw_hdmi_driver = { |
227 | .probe = snd_dw_hdmi_probe, |
228 | .remove_new = snd_dw_hdmi_remove, |
229 | .driver = { |
230 | .name = DRIVER_NAME, |
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); |
239 | |