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