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