#include <sys/param.h>
#include <sys/systm.h>
#include <machine/cpufunc.h>
#include <machine/cpufreq.h>
#define AMD0F_MSR_FIDVID_CTL 0xc0010041
#define AMD0F_MSR_FIDVID_STATUS 0xc0010042
#define AMD0F_STA_CFID(x) ((x) & 0x3f)
#define AMD0F_STA_SFID(x) (((x) >> 8) & 0x3f)
#define AMD0F_STA_MFID(x) (((x) >> 16) & 0x3f)
#define AMD0F_STA_PENDING(x) (((x) >> 31) & 0x01)
#define AMD0F_STA_CVID(x) (((x) >> 32) & 0x1f)
#define AMD0F_STA_SVID(x) (((x) >> 40) & 0x1f)
#define AMD0F_STA_MVID(x) (((x) >> 48) & 0x1f)
#define AMD0F_WRITE_FIDVID(fid, vid, ctrl) \
wrmsr(AMD0F_MSR_FIDVID_CTL, \
(((ctrl) << 32) | (1ULL << 16) | ((vid) << 8) | (fid)))
#define AMD0F_WAIT_FIDVID_CHG(status) \
do { \
(status) = rdmsr(AMD0F_MSR_FIDVID_STATUS); \
} while (AMD0F_STA_PENDING(status))
#define AMD0F_FID2VCO(fid) \
(((fid) < 8) ? (8 + ((fid) << 1)) : (fid))
#define AMD0F_DELAY_VST(vst) DELAY(20 * (vst))
#define AMD0F_DELAY_IRT(irt) DELAY(10 * (1 << (irt)))
#define abs(x) ((x) < 0 ? -(x) : (x))
int
amd0f_set_fidvid(const struct amd0f_fidvid *fv, const struct amd0f_xsit *xsit)
{
uint32_t val, cfid, cvid;
int rvo;
uint64_t status;
status = rdmsr(AMD0F_MSR_FIDVID_STATUS);
if (AMD0F_STA_PENDING(status))
return EBUSY;
cfid = AMD0F_STA_CFID(status);
cvid = AMD0F_STA_CVID(status);
if (fv->fid == cfid && fv->vid == cvid)
return 0;
if ((fv->fid & ~0x1) > (cfid & ~0x1) || cvid > fv->vid) {
KKASSERT(fv->vid >= xsit->rvo);
} else {
KKASSERT(cvid >= xsit->rvo);
}
while (cvid > fv->vid) {
if (cvid > (1 << xsit->mvs))
val = cvid - (1 << xsit->mvs);
else
val = 0;
AMD0F_WRITE_FIDVID(cfid, val, 0ULL);
AMD0F_WAIT_FIDVID_CHG(status);
cvid = AMD0F_STA_CVID(status);
AMD0F_DELAY_VST(xsit->vst);
}
for (rvo = xsit->rvo; rvo > 0 && cvid > 0; --rvo) {
AMD0F_WRITE_FIDVID(cfid, cvid - 1, 0ULL);
AMD0F_WAIT_FIDVID_CHG(status);
cvid = AMD0F_STA_CVID(status);
AMD0F_DELAY_VST(xsit->vst);
}
if (cfid != fv->fid) {
int vco_fid, vco_cfid;
vco_fid = AMD0F_FID2VCO(fv->fid);
vco_cfid = AMD0F_FID2VCO(cfid);
while (abs(vco_fid - vco_cfid) > 2) {
if (fv->fid > cfid) {
if (cfid > 6)
val = cfid + 2;
else
val = AMD0F_FID2VCO(cfid) + 2;
} else {
KKASSERT(cfid >= 2);
val = cfid - 2;
}
AMD0F_WRITE_FIDVID(val, cvid,
(uint64_t)xsit->pll_time * 1000 / 5);
AMD0F_WAIT_FIDVID_CHG(status);
cfid = AMD0F_STA_CFID(status);
AMD0F_DELAY_IRT(xsit->irt);
vco_cfid = AMD0F_FID2VCO(cfid);
}
if (cfid != fv->fid) {
AMD0F_WRITE_FIDVID(fv->fid, cvid,
(uint64_t)xsit->pll_time * 1000 / 5);
AMD0F_WAIT_FIDVID_CHG(status);
cfid = AMD0F_STA_CFID(status);
AMD0F_DELAY_IRT(xsit->irt);
}
}
if (cvid != fv->vid) {
AMD0F_WRITE_FIDVID(cfid, fv->vid, 0ULL);
AMD0F_WAIT_FIDVID_CHG(status);
cvid = AMD0F_STA_CVID(status);
AMD0F_DELAY_VST(xsit->vst);
}
return 0;
}
int
amd0f_get_fidvid(struct amd0f_fidvid *fv)
{
uint64_t status;
status = rdmsr(AMD0F_MSR_FIDVID_STATUS);
if (AMD0F_STA_PENDING(status))
return EBUSY;
fv->fid = AMD0F_STA_CFID(status);
fv->vid = AMD0F_STA_CVID(status);
return 0;
}
void
amd0f_fidvid_limit(struct amd0f_fidvid *fv_min, struct amd0f_fidvid *fv_max)
{
uint32_t max_fid, max_vid, start_fid, start_vid;
uint64_t status;
status = rdmsr(AMD0F_MSR_FIDVID_STATUS);
start_fid = AMD0F_STA_SFID(status);
max_fid = AMD0F_STA_MFID(status);
start_vid = AMD0F_STA_SVID(status);
max_vid = AMD0F_STA_MVID(status);
if (max_fid == 0x2a && max_vid != 0) {
fv_max->fid = start_fid + 0xa;
fv_max->vid = max_vid + 0x2;
fv_min->fid = 0x2;
fv_min->vid = start_vid;
} else {
fv_max->fid = max_fid;
fv_max->vid = max_vid + 0x2;
fv_min->fid = start_fid;
fv_min->vid = start_vid;
}
}