Commit | Line | Data |
---|---|---|
83f7649c GR |
1 | /* |
2 | * Hardware monitoring driver for Analog Devices ADM1275 Hot-Swap Controller | |
3 | * and Digital Power Monitor | |
4 | * | |
5 | * Copyright (c) 2011 Ericsson AB. | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; either version 2 of the License, or | |
10 | * (at your option) any later version. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | */ | |
17 | ||
18 | #include <linux/kernel.h> | |
19 | #include <linux/module.h> | |
20 | #include <linux/init.h> | |
21 | #include <linux/err.h> | |
22 | #include <linux/slab.h> | |
23 | #include <linux/i2c.h> | |
24 | #include "pmbus.h" | |
25 | ||
5cf231a3 GR |
26 | enum chips { adm1275, adm1276 }; |
27 | ||
c576e30c GR |
28 | #define ADM1275_PEAK_IOUT 0xd0 |
29 | #define ADM1275_PEAK_VIN 0xd1 | |
30 | #define ADM1275_PEAK_VOUT 0xd2 | |
83f7649c GR |
31 | #define ADM1275_PMON_CONFIG 0xd4 |
32 | ||
33 | #define ADM1275_VIN_VOUT_SELECT (1 << 6) | |
34 | #define ADM1275_VRANGE (1 << 5) | |
35 | ||
c5e67636 GR |
36 | #define ADM1275_IOUT_WARN2_LIMIT 0xd7 |
37 | #define ADM1275_DEVICE_CONFIG 0xd8 | |
38 | ||
39 | #define ADM1275_IOUT_WARN2_SELECT (1 << 4) | |
40 | ||
5cf231a3 GR |
41 | #define ADM1276_PEAK_PIN 0xda |
42 | ||
c5e67636 GR |
43 | #define ADM1275_MFR_STATUS_IOUT_WARN2 (1 << 0) |
44 | ||
45 | struct adm1275_data { | |
5cf231a3 | 46 | int id; |
c5e67636 GR |
47 | bool have_oc_fault; |
48 | struct pmbus_driver_info info; | |
49 | }; | |
50 | ||
51 | #define to_adm1275_data(x) container_of(x, struct adm1275_data, info) | |
52 | ||
c576e30c GR |
53 | static int adm1275_read_word_data(struct i2c_client *client, int page, int reg) |
54 | { | |
c5e67636 GR |
55 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); |
56 | const struct adm1275_data *data = to_adm1275_data(info); | |
5cf231a3 | 57 | int ret = 0; |
c576e30c GR |
58 | |
59 | if (page) | |
c5e67636 | 60 | return -ENXIO; |
c576e30c GR |
61 | |
62 | switch (reg) { | |
c5e67636 GR |
63 | case PMBUS_IOUT_UC_FAULT_LIMIT: |
64 | if (data->have_oc_fault) { | |
65 | ret = -ENXIO; | |
66 | break; | |
67 | } | |
68 | ret = pmbus_read_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT); | |
69 | break; | |
70 | case PMBUS_IOUT_OC_FAULT_LIMIT: | |
71 | if (!data->have_oc_fault) { | |
72 | ret = -ENXIO; | |
73 | break; | |
74 | } | |
75 | ret = pmbus_read_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT); | |
76 | break; | |
c576e30c GR |
77 | case PMBUS_VIRT_READ_IOUT_MAX: |
78 | ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_IOUT); | |
79 | break; | |
80 | case PMBUS_VIRT_READ_VOUT_MAX: | |
81 | ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_VOUT); | |
82 | break; | |
83 | case PMBUS_VIRT_READ_VIN_MAX: | |
84 | ret = pmbus_read_word_data(client, 0, ADM1275_PEAK_VIN); | |
85 | break; | |
5cf231a3 GR |
86 | case PMBUS_VIRT_READ_PIN_MAX: |
87 | if (data->id != adm1276) { | |
88 | ret = -ENXIO; | |
89 | break; | |
90 | } | |
91 | ret = pmbus_read_word_data(client, 0, ADM1276_PEAK_PIN); | |
92 | break; | |
c576e30c GR |
93 | case PMBUS_VIRT_RESET_IOUT_HISTORY: |
94 | case PMBUS_VIRT_RESET_VOUT_HISTORY: | |
95 | case PMBUS_VIRT_RESET_VIN_HISTORY: | |
5cf231a3 GR |
96 | break; |
97 | case PMBUS_VIRT_RESET_PIN_HISTORY: | |
98 | if (data->id != adm1276) | |
99 | ret = -ENXIO; | |
c576e30c GR |
100 | break; |
101 | default: | |
102 | ret = -ENODATA; | |
103 | break; | |
104 | } | |
105 | return ret; | |
106 | } | |
107 | ||
108 | static int adm1275_write_word_data(struct i2c_client *client, int page, int reg, | |
109 | u16 word) | |
110 | { | |
111 | int ret; | |
112 | ||
113 | if (page) | |
c5e67636 | 114 | return -ENXIO; |
c576e30c GR |
115 | |
116 | switch (reg) { | |
c5e67636 GR |
117 | case PMBUS_IOUT_UC_FAULT_LIMIT: |
118 | case PMBUS_IOUT_OC_FAULT_LIMIT: | |
119 | ret = pmbus_write_word_data(client, 0, ADM1275_IOUT_WARN2_LIMIT, | |
120 | word); | |
121 | break; | |
c576e30c GR |
122 | case PMBUS_VIRT_RESET_IOUT_HISTORY: |
123 | ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_IOUT, 0); | |
124 | break; | |
125 | case PMBUS_VIRT_RESET_VOUT_HISTORY: | |
126 | ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_VOUT, 0); | |
127 | break; | |
128 | case PMBUS_VIRT_RESET_VIN_HISTORY: | |
129 | ret = pmbus_write_word_data(client, 0, ADM1275_PEAK_VIN, 0); | |
130 | break; | |
5cf231a3 GR |
131 | case PMBUS_VIRT_RESET_PIN_HISTORY: |
132 | ret = pmbus_write_word_data(client, 0, ADM1276_PEAK_PIN, 0); | |
133 | break; | |
c576e30c GR |
134 | default: |
135 | ret = -ENODATA; | |
136 | break; | |
137 | } | |
138 | return ret; | |
139 | } | |
140 | ||
c5e67636 GR |
141 | static int adm1275_read_byte_data(struct i2c_client *client, int page, int reg) |
142 | { | |
143 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | |
144 | const struct adm1275_data *data = to_adm1275_data(info); | |
145 | int mfr_status, ret; | |
146 | ||
da8e48ab | 147 | if (page > 0) |
c5e67636 GR |
148 | return -ENXIO; |
149 | ||
150 | switch (reg) { | |
151 | case PMBUS_STATUS_IOUT: | |
152 | ret = pmbus_read_byte_data(client, page, PMBUS_STATUS_IOUT); | |
153 | if (ret < 0) | |
154 | break; | |
155 | mfr_status = pmbus_read_byte_data(client, page, | |
156 | PMBUS_STATUS_MFR_SPECIFIC); | |
157 | if (mfr_status < 0) { | |
158 | ret = mfr_status; | |
159 | break; | |
160 | } | |
161 | if (mfr_status & ADM1275_MFR_STATUS_IOUT_WARN2) { | |
162 | ret |= data->have_oc_fault ? | |
163 | PB_IOUT_OC_FAULT : PB_IOUT_UC_FAULT; | |
164 | } | |
165 | break; | |
166 | default: | |
167 | ret = -ENODATA; | |
168 | break; | |
169 | } | |
170 | return ret; | |
171 | } | |
172 | ||
83f7649c GR |
173 | static int adm1275_probe(struct i2c_client *client, |
174 | const struct i2c_device_id *id) | |
175 | { | |
c5e67636 | 176 | int config, device_config; |
3b33ca41 | 177 | int ret; |
83f7649c | 178 | struct pmbus_driver_info *info; |
c5e67636 | 179 | struct adm1275_data *data; |
83f7649c GR |
180 | |
181 | if (!i2c_check_functionality(client->adapter, | |
182 | I2C_FUNC_SMBUS_READ_BYTE_DATA)) | |
183 | return -ENODEV; | |
184 | ||
c5e67636 GR |
185 | data = kzalloc(sizeof(struct adm1275_data), GFP_KERNEL); |
186 | if (!data) | |
83f7649c GR |
187 | return -ENOMEM; |
188 | ||
189 | config = i2c_smbus_read_byte_data(client, ADM1275_PMON_CONFIG); | |
3b33ca41 GR |
190 | if (config < 0) { |
191 | ret = config; | |
192 | goto err_mem; | |
193 | } | |
83f7649c | 194 | |
c5e67636 GR |
195 | device_config = i2c_smbus_read_byte_data(client, ADM1275_DEVICE_CONFIG); |
196 | if (device_config < 0) { | |
197 | ret = device_config; | |
198 | goto err_mem; | |
199 | } | |
200 | ||
5cf231a3 | 201 | data->id = id->driver_data; |
c5e67636 GR |
202 | info = &data->info; |
203 | ||
83f7649c | 204 | info->pages = 1; |
1061d851 GR |
205 | info->format[PSC_VOLTAGE_IN] = direct; |
206 | info->format[PSC_VOLTAGE_OUT] = direct; | |
207 | info->format[PSC_CURRENT_OUT] = direct; | |
7e97bbba | 208 | info->m[PSC_CURRENT_OUT] = 807; |
83f7649c GR |
209 | info->b[PSC_CURRENT_OUT] = 20475; |
210 | info->R[PSC_CURRENT_OUT] = -1; | |
211 | info->func[0] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; | |
212 | ||
c576e30c | 213 | info->read_word_data = adm1275_read_word_data; |
c5e67636 | 214 | info->read_byte_data = adm1275_read_byte_data; |
c576e30c GR |
215 | info->write_word_data = adm1275_write_word_data; |
216 | ||
83f7649c | 217 | if (config & ADM1275_VRANGE) { |
7e97bbba | 218 | info->m[PSC_VOLTAGE_IN] = 19199; |
83f7649c GR |
219 | info->b[PSC_VOLTAGE_IN] = 0; |
220 | info->R[PSC_VOLTAGE_IN] = -2; | |
7e97bbba | 221 | info->m[PSC_VOLTAGE_OUT] = 19199; |
83f7649c GR |
222 | info->b[PSC_VOLTAGE_OUT] = 0; |
223 | info->R[PSC_VOLTAGE_OUT] = -2; | |
224 | } else { | |
7e97bbba | 225 | info->m[PSC_VOLTAGE_IN] = 6720; |
83f7649c GR |
226 | info->b[PSC_VOLTAGE_IN] = 0; |
227 | info->R[PSC_VOLTAGE_IN] = -1; | |
7e97bbba | 228 | info->m[PSC_VOLTAGE_OUT] = 6720; |
83f7649c GR |
229 | info->b[PSC_VOLTAGE_OUT] = 0; |
230 | info->R[PSC_VOLTAGE_OUT] = -1; | |
231 | } | |
232 | ||
c5e67636 GR |
233 | if (device_config & ADM1275_IOUT_WARN2_SELECT) |
234 | data->have_oc_fault = true; | |
235 | ||
5cf231a3 GR |
236 | switch (id->driver_data) { |
237 | case adm1275: | |
238 | if (config & ADM1275_VIN_VOUT_SELECT) | |
239 | info->func[0] |= | |
240 | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; | |
241 | else | |
242 | info->func[0] |= | |
243 | PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT; | |
244 | break; | |
245 | case adm1276: | |
246 | info->format[PSC_POWER] = direct; | |
247 | info->func[0] |= PMBUS_HAVE_VIN | PMBUS_HAVE_PIN | |
248 | | PMBUS_HAVE_STATUS_INPUT; | |
249 | if (config & ADM1275_VIN_VOUT_SELECT) | |
250 | info->func[0] |= | |
251 | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; | |
252 | if (config & ADM1275_VRANGE) { | |
253 | info->m[PSC_POWER] = 6043; | |
254 | info->b[PSC_POWER] = 0; | |
255 | info->R[PSC_POWER] = -2; | |
256 | } else { | |
257 | info->m[PSC_POWER] = 2115; | |
258 | info->b[PSC_POWER] = 0; | |
259 | info->R[PSC_POWER] = -1; | |
260 | } | |
261 | break; | |
262 | } | |
83f7649c | 263 | |
3b33ca41 GR |
264 | ret = pmbus_do_probe(client, id, info); |
265 | if (ret) | |
266 | goto err_mem; | |
267 | return 0; | |
268 | ||
269 | err_mem: | |
c5e67636 | 270 | kfree(data); |
3b33ca41 | 271 | return ret; |
83f7649c GR |
272 | } |
273 | ||
274 | static int adm1275_remove(struct i2c_client *client) | |
275 | { | |
276 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | |
c5e67636 | 277 | const struct adm1275_data *data = to_adm1275_data(info); |
83f7649c | 278 | |
866cf12a | 279 | pmbus_do_remove(client); |
c5e67636 | 280 | kfree(data); |
866cf12a | 281 | return 0; |
83f7649c GR |
282 | } |
283 | ||
284 | static const struct i2c_device_id adm1275_id[] = { | |
5cf231a3 GR |
285 | { "adm1275", adm1275 }, |
286 | { "adm1276", adm1276 }, | |
83f7649c GR |
287 | { } |
288 | }; | |
289 | MODULE_DEVICE_TABLE(i2c, adm1275_id); | |
290 | ||
291 | static struct i2c_driver adm1275_driver = { | |
292 | .driver = { | |
293 | .name = "adm1275", | |
294 | }, | |
295 | .probe = adm1275_probe, | |
296 | .remove = adm1275_remove, | |
297 | .id_table = adm1275_id, | |
298 | }; | |
299 | ||
300 | static int __init adm1275_init(void) | |
301 | { | |
302 | return i2c_add_driver(&adm1275_driver); | |
303 | } | |
304 | ||
305 | static void __exit adm1275_exit(void) | |
306 | { | |
307 | i2c_del_driver(&adm1275_driver); | |
308 | } | |
309 | ||
310 | MODULE_AUTHOR("Guenter Roeck"); | |
5cf231a3 | 311 | MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1275 and compatibles"); |
83f7649c GR |
312 | MODULE_LICENSE("GPL"); |
313 | module_init(adm1275_init); | |
314 | module_exit(adm1275_exit); |