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