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