xref: /linux/drivers/gpu/drm/display/drm_hdmi_cec_helper.c (revision 22c55fb9eb92395d999b8404d73e58540d11bdd8)
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 
16 struct drm_connector_hdmi_cec_data {
17 	struct cec_adapter *adapter;
18 	const struct drm_connector_hdmi_cec_funcs *funcs;
19 };
20 
21 static 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 
29 static 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 
37 static 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 
46 static 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 
52 static 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 
59 static 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 
67 static 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 
81 static 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 
86 int 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(sizeof(*data), GFP_KERNEL);
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 
151 err_delete_adapter:
152 	cec_delete_adapter(cec_adap);
153 
154 	connector->cec.data = NULL;
155 
156 	mutex_unlock(&connector->cec.mutex);
157 
158 err_free:
159 	kfree(data);
160 
161 	return ret;
162 }
163 EXPORT_SYMBOL(drmm_connector_hdmi_cec_register);
164 
165 void 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 }
172 EXPORT_SYMBOL(drm_connector_hdmi_cec_received_msg);
173 
174 void 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 }
181 EXPORT_SYMBOL(drm_connector_hdmi_cec_transmit_attempt_done);
182 
183 void 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 }
193 EXPORT_SYMBOL(drm_connector_hdmi_cec_transmit_done);
194