blob: 249c09d703ad76a4b644434e4596fe2103c2b9f6 [file] [log] [blame] [edit]
// 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(sizeof(*this), GFP_KERNEL);
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);
}