]>
Commit | Line | Data |
---|---|---|
79ffe859 GR |
1 | /* |
2 | * Driver for Linear Technology LTC4222 Dual Hot Swap controller | |
3 | * | |
4 | * Copyright (c) 2014 Guenter Roeck | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | */ | |
16 | ||
17 | #include <linux/kernel.h> | |
18 | #include <linux/module.h> | |
19 | #include <linux/err.h> | |
20 | #include <linux/slab.h> | |
21 | #include <linux/bitops.h> | |
22 | #include <linux/i2c.h> | |
23 | #include <linux/hwmon.h> | |
24 | #include <linux/hwmon-sysfs.h> | |
25 | #include <linux/jiffies.h> | |
26 | #include <linux/regmap.h> | |
27 | ||
28 | /* chip registers */ | |
29 | ||
30 | #define LTC4222_CONTROL1 0xd0 | |
31 | #define LTC4222_ALERT1 0xd1 | |
32 | #define LTC4222_STATUS1 0xd2 | |
33 | #define LTC4222_FAULT1 0xd3 | |
34 | #define LTC4222_CONTROL2 0xd4 | |
35 | #define LTC4222_ALERT2 0xd5 | |
36 | #define LTC4222_STATUS2 0xd6 | |
37 | #define LTC4222_FAULT2 0xd7 | |
38 | #define LTC4222_SOURCE1 0xd8 | |
39 | #define LTC4222_SOURCE2 0xda | |
40 | #define LTC4222_ADIN1 0xdc | |
41 | #define LTC4222_ADIN2 0xde | |
42 | #define LTC4222_SENSE1 0xe0 | |
43 | #define LTC4222_SENSE2 0xe2 | |
44 | #define LTC4222_ADC_CONTROL 0xe4 | |
45 | ||
46 | /* | |
47 | * Fault register bits | |
48 | */ | |
49 | #define FAULT_OV BIT(0) | |
50 | #define FAULT_UV BIT(1) | |
51 | #define FAULT_OC BIT(2) | |
52 | #define FAULT_POWER_BAD BIT(3) | |
53 | #define FAULT_FET_BAD BIT(5) | |
54 | ||
55 | /* Return the voltage from the given register in mV or mA */ | |
56 | static int ltc4222_get_value(struct device *dev, u8 reg) | |
57 | { | |
58 | struct regmap *regmap = dev_get_drvdata(dev); | |
59 | unsigned int val; | |
60 | u8 buf[2]; | |
61 | int ret; | |
62 | ||
63 | ret = regmap_bulk_read(regmap, reg, buf, 2); | |
64 | if (ret < 0) | |
65 | return ret; | |
66 | ||
67 | val = ((buf[0] << 8) + buf[1]) >> 6; | |
68 | ||
69 | switch (reg) { | |
70 | case LTC4222_ADIN1: | |
71 | case LTC4222_ADIN2: | |
72 | /* 1.25 mV resolution. Convert to mV. */ | |
73 | val = DIV_ROUND_CLOSEST(val * 5, 4); | |
74 | break; | |
75 | case LTC4222_SOURCE1: | |
76 | case LTC4222_SOURCE2: | |
77 | /* 31.25 mV resolution. Convert to mV. */ | |
78 | val = DIV_ROUND_CLOSEST(val * 125, 4); | |
79 | break; | |
80 | case LTC4222_SENSE1: | |
81 | case LTC4222_SENSE2: | |
82 | /* | |
83 | * 62.5 uV resolution. Convert to current as measured with | |
84 | * an 1 mOhm sense resistor, in mA. If a different sense | |
85 | * resistor is installed, calculate the actual current by | |
86 | * dividing the reported current by the sense resistor value | |
87 | * in mOhm. | |
88 | */ | |
89 | val = DIV_ROUND_CLOSEST(val * 125, 2); | |
90 | break; | |
91 | default: | |
92 | return -EINVAL; | |
93 | } | |
94 | return val; | |
95 | } | |
96 | ||
97 | static ssize_t ltc4222_show_value(struct device *dev, | |
98 | struct device_attribute *da, char *buf) | |
99 | { | |
100 | struct sensor_device_attribute *attr = to_sensor_dev_attr(da); | |
101 | int value; | |
102 | ||
103 | value = ltc4222_get_value(dev, attr->index); | |
104 | if (value < 0) | |
105 | return value; | |
106 | return snprintf(buf, PAGE_SIZE, "%d\n", value); | |
107 | } | |
108 | ||
109 | static ssize_t ltc4222_show_bool(struct device *dev, | |
110 | struct device_attribute *da, char *buf) | |
111 | { | |
112 | struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(da); | |
113 | struct regmap *regmap = dev_get_drvdata(dev); | |
114 | unsigned int fault; | |
115 | int ret; | |
116 | ||
117 | ret = regmap_read(regmap, attr->nr, &fault); | |
118 | if (ret < 0) | |
119 | return ret; | |
120 | fault &= attr->index; | |
121 | if (fault) /* Clear reported faults in chip register */ | |
122 | regmap_update_bits(regmap, attr->nr, attr->index, 0); | |
123 | ||
124 | return snprintf(buf, PAGE_SIZE, "%d\n", !!fault); | |
125 | } | |
126 | ||
127 | /* Voltages */ | |
128 | static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, ltc4222_show_value, NULL, | |
129 | LTC4222_SOURCE1); | |
130 | static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, ltc4222_show_value, NULL, | |
131 | LTC4222_ADIN1); | |
132 | static SENSOR_DEVICE_ATTR(in3_input, S_IRUGO, ltc4222_show_value, NULL, | |
133 | LTC4222_SOURCE2); | |
134 | static SENSOR_DEVICE_ATTR(in4_input, S_IRUGO, ltc4222_show_value, NULL, | |
135 | LTC4222_ADIN2); | |
136 | ||
137 | /* | |
138 | * Voltage alarms | |
139 | * UV/OV faults are associated with the input voltage, and power bad and fet | |
140 | * faults are associated with the output voltage. | |
141 | */ | |
142 | static SENSOR_DEVICE_ATTR_2(in1_min_alarm, S_IRUGO, ltc4222_show_bool, NULL, | |
143 | LTC4222_FAULT1, FAULT_UV); | |
144 | static SENSOR_DEVICE_ATTR_2(in1_max_alarm, S_IRUGO, ltc4222_show_bool, NULL, | |
145 | LTC4222_FAULT1, FAULT_OV); | |
146 | static SENSOR_DEVICE_ATTR_2(in2_alarm, S_IRUGO, ltc4222_show_bool, NULL, | |
147 | LTC4222_FAULT1, FAULT_POWER_BAD | FAULT_FET_BAD); | |
148 | ||
149 | static SENSOR_DEVICE_ATTR_2(in3_min_alarm, S_IRUGO, ltc4222_show_bool, NULL, | |
150 | LTC4222_FAULT2, FAULT_UV); | |
151 | static SENSOR_DEVICE_ATTR_2(in3_max_alarm, S_IRUGO, ltc4222_show_bool, NULL, | |
152 | LTC4222_FAULT2, FAULT_OV); | |
153 | static SENSOR_DEVICE_ATTR_2(in4_alarm, S_IRUGO, ltc4222_show_bool, NULL, | |
154 | LTC4222_FAULT2, FAULT_POWER_BAD | FAULT_FET_BAD); | |
155 | ||
156 | /* Current (via sense resistor) */ | |
157 | static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, ltc4222_show_value, NULL, | |
158 | LTC4222_SENSE1); | |
159 | static SENSOR_DEVICE_ATTR(curr2_input, S_IRUGO, ltc4222_show_value, NULL, | |
160 | LTC4222_SENSE2); | |
161 | ||
162 | /* Overcurrent alarm */ | |
163 | static SENSOR_DEVICE_ATTR_2(curr1_max_alarm, S_IRUGO, ltc4222_show_bool, NULL, | |
164 | LTC4222_FAULT1, FAULT_OC); | |
165 | static SENSOR_DEVICE_ATTR_2(curr2_max_alarm, S_IRUGO, ltc4222_show_bool, NULL, | |
166 | LTC4222_FAULT2, FAULT_OC); | |
167 | ||
168 | static struct attribute *ltc4222_attrs[] = { | |
169 | &sensor_dev_attr_in1_input.dev_attr.attr, | |
170 | &sensor_dev_attr_in1_min_alarm.dev_attr.attr, | |
171 | &sensor_dev_attr_in1_max_alarm.dev_attr.attr, | |
172 | &sensor_dev_attr_in2_input.dev_attr.attr, | |
173 | &sensor_dev_attr_in2_alarm.dev_attr.attr, | |
174 | &sensor_dev_attr_in3_input.dev_attr.attr, | |
175 | &sensor_dev_attr_in3_min_alarm.dev_attr.attr, | |
176 | &sensor_dev_attr_in3_max_alarm.dev_attr.attr, | |
177 | &sensor_dev_attr_in4_input.dev_attr.attr, | |
178 | &sensor_dev_attr_in4_alarm.dev_attr.attr, | |
179 | ||
180 | &sensor_dev_attr_curr1_input.dev_attr.attr, | |
181 | &sensor_dev_attr_curr1_max_alarm.dev_attr.attr, | |
182 | &sensor_dev_attr_curr2_input.dev_attr.attr, | |
183 | &sensor_dev_attr_curr2_max_alarm.dev_attr.attr, | |
184 | ||
185 | NULL, | |
186 | }; | |
187 | ATTRIBUTE_GROUPS(ltc4222); | |
188 | ||
034b44b4 | 189 | static const struct regmap_config ltc4222_regmap_config = { |
79ffe859 GR |
190 | .reg_bits = 8, |
191 | .val_bits = 8, | |
192 | .max_register = LTC4222_ADC_CONTROL, | |
193 | }; | |
194 | ||
195 | static int ltc4222_probe(struct i2c_client *client, | |
196 | const struct i2c_device_id *id) | |
197 | { | |
198 | struct device *dev = &client->dev; | |
199 | struct device *hwmon_dev; | |
200 | struct regmap *regmap; | |
201 | ||
202 | regmap = devm_regmap_init_i2c(client, <c4222_regmap_config); | |
203 | if (IS_ERR(regmap)) { | |
204 | dev_err(dev, "failed to allocate register map\n"); | |
205 | return PTR_ERR(regmap); | |
206 | } | |
207 | ||
208 | /* Clear faults */ | |
209 | regmap_write(regmap, LTC4222_FAULT1, 0x00); | |
210 | regmap_write(regmap, LTC4222_FAULT2, 0x00); | |
211 | ||
212 | hwmon_dev = devm_hwmon_device_register_with_groups(dev, client->name, | |
213 | regmap, | |
214 | ltc4222_groups); | |
215 | return PTR_ERR_OR_ZERO(hwmon_dev); | |
216 | } | |
217 | ||
218 | static const struct i2c_device_id ltc4222_id[] = { | |
219 | {"ltc4222", 0}, | |
220 | { } | |
221 | }; | |
222 | ||
223 | MODULE_DEVICE_TABLE(i2c, ltc4222_id); | |
224 | ||
225 | static struct i2c_driver ltc4222_driver = { | |
226 | .driver = { | |
227 | .name = "ltc4222", | |
228 | }, | |
229 | .probe = ltc4222_probe, | |
230 | .id_table = ltc4222_id, | |
231 | }; | |
232 | ||
233 | module_i2c_driver(ltc4222_driver); | |
234 | ||
235 | MODULE_AUTHOR("Guenter Roeck <[email protected]>"); | |
236 | MODULE_DESCRIPTION("LTC4222 driver"); | |
237 | MODULE_LICENSE("GPL"); |