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