cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

fan.c (7704B)


      1/*
      2 * Copyright 2012 Red Hat Inc.
      3 *
      4 * Permission is hereby granted, free of charge, to any person obtaining a
      5 * copy of this software and associated documentation files (the "Software"),
      6 * to deal in the Software without restriction, including without limitation
      7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      8 * and/or sell copies of the Software, and to permit persons to whom the
      9 * Software is furnished to do so, subject to the following conditions:
     10 *
     11 * The above copyright notice and this permission notice shall be included in
     12 * all copies or substantial portions of the Software.
     13 *
     14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
     17 * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
     18 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
     19 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
     20 * OTHER DEALINGS IN THE SOFTWARE.
     21 *
     22 * Authors: Ben Skeggs
     23 * 	    Martin Peres
     24 */
     25#include "priv.h"
     26
     27#include <subdev/bios/fan.h>
     28#include <subdev/gpio.h>
     29#include <subdev/timer.h>
     30
     31static int
     32nvkm_fan_update(struct nvkm_fan *fan, bool immediate, int target)
     33{
     34	struct nvkm_therm *therm = fan->parent;
     35	struct nvkm_subdev *subdev = &therm->subdev;
     36	struct nvkm_timer *tmr = subdev->device->timer;
     37	unsigned long flags;
     38	int ret = 0;
     39	int duty;
     40
     41	/* update target fan speed, restricting to allowed range */
     42	spin_lock_irqsave(&fan->lock, flags);
     43	if (target < 0)
     44		target = fan->percent;
     45	target = max_t(u8, target, fan->bios.min_duty);
     46	target = min_t(u8, target, fan->bios.max_duty);
     47	if (fan->percent != target) {
     48		nvkm_debug(subdev, "FAN target: %d\n", target);
     49		fan->percent = target;
     50	}
     51
     52	/* check that we're not already at the target duty cycle */
     53	duty = fan->get(therm);
     54	if (duty == target) {
     55		spin_unlock_irqrestore(&fan->lock, flags);
     56		return 0;
     57	}
     58
     59	/* smooth out the fanspeed increase/decrease */
     60	if (!immediate && duty >= 0) {
     61		/* the constant "3" is a rough approximation taken from
     62		 * nvidia's behaviour.
     63		 * it is meant to bump the fan speed more incrementally
     64		 */
     65		if (duty < target)
     66			duty = min(duty + 3, target);
     67		else if (duty > target)
     68			duty = max(duty - 3, target);
     69	} else {
     70		duty = target;
     71	}
     72
     73	nvkm_debug(subdev, "FAN update: %d\n", duty);
     74	ret = fan->set(therm, duty);
     75	if (ret) {
     76		spin_unlock_irqrestore(&fan->lock, flags);
     77		return ret;
     78	}
     79
     80	/* fan speed updated, drop the fan lock before grabbing the
     81	 * alarm-scheduling lock and risking a deadlock
     82	 */
     83	spin_unlock_irqrestore(&fan->lock, flags);
     84
     85	/* schedule next fan update, if not at target speed already */
     86	if (target != duty) {
     87		u16 bump_period = fan->bios.bump_period;
     88		u16 slow_down_period = fan->bios.slow_down_period;
     89		u64 delay;
     90
     91		if (duty > target)
     92			delay = slow_down_period;
     93		else if (duty == target)
     94			delay = min(bump_period, slow_down_period) ;
     95		else
     96			delay = bump_period;
     97
     98		nvkm_timer_alarm(tmr, delay * 1000 * 1000, &fan->alarm);
     99	}
    100
    101	return ret;
    102}
    103
    104static void
    105nvkm_fan_alarm(struct nvkm_alarm *alarm)
    106{
    107	struct nvkm_fan *fan = container_of(alarm, struct nvkm_fan, alarm);
    108	nvkm_fan_update(fan, false, -1);
    109}
    110
    111int
    112nvkm_therm_fan_get(struct nvkm_therm *therm)
    113{
    114	return therm->fan->get(therm);
    115}
    116
    117int
    118nvkm_therm_fan_set(struct nvkm_therm *therm, bool immediate, int percent)
    119{
    120	return nvkm_fan_update(therm->fan, immediate, percent);
    121}
    122
    123int
    124nvkm_therm_fan_sense(struct nvkm_therm *therm)
    125{
    126	struct nvkm_device *device = therm->subdev.device;
    127	struct nvkm_timer *tmr = device->timer;
    128	struct nvkm_gpio *gpio = device->gpio;
    129	u32 cycles, cur, prev;
    130	u64 start, end, tach;
    131
    132	if (therm->func->fan_sense)
    133		return therm->func->fan_sense(therm);
    134
    135	if (therm->fan->tach.func == DCB_GPIO_UNUSED)
    136		return -ENODEV;
    137
    138	/* Time a complete rotation and extrapolate to RPM:
    139	 * When the fan spins, it changes the value of GPIO FAN_SENSE.
    140	 * We get 4 changes (0 -> 1 -> 0 -> 1) per complete rotation.
    141	 */
    142	start = nvkm_timer_read(tmr);
    143	prev = nvkm_gpio_get(gpio, 0, therm->fan->tach.func,
    144				      therm->fan->tach.line);
    145	cycles = 0;
    146	do {
    147		usleep_range(500, 1000); /* supports 0 < rpm < 7500 */
    148
    149		cur = nvkm_gpio_get(gpio, 0, therm->fan->tach.func,
    150					     therm->fan->tach.line);
    151		if (prev != cur) {
    152			if (!start)
    153				start = nvkm_timer_read(tmr);
    154			cycles++;
    155			prev = cur;
    156		}
    157	} while (cycles < 5 && nvkm_timer_read(tmr) - start < 250000000);
    158	end = nvkm_timer_read(tmr);
    159
    160	if (cycles == 5) {
    161		tach = (u64)60000000000ULL;
    162		do_div(tach, (end - start));
    163		return tach;
    164	} else
    165		return 0;
    166}
    167
    168int
    169nvkm_therm_fan_user_get(struct nvkm_therm *therm)
    170{
    171	return nvkm_therm_fan_get(therm);
    172}
    173
    174int
    175nvkm_therm_fan_user_set(struct nvkm_therm *therm, int percent)
    176{
    177	if (therm->mode != NVKM_THERM_CTRL_MANUAL)
    178		return -EINVAL;
    179
    180	return nvkm_therm_fan_set(therm, true, percent);
    181}
    182
    183static void
    184nvkm_therm_fan_set_defaults(struct nvkm_therm *therm)
    185{
    186	therm->fan->bios.pwm_freq = 0;
    187	therm->fan->bios.min_duty = 0;
    188	therm->fan->bios.max_duty = 100;
    189	therm->fan->bios.bump_period = 500;
    190	therm->fan->bios.slow_down_period = 2000;
    191	therm->fan->bios.linear_min_temp = 40;
    192	therm->fan->bios.linear_max_temp = 85;
    193}
    194
    195static void
    196nvkm_therm_fan_safety_checks(struct nvkm_therm *therm)
    197{
    198	if (therm->fan->bios.min_duty > 100)
    199		therm->fan->bios.min_duty = 100;
    200	if (therm->fan->bios.max_duty > 100)
    201		therm->fan->bios.max_duty = 100;
    202
    203	if (therm->fan->bios.min_duty > therm->fan->bios.max_duty)
    204		therm->fan->bios.min_duty = therm->fan->bios.max_duty;
    205}
    206
    207int
    208nvkm_therm_fan_init(struct nvkm_therm *therm)
    209{
    210	return 0;
    211}
    212
    213int
    214nvkm_therm_fan_fini(struct nvkm_therm *therm, bool suspend)
    215{
    216	struct nvkm_timer *tmr = therm->subdev.device->timer;
    217	if (suspend)
    218		nvkm_timer_alarm(tmr, 0, &therm->fan->alarm);
    219	return 0;
    220}
    221
    222int
    223nvkm_therm_fan_ctor(struct nvkm_therm *therm)
    224{
    225	struct nvkm_subdev *subdev = &therm->subdev;
    226	struct nvkm_device *device = subdev->device;
    227	struct nvkm_gpio *gpio = device->gpio;
    228	struct nvkm_bios *bios = device->bios;
    229	struct dcb_gpio_func func;
    230	int ret;
    231
    232	/* attempt to locate a drivable fan, and determine control method */
    233	ret = nvkm_gpio_find(gpio, 0, DCB_GPIO_FAN, 0xff, &func);
    234	if (ret == 0) {
    235		/* FIXME: is this really the place to perform such checks ? */
    236		if (func.line != 16 && func.log[0] & DCB_GPIO_LOG_DIR_IN) {
    237			nvkm_debug(subdev, "GPIO_FAN is in input mode\n");
    238			ret = -EINVAL;
    239		} else {
    240			ret = nvkm_fanpwm_create(therm, &func);
    241			if (ret != 0)
    242				ret = nvkm_fantog_create(therm, &func);
    243		}
    244	}
    245
    246	/* no controllable fan found, create a dummy fan module */
    247	if (ret != 0) {
    248		ret = nvkm_fannil_create(therm);
    249		if (ret)
    250			return ret;
    251	}
    252
    253	nvkm_debug(subdev, "FAN control: %s\n", therm->fan->type);
    254
    255	/* read the current speed, it is useful when resuming */
    256	therm->fan->percent = nvkm_therm_fan_get(therm);
    257
    258	/* attempt to detect a tachometer connection */
    259	ret = nvkm_gpio_find(gpio, 0, DCB_GPIO_FAN_SENSE, 0xff,
    260			     &therm->fan->tach);
    261	if (ret)
    262		therm->fan->tach.func = DCB_GPIO_UNUSED;
    263
    264	/* initialise fan bump/slow update handling */
    265	therm->fan->parent = therm;
    266	nvkm_alarm_init(&therm->fan->alarm, nvkm_fan_alarm);
    267	spin_lock_init(&therm->fan->lock);
    268
    269	/* other random init... */
    270	nvkm_therm_fan_set_defaults(therm);
    271	nvbios_perf_fan_parse(bios, &therm->fan->perf);
    272	if (!nvbios_fan_parse(bios, &therm->fan->bios)) {
    273		nvkm_debug(subdev, "parsing the fan table failed\n");
    274		if (nvbios_therm_fan_parse(bios, &therm->fan->bios))
    275			nvkm_error(subdev, "parsing both fan tables failed\n");
    276	}
    277	nvkm_therm_fan_safety_checks(therm);
    278	return 0;
    279}