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(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, &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