summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/display/msm/cec.txt2
-rw-r--r--arch/arm/boot/dts/qcom/msm8998-sde-display.dtsi34
-rw-r--r--arch/arm/boot/dts/qcom/msm8998-sde.dtsi4
-rw-r--r--arch/arm64/configs/msmcortex_mediabox-perf_defconfig1
-rw-r--r--arch/arm64/configs/msmcortex_mediabox_defconfig1
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c47
-rw-r--r--drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h3
-rw-r--r--drivers/media/Kconfig4
-rw-r--r--drivers/media/Makefile4
-rw-r--r--drivers/media/cec-notifier.c129
-rw-r--r--drivers/media/cec/cec-core.c22
-rw-r--r--drivers/media/platform/msm/sde/Kconfig1
-rw-r--r--drivers/media/platform/msm/sde/cec/sde_hdmi_cec.c40
-rw-r--r--include/media/cec-notifier.h111
-rw-r--r--include/media/cec.h10
15 files changed, 403 insertions, 10 deletions
diff --git a/Documentation/devicetree/bindings/display/msm/cec.txt b/Documentation/devicetree/bindings/display/msm/cec.txt
index ba51b0d1dd18..e5282f1f5e9e 100644
--- a/Documentation/devicetree/bindings/display/msm/cec.txt
+++ b/Documentation/devicetree/bindings/display/msm/cec.txt
@@ -5,6 +5,7 @@ various audiovisual products in a user environment.
Required properties:
- compatible: Must be "qcom,hdmi-cec".
+- qcom,hdmi-dev: Phandle for the hdmi device node.
- interrupt-parent: Must be the hdmi interrupt controller.
- interrupts: Interrupt associated with cec.
- reg: Physical base address and length of the controller's registers.
@@ -41,6 +42,7 @@ Example:
sde_hdmi_cec: qcom,hdmi-cec@c9a0000 {
compatible = "qcom,hdmi-cec";
label = "sde_hdmi_cec";
+ qcom,hdmi-dev = <&sde_hdmi>;
interrupt-parent = <&sde_hdmi_tx>;
interrupts = <1 0>;
diff --git a/arch/arm/boot/dts/qcom/msm8998-sde-display.dtsi b/arch/arm/boot/dts/qcom/msm8998-sde-display.dtsi
index 6098a96db206..6e19c77995c1 100644
--- a/arch/arm/boot/dts/qcom/msm8998-sde-display.dtsi
+++ b/arch/arm/boot/dts/qcom/msm8998-sde-display.dtsi
@@ -34,6 +34,40 @@
qcom,msm_ext_disp = <&msm_ext_disp>;
};
+ sde_hdmi_cec: qcom,hdmi-cec@c9a0000 {
+ compatible = "qcom,hdmi-cec";
+ label = "sde_hdmi_cec";
+ qcom,hdmi-dev = <&sde_hdmi>;
+ interrupt-parent = <&sde_hdmi_tx>;
+ interrupts = <1 0>;
+
+ reg = <0xc9a0000 0x50c>;
+ reg-names = "hdmi_cec";
+
+ clocks = <&clock_mmss clk_mmss_mnoc_ahb_clk>,
+ <&clock_mmss clk_mmss_mdss_ahb_clk>,
+ <&clock_mmss clk_mmss_mdss_hdmi_clk>;
+ clock-names = "cec_mnoc_clk", "cec_iface_clk", "cec_core_clk";
+
+ pinctrl-names = "cec_active", "cec_sleep";
+ pinctrl-0 = <&mdss_hdmi_cec_active>;
+ pinctrl-1 = <&mdss_hdmi_cec_suspend>;
+
+ cec-gdsc-supply = <&gdsc_mdss>;
+ qcom,platform-supply-entries {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ qcom,platform-supply-entry@0 {
+ reg = <0>;
+ qcom,supply-name = "cec-gdsc";
+ qcom,supply-min-voltage = <0>;
+ qcom,supply-max-voltage = <0>;
+ qcom,supply-enable-load = <0>;
+ qcom,supply-disable-load = <0>;
+ };
+ };
+ };
};
&sde_kms {
diff --git a/arch/arm/boot/dts/qcom/msm8998-sde.dtsi b/arch/arm/boot/dts/qcom/msm8998-sde.dtsi
index f7dbda515643..795635d8d13d 100644
--- a/arch/arm/boot/dts/qcom/msm8998-sde.dtsi
+++ b/arch/arm/boot/dts/qcom/msm8998-sde.dtsi
@@ -175,6 +175,8 @@
reg-names = "core_physical", "qfprom_physical", "hdcp_physical";
interrupt-parent = <&sde_kms>;
interrupts = <8 0>;
+ interrupt-controller;
+ #interrupt-cells = <1>;
qcom,hdmi-tx-ddc-clk-gpio = <&tlmm 32 0>;
qcom,hdmi-tx-ddc-data-gpio = <&tlmm 33 0>;
qcom,hdmi-tx-hpd-gpio = <&tlmm 34 0>;
@@ -182,11 +184,9 @@
pinctrl-names = "default", "sleep";
pinctrl-0 = <&mdss_hdmi_hpd_active
&mdss_hdmi_ddc_active
- &mdss_hdmi_cec_active
&mdss_hdmi_5v_active>;
pinctrl-1 = <&mdss_hdmi_hpd_suspend
&mdss_hdmi_ddc_suspend
- &mdss_hdmi_cec_suspend
&mdss_hdmi_5v_suspend>;
hpd-gdsc-supply = <&gdsc_mdss>;
qcom,supply-names = "hpd-gdsc";
diff --git a/arch/arm64/configs/msmcortex_mediabox-perf_defconfig b/arch/arm64/configs/msmcortex_mediabox-perf_defconfig
index 953d700c20cc..b359c78a0d29 100644
--- a/arch/arm64/configs/msmcortex_mediabox-perf_defconfig
+++ b/arch/arm64/configs/msmcortex_mediabox-perf_defconfig
@@ -405,6 +405,7 @@ CONFIG_MSM_VIDC_VMEM=y
CONFIG_MSM_VIDC_GOVERNORS=y
CONFIG_MSM_SDE_ROTATOR=y
CONFIG_MSM_SDE_ROTATOR_EVTLOG_DEBUG=y
+CONFIG_MSM_SDE_HDMI_CEC=y
CONFIG_DVB_MPQ=m
CONFIG_DVB_MPQ_DEMUX=m
CONFIG_DVB_MPQ_MEDIA_BOX_DEMUX=y
diff --git a/arch/arm64/configs/msmcortex_mediabox_defconfig b/arch/arm64/configs/msmcortex_mediabox_defconfig
index d81c10d11318..994b0f230968 100644
--- a/arch/arm64/configs/msmcortex_mediabox_defconfig
+++ b/arch/arm64/configs/msmcortex_mediabox_defconfig
@@ -407,6 +407,7 @@ CONFIG_MSM_VIDC_VMEM=y
CONFIG_MSM_VIDC_GOVERNORS=y
CONFIG_MSM_SDE_ROTATOR=y
CONFIG_MSM_SDE_ROTATOR_EVTLOG_DEBUG=y
+CONFIG_MSM_SDE_HDMI_CEC=y
CONFIG_DVB_MPQ=m
CONFIG_DVB_MPQ_DEMUX=m
CONFIG_DVB_MPQ_MEDIA_BOX_DEMUX=y
diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c
index 4c70472bd338..acc417737558 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c
@@ -907,6 +907,17 @@ static void _sde_hdmi_hdp_disable(struct sde_hdmi *sde_hdmi)
}
}
+static void _sde_hdmi_cec_update_phys_addr(struct sde_hdmi *display)
+{
+ struct edid *edid = display->edid_ctrl->edid;
+
+ if (edid)
+ cec_notifier_set_phys_addr_from_edid(display->notifier, edid);
+ else
+ cec_notifier_set_phys_addr(display->notifier,
+ CEC_PHYS_ADDR_INVALID);
+}
+
static void _sde_hdmi_hotplug_work(struct work_struct *work)
{
struct sde_hdmi *sde_hdmi =
@@ -936,6 +947,7 @@ static void _sde_hdmi_hotplug_work(struct work_struct *work)
sde_free_edid((void **)&sde_hdmi->edid_ctrl);
drm_helper_hpd_irq_event(connector->dev);
+ _sde_hdmi_cec_update_phys_addr(sde_hdmi);
}
static void _sde_hdmi_connector_irq(struct sde_hdmi *sde_hdmi)
@@ -1673,10 +1685,28 @@ int sde_hdmi_dev_deinit(struct sde_hdmi *display)
SDE_ERROR("Invalid params\n");
return -EINVAL;
}
+ return 0;
+}
+
+static int _sde_hdmi_cec_init(struct sde_hdmi *display)
+{
+ struct platform_device *pdev = display->pdev;
+
+ display->notifier = cec_notifier_get(&pdev->dev);
+ if (!display->notifier) {
+ SDE_ERROR("CEC notifier get failed\n");
+ return -ENOMEM;
+ }
return 0;
}
+static void _sde_hdmi_cec_deinit(struct sde_hdmi *display)
+{
+ cec_notifier_set_phys_addr(display->notifier, CEC_PHYS_ADDR_INVALID);
+ cec_notifier_put(display->notifier);
+}
+
static int sde_hdmi_bind(struct device *dev, struct device *master, void *data)
{
int rc = 0;
@@ -1715,7 +1745,14 @@ static int sde_hdmi_bind(struct device *dev, struct device *master, void *data)
if (rc) {
SDE_ERROR("[%s]Ext Disp init failed, rc=%d\n",
display->name, rc);
- goto error;
+ goto ext_error;
+ }
+
+ rc = _sde_hdmi_cec_init(display);
+ if (rc) {
+ SDE_ERROR("[%s]CEC init failed, rc=%d\n",
+ display->name, rc);
+ goto ext_error;
}
display->edid_ctrl = sde_edid_init();
@@ -1723,7 +1760,7 @@ static int sde_hdmi_bind(struct device *dev, struct device *master, void *data)
SDE_ERROR("[%s]sde edid init failed\n",
display->name);
rc = -ENOMEM;
- goto error;
+ goto cec_error;
}
display_ctrl = &display->ctrl;
@@ -1732,7 +1769,10 @@ static int sde_hdmi_bind(struct device *dev, struct device *master, void *data)
mutex_unlock(&display->display_lock);
return rc;
-error:
+
+cec_error:
+ (void)_sde_hdmi_cec_deinit(display);
+ext_error:
(void)_sde_hdmi_debugfs_deinit(display);
debug_error:
mutex_unlock(&display->display_lock);
@@ -1758,6 +1798,7 @@ static void sde_hdmi_unbind(struct device *dev, struct device *master,
mutex_lock(&display->display_lock);
(void)_sde_hdmi_debugfs_deinit(display);
(void)sde_edid_deinit((void **)&display->edid_ctrl);
+ (void)_sde_hdmi_cec_deinit(display);
display->drm_dev = NULL;
mutex_unlock(&display->display_lock);
}
diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h
index 54506da4f9b0..dff245dec764 100644
--- a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h
+++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h
@@ -23,6 +23,7 @@
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
+#include <media/cec-notifier.h>
#include "hdmi.h"
#include "sde_edid_parser.h"
@@ -87,6 +88,7 @@ struct sde_hdmi_ctrl {
* @codec_ready: If audio codec is ready.
* @client_notify_pending: If there is client notification pending.
* @irq_domain: IRQ domain structure.
+ * @notifier: CEC notifider to convey physical address information.
* @root: Debug fs root entry.
*/
struct sde_hdmi {
@@ -116,6 +118,7 @@ struct sde_hdmi {
bool client_notify_pending;
struct irq_domain *irq_domain;
+ struct cec_notifier *notifier;
/* DEBUG FS */
struct dentry *root;
diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig
index 0b219a81e8a2..c1b999c6cff2 100644
--- a/drivers/media/Kconfig
+++ b/drivers/media/Kconfig
@@ -99,6 +99,10 @@ config MEDIA_CEC_DEBUG
config MEDIA_CEC_EDID
bool
+config MEDIA_CEC_NOTIFIER
+ bool
+ select MEDIA_CEC_EDID
+
#
# Media controller
# Selectable only for webcam/grabbers, as other drivers don't use it
diff --git a/drivers/media/Makefile b/drivers/media/Makefile
index ba516dcbc6aa..6c7801919b98 100644
--- a/drivers/media/Makefile
+++ b/drivers/media/Makefile
@@ -10,6 +10,10 @@ ifeq ($(CONFIG_MEDIA_CEC_SUPPORT),y)
obj-$(CONFIG_MEDIA_SUPPORT) += cec/
endif
+ifeq ($(CONFIG_MEDIA_CEC_NOTIFIER),y)
+ obj-$(CONFIG_MEDIA_SUPPORT) += cec-notifier.o
+endif
+
media-objs := media-device.o media-devnode.o media-entity.o
#
diff --git a/drivers/media/cec-notifier.c b/drivers/media/cec-notifier.c
new file mode 100644
index 000000000000..5f5209a73665
--- /dev/null
+++ b/drivers/media/cec-notifier.c
@@ -0,0 +1,129 @@
+/*
+ * cec-notifier.c - notify CEC drivers of physical address changes
+ *
+ * Copyright 2016 Russell King <rmk+kernel@arm.linux.org.uk>
+ * Copyright 2016-2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <linux/export.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/kref.h>
+
+#include <media/cec-notifier.h>
+#include <drm/drm_edid.h>
+
+struct cec_notifier {
+ struct mutex lock;
+ struct list_head head;
+ struct kref kref;
+ struct device *dev;
+ struct cec_adapter *cec_adap;
+ void (*callback)(struct cec_adapter *adap, u16 pa);
+
+ u16 phys_addr;
+};
+
+static LIST_HEAD(cec_notifiers);
+static DEFINE_MUTEX(cec_notifiers_lock);
+
+struct cec_notifier *cec_notifier_get(struct device *dev)
+{
+ struct cec_notifier *n;
+
+ mutex_lock(&cec_notifiers_lock);
+ list_for_each_entry(n, &cec_notifiers, head) {
+ if (n->dev == dev) {
+ kref_get(&n->kref);
+ mutex_unlock(&cec_notifiers_lock);
+ return n;
+ }
+ }
+ n = kzalloc(sizeof(*n), GFP_KERNEL);
+ if (!n)
+ goto unlock;
+ n->dev = dev;
+ n->phys_addr = CEC_PHYS_ADDR_INVALID;
+ mutex_init(&n->lock);
+ kref_init(&n->kref);
+ list_add_tail(&n->head, &cec_notifiers);
+unlock:
+ mutex_unlock(&cec_notifiers_lock);
+ return n;
+}
+EXPORT_SYMBOL_GPL(cec_notifier_get);
+
+static void cec_notifier_release(struct kref *kref)
+{
+ struct cec_notifier *n =
+ container_of(kref, struct cec_notifier, kref);
+
+ list_del(&n->head);
+ kfree(n);
+}
+
+void cec_notifier_put(struct cec_notifier *n)
+{
+ mutex_lock(&cec_notifiers_lock);
+ kref_put(&n->kref, cec_notifier_release);
+ mutex_unlock(&cec_notifiers_lock);
+}
+EXPORT_SYMBOL_GPL(cec_notifier_put);
+
+void cec_notifier_set_phys_addr(struct cec_notifier *n, u16 pa)
+{
+ mutex_lock(&n->lock);
+ n->phys_addr = pa;
+ if (n->callback)
+ n->callback(n->cec_adap, n->phys_addr);
+ mutex_unlock(&n->lock);
+}
+EXPORT_SYMBOL_GPL(cec_notifier_set_phys_addr);
+
+void cec_notifier_set_phys_addr_from_edid(struct cec_notifier *n,
+ const struct edid *edid)
+{
+ u16 pa = CEC_PHYS_ADDR_INVALID;
+
+ if (edid && edid->extensions)
+ pa = cec_get_edid_phys_addr((const u8 *)edid,
+ EDID_LENGTH * (edid->extensions + 1), NULL);
+ cec_notifier_set_phys_addr(n, pa);
+}
+EXPORT_SYMBOL_GPL(cec_notifier_set_phys_addr_from_edid);
+
+void cec_notifier_register(struct cec_notifier *n,
+ struct cec_adapter *adap,
+ void (*callback)(struct cec_adapter *adap, u16 pa))
+{
+ kref_get(&n->kref);
+ mutex_lock(&n->lock);
+ n->cec_adap = adap;
+ n->callback = callback;
+ n->callback(adap, n->phys_addr);
+ mutex_unlock(&n->lock);
+}
+EXPORT_SYMBOL_GPL(cec_notifier_register);
+
+void cec_notifier_unregister(struct cec_notifier *n)
+{
+ mutex_lock(&n->lock);
+ n->callback = NULL;
+ mutex_unlock(&n->lock);
+ cec_notifier_put(n);
+}
+EXPORT_SYMBOL_GPL(cec_notifier_unregister);
diff --git a/drivers/media/cec/cec-core.c b/drivers/media/cec/cec-core.c
index aca3ab83a8a1..61adc28ec8ec 100644
--- a/drivers/media/cec/cec-core.c
+++ b/drivers/media/cec/cec-core.c
@@ -195,6 +195,24 @@ static void cec_devnode_unregister(struct cec_devnode *devnode)
put_device(&devnode->dev);
}
+#ifdef CONFIG_MEDIA_CEC_NOTIFIER
+static void cec_cec_notify(struct cec_adapter *adap, u16 pa)
+{
+ cec_s_phys_addr(adap, pa, false);
+}
+
+void cec_register_cec_notifier(struct cec_adapter *adap,
+ struct cec_notifier *notifier)
+{
+ if (WARN_ON(!adap->devnode.registered))
+ return;
+
+ adap->notifier = notifier;
+ cec_notifier_register(adap->notifier, adap, cec_cec_notify);
+}
+EXPORT_SYMBOL_GPL(cec_register_cec_notifier);
+#endif
+
struct cec_adapter *cec_allocate_adapter(const struct cec_adap_ops *ops,
void *priv, const char *name, u32 caps,
u8 available_las)
@@ -344,6 +362,10 @@ void cec_unregister_adapter(struct cec_adapter *adap)
adap->rc = NULL;
#endif
debugfs_remove_recursive(adap->cec_dir);
+#ifdef CONFIG_MEDIA_CEC_NOTIFIER
+ if (adap->notifier)
+ cec_notifier_unregister(adap->notifier);
+#endif
cec_devnode_unregister(&adap->devnode);
}
EXPORT_SYMBOL_GPL(cec_unregister_adapter);
diff --git a/drivers/media/platform/msm/sde/Kconfig b/drivers/media/platform/msm/sde/Kconfig
index c0e73813ab06..d2c2e90f9de9 100644
--- a/drivers/media/platform/msm/sde/Kconfig
+++ b/drivers/media/platform/msm/sde/Kconfig
@@ -21,6 +21,7 @@ config MSM_SDE_HDMI_CEC
depends on DRM_SDE_HDMI
select MEDIA_CEC_SUPPORT
select MEDIA_CEC_EDID
+ select MEDIA_CEC_NOTIFIER
---help---
The HDMI CEC driver provides support to enable HDMI CEC features
which allows various audiovisual products to communicate using HDMI
diff --git a/drivers/media/platform/msm/sde/cec/sde_hdmi_cec.c b/drivers/media/platform/msm/sde/cec/sde_hdmi_cec.c
index 0fed19a01d5b..b3798e8a9d24 100644
--- a/drivers/media/platform/msm/sde/cec/sde_hdmi_cec.c
+++ b/drivers/media/platform/msm/sde/cec/sde_hdmi_cec.c
@@ -18,11 +18,14 @@
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/of_platform.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/cec.h>
#include <media/cec.h>
+#include <media/cec-edid.h>
+#include <media/cec-notifier.h>
#include "sde_hdmi_cec_util.h"
@@ -63,6 +66,7 @@ struct sde_hdmi_cec {
struct cec_hw_resource hw_res;
int irq;
enum cec_irq_status irq_status;
+ struct cec_notifier *notifier;
};
static int sde_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
@@ -287,6 +291,8 @@ static int sde_hdmi_cec_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct sde_hdmi_cec *cec;
+ struct device_node *np;
+ struct platform_device *hdmi_dev;
int ret;
cec = devm_kzalloc(dev, sizeof(*cec), GFP_KERNEL);
@@ -295,6 +301,15 @@ static int sde_hdmi_cec_probe(struct platform_device *pdev)
cec->dev = dev;
+ np = of_parse_phandle(pdev->dev.of_node, "qcom,hdmi-dev", 0);
+ if (!np) {
+ pr_err("failed to find hdmi node in device tree\n");
+ return -ENODEV;
+ }
+ hdmi_dev = of_find_device_by_node(np);
+ if (hdmi_dev == NULL)
+ return -EPROBE_DEFER;
+
cec->irq = of_irq_get(dev->of_node, 0);
if (cec->irq < 0) {
pr_err("failed to get irq\n");
@@ -314,24 +329,38 @@ static int sde_hdmi_cec_probe(struct platform_device *pdev)
cec->adap = cec_allocate_adapter(&sde_hdmi_cec_adap_ops, cec,
CEC_NAME,
CEC_CAP_LOG_ADDRS | CEC_CAP_PASSTHROUGH |
- CEC_CAP_PHYS_ADDR | CEC_CAP_TRANSMIT, 1);
+ CEC_CAP_TRANSMIT, 1);
ret = PTR_ERR_OR_ZERO(cec->adap);
if (ret)
return ret;
ret = cec_register_adapter(cec->adap, &pdev->dev);
- if (ret) {
- cec_delete_adapter(cec->adap);
- return ret;
+ if (ret)
+ goto err_del_adap;
+
+ cec->notifier = cec_notifier_get(&hdmi_dev->dev);
+ if (!cec->notifier) {
+ pr_err("failed to get cec notifier\n");
+ goto err_del_adap;
}
platform_set_drvdata(pdev, cec);
pm_runtime_enable(dev);
+ /*
+ * cec_register_cec_notifier has to be later than pm_runtime_enable
+ * because it calls adap_enable.
+ */
+ cec_register_cec_notifier(cec->adap, cec->notifier);
+
pr_debug("probe done\n");
- return 0;
+ return ret;
+
+err_del_adap:
+ cec_delete_adapter(cec->adap);
+ return ret;
}
static int sde_hdmi_cec_remove(struct platform_device *pdev)
@@ -341,6 +370,7 @@ static int sde_hdmi_cec_remove(struct platform_device *pdev)
pm_runtime_disable(&pdev->dev);
cec_unregister_adapter(cec->adap);
+ cec_notifier_put(cec->notifier);
devm_free_irq(&pdev->dev, cec->irq, cec);
sde_hdmi_cec_deinit_resource(pdev, &cec->hw_res);
diff --git a/include/media/cec-notifier.h b/include/media/cec-notifier.h
new file mode 100644
index 000000000000..035712e0993d
--- /dev/null
+++ b/include/media/cec-notifier.h
@@ -0,0 +1,111 @@
+/*
+ * cec-notifier.h - notify CEC drivers of physical address changes
+ *
+ * Copyright 2016 Russell King <rmk+kernel@arm.linux.org.uk>
+ * Copyright 2016-2017 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
+ *
+ * This program is free software; you may redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef LINUX_CEC_NOTIFIER_H
+#define LINUX_CEC_NOTIFIER_H
+
+#include <linux/types.h>
+#include <media/cec-edid.h>
+
+struct device;
+struct edid;
+struct cec_adapter;
+struct cec_notifier;
+
+#ifdef CONFIG_MEDIA_CEC_NOTIFIER
+
+/**
+ * cec_notifier_get - find or create a new cec_notifier for the given device.
+ * @dev: device that sends the events.
+ *
+ * If a notifier for device @dev already exists, then increase the refcount
+ * and return that notifier.
+ *
+ * If it doesn't exist, then allocate a new notifier struct and return a
+ * pointer to that new struct.
+ *
+ * Return NULL if the memory could not be allocated.
+ */
+struct cec_notifier *cec_notifier_get(struct device *dev);
+
+/**
+ * cec_notifier_put - decrease refcount and delete when the refcount reaches 0.
+ * @n: notifier
+ */
+void cec_notifier_put(struct cec_notifier *n);
+
+/**
+ * cec_notifier_set_phys_addr - set a new physical address.
+ * @n: the CEC notifier
+ * @pa: the CEC physical address
+ *
+ * Set a new CEC physical address.
+ */
+void cec_notifier_set_phys_addr(struct cec_notifier *n, u16 pa);
+
+/**
+ * cec_notifier_set_phys_addr_from_edid - set parse the PA from the EDID.
+ * @n: the CEC notifier
+ * @edid: the struct edid pointer
+ *
+ * Parses the EDID to obtain the new CEC physical address and set it.
+ */
+void cec_notifier_set_phys_addr_from_edid(struct cec_notifier *n,
+ const struct edid *edid);
+
+/**
+ * cec_notifier_register - register a callback with the notifier
+ * @n: the CEC notifier
+ * @adap: the CEC adapter, passed as argument to the callback function
+ * @callback: the callback function
+ */
+void cec_notifier_register(struct cec_notifier *n,
+ struct cec_adapter *adap,
+ void (*callback)(struct cec_adapter *adap, u16 pa));
+
+/**
+ * cec_notifier_unregister - unregister the callback from the notifier.
+ * @n: the CEC notifier
+ */
+void cec_notifier_unregister(struct cec_notifier *n);
+
+#else
+static inline struct cec_notifier *cec_notifier_get(struct device *dev)
+{
+ /* A non-NULL pointer is expected on success */
+ return (struct cec_notifier *)0xdeadfeed;
+}
+
+static inline void cec_notifier_put(struct cec_notifier *n)
+{
+}
+
+static inline void cec_notifier_set_phys_addr(struct cec_notifier *n, u16 pa)
+{
+}
+
+static inline void cec_notifier_set_phys_addr_from_edid(struct cec_notifier *n,
+ const struct edid *edid)
+{
+}
+
+#endif
+
+#endif
diff --git a/include/media/cec.h b/include/media/cec.h
index 96a0aa770d61..307f5dcaf034 100644
--- a/include/media/cec.h
+++ b/include/media/cec.h
@@ -30,6 +30,7 @@
#include <linux/cec-funcs.h>
#include <media/rc-core.h>
#include <media/cec-edid.h>
+#include <media/cec-notifier.h>
/**
* struct cec_devnode - cec device node
@@ -173,6 +174,10 @@ struct cec_adapter {
bool passthrough;
struct cec_log_addrs log_addrs;
+#ifdef CONFIG_MEDIA_CEC_NOTIFIER
+ struct cec_notifier *notifier;
+#endif
+
struct dentry *cec_dir;
struct dentry *status_file;
@@ -213,6 +218,11 @@ void cec_transmit_done(struct cec_adapter *adap, u8 status, u8 arb_lost_cnt,
u8 nack_cnt, u8 low_drive_cnt, u8 error_cnt);
void cec_received_msg(struct cec_adapter *adap, struct cec_msg *msg);
+#ifdef CONFIG_MEDIA_CEC_NOTIFIER
+void cec_register_cec_notifier(struct cec_adapter *adap,
+ struct cec_notifier *notifier);
+#endif
+
#else
static inline int cec_register_adapter(struct cec_adapter *adap,