brintos

brintos / linux-shallow public Read only

0
0
Text · 6.1 KiB · 1fed0b1 Raw
257 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * cec-notifier.c - notify CEC drivers of physical address changes4 *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 given39 * device and connector tuple.40 * @hdmi_dev: device that sends the events.41 * @port_name: the connector name from which the event occurs42 *43 * If a notifier for device @dev already exists, then increase the refcount44 * and return that notifier.45 *46 * If it doesn't exist, then allocate a new notifier struct and return a47 * 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	else119		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#endif243	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 the249	 * cec_notifiers list, it is never actually accessed.250	 * So we decrement the reference here so we don't leak251	 * memory.252	 */253	put_device(hdmi_dev);254	return hdmi_dev;255}256EXPORT_SYMBOL_GPL(cec_notifier_parse_hdmi_phandle);257