#include <sys/cdefs.h>
#include "opt_acpi.h"
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/cpu.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <machine/bus.h>
#include <contrib/dev/acpica/include/acpi.h>
#include <dev/acpica/acpivar.h>
#include <dev/pci/pcivar.h>
#include "cpufreq_if.h"
struct acpi_throttle_softc {
device_t cpu_dev;
ACPI_HANDLE cpu_handle;
uint32_t cpu_p_blk;
uint32_t cpu_p_blk_len;
struct resource *cpu_p_cnt;
int cpu_p_type;
uint32_t cpu_thr_state;
};
#define THR_GET_REG(reg) \
(bus_space_read_4(rman_get_bustag((reg)), \
rman_get_bushandle((reg)), 0))
#define THR_SET_REG(reg, val) \
(bus_space_write_4(rman_get_bustag((reg)), \
rman_get_bushandle((reg)), 0, (val)))
#define CPU_MAX_SPEED (1 << cpu_duty_width)
#define CPU_SPEED_PERCENT(x) ((10000 * (x)) / CPU_MAX_SPEED)
#define CPU_SPEED_PRINTABLE(x) (CPU_SPEED_PERCENT(x) / 10), \
(CPU_SPEED_PERCENT(x) % 10)
#define CPU_P_CNT_THT_EN (1<<4)
#define CPU_QUIRK_NO_THROTTLE (1<<1)
#define PCI_VENDOR_INTEL 0x8086
#define PCI_DEVICE_82371AB_3 0x7113
#define PCI_REVISION_A_STEP 0
#define PCI_REVISION_B_STEP 1
static uint32_t cpu_duty_offset;
static uint32_t cpu_duty_width;
static int thr_rid;
static int thr_quirks;
static void acpi_throttle_identify(driver_t *driver, device_t parent);
static int acpi_throttle_probe(device_t dev);
static int acpi_throttle_attach(device_t dev);
static int acpi_throttle_evaluate(struct acpi_throttle_softc *sc);
static void acpi_throttle_quirks(struct acpi_throttle_softc *sc);
static int acpi_thr_settings(device_t dev, struct cf_setting *sets,
int *count);
static int acpi_thr_set(device_t dev, const struct cf_setting *set);
static int acpi_thr_get(device_t dev, struct cf_setting *set);
static int acpi_thr_type(device_t dev, int *type);
static device_method_t acpi_throttle_methods[] = {
DEVMETHOD(device_identify, acpi_throttle_identify),
DEVMETHOD(device_probe, acpi_throttle_probe),
DEVMETHOD(device_attach, acpi_throttle_attach),
DEVMETHOD(cpufreq_drv_set, acpi_thr_set),
DEVMETHOD(cpufreq_drv_get, acpi_thr_get),
DEVMETHOD(cpufreq_drv_type, acpi_thr_type),
DEVMETHOD(cpufreq_drv_settings, acpi_thr_settings),
DEVMETHOD_END
};
static driver_t acpi_throttle_driver = {
"acpi_throttle",
acpi_throttle_methods,
sizeof(struct acpi_throttle_softc),
};
DRIVER_MODULE(acpi_throttle, cpu, acpi_throttle_driver, 0, 0);
static void
acpi_throttle_identify(driver_t *driver, device_t parent)
{
ACPI_BUFFER buf;
ACPI_HANDLE handle;
ACPI_OBJECT *obj;
if (device_find_child(parent, "acpi_throttle", DEVICE_UNIT_ANY))
return;
handle = acpi_get_handle(parent);
if (handle == NULL)
return;
if (AcpiGbl_FADT.DutyWidth == 0 ||
acpi_get_type(parent) != ACPI_TYPE_PROCESSOR)
return;
buf.Pointer = NULL;
buf.Length = ACPI_ALLOCATE_BUFFER;
if (ACPI_FAILURE(AcpiEvaluateObject(handle, NULL, NULL, &buf)))
return;
obj = (ACPI_OBJECT *)buf.Pointer;
if ((obj->Processor.PblkAddress && obj->Processor.PblkLength >= 4) ||
ACPI_SUCCESS(AcpiEvaluateObject(handle, "_PTC", NULL, NULL))) {
if (BUS_ADD_CHILD(parent, 0, "acpi_throttle",
device_get_unit(parent)) == NULL)
device_printf(parent, "add throttle child failed\n");
}
AcpiOsFree(obj);
}
static int
acpi_throttle_probe(device_t dev)
{
if (resource_disabled("acpi_throttle", 0))
return (ENXIO);
if (device_find_child(device_get_parent(dev), "p4tcc", DEVICE_UNIT_ANY)
&& !resource_disabled("p4tcc", 0))
return (ENXIO);
device_set_desc(dev, "ACPI CPU Throttling");
return (0);
}
static int
acpi_throttle_attach(device_t dev)
{
struct acpi_throttle_softc *sc;
struct cf_setting set;
ACPI_BUFFER buf;
ACPI_OBJECT *obj;
ACPI_STATUS status;
int error;
sc = device_get_softc(dev);
sc->cpu_dev = dev;
sc->cpu_handle = acpi_get_handle(dev);
buf.Pointer = NULL;
buf.Length = ACPI_ALLOCATE_BUFFER;
status = AcpiEvaluateObject(sc->cpu_handle, NULL, NULL, &buf);
if (ACPI_FAILURE(status)) {
device_printf(dev, "attach failed to get Processor obj - %s\n",
AcpiFormatException(status));
return (ENXIO);
}
obj = (ACPI_OBJECT *)buf.Pointer;
sc->cpu_p_blk = obj->Processor.PblkAddress;
sc->cpu_p_blk_len = obj->Processor.PblkLength;
AcpiOsFree(obj);
if (device_get_unit(dev) == 0)
acpi_throttle_quirks(sc);
error = acpi_throttle_evaluate(sc);
if (error)
return (error);
set.freq = 10000;
acpi_thr_set(dev, &set);
cpufreq_register(dev);
return (0);
}
static int
acpi_throttle_evaluate(struct acpi_throttle_softc *sc)
{
uint32_t duty_end;
ACPI_BUFFER buf;
ACPI_OBJECT obj;
ACPI_GENERIC_ADDRESS gas;
ACPI_STATUS status;
if (device_get_unit(sc->cpu_dev) == 0) {
cpu_duty_offset = AcpiGbl_FADT.DutyOffset;
cpu_duty_width = AcpiGbl_FADT.DutyWidth;
}
if (cpu_duty_width == 0 || (thr_quirks & CPU_QUIRK_NO_THROTTLE) != 0)
return (ENXIO);
duty_end = cpu_duty_offset + cpu_duty_width - 1;
if (duty_end > 31) {
device_printf(sc->cpu_dev,
"CLK_VAL field overflows P_CNT register\n");
return (ENXIO);
}
if (cpu_duty_offset <= 4 && duty_end >= 4) {
device_printf(sc->cpu_dev,
"CLK_VAL field overlaps THT_EN bit\n");
return (ENXIO);
}
buf.Pointer = &obj;
buf.Length = sizeof(obj);
status = AcpiEvaluateObject(sc->cpu_handle, "_PTC", NULL, &buf);
if (ACPI_SUCCESS(status)) {
if (obj.Buffer.Length < sizeof(ACPI_GENERIC_ADDRESS) + 3) {
device_printf(sc->cpu_dev, "_PTC buffer too small\n");
return (ENXIO);
}
memcpy(&gas, obj.Buffer.Pointer + 3, sizeof(gas));
acpi_bus_alloc_gas(sc->cpu_dev, &sc->cpu_p_type, &thr_rid,
&gas, &sc->cpu_p_cnt, 0);
if (sc->cpu_p_cnt != NULL && bootverbose) {
device_printf(sc->cpu_dev, "P_CNT from _PTC %#jx\n",
gas.Address);
}
}
if (sc->cpu_p_cnt == NULL) {
if (sc->cpu_p_blk_len < 4)
return (ENXIO);
gas.Address = sc->cpu_p_blk;
gas.SpaceId = ACPI_ADR_SPACE_SYSTEM_IO;
gas.BitWidth = 32;
acpi_bus_alloc_gas(sc->cpu_dev, &sc->cpu_p_type, &thr_rid,
&gas, &sc->cpu_p_cnt, 0);
if (sc->cpu_p_cnt != NULL) {
if (bootverbose)
device_printf(sc->cpu_dev,
"P_CNT from P_BLK %#x\n", sc->cpu_p_blk);
} else {
device_printf(sc->cpu_dev, "failed to attach P_CNT\n");
return (ENXIO);
}
}
thr_rid++;
return (0);
}
static void
acpi_throttle_quirks(struct acpi_throttle_softc *sc)
{
#ifdef __i386__
device_t acpi_dev;
acpi_dev = pci_find_device(PCI_VENDOR_INTEL, PCI_DEVICE_82371AB_3);
if (acpi_dev) {
switch (pci_get_revid(acpi_dev)) {
case PCI_REVISION_A_STEP:
case PCI_REVISION_B_STEP:
thr_quirks |= CPU_QUIRK_NO_THROTTLE;
break;
default:
break;
}
}
#endif
}
static int
acpi_thr_settings(device_t dev, struct cf_setting *sets, int *count)
{
int i, speed;
if (sets == NULL || count == NULL)
return (EINVAL);
if (*count < CPU_MAX_SPEED)
return (E2BIG);
memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * CPU_MAX_SPEED);
for (i = 0, speed = CPU_MAX_SPEED; speed != 0; i++, speed--) {
sets[i].freq = CPU_SPEED_PERCENT(speed);
sets[i].dev = dev;
}
*count = CPU_MAX_SPEED;
return (0);
}
static int
acpi_thr_set(device_t dev, const struct cf_setting *set)
{
struct acpi_throttle_softc *sc;
uint32_t clk_val, p_cnt, speed;
if (set == NULL)
return (EINVAL);
sc = device_get_softc(dev);
speed = set->freq * CPU_MAX_SPEED / 10000;
if (speed * 10000 != set->freq * CPU_MAX_SPEED ||
speed < 1 || speed > CPU_MAX_SPEED)
return (EINVAL);
if (speed == sc->cpu_thr_state)
return (0);
p_cnt = THR_GET_REG(sc->cpu_p_cnt);
p_cnt &= ~CPU_P_CNT_THT_EN;
THR_SET_REG(sc->cpu_p_cnt, p_cnt);
if (speed < CPU_MAX_SPEED) {
clk_val = (CPU_MAX_SPEED - 1) << cpu_duty_offset;
p_cnt &= ~clk_val;
p_cnt |= (speed << cpu_duty_offset);
THR_SET_REG(sc->cpu_p_cnt, p_cnt);
p_cnt |= CPU_P_CNT_THT_EN;
THR_SET_REG(sc->cpu_p_cnt, p_cnt);
}
sc->cpu_thr_state = speed;
return (0);
}
static int
acpi_thr_get(device_t dev, struct cf_setting *set)
{
struct acpi_throttle_softc *sc;
uint32_t p_cnt, clk_val;
if (set == NULL)
return (EINVAL);
sc = device_get_softc(dev);
p_cnt = THR_GET_REG(sc->cpu_p_cnt);
clk_val = (p_cnt >> cpu_duty_offset) & (CPU_MAX_SPEED - 1);
sc->cpu_thr_state = clk_val;
memset(set, CPUFREQ_VAL_UNKNOWN, sizeof(*set));
set->freq = CPU_SPEED_PERCENT(clk_val);
set->dev = dev;
return (0);
}
static int
acpi_thr_type(device_t dev, int *type)
{
if (type == NULL)
return (EINVAL);
*type = CPUFREQ_TYPE_RELATIVE;
return (0);
}