Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 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); } |