Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: MIT
2/*
3 * Copyright (c) 2024 Linaro Ltd
4 */
5
6#include <linux/export.h>
7#include <linux/mutex.h>
8#include <linux/of_graph.h>
9#include <linux/platform_device.h>
10
11#include <drm/drm_connector.h>
12#include <drm/drm_device.h>
13#include <drm/display/drm_hdmi_audio_helper.h>
14
15#include <sound/hdmi-codec.h>
16
17static int drm_connector_hdmi_audio_startup(struct device *dev, void *data)
18{
19 struct drm_connector *connector = data;
20 const struct drm_connector_hdmi_audio_funcs *funcs =
21 connector->hdmi_audio.funcs;
22
23 if (funcs->startup)
24 return funcs->startup(connector);
25
26 return 0;
27}
28
29static int drm_connector_hdmi_audio_prepare(struct device *dev, void *data,
30 struct hdmi_codec_daifmt *fmt,
31 struct hdmi_codec_params *hparms)
32{
33 struct drm_connector *connector = data;
34 const struct drm_connector_hdmi_audio_funcs *funcs =
35 connector->hdmi_audio.funcs;
36
37 return funcs->prepare(connector, fmt, hparms);
38}
39
40static void drm_connector_hdmi_audio_shutdown(struct device *dev, void *data)
41{
42 struct drm_connector *connector = data;
43 const struct drm_connector_hdmi_audio_funcs *funcs =
44 connector->hdmi_audio.funcs;
45
46 return funcs->shutdown(connector);
47}
48
49static int drm_connector_hdmi_audio_mute_stream(struct device *dev, void *data,
50 bool enable, int direction)
51{
52 struct drm_connector *connector = data;
53 const struct drm_connector_hdmi_audio_funcs *funcs =
54 connector->hdmi_audio.funcs;
55
56 if (funcs->mute_stream)
57 return funcs->mute_stream(connector, enable, direction);
58
59 return -ENOTSUPP;
60}
61
62static int drm_connector_hdmi_audio_get_dai_id(struct snd_soc_component *comment,
63 struct device_node *endpoint,
64 void *data)
65{
66 struct drm_connector *connector = data;
67 struct of_endpoint of_ep;
68 int ret;
69
70 if (connector->hdmi_audio.dai_port < 0)
71 return -ENOTSUPP;
72
73 ret = of_graph_parse_endpoint(endpoint, &of_ep);
74 if (ret < 0)
75 return ret;
76
77 if (of_ep.port == connector->hdmi_audio.dai_port)
78 return 0;
79
80 return -EINVAL;
81}
82
83static int drm_connector_hdmi_audio_get_eld(struct device *dev, void *data,
84 uint8_t *buf, size_t len)
85{
86 struct drm_connector *connector = data;
87
88 mutex_lock(&connector->eld_mutex);
89 memcpy(buf, connector->eld, min(sizeof(connector->eld), len));
90 mutex_unlock(&connector->eld_mutex);
91
92 return 0;
93}
94
95static int drm_connector_hdmi_audio_hook_plugged_cb(struct device *dev,
96 void *data,
97 hdmi_codec_plugged_cb fn,
98 struct device *codec_dev)
99{
100 struct drm_connector *connector = data;
101
102 mutex_lock(&connector->hdmi_audio.lock);
103
104 connector->hdmi_audio.plugged_cb = fn;
105 connector->hdmi_audio.plugged_cb_dev = codec_dev;
106
107 if (fn)
108 fn(codec_dev, connector->hdmi_audio.last_state);
109
110 mutex_unlock(&connector->hdmi_audio.lock);
111
112 return 0;
113}
114
115void drm_connector_hdmi_audio_plugged_notify(struct drm_connector *connector,
116 bool plugged)
117{
118 mutex_lock(&connector->hdmi_audio.lock);
119
120 connector->hdmi_audio.last_state = plugged;
121
122 if (connector->hdmi_audio.plugged_cb &&
123 connector->hdmi_audio.plugged_cb_dev)
124 connector->hdmi_audio.plugged_cb(connector->hdmi_audio.plugged_cb_dev,
125 connector->hdmi_audio.last_state);
126
127 mutex_unlock(&connector->hdmi_audio.lock);
128}
129EXPORT_SYMBOL(drm_connector_hdmi_audio_plugged_notify);
130
131static const struct hdmi_codec_ops drm_connector_hdmi_audio_ops = {
132 .audio_startup = drm_connector_hdmi_audio_startup,
133 .prepare = drm_connector_hdmi_audio_prepare,
134 .audio_shutdown = drm_connector_hdmi_audio_shutdown,
135 .mute_stream = drm_connector_hdmi_audio_mute_stream,
136 .get_eld = drm_connector_hdmi_audio_get_eld,
137 .get_dai_id = drm_connector_hdmi_audio_get_dai_id,
138 .hook_plugged_cb = drm_connector_hdmi_audio_hook_plugged_cb,
139};
140
141/**
142 * drm_connector_hdmi_audio_init - Initialize HDMI Codec device for the DRM connector
143 * @connector: A pointer to the connector to allocate codec for
144 * @hdmi_codec_dev: device to be used as a parent for the HDMI Codec
145 * @funcs: callbacks for this HDMI Codec
146 * @max_i2s_playback_channels: maximum number of playback I2S channels
147 * @i2s_formats: set of I2S formats (use 0 for a bus-specific set)
148 * @spdif_playback: set if HDMI codec has S/PDIF playback port
149 * @dai_port: sound DAI port, -1 if it is not enabled
150 *
151 * Create a HDMI codec device to be used with the specified connector.
152 *
153 * Returns:
154 * Zero on success, error code on failure.
155 */
156int drm_connector_hdmi_audio_init(struct drm_connector *connector,
157 struct device *hdmi_codec_dev,
158 const struct drm_connector_hdmi_audio_funcs *funcs,
159 unsigned int max_i2s_playback_channels,
160 u64 i2s_formats,
161 bool spdif_playback,
162 int dai_port)
163{
164 struct hdmi_codec_pdata codec_pdata = {
165 .ops = &drm_connector_hdmi_audio_ops,
166 .max_i2s_channels = max_i2s_playback_channels,
167 .i2s = !!max_i2s_playback_channels,
168 .i2s_formats = i2s_formats,
169 .spdif = spdif_playback,
170 .no_i2s_capture = true,
171 .no_spdif_capture = true,
172 .data = connector,
173 };
174 struct platform_device *pdev;
175
176 if (!funcs ||
177 !funcs->prepare ||
178 !funcs->shutdown)
179 return -EINVAL;
180
181 connector->hdmi_audio.funcs = funcs;
182 connector->hdmi_audio.dai_port = dai_port;
183
184 pdev = platform_device_register_data(hdmi_codec_dev,
185 HDMI_CODEC_DRV_NAME,
186 PLATFORM_DEVID_AUTO,
187 &codec_pdata, sizeof(codec_pdata));
188 if (IS_ERR(pdev))
189 return PTR_ERR(pdev);
190
191 connector->hdmi_audio.codec_pdev = pdev;
192
193 return 0;
194}
195EXPORT_SYMBOL(drm_connector_hdmi_audio_init);