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