1 /*
2  * Copyright (c) 2004 Martin V\xe9giard.
3  * Copyright (c) 2004-2005 Bruno Ducrot
4  * Copyright (c) 2004 FUKUDA Nobuhiko <nfukuda@spa.is.uec.ac.jp>
5  * Copyright (c) 2004, 2006 The NetBSD Foundation, Inc.
6  * All rights reserved.
7  *
8  * This code is derived from software contributed to The NetBSD Foundation
9  * by Juan Romero Pardines and Martin Vegiard.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include <sys/param.h>
32 #include <sys/systm.h>
33 
34 #include <machine/cpufunc.h>
35 #include <machine/cpufreq.h>
36 
37 #define AMD0F_MSR_FIDVID_CTL		0xc0010041
38 #define AMD0F_MSR_FIDVID_STATUS		0xc0010042
39 
40 /* AMD0F_MSR_FIDVID_STATUS */
41 #define AMD0F_STA_CFID(x)		((x) & 0x3f)
42 #define AMD0F_STA_SFID(x)		(((x) >> 8) & 0x3f)
43 #define AMD0F_STA_MFID(x)		(((x) >> 16) & 0x3f)
44 #define AMD0F_STA_PENDING(x)		(((x) >> 31) & 0x01)
45 #define AMD0F_STA_CVID(x)		(((x) >> 32) & 0x1f)
46 #define AMD0F_STA_SVID(x)		(((x) >> 40) & 0x1f)
47 #define AMD0F_STA_MVID(x)		(((x) >> 48) & 0x1f)
48 
49 #define AMD0F_WRITE_FIDVID(fid, vid, ctrl) \
50 	wrmsr(AMD0F_MSR_FIDVID_CTL, \
51 	(((ctrl) << 32) | (1ULL << 16) | ((vid) << 8) | (fid)))
52 
53 #define AMD0F_WAIT_FIDVID_CHG(status) \
54 do { \
55 	(status) = rdmsr(AMD0F_MSR_FIDVID_STATUS); \
56 } while (AMD0F_STA_PENDING(status))
57 
58 #define AMD0F_FID2VCO(fid) \
59 	(((fid) < 8) ? (8 + ((fid) << 1)) : (fid))
60 
61 #define AMD0F_DELAY_VST(vst)		DELAY(20 * (vst))
62 #define AMD0F_DELAY_IRT(irt)		DELAY(10 * (1 << (irt)))
63 
64 /* XXX */
65 #define abs(x) ((x) < 0 ? -(x) : (x))
66 
67 int
68 amd0f_set_fidvid(const struct amd0f_fidvid *fv, const struct amd0f_xsit *xsit)
69 {
70 	uint32_t val, cfid, cvid, tvid;
71 	uint64_t status;
72 
73 	/*
74 	 * We don't wait change pending bit here, need to ensure
75 	 * that it isn't stuck.
76 	 */
77 	status = rdmsr(AMD0F_MSR_FIDVID_STATUS);
78 	if (AMD0F_STA_PENDING(status))
79 		return EBUSY;
80 
81 	cfid = AMD0F_STA_CFID(status);
82 	cvid = AMD0F_STA_CVID(status);
83 	if (fv->fid == cfid && fv->vid == cvid)
84 		return 0;
85 
86 	/*
87 	 * Phase 1: Raise core voltage to the TargetVID
88 	 */
89 	if ((fv->fid & ~0x1) > (cfid & ~0x1) || cvid > fv->vid) {
90 		KKASSERT(fv->vid >= xsit->rvo);
91 		tvid = fv->vid - xsit->rvo;
92 	} else {
93 		KKASSERT(cvid >= xsit->rvo);
94 		tvid = cvid - xsit->rvo;
95 	}
96 	while (cvid > tvid) {
97 		if (cvid > (1 << xsit->mvs))
98 			val = cvid - (1 << xsit->mvs);
99 		else
100 			val = 0;
101 		AMD0F_WRITE_FIDVID(cfid, val, 0ULL);
102 		AMD0F_WAIT_FIDVID_CHG(status);
103 		cvid = AMD0F_STA_CVID(status);
104 		AMD0F_DELAY_VST(xsit->vst);
105 	}
106 
107 	/*
108 	 * Phase 2: Change to requested core frequency
109 	 */
110 	if (cfid != fv->fid) {
111 		/* NOTE: Keep type as int, else following 'abs' will break */
112 		int vco_fid, vco_cfid;
113 
114 		vco_fid = AMD0F_FID2VCO(fv->fid);
115 		vco_cfid = AMD0F_FID2VCO(cfid);
116 		while (abs(vco_fid - vco_cfid) > 2) {
117 			if (fv->fid > cfid) {
118 				if (cfid > 6)
119 					val = cfid + 2;
120 				else
121 					val = AMD0F_FID2VCO(cfid) + 2;
122 			} else {
123 				KKASSERT(cfid >= 2);
124 				val = cfid - 2;
125 			}
126 			AMD0F_WRITE_FIDVID(val, cvid,
127 				(uint64_t)xsit->pll_time * 1000 / 5);
128 			AMD0F_WAIT_FIDVID_CHG(status);
129 			cfid = AMD0F_STA_CFID(status);
130 			AMD0F_DELAY_IRT(xsit->irt);
131 			vco_cfid = AMD0F_FID2VCO(cfid);
132 		}
133 		if (cfid != fv->fid) {
134 			AMD0F_WRITE_FIDVID(fv->fid, cvid,
135 				(uint64_t)xsit->pll_time * 1000 / 5);
136 			AMD0F_WAIT_FIDVID_CHG(status);
137 			cfid = AMD0F_STA_CFID(status);
138 			AMD0F_DELAY_IRT(xsit->irt);
139 		}
140 	}
141 
142 	/*
143 	 * Phase 3: Change to requested voltage
144 	 */
145 	if (cvid != fv->vid) {
146 		AMD0F_WRITE_FIDVID(cfid, fv->vid, 0ULL);
147 		AMD0F_WAIT_FIDVID_CHG(status);
148 		cvid = AMD0F_STA_CVID(status);
149 		AMD0F_DELAY_VST(xsit->vst);
150 	}
151 	return 0;
152 }
153 
154 int
155 amd0f_get_fidvid(struct amd0f_fidvid *fv)
156 {
157 	uint64_t status;
158 
159 	status = rdmsr(AMD0F_MSR_FIDVID_STATUS);
160 	if (AMD0F_STA_PENDING(status))
161 		return EBUSY;
162 
163 	fv->fid = AMD0F_STA_CFID(status);
164 	fv->vid = AMD0F_STA_CVID(status);
165 	return 0;
166 }
167 
168 void
169 amd0f_fidvid_limit(struct amd0f_fidvid *fv_min, struct amd0f_fidvid *fv_max)
170 {
171 	uint32_t max_fid, max_vid, start_fid, start_vid;
172 	uint64_t status;
173 
174 	status = rdmsr(AMD0F_MSR_FIDVID_STATUS);
175 
176 	start_fid = AMD0F_STA_SFID(status);
177 	max_fid = AMD0F_STA_MFID(status);
178 	start_vid = AMD0F_STA_SVID(status);
179 	max_vid = AMD0F_STA_MVID(status);
180 
181 	if (max_fid == 0x2a && max_vid != 0) {
182 		fv_max->fid = start_fid + 0xa;
183 		fv_max->vid = max_vid + 0x2;
184 		fv_min->fid = 0x2;
185 		fv_min->vid = start_vid;
186 	} else {
187 		fv_max->fid = max_fid;
188 		fv_max->vid = max_vid + 0x2;
189 		fv_min->fid = start_fid;
190 		fv_min->vid = start_vid;
191 	}
192 }
193