From 020578dd022e5d869db52e79c5aba95c1f1a84ec Mon Sep 17 00:00:00 2001
From: Abel Vesa <abel.vesa@nxp.com>
Date: Wed, 11 Dec 2019 09:21:23 +0200
Subject: [PATCH] LF-794-3 gpu: cdn: imx8qm: Add firmware loading support

This allows the  HDP i.MX8QM driver to load the firmware on init
and resume. In order to have backward compatibility, if there is
no firmware-name property defined in the hdmi node, the driver
probing sequence skips the firmware loading.

Also, if u-boot has loaded already a firmware, we run with that
but when probing the driver, the request_firmware_nowait is used
to locate and keep safe the firmware for when suspend/resume happens.

This leads to 4 possible scenarios:

1. u-boot loads the firmware, the kernel driver finds the firmware
when rootfs is mounted. This is the most desirable scenario. Also
this is the only scenario that allows the hdmi to work after resume.

2. u-boot loads the firmware, the kernel driver _doesn't_ find
the firmware in rootfs. If there is no suspend ever happening,
the kernel driver will keep using the firmware that was loaded by
u-boot. On the first suspend/resume, the firmware is lost
because the HDMI IP gets powered down.

3. u-boot doesn't load the firmare, the kernel driver probing
tries to load the firmware, assuming this is available
(see CONFIG_EXTRA_FIRMWARE).

4. u-boot doesn't load the firmware and the kernel driver is not
able to find it either. The probing fails and there is no HDMI
available in linux.

Signed-off-by: Abel Vesa <abel.vesa@nxp.com>
Reviewed-by: Sandor Yu <sandor.yu@nxp.com>
Acked-by: Wen He <wen.he_1@nxp.com>
Signed-off-by: Dong Aisheng <aisheng.dong@nxp.com>
---
 drivers/gpu/drm/imx/cdn-mhdp-imx8qm.c | 78 +++++++++++++++++++++++++++++++++--
 drivers/gpu/drm/imx/cdn-mhdp-imxdrv.c | 30 ++++++++++++++
 drivers/gpu/drm/imx/cdns-mhdp-imx.h   |  4 ++
 include/drm/bridge/cdns-mhdp-common.h |  3 ++
 4 files changed, 111 insertions(+), 4 deletions(-)

--- a/drivers/gpu/drm/imx/cdn-mhdp-imx8qm.c
+++ b/drivers/gpu/drm/imx/cdn-mhdp-imx8qm.c
@@ -7,12 +7,17 @@
  */
 #include <dt-bindings/firmware/imx/rsrc.h>
 #include <linux/firmware/imx/sci.h>
+#include <linux/firmware.h>
 #include <linux/pm_domain.h>
 #include <linux/clk.h>
 #include <drm/drmP.h>
 
 #include "cdns-mhdp-imx.h"
 
+#define FW_IRAM_OFFSET		0x2000
+#define FW_IRAM_SIZE		0x10000
+#define FW_DRAM_SIZE		0x8000
+
 #define PLL_800MHZ (800000000)
 
 #define HDP_DUAL_MODE_MIN_PCLK_RATE	300000	/* KHz */
@@ -517,24 +522,69 @@ void cdns_mhdp_pclk_rate_imx8qm(struct c
 	imx8qm_pixel_link_mux(imx_mhdp);
 }
 
-int cdns_mhdp_firmware_init_imx8qm(struct cdns_mhdp_device *mhdp)
+static void cdns_mhdp_firmware_load_cont(const struct firmware *fw, void *context)
 {
-	struct imx_mhdp_device *imx_mhdp =
-				container_of(mhdp, struct imx_mhdp_device, mhdp);
+	struct imx_mhdp_device *imx_mhdp = context;
+
+	imx_mhdp->fw = fw;
+}
+
+static int cdns_mhdp_load_firmware_imx8qm(struct imx_mhdp_device *imx_mhdp)
+{
+	const u8 *iram;
+	const u8 *dram;
 	u32 rate;
 	int ret;
 
 	/* configure HDMI/DP core clock */
 	rate = clk_get_rate(imx_mhdp->clks.clk_core);
-	if (mhdp->is_ls1028a)
+	if (imx_mhdp->mhdp.is_ls1028a)
 		rate = rate / 4;
 
 	cdns_mhdp_set_fw_clk(&imx_mhdp->mhdp, rate);
 
+	/* skip fw loading if none is specified */
+	if (!imx_mhdp->firmware_name)
+		goto out;
+
+	if (!imx_mhdp->fw) {
+		ret = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOHOTPLUG,
+						imx_mhdp->firmware_name,
+						imx_mhdp->mhdp.dev, GFP_KERNEL,
+						imx_mhdp,
+						cdns_mhdp_firmware_load_cont);
+		if (ret < 0) {
+			DRM_ERROR("failed to load firmware\n");
+			return -ENOENT;
+		}
+	} else {
+		iram = imx_mhdp->fw->data + FW_IRAM_OFFSET;
+		dram = iram + FW_IRAM_SIZE;
+
+		cdns_mhdp_load_firmware(&imx_mhdp->mhdp,
+					(const u32 *) iram, FW_IRAM_SIZE,
+					(const u32 *) dram, FW_DRAM_SIZE);
+	}
+
+out:
 	/* un-reset ucpu */
 	cdns_mhdp_bus_write(0, &imx_mhdp->mhdp, APB_CTRL);
 	DRM_INFO("Started firmware!\n");
 
+	return 0;
+}
+
+int cdns_mhdp_firmware_init_imx8qm(struct cdns_mhdp_device *mhdp)
+{
+	struct imx_mhdp_device *imx_mhdp =
+				container_of(mhdp, struct imx_mhdp_device, mhdp);
+	int ret;
+
+	/* load firmware */
+	ret = cdns_mhdp_load_firmware_imx8qm(imx_mhdp);
+	if (ret)
+		return ret;
+
 	ret = cdns_mhdp_check_alive(&imx_mhdp->mhdp);
 	if (ret == false) {
 		DRM_ERROR("NO HDMI FW running\n");
@@ -550,3 +600,23 @@ int cdns_mhdp_firmware_init_imx8qm(struc
 
 	return 0;
 }
+
+int cdns_mhdp_suspend_imx8qm(struct cdns_mhdp_device *mhdp)
+{
+	struct imx_mhdp_device *imx_mhdp =
+				container_of(mhdp, struct imx_mhdp_device, mhdp);
+
+	imx8qm_pixel_clk_disable(imx_mhdp);
+
+	return 0;
+}
+
+int cdns_mhdp_resume_imx8qm(struct cdns_mhdp_device *mhdp)
+{
+	struct imx_mhdp_device *imx_mhdp =
+				container_of(mhdp, struct imx_mhdp_device, mhdp);
+
+	imx8qm_pixel_clk_enable(imx_mhdp);
+
+	return cdns_mhdp_firmware_init_imx8qm(mhdp);
+}
--- a/drivers/gpu/drm/imx/cdn-mhdp-imxdrv.c
+++ b/drivers/gpu/drm/imx/cdn-mhdp-imxdrv.c
@@ -82,6 +82,8 @@ static struct cdns_plat_data imx8qm_hdmi
 	.phy_video_valid = cdns_hdmi_phy_video_valid_imx8qm,
 	.power_on = cdns_mhdp_power_on_imx8qm,
 	.firmware_init = cdns_mhdp_firmware_init_imx8qm,
+	.resume = cdns_mhdp_resume_imx8qm,
+	.suspend = cdns_mhdp_suspend_imx8qm,
 	.pclk_rate = cdns_mhdp_pclk_rate_imx8qm,
 	.plat_init = cdns_mhdp_plat_init_imx8qm,
 	.plat_deinit = cdns_mhdp_plat_deinit_imx8qm,
@@ -96,6 +98,8 @@ static struct cdns_plat_data imx8qm_dp_d
 	.phy_set = cdns_dp_phy_set_imx8qm,
 	.power_on = cdns_mhdp_power_on_imx8qm,
 	.firmware_init = cdns_mhdp_firmware_init_imx8qm,
+	.resume = cdns_mhdp_resume_imx8qm,
+	.suspend = cdns_mhdp_suspend_imx8qm,
 	.pclk_rate = cdns_mhdp_pclk_rate_imx8qm,
 	.plat_init = cdns_mhdp_plat_init_imx8qm,
 	.plat_deinit = cdns_mhdp_plat_deinit_imx8qm,
@@ -157,6 +161,9 @@ static int cdns_mhdp_imx_bind(struct dev
 	encoder = &imx_mhdp->encoder;
 
 	encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
+
+	ret = of_property_read_string(pdev->dev.of_node, "firmware-name",
+					&imx_mhdp->firmware_name);
 	/*
 	 * If we failed to find the CRTC(s) which this encoder is
 	 * supposed to be connected to, it's because the CRTC has
@@ -198,6 +205,24 @@ static const struct component_ops cdns_m
 	.unbind	= cdns_mhdp_imx_unbind,
 };
 
+static int cdns_mhdp_imx_suspend(struct device *dev)
+{
+	struct imx_mhdp_device *imx_mhdp = dev_get_drvdata(dev);
+
+	cdns_mhdp_plat_call(&imx_mhdp->mhdp, suspend);
+
+	return 0;
+}
+
+static int cdns_mhdp_imx_resume(struct device *dev)
+{
+	struct imx_mhdp_device *imx_mhdp = dev_get_drvdata(dev);
+
+	cdns_mhdp_plat_call(&imx_mhdp->mhdp, resume);
+
+	return 0;
+}
+
 static int cdns_mhdp_imx_probe(struct platform_device *pdev)
 {
 	return component_add(&pdev->dev, &cdns_mhdp_imx_ops);
@@ -210,12 +235,17 @@ static int cdns_mhdp_imx_remove(struct p
 	return 0;
 }
 
+static const struct dev_pm_ops cdns_mhdp_imx_pm_ops = {
+        SET_LATE_SYSTEM_SLEEP_PM_OPS(cdns_mhdp_imx_suspend, cdns_mhdp_imx_resume)
+};
+
 static struct platform_driver cdns_mhdp_imx_platform_driver = {
 	.probe  = cdns_mhdp_imx_probe,
 	.remove = cdns_mhdp_imx_remove,
 	.driver = {
 		.name = "cdns-mhdp-imx",
 		.of_match_table = cdns_mhdp_imx_dt_ids,
+		.pm = &cdns_mhdp_imx_pm_ops,
 	},
 };
 
--- a/drivers/gpu/drm/imx/cdns-mhdp-imx.h
+++ b/drivers/gpu/drm/imx/cdns-mhdp-imx.h
@@ -50,6 +50,8 @@ struct imx_mhdp_device {
 	bool active;
 	bool suspended;
 	struct imx_hdp_clks clks;
+	const struct firmware *fw;
+	const char *firmware_name;
 
 	int bus_type;
 
@@ -65,6 +67,8 @@ void cdns_mhdp_plat_init_imx8qm(struct c
 void cdns_mhdp_plat_deinit_imx8qm(struct cdns_mhdp_device *mhdp);
 void cdns_mhdp_pclk_rate_imx8qm(struct cdns_mhdp_device *mhdp);
 int cdns_mhdp_firmware_init_imx8qm(struct cdns_mhdp_device *mhdp);
+int cdns_mhdp_resume_imx8qm(struct cdns_mhdp_device *mhdp);
+int cdns_mhdp_suspend_imx8qm(struct cdns_mhdp_device *mhdp);
 int cdns_mhdp_power_on_imx8qm(struct cdns_mhdp_device *mhdp);
 int cdns_mhdp_power_on_ls1028a(struct cdns_mhdp_device *mhdp);
 void cdns_mhdp_pclk_rate_ls1028a(struct cdns_mhdp_device *mhdp);
--- a/include/drm/bridge/cdns-mhdp-common.h
+++ b/include/drm/bridge/cdns-mhdp-common.h
@@ -645,6 +645,9 @@ struct cdns_plat_data {
 	int (*firmware_init)(struct cdns_mhdp_device *mhdp);
 	void (*pclk_rate)(struct cdns_mhdp_device *mhdp);
 
+	int (*suspend)(struct cdns_mhdp_device *mhdp);
+	int (*resume)(struct cdns_mhdp_device *mhdp);
+
 	int (*power_on)(struct cdns_mhdp_device *mhdp);
 	int (*power_off)(struct cdns_mhdp_device *mhdp);
 
