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 | |
22 | struct 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 | |
34 | static LIST_HEAD(cec_notifiers); |
35 | static 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 | */ |
51 | static struct cec_notifier * |
52 | cec_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(kref: &n->kref); |
62 | mutex_unlock(lock: &cec_notifiers_lock); |
63 | return n; |
64 | } |
65 | } |
66 | n = kzalloc(size: 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(s: port_name, GFP_KERNEL); |
72 | if (!n->port_name) { |
73 | kfree(objp: 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(kref: &n->kref); |
82 | list_add_tail(new: &n->head, head: &cec_notifiers); |
83 | unlock: |
84 | mutex_unlock(lock: &cec_notifiers_lock); |
85 | return n; |
86 | } |
87 | |
88 | static 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(entry: &n->head); |
94 | kfree(objp: n->port_name); |
95 | kfree(objp: n); |
96 | } |
97 | |
98 | static void cec_notifier_put(struct cec_notifier *n) |
99 | { |
100 | mutex_lock(&cec_notifiers_lock); |
101 | kref_put(kref: &n->kref, release: cec_notifier_release); |
102 | mutex_unlock(lock: &cec_notifiers_lock); |
103 | } |
104 | |
105 | struct cec_notifier * |
106 | cec_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(adap: n->cec_adap); |
123 | cec_s_conn_info(adap: n->cec_adap, conn_info); |
124 | } |
125 | mutex_unlock(lock: &n->lock); |
126 | return n; |
127 | } |
128 | EXPORT_SYMBOL_GPL(cec_notifier_conn_register); |
129 | |
130 | void 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(adap: n->cec_adap); |
141 | cec_s_conn_info(adap: n->cec_adap, NULL); |
142 | } |
143 | mutex_unlock(lock: &n->lock); |
144 | cec_notifier_put(n); |
145 | } |
146 | EXPORT_SYMBOL_GPL(cec_notifier_conn_unregister); |
147 | |
148 | struct cec_notifier * |
149 | cec_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, phys_addr: n->phys_addr, block: false); |
167 | mutex_unlock(lock: &n->lock); |
168 | return n; |
169 | } |
170 | EXPORT_SYMBOL_GPL(cec_notifier_cec_adap_register); |
171 | |
172 | void 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(lock: &n->lock); |
182 | cec_notifier_put(n); |
183 | } |
184 | EXPORT_SYMBOL_GPL(cec_notifier_cec_adap_unregister); |
185 | |
186 | void 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(adap: n->cec_adap, phys_addr: n->phys_addr, block: false); |
195 | mutex_unlock(lock: &n->lock); |
196 | } |
197 | EXPORT_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 | */ |
204 | void 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(edid: (const u8 *)edid, |
214 | EDID_LENGTH * (edid->extensions + 1), NULL); |
215 | cec_notifier_set_phys_addr(n, pa); |
216 | } |
217 | EXPORT_SYMBOL_GPL(cec_notifier_set_phys_addr_from_edid); |
218 | |
219 | struct 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(np: dev->of_node, phandle_name: "hdmi-phandle" , index: 0); |
226 | |
227 | if (!np) { |
228 | dev_err(dev, "Failed to find HDMI node in device tree\n" ); |
229 | return ERR_PTR(error: -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(node: np); |
238 | |
239 | if (hdmi_client) |
240 | hdmi_dev = &hdmi_client->dev; |
241 | } |
242 | #endif |
243 | of_node_put(node: np); |
244 | if (!hdmi_dev) |
245 | return ERR_PTR(error: -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(dev: hdmi_dev); |
254 | return hdmi_dev; |
255 | } |
256 | EXPORT_SYMBOL_GPL(cec_notifier_parse_hdmi_phandle); |
257 | |