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