From 9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 Mon Sep 17 00:00:00 2001 From: Yunhong Jiang Date: Tue, 4 Aug 2015 12:17:53 -0700 Subject: Add the rt linux 4.1.3-rt3 as base Import the rt linux 4.1.3-rt3 as OPNFV kvm base. It's from git://git.kernel.org/pub/scm/linux/kernel/git/rt/linux-rt-devel.git linux-4.1.y-rt and the base is: commit 0917f823c59692d751951bf5ea699a2d1e2f26a2 Author: Sebastian Andrzej Siewior Date: Sat Jul 25 12:13:34 2015 +0200 Prepare v4.1.3-rt3 Signed-off-by: Sebastian Andrzej Siewior We lose all the git history this way and it's not good. We should apply another opnfv project repo in future. Change-Id: I87543d81c9df70d99c5001fbdf646b202c19f423 Signed-off-by: Yunhong Jiang --- .../gpu/drm/nouveau/nvkm/subdev/therm/Kbuild | 13 + .../gpu/drm/nouveau/nvkm/subdev/therm/base.c | 367 +++++++++++++++++++++ .../gpu/drm/nouveau/nvkm/subdev/therm/fan.c | 282 ++++++++++++++++ .../gpu/drm/nouveau/nvkm/subdev/therm/fannil.c | 53 +++ .../gpu/drm/nouveau/nvkm/subdev/therm/fanpwm.c | 113 +++++++ .../gpu/drm/nouveau/nvkm/subdev/therm/fantog.c | 118 +++++++ .../gpu/drm/nouveau/nvkm/subdev/therm/g84.c | 266 +++++++++++++++ .../gpu/drm/nouveau/nvkm/subdev/therm/gf110.c | 174 ++++++++++ .../gpu/drm/nouveau/nvkm/subdev/therm/gm107.c | 93 ++++++ .../gpu/drm/nouveau/nvkm/subdev/therm/gt215.c | 100 ++++++ .../drivers/gpu/drm/nouveau/nvkm/subdev/therm/ic.c | 119 +++++++ .../gpu/drm/nouveau/nvkm/subdev/therm/nv40.c | 225 +++++++++++++ .../gpu/drm/nouveau/nvkm/subdev/therm/nv50.c | 198 +++++++++++ .../gpu/drm/nouveau/nvkm/subdev/therm/priv.h | 153 +++++++++ .../gpu/drm/nouveau/nvkm/subdev/therm/temp.c | 259 +++++++++++++++ 15 files changed, 2533 insertions(+) create mode 100644 kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/Kbuild create mode 100644 kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/base.c create mode 100644 kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fan.c create mode 100644 kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fannil.c create mode 100644 kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fanpwm.c create mode 100644 kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fantog.c create mode 100644 kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/g84.c create mode 100644 kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/gf110.c create mode 100644 kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/gm107.c create mode 100644 kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/gt215.c create mode 100644 kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/ic.c create mode 100644 kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/nv40.c create mode 100644 kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/nv50.c create mode 100644 kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/priv.h create mode 100644 kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/temp.c (limited to 'kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm') diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/Kbuild b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/Kbuild new file mode 100644 index 000000000..5837cf129 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/Kbuild @@ -0,0 +1,13 @@ +nvkm-y += nvkm/subdev/therm/base.o +nvkm-y += nvkm/subdev/therm/fan.o +nvkm-y += nvkm/subdev/therm/fannil.o +nvkm-y += nvkm/subdev/therm/fanpwm.o +nvkm-y += nvkm/subdev/therm/fantog.o +nvkm-y += nvkm/subdev/therm/ic.o +nvkm-y += nvkm/subdev/therm/temp.o +nvkm-y += nvkm/subdev/therm/nv40.o +nvkm-y += nvkm/subdev/therm/nv50.o +nvkm-y += nvkm/subdev/therm/g84.o +nvkm-y += nvkm/subdev/therm/gt215.o +nvkm-y += nvkm/subdev/therm/gf110.o +nvkm-y += nvkm/subdev/therm/gm107.o diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/base.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/base.c new file mode 100644 index 000000000..ec327cb64 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/base.c @@ -0,0 +1,367 @@ +/* + * Copyright 2012 The Nouveau community + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Martin Peres + */ +#include "priv.h" + +#include + +static int +nvkm_therm_update_trip(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *priv = (void *)therm; + struct nvbios_therm_trip_point *trip = priv->fan->bios.trip, + *cur_trip = NULL, + *last_trip = priv->last_trip; + u8 temp = therm->temp_get(therm); + u16 duty, i; + + /* look for the trip point corresponding to the current temperature */ + cur_trip = NULL; + for (i = 0; i < priv->fan->bios.nr_fan_trip; i++) { + if (temp >= trip[i].temp) + cur_trip = &trip[i]; + } + + /* account for the hysteresis cycle */ + if (last_trip && temp <= (last_trip->temp) && + temp > (last_trip->temp - last_trip->hysteresis)) + cur_trip = last_trip; + + if (cur_trip) { + duty = cur_trip->fan_duty; + priv->last_trip = cur_trip; + } else { + duty = 0; + priv->last_trip = NULL; + } + + return duty; +} + +static int +nvkm_therm_update_linear(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *priv = (void *)therm; + u8 linear_min_temp = priv->fan->bios.linear_min_temp; + u8 linear_max_temp = priv->fan->bios.linear_max_temp; + u8 temp = therm->temp_get(therm); + u16 duty; + + /* handle the non-linear part first */ + if (temp < linear_min_temp) + return priv->fan->bios.min_duty; + else if (temp > linear_max_temp) + return priv->fan->bios.max_duty; + + /* we are in the linear zone */ + duty = (temp - linear_min_temp); + duty *= (priv->fan->bios.max_duty - priv->fan->bios.min_duty); + duty /= (linear_max_temp - linear_min_temp); + duty += priv->fan->bios.min_duty; + return duty; +} + +static void +nvkm_therm_update(struct nvkm_therm *therm, int mode) +{ + struct nvkm_timer *ptimer = nvkm_timer(therm); + struct nvkm_therm_priv *priv = (void *)therm; + unsigned long flags; + bool immd = true; + bool poll = true; + int duty = -1; + + spin_lock_irqsave(&priv->lock, flags); + if (mode < 0) + mode = priv->mode; + priv->mode = mode; + + switch (mode) { + case NVKM_THERM_CTRL_MANUAL: + ptimer->alarm_cancel(ptimer, &priv->alarm); + duty = nvkm_therm_fan_get(therm); + if (duty < 0) + duty = 100; + poll = false; + break; + case NVKM_THERM_CTRL_AUTO: + switch(priv->fan->bios.fan_mode) { + case NVBIOS_THERM_FAN_TRIP: + duty = nvkm_therm_update_trip(therm); + break; + case NVBIOS_THERM_FAN_LINEAR: + duty = nvkm_therm_update_linear(therm); + break; + case NVBIOS_THERM_FAN_OTHER: + if (priv->cstate) + duty = priv->cstate; + poll = false; + break; + } + immd = false; + break; + case NVKM_THERM_CTRL_NONE: + default: + ptimer->alarm_cancel(ptimer, &priv->alarm); + poll = false; + } + + if (list_empty(&priv->alarm.head) && poll) + ptimer->alarm(ptimer, 1000000000ULL, &priv->alarm); + spin_unlock_irqrestore(&priv->lock, flags); + + if (duty >= 0) { + nv_debug(therm, "FAN target request: %d%%\n", duty); + nvkm_therm_fan_set(therm, immd, duty); + } +} + +int +nvkm_therm_cstate(struct nvkm_therm *ptherm, int fan, int dir) +{ + struct nvkm_therm_priv *priv = (void *)ptherm; + if (!dir || (dir < 0 && fan < priv->cstate) || + (dir > 0 && fan > priv->cstate)) { + nv_debug(ptherm, "default fan speed -> %d%%\n", fan); + priv->cstate = fan; + nvkm_therm_update(ptherm, -1); + } + return 0; +} + +static void +nvkm_therm_alarm(struct nvkm_alarm *alarm) +{ + struct nvkm_therm_priv *priv = + container_of(alarm, struct nvkm_therm_priv, alarm); + nvkm_therm_update(&priv->base, -1); +} + +int +nvkm_therm_fan_mode(struct nvkm_therm *therm, int mode) +{ + struct nvkm_therm_priv *priv = (void *)therm; + struct nvkm_device *device = nv_device(therm); + static const char *name[] = { + "disabled", + "manual", + "automatic" + }; + + /* The default PPWR ucode on fermi interferes with fan management */ + if ((mode >= ARRAY_SIZE(name)) || + (mode != NVKM_THERM_CTRL_NONE && device->card_type >= NV_C0 && + !nvkm_subdev(device, NVDEV_SUBDEV_PMU))) + return -EINVAL; + + /* do not allow automatic fan management if the thermal sensor is + * not available */ + if (mode == NVKM_THERM_CTRL_AUTO && therm->temp_get(therm) < 0) + return -EINVAL; + + if (priv->mode == mode) + return 0; + + nv_info(therm, "fan management: %s\n", name[mode]); + nvkm_therm_update(therm, mode); + return 0; +} + +int +nvkm_therm_attr_get(struct nvkm_therm *therm, + enum nvkm_therm_attr_type type) +{ + struct nvkm_therm_priv *priv = (void *)therm; + + switch (type) { + case NVKM_THERM_ATTR_FAN_MIN_DUTY: + return priv->fan->bios.min_duty; + case NVKM_THERM_ATTR_FAN_MAX_DUTY: + return priv->fan->bios.max_duty; + case NVKM_THERM_ATTR_FAN_MODE: + return priv->mode; + case NVKM_THERM_ATTR_THRS_FAN_BOOST: + return priv->bios_sensor.thrs_fan_boost.temp; + case NVKM_THERM_ATTR_THRS_FAN_BOOST_HYST: + return priv->bios_sensor.thrs_fan_boost.hysteresis; + case NVKM_THERM_ATTR_THRS_DOWN_CLK: + return priv->bios_sensor.thrs_down_clock.temp; + case NVKM_THERM_ATTR_THRS_DOWN_CLK_HYST: + return priv->bios_sensor.thrs_down_clock.hysteresis; + case NVKM_THERM_ATTR_THRS_CRITICAL: + return priv->bios_sensor.thrs_critical.temp; + case NVKM_THERM_ATTR_THRS_CRITICAL_HYST: + return priv->bios_sensor.thrs_critical.hysteresis; + case NVKM_THERM_ATTR_THRS_SHUTDOWN: + return priv->bios_sensor.thrs_shutdown.temp; + case NVKM_THERM_ATTR_THRS_SHUTDOWN_HYST: + return priv->bios_sensor.thrs_shutdown.hysteresis; + } + + return -EINVAL; +} + +int +nvkm_therm_attr_set(struct nvkm_therm *therm, + enum nvkm_therm_attr_type type, int value) +{ + struct nvkm_therm_priv *priv = (void *)therm; + + switch (type) { + case NVKM_THERM_ATTR_FAN_MIN_DUTY: + if (value < 0) + value = 0; + if (value > priv->fan->bios.max_duty) + value = priv->fan->bios.max_duty; + priv->fan->bios.min_duty = value; + return 0; + case NVKM_THERM_ATTR_FAN_MAX_DUTY: + if (value < 0) + value = 0; + if (value < priv->fan->bios.min_duty) + value = priv->fan->bios.min_duty; + priv->fan->bios.max_duty = value; + return 0; + case NVKM_THERM_ATTR_FAN_MODE: + return nvkm_therm_fan_mode(therm, value); + case NVKM_THERM_ATTR_THRS_FAN_BOOST: + priv->bios_sensor.thrs_fan_boost.temp = value; + priv->sensor.program_alarms(therm); + return 0; + case NVKM_THERM_ATTR_THRS_FAN_BOOST_HYST: + priv->bios_sensor.thrs_fan_boost.hysteresis = value; + priv->sensor.program_alarms(therm); + return 0; + case NVKM_THERM_ATTR_THRS_DOWN_CLK: + priv->bios_sensor.thrs_down_clock.temp = value; + priv->sensor.program_alarms(therm); + return 0; + case NVKM_THERM_ATTR_THRS_DOWN_CLK_HYST: + priv->bios_sensor.thrs_down_clock.hysteresis = value; + priv->sensor.program_alarms(therm); + return 0; + case NVKM_THERM_ATTR_THRS_CRITICAL: + priv->bios_sensor.thrs_critical.temp = value; + priv->sensor.program_alarms(therm); + return 0; + case NVKM_THERM_ATTR_THRS_CRITICAL_HYST: + priv->bios_sensor.thrs_critical.hysteresis = value; + priv->sensor.program_alarms(therm); + return 0; + case NVKM_THERM_ATTR_THRS_SHUTDOWN: + priv->bios_sensor.thrs_shutdown.temp = value; + priv->sensor.program_alarms(therm); + return 0; + case NVKM_THERM_ATTR_THRS_SHUTDOWN_HYST: + priv->bios_sensor.thrs_shutdown.hysteresis = value; + priv->sensor.program_alarms(therm); + return 0; + } + + return -EINVAL; +} + +int +_nvkm_therm_init(struct nvkm_object *object) +{ + struct nvkm_therm *therm = (void *)object; + struct nvkm_therm_priv *priv = (void *)therm; + int ret; + + ret = nvkm_subdev_init(&therm->base); + if (ret) + return ret; + + if (priv->suspend >= 0) { + /* restore the pwm value only when on manual or auto mode */ + if (priv->suspend > 0) + nvkm_therm_fan_set(therm, true, priv->fan->percent); + + nvkm_therm_fan_mode(therm, priv->suspend); + } + nvkm_therm_sensor_init(therm); + nvkm_therm_fan_init(therm); + return 0; +} + +int +_nvkm_therm_fini(struct nvkm_object *object, bool suspend) +{ + struct nvkm_therm *therm = (void *)object; + struct nvkm_therm_priv *priv = (void *)therm; + + nvkm_therm_fan_fini(therm, suspend); + nvkm_therm_sensor_fini(therm, suspend); + if (suspend) { + priv->suspend = priv->mode; + priv->mode = NVKM_THERM_CTRL_NONE; + } + + return nvkm_subdev_fini(&therm->base, suspend); +} + +int +nvkm_therm_create_(struct nvkm_object *parent, struct nvkm_object *engine, + struct nvkm_oclass *oclass, int length, void **pobject) +{ + struct nvkm_therm_priv *priv; + int ret; + + ret = nvkm_subdev_create_(parent, engine, oclass, 0, "PTHERM", + "therm", length, pobject); + priv = *pobject; + if (ret) + return ret; + + nvkm_alarm_init(&priv->alarm, nvkm_therm_alarm); + spin_lock_init(&priv->lock); + spin_lock_init(&priv->sensor.alarm_program_lock); + + priv->base.fan_get = nvkm_therm_fan_user_get; + priv->base.fan_set = nvkm_therm_fan_user_set; + priv->base.fan_sense = nvkm_therm_fan_sense; + priv->base.attr_get = nvkm_therm_attr_get; + priv->base.attr_set = nvkm_therm_attr_set; + priv->mode = priv->suspend = -1; /* undefined */ + return 0; +} + +int +nvkm_therm_preinit(struct nvkm_therm *therm) +{ + nvkm_therm_sensor_ctor(therm); + nvkm_therm_ic_ctor(therm); + nvkm_therm_fan_ctor(therm); + + nvkm_therm_fan_mode(therm, NVKM_THERM_CTRL_AUTO); + nvkm_therm_sensor_preinit(therm); + return 0; +} + +void +_nvkm_therm_dtor(struct nvkm_object *object) +{ + struct nvkm_therm_priv *priv = (void *)object; + kfree(priv->fan); + nvkm_subdev_destroy(&priv->base.base); +} diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fan.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fan.c new file mode 100644 index 000000000..434fa745c --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fan.c @@ -0,0 +1,282 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + * Martin Peres + */ +#include "priv.h" + +#include +#include +#include + +static int +nvkm_fan_update(struct nvkm_fan *fan, bool immediate, int target) +{ + struct nvkm_therm *therm = fan->parent; + struct nvkm_therm_priv *priv = (void *)therm; + struct nvkm_timer *ptimer = nvkm_timer(priv); + unsigned long flags; + int ret = 0; + int duty; + + /* update target fan speed, restricting to allowed range */ + spin_lock_irqsave(&fan->lock, flags); + if (target < 0) + target = fan->percent; + target = max_t(u8, target, fan->bios.min_duty); + target = min_t(u8, target, fan->bios.max_duty); + if (fan->percent != target) { + nv_debug(therm, "FAN target: %d\n", target); + fan->percent = target; + } + + /* check that we're not already at the target duty cycle */ + duty = fan->get(therm); + if (duty == target) { + spin_unlock_irqrestore(&fan->lock, flags); + return 0; + } + + /* smooth out the fanspeed increase/decrease */ + if (!immediate && duty >= 0) { + /* the constant "3" is a rough approximation taken from + * nvidia's behaviour. + * it is meant to bump the fan speed more incrementally + */ + if (duty < target) + duty = min(duty + 3, target); + else if (duty > target) + duty = max(duty - 3, target); + } else { + duty = target; + } + + nv_debug(therm, "FAN update: %d\n", duty); + ret = fan->set(therm, duty); + if (ret) { + spin_unlock_irqrestore(&fan->lock, flags); + return ret; + } + + /* fan speed updated, drop the fan lock before grabbing the + * alarm-scheduling lock and risking a deadlock + */ + spin_unlock_irqrestore(&fan->lock, flags); + + /* schedule next fan update, if not at target speed already */ + if (list_empty(&fan->alarm.head) && target != duty) { + u16 bump_period = fan->bios.bump_period; + u16 slow_down_period = fan->bios.slow_down_period; + u64 delay; + + if (duty > target) + delay = slow_down_period; + else if (duty == target) + delay = min(bump_period, slow_down_period) ; + else + delay = bump_period; + + ptimer->alarm(ptimer, delay * 1000 * 1000, &fan->alarm); + } + + return ret; +} + +static void +nvkm_fan_alarm(struct nvkm_alarm *alarm) +{ + struct nvkm_fan *fan = container_of(alarm, struct nvkm_fan, alarm); + nvkm_fan_update(fan, false, -1); +} + +int +nvkm_therm_fan_get(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *priv = (void *)therm; + return priv->fan->get(therm); +} + +int +nvkm_therm_fan_set(struct nvkm_therm *therm, bool immediate, int percent) +{ + struct nvkm_therm_priv *priv = (void *)therm; + return nvkm_fan_update(priv->fan, immediate, percent); +} + +int +nvkm_therm_fan_sense(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *priv = (void *)therm; + struct nvkm_timer *ptimer = nvkm_timer(therm); + struct nvkm_gpio *gpio = nvkm_gpio(therm); + u32 cycles, cur, prev; + u64 start, end, tach; + + if (priv->fan->tach.func == DCB_GPIO_UNUSED) + return -ENODEV; + + /* Time a complete rotation and extrapolate to RPM: + * When the fan spins, it changes the value of GPIO FAN_SENSE. + * We get 4 changes (0 -> 1 -> 0 -> 1) per complete rotation. + */ + start = ptimer->read(ptimer); + prev = gpio->get(gpio, 0, priv->fan->tach.func, priv->fan->tach.line); + cycles = 0; + do { + usleep_range(500, 1000); /* supports 0 < rpm < 7500 */ + + cur = gpio->get(gpio, 0, priv->fan->tach.func, priv->fan->tach.line); + if (prev != cur) { + if (!start) + start = ptimer->read(ptimer); + cycles++; + prev = cur; + } + } while (cycles < 5 && ptimer->read(ptimer) - start < 250000000); + end = ptimer->read(ptimer); + + if (cycles == 5) { + tach = (u64)60000000000ULL; + do_div(tach, (end - start)); + return tach; + } else + return 0; +} + +int +nvkm_therm_fan_user_get(struct nvkm_therm *therm) +{ + return nvkm_therm_fan_get(therm); +} + +int +nvkm_therm_fan_user_set(struct nvkm_therm *therm, int percent) +{ + struct nvkm_therm_priv *priv = (void *)therm; + + if (priv->mode != NVKM_THERM_CTRL_MANUAL) + return -EINVAL; + + return nvkm_therm_fan_set(therm, true, percent); +} + +static void +nvkm_therm_fan_set_defaults(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *priv = (void *)therm; + + priv->fan->bios.pwm_freq = 0; + priv->fan->bios.min_duty = 0; + priv->fan->bios.max_duty = 100; + priv->fan->bios.bump_period = 500; + priv->fan->bios.slow_down_period = 2000; + priv->fan->bios.linear_min_temp = 40; + priv->fan->bios.linear_max_temp = 85; +} + +static void +nvkm_therm_fan_safety_checks(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *priv = (void *)therm; + + if (priv->fan->bios.min_duty > 100) + priv->fan->bios.min_duty = 100; + if (priv->fan->bios.max_duty > 100) + priv->fan->bios.max_duty = 100; + + if (priv->fan->bios.min_duty > priv->fan->bios.max_duty) + priv->fan->bios.min_duty = priv->fan->bios.max_duty; +} + +int +nvkm_therm_fan_init(struct nvkm_therm *therm) +{ + return 0; +} + +int +nvkm_therm_fan_fini(struct nvkm_therm *therm, bool suspend) +{ + struct nvkm_therm_priv *priv = (void *)therm; + struct nvkm_timer *ptimer = nvkm_timer(therm); + + if (suspend) + ptimer->alarm_cancel(ptimer, &priv->fan->alarm); + return 0; +} + +int +nvkm_therm_fan_ctor(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *priv = (void *)therm; + struct nvkm_gpio *gpio = nvkm_gpio(therm); + struct nvkm_bios *bios = nvkm_bios(therm); + struct dcb_gpio_func func; + int ret; + + /* attempt to locate a drivable fan, and determine control method */ + ret = gpio->find(gpio, 0, DCB_GPIO_FAN, 0xff, &func); + if (ret == 0) { + /* FIXME: is this really the place to perform such checks ? */ + if (func.line != 16 && func.log[0] & DCB_GPIO_LOG_DIR_IN) { + nv_debug(therm, "GPIO_FAN is in input mode\n"); + ret = -EINVAL; + } else { + ret = nvkm_fanpwm_create(therm, &func); + if (ret != 0) + ret = nvkm_fantog_create(therm, &func); + } + } + + /* no controllable fan found, create a dummy fan module */ + if (ret != 0) { + ret = nvkm_fannil_create(therm); + if (ret) + return ret; + } + + nv_info(therm, "FAN control: %s\n", priv->fan->type); + + /* read the current speed, it is useful when resuming */ + priv->fan->percent = nvkm_therm_fan_get(therm); + + /* attempt to detect a tachometer connection */ + ret = gpio->find(gpio, 0, DCB_GPIO_FAN_SENSE, 0xff, &priv->fan->tach); + if (ret) + priv->fan->tach.func = DCB_GPIO_UNUSED; + + /* initialise fan bump/slow update handling */ + priv->fan->parent = therm; + nvkm_alarm_init(&priv->fan->alarm, nvkm_fan_alarm); + spin_lock_init(&priv->fan->lock); + + /* other random init... */ + nvkm_therm_fan_set_defaults(therm); + nvbios_perf_fan_parse(bios, &priv->fan->perf); + if (!nvbios_fan_parse(bios, &priv->fan->bios)) { + nv_debug(therm, "parsing the fan table failed\n"); + if (nvbios_therm_fan_parse(bios, &priv->fan->bios)) + nv_error(therm, "parsing both fan tables failed\n"); + } + nvkm_therm_fan_safety_checks(therm); + return 0; +} diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fannil.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fannil.c new file mode 100644 index 000000000..534e5970e --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fannil.c @@ -0,0 +1,53 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ +#include "priv.h" + +static int +nvkm_fannil_get(struct nvkm_therm *therm) +{ + return -ENODEV; +} + +static int +nvkm_fannil_set(struct nvkm_therm *therm, int percent) +{ + return -ENODEV; +} + +int +nvkm_fannil_create(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *tpriv = (void *)therm; + struct nvkm_fan *priv; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + tpriv->fan = priv; + if (!priv) + return -ENOMEM; + + priv->type = "none / external"; + priv->get = nvkm_fannil_get; + priv->set = nvkm_fannil_set; + return 0; +} diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fanpwm.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fanpwm.c new file mode 100644 index 000000000..bde5ceaeb --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fanpwm.c @@ -0,0 +1,113 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + * Martin Peres + */ +#include "priv.h" + +#include +#include +#include +#include +#include + +struct nvkm_fanpwm_priv { + struct nvkm_fan base; + struct dcb_gpio_func func; +}; + +static int +nvkm_fanpwm_get(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *tpriv = (void *)therm; + struct nvkm_fanpwm_priv *priv = (void *)tpriv->fan; + struct nvkm_gpio *gpio = nvkm_gpio(therm); + int card_type = nv_device(therm)->card_type; + u32 divs, duty; + int ret; + + ret = therm->pwm_get(therm, priv->func.line, &divs, &duty); + if (ret == 0 && divs) { + divs = max(divs, duty); + if (card_type <= NV_40 || (priv->func.log[0] & 1)) + duty = divs - duty; + return (duty * 100) / divs; + } + + return gpio->get(gpio, 0, priv->func.func, priv->func.line) * 100; +} + +static int +nvkm_fanpwm_set(struct nvkm_therm *therm, int percent) +{ + struct nvkm_therm_priv *tpriv = (void *)therm; + struct nvkm_fanpwm_priv *priv = (void *)tpriv->fan; + int card_type = nv_device(therm)->card_type; + u32 divs, duty; + int ret; + + divs = priv->base.perf.pwm_divisor; + if (priv->base.bios.pwm_freq) { + divs = 1; + if (therm->pwm_clock) + divs = therm->pwm_clock(therm, priv->func.line); + divs /= priv->base.bios.pwm_freq; + } + + duty = ((divs * percent) + 99) / 100; + if (card_type <= NV_40 || (priv->func.log[0] & 1)) + duty = divs - duty; + + ret = therm->pwm_set(therm, priv->func.line, divs, duty); + if (ret == 0) + ret = therm->pwm_ctrl(therm, priv->func.line, true); + return ret; +} + +int +nvkm_fanpwm_create(struct nvkm_therm *therm, struct dcb_gpio_func *func) +{ + struct nvkm_device *device = nv_device(therm); + struct nvkm_therm_priv *tpriv = (void *)therm; + struct nvkm_bios *bios = nvkm_bios(therm); + struct nvkm_fanpwm_priv *priv; + struct nvbios_therm_fan fan; + u32 divs, duty; + + nvbios_fan_parse(bios, &fan); + + if (!nvkm_boolopt(device->cfgopt, "NvFanPWM", func->param) || + !therm->pwm_ctrl || fan.type == NVBIOS_THERM_FAN_TOGGLE || + therm->pwm_get(therm, func->line, &divs, &duty) == -ENODEV) + return -ENODEV; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + tpriv->fan = &priv->base; + if (!priv) + return -ENOMEM; + + priv->base.type = "PWM"; + priv->base.get = nvkm_fanpwm_get; + priv->base.set = nvkm_fanpwm_set; + priv->func = *func; + return 0; +} diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fantog.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fantog.c new file mode 100644 index 000000000..4ce041e81 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/fantog.c @@ -0,0 +1,118 @@ +/* + * Copyright 2012 The Nouveau community + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Martin Peres + */ +#include "priv.h" + +#include +#include + +struct nvkm_fantog_priv { + struct nvkm_fan base; + struct nvkm_alarm alarm; + spinlock_t lock; + u32 period_us; + u32 percent; + struct dcb_gpio_func func; +}; + +static void +nvkm_fantog_update(struct nvkm_fantog_priv *priv, int percent) +{ + struct nvkm_therm_priv *tpriv = (void *)priv->base.parent; + struct nvkm_timer *ptimer = nvkm_timer(tpriv); + struct nvkm_gpio *gpio = nvkm_gpio(tpriv); + unsigned long flags; + int duty; + + spin_lock_irqsave(&priv->lock, flags); + if (percent < 0) + percent = priv->percent; + priv->percent = percent; + + duty = !gpio->get(gpio, 0, DCB_GPIO_FAN, 0xff); + gpio->set(gpio, 0, DCB_GPIO_FAN, 0xff, duty); + + if (list_empty(&priv->alarm.head) && percent != (duty * 100)) { + u64 next_change = (percent * priv->period_us) / 100; + if (!duty) + next_change = priv->period_us - next_change; + ptimer->alarm(ptimer, next_change * 1000, &priv->alarm); + } + spin_unlock_irqrestore(&priv->lock, flags); +} + +static void +nvkm_fantog_alarm(struct nvkm_alarm *alarm) +{ + struct nvkm_fantog_priv *priv = + container_of(alarm, struct nvkm_fantog_priv, alarm); + nvkm_fantog_update(priv, -1); +} + +static int +nvkm_fantog_get(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *tpriv = (void *)therm; + struct nvkm_fantog_priv *priv = (void *)tpriv->fan; + return priv->percent; +} + +static int +nvkm_fantog_set(struct nvkm_therm *therm, int percent) +{ + struct nvkm_therm_priv *tpriv = (void *)therm; + struct nvkm_fantog_priv *priv = (void *)tpriv->fan; + if (therm->pwm_ctrl) + therm->pwm_ctrl(therm, priv->func.line, false); + nvkm_fantog_update(priv, percent); + return 0; +} + +int +nvkm_fantog_create(struct nvkm_therm *therm, struct dcb_gpio_func *func) +{ + struct nvkm_therm_priv *tpriv = (void *)therm; + struct nvkm_fantog_priv *priv; + int ret; + + if (therm->pwm_ctrl) { + ret = therm->pwm_ctrl(therm, func->line, false); + if (ret) + return ret; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + tpriv->fan = &priv->base; + if (!priv) + return -ENOMEM; + + priv->base.type = "toggle"; + priv->base.get = nvkm_fantog_get; + priv->base.set = nvkm_fantog_set; + nvkm_alarm_init(&priv->alarm, nvkm_fantog_alarm); + priv->period_us = 100000; /* 10Hz */ + priv->percent = 100; + priv->func = *func; + spin_lock_init(&priv->lock); + return 0; +} diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/g84.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/g84.c new file mode 100644 index 000000000..85b5d0c18 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/g84.c @@ -0,0 +1,266 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + * Martin Peres + */ +#include "priv.h" + +#include + +struct g84_therm_priv { + struct nvkm_therm_priv base; +}; + +int +g84_temp_get(struct nvkm_therm *therm) +{ + struct nvkm_fuse *fuse = nvkm_fuse(therm); + + if (nv_ro32(fuse, 0x1a8) == 1) + return nv_rd32(therm, 0x20400); + else + return -ENODEV; +} + +void +g84_sensor_setup(struct nvkm_therm *therm) +{ + struct nvkm_fuse *fuse = nvkm_fuse(therm); + + /* enable temperature reading for cards with insane defaults */ + if (nv_ro32(fuse, 0x1a8) == 1) { + nv_mask(therm, 0x20008, 0x80008000, 0x80000000); + nv_mask(therm, 0x2000c, 0x80000003, 0x00000000); + mdelay(20); /* wait for the temperature to stabilize */ + } +} + +static void +g84_therm_program_alarms(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *priv = (void *)therm; + struct nvbios_therm_sensor *sensor = &priv->bios_sensor; + unsigned long flags; + + spin_lock_irqsave(&priv->sensor.alarm_program_lock, flags); + + /* enable RISING and FALLING IRQs for shutdown, THRS 0, 1, 2 and 4 */ + nv_wr32(therm, 0x20000, 0x000003ff); + + /* shutdown: The computer should be shutdown when reached */ + nv_wr32(therm, 0x20484, sensor->thrs_shutdown.hysteresis); + nv_wr32(therm, 0x20480, sensor->thrs_shutdown.temp); + + /* THRS_1 : fan boost*/ + nv_wr32(therm, 0x204c4, sensor->thrs_fan_boost.temp); + + /* THRS_2 : critical */ + nv_wr32(therm, 0x204c0, sensor->thrs_critical.temp); + + /* THRS_4 : down clock */ + nv_wr32(therm, 0x20414, sensor->thrs_down_clock.temp); + spin_unlock_irqrestore(&priv->sensor.alarm_program_lock, flags); + + nv_debug(therm, + "Programmed thresholds [ %d(%d), %d(%d), %d(%d), %d(%d) ]\n", + sensor->thrs_fan_boost.temp, sensor->thrs_fan_boost.hysteresis, + sensor->thrs_down_clock.temp, + sensor->thrs_down_clock.hysteresis, + sensor->thrs_critical.temp, sensor->thrs_critical.hysteresis, + sensor->thrs_shutdown.temp, sensor->thrs_shutdown.hysteresis); + +} + +/* must be called with alarm_program_lock taken ! */ +static void +g84_therm_threshold_hyst_emulation(struct nvkm_therm *therm, + uint32_t thrs_reg, u8 status_bit, + const struct nvbios_therm_threshold *thrs, + enum nvkm_therm_thrs thrs_name) +{ + enum nvkm_therm_thrs_direction direction; + enum nvkm_therm_thrs_state prev_state, new_state; + int temp, cur; + + prev_state = nvkm_therm_sensor_get_threshold_state(therm, thrs_name); + temp = nv_rd32(therm, thrs_reg); + + /* program the next threshold */ + if (temp == thrs->temp) { + nv_wr32(therm, thrs_reg, thrs->temp - thrs->hysteresis); + new_state = NVKM_THERM_THRS_HIGHER; + } else { + nv_wr32(therm, thrs_reg, thrs->temp); + new_state = NVKM_THERM_THRS_LOWER; + } + + /* fix the state (in case someone reprogrammed the alarms) */ + cur = therm->temp_get(therm); + if (new_state == NVKM_THERM_THRS_LOWER && cur > thrs->temp) + new_state = NVKM_THERM_THRS_HIGHER; + else if (new_state == NVKM_THERM_THRS_HIGHER && + cur < thrs->temp - thrs->hysteresis) + new_state = NVKM_THERM_THRS_LOWER; + nvkm_therm_sensor_set_threshold_state(therm, thrs_name, new_state); + + /* find the direction */ + if (prev_state < new_state) + direction = NVKM_THERM_THRS_RISING; + else if (prev_state > new_state) + direction = NVKM_THERM_THRS_FALLING; + else + return; + + /* advertise a change in direction */ + nvkm_therm_sensor_event(therm, thrs_name, direction); +} + +static void +g84_therm_intr(struct nvkm_subdev *subdev) +{ + struct nvkm_therm *therm = nvkm_therm(subdev); + struct nvkm_therm_priv *priv = (void *)therm; + struct nvbios_therm_sensor *sensor = &priv->bios_sensor; + unsigned long flags; + uint32_t intr; + + spin_lock_irqsave(&priv->sensor.alarm_program_lock, flags); + + intr = nv_rd32(therm, 0x20100) & 0x3ff; + + /* THRS_4: downclock */ + if (intr & 0x002) { + g84_therm_threshold_hyst_emulation(therm, 0x20414, 24, + &sensor->thrs_down_clock, + NVKM_THERM_THRS_DOWNCLOCK); + intr &= ~0x002; + } + + /* shutdown */ + if (intr & 0x004) { + g84_therm_threshold_hyst_emulation(therm, 0x20480, 20, + &sensor->thrs_shutdown, + NVKM_THERM_THRS_SHUTDOWN); + intr &= ~0x004; + } + + /* THRS_1 : fan boost */ + if (intr & 0x008) { + g84_therm_threshold_hyst_emulation(therm, 0x204c4, 21, + &sensor->thrs_fan_boost, + NVKM_THERM_THRS_FANBOOST); + intr &= ~0x008; + } + + /* THRS_2 : critical */ + if (intr & 0x010) { + g84_therm_threshold_hyst_emulation(therm, 0x204c0, 22, + &sensor->thrs_critical, + NVKM_THERM_THRS_CRITICAL); + intr &= ~0x010; + } + + if (intr) + nv_error(therm, "unhandled intr 0x%08x\n", intr); + + /* ACK everything */ + nv_wr32(therm, 0x20100, 0xffffffff); + nv_wr32(therm, 0x1100, 0x10000); /* PBUS */ + + spin_unlock_irqrestore(&priv->sensor.alarm_program_lock, flags); +} + +static int +g84_therm_init(struct nvkm_object *object) +{ + struct g84_therm_priv *priv = (void *)object; + int ret; + + ret = nvkm_therm_init(&priv->base.base); + if (ret) + return ret; + + g84_sensor_setup(&priv->base.base); + return 0; +} + +static int +g84_therm_ctor(struct nvkm_object *parent, struct nvkm_object *engine, + struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject) +{ + struct g84_therm_priv *priv; + int ret; + + ret = nvkm_therm_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->base.base.pwm_ctrl = nv50_fan_pwm_ctrl; + priv->base.base.pwm_get = nv50_fan_pwm_get; + priv->base.base.pwm_set = nv50_fan_pwm_set; + priv->base.base.pwm_clock = nv50_fan_pwm_clock; + priv->base.base.temp_get = g84_temp_get; + priv->base.sensor.program_alarms = g84_therm_program_alarms; + nv_subdev(priv)->intr = g84_therm_intr; + + /* init the thresholds */ + nvkm_therm_sensor_set_threshold_state(&priv->base.base, + NVKM_THERM_THRS_SHUTDOWN, + NVKM_THERM_THRS_LOWER); + nvkm_therm_sensor_set_threshold_state(&priv->base.base, + NVKM_THERM_THRS_FANBOOST, + NVKM_THERM_THRS_LOWER); + nvkm_therm_sensor_set_threshold_state(&priv->base.base, + NVKM_THERM_THRS_CRITICAL, + NVKM_THERM_THRS_LOWER); + nvkm_therm_sensor_set_threshold_state(&priv->base.base, + NVKM_THERM_THRS_DOWNCLOCK, + NVKM_THERM_THRS_LOWER); + + return nvkm_therm_preinit(&priv->base.base); +} + +int +g84_therm_fini(struct nvkm_object *object, bool suspend) +{ + /* Disable PTherm IRQs */ + nv_wr32(object, 0x20000, 0x00000000); + + /* ACK all PTherm IRQs */ + nv_wr32(object, 0x20100, 0xffffffff); + nv_wr32(object, 0x1100, 0x10000); /* PBUS */ + + return _nvkm_therm_fini(object, suspend); +} + +struct nvkm_oclass +g84_therm_oclass = { + .handle = NV_SUBDEV(THERM, 0x84), + .ofuncs = &(struct nvkm_ofuncs) { + .ctor = g84_therm_ctor, + .dtor = _nvkm_therm_dtor, + .init = g84_therm_init, + .fini = g84_therm_fini, + }, +}; diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/gf110.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/gf110.c new file mode 100644 index 000000000..46b7e656a --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/gf110.c @@ -0,0 +1,174 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ +#include "priv.h" + +#include + +struct gf110_therm_priv { + struct nvkm_therm_priv base; +}; + +static int +pwm_info(struct nvkm_therm *therm, int line) +{ + u32 gpio = nv_rd32(therm, 0x00d610 + (line * 0x04)); + + switch (gpio & 0x000000c0) { + case 0x00000000: /* normal mode, possibly pwm forced off by us */ + case 0x00000040: /* nvio special */ + switch (gpio & 0x0000001f) { + case 0x00: return 2; + case 0x19: return 1; + case 0x1c: return 0; + case 0x1e: return 2; + default: + break; + } + default: + break; + } + + nv_error(therm, "GPIO %d unknown PWM: 0x%08x\n", line, gpio); + return -ENODEV; +} + +static int +gf110_fan_pwm_ctrl(struct nvkm_therm *therm, int line, bool enable) +{ + u32 data = enable ? 0x00000040 : 0x00000000; + int indx = pwm_info(therm, line); + if (indx < 0) + return indx; + else if (indx < 2) + nv_mask(therm, 0x00d610 + (line * 0x04), 0x000000c0, data); + /* nothing to do for indx == 2, it seems hardwired to PTHERM */ + return 0; +} + +static int +gf110_fan_pwm_get(struct nvkm_therm *therm, int line, u32 *divs, u32 *duty) +{ + int indx = pwm_info(therm, line); + if (indx < 0) + return indx; + else if (indx < 2) { + if (nv_rd32(therm, 0x00d610 + (line * 0x04)) & 0x00000040) { + *divs = nv_rd32(therm, 0x00e114 + (indx * 8)); + *duty = nv_rd32(therm, 0x00e118 + (indx * 8)); + return 0; + } + } else if (indx == 2) { + *divs = nv_rd32(therm, 0x0200d8) & 0x1fff; + *duty = nv_rd32(therm, 0x0200dc) & 0x1fff; + return 0; + } + + return -EINVAL; +} + +static int +gf110_fan_pwm_set(struct nvkm_therm *therm, int line, u32 divs, u32 duty) +{ + int indx = pwm_info(therm, line); + if (indx < 0) + return indx; + else if (indx < 2) { + nv_wr32(therm, 0x00e114 + (indx * 8), divs); + nv_wr32(therm, 0x00e118 + (indx * 8), duty | 0x80000000); + } else if (indx == 2) { + nv_mask(therm, 0x0200d8, 0x1fff, divs); /* keep the high bits */ + nv_wr32(therm, 0x0200dc, duty | 0x40000000); + } + return 0; +} + +static int +gf110_fan_pwm_clock(struct nvkm_therm *therm, int line) +{ + int indx = pwm_info(therm, line); + if (indx < 0) + return 0; + else if (indx < 2) + return (nv_device(therm)->crystal * 1000) / 20; + else + return nv_device(therm)->crystal * 1000 / 10; +} + +int +gf110_therm_init(struct nvkm_object *object) +{ + struct gf110_therm_priv *priv = (void *)object; + int ret; + + ret = nvkm_therm_init(&priv->base.base); + if (ret) + return ret; + + /* enable fan tach, count revolutions per-second */ + nv_mask(priv, 0x00e720, 0x00000003, 0x00000002); + if (priv->base.fan->tach.func != DCB_GPIO_UNUSED) { + nv_mask(priv, 0x00d79c, 0x000000ff, priv->base.fan->tach.line); + nv_wr32(priv, 0x00e724, nv_device(priv)->crystal * 1000); + nv_mask(priv, 0x00e720, 0x00000001, 0x00000001); + } + nv_mask(priv, 0x00e720, 0x00000002, 0x00000000); + + return 0; +} + +static int +gf110_therm_ctor(struct nvkm_object *parent, struct nvkm_object *engine, + struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject) +{ + struct gf110_therm_priv *priv; + int ret; + + ret = nvkm_therm_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + g84_sensor_setup(&priv->base.base); + + priv->base.base.pwm_ctrl = gf110_fan_pwm_ctrl; + priv->base.base.pwm_get = gf110_fan_pwm_get; + priv->base.base.pwm_set = gf110_fan_pwm_set; + priv->base.base.pwm_clock = gf110_fan_pwm_clock; + priv->base.base.temp_get = g84_temp_get; + priv->base.base.fan_sense = gt215_therm_fan_sense; + priv->base.sensor.program_alarms = nvkm_therm_program_alarms_polling; + return nvkm_therm_preinit(&priv->base.base); +} + +struct nvkm_oclass +gf110_therm_oclass = { + .handle = NV_SUBDEV(THERM, 0xd0), + .ofuncs = &(struct nvkm_ofuncs) { + .ctor = gf110_therm_ctor, + .dtor = _nvkm_therm_dtor, + .init = gf110_therm_init, + .fini = g84_therm_fini, + }, +}; diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/gm107.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/gm107.c new file mode 100644 index 000000000..2fd110f09 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/gm107.c @@ -0,0 +1,93 @@ +/* + * Copyright 2014 Martin Peres + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Martin Peres + */ +#include "priv.h" + +#include + +struct gm107_therm_priv { + struct nvkm_therm_priv base; +}; + +static int +gm107_fan_pwm_ctrl(struct nvkm_therm *therm, int line, bool enable) +{ + /* nothing to do, it seems hardwired */ + return 0; +} + +static int +gm107_fan_pwm_get(struct nvkm_therm *therm, int line, u32 *divs, u32 *duty) +{ + *divs = nv_rd32(therm, 0x10eb20) & 0x1fff; + *duty = nv_rd32(therm, 0x10eb24) & 0x1fff; + return 0; +} + +static int +gm107_fan_pwm_set(struct nvkm_therm *therm, int line, u32 divs, u32 duty) +{ + nv_mask(therm, 0x10eb10, 0x1fff, divs); /* keep the high bits */ + nv_wr32(therm, 0x10eb14, duty | 0x80000000); + return 0; +} + +static int +gm107_fan_pwm_clock(struct nvkm_therm *therm, int line) +{ + return nv_device(therm)->crystal * 1000; +} + +static int +gm107_therm_ctor(struct nvkm_object *parent, struct nvkm_object *engine, + struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject) +{ + struct gm107_therm_priv *priv; + int ret; + + ret = nvkm_therm_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->base.base.pwm_ctrl = gm107_fan_pwm_ctrl; + priv->base.base.pwm_get = gm107_fan_pwm_get; + priv->base.base.pwm_set = gm107_fan_pwm_set; + priv->base.base.pwm_clock = gm107_fan_pwm_clock; + priv->base.base.temp_get = g84_temp_get; + priv->base.base.fan_sense = gt215_therm_fan_sense; + priv->base.sensor.program_alarms = nvkm_therm_program_alarms_polling; + return nvkm_therm_preinit(&priv->base.base); +} + +struct nvkm_oclass +gm107_therm_oclass = { + .handle = NV_SUBDEV(THERM, 0x117), + .ofuncs = &(struct nvkm_ofuncs) { + .ctor = gm107_therm_ctor, + .dtor = _nvkm_therm_dtor, + .init = gf110_therm_init, + .fini = g84_therm_fini, + }, +}; diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/gt215.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/gt215.c new file mode 100644 index 000000000..e99be2033 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/gt215.c @@ -0,0 +1,100 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + */ +#include "priv.h" + +#include +#include + +struct gt215_therm_priv { + struct nvkm_therm_priv base; +}; + +int +gt215_therm_fan_sense(struct nvkm_therm *therm) +{ + u32 tach = nv_rd32(therm, 0x00e728) & 0x0000ffff; + u32 ctrl = nv_rd32(therm, 0x00e720); + if (ctrl & 0x00000001) + return tach * 60 / 2; + return -ENODEV; +} + +static int +gt215_therm_init(struct nvkm_object *object) +{ + struct gt215_therm_priv *priv = (void *)object; + struct dcb_gpio_func *tach = &priv->base.fan->tach; + int ret; + + ret = nvkm_therm_init(&priv->base.base); + if (ret) + return ret; + + g84_sensor_setup(&priv->base.base); + + /* enable fan tach, count revolutions per-second */ + nv_mask(priv, 0x00e720, 0x00000003, 0x00000002); + if (tach->func != DCB_GPIO_UNUSED) { + nv_wr32(priv, 0x00e724, nv_device(priv)->crystal * 1000); + nv_mask(priv, 0x00e720, 0x001f0000, tach->line << 16); + nv_mask(priv, 0x00e720, 0x00000001, 0x00000001); + } + nv_mask(priv, 0x00e720, 0x00000002, 0x00000000); + + return 0; +} + +static int +gt215_therm_ctor(struct nvkm_object *parent, struct nvkm_object *engine, + struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject) +{ + struct gt215_therm_priv *priv; + int ret; + + ret = nvkm_therm_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->base.base.pwm_ctrl = nv50_fan_pwm_ctrl; + priv->base.base.pwm_get = nv50_fan_pwm_get; + priv->base.base.pwm_set = nv50_fan_pwm_set; + priv->base.base.pwm_clock = nv50_fan_pwm_clock; + priv->base.base.temp_get = g84_temp_get; + priv->base.base.fan_sense = gt215_therm_fan_sense; + priv->base.sensor.program_alarms = nvkm_therm_program_alarms_polling; + return nvkm_therm_preinit(&priv->base.base); +} + +struct nvkm_oclass +gt215_therm_oclass = { + .handle = NV_SUBDEV(THERM, 0xa3), + .ofuncs = &(struct nvkm_ofuncs) { + .ctor = gt215_therm_ctor, + .dtor = _nvkm_therm_dtor, + .init = gt215_therm_init, + .fini = g84_therm_fini, + }, +}; diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/ic.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/ic.c new file mode 100644 index 000000000..09fc4605e --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/ic.c @@ -0,0 +1,119 @@ +/* + * Copyright 2012 Nouveau community + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Martin Peres + */ +#include "priv.h" + +#include +#include + +static bool +probe_monitoring_device(struct nvkm_i2c_port *i2c, + struct i2c_board_info *info, void *data) +{ + struct nvkm_therm_priv *priv = data; + struct nvbios_therm_sensor *sensor = &priv->bios_sensor; + struct i2c_client *client; + + request_module("%s%s", I2C_MODULE_PREFIX, info->type); + + client = i2c_new_device(&i2c->adapter, info); + if (!client) + return false; + + if (!client->dev.driver || + to_i2c_driver(client->dev.driver)->detect(client, info)) { + i2c_unregister_device(client); + return false; + } + + nv_info(priv, + "Found an %s at address 0x%x (controlled by lm_sensors, " + "temp offset %+i C)\n", + info->type, info->addr, sensor->offset_constant); + priv->ic = client; + return true; +} + +static struct nvkm_i2c_board_info +nv_board_infos[] = { + { { I2C_BOARD_INFO("w83l785ts", 0x2d) }, 0 }, + { { I2C_BOARD_INFO("w83781d", 0x2d) }, 0 }, + { { I2C_BOARD_INFO("adt7473", 0x2e) }, 40 }, + { { I2C_BOARD_INFO("adt7473", 0x2d) }, 40 }, + { { I2C_BOARD_INFO("adt7473", 0x2c) }, 40 }, + { { I2C_BOARD_INFO("f75375", 0x2e) }, 0 }, + { { I2C_BOARD_INFO("lm99", 0x4c) }, 0 }, + { { I2C_BOARD_INFO("lm90", 0x4c) }, 0 }, + { { I2C_BOARD_INFO("lm90", 0x4d) }, 0 }, + { { I2C_BOARD_INFO("adm1021", 0x18) }, 0 }, + { { I2C_BOARD_INFO("adm1021", 0x19) }, 0 }, + { { I2C_BOARD_INFO("adm1021", 0x1a) }, 0 }, + { { I2C_BOARD_INFO("adm1021", 0x29) }, 0 }, + { { I2C_BOARD_INFO("adm1021", 0x2a) }, 0 }, + { { I2C_BOARD_INFO("adm1021", 0x2b) }, 0 }, + { { I2C_BOARD_INFO("adm1021", 0x4c) }, 0 }, + { { I2C_BOARD_INFO("adm1021", 0x4d) }, 0 }, + { { I2C_BOARD_INFO("adm1021", 0x4e) }, 0 }, + { { I2C_BOARD_INFO("lm63", 0x18) }, 0 }, + { { I2C_BOARD_INFO("lm63", 0x4e) }, 0 }, + { } +}; + +void +nvkm_therm_ic_ctor(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *priv = (void *)therm; + struct nvkm_bios *bios = nvkm_bios(therm); + struct nvkm_i2c *i2c = nvkm_i2c(therm); + struct nvbios_extdev_func extdev_entry; + + if (!nvbios_extdev_find(bios, NVBIOS_EXTDEV_LM89, &extdev_entry)) { + struct nvkm_i2c_board_info board[] = { + { { I2C_BOARD_INFO("lm90", extdev_entry.addr >> 1) }, 0}, + { } + }; + + i2c->identify(i2c, NV_I2C_DEFAULT(0), "monitoring device", + board, probe_monitoring_device, therm); + if (priv->ic) + return; + } + + if (!nvbios_extdev_find(bios, NVBIOS_EXTDEV_ADT7473, &extdev_entry)) { + struct nvkm_i2c_board_info board[] = { + { { I2C_BOARD_INFO("adt7473", extdev_entry.addr >> 1) }, 20 }, + { } + }; + + i2c->identify(i2c, NV_I2C_DEFAULT(0), "monitoring device", + board, probe_monitoring_device, therm); + if (priv->ic) + return; + } + + /* The vbios doesn't provide the address of an exisiting monitoring + device. Let's try our static list. + */ + i2c->identify(i2c, NV_I2C_DEFAULT(0), "monitoring device", + nv_board_infos, probe_monitoring_device, therm); +} diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/nv40.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/nv40.c new file mode 100644 index 000000000..8496fffd4 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/nv40.c @@ -0,0 +1,225 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + * Martin Peres + */ +#include "priv.h" + +#include + +struct nv40_therm_priv { + struct nvkm_therm_priv base; +}; + +enum nv40_sensor_style { INVALID_STYLE = -1, OLD_STYLE = 0, NEW_STYLE = 1 }; + +static enum nv40_sensor_style +nv40_sensor_style(struct nvkm_therm *therm) +{ + struct nvkm_device *device = nv_device(therm); + + switch (device->chipset) { + case 0x43: + case 0x44: + case 0x4a: + case 0x47: + return OLD_STYLE; + + case 0x46: + case 0x49: + case 0x4b: + case 0x4e: + case 0x4c: + case 0x67: + case 0x68: + case 0x63: + return NEW_STYLE; + default: + return INVALID_STYLE; + } +} + +static int +nv40_sensor_setup(struct nvkm_therm *therm) +{ + enum nv40_sensor_style style = nv40_sensor_style(therm); + + /* enable ADC readout and disable the ALARM threshold */ + if (style == NEW_STYLE) { + nv_mask(therm, 0x15b8, 0x80000000, 0); + nv_wr32(therm, 0x15b0, 0x80003fff); + mdelay(20); /* wait for the temperature to stabilize */ + return nv_rd32(therm, 0x15b4) & 0x3fff; + } else if (style == OLD_STYLE) { + nv_wr32(therm, 0x15b0, 0xff); + mdelay(20); /* wait for the temperature to stabilize */ + return nv_rd32(therm, 0x15b4) & 0xff; + } else + return -ENODEV; +} + +static int +nv40_temp_get(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *priv = (void *)therm; + struct nvbios_therm_sensor *sensor = &priv->bios_sensor; + enum nv40_sensor_style style = nv40_sensor_style(therm); + int core_temp; + + if (style == NEW_STYLE) { + nv_wr32(therm, 0x15b0, 0x80003fff); + core_temp = nv_rd32(therm, 0x15b4) & 0x3fff; + } else if (style == OLD_STYLE) { + nv_wr32(therm, 0x15b0, 0xff); + core_temp = nv_rd32(therm, 0x15b4) & 0xff; + } else + return -ENODEV; + + /* if the slope or the offset is unset, do no use the sensor */ + if (!sensor->slope_div || !sensor->slope_mult || + !sensor->offset_num || !sensor->offset_den) + return -ENODEV; + + core_temp = core_temp * sensor->slope_mult / sensor->slope_div; + core_temp = core_temp + sensor->offset_num / sensor->offset_den; + core_temp = core_temp + sensor->offset_constant - 8; + + /* reserve negative temperatures for errors */ + if (core_temp < 0) + core_temp = 0; + + return core_temp; +} + +static int +nv40_fan_pwm_ctrl(struct nvkm_therm *therm, int line, bool enable) +{ + u32 mask = enable ? 0x80000000 : 0x0000000; + if (line == 2) nv_mask(therm, 0x0010f0, 0x80000000, mask); + else if (line == 9) nv_mask(therm, 0x0015f4, 0x80000000, mask); + else { + nv_error(therm, "unknown pwm ctrl for gpio %d\n", line); + return -ENODEV; + } + return 0; +} + +static int +nv40_fan_pwm_get(struct nvkm_therm *therm, int line, u32 *divs, u32 *duty) +{ + if (line == 2) { + u32 reg = nv_rd32(therm, 0x0010f0); + if (reg & 0x80000000) { + *duty = (reg & 0x7fff0000) >> 16; + *divs = (reg & 0x00007fff); + return 0; + } + } else + if (line == 9) { + u32 reg = nv_rd32(therm, 0x0015f4); + if (reg & 0x80000000) { + *divs = nv_rd32(therm, 0x0015f8); + *duty = (reg & 0x7fffffff); + return 0; + } + } else { + nv_error(therm, "unknown pwm ctrl for gpio %d\n", line); + return -ENODEV; + } + + return -EINVAL; +} + +static int +nv40_fan_pwm_set(struct nvkm_therm *therm, int line, u32 divs, u32 duty) +{ + if (line == 2) { + nv_mask(therm, 0x0010f0, 0x7fff7fff, (duty << 16) | divs); + } else + if (line == 9) { + nv_wr32(therm, 0x0015f8, divs); + nv_mask(therm, 0x0015f4, 0x7fffffff, duty); + } else { + nv_error(therm, "unknown pwm ctrl for gpio %d\n", line); + return -ENODEV; + } + + return 0; +} + +void +nv40_therm_intr(struct nvkm_subdev *subdev) +{ + struct nvkm_therm *therm = nvkm_therm(subdev); + uint32_t stat = nv_rd32(therm, 0x1100); + + /* traitement */ + + /* ack all IRQs */ + nv_wr32(therm, 0x1100, 0x70000); + + nv_error(therm, "THERM received an IRQ: stat = %x\n", stat); +} + +static int +nv40_therm_ctor(struct nvkm_object *parent, + struct nvkm_object *engine, + struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject) +{ + struct nv40_therm_priv *priv; + int ret; + + ret = nvkm_therm_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->base.base.pwm_ctrl = nv40_fan_pwm_ctrl; + priv->base.base.pwm_get = nv40_fan_pwm_get; + priv->base.base.pwm_set = nv40_fan_pwm_set; + priv->base.base.temp_get = nv40_temp_get; + priv->base.sensor.program_alarms = nvkm_therm_program_alarms_polling; + nv_subdev(priv)->intr = nv40_therm_intr; + return nvkm_therm_preinit(&priv->base.base); +} + +static int +nv40_therm_init(struct nvkm_object *object) +{ + struct nvkm_therm *therm = (void *)object; + + nv40_sensor_setup(therm); + + return _nvkm_therm_init(object); +} + +struct nvkm_oclass +nv40_therm_oclass = { + .handle = NV_SUBDEV(THERM, 0x40), + .ofuncs = &(struct nvkm_ofuncs) { + .ctor = nv40_therm_ctor, + .dtor = _nvkm_therm_dtor, + .init = nv40_therm_init, + .fini = _nvkm_therm_fini, + }, +}; diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/nv50.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/nv50.c new file mode 100644 index 000000000..1ef59e892 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/nv50.c @@ -0,0 +1,198 @@ +/* + * Copyright 2012 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Ben Skeggs + * Martin Peres + */ +#include "priv.h" + +#include + +struct nv50_therm_priv { + struct nvkm_therm_priv base; +}; + +static int +pwm_info(struct nvkm_therm *therm, int *line, int *ctrl, int *indx) +{ + if (*line == 0x04) { + *ctrl = 0x00e100; + *line = 4; + *indx = 0; + } else + if (*line == 0x09) { + *ctrl = 0x00e100; + *line = 9; + *indx = 1; + } else + if (*line == 0x10) { + *ctrl = 0x00e28c; + *line = 0; + *indx = 0; + } else { + nv_error(therm, "unknown pwm ctrl for gpio %d\n", *line); + return -ENODEV; + } + + return 0; +} + +int +nv50_fan_pwm_ctrl(struct nvkm_therm *therm, int line, bool enable) +{ + u32 data = enable ? 0x00000001 : 0x00000000; + int ctrl, id, ret = pwm_info(therm, &line, &ctrl, &id); + if (ret == 0) + nv_mask(therm, ctrl, 0x00010001 << line, data << line); + return ret; +} + +int +nv50_fan_pwm_get(struct nvkm_therm *therm, int line, u32 *divs, u32 *duty) +{ + int ctrl, id, ret = pwm_info(therm, &line, &ctrl, &id); + if (ret) + return ret; + + if (nv_rd32(therm, ctrl) & (1 << line)) { + *divs = nv_rd32(therm, 0x00e114 + (id * 8)); + *duty = nv_rd32(therm, 0x00e118 + (id * 8)); + return 0; + } + + return -EINVAL; +} + +int +nv50_fan_pwm_set(struct nvkm_therm *therm, int line, u32 divs, u32 duty) +{ + int ctrl, id, ret = pwm_info(therm, &line, &ctrl, &id); + if (ret) + return ret; + + nv_wr32(therm, 0x00e114 + (id * 8), divs); + nv_wr32(therm, 0x00e118 + (id * 8), duty | 0x80000000); + return 0; +} + +int +nv50_fan_pwm_clock(struct nvkm_therm *therm, int line) +{ + int chipset = nv_device(therm)->chipset; + int crystal = nv_device(therm)->crystal; + int pwm_clock; + + /* determine the PWM source clock */ + if (chipset > 0x50 && chipset < 0x94) { + u8 pwm_div = nv_rd32(therm, 0x410c); + if (nv_rd32(therm, 0xc040) & 0x800000) { + /* Use the HOST clock (100 MHz) + * Where does this constant(2.4) comes from? */ + pwm_clock = (100000000 >> pwm_div) * 10 / 24; + } else { + /* Where does this constant(20) comes from? */ + pwm_clock = (crystal * 1000) >> pwm_div; + pwm_clock /= 20; + } + } else { + pwm_clock = (crystal * 1000) / 20; + } + + return pwm_clock; +} + +static void +nv50_sensor_setup(struct nvkm_therm *therm) +{ + nv_mask(therm, 0x20010, 0x40000000, 0x0); + mdelay(20); /* wait for the temperature to stabilize */ +} + +static int +nv50_temp_get(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *priv = (void *)therm; + struct nvbios_therm_sensor *sensor = &priv->bios_sensor; + int core_temp; + + core_temp = nv_rd32(therm, 0x20014) & 0x3fff; + + /* if the slope or the offset is unset, do no use the sensor */ + if (!sensor->slope_div || !sensor->slope_mult || + !sensor->offset_num || !sensor->offset_den) + return -ENODEV; + + core_temp = core_temp * sensor->slope_mult / sensor->slope_div; + core_temp = core_temp + sensor->offset_num / sensor->offset_den; + core_temp = core_temp + sensor->offset_constant - 8; + + /* reserve negative temperatures for errors */ + if (core_temp < 0) + core_temp = 0; + + return core_temp; +} + +static int +nv50_therm_ctor(struct nvkm_object *parent, + struct nvkm_object *engine, + struct nvkm_oclass *oclass, void *data, u32 size, + struct nvkm_object **pobject) +{ + struct nv50_therm_priv *priv; + int ret; + + ret = nvkm_therm_create(parent, engine, oclass, &priv); + *pobject = nv_object(priv); + if (ret) + return ret; + + priv->base.base.pwm_ctrl = nv50_fan_pwm_ctrl; + priv->base.base.pwm_get = nv50_fan_pwm_get; + priv->base.base.pwm_set = nv50_fan_pwm_set; + priv->base.base.pwm_clock = nv50_fan_pwm_clock; + priv->base.base.temp_get = nv50_temp_get; + priv->base.sensor.program_alarms = nvkm_therm_program_alarms_polling; + nv_subdev(priv)->intr = nv40_therm_intr; + + return nvkm_therm_preinit(&priv->base.base); +} + +static int +nv50_therm_init(struct nvkm_object *object) +{ + struct nvkm_therm *therm = (void *)object; + + nv50_sensor_setup(therm); + + return _nvkm_therm_init(object); +} + +struct nvkm_oclass +nv50_therm_oclass = { + .handle = NV_SUBDEV(THERM, 0x50), + .ofuncs = &(struct nvkm_ofuncs) { + .ctor = nv50_therm_ctor, + .dtor = _nvkm_therm_dtor, + .init = nv50_therm_init, + .fini = _nvkm_therm_fini, + }, +}; diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/priv.h b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/priv.h new file mode 100644 index 000000000..916a149ef --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/priv.h @@ -0,0 +1,153 @@ +#ifndef __NVTHERM_PRIV_H__ +#define __NVTHERM_PRIV_H__ +/* + * Copyright 2012 The Nouveau community + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Martin Peres + */ +#include +#include +#include +#include +#include +#include +#include + +struct nvkm_fan { + struct nvkm_therm *parent; + const char *type; + + struct nvbios_therm_fan bios; + struct nvbios_perf_fan perf; + + struct nvkm_alarm alarm; + spinlock_t lock; + int percent; + + int (*get)(struct nvkm_therm *); + int (*set)(struct nvkm_therm *, int percent); + + struct dcb_gpio_func tach; +}; + +enum nvkm_therm_thrs_direction { + NVKM_THERM_THRS_FALLING = 0, + NVKM_THERM_THRS_RISING = 1 +}; + +enum nvkm_therm_thrs_state { + NVKM_THERM_THRS_LOWER = 0, + NVKM_THERM_THRS_HIGHER = 1 +}; + +enum nvkm_therm_thrs { + NVKM_THERM_THRS_FANBOOST = 0, + NVKM_THERM_THRS_DOWNCLOCK = 1, + NVKM_THERM_THRS_CRITICAL = 2, + NVKM_THERM_THRS_SHUTDOWN = 3, + NVKM_THERM_THRS_NR +}; + +struct nvkm_therm_priv { + struct nvkm_therm base; + + /* automatic thermal management */ + struct nvkm_alarm alarm; + spinlock_t lock; + struct nvbios_therm_trip_point *last_trip; + int mode; + int cstate; + int suspend; + + /* bios */ + struct nvbios_therm_sensor bios_sensor; + + /* fan priv */ + struct nvkm_fan *fan; + + /* alarms priv */ + struct { + spinlock_t alarm_program_lock; + struct nvkm_alarm therm_poll_alarm; + enum nvkm_therm_thrs_state alarm_state[NVKM_THERM_THRS_NR]; + void (*program_alarms)(struct nvkm_therm *); + } sensor; + + /* what should be done if the card overheats */ + struct { + void (*downclock)(struct nvkm_therm *, bool active); + void (*pause)(struct nvkm_therm *, bool active); + } emergency; + + /* ic */ + struct i2c_client *ic; +}; + +int nvkm_therm_fan_mode(struct nvkm_therm *, int mode); +int nvkm_therm_attr_get(struct nvkm_therm *, enum nvkm_therm_attr_type); +int nvkm_therm_attr_set(struct nvkm_therm *, enum nvkm_therm_attr_type, int); + +void nvkm_therm_ic_ctor(struct nvkm_therm *); + +int nvkm_therm_sensor_ctor(struct nvkm_therm *); + +int nvkm_therm_fan_ctor(struct nvkm_therm *); +int nvkm_therm_fan_init(struct nvkm_therm *); +int nvkm_therm_fan_fini(struct nvkm_therm *, bool suspend); +int nvkm_therm_fan_get(struct nvkm_therm *); +int nvkm_therm_fan_set(struct nvkm_therm *, bool now, int percent); +int nvkm_therm_fan_user_get(struct nvkm_therm *); +int nvkm_therm_fan_user_set(struct nvkm_therm *, int percent); + +int nvkm_therm_fan_sense(struct nvkm_therm *); + +int nvkm_therm_preinit(struct nvkm_therm *); + +int nvkm_therm_sensor_init(struct nvkm_therm *); +int nvkm_therm_sensor_fini(struct nvkm_therm *, bool suspend); +void nvkm_therm_sensor_preinit(struct nvkm_therm *); +void nvkm_therm_sensor_set_threshold_state(struct nvkm_therm *, + enum nvkm_therm_thrs, + enum nvkm_therm_thrs_state); +enum nvkm_therm_thrs_state +nvkm_therm_sensor_get_threshold_state(struct nvkm_therm *, + enum nvkm_therm_thrs); +void nvkm_therm_sensor_event(struct nvkm_therm *, enum nvkm_therm_thrs, + enum nvkm_therm_thrs_direction); +void nvkm_therm_program_alarms_polling(struct nvkm_therm *); + +void nv40_therm_intr(struct nvkm_subdev *); +int nv50_fan_pwm_ctrl(struct nvkm_therm *, int, bool); +int nv50_fan_pwm_get(struct nvkm_therm *, int, u32 *, u32 *); +int nv50_fan_pwm_set(struct nvkm_therm *, int, u32, u32); +int nv50_fan_pwm_clock(struct nvkm_therm *, int); +int g84_temp_get(struct nvkm_therm *); +void g84_sensor_setup(struct nvkm_therm *); +int g84_therm_fini(struct nvkm_object *, bool suspend); + +int gt215_therm_fan_sense(struct nvkm_therm *); + +int gf110_therm_init(struct nvkm_object *); + +int nvkm_fanpwm_create(struct nvkm_therm *, struct dcb_gpio_func *); +int nvkm_fantog_create(struct nvkm_therm *, struct dcb_gpio_func *); +int nvkm_fannil_create(struct nvkm_therm *); +#endif diff --git a/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/temp.c b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/temp.c new file mode 100644 index 000000000..aa13744f3 --- /dev/null +++ b/kernel/drivers/gpu/drm/nouveau/nvkm/subdev/therm/temp.c @@ -0,0 +1,259 @@ +/* + * Copyright 2012 The Nouveau community + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * Authors: Martin Peres + */ +#include "priv.h" + +static void +nvkm_therm_temp_set_defaults(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *priv = (void *)therm; + + priv->bios_sensor.offset_constant = 0; + + priv->bios_sensor.thrs_fan_boost.temp = 90; + priv->bios_sensor.thrs_fan_boost.hysteresis = 3; + + priv->bios_sensor.thrs_down_clock.temp = 95; + priv->bios_sensor.thrs_down_clock.hysteresis = 3; + + priv->bios_sensor.thrs_critical.temp = 105; + priv->bios_sensor.thrs_critical.hysteresis = 5; + + priv->bios_sensor.thrs_shutdown.temp = 135; + priv->bios_sensor.thrs_shutdown.hysteresis = 5; /*not that it matters */ +} + + +static void +nvkm_therm_temp_safety_checks(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *priv = (void *)therm; + struct nvbios_therm_sensor *s = &priv->bios_sensor; + + /* enforce a minimum hysteresis on thresholds */ + s->thrs_fan_boost.hysteresis = max_t(u8, s->thrs_fan_boost.hysteresis, 2); + s->thrs_down_clock.hysteresis = max_t(u8, s->thrs_down_clock.hysteresis, 2); + s->thrs_critical.hysteresis = max_t(u8, s->thrs_critical.hysteresis, 2); + s->thrs_shutdown.hysteresis = max_t(u8, s->thrs_shutdown.hysteresis, 2); +} + +/* must be called with alarm_program_lock taken ! */ +void +nvkm_therm_sensor_set_threshold_state(struct nvkm_therm *therm, + enum nvkm_therm_thrs thrs, + enum nvkm_therm_thrs_state st) +{ + struct nvkm_therm_priv *priv = (void *)therm; + priv->sensor.alarm_state[thrs] = st; +} + +/* must be called with alarm_program_lock taken ! */ +enum nvkm_therm_thrs_state +nvkm_therm_sensor_get_threshold_state(struct nvkm_therm *therm, + enum nvkm_therm_thrs thrs) +{ + struct nvkm_therm_priv *priv = (void *)therm; + return priv->sensor.alarm_state[thrs]; +} + +static void +nv_poweroff_work(struct work_struct *work) +{ + orderly_poweroff(true); + kfree(work); +} + +void +nvkm_therm_sensor_event(struct nvkm_therm *therm, enum nvkm_therm_thrs thrs, + enum nvkm_therm_thrs_direction dir) +{ + struct nvkm_therm_priv *priv = (void *)therm; + bool active; + const char *thresolds[] = { + "fanboost", "downclock", "critical", "shutdown" + }; + int temperature = therm->temp_get(therm); + + if (thrs < 0 || thrs > 3) + return; + + if (dir == NVKM_THERM_THRS_FALLING) + nv_info(therm, "temperature (%i C) went below the '%s' threshold\n", + temperature, thresolds[thrs]); + else + nv_info(therm, "temperature (%i C) hit the '%s' threshold\n", + temperature, thresolds[thrs]); + + active = (dir == NVKM_THERM_THRS_RISING); + switch (thrs) { + case NVKM_THERM_THRS_FANBOOST: + if (active) { + nvkm_therm_fan_set(therm, true, 100); + nvkm_therm_fan_mode(therm, NVKM_THERM_CTRL_AUTO); + } + break; + case NVKM_THERM_THRS_DOWNCLOCK: + if (priv->emergency.downclock) + priv->emergency.downclock(therm, active); + break; + case NVKM_THERM_THRS_CRITICAL: + if (priv->emergency.pause) + priv->emergency.pause(therm, active); + break; + case NVKM_THERM_THRS_SHUTDOWN: + if (active) { + struct work_struct *work; + + work = kmalloc(sizeof(*work), GFP_ATOMIC); + if (work) { + INIT_WORK(work, nv_poweroff_work); + schedule_work(work); + } + } + break; + case NVKM_THERM_THRS_NR: + break; + } + +} + +/* must be called with alarm_program_lock taken ! */ +static void +nvkm_therm_threshold_hyst_polling(struct nvkm_therm *therm, + const struct nvbios_therm_threshold *thrs, + enum nvkm_therm_thrs thrs_name) +{ + enum nvkm_therm_thrs_direction direction; + enum nvkm_therm_thrs_state prev_state, new_state; + int temp = therm->temp_get(therm); + + prev_state = nvkm_therm_sensor_get_threshold_state(therm, thrs_name); + + if (temp >= thrs->temp && prev_state == NVKM_THERM_THRS_LOWER) { + direction = NVKM_THERM_THRS_RISING; + new_state = NVKM_THERM_THRS_HIGHER; + } else if (temp <= thrs->temp - thrs->hysteresis && + prev_state == NVKM_THERM_THRS_HIGHER) { + direction = NVKM_THERM_THRS_FALLING; + new_state = NVKM_THERM_THRS_LOWER; + } else + return; /* nothing to do */ + + nvkm_therm_sensor_set_threshold_state(therm, thrs_name, new_state); + nvkm_therm_sensor_event(therm, thrs_name, direction); +} + +static void +alarm_timer_callback(struct nvkm_alarm *alarm) +{ + struct nvkm_therm_priv *priv = + container_of(alarm, struct nvkm_therm_priv, sensor.therm_poll_alarm); + struct nvbios_therm_sensor *sensor = &priv->bios_sensor; + struct nvkm_timer *ptimer = nvkm_timer(priv); + struct nvkm_therm *therm = &priv->base; + unsigned long flags; + + spin_lock_irqsave(&priv->sensor.alarm_program_lock, flags); + + nvkm_therm_threshold_hyst_polling(therm, &sensor->thrs_fan_boost, + NVKM_THERM_THRS_FANBOOST); + + nvkm_therm_threshold_hyst_polling(therm, &sensor->thrs_down_clock, + NVKM_THERM_THRS_DOWNCLOCK); + + nvkm_therm_threshold_hyst_polling(therm, &sensor->thrs_critical, + NVKM_THERM_THRS_CRITICAL); + + nvkm_therm_threshold_hyst_polling(therm, &sensor->thrs_shutdown, + NVKM_THERM_THRS_SHUTDOWN); + + spin_unlock_irqrestore(&priv->sensor.alarm_program_lock, flags); + + /* schedule the next poll in one second */ + if (therm->temp_get(therm) >= 0 && list_empty(&alarm->head)) + ptimer->alarm(ptimer, 1000000000ULL, alarm); +} + +void +nvkm_therm_program_alarms_polling(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *priv = (void *)therm; + struct nvbios_therm_sensor *sensor = &priv->bios_sensor; + + nv_debug(therm, + "programmed thresholds [ %d(%d), %d(%d), %d(%d), %d(%d) ]\n", + sensor->thrs_fan_boost.temp, sensor->thrs_fan_boost.hysteresis, + sensor->thrs_down_clock.temp, + sensor->thrs_down_clock.hysteresis, + sensor->thrs_critical.temp, sensor->thrs_critical.hysteresis, + sensor->thrs_shutdown.temp, sensor->thrs_shutdown.hysteresis); + + alarm_timer_callback(&priv->sensor.therm_poll_alarm); +} + +int +nvkm_therm_sensor_init(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *priv = (void *)therm; + priv->sensor.program_alarms(therm); + return 0; +} + +int +nvkm_therm_sensor_fini(struct nvkm_therm *therm, bool suspend) +{ + struct nvkm_therm_priv *priv = (void *)therm; + struct nvkm_timer *ptimer = nvkm_timer(therm); + + if (suspend) + ptimer->alarm_cancel(ptimer, &priv->sensor.therm_poll_alarm); + return 0; +} + +void +nvkm_therm_sensor_preinit(struct nvkm_therm *therm) +{ + const char *sensor_avail = "yes"; + + if (therm->temp_get(therm) < 0) + sensor_avail = "no"; + + nv_info(therm, "internal sensor: %s\n", sensor_avail); +} + +int +nvkm_therm_sensor_ctor(struct nvkm_therm *therm) +{ + struct nvkm_therm_priv *priv = (void *)therm; + struct nvkm_bios *bios = nvkm_bios(therm); + + nvkm_alarm_init(&priv->sensor.therm_poll_alarm, alarm_timer_callback); + + nvkm_therm_temp_set_defaults(therm); + if (nvbios_therm_sensor_parse(bios, NVBIOS_THERM_DOMAIN_CORE, + &priv->bios_sensor)) + nv_error(therm, "nvbios_therm_sensor_parse failed\n"); + nvkm_therm_temp_safety_checks(therm); + + return 0; +} -- cgit 1.2.3-korg