1/* 2 * Copyright 2012 Nouveau Community 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: Martin Peres 23 */ 24#include <subdev/bios.h> 25#include <subdev/bios/bit.h> 26#include <subdev/bios/therm.h> 27 28#include <core/device.h> 29 30static u16 31therm_table(struct nvkm_bios *bios, u8 *ver, u8 *hdr, u8 *len, u8 *cnt) 32{ 33 struct bit_entry bit_P; 34 u16 therm = 0; 35 36 if (!bit_entry(bios, 'P', &bit_P)) { 37 if (bit_P.version == 1) 38 therm = nv_ro16(bios, bit_P.offset + 12); 39 else if (bit_P.version == 2) 40 therm = nv_ro16(bios, bit_P.offset + 16); 41 else 42 nv_error(bios, 43 "unknown offset for thermal in BIT P %d\n", 44 bit_P.version); 45 } 46 47 /* exit now if we haven't found the thermal table */ 48 if (!therm) 49 return 0x0000; 50 51 *ver = nv_ro08(bios, therm + 0); 52 *hdr = nv_ro08(bios, therm + 1); 53 *len = nv_ro08(bios, therm + 2); 54 *cnt = nv_ro08(bios, therm + 3); 55 return therm + nv_ro08(bios, therm + 1); 56} 57 58static u16 59nvbios_therm_entry(struct nvkm_bios *bios, int idx, u8 *ver, u8 *len) 60{ 61 u8 hdr, cnt; 62 u16 therm = therm_table(bios, ver, &hdr, len, &cnt); 63 if (therm && idx < cnt) 64 return therm + idx * *len; 65 return 0x0000; 66} 67 68int 69nvbios_therm_sensor_parse(struct nvkm_bios *bios, 70 enum nvbios_therm_domain domain, 71 struct nvbios_therm_sensor *sensor) 72{ 73 s8 thrs_section, sensor_section, offset; 74 u8 ver, len, i; 75 u16 entry; 76 77 /* we only support the core domain for now */ 78 if (domain != NVBIOS_THERM_DOMAIN_CORE) 79 return -EINVAL; 80 81 /* Read the entries from the table */ 82 thrs_section = 0; 83 sensor_section = -1; 84 i = 0; 85 while ((entry = nvbios_therm_entry(bios, i++, &ver, &len))) { 86 s16 value = nv_ro16(bios, entry + 1); 87 88 switch (nv_ro08(bios, entry + 0)) { 89 case 0x0: 90 thrs_section = value; 91 if (value > 0) 92 return 0; /* we do not try to support ambient */ 93 break; 94 case 0x01: 95 sensor_section++; 96 if (sensor_section == 0) { 97 offset = ((s8) nv_ro08(bios, entry + 2)) / 2; 98 sensor->offset_constant = offset; 99 } 100 break; 101 102 case 0x04: 103 if (thrs_section == 0) { 104 sensor->thrs_critical.temp = (value & 0xff0) >> 4; 105 sensor->thrs_critical.hysteresis = value & 0xf; 106 } 107 break; 108 109 case 0x07: 110 if (thrs_section == 0) { 111 sensor->thrs_down_clock.temp = (value & 0xff0) >> 4; 112 sensor->thrs_down_clock.hysteresis = value & 0xf; 113 } 114 break; 115 116 case 0x08: 117 if (thrs_section == 0) { 118 sensor->thrs_fan_boost.temp = (value & 0xff0) >> 4; 119 sensor->thrs_fan_boost.hysteresis = value & 0xf; 120 } 121 break; 122 123 case 0x10: 124 if (sensor_section == 0) 125 sensor->offset_num = value; 126 break; 127 128 case 0x11: 129 if (sensor_section == 0) 130 sensor->offset_den = value; 131 break; 132 133 case 0x12: 134 if (sensor_section == 0) 135 sensor->slope_mult = value; 136 break; 137 138 case 0x13: 139 if (sensor_section == 0) 140 sensor->slope_div = value; 141 break; 142 case 0x32: 143 if (thrs_section == 0) { 144 sensor->thrs_shutdown.temp = (value & 0xff0) >> 4; 145 sensor->thrs_shutdown.hysteresis = value & 0xf; 146 } 147 break; 148 } 149 } 150 151 return 0; 152} 153 154int 155nvbios_therm_fan_parse(struct nvkm_bios *bios, struct nvbios_therm_fan *fan) 156{ 157 struct nvbios_therm_trip_point *cur_trip = NULL; 158 u8 ver, len, i; 159 u16 entry; 160 161 uint8_t duty_lut[] = { 0, 0, 25, 0, 40, 0, 50, 0, 162 75, 0, 85, 0, 100, 0, 100, 0 }; 163 164 i = 0; 165 fan->nr_fan_trip = 0; 166 fan->fan_mode = NVBIOS_THERM_FAN_OTHER; 167 while ((entry = nvbios_therm_entry(bios, i++, &ver, &len))) { 168 s16 value = nv_ro16(bios, entry + 1); 169 170 switch (nv_ro08(bios, entry + 0)) { 171 case 0x22: 172 fan->min_duty = value & 0xff; 173 fan->max_duty = (value & 0xff00) >> 8; 174 break; 175 case 0x24: 176 fan->nr_fan_trip++; 177 if (fan->fan_mode > NVBIOS_THERM_FAN_TRIP) 178 fan->fan_mode = NVBIOS_THERM_FAN_TRIP; 179 cur_trip = &fan->trip[fan->nr_fan_trip - 1]; 180 cur_trip->hysteresis = value & 0xf; 181 cur_trip->temp = (value & 0xff0) >> 4; 182 cur_trip->fan_duty = duty_lut[(value & 0xf000) >> 12]; 183 break; 184 case 0x25: 185 cur_trip = &fan->trip[fan->nr_fan_trip - 1]; 186 cur_trip->fan_duty = value; 187 break; 188 case 0x26: 189 if (!fan->pwm_freq) 190 fan->pwm_freq = value; 191 break; 192 case 0x3b: 193 fan->bump_period = value; 194 break; 195 case 0x3c: 196 fan->slow_down_period = value; 197 break; 198 case 0x46: 199 if (fan->fan_mode > NVBIOS_THERM_FAN_LINEAR) 200 fan->fan_mode = NVBIOS_THERM_FAN_LINEAR; 201 fan->linear_min_temp = nv_ro08(bios, entry + 1); 202 fan->linear_max_temp = nv_ro08(bios, entry + 2); 203 break; 204 } 205 } 206 207 /* starting from fermi, fan management is always linear */ 208 if (nv_device(bios)->card_type >= NV_C0 && 209 fan->fan_mode == NVBIOS_THERM_FAN_OTHER) { 210 fan->fan_mode = NVBIOS_THERM_FAN_LINEAR; 211 } 212 213 return 0; 214} 215