Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
fan.c
Go to the documentation of this file.
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 
26 #include "priv.h"
27 
28 #include <core/object.h>
29 #include <core/device.h>
30 #include <subdev/gpio.h>
31 #include <subdev/timer.h>
32 
33 int
35 {
36  struct nouveau_therm_priv *priv = (void *)therm;
37  struct nouveau_gpio *gpio = nouveau_gpio(therm);
38  struct dcb_gpio_func func;
39  int card_type = nv_device(therm)->card_type;
40  u32 divs, duty;
41  int ret;
42 
43  if (!priv->fan.pwm_get)
44  return -ENODEV;
45 
46  ret = gpio->find(gpio, 0, DCB_GPIO_PWM_FAN, 0xff, &func);
47  if (ret == 0) {
48  ret = priv->fan.pwm_get(therm, func.line, &divs, &duty);
49  if (ret == 0 && divs) {
50  divs = max(divs, duty);
51  if (card_type <= NV_40 || (func.log[0] & 1))
52  duty = divs - duty;
53  return (duty * 100) / divs;
54  }
55 
56  return gpio->get(gpio, 0, func.func, func.line) * 100;
57  }
58 
59  return -ENODEV;
60 }
61 
62 int
64 {
65  struct nouveau_therm_priv *priv = (void *)therm;
66  struct nouveau_gpio *gpio = nouveau_gpio(therm);
67  struct dcb_gpio_func func;
68  int card_type = nv_device(therm)->card_type;
69  u32 divs, duty;
70  int ret;
71 
72  if (priv->fan.mode == FAN_CONTROL_NONE)
73  return -EINVAL;
74 
75  if (!priv->fan.pwm_set)
76  return -ENODEV;
77 
78  if (percent < priv->bios_fan.min_duty)
79  percent = priv->bios_fan.min_duty;
80  if (percent > priv->bios_fan.max_duty)
81  percent = priv->bios_fan.max_duty;
82 
83  ret = gpio->find(gpio, 0, DCB_GPIO_PWM_FAN, 0xff, &func);
84  if (ret == 0) {
85  divs = priv->bios_perf_fan.pwm_divisor;
86  if (priv->bios_fan.pwm_freq) {
87  divs = 1;
88  if (priv->fan.pwm_clock)
89  divs = priv->fan.pwm_clock(therm);
90  divs /= priv->bios_fan.pwm_freq;
91  }
92 
93  duty = ((divs * percent) + 99) / 100;
94  if (card_type <= NV_40 || (func.log[0] & 1))
95  duty = divs - duty;
96 
97  ret = priv->fan.pwm_set(therm, func.line, divs, duty);
98  return ret;
99  }
100 
101  return -ENODEV;
102 }
103 
104 int
106 {
107  struct nouveau_timer *ptimer = nouveau_timer(therm);
108  struct nouveau_gpio *gpio = nouveau_gpio(therm);
109  struct dcb_gpio_func func;
110  u32 cycles, cur, prev;
111  u64 start, end, tach;
112 
113  if (gpio->find(gpio, 0, DCB_GPIO_FAN_SENSE, 0xff, &func))
114  return -ENODEV;
115 
116  /* Time a complete rotation and extrapolate to RPM:
117  * When the fan spins, it changes the value of GPIO FAN_SENSE.
118  * We get 4 changes (0 -> 1 -> 0 -> 1) per complete rotation.
119  */
120  start = ptimer->read(ptimer);
121  prev = gpio->get(gpio, 0, func.func, func.line);
122  cycles = 0;
123  do {
124  usleep_range(500, 1000); /* supports 0 < rpm < 7500 */
125 
126  cur = gpio->get(gpio, 0, func.func, func.line);
127  if (prev != cur) {
128  if (!start)
129  start = ptimer->read(ptimer);
130  cycles++;
131  prev = cur;
132  }
133  } while (cycles < 5 && ptimer->read(ptimer) - start < 250000000);
134  end = ptimer->read(ptimer);
135 
136  if (cycles == 5) {
137  tach = (u64)60000000000ULL;
138  do_div(tach, (end - start));
139  return tach;
140  } else
141  return 0;
142 }
143 
144 int
147 {
148  struct nouveau_therm_priv *priv = (void *)therm;
149 
150  if (priv->fan.mode == mode)
151  return 0;
152 
153  if (mode < FAN_CONTROL_NONE || mode >= FAN_CONTROL_NR)
154  return -EINVAL;
155 
156  switch (mode)
157  {
158  case FAN_CONTROL_NONE:
159  nv_info(therm, "switch fan to no-control mode\n");
160  break;
161  case FAN_CONTROL_MANUAL:
162  nv_info(therm, "switch fan to manual mode\n");
163  break;
164  case FAN_CONTROL_NR:
165  break;
166  }
167 
168  priv->fan.mode = mode;
169  return 0;
170 }
171 
172 int
174 {
175  return nouveau_therm_fan_get(therm);
176 }
177 
178 int
180 {
181  struct nouveau_therm_priv *priv = (void *)therm;
182 
183  if (priv->fan.mode != FAN_CONTROL_MANUAL)
184  return -EINVAL;
185 
186  return nouveau_therm_fan_set(therm, percent);
187 }
188 
189 void
191 {
192  struct nouveau_therm_priv *priv = (void *)therm;
193 
194  priv->bios_fan.pwm_freq = 0;
195  priv->bios_fan.min_duty = 0;
196  priv->bios_fan.max_duty = 100;
197 }
198 
199 
200 static void
201 nouveau_therm_fan_safety_checks(struct nouveau_therm *therm)
202 {
203  struct nouveau_therm_priv *priv = (void *)therm;
204 
205  if (priv->bios_fan.min_duty > 100)
206  priv->bios_fan.min_duty = 100;
207  if (priv->bios_fan.max_duty > 100)
208  priv->bios_fan.max_duty = 100;
209 
210  if (priv->bios_fan.min_duty > priv->bios_fan.max_duty)
211  priv->bios_fan.min_duty = priv->bios_fan.max_duty;
212 }
213 
215 {
216  return 1;
217 }
218 
219 int
221 {
222  struct nouveau_therm_priv *priv = (void *)therm;
223  struct nouveau_bios *bios = nouveau_bios(therm);
224 
226  nvbios_perf_fan_parse(bios, &priv->bios_perf_fan);
227  if (nvbios_therm_fan_parse(bios, &priv->bios_fan))
228  nv_error(therm, "parsing the thermal table failed\n");
229  nouveau_therm_fan_safety_checks(therm);
230 
232 
233  return 0;
234 }