/*
 * SPDX-FileCopyrightText: Copyright (c) 2019-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * 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/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/of_device.h>
#include <linux/of_address.h>

#include "nv-platform.h"
#include "nv-linux.h"
#include <linux/reset.h>

static irqreturn_t
nvidia_soc_isr_kthread_bh(
    int irq,
    void *data
)
{
    NV_STATUS status;
    irqreturn_t ret;
    nv_state_t *nv = (nv_state_t *) data;
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    NvU32 irq_count;
    unsigned long flags;

    //
    // Synchronize kthreads servicing bottom halves
    //
    status = os_acquire_mutex(nvl->soc_bh_mutex);

    // os_acquire_mutex can only fail if we cannot sleep and we can
    WARN_ON(status != NV_OK);

    ret = nvidia_isr_kthread_bh(irq, data);

    os_release_mutex(nvl->soc_bh_mutex);

    NV_SPIN_LOCK_IRQSAVE(&nvl->soc_isr_info_lock, flags);
    for (irq_count = 0; irq_count < nv->num_soc_irqs; irq_count++)
    {
        if (nv->soc_irq_info[irq_count].irq_num == irq)
        {
            nv->soc_irq_info[irq_count].bh_pending = NV_FALSE;
        }
        if (nv->soc_irq_info[irq_count].ref_count == 0)
        {
            nv->soc_irq_info[irq_count].ref_count++;
            enable_irq(nv->soc_irq_info[irq_count].irq_num);
        }
    }
    nv->current_soc_irq = -1;
    NV_SPIN_UNLOCK_IRQRESTORE(&nvl->soc_isr_info_lock, flags);

    return ret;
}

static irqreturn_t nvidia_soc_isr(int irq, void *arg)
{
    unsigned long flags = 0;
    irqreturn_t ret;
    nv_linux_state_t *nvl = (void *) arg;
    nv_state_t *nv = NV_STATE_PTR(nvl);
    NvU32 irq_count;

    NV_SPIN_LOCK_IRQSAVE(&nvl->soc_isr_lock, flags);
    NV_SPIN_LOCK_IRQSAVE(&nvl->soc_isr_info_lock, flags);

    /*
     * > Only 1 interrupt at a time is allowed to be serviced.
     * > So when bh_pending is true, bottom half is scheduled/active
     *   and serving previous interrupt by disabling all interrupts
     *   at interrupt controller level, also here GPU lock is already
     *   taken so this interrupt will anyways be blocked until bottom
     *   half releases GPU lock, so return early for now.
     * > Once bottom half processed earlier interrupt, it will release
     *   GPU lock and re-enable all interrupts and set bh_pending to
     *   false. Upon re-enabling, this interrupt will be serviced
     *   again because all interrupts that we care are level triggered.
     */
    for (irq_count = 0; irq_count < nv->num_soc_irqs; irq_count++)
    {
        if (nv->soc_irq_info[irq_count].bh_pending == NV_TRUE)
        {
            NV_SPIN_UNLOCK_IRQRESTORE(&nvl->soc_isr_info_lock, flags);
            NV_SPIN_UNLOCK_IRQRESTORE(&nvl->soc_isr_lock, flags);
            return IRQ_NONE;
        }
    }
    nv->current_soc_irq = irq;

    // Don't hold info lock across callstacks
    NV_SPIN_UNLOCK_IRQRESTORE(&nvl->soc_isr_info_lock, flags);

    ret = nvidia_isr(irq, arg);

    NV_SPIN_LOCK_IRQSAVE(&nvl->soc_isr_info_lock, flags);
    if (ret == IRQ_WAKE_THREAD)
    {
        for (irq_count = 0; irq_count < nv->num_soc_irqs; irq_count++)
        {
            if (nv->soc_irq_info[irq_count].irq_num == irq)
            {
                nv->soc_irq_info[irq_count].bh_pending = NV_TRUE;
            }
        }
    }
    else
    {
        nv->current_soc_irq = -1;
    }

    NV_SPIN_UNLOCK_IRQRESTORE(&nvl->soc_isr_info_lock, flags);
    NV_SPIN_UNLOCK_IRQRESTORE(&nvl->soc_isr_lock, flags);

    return ret;
}

NvS32 nv_request_soc_irq(
    nv_linux_state_t *nvl,
    NvU32 irq,
    nv_soc_irq_type_t type,
    NvU32 flags,
    NvU32 priv_data,
    const char *device_name)
{
    nv_state_t *nv = NV_STATE_PTR(nvl);
    NvS32 ret;
    NvU32 irq_index;

    if (nv->num_soc_irqs >= NV_MAX_SOC_IRQS)
    {
        nv_printf(NV_DBG_ERRORS, "Exceeds Maximum SOC interrupts\n");
        return -EINVAL;
    }

    ret = request_threaded_irq(irq, nvidia_soc_isr, nvidia_soc_isr_kthread_bh,
                               flags, device_name, (void *)nvl);
    if (ret != 0)
    {
        nv_printf(NV_DBG_ERRORS, "nv_request_soc_irq for irq %d failed\n", irq);
        return ret;
    }

    disable_irq_nosync(irq);

    irq_index = nv->num_soc_irqs;
    nv->soc_irq_info[irq_index].irq_num = irq;
    nv->soc_irq_info[irq_index].irq_type = type;
    if (type == NV_SOC_IRQ_GPIO_TYPE)
    {
        nv->soc_irq_info[irq_index].irq_data.gpio_num = priv_data;
    }
    else if (type == NV_SOC_IRQ_DPAUX_TYPE)
    {
        nv->soc_irq_info[irq_index].irq_data.dpaux_instance = priv_data;
    }
    nv->num_soc_irqs++;
    nv->soc_irq_info[irq_index].ref_count = 0;

    return ret;
}

nv_soc_irq_type_t NV_API_CALL nv_get_current_irq_type(nv_state_t *nv)
{
    int count;

    for (count = 0; count < nv->num_soc_irqs; count++)
    {
        if (nv->soc_irq_info[count].irq_num == nv->current_soc_irq)
        {
            return nv->soc_irq_info[count].irq_type;
        }
    }

    return NV_SOC_IRQ_INVALID_TYPE;
}

NV_STATUS NV_API_CALL nv_get_current_irq_priv_data(nv_state_t *nv, NvU32 *priv_data)
{
    int count;

    if (nv->current_soc_irq == -1)
    {
        nv_printf(NV_DBG_ERRORS, "%s:No SOC interrupt in progress\n", __func__);
        return NV_ERR_GENERIC;
    }

    for (count = 0; count < nv->num_soc_irqs; count++)
    {
        if (nv->soc_irq_info[count].irq_num == nv->current_soc_irq)
        {
            if (nv->soc_irq_info[count].irq_type == NV_SOC_IRQ_GPIO_TYPE)
            {
                *priv_data = nv->soc_irq_info[count].irq_data.gpio_num;
            }
            else if (nv->soc_irq_info[count].irq_type == NV_SOC_IRQ_DPAUX_TYPE)
            {
                *priv_data = nv->soc_irq_info[count].irq_data.dpaux_instance;
            }
        }
    }

    return NV_OK;
}

static void nv_soc_free_irq_by_type(nv_state_t *nv, nv_soc_irq_type_t type)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    int count;

    if ((nv->num_soc_irqs == 0) || (type == 0))
    {
        return;
    }

    for (count = 0; count < NV_MAX_SOC_IRQS; count++)
    {
        if (type == nv->soc_irq_info[count].irq_type)
        {
            free_irq(nv->soc_irq_info[count].irq_num, (void *)nvl);
            nv->soc_irq_info[count].irq_type = 0;
            nv->soc_irq_info[count].irq_num = 0;
            nv->soc_irq_info[count].bh_pending = NV_FALSE;
            nv->soc_irq_info[count].ref_count = 0;
            nv->num_soc_irqs--;
        }
    }
}

int nv_soc_register_irqs(nv_state_t *nv)
{
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    int rc;
    int dpauxindex;

    nv->current_soc_irq = -1;

    rc = nv_request_soc_irq(nvl, nv->interrupt_line,
                            NV_SOC_IRQ_DISPLAY_TYPE,
                            nv_default_irq_flags(nv), 0,
                            "nvdisplay");
    if (rc != 0)
    {
        nv_printf(NV_DBG_ERRORS, "failed to request display irq (%d)\n", rc);
        return rc;
    }

    /* Skip registering other interrupts than nvdisplay for OpenRM */
    if (nv->request_firmware)
        return 0;

    rc = nv_request_soc_irq(nvl, nv->hdacodec_irq, NV_SOC_IRQ_HDACODEC_TYPE,
                            nv_default_irq_flags(nv), 0, "hdacodec");
    if (rc != 0)
    {
        nv_printf(NV_DBG_ERRORS, "failed to request hdacodec irq (%d)\n", rc);
        free_irq(nv->interrupt_line, (void *) nvl);
        return rc;
    }

    if (nv->soc_is_dpalt_mode_supported)
    {
        /* Type-C port controller to display interrupt*/
        rc = nv_request_soc_irq(nvl, nv->tcpc2disp_irq, NV_SOC_IRQ_TCPC2DISP_TYPE,
                nv_default_irq_flags(nv), 0, "tcpc2disp");
        if (rc != 0)
        {
            nv_printf(NV_DBG_ERRORS, "failed to request Tcpc2disp irq (%d)\n", rc);
            free_irq(nv->interrupt_line, (void *) nvl);
            free_irq(nv->hdacodec_irq, (void *)nvl);
            return rc;
        }
    }

    for (dpauxindex = 0; dpauxindex < nv->num_dpaux_instance; dpauxindex++)
    {
        rc = nv_request_soc_irq(nvl, nv->dpaux_irqs[dpauxindex],
                                NV_SOC_IRQ_DPAUX_TYPE,
                                nv_default_irq_flags(nv),
                                dpauxindex,
                                nv->dpaux_devname[dpauxindex]);
        if (rc != 0)
        {
            nv_printf(NV_DBG_ERRORS, "failed to request dpaux irq (%d)\n", rc);
            free_irq(nv->interrupt_line, (void *)nvl);
            free_irq(nv->hdacodec_irq, (void *)nvl);
            if (nv->soc_is_dpalt_mode_supported)
            {
                free_irq(nv->tcpc2disp_irq, (void *)nvl);
            }
            return rc;
        }
    }

    return 0;
}

void nv_soc_free_irqs(nv_state_t *nv)
{
    nv_soc_free_irq_by_type(nv, NV_SOC_IRQ_DISPLAY_TYPE);
    nv_soc_free_irq_by_type(nv, NV_SOC_IRQ_HDACODEC_TYPE);
    nv_soc_free_irq_by_type(nv, NV_SOC_IRQ_DPAUX_TYPE);
    nv_soc_free_irq_by_type(nv, NV_SOC_IRQ_GPIO_TYPE);

    if (nv->soc_is_dpalt_mode_supported)
    {
        nv_soc_free_irq_by_type(nv, NV_SOC_IRQ_TCPC2DISP_TYPE);
    }
}

static void nv_platform_free_device_dpaux(nv_state_t *nv)
{
    int dpauxindex;

    for (dpauxindex = 0; dpauxindex < nv->num_dpaux_instance; dpauxindex++)
    {
        /* Note that the memory region is being released only for dpaux0. This is
         * because the memory mapping is done only for dpaux0. Refer to the memory
         * mapping section in nv_platform_alloc_device_dpaux() for details on
         * why this is done, and how the same mapping is reused for other dpaux
         * instances.
         */
        if ((dpauxindex == 0) &&
            (nv->dpaux[dpauxindex] != NULL) &&
            (nv->dpaux[dpauxindex]->size != 0) &&
            (nv->dpaux[dpauxindex]->cpu_address != 0))
        {
            release_mem_region(nv->dpaux[dpauxindex]->cpu_address,
                               nv->dpaux[dpauxindex]->size);
        }

        if (nv->dpaux[dpauxindex] != NULL)
        {
            NV_KFREE(nv->dpaux[dpauxindex], sizeof(*(nv->dpaux[dpauxindex])));
        }
    }
}

static int nv_platform_alloc_device_dpaux(struct platform_device *plat_dev, nv_state_t *nv)
{
#if NV_SUPPORTS_PLATFORM_DEVICE
    const char *sdpaux = "dpaux";
    int dpauxindex = 0;
    int irq = 0;
    int rc = 0;
    int num_dpaux_instance = 0;
    const struct resource *res;
    phys_addr_t res_addr = 0;
    resource_size_t res_size = 0;
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);

    nv->num_dpaux_instance = 0;
    if (!of_property_read_u32(nvl->dev->of_node, "nvidia,num-dpaux-instance", &num_dpaux_instance))
    {
        nv->num_dpaux_instance = (unsigned) num_dpaux_instance;
        nv_printf(NV_DBG_INFO, "NVRM: Found %d dpAux instances in device tree.\n",
                  num_dpaux_instance);
    }

    if (nv->num_dpaux_instance > NV_MAX_DPAUX_NUM_DEVICES)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: Number of dpAux instances [%d] in device tree are more than"
                  "that of allowed [%d]. Initilizing %d dpAux instances.\n", nv->num_dpaux_instance,
                  NV_MAX_DPAUX_NUM_DEVICES, NV_MAX_DPAUX_NUM_DEVICES);
        nv->num_dpaux_instance = NV_MAX_DPAUX_NUM_DEVICES;
    }

    /* Memory region is being mapped only for dpaux0 because the size specified
     * in device tree for dpaux0 register space is large enough to accomodate
     * the register spaces of all dpaux instances. The iomapped memory of
     * dpaux0 is reused for all other dpaux instances.
     *
     * This is also required for RM because RM indexes the dpaux register space
     * for all dpaux instances using dpaux0 base address.
     */
    res = platform_get_resource_byname(plat_dev, IORESOURCE_MEM, "dpaux0");
    if (!res)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to get IO memory resource\n");
        rc = -ENXIO;
        goto err_free_dpaux_dev;
    }
    res_addr = res->start;
    res_size = res->end - res->start;

    if (!request_mem_region(res_addr, res_size, nv_device_name))
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: request_mem_region failed for %pa\n",
                &res_addr);
        rc = -ENXIO;
        goto err_free_dpaux_dev;
    }

    for (dpauxindex = 0; dpauxindex < nv->num_dpaux_instance; dpauxindex++)
    {
        snprintf(nv->dpaux_devname[dpauxindex], sizeof(nv->dpaux_devname[dpauxindex]), "%s%d", sdpaux, dpauxindex);

        NV_KMALLOC(nv->dpaux[dpauxindex], sizeof(*(nv->dpaux[dpauxindex])));
        if (nv->dpaux[dpauxindex] == NULL)
        {
            nv_printf(NV_DBG_ERRORS, "NVRM: failed to allocate nv->dpaux[%d] memory\n", dpauxindex);
            rc = -ENOMEM;
            goto err_free_dpaux_dev;
        }

        os_mem_set(nv->dpaux[dpauxindex], 0, sizeof(*(nv->dpaux[dpauxindex])));

        irq = platform_get_irq_byname(plat_dev, nv->dpaux_devname[dpauxindex]);
        if (irq < 0)
        {
            nv_printf(NV_DBG_ERRORS, "NVRM: failed to get IO irq resource\n");
            rc = irq;
            goto err_free_dpaux_dev;
        }

        nv->dpaux[dpauxindex]->cpu_address = res_addr;
        nv->dpaux[dpauxindex]->size = res_size;
        nv->dpaux_irqs[dpauxindex] = irq;
    }

    return rc;

err_free_dpaux_dev:
    nv_platform_free_device_dpaux(nv);

    return rc;
#else
    return -1;
#endif
}

NV_STATUS NV_API_CALL nv_soc_device_reset(nv_state_t *nv)
{
#if NV_SUPPORTS_PLATFORM_DISPLAY_DEVICE
    NV_STATUS status = NV_OK;
    int rc = 0;
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);

    /*
     * Skip all reset functions if the 'nvidia,skip-clk-rsts' DT property
     * is present. This property is currently present in the System FPGA device
     * tree because the BPMP firmware isn't available on FPGA yet.
     */
    bool skip_clk_rsts = of_property_read_bool(nvl->dev->of_node, "nvidia,skip-clk-rsts");
    if (!skip_clk_rsts)
    {
        if (nvl->nvdisplay_reset != NULL)
        {
            rc = reset_control_reset(nvl->nvdisplay_reset);
            if (rc != 0)
            {
                status = NV_ERR_GENERIC;
                nv_printf(NV_DBG_ERRORS, "NVRM: reset_control_reset failed, rc: %d\n", rc);
                goto out;
            }
        }

        if (nvl->dpaux0_reset != NULL)
        {
            rc = reset_control_reset(nvl->dpaux0_reset);
            if (rc != 0)
            {
                status = NV_ERR_GENERIC;
                nv_printf(NV_DBG_ERRORS, "NVRM: reset_control_reset failed, rc: %d\n", rc);
                goto out;
            }
        }

        if (nvl->dsi_core_reset != NULL)
        {
            rc = reset_control_reset(nvl->dsi_core_reset);
            if (rc != 0)
            {
                status = NV_ERR_GENERIC;
                nv_printf(NV_DBG_ERRORS, "NVRM: reset_control_reset failed, rc: %d\n", rc);
                goto out;
            }
        }
    }
out:
    return status;
#else
    return NV_OK;
#endif
}

NV_STATUS NV_API_CALL nv_soc_mipi_cal_reset(nv_state_t *nv)
{
    NV_STATUS status = NV_OK;
#if NV_SUPPORTS_PLATFORM_DISPLAY_DEVICE
    int rc = 0;
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);
    bool skip_clk_rsts = of_property_read_bool(nvl->dev->of_node, "nvidia,skip-clk-rsts");

    if (skip_clk_rsts)
        return NV_OK;

    if (nvl->mipi_cal_reset != NULL)
    {
        rc = reset_control_reset(nvl->mipi_cal_reset);
        if (rc != 0)
        {
            status = NV_ERR_GENERIC;
            nv_printf(NV_DBG_ERRORS, "NVRM: mipi_cal reset_control_reset failed, rc: %d\n", rc);
        }
    }
    else
    {
        status = NV_ERR_GENERIC;
    }
#endif
    return status;
}

// This function gets called only for Tegra
static NV_STATUS nv_platform_get_iommu_availability(struct platform_device *plat_dev,
                                              nv_state_t *nv)
{
    struct device_node *np = plat_dev->dev.of_node;
    struct device_node *niso_np_with_iommus = NULL;
    struct device_node *niso_np = NULL;
    struct device_node *iso_np = NULL;
    NV_STATUS status = NV_OK;

    nv->iommus.iso_iommu_present = NV_FALSE;
    nv->iommus.niso_iommu_present = NV_FALSE;

    iso_np = of_parse_phandle(np, "iommus", 0);
    if (iso_np && of_device_is_available(iso_np)) {
        nv->iommus.iso_iommu_present = NV_TRUE;
    }

    niso_np = of_get_child_by_name(np, "nvdisplay-niso");
    if (niso_np) {
        niso_np_with_iommus = of_parse_phandle(niso_np, "iommus", 0);
        if (niso_np_with_iommus && of_device_is_available(niso_np_with_iommus)) {
            nv->iommus.niso_iommu_present = NV_TRUE;
        }
    }

    if (niso_np_with_iommus)
        of_node_put(niso_np_with_iommus);

    if (niso_np)
        of_node_put(niso_np);

    if (iso_np)
        of_node_put(iso_np);

    return status;
}

#define DISP_DT_SMMU_STREAM_ID_MASK     0xFF
// This function gets called only for Tegra
static NV_STATUS nv_platform_get_iso_niso_stream_ids(struct platform_device *plat_dev,
                                              nv_state_t *nv)
{
    struct device_node *np = plat_dev->dev.of_node;
    NvU32 value = 0;
    NV_STATUS status = NV_OK;
    int ret = 0;

    /* NV_U32_MAX is used to indicate that the platform does not support SMMU */
    nv->iommus.dispIsoStreamId = NV_U32_MAX;
    nv->iommus.dispNisoStreamId = NV_U32_MAX;

    /* Parse ISO StreamID */
    ret = of_property_read_u32(np, "iso_sid", &value);
    if (ret == 0)
    {
        nv->iommus.dispIsoStreamId = (value & DISP_DT_SMMU_STREAM_ID_MASK);
    }
    else if (ret == -EINVAL)
    {
        /* iso_sid will not be specified in device tree if SMMU needs to be bypassed. Continue without failing */
        nv_printf(NV_DBG_INFO, "NVRM: nv_platform_get_iso_niso_stream_ids, iso_sid not specified under display node\n");
     }
     else
     {
        nv_printf(NV_DBG_ERRORS, "NVRM: nv_platform_get_iso_niso_stream_ids, iso_sid has invalid value\n");
        status = NV_ERR_GENERIC;
        goto fail;
     }

    /* Parse NISO StreamID */
    ret = of_property_read_u32(np, "niso_sid", &value);
    if (ret == 0)
    {
        nv->iommus.dispNisoStreamId = (value & DISP_DT_SMMU_STREAM_ID_MASK);
    }
    else if (ret == -EINVAL)
    {
        /* niso_sid will not be specified in device tree if SMMU needs to be bypassed. Continue without failing */
        nv_printf(NV_DBG_INFO, "NVRM: nv_platform_get_iso_niso_stream_ids, niso_sid not specified under display node\n");
    }
    else
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: nv_platform_get_iso_niso_stream_ids, niso_sid has invalid value\n");
        status = NV_ERR_GENERIC;
        goto fail;
    }

fail:
    return status;
}

#define DISP_CLK_NUM_PARENTS 2
// This function gets called only for Tegra
static NV_STATUS nv_platform_get_disp_clocks_max_rate(struct platform_device *plat_dev,
                                              nv_state_t *nv)
{
    struct device_node *np = plat_dev->dev.of_node;
    u32 value = 0;
    u32 disp_clk_rates[DISP_CLK_NUM_PARENTS];
    NV_STATUS status = NV_OK;
    int ret = 0;

    nv->clocks.max_dispclk_rate_using_disppllkhz = 0;
    nv->clocks.max_dispclk_rate_using_sppll0clkoutakhz = 0;
    nv->clocks.max_hubclk_rate_using_sppll0clkoutbkhz = 0;

    /* Parse disp clock max rates for each possible parent passed by UEFI */
    ret = of_property_read_u32_array(np, "nvidia,max-disp-clk-rate-khz", disp_clk_rates, DISP_CLK_NUM_PARENTS);
    if (ret == 0)
    {
        nv->clocks.max_dispclk_rate_using_disppllkhz = disp_clk_rates[0];
        nv->clocks.max_dispclk_rate_using_sppll0clkoutakhz = disp_clk_rates[1];
    }
    else if (ret == -EINVAL)
    {
        nv_printf(NV_DBG_INFO, "NVRM: nv_platform_get_disp_clocks_max_rate, nvidia,max-disp-clk-rate-khz not specified under display node\n");
    }
    else
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: nv_platform_get_disp_clocks_max_rate, nvidia,max-disp-clk-rate-khz has invalid value\n");
        status = NV_ERR_GENERIC;
        goto done;
    }

    /* Parse hub clock max rate for its parent passed by UEFI */
    ret = of_property_read_u32(np, "nvidia,max-hub-clk-rate-khz", &value);
    if (ret == 0)
    {
        nv->clocks.max_hubclk_rate_using_sppll0clkoutbkhz = value;
    }
    else if (ret == -EINVAL)
    {
        nv_printf(NV_DBG_INFO, "NVRM: nv_platform_get_disp_clocks_max_rate, nvidia,max-hub-clk-rate-khz not specified under display node\n");
    }
    else
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: nv_platform_get_disp_clocks_max_rate, nvidia,max-hub-clk-rate-khz has invalid value\n");
        status = NV_ERR_GENERIC;
        goto done;
    }

done:
    return status;
}

static int nv_platform_register_mapping_devs(struct platform_device *plat_dev,
                                             nv_state_t *nv)
{
#if NV_SUPPORTS_PLATFORM_DEVICE
    struct device_node *np = plat_dev->dev.of_node;
    struct device_node *niso_np = NULL;
    struct platform_device *niso_plat_dev = NULL;
    int rc = 0;
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);

    nv->niso_dma_dev = NULL;

    niso_np = of_get_child_by_name(np, "nvdisplay-niso");
    if (niso_np == NULL)
    {
        nv_printf(NV_DBG_INFO, "NVRM: no nvdisplay-niso child node\n");
        goto register_mapping_devs_end;
    }

    rc = devm_of_platform_populate(&plat_dev->dev);
    if (rc != 0)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: devm_of_platform_populate failed\n");
        goto register_mapping_devs_end;
    }

    niso_plat_dev = of_find_device_by_node(niso_np);
    if (niso_plat_dev == NULL)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: no nvdisplay-niso platform devices\n");
        rc = -ENODEV;
        goto register_mapping_devs_end;
    }

    rc = of_dma_configure(
        &niso_plat_dev->dev,
        niso_np
#if NV_OF_DMA_CONFIGURE_ARGUMENT_COUNT > 2
        , true
#endif
    );
    if (rc != 0)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: nv_of_dma_configure failed for niso\n");
        goto register_mapping_devs_end;
    }

    nvl->niso_dma_dev.dev = &niso_plat_dev->dev;
    nvl->niso_dma_dev.addressable_range.start = 0;
    nvl->niso_dma_dev.addressable_range.limit = NV_U64_MAX;
    nv->niso_dma_dev = &nvl->niso_dma_dev;

register_mapping_devs_end:
    of_node_put(niso_np);
    return rc;
#else
    return -ENOSYS;
#endif
}

static int nv_platform_parse_dcb(struct platform_device *plat_dev,
        nv_state_t *nv)
{
    int ret;

    struct device_node *np = plat_dev->dev.of_node;
    ret = of_property_count_elems_of_size(np, "nvidia,dcb-image", sizeof(u8));
    if (ret > 0)
    {
        nv->soc_dcb_size = ret;
        /* Allocate dcb array */
        NV_KMALLOC(nv->soc_dcb_blob, nv->soc_dcb_size);
        if (nv->soc_dcb_blob == NULL)
        {
            nv_printf(NV_DBG_ERRORS, "failed to allocate dcb array");
            return -ENOMEM;
        }
    }

    ret = of_property_read_variable_u8_array(np, "nvidia,dcb-image",
            nv->soc_dcb_blob, 0, nv->soc_dcb_size);
    if (IS_ERR(&ret))
    {
        nv_printf(NV_DBG_ERRORS, "failed to read dcb blob");
        NV_KFREE(nv->soc_dcb_blob, nv->soc_dcb_size);
        nv->soc_dcb_blob = NULL;
        nv->soc_dcb_size = 0;
        return ret;
    }

    return 0;
}

#if NV_SUPPORTS_PLATFORM_DISPLAY_DEVICE
static int nv_platform_device_display_probe(struct platform_device *plat_dev)
{
    nv_state_t *nv = NULL;
    nv_linux_state_t *nvl = NULL;
    nvidia_stack_t *sp = NULL;
    phys_addr_t res_addr = 0;
    resource_size_t res_size = 0;
    int irq = 0;
    int rc = 0;
    const struct resource *res;
    bool skip_clk_rsts;
    NV_STATUS   status;

    rc = nv_kmem_cache_alloc_stack(&sp);
    if (rc < 0)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: %s: failed to allocate stack!\n",
                  __FUNCTION__);
        return rc;
    }

    NV_KMALLOC(nvl, sizeof(*nvl));
    if (nvl == NULL)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to allocate nvl memory\n");
        rc = -ENOMEM;
        goto err_free_stack;
    }
    os_mem_set(nvl, 0, sizeof(*nvl));

    nv = NV_STATE_PTR(nvl);

    platform_set_drvdata(plat_dev, (void *)nvl);

    nvl->dev = &plat_dev->dev;

    /*
     * fill SOC dma device information
     */
    nvl->dma_dev.dev = nvl->dev;
    nvl->dma_dev.addressable_range.start = 0;
    nvl->dma_dev.addressable_range.limit = NV_U64_MAX;
    nv->dma_dev = &nvl->dma_dev;

    nvl->tce_bypass_enabled = NV_TRUE;

    NV_KMALLOC(nv->regs, sizeof(*(nv->regs)));
    if (nv->regs == NULL)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to allocate nv->regs memory\n");
        rc = -ENOMEM;
        goto err_free_nvl;
    }
    os_mem_set(nv->regs, 0, sizeof(*(nv->regs)));

    res = platform_get_resource_byname(plat_dev, IORESOURCE_MEM, "nvdisplay");
    if (!res)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to get IO memory resource\n");
        rc = -ENODEV;
        goto err_free_nv_regs;
    }
    res_addr = res->start;
    res_size = res->end - res->start;

    irq = platform_get_irq_byname(plat_dev, "nvdisplay");
    if (irq < 0)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to get IO irq resource\n");
        rc = -ENODEV;
        goto err_free_nv_regs;
    }

    if (!request_mem_region(res_addr, res_size, nv_device_name))
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: request_mem_region failed for %pa\n",
                  &res_addr);
        rc = -ENOMEM;
        goto err_free_nv_regs;
    }

    nv->regs->cpu_address = res_addr;
    nv->regs->size = res_size;
    nv->interrupt_line = irq;
    nv->flags = NV_FLAG_SOC_DISPLAY;

    nv->os_state = (void *) nvl;

    // Check ISO/NISO SMMU status
    status = nv_platform_get_iommu_availability(plat_dev, nv);
    if (status != NV_OK)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: nv_platform_device_display_probe: parsing iommu node failed\n");
        goto err_release_mem_region_regs;
    }

    // Parse ISO/NISO SMMU StreamIDs
    status = nv_platform_get_iso_niso_stream_ids(plat_dev, nv);
    if (status != NV_OK)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: nv_platform_device_display_probe: parsing ISO/NISO StreamIDs failed\n");
        goto err_release_mem_region_regs;
    }

    // Parse Display Clocks max rates passed by UEFI through DT
    status = nv_platform_get_disp_clocks_max_rate(plat_dev, nv);
    if (status != NV_OK)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: nv_platform_device_display_probe: parsing display clocks max rates failed\n");
        goto err_release_mem_region_regs;
    }

    rc = nv_platform_register_mapping_devs(plat_dev, nv);
    if (rc != 0)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to allocate niso platform device\n");
        goto err_release_mem_region_regs;
    }

    rc = nv_platform_alloc_device_dpaux(plat_dev, nv);
    if (rc < 0)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to alloc DpAux device\n");
        goto err_release_mem_region_regs;
    }

    NV_KMALLOC(nv->hdacodec_regs, sizeof(*(nv->hdacodec_regs)));
    if (nv->hdacodec_regs == NULL)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to allocate hdacodecregs memory\n");
        rc = -ENOMEM;
        goto err_remove_dpaux_device;
    }
    os_mem_set(nv->hdacodec_regs, 0, sizeof(*(nv->hdacodec_regs)));

    res = platform_get_resource_byname(plat_dev, IORESOURCE_MEM, "hdacodec");
    if (!res)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to get hdacodec IO memory resource\n");
        rc = -ENODEV;
        goto err_free_nv_codec_regs;
    }
    res_addr = res->start;
    res_size = res->end - res->start;

    if (!request_mem_region(res_addr, res_size, nv_device_name))
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: request_mem_region of hdacodec failed for %pa\n",
                  &res_addr);
        rc = -ENOMEM;
        goto err_free_nv_codec_regs;
    }

    nv->hdacodec_regs->cpu_address = res_addr;
    nv->hdacodec_regs->size = res_size;
    nv->soc_is_dpalt_mode_supported = false;
    nv->soc_is_hfrp_supported = false;
    nv->soc_is_external_phy_supported = false;

    nv->hdacodec_irq = platform_get_irq_byname(plat_dev, "hdacodec");
    if (nv->hdacodec_irq < 0)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to get HDACODEC IO irq resource\n");
        rc = -ENODEV;
        goto err_release_mem_hdacodec_region_regs;
    }

    rc = of_property_read_bool(nvl->dev->of_node, "nvidia,dpalt-supported");
    if (rc == true)
    {
        irq = platform_get_irq_byname(plat_dev, "tcpc2disp");
        if (irq < 0)
        {
            nv->soc_is_dpalt_mode_supported = false;
        }
        else
        {
            nv->tcpc2disp_irq = irq;
            nv->soc_is_dpalt_mode_supported = true;
        }
    }

    rc = of_property_read_bool(nvl->dev->of_node, "nvidia,hfrp-supported");
    if (rc == true)
    {
        nv->soc_is_hfrp_supported = true;
    }

    rc = of_property_read_bool(nvl->dev->of_node, "nvidia,external-phy-supported");
    if (rc == true)
    {
        nv->soc_is_external_phy_supported = true;
    }

    NV_KMALLOC(nv->mipical_regs, sizeof(*(nv->mipical_regs)));
    if (nv->mipical_regs == NULL)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to allocate mipical registers memory\n");
        rc = -ENOMEM;
        goto err_release_mem_hdacodec_region_regs;
    }
    os_mem_set(nv->mipical_regs, 0, sizeof(*(nv->mipical_regs)));

    res = platform_get_resource_byname(plat_dev, IORESOURCE_MEM, "mipical");
    if (!res)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to get mipical IO memory resource\n");
        rc = -ENODEV;
        goto err_free_mipical_regs;
    }
    res_addr = res->start;
    res_size = res->end - res->start;

    if (!request_mem_region(res_addr, res_size, nv_device_name))
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: request_mem_region of mipical failed for %pa\n",
                  &res_addr);
        rc = -ENOMEM;
        goto err_free_mipical_regs;
    }

    nv->mipical_regs->cpu_address = res_addr;
    nv->mipical_regs->size = res_size;

    pm_vt_switch_required(&plat_dev->dev, NV_TRUE);

    // Enabling power management for the device.
    pm_runtime_enable(&plat_dev->dev);

    /*
     * Skip all clock/reset functions if the 'nvidia,skip-clk-rsts' DT property
     * is present. This property is currently present in the System FPGA device
     * tree because the BPMP firmware isn't available on FPGA yet.
     */
    skip_clk_rsts = of_property_read_bool(nvl->dev->of_node, "nvidia,skip-clk-rsts");
    if (!skip_clk_rsts)
    {
        /*
         * Getting all the display-clock handles
         * from BPMP FW at the time of probe.
         */
        status = nv_clk_get_handles(nv);
        if (status != NV_OK)
        {
            nv_printf(NV_DBG_ERRORS, "NVRM: failed to get clock handles\n");
            rc = -EPERM;
            goto err_release_mem_mipical_region_regs;
        }

        /*
         * Getting dpaux-reset handles
         * from device tree at the time of probe.
         */
        nvl->dpaux0_reset = devm_reset_control_get(nvl->dev, "dpaux0_reset");
        if (IS_ERR(nvl->dpaux0_reset))
        {
            nv_printf(NV_DBG_ERRORS, "NVRM: devm_reset_control_get failed, err: %ld\n", PTR_ERR(nvl->dpaux0_reset));
            nvl->dpaux0_reset = NULL;
        }

        /*
         * Getting display-reset handles
         * from device tree at the time of probe.
         */
        nvl->nvdisplay_reset = devm_reset_control_get(nvl->dev, "nvdisplay_reset");
        if (IS_ERR(nvl->nvdisplay_reset))
        {
            nv_printf(NV_DBG_ERRORS, "NVRM: devm_reset_control_get failed, err: %ld\n", PTR_ERR(nvl->nvdisplay_reset));
            nvl->nvdisplay_reset = NULL;
        }

        /*
         * Getting dsi-core reset handles
         * from device tree at the time of probe.
         */
        nvl->dsi_core_reset = devm_reset_control_get(nvl->dev, "dsi_core_reset");
        if (IS_ERR(nvl->dsi_core_reset))
        {
            nv_printf(NV_DBG_ERRORS, "NVRM: devm_reset_control_get failed, err: %ld\n",  PTR_ERR(nvl->dsi_core_reset));
            nvl->dsi_core_reset = NULL;
        }

        /*
         * Getting mipi_cal reset handle
         * from device tree at the time of probe.
         */
        nvl->mipi_cal_reset = devm_reset_control_get(nvl->dev, "mipi_cal_reset");
        if (IS_ERR(nvl->mipi_cal_reset))
        {
            nv_printf(NV_DBG_ERRORS, "NVRM: mipi_cal devm_reset_control_get failed, err: %ld\n",  PTR_ERR(nvl->mipi_cal_reset));
            nvl->mipi_cal_reset = NULL;
        }
    }

    status = nv_imp_icc_get(nv);
    if (status != NV_OK)
    {
        //
        // nv_imp_icc_get errors are normally treated as fatal, but ICC is
        // expected to be disabled on AV + L (causing NV_ERR_NOT_SUPPORTED to
        // be returned), so this is not treated as fatal.
        //
        if (status != NV_ERR_NOT_SUPPORTED)
        {
            nv_printf(NV_DBG_ERRORS, "NVRM: failed to get icc handle\n");
            rc = -EPERM;
            goto err_destroy_clk_handles;
        }
    }
    /*
     * Get the backlight device name
     */
    of_property_read_string(nvl->dev->of_node, "nvidia,backlight-name",
                            &nvl->backlight.device_name);

    num_probed_nv_devices++;

    if (!nv_lock_init_locks(sp, nv))
    {
        rc = -EPERM;
        goto err_put_icc_handle;
    }

    nvl->safe_to_mmap = NV_TRUE;
    INIT_LIST_HEAD(&nvl->open_files);
    NV_SPIN_LOCK_INIT(&nvl->soc_isr_lock);
    NV_SPIN_LOCK_INIT(&nvl->soc_isr_info_lock);

    if (!rm_init_private_state(sp, nv))
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: rm_init_private_state() failed!\n");
        rc = -EPERM;
        goto err_destroy_lock;
    }

    /* Initialize per-device init_on_probe from registry value NVreg_GpuInitOnProbe */
    nvl->init_on_probe = (NVreg_GpuInitOnProbe != 0);

    if (nv_linux_init_open_q(nvl) != 0)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: nv_linux_init_open_q() failed!\n");
        goto err_free_private_state;
    }

    num_nv_devices++;

    /*
     * The newly created nvl object is added to the nv_linux_devices global list
     * only after all the initialization operations for that nvl object are
     * completed, so as to protect against simultaneous lookup operations which
     * may discover a partially initialized nvl object in the list
     */
    LOCK_NV_LINUX_DEVICES();
    if (nv_linux_assign_minor_locked(nvl) != 0)
    {
        UNLOCK_NV_LINUX_DEVICES();
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to assign minor\n");
        rc = -ENODEV;
        goto err_stop_open_q;
    }
    UNLOCK_NV_LINUX_DEVICES();

    if ((nvl->init_on_probe) && !(nv->flags & NV_FLAG_EXCLUDE))
    {
        int ret;
        ret = nv_start_device(nv, sp);
        if (ret)
        {
            nv_printf(NV_DBG_ERRORS, "NVRM: failed to start device\n");
            goto err_remove_minor;
        }
    }

    LOCK_NV_LINUX_DEVICES();
    nv_linux_add_device_locked(nvl);
    UNLOCK_NV_LINUX_DEVICES();

    rm_set_rm_firmware_requested(sp, nv);

    /*
     * Parse DCB blob
     */
    rc = nv_platform_parse_dcb(plat_dev, nv);
    if (rc != 0)
    {
        goto err_remove_device;
    }

    /*
     * Parse display rm sw-soc-chip-id
     */
    rc = of_property_read_u32(nvl->dev->of_node, "nvidia,disp-sw-soc-chip-id",
                              &nv->disp_sw_soc_chip_id);
    if (rc != 0)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: Unable to read disp_sw_soc_chip_id\n");
        goto err_remove_device;
    }

    /*
     * TODO: procfs, vt_switch, dynamic_power_management
     */

    dma_set_mask(nv->dma_dev->dev, DMA_BIT_MASK(39));
    if (nv->niso_dma_dev != NULL)
    {
        dma_set_mask_and_coherent(nv->niso_dma_dev->dev, DMA_BIT_MASK(39));
    }

    rc = os_alloc_mutex(&nvl->soc_bh_mutex);
    if (rc != 0)
    {
        goto err_remove_device;
    }

    nv_kmem_cache_free_stack(sp);

    return 0;

err_remove_device:
    LOCK_NV_LINUX_DEVICES();
    nv_linux_remove_device_locked(nvl);
    UNLOCK_NV_LINUX_DEVICES();
    if (nvl->init_on_probe)
        nv_stop_device(nv, sp);
err_remove_minor:
    LOCK_NV_LINUX_DEVICES();
    nv_linux_remove_minor_locked(nvl);
    UNLOCK_NV_LINUX_DEVICES();
err_stop_open_q:
    nv_linux_stop_open_q(nvl);
err_free_private_state:
    rm_free_private_state(sp, nv);
err_destroy_lock:
    nv_lock_destroy_locks(sp, nv);
err_put_icc_handle:
    nv_imp_icc_put(nv);
err_destroy_clk_handles:
    if (!skip_clk_rsts)
    {
        nv_clk_clear_handles(nv);
    }
err_release_mem_mipical_region_regs:
    release_mem_region(nv->mipical_regs->cpu_address, nv->mipical_regs->size);
err_free_mipical_regs:
    NV_KFREE(nv->mipical_regs, sizeof(*(nv->mipical_regs)));
err_release_mem_hdacodec_region_regs:
    release_mem_region(nv->hdacodec_regs->cpu_address, nv->hdacodec_regs->size);
err_free_nv_codec_regs:
    NV_KFREE(nv->hdacodec_regs, sizeof(*(nv->hdacodec_regs)));
err_remove_dpaux_device:
    nv_platform_free_device_dpaux(nv);
err_release_mem_region_regs:
    release_mem_region(nv->regs->cpu_address, nv->regs->size);
err_free_nv_regs:
    NV_KFREE(nv->regs, sizeof(*(nv->regs)));
err_free_nvl:
    platform_set_drvdata(plat_dev, NULL);
    NV_KFREE(nvl, sizeof(*nvl));
err_free_stack:
    nv_kmem_cache_free_stack(sp);

    return rc;
}
#endif

#if NV_SUPPORTS_PLATFORM_DISPLAY_DEVICE
static void nv_platform_device_display_remove(struct platform_device *plat_dev)
{
    nv_linux_state_t *nvl = NULL;
    nv_state_t *nv;
    nvidia_stack_t *sp = NULL;

    nv_printf(NV_DBG_SETUP, "NVRM: removing SOC Display device\n");

    nvl = platform_get_drvdata(plat_dev);
    if (WARN_ON(!nvl || (nvl->dev != &plat_dev->dev)))
    {
        return;
    }

    if (WARN_ON(nv_kmem_cache_alloc_stack(&sp) < 0))
    {
        return;
    }

    /*
     * Flush and stop open_q before proceeding with removal to ensure nvl
     * outlives all enqueued work items.
     */
    nv_linux_stop_open_q(nvl);

    LOCK_NV_LINUX_DEVICES();
    nv_linux_remove_device_locked(nvl);
    UNLOCK_NV_LINUX_DEVICES();

    if ((nvl->init_on_probe) && !(nv->flags & NV_FLAG_EXCLUDE))
        nv_stop_device(nv, sp);

    LOCK_NV_LINUX_DEVICES();
    nv_linux_remove_minor_locked(nvl);

    /*
     * TODO: procfs
     */

    down(&nvl->ldata_lock);
    UNLOCK_NV_LINUX_DEVICES();

    /*
     * TODO: vt_switch, dynamic_power_management
     */

    nv = NV_STATE_PTR(nvl);

    if ((nv->flags & NV_FLAG_PERSISTENT_SW_STATE) || (nv->flags & NV_FLAG_INITIALIZED))
    {
        nv_acpi_unregister_notifier(nvl);
        if (nv->flags & NV_FLAG_PERSISTENT_SW_STATE)
        {
            rm_disable_gpu_state_persistence(sp, nv);
        }
        nv_shutdown_adapter(sp, nv, nvl);
        nv_dev_free_stacks(nvl);
    }

    nv_lock_destroy_locks(sp, nv);

    num_probed_nv_devices--;

    rm_free_private_state(sp, nv);

    release_mem_region(nv->mipical_regs->cpu_address, nv->mipical_regs->size);

    NV_KFREE(nv->mipical_regs, sizeof(*(nv->mipical_regs)));

    release_mem_region(nv->hdacodec_regs->cpu_address, nv->hdacodec_regs->size);

    NV_KFREE(nv->hdacodec_regs, sizeof(*(nv->hdacodec_regs)));

    release_mem_region(nv->regs->cpu_address, nv->regs->size);

    NV_KFREE(nv->regs, sizeof(*(nv->regs)));

    nv_imp_icc_put(nv);

    nv_platform_free_device_dpaux(nv);

    /*
     * Clearing all the display-clock handles
     * at the time of device remove.
     */
    nv_clk_clear_handles(nv);

    // Disabling power management for the device.
    pm_runtime_disable(&plat_dev->dev);

    num_nv_devices--;

    NV_KFREE(nv->soc_dcb_blob, nv->soc_dcb_size);

    os_free_mutex(nvl->soc_bh_mutex);

    NV_KFREE(nvl, sizeof(*nvl));

    nv_kmem_cache_free_stack(sp);
}
#endif

static int nv_platform_device_probe(struct platform_device *plat_dev)
{
    int rc = 0;

#if NV_SUPPORTS_PLATFORM_DISPLAY_DEVICE
    rc = nv_platform_device_display_probe(plat_dev);
#endif

    return rc;
}

static void nv_platform_device_remove(struct platform_device *plat_dev)
{
#if NV_SUPPORTS_PLATFORM_DISPLAY_DEVICE
    nv_platform_device_display_remove(plat_dev);
#endif
}

#if defined(NV_PLATFORM_DRIVER_STRUCT_REMOVE_RETURNS_VOID) /* Linux v6.11 */
static void nv_platform_device_remove_wrapper(struct platform_device *pdev)
{
    nv_platform_device_remove(pdev);
}
#else
static int nv_platform_device_remove_wrapper(struct platform_device *pdev)
{
    nv_platform_device_remove(pdev);

    return 0;
}
#endif

static void nv_platform_device_shutdown(struct platform_device *pdev)
{
    nv_linux_state_t *nvl = platform_get_drvdata(pdev);

    if (nvl != NULL && !nvl->is_forced_shutdown)
    {
        nv_state_t *nv = NV_STATE_PTR(nvl);

        nvidia_modeset_remove(nv->gpu_id);
    }
}

const struct of_device_id nv_platform_device_table[] =
{
    { .compatible = "nvidia,tegra234-display",},
    { .compatible = "nvidia,tegra264-display",},
    {},
};
MODULE_DEVICE_TABLE(of, nv_platform_device_table);

#if defined(CONFIG_PM)
extern struct dev_pm_ops nv_pm_ops;
#endif

struct platform_driver nv_platform_driver = {
    .driver = {
        .name  = "nv_platform",
        .of_match_table = nv_platform_device_table,
        .owner = THIS_MODULE,
#if defined(CONFIG_PM)
        .pm = &nv_pm_ops,
#endif
        .probe_type = PROBE_FORCE_SYNCHRONOUS,
    },
    .probe     = nv_platform_device_probe,
    .remove    = nv_platform_device_remove_wrapper,
    .shutdown  = nv_platform_device_shutdown,
};

int nv_platform_count_devices(void)
{
    int count = 0;
    struct device_node *np = NULL;

    if (NVreg_RegisterPlatformDeviceDriver == 0)
    {
        return 0;
    }

    while ((np = of_find_matching_node(np, nv_platform_device_table)))
    {
        count++;
    }

    return count;
}

int nv_platform_register_driver(void)
{
#if NV_SUPPORTS_PLATFORM_DEVICE
    if (NVreg_RegisterPlatformDeviceDriver > 0)
    {
        return platform_driver_register(&nv_platform_driver);
    }

    return 0;
#else
    nv_printf(NV_DBG_ERRORS, "NVRM: Not registering platform driver\n");
    return -1;
#endif
}

void nv_platform_unregister_driver(void)
{
#if NV_SUPPORTS_PLATFORM_DEVICE
    if (NVreg_RegisterPlatformDeviceDriver > 0)
    {
        platform_driver_unregister(&nv_platform_driver);
    }
#endif
}

extern int tegra_fuse_control_read(unsigned long addr, unsigned int *data);

unsigned int NV_API_CALL nv_soc_fuse_register_read (unsigned int addr)
{
   unsigned int data = 0;

#if NV_SUPPORTS_PLATFORM_DEVICE && NV_IS_EXPORT_SYMBOL_PRESENT_tegra_fuse_control_read
   tegra_fuse_control_read ((unsigned long)(addr), &data);
#endif

   return data;
}

#if NV_IS_EXPORT_SYMBOL_PRESENT_tsec_comms_send_cmd
extern int tsec_comms_send_cmd(void* cmd, unsigned int queue_id, nv_soc_tsec_cb_func_t cb_func, void* cb_context);
unsigned int NV_API_CALL nv_soc_tsec_send_cmd(void* cmd, nv_soc_tsec_cb_func_t cb_func, void* cb_context)
{
    return (unsigned int)tsec_comms_send_cmd(cmd, 0, cb_func, cb_context);
}
#else
unsigned int NV_API_CALL nv_soc_tsec_send_cmd(void* cmd, nv_soc_tsec_cb_func_t cb_func, void* cb_context)
{
    return (unsigned int)NV_ERR_NOT_SUPPORTED;
}
#endif // NV_IS_EXPORT_SYMBOL_PRESENT_tsec_comms_send_cmd

#if NV_IS_EXPORT_SYMBOL_PRESENT_tsec_comms_set_init_cb
extern int tsec_comms_set_init_cb(nv_soc_tsec_cb_func_t cb_func, void* cb_context);
unsigned int NV_API_CALL nv_soc_tsec_event_register(nv_soc_tsec_cb_func_t cb_func, void* cb_context, NvBool is_init_event)
{
    if (is_init_event)
    {
        return (unsigned int)tsec_comms_set_init_cb(cb_func, cb_context);
    }
    else
    {
         // TODO: Add DeInit Event support for TSEC if required
        return 0;
    }
}
#else
unsigned int NV_API_CALL nv_soc_tsec_event_register(nv_soc_tsec_cb_func_t cb_func, void* cb_context, NvBool is_init_event)
{
    return (unsigned int)NV_ERR_NOT_SUPPORTED;
}
#endif // NV_IS_EXPORT_SYMBOL_PRESENT_tsec_comms_set_init_cb

#if NV_IS_EXPORT_SYMBOL_PRESENT_tsec_comms_clear_init_cb
extern void tsec_comms_clear_init_cb(void);
unsigned int NV_API_CALL nv_soc_tsec_event_unregister(NvBool is_init_event)
{
    if (is_init_event)
    {
        tsec_comms_clear_init_cb();
	return 0;
    }
    else
    {
         // TODO: Add DeInit Event support for TSEC if required
        return 0;
    }
}
#else
unsigned int NV_API_CALL nv_soc_tsec_event_unregister(NvBool is_init_event)
{
    return (unsigned int)NV_ERR_NOT_SUPPORTED;
}
#endif // NV_IS_EXPORT_SYMBOL_PRESENT_tsec_comms_clear_init_cb

void* NV_API_CALL nv_soc_tsec_alloc_mem_desc(NvU32 num_bytes, NvU32 *flcn_addr)
{
#if NV_IS_EXPORT_SYMBOL_PRESENT_tsec_comms_alloc_mem_from_gscco
    extern void *tsec_comms_alloc_mem_from_gscco(u32 size_in_bytes, u32 *gscco_offset);
    return tsec_comms_alloc_mem_from_gscco(num_bytes, flcn_addr);
#else
    return NULL;
#endif // NV_IS_EXPORT_SYMBOL_PRESENT_tsec_comms_alloc_mem_from_gscco
}

void NV_API_CALL nv_soc_tsec_free_mem_desc(void *mem_desc)
{
#if NV_IS_EXPORT_SYMBOL_PRESENT_tsec_comms_free_gscco_mem
    extern void tsec_comms_free_gscco_mem(void *page_va);
    tsec_comms_free_gscco_mem(mem_desc);
#endif // NV_IS_EXPORT_SYMBOL_PRESENT_tsec_comms_free_gscco_mem
}

NV_STATUS nv_get_valid_window_head_mask(nv_state_t *nv, NvU64 *window_head_mask)
{
#if NV_SUPPORTS_PLATFORM_DEVICE
    int ret = 0;
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);

    if ((nvl == NULL) || (window_head_mask == NULL))
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: Wrong input arguments \n");
        return NV_ERR_INVALID_ARGUMENT;
    }

    ret = of_property_read_u64(nvl->dev->of_node, "nvidia,window-head-mask", window_head_mask);
    if (ret == -EINVAL)
    {
        // Property does not exist.
        return NV_ERR_NOT_SUPPORTED;
    }
    else if (ret)
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: failed to read device node window-head-mask ret=%d\n", ret);
        return NV_ERR_GENERIC;
    }
    return NV_OK;
#else
    return NV_ERR_NOT_SUPPORTED;
#endif
}

NV_STATUS NV_API_CALL nv_soc_i2c_hsp_semaphore_acquire(NvU32 ownerId, NvBool bAcquire, NvU64 timeout)
{
    // TODO: This needs to be updated once HSP side implementation is done.
    return NV_OK;
}

NvBool nv_get_hdcp_enabled(nv_state_t *nv)
{
    bool rc = false;
    nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv);

    rc = of_property_read_bool(nvl->dev->of_node, "hdcp_enabled");
    if (rc == true)
    {
        return NV_TRUE;
    }

    return NV_FALSE;
}

#if defined(CONFIG_OF)

// Global framebuffer cache structure
typedef struct {
    NvBool dt_parsed;
    NvBool dt_success;
    NvU64 physical_address;
    NvU32 fb_width;
    NvU32 fb_height;
    NvU32 fb_depth;
    NvU32 fb_pitch;
    NvU64 fb_size;
} screen_info_cache_t;

// Global framebuffer cache instance
static screen_info_cache_t g_screen_info_cache = {
    .dt_parsed = NV_FALSE,
    .dt_success = NV_FALSE,
    .physical_address = 0,
    .fb_width = 0,
    .fb_height = 0,
    .fb_depth = 0,
    .fb_pitch = 0,
    .fb_size = 0
};

NV_STATUS nv_platform_get_screen_info_dt(
    NvU64 *pPhysicalAddress,
    NvU32       *pFbWidth,
    NvU32       *pFbHeight,
    NvU32       *pFbDepth,
    NvU32       *pFbPitch,
    NvU64       *pFbSize
)
{
    //
    // Try to get the framebuffer information from device tree simple-framebuffer node.
    // This is particularly useful for Tegra platforms where the bootloader
    // sets up a simple framebuffer via device tree.
    //

    struct device_node *fb_node;
    struct device_node *mem_node;
    struct resource res;
    u32 width, height, stride;
    const char *format;
    int ret;

    if (g_screen_info_cache.dt_parsed)
    {
        if (g_screen_info_cache.dt_success)
        {
            *pPhysicalAddress = g_screen_info_cache.physical_address;
            *pFbWidth = g_screen_info_cache.fb_width;
            *pFbHeight = g_screen_info_cache.fb_height;
            *pFbDepth = g_screen_info_cache.fb_depth;
            *pFbPitch = g_screen_info_cache.fb_pitch;
            *pFbSize = g_screen_info_cache.fb_size;
            return NV_OK;
        }
        else
        {
            return NV_ERR_NOT_SUPPORTED;
        }
    }

    g_screen_info_cache.dt_parsed = NV_TRUE;

    // Look for simple-framebuffer node in chosen
    fb_node = of_find_node_by_path("/chosen/framebuffer");
    if (fb_node == NULL)
    {
        // Alternative path for some platforms
        for_each_compatible_node(fb_node, NULL, "simple-framebuffer")
        {
            nv_printf(NV_DBG_INFO, "NVRM: Found simple-framebuffer compatible node: %s\n",
                        fb_node->full_name ? fb_node->full_name : "unknown");
            if (of_property_read_bool(fb_node, "status") &&
                !strcmp(of_get_property(fb_node, "status", NULL), "disabled"))
            {
                nv_printf(NV_DBG_INFO, "NVRM: Skipping disabled framebuffer node\n");
                of_node_put(fb_node);
                continue;
            }
            break;
        }
    }
    else
    {
        nv_printf(NV_DBG_INFO, "NVRM: Found framebuffer node in /chosen/framebuffer\n");
    }

    if (fb_node != NULL)
    {
        nv_printf(NV_DBG_INFO, "NVRM: Processing framebuffer node: %s\n",
                    fb_node->full_name ? fb_node->full_name : "unknown");
        // Get memory region
        mem_node = of_parse_phandle(fb_node, "memory-region", 0);
        if (mem_node != NULL)
        {
            nv_printf(NV_DBG_INFO, "NVRM: Found memory-region: %s\n",
                        mem_node->full_name ? mem_node->full_name : "unknown");
            ret = of_address_to_resource(mem_node, 0, &res);
            of_node_put(mem_node);
            if (ret == 0)
            {
                nv_printf(NV_DBG_INFO, "NVRM: Memory region: start=0x%llx, size=0x%llx\n",
                            (NvU64)res.start, (NvU64)resource_size(&res));
                // Get display parameters
                if (!of_property_read_u32(fb_node, "width", &width) &&
                    !of_property_read_u32(fb_node, "height", &height) &&
                    !of_property_read_u32(fb_node, "stride", &stride) &&
                    !of_property_read_string(fb_node, "format", &format))
                {
                    // Calculate depth from format
                    u32 depth = 32; // default
                    if (!strcmp(format, "r5g6b5") || !strcmp(format, "r5g5b5a1") ||
                        !strcmp(format, "x1r5g5b5") || !strcmp(format, "a1r5g5b5"))
                    {
                        depth = 16;
                    }
                    else if (!strcmp(format, "r8g8b8"))
                    {
                        depth = 24;
                    }
                    nv_printf(NV_DBG_INFO, "NVRM: Found simple-framebuffer in DT: %dx%d@%d, addr=0x%llx, size=0x%llx, stride=%d, format=%s\n",
                                width, height, depth, (NvU64)res.start, (NvU64)resource_size(&res), stride, format);

                    g_screen_info_cache.dt_success = NV_TRUE;

                    g_screen_info_cache.physical_address = res.start;
                    g_screen_info_cache.fb_width = width;
                    g_screen_info_cache.fb_height = height;
                    g_screen_info_cache.fb_depth = depth;
                    g_screen_info_cache.fb_pitch = stride;
                    g_screen_info_cache.fb_size = resource_size(&res);

                    *pPhysicalAddress = g_screen_info_cache.physical_address;
                    *pFbWidth = g_screen_info_cache.fb_width;
                    *pFbHeight = g_screen_info_cache.fb_height;
                    *pFbDepth = g_screen_info_cache.fb_depth;
                    *pFbPitch = g_screen_info_cache.fb_pitch;
                    *pFbSize = g_screen_info_cache.fb_size;

                    of_node_put(fb_node);
                    return NV_OK;
                }
                else
                {
                    nv_printf(NV_DBG_ERRORS, "NVRM: Failed to read display parameters from DT\n");
                }
            }
            else
            {
                nv_printf(NV_DBG_ERRORS, "NVRM: Failed to convert memory region to resource: %d\n", ret);
            }
        }
        else
        {
            nv_printf(NV_DBG_ERRORS, "NVRM: No memory-region property found\n");
        }
        of_node_put(fb_node);
    }
    else
    {
        nv_printf(NV_DBG_ERRORS, "NVRM: No simple-framebuffer node found - fb_node is NULL\n");
    }

    return NV_ERR_NOT_SUPPORTED;
}
#endif // CONFIG_OF
