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 if (fn)
107 fn(codec_dev, connector->hdmi_audio.last_state);
108
109 mutex_unlock(&connector->hdmi_audio.lock);
110
111 return 0;
112 }
113
drm_connector_hdmi_audio_plugged_notify(struct drm_connector * connector,bool plugged)114 void drm_connector_hdmi_audio_plugged_notify(struct drm_connector *connector,
115 bool plugged)
116 {
117 mutex_lock(&connector->hdmi_audio.lock);
118
119 connector->hdmi_audio.last_state = plugged;
120
121 if (connector->hdmi_audio.plugged_cb &&
122 connector->hdmi_audio.plugged_cb_dev)
123 connector->hdmi_audio.plugged_cb(connector->hdmi_audio.plugged_cb_dev,
124 connector->hdmi_audio.last_state);
125
126 mutex_unlock(&connector->hdmi_audio.lock);
127 }
128 EXPORT_SYMBOL(drm_connector_hdmi_audio_plugged_notify);
129
130 static const struct hdmi_codec_ops drm_connector_hdmi_audio_ops = {
131 .audio_startup = drm_connector_hdmi_audio_startup,
132 .prepare = drm_connector_hdmi_audio_prepare,
133 .audio_shutdown = drm_connector_hdmi_audio_shutdown,
134 .mute_stream = drm_connector_hdmi_audio_mute_stream,
135 .get_eld = drm_connector_hdmi_audio_get_eld,
136 .get_dai_id = drm_connector_hdmi_audio_get_dai_id,
137 .hook_plugged_cb = drm_connector_hdmi_audio_hook_plugged_cb,
138 };
139
140 /**
141 * drm_connector_hdmi_audio_init - Initialize HDMI Codec device for the DRM connector
142 * @connector: A pointer to the connector to allocate codec for
143 * @hdmi_codec_dev: device to be used as a parent for the HDMI Codec
144 * @funcs: callbacks for this HDMI Codec
145 * @max_i2s_playback_channels: maximum number of playback I2S channels
146 * @spdif_playback: set if HDMI codec has S/PDIF playback port
147 * @dai_port: sound DAI port, -1 if it is not enabled
148 *
149 * Create a HDMI codec device to be used with the specified connector.
150 *
151 * Returns:
152 * Zero on success, error code on failure.
153 */
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)154 int drm_connector_hdmi_audio_init(struct drm_connector *connector,
155 struct device *hdmi_codec_dev,
156 const struct drm_connector_hdmi_audio_funcs *funcs,
157 unsigned int max_i2s_playback_channels,
158 bool spdif_playback,
159 int dai_port)
160 {
161 struct hdmi_codec_pdata codec_pdata = {
162 .ops = &drm_connector_hdmi_audio_ops,
163 .max_i2s_channels = max_i2s_playback_channels,
164 .i2s = !!max_i2s_playback_channels,
165 .spdif = spdif_playback,
166 .no_i2s_capture = true,
167 .no_spdif_capture = true,
168 .data = connector,
169 };
170 struct platform_device *pdev;
171
172 if (!funcs ||
173 !funcs->prepare ||
174 !funcs->shutdown)
175 return -EINVAL;
176
177 connector->hdmi_audio.funcs = funcs;
178 connector->hdmi_audio.dai_port = dai_port;
179
180 pdev = platform_device_register_data(hdmi_codec_dev,
181 HDMI_CODEC_DRV_NAME,
182 PLATFORM_DEVID_AUTO,
183 &codec_pdata, sizeof(codec_pdata));
184 if (IS_ERR(pdev))
185 return PTR_ERR(pdev);
186
187 connector->hdmi_audio.codec_pdev = pdev;
188
189 return 0;
190 }
191 EXPORT_SYMBOL(drm_connector_hdmi_audio_init);
192