diff options
author | Mark Kettenis <kettenis@cvs.openbsd.org> | 2005-10-23 13:47:50 +0000 |
---|---|---|
committer | Mark Kettenis <kettenis@cvs.openbsd.org> | 2005-10-23 13:47:50 +0000 |
commit | 0268f798c6958b16f8f7863a6864e1dccf1215aa (patch) | |
tree | d34e3c966e49b3a4d6e4ac75b2aaa54de11ce657 /sys/arch/macppc | |
parent | f215e1984228c8c2ecee831e7f8e89df170b8b69 (diff) |
Preliminary sensors support for iMac G5.
Diffstat (limited to 'sys/arch/macppc')
-rw-r--r-- | sys/arch/macppc/dev/smu.c | 361 |
1 files changed, 349 insertions, 12 deletions
diff --git a/sys/arch/macppc/dev/smu.c b/sys/arch/macppc/dev/smu.c index d186d34d479..69a91dde729 100644 --- a/sys/arch/macppc/dev/smu.c +++ b/sys/arch/macppc/dev/smu.c @@ -1,4 +1,4 @@ -/* $OpenBSD: smu.c,v 1.2 2005/10/21 22:07:45 kettenis Exp $ */ +/* $OpenBSD: smu.c,v 1.3 2005/10/23 13:47:49 kettenis Exp $ */ /* * Copyright (c) 2005 Mark Kettenis @@ -19,7 +19,12 @@ #include <sys/param.h> #include <sys/systm.h> #include <sys/device.h> -#include <sys/types.h> +#include <sys/kernel.h> +#include <sys/kthread.h> +#include <sys/lock.h> +#include <sys/proc.h> +#include <sys/sensors.h> +#include <sys/timeout.h> #include <machine/autoconf.h> @@ -29,6 +34,23 @@ int smu_match(struct device *, void *, void *); void smu_attach(struct device *, struct device *, void *); +#define SMU_MAXFANS 3 + +struct smu_fan { + u_int8_t reg; + u_int16_t min_rpm; + u_int16_t max_rpm; + u_int16_t unmanaged_rpm; + struct sensor sensor; +}; + +#define SMU_MAXSENSORS 3 + +struct smu_sensor { + u_int8_t reg; + struct sensor sensor; +}; + struct smu_softc { struct device sc_dev; @@ -37,22 +59,39 @@ struct smu_softc { bus_dmamap_t sc_cmdmap; bus_dma_segment_t sc_cmdseg[1]; caddr_t sc_cmd; + struct lock sc_lock; /* Doorbell and mailbox. */ struct ppc_bus_space sc_mem_bus_space; bus_space_tag_t sc_memt; bus_space_handle_t sc_gpioh; bus_space_handle_t sc_buffh; + + struct smu_fan sc_fans[SMU_MAXFANS]; + int sc_num_fans; + + struct smu_sensor sc_sensors[SMU_MAXSENSORS]; + int sc_num_sensors; + + u_int16_t sc_cpu_diode_scale; + int16_t sc_cpu_diode_offset; + u_int16_t sc_cpu_volt_scale; + int16_t sc_cpu_volt_offset; + u_int16_t sc_cpu_curr_scale; + int16_t sc_cpu_curr_offset; + + struct timeout sc_timo; }; struct cfattach smu_ca = { sizeof(struct smu_softc), smu_match, smu_attach }; + struct cfdriver smu_cd = { NULL, "smu", DV_DULL, }; -/* SMU command. */ +/* SMU command */ struct smu_cmd { u_int8_t cmd; u_int8_t len; @@ -60,14 +99,38 @@ struct smu_cmd { }; #define SMU_CMDSZ sizeof(struct smu_cmd) -/* Real Time Clock. */ +/* RTC */ #define SMU_RTC 0x8e #define SMU_RTC_SET_DATETIME 0x80 #define SMU_RTC_GET_DATETIME 0x81 +/* ADC */ +#define SMU_ADC 0xd8 + +/* Fan control */ +#define SMU_FAN 0x4a + +/* Data partitions */ +#define SMU_PARTITION 0x3e +#define SMU_PARTITION_LATEST 0x01 +#define SMU_PARTITION_BASE 0x02 +#define SMU_PARTITION_UPDATE 0x03 + +/* Miscellaneous */ +#define SMU_MISC 0xee +#define SMU_MISC_GET_DATA 0x02 + +void smu_create_thread(void *); +int smu_intr(void *); + int smu_do_cmd(struct smu_softc *, int); int smu_time_read(time_t *); int smu_time_write(time_t); +int smu_get_datablock(struct smu_softc *sc, u_int8_t, u_int8_t *, size_t); +int smu_fan_set_rpm(struct smu_softc *, struct smu_fan *, u_int16_t); +int smu_fan_refresh(struct smu_softc *, struct smu_fan *); +int smu_sensor_refresh(struct smu_softc *, struct smu_sensor *); +void smu_refresh_sensors(void *); #define GPIO_DDR 0x04 /* Data direction */ #define GPIO_DDR_OUTPUT 0x04 /* Output */ @@ -94,7 +157,13 @@ void smu_attach(struct device *parent, struct device *self, void *aux) { struct smu_softc *sc = (struct smu_softc *)self; - int nseg; + struct confargs *ca = aux; + struct smu_fan *fan; + struct smu_sensor *sensor; + int nseg, node; + char type[32], loc[32]; + u_int32_t reg, intr, gpio, val; + u_int8_t data[12]; /* XXX */ sc->sc_mem_bus_space.bus_base = 0x80000000; @@ -102,8 +171,16 @@ smu_attach(struct device *parent, struct device *self, void *aux) sc->sc_mem_bus_space.bus_io = 0; sc->sc_memt = &sc->sc_mem_bus_space; - /* XXX Should get this from OF or macgpio. */ - if (bus_space_map(sc->sc_memt, 0x50 + 0x12, 1, 0, &sc->sc_gpioh)) { + /* Map smu-doorbell gpio. */ + if (OF_getprop(ca->ca_node, "platform-doorbell-ack", + &node, sizeof node) <= 0 || + OF_getprop(node, "reg", ®, sizeof reg) <= 0 || + OF_getprop(node, "interrupts", &intr, sizeof intr) <= 0 || + OF_getprop(OF_parent(node), "reg", &gpio, sizeof gpio) <= 0) { + printf(": cannot find smu-doorbell gpio\n"); + return; + } + if (bus_space_map(sc->sc_memt, gpio + reg, 1, 0, &sc->sc_gpioh)) { printf(": cannot map smu-doorbell gpio\n"); return; } @@ -145,18 +222,129 @@ smu_attach(struct device *parent, struct device *self, void *aux) return; } + lockinit(&sc->sc_lock, PZERO, "smulk", 0, 0); + + /* Establish smu-doorbell interrupt. */ + mac_intr_establish(parent, intr, IST_EDGE, IPL_BIO, + smu_intr, sc, "smu"); + /* Initialize global variables that control RTC functionality. */ time_read = smu_time_read; time_write = smu_time_write; + /* Fans */ + node = OF_getnodebyname(ca->ca_node, "fans"); + for (node = OF_child(node); node; node = OF_peer(node)) { + if (OF_getprop(node, "reg", ®, sizeof reg) <= 0 || + OF_getprop(node, "device_type", type, sizeof type) <= 0) + continue; + + if (strcmp(type, "fan-rpm-control") != 0) { + printf(": unsupported fan type: %s\n", type); + return; + } + + if (sc->sc_num_fans >= SMU_MAXFANS) { + printf(": too many fans\n"); + return; + } + + fan = &sc->sc_fans[sc->sc_num_fans++]; + strlcpy(fan->sensor.device, sc->sc_dev.dv_xname, + sizeof(fan->sensor.device)); + fan->sensor.type = SENSOR_FANRPM; + fan->sensor.flags = SENSOR_FINVALID; + fan->reg = reg; + + if (OF_getprop(node, "min-value", &val, sizeof val) <= 0) + val = 0; + fan->min_rpm = val; + if (OF_getprop(node, "max-value", &val, sizeof val) <= 0) + val = 0xffff; + fan->max_rpm = val; + if (OF_getprop(node, "unmanage-value", &val, sizeof val) <= 0) + val = fan->max_rpm; + fan->unmanaged_rpm = val; + + if (OF_getprop(node, "location", loc, sizeof loc) <= 0) + strlcpy(loc, "Unknown", sizeof loc); + strlcpy(fan->sensor.desc, loc, sizeof sensor->sensor.desc); + + /* Start running fans at their "unmanaged" speed. */ + smu_fan_set_rpm(sc, fan, fan->unmanaged_rpm); + + SENSOR_ADD(&fan->sensor); + } + + /* Sensors */ + node = OF_getnodebyname(ca->ca_node, "sensors"); + for (node = OF_child(node); node; node = OF_peer(node)) { + if (OF_getprop(node, "reg", &val, sizeof val) <= 0 || + OF_getprop(node, "device_type", type, sizeof type) <= 0) + continue; + + sensor = &sc->sc_sensors[sc->sc_num_sensors++]; + strlcpy(sensor->sensor.device, sc->sc_dev.dv_xname, + sizeof(sensor->sensor.device)); + sensor->sensor.flags = SENSOR_FINVALID; + sensor->reg = val; + + if (strcmp(type, "current-sensor") == 0) { + sensor->sensor.type = SENSOR_AMPS; + } else if (strcmp(type, "temp-sensor") == 0) { + sensor->sensor.type = SENSOR_TEMP; + } else if (strcmp(type, "voltage-sensor") == 0) { + sensor->sensor.type = SENSOR_VOLTS_DC; + } else { + sensor->sensor.type = SENSOR_INTEGER; + } + + if (OF_getprop(node, "location", loc, sizeof loc) <= 0) + strlcpy(loc, "Unknown", sizeof loc); + strlcpy(sensor->sensor.desc, loc, sizeof sensor->sensor.desc); + + SENSOR_ADD(&sensor->sensor); + } + + /* CPU temperature diode calibration */ + smu_get_datablock(sc, 0x18, data, sizeof data); + sc->sc_cpu_diode_scale = (data[4] << 8) + data[5]; + sc->sc_cpu_diode_offset = (data[6] << 8) + data[7]; + + /* CPU power (voltage and current) calibration */ + smu_get_datablock(sc, 0x21, data, sizeof data); + sc->sc_cpu_volt_scale = (data[4] << 8) + data[5]; + sc->sc_cpu_volt_offset = (data[6] << 8) + data[7]; + sc->sc_cpu_curr_scale = (data[8] << 8) + data[9]; + sc->sc_cpu_curr_offset = (data[10] << 8) + data[11]; + + kthread_create_deferred(smu_create_thread, sc); printf("\n"); } +void +smu_create_thread(void *arg) +{ + struct smu_softc *sc = arg; + + if (kthread_create(smu_refresh_sensors, sc, NULL, + sc->sc_dev.dv_xname)) + panic("smu thread"); +} + +int +smu_intr(void *arg) +{ + wakeup(arg); + return 1; +} + int smu_do_cmd(struct smu_softc *sc, int timo) { struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd; u_int8_t gpio, ack = ~cmd->cmd; + int error; /* Write to mailbox. */ bus_space_write_4(sc->sc_memt, sc->sc_buffh, 0, @@ -167,12 +355,12 @@ smu_do_cmd(struct smu_softc *sc, int timo) /* Ring doorbell. */ bus_space_write_1(sc->sc_memt, sc->sc_gpioh, 0, GPIO_DDR_OUTPUT); + do { + error = tsleep(sc, PWAIT, "smu", (timo * hz) / 1000); + if (error) + return (error); gpio = bus_space_read_1(sc->sc_memt, sc->sc_gpioh, 0); - timo -= 50; - if (timo < 0) - return (ETIMEDOUT); - delay(50 * 1000); } while (!(gpio & (GPIO_DATA))); /* CPU might have brought back the cache line. */ @@ -191,6 +379,8 @@ smu_time_read(time_t *secs) struct clock_ymdhms dt; int error; + lockmgr(&sc->sc_lock, LK_EXCLUSIVE, NULL, curproc); + cmd->cmd = SMU_RTC; cmd->len = 1; cmd->data[0] = SMU_RTC_GET_DATETIME; @@ -206,6 +396,9 @@ smu_time_read(time_t *secs) dt.dt_hour = FROMBCD(cmd->data[2]); dt.dt_min = FROMBCD(cmd->data[1]); dt.dt_sec = FROMBCD(cmd->data[0]); + + lockmgr(&sc->sc_lock, LK_RELEASE, NULL, curproc); + *secs = clock_ymdhms_to_secs(&dt); return (0); } @@ -216,9 +409,12 @@ smu_time_write(time_t secs) struct smu_softc *sc = smu_cd.cd_devs[0]; struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd; struct clock_ymdhms dt; + int error; clock_secs_to_ymdhms(secs, &dt); + lockmgr(&sc->sc_lock, LK_EXCLUSIVE, NULL, curproc); + cmd->cmd = SMU_RTC; cmd->len = 8; cmd->data[0] = SMU_RTC_SET_DATETIME; @@ -229,6 +425,147 @@ smu_time_write(time_t secs) cmd->data[5] = TOBCD(dt.dt_day); cmd->data[6] = TOBCD(dt.dt_mon); cmd->data[7] = TOBCD(dt.dt_year - 2000); + error = smu_do_cmd(sc, 800); + + lockmgr(&sc->sc_lock, LK_RELEASE, NULL, curproc); + + return (error); +} + - return (smu_do_cmd(sc, 800)); +int +smu_get_datablock(struct smu_softc *sc, u_int8_t id, u_int8_t *buf, size_t len) +{ + struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd; + u_int8_t addr[4]; + int error; + + cmd->cmd = SMU_PARTITION; + cmd->len = 2; + cmd->data[0] = SMU_PARTITION_LATEST; + cmd->data[1] = id; + error = smu_do_cmd(sc, 800); + if (error) + return (error); + + addr[0] = 0x00; + addr[1] = 0x00; + addr[2] = cmd->data[0]; + addr[3] = cmd->data[1]; + + cmd->cmd = SMU_MISC; + cmd->len = 7; + cmd->data[0] = SMU_MISC_GET_DATA; + cmd->data[1] = sizeof(u_int32_t); + cmd->data[2] = addr[0]; + cmd->data[3] = addr[1]; + cmd->data[4] = addr[2]; + cmd->data[5] = addr[3]; + cmd->data[6] = len; + error = smu_do_cmd(sc, 800); + if (error) + return (error); + + memcpy(buf, cmd->data, len); + return (0); +} + +int +smu_fan_set_rpm(struct smu_softc *sc, struct smu_fan *fan, u_int16_t rpm) +{ + struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd; + + cmd->cmd = SMU_FAN; + cmd->len = 4; + cmd->data[0] = 0x00; /* fan-rpm-control */ + cmd->data[1] = 0x01 << fan->reg; + cmd->data[2] = (rpm >> 8) & 0xff; + cmd->data[3] = (rpm & 0xff); + return smu_do_cmd(sc, 800); +} + +int +smu_fan_refresh(struct smu_softc *sc, struct smu_fan *fan) +{ + struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd; + int error; + + cmd->cmd = SMU_FAN; + cmd->len = 2; + cmd->data[0] = 0x01; /* fan-rpm-control */ + cmd->data[1] = 0x01 << fan->reg; + error = smu_do_cmd(sc, 800); + if (error) { + fan->sensor.flags = SENSOR_FINVALID; + return (error); + } + fan->sensor.value = (cmd->data[1] << 8) + cmd->data[2]; + fan->sensor.flags = 0; + return (0); +} + +int +smu_sensor_refresh(struct smu_softc *sc, struct smu_sensor *sensor) +{ + struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd; + int64_t value; + int error; + + cmd->cmd = SMU_ADC; + cmd->len = 1; + cmd->data[0] = sensor->reg; + error = smu_do_cmd(sc, 800); + if (error) { + sensor->sensor.flags = SENSOR_FINVALID; + return (error); + } + value = (cmd->data[0] << 8) + cmd->data[1]; + if (sensor->sensor.type == SENSOR_TEMP) { + value *= sc->sc_cpu_diode_scale; + value >>= 3; + value += ((int64_t)sc->sc_cpu_diode_offset) << 9; + value <<= 1; + + /* Convert from 16.16 fixed point degC into muK. */ + value *= 15625; + value /= 1024; + value += 273150000; + } else if (sensor->sensor.type == SENSOR_VOLTS_DC) { + value *= sc->sc_cpu_volt_scale; + value += sc->sc_cpu_volt_offset; + value <<= 4; + + /* Convert from 16.16 fixed point V into muV. */ + value *= 15625; + value /= 1024; + } else if (sensor->sensor.type == SENSOR_AMPS) { + value *= sc->sc_cpu_curr_scale; + value += sc->sc_cpu_curr_offset; + value <<= 4; + + /* Convert from 16.16 fixed point A into muA. */ + value *= 15625; + value /= 1024; + } + sensor->sensor.value = value; + sensor->sensor.flags = 0; + return (0); +} + +void +smu_refresh_sensors(void *arg) +{ + struct smu_softc *sc = arg; + int i; + + while (1) { + lockmgr(&sc->sc_lock, LK_EXCLUSIVE, NULL, curproc); + for (i = 0; i < sc->sc_num_sensors; i++) + smu_sensor_refresh(sc, &sc->sc_sensors[i]); + for (i = 0; i < sc->sc_num_fans; i++) + smu_fan_refresh(sc, &sc->sc_fans[i]); + lockmgr(&sc->sc_lock, LK_RELEASE, NULL, curproc); + + tsleep(smu_refresh_sensors, PWAIT, "timeout", 5 * hz); + }; } |