1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Driver for Linear Technology LTC4222 Dual Hot Swap controller |
4 | * |
5 | * Copyright (c) 2014 Guenter Roeck |
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(map: regmap, reg, val: buf, val_count: 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 | |
88 | static ssize_t ltc4222_value_show(struct device *dev, |
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, reg: attr->index); |
95 | if (value < 0) |
96 | return value; |
97 | return sysfs_emit(buf, fmt: "%d\n" , value); |
98 | } |
99 | |
100 | static ssize_t ltc4222_bool_show(struct device *dev, |
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(map: regmap, reg: attr->nr, val: &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(map: regmap, reg: attr->nr, mask: attr->index, val: 0); |
114 | |
115 | return sysfs_emit(buf, fmt: "%d\n" , !!fault); |
116 | } |
117 | |
118 | /* Voltages */ |
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); |
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 | */ |
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); |
142 | |
143 | /* Current (via sense resistor) */ |
144 | static SENSOR_DEVICE_ATTR_RO(curr1_input, ltc4222_value, LTC4222_SENSE1); |
145 | static SENSOR_DEVICE_ATTR_RO(curr2_input, ltc4222_value, LTC4222_SENSE2); |
146 | |
147 | /* Overcurrent alarm */ |
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); |
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 | |
174 | static const struct regmap_config ltc4222_regmap_config = { |
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 | { |
182 | struct device *dev = &client->dev; |
183 | struct device *hwmon_dev; |
184 | struct regmap *regmap; |
185 | |
186 | regmap = devm_regmap_init_i2c(client, <c4222_regmap_config); |
187 | if (IS_ERR(ptr: regmap)) { |
188 | dev_err(dev, "failed to allocate register map\n" ); |
189 | return PTR_ERR(ptr: regmap); |
190 | } |
191 | |
192 | /* Clear faults */ |
193 | regmap_write(map: regmap, LTC4222_FAULT1, val: 0x00); |
194 | regmap_write(map: regmap, LTC4222_FAULT2, val: 0x00); |
195 | |
196 | hwmon_dev = devm_hwmon_device_register_with_groups(dev, name: client->name, |
197 | drvdata: regmap, |
198 | groups: ltc4222_groups); |
199 | return PTR_ERR_OR_ZERO(ptr: hwmon_dev); |
200 | } |
201 | |
202 | static const struct i2c_device_id ltc4222_id[] = { |
203 | {"ltc4222" , 0}, |
204 | { } |
205 | }; |
206 | |
207 | MODULE_DEVICE_TABLE(i2c, ltc4222_id); |
208 | |
209 | static struct i2c_driver ltc4222_driver = { |
210 | .driver = { |
211 | .name = "ltc4222" , |
212 | }, |
213 | .probe = ltc4222_probe, |
214 | .id_table = ltc4222_id, |
215 | }; |
216 | |
217 | module_i2c_driver(ltc4222_driver); |
218 | |
219 | MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>" ); |
220 | MODULE_DESCRIPTION("LTC4222 driver" ); |
221 | MODULE_LICENSE("GPL" ); |
222 | |