Skip to content

Commit e560518

Browse files
committed
drm/bridge: implement generic DP HPD bridge
Several USB-C controllers implement a pretty simple DRM bridge which implements just the HPD notification operations. Add special helper for creating such simple bridges. Acked-by: Neil Armstrong <[email protected]> Signed-off-by: Dmitry Baryshkov <[email protected]> Link: https://patchwork.freedesktop.org/patch/msgid/[email protected]
1 parent c5d296b commit e560518

File tree

4 files changed

+190
-0
lines changed

4 files changed

+190
-0
lines changed

drivers/gpu/drm/bridge/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ config DRM_AUX_BRIDGE
2121
Simple transparent bridge that is used by several non-DRM drivers to
2222
build bridges chain.
2323

24+
config DRM_AUX_HPD_BRIDGE
25+
tristate
26+
depends on DRM_BRIDGE && OF
27+
select AUXILIARY_BUS
28+
help
29+
Simple bridge that terminates the bridge chain and provides HPD
30+
support.
31+
2432
menu "Display Interface Bridges"
2533
depends on DRM && DRM_BRIDGE
2634

drivers/gpu/drm/bridge/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# SPDX-License-Identifier: GPL-2.0
22
obj-$(CONFIG_DRM_AUX_BRIDGE) += aux-bridge.o
3+
obj-$(CONFIG_DRM_AUX_HPD_BRIDGE) += aux-hpd-bridge.o
34
obj-$(CONFIG_DRM_CHIPONE_ICN6211) += chipone-icn6211.o
45
obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o
56
obj-$(CONFIG_DRM_CROS_EC_ANX7688) += cros-ec-anx7688.o
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// SPDX-License-Identifier: GPL-2.0+
2+
/*
3+
* Copyright (C) 2023 Linaro Ltd.
4+
*
5+
* Author: Dmitry Baryshkov <[email protected]>
6+
*/
7+
#include <linux/auxiliary_bus.h>
8+
#include <linux/module.h>
9+
#include <linux/of_device.h>
10+
11+
#include <drm/drm_bridge.h>
12+
#include <drm/bridge/aux-bridge.h>
13+
14+
static DEFINE_IDA(drm_aux_hpd_bridge_ida);
15+
16+
struct drm_aux_hpd_bridge_data {
17+
struct drm_bridge bridge;
18+
struct device *dev;
19+
};
20+
21+
static 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+
29+
kfree(adev);
30+
}
31+
32+
static void drm_aux_hpd_bridge_unregister_adev(void *_adev)
33+
{
34+
struct auxiliary_device *adev = _adev;
35+
36+
auxiliary_device_delete(adev);
37+
auxiliary_device_uninit(adev);
38+
}
39+
40+
/**
41+
* drm_dp_hpd_bridge_register - Create a simple 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: device instance that will handle created bridge or an error code
50+
* encoded into the pointer.
51+
*/
52+
struct device *drm_dp_hpd_bridge_register(struct device *parent,
53+
struct device_node *np)
54+
{
55+
struct auxiliary_device *adev;
56+
int ret;
57+
58+
adev = kzalloc(sizeof(*adev), GFP_KERNEL);
59+
if (!adev)
60+
return ERR_PTR(-ENOMEM);
61+
62+
ret = ida_alloc(&drm_aux_hpd_bridge_ida, GFP_KERNEL);
63+
if (ret < 0) {
64+
kfree(adev);
65+
return ERR_PTR(ret);
66+
}
67+
68+
adev->id = ret;
69+
adev->name = "dp_hpd_bridge";
70+
adev->dev.parent = parent;
71+
adev->dev.of_node = parent->of_node;
72+
adev->dev.release = drm_aux_hpd_bridge_release;
73+
adev->dev.platform_data = np;
74+
75+
ret = auxiliary_device_init(adev);
76+
if (ret) {
77+
ida_free(&drm_aux_hpd_bridge_ida, adev->id);
78+
kfree(adev);
79+
return ERR_PTR(ret);
80+
}
81+
82+
ret = auxiliary_device_add(adev);
83+
if (ret) {
84+
auxiliary_device_uninit(adev);
85+
return ERR_PTR(ret);
86+
}
87+
88+
ret = devm_add_action_or_reset(parent, drm_aux_hpd_bridge_unregister_adev, adev);
89+
if (ret)
90+
return ERR_PTR(ret);
91+
92+
return &adev->dev;
93+
}
94+
EXPORT_SYMBOL_GPL(drm_dp_hpd_bridge_register);
95+
96+
/**
97+
* drm_aux_hpd_bridge_notify - notify hot plug detection events
98+
* @dev: device created for the HPD bridge
99+
* @status: output connection status
100+
*
101+
* A wrapper around drm_bridge_hpd_notify() that is used to report hot plug
102+
* detection events for bridges created via drm_dp_hpd_bridge_register().
103+
*
104+
* This function shall be called in a context that can sleep.
105+
*/
106+
void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status)
107+
{
108+
struct auxiliary_device *adev = to_auxiliary_dev(dev);
109+
struct drm_aux_hpd_bridge_data *data = auxiliary_get_drvdata(adev);
110+
111+
if (!data)
112+
return;
113+
114+
drm_bridge_hpd_notify(&data->bridge, status);
115+
}
116+
EXPORT_SYMBOL_GPL(drm_aux_hpd_bridge_notify);
117+
118+
static int drm_aux_hpd_bridge_attach(struct drm_bridge *bridge,
119+
enum drm_bridge_attach_flags flags)
120+
{
121+
return flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR ? 0 : -EINVAL;
122+
}
123+
124+
static const struct drm_bridge_funcs drm_aux_hpd_bridge_funcs = {
125+
.attach = drm_aux_hpd_bridge_attach,
126+
};
127+
128+
static int drm_aux_hpd_bridge_probe(struct auxiliary_device *auxdev,
129+
const struct auxiliary_device_id *id)
130+
{
131+
struct drm_aux_hpd_bridge_data *data;
132+
133+
data = devm_kzalloc(&auxdev->dev, sizeof(*data), GFP_KERNEL);
134+
if (!data)
135+
return -ENOMEM;
136+
137+
data->dev = &auxdev->dev;
138+
data->bridge.funcs = &drm_aux_hpd_bridge_funcs;
139+
data->bridge.of_node = dev_get_platdata(data->dev);
140+
data->bridge.ops = DRM_BRIDGE_OP_HPD;
141+
data->bridge.type = id->driver_data;
142+
143+
auxiliary_set_drvdata(auxdev, data);
144+
145+
return devm_drm_bridge_add(data->dev, &data->bridge);
146+
}
147+
148+
static const struct auxiliary_device_id drm_aux_hpd_bridge_table[] = {
149+
{ .name = KBUILD_MODNAME ".dp_hpd_bridge", .driver_data = DRM_MODE_CONNECTOR_DisplayPort, },
150+
{},
151+
};
152+
MODULE_DEVICE_TABLE(auxiliary, drm_aux_hpd_bridge_table);
153+
154+
static struct auxiliary_driver drm_aux_hpd_bridge_drv = {
155+
.name = "aux_hpd_bridge",
156+
.id_table = drm_aux_hpd_bridge_table,
157+
.probe = drm_aux_hpd_bridge_probe,
158+
};
159+
module_auxiliary_driver(drm_aux_hpd_bridge_drv);
160+
161+
MODULE_AUTHOR("Dmitry Baryshkov <[email protected]>");
162+
MODULE_DESCRIPTION("DRM HPD bridge");
163+
MODULE_LICENSE("GPL");

include/drm/bridge/aux-bridge.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#ifndef DRM_AUX_BRIDGE_H
88
#define DRM_AUX_BRIDGE_H
99

10+
#include <drm/drm_connector.h>
11+
1012
#if IS_ENABLED(CONFIG_DRM_AUX_BRIDGE)
1113
int drm_aux_bridge_register(struct device *parent);
1214
#else
@@ -16,4 +18,20 @@ static inline int drm_aux_bridge_register(struct device *parent)
1618
}
1719
#endif
1820

21+
#if IS_ENABLED(CONFIG_DRM_AUX_HPD_BRIDGE)
22+
struct device *drm_dp_hpd_bridge_register(struct device *parent,
23+
struct device_node *np);
24+
void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status);
25+
#else
26+
static inline struct device *drm_dp_hpd_bridge_register(struct device *parent,
27+
struct device_node *np)
28+
{
29+
return 0;
30+
}
31+
32+
static inline void drm_aux_hpd_bridge_notify(struct device *dev, enum drm_connector_status status)
33+
{
34+
}
35+
#endif
36+
1937
#endif

0 commit comments

Comments
 (0)