200 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Copyright (C) 2023 Loongson Technology Corporation Limited4 */5 6#include <linux/delay.h>7 8#include <drm/drm_file.h>9#include <drm/drm_managed.h>10#include <drm/drm_print.h>11 12#include "lsdc_drv.h"13 14/*15 * GFX PLL is the PLL used by DC, GMC and GPU, the structure of the GFX PLL16 * may suffer from change across chip variants.17 *18 *19 * +-------------+ sel_out_dc20 * +----| / div_out_0 | _____/ _____ DC21 * | +-------------+22 * refclk +---------+ +-------+ | +-------------+ sel_out_gmc23 * ---+---> | div_ref | ---> | loopc | --+--> | / div_out_1 | _____/ _____ GMC24 * | +---------+ +-------+ | +-------------+25 * | / * | +-------------+ sel_out_gpu26 * | +----| / div_out_2 | _____/ _____ GPU27 * | +-------------+28 * | ^29 * | |30 * +--------------------------- bypass ----------------------+31 */32 33struct loongson_gfxpll_bitmap {34 /* Byte 0 ~ Byte 3 */35 unsigned div_out_dc : 7; /* 6 : 0 DC output clock divider */36 unsigned div_out_gmc : 7; /* 13 : 7 GMC output clock divider */37 unsigned div_out_gpu : 7; /* 20 : 14 GPU output clock divider */38 unsigned loopc : 9; /* 29 : 21 clock multiplier */39 unsigned _reserved_1_ : 2; /* 31 : 30 */40 41 /* Byte 4 ~ Byte 7 */42 unsigned div_ref : 7; /* 38 : 32 Input clock divider */43 unsigned locked : 1; /* 39 PLL locked indicator */44 unsigned sel_out_dc : 1; /* 40 dc output clk enable */45 unsigned sel_out_gmc : 1; /* 41 gmc output clk enable */46 unsigned sel_out_gpu : 1; /* 42 gpu output clk enable */47 unsigned set_param : 1; /* 43 Trigger the update */48 unsigned bypass : 1; /* 44 */49 unsigned powerdown : 1; /* 45 */50 unsigned _reserved_2_ : 18; /* 46 : 63 no use */51};52 53union loongson_gfxpll_reg_bitmap {54 struct loongson_gfxpll_bitmap bitmap;55 u32 w[2];56 u64 d;57};58 59static void __gfxpll_rreg(struct loongson_gfxpll *this,60 union loongson_gfxpll_reg_bitmap *reg)61{62#if defined(CONFIG_64BIT)63 reg->d = readq(this->mmio);64#else65 reg->w[0] = readl(this->mmio);66 reg->w[1] = readl(this->mmio + 4);67#endif68}69 70/* Update new parameters to the hardware */71 72static int loongson_gfxpll_update(struct loongson_gfxpll * const this,73 struct loongson_gfxpll_parms const *pin)74{75 /* None, TODO */76 77 return 0;78}79 80static void loongson_gfxpll_get_rates(struct loongson_gfxpll * const this,81 unsigned int *dc,82 unsigned int *gmc,83 unsigned int *gpu)84{85 struct loongson_gfxpll_parms *pparms = &this->parms;86 union loongson_gfxpll_reg_bitmap gfxpll_reg;87 unsigned int pre_output;88 unsigned int dc_mhz;89 unsigned int gmc_mhz;90 unsigned int gpu_mhz;91 92 __gfxpll_rreg(this, &gfxpll_reg);93 94 pparms->div_ref = gfxpll_reg.bitmap.div_ref;95 pparms->loopc = gfxpll_reg.bitmap.loopc;96 97 pparms->div_out_dc = gfxpll_reg.bitmap.div_out_dc;98 pparms->div_out_gmc = gfxpll_reg.bitmap.div_out_gmc;99 pparms->div_out_gpu = gfxpll_reg.bitmap.div_out_gpu;100 101 pre_output = pparms->ref_clock / pparms->div_ref * pparms->loopc;102 103 dc_mhz = pre_output / pparms->div_out_dc / 1000;104 gmc_mhz = pre_output / pparms->div_out_gmc / 1000;105 gpu_mhz = pre_output / pparms->div_out_gpu / 1000;106 107 if (dc)108 *dc = dc_mhz;109 110 if (gmc)111 *gmc = gmc_mhz;112 113 if (gpu)114 *gpu = gpu_mhz;115}116 117static void loongson_gfxpll_print(struct loongson_gfxpll * const this,118 struct drm_printer *p,119 bool verbose)120{121 struct loongson_gfxpll_parms *parms = &this->parms;122 unsigned int dc, gmc, gpu;123 124 if (verbose) {125 drm_printf(p, "reference clock: %u\n", parms->ref_clock);126 drm_printf(p, "div_ref = %u\n", parms->div_ref);127 drm_printf(p, "loopc = %u\n", parms->loopc);128 129 drm_printf(p, "div_out_dc = %u\n", parms->div_out_dc);130 drm_printf(p, "div_out_gmc = %u\n", parms->div_out_gmc);131 drm_printf(p, "div_out_gpu = %u\n", parms->div_out_gpu);132 }133 134 this->funcs->get_rates(this, &dc, &gmc, &gpu);135 136 drm_printf(p, "dc: %uMHz, gmc: %uMHz, gpu: %uMHz\n", dc, gmc, gpu);137}138 139/* GFX (DC, GPU, GMC) PLL initialization and destroy function */140 141static void loongson_gfxpll_fini(struct drm_device *ddev, void *data)142{143 struct loongson_gfxpll *this = (struct loongson_gfxpll *)data;144 145 iounmap(this->mmio);146 147 kfree(this);148}149 150static int loongson_gfxpll_init(struct loongson_gfxpll * const this)151{152 struct loongson_gfxpll_parms *pparms = &this->parms;153 struct drm_printer printer = drm_info_printer(this->ddev->dev);154 155 pparms->ref_clock = LSDC_PLL_REF_CLK_KHZ;156 157 this->mmio = ioremap(this->reg_base, this->reg_size);158 if (IS_ERR_OR_NULL(this->mmio))159 return -ENOMEM;160 161 this->funcs->print(this, &printer, false);162 163 return 0;164}165 166static const struct loongson_gfxpll_funcs lsdc_gmc_gpu_funcs = {167 .init = loongson_gfxpll_init,168 .update = loongson_gfxpll_update,169 .get_rates = loongson_gfxpll_get_rates,170 .print = loongson_gfxpll_print,171};172 173int loongson_gfxpll_create(struct drm_device *ddev,174 struct loongson_gfxpll **ppout)175{176 struct lsdc_device *ldev = to_lsdc(ddev);177 const struct loongson_gfx_desc *gfx = to_loongson_gfx(ldev->descp);178 struct loongson_gfxpll *this;179 int ret;180 181 this = kzalloc(sizeof(*this), GFP_KERNEL);182 if (IS_ERR_OR_NULL(this))183 return -ENOMEM;184 185 this->ddev = ddev;186 this->reg_size = gfx->gfxpll.reg_size;187 this->reg_base = gfx->conf_reg_base + gfx->gfxpll.reg_offset;188 this->funcs = &lsdc_gmc_gpu_funcs;189 190 ret = this->funcs->init(this);191 if (unlikely(ret)) {192 kfree(this);193 return ret;194 }195 196 *ppout = this;197 198 return drmm_add_action_or_reset(ddev, loongson_gfxpll_fini, this);199}200