diff options
Diffstat (limited to 'drivers/gpu/drm/loongson/lsdc_gfxpll.c')
-rw-r--r-- | drivers/gpu/drm/loongson/lsdc_gfxpll.c | 199 |
1 files changed, 199 insertions, 0 deletions
diff --git a/drivers/gpu/drm/loongson/lsdc_gfxpll.c b/drivers/gpu/drm/loongson/lsdc_gfxpll.c new file mode 100644 index 000000000000..249c09d703ad --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_gfxpll.c @@ -0,0 +1,199 @@ +// 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); +} |