root/drivers/gpu/drm/loongson/lsdc_gfxpll.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2023 Loongson Technology Corporation Limited
 */

#include <linux/delay.h>

#include <drm/drm_file.h>
#include <drm/drm_managed.h>
#include <drm/drm_print.h>

#include "lsdc_drv.h"

/*
 * GFX PLL is the PLL used by DC, GMC and GPU, the structure of the GFX PLL
 * may suffer from change across chip variants.
 *
 *
 *                                            +-------------+  sel_out_dc
 *                                       +----| / div_out_0 | _____/ _____ DC
 *                                       |    +-------------+
 * refclk   +---------+      +-------+   |    +-------------+  sel_out_gmc
 * ---+---> | div_ref | ---> | loopc | --+--> | / div_out_1 | _____/ _____ GMC
 *    |     +---------+      +-------+   |    +-------------+
 *    |          /               *       |    +-------------+  sel_out_gpu
 *    |                                  +----| / div_out_2 | _____/ _____ GPU
 *    |                                       +-------------+
 *    |                                                         ^
 *    |                                                         |
 *    +--------------------------- bypass ----------------------+
 */

struct loongson_gfxpll_bitmap {
        /* Byte 0 ~ Byte 3 */
        unsigned div_out_dc    : 7;  /*  6 : 0    DC output clock divider  */
        unsigned div_out_gmc   : 7;  /* 13 : 7    GMC output clock divider */
        unsigned div_out_gpu   : 7;  /* 20 : 14   GPU output clock divider */
        unsigned loopc         : 9;  /* 29 : 21   clock multiplier         */
        unsigned _reserved_1_  : 2;  /* 31 : 30                            */

        /* Byte 4 ~ Byte 7 */
        unsigned div_ref       : 7;   /* 38 : 32   Input clock divider    */
        unsigned locked        : 1;   /* 39        PLL locked indicator   */
        unsigned sel_out_dc    : 1;   /* 40        dc output clk enable   */
        unsigned sel_out_gmc   : 1;   /* 41        gmc output clk enable  */
        unsigned sel_out_gpu   : 1;   /* 42        gpu output clk enable  */
        unsigned set_param     : 1;   /* 43        Trigger the update     */
        unsigned bypass        : 1;   /* 44                               */
        unsigned powerdown     : 1;   /* 45                               */
        unsigned _reserved_2_  : 18;  /* 46 : 63   no use                 */
};

union loongson_gfxpll_reg_bitmap {
        struct loongson_gfxpll_bitmap bitmap;
        u32 w[2];
        u64 d;
};

static void __gfxpll_rreg(struct loongson_gfxpll *this,
                          union loongson_gfxpll_reg_bitmap *reg)
{
#if defined(CONFIG_64BIT)
        reg->d = readq(this->mmio);
#else
        reg->w[0] = readl(this->mmio);
        reg->w[1] = readl(this->mmio + 4);
#endif
}

/* Update new parameters to the hardware */

static int loongson_gfxpll_update(struct loongson_gfxpll * const this,
                                  struct loongson_gfxpll_parms const *pin)
{
        /* None, TODO */

        return 0;
}

static void loongson_gfxpll_get_rates(struct loongson_gfxpll * const this,
                                      unsigned int *dc,
                                      unsigned int *gmc,
                                      unsigned int *gpu)
{
        struct loongson_gfxpll_parms *pparms = &this->parms;
        union loongson_gfxpll_reg_bitmap gfxpll_reg;
        unsigned int pre_output;
        unsigned int dc_mhz;
        unsigned int gmc_mhz;
        unsigned int gpu_mhz;

        __gfxpll_rreg(this, &gfxpll_reg);

        pparms->div_ref = gfxpll_reg.bitmap.div_ref;
        pparms->loopc = gfxpll_reg.bitmap.loopc;

        pparms->div_out_dc = gfxpll_reg.bitmap.div_out_dc;
        pparms->div_out_gmc = gfxpll_reg.bitmap.div_out_gmc;
        pparms->div_out_gpu = gfxpll_reg.bitmap.div_out_gpu;

        pre_output = pparms->ref_clock / pparms->div_ref * pparms->loopc;

        dc_mhz = pre_output / pparms->div_out_dc / 1000;
        gmc_mhz = pre_output / pparms->div_out_gmc / 1000;
        gpu_mhz = pre_output / pparms->div_out_gpu / 1000;

        if (dc)
                *dc = dc_mhz;

        if (gmc)
                *gmc = gmc_mhz;

        if (gpu)
                *gpu = gpu_mhz;
}

static void loongson_gfxpll_print(struct loongson_gfxpll * const this,
                                  struct drm_printer *p,
                                  bool verbose)
{
        struct loongson_gfxpll_parms *parms = &this->parms;
        unsigned int dc, gmc, gpu;

        if (verbose) {
                drm_printf(p, "reference clock: %u\n", parms->ref_clock);
                drm_printf(p, "div_ref = %u\n", parms->div_ref);
                drm_printf(p, "loopc = %u\n", parms->loopc);

                drm_printf(p, "div_out_dc = %u\n", parms->div_out_dc);
                drm_printf(p, "div_out_gmc = %u\n", parms->div_out_gmc);
                drm_printf(p, "div_out_gpu = %u\n", parms->div_out_gpu);
        }

        this->funcs->get_rates(this, &dc, &gmc, &gpu);

        drm_printf(p, "dc: %uMHz, gmc: %uMHz, gpu: %uMHz\n", dc, gmc, gpu);
}

/* GFX (DC, GPU, GMC) PLL initialization and destroy function */

static void loongson_gfxpll_fini(struct drm_device *ddev, void *data)
{
        struct loongson_gfxpll *this = (struct loongson_gfxpll *)data;

        iounmap(this->mmio);

        kfree(this);
}

static int loongson_gfxpll_init(struct loongson_gfxpll * const this)
{
        struct loongson_gfxpll_parms *pparms = &this->parms;
        struct drm_printer printer = drm_info_printer(this->ddev->dev);

        pparms->ref_clock = LSDC_PLL_REF_CLK_KHZ;

        this->mmio = ioremap(this->reg_base, this->reg_size);
        if (IS_ERR_OR_NULL(this->mmio))
                return -ENOMEM;

        this->funcs->print(this, &printer, false);

        return 0;
}

static const struct loongson_gfxpll_funcs lsdc_gmc_gpu_funcs = {
        .init = loongson_gfxpll_init,
        .update = loongson_gfxpll_update,
        .get_rates = loongson_gfxpll_get_rates,
        .print = loongson_gfxpll_print,
};

int loongson_gfxpll_create(struct drm_device *ddev,
                           struct loongson_gfxpll **ppout)
{
        struct lsdc_device *ldev = to_lsdc(ddev);
        const struct loongson_gfx_desc *gfx = to_loongson_gfx(ldev->descp);
        struct loongson_gfxpll *this;
        int ret;

        this = kzalloc_obj(*this);
        if (IS_ERR_OR_NULL(this))
                return -ENOMEM;

        this->ddev = ddev;
        this->reg_size = gfx->gfxpll.reg_size;
        this->reg_base = gfx->conf_reg_base + gfx->gfxpll.reg_offset;
        this->funcs = &lsdc_gmc_gpu_funcs;

        ret = this->funcs->init(this);
        if (unlikely(ret)) {
                kfree(this);
                return ret;
        }

        *ppout = this;

        return drmm_add_action_or_reset(ddev, loongson_gfxpll_fini, this);
}