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