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