xref: /linux/drivers/gpu/drm/display/drm_hdmi_cec_helper.c (revision 8d2b0853add1d7534dc0794e3c8e0b9e8c4ec640)
18b1a8f8bSDmitry Baryshkov // SPDX-License-Identifier: MIT
28b1a8f8bSDmitry Baryshkov /*
38b1a8f8bSDmitry Baryshkov  * Copyright (c) 2024 Linaro Ltd
48b1a8f8bSDmitry Baryshkov  */
58b1a8f8bSDmitry Baryshkov 
68b1a8f8bSDmitry Baryshkov #include <drm/drm_bridge.h>
78b1a8f8bSDmitry Baryshkov #include <drm/drm_connector.h>
88b1a8f8bSDmitry Baryshkov #include <drm/drm_managed.h>
98b1a8f8bSDmitry Baryshkov #include <drm/display/drm_hdmi_cec_helper.h>
108b1a8f8bSDmitry Baryshkov 
118f194494SThomas Zimmermann #include <linux/export.h>
128b1a8f8bSDmitry Baryshkov #include <linux/mutex.h>
138b1a8f8bSDmitry Baryshkov 
148b1a8f8bSDmitry Baryshkov #include <media/cec.h>
158b1a8f8bSDmitry Baryshkov 
168b1a8f8bSDmitry Baryshkov struct drm_connector_hdmi_cec_data {
178b1a8f8bSDmitry Baryshkov 	struct cec_adapter *adapter;
188b1a8f8bSDmitry Baryshkov 	const struct drm_connector_hdmi_cec_funcs *funcs;
198b1a8f8bSDmitry Baryshkov };
208b1a8f8bSDmitry Baryshkov 
218b1a8f8bSDmitry Baryshkov static int drm_connector_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
228b1a8f8bSDmitry Baryshkov {
238b1a8f8bSDmitry Baryshkov 	struct drm_connector *connector = cec_get_drvdata(adap);
248b1a8f8bSDmitry Baryshkov 	struct drm_connector_hdmi_cec_data *data = connector->cec.data;
258b1a8f8bSDmitry Baryshkov 
268b1a8f8bSDmitry Baryshkov 	return data->funcs->enable(connector, enable);
278b1a8f8bSDmitry Baryshkov }
288b1a8f8bSDmitry Baryshkov 
298b1a8f8bSDmitry Baryshkov static int drm_connector_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr)
308b1a8f8bSDmitry Baryshkov {
318b1a8f8bSDmitry Baryshkov 	struct drm_connector *connector = cec_get_drvdata(adap);
328b1a8f8bSDmitry Baryshkov 	struct drm_connector_hdmi_cec_data *data = connector->cec.data;
338b1a8f8bSDmitry Baryshkov 
348b1a8f8bSDmitry Baryshkov 	return data->funcs->log_addr(connector, logical_addr);
358b1a8f8bSDmitry Baryshkov }
368b1a8f8bSDmitry Baryshkov 
378b1a8f8bSDmitry Baryshkov static int drm_connector_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
388b1a8f8bSDmitry Baryshkov 						u32 signal_free_time, struct cec_msg *msg)
398b1a8f8bSDmitry Baryshkov {
408b1a8f8bSDmitry Baryshkov 	struct drm_connector *connector = cec_get_drvdata(adap);
418b1a8f8bSDmitry Baryshkov 	struct drm_connector_hdmi_cec_data *data = connector->cec.data;
428b1a8f8bSDmitry Baryshkov 
438b1a8f8bSDmitry Baryshkov 	return data->funcs->transmit(connector, attempts, signal_free_time, msg);
448b1a8f8bSDmitry Baryshkov }
458b1a8f8bSDmitry Baryshkov 
468b1a8f8bSDmitry Baryshkov static const struct cec_adap_ops drm_connector_hdmi_cec_adap_ops = {
478b1a8f8bSDmitry Baryshkov 	.adap_enable = drm_connector_hdmi_cec_adap_enable,
488b1a8f8bSDmitry Baryshkov 	.adap_log_addr = drm_connector_hdmi_cec_adap_log_addr,
498b1a8f8bSDmitry Baryshkov 	.adap_transmit = drm_connector_hdmi_cec_adap_transmit,
508b1a8f8bSDmitry Baryshkov };
518b1a8f8bSDmitry Baryshkov 
528b1a8f8bSDmitry Baryshkov static void drm_connector_hdmi_cec_adapter_phys_addr_invalidate(struct drm_connector *connector)
538b1a8f8bSDmitry Baryshkov {
548b1a8f8bSDmitry Baryshkov 	struct drm_connector_hdmi_cec_data *data = connector->cec.data;
558b1a8f8bSDmitry Baryshkov 
568b1a8f8bSDmitry Baryshkov 	cec_phys_addr_invalidate(data->adapter);
578b1a8f8bSDmitry Baryshkov }
588b1a8f8bSDmitry Baryshkov 
598b1a8f8bSDmitry Baryshkov static void drm_connector_hdmi_cec_adapter_phys_addr_set(struct drm_connector *connector,
608b1a8f8bSDmitry Baryshkov 							 u16 addr)
618b1a8f8bSDmitry Baryshkov {
628b1a8f8bSDmitry Baryshkov 	struct drm_connector_hdmi_cec_data *data = connector->cec.data;
638b1a8f8bSDmitry Baryshkov 
648b1a8f8bSDmitry Baryshkov 	cec_s_phys_addr(data->adapter, addr, false);
658b1a8f8bSDmitry Baryshkov }
668b1a8f8bSDmitry Baryshkov 
678b1a8f8bSDmitry Baryshkov static void drm_connector_hdmi_cec_adapter_unregister(struct drm_device *dev, void *res)
688b1a8f8bSDmitry Baryshkov {
698b1a8f8bSDmitry Baryshkov 	struct drm_connector *connector = res;
708b1a8f8bSDmitry Baryshkov 	struct drm_connector_hdmi_cec_data *data = connector->cec.data;
718b1a8f8bSDmitry Baryshkov 
72*19920ab9SCristian Ciocaltea 	cec_unregister_adapter(data->adapter);
738b1a8f8bSDmitry Baryshkov 
748b1a8f8bSDmitry Baryshkov 	if (data->funcs->uninit)
758b1a8f8bSDmitry Baryshkov 		data->funcs->uninit(connector);
768b1a8f8bSDmitry Baryshkov 
778b1a8f8bSDmitry Baryshkov 	kfree(data);
788b1a8f8bSDmitry Baryshkov 	connector->cec.data = NULL;
798b1a8f8bSDmitry Baryshkov }
808b1a8f8bSDmitry Baryshkov 
818b1a8f8bSDmitry Baryshkov static struct drm_connector_cec_funcs drm_connector_hdmi_cec_adapter_funcs = {
828b1a8f8bSDmitry Baryshkov 	.phys_addr_invalidate = drm_connector_hdmi_cec_adapter_phys_addr_invalidate,
838b1a8f8bSDmitry Baryshkov 	.phys_addr_set = drm_connector_hdmi_cec_adapter_phys_addr_set,
848b1a8f8bSDmitry Baryshkov };
858b1a8f8bSDmitry Baryshkov 
868b1a8f8bSDmitry Baryshkov int drmm_connector_hdmi_cec_register(struct drm_connector *connector,
878b1a8f8bSDmitry Baryshkov 				     const struct drm_connector_hdmi_cec_funcs *funcs,
888b1a8f8bSDmitry Baryshkov 				     const char *name,
898b1a8f8bSDmitry Baryshkov 				     u8 available_las,
908b1a8f8bSDmitry Baryshkov 				     struct device *dev)
918b1a8f8bSDmitry Baryshkov {
928b1a8f8bSDmitry Baryshkov 	struct drm_connector_hdmi_cec_data *data;
938b1a8f8bSDmitry Baryshkov 	struct cec_connector_info conn_info;
948b1a8f8bSDmitry Baryshkov 	struct cec_adapter *cec_adap;
958b1a8f8bSDmitry Baryshkov 	int ret;
968b1a8f8bSDmitry Baryshkov 
978b1a8f8bSDmitry Baryshkov 	if (!funcs->init || !funcs->enable || !funcs->log_addr || !funcs->transmit)
988b1a8f8bSDmitry Baryshkov 		return -EINVAL;
998b1a8f8bSDmitry Baryshkov 
1008b1a8f8bSDmitry Baryshkov 	data = kzalloc(sizeof(*data), GFP_KERNEL);
1018b1a8f8bSDmitry Baryshkov 	if (!data)
1028b1a8f8bSDmitry Baryshkov 		return -ENOMEM;
1038b1a8f8bSDmitry Baryshkov 
1048b1a8f8bSDmitry Baryshkov 	data->funcs = funcs;
1058b1a8f8bSDmitry Baryshkov 
1068b1a8f8bSDmitry Baryshkov 	cec_adap = cec_allocate_adapter(&drm_connector_hdmi_cec_adap_ops, connector, name,
1078b1a8f8bSDmitry Baryshkov 					CEC_CAP_DEFAULTS | CEC_CAP_CONNECTOR_INFO,
1088b1a8f8bSDmitry Baryshkov 					available_las ? : CEC_MAX_LOG_ADDRS);
1098b1a8f8bSDmitry Baryshkov 	ret = PTR_ERR_OR_ZERO(cec_adap);
1108b1a8f8bSDmitry Baryshkov 	if (ret < 0)
1118b1a8f8bSDmitry Baryshkov 		goto err_free;
1128b1a8f8bSDmitry Baryshkov 
1138b1a8f8bSDmitry Baryshkov 	cec_fill_conn_info_from_drm(&conn_info, connector);
1148b1a8f8bSDmitry Baryshkov 	cec_s_conn_info(cec_adap, &conn_info);
1158b1a8f8bSDmitry Baryshkov 
1168b1a8f8bSDmitry Baryshkov 	data->adapter = cec_adap;
1178b1a8f8bSDmitry Baryshkov 
1188b1a8f8bSDmitry Baryshkov 	mutex_lock(&connector->cec.mutex);
1198b1a8f8bSDmitry Baryshkov 
1208b1a8f8bSDmitry Baryshkov 	connector->cec.data = data;
1218b1a8f8bSDmitry Baryshkov 	connector->cec.funcs = &drm_connector_hdmi_cec_adapter_funcs;
1228b1a8f8bSDmitry Baryshkov 
1238b1a8f8bSDmitry Baryshkov 	ret = funcs->init(connector);
1248b1a8f8bSDmitry Baryshkov 	if (ret < 0)
1258b1a8f8bSDmitry Baryshkov 		goto err_delete_adapter;
1268b1a8f8bSDmitry Baryshkov 
1278b1a8f8bSDmitry Baryshkov 	/*
1288b1a8f8bSDmitry Baryshkov 	 * NOTE: the CEC adapter will be unregistered by drmm cleanup from
1298b1a8f8bSDmitry Baryshkov 	 * drm_managed_release(), which is called from drm_dev_release()
1308b1a8f8bSDmitry Baryshkov 	 * during device unbind.
1318b1a8f8bSDmitry Baryshkov 	 *
1328b1a8f8bSDmitry Baryshkov 	 * However, the CEC framework cleans up the CEC adapter only when the
1338b1a8f8bSDmitry Baryshkov 	 * last user has closed its file descriptor, so we don't need to handle
1348b1a8f8bSDmitry Baryshkov 	 * it in DRM.
1358b1a8f8bSDmitry Baryshkov 	 *
1368b1a8f8bSDmitry Baryshkov 	 * Before that CEC framework makes sure that even if the userspace
1378b1a8f8bSDmitry Baryshkov 	 * still holds CEC device open, all calls will be shortcut via
1388b1a8f8bSDmitry Baryshkov 	 * cec_is_registered(), making sure that there is no access to the
1398b1a8f8bSDmitry Baryshkov 	 * freed memory.
1408b1a8f8bSDmitry Baryshkov 	 */
1418b1a8f8bSDmitry Baryshkov 	ret = cec_register_adapter(cec_adap, dev);
1428b1a8f8bSDmitry Baryshkov 	if (ret < 0)
1438b1a8f8bSDmitry Baryshkov 		goto err_delete_adapter;
1448b1a8f8bSDmitry Baryshkov 
1458b1a8f8bSDmitry Baryshkov 	mutex_unlock(&connector->cec.mutex);
1468b1a8f8bSDmitry Baryshkov 
1478b1a8f8bSDmitry Baryshkov 	return drmm_add_action_or_reset(connector->dev,
1488b1a8f8bSDmitry Baryshkov 					drm_connector_hdmi_cec_adapter_unregister,
1498b1a8f8bSDmitry Baryshkov 					connector);
1508b1a8f8bSDmitry Baryshkov 
1518b1a8f8bSDmitry Baryshkov err_delete_adapter:
1528b1a8f8bSDmitry Baryshkov 	cec_delete_adapter(cec_adap);
1538b1a8f8bSDmitry Baryshkov 
1548b1a8f8bSDmitry Baryshkov 	connector->cec.data = NULL;
1558b1a8f8bSDmitry Baryshkov 
1568b1a8f8bSDmitry Baryshkov 	mutex_unlock(&connector->cec.mutex);
1578b1a8f8bSDmitry Baryshkov 
1588b1a8f8bSDmitry Baryshkov err_free:
1598b1a8f8bSDmitry Baryshkov 	kfree(data);
1608b1a8f8bSDmitry Baryshkov 
1618b1a8f8bSDmitry Baryshkov 	return ret;
1628b1a8f8bSDmitry Baryshkov }
1638b1a8f8bSDmitry Baryshkov EXPORT_SYMBOL(drmm_connector_hdmi_cec_register);
1648b1a8f8bSDmitry Baryshkov 
1658b1a8f8bSDmitry Baryshkov void drm_connector_hdmi_cec_received_msg(struct drm_connector *connector,
1668b1a8f8bSDmitry Baryshkov 					 struct cec_msg *msg)
1678b1a8f8bSDmitry Baryshkov {
1688b1a8f8bSDmitry Baryshkov 	struct drm_connector_hdmi_cec_data *data = connector->cec.data;
1698b1a8f8bSDmitry Baryshkov 
1708b1a8f8bSDmitry Baryshkov 	cec_received_msg(data->adapter, msg);
1718b1a8f8bSDmitry Baryshkov }
1728b1a8f8bSDmitry Baryshkov EXPORT_SYMBOL(drm_connector_hdmi_cec_received_msg);
1738b1a8f8bSDmitry Baryshkov 
1748b1a8f8bSDmitry Baryshkov void drm_connector_hdmi_cec_transmit_attempt_done(struct drm_connector *connector,
1758b1a8f8bSDmitry Baryshkov 						  u8 status)
1768b1a8f8bSDmitry Baryshkov {
1778b1a8f8bSDmitry Baryshkov 	struct drm_connector_hdmi_cec_data *data = connector->cec.data;
1788b1a8f8bSDmitry Baryshkov 
1798b1a8f8bSDmitry Baryshkov 	cec_transmit_attempt_done(data->adapter, status);
1808b1a8f8bSDmitry Baryshkov }
1818b1a8f8bSDmitry Baryshkov EXPORT_SYMBOL(drm_connector_hdmi_cec_transmit_attempt_done);
1828b1a8f8bSDmitry Baryshkov 
1838b1a8f8bSDmitry Baryshkov void drm_connector_hdmi_cec_transmit_done(struct drm_connector *connector,
1848b1a8f8bSDmitry Baryshkov 					  u8 status,
1858b1a8f8bSDmitry Baryshkov 					  u8 arb_lost_cnt, u8 nack_cnt,
1868b1a8f8bSDmitry Baryshkov 					  u8 low_drive_cnt, u8 error_cnt)
1878b1a8f8bSDmitry Baryshkov {
1888b1a8f8bSDmitry Baryshkov 	struct drm_connector_hdmi_cec_data *data = connector->cec.data;
1898b1a8f8bSDmitry Baryshkov 
1908b1a8f8bSDmitry Baryshkov 	cec_transmit_done(data->adapter, status,
1918b1a8f8bSDmitry Baryshkov 			  arb_lost_cnt, nack_cnt, low_drive_cnt, error_cnt);
1928b1a8f8bSDmitry Baryshkov }
1938b1a8f8bSDmitry Baryshkov EXPORT_SYMBOL(drm_connector_hdmi_cec_transmit_done);
194