1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * cec-notifier.c - notify CEC drivers of physical address changes 4 * 5 * Copyright 2016 Russell King. 6 * Copyright 2016-2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved. 7 */ 8 9#include <linux/export.h> 10#include <linux/platform_device.h> 11#include <linux/string.h> 12#include <linux/slab.h> 13#include <linux/i2c.h> 14#include <linux/list.h> 15#include <linux/kref.h> 16#include <linux/of_platform.h> 17 18#include <media/cec.h> 19#include <media/cec-notifier.h> 20#include <drm/drm_edid.h> 21 22struct cec_notifier { 23 struct mutex lock; 24 struct list_head head; 25 struct kref kref; 26 struct device *hdmi_dev; 27 struct cec_connector_info conn_info; 28 const char *port_name; 29 struct cec_adapter *cec_adap; 30 31 u16 phys_addr; 32}; 33 34static LIST_HEAD(cec_notifiers); 35static DEFINE_MUTEX(cec_notifiers_lock); 36 37/** 38 * cec_notifier_get_conn - find or create a new cec_notifier for the given 39 * device and connector tuple. 40 * @hdmi_dev: device that sends the events. 41 * @port_name: the connector name from which the event occurs 42 * 43 * If a notifier for device @dev already exists, then increase the refcount 44 * and return that notifier. 45 * 46 * If it doesn't exist, then allocate a new notifier struct and return a 47 * pointer to that new struct. 48 * 49 * Return NULL if the memory could not be allocated. 50 */ 51static struct cec_notifier * 52cec_notifier_get_conn(struct device *hdmi_dev, const char *port_name) 53{ 54 struct cec_notifier *n; 55 56 mutex_lock(&cec_notifiers_lock); 57 list_for_each_entry(n, &cec_notifiers, head) { 58 if (n->hdmi_dev == hdmi_dev && 59 (!port_name || 60 (n->port_name && !strcmp(n->port_name, port_name)))) { 61 kref_get(&n->kref); 62 mutex_unlock(&cec_notifiers_lock); 63 return n; 64 } 65 } 66 n = kzalloc(sizeof(*n), GFP_KERNEL); 67 if (!n) 68 goto unlock; 69 n->hdmi_dev = hdmi_dev; 70 if (port_name) { 71 n->port_name = kstrdup(port_name, GFP_KERNEL); 72 if (!n->port_name) { 73 kfree(n); 74 n = NULL; 75 goto unlock; 76 } 77 } 78 n->phys_addr = CEC_PHYS_ADDR_INVALID; 79 80 mutex_init(&n->lock); 81 kref_init(&n->kref); 82 list_add_tail(&n->head, &cec_notifiers); 83unlock: 84 mutex_unlock(&cec_notifiers_lock); 85 return n; 86} 87 88static void cec_notifier_release(struct kref *kref) 89{ 90 struct cec_notifier *n = 91 container_of(kref, struct cec_notifier, kref); 92 93 list_del(&n->head); 94 kfree(n->port_name); 95 kfree(n); 96} 97 98static void cec_notifier_put(struct cec_notifier *n) 99{ 100 mutex_lock(&cec_notifiers_lock); 101 kref_put(&n->kref, cec_notifier_release); 102 mutex_unlock(&cec_notifiers_lock); 103} 104 105struct cec_notifier * 106cec_notifier_conn_register(struct device *hdmi_dev, const char *port_name, 107 const struct cec_connector_info *conn_info) 108{ 109 struct cec_notifier *n = cec_notifier_get_conn(hdmi_dev, port_name); 110 111 if (!n) 112 return n; 113 114 mutex_lock(&n->lock); 115 n->phys_addr = CEC_PHYS_ADDR_INVALID; 116 if (conn_info) 117 n->conn_info = *conn_info; 118 else 119 memset(&n->conn_info, 0, sizeof(n->conn_info)); 120 if (n->cec_adap) { 121 if (!n->cec_adap->adap_controls_phys_addr) 122 cec_phys_addr_invalidate(n->cec_adap); 123 cec_s_conn_info(n->cec_adap, conn_info); 124 } 125 mutex_unlock(&n->lock); 126 return n; 127} 128EXPORT_SYMBOL_GPL(cec_notifier_conn_register); 129 130void cec_notifier_conn_unregister(struct cec_notifier *n) 131{ 132 if (!n) 133 return; 134 135 mutex_lock(&n->lock); 136 memset(&n->conn_info, 0, sizeof(n->conn_info)); 137 n->phys_addr = CEC_PHYS_ADDR_INVALID; 138 if (n->cec_adap) { 139 if (!n->cec_adap->adap_controls_phys_addr) 140 cec_phys_addr_invalidate(n->cec_adap); 141 cec_s_conn_info(n->cec_adap, NULL); 142 } 143 mutex_unlock(&n->lock); 144 cec_notifier_put(n); 145} 146EXPORT_SYMBOL_GPL(cec_notifier_conn_unregister); 147 148struct cec_notifier * 149cec_notifier_cec_adap_register(struct device *hdmi_dev, const char *port_name, 150 struct cec_adapter *adap) 151{ 152 struct cec_notifier *n; 153 154 if (WARN_ON(!adap)) 155 return NULL; 156 157 n = cec_notifier_get_conn(hdmi_dev, port_name); 158 if (!n) 159 return n; 160 161 mutex_lock(&n->lock); 162 n->cec_adap = adap; 163 adap->conn_info = n->conn_info; 164 adap->notifier = n; 165 if (!adap->adap_controls_phys_addr) 166 cec_s_phys_addr(adap, n->phys_addr, false); 167 mutex_unlock(&n->lock); 168 return n; 169} 170EXPORT_SYMBOL_GPL(cec_notifier_cec_adap_register); 171 172void cec_notifier_cec_adap_unregister(struct cec_notifier *n, 173 struct cec_adapter *adap) 174{ 175 if (!n) 176 return; 177 178 mutex_lock(&n->lock); 179 adap->notifier = NULL; 180 n->cec_adap = NULL; 181 mutex_unlock(&n->lock); 182 cec_notifier_put(n); 183} 184EXPORT_SYMBOL_GPL(cec_notifier_cec_adap_unregister); 185 186void cec_notifier_set_phys_addr(struct cec_notifier *n, u16 pa) 187{ 188 if (n == NULL) 189 return; 190 191 mutex_lock(&n->lock); 192 n->phys_addr = pa; 193 if (n->cec_adap && !n->cec_adap->adap_controls_phys_addr) 194 cec_s_phys_addr(n->cec_adap, n->phys_addr, false); 195 mutex_unlock(&n->lock); 196} 197EXPORT_SYMBOL_GPL(cec_notifier_set_phys_addr); 198 199/* 200 * Note: In the drm subsystem, prefer calling (if possible): 201 * 202 * cec_notifier_set_phys_addr(n, connector->display_info.source_physical_address); 203 */ 204void cec_notifier_set_phys_addr_from_edid(struct cec_notifier *n, 205 const struct edid *edid) 206{ 207 u16 pa = CEC_PHYS_ADDR_INVALID; 208 209 if (n == NULL) 210 return; 211 212 if (edid && edid->extensions) 213 pa = cec_get_edid_phys_addr((const u8 *)edid, 214 EDID_LENGTH * (edid->extensions + 1), NULL); 215 cec_notifier_set_phys_addr(n, pa); 216} 217EXPORT_SYMBOL_GPL(cec_notifier_set_phys_addr_from_edid); 218 219struct device *cec_notifier_parse_hdmi_phandle(struct device *dev) 220{ 221 struct platform_device *hdmi_pdev; 222 struct device *hdmi_dev = NULL; 223 struct device_node *np; 224 225 np = of_parse_phandle(dev->of_node, "hdmi-phandle", 0); 226 227 if (!np) { 228 dev_err(dev, "Failed to find HDMI node in device tree\n"); 229 return ERR_PTR(-ENODEV); 230 } 231 232 hdmi_pdev = of_find_device_by_node(np); 233 if (hdmi_pdev) 234 hdmi_dev = &hdmi_pdev->dev; 235#if IS_REACHABLE(CONFIG_I2C) 236 if (!hdmi_dev) { 237 struct i2c_client *hdmi_client = of_find_i2c_device_by_node(np); 238 239 if (hdmi_client) 240 hdmi_dev = &hdmi_client->dev; 241 } 242#endif 243 of_node_put(np); 244 if (!hdmi_dev) 245 return ERR_PTR(-EPROBE_DEFER); 246 247 /* 248 * Note that the device struct is only used as a key into the 249 * cec_notifiers list, it is never actually accessed. 250 * So we decrement the reference here so we don't leak 251 * memory. 252 */ 253 put_device(hdmi_dev); 254 return hdmi_dev; 255} 256EXPORT_SYMBOL_GPL(cec_notifier_parse_hdmi_phandle); 257