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; | |
64 | /* Fall-thru */ | |
65 | case 5 ... 6: | |
66 | conf0 |= HDMI_AUD_CONF0_I2S_EN2; | |
67 | /* Fall-thru */ | |
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); |
17a1e555 | 105 | dw_hdmi_set_channel_count(hdmi, hparms->channels); |
0c609885 | 106 | dw_hdmi_set_channel_allocation(hdmi, hparms->cea.channel_allocation); |
2761ba6c KM |
107 | |
108 | hdmi_write(audio, inputclkfs, HDMI_AUD_INPUTCLKFS); | |
109 | hdmi_write(audio, conf0, HDMI_AUD_CONF0); | |
110 | hdmi_write(audio, conf1, HDMI_AUD_CONF1); | |
111 | ||
112 | dw_hdmi_audio_enable(hdmi); | |
113 | ||
114 | return 0; | |
115 | } | |
116 | ||
117 | static void dw_hdmi_i2s_audio_shutdown(struct device *dev, void *data) | |
118 | { | |
119 | struct dw_hdmi_i2s_audio_data *audio = data; | |
120 | struct dw_hdmi *hdmi = audio->hdmi; | |
121 | ||
122 | dw_hdmi_audio_disable(hdmi); | |
2761ba6c KM |
123 | } |
124 | ||
fc1ca6e0 JB |
125 | static int dw_hdmi_i2s_get_eld(struct device *dev, void *data, uint8_t *buf, |
126 | size_t len) | |
127 | { | |
128 | struct dw_hdmi_i2s_audio_data *audio = data; | |
129 | ||
130 | memcpy(buf, audio->eld, min_t(size_t, MAX_ELD_BYTES, len)); | |
131 | return 0; | |
132 | } | |
133 | ||
e3839bd6 KM |
134 | static int dw_hdmi_i2s_get_dai_id(struct snd_soc_component *component, |
135 | struct device_node *endpoint) | |
136 | { | |
137 | struct of_endpoint of_ep; | |
138 | int ret; | |
139 | ||
140 | ret = of_graph_parse_endpoint(endpoint, &of_ep); | |
141 | if (ret < 0) | |
142 | return ret; | |
143 | ||
144 | /* | |
145 | * HDMI sound should be located as reg = <2> | |
146 | * Then, it is sound port 0 | |
147 | */ | |
148 | if (of_ep.port == 2) | |
149 | return 0; | |
150 | ||
151 | return -EINVAL; | |
152 | } | |
153 | ||
a9c82d63 CYC |
154 | static int dw_hdmi_i2s_hook_plugged_cb(struct device *dev, void *data, |
155 | hdmi_codec_plugged_cb fn, | |
156 | struct device *codec_dev) | |
157 | { | |
158 | struct dw_hdmi_i2s_audio_data *audio = data; | |
159 | struct dw_hdmi *hdmi = audio->hdmi; | |
160 | ||
161 | return dw_hdmi_set_plugged_cb(hdmi, fn, codec_dev); | |
162 | } | |
163 | ||
2761ba6c KM |
164 | static struct hdmi_codec_ops dw_hdmi_i2s_ops = { |
165 | .hw_params = dw_hdmi_i2s_hw_params, | |
166 | .audio_shutdown = dw_hdmi_i2s_audio_shutdown, | |
fc1ca6e0 | 167 | .get_eld = dw_hdmi_i2s_get_eld, |
e3839bd6 | 168 | .get_dai_id = dw_hdmi_i2s_get_dai_id, |
a9c82d63 | 169 | .hook_plugged_cb = dw_hdmi_i2s_hook_plugged_cb, |
2761ba6c KM |
170 | }; |
171 | ||
172 | static int snd_dw_hdmi_probe(struct platform_device *pdev) | |
173 | { | |
174 | struct dw_hdmi_i2s_audio_data *audio = pdev->dev.platform_data; | |
175 | struct platform_device_info pdevinfo; | |
176 | struct hdmi_codec_pdata pdata; | |
177 | struct platform_device *platform; | |
178 | ||
179 | pdata.ops = &dw_hdmi_i2s_ops; | |
180 | pdata.i2s = 1; | |
17a1e555 | 181 | pdata.max_i2s_channels = 8; |
2761ba6c KM |
182 | pdata.data = audio; |
183 | ||
184 | memset(&pdevinfo, 0, sizeof(pdevinfo)); | |
185 | pdevinfo.parent = pdev->dev.parent; | |
186 | pdevinfo.id = PLATFORM_DEVID_AUTO; | |
187 | pdevinfo.name = HDMI_CODEC_DRV_NAME; | |
188 | pdevinfo.data = &pdata; | |
189 | pdevinfo.size_data = sizeof(pdata); | |
190 | pdevinfo.dma_mask = DMA_BIT_MASK(32); | |
191 | ||
192 | platform = platform_device_register_full(&pdevinfo); | |
193 | if (IS_ERR(platform)) | |
194 | return PTR_ERR(platform); | |
195 | ||
196 | dev_set_drvdata(&pdev->dev, platform); | |
197 | ||
198 | return 0; | |
199 | } | |
200 | ||
201 | static int snd_dw_hdmi_remove(struct platform_device *pdev) | |
202 | { | |
203 | struct platform_device *platform = dev_get_drvdata(&pdev->dev); | |
204 | ||
205 | platform_device_unregister(platform); | |
206 | ||
207 | return 0; | |
208 | } | |
209 | ||
210 | static struct platform_driver snd_dw_hdmi_driver = { | |
211 | .probe = snd_dw_hdmi_probe, | |
212 | .remove = snd_dw_hdmi_remove, | |
213 | .driver = { | |
214 | .name = DRIVER_NAME, | |
2761ba6c KM |
215 | }, |
216 | }; | |
217 | module_platform_driver(snd_dw_hdmi_driver); | |
218 | ||
219 | MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); | |
220 | MODULE_DESCRIPTION("Synopsis Designware HDMI I2S ALSA SoC interface"); | |
221 | MODULE_LICENSE("GPL v2"); | |
222 | MODULE_ALIAS("platform:" DRIVER_NAME); |