GNU Linux-libre 5.19-rc6-gnu
[releases.git] / drivers / gpu / drm / bridge / synopsys / dw-hdmi-gp-audio.c
1 // SPDX-License-Identifier: (GPL-2.0+ OR MIT)
2 /*
3  * dw-hdmi-gp-audio.c
4  *
5  * Copyright 2020-2022 NXP
6  */
7 #include <linux/io.h>
8 #include <linux/interrupt.h>
9 #include <linux/module.h>
10 #include <linux/platform_device.h>
11 #include <linux/dmaengine.h>
12 #include <linux/dma-mapping.h>
13 #include <drm/bridge/dw_hdmi.h>
14 #include <drm/drm_edid.h>
15 #include <drm/drm_connector.h>
16
17 #include <sound/hdmi-codec.h>
18 #include <sound/asoundef.h>
19 #include <sound/core.h>
20 #include <sound/initval.h>
21 #include <sound/pcm.h>
22 #include <sound/pcm_drm_eld.h>
23 #include <sound/pcm_iec958.h>
24 #include <sound/dmaengine_pcm.h>
25
26 #include "dw-hdmi-audio.h"
27
28 #define DRIVER_NAME "dw-hdmi-gp-audio"
29 #define DRV_NAME    "hdmi-gp-audio"
30
31 struct snd_dw_hdmi {
32         struct dw_hdmi_audio_data data;
33         struct platform_device  *audio_pdev;
34         unsigned int pos;
35 };
36
37 struct dw_hdmi_channel_conf {
38         u8 conf1;
39         u8 ca;
40 };
41
42 /*
43  * The default mapping of ALSA channels to HDMI channels and speaker
44  * allocation bits.  Note that we can't do channel remapping here -
45  * channels must be in the same order.
46  *
47  * Mappings for alsa-lib pcm/surround*.conf files:
48  *
49  *              Front   Sur4.0  Sur4.1  Sur5.0  Sur5.1  Sur7.1
50  * Channels     2       4       6       6       6       8
51  *
52  * Our mapping from ALSA channel to CEA686D speaker name and HDMI channel:
53  *
54  *                              Number of ALSA channels
55  * ALSA Channel 2       3       4       5       6       7       8
56  * 0            FL:0    =       =       =       =       =       =
57  * 1            FR:1    =       =       =       =       =       =
58  * 2                    FC:3    RL:4    LFE:2   =       =       =
59  * 3                            RR:5    RL:4    FC:3    =       =
60  * 4                                    RR:5    RL:4    =       =
61  * 5                                            RR:5    =       =
62  * 6                                                    RC:6    =
63  * 7                                                    RLC/FRC RLC/FRC
64  */
65 static struct dw_hdmi_channel_conf default_hdmi_channel_config[7] = {
66         { 0x03, 0x00 }, /* FL,FR */
67         { 0x0b, 0x02 }, /* FL,FR,FC */
68         { 0x33, 0x08 }, /* FL,FR,RL,RR */
69         { 0x37, 0x09 }, /* FL,FR,LFE,RL,RR */
70         { 0x3f, 0x0b }, /* FL,FR,LFE,FC,RL,RR */
71         { 0x7f, 0x0f }, /* FL,FR,LFE,FC,RL,RR,RC */
72         { 0xff, 0x13 }, /* FL,FR,LFE,FC,RL,RR,[FR]RC,[FR]LC */
73 };
74
75 static int audio_hw_params(struct device *dev,  void *data,
76                            struct hdmi_codec_daifmt *daifmt,
77                            struct hdmi_codec_params *params)
78 {
79         struct snd_dw_hdmi *dw = dev_get_drvdata(dev);
80         u8 ca;
81
82         dw_hdmi_set_sample_rate(dw->data.hdmi, params->sample_rate);
83
84         ca = default_hdmi_channel_config[params->channels - 2].ca;
85
86         dw_hdmi_set_channel_count(dw->data.hdmi, params->channels);
87         dw_hdmi_set_channel_allocation(dw->data.hdmi, ca);
88
89         dw_hdmi_set_sample_non_pcm(dw->data.hdmi,
90                                    params->iec.status[0] & IEC958_AES0_NONAUDIO);
91         dw_hdmi_set_sample_width(dw->data.hdmi, params->sample_width);
92
93         return 0;
94 }
95
96 static void audio_shutdown(struct device *dev, void *data)
97 {
98 }
99
100 static int audio_mute_stream(struct device *dev, void *data,
101                              bool enable, int direction)
102 {
103         struct snd_dw_hdmi *dw = dev_get_drvdata(dev);
104
105         if (!enable)
106                 dw_hdmi_audio_enable(dw->data.hdmi);
107         else
108                 dw_hdmi_audio_disable(dw->data.hdmi);
109
110         return 0;
111 }
112
113 static int audio_get_eld(struct device *dev, void *data,
114                          u8 *buf, size_t len)
115 {
116         struct dw_hdmi_audio_data *audio = data;
117         u8 *eld;
118
119         eld = audio->get_eld(audio->hdmi);
120         if (eld)
121                 memcpy(buf, eld, min_t(size_t, MAX_ELD_BYTES, len));
122         else
123                 /* Pass en empty ELD if connector not available */
124                 memset(buf, 0, len);
125
126         return 0;
127 }
128
129 static int audio_hook_plugged_cb(struct device *dev, void *data,
130                                  hdmi_codec_plugged_cb fn,
131                                  struct device *codec_dev)
132 {
133         struct snd_dw_hdmi *dw = dev_get_drvdata(dev);
134
135         return dw_hdmi_set_plugged_cb(dw->data.hdmi, fn, codec_dev);
136 }
137
138 static const struct hdmi_codec_ops audio_codec_ops = {
139         .hw_params = audio_hw_params,
140         .audio_shutdown = audio_shutdown,
141         .mute_stream = audio_mute_stream,
142         .get_eld = audio_get_eld,
143         .hook_plugged_cb = audio_hook_plugged_cb,
144 };
145
146 static int snd_dw_hdmi_probe(struct platform_device *pdev)
147 {
148         struct dw_hdmi_audio_data *data = pdev->dev.platform_data;
149         struct snd_dw_hdmi *dw;
150
151         const struct hdmi_codec_pdata codec_data = {
152                 .i2s = 1,
153                 .spdif = 0,
154                 .ops = &audio_codec_ops,
155                 .max_i2s_channels = 8,
156                 .data = data,
157         };
158
159         dw = devm_kzalloc(&pdev->dev, sizeof(*dw), GFP_KERNEL);
160         if (!dw)
161                 return -ENOMEM;
162
163         dw->data = *data;
164
165         platform_set_drvdata(pdev, dw);
166
167         dw->audio_pdev = platform_device_register_data(&pdev->dev,
168                                                        HDMI_CODEC_DRV_NAME, 1,
169                                                        &codec_data,
170                                                        sizeof(codec_data));
171
172         return PTR_ERR_OR_ZERO(dw->audio_pdev);
173 }
174
175 static int snd_dw_hdmi_remove(struct platform_device *pdev)
176 {
177         struct snd_dw_hdmi *dw = platform_get_drvdata(pdev);
178
179         platform_device_unregister(dw->audio_pdev);
180
181         return 0;
182 }
183
184 static struct platform_driver snd_dw_hdmi_driver = {
185         .probe  = snd_dw_hdmi_probe,
186         .remove = snd_dw_hdmi_remove,
187         .driver = {
188                 .name = DRIVER_NAME,
189         },
190 };
191
192 module_platform_driver(snd_dw_hdmi_driver);
193
194 MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>");
195 MODULE_DESCRIPTION("Synopsys Designware HDMI GPA ALSA interface");
196 MODULE_LICENSE("GPL");
197 MODULE_ALIAS("platform:" DRIVER_NAME);