Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1// SPDX-License-Identifier: MIT
2/*
3 * Copyright (c) 2024 Linaro Ltd
4 */
5
6#include <drm/drm_bridge.h>
7#include <drm/drm_connector.h>
8#include <drm/drm_managed.h>
9#include <drm/display/drm_hdmi_cec_helper.h>
10
11#include <linux/export.h>
12#include <linux/mutex.h>
13
14#include <media/cec.h>
15
16struct drm_connector_hdmi_cec_data {
17 struct cec_adapter *adapter;
18 const struct drm_connector_hdmi_cec_funcs *funcs;
19};
20
21static int drm_connector_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
22{
23 struct drm_connector *connector = cec_get_drvdata(adap);
24 struct drm_connector_hdmi_cec_data *data = connector->cec.data;
25
26 return data->funcs->enable(connector, enable);
27}
28
29static int drm_connector_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr)
30{
31 struct drm_connector *connector = cec_get_drvdata(adap);
32 struct drm_connector_hdmi_cec_data *data = connector->cec.data;
33
34 return data->funcs->log_addr(connector, logical_addr);
35}
36
37static int drm_connector_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
38 u32 signal_free_time, struct cec_msg *msg)
39{
40 struct drm_connector *connector = cec_get_drvdata(adap);
41 struct drm_connector_hdmi_cec_data *data = connector->cec.data;
42
43 return data->funcs->transmit(connector, attempts, signal_free_time, msg);
44}
45
46static const struct cec_adap_ops drm_connector_hdmi_cec_adap_ops = {
47 .adap_enable = drm_connector_hdmi_cec_adap_enable,
48 .adap_log_addr = drm_connector_hdmi_cec_adap_log_addr,
49 .adap_transmit = drm_connector_hdmi_cec_adap_transmit,
50};
51
52static void drm_connector_hdmi_cec_adapter_phys_addr_invalidate(struct drm_connector *connector)
53{
54 struct drm_connector_hdmi_cec_data *data = connector->cec.data;
55
56 cec_phys_addr_invalidate(data->adapter);
57}
58
59static void drm_connector_hdmi_cec_adapter_phys_addr_set(struct drm_connector *connector,
60 u16 addr)
61{
62 struct drm_connector_hdmi_cec_data *data = connector->cec.data;
63
64 cec_s_phys_addr(data->adapter, addr, false);
65}
66
67static void drm_connector_hdmi_cec_adapter_unregister(struct drm_device *dev, void *res)
68{
69 struct drm_connector *connector = res;
70 struct drm_connector_hdmi_cec_data *data = connector->cec.data;
71
72 cec_unregister_adapter(data->adapter);
73
74 if (data->funcs->uninit)
75 data->funcs->uninit(connector);
76
77 kfree(data);
78 connector->cec.data = NULL;
79}
80
81static struct drm_connector_cec_funcs drm_connector_hdmi_cec_adapter_funcs = {
82 .phys_addr_invalidate = drm_connector_hdmi_cec_adapter_phys_addr_invalidate,
83 .phys_addr_set = drm_connector_hdmi_cec_adapter_phys_addr_set,
84};
85
86int drmm_connector_hdmi_cec_register(struct drm_connector *connector,
87 const struct drm_connector_hdmi_cec_funcs *funcs,
88 const char *name,
89 u8 available_las,
90 struct device *dev)
91{
92 struct drm_connector_hdmi_cec_data *data;
93 struct cec_connector_info conn_info;
94 struct cec_adapter *cec_adap;
95 int ret;
96
97 if (!funcs->init || !funcs->enable || !funcs->log_addr || !funcs->transmit)
98 return -EINVAL;
99
100 data = kzalloc_obj(*data);
101 if (!data)
102 return -ENOMEM;
103
104 data->funcs = funcs;
105
106 cec_adap = cec_allocate_adapter(&drm_connector_hdmi_cec_adap_ops, connector, name,
107 CEC_CAP_DEFAULTS | CEC_CAP_CONNECTOR_INFO,
108 available_las ? : CEC_MAX_LOG_ADDRS);
109 ret = PTR_ERR_OR_ZERO(cec_adap);
110 if (ret < 0)
111 goto err_free;
112
113 cec_fill_conn_info_from_drm(&conn_info, connector);
114 cec_s_conn_info(cec_adap, &conn_info);
115
116 data->adapter = cec_adap;
117
118 mutex_lock(&connector->cec.mutex);
119
120 connector->cec.data = data;
121 connector->cec.funcs = &drm_connector_hdmi_cec_adapter_funcs;
122
123 ret = funcs->init(connector);
124 if (ret < 0)
125 goto err_delete_adapter;
126
127 /*
128 * NOTE: the CEC adapter will be unregistered by drmm cleanup from
129 * drm_managed_release(), which is called from drm_dev_release()
130 * during device unbind.
131 *
132 * However, the CEC framework cleans up the CEC adapter only when the
133 * last user has closed its file descriptor, so we don't need to handle
134 * it in DRM.
135 *
136 * Before that CEC framework makes sure that even if the userspace
137 * still holds CEC device open, all calls will be shortcut via
138 * cec_is_registered(), making sure that there is no access to the
139 * freed memory.
140 */
141 ret = cec_register_adapter(cec_adap, dev);
142 if (ret < 0)
143 goto err_delete_adapter;
144
145 mutex_unlock(&connector->cec.mutex);
146
147 return drmm_add_action_or_reset(connector->dev,
148 drm_connector_hdmi_cec_adapter_unregister,
149 connector);
150
151err_delete_adapter:
152 cec_delete_adapter(cec_adap);
153
154 connector->cec.data = NULL;
155
156 mutex_unlock(&connector->cec.mutex);
157
158err_free:
159 kfree(data);
160
161 return ret;
162}
163EXPORT_SYMBOL(drmm_connector_hdmi_cec_register);
164
165void drm_connector_hdmi_cec_received_msg(struct drm_connector *connector,
166 struct cec_msg *msg)
167{
168 struct drm_connector_hdmi_cec_data *data = connector->cec.data;
169
170 cec_received_msg(data->adapter, msg);
171}
172EXPORT_SYMBOL(drm_connector_hdmi_cec_received_msg);
173
174void drm_connector_hdmi_cec_transmit_attempt_done(struct drm_connector *connector,
175 u8 status)
176{
177 struct drm_connector_hdmi_cec_data *data = connector->cec.data;
178
179 cec_transmit_attempt_done(data->adapter, status);
180}
181EXPORT_SYMBOL(drm_connector_hdmi_cec_transmit_attempt_done);
182
183void drm_connector_hdmi_cec_transmit_done(struct drm_connector *connector,
184 u8 status,
185 u8 arb_lost_cnt, u8 nack_cnt,
186 u8 low_drive_cnt, u8 error_cnt)
187{
188 struct drm_connector_hdmi_cec_data *data = connector->cec.data;
189
190 cec_transmit_done(data->adapter, status,
191 arb_lost_cnt, nack_cnt, low_drive_cnt, error_cnt);
192}
193EXPORT_SYMBOL(drm_connector_hdmi_cec_transmit_done);