root/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) STMicroelectronics SA 2014
 * Author: Vincent Abriou <vincent.abriou@st.com> for STMicroelectronics.
 */

#include <drm/drm_print.h>

#include "sti_hdmi_tx3g4c28phy.h"

#define HDMI_SRZ_CFG                             0x504
#define HDMI_SRZ_PLL_CFG                         0x510
#define HDMI_SRZ_ICNTL                           0x518
#define HDMI_SRZ_CALCODE_EXT                     0x520

#define HDMI_SRZ_CFG_EN                          BIT(0)
#define HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT BIT(1)
#define HDMI_SRZ_CFG_EXTERNAL_DATA               BIT(16)
#define HDMI_SRZ_CFG_RBIAS_EXT                   BIT(17)
#define HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION      BIT(18)
#define HDMI_SRZ_CFG_EN_BIASRES_DETECTION        BIT(19)
#define HDMI_SRZ_CFG_EN_SRC_TERMINATION          BIT(24)

#define HDMI_SRZ_CFG_INTERNAL_MASK  (HDMI_SRZ_CFG_EN     | \
                HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT | \
                HDMI_SRZ_CFG_EXTERNAL_DATA               | \
                HDMI_SRZ_CFG_RBIAS_EXT                   | \
                HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION      | \
                HDMI_SRZ_CFG_EN_BIASRES_DETECTION        | \
                HDMI_SRZ_CFG_EN_SRC_TERMINATION)

#define PLL_CFG_EN                               BIT(0)
#define PLL_CFG_NDIV_SHIFT                       (8)
#define PLL_CFG_IDF_SHIFT                        (16)
#define PLL_CFG_ODF_SHIFT                        (24)

#define ODF_DIV_1                                (0)
#define ODF_DIV_2                                (1)
#define ODF_DIV_4                                (2)
#define ODF_DIV_8                                (3)

#define HDMI_TIMEOUT_PLL_LOCK  50  /*milliseconds */

struct plldividers_s {
        uint32_t min;
        uint32_t max;
        uint32_t idf;
        uint32_t odf;
};

/*
 * Functional specification recommended values
 */
#define NB_PLL_MODE 5
static struct plldividers_s plldividers[NB_PLL_MODE] = {
        {0, 20000000, 1, ODF_DIV_8},
        {20000000, 42500000, 2, ODF_DIV_8},
        {42500000, 85000000, 4, ODF_DIV_4},
        {85000000, 170000000, 8, ODF_DIV_2},
        {170000000, 340000000, 16, ODF_DIV_1}
};

#define NB_HDMI_PHY_CONFIG 2
static struct hdmi_phy_config hdmiphy_config[NB_HDMI_PHY_CONFIG] = {
        {0, 250000000, {0x0, 0x0, 0x0, 0x0} },
        {250000000, 300000000, {0x1110, 0x0, 0x0, 0x0} },
};

/**
 * sti_hdmi_tx3g4c28phy_start - Start hdmi phy macro cell tx3g4c28
 *
 * @hdmi: pointer on the hdmi internal structure
 *
 * Return false if an error occur
 */
static bool sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi)
{
        u32 ckpxpll = hdmi->mode.clock * 1000;
        u32 val, tmdsck, idf, odf, pllctrl = 0;
        bool foundplldivides = false;
        int i;

        DRM_DEBUG_DRIVER("ckpxpll = %dHz\n", ckpxpll);

        for (i = 0; i < NB_PLL_MODE; i++) {
                if (ckpxpll >= plldividers[i].min &&
                    ckpxpll < plldividers[i].max) {
                        idf = plldividers[i].idf;
                        odf = plldividers[i].odf;
                        foundplldivides = true;
                        break;
                }
        }

        if (!foundplldivides) {
                DRM_ERROR("input TMDS clock speed (%d) not supported\n",
                          ckpxpll);
                goto err;
        }

        /* Assuming no pixel repetition and 24bits color */
        tmdsck = ckpxpll;
        pllctrl |= 40 << PLL_CFG_NDIV_SHIFT;

        if (tmdsck > 340000000) {
                DRM_ERROR("output TMDS clock (%d) out of range\n", tmdsck);
                goto err;
        }

        pllctrl |= idf << PLL_CFG_IDF_SHIFT;
        pllctrl |= odf << PLL_CFG_ODF_SHIFT;

        /*
         * Configure and power up the PHY PLL
         */
        hdmi->event_received = false;
        DRM_DEBUG_DRIVER("pllctrl = 0x%x\n", pllctrl);
        hdmi_write(hdmi, (pllctrl | PLL_CFG_EN), HDMI_SRZ_PLL_CFG);

        /* wait PLL interrupt */
        wait_event_interruptible_timeout(hdmi->wait_event,
                                         hdmi->event_received == true,
                                         msecs_to_jiffies
                                         (HDMI_TIMEOUT_PLL_LOCK));

        if ((hdmi_read(hdmi, HDMI_STA) & HDMI_STA_DLL_LCK) == 0) {
                DRM_ERROR("hdmi phy pll not locked\n");
                goto err;
        }

        DRM_DEBUG_DRIVER("got PHY PLL Lock\n");

        val = (HDMI_SRZ_CFG_EN |
               HDMI_SRZ_CFG_EXTERNAL_DATA |
               HDMI_SRZ_CFG_EN_BIASRES_DETECTION |
               HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION);

        if (tmdsck > 165000000)
                val |= HDMI_SRZ_CFG_EN_SRC_TERMINATION;

        /*
         * To configure the source termination and pre-emphasis appropriately
         * for different high speed TMDS clock frequencies a phy configuration
         * table must be provided, tailored to the SoC and board combination.
         */
        for (i = 0; i < NB_HDMI_PHY_CONFIG; i++) {
                if ((hdmiphy_config[i].min_tmds_freq <= tmdsck) &&
                    (hdmiphy_config[i].max_tmds_freq >= tmdsck)) {
                        val |= (hdmiphy_config[i].config[0]
                                & ~HDMI_SRZ_CFG_INTERNAL_MASK);
                        hdmi_write(hdmi, val, HDMI_SRZ_CFG);

                        val = hdmiphy_config[i].config[1];
                        hdmi_write(hdmi, val, HDMI_SRZ_ICNTL);

                        val = hdmiphy_config[i].config[2];
                        hdmi_write(hdmi, val, HDMI_SRZ_CALCODE_EXT);

                        DRM_DEBUG_DRIVER("serializer cfg 0x%x 0x%x 0x%x\n",
                                         hdmiphy_config[i].config[0],
                                         hdmiphy_config[i].config[1],
                                         hdmiphy_config[i].config[2]);
                        return true;
                }
        }

        /*
         * Default, power up the serializer with no pre-emphasis or
         * output swing correction
         */
        hdmi_write(hdmi, val,  HDMI_SRZ_CFG);
        hdmi_write(hdmi, 0x0, HDMI_SRZ_ICNTL);
        hdmi_write(hdmi, 0x0, HDMI_SRZ_CALCODE_EXT);

        return true;

err:
        return false;
}

/**
 * sti_hdmi_tx3g4c28phy_stop - Stop hdmi phy macro cell tx3g4c28
 *
 * @hdmi: pointer on the hdmi internal structure
 */
static void sti_hdmi_tx3g4c28phy_stop(struct sti_hdmi *hdmi)
{
        int val = 0;

        DRM_DEBUG_DRIVER("\n");

        hdmi->event_received = false;

        val = HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION;
        val |= HDMI_SRZ_CFG_EN_BIASRES_DETECTION;

        hdmi_write(hdmi, val, HDMI_SRZ_CFG);
        hdmi_write(hdmi, 0, HDMI_SRZ_PLL_CFG);

        /* wait PLL interrupt */
        wait_event_interruptible_timeout(hdmi->wait_event,
                                         hdmi->event_received == true,
                                         msecs_to_jiffies
                                         (HDMI_TIMEOUT_PLL_LOCK));

        if (hdmi_read(hdmi, HDMI_STA) & HDMI_STA_DLL_LCK)
                DRM_ERROR("hdmi phy pll not well disabled\n");
}

struct hdmi_phy_ops tx3g4c28phy_ops = {
        .start = sti_hdmi_tx3g4c28phy_start,
        .stop = sti_hdmi_tx3g4c28phy_stop,
};