blob: 5130630fd1c19e44f465d40b6088898e5a047b7e [file] [log] [blame]
Nico Huberffea2372017-08-24 23:30:44 +02001/*
2 * This file is part of the coreboot project.
3 *
Nico Huberffea2372017-08-24 23:30:44 +02004 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 2 of the License.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
13
14#include <delay.h>
15#include <timer.h>
16#include <console/console.h>
17#include <device/device.h>
18#include <device/i2c_bus.h>
19
20#include "lm96000.h"
21#include "chip.h"
22
23static inline int lm96000_read(struct device *const dev, const u8 reg)
24{
25 return i2c_dev_readb_at(dev, reg);
26}
27
28static inline int lm96000_write(struct device *const dev,
29 const u8 reg, const u8 value)
30{
31 return i2c_dev_writeb_at(dev, reg, value);
32}
33
34static inline int lm96000_update(struct device *const dev, const u8 reg,
35 const u8 clear_mask, const u8 set_mask)
36{
37 const int val = i2c_dev_readb_at(dev, reg);
38 if (val < 0)
39 return val;
40 return i2c_dev_writeb_at(dev, reg, (val & ~clear_mask) | set_mask);
41}
42
43static const unsigned int ref_mv[] = { 2500, 2250, 3300, 5000, 12000 };
44
45static u8 lm96000_to_low_limit(const enum lm96000_vin ref, const u16 limit)
46{
47 const unsigned int reg =
48 (unsigned int)limit * 0xc0 / ref_mv[ref];
49 return reg < 0xff ? reg : 0xff;
50}
51
52static u8 lm96000_to_high_limit(const enum lm96000_vin ref, const u16 limit)
53{
54 const unsigned int reg =
55 DIV_ROUND_UP((unsigned int)limit * 0xc0, ref_mv[ref]);
56 return reg < 0xff ? reg : 0xff;
57}
58
59static void lm96000_set_vin_limits(struct device *const dev,
60 const struct drivers_i2c_lm96000_config *const config)
61{
62 unsigned int i;
63
64 for (i = 0; i < LM96000_VIN_CNT; ++i) {
65 lm96000_write(dev, LM96000_VIN_LOW_LIMIT(i),
66 lm96000_to_low_limit(i, config->vin[i].low));
67 if (config->vin[i].high > config->vin[i].low)
68 lm96000_write(dev, LM96000_VIN_HIGH_LIMIT(i),
69 lm96000_to_high_limit(i, config->vin[i].high));
70 else
71 lm96000_write(dev, LM96000_VIN_HIGH_LIMIT(i), 0xff);
72 }
73}
74
75static void lm96000_set_temp_limits(struct device *const dev,
76 const struct drivers_i2c_lm96000_config *const config)
77{
78 unsigned int i;
79
80 for (i = 0; i < LM96000_TEMP_IN_CNT; ++i) {
81 lm96000_write(dev, LM96000_TEMP_LOW_LIMIT(i),
82 config->temp_in[i].low);
83 if (config->temp_in[i].high > config->temp_in[i].low)
84 lm96000_write(dev, LM96000_TEMP_HIGH_LIMIT(i),
85 config->temp_in[i].high);
86 else
87 lm96000_write(dev, LM96000_TEMP_HIGH_LIMIT(i), 0x7f);
88 }
89}
90
91static u16 lm96000_rpm_to_tach(const u16 rpm)
92{
93 return rpm ? (60 * 90000 / rpm) & 0xfffc : 0xfffc;
94}
95
96static void lm96000_set_fan_limits(struct device *const dev,
97 const struct drivers_i2c_lm96000_config *const config)
98{
99 unsigned int i;
100
101 for (i = 0; i < LM96000_FAN_IN_CNT; ++i) {
102 const u16 tach = lm96000_rpm_to_tach(config->fan_in[i].low);
103 lm96000_write(dev, LM96000_FAN_LOW_LIMIT(i), tach & 0xff);
104 lm96000_write(dev, LM96000_FAN_LOW_LIMIT(i) + 1, tach >> 8);
105 }
106}
107
108static u8 lm96000_to_duty(const u8 duty_cycle)
109{
110 return duty_cycle * 255 / 100;
111}
112
113static void lm96000_configure_pwm(struct device *const dev,
114 const unsigned int fan,
115 const struct lm96000_fan_config *const config)
116{
117 lm96000_update(dev, LM96000_FAN_CFG(fan),
118 LM96000_FAN_CFG_MODE_MASK | LM96000_FAN_CFG_PWM_INVERT |
119 LM96000_FAN_CFG_SPINUP_MASK,
120 ((config->mode << LM96000_FAN_CFG_MODE_SHIFT)
121 & LM96000_FAN_CFG_MODE_MASK) |
122 (config->invert ? LM96000_FAN_CFG_PWM_INVERT : 0) |
123 config->spinup);
124 lm96000_update(dev, LM96000_FAN_FREQ(fan),
125 LM96000_FAN_FREQ_MASK, config->freq);
126 lm96000_update(dev, LM96000_TACH_MONITOR_MODE,
127 LM96000_TACH_MODE_FAN_MASK(fan),
128 config->freq <= LM96000_PWM_94HZ
129 ? config->tach << LM96000_TACH_MODE_FAN_SHIFT(fan) : 0);
130
131 switch (config->mode) {
132 case LM96000_FAN_ZONE_1_AUTO:
133 case LM96000_FAN_ZONE_2_AUTO:
134 case LM96000_FAN_ZONE_3_AUTO:
135 case LM96000_FAN_HOTTEST_23:
136 case LM96000_FAN_HOTTEST_123:
137 lm96000_write(dev, LM96000_FAN_MIN_PWM(fan),
138 lm96000_to_duty(config->min_duty));
139 break;
140 case LM96000_FAN_MANUAL:
141 lm96000_write(dev, LM96000_FAN_DUTY(fan),
142 lm96000_to_duty(config->duty_cycle));
143 break;
144 default:
145 break;
146 }
147}
148
149static void lm96000_configure_temp_zone(struct device *const dev,
150 const unsigned int zone,
151 const struct lm96000_temp_zone *const config)
152{
153 static const u8 temp_range[] =
154 { 2, 3, 3, 4, 5, 7, 8, 10, 13, 16, 20, 27, 32, 40, 53, 80 };
155 unsigned int i;
156
Nico Huberb56fcfe2019-08-06 18:05:50 +0200157 /* find longest range that starts from `low_temp` */
Nico Huberffea2372017-08-24 23:30:44 +0200158 for (i = ARRAY_SIZE(temp_range) - 1; i > 0; --i) {
Nico Huberb56fcfe2019-08-06 18:05:50 +0200159 if (config->low_temp + temp_range[i] <= config->target_temp)
Nico Huberffea2372017-08-24 23:30:44 +0200160 break;
161 }
162
163 lm96000_update(dev, LM96000_ZONE_RANGE(zone),
164 LM96000_ZONE_RANGE_MASK, i << LM96000_ZONE_RANGE_SHIFT);
165 lm96000_write(dev, LM96000_ZONE_TEMP_LOW(zone),
166 config->target_temp >= temp_range[i]
167 ? config->target_temp - temp_range[i]
168 : 0);
169 lm96000_write(dev, LM96000_ZONE_TEMP_PANIC(zone),
170 config->panic_temp ? config->panic_temp : 100);
Nico Huberb56fcfe2019-08-06 18:05:50 +0200171 lm96000_update(dev, LM96000_ZONE_SMOOTH(zone),
172 LM96000_ZONE_SMOOTH_MASK(zone),
173 LM96000_ZONE_SMOOTH_EN(zone) | 0); /* 0: 35s */
Nico Huberffea2372017-08-24 23:30:44 +0200174 lm96000_update(dev, LM96000_FAN_MIN_OFF,
175 LM96000_FAN_MIN(zone),
176 config->min_off ? LM96000_FAN_MIN(zone) : 0);
Nico Huberb56fcfe2019-08-06 18:05:50 +0200177 lm96000_update(dev, LM96000_ZONE_HYSTERESIS(zone),
178 LM96000_ZONE_HYST_MASK(zone),
179 config->hysteresis << LM96000_ZONE_HYST_SHIFT(zone)
180 & LM96000_ZONE_HYST_MASK(zone));
Nico Huberffea2372017-08-24 23:30:44 +0200181}
182
183static void lm96000_init(struct device *const dev)
184{
185 const struct drivers_i2c_lm96000_config *const config = dev->chip_info;
Nico Huber9a940bf2019-05-20 14:13:12 +0200186 unsigned int i;
187 int lm_config;
Nico Huberffea2372017-08-24 23:30:44 +0200188 struct stopwatch sw;
189
190 printk(BIOS_DEBUG, "lm96000: Initialization hardware monitoring.\n");
191
192 stopwatch_init_msecs_expire(&sw, 1000);
193 lm_config = lm96000_read(dev, LM96000_CONFIG);
Nico Huber9a940bf2019-05-20 14:13:12 +0200194 while ((lm_config < 0 || !((unsigned int)lm_config & LM96000_READY))) {
Nico Huberffea2372017-08-24 23:30:44 +0200195 mdelay(1);
196 lm_config = lm96000_read(dev, LM96000_CONFIG);
197 if (stopwatch_expired(&sw))
198 break;
199 }
Nico Huber9a940bf2019-05-20 14:13:12 +0200200 if (lm_config < 0 || !((unsigned int)lm_config & LM96000_READY)) {
Nico Huberffea2372017-08-24 23:30:44 +0200201 printk(BIOS_INFO, "lm96000: Not ready after 1s.\n");
202 return;
203 }
204
205 lm96000_set_vin_limits(dev, config);
206 lm96000_set_temp_limits(dev, config);
207 lm96000_set_fan_limits(dev, config);
208 for (i = 0; i < LM96000_PWM_CTL_CNT; ++i) {
209 if (config->fan[i].mode != LM96000_FAN_IGNORE)
210 lm96000_configure_pwm(dev, i, config->fan + i);
211 }
212 for (i = 0; i < LM96000_TEMP_ZONE_CNT; ++i)
213 lm96000_configure_temp_zone(dev, i, config->zone + i);
214 lm96000_update(dev, LM96000_CONFIG, 0, LM96000_START);
215}
216
217static struct device_operations lm96000_ops = {
218 .read_resources = DEVICE_NOOP,
219 .set_resources = DEVICE_NOOP,
220 .enable_resources = DEVICE_NOOP,
221 .init = lm96000_init,
222};
223
224static void lm96000_enable(struct device *const dev)
225{
226 dev->ops = &lm96000_ops;
227}
228
229struct chip_operations drivers_i2c_lm96000_ops = {
230 CHIP_NAME("LM96000")
231 .enable_dev = lm96000_enable
232};