summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/loongson/lsdc_gfxpll.c
blob: 249c09d703ad76a4b644434e4596fe2103c2b9f6 (plain)
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);
}