200 lines
5.8 KiB
C
200 lines
5.8 KiB
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(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);
|
|
}
|