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 */ | |
8067f62b JB |
48 | if (fmt->bit_clk_master | fmt->frame_clk_master) { |
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; | |
138 | ||
139 | memcpy(buf, audio->eld, min_t(size_t, MAX_ELD_BYTES, len)); | |
140 | return 0; | |
141 | } | |
142 | ||
e3839bd6 KM |
143 | static int dw_hdmi_i2s_get_dai_id(struct snd_soc_component *component, |
144 | struct device_node *endpoint) | |
145 | { | |
146 | struct of_endpoint of_ep; | |
147 | int ret; | |
148 | ||
149 | ret = of_graph_parse_endpoint(endpoint, &of_ep); | |
150 | if (ret < 0) | |
151 | return ret; | |
152 | ||
153 | /* | |
154 | * HDMI sound should be located as reg = <2> | |
155 | * Then, it is sound port 0 | |
156 | */ | |
157 | if (of_ep.port == 2) | |
158 | return 0; | |
159 | ||
160 | return -EINVAL; | |
161 | } | |
162 | ||
a9c82d63 CYC |
163 | static int dw_hdmi_i2s_hook_plugged_cb(struct device *dev, void *data, |
164 | hdmi_codec_plugged_cb fn, | |
165 | struct device *codec_dev) | |
166 | { | |
167 | struct dw_hdmi_i2s_audio_data *audio = data; | |
168 | struct dw_hdmi *hdmi = audio->hdmi; | |
169 | ||
170 | return dw_hdmi_set_plugged_cb(hdmi, fn, codec_dev); | |
171 | } | |
172 | ||
2761ba6c KM |
173 | static struct hdmi_codec_ops dw_hdmi_i2s_ops = { |
174 | .hw_params = dw_hdmi_i2s_hw_params, | |
c41784b0 | 175 | .audio_startup = dw_hdmi_i2s_audio_startup, |
2761ba6c | 176 | .audio_shutdown = dw_hdmi_i2s_audio_shutdown, |
fc1ca6e0 | 177 | .get_eld = dw_hdmi_i2s_get_eld, |
e3839bd6 | 178 | .get_dai_id = dw_hdmi_i2s_get_dai_id, |
a9c82d63 | 179 | .hook_plugged_cb = dw_hdmi_i2s_hook_plugged_cb, |
2761ba6c KM |
180 | }; |
181 | ||
182 | static int snd_dw_hdmi_probe(struct platform_device *pdev) | |
183 | { | |
184 | struct dw_hdmi_i2s_audio_data *audio = pdev->dev.platform_data; | |
185 | struct platform_device_info pdevinfo; | |
186 | struct hdmi_codec_pdata pdata; | |
187 | struct platform_device *platform; | |
188 | ||
189 | pdata.ops = &dw_hdmi_i2s_ops; | |
190 | pdata.i2s = 1; | |
17a1e555 | 191 | pdata.max_i2s_channels = 8; |
2761ba6c KM |
192 | pdata.data = audio; |
193 | ||
194 | memset(&pdevinfo, 0, sizeof(pdevinfo)); | |
195 | pdevinfo.parent = pdev->dev.parent; | |
196 | pdevinfo.id = PLATFORM_DEVID_AUTO; | |
197 | pdevinfo.name = HDMI_CODEC_DRV_NAME; | |
198 | pdevinfo.data = &pdata; | |
199 | pdevinfo.size_data = sizeof(pdata); | |
200 | pdevinfo.dma_mask = DMA_BIT_MASK(32); | |
201 | ||
202 | platform = platform_device_register_full(&pdevinfo); | |
203 | if (IS_ERR(platform)) | |
204 | return PTR_ERR(platform); | |
205 | ||
206 | dev_set_drvdata(&pdev->dev, platform); | |
207 | ||
208 | return 0; | |
209 | } | |
210 | ||
211 | static int snd_dw_hdmi_remove(struct platform_device *pdev) | |
212 | { | |
213 | struct platform_device *platform = dev_get_drvdata(&pdev->dev); | |
214 | ||
215 | platform_device_unregister(platform); | |
216 | ||
217 | return 0; | |
218 | } | |
219 | ||
220 | static struct platform_driver snd_dw_hdmi_driver = { | |
221 | .probe = snd_dw_hdmi_probe, | |
222 | .remove = snd_dw_hdmi_remove, | |
223 | .driver = { | |
224 | .name = DRIVER_NAME, | |
2761ba6c KM |
225 | }, |
226 | }; | |
227 | module_platform_driver(snd_dw_hdmi_driver); | |
228 | ||
229 | MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); | |
230 | MODULE_DESCRIPTION("Synopsis Designware HDMI I2S ALSA SoC interface"); | |
231 | MODULE_LICENSE("GPL v2"); | |
232 | MODULE_ALIAS("platform:" DRIVER_NAME); |