]>
Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
f69316d6 EJ |
2 | /* |
3 | * Copyright 2017 IBM Corp. | |
f69316d6 EJ |
4 | */ |
5 | ||
da806a17 | 6 | #include <linux/bitfield.h> |
f69316d6 | 7 | #include <linux/bitops.h> |
d6bb645a | 8 | #include <linux/debugfs.h> |
f69316d6 | 9 | #include <linux/device.h> |
d6bb645a | 10 | #include <linux/fs.h> |
f69316d6 | 11 | #include <linux/i2c.h> |
d6bb645a | 12 | #include <linux/jiffies.h> |
ef9e1cdf | 13 | #include <linux/leds.h> |
f69316d6 | 14 | #include <linux/module.h> |
d6bb645a | 15 | #include <linux/mutex.h> |
2f8a855e | 16 | #include <linux/of_device.h> |
4471879a | 17 | #include <linux/pmbus.h> |
f69316d6 EJ |
18 | |
19 | #include "pmbus.h" | |
20 | ||
d6bb645a EJ |
21 | #define CFFPS_FRU_CMD 0x9A |
22 | #define CFFPS_PN_CMD 0x9B | |
abe508b6 | 23 | #define CFFPS_HEADER_CMD 0x9C |
d6bb645a | 24 | #define CFFPS_SN_CMD 0x9E |
abe508b6 | 25 | #define CFFPS_MAX_POWER_OUT_CMD 0xA7 |
d6bb645a | 26 | #define CFFPS_CCIN_CMD 0xBD |
2f8a855e EJ |
27 | #define CFFPS_FW_CMD 0xFA |
28 | #define CFFPS1_FW_NUM_BYTES 4 | |
29 | #define CFFPS2_FW_NUM_WORDS 3 | |
ef9e1cdf | 30 | #define CFFPS_SYS_CONFIG_CMD 0xDA |
1952d79a | 31 | #define CFFPS_12VCS_VOUT_CMD 0xDE |
d6bb645a EJ |
32 | |
33 | #define CFFPS_INPUT_HISTORY_CMD 0xD6 | |
34 | #define CFFPS_INPUT_HISTORY_SIZE 100 | |
35 | ||
b1fbe673 EJ |
36 | #define CFFPS_CCIN_REVISION GENMASK(7, 0) |
37 | #define CFFPS_CCIN_REVISION_LEGACY 0xde | |
da806a17 EJ |
38 | #define CFFPS_CCIN_VERSION GENMASK(15, 8) |
39 | #define CFFPS_CCIN_VERSION_1 0x2b | |
40 | #define CFFPS_CCIN_VERSION_2 0x2e | |
b1fbe673 | 41 | #define CFFPS_CCIN_VERSION_3 0x51 |
da806a17 | 42 | |
f69316d6 EJ |
43 | /* STATUS_MFR_SPECIFIC bits */ |
44 | #define CFFPS_MFR_FAN_FAULT BIT(0) | |
45 | #define CFFPS_MFR_THERMAL_FAULT BIT(1) | |
46 | #define CFFPS_MFR_OV_FAULT BIT(2) | |
47 | #define CFFPS_MFR_UV_FAULT BIT(3) | |
48 | #define CFFPS_MFR_PS_KILL BIT(4) | |
49 | #define CFFPS_MFR_OC_FAULT BIT(5) | |
50 | #define CFFPS_MFR_VAUX_FAULT BIT(6) | |
51 | #define CFFPS_MFR_CURRENT_SHARE_WARNING BIT(7) | |
52 | ||
76b72736 BW |
53 | #define CFFPS_LED_BLINK (BIT(0) | BIT(6)) |
54 | #define CFFPS_LED_ON (BIT(1) | BIT(6)) | |
55 | #define CFFPS_LED_OFF (BIT(2) | BIT(6)) | |
ef9e1cdf | 56 | #define CFFPS_BLINK_RATE_MS 250 |
57 | ||
d6bb645a EJ |
58 | enum { |
59 | CFFPS_DEBUGFS_INPUT_HISTORY = 0, | |
60 | CFFPS_DEBUGFS_FRU, | |
61 | CFFPS_DEBUGFS_PN, | |
abe508b6 | 62 | CFFPS_DEBUGFS_HEADER, |
d6bb645a | 63 | CFFPS_DEBUGFS_SN, |
abe508b6 | 64 | CFFPS_DEBUGFS_MAX_POWER_OUT, |
d6bb645a EJ |
65 | CFFPS_DEBUGFS_CCIN, |
66 | CFFPS_DEBUGFS_FW, | |
abe508b6 | 67 | CFFPS_DEBUGFS_ON_OFF_CONFIG, |
d6bb645a EJ |
68 | CFFPS_DEBUGFS_NUM_ENTRIES |
69 | }; | |
70 | ||
da806a17 | 71 | enum versions { cffps1, cffps2, cffps_unknown }; |
2f8a855e | 72 | |
d6bb645a EJ |
73 | struct ibm_cffps_input_history { |
74 | struct mutex update_lock; | |
75 | unsigned long last_update; | |
76 | ||
77 | u8 byte_count; | |
78 | u8 data[CFFPS_INPUT_HISTORY_SIZE]; | |
79 | }; | |
80 | ||
81 | struct ibm_cffps { | |
2f8a855e | 82 | enum versions version; |
d6bb645a EJ |
83 | struct i2c_client *client; |
84 | ||
85 | struct ibm_cffps_input_history input_history; | |
86 | ||
87 | int debugfs_entries[CFFPS_DEBUGFS_NUM_ENTRIES]; | |
ef9e1cdf | 88 | |
89 | char led_name[32]; | |
90 | u8 led_state; | |
91 | struct led_classdev led; | |
d6bb645a EJ |
92 | }; |
93 | ||
dd431939 SK |
94 | static const struct i2c_device_id ibm_cffps_id[]; |
95 | ||
d6bb645a EJ |
96 | #define to_psu(x, y) container_of((x), struct ibm_cffps, debugfs_entries[(y)]) |
97 | ||
98 | static ssize_t ibm_cffps_read_input_history(struct ibm_cffps *psu, | |
99 | char __user *buf, size_t count, | |
100 | loff_t *ppos) | |
101 | { | |
102 | int rc; | |
103 | u8 msgbuf0[1] = { CFFPS_INPUT_HISTORY_CMD }; | |
104 | u8 msgbuf1[CFFPS_INPUT_HISTORY_SIZE + 1] = { 0 }; | |
105 | struct i2c_msg msg[2] = { | |
106 | { | |
107 | .addr = psu->client->addr, | |
108 | .flags = psu->client->flags, | |
109 | .len = 1, | |
110 | .buf = msgbuf0, | |
111 | }, { | |
112 | .addr = psu->client->addr, | |
113 | .flags = psu->client->flags | I2C_M_RD, | |
114 | .len = CFFPS_INPUT_HISTORY_SIZE + 1, | |
115 | .buf = msgbuf1, | |
116 | }, | |
117 | }; | |
118 | ||
119 | if (!*ppos) { | |
120 | mutex_lock(&psu->input_history.update_lock); | |
121 | if (time_after(jiffies, psu->input_history.last_update + HZ)) { | |
122 | /* | |
123 | * Use a raw i2c transfer, since we need more bytes | |
124 | * than Linux I2C supports through smbus xfr (only 32). | |
125 | */ | |
126 | rc = i2c_transfer(psu->client->adapter, msg, 2); | |
127 | if (rc < 0) { | |
128 | mutex_unlock(&psu->input_history.update_lock); | |
129 | return rc; | |
130 | } | |
131 | ||
132 | psu->input_history.byte_count = msgbuf1[0]; | |
133 | memcpy(psu->input_history.data, &msgbuf1[1], | |
134 | CFFPS_INPUT_HISTORY_SIZE); | |
135 | psu->input_history.last_update = jiffies; | |
136 | } | |
137 | ||
138 | mutex_unlock(&psu->input_history.update_lock); | |
139 | } | |
140 | ||
141 | return simple_read_from_buffer(buf, count, ppos, | |
142 | psu->input_history.data, | |
143 | psu->input_history.byte_count); | |
144 | } | |
145 | ||
abe508b6 EJ |
146 | static ssize_t ibm_cffps_debugfs_read(struct file *file, char __user *buf, |
147 | size_t count, loff_t *ppos) | |
d6bb645a EJ |
148 | { |
149 | u8 cmd; | |
150 | int i, rc; | |
151 | int *idxp = file->private_data; | |
152 | int idx = *idxp; | |
153 | struct ibm_cffps *psu = to_psu(idxp, idx); | |
abe508b6 | 154 | char data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 }; |
d6bb645a | 155 | |
43f33b6e | 156 | pmbus_set_page(psu->client, 0, 0xff); |
2f8a855e | 157 | |
d6bb645a EJ |
158 | switch (idx) { |
159 | case CFFPS_DEBUGFS_INPUT_HISTORY: | |
160 | return ibm_cffps_read_input_history(psu, buf, count, ppos); | |
161 | case CFFPS_DEBUGFS_FRU: | |
162 | cmd = CFFPS_FRU_CMD; | |
163 | break; | |
164 | case CFFPS_DEBUGFS_PN: | |
165 | cmd = CFFPS_PN_CMD; | |
166 | break; | |
abe508b6 EJ |
167 | case CFFPS_DEBUGFS_HEADER: |
168 | cmd = CFFPS_HEADER_CMD; | |
169 | break; | |
d6bb645a EJ |
170 | case CFFPS_DEBUGFS_SN: |
171 | cmd = CFFPS_SN_CMD; | |
172 | break; | |
abe508b6 | 173 | case CFFPS_DEBUGFS_MAX_POWER_OUT: |
f067d558 BW |
174 | if (psu->version == cffps1) { |
175 | rc = i2c_smbus_read_word_swapped(psu->client, | |
176 | CFFPS_MAX_POWER_OUT_CMD); | |
177 | } else { | |
178 | rc = i2c_smbus_read_word_data(psu->client, | |
179 | CFFPS_MAX_POWER_OUT_CMD); | |
180 | } | |
181 | ||
abe508b6 EJ |
182 | if (rc < 0) |
183 | return rc; | |
184 | ||
185 | rc = snprintf(data, I2C_SMBUS_BLOCK_MAX, "%d", rc); | |
186 | goto done; | |
d6bb645a EJ |
187 | case CFFPS_DEBUGFS_CCIN: |
188 | rc = i2c_smbus_read_word_swapped(psu->client, CFFPS_CCIN_CMD); | |
189 | if (rc < 0) | |
190 | return rc; | |
191 | ||
192 | rc = snprintf(data, 5, "%04X", rc); | |
193 | goto done; | |
194 | case CFFPS_DEBUGFS_FW: | |
2f8a855e EJ |
195 | switch (psu->version) { |
196 | case cffps1: | |
197 | for (i = 0; i < CFFPS1_FW_NUM_BYTES; ++i) { | |
198 | rc = i2c_smbus_read_byte_data(psu->client, | |
199 | CFFPS_FW_CMD + | |
200 | i); | |
201 | if (rc < 0) | |
202 | return rc; | |
203 | ||
204 | snprintf(&data[i * 2], 3, "%02X", rc); | |
205 | } | |
d6bb645a | 206 | |
2f8a855e EJ |
207 | rc = i * 2; |
208 | break; | |
209 | case cffps2: | |
210 | for (i = 0; i < CFFPS2_FW_NUM_WORDS; ++i) { | |
211 | rc = i2c_smbus_read_word_data(psu->client, | |
212 | CFFPS_FW_CMD + | |
213 | i); | |
214 | if (rc < 0) | |
215 | return rc; | |
216 | ||
217 | snprintf(&data[i * 4], 5, "%04X", rc); | |
218 | } | |
d6bb645a | 219 | |
2f8a855e EJ |
220 | rc = i * 4; |
221 | break; | |
222 | default: | |
223 | return -EOPNOTSUPP; | |
224 | } | |
d6bb645a | 225 | goto done; |
abe508b6 EJ |
226 | case CFFPS_DEBUGFS_ON_OFF_CONFIG: |
227 | rc = i2c_smbus_read_byte_data(psu->client, | |
228 | PMBUS_ON_OFF_CONFIG); | |
229 | if (rc < 0) | |
230 | return rc; | |
231 | ||
232 | rc = snprintf(data, 3, "%02x", rc); | |
233 | goto done; | |
d6bb645a EJ |
234 | default: |
235 | return -EINVAL; | |
236 | } | |
237 | ||
238 | rc = i2c_smbus_read_block_data(psu->client, cmd, data); | |
239 | if (rc < 0) | |
240 | return rc; | |
241 | ||
242 | done: | |
243 | data[rc] = '\n'; | |
244 | rc += 2; | |
245 | ||
246 | return simple_read_from_buffer(buf, count, ppos, data, rc); | |
247 | } | |
248 | ||
abe508b6 EJ |
249 | static ssize_t ibm_cffps_debugfs_write(struct file *file, |
250 | const char __user *buf, size_t count, | |
251 | loff_t *ppos) | |
252 | { | |
253 | u8 data; | |
254 | ssize_t rc; | |
255 | int *idxp = file->private_data; | |
256 | int idx = *idxp; | |
257 | struct ibm_cffps *psu = to_psu(idxp, idx); | |
258 | ||
259 | switch (idx) { | |
260 | case CFFPS_DEBUGFS_ON_OFF_CONFIG: | |
43f33b6e | 261 | pmbus_set_page(psu->client, 0, 0xff); |
abe508b6 EJ |
262 | |
263 | rc = simple_write_to_buffer(&data, 1, ppos, buf, count); | |
d9c8ae69 | 264 | if (rc <= 0) |
abe508b6 EJ |
265 | return rc; |
266 | ||
267 | rc = i2c_smbus_write_byte_data(psu->client, | |
268 | PMBUS_ON_OFF_CONFIG, data); | |
269 | if (rc) | |
270 | return rc; | |
271 | ||
272 | rc = 1; | |
273 | break; | |
274 | default: | |
275 | return -EINVAL; | |
276 | } | |
277 | ||
278 | return rc; | |
279 | } | |
280 | ||
d6bb645a EJ |
281 | static const struct file_operations ibm_cffps_fops = { |
282 | .llseek = noop_llseek, | |
abe508b6 EJ |
283 | .read = ibm_cffps_debugfs_read, |
284 | .write = ibm_cffps_debugfs_write, | |
d6bb645a EJ |
285 | .open = simple_open, |
286 | }; | |
287 | ||
f69316d6 EJ |
288 | static int ibm_cffps_read_byte_data(struct i2c_client *client, int page, |
289 | int reg) | |
290 | { | |
291 | int rc, mfr; | |
292 | ||
293 | switch (reg) { | |
294 | case PMBUS_STATUS_VOUT: | |
295 | case PMBUS_STATUS_IOUT: | |
296 | case PMBUS_STATUS_TEMPERATURE: | |
297 | case PMBUS_STATUS_FAN_12: | |
298 | rc = pmbus_read_byte_data(client, page, reg); | |
299 | if (rc < 0) | |
300 | return rc; | |
301 | ||
302 | mfr = pmbus_read_byte_data(client, page, | |
303 | PMBUS_STATUS_MFR_SPECIFIC); | |
304 | if (mfr < 0) | |
305 | /* | |
306 | * Return the status register instead of an error, | |
307 | * since we successfully read status. | |
308 | */ | |
309 | return rc; | |
310 | ||
311 | /* Add MFR_SPECIFIC bits to the standard pmbus status regs. */ | |
312 | if (reg == PMBUS_STATUS_FAN_12) { | |
313 | if (mfr & CFFPS_MFR_FAN_FAULT) | |
314 | rc |= PB_FAN_FAN1_FAULT; | |
315 | } else if (reg == PMBUS_STATUS_TEMPERATURE) { | |
316 | if (mfr & CFFPS_MFR_THERMAL_FAULT) | |
317 | rc |= PB_TEMP_OT_FAULT; | |
318 | } else if (reg == PMBUS_STATUS_VOUT) { | |
319 | if (mfr & (CFFPS_MFR_OV_FAULT | CFFPS_MFR_VAUX_FAULT)) | |
320 | rc |= PB_VOLTAGE_OV_FAULT; | |
321 | if (mfr & CFFPS_MFR_UV_FAULT) | |
322 | rc |= PB_VOLTAGE_UV_FAULT; | |
323 | } else if (reg == PMBUS_STATUS_IOUT) { | |
324 | if (mfr & CFFPS_MFR_OC_FAULT) | |
325 | rc |= PB_IOUT_OC_FAULT; | |
326 | if (mfr & CFFPS_MFR_CURRENT_SHARE_WARNING) | |
327 | rc |= PB_CURRENT_SHARE_FAULT; | |
328 | } | |
329 | break; | |
330 | default: | |
331 | rc = -ENODATA; | |
332 | break; | |
333 | } | |
334 | ||
335 | return rc; | |
336 | } | |
337 | ||
338 | static int ibm_cffps_read_word_data(struct i2c_client *client, int page, | |
43f33b6e | 339 | int phase, int reg) |
f69316d6 EJ |
340 | { |
341 | int rc, mfr; | |
342 | ||
343 | switch (reg) { | |
344 | case PMBUS_STATUS_WORD: | |
43f33b6e | 345 | rc = pmbus_read_word_data(client, page, phase, reg); |
f69316d6 EJ |
346 | if (rc < 0) |
347 | return rc; | |
348 | ||
349 | mfr = pmbus_read_byte_data(client, page, | |
350 | PMBUS_STATUS_MFR_SPECIFIC); | |
351 | if (mfr < 0) | |
352 | /* | |
353 | * Return the status register instead of an error, | |
354 | * since we successfully read status. | |
355 | */ | |
356 | return rc; | |
357 | ||
358 | if (mfr & CFFPS_MFR_PS_KILL) | |
359 | rc |= PB_STATUS_OFF; | |
360 | break; | |
1952d79a | 361 | case PMBUS_VIRT_READ_VMON: |
43f33b6e GR |
362 | rc = pmbus_read_word_data(client, page, phase, |
363 | CFFPS_12VCS_VOUT_CMD); | |
1952d79a | 364 | break; |
f69316d6 EJ |
365 | default: |
366 | rc = -ENODATA; | |
367 | break; | |
368 | } | |
369 | ||
370 | return rc; | |
371 | } | |
372 | ||
9861ff95 EJ |
373 | static int ibm_cffps_led_brightness_set(struct led_classdev *led_cdev, |
374 | enum led_brightness brightness) | |
ef9e1cdf | 375 | { |
376 | int rc; | |
92b39ad4 | 377 | u8 next_led_state; |
ef9e1cdf | 378 | struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); |
379 | ||
380 | if (brightness == LED_OFF) { | |
92b39ad4 | 381 | next_led_state = CFFPS_LED_OFF; |
ef9e1cdf | 382 | } else { |
383 | brightness = LED_FULL; | |
92b39ad4 | 384 | |
ef9e1cdf | 385 | if (psu->led_state != CFFPS_LED_BLINK) |
92b39ad4 EJ |
386 | next_led_state = CFFPS_LED_ON; |
387 | else | |
388 | next_led_state = CFFPS_LED_BLINK; | |
ef9e1cdf | 389 | } |
390 | ||
92b39ad4 EJ |
391 | dev_dbg(&psu->client->dev, "LED brightness set: %d. Command: %d.\n", |
392 | brightness, next_led_state); | |
393 | ||
43f33b6e | 394 | pmbus_set_page(psu->client, 0, 0xff); |
2f8a855e | 395 | |
ef9e1cdf | 396 | rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, |
92b39ad4 | 397 | next_led_state); |
ef9e1cdf | 398 | if (rc < 0) |
9861ff95 | 399 | return rc; |
ef9e1cdf | 400 | |
92b39ad4 | 401 | psu->led_state = next_led_state; |
ef9e1cdf | 402 | led_cdev->brightness = brightness; |
9861ff95 EJ |
403 | |
404 | return 0; | |
ef9e1cdf | 405 | } |
406 | ||
407 | static int ibm_cffps_led_blink_set(struct led_classdev *led_cdev, | |
408 | unsigned long *delay_on, | |
409 | unsigned long *delay_off) | |
410 | { | |
411 | int rc; | |
412 | struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); | |
413 | ||
92b39ad4 | 414 | dev_dbg(&psu->client->dev, "LED blink set.\n"); |
ef9e1cdf | 415 | |
43f33b6e | 416 | pmbus_set_page(psu->client, 0, 0xff); |
2f8a855e | 417 | |
ef9e1cdf | 418 | rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, |
419 | CFFPS_LED_BLINK); | |
420 | if (rc < 0) | |
421 | return rc; | |
422 | ||
92b39ad4 EJ |
423 | psu->led_state = CFFPS_LED_BLINK; |
424 | led_cdev->brightness = LED_FULL; | |
ef9e1cdf | 425 | *delay_on = CFFPS_BLINK_RATE_MS; |
426 | *delay_off = CFFPS_BLINK_RATE_MS; | |
427 | ||
428 | return 0; | |
429 | } | |
430 | ||
431 | static void ibm_cffps_create_led_class(struct ibm_cffps *psu) | |
432 | { | |
433 | int rc; | |
434 | struct i2c_client *client = psu->client; | |
435 | struct device *dev = &client->dev; | |
436 | ||
437 | snprintf(psu->led_name, sizeof(psu->led_name), "%s-%02x", client->name, | |
438 | client->addr); | |
439 | psu->led.name = psu->led_name; | |
440 | psu->led.max_brightness = LED_FULL; | |
9861ff95 | 441 | psu->led.brightness_set_blocking = ibm_cffps_led_brightness_set; |
ef9e1cdf | 442 | psu->led.blink_set = ibm_cffps_led_blink_set; |
443 | ||
444 | rc = devm_led_classdev_register(dev, &psu->led); | |
445 | if (rc) | |
446 | dev_warn(dev, "failed to register led class: %d\n", rc); | |
74a71a83 EJ |
447 | else |
448 | i2c_smbus_write_byte_data(client, CFFPS_SYS_CONFIG_CMD, | |
449 | CFFPS_LED_OFF); | |
ef9e1cdf | 450 | } |
451 | ||
2f8a855e EJ |
452 | static struct pmbus_driver_info ibm_cffps_info[] = { |
453 | [cffps1] = { | |
454 | .pages = 1, | |
455 | .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | | |
456 | PMBUS_HAVE_PIN | PMBUS_HAVE_FAN12 | PMBUS_HAVE_TEMP | | |
457 | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 | | |
458 | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | | |
459 | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP | | |
460 | PMBUS_HAVE_STATUS_FAN12, | |
461 | .read_byte_data = ibm_cffps_read_byte_data, | |
462 | .read_word_data = ibm_cffps_read_word_data, | |
463 | }, | |
464 | [cffps2] = { | |
465 | .pages = 2, | |
466 | .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | | |
467 | PMBUS_HAVE_PIN | PMBUS_HAVE_FAN12 | PMBUS_HAVE_TEMP | | |
468 | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 | | |
469 | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | | |
470 | PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP | | |
1952d79a | 471 | PMBUS_HAVE_STATUS_FAN12 | PMBUS_HAVE_VMON, |
2f8a855e EJ |
472 | .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | |
473 | PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 | | |
474 | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT, | |
475 | .read_byte_data = ibm_cffps_read_byte_data, | |
476 | .read_word_data = ibm_cffps_read_word_data, | |
477 | }, | |
f69316d6 EJ |
478 | }; |
479 | ||
4471879a | 480 | static struct pmbus_platform_data ibm_cffps_pdata = { |
f7a65218 | 481 | .flags = PMBUS_SKIP_STATUS_CHECK | PMBUS_NO_CAPABILITY, |
4471879a EJ |
482 | }; |
483 | ||
dd431939 | 484 | static int ibm_cffps_probe(struct i2c_client *client) |
f69316d6 | 485 | { |
d6bb645a | 486 | int i, rc; |
da806a17 | 487 | enum versions vs = cffps_unknown; |
d6bb645a EJ |
488 | struct dentry *debugfs; |
489 | struct dentry *ibm_cffps_dir; | |
490 | struct ibm_cffps *psu; | |
2f8a855e | 491 | const void *md = of_device_get_match_data(&client->dev); |
dd431939 | 492 | const struct i2c_device_id *id; |
2f8a855e | 493 | |
dd431939 | 494 | if (md) { |
2f8a855e | 495 | vs = (enum versions)md; |
dd431939 SK |
496 | } else { |
497 | id = i2c_match_id(ibm_cffps_id, client); | |
498 | if (id) | |
499 | vs = (enum versions)id->driver_data; | |
500 | } | |
da806a17 EJ |
501 | |
502 | if (vs == cffps_unknown) { | |
b1fbe673 | 503 | u16 ccin_revision = 0; |
da806a17 EJ |
504 | u16 ccin_version = CFFPS_CCIN_VERSION_1; |
505 | int ccin = i2c_smbus_read_word_swapped(client, CFFPS_CCIN_CMD); | |
506 | ||
b1fbe673 EJ |
507 | if (ccin > 0) { |
508 | ccin_revision = FIELD_GET(CFFPS_CCIN_REVISION, ccin); | |
da806a17 | 509 | ccin_version = FIELD_GET(CFFPS_CCIN_VERSION, ccin); |
b1fbe673 | 510 | } |
da806a17 EJ |
511 | |
512 | switch (ccin_version) { | |
513 | default: | |
514 | case CFFPS_CCIN_VERSION_1: | |
515 | vs = cffps1; | |
516 | break; | |
517 | case CFFPS_CCIN_VERSION_2: | |
518 | vs = cffps2; | |
519 | break; | |
b1fbe673 EJ |
520 | case CFFPS_CCIN_VERSION_3: |
521 | if (ccin_revision == CFFPS_CCIN_REVISION_LEGACY) | |
522 | vs = cffps1; | |
523 | else | |
524 | vs = cffps2; | |
525 | break; | |
da806a17 EJ |
526 | } |
527 | ||
528 | /* Set the client name to include the version number. */ | |
529 | snprintf(client->name, I2C_NAME_SIZE, "cffps%d", vs + 1); | |
530 | } | |
d6bb645a | 531 | |
4471879a | 532 | client->dev.platform_data = &ibm_cffps_pdata; |
dd431939 | 533 | rc = pmbus_do_probe(client, &ibm_cffps_info[vs]); |
d6bb645a EJ |
534 | if (rc) |
535 | return rc; | |
536 | ||
ef9e1cdf | 537 | /* |
538 | * Don't fail the probe if there isn't enough memory for leds and | |
539 | * debugfs. | |
540 | */ | |
541 | psu = devm_kzalloc(&client->dev, sizeof(*psu), GFP_KERNEL); | |
542 | if (!psu) | |
543 | return 0; | |
544 | ||
2f8a855e | 545 | psu->version = vs; |
ef9e1cdf | 546 | psu->client = client; |
547 | mutex_init(&psu->input_history.update_lock); | |
548 | psu->input_history.last_update = jiffies - HZ; | |
549 | ||
550 | ibm_cffps_create_led_class(psu); | |
551 | ||
d6bb645a EJ |
552 | /* Don't fail the probe if we can't create debugfs */ |
553 | debugfs = pmbus_get_debugfs_dir(client); | |
554 | if (!debugfs) | |
555 | return 0; | |
556 | ||
557 | ibm_cffps_dir = debugfs_create_dir(client->name, debugfs); | |
558 | if (!ibm_cffps_dir) | |
559 | return 0; | |
560 | ||
d6bb645a EJ |
561 | for (i = 0; i < CFFPS_DEBUGFS_NUM_ENTRIES; ++i) |
562 | psu->debugfs_entries[i] = i; | |
563 | ||
564 | debugfs_create_file("input_history", 0444, ibm_cffps_dir, | |
565 | &psu->debugfs_entries[CFFPS_DEBUGFS_INPUT_HISTORY], | |
566 | &ibm_cffps_fops); | |
567 | debugfs_create_file("fru", 0444, ibm_cffps_dir, | |
568 | &psu->debugfs_entries[CFFPS_DEBUGFS_FRU], | |
569 | &ibm_cffps_fops); | |
570 | debugfs_create_file("part_number", 0444, ibm_cffps_dir, | |
571 | &psu->debugfs_entries[CFFPS_DEBUGFS_PN], | |
572 | &ibm_cffps_fops); | |
abe508b6 EJ |
573 | debugfs_create_file("header", 0444, ibm_cffps_dir, |
574 | &psu->debugfs_entries[CFFPS_DEBUGFS_HEADER], | |
575 | &ibm_cffps_fops); | |
d6bb645a EJ |
576 | debugfs_create_file("serial_number", 0444, ibm_cffps_dir, |
577 | &psu->debugfs_entries[CFFPS_DEBUGFS_SN], | |
578 | &ibm_cffps_fops); | |
abe508b6 EJ |
579 | debugfs_create_file("max_power_out", 0444, ibm_cffps_dir, |
580 | &psu->debugfs_entries[CFFPS_DEBUGFS_MAX_POWER_OUT], | |
581 | &ibm_cffps_fops); | |
d6bb645a EJ |
582 | debugfs_create_file("ccin", 0444, ibm_cffps_dir, |
583 | &psu->debugfs_entries[CFFPS_DEBUGFS_CCIN], | |
584 | &ibm_cffps_fops); | |
585 | debugfs_create_file("fw_version", 0444, ibm_cffps_dir, | |
586 | &psu->debugfs_entries[CFFPS_DEBUGFS_FW], | |
587 | &ibm_cffps_fops); | |
abe508b6 EJ |
588 | debugfs_create_file("on_off_config", 0644, ibm_cffps_dir, |
589 | &psu->debugfs_entries[CFFPS_DEBUGFS_ON_OFF_CONFIG], | |
590 | &ibm_cffps_fops); | |
d6bb645a EJ |
591 | |
592 | return 0; | |
f69316d6 EJ |
593 | } |
594 | ||
595 | static const struct i2c_device_id ibm_cffps_id[] = { | |
2f8a855e EJ |
596 | { "ibm_cffps1", cffps1 }, |
597 | { "ibm_cffps2", cffps2 }, | |
da806a17 | 598 | { "ibm_cffps", cffps_unknown }, |
f69316d6 EJ |
599 | {} |
600 | }; | |
601 | MODULE_DEVICE_TABLE(i2c, ibm_cffps_id); | |
602 | ||
603 | static const struct of_device_id ibm_cffps_of_match[] = { | |
2f8a855e EJ |
604 | { |
605 | .compatible = "ibm,cffps1", | |
606 | .data = (void *)cffps1 | |
607 | }, | |
608 | { | |
609 | .compatible = "ibm,cffps2", | |
610 | .data = (void *)cffps2 | |
611 | }, | |
da806a17 EJ |
612 | { |
613 | .compatible = "ibm,cffps", | |
614 | .data = (void *)cffps_unknown | |
615 | }, | |
f69316d6 EJ |
616 | {} |
617 | }; | |
618 | MODULE_DEVICE_TABLE(of, ibm_cffps_of_match); | |
619 | ||
620 | static struct i2c_driver ibm_cffps_driver = { | |
621 | .driver = { | |
622 | .name = "ibm-cffps", | |
623 | .of_match_table = ibm_cffps_of_match, | |
624 | }, | |
dd431939 | 625 | .probe_new = ibm_cffps_probe, |
f69316d6 EJ |
626 | .id_table = ibm_cffps_id, |
627 | }; | |
628 | ||
629 | module_i2c_driver(ibm_cffps_driver); | |
630 | ||
631 | MODULE_AUTHOR("Eddie James"); | |
632 | MODULE_DESCRIPTION("PMBus driver for IBM Common Form Factor power supplies"); | |
633 | MODULE_LICENSE("GPL"); | |
b94ca77e | 634 | MODULE_IMPORT_NS(PMBUS); |