| 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); |
| |