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
31struct snd_dw_hdmi {
32 struct dw_hdmi_audio_data data;
33 struct platform_device *audio_pdev;
34 unsigned int pos;
35};
36
37struct 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 */
65static 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
75static 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(hdmi: dw->data.hdmi, rate: params->sample_rate);
83
84 ca = default_hdmi_channel_config[params->channels - 2].ca;
85
86 dw_hdmi_set_channel_count(hdmi: dw->data.hdmi, cnt: params->channels);
87 dw_hdmi_set_channel_allocation(hdmi: dw->data.hdmi, ca);
88
89 dw_hdmi_set_sample_non_pcm(hdmi: dw->data.hdmi,
90 non_pcm: params->iec.status[0] & IEC958_AES0_NONAUDIO);
91 dw_hdmi_set_sample_width(hdmi: dw->data.hdmi, width: params->sample_width);
92
93 return 0;
94}
95
96static void audio_shutdown(struct device *dev, void *data)
97{
98}
99
100static 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(hdmi: dw->data.hdmi);
107 else
108 dw_hdmi_audio_disable(hdmi: dw->data.hdmi);
109
110 return 0;
111}
112
113static 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
129static 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(hdmi: dw->data.hdmi, fn, codec_dev);
136}
137
138static 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
146static 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(dev: &pdev->dev, size: sizeof(*dw), GFP_KERNEL);
160 if (!dw)
161 return -ENOMEM;
162
163 dw->data = *data;
164
165 platform_set_drvdata(pdev, data: dw);
166
167 dw->audio_pdev = platform_device_register_data(parent: &pdev->dev,
168 HDMI_CODEC_DRV_NAME, id: 1,
169 data: &codec_data,
170 size: sizeof(codec_data));
171
172 return PTR_ERR_OR_ZERO(ptr: dw->audio_pdev);
173}
174
175static void 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
182static struct platform_driver snd_dw_hdmi_driver = {
183 .probe = snd_dw_hdmi_probe,
184 .remove_new = snd_dw_hdmi_remove,
185 .driver = {
186 .name = DRIVER_NAME,
187 },
188};
189
190module_platform_driver(snd_dw_hdmi_driver);
191
192MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>");
193MODULE_DESCRIPTION("Synopsys Designware HDMI GPA ALSA interface");
194MODULE_LICENSE("GPL");
195MODULE_ALIAS("platform:" DRIVER_NAME);
196

source code of linux/drivers/gpu/drm/bridge/synopsys/dw-hdmi-gp-audio.c