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 
33 struct 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 
53 union loongson_gfxpll_reg_bitmap {
54 	struct loongson_gfxpll_bitmap bitmap;
55 	u32 w[2];
56 	u64 d;
57 };
58 
59 static 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 #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 
72 static 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 
80 static 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 
117 static 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 
141 static 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 
150 static 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 
166 static 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 
173 int 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