xref: /linux/drivers/gpu/drm/display/drm_hdmi_audio_helper.c (revision e9ef810dfee7a2227da9d423aecb0ced35faddbe)
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