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