xref: /linux/drivers/gpu/drm/bridge/aux-hpd-bridge.c (revision 3a2c4d55e32ad65efebdb6de44eef3bfa08bb49d)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright (C) 2023 Linaro Ltd.
4  *
5  * Author: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
6  */
7 #include <linux/auxiliary_bus.h>
8 #include <linux/export.h>
9 #include <linux/module.h>
10 #include <linux/of.h>
11 
12 #include <drm/drm_atomic_helper.h>
13 #include <drm/drm_bridge.h>
14 #include <drm/bridge/aux-bridge.h>
15 
16 static DEFINE_IDA(drm_aux_hpd_bridge_ida);
17 
18 struct drm_aux_hpd_bridge_data {
19 	struct drm_bridge bridge;
20 	struct device *dev;
21 };
22 
23 static void drm_aux_hpd_bridge_release(struct device *dev)
24 {
25 	struct auxiliary_device *adev = to_auxiliary_dev(dev);
26 
27 	ida_free(&drm_aux_hpd_bridge_ida, adev->id);
28 
29 	of_node_put(adev->dev.platform_data);
30 	of_node_put(adev->dev.of_node);
31 
32 	kfree(adev);
33 }
34 
35 static void drm_aux_hpd_bridge_free_adev(void *_adev)
36 {
37 	auxiliary_device_uninit(_adev);
38 }
39 
40 /**
41  * devm_drm_dp_hpd_bridge_alloc - allocate a HPD DisplayPort bridge
42  * @parent: device instance providing this bridge
43  * @np: device node pointer corresponding to this bridge instance
44  *
45  * Creates a simple DRM bridge with the type set to
46  * DRM_MODE_CONNECTOR_DisplayPort, which terminates the bridge chain and is
47  * able to send the HPD events.
48  *
49  * Return: bridge auxiliary device pointer or an error pointer
50  */
51 struct auxiliary_device *devm_drm_dp_hpd_bridge_alloc(struct device *parent, struct device_node *np)
52 {
53 	struct auxiliary_device *adev;
54 	int ret;
55 
56 	adev = kzalloc_obj(*adev);
57 	if (!adev)
58 		return ERR_PTR(-ENOMEM);
59 
60 	ret = ida_alloc(&drm_aux_hpd_bridge_ida, GFP_KERNEL);
61 	if (ret < 0) {
62 		kfree(adev);
63 		return ERR_PTR(ret);
64 	}
65 
66 	adev->id = ret;
67 	adev->name = "dp_hpd_bridge";
68 	adev->dev.parent = parent;
69 	adev->dev.release = drm_aux_hpd_bridge_release;
70 	adev->dev.platform_data = of_node_get(np);
71 
72 	device_set_of_node_from_dev(&adev->dev, parent);
73 
74 	ret = auxiliary_device_init(adev);
75 	if (ret) {
76 		of_node_put(adev->dev.platform_data);
77 		of_node_put(adev->dev.of_node);
78 		ida_free(&drm_aux_hpd_bridge_ida, adev->id);
79 		kfree(adev);
80 		return ERR_PTR(ret);
81 	}
82 
83 	ret = devm_add_action_or_reset(parent, drm_aux_hpd_bridge_free_adev, adev);
84 	if (ret)
85 		return ERR_PTR(ret);
86 
87 	return adev;
88 }
89 EXPORT_SYMBOL_GPL(devm_drm_dp_hpd_bridge_alloc);
90 
91 static void drm_aux_hpd_bridge_del_adev(void *_adev)
92 {
93 	auxiliary_device_delete(_adev);
94 }
95 
96 /**
97  * devm_drm_dp_hpd_bridge_add - register a HDP DisplayPort bridge
98  * @dev: struct device to tie registration lifetime to
99  * @adev: bridge auxiliary device to be registered
100  *
101  * Returns: zero on success or a negative errno
102  */
103 int devm_drm_dp_hpd_bridge_add(struct device *dev, struct auxiliary_device *adev)
104 {
105 	int ret;
106 
107 	ret = auxiliary_device_add(adev);
108 	if (ret)
109 		return ret;
110 
111 	return devm_add_action_or_reset(dev, drm_aux_hpd_bridge_del_adev, adev);
112 }
113 EXPORT_SYMBOL_GPL(devm_drm_dp_hpd_bridge_add);
114 
115 /**
116  * drm_dp_hpd_bridge_register - allocate and register a HDP DisplayPort bridge
117  * @parent: device instance providing this bridge
118  * @np: device node pointer corresponding to this bridge instance
119  *
120  * Return: device instance that will handle created bridge or an error pointer
121  */
122 struct device *drm_dp_hpd_bridge_register(struct device *parent, struct device_node *np)
123 {
124 	struct auxiliary_device *adev;
125 	int ret;
126 
127 	adev = devm_drm_dp_hpd_bridge_alloc(parent, np);
128 	if (IS_ERR(adev))
129 		return ERR_CAST(adev);
130 
131 	ret = devm_drm_dp_hpd_bridge_add(parent, adev);
132 	if (ret)
133 		return ERR_PTR(ret);
134 
135 	return &adev->dev;
136 }
137 EXPORT_SYMBOL_GPL(drm_dp_hpd_bridge_register);
138 
139 /**
140  * drm_aux_hpd_bridge_notify - notify hot plug detection events
141  * @dev: device created for the HPD bridge
142  * @status: output connection status
143  *
144  * A wrapper around drm_bridge_hpd_notify() that is used to report hot plug
145  * detection events for bridges created via drm_dp_hpd_bridge_register().
146  *
147  * This function shall be called in a context that can sleep.
148  */
149 void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status)
150 {
151 	struct auxiliary_device *adev = to_auxiliary_dev(dev);
152 	struct drm_aux_hpd_bridge_data *data = auxiliary_get_drvdata(adev);
153 
154 	if (!data)
155 		return;
156 
157 	drm_bridge_hpd_notify(&data->bridge, status);
158 }
159 EXPORT_SYMBOL_GPL(drm_aux_hpd_bridge_notify);
160 
161 static int drm_aux_hpd_bridge_attach(struct drm_bridge *bridge,
162 				     struct drm_encoder *encoder,
163 				     enum drm_bridge_attach_flags flags)
164 {
165 	return flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR ? 0 : -EINVAL;
166 }
167 
168 static const struct drm_bridge_funcs drm_aux_hpd_bridge_funcs = {
169 	.atomic_create_state = drm_atomic_helper_bridge_create_state,
170 	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
171 	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
172 	.attach	= drm_aux_hpd_bridge_attach,
173 };
174 
175 static int drm_aux_hpd_bridge_probe(struct auxiliary_device *auxdev,
176 				    const struct auxiliary_device_id *id)
177 {
178 	struct drm_aux_hpd_bridge_data *data;
179 
180 	data = devm_drm_bridge_alloc(&auxdev->dev,
181 				     struct drm_aux_hpd_bridge_data, bridge,
182 				     &drm_aux_hpd_bridge_funcs);
183 	if (IS_ERR(data))
184 		return PTR_ERR(data);
185 
186 	data->dev = &auxdev->dev;
187 	data->bridge.of_node = dev_get_platdata(data->dev);
188 	data->bridge.ops = DRM_BRIDGE_OP_HPD;
189 	data->bridge.type = id->driver_data;
190 
191 	/* passthrough data, allow everything */
192 	data->bridge.interlace_allowed = true;
193 	data->bridge.ycbcr_420_allowed = true;
194 
195 	auxiliary_set_drvdata(auxdev, data);
196 
197 	return devm_drm_bridge_add(data->dev, &data->bridge);
198 }
199 
200 static const struct auxiliary_device_id drm_aux_hpd_bridge_table[] = {
201 	{ .name = KBUILD_MODNAME ".dp_hpd_bridge", .driver_data = DRM_MODE_CONNECTOR_DisplayPort, },
202 	{},
203 };
204 MODULE_DEVICE_TABLE(auxiliary, drm_aux_hpd_bridge_table);
205 
206 static struct auxiliary_driver drm_aux_hpd_bridge_drv = {
207 	.name = "aux_hpd_bridge",
208 	.id_table = drm_aux_hpd_bridge_table,
209 	.probe = drm_aux_hpd_bridge_probe,
210 };
211 module_auxiliary_driver(drm_aux_hpd_bridge_drv);
212 
213 MODULE_AUTHOR("Dmitry Baryshkov <dmitry.baryshkov@linaro.org>");
214 MODULE_DESCRIPTION("DRM HPD bridge");
215 MODULE_LICENSE("GPL");
216