]>
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 | ||
87102808 GR |
173 | static const struct i2c_device_id adm1275_id[] = { |
174 | { "adm1275", adm1275 }, | |
175 | { "adm1276", adm1276 }, | |
176 | { } | |
177 | }; | |
178 | MODULE_DEVICE_TABLE(i2c, adm1275_id); | |
179 | ||
83f7649c GR |
180 | static int adm1275_probe(struct i2c_client *client, |
181 | const struct i2c_device_id *id) | |
182 | { | |
87102808 | 183 | u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; |
c5e67636 | 184 | int config, device_config; |
3b33ca41 | 185 | int ret; |
83f7649c | 186 | struct pmbus_driver_info *info; |
c5e67636 | 187 | struct adm1275_data *data; |
87102808 | 188 | const struct i2c_device_id *mid; |
83f7649c GR |
189 | |
190 | if (!i2c_check_functionality(client->adapter, | |
87102808 GR |
191 | I2C_FUNC_SMBUS_READ_BYTE_DATA |
192 | | I2C_FUNC_SMBUS_BLOCK_DATA)) | |
83f7649c GR |
193 | return -ENODEV; |
194 | ||
87102808 GR |
195 | ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer); |
196 | if (ret < 0) { | |
197 | dev_err(&client->dev, "Failed to read Manufacturer ID\n"); | |
198 | return ret; | |
199 | } | |
200 | if (ret != 3 || strncmp(block_buffer, "ADI", 3)) { | |
201 | dev_err(&client->dev, "Unsupported Manufacturer ID\n"); | |
202 | return -ENODEV; | |
203 | } | |
83f7649c | 204 | |
87102808 GR |
205 | ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer); |
206 | if (ret < 0) { | |
207 | dev_err(&client->dev, "Failed to read Manufacturer Model\n"); | |
208 | return ret; | |
209 | } | |
210 | for (mid = adm1275_id; mid->name[0]; mid++) { | |
211 | if (!strncasecmp(mid->name, block_buffer, strlen(mid->name))) | |
212 | break; | |
213 | } | |
214 | if (!mid->name[0]) { | |
215 | dev_err(&client->dev, "Unsupported device\n"); | |
216 | return -ENODEV; | |
3b33ca41 | 217 | } |
83f7649c | 218 | |
87102808 GR |
219 | if (id->driver_data != mid->driver_data) |
220 | dev_notice(&client->dev, | |
221 | "Device mismatch: Configured %s, detected %s\n", | |
222 | id->name, mid->name); | |
223 | ||
224 | config = i2c_smbus_read_byte_data(client, ADM1275_PMON_CONFIG); | |
225 | if (config < 0) | |
226 | return config; | |
227 | ||
c5e67636 | 228 | device_config = i2c_smbus_read_byte_data(client, ADM1275_DEVICE_CONFIG); |
87102808 GR |
229 | if (device_config < 0) |
230 | return device_config; | |
231 | ||
232 | data = kzalloc(sizeof(struct adm1275_data), GFP_KERNEL); | |
233 | if (!data) | |
234 | return -ENOMEM; | |
235 | ||
236 | data->id = mid->driver_data; | |
c5e67636 GR |
237 | |
238 | info = &data->info; | |
239 | ||
83f7649c | 240 | info->pages = 1; |
1061d851 GR |
241 | info->format[PSC_VOLTAGE_IN] = direct; |
242 | info->format[PSC_VOLTAGE_OUT] = direct; | |
243 | info->format[PSC_CURRENT_OUT] = direct; | |
7e97bbba | 244 | info->m[PSC_CURRENT_OUT] = 807; |
83f7649c GR |
245 | info->b[PSC_CURRENT_OUT] = 20475; |
246 | info->R[PSC_CURRENT_OUT] = -1; | |
247 | info->func[0] = PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT; | |
248 | ||
c576e30c | 249 | info->read_word_data = adm1275_read_word_data; |
c5e67636 | 250 | info->read_byte_data = adm1275_read_byte_data; |
c576e30c GR |
251 | info->write_word_data = adm1275_write_word_data; |
252 | ||
83f7649c | 253 | if (config & ADM1275_VRANGE) { |
7e97bbba | 254 | info->m[PSC_VOLTAGE_IN] = 19199; |
83f7649c GR |
255 | info->b[PSC_VOLTAGE_IN] = 0; |
256 | info->R[PSC_VOLTAGE_IN] = -2; | |
7e97bbba | 257 | info->m[PSC_VOLTAGE_OUT] = 19199; |
83f7649c GR |
258 | info->b[PSC_VOLTAGE_OUT] = 0; |
259 | info->R[PSC_VOLTAGE_OUT] = -2; | |
260 | } else { | |
7e97bbba | 261 | info->m[PSC_VOLTAGE_IN] = 6720; |
83f7649c GR |
262 | info->b[PSC_VOLTAGE_IN] = 0; |
263 | info->R[PSC_VOLTAGE_IN] = -1; | |
7e97bbba | 264 | info->m[PSC_VOLTAGE_OUT] = 6720; |
83f7649c GR |
265 | info->b[PSC_VOLTAGE_OUT] = 0; |
266 | info->R[PSC_VOLTAGE_OUT] = -1; | |
267 | } | |
268 | ||
c5e67636 GR |
269 | if (device_config & ADM1275_IOUT_WARN2_SELECT) |
270 | data->have_oc_fault = true; | |
271 | ||
87102808 | 272 | switch (data->id) { |
5cf231a3 GR |
273 | case adm1275: |
274 | if (config & ADM1275_VIN_VOUT_SELECT) | |
275 | info->func[0] |= | |
276 | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; | |
277 | else | |
278 | info->func[0] |= | |
279 | PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT; | |
280 | break; | |
281 | case adm1276: | |
282 | info->format[PSC_POWER] = direct; | |
283 | info->func[0] |= PMBUS_HAVE_VIN | PMBUS_HAVE_PIN | |
284 | | PMBUS_HAVE_STATUS_INPUT; | |
285 | if (config & ADM1275_VIN_VOUT_SELECT) | |
286 | info->func[0] |= | |
287 | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT; | |
288 | if (config & ADM1275_VRANGE) { | |
289 | info->m[PSC_POWER] = 6043; | |
290 | info->b[PSC_POWER] = 0; | |
291 | info->R[PSC_POWER] = -2; | |
292 | } else { | |
293 | info->m[PSC_POWER] = 2115; | |
294 | info->b[PSC_POWER] = 0; | |
295 | info->R[PSC_POWER] = -1; | |
296 | } | |
297 | break; | |
298 | } | |
83f7649c | 299 | |
3b33ca41 GR |
300 | ret = pmbus_do_probe(client, id, info); |
301 | if (ret) | |
302 | goto err_mem; | |
303 | return 0; | |
304 | ||
305 | err_mem: | |
c5e67636 | 306 | kfree(data); |
3b33ca41 | 307 | return ret; |
83f7649c GR |
308 | } |
309 | ||
310 | static int adm1275_remove(struct i2c_client *client) | |
311 | { | |
312 | const struct pmbus_driver_info *info = pmbus_get_driver_info(client); | |
c5e67636 | 313 | const struct adm1275_data *data = to_adm1275_data(info); |
83f7649c | 314 | |
866cf12a | 315 | pmbus_do_remove(client); |
c5e67636 | 316 | kfree(data); |
866cf12a | 317 | return 0; |
83f7649c GR |
318 | } |
319 | ||
83f7649c GR |
320 | static struct i2c_driver adm1275_driver = { |
321 | .driver = { | |
322 | .name = "adm1275", | |
323 | }, | |
324 | .probe = adm1275_probe, | |
325 | .remove = adm1275_remove, | |
326 | .id_table = adm1275_id, | |
327 | }; | |
328 | ||
329 | static int __init adm1275_init(void) | |
330 | { | |
331 | return i2c_add_driver(&adm1275_driver); | |
332 | } | |
333 | ||
334 | static void __exit adm1275_exit(void) | |
335 | { | |
336 | i2c_del_driver(&adm1275_driver); | |
337 | } | |
338 | ||
339 | MODULE_AUTHOR("Guenter Roeck"); | |
5cf231a3 | 340 | MODULE_DESCRIPTION("PMBus driver for Analog Devices ADM1275 and compatibles"); |
83f7649c GR |
341 | MODULE_LICENSE("GPL"); |
342 | module_init(adm1275_init); | |
343 | module_exit(adm1275_exit); |