blob: b1cf06eca28e172822dc1e74fb7d12bd901213ed [file] [log] [blame]
Angel Pons8a3453f2020-04-02 23:48:19 +02001/* SPDX-License-Identifier: GPL-2.0-only */
2/* This file is part of the coreboot project. */
Nico Huberffea2372017-08-24 23:30:44 +02003
4#include <delay.h>
5#include <timer.h>
6#include <console/console.h>
7#include <device/device.h>
8#include <device/i2c_bus.h>
9
10#include "lm96000.h"
11#include "chip.h"
12
13static inline int lm96000_read(struct device *const dev, const u8 reg)
14{
15 return i2c_dev_readb_at(dev, reg);
16}
17
18static inline int lm96000_write(struct device *const dev,
19 const u8 reg, const u8 value)
20{
21 return i2c_dev_writeb_at(dev, reg, value);
22}
23
24static inline int lm96000_update(struct device *const dev, const u8 reg,
25 const u8 clear_mask, const u8 set_mask)
26{
27 const int val = i2c_dev_readb_at(dev, reg);
28 if (val < 0)
29 return val;
30 return i2c_dev_writeb_at(dev, reg, (val & ~clear_mask) | set_mask);
31}
32
33static const unsigned int ref_mv[] = { 2500, 2250, 3300, 5000, 12000 };
34
35static u8 lm96000_to_low_limit(const enum lm96000_vin ref, const u16 limit)
36{
37 const unsigned int reg =
38 (unsigned int)limit * 0xc0 / ref_mv[ref];
39 return reg < 0xff ? reg : 0xff;
40}
41
42static u8 lm96000_to_high_limit(const enum lm96000_vin ref, const u16 limit)
43{
44 const unsigned int reg =
45 DIV_ROUND_UP((unsigned int)limit * 0xc0, ref_mv[ref]);
46 return reg < 0xff ? reg : 0xff;
47}
48
49static void lm96000_set_vin_limits(struct device *const dev,
50 const struct drivers_i2c_lm96000_config *const config)
51{
52 unsigned int i;
53
54 for (i = 0; i < LM96000_VIN_CNT; ++i) {
55 lm96000_write(dev, LM96000_VIN_LOW_LIMIT(i),
56 lm96000_to_low_limit(i, config->vin[i].low));
57 if (config->vin[i].high > config->vin[i].low)
58 lm96000_write(dev, LM96000_VIN_HIGH_LIMIT(i),
59 lm96000_to_high_limit(i, config->vin[i].high));
60 else
61 lm96000_write(dev, LM96000_VIN_HIGH_LIMIT(i), 0xff);
62 }
63}
64
65static void lm96000_set_temp_limits(struct device *const dev,
66 const struct drivers_i2c_lm96000_config *const config)
67{
68 unsigned int i;
69
70 for (i = 0; i < LM96000_TEMP_IN_CNT; ++i) {
71 lm96000_write(dev, LM96000_TEMP_LOW_LIMIT(i),
72 config->temp_in[i].low);
73 if (config->temp_in[i].high > config->temp_in[i].low)
74 lm96000_write(dev, LM96000_TEMP_HIGH_LIMIT(i),
75 config->temp_in[i].high);
76 else
77 lm96000_write(dev, LM96000_TEMP_HIGH_LIMIT(i), 0x7f);
78 }
79}
80
81static u16 lm96000_rpm_to_tach(const u16 rpm)
82{
83 return rpm ? (60 * 90000 / rpm) & 0xfffc : 0xfffc;
84}
85
86static void lm96000_set_fan_limits(struct device *const dev,
87 const struct drivers_i2c_lm96000_config *const config)
88{
89 unsigned int i;
90
91 for (i = 0; i < LM96000_FAN_IN_CNT; ++i) {
92 const u16 tach = lm96000_rpm_to_tach(config->fan_in[i].low);
93 lm96000_write(dev, LM96000_FAN_LOW_LIMIT(i), tach & 0xff);
94 lm96000_write(dev, LM96000_FAN_LOW_LIMIT(i) + 1, tach >> 8);
95 }
96}
97
98static u8 lm96000_to_duty(const u8 duty_cycle)
99{
100 return duty_cycle * 255 / 100;
101}
102
103static void lm96000_configure_pwm(struct device *const dev,
104 const unsigned int fan,
105 const struct lm96000_fan_config *const config)
106{
107 lm96000_update(dev, LM96000_FAN_CFG(fan),
108 LM96000_FAN_CFG_MODE_MASK | LM96000_FAN_CFG_PWM_INVERT |
109 LM96000_FAN_CFG_SPINUP_MASK,
110 ((config->mode << LM96000_FAN_CFG_MODE_SHIFT)
111 & LM96000_FAN_CFG_MODE_MASK) |
112 (config->invert ? LM96000_FAN_CFG_PWM_INVERT : 0) |
113 config->spinup);
114 lm96000_update(dev, LM96000_FAN_FREQ(fan),
115 LM96000_FAN_FREQ_MASK, config->freq);
116 lm96000_update(dev, LM96000_TACH_MONITOR_MODE,
117 LM96000_TACH_MODE_FAN_MASK(fan),
118 config->freq <= LM96000_PWM_94HZ
119 ? config->tach << LM96000_TACH_MODE_FAN_SHIFT(fan) : 0);
120
121 switch (config->mode) {
122 case LM96000_FAN_ZONE_1_AUTO:
123 case LM96000_FAN_ZONE_2_AUTO:
124 case LM96000_FAN_ZONE_3_AUTO:
125 case LM96000_FAN_HOTTEST_23:
126 case LM96000_FAN_HOTTEST_123:
127 lm96000_write(dev, LM96000_FAN_MIN_PWM(fan),
128 lm96000_to_duty(config->min_duty));
129 break;
130 case LM96000_FAN_MANUAL:
131 lm96000_write(dev, LM96000_FAN_DUTY(fan),
132 lm96000_to_duty(config->duty_cycle));
133 break;
134 default:
135 break;
136 }
137}
138
139static void lm96000_configure_temp_zone(struct device *const dev,
140 const unsigned int zone,
141 const struct lm96000_temp_zone *const config)
142{
143 static const u8 temp_range[] =
144 { 2, 3, 3, 4, 5, 7, 8, 10, 13, 16, 20, 27, 32, 40, 53, 80 };
145 unsigned int i;
146
Nico Huberb56fcfe2019-08-06 18:05:50 +0200147 /* find longest range that starts from `low_temp` */
Nico Huberffea2372017-08-24 23:30:44 +0200148 for (i = ARRAY_SIZE(temp_range) - 1; i > 0; --i) {
Nico Huberb56fcfe2019-08-06 18:05:50 +0200149 if (config->low_temp + temp_range[i] <= config->target_temp)
Nico Huberffea2372017-08-24 23:30:44 +0200150 break;
151 }
152
153 lm96000_update(dev, LM96000_ZONE_RANGE(zone),
154 LM96000_ZONE_RANGE_MASK, i << LM96000_ZONE_RANGE_SHIFT);
155 lm96000_write(dev, LM96000_ZONE_TEMP_LOW(zone),
156 config->target_temp >= temp_range[i]
157 ? config->target_temp - temp_range[i]
158 : 0);
159 lm96000_write(dev, LM96000_ZONE_TEMP_PANIC(zone),
160 config->panic_temp ? config->panic_temp : 100);
Nico Huberb56fcfe2019-08-06 18:05:50 +0200161 lm96000_update(dev, LM96000_ZONE_SMOOTH(zone),
162 LM96000_ZONE_SMOOTH_MASK(zone),
163 LM96000_ZONE_SMOOTH_EN(zone) | 0); /* 0: 35s */
Nico Huberffea2372017-08-24 23:30:44 +0200164 lm96000_update(dev, LM96000_FAN_MIN_OFF,
165 LM96000_FAN_MIN(zone),
166 config->min_off ? LM96000_FAN_MIN(zone) : 0);
Nico Huberb56fcfe2019-08-06 18:05:50 +0200167 lm96000_update(dev, LM96000_ZONE_HYSTERESIS(zone),
168 LM96000_ZONE_HYST_MASK(zone),
169 config->hysteresis << LM96000_ZONE_HYST_SHIFT(zone)
170 & LM96000_ZONE_HYST_MASK(zone));
Nico Huberffea2372017-08-24 23:30:44 +0200171}
172
173static void lm96000_init(struct device *const dev)
174{
175 const struct drivers_i2c_lm96000_config *const config = dev->chip_info;
Nico Huber9a940bf2019-05-20 14:13:12 +0200176 unsigned int i;
177 int lm_config;
Nico Huberffea2372017-08-24 23:30:44 +0200178 struct stopwatch sw;
179
180 printk(BIOS_DEBUG, "lm96000: Initialization hardware monitoring.\n");
181
182 stopwatch_init_msecs_expire(&sw, 1000);
183 lm_config = lm96000_read(dev, LM96000_CONFIG);
Nico Huber9a940bf2019-05-20 14:13:12 +0200184 while ((lm_config < 0 || !((unsigned int)lm_config & LM96000_READY))) {
Nico Huberffea2372017-08-24 23:30:44 +0200185 mdelay(1);
186 lm_config = lm96000_read(dev, LM96000_CONFIG);
187 if (stopwatch_expired(&sw))
188 break;
189 }
Nico Huber9a940bf2019-05-20 14:13:12 +0200190 if (lm_config < 0 || !((unsigned int)lm_config & LM96000_READY)) {
Nico Huberffea2372017-08-24 23:30:44 +0200191 printk(BIOS_INFO, "lm96000: Not ready after 1s.\n");
192 return;
193 }
194
195 lm96000_set_vin_limits(dev, config);
196 lm96000_set_temp_limits(dev, config);
197 lm96000_set_fan_limits(dev, config);
198 for (i = 0; i < LM96000_PWM_CTL_CNT; ++i) {
199 if (config->fan[i].mode != LM96000_FAN_IGNORE)
200 lm96000_configure_pwm(dev, i, config->fan + i);
201 }
202 for (i = 0; i < LM96000_TEMP_ZONE_CNT; ++i)
203 lm96000_configure_temp_zone(dev, i, config->zone + i);
204 lm96000_update(dev, LM96000_CONFIG, 0, LM96000_START);
205}
206
207static struct device_operations lm96000_ops = {
Nico Huber2f8ba692020-04-05 14:05:24 +0200208 .read_resources = noop_read_resources,
209 .set_resources = noop_set_resources,
Nico Huberffea2372017-08-24 23:30:44 +0200210 .init = lm96000_init,
211};
212
213static void lm96000_enable(struct device *const dev)
214{
215 dev->ops = &lm96000_ops;
216}
217
218struct chip_operations drivers_i2c_lm96000_ops = {
219 CHIP_NAME("LM96000")
220 .enable_dev = lm96000_enable
221};