#include <sys/param.h>
#include <sys/systm.h>
#include <sys/atomic.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/sysctl.h>
#include <sys/task.h>
#include <uvm/uvm_extern.h>
#include <machine/cpufunc.h>
#include <machine/elf.h>
#include <machine/fdt.h>
#include <machine/pmap.h>
#include <machine/sbi.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_clock.h>
#include <dev/ofw/ofw_regulator.h>
#include <dev/ofw/ofw_thermal.h>
#include <dev/ofw/fdt.h>
#define CPU_VENDOR_MIPS 0x029
#define CPU_VENDOR_ANDES 0x31e
#define CPU_VENDOR_SIFIVE 0x489
#define CPU_VENDOR_THEAD 0x5b7
#define CPU_VENDOR_SPACEMIT 0x710
#define CPU_ARCH_U5 0x0000000000000001
#define CPU_ARCH_U7 0x8000000000000007
#define CPU_ARCH_X60 0x8000000058000001
#define CPU_ARCH_X100 0x8000000058000002
struct arch {
uint64_t id;
char *name;
};
struct arch cpu_arch_none[] = {
{ 0, NULL }
};
struct arch cpu_arch_sifive[] = {
{ CPU_ARCH_U5, "U5" },
{ CPU_ARCH_U7, "U7" },
{ 0, NULL }
};
struct arch cpu_arch_spacemit[] = {
{ CPU_ARCH_X60, "X60" },
{ CPU_ARCH_X100, "X100" },
{ 0, NULL }
};
const struct vendor {
uint32_t id;
char *name;
struct arch *archlist;
} cpu_vendors[] = {
{ CPU_VENDOR_ANDES, "Andes", cpu_arch_none },
{ CPU_VENDOR_MIPS, "MIPS", cpu_arch_none },
{ CPU_VENDOR_SIFIVE, "SiFive", cpu_arch_sifive },
{ CPU_VENDOR_SPACEMIT, "SpacemiT", cpu_arch_spacemit },
{ CPU_VENDOR_THEAD, "T-Head", cpu_arch_none },
{ 0, NULL, NULL }
};
size_t riscv_vlenb;
unsigned long riscv_hwcap;
unsigned long riscv_hwcap2;
const struct extension {
const char *name;
unsigned long hwcap;
unsigned long hwcap2;
} cpu_extensions[] = {
{ "b", HWCAP_ISA_B, 0 },
{ "h", HWCAP_ISA_H, 0 },
{ "scofpmf", HWCAP_ISA_SCOFPMF, 0 },
{ "ssnpm", HWCAP_ISA_SSNPM, 0 },
{ "sstc", HWCAP_ISA_SSTC, 0 },
{ "svinval", HWCAP_ISA_SVINVAL, 0 },
{ "svnapot", HWCAP_ISA_SVNAPOT, 0 },
{ "svpbmt", HWCAP_ISA_SVPBMT, 0 },
{ "v", HWCAP_ISA_V, 0 },
{ "zba", 0, HWCAP2_ISA_ZBA },
{ "zbb", 0, HWCAP2_ISA_ZBB },
{ "zbc", 0, HWCAP2_ISA_ZBC },
{ "zbs", 0, HWCAP2_ISA_ZBS },
{ "zicbom", HWCAP_ISA_ZICBOM, 0 },
{ "zicbop", HWCAP_ISA_ZICBOP, 0 },
{ "zicboz", HWCAP_ISA_ZICBOZ, 0 },
{ "zkt", 0, HWCAP2_ISA_ZKT },
{ "zvbb", 0, HWCAP2_ISA_ZVBB },
{ "zvbc", 0, HWCAP2_ISA_ZVBC },
{ "zvfh", 0, HWCAP2_ISA_ZVFH },
{ "zvkg", 0, HWCAP2_ISA_ZVKG },
{ "zvkned", 0, HWCAP2_ISA_ZVKNED },
{ "zvknha", 0, HWCAP2_ISA_ZVKNHA },
{ "zvknhb", 0, HWCAP2_ISA_ZVKNHB },
{ "zvksed", 0, HWCAP2_ISA_ZVKSED },
{ "zvksh", 0, HWCAP2_ISA_ZVKSH },
{ "zvkt", 0, HWCAP2_ISA_ZVKT },
{ NULL, 0, 0 }
};
char cpu_model[64];
int cpu_node;
struct cpu_info *cpu_info_list = &cpu_info_primary;
int cpu_match(struct device *, void *, void *);
void cpu_attach(struct device *, struct device *, void *);
const struct cfattach cpu_ca = {
sizeof(struct device), cpu_match, cpu_attach
};
struct cfdriver cpu_cd = {
NULL, "cpu", DV_DULL
};
int cpu_errata_sifive_cip_1200;
void cpu_opp_init(struct cpu_info *, uint32_t);
size_t zicbom_dcache_line_size;
void zicbom_dcache_wbinv_range(vaddr_t, vsize_t);
void zicbom_dcache_inv_range(vaddr_t, vsize_t);
void zicbom_dcache_wb_range(vaddr_t, vsize_t);
size_t thead_dcache_line_size;
void thead_dcache_wbinv_range(vaddr_t, vsize_t);
void thead_dcache_inv_range(vaddr_t, vsize_t);
void thead_dcache_wb_range(vaddr_t, vsize_t);
void
cpu_identify(struct cpu_info *ci)
{
uint64_t marchid, mimpid;
uint32_t mvendorid;
const char *vendor_name = NULL;
const char *arch_name = NULL;
struct arch *archlist = cpu_arch_none;
unsigned long cpu_hwcap, cpu_hwcap2;
size_t vlenb;
char *names;
char *name;
char *end;
int i, len;
mvendorid = sbi_get_mvendorid();
marchid = sbi_get_marchid();
mimpid = sbi_get_mimpid();
cpu_hwcap = cpu_hwcap2 = 0;
for (i = 0; cpu_vendors[i].name; i++) {
if (mvendorid == cpu_vendors[i].id) {
vendor_name = cpu_vendors[i].name;
archlist = cpu_vendors[i].archlist;
break;
}
}
for (i = 0; archlist[i].name; i++) {
if (marchid == archlist[i].id) {
arch_name = archlist[i].name;
break;
}
}
if (vendor_name)
printf(": %s", vendor_name);
else
printf(": vendor %x", mvendorid);
if (arch_name)
printf(" %s", arch_name);
else
printf(" arch %llx", marchid);
printf(" imp %llx\n", mimpid);
if (CPU_IS_PRIMARY(ci)) {
if (vendor_name && arch_name)
snprintf(cpu_model, sizeof(cpu_model),
"%s %s imp %llx", vendor_name, arch_name, mimpid);
else if (vendor_name)
snprintf(cpu_model, sizeof(cpu_model),
"%s arch %llx imp %llx", vendor_name, marchid,
mimpid);
else
snprintf(cpu_model, sizeof(cpu_model), "Unknown");
}
len = OF_getproplen(ci->ci_node, "riscv,isa-extensions");
if (len > 0) {
names = malloc(len, M_TEMP, M_WAITOK);
OF_getprop(ci->ci_node, "riscv,isa-extensions", names, len);
end = names + len;
name = names;
while (name < end) {
for (i = 0; cpu_extensions[i].name; i++) {
if (strcmp(name, cpu_extensions[i].name) == 0) {
cpu_hwcap |= cpu_extensions[i].hwcap;
cpu_hwcap2 |= cpu_extensions[i].hwcap2;
break;
}
}
name += strlen(name) + 1;
}
free(names, M_TEMP, len);
}
if (CPU_IS_PRIMARY(ci)) {
riscv_hwcap = cpu_hwcap;
riscv_hwcap2 = cpu_hwcap2;
if ((riscv_hwcap & HWCAP_ISA_SVPBMT) != 0) {
pmap_pma = PTE_PMA;
pmap_nc = PTE_NC;
pmap_io = PTE_IO;
}
if ((riscv_hwcap & HWCAP_ISA_ZICBOM) != 0) {
cpu_dcache_wbinv_range = zicbom_dcache_wbinv_range;
cpu_dcache_inv_range = zicbom_dcache_inv_range;
cpu_dcache_wb_range = zicbom_dcache_wb_range;
zicbom_dcache_line_size =
OF_getpropint(ci->ci_node, "riscv,cbom-block-size", 64);
}
if (mvendorid == CPU_VENDOR_SIFIVE && marchid == CPU_ARCH_U7)
cpu_errata_sifive_cip_1200 = 1;
if (mvendorid == CPU_VENDOR_THEAD && marchid == 0 && mimpid == 0) {
cpu_dcache_wbinv_range = thead_dcache_wbinv_range;
cpu_dcache_inv_range = thead_dcache_inv_range;
cpu_dcache_wb_range = thead_dcache_wb_range;
thead_dcache_line_size =
OF_getpropint(ci->ci_node, "d-cache-block-size", 64);
}
}
if (riscv_hwcap != cpu_hwcap) {
printf("%s: mismatched extensions (hwcap 0x%lx != 0x%lx)\n",
ci->ci_dev->dv_xname, riscv_hwcap, cpu_hwcap);
riscv_hwcap &= cpu_hwcap;
}
if (riscv_hwcap2 != cpu_hwcap2) {
printf("%s: mismatched extensions (hwcap2 0x%lx != 0x%lx)\n",
ci->ci_dev->dv_xname, riscv_hwcap2, cpu_hwcap2);
riscv_hwcap2 &= cpu_hwcap2;
}
if ((riscv_hwcap & HWCAP_ISA_V) != 0) {
vlenb = csr_read(vlenb);
if (CPU_IS_PRIMARY(ci)) {
if (vlenb > VLEN_BYTES_MAX) {
printf("%s: vlenb exceeds maximum (%lu > %d)\n",
ci->ci_dev->dv_xname, vlenb, VLEN_BYTES_MAX);
} else {
riscv_vlenb = vlenb;
}
} else if (riscv_vlenb != vlenb) {
printf("%s: mismatched vlenb (%zu != %lu)\n",
ci->ci_dev->dv_xname, riscv_vlenb, vlenb);
riscv_hwcap &= ~HWCAP_ISA_V;
}
}
}
void
cpu_identify_cleanup(void)
{
hwcap = HWCAP_ISA_G | HWCAP_ISA_C;
hwcap |= riscv_hwcap & ~(HWCAP_ISA_K_MASK | HWCAP_ISA_S_MASK);
hwcap2 = riscv_hwcap2;
if ((riscv_hwcap & HWCAP_ISA_B) != 0)
hwcap2 |= HWCAP2_ISA_ZBA | HWCAP2_ISA_ZBB | HWCAP2_ISA_ZBS;
hwcap &= ~HWCAP_ISA_H;
}
#ifdef MULTIPROCESSOR
int cpu_hatch_secondary(struct cpu_info *ci);
#endif
int cpu_clockspeed(int *);
int
cpu_match(struct device *parent, void *cfdata, void *aux)
{
struct fdt_attach_args *faa = aux;
char buf[32];
if (OF_getprop(faa->fa_node, "device_type", buf, sizeof(buf)) <= 0 ||
strcmp(buf, "cpu") != 0)
return 0;
if (ncpus < MAXCPUS || faa->fa_reg[0].addr == boot_hart)
return 1;
return 0;
}
void
cpu_attach(struct device *parent, struct device *dev, void *aux)
{
struct fdt_attach_args *faa = aux;
struct cpu_info *ci;
int node, level;
uint32_t opp;
KASSERT(faa->fa_nreg > 0);
#ifdef MULTIPROCESSOR
if (faa->fa_reg[0].addr == boot_hart) {
ci = &cpu_info_primary;
ci->ci_flags |= CPUF_RUNNING | CPUF_PRESENT | CPUF_PRIMARY;
csr_set(sie, SIE_SSIE);
} else {
struct cpu_info *ci_last;
ci = malloc(sizeof(*ci), M_DEVBUF, M_WAITOK | M_ZERO);
cpu_info[dev->dv_unit] = ci;
ci_last = cpu_info_list;
while (ci_last->ci_next != NULL)
ci_last = ci_last->ci_next;
ci_last->ci_next = ci;
ci->ci_flags |= CPUF_AP;
ncpus++;
}
#else
ci = &cpu_info_primary;
#endif
ci->ci_dev = dev;
ci->ci_cpuid = dev->dv_unit;
ci->ci_hartid = faa->fa_reg[0].addr;
ci->ci_node = faa->fa_node;
ci->ci_self = ci;
#ifdef MULTIPROCESSOR
if (ci->ci_flags & CPUF_AP) {
int timeout = 10000;
clockqueue_init(&ci->ci_queue);
sched_init_cpu(ci);
if (cpu_hatch_secondary(ci)) {
atomic_setbits_int(&ci->ci_flags, CPUF_IDENTIFY);
membar_producer();
while ((ci->ci_flags & CPUF_IDENTIFIED) == 0 &&
--timeout)
delay(1000);
if (timeout == 0) {
printf(" failed to identify");
ci->ci_flags = 0;
}
} else {
printf(" failed to spin up");
ci->ci_flags = 0;
}
} else {
#endif
cpu_identify(ci);
if (OF_getproplen(ci->ci_node, "clocks") > 0) {
cpu_node = ci->ci_node;
cpu_cpuspeed = cpu_clockspeed;
}
struct fdt_attach_args fa_intc;
for (node = OF_child(faa->fa_node); node; node = OF_peer(node)) {
fa_intc.fa_node = node;
config_found(dev, &fa_intc, NULL);
}
#ifdef MULTIPROCESSOR
}
#endif
opp = OF_getpropint(ci->ci_node, "operating-points-v2", 0);
if (opp)
cpu_opp_init(ci, opp);
node = faa->fa_node;
level = 1;
while (node) {
const char *unit = "KB";
uint32_t line, iline, dline;
uint32_t size, isize, dsize;
uint32_t ways, iways, dways;
uint32_t cache;
line = OF_getpropint(node, "cache-block-size", 0);
size = OF_getpropint(node, "cache-size", 0);
ways = OF_getpropint(node, "cache-sets", 0);
iline = OF_getpropint(node, "i-cache-block-size", line);
isize = OF_getpropint(node, "i-cache-size", size) / 1024;
iways = OF_getpropint(node, "i-cache-sets", ways);
dline = OF_getpropint(node, "d-cache-block-size", line);
dsize = OF_getpropint(node, "d-cache-size", size) / 1024;
dways = OF_getpropint(node, "d-cache-sets", ways);
if (isize == 0 && dsize == 0)
break;
if (isize > 4096 && dsize > 4096) {
unit = "MB";
isize /= 1024;
dsize /= 1024;
}
printf("%s:", dev->dv_xname);
if (OF_getproplen(node, "cache-unified") == 0) {
printf(" %d%s %db/line %d-way L%d cache",
isize, unit, iline, iways, level);
} else {
printf(" %d%s %db/line %d-way L%d I-cache",
isize, unit, iline, iways, level);
printf(", %d%s %db/line %d-way L%d D-cache",
dsize, unit, dline, dways, level);
}
cache = OF_getpropint(node, "next-level-cache", 0);
node = OF_getnodebyphandle(cache);
level++;
printf("\n");
}
}
int
cpu_clockspeed(int *freq)
{
*freq = clock_get_frequency(cpu_node, NULL) / 1000000;
return 0;
}
void
cpu_cache_nop_range(vaddr_t va, vsize_t len)
{
}
__attribute__((target("arch=+zicbom")))
void
zicbom_dcache_wbinv_range(vaddr_t va, vsize_t len)
{
vaddr_t end, mask;
mask = zicbom_dcache_line_size - 1;
end = (va + len + mask) & ~mask;
va &= ~mask;
while (va != end) {
__asm volatile ("cbo.flush (%0)" :: "r"(va) : "memory");
va += zicbom_dcache_line_size;
}
__asm volatile ("fence iorw,iorw" ::: "memory");
}
__attribute__((target("arch=+zicbom")))
void
zicbom_dcache_inv_range(vaddr_t va, vsize_t len)
{
vaddr_t end, mask;
mask = zicbom_dcache_line_size - 1;
end = (va + len + mask) & ~mask;
va &= ~mask;
while (va != end) {
__asm volatile ("cbo.inval (%0)" :: "r"(va) : "memory");
va += zicbom_dcache_line_size;
}
__asm volatile ("fence iorw,iorw" ::: "memory");
}
__attribute__((target("arch=+zicbom")))
void
zicbom_dcache_wb_range(vaddr_t va, vsize_t len)
{
vaddr_t end, mask;
mask = zicbom_dcache_line_size - 1;
end = (va + len + mask) & ~mask;
va &= ~mask;
while (va != end) {
__asm volatile ("cbo.clean (%0)" :: "r"(va) : "memory");
va += zicbom_dcache_line_size;
}
__asm volatile ("fence iorw,iorw" ::: "memory");
}
__attribute__((target("arch=+xtheadcmo,+xtheadsync")))
void
thead_dcache_wbinv_range(vaddr_t va, vsize_t len)
{
vaddr_t end, mask;
mask = thead_dcache_line_size - 1;
end = (va + len + mask) & ~mask;
va &= ~mask;
while (va != end) {
__asm volatile ("th.dcache.civa %0" :: "r"(va) : "memory");
va += thead_dcache_line_size;
}
__asm volatile ("th.sync.s" ::: "memory");
}
__attribute__((target("arch=+xtheadcmo,+xtheadsync")))
void
thead_dcache_inv_range(vaddr_t va, vsize_t len)
{
vaddr_t end, mask;
mask = thead_dcache_line_size - 1;
end = (va + len + mask) & ~mask;
va &= ~mask;
while (va != end) {
__asm volatile ("th.dcache.iva %0" :: "r"(va) : "memory");
va += thead_dcache_line_size;
}
__asm volatile ("th.sync.s" ::: "memory");
}
__attribute__((target("arch=+xtheadcmo,+xtheadsync")))
void
thead_dcache_wb_range(vaddr_t va, vsize_t len)
{
vaddr_t end, mask;
mask = thead_dcache_line_size - 1;
end = (va + len + mask) & ~mask;
va &= ~mask;
while (va != end) {
__asm volatile ("th.dcache.cva %0" :: "r"(va) : "memory");
va += thead_dcache_line_size;
}
__asm volatile ("th.sync.s" ::: "memory");
}
void (*cpu_dcache_wbinv_range)(vaddr_t, vsize_t) = cpu_cache_nop_range;
void (*cpu_dcache_inv_range)(vaddr_t, vsize_t) = cpu_cache_nop_range;
void (*cpu_dcache_wb_range)(vaddr_t, vsize_t) = cpu_cache_nop_range;
#ifdef MULTIPROCESSOR
void cpu_hatch(void);
void
cpu_boot_secondary(struct cpu_info *ci)
{
atomic_setbits_int(&ci->ci_flags, CPUF_GO);
membar_producer();
while ((ci->ci_flags & CPUF_RUNNING) == 0)
CPU_BUSY_CYCLE();
}
void
cpu_boot_secondary_processors(void)
{
struct cpu_info *ci;
CPU_INFO_ITERATOR cii;
CPU_INFO_FOREACH(cii, ci) {
if ((ci->ci_flags & CPUF_AP) == 0)
continue;
if (ci->ci_flags & CPUF_PRIMARY)
continue;
ci->ci_randseed = (arc4random() & 0x7fffffff) + 1;
cpu_boot_secondary(ci);
}
}
int
cpu_hatch_secondary(struct cpu_info *ci)
{
paddr_t start_addr, a1;
void *kstack;
int error;
kstack = km_alloc(USPACE, &kv_any, &kp_zero, &kd_waitok);
ci->ci_initstack_end = (vaddr_t)kstack + USPACE - 16;
pmap_extract(pmap_kernel(), (vaddr_t)cpu_hatch, &start_addr);
pmap_extract(pmap_kernel(), (vaddr_t)ci, &a1);
ci->ci_satp = pmap_kernel()->pm_satp;
error = sbi_hsm_hart_start(ci->ci_hartid, start_addr, a1);
return (error == SBI_SUCCESS);
}
void cpu_startclock(void);
void
cpu_start_secondary(void)
{
struct cpu_info *ci = curcpu();
int s;
ci->ci_flags |= CPUF_PRESENT;
membar_producer();
while ((ci->ci_flags & CPUF_IDENTIFY) == 0)
membar_consumer();
cpu_identify(ci);
atomic_setbits_int(&ci->ci_flags, CPUF_IDENTIFIED);
membar_producer();
while ((ci->ci_flags & CPUF_GO) == 0)
membar_consumer();
s = splhigh();
riscv_intr_cpu_enable();
cpu_startclock();
csr_clear(sstatus, SSTATUS_FS_MASK | SSTATUS_VS_MASK);
csr_set(sie, SIE_SSIE);
atomic_setbits_int(&ci->ci_flags, CPUF_RUNNING);
membar_producer();
spllower(IPL_NONE);
intr_enable();
sched_toidle();
}
void
cpu_kick(struct cpu_info *ci)
{
if (ci != curcpu())
intr_send_ipi(ci, IPI_NOP);
}
void
cpu_unidle(struct cpu_info *ci)
{
if (ci != curcpu())
intr_send_ipi(ci, IPI_NOP);
}
#endif
extern int perflevel;
struct opp {
uint64_t opp_hz;
uint32_t opp_microvolt;
};
struct opp_table {
LIST_ENTRY(opp_table) ot_list;
uint32_t ot_phandle;
struct opp *ot_opp;
u_int ot_nopp;
uint64_t ot_opp_hz_min;
uint64_t ot_opp_hz_max;
struct cpu_info *ot_master;
};
LIST_HEAD(, opp_table) opp_tables = LIST_HEAD_INITIALIZER(opp_tables);
struct task cpu_opp_task;
void cpu_opp_mountroot(struct device *);
void cpu_opp_dotask(void *);
void cpu_opp_setperf(int);
uint32_t cpu_opp_get_cooling_level(void *, uint32_t *);
void cpu_opp_set_cooling_level(void *, uint32_t *, uint32_t);
void
cpu_opp_init(struct cpu_info *ci, uint32_t phandle)
{
struct opp_table *ot;
struct cooling_device *cd;
int count, node, child;
uint32_t opp_hz, opp_microvolt;
uint32_t values[3];
int i, j, len;
LIST_FOREACH(ot, &opp_tables, ot_list) {
if (ot->ot_phandle == phandle) {
ci->ci_opp_table = ot;
return;
}
}
node = OF_getnodebyphandle(phandle);
if (node == 0)
return;
if (!OF_is_compatible(node, "operating-points-v2"))
return;
count = 0;
for (child = OF_child(node); child != 0; child = OF_peer(child)) {
if (OF_getproplen(child, "turbo-mode") == 0)
continue;
count++;
}
if (count == 0)
return;
ot = malloc(sizeof(struct opp_table), M_DEVBUF, M_ZERO | M_WAITOK);
ot->ot_phandle = phandle;
ot->ot_opp = mallocarray(count, sizeof(struct opp),
M_DEVBUF, M_ZERO | M_WAITOK);
ot->ot_nopp = count;
count = 0;
for (child = OF_child(node); child != 0; child = OF_peer(child)) {
if (OF_getproplen(child, "turbo-mode") == 0)
continue;
opp_hz = OF_getpropint64(child, "opp-hz", 0);
len = OF_getpropintarray(child, "opp-microvolt",
values, sizeof(values));
opp_microvolt = 0;
if (len == sizeof(uint32_t) || len == 3 * sizeof(uint32_t))
opp_microvolt = values[0];
for (i = 0; i < count; i++) {
if (opp_hz < ot->ot_opp[i].opp_hz)
break;
}
for (j = count; j > i; j--)
ot->ot_opp[j] = ot->ot_opp[j - 1];
ot->ot_opp[i].opp_hz = opp_hz;
ot->ot_opp[i].opp_microvolt = opp_microvolt;
count++;
}
ot->ot_opp_hz_min = ot->ot_opp[0].opp_hz;
ot->ot_opp_hz_max = ot->ot_opp[count - 1].opp_hz;
if (OF_getproplen(node, "opp-shared") == 0)
ot->ot_master = ci;
LIST_INSERT_HEAD(&opp_tables, ot, ot_list);
ci->ci_opp_table = ot;
ci->ci_opp_idx = -1;
ci->ci_opp_max = ot->ot_nopp - 1;
ci->ci_cpu_supply = OF_getpropint(ci->ci_node, "cpu-supply", 0);
cd = malloc(sizeof(struct cooling_device), M_DEVBUF, M_ZERO | M_WAITOK);
cd->cd_node = ci->ci_node;
cd->cd_cookie = ci;
cd->cd_get_level = cpu_opp_get_cooling_level;
cd->cd_set_level = cpu_opp_set_cooling_level;
cooling_device_register(cd);
config_mountroot(ci->ci_dev, cpu_opp_mountroot);
}
void
cpu_opp_mountroot(struct device *self)
{
struct cpu_info *ci;
CPU_INFO_ITERATOR cii;
int count = 0;
int level = 0;
if (cpu_setperf)
return;
CPU_INFO_FOREACH(cii, ci) {
struct opp_table *ot = ci->ci_opp_table;
uint64_t curr_hz;
uint32_t curr_microvolt;
int error;
if (ot == NULL)
continue;
if (ot->ot_master && ot->ot_master != ci)
continue;
regulator_enable(ci->ci_cpu_supply);
curr_hz = clock_get_frequency(ci->ci_node, NULL);
curr_microvolt = regulator_get_voltage(ci->ci_cpu_supply);
error = ENODEV;
if (curr_hz != 0)
error = clock_set_frequency(ci->ci_node, NULL, curr_hz);
if (error) {
ci->ci_opp_table = NULL;
printf("%s: clock not implemented\n",
ci->ci_dev->dv_xname);
continue;
}
error = ci->ci_cpu_supply ? ENODEV : 0;
if (ci->ci_cpu_supply && curr_microvolt != 0)
error = regulator_set_voltage(ci->ci_cpu_supply,
curr_microvolt);
if (error) {
ci->ci_opp_table = NULL;
printf("%s: regulator not implemented\n",
ci->ci_dev->dv_xname);
continue;
}
if (level == 0) {
uint64_t min, max;
uint64_t level_hz;
min = ot->ot_opp_hz_min;
max = ot->ot_opp_hz_max;
level_hz = clock_get_frequency(ci->ci_node, NULL);
if (level_hz < min)
level_hz = min;
if (level_hz > max)
level_hz = max;
level = howmany(100 * (level_hz - min), (max - min));
}
count++;
}
if (count > 0) {
task_set(&cpu_opp_task, cpu_opp_dotask, NULL);
cpu_setperf = cpu_opp_setperf;
perflevel = (level > 0) ? level : 0;
cpu_setperf(perflevel);
}
}
void
cpu_opp_dotask(void *arg)
{
struct cpu_info *ci;
CPU_INFO_ITERATOR cii;
CPU_INFO_FOREACH(cii, ci) {
struct opp_table *ot = ci->ci_opp_table;
uint64_t curr_hz, opp_hz;
uint32_t curr_microvolt, opp_microvolt;
int opp_idx;
int error = 0;
if (ot == NULL)
continue;
if (ot->ot_master && ot->ot_master != ci)
continue;
opp_idx = MIN(ci->ci_opp_idx, ci->ci_opp_max);
opp_hz = ot->ot_opp[opp_idx].opp_hz;
opp_microvolt = ot->ot_opp[opp_idx].opp_microvolt;
curr_hz = clock_get_frequency(ci->ci_node, NULL);
curr_microvolt = regulator_get_voltage(ci->ci_cpu_supply);
if (error == 0 && opp_hz < curr_hz)
error = clock_set_frequency(ci->ci_node, NULL, opp_hz);
if (error == 0 && ci->ci_cpu_supply &&
opp_microvolt != 0 && opp_microvolt != curr_microvolt) {
error = regulator_set_voltage(ci->ci_cpu_supply,
opp_microvolt);
}
if (error == 0 && opp_hz > curr_hz)
error = clock_set_frequency(ci->ci_node, NULL, opp_hz);
if (error)
printf("%s: DVFS failed\n", ci->ci_dev->dv_xname);
}
}
void
cpu_opp_setperf(int level)
{
struct cpu_info *ci;
CPU_INFO_ITERATOR cii;
int update = 0;
CPU_INFO_FOREACH(cii, ci) {
struct opp_table *ot = ci->ci_opp_table;
uint64_t min, max;
uint64_t level_hz, opp_hz;
int opp_idx = -1;
int i;
if (ot == NULL)
continue;
if (ot->ot_master && ot->ot_master != ci)
continue;
min = ot->ot_opp_hz_min;
max = ot->ot_opp_hz_max;
level_hz = min + (level * (max - min)) / 100;
opp_hz = min;
for (i = 0; i < ot->ot_nopp; i++) {
if (ot->ot_opp[i].opp_hz <= level_hz &&
ot->ot_opp[i].opp_hz >= opp_hz)
opp_hz = ot->ot_opp[i].opp_hz;
}
for (i = 0; i < ot->ot_nopp; i++) {
if (ot->ot_opp[i].opp_hz == opp_hz) {
opp_idx = i;
break;
}
}
KASSERT(opp_idx >= 0);
if (ci->ci_opp_idx != opp_idx) {
ci->ci_opp_idx = opp_idx;
update = 1;
}
}
if (update)
task_add(systq, &cpu_opp_task);
}
uint32_t
cpu_opp_get_cooling_level(void *cookie, uint32_t *cells)
{
struct cpu_info *ci = cookie;
struct opp_table *ot = ci->ci_opp_table;
return ot->ot_nopp - ci->ci_opp_max - 1;
}
void
cpu_opp_set_cooling_level(void *cookie, uint32_t *cells, uint32_t level)
{
struct cpu_info *ci = cookie;
struct opp_table *ot = ci->ci_opp_table;
int opp_max;
if (level > (ot->ot_nopp - 1))
level = ot->ot_nopp - 1;
opp_max = (ot->ot_nopp - level - 1);
if (ci->ci_opp_max != opp_max) {
ci->ci_opp_max = opp_max;
task_add(systq, &cpu_opp_task);
}
}