Index: head/sys/arm/allwinner/axp209.c =================================================================== --- head/sys/arm/allwinner/axp209.c (revision 300420) +++ head/sys/arm/allwinner/axp209.c (revision 300421) @@ -1,226 +1,226 @@ /*- * Copyright (c) 2015 Emmanuel Vadot * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * X-Power AXP209 PMU for Allwinner SoCs */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" /* Power State Register */ #define AXP209_PSR 0x00 #define AXP209_PSR_ACIN 0x80 #define AXP209_PSR_ACIN_SHIFT 7 #define AXP209_PSR_VBUS 0x20 #define AXP209_PSR_VBUS_SHIFT 5 /* Shutdown and battery control */ #define AXP209_SHUTBAT 0x32 #define AXP209_SHUTBAT_SHUTDOWN 0x80 /* Temperature monitor */ #define AXP209_TEMPMON 0x5e #define AXP209_TEMPMON_H(a) ((a) << 4) #define AXP209_TEMPMON_L(a) ((a) & 0xf) #define AXP209_TEMPMON_MIN 1447 /* -144.7C */ -#define AXP209_0C_TO_K 2732 +#define AXP209_0C_TO_K 2731 struct axp209_softc { uint32_t addr; struct intr_config_hook enum_hook; }; enum axp209_sensor { AXP209_TEMP }; static int axp209_read(device_t dev, uint8_t reg, uint8_t *data, uint8_t size) { struct axp209_softc *sc = device_get_softc(dev); struct iic_msg msg[2]; msg[0].slave = sc->addr; msg[0].flags = IIC_M_WR; msg[0].len = 1; msg[0].buf = ® msg[1].slave = sc->addr; msg[1].flags = IIC_M_RD; msg[1].len = size; msg[1].buf = data; return (iicbus_transfer(dev, msg, 2)); } static int axp209_write(device_t dev, uint8_t reg, uint8_t data) { uint8_t buffer[2]; struct axp209_softc *sc = device_get_softc(dev); struct iic_msg msg; buffer[0] = reg; buffer[1] = data; msg.slave = sc->addr; msg.flags = IIC_M_WR; msg.len = 2; msg.buf = buffer; return (iicbus_transfer(dev, &msg, 1)); } static int axp209_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev = arg1; enum axp209_sensor sensor = arg2; uint8_t data[2]; int val, error; if (sensor != AXP209_TEMP) return (ENOENT); error = axp209_read(dev, AXP209_TEMPMON, data, 2); if (error != 0) return (error); /* Temperature is between -144.7C and 264.8C, step +0.1C */ val = (AXP209_TEMPMON_H(data[0]) | AXP209_TEMPMON_L(data[1])) - AXP209_TEMPMON_MIN + AXP209_0C_TO_K; return sysctl_handle_opaque(oidp, &val, sizeof(val), req); } static void axp209_shutdown(void *devp, int howto) { device_t dev; if (!(howto & RB_POWEROFF)) return; dev = (device_t)devp; if (bootverbose) device_printf(dev, "Shutdown AXP209\n"); axp209_write(dev, AXP209_SHUTBAT, AXP209_SHUTBAT_SHUTDOWN); } static int axp209_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "x-powers,axp209")) return (ENXIO); device_set_desc(dev, "X-Power AXP209 Power Management Unit"); return (BUS_PROBE_DEFAULT); } static int axp209_attach(device_t dev) { struct axp209_softc *sc; const char *pwr_name[] = {"Battery", "AC", "USB", "AC and USB"}; uint8_t data; uint8_t pwr_src; sc = device_get_softc(dev); sc->addr = iicbus_get_addr(dev); if (bootverbose) { /* * Read the Power State register. * Shift the AC presence into bit 0. * Shift the Battery presence into bit 1. */ axp209_read(dev, AXP209_PSR, &data, 1); pwr_src = ((data & AXP209_PSR_ACIN) >> AXP209_PSR_ACIN_SHIFT) | ((data & AXP209_PSR_VBUS) >> (AXP209_PSR_VBUS_SHIFT - 1)); device_printf(dev, "AXP209 Powered by %s\n", pwr_name[pwr_src]); } EVENTHANDLER_REGISTER(shutdown_final, axp209_shutdown, dev, SHUTDOWN_PRI_LAST); SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "temp", CTLTYPE_INT | CTLFLAG_RD, dev, AXP209_TEMP, axp209_sysctl, "IK", "Internal temperature"); return (0); } static device_method_t axp209_methods[] = { DEVMETHOD(device_probe, axp209_probe), DEVMETHOD(device_attach, axp209_attach), {0, 0}, }; static driver_t axp209_driver = { "axp209_pmu", axp209_methods, sizeof(struct axp209_softc), }; static devclass_t axp209_devclass; DRIVER_MODULE(axp209, iicbus, axp209_driver, axp209_devclass, 0, 0); MODULE_VERSION(axp209, 1); MODULE_DEPEND(axp209, iicbus, 1, 1, 1); Index: head/sys/arm/broadcom/bcm2835/bcm2835_cpufreq.c =================================================================== --- head/sys/arm/broadcom/bcm2835/bcm2835_cpufreq.c (revision 300420) +++ head/sys/arm/broadcom/bcm2835/bcm2835_cpufreq.c (revision 300421) @@ -1,1619 +1,1619 @@ /*- * Copyright (C) 2013-2015 Daisuke Aoyama * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" #include "mbox_if.h" #ifdef DEBUG #define DPRINTF(fmt, ...) do { \ printf("%s:%u: ", __func__, __LINE__); \ printf(fmt, ##__VA_ARGS__); \ } while (0) #else #define DPRINTF(fmt, ...) #endif #define HZ2MHZ(freq) ((freq) / (1000 * 1000)) #define MHZ2HZ(freq) ((freq) * (1000 * 1000)) #ifdef SOC_BCM2836 #define OFFSET2MVOLT(val) (((val) / 1000)) #define MVOLT2OFFSET(val) (((val) * 1000)) #define DEFAULT_ARM_FREQUENCY 600 #define DEFAULT_LOWEST_FREQ 600 #else #define OFFSET2MVOLT(val) (1200 + ((val) * 25)) #define MVOLT2OFFSET(val) (((val) - 1200) / 25) #define DEFAULT_ARM_FREQUENCY 700 #define DEFAULT_LOWEST_FREQ 300 #endif #define DEFAULT_CORE_FREQUENCY 250 #define DEFAULT_SDRAM_FREQUENCY 400 #define TRANSITION_LATENCY 1000 #define MIN_OVER_VOLTAGE -16 #define MAX_OVER_VOLTAGE 6 #define MSG_ERROR -999999999 #define MHZSTEP 100 #define HZSTEP (MHZ2HZ(MHZSTEP)) -#define TZ_ZEROC 2732 +#define TZ_ZEROC 2731 #define VC_LOCK(sc) do { \ sema_wait(&vc_sema); \ } while (0) #define VC_UNLOCK(sc) do { \ sema_post(&vc_sema); \ } while (0) /* ARM->VC mailbox property semaphore */ static struct sema vc_sema; static struct sysctl_ctx_list bcm2835_sysctl_ctx; struct bcm2835_cpufreq_softc { device_t dev; int arm_max_freq; int arm_min_freq; int core_max_freq; int core_min_freq; int sdram_max_freq; int sdram_min_freq; int max_voltage_core; int min_voltage_core; /* the values written in mbox */ int voltage_core; int voltage_sdram; int voltage_sdram_c; int voltage_sdram_i; int voltage_sdram_p; int turbo_mode; /* initial hook for waiting mbox intr */ struct intr_config_hook init_hook; }; static int cpufreq_verbose = 0; TUNABLE_INT("hw.bcm2835.cpufreq.verbose", &cpufreq_verbose); static int cpufreq_lowest_freq = DEFAULT_LOWEST_FREQ; TUNABLE_INT("hw.bcm2835.cpufreq.lowest_freq", &cpufreq_lowest_freq); #ifdef PROP_DEBUG static void bcm2835_dump(const void *data, int len) { const uint8_t *p = (const uint8_t*)data; int i; printf("dump @ %p:\n", data); for (i = 0; i < len; i++) { printf("%2.2x ", p[i]); if ((i % 4) == 3) printf(" "); if ((i % 16) == 15) printf("\n"); } printf("\n"); } #endif static int bcm2835_cpufreq_get_clock_rate(struct bcm2835_cpufreq_softc *sc, uint32_t clock_id) { struct msg_get_clock_rate msg; int rate; int err; /* * Get clock rate * Tag: 0x00030002 * Request: * Length: 4 * Value: * u32: clock id * Response: * Length: 8 * Value: * u32: clock id * u32: rate (in Hz) */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.hdr.buf_size = sizeof(msg); msg.hdr.code = BCM2835_MBOX_CODE_REQ; msg.tag_hdr.tag = BCM2835_MBOX_TAG_GET_CLOCK_RATE; msg.tag_hdr.val_buf_size = sizeof(msg.body); msg.tag_hdr.val_len = sizeof(msg.body.req); msg.body.req.clock_id = clock_id; msg.end_tag = 0; /* call mailbox property */ err = bcm2835_mbox_property(&msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't get clock rate (id=%u)\n", clock_id); return (MSG_ERROR); } /* result (Hz) */ rate = (int)msg.body.resp.rate_hz; DPRINTF("clock = %d(Hz)\n", rate); return (rate); } static int bcm2835_cpufreq_get_max_clock_rate(struct bcm2835_cpufreq_softc *sc, uint32_t clock_id) { struct msg_get_max_clock_rate msg; int rate; int err; /* * Get max clock rate * Tag: 0x00030004 * Request: * Length: 4 * Value: * u32: clock id * Response: * Length: 8 * Value: * u32: clock id * u32: rate (in Hz) */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.hdr.buf_size = sizeof(msg); msg.hdr.code = BCM2835_MBOX_CODE_REQ; msg.tag_hdr.tag = BCM2835_MBOX_TAG_GET_MAX_CLOCK_RATE; msg.tag_hdr.val_buf_size = sizeof(msg.body); msg.tag_hdr.val_len = sizeof(msg.body.req); msg.body.req.clock_id = clock_id; msg.end_tag = 0; /* call mailbox property */ err = bcm2835_mbox_property(&msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't get max clock rate (id=%u)\n", clock_id); return (MSG_ERROR); } /* result (Hz) */ rate = (int)msg.body.resp.rate_hz; DPRINTF("clock = %d(Hz)\n", rate); return (rate); } static int bcm2835_cpufreq_get_min_clock_rate(struct bcm2835_cpufreq_softc *sc, uint32_t clock_id) { struct msg_get_min_clock_rate msg; int rate; int err; /* * Get min clock rate * Tag: 0x00030007 * Request: * Length: 4 * Value: * u32: clock id * Response: * Length: 8 * Value: * u32: clock id * u32: rate (in Hz) */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.hdr.buf_size = sizeof(msg); msg.hdr.code = BCM2835_MBOX_CODE_REQ; msg.tag_hdr.tag = BCM2835_MBOX_TAG_GET_MIN_CLOCK_RATE; msg.tag_hdr.val_buf_size = sizeof(msg.body); msg.tag_hdr.val_len = sizeof(msg.body.req); msg.body.req.clock_id = clock_id; msg.end_tag = 0; /* call mailbox property */ err = bcm2835_mbox_property(&msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't get min clock rate (id=%u)\n", clock_id); return (MSG_ERROR); } /* result (Hz) */ rate = (int)msg.body.resp.rate_hz; DPRINTF("clock = %d(Hz)\n", rate); return (rate); } static int bcm2835_cpufreq_set_clock_rate(struct bcm2835_cpufreq_softc *sc, uint32_t clock_id, uint32_t rate_hz) { struct msg_set_clock_rate msg; int rate; int err; /* * Set clock rate * Tag: 0x00038002 * Request: * Length: 8 * Value: * u32: clock id * u32: rate (in Hz) * Response: * Length: 8 * Value: * u32: clock id * u32: rate (in Hz) */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.hdr.buf_size = sizeof(msg); msg.hdr.code = BCM2835_MBOX_CODE_REQ; msg.tag_hdr.tag = BCM2835_MBOX_TAG_SET_CLOCK_RATE; msg.tag_hdr.val_buf_size = sizeof(msg.body); msg.tag_hdr.val_len = sizeof(msg.body.req); msg.body.req.clock_id = clock_id; msg.body.req.rate_hz = rate_hz; msg.end_tag = 0; /* call mailbox property */ err = bcm2835_mbox_property(&msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't set clock rate (id=%u)\n", clock_id); return (MSG_ERROR); } /* workaround for core clock */ if (clock_id == BCM2835_MBOX_CLOCK_ID_CORE) { /* for safety (may change voltage without changing clock) */ DELAY(TRANSITION_LATENCY); /* * XXX: the core clock is unable to change at once, * to change certainly, write it twice now. */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.hdr.buf_size = sizeof(msg); msg.hdr.code = BCM2835_MBOX_CODE_REQ; msg.tag_hdr.tag = BCM2835_MBOX_TAG_SET_CLOCK_RATE; msg.tag_hdr.val_buf_size = sizeof(msg.body); msg.tag_hdr.val_len = sizeof(msg.body.req); msg.body.req.clock_id = clock_id; msg.body.req.rate_hz = rate_hz; msg.end_tag = 0; /* call mailbox property */ err = bcm2835_mbox_property(&msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't set clock rate (id=%u)\n", clock_id); return (MSG_ERROR); } } /* result (Hz) */ rate = (int)msg.body.resp.rate_hz; DPRINTF("clock = %d(Hz)\n", rate); return (rate); } static int bcm2835_cpufreq_get_turbo(struct bcm2835_cpufreq_softc *sc) { struct msg_get_turbo msg; int level; int err; /* * Get turbo * Tag: 0x00030009 * Request: * Length: 4 * Value: * u32: id * Response: * Length: 8 * Value: * u32: id * u32: level */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.hdr.buf_size = sizeof(msg); msg.hdr.code = BCM2835_MBOX_CODE_REQ; msg.tag_hdr.tag = BCM2835_MBOX_TAG_GET_TURBO; msg.tag_hdr.val_buf_size = sizeof(msg.body); msg.tag_hdr.val_len = sizeof(msg.body.req); msg.body.req.id = 0; msg.end_tag = 0; /* call mailbox property */ err = bcm2835_mbox_property(&msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't get turbo\n"); return (MSG_ERROR); } /* result 0=non-turbo, 1=turbo */ level = (int)msg.body.resp.level; DPRINTF("level = %d\n", level); return (level); } static int bcm2835_cpufreq_set_turbo(struct bcm2835_cpufreq_softc *sc, uint32_t level) { struct msg_set_turbo msg; int value; int err; /* * Set turbo * Tag: 0x00038009 * Request: * Length: 8 * Value: * u32: id * u32: level * Response: * Length: 8 * Value: * u32: id * u32: level */ /* replace unknown value to OFF */ if (level != BCM2835_MBOX_TURBO_ON && level != BCM2835_MBOX_TURBO_OFF) level = BCM2835_MBOX_TURBO_OFF; /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.hdr.buf_size = sizeof(msg); msg.hdr.code = BCM2835_MBOX_CODE_REQ; msg.tag_hdr.tag = BCM2835_MBOX_TAG_SET_TURBO; msg.tag_hdr.val_buf_size = sizeof(msg.body); msg.tag_hdr.val_len = sizeof(msg.body.req); msg.body.req.id = 0; msg.body.req.level = level; msg.end_tag = 0; /* call mailbox property */ err = bcm2835_mbox_property(&msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't set turbo\n"); return (MSG_ERROR); } /* result 0=non-turbo, 1=turbo */ value = (int)msg.body.resp.level; DPRINTF("level = %d\n", value); return (value); } static int bcm2835_cpufreq_get_voltage(struct bcm2835_cpufreq_softc *sc, uint32_t voltage_id) { struct msg_get_voltage msg; int value; int err; /* * Get voltage * Tag: 0x00030003 * Request: * Length: 4 * Value: * u32: voltage id * Response: * Length: 8 * Value: * u32: voltage id * u32: value (offset from 1.2V in units of 0.025V) */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.hdr.buf_size = sizeof(msg); msg.hdr.code = BCM2835_MBOX_CODE_REQ; msg.tag_hdr.tag = BCM2835_MBOX_TAG_GET_VOLTAGE; msg.tag_hdr.val_buf_size = sizeof(msg.body); msg.tag_hdr.val_len = sizeof(msg.body.req); msg.body.req.voltage_id = voltage_id; msg.end_tag = 0; /* call mailbox property */ err = bcm2835_mbox_property(&msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't get voltage\n"); return (MSG_ERROR); } /* result (offset from 1.2V) */ value = (int)msg.body.resp.value; DPRINTF("value = %d\n", value); return (value); } static int bcm2835_cpufreq_get_max_voltage(struct bcm2835_cpufreq_softc *sc, uint32_t voltage_id) { struct msg_get_max_voltage msg; int value; int err; /* * Get voltage * Tag: 0x00030005 * Request: * Length: 4 * Value: * u32: voltage id * Response: * Length: 8 * Value: * u32: voltage id * u32: value (offset from 1.2V in units of 0.025V) */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.hdr.buf_size = sizeof(msg); msg.hdr.code = BCM2835_MBOX_CODE_REQ; msg.tag_hdr.tag = BCM2835_MBOX_TAG_GET_MAX_VOLTAGE; msg.tag_hdr.val_buf_size = sizeof(msg.body); msg.tag_hdr.val_len = sizeof(msg.body.req); msg.body.req.voltage_id = voltage_id; msg.end_tag = 0; /* call mailbox property */ err = bcm2835_mbox_property(&msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't get max voltage\n"); return (MSG_ERROR); } /* result (offset from 1.2V) */ value = (int)msg.body.resp.value; DPRINTF("value = %d\n", value); return (value); } static int bcm2835_cpufreq_get_min_voltage(struct bcm2835_cpufreq_softc *sc, uint32_t voltage_id) { struct msg_get_min_voltage msg; int value; int err; /* * Get voltage * Tag: 0x00030008 * Request: * Length: 4 * Value: * u32: voltage id * Response: * Length: 8 * Value: * u32: voltage id * u32: value (offset from 1.2V in units of 0.025V) */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.hdr.buf_size = sizeof(msg); msg.hdr.code = BCM2835_MBOX_CODE_REQ; msg.tag_hdr.tag = BCM2835_MBOX_TAG_GET_MIN_VOLTAGE; msg.tag_hdr.val_buf_size = sizeof(msg.body); msg.tag_hdr.val_len = sizeof(msg.body.req); msg.body.req.voltage_id = voltage_id; msg.end_tag = 0; /* call mailbox property */ err = bcm2835_mbox_property(&msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't get min voltage\n"); return (MSG_ERROR); } /* result (offset from 1.2V) */ value = (int)msg.body.resp.value; DPRINTF("value = %d\n", value); return (value); } static int bcm2835_cpufreq_set_voltage(struct bcm2835_cpufreq_softc *sc, uint32_t voltage_id, int32_t value) { struct msg_set_voltage msg; int err; /* * Set voltage * Tag: 0x00038003 * Request: * Length: 4 * Value: * u32: voltage id * u32: value (offset from 1.2V in units of 0.025V) * Response: * Length: 8 * Value: * u32: voltage id * u32: value (offset from 1.2V in units of 0.025V) */ /* * over_voltage: * 0 (1.2 V). Values above 6 are only allowed when force_turbo or * current_limit_override are specified (which set the warranty bit). */ if (value > MAX_OVER_VOLTAGE || value < MIN_OVER_VOLTAGE) { /* currently not supported */ device_printf(sc->dev, "not supported voltage: %d\n", value); return (MSG_ERROR); } /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.hdr.buf_size = sizeof(msg); msg.hdr.code = BCM2835_MBOX_CODE_REQ; msg.tag_hdr.tag = BCM2835_MBOX_TAG_SET_VOLTAGE; msg.tag_hdr.val_buf_size = sizeof(msg.body); msg.tag_hdr.val_len = sizeof(msg.body.req); msg.body.req.voltage_id = voltage_id; msg.body.req.value = (uint32_t)value; msg.end_tag = 0; /* call mailbox property */ err = bcm2835_mbox_property(&msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't set voltage\n"); return (MSG_ERROR); } /* result (offset from 1.2V) */ value = (int)msg.body.resp.value; DPRINTF("value = %d\n", value); return (value); } static int bcm2835_cpufreq_get_temperature(struct bcm2835_cpufreq_softc *sc) { struct msg_get_temperature msg; int value; int err; /* * Get temperature * Tag: 0x00030006 * Request: * Length: 4 * Value: * u32: temperature id * Response: * Length: 8 * Value: * u32: temperature id * u32: value */ /* setup single tag buffer */ memset(&msg, 0, sizeof(msg)); msg.hdr.buf_size = sizeof(msg); msg.hdr.code = BCM2835_MBOX_CODE_REQ; msg.tag_hdr.tag = BCM2835_MBOX_TAG_GET_TEMPERATURE; msg.tag_hdr.val_buf_size = sizeof(msg.body); msg.tag_hdr.val_len = sizeof(msg.body.req); msg.body.req.temperature_id = 0; msg.end_tag = 0; /* call mailbox property */ err = bcm2835_mbox_property(&msg, sizeof(msg)); if (err) { device_printf(sc->dev, "can't get temperature\n"); return (MSG_ERROR); } /* result (temperature of degree C) */ value = (int)msg.body.resp.value; DPRINTF("value = %d\n", value); return (value); } static int sysctl_bcm2835_cpufreq_arm_freq(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_ARM); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ VC_LOCK(sc); err = bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_ARM, val); VC_UNLOCK(sc); if (err == MSG_ERROR) { device_printf(sc->dev, "set clock arm_freq error\n"); return (EIO); } DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_core_freq(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ VC_LOCK(sc); err = bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE, val); if (err == MSG_ERROR) { VC_UNLOCK(sc); device_printf(sc->dev, "set clock core_freq error\n"); return (EIO); } VC_UNLOCK(sc); DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_sdram_freq(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_SDRAM); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ VC_LOCK(sc); err = bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_SDRAM, val); VC_UNLOCK(sc); if (err == MSG_ERROR) { device_printf(sc->dev, "set clock sdram_freq error\n"); return (EIO); } DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_turbo(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_turbo(sc); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ if (val > 0) sc->turbo_mode = BCM2835_MBOX_TURBO_ON; else sc->turbo_mode = BCM2835_MBOX_TURBO_OFF; VC_LOCK(sc); err = bcm2835_cpufreq_set_turbo(sc, sc->turbo_mode); VC_UNLOCK(sc); if (err == MSG_ERROR) { device_printf(sc->dev, "set turbo error\n"); return (EIO); } DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_voltage_core(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_CORE); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ if (val > MAX_OVER_VOLTAGE || val < MIN_OVER_VOLTAGE) return (EINVAL); sc->voltage_core = val; VC_LOCK(sc); err = bcm2835_cpufreq_set_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_CORE, sc->voltage_core); VC_UNLOCK(sc); if (err == MSG_ERROR) { device_printf(sc->dev, "set voltage core error\n"); return (EIO); } DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_voltage_sdram_c(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_C); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ if (val > MAX_OVER_VOLTAGE || val < MIN_OVER_VOLTAGE) return (EINVAL); sc->voltage_sdram_c = val; VC_LOCK(sc); err = bcm2835_cpufreq_set_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_C, sc->voltage_sdram_c); VC_UNLOCK(sc); if (err == MSG_ERROR) { device_printf(sc->dev, "set voltage sdram_c error\n"); return (EIO); } DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_voltage_sdram_i(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_I); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ if (val > MAX_OVER_VOLTAGE || val < MIN_OVER_VOLTAGE) return (EINVAL); sc->voltage_sdram_i = val; VC_LOCK(sc); err = bcm2835_cpufreq_set_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_I, sc->voltage_sdram_i); VC_UNLOCK(sc); if (err == MSG_ERROR) { device_printf(sc->dev, "set voltage sdram_i error\n"); return (EIO); } DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_voltage_sdram_p(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_P); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ if (val > MAX_OVER_VOLTAGE || val < MIN_OVER_VOLTAGE) return (EINVAL); sc->voltage_sdram_p = val; VC_LOCK(sc); err = bcm2835_cpufreq_set_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_P, sc->voltage_sdram_p); VC_UNLOCK(sc); if (err == MSG_ERROR) { device_printf(sc->dev, "set voltage sdram_p error\n"); return (EIO); } DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_voltage_sdram(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* multiple write only */ if (!req->newptr) return (EINVAL); val = 0; err = sysctl_handle_int(oidp, &val, 0, req); if (err) return (err); /* write request */ if (val > MAX_OVER_VOLTAGE || val < MIN_OVER_VOLTAGE) return (EINVAL); sc->voltage_sdram = val; VC_LOCK(sc); err = bcm2835_cpufreq_set_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_C, val); if (err == MSG_ERROR) { VC_UNLOCK(sc); device_printf(sc->dev, "set voltage sdram_c error\n"); return (EIO); } err = bcm2835_cpufreq_set_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_I, val); if (err == MSG_ERROR) { VC_UNLOCK(sc); device_printf(sc->dev, "set voltage sdram_i error\n"); return (EIO); } err = bcm2835_cpufreq_set_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_P, val); if (err == MSG_ERROR) { VC_UNLOCK(sc); device_printf(sc->dev, "set voltage sdram_p error\n"); return (EIO); } VC_UNLOCK(sc); DELAY(TRANSITION_LATENCY); return (0); } static int sysctl_bcm2835_cpufreq_temperature(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_temperature(sc); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ return (EINVAL); } static int sysctl_bcm2835_devcpu_temperature(SYSCTL_HANDLER_ARGS) { struct bcm2835_cpufreq_softc *sc = arg1; int val; int err; /* get realtime value */ VC_LOCK(sc); val = bcm2835_cpufreq_get_temperature(sc); VC_UNLOCK(sc); if (val == MSG_ERROR) return (EIO); /* 1/1000 celsius (raw) to 1/10 kelvin */ val = val / 100 + TZ_ZEROC; err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); /* write request */ return (EINVAL); } static void bcm2835_cpufreq_init(void *arg) { struct bcm2835_cpufreq_softc *sc = arg; struct sysctl_ctx_list *ctx; device_t cpu; int arm_freq, core_freq, sdram_freq; int arm_max_freq, arm_min_freq, core_max_freq, core_min_freq; int sdram_max_freq, sdram_min_freq; int voltage_core, voltage_sdram_c, voltage_sdram_i, voltage_sdram_p; int max_voltage_core, min_voltage_core; int max_voltage_sdram_c, min_voltage_sdram_c; int max_voltage_sdram_i, min_voltage_sdram_i; int max_voltage_sdram_p, min_voltage_sdram_p; int turbo, temperature; VC_LOCK(sc); /* current clock */ arm_freq = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_ARM); core_freq = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE); sdram_freq = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_SDRAM); /* max/min clock */ arm_max_freq = bcm2835_cpufreq_get_max_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_ARM); arm_min_freq = bcm2835_cpufreq_get_min_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_ARM); core_max_freq = bcm2835_cpufreq_get_max_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE); core_min_freq = bcm2835_cpufreq_get_min_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE); sdram_max_freq = bcm2835_cpufreq_get_max_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_SDRAM); sdram_min_freq = bcm2835_cpufreq_get_min_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_SDRAM); /* turbo mode */ turbo = bcm2835_cpufreq_get_turbo(sc); if (turbo > 0) sc->turbo_mode = BCM2835_MBOX_TURBO_ON; else sc->turbo_mode = BCM2835_MBOX_TURBO_OFF; /* voltage */ voltage_core = bcm2835_cpufreq_get_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_CORE); voltage_sdram_c = bcm2835_cpufreq_get_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_C); voltage_sdram_i = bcm2835_cpufreq_get_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_I); voltage_sdram_p = bcm2835_cpufreq_get_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_P); /* current values (offset from 1.2V) */ sc->voltage_core = voltage_core; sc->voltage_sdram = voltage_sdram_c; sc->voltage_sdram_c = voltage_sdram_c; sc->voltage_sdram_i = voltage_sdram_i; sc->voltage_sdram_p = voltage_sdram_p; /* max/min voltage */ max_voltage_core = bcm2835_cpufreq_get_max_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_CORE); min_voltage_core = bcm2835_cpufreq_get_min_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_CORE); max_voltage_sdram_c = bcm2835_cpufreq_get_max_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_C); max_voltage_sdram_i = bcm2835_cpufreq_get_max_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_I); max_voltage_sdram_p = bcm2835_cpufreq_get_max_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_P); min_voltage_sdram_c = bcm2835_cpufreq_get_min_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_C); min_voltage_sdram_i = bcm2835_cpufreq_get_min_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_I); min_voltage_sdram_p = bcm2835_cpufreq_get_min_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_P); /* temperature */ temperature = bcm2835_cpufreq_get_temperature(sc); /* show result */ if (cpufreq_verbose || bootverbose) { device_printf(sc->dev, "Boot settings:\n"); device_printf(sc->dev, "current ARM %dMHz, Core %dMHz, SDRAM %dMHz, Turbo %s\n", HZ2MHZ(arm_freq), HZ2MHZ(core_freq), HZ2MHZ(sdram_freq), (sc->turbo_mode == BCM2835_MBOX_TURBO_ON) ? "ON" : "OFF"); device_printf(sc->dev, "max/min ARM %d/%dMHz, Core %d/%dMHz, SDRAM %d/%dMHz\n", HZ2MHZ(arm_max_freq), HZ2MHZ(arm_min_freq), HZ2MHZ(core_max_freq), HZ2MHZ(core_min_freq), HZ2MHZ(sdram_max_freq), HZ2MHZ(sdram_min_freq)); device_printf(sc->dev, "current Core %dmV, SDRAM_C %dmV, SDRAM_I %dmV, " "SDRAM_P %dmV\n", OFFSET2MVOLT(voltage_core), OFFSET2MVOLT(voltage_sdram_c), OFFSET2MVOLT(voltage_sdram_i), OFFSET2MVOLT(voltage_sdram_p)); device_printf(sc->dev, "max/min Core %d/%dmV, SDRAM_C %d/%dmV, SDRAM_I %d/%dmV, " "SDRAM_P %d/%dmV\n", OFFSET2MVOLT(max_voltage_core), OFFSET2MVOLT(min_voltage_core), OFFSET2MVOLT(max_voltage_sdram_c), OFFSET2MVOLT(min_voltage_sdram_c), OFFSET2MVOLT(max_voltage_sdram_i), OFFSET2MVOLT(min_voltage_sdram_i), OFFSET2MVOLT(max_voltage_sdram_p), OFFSET2MVOLT(min_voltage_sdram_p)); device_printf(sc->dev, "Temperature %d.%dC\n", (temperature / 1000), (temperature % 1000) / 100); } else { /* !cpufreq_verbose && !bootverbose */ device_printf(sc->dev, "ARM %dMHz, Core %dMHz, SDRAM %dMHz, Turbo %s\n", HZ2MHZ(arm_freq), HZ2MHZ(core_freq), HZ2MHZ(sdram_freq), (sc->turbo_mode == BCM2835_MBOX_TURBO_ON) ? "ON" : "OFF"); } /* keep in softc (MHz/mV) */ sc->arm_max_freq = HZ2MHZ(arm_max_freq); sc->arm_min_freq = HZ2MHZ(arm_min_freq); sc->core_max_freq = HZ2MHZ(core_max_freq); sc->core_min_freq = HZ2MHZ(core_min_freq); sc->sdram_max_freq = HZ2MHZ(sdram_max_freq); sc->sdram_min_freq = HZ2MHZ(sdram_min_freq); sc->max_voltage_core = OFFSET2MVOLT(max_voltage_core); sc->min_voltage_core = OFFSET2MVOLT(min_voltage_core); /* if turbo is on, set to max values */ if (sc->turbo_mode == BCM2835_MBOX_TURBO_ON) { bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_ARM, arm_max_freq); DELAY(TRANSITION_LATENCY); bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE, core_max_freq); DELAY(TRANSITION_LATENCY); bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_SDRAM, sdram_max_freq); DELAY(TRANSITION_LATENCY); } else { bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_ARM, arm_min_freq); DELAY(TRANSITION_LATENCY); bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE, core_min_freq); DELAY(TRANSITION_LATENCY); bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_SDRAM, sdram_min_freq); DELAY(TRANSITION_LATENCY); } VC_UNLOCK(sc); /* add human readable temperature to dev.cpu node */ cpu = device_get_parent(sc->dev); if (cpu != NULL) { ctx = device_get_sysctl_ctx(cpu); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(cpu)), OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD, sc, 0, sysctl_bcm2835_devcpu_temperature, "IK", "Current SoC temperature"); } /* release this hook (continue boot) */ config_intrhook_disestablish(&sc->init_hook); } static void bcm2835_cpufreq_identify(driver_t *driver, device_t parent) { DPRINTF("driver=%p, parent=%p\n", driver, parent); if (device_find_child(parent, "bcm2835_cpufreq", -1) != NULL) return; if (BUS_ADD_CHILD(parent, 0, "bcm2835_cpufreq", -1) == NULL) device_printf(parent, "add child failed\n"); } static int bcm2835_cpufreq_probe(device_t dev) { if (device_get_unit(dev) != 0) return (ENXIO); device_set_desc(dev, "CPU Frequency Control"); return (0); } static int bcm2835_cpufreq_attach(device_t dev) { struct bcm2835_cpufreq_softc *sc; struct sysctl_oid *oid; /* set self dev */ sc = device_get_softc(dev); sc->dev = dev; /* initial values */ sc->arm_max_freq = -1; sc->arm_min_freq = -1; sc->core_max_freq = -1; sc->core_min_freq = -1; sc->sdram_max_freq = -1; sc->sdram_min_freq = -1; sc->max_voltage_core = 0; sc->min_voltage_core = 0; /* setup sysctl at first device */ if (device_get_unit(dev) == 0) { sysctl_ctx_init(&bcm2835_sysctl_ctx); /* create node for hw.cpufreq */ oid = SYSCTL_ADD_NODE(&bcm2835_sysctl_ctx, SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, "cpufreq", CTLFLAG_RD, NULL, ""); /* Frequency (Hz) */ SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "arm_freq", CTLTYPE_INT | CTLFLAG_RW, sc, 0, sysctl_bcm2835_cpufreq_arm_freq, "IU", "ARM frequency (Hz)"); SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "core_freq", CTLTYPE_INT | CTLFLAG_RW, sc, 0, sysctl_bcm2835_cpufreq_core_freq, "IU", "Core frequency (Hz)"); SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "sdram_freq", CTLTYPE_INT | CTLFLAG_RW, sc, 0, sysctl_bcm2835_cpufreq_sdram_freq, "IU", "SDRAM frequency (Hz)"); /* Turbo state */ SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "turbo", CTLTYPE_INT | CTLFLAG_RW, sc, 0, sysctl_bcm2835_cpufreq_turbo, "IU", "Disables dynamic clocking"); /* Voltage (offset from 1.2V in units of 0.025V) */ SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "voltage_core", CTLTYPE_INT | CTLFLAG_RW, sc, 0, sysctl_bcm2835_cpufreq_voltage_core, "I", "ARM/GPU core voltage" "(offset from 1.2V in units of 0.025V)"); SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "voltage_sdram", CTLTYPE_INT | CTLFLAG_WR, sc, 0, sysctl_bcm2835_cpufreq_voltage_sdram, "I", "SDRAM voltage (offset from 1.2V in units of 0.025V)"); /* Voltage individual SDRAM */ SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "voltage_sdram_c", CTLTYPE_INT | CTLFLAG_RW, sc, 0, sysctl_bcm2835_cpufreq_voltage_sdram_c, "I", "SDRAM controller voltage" "(offset from 1.2V in units of 0.025V)"); SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "voltage_sdram_i", CTLTYPE_INT | CTLFLAG_RW, sc, 0, sysctl_bcm2835_cpufreq_voltage_sdram_i, "I", "SDRAM I/O voltage (offset from 1.2V in units of 0.025V)"); SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "voltage_sdram_p", CTLTYPE_INT | CTLFLAG_RW, sc, 0, sysctl_bcm2835_cpufreq_voltage_sdram_p, "I", "SDRAM phy voltage (offset from 1.2V in units of 0.025V)"); /* Temperature */ SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD, sc, 0, sysctl_bcm2835_cpufreq_temperature, "I", "SoC temperature (thousandths of a degree C)"); } /* ARM->VC lock */ sema_init(&vc_sema, 1, "vcsema"); /* register callback for using mbox when interrupts are enabled */ sc->init_hook.ich_func = bcm2835_cpufreq_init; sc->init_hook.ich_arg = sc; if (config_intrhook_establish(&sc->init_hook) != 0) { device_printf(dev, "config_intrhook_establish failed\n"); return (ENOMEM); } /* this device is controlled by cpufreq(4) */ cpufreq_register(dev); return (0); } static int bcm2835_cpufreq_detach(device_t dev) { struct bcm2835_cpufreq_softc *sc; sc = device_get_softc(dev); sema_destroy(&vc_sema); return (cpufreq_unregister(dev)); } static int bcm2835_cpufreq_set(device_t dev, const struct cf_setting *cf) { struct bcm2835_cpufreq_softc *sc; uint32_t rate_hz, rem; int cur_freq, resp_freq, arm_freq, min_freq, core_freq; if (cf == NULL || cf->freq < 0) return (EINVAL); sc = device_get_softc(dev); /* setting clock (Hz) */ rate_hz = (uint32_t)MHZ2HZ(cf->freq); rem = rate_hz % HZSTEP; rate_hz -= rem; if (rate_hz == 0) return (EINVAL); /* adjust min freq */ min_freq = sc->arm_min_freq; if (sc->turbo_mode != BCM2835_MBOX_TURBO_ON) if (min_freq > cpufreq_lowest_freq) min_freq = cpufreq_lowest_freq; if (rate_hz < MHZ2HZ(min_freq) || rate_hz > MHZ2HZ(sc->arm_max_freq)) return (EINVAL); /* set new value and verify it */ VC_LOCK(sc); cur_freq = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_ARM); resp_freq = bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_ARM, rate_hz); DELAY(TRANSITION_LATENCY); arm_freq = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_ARM); /* * if non-turbo and lower than or equal min_freq, * clock down core and sdram to default first. */ if (sc->turbo_mode != BCM2835_MBOX_TURBO_ON) { core_freq = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE); if (rate_hz > MHZ2HZ(sc->arm_min_freq)) { bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE, MHZ2HZ(sc->core_max_freq)); DELAY(TRANSITION_LATENCY); bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_SDRAM, MHZ2HZ(sc->sdram_max_freq)); DELAY(TRANSITION_LATENCY); } else { if (sc->core_min_freq < DEFAULT_CORE_FREQUENCY && core_freq > DEFAULT_CORE_FREQUENCY) { /* first, down to 250, then down to min */ DELAY(TRANSITION_LATENCY); bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE, MHZ2HZ(DEFAULT_CORE_FREQUENCY)); DELAY(TRANSITION_LATENCY); /* reset core voltage */ bcm2835_cpufreq_set_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_CORE, 0); DELAY(TRANSITION_LATENCY); } bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE, MHZ2HZ(sc->core_min_freq)); DELAY(TRANSITION_LATENCY); bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_SDRAM, MHZ2HZ(sc->sdram_min_freq)); DELAY(TRANSITION_LATENCY); } } VC_UNLOCK(sc); if (resp_freq < 0 || arm_freq < 0 || resp_freq != arm_freq) { device_printf(dev, "wrong freq\n"); return (EIO); } DPRINTF("cpufreq: %d -> %d\n", cur_freq, arm_freq); return (0); } static int bcm2835_cpufreq_get(device_t dev, struct cf_setting *cf) { struct bcm2835_cpufreq_softc *sc; int arm_freq; if (cf == NULL) return (EINVAL); sc = device_get_softc(dev); memset(cf, CPUFREQ_VAL_UNKNOWN, sizeof(*cf)); cf->dev = NULL; /* get cuurent value */ VC_LOCK(sc); arm_freq = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_ARM); VC_UNLOCK(sc); if (arm_freq < 0) { device_printf(dev, "can't get clock\n"); return (EINVAL); } /* CPU clock in MHz or 100ths of a percent. */ cf->freq = HZ2MHZ(arm_freq); /* Voltage in mV. */ cf->volts = CPUFREQ_VAL_UNKNOWN; /* Power consumed in mW. */ cf->power = CPUFREQ_VAL_UNKNOWN; /* Transition latency in us. */ cf->lat = TRANSITION_LATENCY; /* Driver providing this setting. */ cf->dev = dev; return (0); } static int bcm2835_cpufreq_make_freq_list(device_t dev, struct cf_setting *sets, int *count) { struct bcm2835_cpufreq_softc *sc; int freq, min_freq, volts, rem; int idx; sc = device_get_softc(dev); freq = sc->arm_max_freq; min_freq = sc->arm_min_freq; /* adjust head freq to STEP */ rem = freq % MHZSTEP; freq -= rem; if (freq < min_freq) freq = min_freq; /* if non-turbo, add extra low freq */ if (sc->turbo_mode != BCM2835_MBOX_TURBO_ON) if (min_freq > cpufreq_lowest_freq) min_freq = cpufreq_lowest_freq; #ifdef SOC_BCM2836 /* XXX RPi2 have only 900/600MHz */ idx = 0; volts = sc->min_voltage_core; sets[idx].freq = freq; sets[idx].volts = volts; sets[idx].lat = TRANSITION_LATENCY; sets[idx].dev = dev; idx++; if (freq != min_freq) { sets[idx].freq = min_freq; sets[idx].volts = volts; sets[idx].lat = TRANSITION_LATENCY; sets[idx].dev = dev; idx++; } #else /* from freq to min_freq */ for (idx = 0; idx < *count && freq >= min_freq; idx++) { if (freq > sc->arm_min_freq) volts = sc->max_voltage_core; else volts = sc->min_voltage_core; sets[idx].freq = freq; sets[idx].volts = volts; sets[idx].lat = TRANSITION_LATENCY; sets[idx].dev = dev; freq -= MHZSTEP; } #endif *count = idx; return (0); } static int bcm2835_cpufreq_settings(device_t dev, struct cf_setting *sets, int *count) { struct bcm2835_cpufreq_softc *sc; if (sets == NULL || count == NULL) return (EINVAL); sc = device_get_softc(dev); if (sc->arm_min_freq < 0 || sc->arm_max_freq < 0) { printf("device is not configured\n"); return (EINVAL); } /* fill data with unknown value */ memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * (*count)); /* create new array up to count */ bcm2835_cpufreq_make_freq_list(dev, sets, count); return (0); } static int bcm2835_cpufreq_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE; return (0); } static device_method_t bcm2835_cpufreq_methods[] = { /* Device interface */ DEVMETHOD(device_identify, bcm2835_cpufreq_identify), DEVMETHOD(device_probe, bcm2835_cpufreq_probe), DEVMETHOD(device_attach, bcm2835_cpufreq_attach), DEVMETHOD(device_detach, bcm2835_cpufreq_detach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, bcm2835_cpufreq_set), DEVMETHOD(cpufreq_drv_get, bcm2835_cpufreq_get), DEVMETHOD(cpufreq_drv_settings, bcm2835_cpufreq_settings), DEVMETHOD(cpufreq_drv_type, bcm2835_cpufreq_type), DEVMETHOD_END }; static devclass_t bcm2835_cpufreq_devclass; static driver_t bcm2835_cpufreq_driver = { "bcm2835_cpufreq", bcm2835_cpufreq_methods, sizeof(struct bcm2835_cpufreq_softc), }; DRIVER_MODULE(bcm2835_cpufreq, cpu, bcm2835_cpufreq_driver, bcm2835_cpufreq_devclass, 0, 0); Index: head/sys/arm/freescale/imx/imx6_anatop.c =================================================================== --- head/sys/arm/freescale/imx/imx6_anatop.c (revision 300420) +++ head/sys/arm/freescale/imx/imx6_anatop.c (revision 300421) @@ -1,805 +1,805 @@ /*- * Copyright (c) 2013 Ian Lepore * Copyright (c) 2014 Steven Lawrance * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * Analog PLL and power regulator driver for Freescale i.MX6 family of SoCs. * Also, temperature montoring and cpu frequency control. It was Freescale who * kitchen-sinked this device, not us. :) * * We don't really do anything with analog PLLs, but the registers for * controlling them belong to the same block as the power regulator registers. * Since the newbus hierarchy makes it hard for anyone other than us to get at * them, we just export a couple public functions to allow the imx6 CCM clock * driver to read and write those registers. * * We also don't do anything about power regulation yet, but when the need * arises, this would be the place for that code to live. * * I have no idea where the "anatop" name comes from. It's in the standard DTS * source describing i.MX6 SoCs, and in the linux and u-boot code which comes * from Freescale, but it's not in the SoC manual. * * Note that temperature values throughout this code are handled in two types of * units. Items with '_cnt' in the name use the hardware temperature count * units (higher counts are lower temperatures). Items with '_val' in the name * are deci-Celcius, which are converted to/from deci-Kelvins in the sysctl * handlers (dK is the standard unit for temperature in sysctl). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct resource_spec imx6_anatop_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { SYS_RES_IRQ, 0, RF_ACTIVE }, { -1, 0 } }; #define MEMRES 0 #define IRQRES 1 struct imx6_anatop_softc { device_t dev; struct resource *res[2]; struct intr_config_hook intr_setup_hook; uint32_t cpu_curmhz; uint32_t cpu_curmv; uint32_t cpu_minmhz; uint32_t cpu_minmv; uint32_t cpu_maxmhz; uint32_t cpu_maxmv; uint32_t cpu_maxmhz_hw; boolean_t cpu_overclock_enable; boolean_t cpu_init_done; uint32_t refosc_mhz; void *temp_intrhand; uint32_t temp_high_val; uint32_t temp_high_cnt; uint32_t temp_last_cnt; uint32_t temp_room_cnt; struct callout temp_throttle_callout; sbintime_t temp_throttle_delay; uint32_t temp_throttle_reset_cnt; uint32_t temp_throttle_trigger_cnt; uint32_t temp_throttle_val; }; static struct imx6_anatop_softc *imx6_anatop_sc; /* * Table of "operating points". * These are combinations of frequency and voltage blessed by Freescale. * While the datasheet says the ARM voltage can be as low as 925mV at * 396MHz, it also says that the ARM and SOC voltages can't differ by * more than 200mV, and the minimum SOC voltage is 1150mV, so that * dictates the 950mV entry in this table. */ static struct oppt { uint32_t mhz; uint32_t mv; } imx6_oppt_table[] = { { 396, 950}, { 792, 1150}, { 852, 1225}, { 996, 1225}, {1200, 1275}, }; /* * Table of CPU max frequencies. This is used to translate the max frequency * value (0-3) from the ocotp CFG3 register into a mhz value that can be looked * up in the operating points table. */ static uint32_t imx6_ocotp_mhz_tab[] = {792, 852, 996, 1200}; -#define TZ_ZEROC 2732 /* deci-Kelvin <-> deci-Celcius offset. */ +#define TZ_ZEROC 2731 /* deci-Kelvin <-> deci-Celcius offset. */ uint32_t imx6_anatop_read_4(bus_size_t offset) { KASSERT(imx6_anatop_sc != NULL, ("imx6_anatop_read_4 sc NULL")); return (bus_read_4(imx6_anatop_sc->res[MEMRES], offset)); } void imx6_anatop_write_4(bus_size_t offset, uint32_t value) { KASSERT(imx6_anatop_sc != NULL, ("imx6_anatop_write_4 sc NULL")); bus_write_4(imx6_anatop_sc->res[MEMRES], offset, value); } static void vdd_set(struct imx6_anatop_softc *sc, int mv) { int newtarg, newtargSoc, oldtarg; uint32_t delay, pmureg; static boolean_t init_done = false; /* * The datasheet says VDD_PU and VDD_SOC must be equal, and VDD_ARM * can't be more than 50mV above or 200mV below them. We keep them the * same except in the case of the lowest operating point, which is * handled as a special case below. */ pmureg = imx6_anatop_read_4(IMX6_ANALOG_PMU_REG_CORE); oldtarg = pmureg & IMX6_ANALOG_PMU_REG0_TARG_MASK; /* Convert mV to target value. Clamp target to valid range. */ if (mv < 725) newtarg = 0x00; else if (mv > 1450) newtarg = 0x1F; else newtarg = (mv - 700) / 25; /* * The SOC voltage can't go below 1150mV, and thus because of the 200mV * rule, the ARM voltage can't go below 950mV. The 950 is encoded in * our oppt table, here we handle the SOC 1150 rule as a special case. * (1150-700/25=18). */ newtargSoc = (newtarg < 18) ? 18 : newtarg; /* * The first time through the 3 voltages might not be equal so use a * long conservative delay. After that we need to delay 3uS for every * 25mV step upward; we actually delay 6uS because empirically, it works * and the 3uS per step recommended by the docs doesn't (3uS fails when * going from 400->1200, but works for smaller changes). */ if (init_done) { if (newtarg == oldtarg) return; else if (newtarg > oldtarg) delay = (newtarg - oldtarg) * 6; else delay = 0; } else { delay = (700 / 25) * 6; init_done = true; } /* * Make the change and wait for it to take effect. */ pmureg &= ~(IMX6_ANALOG_PMU_REG0_TARG_MASK | IMX6_ANALOG_PMU_REG1_TARG_MASK | IMX6_ANALOG_PMU_REG2_TARG_MASK); pmureg |= newtarg << IMX6_ANALOG_PMU_REG0_TARG_SHIFT; pmureg |= newtarg << IMX6_ANALOG_PMU_REG1_TARG_SHIFT; pmureg |= newtargSoc << IMX6_ANALOG_PMU_REG2_TARG_SHIFT; imx6_anatop_write_4(IMX6_ANALOG_PMU_REG_CORE, pmureg); DELAY(delay); sc->cpu_curmv = newtarg * 25 + 700; } static inline uint32_t cpufreq_mhz_from_div(struct imx6_anatop_softc *sc, uint32_t corediv, uint32_t plldiv) { return ((sc->refosc_mhz * (plldiv / 2)) / (corediv + 1)); } static inline void cpufreq_mhz_to_div(struct imx6_anatop_softc *sc, uint32_t cpu_mhz, uint32_t *corediv, uint32_t *plldiv) { *corediv = (cpu_mhz < 650) ? 1 : 0; *plldiv = ((*corediv + 1) * cpu_mhz) / (sc->refosc_mhz / 2); } static inline uint32_t cpufreq_actual_mhz(struct imx6_anatop_softc *sc, uint32_t cpu_mhz) { uint32_t corediv, plldiv; cpufreq_mhz_to_div(sc, cpu_mhz, &corediv, &plldiv); return (cpufreq_mhz_from_div(sc, corediv, plldiv)); } static struct oppt * cpufreq_nearest_oppt(struct imx6_anatop_softc *sc, uint32_t cpu_newmhz) { int d, diff, i, nearest; if (cpu_newmhz > sc->cpu_maxmhz_hw && !sc->cpu_overclock_enable) cpu_newmhz = sc->cpu_maxmhz_hw; diff = INT_MAX; nearest = 0; for (i = 0; i < nitems(imx6_oppt_table); ++i) { d = abs((int)cpu_newmhz - (int)imx6_oppt_table[i].mhz); if (diff > d) { diff = d; nearest = i; } } return (&imx6_oppt_table[nearest]); } static void cpufreq_set_clock(struct imx6_anatop_softc * sc, struct oppt *op) { uint32_t corediv, plldiv, timeout, wrk32; /* If increasing the frequency, we must first increase the voltage. */ if (op->mhz > sc->cpu_curmhz) { vdd_set(sc, op->mv); } /* * I can't find a documented procedure for changing the ARM PLL divisor, * but some trial and error came up with this: * - Set the bypass clock source to REF_CLK_24M (source #0). * - Set the PLL into bypass mode; cpu should now be running at 24mhz. * - Change the divisor. * - Wait for the LOCK bit to come on; it takes ~50 loop iterations. * - Turn off bypass mode; cpu should now be running at the new speed. */ cpufreq_mhz_to_div(sc, op->mhz, &corediv, &plldiv); imx6_anatop_write_4(IMX6_ANALOG_CCM_PLL_ARM_CLR, IMX6_ANALOG_CCM_PLL_ARM_CLK_SRC_MASK); imx6_anatop_write_4(IMX6_ANALOG_CCM_PLL_ARM_SET, IMX6_ANALOG_CCM_PLL_ARM_BYPASS); wrk32 = imx6_anatop_read_4(IMX6_ANALOG_CCM_PLL_ARM); wrk32 &= ~IMX6_ANALOG_CCM_PLL_ARM_DIV_MASK; wrk32 |= plldiv; imx6_anatop_write_4(IMX6_ANALOG_CCM_PLL_ARM, wrk32); timeout = 10000; while ((imx6_anatop_read_4(IMX6_ANALOG_CCM_PLL_ARM) & IMX6_ANALOG_CCM_PLL_ARM_LOCK) == 0) if (--timeout == 0) panic("imx6_set_cpu_clock(): PLL never locked"); imx6_anatop_write_4(IMX6_ANALOG_CCM_PLL_ARM_CLR, IMX6_ANALOG_CCM_PLL_ARM_BYPASS); imx_ccm_set_cacrr(corediv); /* If lowering the frequency, it is now safe to lower the voltage. */ if (op->mhz < sc->cpu_curmhz) vdd_set(sc, op->mv); sc->cpu_curmhz = op->mhz; /* Tell the mpcore timer that its frequency has changed. */ arm_tmr_change_frequency( cpufreq_actual_mhz(sc, sc->cpu_curmhz) * 1000000 / 2); } static int cpufreq_sysctl_minmhz(SYSCTL_HANDLER_ARGS) { struct imx6_anatop_softc *sc; struct oppt * op; uint32_t temp; int err; sc = arg1; temp = sc->cpu_minmhz; err = sysctl_handle_int(oidp, &temp, 0, req); if (err != 0 || req->newptr == NULL) return (err); op = cpufreq_nearest_oppt(sc, temp); if (op->mhz > sc->cpu_maxmhz) return (ERANGE); else if (op->mhz == sc->cpu_minmhz) return (0); /* * Value changed, update softc. If the new min is higher than the * current speed, raise the current speed to match. */ sc->cpu_minmhz = op->mhz; if (sc->cpu_minmhz > sc->cpu_curmhz) { cpufreq_set_clock(sc, op); } return (err); } static int cpufreq_sysctl_maxmhz(SYSCTL_HANDLER_ARGS) { struct imx6_anatop_softc *sc; struct oppt * op; uint32_t temp; int err; sc = arg1; temp = sc->cpu_maxmhz; err = sysctl_handle_int(oidp, &temp, 0, req); if (err != 0 || req->newptr == NULL) return (err); op = cpufreq_nearest_oppt(sc, temp); if (op->mhz < sc->cpu_minmhz) return (ERANGE); else if (op->mhz == sc->cpu_maxmhz) return (0); /* * Value changed, update softc and hardware. The hardware update is * unconditional. We always try to run at max speed, so any change of * the max means we need to change the current speed too, regardless of * whether it is higher or lower than the old max. */ sc->cpu_maxmhz = op->mhz; cpufreq_set_clock(sc, op); return (err); } static void cpufreq_initialize(struct imx6_anatop_softc *sc) { uint32_t cfg3speed; struct oppt * op; SYSCTL_ADD_INT(NULL, SYSCTL_STATIC_CHILDREN(_hw_imx), OID_AUTO, "cpu_mhz", CTLFLAG_RD, &sc->cpu_curmhz, 0, "CPU frequency"); SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_hw_imx), OID_AUTO, "cpu_minmhz", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NOFETCH, sc, 0, cpufreq_sysctl_minmhz, "IU", "Minimum CPU frequency"); SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_hw_imx), OID_AUTO, "cpu_maxmhz", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_NOFETCH, sc, 0, cpufreq_sysctl_maxmhz, "IU", "Maximum CPU frequency"); SYSCTL_ADD_INT(NULL, SYSCTL_STATIC_CHILDREN(_hw_imx), OID_AUTO, "cpu_maxmhz_hw", CTLFLAG_RD, &sc->cpu_maxmhz_hw, 0, "Maximum CPU frequency allowed by hardware"); SYSCTL_ADD_INT(NULL, SYSCTL_STATIC_CHILDREN(_hw_imx), OID_AUTO, "cpu_overclock_enable", CTLFLAG_RWTUN, &sc->cpu_overclock_enable, 0, "Allow setting CPU frequency higher than cpu_maxmhz_hw"); /* * XXX 24mhz shouldn't be hard-coded, should get this from imx6_ccm * (even though in the real world it will always be 24mhz). Oh wait a * sec, I never wrote imx6_ccm. */ sc->refosc_mhz = 24; /* * Get the maximum speed this cpu can be set to. The values in the * OCOTP CFG3 register are not documented in the reference manual. * The following info was in an archived email found via web search: * - 2b'11: 1200000000Hz; * - 2b'10: 996000000Hz; * - 2b'01: 852000000Hz; -- i.MX6Q Only, exclusive with 996MHz. * - 2b'00: 792000000Hz; * The default hardware max speed can be overridden by a tunable. */ cfg3speed = (fsl_ocotp_read_4(FSL_OCOTP_CFG3) & FSL_OCOTP_CFG3_SPEED_MASK) >> FSL_OCOTP_CFG3_SPEED_SHIFT; sc->cpu_maxmhz_hw = imx6_ocotp_mhz_tab[cfg3speed]; sc->cpu_maxmhz = sc->cpu_maxmhz_hw; TUNABLE_INT_FETCH("hw.imx6.cpu_minmhz", &sc->cpu_minmhz); op = cpufreq_nearest_oppt(sc, sc->cpu_minmhz); sc->cpu_minmhz = op->mhz; sc->cpu_minmv = op->mv; TUNABLE_INT_FETCH("hw.imx6.cpu_maxmhz", &sc->cpu_maxmhz); op = cpufreq_nearest_oppt(sc, sc->cpu_maxmhz); sc->cpu_maxmhz = op->mhz; sc->cpu_maxmv = op->mv; /* * Set the CPU to maximum speed. * * We won't have thermal throttling until interrupts are enabled, but we * want to run at full speed through all the device init stuff. This * basically assumes that a single core can't overheat before interrupts * are enabled; empirical testing shows that to be a safe assumption. */ cpufreq_set_clock(sc, op); } static inline uint32_t temp_from_count(struct imx6_anatop_softc *sc, uint32_t count) { return (((sc->temp_high_val - (count - sc->temp_high_cnt) * (sc->temp_high_val - 250) / (sc->temp_room_cnt - sc->temp_high_cnt)))); } static inline uint32_t temp_to_count(struct imx6_anatop_softc *sc, uint32_t temp) { return ((sc->temp_room_cnt - sc->temp_high_cnt) * (sc->temp_high_val - temp) / (sc->temp_high_val - 250) + sc->temp_high_cnt); } static void temp_update_count(struct imx6_anatop_softc *sc) { uint32_t val; val = imx6_anatop_read_4(IMX6_ANALOG_TEMPMON_TEMPSENSE0); if (!(val & IMX6_ANALOG_TEMPMON_TEMPSENSE0_VALID)) return; sc->temp_last_cnt = (val & IMX6_ANALOG_TEMPMON_TEMPSENSE0_TEMP_CNT_MASK) >> IMX6_ANALOG_TEMPMON_TEMPSENSE0_TEMP_CNT_SHIFT; } static int temp_sysctl_handler(SYSCTL_HANDLER_ARGS) { struct imx6_anatop_softc *sc = arg1; uint32_t t; temp_update_count(sc); t = temp_from_count(sc, sc->temp_last_cnt) + TZ_ZEROC; return (sysctl_handle_int(oidp, &t, 0, req)); } static int temp_throttle_sysctl_handler(SYSCTL_HANDLER_ARGS) { struct imx6_anatop_softc *sc = arg1; int err; uint32_t temp; temp = sc->temp_throttle_val + TZ_ZEROC; err = sysctl_handle_int(oidp, &temp, 0, req); if (temp < TZ_ZEROC) return (ERANGE); temp -= TZ_ZEROC; if (err != 0 || req->newptr == NULL || temp == sc->temp_throttle_val) return (err); /* Value changed, update counts in softc and hardware. */ sc->temp_throttle_val = temp; sc->temp_throttle_trigger_cnt = temp_to_count(sc, sc->temp_throttle_val); sc->temp_throttle_reset_cnt = temp_to_count(sc, sc->temp_throttle_val - 100); imx6_anatop_write_4(IMX6_ANALOG_TEMPMON_TEMPSENSE0_CLR, IMX6_ANALOG_TEMPMON_TEMPSENSE0_ALARM_MASK); imx6_anatop_write_4(IMX6_ANALOG_TEMPMON_TEMPSENSE0_SET, (sc->temp_throttle_trigger_cnt << IMX6_ANALOG_TEMPMON_TEMPSENSE0_ALARM_SHIFT)); return (err); } static void tempmon_gofast(struct imx6_anatop_softc *sc) { if (sc->cpu_curmhz < sc->cpu_maxmhz) { cpufreq_set_clock(sc, cpufreq_nearest_oppt(sc, sc->cpu_maxmhz)); } } static void tempmon_goslow(struct imx6_anatop_softc *sc) { if (sc->cpu_curmhz > sc->cpu_minmhz) { cpufreq_set_clock(sc, cpufreq_nearest_oppt(sc, sc->cpu_minmhz)); } } static int tempmon_intr(void *arg) { struct imx6_anatop_softc *sc = arg; /* * XXX Note that this code doesn't currently run (for some mysterious * reason we just never get an interrupt), so the real monitoring is * done by tempmon_throttle_check(). */ tempmon_goslow(sc); /* XXX Schedule callout to speed back up eventually. */ return (FILTER_HANDLED); } static void tempmon_throttle_check(void *arg) { struct imx6_anatop_softc *sc = arg; /* Lower counts are higher temperatures. */ if (sc->temp_last_cnt < sc->temp_throttle_trigger_cnt) tempmon_goslow(sc); else if (sc->temp_last_cnt > (sc->temp_throttle_reset_cnt)) tempmon_gofast(sc); callout_reset_sbt(&sc->temp_throttle_callout, sc->temp_throttle_delay, 0, tempmon_throttle_check, sc, 0); } static void initialize_tempmon(struct imx6_anatop_softc *sc) { uint32_t cal; /* * Fetch calibration data: a sensor count at room temperature (25C), * a sensor count at a high temperature, and that temperature */ cal = fsl_ocotp_read_4(FSL_OCOTP_ANA1); sc->temp_room_cnt = (cal & 0xFFF00000) >> 20; sc->temp_high_cnt = (cal & 0x000FFF00) >> 8; sc->temp_high_val = (cal & 0x000000FF) * 10; /* * Throttle to a lower cpu freq at 10C below the "hot" temperature, and * reset back to max cpu freq at 5C below the trigger. */ sc->temp_throttle_val = sc->temp_high_val - 100; sc->temp_throttle_trigger_cnt = temp_to_count(sc, sc->temp_throttle_val); sc->temp_throttle_reset_cnt = temp_to_count(sc, sc->temp_throttle_val - 50); /* * Set the sensor to sample automatically at 16Hz (32.768KHz/0x800), set * the throttle count, and begin making measurements. */ imx6_anatop_write_4(IMX6_ANALOG_TEMPMON_TEMPSENSE1, 0x0800); imx6_anatop_write_4(IMX6_ANALOG_TEMPMON_TEMPSENSE0, (sc->temp_throttle_trigger_cnt << IMX6_ANALOG_TEMPMON_TEMPSENSE0_ALARM_SHIFT) | IMX6_ANALOG_TEMPMON_TEMPSENSE0_MEASURE); /* * XXX Note that the alarm-interrupt feature isn't working yet, so * we'll use a callout handler to check at 10Hz. Make sure we have an * initial temperature reading before starting up the callouts so we * don't get a bogus reading of zero. */ while (sc->temp_last_cnt == 0) temp_update_count(sc); sc->temp_throttle_delay = 100 * SBT_1MS; callout_init(&sc->temp_throttle_callout, 0); callout_reset_sbt(&sc->temp_throttle_callout, sc->temp_throttle_delay, 0, tempmon_throttle_check, sc, 0); SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_hw_imx), OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD, sc, 0, temp_sysctl_handler, "IK", "Current die temperature"); SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_hw_imx), OID_AUTO, "throttle_temperature", CTLTYPE_INT | CTLFLAG_RW, sc, 0, temp_throttle_sysctl_handler, "IK", "Throttle CPU when exceeding this temperature"); } static void intr_setup(void *arg) { struct imx6_anatop_softc *sc; sc = arg; bus_setup_intr(sc->dev, sc->res[IRQRES], INTR_TYPE_MISC | INTR_MPSAFE, tempmon_intr, NULL, sc, &sc->temp_intrhand); config_intrhook_disestablish(&sc->intr_setup_hook); } static void imx6_anatop_new_pass(device_t dev) { struct imx6_anatop_softc *sc; const int cpu_init_pass = BUS_PASS_CPU + BUS_PASS_ORDER_MIDDLE; /* * We attach during BUS_PASS_BUS (because some day we will be a * simplebus that has regulator devices as children), but some of our * init work cannot be done until BUS_PASS_CPU (we rely on other devices * that attach on the CPU pass). */ sc = device_get_softc(dev); if (!sc->cpu_init_done && bus_current_pass >= cpu_init_pass) { sc->cpu_init_done = true; cpufreq_initialize(sc); initialize_tempmon(sc); if (bootverbose) { device_printf(sc->dev, "CPU %uMHz @ %umV\n", sc->cpu_curmhz, sc->cpu_curmv); } } bus_generic_new_pass(dev); } static int imx6_anatop_detach(device_t dev) { /* This device can never detach. */ return (EBUSY); } static int imx6_anatop_attach(device_t dev) { struct imx6_anatop_softc *sc; int err; sc = device_get_softc(dev); sc->dev = dev; /* Allocate bus_space resources. */ if (bus_alloc_resources(dev, imx6_anatop_spec, sc->res)) { device_printf(dev, "Cannot allocate resources\n"); err = ENXIO; goto out; } sc->intr_setup_hook.ich_func = intr_setup; sc->intr_setup_hook.ich_arg = sc; config_intrhook_establish(&sc->intr_setup_hook); SYSCTL_ADD_UINT(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "cpu_voltage", CTLFLAG_RD, &sc->cpu_curmv, 0, "Current CPU voltage in millivolts"); imx6_anatop_sc = sc; /* * Other code seen on the net sets this SELFBIASOFF flag around the same * time the temperature sensor is set up, although it's unclear how the * two are related (if at all). */ imx6_anatop_write_4(IMX6_ANALOG_PMU_MISC0_SET, IMX6_ANALOG_PMU_MISC0_SELFBIASOFF); /* * Some day, when we're ready to deal with the actual anatop regulators * that are described in fdt data as children of this "bus", this would * be the place to invoke a simplebus helper routine to instantiate the * children from the fdt data. */ err = 0; out: if (err != 0) { bus_release_resources(dev, imx6_anatop_spec, sc->res); } return (err); } uint32_t pll4_configure_output(uint32_t mfi, uint32_t mfn, uint32_t mfd) { int reg; /* * Audio PLL (PLL4). * PLL output frequency = Fref * (DIV_SELECT + NUM/DENOM) */ reg = (IMX6_ANALOG_CCM_PLL_AUDIO_ENABLE); reg &= ~(IMX6_ANALOG_CCM_PLL_AUDIO_DIV_SELECT_MASK << \ IMX6_ANALOG_CCM_PLL_AUDIO_DIV_SELECT_SHIFT); reg |= (mfi << IMX6_ANALOG_CCM_PLL_AUDIO_DIV_SELECT_SHIFT); imx6_anatop_write_4(IMX6_ANALOG_CCM_PLL_AUDIO, reg); imx6_anatop_write_4(IMX6_ANALOG_CCM_PLL_AUDIO_NUM, mfn); imx6_anatop_write_4(IMX6_ANALOG_CCM_PLL_AUDIO_DENOM, mfd); return (0); } static int imx6_anatop_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_is_compatible(dev, "fsl,imx6q-anatop") == 0) return (ENXIO); device_set_desc(dev, "Freescale i.MX6 Analog PLLs and Power"); return (BUS_PROBE_DEFAULT); } uint32_t imx6_get_cpu_clock() { uint32_t corediv, plldiv; corediv = imx_ccm_get_cacrr(); plldiv = imx6_anatop_read_4(IMX6_ANALOG_CCM_PLL_ARM) & IMX6_ANALOG_CCM_PLL_ARM_DIV_MASK; return (cpufreq_mhz_from_div(imx6_anatop_sc, corediv, plldiv)); } static device_method_t imx6_anatop_methods[] = { /* Device interface */ DEVMETHOD(device_probe, imx6_anatop_probe), DEVMETHOD(device_attach, imx6_anatop_attach), DEVMETHOD(device_detach, imx6_anatop_detach), /* Bus interface */ DEVMETHOD(bus_new_pass, imx6_anatop_new_pass), DEVMETHOD_END }; static driver_t imx6_anatop_driver = { "imx6_anatop", imx6_anatop_methods, sizeof(struct imx6_anatop_softc) }; static devclass_t imx6_anatop_devclass; EARLY_DRIVER_MODULE(imx6_anatop, simplebus, imx6_anatop_driver, imx6_anatop_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); EARLY_DRIVER_MODULE(imx6_anatop, ofwbus, imx6_anatop_driver, imx6_anatop_devclass, 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); Index: head/sys/arm/mv/mv_ts.c =================================================================== --- head/sys/arm/mv/mv_ts.c (revision 300420) +++ head/sys/arm/mv/mv_ts.c (revision 300421) @@ -1,180 +1,180 @@ /*- * Copyright (c) 2012 Hiroki Sato * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Driver for on-die thermal sensor in 88F6282 and 88F6283. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include static struct resource_spec mvts_res[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { -1, 0 } }; struct mvts_softc { device_t sc_dev; struct resource *sc_res[sizeof(mvts_res)]; }; static int ts_probe(device_t dev) { uint32_t d, r; if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "mrvl,ts")) return (ENXIO); soc_id(&d, &r); switch (d) { case MV_DEV_88F6282: break; default: device_printf(dev, "unsupported SoC (ID: 0x%08X)!\n", d); return (ENXIO); } device_set_desc(dev, "Marvell Thermal Sensor"); return (0); } #define MV_TEMP_VALID_BIT (1 << 9) #define MV_TEMP_SENS_OFFS 10 #define MV_TEMP_SENS_MASK 0x1ff #define MV_TEMP_SENS_READ_MAX 16 -#define TZ_ZEROC 2732 +#define TZ_ZEROC 2731 #define MV_TEMP_CONVERT(x) ((((322 - x) * 100000) / 13625) + TZ_ZEROC) /* * MSB LSB * 0000 0000 0000 0000 0000 0000 0000 0000 * ^- valid bit * |---------| * ^--- temperature (9 bits) */ static int ts_sysctl_handler(SYSCTL_HANDLER_ARGS) { struct mvts_softc *sc; device_t dev; uint32_t ret, ret0; u_int val; int i; dev = (device_t)arg1; sc = device_get_softc(dev); val = TZ_ZEROC; ret = bus_read_4(sc->sc_res[0], 0); if ((ret & MV_TEMP_VALID_BIT) == 0) { device_printf(dev, "temperature sensor is broken.\n"); goto ts_sysctl_handle_int; } ret0 = 0; for (i = 0; i < MV_TEMP_SENS_READ_MAX; i++) { ret = bus_read_4(sc->sc_res[0], 0); ret = (ret >> MV_TEMP_SENS_OFFS) & MV_TEMP_SENS_MASK; /* * Successive reads should returns the same value except * for the LSB when the sensor is normal. */ if (((ret0 ^ ret) & 0x1fe) == 0) break; else ret0 = ret; } if (i == MV_TEMP_SENS_READ_MAX) { device_printf(dev, "temperature sensor is unstable.\n"); goto ts_sysctl_handle_int; } val = (u_int)MV_TEMP_CONVERT(ret); ts_sysctl_handle_int: return (sysctl_handle_int(oidp, &val, 0, req)); } static int ts_attach(device_t dev) { struct mvts_softc *sc; struct sysctl_ctx_list *ctx; int error; sc = device_get_softc(dev); sc->sc_dev = dev; error = bus_alloc_resources(dev, mvts_res, sc->sc_res); if (error) { device_printf(dev, "could not allocate resources\n"); return (ENXIO); } ctx = device_get_sysctl_ctx(dev); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD, dev, 0, ts_sysctl_handler, "IK", "Current Temperature"); return (0); } static int ts_detach(device_t dev) { return (0); } static device_method_t ts_methods[] = { DEVMETHOD(device_probe, ts_probe), DEVMETHOD(device_attach, ts_attach), DEVMETHOD(device_detach, ts_detach), {0, 0}, }; static driver_t ts_driver = { "mvts", ts_methods, sizeof(struct mvts_softc), }; static devclass_t ts_devclass; DRIVER_MODULE(mvts, simplebus, ts_driver, ts_devclass, 0, 0); MODULE_VERSION(mvts, 1); Index: head/sys/dev/acpi_support/acpi_asus_wmi.c =================================================================== --- head/sys/dev/acpi_support/acpi_asus_wmi.c (revision 300420) +++ head/sys/dev/acpi_support/acpi_asus_wmi.c (revision 300421) @@ -1,637 +1,637 @@ /*- * Copyright (c) 2012 Alexander Motin * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "acpi_wmi_if.h" #define _COMPONENT ACPI_OEM ACPI_MODULE_NAME("ASUS-WMI") #define ACPI_ASUS_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66" #define ACPI_ASUS_WMI_EVENT_GUID "0B3CBB35-E3C2-45ED-91C2-4C5A6D195D1C" #define ACPI_EEEPC_WMI_EVENT_GUID "ABBC0F72-8EA1-11D1-00A0-C90629100000" /* WMI Methods */ #define ASUS_WMI_METHODID_SPEC 0x43455053 #define ASUS_WMI_METHODID_SFUN 0x4E554653 #define ASUS_WMI_METHODID_DSTS 0x53544344 #define ASUS_WMI_METHODID_DSTS2 0x53545344 #define ASUS_WMI_METHODID_DEVS 0x53564544 #define ASUS_WMI_METHODID_INIT 0x54494E49 #define ASUS_WMI_METHODID_HKEY 0x59454B48 #define ASUS_WMI_UNSUPPORTED_METHOD 0xFFFFFFFE /* Wireless */ #define ASUS_WMI_DEVID_HW_SWITCH 0x00010001 #define ASUS_WMI_DEVID_WIRELESS_LED 0x00010002 #define ASUS_WMI_DEVID_CWAP 0x00010003 #define ASUS_WMI_DEVID_WLAN 0x00010011 #define ASUS_WMI_DEVID_BLUETOOTH 0x00010013 #define ASUS_WMI_DEVID_GPS 0x00010015 #define ASUS_WMI_DEVID_WIMAX 0x00010017 #define ASUS_WMI_DEVID_WWAN3G 0x00010019 #define ASUS_WMI_DEVID_UWB 0x00010021 /* LEDs */ #define ASUS_WMI_DEVID_LED1 0x00020011 #define ASUS_WMI_DEVID_LED2 0x00020012 #define ASUS_WMI_DEVID_LED3 0x00020013 #define ASUS_WMI_DEVID_LED4 0x00020014 #define ASUS_WMI_DEVID_LED5 0x00020015 #define ASUS_WMI_DEVID_LED6 0x00020016 /* Backlight and Brightness */ #define ASUS_WMI_DEVID_BACKLIGHT 0x00050011 #define ASUS_WMI_DEVID_BRIGHTNESS 0x00050012 #define ASUS_WMI_DEVID_KBD_BACKLIGHT 0x00050021 #define ASUS_WMI_DEVID_LIGHT_SENSOR 0x00050022 /* Misc */ #define ASUS_WMI_DEVID_CAMERA 0x00060013 #define ASUS_WMI_DEVID_CARDREADER 0x00080013 #define ASUS_WMI_DEVID_TOUCHPAD 0x00100011 #define ASUS_WMI_DEVID_TOUCHPAD_LED 0x00100012 #define ASUS_WMI_DEVID_THERMAL_CTRL 0x00110011 #define ASUS_WMI_DEVID_FAN_CTRL 0x00110012 #define ASUS_WMI_DEVID_PROCESSOR_STATE 0x00120012 /* DSTS masks */ #define ASUS_WMI_DSTS_STATUS_BIT 0x00000001 #define ASUS_WMI_DSTS_UNKNOWN_BIT 0x00000002 #define ASUS_WMI_DSTS_PRESENCE_BIT 0x00010000 #define ASUS_WMI_DSTS_USER_BIT 0x00020000 #define ASUS_WMI_DSTS_BIOS_BIT 0x00040000 #define ASUS_WMI_DSTS_BRIGHTNESS_MASK 0x000000FF #define ASUS_WMI_DSTS_MAX_BRIGTH_MASK 0x0000FF00 struct acpi_asus_wmi_softc { device_t dev; device_t wmi_dev; const char *notify_guid; struct sysctl_ctx_list *sysctl_ctx; struct sysctl_oid *sysctl_tree; int dsts_id; int handle_keys; }; static struct { char *name; int dev_id; char *description; int flag_rdonly; } acpi_asus_wmi_sysctls[] = { { .name = "hw_switch", .dev_id = ASUS_WMI_DEVID_HW_SWITCH, .description = "hw_switch", }, { .name = "wireless_led", .dev_id = ASUS_WMI_DEVID_WIRELESS_LED, .description = "Wireless LED control", }, { .name = "cwap", .dev_id = ASUS_WMI_DEVID_CWAP, .description = "Alt+F2 function", }, { .name = "wlan", .dev_id = ASUS_WMI_DEVID_WLAN, .description = "WLAN power control", }, { .name = "bluetooth", .dev_id = ASUS_WMI_DEVID_BLUETOOTH, .description = "Bluetooth power control", }, { .name = "gps", .dev_id = ASUS_WMI_DEVID_GPS, .description = "GPS power control", }, { .name = "wimax", .dev_id = ASUS_WMI_DEVID_WIMAX, .description = "WiMAX power control", }, { .name = "wwan3g", .dev_id = ASUS_WMI_DEVID_WWAN3G, .description = "WWAN-3G power control", }, { .name = "uwb", .dev_id = ASUS_WMI_DEVID_UWB, .description = "UWB power control", }, { .name = "led1", .dev_id = ASUS_WMI_DEVID_LED1, .description = "LED1 control", }, { .name = "led2", .dev_id = ASUS_WMI_DEVID_LED2, .description = "LED2 control", }, { .name = "led3", .dev_id = ASUS_WMI_DEVID_LED3, .description = "LED3 control", }, { .name = "led4", .dev_id = ASUS_WMI_DEVID_LED4, .description = "LED4 control", }, { .name = "led5", .dev_id = ASUS_WMI_DEVID_LED5, .description = "LED5 control", }, { .name = "led6", .dev_id = ASUS_WMI_DEVID_LED6, .description = "LED6 control", }, { .name = "backlight", .dev_id = ASUS_WMI_DEVID_BACKLIGHT, .description = "LCD backlight on/off control", }, { .name = "brightness", .dev_id = ASUS_WMI_DEVID_BRIGHTNESS, .description = "LCD backlight brightness control", }, { .name = "kbd_backlight", .dev_id = ASUS_WMI_DEVID_KBD_BACKLIGHT, .description = "Keyboard backlight brightness control", }, { .name = "light_sensor", .dev_id = ASUS_WMI_DEVID_LIGHT_SENSOR, .description = "Ambient light sensor", }, { .name = "camera", .dev_id = ASUS_WMI_DEVID_CAMERA, .description = "Camera power control", }, { .name = "cardreader", .dev_id = ASUS_WMI_DEVID_CARDREADER, .description = "Cardreader power control", }, { .name = "touchpad", .dev_id = ASUS_WMI_DEVID_TOUCHPAD, .description = "Touchpad control", }, { .name = "touchpad_led", .dev_id = ASUS_WMI_DEVID_TOUCHPAD_LED, .description = "Touchpad LED control", }, { .name = "themperature", .dev_id = ASUS_WMI_DEVID_THERMAL_CTRL, .description = "Temperature (C)", .flag_rdonly = 1 }, { .name = "fan_speed", .dev_id = ASUS_WMI_DEVID_FAN_CTRL, .description = "Fan speed (0-3)", .flag_rdonly = 1 }, { .name = "processor_state", .dev_id = ASUS_WMI_DEVID_PROCESSOR_STATE, .flag_rdonly = 1 }, { NULL, 0, NULL, 0 } }; ACPI_SERIAL_DECL(asus_wmi, "ASUS WMI device"); static void acpi_asus_wmi_identify(driver_t *driver, device_t parent); static int acpi_asus_wmi_probe(device_t dev); static int acpi_asus_wmi_attach(device_t dev); static int acpi_asus_wmi_detach(device_t dev); static int acpi_asus_wmi_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_asus_wmi_sysctl_set(struct acpi_asus_wmi_softc *sc, int dev_id, int arg, int oldarg); static int acpi_asus_wmi_sysctl_get(struct acpi_asus_wmi_softc *sc, int dev_id); static int acpi_asus_wmi_evaluate_method(device_t wmi_dev, int method, UINT32 arg0, UINT32 arg1, UINT32 *retval); static int acpi_wpi_asus_get_devstate(struct acpi_asus_wmi_softc *sc, UINT32 dev_id, UINT32 *retval); static int acpi_wpi_asus_set_devstate(struct acpi_asus_wmi_softc *sc, UINT32 dev_id, UINT32 ctrl_param, UINT32 *retval); static void acpi_asus_wmi_notify(ACPI_HANDLE h, UINT32 notify, void *context); static device_method_t acpi_asus_wmi_methods[] = { DEVMETHOD(device_identify, acpi_asus_wmi_identify), DEVMETHOD(device_probe, acpi_asus_wmi_probe), DEVMETHOD(device_attach, acpi_asus_wmi_attach), DEVMETHOD(device_detach, acpi_asus_wmi_detach), DEVMETHOD_END }; static driver_t acpi_asus_wmi_driver = { "acpi_asus_wmi", acpi_asus_wmi_methods, sizeof(struct acpi_asus_wmi_softc), }; static devclass_t acpi_asus_wmi_devclass; DRIVER_MODULE(acpi_asus_wmi, acpi_wmi, acpi_asus_wmi_driver, acpi_asus_wmi_devclass, 0, 0); MODULE_DEPEND(acpi_asus_wmi, acpi_wmi, 1, 1, 1); MODULE_DEPEND(acpi_asus_wmi, acpi, 1, 1, 1); static void acpi_asus_wmi_identify(driver_t *driver, device_t parent) { /* Don't do anything if driver is disabled. */ if (acpi_disabled("asus_wmi")) return; /* Add only a single device instance. */ if (device_find_child(parent, "acpi_asus_wmi", -1) != NULL) return; /* Check management GUID to see whether system is compatible. */ if (!ACPI_WMI_PROVIDES_GUID_STRING(parent, ACPI_ASUS_WMI_MGMT_GUID)) return; if (BUS_ADD_CHILD(parent, 0, "acpi_asus_wmi", -1) == NULL) device_printf(parent, "add acpi_asus_wmi child failed\n"); } static int acpi_asus_wmi_probe(device_t dev) { if (!ACPI_WMI_PROVIDES_GUID_STRING(device_get_parent(dev), ACPI_ASUS_WMI_MGMT_GUID)) return (EINVAL); device_set_desc(dev, "ASUS WMI device"); return (0); } static int acpi_asus_wmi_attach(device_t dev) { struct acpi_asus_wmi_softc *sc; UINT32 val; int dev_id, i; ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); sc = device_get_softc(dev); sc->dev = dev; sc->wmi_dev = device_get_parent(dev); sc->handle_keys = 1; /* Check management GUID. */ if (!ACPI_WMI_PROVIDES_GUID_STRING(sc->wmi_dev, ACPI_ASUS_WMI_MGMT_GUID)) { device_printf(dev, "WMI device does not provide the ASUS management GUID\n"); return (EINVAL); } /* Find proper DSTS method. */ sc->dsts_id = ASUS_WMI_METHODID_DSTS; next: for (i = 0; acpi_asus_wmi_sysctls[i].name != NULL; ++i) { dev_id = acpi_asus_wmi_sysctls[i].dev_id; if (acpi_wpi_asus_get_devstate(sc, dev_id, &val)) continue; break; } if (acpi_asus_wmi_sysctls[i].name == NULL) { if (sc->dsts_id == ASUS_WMI_METHODID_DSTS) { sc->dsts_id = ASUS_WMI_METHODID_DSTS2; goto next; } else { device_printf(dev, "Can not detect DSTS method ID\n"); return (EINVAL); } } /* Find proper and attach to notufy GUID. */ if (ACPI_WMI_PROVIDES_GUID_STRING(sc->wmi_dev, ACPI_ASUS_WMI_EVENT_GUID)) sc->notify_guid = ACPI_ASUS_WMI_EVENT_GUID; else if (ACPI_WMI_PROVIDES_GUID_STRING(sc->wmi_dev, ACPI_EEEPC_WMI_EVENT_GUID)) sc->notify_guid = ACPI_EEEPC_WMI_EVENT_GUID; else sc->notify_guid = NULL; if (sc->notify_guid != NULL) { if (ACPI_WMI_INSTALL_EVENT_HANDLER(sc->wmi_dev, sc->notify_guid, acpi_asus_wmi_notify, dev)) sc->notify_guid = NULL; } if (sc->notify_guid == NULL) device_printf(dev, "Could not install event handler!\n"); /* Initialize. */ if (!acpi_asus_wmi_evaluate_method(sc->wmi_dev, ASUS_WMI_METHODID_INIT, 0, 0, &val) && bootverbose) device_printf(dev, "Initialization: %#x\n", val); if (!acpi_asus_wmi_evaluate_method(sc->wmi_dev, ASUS_WMI_METHODID_SPEC, 0, 0x9, &val) && bootverbose) device_printf(dev, "WMI BIOS version: %d.%d\n", val >> 16, val & 0xFF); if (!acpi_asus_wmi_evaluate_method(sc->wmi_dev, ASUS_WMI_METHODID_SFUN, 0, 0, &val) && bootverbose) device_printf(dev, "SFUN value: %#x\n", val); ACPI_SERIAL_BEGIN(asus_wmi); sc->sysctl_ctx = device_get_sysctl_ctx(dev); sc->sysctl_tree = device_get_sysctl_tree(dev); SYSCTL_ADD_INT(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "handle_keys", CTLFLAG_RW, &sc->handle_keys, 0, "Handle some hardware keys inside the driver"); for (i = 0; acpi_asus_wmi_sysctls[i].name != NULL; ++i) { dev_id = acpi_asus_wmi_sysctls[i].dev_id; if (acpi_wpi_asus_get_devstate(sc, dev_id, &val)) continue; switch (dev_id) { case ASUS_WMI_DEVID_THERMAL_CTRL: case ASUS_WMI_DEVID_PROCESSOR_STATE: case ASUS_WMI_DEVID_FAN_CTRL: case ASUS_WMI_DEVID_BRIGHTNESS: if (val == 0) continue; break; default: if ((val & ASUS_WMI_DSTS_PRESENCE_BIT) == 0) continue; break; } if (acpi_asus_wmi_sysctls[i].flag_rdonly != 0) { SYSCTL_ADD_PROC(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, acpi_asus_wmi_sysctls[i].name, CTLTYPE_INT | CTLFLAG_RD, sc, i, acpi_asus_wmi_sysctl, "I", acpi_asus_wmi_sysctls[i].description); } else { SYSCTL_ADD_PROC(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, acpi_asus_wmi_sysctls[i].name, CTLTYPE_INT | CTLFLAG_RW, sc, i, acpi_asus_wmi_sysctl, "I", acpi_asus_wmi_sysctls[i].description); } } ACPI_SERIAL_END(asus_wmi); return (0); } static int acpi_asus_wmi_detach(device_t dev) { struct acpi_asus_wmi_softc *sc = device_get_softc(dev); ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); if (sc->notify_guid) ACPI_WMI_REMOVE_EVENT_HANDLER(dev, sc->notify_guid); return (0); } static int acpi_asus_wmi_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_asus_wmi_softc *sc; int arg; int oldarg; int error = 0; int function; int dev_id; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_asus_wmi_softc *)oidp->oid_arg1; function = oidp->oid_arg2; dev_id = acpi_asus_wmi_sysctls[function].dev_id; ACPI_SERIAL_BEGIN(asus_wmi); arg = acpi_asus_wmi_sysctl_get(sc, dev_id); oldarg = arg; error = sysctl_handle_int(oidp, &arg, 0, req); if (!error && req->newptr != NULL) error = acpi_asus_wmi_sysctl_set(sc, dev_id, arg, oldarg); ACPI_SERIAL_END(asus_wmi); return (error); } static int acpi_asus_wmi_sysctl_get(struct acpi_asus_wmi_softc *sc, int dev_id) { UINT32 val = 0; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(asus_wmi); acpi_wpi_asus_get_devstate(sc, dev_id, &val); switch(dev_id) { case ASUS_WMI_DEVID_THERMAL_CTRL: - val = (val - 2732 + 5) / 10; + val = (val - 2731 + 5) / 10; break; case ASUS_WMI_DEVID_PROCESSOR_STATE: case ASUS_WMI_DEVID_FAN_CTRL: break; case ASUS_WMI_DEVID_BRIGHTNESS: val &= ASUS_WMI_DSTS_BRIGHTNESS_MASK; break; case ASUS_WMI_DEVID_KBD_BACKLIGHT: val &= 0x7; break; default: if (val & ASUS_WMI_DSTS_UNKNOWN_BIT) val = -1; else val = !!(val & ASUS_WMI_DSTS_STATUS_BIT); break; } return (val); } static int acpi_asus_wmi_sysctl_set(struct acpi_asus_wmi_softc *sc, int dev_id, int arg, int oldarg) { ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(asus_wmi); switch(dev_id) { case ASUS_WMI_DEVID_KBD_BACKLIGHT: arg = min(0x7, arg); if (arg != 0) arg |= 0x80; break; } acpi_wpi_asus_set_devstate(sc, dev_id, arg, NULL); return (0); } static __inline void acpi_asus_wmi_free_buffer(ACPI_BUFFER* buf) { if (buf && buf->Pointer) { AcpiOsFree(buf->Pointer); } } static void acpi_asus_wmi_notify(ACPI_HANDLE h, UINT32 notify, void *context) { device_t dev = context; ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify); UINT32 val; int code = 0; struct acpi_asus_wmi_softc *sc = device_get_softc(dev); ACPI_BUFFER response = { ACPI_ALLOCATE_BUFFER, NULL }; ACPI_OBJECT *obj; ACPI_WMI_GET_EVENT_DATA(sc->wmi_dev, notify, &response); obj = (ACPI_OBJECT*) response.Pointer; if (obj && obj->Type == ACPI_TYPE_INTEGER) { code = obj->Integer.Value; acpi_UserNotify("ASUS", ACPI_ROOT_OBJECT, code); } if (code && sc->handle_keys) { /* Keyboard backlight control. */ if (code == 0xc4 || code == 0xc5) { acpi_wpi_asus_get_devstate(sc, ASUS_WMI_DEVID_KBD_BACKLIGHT, &val); val &= 0x7; if (code == 0xc4) { if (val < 0x7) val++; } else if (val > 0) val--; if (val != 0) val |= 0x80; acpi_wpi_asus_set_devstate(sc, ASUS_WMI_DEVID_KBD_BACKLIGHT, val, NULL); } /* Touchpad control. */ if (code == 0x6b) { acpi_wpi_asus_get_devstate(sc, ASUS_WMI_DEVID_TOUCHPAD, &val); val = !(val & 1); acpi_wpi_asus_set_devstate(sc, ASUS_WMI_DEVID_TOUCHPAD, val, NULL); } } acpi_asus_wmi_free_buffer(&response); } static int acpi_asus_wmi_evaluate_method(device_t wmi_dev, int method, UINT32 arg0, UINT32 arg1, UINT32 *retval) { UINT32 params[2] = { arg0, arg1 }; UINT32 result; ACPI_OBJECT *obj; ACPI_BUFFER in = { sizeof(params), ¶ms }; ACPI_BUFFER out = { ACPI_ALLOCATE_BUFFER, NULL }; if (ACPI_FAILURE(ACPI_WMI_EVALUATE_CALL(wmi_dev, ACPI_ASUS_WMI_MGMT_GUID, 1, method, &in, &out))) { acpi_asus_wmi_free_buffer(&out); return (-EINVAL); } obj = out.Pointer; if (obj && obj->Type == ACPI_TYPE_INTEGER) result = (UINT32) obj->Integer.Value; else result = 0; acpi_asus_wmi_free_buffer(&out); if (retval) *retval = result; return (result == ASUS_WMI_UNSUPPORTED_METHOD ? -ENODEV : 0); } static int acpi_wpi_asus_get_devstate(struct acpi_asus_wmi_softc *sc, UINT32 dev_id, UINT32 *retval) { return (acpi_asus_wmi_evaluate_method(sc->wmi_dev, sc->dsts_id, dev_id, 0, retval)); } static int acpi_wpi_asus_set_devstate(struct acpi_asus_wmi_softc *sc, UINT32 dev_id, UINT32 ctrl_param, UINT32 *retval) { return (acpi_asus_wmi_evaluate_method(sc->wmi_dev, ASUS_WMI_METHODID_DEVS, dev_id, ctrl_param, retval)); } Index: head/sys/dev/acpi_support/acpi_ibm.c =================================================================== --- head/sys/dev/acpi_support/acpi_ibm.c (revision 300420) +++ head/sys/dev/acpi_support/acpi_ibm.c (revision 300421) @@ -1,1315 +1,1315 @@ /*- * Copyright (c) 2004 Takanori Watanabe * Copyright (c) 2005 Markus Brueffer * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * Driver for extra ACPI-controlled gadgets found on IBM ThinkPad laptops. * Inspired by the ibm-acpi and tpb projects which implement these features * on Linux. * * acpi-ibm: * tpb: */ #include "opt_acpi.h" #include #include #include #include #include #include #include #include "acpi_if.h" #include #include #include #include #include #include #include #define _COMPONENT ACPI_OEM ACPI_MODULE_NAME("IBM") /* Internal methods */ #define ACPI_IBM_METHOD_EVENTS 1 #define ACPI_IBM_METHOD_EVENTMASK 2 #define ACPI_IBM_METHOD_HOTKEY 3 #define ACPI_IBM_METHOD_BRIGHTNESS 4 #define ACPI_IBM_METHOD_VOLUME 5 #define ACPI_IBM_METHOD_MUTE 6 #define ACPI_IBM_METHOD_THINKLIGHT 7 #define ACPI_IBM_METHOD_BLUETOOTH 8 #define ACPI_IBM_METHOD_WLAN 9 #define ACPI_IBM_METHOD_FANSPEED 10 #define ACPI_IBM_METHOD_FANLEVEL 11 #define ACPI_IBM_METHOD_FANSTATUS 12 #define ACPI_IBM_METHOD_THERMAL 13 #define ACPI_IBM_METHOD_HANDLEREVENTS 14 /* Hotkeys/Buttons */ #define IBM_RTC_HOTKEY1 0x64 #define IBM_RTC_MASK_HOME (1 << 0) #define IBM_RTC_MASK_SEARCH (1 << 1) #define IBM_RTC_MASK_MAIL (1 << 2) #define IBM_RTC_MASK_WLAN (1 << 5) #define IBM_RTC_HOTKEY2 0x65 #define IBM_RTC_MASK_THINKPAD (1 << 3) #define IBM_RTC_MASK_ZOOM (1 << 5) #define IBM_RTC_MASK_VIDEO (1 << 6) #define IBM_RTC_MASK_HIBERNATE (1 << 7) #define IBM_RTC_THINKLIGHT 0x66 #define IBM_RTC_MASK_THINKLIGHT (1 << 4) #define IBM_RTC_SCREENEXPAND 0x67 #define IBM_RTC_MASK_SCREENEXPAND (1 << 5) #define IBM_RTC_BRIGHTNESS 0x6c #define IBM_RTC_MASK_BRIGHTNESS (1 << 5) #define IBM_RTC_VOLUME 0x6e #define IBM_RTC_MASK_VOLUME (1 << 7) /* Embedded Controller registers */ #define IBM_EC_BRIGHTNESS 0x31 #define IBM_EC_MASK_BRI 0x7 #define IBM_EC_VOLUME 0x30 #define IBM_EC_MASK_VOL 0xf #define IBM_EC_MASK_MUTE (1 << 6) #define IBM_EC_FANSTATUS 0x2F #define IBM_EC_MASK_FANLEVEL 0x3f #define IBM_EC_MASK_FANDISENGAGED (1 << 6) #define IBM_EC_MASK_FANSTATUS (1 << 7) #define IBM_EC_FANSPEED 0x84 /* CMOS Commands */ #define IBM_CMOS_VOLUME_DOWN 0 #define IBM_CMOS_VOLUME_UP 1 #define IBM_CMOS_VOLUME_MUTE 2 #define IBM_CMOS_BRIGHTNESS_UP 4 #define IBM_CMOS_BRIGHTNESS_DOWN 5 /* ACPI methods */ #define IBM_NAME_KEYLIGHT "KBLT" #define IBM_NAME_WLAN_BT_GET "GBDC" #define IBM_NAME_WLAN_BT_SET "SBDC" #define IBM_NAME_MASK_BT (1 << 1) #define IBM_NAME_MASK_WLAN (1 << 2) #define IBM_NAME_THERMAL_GET "TMP7" #define IBM_NAME_THERMAL_UPDT "UPDT" #define IBM_NAME_EVENTS_STATUS_GET "DHKC" #define IBM_NAME_EVENTS_MASK_GET "DHKN" #define IBM_NAME_EVENTS_STATUS_SET "MHKC" #define IBM_NAME_EVENTS_MASK_SET "MHKM" #define IBM_NAME_EVENTS_GET "MHKP" #define IBM_NAME_EVENTS_AVAILMASK "MHKA" /* Event Code */ #define IBM_EVENT_LCD_BACKLIGHT 0x03 #define IBM_EVENT_SUSPEND_TO_RAM 0x04 #define IBM_EVENT_BLUETOOTH 0x05 #define IBM_EVENT_SCREEN_EXPAND 0x07 #define IBM_EVENT_SUSPEND_TO_DISK 0x0c #define IBM_EVENT_BRIGHTNESS_UP 0x10 #define IBM_EVENT_BRIGHTNESS_DOWN 0x11 #define IBM_EVENT_THINKLIGHT 0x12 #define IBM_EVENT_ZOOM 0x14 #define IBM_EVENT_VOLUME_UP 0x15 #define IBM_EVENT_VOLUME_DOWN 0x16 #define IBM_EVENT_MUTE 0x17 #define IBM_EVENT_ACCESS_IBM_BUTTON 0x18 #define ABS(x) (((x) < 0)? -(x) : (x)) struct acpi_ibm_softc { device_t dev; ACPI_HANDLE handle; /* Embedded controller */ device_t ec_dev; ACPI_HANDLE ec_handle; /* CMOS */ ACPI_HANDLE cmos_handle; /* Fan status */ ACPI_HANDLE fan_handle; int fan_levels; /* Keylight commands and states */ ACPI_HANDLE light_handle; int light_cmd_on; int light_cmd_off; int light_val; int light_get_supported; int light_set_supported; /* led(4) interface */ struct cdev *led_dev; int led_busy; int led_state; int wlan_bt_flags; int thermal_updt_supported; unsigned int events_availmask; unsigned int events_initialmask; int events_mask_supported; int events_enable; unsigned int handler_events; struct sysctl_ctx_list *sysctl_ctx; struct sysctl_oid *sysctl_tree; }; static struct { char *name; int method; char *description; int flag_rdonly; } acpi_ibm_sysctls[] = { { .name = "events", .method = ACPI_IBM_METHOD_EVENTS, .description = "ACPI events enable", }, { .name = "eventmask", .method = ACPI_IBM_METHOD_EVENTMASK, .description = "ACPI eventmask", }, { .name = "hotkey", .method = ACPI_IBM_METHOD_HOTKEY, .description = "Key Status", .flag_rdonly = 1 }, { .name = "lcd_brightness", .method = ACPI_IBM_METHOD_BRIGHTNESS, .description = "LCD Brightness", }, { .name = "volume", .method = ACPI_IBM_METHOD_VOLUME, .description = "Volume", }, { .name = "mute", .method = ACPI_IBM_METHOD_MUTE, .description = "Mute", }, { .name = "thinklight", .method = ACPI_IBM_METHOD_THINKLIGHT, .description = "Thinklight enable", }, { .name = "bluetooth", .method = ACPI_IBM_METHOD_BLUETOOTH, .description = "Bluetooth enable", }, { .name = "wlan", .method = ACPI_IBM_METHOD_WLAN, .description = "WLAN enable", .flag_rdonly = 1 }, { .name = "fan_speed", .method = ACPI_IBM_METHOD_FANSPEED, .description = "Fan speed", .flag_rdonly = 1 }, { .name = "fan_level", .method = ACPI_IBM_METHOD_FANLEVEL, .description = "Fan level", }, { .name = "fan", .method = ACPI_IBM_METHOD_FANSTATUS, .description = "Fan enable", }, { NULL, 0, NULL, 0 } }; /* * Per-model default list of event mask. */ #define ACPI_IBM_HKEY_RFKILL_MASK (1 << 4) #define ACPI_IBM_HKEY_DSWITCH_MASK (1 << 6) #define ACPI_IBM_HKEY_BRIGHTNESS_UP_MASK (1 << 15) #define ACPI_IBM_HKEY_BRIGHTNESS_DOWN_MASK (1 << 16) #define ACPI_IBM_HKEY_SEARCH_MASK (1 << 18) #define ACPI_IBM_HKEY_MICMUTE_MASK (1 << 26) #define ACPI_IBM_HKEY_SETTINGS_MASK (1 << 28) #define ACPI_IBM_HKEY_VIEWOPEN_MASK (1 << 30) #define ACPI_IBM_HKEY_VIEWALL_MASK (1 << 31) struct acpi_ibm_models { const char *maker; const char *product; uint32_t eventmask; } acpi_ibm_models[] = { { "LENOVO", "20BSCTO1WW", ACPI_IBM_HKEY_RFKILL_MASK | ACPI_IBM_HKEY_DSWITCH_MASK | ACPI_IBM_HKEY_BRIGHTNESS_UP_MASK | ACPI_IBM_HKEY_BRIGHTNESS_DOWN_MASK | ACPI_IBM_HKEY_SEARCH_MASK | ACPI_IBM_HKEY_MICMUTE_MASK | ACPI_IBM_HKEY_SETTINGS_MASK | ACPI_IBM_HKEY_VIEWOPEN_MASK | ACPI_IBM_HKEY_VIEWALL_MASK } }; ACPI_SERIAL_DECL(ibm, "ACPI IBM extras"); static int acpi_ibm_probe(device_t dev); static int acpi_ibm_attach(device_t dev); static int acpi_ibm_detach(device_t dev); static int acpi_ibm_resume(device_t dev); static void ibm_led(void *softc, int onoff); static void ibm_led_task(struct acpi_ibm_softc *sc, int pending __unused); static int acpi_ibm_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_ibm_sysctl_init(struct acpi_ibm_softc *sc, int method); static int acpi_ibm_sysctl_get(struct acpi_ibm_softc *sc, int method); static int acpi_ibm_sysctl_set(struct acpi_ibm_softc *sc, int method, int val); static int acpi_ibm_eventmask_set(struct acpi_ibm_softc *sc, int val); static int acpi_ibm_thermal_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_ibm_handlerevents_sysctl(SYSCTL_HANDLER_ARGS); static void acpi_ibm_notify(ACPI_HANDLE h, UINT32 notify, void *context); static int acpi_ibm_brightness_set(struct acpi_ibm_softc *sc, int arg); static int acpi_ibm_bluetooth_set(struct acpi_ibm_softc *sc, int arg); static int acpi_ibm_thinklight_set(struct acpi_ibm_softc *sc, int arg); static int acpi_ibm_volume_set(struct acpi_ibm_softc *sc, int arg); static int acpi_ibm_mute_set(struct acpi_ibm_softc *sc, int arg); static device_method_t acpi_ibm_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_ibm_probe), DEVMETHOD(device_attach, acpi_ibm_attach), DEVMETHOD(device_detach, acpi_ibm_detach), DEVMETHOD(device_resume, acpi_ibm_resume), DEVMETHOD_END }; static driver_t acpi_ibm_driver = { "acpi_ibm", acpi_ibm_methods, sizeof(struct acpi_ibm_softc), }; static devclass_t acpi_ibm_devclass; DRIVER_MODULE(acpi_ibm, acpi, acpi_ibm_driver, acpi_ibm_devclass, 0, 0); MODULE_DEPEND(acpi_ibm, acpi, 1, 1, 1); static char *ibm_ids[] = {"IBM0068", "LEN0068", NULL}; static void ibm_led(void *softc, int onoff) { struct acpi_ibm_softc* sc = (struct acpi_ibm_softc*) softc; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (sc->led_busy) return; sc->led_busy = 1; sc->led_state = onoff; AcpiOsExecute(OSL_NOTIFY_HANDLER, (void *)ibm_led_task, sc); } static void ibm_led_task(struct acpi_ibm_softc *sc, int pending __unused) { ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_BEGIN(ibm); acpi_ibm_sysctl_set(sc, ACPI_IBM_METHOD_THINKLIGHT, sc->led_state); ACPI_SERIAL_END(ibm); sc->led_busy = 0; } static int acpi_ibm_probe(device_t dev) { if (acpi_disabled("ibm") || ACPI_ID_PROBE(device_get_parent(dev), dev, ibm_ids) == NULL || device_get_unit(dev) != 0) return (ENXIO); device_set_desc(dev, "IBM ThinkPad ACPI Extras"); return (0); } static int acpi_ibm_attach(device_t dev) { int i; struct acpi_ibm_softc *sc; char *maker, *product; devclass_t ec_devclass; ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); sc = device_get_softc(dev); sc->dev = dev; sc->handle = acpi_get_handle(dev); /* Look for the first embedded controller */ if (!(ec_devclass = devclass_find ("acpi_ec"))) { if (bootverbose) device_printf(dev, "Couldn't find acpi_ec devclass\n"); return (EINVAL); } if (!(sc->ec_dev = devclass_get_device(ec_devclass, 0))) { if (bootverbose) device_printf(dev, "Couldn't find acpi_ec device\n"); return (EINVAL); } sc->ec_handle = acpi_get_handle(sc->ec_dev); /* Get the sysctl tree */ sc->sysctl_ctx = device_get_sysctl_ctx(dev); sc->sysctl_tree = device_get_sysctl_tree(dev); /* Look for event mask and hook up the nodes */ sc->events_mask_supported = ACPI_SUCCESS(acpi_GetInteger(sc->handle, IBM_NAME_EVENTS_MASK_GET, &sc->events_initialmask)); if (sc->events_mask_supported) { SYSCTL_ADD_UINT(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "initialmask", CTLFLAG_RD, &sc->events_initialmask, 0, "Initial eventmask"); /* The availmask is the bitmask of supported events */ if (ACPI_FAILURE(acpi_GetInteger(sc->handle, IBM_NAME_EVENTS_AVAILMASK, &sc->events_availmask))) sc->events_availmask = 0xffffffff; SYSCTL_ADD_UINT(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "availmask", CTLFLAG_RD, &sc->events_availmask, 0, "Mask of supported events"); } /* Hook up proc nodes */ for (int i = 0; acpi_ibm_sysctls[i].name != NULL; i++) { if (!acpi_ibm_sysctl_init(sc, acpi_ibm_sysctls[i].method)) continue; if (acpi_ibm_sysctls[i].flag_rdonly != 0) { SYSCTL_ADD_PROC(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, acpi_ibm_sysctls[i].name, CTLTYPE_INT | CTLFLAG_RD, sc, i, acpi_ibm_sysctl, "I", acpi_ibm_sysctls[i].description); } else { SYSCTL_ADD_PROC(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, acpi_ibm_sysctls[i].name, CTLTYPE_INT | CTLFLAG_RW, sc, i, acpi_ibm_sysctl, "I", acpi_ibm_sysctls[i].description); } } /* Hook up thermal node */ if (acpi_ibm_sysctl_init(sc, ACPI_IBM_METHOD_THERMAL)) { SYSCTL_ADD_PROC(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "thermal", CTLTYPE_INT | CTLFLAG_RD, sc, 0, acpi_ibm_thermal_sysctl, "I", "Thermal zones"); } /* Hook up handlerevents node */ if (acpi_ibm_sysctl_init(sc, ACPI_IBM_METHOD_HANDLEREVENTS)) { SYSCTL_ADD_PROC(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "handlerevents", CTLTYPE_STRING | CTLFLAG_RW, sc, 0, acpi_ibm_handlerevents_sysctl, "I", "devd(8) events handled by acpi_ibm"); } /* Handle notifies */ AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, acpi_ibm_notify, dev); /* Hook up light to led(4) */ if (sc->light_set_supported) sc->led_dev = led_create_state(ibm_led, sc, "thinklight", (sc->light_val ? 1 : 0)); /* Enable per-model events. */ maker = kern_getenv("smbios.system.maker"); product = kern_getenv("smbios.system.product"); if (maker == NULL || product == NULL) goto nosmbios; for (i = 0; i < nitems(acpi_ibm_models); i++) { if (strcmp(maker, acpi_ibm_models[i].maker) == 0 && strcmp(product, acpi_ibm_models[i].product) == 0) { ACPI_SERIAL_BEGIN(ibm); acpi_ibm_sysctl_set(sc, ACPI_IBM_METHOD_EVENTMASK, acpi_ibm_models[i].eventmask); ACPI_SERIAL_END(ibm); } } nosmbios: freeenv(maker); freeenv(product); /* Enable events by default. */ ACPI_SERIAL_BEGIN(ibm); acpi_ibm_sysctl_set(sc, ACPI_IBM_METHOD_EVENTS, 1); ACPI_SERIAL_END(ibm); return (0); } static int acpi_ibm_detach(device_t dev) { ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); struct acpi_ibm_softc *sc = device_get_softc(dev); /* Disable events and restore eventmask */ ACPI_SERIAL_BEGIN(ibm); acpi_ibm_sysctl_set(sc, ACPI_IBM_METHOD_EVENTS, 0); acpi_ibm_sysctl_set(sc, ACPI_IBM_METHOD_EVENTMASK, sc->events_initialmask); ACPI_SERIAL_END(ibm); AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, acpi_ibm_notify); if (sc->led_dev != NULL) led_destroy(sc->led_dev); return (0); } static int acpi_ibm_resume(device_t dev) { struct acpi_ibm_softc *sc = device_get_softc(dev); ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); ACPI_SERIAL_BEGIN(ibm); for (int i = 0; acpi_ibm_sysctls[i].name != NULL; i++) { int val; val = acpi_ibm_sysctl_get(sc, i); if (acpi_ibm_sysctls[i].flag_rdonly != 0) continue; acpi_ibm_sysctl_set(sc, i, val); } ACPI_SERIAL_END(ibm); return (0); } static int acpi_ibm_eventmask_set(struct acpi_ibm_softc *sc, int val) { ACPI_OBJECT arg[2]; ACPI_OBJECT_LIST args; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(ibm); args.Count = 2; args.Pointer = arg; arg[0].Type = ACPI_TYPE_INTEGER; arg[1].Type = ACPI_TYPE_INTEGER; for (int i = 0; i < 32; ++i) { arg[0].Integer.Value = i+1; arg[1].Integer.Value = (((1 << i) & val) != 0); status = AcpiEvaluateObject(sc->handle, IBM_NAME_EVENTS_MASK_SET, &args, NULL); if (ACPI_FAILURE(status)) return (status); } return (0); } static int acpi_ibm_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_ibm_softc *sc; int arg; int error = 0; int function; int method; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_ibm_softc *)oidp->oid_arg1; function = oidp->oid_arg2; method = acpi_ibm_sysctls[function].method; ACPI_SERIAL_BEGIN(ibm); arg = acpi_ibm_sysctl_get(sc, method); error = sysctl_handle_int(oidp, &arg, 0, req); /* Sanity check */ if (error != 0 || req->newptr == NULL) goto out; /* Update */ error = acpi_ibm_sysctl_set(sc, method, arg); out: ACPI_SERIAL_END(ibm); return (error); } static int acpi_ibm_sysctl_get(struct acpi_ibm_softc *sc, int method) { UINT64 val_ec; int val = 0, key; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(ibm); switch (method) { case ACPI_IBM_METHOD_EVENTS: acpi_GetInteger(sc->handle, IBM_NAME_EVENTS_STATUS_GET, &val); break; case ACPI_IBM_METHOD_EVENTMASK: if (sc->events_mask_supported) acpi_GetInteger(sc->handle, IBM_NAME_EVENTS_MASK_GET, &val); break; case ACPI_IBM_METHOD_HOTKEY: /* * Construct the hotkey as a bitmask as illustrated below. * Note that whenever a key was pressed, the respecting bit * toggles and nothing else changes. * +--+--+-+-+-+-+-+-+-+-+-+-+ * |11|10|9|8|7|6|5|4|3|2|1|0| * +--+--+-+-+-+-+-+-+-+-+-+-+ * | | | | | | | | | | | | * | | | | | | | | | | | +- Home Button * | | | | | | | | | | +--- Search Button * | | | | | | | | | +----- Mail Button * | | | | | | | | +------- Thinkpad Button * | | | | | | | +--------- Zoom (Fn + Space) * | | | | | | +----------- WLAN Button * | | | | | +------------- Video Button * | | | | +--------------- Hibernate Button * | | | +----------------- Thinklight Button * | | +------------------- Screen expand (Fn + F8) * | +--------------------- Brightness * +------------------------ Volume/Mute */ key = rtcin(IBM_RTC_HOTKEY1); val = (IBM_RTC_MASK_HOME | IBM_RTC_MASK_SEARCH | IBM_RTC_MASK_MAIL | IBM_RTC_MASK_WLAN) & key; key = rtcin(IBM_RTC_HOTKEY2); val |= (IBM_RTC_MASK_THINKPAD | IBM_RTC_MASK_VIDEO | IBM_RTC_MASK_HIBERNATE) & key; val |= (IBM_RTC_MASK_ZOOM & key) >> 1; key = rtcin(IBM_RTC_THINKLIGHT); val |= (IBM_RTC_MASK_THINKLIGHT & key) << 4; key = rtcin(IBM_RTC_SCREENEXPAND); val |= (IBM_RTC_MASK_THINKLIGHT & key) << 4; key = rtcin(IBM_RTC_BRIGHTNESS); val |= (IBM_RTC_MASK_BRIGHTNESS & key) << 5; key = rtcin(IBM_RTC_VOLUME); val |= (IBM_RTC_MASK_VOLUME & key) << 4; break; case ACPI_IBM_METHOD_BRIGHTNESS: ACPI_EC_READ(sc->ec_dev, IBM_EC_BRIGHTNESS, &val_ec, 1); val = val_ec & IBM_EC_MASK_BRI; break; case ACPI_IBM_METHOD_VOLUME: ACPI_EC_READ(sc->ec_dev, IBM_EC_VOLUME, &val_ec, 1); val = val_ec & IBM_EC_MASK_VOL; break; case ACPI_IBM_METHOD_MUTE: ACPI_EC_READ(sc->ec_dev, IBM_EC_VOLUME, &val_ec, 1); val = ((val_ec & IBM_EC_MASK_MUTE) == IBM_EC_MASK_MUTE); break; case ACPI_IBM_METHOD_THINKLIGHT: if (sc->light_get_supported) acpi_GetInteger(sc->ec_handle, IBM_NAME_KEYLIGHT, &val); else val = sc->light_val; break; case ACPI_IBM_METHOD_BLUETOOTH: acpi_GetInteger(sc->handle, IBM_NAME_WLAN_BT_GET, &val); sc->wlan_bt_flags = val; val = ((val & IBM_NAME_MASK_BT) != 0); break; case ACPI_IBM_METHOD_WLAN: acpi_GetInteger(sc->handle, IBM_NAME_WLAN_BT_GET, &val); sc->wlan_bt_flags = val; val = ((val & IBM_NAME_MASK_WLAN) != 0); break; case ACPI_IBM_METHOD_FANSPEED: if (sc->fan_handle) { if(ACPI_FAILURE(acpi_GetInteger(sc->fan_handle, NULL, &val))) val = -1; } else { ACPI_EC_READ(sc->ec_dev, IBM_EC_FANSPEED, &val_ec, 2); val = val_ec; } break; case ACPI_IBM_METHOD_FANLEVEL: /* * The IBM_EC_FANSTATUS register works as follows: * Bit 0-5 indicate the level at which the fan operates. Only * values between 0 and 7 have an effect. Everything * above 7 is treated the same as level 7 * Bit 6 overrides the fan speed limit if set to 1 * Bit 7 indicates at which mode the fan operates: * manual (0) or automatic (1) */ if (!sc->fan_handle) { ACPI_EC_READ(sc->ec_dev, IBM_EC_FANSTATUS, &val_ec, 1); val = val_ec & IBM_EC_MASK_FANLEVEL; } break; case ACPI_IBM_METHOD_FANSTATUS: if (!sc->fan_handle) { ACPI_EC_READ(sc->ec_dev, IBM_EC_FANSTATUS, &val_ec, 1); val = (val_ec & IBM_EC_MASK_FANSTATUS) == IBM_EC_MASK_FANSTATUS; } else val = -1; break; } return (val); } static int acpi_ibm_sysctl_set(struct acpi_ibm_softc *sc, int method, int arg) { int val; UINT64 val_ec; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(ibm); switch (method) { case ACPI_IBM_METHOD_EVENTS: if (arg < 0 || arg > 1) return (EINVAL); status = acpi_SetInteger(sc->handle, IBM_NAME_EVENTS_STATUS_SET, arg); if (ACPI_FAILURE(status)) return (status); if (sc->events_mask_supported) return acpi_ibm_eventmask_set(sc, sc->events_availmask); break; case ACPI_IBM_METHOD_EVENTMASK: if (sc->events_mask_supported) return acpi_ibm_eventmask_set(sc, arg); break; case ACPI_IBM_METHOD_BRIGHTNESS: return acpi_ibm_brightness_set(sc, arg); break; case ACPI_IBM_METHOD_VOLUME: return acpi_ibm_volume_set(sc, arg); break; case ACPI_IBM_METHOD_MUTE: return acpi_ibm_mute_set(sc, arg); break; case ACPI_IBM_METHOD_THINKLIGHT: return acpi_ibm_thinklight_set(sc, arg); break; case ACPI_IBM_METHOD_BLUETOOTH: return acpi_ibm_bluetooth_set(sc, arg); break; case ACPI_IBM_METHOD_FANLEVEL: if (arg < 0 || arg > 7) return (EINVAL); if (!sc->fan_handle) { /* Read the current fanstatus */ ACPI_EC_READ(sc->ec_dev, IBM_EC_FANSTATUS, &val_ec, 1); val = val_ec & (~IBM_EC_MASK_FANLEVEL); return ACPI_EC_WRITE(sc->ec_dev, IBM_EC_FANSTATUS, val | arg, 1); } break; case ACPI_IBM_METHOD_FANSTATUS: if (arg < 0 || arg > 1) return (EINVAL); if (!sc->fan_handle) { /* Read the current fanstatus */ ACPI_EC_READ(sc->ec_dev, IBM_EC_FANSTATUS, &val_ec, 1); return ACPI_EC_WRITE(sc->ec_dev, IBM_EC_FANSTATUS, (arg == 1) ? (val_ec | IBM_EC_MASK_FANSTATUS) : (val_ec & (~IBM_EC_MASK_FANSTATUS)), 1); } break; } return (0); } static int acpi_ibm_sysctl_init(struct acpi_ibm_softc *sc, int method) { int dummy; ACPI_OBJECT_TYPE cmos_t; ACPI_HANDLE ledb_handle; switch (method) { case ACPI_IBM_METHOD_EVENTS: return (TRUE); case ACPI_IBM_METHOD_EVENTMASK: return (sc->events_mask_supported); case ACPI_IBM_METHOD_HOTKEY: case ACPI_IBM_METHOD_BRIGHTNESS: case ACPI_IBM_METHOD_VOLUME: case ACPI_IBM_METHOD_MUTE: /* EC is required here, which was already checked before */ return (TRUE); case ACPI_IBM_METHOD_THINKLIGHT: sc->cmos_handle = NULL; sc->light_get_supported = ACPI_SUCCESS(acpi_GetInteger( sc->ec_handle, IBM_NAME_KEYLIGHT, &sc->light_val)); if ((ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\UCMS", &sc->light_handle)) || ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\CMOS", &sc->light_handle)) || ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\CMS", &sc->light_handle))) && ACPI_SUCCESS(AcpiGetType(sc->light_handle, &cmos_t)) && cmos_t == ACPI_TYPE_METHOD) { sc->light_cmd_on = 0x0c; sc->light_cmd_off = 0x0d; sc->cmos_handle = sc->light_handle; } else if (ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\LGHT", &sc->light_handle))) { sc->light_cmd_on = 1; sc->light_cmd_off = 0; } else sc->light_handle = NULL; sc->light_set_supported = (sc->light_handle && ACPI_FAILURE(AcpiGetHandle(sc->ec_handle, "LEDB", &ledb_handle))); if (sc->light_get_supported) return (TRUE); if (sc->light_set_supported) { sc->light_val = 0; return (TRUE); } return (FALSE); case ACPI_IBM_METHOD_BLUETOOTH: case ACPI_IBM_METHOD_WLAN: if (ACPI_SUCCESS(acpi_GetInteger(sc->handle, IBM_NAME_WLAN_BT_GET, &dummy))) return (TRUE); return (FALSE); case ACPI_IBM_METHOD_FANSPEED: /* * Some models report the fan speed in levels from 0-7 * Newer models report it contiguously */ sc->fan_levels = (ACPI_SUCCESS(AcpiGetHandle(sc->handle, "GFAN", &sc->fan_handle)) || ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\FSPD", &sc->fan_handle))); return (TRUE); case ACPI_IBM_METHOD_FANLEVEL: case ACPI_IBM_METHOD_FANSTATUS: /* * Fan status is only supported on those models, * which report fan RPM contiguously, not in levels */ if (sc->fan_levels) return (FALSE); return (TRUE); case ACPI_IBM_METHOD_THERMAL: if (ACPI_SUCCESS(acpi_GetInteger(sc->ec_handle, IBM_NAME_THERMAL_GET, &dummy))) { sc->thermal_updt_supported = ACPI_SUCCESS(acpi_GetInteger(sc->ec_handle, IBM_NAME_THERMAL_UPDT, &dummy)); return (TRUE); } return (FALSE); case ACPI_IBM_METHOD_HANDLEREVENTS: return (TRUE); } return (FALSE); } static int acpi_ibm_thermal_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_ibm_softc *sc; int error = 0; char temp_cmd[] = "TMP0"; int temp[8]; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_ibm_softc *)oidp->oid_arg1; ACPI_SERIAL_BEGIN(ibm); for (int i = 0; i < 8; ++i) { temp_cmd[3] = '0' + i; /* * The TMPx methods seem to return +/- 128 or 0 * when the respecting sensor is not available */ if (ACPI_FAILURE(acpi_GetInteger(sc->ec_handle, temp_cmd, &temp[i])) || ABS(temp[i]) == 128 || temp[i] == 0) temp[i] = -1; else if (sc->thermal_updt_supported) /* Temperature is reported in tenth of Kelvin */ - temp[i] = (temp[i] - 2732 + 5) / 10; + temp[i] = (temp[i] - 2731 + 5) / 10; } error = sysctl_handle_opaque(oidp, &temp, 8*sizeof(int), req); ACPI_SERIAL_END(ibm); return (error); } static int acpi_ibm_handlerevents_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_ibm_softc *sc; int error = 0; struct sbuf sb; char *cp, *ep; int l, val; unsigned int handler_events; char temp[128]; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_ibm_softc *)oidp->oid_arg1; if (sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND) == NULL) return (ENOMEM); ACPI_SERIAL_BEGIN(ibm); /* Get old values if this is a get request. */ if (req->newptr == NULL) { for (int i = 0; i < 8 * sizeof(sc->handler_events); i++) if (sc->handler_events & (1 << i)) sbuf_printf(&sb, "0x%02x ", i + 1); if (sbuf_len(&sb) == 0) sbuf_printf(&sb, "NONE"); } sbuf_trim(&sb); sbuf_finish(&sb); strlcpy(temp, sbuf_data(&sb), sizeof(temp)); sbuf_delete(&sb); error = sysctl_handle_string(oidp, temp, sizeof(temp), req); /* Check for error or no change */ if (error != 0 || req->newptr == NULL) goto out; /* If the user is setting a string, parse it. */ handler_events = 0; cp = temp; while (*cp) { if (isspace(*cp)) { cp++; continue; } ep = cp; while (*ep && !isspace(*ep)) ep++; l = ep - cp; if (l == 0) break; if (strncmp(cp, "NONE", 4) == 0) { cp = ep; continue; } if (l >= 3 && cp[0] == '0' && (cp[1] == 'X' || cp[1] == 'x')) val = strtoul(cp, &ep, 16); else val = strtoul(cp, &ep, 10); if (val == 0 || ep == cp || val >= 8 * sizeof(handler_events)) { cp[l] = '\0'; device_printf(sc->dev, "invalid event code: %s\n", cp); error = EINVAL; goto out; } handler_events |= 1 << (val - 1); cp = ep; } sc->handler_events = handler_events; out: ACPI_SERIAL_END(ibm); return (error); } static int acpi_ibm_brightness_set(struct acpi_ibm_softc *sc, int arg) { int val, step; UINT64 val_ec; ACPI_OBJECT Arg; ACPI_OBJECT_LIST Args; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(ibm); if (arg < 0 || arg > 7) return (EINVAL); /* Read the current brightness */ status = ACPI_EC_READ(sc->ec_dev, IBM_EC_BRIGHTNESS, &val_ec, 1); if (ACPI_FAILURE(status)) return (status); if (sc->cmos_handle) { val = val_ec & IBM_EC_MASK_BRI; Args.Count = 1; Args.Pointer = &Arg; Arg.Type = ACPI_TYPE_INTEGER; Arg.Integer.Value = (arg > val) ? IBM_CMOS_BRIGHTNESS_UP : IBM_CMOS_BRIGHTNESS_DOWN; step = (arg > val) ? 1 : -1; for (int i = val; i != arg; i += step) { status = AcpiEvaluateObject(sc->cmos_handle, NULL, &Args, NULL); if (ACPI_FAILURE(status)) { /* Record the last value */ if (i != val) { ACPI_EC_WRITE(sc->ec_dev, IBM_EC_BRIGHTNESS, i - step, 1); } return (status); } } } return ACPI_EC_WRITE(sc->ec_dev, IBM_EC_BRIGHTNESS, arg, 1); } static int acpi_ibm_bluetooth_set(struct acpi_ibm_softc *sc, int arg) { int val; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(ibm); if (arg < 0 || arg > 1) return (EINVAL); val = (arg == 1) ? sc->wlan_bt_flags | IBM_NAME_MASK_BT : sc->wlan_bt_flags & (~IBM_NAME_MASK_BT); return acpi_SetInteger(sc->handle, IBM_NAME_WLAN_BT_SET, val); } static int acpi_ibm_thinklight_set(struct acpi_ibm_softc *sc, int arg) { ACPI_OBJECT Arg; ACPI_OBJECT_LIST Args; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(ibm); if (arg < 0 || arg > 1) return (EINVAL); if (sc->light_set_supported) { Args.Count = 1; Args.Pointer = &Arg; Arg.Type = ACPI_TYPE_INTEGER; Arg.Integer.Value = arg ? sc->light_cmd_on : sc->light_cmd_off; status = AcpiEvaluateObject(sc->light_handle, NULL, &Args, NULL); if (ACPI_SUCCESS(status)) sc->light_val = arg; return (status); } return (0); } static int acpi_ibm_volume_set(struct acpi_ibm_softc *sc, int arg) { int val, step; UINT64 val_ec; ACPI_OBJECT Arg; ACPI_OBJECT_LIST Args; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(ibm); if (arg < 0 || arg > 14) return (EINVAL); /* Read the current volume */ status = ACPI_EC_READ(sc->ec_dev, IBM_EC_VOLUME, &val_ec, 1); if (ACPI_FAILURE(status)) return (status); if (sc->cmos_handle) { val = val_ec & IBM_EC_MASK_VOL; Args.Count = 1; Args.Pointer = &Arg; Arg.Type = ACPI_TYPE_INTEGER; Arg.Integer.Value = (arg > val) ? IBM_CMOS_VOLUME_UP : IBM_CMOS_VOLUME_DOWN; step = (arg > val) ? 1 : -1; for (int i = val; i != arg; i += step) { status = AcpiEvaluateObject(sc->cmos_handle, NULL, &Args, NULL); if (ACPI_FAILURE(status)) { /* Record the last value */ if (i != val) { val_ec = i - step + (val_ec & (~IBM_EC_MASK_VOL)); ACPI_EC_WRITE(sc->ec_dev, IBM_EC_VOLUME, val_ec, 1); } return (status); } } } val_ec = arg + (val_ec & (~IBM_EC_MASK_VOL)); return ACPI_EC_WRITE(sc->ec_dev, IBM_EC_VOLUME, val_ec, 1); } static int acpi_ibm_mute_set(struct acpi_ibm_softc *sc, int arg) { UINT64 val_ec; ACPI_OBJECT Arg; ACPI_OBJECT_LIST Args; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(ibm); if (arg < 0 || arg > 1) return (EINVAL); status = ACPI_EC_READ(sc->ec_dev, IBM_EC_VOLUME, &val_ec, 1); if (ACPI_FAILURE(status)) return (status); if (sc->cmos_handle) { Args.Count = 1; Args.Pointer = &Arg; Arg.Type = ACPI_TYPE_INTEGER; Arg.Integer.Value = IBM_CMOS_VOLUME_MUTE; status = AcpiEvaluateObject(sc->cmos_handle, NULL, &Args, NULL); if (ACPI_FAILURE(status)) return (status); } val_ec = (arg == 1) ? val_ec | IBM_EC_MASK_MUTE : val_ec & (~IBM_EC_MASK_MUTE); return ACPI_EC_WRITE(sc->ec_dev, IBM_EC_VOLUME, val_ec, 1); } static void acpi_ibm_eventhandler(struct acpi_ibm_softc *sc, int arg) { int val; UINT64 val_ec; ACPI_STATUS status; ACPI_SERIAL_BEGIN(ibm); switch (arg) { case IBM_EVENT_SUSPEND_TO_RAM: power_pm_suspend(POWER_SLEEP_STATE_SUSPEND); break; case IBM_EVENT_BLUETOOTH: acpi_ibm_bluetooth_set(sc, (sc->wlan_bt_flags == 0)); break; case IBM_EVENT_BRIGHTNESS_UP: case IBM_EVENT_BRIGHTNESS_DOWN: /* Read the current brightness */ status = ACPI_EC_READ(sc->ec_dev, IBM_EC_BRIGHTNESS, &val_ec, 1); if (ACPI_FAILURE(status)) return; val = val_ec & IBM_EC_MASK_BRI; val = (arg == IBM_EVENT_BRIGHTNESS_UP) ? val + 1 : val - 1; acpi_ibm_brightness_set(sc, val); break; case IBM_EVENT_THINKLIGHT: acpi_ibm_thinklight_set(sc, (sc->light_val == 0)); break; case IBM_EVENT_VOLUME_UP: case IBM_EVENT_VOLUME_DOWN: /* Read the current volume */ status = ACPI_EC_READ(sc->ec_dev, IBM_EC_VOLUME, &val_ec, 1); if (ACPI_FAILURE(status)) return; val = val_ec & IBM_EC_MASK_VOL; val = (arg == IBM_EVENT_VOLUME_UP) ? val + 1 : val - 1; acpi_ibm_volume_set(sc, val); break; case IBM_EVENT_MUTE: /* Read the current value */ status = ACPI_EC_READ(sc->ec_dev, IBM_EC_VOLUME, &val_ec, 1); if (ACPI_FAILURE(status)) return; val = ((val_ec & IBM_EC_MASK_MUTE) == IBM_EC_MASK_MUTE); acpi_ibm_mute_set(sc, (val == 0)); break; default: break; } ACPI_SERIAL_END(ibm); } static void acpi_ibm_notify(ACPI_HANDLE h, UINT32 notify, void *context) { int event, arg, type; device_t dev = context; struct acpi_ibm_softc *sc = device_get_softc(dev); ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify); if (notify != 0x80) device_printf(dev, "Unknown notify\n"); for (;;) { acpi_GetInteger(acpi_get_handle(dev), IBM_NAME_EVENTS_GET, &event); if (event == 0) break; type = (event >> 12) & 0xf; arg = event & 0xfff; switch (type) { case 1: if (!(sc->events_availmask & (1 << (arg - 1)))) { device_printf(dev, "Unknown key %d\n", arg); break; } /* Execute event handler */ if (sc->handler_events & (1 << (arg - 1))) acpi_ibm_eventhandler(sc, (arg & 0xff)); /* Notify devd(8) */ acpi_UserNotify("IBM", h, (arg & 0xff)); break; default: break; } } } Index: head/sys/dev/acpi_support/atk0110.c =================================================================== --- head/sys/dev/acpi_support/atk0110.c (revision 300420) +++ head/sys/dev/acpi_support/atk0110.c (revision 300421) @@ -1,358 +1,358 @@ /* $NetBSD: atk0110.c,v 1.4 2010/02/11 06:54:57 cnst Exp $ */ /* $OpenBSD: atk0110.c,v 1.1 2009/07/23 01:38:16 cnst Exp $ */ /* * Copyright (c) 2009, 2010 Constantine A. Murenin * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include /* * ASUSTeK AI Booster (ACPI ASOC ATK0110). * * This code was originally written for OpenBSD after the techniques * described in the Linux's asus_atk0110.c and FreeBSD's Takanori Watanabe's * acpi_aiboost.c were verified to be accurate on the actual hardware kindly * provided by Sam Fourman Jr. It was subsequently ported from OpenBSD to * DragonFly BSD, to NetBSD's sysmon_envsys(9) and to FreeBSD's sysctl(9). * * -- Constantine A. Murenin */ #define _COMPONENT ACPI_OEM ACPI_MODULE_NAME("aibs"); ACPI_SERIAL_DECL(aibs, "aibs"); #define AIBS_MORE_SENSORS #define AIBS_VERBOSE enum aibs_type { AIBS_VOLT, AIBS_TEMP, AIBS_FAN }; struct aibs_sensor { ACPI_INTEGER v; ACPI_INTEGER i; ACPI_INTEGER l; ACPI_INTEGER h; enum aibs_type t; }; struct aibs_softc { device_t sc_dev; ACPI_HANDLE sc_ah; struct aibs_sensor *sc_asens_volt; struct aibs_sensor *sc_asens_temp; struct aibs_sensor *sc_asens_fan; }; static int aibs_probe(device_t); static int aibs_attach(device_t); static int aibs_detach(device_t); static int aibs_sysctl(SYSCTL_HANDLER_ARGS); static void aibs_attach_sif(struct aibs_softc *, enum aibs_type); static device_method_t aibs_methods[] = { DEVMETHOD(device_probe, aibs_probe), DEVMETHOD(device_attach, aibs_attach), DEVMETHOD(device_detach, aibs_detach), { NULL, NULL } }; static driver_t aibs_driver = { "aibs", aibs_methods, sizeof(struct aibs_softc) }; static devclass_t aibs_devclass; DRIVER_MODULE(aibs, acpi, aibs_driver, aibs_devclass, NULL, NULL); MODULE_DEPEND(aibs, acpi, 1, 1, 1); static char* aibs_hids[] = { "ATK0110", NULL }; static int aibs_probe(device_t dev) { if (acpi_disabled("aibs") || ACPI_ID_PROBE(device_get_parent(dev), dev, aibs_hids) == NULL) return ENXIO; device_set_desc(dev, "ASUSTeK AI Booster (ACPI ASOC ATK0110)"); return 0; } static int aibs_attach(device_t dev) { struct aibs_softc *sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_ah = acpi_get_handle(dev); aibs_attach_sif(sc, AIBS_VOLT); aibs_attach_sif(sc, AIBS_TEMP); aibs_attach_sif(sc, AIBS_FAN); return 0; } static void aibs_attach_sif(struct aibs_softc *sc, enum aibs_type st) { ACPI_STATUS s; ACPI_BUFFER b; ACPI_OBJECT *bp, *o; int i, n; const char *node; char name[] = "?SIF"; struct aibs_sensor *as; struct sysctl_oid *so; switch (st) { case AIBS_VOLT: node = "volt"; name[0] = 'V'; break; case AIBS_TEMP: node = "temp"; name[0] = 'T'; break; case AIBS_FAN: node = "fan"; name[0] = 'F'; break; default: return; } b.Length = ACPI_ALLOCATE_BUFFER; s = AcpiEvaluateObjectTyped(sc->sc_ah, name, NULL, &b, ACPI_TYPE_PACKAGE); if (ACPI_FAILURE(s)) { device_printf(sc->sc_dev, "%s not found\n", name); return; } bp = b.Pointer; o = bp->Package.Elements; if (o[0].Type != ACPI_TYPE_INTEGER) { device_printf(sc->sc_dev, "%s[0]: invalid type\n", name); AcpiOsFree(b.Pointer); return; } n = o[0].Integer.Value; if (bp->Package.Count - 1 < n) { device_printf(sc->sc_dev, "%s: invalid package\n", name); AcpiOsFree(b.Pointer); return; } else if (bp->Package.Count - 1 > n) { int on = n; #ifdef AIBS_MORE_SENSORS n = bp->Package.Count - 1; #endif device_printf(sc->sc_dev, "%s: malformed package: %i/%i" ", assume %i\n", name, on, bp->Package.Count - 1, n); } if (n < 1) { device_printf(sc->sc_dev, "%s: no members in the package\n", name); AcpiOsFree(b.Pointer); return; } as = malloc(sizeof(*as) * n, M_DEVBUF, M_NOWAIT | M_ZERO); if (as == NULL) { device_printf(sc->sc_dev, "%s: malloc fail\n", name); AcpiOsFree(b.Pointer); return; } switch (st) { case AIBS_VOLT: sc->sc_asens_volt = as; break; case AIBS_TEMP: sc->sc_asens_temp = as; break; case AIBS_FAN: sc->sc_asens_fan = as; break; } /* sysctl subtree for sensors of this type */ so = SYSCTL_ADD_NODE(device_get_sysctl_ctx(sc->sc_dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev)), st, node, CTLFLAG_RD, NULL, NULL); for (i = 0, o++; i < n; i++, o++) { ACPI_OBJECT *oi; char si[3]; const char *desc; /* acpica5 automatically evaluates the referenced package */ if (o[0].Type != ACPI_TYPE_PACKAGE) { device_printf(sc->sc_dev, "%s: %i: not a package: %i type\n", name, i, o[0].Type); continue; } oi = o[0].Package.Elements; if (o[0].Package.Count != 5 || oi[0].Type != ACPI_TYPE_INTEGER || oi[1].Type != ACPI_TYPE_STRING || oi[2].Type != ACPI_TYPE_INTEGER || oi[3].Type != ACPI_TYPE_INTEGER || oi[4].Type != ACPI_TYPE_INTEGER) { device_printf(sc->sc_dev, "%s: %i: invalid package\n", name, i); continue; } as[i].i = oi[0].Integer.Value; desc = oi[1].String.Pointer; as[i].l = oi[2].Integer.Value; as[i].h = oi[3].Integer.Value; as[i].t = st; #ifdef AIBS_VERBOSE device_printf(sc->sc_dev, "%c%i: " "0x%08"PRIx64" %20s %5"PRIi64" / %5"PRIi64" " "0x%"PRIx64"\n", name[0], i, (uint64_t)as[i].i, desc, (int64_t)as[i].l, (int64_t)as[i].h, (uint64_t)oi[4].Integer.Value); #endif snprintf(si, sizeof(si), "%i", i); SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->sc_dev), SYSCTL_CHILDREN(so), i, si, CTLTYPE_INT | CTLFLAG_RD, sc, st, aibs_sysctl, st == AIBS_TEMP ? "IK" : "I", desc); } AcpiOsFree(b.Pointer); } static int aibs_detach(device_t dev) { struct aibs_softc *sc = device_get_softc(dev); if (sc->sc_asens_volt != NULL) free(sc->sc_asens_volt, M_DEVBUF); if (sc->sc_asens_temp != NULL) free(sc->sc_asens_temp, M_DEVBUF); if (sc->sc_asens_fan != NULL) free(sc->sc_asens_fan, M_DEVBUF); return 0; } #ifdef AIBS_VERBOSE #define ddevice_printf(x...) device_printf(x) #else #define ddevice_printf(x...) #endif static int aibs_sysctl(SYSCTL_HANDLER_ARGS) { struct aibs_softc *sc = arg1; enum aibs_type st = arg2; int i = oidp->oid_number; ACPI_STATUS rs; ACPI_OBJECT p, *bp; ACPI_OBJECT_LIST mp; ACPI_BUFFER b; char *name; struct aibs_sensor *as; ACPI_INTEGER v, l, h; int so[3]; switch (st) { case AIBS_VOLT: name = "RVLT"; as = sc->sc_asens_volt; break; case AIBS_TEMP: name = "RTMP"; as = sc->sc_asens_temp; break; case AIBS_FAN: name = "RFAN"; as = sc->sc_asens_fan; break; default: return ENOENT; } if (as == NULL) return ENOENT; l = as[i].l; h = as[i].h; p.Type = ACPI_TYPE_INTEGER; p.Integer.Value = as[i].i; mp.Count = 1; mp.Pointer = &p; b.Length = ACPI_ALLOCATE_BUFFER; ACPI_SERIAL_BEGIN(aibs); rs = AcpiEvaluateObjectTyped(sc->sc_ah, name, &mp, &b, ACPI_TYPE_INTEGER); if (ACPI_FAILURE(rs)) { ddevice_printf(sc->sc_dev, "%s: %i: evaluation failed\n", name, i); ACPI_SERIAL_END(aibs); return EIO; } bp = b.Pointer; v = bp->Integer.Value; AcpiOsFree(b.Pointer); ACPI_SERIAL_END(aibs); switch (st) { case AIBS_VOLT: break; case AIBS_TEMP: - v += 2732; - l += 2732; - h += 2732; + v += 2731; + l += 2731; + h += 2731; break; case AIBS_FAN: break; } so[0] = v; so[1] = l; so[2] = h; return sysctl_handle_opaque(oidp, &so, sizeof(so), req); } Index: head/sys/dev/acpica/acpi_thermal.c =================================================================== --- head/sys/dev/acpica/acpi_thermal.c (revision 300420) +++ head/sys/dev/acpica/acpi_thermal.c (revision 300421) @@ -1,1214 +1,1214 @@ /*- * Copyright (c) 2000, 2001 Michael Smith * Copyright (c) 2000 BSDi * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "cpufreq_if.h" #include #include #include /* Hooks for the ACPI CA debugging infrastructure */ #define _COMPONENT ACPI_THERMAL ACPI_MODULE_NAME("THERMAL") -#define TZ_ZEROC 2732 +#define TZ_ZEROC 2731 #define TZ_KELVTOC(x) (((x) - TZ_ZEROC) / 10), abs(((x) - TZ_ZEROC) % 10) #define TZ_NOTIFY_TEMPERATURE 0x80 /* Temperature changed. */ #define TZ_NOTIFY_LEVELS 0x81 /* Cooling levels changed. */ #define TZ_NOTIFY_DEVICES 0x82 /* Device lists changed. */ #define TZ_NOTIFY_CRITICAL 0xcc /* Fake notify that _CRT/_HOT reached. */ /* Check for temperature changes every 10 seconds by default */ #define TZ_POLLRATE 10 /* Make sure the reported temperature is valid for this number of polls. */ #define TZ_VALIDCHECKS 3 /* Notify the user we will be shutting down in one more poll cycle. */ #define TZ_NOTIFYCOUNT (TZ_VALIDCHECKS - 1) /* ACPI spec defines this */ #define TZ_NUMLEVELS 10 struct acpi_tz_zone { int ac[TZ_NUMLEVELS]; ACPI_BUFFER al[TZ_NUMLEVELS]; int crt; int hot; ACPI_BUFFER psl; int psv; int tc1; int tc2; int tsp; int tzp; }; struct acpi_tz_softc { device_t tz_dev; ACPI_HANDLE tz_handle; /*Thermal zone handle*/ int tz_temperature; /*Current temperature*/ int tz_active; /*Current active cooling*/ #define TZ_ACTIVE_NONE -1 #define TZ_ACTIVE_UNKNOWN -2 int tz_requested; /*Minimum active cooling*/ int tz_thflags; /*Current temp-related flags*/ #define TZ_THFLAG_NONE 0 #define TZ_THFLAG_PSV (1<<0) #define TZ_THFLAG_HOT (1<<2) #define TZ_THFLAG_CRT (1<<3) int tz_flags; #define TZ_FLAG_NO_SCP (1<<0) /*No _SCP method*/ #define TZ_FLAG_GETPROFILE (1<<1) /*Get power_profile in timeout*/ #define TZ_FLAG_GETSETTINGS (1<<2) /*Get devs/setpoints*/ struct timespec tz_cooling_started; /*Current cooling starting time*/ struct sysctl_ctx_list tz_sysctl_ctx; struct sysctl_oid *tz_sysctl_tree; eventhandler_tag tz_event; struct acpi_tz_zone tz_zone; /*Thermal zone parameters*/ int tz_validchecks; int tz_insane_tmp_notified; /* passive cooling */ struct proc *tz_cooling_proc; int tz_cooling_proc_running; int tz_cooling_enabled; int tz_cooling_active; int tz_cooling_updated; int tz_cooling_saved_freq; }; #define TZ_ACTIVE_LEVEL(act) ((act) >= 0 ? (act) : TZ_NUMLEVELS) #define CPUFREQ_MAX_LEVELS 64 /* XXX cpufreq should export this */ static int acpi_tz_probe(device_t dev); static int acpi_tz_attach(device_t dev); static int acpi_tz_establish(struct acpi_tz_softc *sc); static void acpi_tz_monitor(void *Context); static void acpi_tz_switch_cooler_off(ACPI_OBJECT *obj, void *arg); static void acpi_tz_switch_cooler_on(ACPI_OBJECT *obj, void *arg); static void acpi_tz_getparam(struct acpi_tz_softc *sc, char *node, int *data); static void acpi_tz_sanity(struct acpi_tz_softc *sc, int *val, char *what); static int acpi_tz_active_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_tz_cooling_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_tz_temp_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_tz_passive_sysctl(SYSCTL_HANDLER_ARGS); static void acpi_tz_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context); static void acpi_tz_signal(struct acpi_tz_softc *sc, int flags); static void acpi_tz_timeout(struct acpi_tz_softc *sc, int flags); static void acpi_tz_power_profile(void *arg); static void acpi_tz_thread(void *arg); static int acpi_tz_cooling_is_available(struct acpi_tz_softc *sc); static int acpi_tz_cooling_thread_start(struct acpi_tz_softc *sc); static device_method_t acpi_tz_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_tz_probe), DEVMETHOD(device_attach, acpi_tz_attach), DEVMETHOD_END }; static driver_t acpi_tz_driver = { "acpi_tz", acpi_tz_methods, sizeof(struct acpi_tz_softc), }; static char *acpi_tz_tmp_name = "_TMP"; static devclass_t acpi_tz_devclass; DRIVER_MODULE(acpi_tz, acpi, acpi_tz_driver, acpi_tz_devclass, 0, 0); MODULE_DEPEND(acpi_tz, acpi, 1, 1, 1); static struct sysctl_ctx_list acpi_tz_sysctl_ctx; static struct sysctl_oid *acpi_tz_sysctl_tree; /* Minimum cooling run time */ static int acpi_tz_min_runtime; static int acpi_tz_polling_rate = TZ_POLLRATE; static int acpi_tz_override; /* Timezone polling thread */ static struct proc *acpi_tz_proc; ACPI_LOCK_DECL(thermal, "ACPI thermal zone"); static int acpi_tz_cooling_unit = -1; static int acpi_tz_probe(device_t dev) { int result; if (acpi_get_type(dev) == ACPI_TYPE_THERMAL && !acpi_disabled("thermal")) { device_set_desc(dev, "Thermal Zone"); result = -10; } else result = ENXIO; return (result); } static int acpi_tz_attach(device_t dev) { struct acpi_tz_softc *sc; struct acpi_softc *acpi_sc; int error; char oidname[8]; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); sc->tz_dev = dev; sc->tz_handle = acpi_get_handle(dev); sc->tz_requested = TZ_ACTIVE_NONE; sc->tz_active = TZ_ACTIVE_UNKNOWN; sc->tz_thflags = TZ_THFLAG_NONE; sc->tz_cooling_proc = NULL; sc->tz_cooling_proc_running = FALSE; sc->tz_cooling_active = FALSE; sc->tz_cooling_updated = FALSE; sc->tz_cooling_enabled = FALSE; /* * Parse the current state of the thermal zone and build control * structures. We don't need to worry about interference with the * control thread since we haven't fully attached this device yet. */ if ((error = acpi_tz_establish(sc)) != 0) return (error); /* * Register for any Notify events sent to this zone. */ AcpiInstallNotifyHandler(sc->tz_handle, ACPI_DEVICE_NOTIFY, acpi_tz_notify_handler, sc); /* * Create our sysctl nodes. * * XXX we need a mechanism for adding nodes under ACPI. */ if (device_get_unit(dev) == 0) { acpi_sc = acpi_device_get_parent_softc(dev); sysctl_ctx_init(&acpi_tz_sysctl_ctx); acpi_tz_sysctl_tree = SYSCTL_ADD_NODE(&acpi_tz_sysctl_ctx, SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO, "thermal", CTLFLAG_RD, 0, ""); SYSCTL_ADD_INT(&acpi_tz_sysctl_ctx, SYSCTL_CHILDREN(acpi_tz_sysctl_tree), OID_AUTO, "min_runtime", CTLFLAG_RW, &acpi_tz_min_runtime, 0, "minimum cooling run time in sec"); SYSCTL_ADD_INT(&acpi_tz_sysctl_ctx, SYSCTL_CHILDREN(acpi_tz_sysctl_tree), OID_AUTO, "polling_rate", CTLFLAG_RW, &acpi_tz_polling_rate, 0, "monitor polling interval in seconds"); SYSCTL_ADD_INT(&acpi_tz_sysctl_ctx, SYSCTL_CHILDREN(acpi_tz_sysctl_tree), OID_AUTO, "user_override", CTLFLAG_RW, &acpi_tz_override, 0, "allow override of thermal settings"); } sysctl_ctx_init(&sc->tz_sysctl_ctx); sprintf(oidname, "tz%d", device_get_unit(dev)); sc->tz_sysctl_tree = SYSCTL_ADD_NODE(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(acpi_tz_sysctl_tree), OID_AUTO, oidname, CTLFLAG_RD, 0, ""); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD, &sc->tz_temperature, 0, sysctl_handle_int, "IK", "current thermal zone temperature"); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "active", CTLTYPE_INT | CTLFLAG_RW, sc, 0, acpi_tz_active_sysctl, "I", "cooling is active"); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "passive_cooling", CTLTYPE_INT | CTLFLAG_RW, sc, 0, acpi_tz_cooling_sysctl, "I", "enable passive (speed reduction) cooling"); SYSCTL_ADD_INT(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "thermal_flags", CTLFLAG_RD, &sc->tz_thflags, 0, "thermal zone flags"); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "_PSV", CTLTYPE_INT | CTLFLAG_RW, sc, offsetof(struct acpi_tz_softc, tz_zone.psv), acpi_tz_temp_sysctl, "IK", "passive cooling temp setpoint"); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "_HOT", CTLTYPE_INT | CTLFLAG_RW, sc, offsetof(struct acpi_tz_softc, tz_zone.hot), acpi_tz_temp_sysctl, "IK", "too hot temp setpoint (suspend now)"); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "_CRT", CTLTYPE_INT | CTLFLAG_RW, sc, offsetof(struct acpi_tz_softc, tz_zone.crt), acpi_tz_temp_sysctl, "IK", "critical temp setpoint (shutdown now)"); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "_ACx", CTLTYPE_INT | CTLFLAG_RD, &sc->tz_zone.ac, sizeof(sc->tz_zone.ac), sysctl_handle_opaque, "IK", ""); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "_TC1", CTLTYPE_INT | CTLFLAG_RW, sc, offsetof(struct acpi_tz_softc, tz_zone.tc1), acpi_tz_passive_sysctl, "I", "thermal constant 1 for passive cooling"); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "_TC2", CTLTYPE_INT | CTLFLAG_RW, sc, offsetof(struct acpi_tz_softc, tz_zone.tc2), acpi_tz_passive_sysctl, "I", "thermal constant 2 for passive cooling"); SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), OID_AUTO, "_TSP", CTLTYPE_INT | CTLFLAG_RW, sc, offsetof(struct acpi_tz_softc, tz_zone.tsp), acpi_tz_passive_sysctl, "I", "thermal sampling period for passive cooling"); /* * Create thread to service all of the thermal zones. Register * our power profile event handler. */ sc->tz_event = EVENTHANDLER_REGISTER(power_profile_change, acpi_tz_power_profile, sc, 0); if (acpi_tz_proc == NULL) { error = kproc_create(acpi_tz_thread, NULL, &acpi_tz_proc, RFHIGHPID, 0, "acpi_thermal"); if (error != 0) { device_printf(sc->tz_dev, "could not create thread - %d", error); goto out; } } /* * Create a thread to handle passive cooling for 1st zone which * has _PSV, _TSP, _TC1 and _TC2. Users can enable it for other * zones manually for now. * * XXX We enable only one zone to avoid multiple zones conflict * with each other since cpufreq currently sets all CPUs to the * given frequency whereas it's possible for different thermal * zones to specify independent settings for multiple CPUs. */ if (acpi_tz_cooling_unit < 0 && acpi_tz_cooling_is_available(sc)) sc->tz_cooling_enabled = TRUE; if (sc->tz_cooling_enabled) { error = acpi_tz_cooling_thread_start(sc); if (error != 0) { sc->tz_cooling_enabled = FALSE; goto out; } acpi_tz_cooling_unit = device_get_unit(dev); } /* * Flag the event handler for a manual invocation by our timeout. * We defer it like this so that the rest of the subsystem has time * to come up. Don't bother evaluating/printing the temperature at * this point; on many systems it'll be bogus until the EC is running. */ sc->tz_flags |= TZ_FLAG_GETPROFILE; out: if (error != 0) { EVENTHANDLER_DEREGISTER(power_profile_change, sc->tz_event); AcpiRemoveNotifyHandler(sc->tz_handle, ACPI_DEVICE_NOTIFY, acpi_tz_notify_handler); sysctl_ctx_free(&sc->tz_sysctl_ctx); } return_VALUE (error); } /* * Parse the current state of this thermal zone and set up to use it. * * Note that we may have previous state, which will have to be discarded. */ static int acpi_tz_establish(struct acpi_tz_softc *sc) { ACPI_OBJECT *obj; int i; char nbuf[8]; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); /* Erase any existing state. */ for (i = 0; i < TZ_NUMLEVELS; i++) if (sc->tz_zone.al[i].Pointer != NULL) AcpiOsFree(sc->tz_zone.al[i].Pointer); if (sc->tz_zone.psl.Pointer != NULL) AcpiOsFree(sc->tz_zone.psl.Pointer); /* * XXX: We initialize only ACPI_BUFFER to avoid race condition * with passive cooling thread which refers psv, tc1, tc2 and tsp. */ bzero(sc->tz_zone.ac, sizeof(sc->tz_zone.ac)); bzero(sc->tz_zone.al, sizeof(sc->tz_zone.al)); bzero(&sc->tz_zone.psl, sizeof(sc->tz_zone.psl)); /* Evaluate thermal zone parameters. */ for (i = 0; i < TZ_NUMLEVELS; i++) { sprintf(nbuf, "_AC%d", i); acpi_tz_getparam(sc, nbuf, &sc->tz_zone.ac[i]); sprintf(nbuf, "_AL%d", i); sc->tz_zone.al[i].Length = ACPI_ALLOCATE_BUFFER; sc->tz_zone.al[i].Pointer = NULL; AcpiEvaluateObject(sc->tz_handle, nbuf, NULL, &sc->tz_zone.al[i]); obj = (ACPI_OBJECT *)sc->tz_zone.al[i].Pointer; if (obj != NULL) { /* Should be a package containing a list of power objects */ if (obj->Type != ACPI_TYPE_PACKAGE) { device_printf(sc->tz_dev, "%s has unknown type %d, rejecting\n", nbuf, obj->Type); return_VALUE (ENXIO); } } } acpi_tz_getparam(sc, "_CRT", &sc->tz_zone.crt); acpi_tz_getparam(sc, "_HOT", &sc->tz_zone.hot); sc->tz_zone.psl.Length = ACPI_ALLOCATE_BUFFER; sc->tz_zone.psl.Pointer = NULL; AcpiEvaluateObject(sc->tz_handle, "_PSL", NULL, &sc->tz_zone.psl); acpi_tz_getparam(sc, "_PSV", &sc->tz_zone.psv); acpi_tz_getparam(sc, "_TC1", &sc->tz_zone.tc1); acpi_tz_getparam(sc, "_TC2", &sc->tz_zone.tc2); acpi_tz_getparam(sc, "_TSP", &sc->tz_zone.tsp); acpi_tz_getparam(sc, "_TZP", &sc->tz_zone.tzp); /* * Sanity-check the values we've been given. * * XXX what do we do about systems that give us the same value for * more than one of these setpoints? */ acpi_tz_sanity(sc, &sc->tz_zone.crt, "_CRT"); acpi_tz_sanity(sc, &sc->tz_zone.hot, "_HOT"); acpi_tz_sanity(sc, &sc->tz_zone.psv, "_PSV"); for (i = 0; i < TZ_NUMLEVELS; i++) acpi_tz_sanity(sc, &sc->tz_zone.ac[i], "_ACx"); return_VALUE (0); } static char *aclevel_string[] = { "NONE", "_AC0", "_AC1", "_AC2", "_AC3", "_AC4", "_AC5", "_AC6", "_AC7", "_AC8", "_AC9" }; static __inline const char * acpi_tz_aclevel_string(int active) { if (active < -1 || active >= TZ_NUMLEVELS) return (aclevel_string[0]); return (aclevel_string[active + 1]); } /* * Get the current temperature. */ static int acpi_tz_get_temperature(struct acpi_tz_softc *sc) { int temp; ACPI_STATUS status; ACPI_FUNCTION_NAME ("acpi_tz_get_temperature"); /* Evaluate the thermal zone's _TMP method. */ status = acpi_GetInteger(sc->tz_handle, acpi_tz_tmp_name, &temp); if (ACPI_FAILURE(status)) { ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), "error fetching current temperature -- %s\n", AcpiFormatException(status)); return (FALSE); } /* Check it for validity. */ acpi_tz_sanity(sc, &temp, acpi_tz_tmp_name); if (temp == -1) return (FALSE); ACPI_DEBUG_PRINT((ACPI_DB_VALUES, "got %d.%dC\n", TZ_KELVTOC(temp))); sc->tz_temperature = temp; return (TRUE); } /* * Evaluate the condition of a thermal zone, take appropriate actions. */ static void acpi_tz_monitor(void *Context) { struct acpi_tz_softc *sc; struct timespec curtime; int temp; int i; int newactive, newflags; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_tz_softc *)Context; /* Get the current temperature. */ if (!acpi_tz_get_temperature(sc)) { /* XXX disable zone? go to max cooling? */ return_VOID; } temp = sc->tz_temperature; /* * Work out what we ought to be doing right now. * * Note that the _ACx levels sort from hot to cold. */ newactive = TZ_ACTIVE_NONE; for (i = TZ_NUMLEVELS - 1; i >= 0; i--) { if (sc->tz_zone.ac[i] != -1 && temp >= sc->tz_zone.ac[i]) newactive = i; } /* * We are going to get _ACx level down (colder side), but give a guaranteed * minimum cooling run time if requested. */ if (acpi_tz_min_runtime > 0 && sc->tz_active != TZ_ACTIVE_NONE && sc->tz_active != TZ_ACTIVE_UNKNOWN && (newactive == TZ_ACTIVE_NONE || newactive > sc->tz_active)) { getnanotime(&curtime); timespecsub(&curtime, &sc->tz_cooling_started); if (curtime.tv_sec < acpi_tz_min_runtime) newactive = sc->tz_active; } /* Handle user override of active mode */ if (sc->tz_requested != TZ_ACTIVE_NONE && (newactive == TZ_ACTIVE_NONE || sc->tz_requested < newactive)) newactive = sc->tz_requested; /* update temperature-related flags */ newflags = TZ_THFLAG_NONE; if (sc->tz_zone.psv != -1 && temp >= sc->tz_zone.psv) newflags |= TZ_THFLAG_PSV; if (sc->tz_zone.hot != -1 && temp >= sc->tz_zone.hot) newflags |= TZ_THFLAG_HOT; if (sc->tz_zone.crt != -1 && temp >= sc->tz_zone.crt) newflags |= TZ_THFLAG_CRT; /* If the active cooling state has changed, we have to switch things. */ if (sc->tz_active == TZ_ACTIVE_UNKNOWN) { /* * We don't know which cooling device is on or off, * so stop them all, because we now know which * should be on (if any). */ for (i = 0; i < TZ_NUMLEVELS; i++) { if (sc->tz_zone.al[i].Pointer != NULL) { acpi_ForeachPackageObject( (ACPI_OBJECT *)sc->tz_zone.al[i].Pointer, acpi_tz_switch_cooler_off, sc); } } /* now we know that all devices are off */ sc->tz_active = TZ_ACTIVE_NONE; } if (newactive != sc->tz_active) { /* Turn off unneeded cooling devices that are on, if any are */ for (i = TZ_ACTIVE_LEVEL(sc->tz_active); i < TZ_ACTIVE_LEVEL(newactive); i++) { acpi_ForeachPackageObject( (ACPI_OBJECT *)sc->tz_zone.al[i].Pointer, acpi_tz_switch_cooler_off, sc); } /* Turn on cooling devices that are required, if any are */ for (i = TZ_ACTIVE_LEVEL(sc->tz_active) - 1; i >= TZ_ACTIVE_LEVEL(newactive); i--) { acpi_ForeachPackageObject( (ACPI_OBJECT *)sc->tz_zone.al[i].Pointer, acpi_tz_switch_cooler_on, sc); } ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), "switched from %s to %s: %d.%dC\n", acpi_tz_aclevel_string(sc->tz_active), acpi_tz_aclevel_string(newactive), TZ_KELVTOC(temp)); sc->tz_active = newactive; getnanotime(&sc->tz_cooling_started); } /* XXX (de)activate any passive cooling that may be required. */ /* * If the temperature is at _HOT or _CRT, increment our event count. * If it has occurred enough times, shutdown the system. This is * needed because some systems will report an invalid high temperature * for one poll cycle. It is suspected this is due to the embedded * controller timing out. A typical value is 138C for one cycle on * a system that is otherwise 65C. * * If we're almost at that threshold, notify the user through devd(8). */ if ((newflags & (TZ_THFLAG_HOT | TZ_THFLAG_CRT)) != 0) { sc->tz_validchecks++; if (sc->tz_validchecks == TZ_VALIDCHECKS) { device_printf(sc->tz_dev, "WARNING - current temperature (%d.%dC) exceeds safe limits\n", TZ_KELVTOC(sc->tz_temperature)); shutdown_nice(RB_POWEROFF); } else if (sc->tz_validchecks == TZ_NOTIFYCOUNT) acpi_UserNotify("Thermal", sc->tz_handle, TZ_NOTIFY_CRITICAL); } else { sc->tz_validchecks = 0; } sc->tz_thflags = newflags; return_VOID; } /* * Given an object, verify that it's a reference to a device of some sort, * and try to switch it off. */ static void acpi_tz_switch_cooler_off(ACPI_OBJECT *obj, void *arg) { ACPI_HANDLE cooler; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); cooler = acpi_GetReference(NULL, obj); if (cooler == NULL) { ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "can't get handle\n")); return_VOID; } ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "called to turn %s off\n", acpi_name(cooler))); acpi_pwr_switch_consumer(cooler, ACPI_STATE_D3); return_VOID; } /* * Given an object, verify that it's a reference to a device of some sort, * and try to switch it on. * * XXX replication of off/on function code is bad. */ static void acpi_tz_switch_cooler_on(ACPI_OBJECT *obj, void *arg) { struct acpi_tz_softc *sc = (struct acpi_tz_softc *)arg; ACPI_HANDLE cooler; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); cooler = acpi_GetReference(NULL, obj); if (cooler == NULL) { ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "can't get handle\n")); return_VOID; } ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "called to turn %s on\n", acpi_name(cooler))); status = acpi_pwr_switch_consumer(cooler, ACPI_STATE_D0); if (ACPI_FAILURE(status)) { ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), "failed to activate %s - %s\n", acpi_name(cooler), AcpiFormatException(status)); } return_VOID; } /* * Read/debug-print a parameter, default it to -1. */ static void acpi_tz_getparam(struct acpi_tz_softc *sc, char *node, int *data) { ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (ACPI_FAILURE(acpi_GetInteger(sc->tz_handle, node, data))) { *data = -1; } else { ACPI_DEBUG_PRINT((ACPI_DB_VALUES, "%s.%s = %d\n", acpi_name(sc->tz_handle), node, *data)); } return_VOID; } /* * Sanity-check a temperature value. Assume that setpoints * should be between 0C and 200C. */ static void acpi_tz_sanity(struct acpi_tz_softc *sc, int *val, char *what) { if (*val != -1 && (*val < TZ_ZEROC || *val > TZ_ZEROC + 2000)) { /* * If the value we are checking is _TMP, warn the user only * once. This avoids spamming messages if, for instance, the * sensor is broken and always returns an invalid temperature. * * This is only done for _TMP; other values always emit a * warning. */ if (what != acpi_tz_tmp_name || !sc->tz_insane_tmp_notified) { device_printf(sc->tz_dev, "%s value is absurd, ignored (%d.%dC)\n", what, TZ_KELVTOC(*val)); /* Don't warn the user again if the read value doesn't improve. */ if (what == acpi_tz_tmp_name) sc->tz_insane_tmp_notified = 1; } *val = -1; return; } /* This value is correct. Warn if it's incorrect again. */ if (what == acpi_tz_tmp_name) sc->tz_insane_tmp_notified = 0; } /* * Respond to a sysctl on the active state node. */ static int acpi_tz_active_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_tz_softc *sc; int active; int error; sc = (struct acpi_tz_softc *)oidp->oid_arg1; active = sc->tz_active; error = sysctl_handle_int(oidp, &active, 0, req); /* Error or no new value */ if (error != 0 || req->newptr == NULL) return (error); if (active < -1 || active >= TZ_NUMLEVELS) return (EINVAL); /* Set new preferred level and re-switch */ sc->tz_requested = active; acpi_tz_signal(sc, 0); return (0); } static int acpi_tz_cooling_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_tz_softc *sc; int enabled, error; sc = (struct acpi_tz_softc *)oidp->oid_arg1; enabled = sc->tz_cooling_enabled; error = sysctl_handle_int(oidp, &enabled, 0, req); /* Error or no new value */ if (error != 0 || req->newptr == NULL) return (error); if (enabled != TRUE && enabled != FALSE) return (EINVAL); if (enabled) { if (acpi_tz_cooling_is_available(sc)) error = acpi_tz_cooling_thread_start(sc); else error = ENODEV; if (error) enabled = FALSE; } sc->tz_cooling_enabled = enabled; return (error); } static int acpi_tz_temp_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_tz_softc *sc; int temp, *temp_ptr; int error; sc = oidp->oid_arg1; temp_ptr = (int *)(void *)(uintptr_t)((uintptr_t)sc + oidp->oid_arg2); temp = *temp_ptr; error = sysctl_handle_int(oidp, &temp, 0, req); /* Error or no new value */ if (error != 0 || req->newptr == NULL) return (error); /* Only allow changing settings if override is set. */ if (!acpi_tz_override) return (EPERM); /* Check user-supplied value for sanity. */ acpi_tz_sanity(sc, &temp, "user-supplied temp"); if (temp == -1) return (EINVAL); *temp_ptr = temp; return (0); } static int acpi_tz_passive_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_tz_softc *sc; int val, *val_ptr; int error; sc = oidp->oid_arg1; val_ptr = (int *)(void *)(uintptr_t)((uintptr_t)sc + oidp->oid_arg2); val = *val_ptr; error = sysctl_handle_int(oidp, &val, 0, req); /* Error or no new value */ if (error != 0 || req->newptr == NULL) return (error); /* Only allow changing settings if override is set. */ if (!acpi_tz_override) return (EPERM); *val_ptr = val; return (0); } static void acpi_tz_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context) { struct acpi_tz_softc *sc = (struct acpi_tz_softc *)context; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); switch (notify) { case TZ_NOTIFY_TEMPERATURE: /* Temperature change occurred */ acpi_tz_signal(sc, 0); break; case TZ_NOTIFY_DEVICES: case TZ_NOTIFY_LEVELS: /* Zone devices/setpoints changed */ acpi_tz_signal(sc, TZ_FLAG_GETSETTINGS); break; default: ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), "unknown Notify event 0x%x\n", notify); break; } acpi_UserNotify("Thermal", h, notify); return_VOID; } static void acpi_tz_signal(struct acpi_tz_softc *sc, int flags) { ACPI_LOCK(thermal); sc->tz_flags |= flags; ACPI_UNLOCK(thermal); wakeup(&acpi_tz_proc); } /* * Notifies can be generated asynchronously but have also been seen to be * triggered by other thermal methods. One system generates a notify of * 0x81 when the fan is turned on or off. Another generates it when _SCP * is called. To handle these situations, we check the zone via * acpi_tz_monitor() before evaluating changes to setpoints or the cooling * policy. */ static void acpi_tz_timeout(struct acpi_tz_softc *sc, int flags) { /* Check the current temperature and take action based on it */ acpi_tz_monitor(sc); /* If requested, get the power profile settings. */ if (flags & TZ_FLAG_GETPROFILE) acpi_tz_power_profile(sc); /* * If requested, check for new devices/setpoints. After finding them, * check if we need to switch fans based on the new values. */ if (flags & TZ_FLAG_GETSETTINGS) { acpi_tz_establish(sc); acpi_tz_monitor(sc); } /* XXX passive cooling actions? */ } /* * System power profile may have changed; fetch and notify the * thermal zone accordingly. * * Since this can be called from an arbitrary eventhandler, it needs * to get the ACPI lock itself. */ static void acpi_tz_power_profile(void *arg) { ACPI_STATUS status; struct acpi_tz_softc *sc = (struct acpi_tz_softc *)arg; int state; state = power_profile_get_state(); if (state != POWER_PROFILE_PERFORMANCE && state != POWER_PROFILE_ECONOMY) return; /* check that we haven't decided there's no _SCP method */ if ((sc->tz_flags & TZ_FLAG_NO_SCP) == 0) { /* Call _SCP to set the new profile */ status = acpi_SetInteger(sc->tz_handle, "_SCP", (state == POWER_PROFILE_PERFORMANCE) ? 0 : 1); if (ACPI_FAILURE(status)) { if (status != AE_NOT_FOUND) ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), "can't evaluate %s._SCP - %s\n", acpi_name(sc->tz_handle), AcpiFormatException(status)); sc->tz_flags |= TZ_FLAG_NO_SCP; } else { /* We have to re-evaluate the entire zone now */ acpi_tz_signal(sc, TZ_FLAG_GETSETTINGS); } } } /* * Thermal zone monitor thread. */ static void acpi_tz_thread(void *arg) { device_t *devs; int devcount, i; int flags; struct acpi_tz_softc **sc; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); devs = NULL; devcount = 0; sc = NULL; for (;;) { /* If the number of devices has changed, re-evaluate. */ if (devclass_get_count(acpi_tz_devclass) != devcount) { if (devs != NULL) { free(devs, M_TEMP); free(sc, M_TEMP); } devclass_get_devices(acpi_tz_devclass, &devs, &devcount); sc = malloc(sizeof(struct acpi_tz_softc *) * devcount, M_TEMP, M_WAITOK | M_ZERO); for (i = 0; i < devcount; i++) sc[i] = device_get_softc(devs[i]); } /* Check for temperature events and act on them. */ for (i = 0; i < devcount; i++) { ACPI_LOCK(thermal); flags = sc[i]->tz_flags; sc[i]->tz_flags &= TZ_FLAG_NO_SCP; ACPI_UNLOCK(thermal); acpi_tz_timeout(sc[i], flags); } /* If more work to do, don't go to sleep yet. */ ACPI_LOCK(thermal); for (i = 0; i < devcount; i++) { if (sc[i]->tz_flags & ~TZ_FLAG_NO_SCP) break; } /* * If we have no more work, sleep for a while, setting PDROP so that * the mutex will not be reacquired. Otherwise, drop the mutex and * loop to handle more events. */ if (i == devcount) msleep(&acpi_tz_proc, &thermal_mutex, PZERO | PDROP, "tzpoll", hz * acpi_tz_polling_rate); else ACPI_UNLOCK(thermal); } } static int acpi_tz_cpufreq_restore(struct acpi_tz_softc *sc) { device_t dev; int error; if (!sc->tz_cooling_updated) return (0); if ((dev = devclass_get_device(devclass_find("cpufreq"), 0)) == NULL) return (ENXIO); ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), "temperature %d.%dC: resuming previous clock speed (%d MHz)\n", TZ_KELVTOC(sc->tz_temperature), sc->tz_cooling_saved_freq); error = CPUFREQ_SET(dev, NULL, CPUFREQ_PRIO_KERN); if (error == 0) sc->tz_cooling_updated = FALSE; return (error); } static int acpi_tz_cpufreq_update(struct acpi_tz_softc *sc, int req) { device_t dev; struct cf_level *levels; int num_levels, error, freq, desired_freq, perf, i; levels = malloc(CPUFREQ_MAX_LEVELS * sizeof(*levels), M_TEMP, M_NOWAIT); if (levels == NULL) return (ENOMEM); /* * Find the main device, cpufreq0. We don't yet support independent * CPU frequency control on SMP. */ if ((dev = devclass_get_device(devclass_find("cpufreq"), 0)) == NULL) { error = ENXIO; goto out; } /* Get the current frequency. */ error = CPUFREQ_GET(dev, &levels[0]); if (error) goto out; freq = levels[0].total_set.freq; /* Get the current available frequency levels. */ num_levels = CPUFREQ_MAX_LEVELS; error = CPUFREQ_LEVELS(dev, levels, &num_levels); if (error) { if (error == E2BIG) printf("cpufreq: need to increase CPUFREQ_MAX_LEVELS\n"); goto out; } /* Calculate the desired frequency as a percent of the max frequency. */ perf = 100 * freq / levels[0].total_set.freq - req; if (perf < 0) perf = 0; else if (perf > 100) perf = 100; desired_freq = levels[0].total_set.freq * perf / 100; if (desired_freq < freq) { /* Find the closest available frequency, rounding down. */ for (i = 0; i < num_levels; i++) if (levels[i].total_set.freq <= desired_freq) break; /* If we didn't find a relevant setting, use the lowest. */ if (i == num_levels) i--; } else { /* If we didn't decrease frequency yet, don't increase it. */ if (!sc->tz_cooling_updated) { sc->tz_cooling_active = FALSE; goto out; } /* Use saved cpu frequency as maximum value. */ if (desired_freq > sc->tz_cooling_saved_freq) desired_freq = sc->tz_cooling_saved_freq; /* Find the closest available frequency, rounding up. */ for (i = num_levels - 1; i >= 0; i--) if (levels[i].total_set.freq >= desired_freq) break; /* If we didn't find a relevant setting, use the highest. */ if (i == -1) i++; /* If we're going to the highest frequency, restore the old setting. */ if (i == 0 || desired_freq == sc->tz_cooling_saved_freq) { error = acpi_tz_cpufreq_restore(sc); if (error == 0) sc->tz_cooling_active = FALSE; goto out; } } /* If we are going to a new frequency, activate it. */ if (levels[i].total_set.freq != freq) { ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), "temperature %d.%dC: %screasing clock speed " "from %d MHz to %d MHz\n", TZ_KELVTOC(sc->tz_temperature), (freq > levels[i].total_set.freq) ? "de" : "in", freq, levels[i].total_set.freq); error = CPUFREQ_SET(dev, &levels[i], CPUFREQ_PRIO_KERN); if (error == 0 && !sc->tz_cooling_updated) { sc->tz_cooling_saved_freq = freq; sc->tz_cooling_updated = TRUE; } } out: if (levels) free(levels, M_TEMP); return (error); } /* * Passive cooling thread; monitors current temperature according to the * cooling interval and calculates whether to scale back CPU frequency. */ static void acpi_tz_cooling_thread(void *arg) { struct acpi_tz_softc *sc; int error, perf, curr_temp, prev_temp; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_tz_softc *)arg; prev_temp = sc->tz_temperature; while (sc->tz_cooling_enabled) { if (sc->tz_cooling_active) (void)acpi_tz_get_temperature(sc); curr_temp = sc->tz_temperature; if (curr_temp >= sc->tz_zone.psv) sc->tz_cooling_active = TRUE; if (sc->tz_cooling_active) { perf = sc->tz_zone.tc1 * (curr_temp - prev_temp) + sc->tz_zone.tc2 * (curr_temp - sc->tz_zone.psv); perf /= 10; if (perf != 0) { error = acpi_tz_cpufreq_update(sc, perf); /* * If error and not simply a higher priority setting was * active, disable cooling. */ if (error != 0 && error != EPERM) { device_printf(sc->tz_dev, "failed to set new freq, disabling passive cooling\n"); sc->tz_cooling_enabled = FALSE; } } } prev_temp = curr_temp; tsleep(&sc->tz_cooling_proc, PZERO, "cooling", hz * sc->tz_zone.tsp / 10); } if (sc->tz_cooling_active) { acpi_tz_cpufreq_restore(sc); sc->tz_cooling_active = FALSE; } sc->tz_cooling_proc = NULL; ACPI_LOCK(thermal); sc->tz_cooling_proc_running = FALSE; ACPI_UNLOCK(thermal); kproc_exit(0); } /* * TODO: We ignore _PSL (list of cooling devices) since cpufreq enumerates * all CPUs for us. However, it's possible in the future _PSL will * reference non-CPU devices so we may want to support it then. */ static int acpi_tz_cooling_is_available(struct acpi_tz_softc *sc) { return (sc->tz_zone.tc1 != -1 && sc->tz_zone.tc2 != -1 && sc->tz_zone.tsp != -1 && sc->tz_zone.tsp != 0 && sc->tz_zone.psv != -1); } static int acpi_tz_cooling_thread_start(struct acpi_tz_softc *sc) { int error; ACPI_LOCK(thermal); if (sc->tz_cooling_proc_running) { ACPI_UNLOCK(thermal); return (0); } sc->tz_cooling_proc_running = TRUE; ACPI_UNLOCK(thermal); error = 0; if (sc->tz_cooling_proc == NULL) { error = kproc_create(acpi_tz_cooling_thread, sc, &sc->tz_cooling_proc, RFHIGHPID, 0, "acpi_cooling%d", device_get_unit(sc->tz_dev)); if (error != 0) { device_printf(sc->tz_dev, "could not create thread - %d", error); ACPI_LOCK(thermal); sc->tz_cooling_proc_running = FALSE; ACPI_UNLOCK(thermal); } } return (error); } Index: head/sys/dev/amdtemp/amdtemp.c =================================================================== --- head/sys/dev/amdtemp/amdtemp.c (revision 300420) +++ head/sys/dev/amdtemp/amdtemp.c (revision 300421) @@ -1,559 +1,559 @@ /*- * Copyright (c) 2008, 2009 Rui Paulo * Copyright (c) 2009 Norikatsu Shigemura * Copyright (c) 2009-2012 Jung-uk Kim * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Driver for the AMD CPU on-die thermal sensors. * Initially based on the k8temp Linux driver. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include typedef enum { CORE0_SENSOR0, CORE0_SENSOR1, CORE1_SENSOR0, CORE1_SENSOR1, CORE0, CORE1 } amdsensor_t; struct amdtemp_softc { device_t sc_dev; int sc_ncores; int sc_ntemps; int sc_flags; #define AMDTEMP_FLAG_CS_SWAP 0x01 /* ThermSenseCoreSel is inverted. */ #define AMDTEMP_FLAG_CT_10BIT 0x02 /* CurTmp is 10-bit wide. */ #define AMDTEMP_FLAG_ALT_OFFSET 0x04 /* CurTmp starts at -28C. */ int32_t sc_offset; int32_t (*sc_gettemp)(device_t, amdsensor_t); struct sysctl_oid *sc_sysctl_cpu[MAXCPU]; struct intr_config_hook sc_ich; }; #define VENDORID_AMD 0x1022 #define DEVICEID_AMD_MISC0F 0x1103 #define DEVICEID_AMD_MISC10 0x1203 #define DEVICEID_AMD_MISC11 0x1303 #define DEVICEID_AMD_MISC12 0x1403 #define DEVICEID_AMD_MISC14 0x1703 #define DEVICEID_AMD_MISC15 0x1603 #define DEVICEID_AMD_MISC16 0x1533 #define DEVICEID_AMD_MISC16_M30H 0x1583 #define DEVICEID_AMD_MISC17 0x141d static struct amdtemp_product { uint16_t amdtemp_vendorid; uint16_t amdtemp_deviceid; } amdtemp_products[] = { { VENDORID_AMD, DEVICEID_AMD_MISC0F }, { VENDORID_AMD, DEVICEID_AMD_MISC10 }, { VENDORID_AMD, DEVICEID_AMD_MISC11 }, { VENDORID_AMD, DEVICEID_AMD_MISC12 }, { VENDORID_AMD, DEVICEID_AMD_MISC14 }, { VENDORID_AMD, DEVICEID_AMD_MISC15 }, { VENDORID_AMD, DEVICEID_AMD_MISC16 }, { VENDORID_AMD, DEVICEID_AMD_MISC16_M30H }, { VENDORID_AMD, DEVICEID_AMD_MISC17 }, { 0, 0 } }; /* * Reported Temperature Control Register */ #define AMDTEMP_REPTMP_CTRL 0xa4 /* * Thermaltrip Status Register (Family 0Fh only) */ #define AMDTEMP_THERMTP_STAT 0xe4 #define AMDTEMP_TTSR_SELCORE 0x04 #define AMDTEMP_TTSR_SELSENSOR 0x40 /* * DRAM Configuration High Register */ #define AMDTEMP_DRAM_CONF_HIGH 0x94 /* Function 2 */ #define AMDTEMP_DRAM_MODE_DDR3 0x0100 /* * CPU Family/Model Register */ #define AMDTEMP_CPUID 0xfc /* * Device methods. */ static void amdtemp_identify(driver_t *driver, device_t parent); static int amdtemp_probe(device_t dev); static int amdtemp_attach(device_t dev); static void amdtemp_intrhook(void *arg); static int amdtemp_detach(device_t dev); static int amdtemp_match(device_t dev); static int32_t amdtemp_gettemp0f(device_t dev, amdsensor_t sensor); static int32_t amdtemp_gettemp(device_t dev, amdsensor_t sensor); static int amdtemp_sysctl(SYSCTL_HANDLER_ARGS); static device_method_t amdtemp_methods[] = { /* Device interface */ DEVMETHOD(device_identify, amdtemp_identify), DEVMETHOD(device_probe, amdtemp_probe), DEVMETHOD(device_attach, amdtemp_attach), DEVMETHOD(device_detach, amdtemp_detach), DEVMETHOD_END }; static driver_t amdtemp_driver = { "amdtemp", amdtemp_methods, sizeof(struct amdtemp_softc), }; static devclass_t amdtemp_devclass; DRIVER_MODULE(amdtemp, hostb, amdtemp_driver, amdtemp_devclass, NULL, NULL); static int amdtemp_match(device_t dev) { int i; uint16_t vendor, devid; vendor = pci_get_vendor(dev); devid = pci_get_device(dev); for (i = 0; amdtemp_products[i].amdtemp_vendorid != 0; i++) { if (vendor == amdtemp_products[i].amdtemp_vendorid && devid == amdtemp_products[i].amdtemp_deviceid) return (1); } return (0); } static void amdtemp_identify(driver_t *driver, device_t parent) { device_t child; /* Make sure we're not being doubly invoked. */ if (device_find_child(parent, "amdtemp", -1) != NULL) return; if (amdtemp_match(parent)) { child = device_add_child(parent, "amdtemp", -1); if (child == NULL) device_printf(parent, "add amdtemp child failed\n"); } } static int amdtemp_probe(device_t dev) { uint32_t family, model; if (resource_disabled("amdtemp", 0)) return (ENXIO); family = CPUID_TO_FAMILY(cpu_id); model = CPUID_TO_MODEL(cpu_id); switch (family) { case 0x0f: if ((model == 0x04 && (cpu_id & CPUID_STEPPING) == 0) || (model == 0x05 && (cpu_id & CPUID_STEPPING) <= 1)) return (ENXIO); break; case 0x10: case 0x11: case 0x12: case 0x14: case 0x15: case 0x16: break; default: return (ENXIO); } device_set_desc(dev, "AMD CPU On-Die Thermal Sensors"); return (BUS_PROBE_GENERIC); } static int amdtemp_attach(device_t dev) { char tn[32]; u_int regs[4]; struct amdtemp_softc *sc = device_get_softc(dev); struct sysctl_ctx_list *sysctlctx; struct sysctl_oid *sysctlnode; uint32_t cpuid, family, model; u_int bid; int erratum319, unit; erratum319 = 0; /* * CPUID Register is available from Revision F. */ cpuid = cpu_id; family = CPUID_TO_FAMILY(cpuid); model = CPUID_TO_MODEL(cpuid); if (family != 0x0f || model >= 0x40) { cpuid = pci_read_config(dev, AMDTEMP_CPUID, 4); family = CPUID_TO_FAMILY(cpuid); model = CPUID_TO_MODEL(cpuid); } switch (family) { case 0x0f: /* * Thermaltrip Status Register * * - ThermSenseCoreSel * * Revision F & G: 0 - Core1, 1 - Core0 * Other: 0 - Core0, 1 - Core1 * * - CurTmp * * Revision G: bits 23-14 * Other: bits 23-16 * * XXX According to the BKDG, CurTmp, ThermSenseSel and * ThermSenseCoreSel bits were introduced in Revision F * but CurTmp seems working fine as early as Revision C. * However, it is not clear whether ThermSenseSel and/or * ThermSenseCoreSel work in undocumented cases as well. * In fact, the Linux driver suggests it may not work but * we just assume it does until we find otherwise. * * XXX According to Linux, CurTmp starts at -28C on * Socket AM2 Revision G processors, which is not * documented anywhere. */ if (model >= 0x40) sc->sc_flags |= AMDTEMP_FLAG_CS_SWAP; if (model >= 0x60 && model != 0xc1) { do_cpuid(0x80000001, regs); bid = (regs[1] >> 9) & 0x1f; switch (model) { case 0x68: /* Socket S1g1 */ case 0x6c: case 0x7c: break; case 0x6b: /* Socket AM2 and ASB1 (2 cores) */ if (bid != 0x0b && bid != 0x0c) sc->sc_flags |= AMDTEMP_FLAG_ALT_OFFSET; break; case 0x6f: /* Socket AM2 and ASB1 (1 core) */ case 0x7f: if (bid != 0x07 && bid != 0x09 && bid != 0x0c) sc->sc_flags |= AMDTEMP_FLAG_ALT_OFFSET; break; default: sc->sc_flags |= AMDTEMP_FLAG_ALT_OFFSET; } sc->sc_flags |= AMDTEMP_FLAG_CT_10BIT; } /* * There are two sensors per core. */ sc->sc_ntemps = 2; sc->sc_gettemp = amdtemp_gettemp0f; break; case 0x10: /* * Erratum 319 Inaccurate Temperature Measurement * * http://support.amd.com/us/Processor_TechDocs/41322.pdf */ do_cpuid(0x80000001, regs); switch ((regs[1] >> 28) & 0xf) { case 0: /* Socket F */ erratum319 = 1; break; case 1: /* Socket AM2+ or AM3 */ if ((pci_cfgregread(pci_get_bus(dev), pci_get_slot(dev), 2, AMDTEMP_DRAM_CONF_HIGH, 2) & AMDTEMP_DRAM_MODE_DDR3) != 0 || model > 0x04 || (model == 0x04 && (cpuid & CPUID_STEPPING) >= 3)) break; /* XXX 00100F42h (RB-C2) exists in both formats. */ erratum319 = 1; break; } /* FALLTHROUGH */ case 0x11: case 0x12: case 0x14: case 0x15: case 0x16: /* * There is only one sensor per package. */ sc->sc_ntemps = 1; sc->sc_gettemp = amdtemp_gettemp; break; } /* Find number of cores per package. */ sc->sc_ncores = (amd_feature2 & AMDID2_CMP) != 0 ? (cpu_procinfo2 & AMDID_CMP_CORES) + 1 : 1; if (sc->sc_ncores > MAXCPU) return (ENXIO); if (erratum319) device_printf(dev, "Erratum 319: temperature measurement may be inaccurate\n"); if (bootverbose) device_printf(dev, "Found %d cores and %d sensors.\n", sc->sc_ncores, sc->sc_ntemps > 1 ? sc->sc_ntemps * sc->sc_ncores : 1); /* * dev.amdtemp.N tree. */ unit = device_get_unit(dev); snprintf(tn, sizeof(tn), "dev.amdtemp.%d.sensor_offset", unit); TUNABLE_INT_FETCH(tn, &sc->sc_offset); sysctlctx = device_get_sysctl_ctx(dev); SYSCTL_ADD_INT(sysctlctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensor_offset", CTLFLAG_RW, &sc->sc_offset, 0, "Temperature sensor offset"); sysctlnode = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "core0", CTLFLAG_RD, 0, "Core 0"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "sensor0", CTLTYPE_INT | CTLFLAG_RD, dev, CORE0_SENSOR0, amdtemp_sysctl, "IK", "Core 0 / Sensor 0 temperature"); if (sc->sc_ntemps > 1) { SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "sensor1", CTLTYPE_INT | CTLFLAG_RD, dev, CORE0_SENSOR1, amdtemp_sysctl, "IK", "Core 0 / Sensor 1 temperature"); if (sc->sc_ncores > 1) { sysctlnode = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "core1", CTLFLAG_RD, 0, "Core 1"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "sensor0", CTLTYPE_INT | CTLFLAG_RD, dev, CORE1_SENSOR0, amdtemp_sysctl, "IK", "Core 1 / Sensor 0 temperature"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "sensor1", CTLTYPE_INT | CTLFLAG_RD, dev, CORE1_SENSOR1, amdtemp_sysctl, "IK", "Core 1 / Sensor 1 temperature"); } } /* * Try to create dev.cpu sysctl entries and setup intrhook function. * This is needed because the cpu driver may be loaded late on boot, * after us. */ amdtemp_intrhook(dev); sc->sc_ich.ich_func = amdtemp_intrhook; sc->sc_ich.ich_arg = dev; if (config_intrhook_establish(&sc->sc_ich) != 0) { device_printf(dev, "config_intrhook_establish failed!\n"); return (ENXIO); } return (0); } void amdtemp_intrhook(void *arg) { struct amdtemp_softc *sc; struct sysctl_ctx_list *sysctlctx; device_t dev = (device_t)arg; device_t acpi, cpu, nexus; amdsensor_t sensor; int i; sc = device_get_softc(dev); /* * dev.cpu.N.temperature. */ nexus = device_find_child(root_bus, "nexus", 0); acpi = device_find_child(nexus, "acpi", 0); for (i = 0; i < sc->sc_ncores; i++) { if (sc->sc_sysctl_cpu[i] != NULL) continue; cpu = device_find_child(acpi, "cpu", device_get_unit(dev) * sc->sc_ncores + i); if (cpu != NULL) { sysctlctx = device_get_sysctl_ctx(cpu); sensor = sc->sc_ntemps > 1 ? (i == 0 ? CORE0 : CORE1) : CORE0_SENSOR0; sc->sc_sysctl_cpu[i] = SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(device_get_sysctl_tree(cpu)), OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD, dev, sensor, amdtemp_sysctl, "IK", "Current temparature"); } } if (sc->sc_ich.ich_arg != NULL) config_intrhook_disestablish(&sc->sc_ich); } int amdtemp_detach(device_t dev) { struct amdtemp_softc *sc = device_get_softc(dev); int i; for (i = 0; i < sc->sc_ncores; i++) if (sc->sc_sysctl_cpu[i] != NULL) sysctl_remove_oid(sc->sc_sysctl_cpu[i], 1, 0); /* NewBus removes the dev.amdtemp.N tree by itself. */ return (0); } static int amdtemp_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev = (device_t)arg1; struct amdtemp_softc *sc = device_get_softc(dev); amdsensor_t sensor = (amdsensor_t)arg2; int32_t auxtemp[2], temp; int error; switch (sensor) { case CORE0: auxtemp[0] = sc->sc_gettemp(dev, CORE0_SENSOR0); auxtemp[1] = sc->sc_gettemp(dev, CORE0_SENSOR1); temp = imax(auxtemp[0], auxtemp[1]); break; case CORE1: auxtemp[0] = sc->sc_gettemp(dev, CORE1_SENSOR0); auxtemp[1] = sc->sc_gettemp(dev, CORE1_SENSOR1); temp = imax(auxtemp[0], auxtemp[1]); break; default: temp = sc->sc_gettemp(dev, sensor); break; } error = sysctl_handle_int(oidp, &temp, 0, req); return (error); } -#define AMDTEMP_ZERO_C_TO_K 2732 +#define AMDTEMP_ZERO_C_TO_K 2731 static int32_t amdtemp_gettemp0f(device_t dev, amdsensor_t sensor) { struct amdtemp_softc *sc = device_get_softc(dev); uint32_t mask, offset, temp; /* Set Sensor/Core selector. */ temp = pci_read_config(dev, AMDTEMP_THERMTP_STAT, 1); temp &= ~(AMDTEMP_TTSR_SELCORE | AMDTEMP_TTSR_SELSENSOR); switch (sensor) { case CORE0_SENSOR1: temp |= AMDTEMP_TTSR_SELSENSOR; /* FALLTHROUGH */ case CORE0_SENSOR0: case CORE0: if ((sc->sc_flags & AMDTEMP_FLAG_CS_SWAP) != 0) temp |= AMDTEMP_TTSR_SELCORE; break; case CORE1_SENSOR1: temp |= AMDTEMP_TTSR_SELSENSOR; /* FALLTHROUGH */ case CORE1_SENSOR0: case CORE1: if ((sc->sc_flags & AMDTEMP_FLAG_CS_SWAP) == 0) temp |= AMDTEMP_TTSR_SELCORE; break; } pci_write_config(dev, AMDTEMP_THERMTP_STAT, temp, 1); mask = (sc->sc_flags & AMDTEMP_FLAG_CT_10BIT) != 0 ? 0x3ff : 0x3fc; offset = (sc->sc_flags & AMDTEMP_FLAG_ALT_OFFSET) != 0 ? 28 : 49; temp = pci_read_config(dev, AMDTEMP_THERMTP_STAT, 4); temp = ((temp >> 14) & mask) * 5 / 2; temp += AMDTEMP_ZERO_C_TO_K + (sc->sc_offset - offset) * 10; return (temp); } static int32_t amdtemp_gettemp(device_t dev, amdsensor_t sensor) { struct amdtemp_softc *sc = device_get_softc(dev); uint32_t temp; temp = pci_read_config(dev, AMDTEMP_REPTMP_CTRL, 4); temp = ((temp >> 21) & 0x7ff) * 5 / 4; temp += AMDTEMP_ZERO_C_TO_K + sc->sc_offset * 10; return (temp); } Index: head/sys/dev/coretemp/coretemp.c =================================================================== --- head/sys/dev/coretemp/coretemp.c (revision 300420) +++ head/sys/dev/coretemp/coretemp.c (revision 300421) @@ -1,446 +1,446 @@ /*- * Copyright (c) 2007, 2008 Rui Paulo * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Device driver for Intel's On Die thermal sensor via MSR. * First introduced in Intel's Core line of processors. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include /* for curthread */ #include #include #include #include #include -#define TZ_ZEROC 2732 +#define TZ_ZEROC 2731 #define THERM_STATUS_LOG 0x02 #define THERM_STATUS 0x01 #define THERM_STATUS_TEMP_SHIFT 16 #define THERM_STATUS_TEMP_MASK 0x7f #define THERM_STATUS_RES_SHIFT 27 #define THERM_STATUS_RES_MASK 0x0f #define THERM_STATUS_VALID_SHIFT 31 #define THERM_STATUS_VALID_MASK 0x01 struct coretemp_softc { device_t sc_dev; int sc_tjmax; unsigned int sc_throttle_log; }; /* * Device methods. */ static void coretemp_identify(driver_t *driver, device_t parent); static int coretemp_probe(device_t dev); static int coretemp_attach(device_t dev); static int coretemp_detach(device_t dev); static uint64_t coretemp_get_thermal_msr(int cpu); static void coretemp_clear_thermal_msr(int cpu); static int coretemp_get_val_sysctl(SYSCTL_HANDLER_ARGS); static int coretemp_throttle_log_sysctl(SYSCTL_HANDLER_ARGS); static device_method_t coretemp_methods[] = { /* Device interface */ DEVMETHOD(device_identify, coretemp_identify), DEVMETHOD(device_probe, coretemp_probe), DEVMETHOD(device_attach, coretemp_attach), DEVMETHOD(device_detach, coretemp_detach), DEVMETHOD_END }; static driver_t coretemp_driver = { "coretemp", coretemp_methods, sizeof(struct coretemp_softc), }; enum therm_info { CORETEMP_TEMP, CORETEMP_DELTA, CORETEMP_RESOLUTION, CORETEMP_TJMAX, }; static devclass_t coretemp_devclass; DRIVER_MODULE(coretemp, cpu, coretemp_driver, coretemp_devclass, NULL, NULL); static void coretemp_identify(driver_t *driver, device_t parent) { device_t child; u_int regs[4]; /* Make sure we're not being doubly invoked. */ if (device_find_child(parent, "coretemp", -1) != NULL) return; /* Check that CPUID 0x06 is supported and the vendor is Intel.*/ if (cpu_high < 6 || cpu_vendor_id != CPU_VENDOR_INTEL) return; /* * CPUID 0x06 returns 1 if the processor has on-die thermal * sensors. EBX[0:3] contains the number of sensors. */ do_cpuid(0x06, regs); if ((regs[0] & 0x1) != 1) return; /* * We add a child for each CPU since settings must be performed * on each CPU in the SMP case. */ child = device_add_child(parent, "coretemp", -1); if (child == NULL) device_printf(parent, "add coretemp child failed\n"); } static int coretemp_probe(device_t dev) { if (resource_disabled("coretemp", 0)) return (ENXIO); device_set_desc(dev, "CPU On-Die Thermal Sensors"); return (BUS_PROBE_GENERIC); } static int coretemp_attach(device_t dev) { struct coretemp_softc *sc = device_get_softc(dev); device_t pdev; uint64_t msr; int cpu_model, cpu_stepping; int ret, tjtarget; struct sysctl_oid *oid; struct sysctl_ctx_list *ctx; sc->sc_dev = dev; pdev = device_get_parent(dev); cpu_model = CPUID_TO_MODEL(cpu_id); cpu_stepping = cpu_id & CPUID_STEPPING; /* * Some CPUs, namely the PIII, don't have thermal sensors, but * report them when the CPUID check is performed in * coretemp_identify(). This leads to a later GPF when the sensor * is queried via a MSR, so we stop here. */ if (cpu_model < 0xe) return (ENXIO); #if 0 /* * XXXrpaulo: I have this CPU model and when it returns from C3 * coretemp continues to function properly. */ /* * Check for errata AE18. * "Processor Digital Thermal Sensor (DTS) Readout stops * updating upon returning from C3/C4 state." * * Adapted from the Linux coretemp driver. */ if (cpu_model == 0xe && cpu_stepping < 0xc) { msr = rdmsr(MSR_BIOS_SIGN); msr = msr >> 32; if (msr < 0x39) { device_printf(dev, "not supported (Intel errata " "AE18), try updating your BIOS\n"); return (ENXIO); } } #endif /* * Use 100C as the initial value. */ sc->sc_tjmax = 100; if ((cpu_model == 0xf && cpu_stepping >= 2) || cpu_model == 0xe) { /* * On some Core 2 CPUs, there's an undocumented MSR that * can tell us if Tj(max) is 100 or 85. * * The if-clause for CPUs having the MSR_IA32_EXT_CONFIG was adapted * from the Linux coretemp driver. */ msr = rdmsr(MSR_IA32_EXT_CONFIG); if (msr & (1 << 30)) sc->sc_tjmax = 85; } else if (cpu_model == 0x17) { switch (cpu_stepping) { case 0x6: /* Mobile Core 2 Duo */ sc->sc_tjmax = 105; break; default: /* Unknown stepping */ break; } } else if (cpu_model == 0x1c) { switch (cpu_stepping) { case 0xa: /* 45nm Atom D400, N400 and D500 series */ sc->sc_tjmax = 100; break; default: sc->sc_tjmax = 90; break; } } else { /* * Attempt to get Tj(max) from MSR IA32_TEMPERATURE_TARGET. * * This method is described in Intel white paper "CPU * Monitoring With DTS/PECI". (#322683) */ ret = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &msr); if (ret == 0) { tjtarget = (msr >> 16) & 0xff; /* * On earlier generation of processors, the value * obtained from IA32_TEMPERATURE_TARGET register is * an offset that needs to be summed with a model * specific base. It is however not clear what * these numbers are, with the publicly available * documents from Intel. * * For now, we consider [70, 110]C range, as * described in #322683, as "reasonable" and accept * these values whenever the MSR is available for * read, regardless the CPU model. */ if (tjtarget >= 70 && tjtarget <= 110) sc->sc_tjmax = tjtarget; else device_printf(dev, "Tj(target) value %d " "does not seem right.\n", tjtarget); } else device_printf(dev, "Can not get Tj(target) " "from your CPU, using 100C.\n"); } if (bootverbose) device_printf(dev, "Setting TjMax=%d\n", sc->sc_tjmax); ctx = device_get_sysctl_ctx(dev); oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(pdev)), OID_AUTO, "coretemp", CTLFLAG_RD, NULL, "Per-CPU thermal information"); /* * Add the MIBs to dev.cpu.N and dev.cpu.N.coretemp. */ SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(pdev)), OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_TEMP, coretemp_get_val_sysctl, "IK", "Current temperature"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "delta", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_DELTA, coretemp_get_val_sysctl, "I", "Delta between TCC activation and current temperature"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "resolution", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_RESOLUTION, coretemp_get_val_sysctl, "I", "Resolution of CPU thermal sensor"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "tjmax", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, CORETEMP_TJMAX, coretemp_get_val_sysctl, "IK", "TCC activation temperature"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "throttle_log", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, 0, coretemp_throttle_log_sysctl, "I", "Set to 1 if the thermal sensor has tripped"); return (0); } static int coretemp_detach(device_t dev) { return (0); } static uint64_t coretemp_get_thermal_msr(int cpu) { uint64_t msr; thread_lock(curthread); sched_bind(curthread, cpu); thread_unlock(curthread); /* * The digital temperature reading is located at bit 16 * of MSR_THERM_STATUS. * * There is a bit on that MSR that indicates whether the * temperature is valid or not. * * The temperature is computed by subtracting the temperature * reading by Tj(max). */ msr = rdmsr(MSR_THERM_STATUS); thread_lock(curthread); sched_unbind(curthread); thread_unlock(curthread); return (msr); } static void coretemp_clear_thermal_msr(int cpu) { thread_lock(curthread); sched_bind(curthread, cpu); thread_unlock(curthread); wrmsr(MSR_THERM_STATUS, 0); thread_lock(curthread); sched_unbind(curthread); thread_unlock(curthread); } static int coretemp_get_val_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev; uint64_t msr; int val, tmp; struct coretemp_softc *sc; enum therm_info type; char stemp[16]; dev = (device_t) arg1; msr = coretemp_get_thermal_msr(device_get_unit(dev)); sc = device_get_softc(dev); type = arg2; if (((msr >> THERM_STATUS_VALID_SHIFT) & THERM_STATUS_VALID_MASK) != 1) { val = -1; } else { switch (type) { case CORETEMP_TEMP: tmp = (msr >> THERM_STATUS_TEMP_SHIFT) & THERM_STATUS_TEMP_MASK; val = (sc->sc_tjmax - tmp) * 10 + TZ_ZEROC; break; case CORETEMP_DELTA: val = (msr >> THERM_STATUS_TEMP_SHIFT) & THERM_STATUS_TEMP_MASK; break; case CORETEMP_RESOLUTION: val = (msr >> THERM_STATUS_RES_SHIFT) & THERM_STATUS_RES_MASK; break; case CORETEMP_TJMAX: val = sc->sc_tjmax * 10 + TZ_ZEROC; break; } } if (msr & THERM_STATUS_LOG) { coretemp_clear_thermal_msr(device_get_unit(dev)); sc->sc_throttle_log = 1; /* * Check for Critical Temperature Status and Critical * Temperature Log. It doesn't really matter if the * current temperature is invalid because the "Critical * Temperature Log" bit will tell us if the Critical * Temperature has * been reached in past. It's not * directly related to the current temperature. * * If we reach a critical level, allow devctl(4) * to catch this and shutdown the system. */ if (msr & THERM_STATUS) { tmp = (msr >> THERM_STATUS_TEMP_SHIFT) & THERM_STATUS_TEMP_MASK; tmp = (sc->sc_tjmax - tmp) * 10 + TZ_ZEROC; device_printf(dev, "critical temperature detected, " "suggest system shutdown\n"); snprintf(stemp, sizeof(stemp), "%d", tmp); devctl_notify("coretemp", "Thermal", stemp, "notify=0xcc"); } } return (sysctl_handle_int(oidp, &val, 0, req)); } static int coretemp_throttle_log_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev; uint64_t msr; int error, val; struct coretemp_softc *sc; dev = (device_t) arg1; msr = coretemp_get_thermal_msr(device_get_unit(dev)); sc = device_get_softc(dev); if (msr & THERM_STATUS_LOG) { coretemp_clear_thermal_msr(device_get_unit(dev)); sc->sc_throttle_log = 1; } val = sc->sc_throttle_log; error = sysctl_handle_int(oidp, &val, 0, req); if (error || !req->newptr) return (error); else if (val != 0) return (EINVAL); coretemp_clear_thermal_msr(device_get_unit(dev)); sc->sc_throttle_log = 0; return (0); } Index: head/sys/dev/iicbus/ds3231reg.h =================================================================== --- head/sys/dev/iicbus/ds3231reg.h (revision 300420) +++ head/sys/dev/iicbus/ds3231reg.h (revision 300421) @@ -1,78 +1,78 @@ /*- * Copyright (c) 2014-2015 Luiz Otavio O Souza * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * Maxim DS3231 RTC registers. */ #ifndef _DS3231REG_H_ #define _DS3231REG_H_ #define DS3231_SECS 0x00 #define DS3231_SECS_MASK 0x7f #define DS3231_MINS 0x01 #define DS3231_MINS_MASK 0x7f #define DS3231_HOUR 0x02 #define DS3231_HOUR_MASK 0x3f #define DS3231_WEEKDAY 0x03 #define DS3231_WEEKDAY_MASK 0x07 #define DS3231_DATE 0x04 #define DS3231_DATE_MASK 0x3f #define DS3231_MONTH 0x05 #define DS3231_MONTH_MASK 0x1f #define DS3231_C_MASK 0x80 #define DS3231_YEAR 0x06 #define DS3231_YEAR_MASK 0xff #define DS3231_CONTROL 0x0e #define DS3231_CTRL_EOSC (1 << 7) #define DS3231_CTRL_BBSQW (1 << 6) #define DS3231_CTRL_CONV (1 << 5) #define DS3231_CTRL_RS2 (1 << 4) #define DS3231_CTRL_RS1 (1 << 3) #define DS3231_CTRL_RS_MASK (DS3231_CTRL_RS2 | DS3231_CTRL_RS1) #define DS3231_CTRL_RS_SHIFT 3 #define DS3231_CTRL_INTCN (1 << 2) #define DS3231_CTRL_A2IE (1 << 1) #define DS3231_CTRL_A1IE (1 << 0) #define DS3231_CTRL_MASK \ (DS3231_CTRL_EOSC | DS3231_CTRL_A1IE | DS3231_CTRL_A2IE) #define DS3231_STATUS 0x0f #define DS3231_STATUS_OSF (1 << 7) #define DS3231_STATUS_EN32KHZ (1 << 3) #define DS3231_STATUS_BUSY (1 << 2) #define DS3231_STATUS_A2F (1 << 1) #define DS3231_STATUS_A1F (1 << 0) #define DS3231_TEMP 0x11 #define DS3231_TEMP_MASK 0xffc0 #define DS3231_0500C 0x80 #define DS3231_0250C 0x40 #define DS3231_MSB 0x8000 #define DS3231_NEG_BIT DS3231_MSB -#define TZ_ZEROC 2732 +#define TZ_ZEROC 2731 #endif /* _DS3231REG_H_ */ Index: head/sys/dev/iicbus/lm75.c =================================================================== --- head/sys/dev/iicbus/lm75.c (revision 300420) +++ head/sys/dev/iicbus/lm75.c (revision 300421) @@ -1,579 +1,579 @@ /*- * Copyright (c) 2010 Andreas Tobler. * Copyright (c) 2013-2014 Luiz Otavio O Souza * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include "opt_platform.h" #include #include #include #include #include #include #include #include #include #include #ifdef FDT #include #include #include #endif /* LM75 registers. */ #define LM75_TEMP 0x0 #define LM75_TEMP_MASK 0xff80 #define LM75A_TEMP_MASK 0xffe0 #define LM75_CONF 0x1 #define LM75_CONF_FSHIFT 3 #define LM75_CONF_FAULT 0x18 #define LM75_CONF_POL 0x04 #define LM75_CONF_MODE 0x02 #define LM75_CONF_SHUTD 0x01 #define LM75_CONF_MASK 0x1f #define LM75_THYST 0x2 #define LM75_TOS 0x3 /* LM75 constants. */ #define LM75_TEST_PATTERN 0xa #define LM75_MIN_TEMP -55 #define LM75_MAX_TEMP 125 #define LM75_0500C 0x80 #define LM75_0250C 0x40 #define LM75_0125C 0x20 #define LM75_MSB 0x8000 #define LM75_NEG_BIT LM75_MSB -#define TZ_ZEROC 2732 +#define TZ_ZEROC 2731 /* LM75 supported models. */ #define HWTYPE_LM75 1 #define HWTYPE_LM75A 2 /* Regular bus attachment functions */ static int lm75_probe(device_t); static int lm75_attach(device_t); struct lm75_softc { device_t sc_dev; struct intr_config_hook enum_hook; int32_t sc_hwtype; uint32_t sc_addr; uint32_t sc_conf; }; /* Utility functions */ static int lm75_conf_read(struct lm75_softc *); static int lm75_conf_write(struct lm75_softc *); static int lm75_temp_read(struct lm75_softc *, uint8_t, int *); static int lm75_temp_write(struct lm75_softc *, uint8_t, int); static void lm75_start(void *); static int lm75_read(device_t, uint32_t, uint8_t, uint8_t *, size_t); static int lm75_write(device_t, uint32_t, uint8_t *, size_t); static int lm75_str_mode(char *); static int lm75_str_pol(char *); static int lm75_temp_sysctl(SYSCTL_HANDLER_ARGS); static int lm75_faults_sysctl(SYSCTL_HANDLER_ARGS); static int lm75_mode_sysctl(SYSCTL_HANDLER_ARGS); static int lm75_pol_sysctl(SYSCTL_HANDLER_ARGS); static int lm75_shutdown_sysctl(SYSCTL_HANDLER_ARGS); static device_method_t lm75_methods[] = { /* Device interface */ DEVMETHOD(device_probe, lm75_probe), DEVMETHOD(device_attach, lm75_attach), DEVMETHOD_END }; static driver_t lm75_driver = { "lm75", lm75_methods, sizeof(struct lm75_softc) }; static devclass_t lm75_devclass; DRIVER_MODULE(lm75, iicbus, lm75_driver, lm75_devclass, 0, 0); static int lm75_read(device_t dev, uint32_t addr, uint8_t reg, uint8_t *data, size_t len) { struct iic_msg msg[2] = { { addr, IIC_M_WR | IIC_M_NOSTOP, 1, ® }, { addr, IIC_M_RD, len, data }, }; if (iicbus_transfer(dev, msg, nitems(msg)) != 0) return (-1); return (0); } static int lm75_write(device_t dev, uint32_t addr, uint8_t *data, size_t len) { struct iic_msg msg[1] = { { addr, IIC_M_WR, len, data }, }; if (iicbus_transfer(dev, msg, nitems(msg)) != 0) return (-1); return (0); } static int lm75_probe(device_t dev) { struct lm75_softc *sc; sc = device_get_softc(dev); sc->sc_hwtype = HWTYPE_LM75; #ifdef FDT if (!ofw_bus_is_compatible(dev, "national,lm75")) return (ENXIO); #endif device_set_desc(dev, "LM75 temperature sensor"); return (BUS_PROBE_GENERIC); } static int lm75_attach(device_t dev) { struct lm75_softc *sc; sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_addr = iicbus_get_addr(dev); sc->enum_hook.ich_func = lm75_start; sc->enum_hook.ich_arg = dev; /* * We have to wait until interrupts are enabled. Usually I2C read * and write only works when the interrupts are available. */ if (config_intrhook_establish(&sc->enum_hook) != 0) return (ENOMEM); return (0); } static int lm75_type_detect(struct lm75_softc *sc) { int i, lm75a; uint8_t buf8; uint32_t conf; /* Save the contents of the configuration register. */ if (lm75_conf_read(sc) != 0) return (-1); conf = sc->sc_conf; /* * Just write some pattern at configuration register so we can later * verify. The test pattern should be pretty harmless. */ sc->sc_conf = LM75_TEST_PATTERN; if (lm75_conf_write(sc) != 0) return (-1); /* * Read the configuration register again and check for our test * pattern. */ if (lm75_conf_read(sc) != 0) return (-1); if (sc->sc_conf != LM75_TEST_PATTERN) return (-1); /* * Read from nonexistent registers (0x4 ~ 0x6). * LM75A always return 0xff for nonexistent registers. * LM75 will return the last read value - our test pattern written to * configuration register. */ lm75a = 0; for (i = 4; i <= 6; i++) { if (lm75_read(sc->sc_dev, sc->sc_addr, i, &buf8, sizeof(buf8)) < 0) return (-1); if (buf8 != LM75_TEST_PATTERN && buf8 != 0xff) return (-1); if (buf8 == 0xff) lm75a++; } if (lm75a == 3) sc->sc_hwtype = HWTYPE_LM75A; /* Restore the configuration register. */ sc->sc_conf = conf; if (lm75_conf_write(sc) != 0) return (-1); return (0); } static void lm75_start(void *xdev) { device_t dev; struct lm75_softc *sc; struct sysctl_ctx_list *ctx; struct sysctl_oid *tree_node; struct sysctl_oid_list *tree; dev = (device_t)xdev; sc = device_get_softc(dev); ctx = device_get_sysctl_ctx(dev); tree_node = device_get_sysctl_tree(dev); tree = SYSCTL_CHILDREN(tree_node); config_intrhook_disestablish(&sc->enum_hook); /* * Detect the kind of chip we are attaching to. * This may not work for LM75 clones. */ if (lm75_type_detect(sc) != 0) { device_printf(dev, "cannot read from sensor.\n"); return; } if (sc->sc_hwtype == HWTYPE_LM75A) device_printf(dev, "LM75A type sensor detected (11bits resolution).\n"); /* Temperature. */ SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, LM75_TEMP, lm75_temp_sysctl, "IK", "Current temperature"); SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "thyst", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, LM75_THYST, lm75_temp_sysctl, "IK", "Hysteresis temperature"); SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "tos", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, LM75_TOS, lm75_temp_sysctl, "IK", "Overtemperature"); /* Configuration parameters. */ SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "faults", CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_MPSAFE, dev, 0, lm75_faults_sysctl, "IU", "LM75 fault queue"); SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "mode", CTLFLAG_RW | CTLTYPE_STRING | CTLFLAG_MPSAFE, dev, 0, lm75_mode_sysctl, "A", "LM75 mode"); SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "polarity", CTLFLAG_RW | CTLTYPE_STRING | CTLFLAG_MPSAFE, dev, 0, lm75_pol_sysctl, "A", "LM75 OS polarity"); SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "shutdown", CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_MPSAFE, dev, 0, lm75_shutdown_sysctl, "IU", "LM75 shutdown"); } static int lm75_conf_read(struct lm75_softc *sc) { uint8_t buf8; if (lm75_read(sc->sc_dev, sc->sc_addr, LM75_CONF, &buf8, sizeof(buf8)) < 0) return (-1); sc->sc_conf = (uint32_t)buf8; return (0); } static int lm75_conf_write(struct lm75_softc *sc) { uint8_t buf8[2]; buf8[0] = LM75_CONF; buf8[1] = (uint8_t)sc->sc_conf & LM75_CONF_MASK; if (lm75_write(sc->sc_dev, sc->sc_addr, buf8, sizeof(buf8)) < 0) return (-1); return (0); } static int lm75_temp_read(struct lm75_softc *sc, uint8_t reg, int *temp) { uint8_t buf8[2]; uint16_t buf; int neg, t; if (lm75_read(sc->sc_dev, sc->sc_addr, reg, buf8, sizeof(buf8)) < 0) return (-1); buf = (uint16_t)((buf8[0] << 8) | (buf8[1] & 0xff)); /* * LM75 has a 9 bit ADC with resolution of 0.5 C per bit. * LM75A has an 11 bit ADC with resolution of 0.125 C per bit. * Temperature is stored with two's complement. */ neg = 0; if (buf & LM75_NEG_BIT) { if (sc->sc_hwtype == HWTYPE_LM75A) buf = ~(buf & LM75A_TEMP_MASK) + 1; else buf = ~(buf & LM75_TEMP_MASK) + 1; neg = 1; } *temp = ((int16_t)buf >> 8) * 10; t = 0; if (sc->sc_hwtype == HWTYPE_LM75A) { if (buf & LM75_0125C) t += 125; if (buf & LM75_0250C) t += 250; } if (buf & LM75_0500C) t += 500; t /= 100; *temp += t; if (neg) *temp = -(*temp); *temp += TZ_ZEROC; return (0); } static int lm75_temp_write(struct lm75_softc *sc, uint8_t reg, int temp) { uint8_t buf8[3]; uint16_t buf; temp = (temp - TZ_ZEROC) / 10; if (temp > LM75_MAX_TEMP) temp = LM75_MAX_TEMP; if (temp < LM75_MIN_TEMP) temp = LM75_MIN_TEMP; buf = (uint16_t)temp; buf <<= 8; buf8[0] = reg; buf8[1] = buf >> 8; buf8[2] = buf & 0xff; if (lm75_write(sc->sc_dev, sc->sc_addr, buf8, sizeof(buf8)) < 0) return (-1); return (0); } static int lm75_str_mode(char *buf) { int len, rtrn; rtrn = -1; len = strlen(buf); if (len > 2 && strncasecmp("interrupt", buf, len) == 0) rtrn = 1; else if (len > 2 && strncasecmp("comparator", buf, len) == 0) rtrn = 0; return (rtrn); } static int lm75_str_pol(char *buf) { int len, rtrn; rtrn = -1; len = strlen(buf); if (len > 1 && strncasecmp("high", buf, len) == 0) rtrn = 1; else if (len > 1 && strncasecmp("low", buf, len) == 0) rtrn = 0; else if (len > 8 && strncasecmp("active-high", buf, len) == 0) rtrn = 1; else if (len > 8 && strncasecmp("active-low", buf, len) == 0) rtrn = 0; return (rtrn); } static int lm75_temp_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev; int error, temp; struct lm75_softc *sc; uint8_t reg; dev = (device_t)arg1; reg = (uint8_t)arg2; sc = device_get_softc(dev); if (lm75_temp_read(sc, reg, &temp) != 0) return (EIO); error = sysctl_handle_int(oidp, &temp, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (lm75_temp_write(sc, reg, temp) != 0) return (EIO); return (error); } static int lm75_faults_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev; int lm75_faults[] = { 1, 2, 4, 6 }; int error, faults, i, newf, tmp; struct lm75_softc *sc; dev = (device_t)arg1; sc = device_get_softc(dev); tmp = (sc->sc_conf & LM75_CONF_FAULT) >> LM75_CONF_FSHIFT; if (tmp >= nitems(lm75_faults)) tmp = nitems(lm75_faults) - 1; faults = lm75_faults[tmp]; error = sysctl_handle_int(oidp, &faults, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (faults != lm75_faults[tmp]) { newf = 0; for (i = 0; i < nitems(lm75_faults); i++) if (faults >= lm75_faults[i]) newf = i; sc->sc_conf &= ~LM75_CONF_FAULT; sc->sc_conf |= newf << LM75_CONF_FSHIFT; if (lm75_conf_write(sc) != 0) return (EIO); } return (error); } static int lm75_mode_sysctl(SYSCTL_HANDLER_ARGS) { char buf[16]; device_t dev; int error, mode, newm; struct lm75_softc *sc; dev = (device_t)arg1; sc = device_get_softc(dev); if (sc->sc_conf & LM75_CONF_MODE) { mode = 1; strlcpy(buf, "interrupt", sizeof(buf)); } else { mode = 0; strlcpy(buf, "comparator", sizeof(buf)); } error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); newm = lm75_str_mode(buf); if (newm != -1 && mode != newm) { sc->sc_conf &= ~LM75_CONF_MODE; if (newm == 1) sc->sc_conf |= LM75_CONF_MODE; if (lm75_conf_write(sc) != 0) return (EIO); } return (error); } static int lm75_pol_sysctl(SYSCTL_HANDLER_ARGS) { char buf[16]; device_t dev; int error, newp, pol; struct lm75_softc *sc; dev = (device_t)arg1; sc = device_get_softc(dev); if (sc->sc_conf & LM75_CONF_POL) { pol = 1; strlcpy(buf, "active-high", sizeof(buf)); } else { pol = 0; strlcpy(buf, "active-low", sizeof(buf)); } error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); newp = lm75_str_pol(buf); if (newp != -1 && pol != newp) { sc->sc_conf &= ~LM75_CONF_POL; if (newp == 1) sc->sc_conf |= LM75_CONF_POL; if (lm75_conf_write(sc) != 0) return (EIO); } return (error); } static int lm75_shutdown_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev; int error, shutdown, tmp; struct lm75_softc *sc; dev = (device_t)arg1; sc = device_get_softc(dev); tmp = shutdown = (sc->sc_conf & LM75_CONF_SHUTD) ? 1 : 0; error = sysctl_handle_int(oidp, &shutdown, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (shutdown != tmp) { sc->sc_conf &= ~LM75_CONF_SHUTD; if (shutdown) sc->sc_conf |= LM75_CONF_SHUTD; if (lm75_conf_write(sc) != 0) return (EIO); } return (error); } Index: head/sys/powerpc/powermac/powermac_thermal.h =================================================================== --- head/sys/powerpc/powermac/powermac_thermal.h (revision 300420) +++ head/sys/powerpc/powermac/powermac_thermal.h (revision 300421) @@ -1,56 +1,56 @@ /*- * Copyright (c) 2009-2011 Nathan Whitehorn * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _POWERPC_POWERMAC_POWERMAC_THERMAL_H #define _POWERPC_POWERMAC_POWERMAC_THERMAL_H -#define ZERO_C_TO_K 2732 +#define ZERO_C_TO_K 2731 struct pmac_fan { int min_rpm, max_rpm, default_rpm; char name[32]; int zone; int (*read)(struct pmac_fan *); int (*set)(struct pmac_fan *, int value); }; struct pmac_therm { int target_temp, max_temp; /* Tenths of a degree K */ char name[32]; int zone; int (*read)(struct pmac_therm *); }; void pmac_thermal_fan_register(struct pmac_fan *); void pmac_thermal_sensor_register(struct pmac_therm *); #endif Index: head/sys/powerpc/powermac/smu.c =================================================================== --- head/sys/powerpc/powermac/smu.c (revision 300420) +++ head/sys/powerpc/powermac/smu.c (revision 300421) @@ -1,1579 +1,1579 @@ /*- * Copyright (c) 2009 Nathan Whitehorn * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "clock_if.h" #include "iicbus_if.h" struct smu_cmd { volatile uint8_t cmd; uint8_t len; uint8_t data[254]; STAILQ_ENTRY(smu_cmd) cmd_q; }; STAILQ_HEAD(smu_cmdq, smu_cmd); struct smu_fan { struct pmac_fan fan; device_t dev; cell_t reg; enum { SMU_FAN_RPM, SMU_FAN_PWM } type; int setpoint; int old_style; int rpm; }; /* We can read the PWM and the RPM from a PWM controlled fan. * Offer both values via sysctl. */ enum { SMU_PWM_SYSCTL_PWM = 1 << 8, SMU_PWM_SYSCTL_RPM = 2 << 8 }; struct smu_sensor { struct pmac_therm therm; device_t dev; cell_t reg; enum { SMU_CURRENT_SENSOR, SMU_VOLTAGE_SENSOR, SMU_POWER_SENSOR, SMU_TEMP_SENSOR } type; }; struct smu_softc { device_t sc_dev; struct mtx sc_mtx; struct resource *sc_memr; int sc_memrid; int sc_u3; bus_dma_tag_t sc_dmatag; bus_space_tag_t sc_bt; bus_space_handle_t sc_mailbox; struct smu_cmd *sc_cmd, *sc_cur_cmd; bus_addr_t sc_cmd_phys; bus_dmamap_t sc_cmd_dmamap; struct smu_cmdq sc_cmdq; struct smu_fan *sc_fans; int sc_nfans; int old_style_fans; struct smu_sensor *sc_sensors; int sc_nsensors; int sc_doorbellirqid; struct resource *sc_doorbellirq; void *sc_doorbellirqcookie; struct proc *sc_fanmgt_proc; time_t sc_lastuserchange; /* Calibration data */ uint16_t sc_cpu_diode_scale; int16_t sc_cpu_diode_offset; uint16_t sc_cpu_volt_scale; int16_t sc_cpu_volt_offset; uint16_t sc_cpu_curr_scale; int16_t sc_cpu_curr_offset; uint16_t sc_slots_pow_scale; int16_t sc_slots_pow_offset; struct cdev *sc_leddev; }; /* regular bus attachment functions */ static int smu_probe(device_t); static int smu_attach(device_t); static const struct ofw_bus_devinfo * smu_get_devinfo(device_t bus, device_t dev); /* cpufreq notification hooks */ static void smu_cpufreq_pre_change(device_t, const struct cf_level *level); static void smu_cpufreq_post_change(device_t, const struct cf_level *level); /* clock interface */ static int smu_gettime(device_t dev, struct timespec *ts); static int smu_settime(device_t dev, struct timespec *ts); /* utility functions */ static int smu_run_cmd(device_t dev, struct smu_cmd *cmd, int wait); static int smu_get_datablock(device_t dev, int8_t id, uint8_t *buf, size_t len); static void smu_attach_i2c(device_t dev, phandle_t i2croot); static void smu_attach_fans(device_t dev, phandle_t fanroot); static void smu_attach_sensors(device_t dev, phandle_t sensroot); static void smu_set_sleepled(void *xdev, int onoff); static int smu_server_mode(SYSCTL_HANDLER_ARGS); static void smu_doorbell_intr(void *xdev); static void smu_shutdown(void *xdev, int howto); /* where to find the doorbell GPIO */ static device_t smu_doorbell = NULL; static device_method_t smu_methods[] = { /* Device interface */ DEVMETHOD(device_probe, smu_probe), DEVMETHOD(device_attach, smu_attach), /* Clock interface */ DEVMETHOD(clock_gettime, smu_gettime), DEVMETHOD(clock_settime, smu_settime), /* ofw_bus interface */ DEVMETHOD(bus_child_pnpinfo_str,ofw_bus_gen_child_pnpinfo_str), DEVMETHOD(ofw_bus_get_devinfo, smu_get_devinfo), DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), { 0, 0 }, }; static driver_t smu_driver = { "smu", smu_methods, sizeof(struct smu_softc) }; static devclass_t smu_devclass; DRIVER_MODULE(smu, ofwbus, smu_driver, smu_devclass, 0, 0); static MALLOC_DEFINE(M_SMU, "smu", "SMU Sensor Information"); #define SMU_MAILBOX 0x8000860c #define SMU_FANMGT_INTERVAL 1000 /* ms */ /* Command types */ #define SMU_ADC 0xd8 #define SMU_FAN 0x4a #define SMU_RPM_STATUS 0x01 #define SMU_RPM_SETPOINT 0x02 #define SMU_PWM_STATUS 0x11 #define SMU_PWM_SETPOINT 0x12 #define SMU_I2C 0x9a #define SMU_I2C_SIMPLE 0x00 #define SMU_I2C_NORMAL 0x01 #define SMU_I2C_COMBINED 0x02 #define SMU_MISC 0xee #define SMU_MISC_GET_DATA 0x02 #define SMU_MISC_LED_CTRL 0x04 #define SMU_POWER 0xaa #define SMU_POWER_EVENTS 0x8f #define SMU_PWR_GET_POWERUP 0x00 #define SMU_PWR_SET_POWERUP 0x01 #define SMU_PWR_CLR_POWERUP 0x02 #define SMU_RTC 0x8e #define SMU_RTC_GET 0x81 #define SMU_RTC_SET 0x80 /* Power event types */ #define SMU_WAKEUP_KEYPRESS 0x01 #define SMU_WAKEUP_AC_INSERT 0x02 #define SMU_WAKEUP_AC_CHANGE 0x04 #define SMU_WAKEUP_RING 0x10 /* Data blocks */ #define SMU_CPUTEMP_CAL 0x18 #define SMU_CPUVOLT_CAL 0x21 #define SMU_SLOTPW_CAL 0x78 /* Partitions */ #define SMU_PARTITION 0x3e #define SMU_PARTITION_LATEST 0x01 #define SMU_PARTITION_BASE 0x02 #define SMU_PARTITION_UPDATE 0x03 static int smu_probe(device_t dev) { const char *name = ofw_bus_get_name(dev); if (strcmp(name, "smu") != 0) return (ENXIO); device_set_desc(dev, "Apple System Management Unit"); return (0); } static void smu_phys_callback(void *xsc, bus_dma_segment_t *segs, int nsegs, int error) { struct smu_softc *sc = xsc; sc->sc_cmd_phys = segs[0].ds_addr; } static int smu_attach(device_t dev) { struct smu_softc *sc; phandle_t node, child; uint8_t data[12]; sc = device_get_softc(dev); mtx_init(&sc->sc_mtx, "smu", NULL, MTX_DEF); sc->sc_cur_cmd = NULL; sc->sc_doorbellirqid = -1; sc->sc_u3 = 0; if (OF_finddevice("/u3") != -1) sc->sc_u3 = 1; /* * Map the mailbox area. This should be determined from firmware, * but I have not found a simple way to do that. */ bus_dma_tag_create(NULL, 16, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, PAGE_SIZE, 1, PAGE_SIZE, 0, NULL, NULL, &(sc->sc_dmatag)); sc->sc_bt = &bs_le_tag; bus_space_map(sc->sc_bt, SMU_MAILBOX, 4, 0, &sc->sc_mailbox); /* * Allocate the command buffer. This can be anywhere in the low 4 GB * of memory. */ bus_dmamem_alloc(sc->sc_dmatag, (void **)&sc->sc_cmd, BUS_DMA_WAITOK | BUS_DMA_ZERO, &sc->sc_cmd_dmamap); bus_dmamap_load(sc->sc_dmatag, sc->sc_cmd_dmamap, sc->sc_cmd, PAGE_SIZE, smu_phys_callback, sc, 0); STAILQ_INIT(&sc->sc_cmdq); /* * Set up handlers to change CPU voltage when CPU frequency is changed. */ EVENTHANDLER_REGISTER(cpufreq_pre_change, smu_cpufreq_pre_change, dev, EVENTHANDLER_PRI_ANY); EVENTHANDLER_REGISTER(cpufreq_post_change, smu_cpufreq_post_change, dev, EVENTHANDLER_PRI_ANY); node = ofw_bus_get_node(dev); /* Some SMUs have RPM and PWM controlled fans which do not sit * under the same node. So we have to attach them separately. */ smu_attach_fans(dev, node); /* * Now detect and attach the other child devices. */ for (child = OF_child(node); child != 0; child = OF_peer(child)) { char name[32]; memset(name, 0, sizeof(name)); OF_getprop(child, "name", name, sizeof(name)); if (strncmp(name, "sensors", 8) == 0) smu_attach_sensors(dev, child); if (strncmp(name, "smu-i2c-control", 15) == 0) smu_attach_i2c(dev, child); } /* Some SMUs have the I2C children directly under the bus. */ smu_attach_i2c(dev, node); /* * Collect calibration constants. */ smu_get_datablock(dev, SMU_CPUTEMP_CAL, data, sizeof(data)); sc->sc_cpu_diode_scale = (data[4] << 8) + data[5]; sc->sc_cpu_diode_offset = (data[6] << 8) + data[7]; smu_get_datablock(dev, SMU_CPUVOLT_CAL, 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]; smu_get_datablock(dev, SMU_SLOTPW_CAL, data, sizeof(data)); sc->sc_slots_pow_scale = (data[4] << 8) + data[5]; sc->sc_slots_pow_offset = (data[6] << 8) + data[7]; /* * Set up LED interface */ sc->sc_leddev = led_create(smu_set_sleepled, dev, "sleepled"); /* * Reset on power loss behavior */ SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "server_mode", CTLTYPE_INT | CTLFLAG_RW, dev, 0, smu_server_mode, "I", "Enable reboot after power failure"); /* * Set up doorbell interrupt. */ sc->sc_doorbellirqid = 0; sc->sc_doorbellirq = bus_alloc_resource_any(smu_doorbell, SYS_RES_IRQ, &sc->sc_doorbellirqid, RF_ACTIVE); bus_setup_intr(smu_doorbell, sc->sc_doorbellirq, INTR_TYPE_MISC | INTR_MPSAFE, NULL, smu_doorbell_intr, dev, &sc->sc_doorbellirqcookie); powerpc_config_intr(rman_get_start(sc->sc_doorbellirq), INTR_TRIGGER_EDGE, INTR_POLARITY_LOW); /* * Connect RTC interface. */ clock_register(dev, 1000); /* * Learn about shutdown events */ EVENTHANDLER_REGISTER(shutdown_final, smu_shutdown, dev, SHUTDOWN_PRI_LAST); return (bus_generic_attach(dev)); } static const struct ofw_bus_devinfo * smu_get_devinfo(device_t bus, device_t dev) { return (device_get_ivars(dev)); } static void smu_send_cmd(device_t dev, struct smu_cmd *cmd) { struct smu_softc *sc; sc = device_get_softc(dev); mtx_assert(&sc->sc_mtx, MA_OWNED); if (sc->sc_u3) powerpc_pow_enabled = 0; /* SMU cannot work if we go to NAP */ sc->sc_cur_cmd = cmd; /* Copy the command to the mailbox */ sc->sc_cmd->cmd = cmd->cmd; sc->sc_cmd->len = cmd->len; memcpy(sc->sc_cmd->data, cmd->data, sizeof(cmd->data)); bus_dmamap_sync(sc->sc_dmatag, sc->sc_cmd_dmamap, BUS_DMASYNC_PREWRITE); bus_space_write_4(sc->sc_bt, sc->sc_mailbox, 0, sc->sc_cmd_phys); /* Flush the cacheline it is in -- SMU bypasses the cache */ __asm __volatile("sync; dcbf 0,%0; sync" :: "r"(sc->sc_cmd): "memory"); /* Ring SMU doorbell */ macgpio_write(smu_doorbell, GPIO_DDR_OUTPUT); } static void smu_doorbell_intr(void *xdev) { device_t smu; struct smu_softc *sc; int doorbell_ack; smu = xdev; doorbell_ack = macgpio_read(smu_doorbell); sc = device_get_softc(smu); if (doorbell_ack != (GPIO_DDR_OUTPUT | GPIO_LEVEL_RO | GPIO_DATA)) return; mtx_lock(&sc->sc_mtx); if (sc->sc_cur_cmd == NULL) /* spurious */ goto done; /* Check result. First invalidate the cache again... */ __asm __volatile("dcbf 0,%0; sync" :: "r"(sc->sc_cmd) : "memory"); bus_dmamap_sync(sc->sc_dmatag, sc->sc_cmd_dmamap, BUS_DMASYNC_POSTREAD); sc->sc_cur_cmd->cmd = sc->sc_cmd->cmd; sc->sc_cur_cmd->len = sc->sc_cmd->len; memcpy(sc->sc_cur_cmd->data, sc->sc_cmd->data, sizeof(sc->sc_cmd->data)); wakeup(sc->sc_cur_cmd); sc->sc_cur_cmd = NULL; if (sc->sc_u3) powerpc_pow_enabled = 1; done: /* Queue next command if one is pending */ if (STAILQ_FIRST(&sc->sc_cmdq) != NULL) { sc->sc_cur_cmd = STAILQ_FIRST(&sc->sc_cmdq); STAILQ_REMOVE_HEAD(&sc->sc_cmdq, cmd_q); smu_send_cmd(smu, sc->sc_cur_cmd); } mtx_unlock(&sc->sc_mtx); } static int smu_run_cmd(device_t dev, struct smu_cmd *cmd, int wait) { struct smu_softc *sc; uint8_t cmd_code; int error; sc = device_get_softc(dev); cmd_code = cmd->cmd; mtx_lock(&sc->sc_mtx); if (sc->sc_cur_cmd != NULL) { STAILQ_INSERT_TAIL(&sc->sc_cmdq, cmd, cmd_q); } else smu_send_cmd(dev, cmd); mtx_unlock(&sc->sc_mtx); if (!wait) return (0); if (sc->sc_doorbellirqid < 0) { /* Poll if the IRQ has not been set up yet */ do { DELAY(50); smu_doorbell_intr(dev); } while (sc->sc_cur_cmd != NULL); } else { /* smu_doorbell_intr will wake us when the command is ACK'ed */ error = tsleep(cmd, 0, "smu", 800 * hz / 1000); if (error != 0) smu_doorbell_intr(dev); /* One last chance */ if (error != 0) { mtx_lock(&sc->sc_mtx); if (cmd->cmd == cmd_code) { /* Never processed */ /* Abort this command if we timed out */ if (sc->sc_cur_cmd == cmd) sc->sc_cur_cmd = NULL; else STAILQ_REMOVE(&sc->sc_cmdq, cmd, smu_cmd, cmd_q); mtx_unlock(&sc->sc_mtx); return (error); } error = 0; mtx_unlock(&sc->sc_mtx); } } /* SMU acks the command by inverting the command bits */ if (cmd->cmd == ((~cmd_code) & 0xff)) error = 0; else error = EIO; return (error); } static int smu_get_datablock(device_t dev, int8_t id, uint8_t *buf, size_t len) { struct smu_cmd cmd; uint8_t addr[4]; cmd.cmd = SMU_PARTITION; cmd.len = 2; cmd.data[0] = SMU_PARTITION_LATEST; cmd.data[1] = id; smu_run_cmd(dev, &cmd, 1); addr[0] = addr[1] = 0; 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(addr); memcpy(&cmd.data[2], addr, sizeof(addr)); cmd.data[6] = len; smu_run_cmd(dev, &cmd, 1); memcpy(buf, cmd.data, len); return (0); } static void smu_slew_cpu_voltage(device_t dev, int to) { struct smu_cmd cmd; cmd.cmd = SMU_POWER; cmd.len = 8; cmd.data[0] = 'V'; cmd.data[1] = 'S'; cmd.data[2] = 'L'; cmd.data[3] = 'E'; cmd.data[4] = 'W'; cmd.data[5] = 0xff; cmd.data[6] = 1; cmd.data[7] = to; smu_run_cmd(dev, &cmd, 1); } static void smu_cpufreq_pre_change(device_t dev, const struct cf_level *level) { /* * Make sure the CPU voltage is raised before we raise * the clock. */ if (level->rel_set[0].freq == 10000 /* max */) smu_slew_cpu_voltage(dev, 0); } static void smu_cpufreq_post_change(device_t dev, const struct cf_level *level) { /* We are safe to reduce CPU voltage after a downward transition */ if (level->rel_set[0].freq < 10000 /* max */) smu_slew_cpu_voltage(dev, 1); /* XXX: 1/4 voltage for 970MP? */ } /* Routines for probing the SMU doorbell GPIO */ static int doorbell_probe(device_t dev); static int doorbell_attach(device_t dev); static device_method_t doorbell_methods[] = { /* Device interface */ DEVMETHOD(device_probe, doorbell_probe), DEVMETHOD(device_attach, doorbell_attach), { 0, 0 }, }; static driver_t doorbell_driver = { "smudoorbell", doorbell_methods, 0 }; static devclass_t doorbell_devclass; DRIVER_MODULE(smudoorbell, macgpio, doorbell_driver, doorbell_devclass, 0, 0); static int doorbell_probe(device_t dev) { const char *name = ofw_bus_get_name(dev); if (strcmp(name, "smu-doorbell") != 0) return (ENXIO); device_set_desc(dev, "SMU Doorbell GPIO"); device_quiet(dev); return (0); } static int doorbell_attach(device_t dev) { smu_doorbell = dev; return (0); } /* * Sensor and fan management */ static int smu_fan_check_old_style(struct smu_fan *fan) { device_t smu = fan->dev; struct smu_softc *sc = device_get_softc(smu); struct smu_cmd cmd; int error; if (sc->old_style_fans != -1) return (sc->old_style_fans); /* * Apple has two fan control mechanisms. We can't distinguish * them except by seeing if the new one fails. If the new one * fails, use the old one. */ cmd.cmd = SMU_FAN; cmd.len = 2; cmd.data[0] = 0x31; cmd.data[1] = fan->reg; do { error = smu_run_cmd(smu, &cmd, 1); } while (error == EWOULDBLOCK); sc->old_style_fans = (error != 0); return (sc->old_style_fans); } static int smu_fan_set_rpm(struct smu_fan *fan, int rpm) { device_t smu = fan->dev; struct smu_cmd cmd; int error; cmd.cmd = SMU_FAN; error = EIO; /* Clamp to allowed range */ rpm = max(fan->fan.min_rpm, rpm); rpm = min(fan->fan.max_rpm, rpm); smu_fan_check_old_style(fan); if (!fan->old_style) { cmd.len = 4; cmd.data[0] = 0x30; cmd.data[1] = fan->reg; cmd.data[2] = (rpm >> 8) & 0xff; cmd.data[3] = rpm & 0xff; error = smu_run_cmd(smu, &cmd, 1); if (error && error != EWOULDBLOCK) fan->old_style = 1; } else { cmd.len = 14; cmd.data[0] = 0x00; /* RPM fan. */ cmd.data[1] = 1 << fan->reg; cmd.data[2 + 2*fan->reg] = (rpm >> 8) & 0xff; cmd.data[3 + 2*fan->reg] = rpm & 0xff; error = smu_run_cmd(smu, &cmd, 1); } if (error == 0) fan->setpoint = rpm; return (error); } static int smu_fan_read_rpm(struct smu_fan *fan) { device_t smu = fan->dev; struct smu_cmd cmd; int rpm, error; smu_fan_check_old_style(fan); if (!fan->old_style) { cmd.cmd = SMU_FAN; cmd.len = 2; cmd.data[0] = 0x31; cmd.data[1] = fan->reg; error = smu_run_cmd(smu, &cmd, 1); if (error && error != EWOULDBLOCK) fan->old_style = 1; rpm = (cmd.data[0] << 8) | cmd.data[1]; } if (fan->old_style) { cmd.cmd = SMU_FAN; cmd.len = 1; cmd.data[0] = SMU_RPM_STATUS; error = smu_run_cmd(smu, &cmd, 1); if (error) return (error); rpm = (cmd.data[fan->reg*2+1] << 8) | cmd.data[fan->reg*2+2]; } return (rpm); } static int smu_fan_set_pwm(struct smu_fan *fan, int pwm) { device_t smu = fan->dev; struct smu_cmd cmd; int error; cmd.cmd = SMU_FAN; error = EIO; /* Clamp to allowed range */ pwm = max(fan->fan.min_rpm, pwm); pwm = min(fan->fan.max_rpm, pwm); /* * Apple has two fan control mechanisms. We can't distinguish * them except by seeing if the new one fails. If the new one * fails, use the old one. */ if (!fan->old_style) { cmd.len = 4; cmd.data[0] = 0x30; cmd.data[1] = fan->reg; cmd.data[2] = (pwm >> 8) & 0xff; cmd.data[3] = pwm & 0xff; error = smu_run_cmd(smu, &cmd, 1); if (error && error != EWOULDBLOCK) fan->old_style = 1; } if (fan->old_style) { cmd.len = 14; cmd.data[0] = 0x10; /* PWM fan. */ cmd.data[1] = 1 << fan->reg; cmd.data[2 + 2*fan->reg] = (pwm >> 8) & 0xff; cmd.data[3 + 2*fan->reg] = pwm & 0xff; error = smu_run_cmd(smu, &cmd, 1); } if (error == 0) fan->setpoint = pwm; return (error); } static int smu_fan_read_pwm(struct smu_fan *fan, int *pwm, int *rpm) { device_t smu = fan->dev; struct smu_cmd cmd; int error; if (!fan->old_style) { cmd.cmd = SMU_FAN; cmd.len = 2; cmd.data[0] = 0x31; cmd.data[1] = fan->reg; error = smu_run_cmd(smu, &cmd, 1); if (error && error != EWOULDBLOCK) fan->old_style = 1; *rpm = (cmd.data[0] << 8) | cmd.data[1]; } if (fan->old_style) { cmd.cmd = SMU_FAN; cmd.len = 1; cmd.data[0] = SMU_PWM_STATUS; error = smu_run_cmd(smu, &cmd, 1); if (error) return (error); *rpm = (cmd.data[fan->reg*2+1] << 8) | cmd.data[fan->reg*2+2]; } if (fan->old_style) { cmd.cmd = SMU_FAN; cmd.len = 14; cmd.data[0] = SMU_PWM_SETPOINT; cmd.data[1] = 1 << fan->reg; error = smu_run_cmd(smu, &cmd, 1); if (error) return (error); *pwm = cmd.data[fan->reg*2+2]; } return (0); } static int smu_fanrpm_sysctl(SYSCTL_HANDLER_ARGS) { device_t smu; struct smu_softc *sc; struct smu_fan *fan; int pwm = 0, rpm, error = 0; smu = arg1; sc = device_get_softc(smu); fan = &sc->sc_fans[arg2 & 0xff]; if (fan->type == SMU_FAN_RPM) { rpm = smu_fan_read_rpm(fan); if (rpm < 0) return (rpm); error = sysctl_handle_int(oidp, &rpm, 0, req); } else { error = smu_fan_read_pwm(fan, &pwm, &rpm); if (error < 0) return (EIO); switch (arg2 & 0xff00) { case SMU_PWM_SYSCTL_PWM: error = sysctl_handle_int(oidp, &pwm, 0, req); break; case SMU_PWM_SYSCTL_RPM: error = sysctl_handle_int(oidp, &rpm, 0, req); break; default: /* This should never happen */ return (EINVAL); } } /* We can only read the RPM from a PWM controlled fan, so return. */ if ((arg2 & 0xff00) == SMU_PWM_SYSCTL_RPM) return (0); if (error || !req->newptr) return (error); sc->sc_lastuserchange = time_uptime; if (fan->type == SMU_FAN_RPM) return (smu_fan_set_rpm(fan, rpm)); else return (smu_fan_set_pwm(fan, pwm)); } static void smu_fill_fan_prop(device_t dev, phandle_t child, int id) { struct smu_fan *fan; struct smu_softc *sc; char type[32]; sc = device_get_softc(dev); fan = &sc->sc_fans[id]; OF_getprop(child, "device_type", type, sizeof(type)); /* We have either RPM or PWM controlled fans. */ if (strcmp(type, "fan-rpm-control") == 0) fan->type = SMU_FAN_RPM; else fan->type = SMU_FAN_PWM; fan->dev = dev; fan->old_style = 0; OF_getprop(child, "reg", &fan->reg, sizeof(cell_t)); OF_getprop(child, "min-value", &fan->fan.min_rpm, sizeof(int)); OF_getprop(child, "max-value", &fan->fan.max_rpm, sizeof(int)); OF_getprop(child, "zone", &fan->fan.zone, sizeof(int)); if (OF_getprop(child, "unmanaged-value", &fan->fan.default_rpm, sizeof(int)) != sizeof(int)) fan->fan.default_rpm = fan->fan.max_rpm; OF_getprop(child, "location", fan->fan.name, sizeof(fan->fan.name)); if (fan->type == SMU_FAN_RPM) fan->setpoint = smu_fan_read_rpm(fan); else smu_fan_read_pwm(fan, &fan->setpoint, &fan->rpm); } /* On the first call count the number of fans. In the second call, * after allocating the fan struct, fill the properties of the fans. */ static int smu_count_fans(device_t dev) { struct smu_softc *sc; phandle_t child, node, root; int nfans = 0; node = ofw_bus_get_node(dev); sc = device_get_softc(dev); /* First find the fanroots and count the number of fans. */ for (root = OF_child(node); root != 0; root = OF_peer(root)) { char name[32]; memset(name, 0, sizeof(name)); OF_getprop(root, "name", name, sizeof(name)); if (strncmp(name, "rpm-fans", 9) == 0 || strncmp(name, "pwm-fans", 9) == 0 || strncmp(name, "fans", 5) == 0) for (child = OF_child(root); child != 0; child = OF_peer(child)) { nfans++; /* When allocated, fill the fan properties. */ if (sc->sc_fans != NULL) { smu_fill_fan_prop(dev, child, nfans - 1); } } } if (nfans == 0) { device_printf(dev, "WARNING: No fans detected!\n"); return (0); } return (nfans); } static void smu_attach_fans(device_t dev, phandle_t fanroot) { struct smu_fan *fan; struct smu_softc *sc; struct sysctl_oid *oid, *fanroot_oid; struct sysctl_ctx_list *ctx; char sysctl_name[32]; int i, j; sc = device_get_softc(dev); /* Get the number of fans. */ sc->sc_nfans = smu_count_fans(dev); if (sc->sc_nfans == 0) return; /* Now we're able to allocate memory for the fans struct. */ sc->sc_fans = malloc(sc->sc_nfans * sizeof(struct smu_fan), M_SMU, M_WAITOK | M_ZERO); /* Now fill in the properties. */ smu_count_fans(dev); /* Register fans with pmac_thermal */ for (i = 0; i < sc->sc_nfans; i++) pmac_thermal_fan_register(&sc->sc_fans[i].fan); ctx = device_get_sysctl_ctx(dev); fanroot_oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "fans", CTLFLAG_RD, 0, "SMU Fan Information"); /* Add sysctls */ for (i = 0; i < sc->sc_nfans; i++) { fan = &sc->sc_fans[i]; for (j = 0; j < strlen(fan->fan.name); j++) { sysctl_name[j] = tolower(fan->fan.name[j]); if (isspace(sysctl_name[j])) sysctl_name[j] = '_'; } sysctl_name[j] = 0; if (fan->type == SMU_FAN_RPM) { oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(fanroot_oid), OID_AUTO, sysctl_name, CTLFLAG_RD, 0, "Fan Information"); SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "minrpm", CTLFLAG_RD, &fan->fan.min_rpm, 0, "Minimum allowed RPM"); SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "maxrpm", CTLFLAG_RD, &fan->fan.max_rpm, 0, "Maximum allowed RPM"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "rpm",CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, i, smu_fanrpm_sysctl, "I", "Fan RPM"); fan->fan.read = (int (*)(struct pmac_fan *))smu_fan_read_rpm; fan->fan.set = (int (*)(struct pmac_fan *, int))smu_fan_set_rpm; } else { oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(fanroot_oid), OID_AUTO, sysctl_name, CTLFLAG_RD, 0, "Fan Information"); SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "minpwm", CTLFLAG_RD, &fan->fan.min_rpm, 0, "Minimum allowed PWM in %"); SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "maxpwm", CTLFLAG_RD, &fan->fan.max_rpm, 0, "Maximum allowed PWM in %"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "pwm",CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE, dev, SMU_PWM_SYSCTL_PWM | i, smu_fanrpm_sysctl, "I", "Fan PWM in %"); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(oid), OID_AUTO, "rpm",CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, SMU_PWM_SYSCTL_RPM | i, smu_fanrpm_sysctl, "I", "Fan RPM"); fan->fan.read = NULL; fan->fan.set = (int (*)(struct pmac_fan *, int))smu_fan_set_pwm; } if (bootverbose) device_printf(dev, "Fan: %s type: %d\n", fan->fan.name, fan->type); } } static int smu_sensor_read(struct smu_sensor *sens) { device_t smu = sens->dev; struct smu_cmd cmd; struct smu_softc *sc; int64_t value; int error; cmd.cmd = SMU_ADC; cmd.len = 1; cmd.data[0] = sens->reg; error = 0; error = smu_run_cmd(smu, &cmd, 1); if (error != 0) return (-1); sc = device_get_softc(smu); value = (cmd.data[0] << 8) | cmd.data[1]; switch (sens->type) { case SMU_TEMP_SENSOR: 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 integer 0.1 K. */ - value = 10*(value >> 16) + ((10*(value & 0xffff)) >> 16) + 2732; + value = 10*(value >> 16) + ((10*(value & 0xffff)) >> 16) + 2731; break; case SMU_VOLTAGE_SENSOR: value *= sc->sc_cpu_volt_scale; value += sc->sc_cpu_volt_offset; value <<= 4; /* Convert from 16.16 fixed point V into mV. */ value *= 15625; value /= 1024; value /= 1000; break; case SMU_CURRENT_SENSOR: value *= sc->sc_cpu_curr_scale; value += sc->sc_cpu_curr_offset; value <<= 4; /* Convert from 16.16 fixed point A into mA. */ value *= 15625; value /= 1024; value /= 1000; break; case SMU_POWER_SENSOR: value *= sc->sc_slots_pow_scale; value += sc->sc_slots_pow_offset; value <<= 4; /* Convert from 16.16 fixed point W into mW. */ value *= 15625; value /= 1024; value /= 1000; break; } return (value); } static int smu_sensor_sysctl(SYSCTL_HANDLER_ARGS) { device_t smu; struct smu_softc *sc; struct smu_sensor *sens; int value, error; smu = arg1; sc = device_get_softc(smu); sens = &sc->sc_sensors[arg2]; value = smu_sensor_read(sens); if (value < 0) return (EBUSY); error = sysctl_handle_int(oidp, &value, 0, req); return (error); } static void smu_attach_sensors(device_t dev, phandle_t sensroot) { struct smu_sensor *sens; struct smu_softc *sc; struct sysctl_oid *sensroot_oid; struct sysctl_ctx_list *ctx; phandle_t child; char type[32]; int i; sc = device_get_softc(dev); sc->sc_nsensors = 0; for (child = OF_child(sensroot); child != 0; child = OF_peer(child)) sc->sc_nsensors++; if (sc->sc_nsensors == 0) { device_printf(dev, "WARNING: No sensors detected!\n"); return; } sc->sc_sensors = malloc(sc->sc_nsensors * sizeof(struct smu_sensor), M_SMU, M_WAITOK | M_ZERO); sens = sc->sc_sensors; sc->sc_nsensors = 0; ctx = device_get_sysctl_ctx(dev); sensroot_oid = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "sensors", CTLFLAG_RD, 0, "SMU Sensor Information"); for (child = OF_child(sensroot); child != 0; child = OF_peer(child)) { char sysctl_name[40], sysctl_desc[40]; const char *units; sens->dev = dev; OF_getprop(child, "device_type", type, sizeof(type)); if (strcmp(type, "current-sensor") == 0) { sens->type = SMU_CURRENT_SENSOR; units = "mA"; } else if (strcmp(type, "temp-sensor") == 0) { sens->type = SMU_TEMP_SENSOR; units = "C"; } else if (strcmp(type, "voltage-sensor") == 0) { sens->type = SMU_VOLTAGE_SENSOR; units = "mV"; } else if (strcmp(type, "power-sensor") == 0) { sens->type = SMU_POWER_SENSOR; units = "mW"; } else { continue; } OF_getprop(child, "reg", &sens->reg, sizeof(cell_t)); OF_getprop(child, "zone", &sens->therm.zone, sizeof(int)); OF_getprop(child, "location", sens->therm.name, sizeof(sens->therm.name)); for (i = 0; i < strlen(sens->therm.name); i++) { sysctl_name[i] = tolower(sens->therm.name[i]); if (isspace(sysctl_name[i])) sysctl_name[i] = '_'; } sysctl_name[i] = 0; sprintf(sysctl_desc,"%s (%s)", sens->therm.name, units); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(sensroot_oid), OID_AUTO, sysctl_name, CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, sc->sc_nsensors, smu_sensor_sysctl, (sens->type == SMU_TEMP_SENSOR) ? "IK" : "I", sysctl_desc); if (sens->type == SMU_TEMP_SENSOR) { /* Make up some numbers */ - sens->therm.target_temp = 500 + 2732; /* 50 C */ - sens->therm.max_temp = 900 + 2732; /* 90 C */ + sens->therm.target_temp = 500 + 2731; /* 50 C */ + sens->therm.max_temp = 900 + 2731; /* 90 C */ sens->therm.read = (int (*)(struct pmac_therm *))smu_sensor_read; pmac_thermal_sensor_register(&sens->therm); } sens++; sc->sc_nsensors++; } } static void smu_set_sleepled(void *xdev, int onoff) { static struct smu_cmd cmd; device_t smu = xdev; cmd.cmd = SMU_MISC; cmd.len = 3; cmd.data[0] = SMU_MISC_LED_CTRL; cmd.data[1] = 0; cmd.data[2] = onoff; smu_run_cmd(smu, &cmd, 0); } static int smu_server_mode(SYSCTL_HANDLER_ARGS) { struct smu_cmd cmd; u_int server_mode; device_t smu = arg1; int error; cmd.cmd = SMU_POWER_EVENTS; cmd.len = 1; cmd.data[0] = SMU_PWR_GET_POWERUP; error = smu_run_cmd(smu, &cmd, 1); if (error) return (error); server_mode = (cmd.data[1] & SMU_WAKEUP_AC_INSERT) ? 1 : 0; error = sysctl_handle_int(oidp, &server_mode, 0, req); if (error || !req->newptr) return (error); if (server_mode == 1) cmd.data[0] = SMU_PWR_SET_POWERUP; else if (server_mode == 0) cmd.data[0] = SMU_PWR_CLR_POWERUP; else return (EINVAL); cmd.len = 3; cmd.data[1] = 0; cmd.data[2] = SMU_WAKEUP_AC_INSERT; return (smu_run_cmd(smu, &cmd, 1)); } static void smu_shutdown(void *xdev, int howto) { device_t smu = xdev; struct smu_cmd cmd; cmd.cmd = SMU_POWER; if (howto & RB_HALT) strcpy(cmd.data, "SHUTDOWN"); else strcpy(cmd.data, "RESTART"); cmd.len = strlen(cmd.data); smu_run_cmd(smu, &cmd, 1); for (;;); } static int smu_gettime(device_t dev, struct timespec *ts) { struct smu_cmd cmd; struct clocktime ct; cmd.cmd = SMU_RTC; cmd.len = 1; cmd.data[0] = SMU_RTC_GET; if (smu_run_cmd(dev, &cmd, 1) != 0) return (ENXIO); ct.nsec = 0; ct.sec = bcd2bin(cmd.data[0]); ct.min = bcd2bin(cmd.data[1]); ct.hour = bcd2bin(cmd.data[2]); ct.dow = bcd2bin(cmd.data[3]); ct.day = bcd2bin(cmd.data[4]); ct.mon = bcd2bin(cmd.data[5]); ct.year = bcd2bin(cmd.data[6]) + 2000; return (clock_ct_to_ts(&ct, ts)); } static int smu_settime(device_t dev, struct timespec *ts) { static struct smu_cmd cmd; struct clocktime ct; cmd.cmd = SMU_RTC; cmd.len = 8; cmd.data[0] = SMU_RTC_SET; clock_ts_to_ct(ts, &ct); cmd.data[1] = bin2bcd(ct.sec); cmd.data[2] = bin2bcd(ct.min); cmd.data[3] = bin2bcd(ct.hour); cmd.data[4] = bin2bcd(ct.dow); cmd.data[5] = bin2bcd(ct.day); cmd.data[6] = bin2bcd(ct.mon); cmd.data[7] = bin2bcd(ct.year - 2000); return (smu_run_cmd(dev, &cmd, 0)); } /* SMU I2C Interface */ static int smuiic_probe(device_t dev); static int smuiic_attach(device_t dev); static int smuiic_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs); static phandle_t smuiic_get_node(device_t bus, device_t dev); static device_method_t smuiic_methods[] = { /* device interface */ DEVMETHOD(device_probe, smuiic_probe), DEVMETHOD(device_attach, smuiic_attach), /* iicbus interface */ DEVMETHOD(iicbus_callback, iicbus_null_callback), DEVMETHOD(iicbus_transfer, smuiic_transfer), /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, smuiic_get_node), { 0, 0 } }; struct smuiic_softc { struct mtx sc_mtx; volatile int sc_iic_inuse; int sc_busno; }; static driver_t smuiic_driver = { "iichb", smuiic_methods, sizeof(struct smuiic_softc) }; static devclass_t smuiic_devclass; DRIVER_MODULE(smuiic, smu, smuiic_driver, smuiic_devclass, 0, 0); static void smu_attach_i2c(device_t smu, phandle_t i2croot) { phandle_t child; device_t cdev; struct ofw_bus_devinfo *dinfo; char name[32]; for (child = OF_child(i2croot); child != 0; child = OF_peer(child)) { if (OF_getprop(child, "name", name, sizeof(name)) <= 0) continue; if (strcmp(name, "i2c-bus") != 0 && strcmp(name, "i2c") != 0) continue; dinfo = malloc(sizeof(struct ofw_bus_devinfo), M_SMU, M_WAITOK | M_ZERO); if (ofw_bus_gen_setup_devinfo(dinfo, child) != 0) { free(dinfo, M_SMU); continue; } cdev = device_add_child(smu, NULL, -1); if (cdev == NULL) { device_printf(smu, "<%s>: device_add_child failed\n", dinfo->obd_name); ofw_bus_gen_destroy_devinfo(dinfo); free(dinfo, M_SMU); continue; } device_set_ivars(cdev, dinfo); } } static int smuiic_probe(device_t dev) { const char *name; name = ofw_bus_get_name(dev); if (name == NULL) return (ENXIO); if (strcmp(name, "i2c-bus") == 0 || strcmp(name, "i2c") == 0) { device_set_desc(dev, "SMU I2C controller"); return (0); } return (ENXIO); } static int smuiic_attach(device_t dev) { struct smuiic_softc *sc = device_get_softc(dev); mtx_init(&sc->sc_mtx, "smuiic", NULL, MTX_DEF); sc->sc_iic_inuse = 0; /* Get our bus number */ OF_getprop(ofw_bus_get_node(dev), "reg", &sc->sc_busno, sizeof(sc->sc_busno)); /* Add the IIC bus layer */ device_add_child(dev, "iicbus", -1); return (bus_generic_attach(dev)); } static int smuiic_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) { struct smuiic_softc *sc = device_get_softc(dev); struct smu_cmd cmd; int i, j, error; mtx_lock(&sc->sc_mtx); while (sc->sc_iic_inuse) mtx_sleep(sc, &sc->sc_mtx, 0, "smuiic", 100); sc->sc_iic_inuse = 1; error = 0; for (i = 0; i < nmsgs; i++) { cmd.cmd = SMU_I2C; cmd.data[0] = sc->sc_busno; if (msgs[i].flags & IIC_M_NOSTOP) cmd.data[1] = SMU_I2C_COMBINED; else cmd.data[1] = SMU_I2C_SIMPLE; cmd.data[2] = msgs[i].slave; if (msgs[i].flags & IIC_M_RD) cmd.data[2] |= 1; if (msgs[i].flags & IIC_M_NOSTOP) { KASSERT(msgs[i].len < 4, ("oversize I2C combined message")); cmd.data[3] = min(msgs[i].len, 3); memcpy(&cmd.data[4], msgs[i].buf, min(msgs[i].len, 3)); i++; /* Advance to next part of message */ } else { cmd.data[3] = 0; memset(&cmd.data[4], 0, 3); } cmd.data[7] = msgs[i].slave; if (msgs[i].flags & IIC_M_RD) cmd.data[7] |= 1; cmd.data[8] = msgs[i].len; if (msgs[i].flags & IIC_M_RD) { memset(&cmd.data[9], 0xff, msgs[i].len); cmd.len = 9; } else { memcpy(&cmd.data[9], msgs[i].buf, msgs[i].len); cmd.len = 9 + msgs[i].len; } mtx_unlock(&sc->sc_mtx); smu_run_cmd(device_get_parent(dev), &cmd, 1); mtx_lock(&sc->sc_mtx); for (j = 0; j < 10; j++) { cmd.cmd = SMU_I2C; cmd.len = 1; cmd.data[0] = 0; memset(&cmd.data[1], 0xff, msgs[i].len); mtx_unlock(&sc->sc_mtx); smu_run_cmd(device_get_parent(dev), &cmd, 1); mtx_lock(&sc->sc_mtx); if (!(cmd.data[0] & 0x80)) break; mtx_sleep(sc, &sc->sc_mtx, 0, "smuiic", 10); } if (cmd.data[0] & 0x80) { error = EIO; msgs[i].len = 0; goto exit; } memcpy(msgs[i].buf, &cmd.data[1], msgs[i].len); msgs[i].len = cmd.len - 1; } exit: sc->sc_iic_inuse = 0; mtx_unlock(&sc->sc_mtx); wakeup(sc); return (error); } static phandle_t smuiic_get_node(device_t bus, device_t dev) { return (ofw_bus_get_node(bus)); } Index: head/sys/powerpc/powermac/smusat.c =================================================================== --- head/sys/powerpc/powermac/smusat.c (revision 300420) +++ head/sys/powerpc/powermac/smusat.c (revision 300421) @@ -1,293 +1,293 @@ /*- * Copyright (c) 2010 Nathan Whitehorn * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct smu_sensor { struct pmac_therm therm; device_t dev; cell_t reg; enum { SMU_CURRENT_SENSOR, SMU_VOLTAGE_SENSOR, SMU_POWER_SENSOR, SMU_TEMP_SENSOR } type; }; static int smusat_probe(device_t); static int smusat_attach(device_t); static int smusat_sensor_sysctl(SYSCTL_HANDLER_ARGS); static int smusat_sensor_read(struct smu_sensor *sens); static MALLOC_DEFINE(M_SMUSAT, "smusat", "SMU Sattelite Sensors"); static device_method_t smusat_methods[] = { /* Device interface */ DEVMETHOD(device_probe, smusat_probe), DEVMETHOD(device_attach, smusat_attach), { 0, 0 }, }; struct smusat_softc { struct smu_sensor *sc_sensors; int sc_nsensors; uint8_t sc_cache[16]; time_t sc_last_update; }; static driver_t smusat_driver = { "smusat", smusat_methods, sizeof(struct smusat_softc) }; static devclass_t smusat_devclass; DRIVER_MODULE(smusat, iicbus, smusat_driver, smusat_devclass, 0, 0); static int smusat_probe(device_t dev) { const char *compat = ofw_bus_get_compat(dev); if (compat == NULL || strcmp(compat, "smu-sat") != 0) return (ENXIO); device_set_desc(dev, "SMU Satellite Sensors"); return (0); } static int smusat_attach(device_t dev) { phandle_t child; struct smu_sensor *sens; struct smusat_softc *sc; struct sysctl_oid *sensroot_oid; struct sysctl_ctx_list *ctx; char type[32]; int i; sc = device_get_softc(dev); sc->sc_nsensors = 0; sc->sc_last_update = 0; for (child = OF_child(ofw_bus_get_node(dev)); child != 0; child = OF_peer(child)) sc->sc_nsensors++; if (sc->sc_nsensors == 0) { device_printf(dev, "WARNING: No sensors detected!\n"); return (-1); } sc->sc_sensors = malloc(sc->sc_nsensors * sizeof(struct smu_sensor), M_SMUSAT, M_WAITOK | M_ZERO); sens = sc->sc_sensors; sc->sc_nsensors = 0; ctx = device_get_sysctl_ctx(dev); sensroot_oid = device_get_sysctl_tree(dev); for (child = OF_child(ofw_bus_get_node(dev)); child != 0; child = OF_peer(child)) { char sysctl_name[40], sysctl_desc[40]; const char *units; sens->dev = dev; sens->reg = 0; OF_getprop(child, "reg", &sens->reg, sizeof(sens->reg)); if (sens->reg < 0x30) continue; sens->reg -= 0x30; OF_getprop(child, "zone", &sens->therm.zone, sizeof(int)); OF_getprop(child, "location", sens->therm.name, sizeof(sens->therm.name)); OF_getprop(child, "device_type", type, sizeof(type)); if (strcmp(type, "current-sensor") == 0) { sens->type = SMU_CURRENT_SENSOR; units = "mA"; } else if (strcmp(type, "temp-sensor") == 0) { sens->type = SMU_TEMP_SENSOR; units = "C"; } else if (strcmp(type, "voltage-sensor") == 0) { sens->type = SMU_VOLTAGE_SENSOR; units = "mV"; } else if (strcmp(type, "power-sensor") == 0) { sens->type = SMU_POWER_SENSOR; units = "mW"; } else { continue; } for (i = 0; i < strlen(sens->therm.name); i++) { sysctl_name[i] = tolower(sens->therm.name[i]); if (isspace(sysctl_name[i])) sysctl_name[i] = '_'; } sysctl_name[i] = 0; sprintf(sysctl_desc,"%s (%s)", sens->therm.name, units); SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(sensroot_oid), OID_AUTO, sysctl_name, CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, sc->sc_nsensors, smusat_sensor_sysctl, (sens->type == SMU_TEMP_SENSOR) ? "IK" : "I", sysctl_desc); if (sens->type == SMU_TEMP_SENSOR) { /* Make up some numbers */ - sens->therm.target_temp = 500 + 2732; /* 50 C */ - sens->therm.max_temp = 900 + 2732; /* 90 C */ + sens->therm.target_temp = 500 + 2731; /* 50 C */ + sens->therm.max_temp = 900 + 2731; /* 90 C */ sens->therm.read = (int (*)(struct pmac_therm *))smusat_sensor_read; pmac_thermal_sensor_register(&sens->therm); } sens++; sc->sc_nsensors++; } return (0); } static int smusat_updatecache(device_t dev) { uint8_t reg = 0x3f; uint8_t value[16]; struct smusat_softc *sc = device_get_softc(dev); int error; struct iic_msg msgs[2] = { {0, IIC_M_WR | IIC_M_NOSTOP, 1, ®}, {0, IIC_M_RD, 16, value}, }; msgs[0].slave = msgs[1].slave = iicbus_get_addr(dev); error = iicbus_transfer(dev, msgs, 2); if (error) return (error); sc->sc_last_update = time_uptime; memcpy(sc->sc_cache, value, sizeof(value)); return (0); } static int smusat_sensor_read(struct smu_sensor *sens) { int value, error; device_t dev; struct smusat_softc *sc; dev = sens->dev; sc = device_get_softc(dev); error = 0; if (time_uptime - sc->sc_last_update > 1) error = smusat_updatecache(dev); if (error) return (-error); value = (sc->sc_cache[sens->reg*2] << 8) + sc->sc_cache[sens->reg*2 + 1]; if (value == 0xffff) { sc->sc_last_update = 0; /* Result was bad, don't cache */ return (-EINVAL); } switch (sens->type) { case SMU_TEMP_SENSOR: /* 16.16 */ value <<= 10; /* From 16.16 to 0.1 C */ - value = 10*(value >> 16) + ((10*(value & 0xffff)) >> 16) + 2732; + value = 10*(value >> 16) + ((10*(value & 0xffff)) >> 16) + 2731; break; case SMU_VOLTAGE_SENSOR: /* 16.16 */ value <<= 4; /* Kill the .16 */ value >>= 16; break; case SMU_CURRENT_SENSOR: /* 16.16 */ value <<= 8; /* Kill the .16 */ value >>= 16; break; case SMU_POWER_SENSOR: /* Doesn't exist */ break; } return (value); } static int smusat_sensor_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev; struct smusat_softc *sc; struct smu_sensor *sens; int value, error; dev = arg1; sc = device_get_softc(dev); sens = &sc->sc_sensors[arg2]; value = smusat_sensor_read(sens); if (value < 0) return (EBUSY); error = sysctl_handle_int(oidp, &value, 0, req); return (error); }