1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * Copyright (C) 2023 Loongson Technology Corporation Limited
4 */
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 PLL
16 * may suffer from change across chip variants.
17 *
18 *
19 * +-------------+ sel_out_dc
20 * +----| / div_out_0 | _____/ _____ DC
21 * | +-------------+
22 * refclk +---------+ +-------+ | +-------------+ sel_out_gmc
23 * ---+---> | div_ref | ---> | loopc | --+--> | / div_out_1 | _____/ _____ GMC
24 * | +---------+ +-------+ | +-------------+
25 * | / * | +-------------+ sel_out_gpu
26 * | +----| / div_out_2 | _____/ _____ GPU
27 * | +-------------+
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(addr: this->mmio);
64#else
65 reg->w[0] = readl(this->mmio);
66 reg->w[1] = readl(this->mmio + 4);
67#endif
68}
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, reg: &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, f: "reference clock: %u\n", parms->ref_clock);
126 drm_printf(p, f: "div_ref = %u\n", parms->div_ref);
127 drm_printf(p, f: "loopc = %u\n", parms->loopc);
128
129 drm_printf(p, f: "div_out_dc = %u\n", parms->div_out_dc);
130 drm_printf(p, f: "div_out_gmc = %u\n", parms->div_out_gmc);
131 drm_printf(p, f: "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, f: "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(addr: this->mmio);
146
147 kfree(objp: 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(dev: this->ddev->dev);
154
155 pparms->ref_clock = LSDC_PLL_REF_CLK_KHZ;
156
157 this->mmio = ioremap(offset: this->reg_base, size: this->reg_size);
158 if (IS_ERR_OR_NULL(ptr: 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(dcp: ldev->descp);
178 struct loongson_gfxpll *this;
179 int ret;
180
181 this = kzalloc(size: sizeof(*this), GFP_KERNEL);
182 if (IS_ERR_OR_NULL(ptr: 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(objp: this);
193 return ret;
194 }
195
196 *ppout = this;
197
198 return drmm_add_action_or_reset(ddev, loongson_gfxpll_fini, this);
199}
200

source code of linux/drivers/gpu/drm/loongson/lsdc_gfxpll.c