Linux Audio

Check our new training course

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