diff options
Diffstat (limited to 'drivers/gpu/drm/nouveau/core/subdev/therm/fan.c')
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/therm/fan.c | 244 |
1 files changed, 132 insertions, 112 deletions
diff --git a/drivers/gpu/drm/nouveau/core/subdev/therm/fan.c b/drivers/gpu/drm/nouveau/core/subdev/therm/fan.c index 52317868518..c728380d3d6 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/therm/fan.c +++ b/drivers/gpu/drm/nouveau/core/subdev/therm/fan.c @@ -27,90 +27,107 @@ #include <core/object.h> #include <core/device.h> + #include <subdev/gpio.h> #include <subdev/timer.h> -int -nouveau_therm_fan_get(struct nouveau_therm *therm) +static int +nouveau_fan_update(struct nouveau_fan *fan, bool immediate, int target) { + struct nouveau_therm *therm = fan->parent; struct nouveau_therm_priv *priv = (void *)therm; - struct nouveau_gpio *gpio = nouveau_gpio(therm); - struct dcb_gpio_func func; - int card_type = nv_device(therm)->card_type; - u32 divs, duty; - int ret; - - if (!priv->fan.pwm_get) - return -ENODEV; + struct nouveau_timer *ptimer = nouveau_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; + } - ret = gpio->find(gpio, 0, DCB_GPIO_PWM_FAN, 0xff, &func); - if (ret == 0) { - ret = priv->fan.pwm_get(therm, func.line, &divs, &duty); - if (ret == 0 && divs) { - divs = max(divs, duty); - if (card_type <= NV_40 || (func.log[0] & 1)) - duty = divs - duty; - return (duty * 100) / divs; - } + /* check that we're not already at the target duty cycle */ + duty = fan->get(therm); + if (duty == target) + goto done; + + /* 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; + } - return gpio->get(gpio, 0, func.func, func.line) * 100; + nv_debug(therm, "FAN update: %d\n", duty); + ret = fan->set(therm, duty); + if (ret) + goto done; + + /* 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 -ENODEV; +done: + spin_unlock_irqrestore(&fan->lock, flags); + return ret; +} + +static void +nouveau_fan_alarm(struct nouveau_alarm *alarm) +{ + struct nouveau_fan *fan = container_of(alarm, struct nouveau_fan, alarm); + nouveau_fan_update(fan, false, -1); } int -nouveau_therm_fan_set(struct nouveau_therm *therm, int percent) +nouveau_therm_fan_get(struct nouveau_therm *therm) { struct nouveau_therm_priv *priv = (void *)therm; - struct nouveau_gpio *gpio = nouveau_gpio(therm); - struct dcb_gpio_func func; - int card_type = nv_device(therm)->card_type; - u32 divs, duty; - int ret; - - if (priv->fan.mode == FAN_CONTROL_NONE) - return -EINVAL; - - if (!priv->fan.pwm_set) - return -ENODEV; - - if (percent < priv->bios_fan.min_duty) - percent = priv->bios_fan.min_duty; - if (percent > priv->bios_fan.max_duty) - percent = priv->bios_fan.max_duty; - - ret = gpio->find(gpio, 0, DCB_GPIO_PWM_FAN, 0xff, &func); - if (ret == 0) { - divs = priv->bios_perf_fan.pwm_divisor; - if (priv->bios_fan.pwm_freq) { - divs = 1; - if (priv->fan.pwm_clock) - divs = priv->fan.pwm_clock(therm); - divs /= priv->bios_fan.pwm_freq; - } - - duty = ((divs * percent) + 99) / 100; - if (card_type <= NV_40 || (func.log[0] & 1)) - duty = divs - duty; - - ret = priv->fan.pwm_set(therm, func.line, divs, duty); - return ret; - } + return priv->fan->get(therm); +} - return -ENODEV; +int +nouveau_therm_fan_set(struct nouveau_therm *therm, bool immediate, int percent) +{ + struct nouveau_therm_priv *priv = (void *)therm; + return nouveau_fan_update(priv->fan, immediate, percent); } int nouveau_therm_fan_sense(struct nouveau_therm *therm) { + struct nouveau_therm_priv *priv = (void *)therm; struct nouveau_timer *ptimer = nouveau_timer(therm); struct nouveau_gpio *gpio = nouveau_gpio(therm); - struct dcb_gpio_func func; u32 cycles, cur, prev; u64 start, end, tach; - if (gpio->find(gpio, 0, DCB_GPIO_FAN_SENSE, 0xff, &func)) + if (priv->fan->tach.func == DCB_GPIO_UNUSED) return -ENODEV; /* Time a complete rotation and extrapolate to RPM: @@ -118,12 +135,12 @@ nouveau_therm_fan_sense(struct nouveau_therm *therm) * We get 4 changes (0 -> 1 -> 0 -> 1) per complete rotation. */ start = ptimer->read(ptimer); - prev = gpio->get(gpio, 0, func.func, func.line); + 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, func.func, func.line); + cur = gpio->get(gpio, 0, priv->fan->tach.func, priv->fan->tach.line); if (prev != cur) { if (!start) start = ptimer->read(ptimer); @@ -142,34 +159,6 @@ nouveau_therm_fan_sense(struct nouveau_therm *therm) } int -nouveau_therm_fan_set_mode(struct nouveau_therm *therm, - enum nouveau_therm_fan_mode mode) -{ - struct nouveau_therm_priv *priv = (void *)therm; - - if (priv->fan.mode == mode) - return 0; - - if (mode < FAN_CONTROL_NONE || mode >= FAN_CONTROL_NR) - return -EINVAL; - - switch (mode) - { - case FAN_CONTROL_NONE: - nv_info(therm, "switch fan to no-control mode\n"); - break; - case FAN_CONTROL_MANUAL: - nv_info(therm, "switch fan to manual mode\n"); - break; - case FAN_CONTROL_NR: - break; - } - - priv->fan.mode = mode; - return 0; -} - -int nouveau_therm_fan_user_get(struct nouveau_therm *therm) { return nouveau_therm_fan_get(therm); @@ -180,55 +169,86 @@ nouveau_therm_fan_user_set(struct nouveau_therm *therm, int percent) { struct nouveau_therm_priv *priv = (void *)therm; - if (priv->fan.mode != FAN_CONTROL_MANUAL) + if (priv->mode != NOUVEAU_THERM_CTRL_MANUAL) return -EINVAL; - return nouveau_therm_fan_set(therm, percent); + return nouveau_therm_fan_set(therm, true, percent); } -void +static void nouveau_therm_fan_set_defaults(struct nouveau_therm *therm) { struct nouveau_therm_priv *priv = (void *)therm; - priv->bios_fan.pwm_freq = 0; - priv->bios_fan.min_duty = 0; - priv->bios_fan.max_duty = 100; + 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 nouveau_therm_fan_safety_checks(struct nouveau_therm *therm) { struct nouveau_therm_priv *priv = (void *)therm; - if (priv->bios_fan.min_duty > 100) - priv->bios_fan.min_duty = 100; - if (priv->bios_fan.max_duty > 100) - priv->bios_fan.max_duty = 100; + 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->bios_fan.min_duty > priv->bios_fan.max_duty) - priv->bios_fan.min_duty = priv->bios_fan.max_duty; -} - -int nouveau_fan_pwm_clock_dummy(struct nouveau_therm *therm) -{ - return 1; + if (priv->fan->bios.min_duty > priv->fan->bios.max_duty) + priv->fan->bios.min_duty = priv->fan->bios.max_duty; } int nouveau_therm_fan_ctor(struct nouveau_therm *therm) { struct nouveau_therm_priv *priv = (void *)therm; + struct nouveau_gpio *gpio = nouveau_gpio(therm); struct nouveau_bios *bios = nouveau_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) { + if (func.log[0] & DCB_GPIO_LOG_DIR_IN) { + nv_debug(therm, "GPIO_FAN is in input mode\n"); + ret = -EINVAL; + } else { + ret = nouveau_fanpwm_create(therm, &func); + if (ret != 0) + ret = nouveau_fantog_create(therm, &func); + } + } + + /* no controllable fan found, create a dummy fan module */ + if (ret != 0) { + ret = nouveau_fannil_create(therm); + if (ret) + return ret; + } + + nv_info(therm, "FAN control: %s\n", priv->fan->type); + + /* 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; + nouveau_alarm_init(&priv->fan->alarm, nouveau_fan_alarm); + spin_lock_init(&priv->fan->lock); + + /* other random init... */ nouveau_therm_fan_set_defaults(therm); - nvbios_perf_fan_parse(bios, &priv->bios_perf_fan); - if (nvbios_therm_fan_parse(bios, &priv->bios_fan)) + nvbios_perf_fan_parse(bios, &priv->fan->perf); + if (nvbios_therm_fan_parse(bios, &priv->fan->bios)) nv_error(therm, "parsing the thermal table failed\n"); nouveau_therm_fan_safety_checks(therm); - - nouveau_therm_fan_set_mode(therm, FAN_CONTROL_NONE); - return 0; } |