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
drm_connector_hdmi_cec_adap_enable(struct cec_adapter * adap,bool enable)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
drm_connector_hdmi_cec_adap_log_addr(struct cec_adapter * adap,u8 logical_addr)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
drm_connector_hdmi_cec_adap_transmit(struct cec_adapter * adap,u8 attempts,u32 signal_free_time,struct cec_msg * msg)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
drm_connector_hdmi_cec_adapter_phys_addr_invalidate(struct drm_connector * connector)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
drm_connector_hdmi_cec_adapter_phys_addr_set(struct drm_connector * connector,u16 addr)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
drm_connector_hdmi_cec_adapter_unregister(struct drm_device * dev,void * res)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
drmm_connector_hdmi_cec_register(struct drm_connector * connector,const struct drm_connector_hdmi_cec_funcs * funcs,const char * name,u8 available_las,struct device * dev)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
drm_connector_hdmi_cec_received_msg(struct drm_connector * connector,struct cec_msg * msg)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
drm_connector_hdmi_cec_transmit_attempt_done(struct drm_connector * connector,u8 status)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
drm_connector_hdmi_cec_transmit_done(struct drm_connector * connector,u8 status,u8 arb_lost_cnt,u8 nack_cnt,u8 low_drive_cnt,u8 error_cnt)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