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
drm_connector_hdmi_audio_startup(struct device * dev,void * data)17 static 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
drm_connector_hdmi_audio_prepare(struct device * dev,void * data,struct hdmi_codec_daifmt * fmt,struct hdmi_codec_params * hparms)29 static 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
drm_connector_hdmi_audio_shutdown(struct device * dev,void * data)40 static 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
drm_connector_hdmi_audio_mute_stream(struct device * dev,void * data,bool enable,int direction)49 static 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
drm_connector_hdmi_audio_get_dai_id(struct snd_soc_component * comment,struct device_node * endpoint,void * data)62 static 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
drm_connector_hdmi_audio_get_eld(struct device * dev,void * data,uint8_t * buf,size_t len)83 static 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
drm_connector_hdmi_audio_hook_plugged_cb(struct device * dev,void * data,hdmi_codec_plugged_cb fn,struct device * codec_dev)95 static 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
drm_connector_hdmi_audio_plugged_notify(struct drm_connector * connector,bool plugged)115 void 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 }
129 EXPORT_SYMBOL(drm_connector_hdmi_audio_plugged_notify);
130
131 static 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 */
drm_connector_hdmi_audio_init(struct drm_connector * connector,struct device * hdmi_codec_dev,const struct drm_connector_hdmi_audio_funcs * funcs,unsigned int max_i2s_playback_channels,u64 i2s_formats,bool spdif_playback,int dai_port)156 int 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 }
195 EXPORT_SYMBOL(drm_connector_hdmi_audio_init);
196