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