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