18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright 2017 IBM Corp. 48c2ecf20Sopenharmony_ci */ 58c2ecf20Sopenharmony_ci 68c2ecf20Sopenharmony_ci#include <linux/bitfield.h> 78c2ecf20Sopenharmony_ci#include <linux/bitops.h> 88c2ecf20Sopenharmony_ci#include <linux/debugfs.h> 98c2ecf20Sopenharmony_ci#include <linux/device.h> 108c2ecf20Sopenharmony_ci#include <linux/fs.h> 118c2ecf20Sopenharmony_ci#include <linux/i2c.h> 128c2ecf20Sopenharmony_ci#include <linux/jiffies.h> 138c2ecf20Sopenharmony_ci#include <linux/leds.h> 148c2ecf20Sopenharmony_ci#include <linux/module.h> 158c2ecf20Sopenharmony_ci#include <linux/mutex.h> 168c2ecf20Sopenharmony_ci#include <linux/of_device.h> 178c2ecf20Sopenharmony_ci#include <linux/pmbus.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#include "pmbus.h" 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_ci#define CFFPS_FRU_CMD 0x9A 228c2ecf20Sopenharmony_ci#define CFFPS_PN_CMD 0x9B 238c2ecf20Sopenharmony_ci#define CFFPS_HEADER_CMD 0x9C 248c2ecf20Sopenharmony_ci#define CFFPS_SN_CMD 0x9E 258c2ecf20Sopenharmony_ci#define CFFPS_MAX_POWER_OUT_CMD 0xA7 268c2ecf20Sopenharmony_ci#define CFFPS_CCIN_CMD 0xBD 278c2ecf20Sopenharmony_ci#define CFFPS_FW_CMD 0xFA 288c2ecf20Sopenharmony_ci#define CFFPS1_FW_NUM_BYTES 4 298c2ecf20Sopenharmony_ci#define CFFPS2_FW_NUM_WORDS 3 308c2ecf20Sopenharmony_ci#define CFFPS_SYS_CONFIG_CMD 0xDA 318c2ecf20Sopenharmony_ci#define CFFPS_12VCS_VOUT_CMD 0xDE 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci#define CFFPS_INPUT_HISTORY_CMD 0xD6 348c2ecf20Sopenharmony_ci#define CFFPS_INPUT_HISTORY_SIZE 100 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci#define CFFPS_CCIN_REVISION GENMASK(7, 0) 378c2ecf20Sopenharmony_ci#define CFFPS_CCIN_REVISION_LEGACY 0xde 388c2ecf20Sopenharmony_ci#define CFFPS_CCIN_VERSION GENMASK(15, 8) 398c2ecf20Sopenharmony_ci#define CFFPS_CCIN_VERSION_1 0x2b 408c2ecf20Sopenharmony_ci#define CFFPS_CCIN_VERSION_2 0x2e 418c2ecf20Sopenharmony_ci#define CFFPS_CCIN_VERSION_3 0x51 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci/* STATUS_MFR_SPECIFIC bits */ 448c2ecf20Sopenharmony_ci#define CFFPS_MFR_FAN_FAULT BIT(0) 458c2ecf20Sopenharmony_ci#define CFFPS_MFR_THERMAL_FAULT BIT(1) 468c2ecf20Sopenharmony_ci#define CFFPS_MFR_OV_FAULT BIT(2) 478c2ecf20Sopenharmony_ci#define CFFPS_MFR_UV_FAULT BIT(3) 488c2ecf20Sopenharmony_ci#define CFFPS_MFR_PS_KILL BIT(4) 498c2ecf20Sopenharmony_ci#define CFFPS_MFR_OC_FAULT BIT(5) 508c2ecf20Sopenharmony_ci#define CFFPS_MFR_VAUX_FAULT BIT(6) 518c2ecf20Sopenharmony_ci#define CFFPS_MFR_CURRENT_SHARE_WARNING BIT(7) 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci#define CFFPS_LED_BLINK (BIT(0) | BIT(6)) 548c2ecf20Sopenharmony_ci#define CFFPS_LED_ON (BIT(1) | BIT(6)) 558c2ecf20Sopenharmony_ci#define CFFPS_LED_OFF (BIT(2) | BIT(6)) 568c2ecf20Sopenharmony_ci#define CFFPS_BLINK_RATE_MS 250 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cienum { 598c2ecf20Sopenharmony_ci CFFPS_DEBUGFS_INPUT_HISTORY = 0, 608c2ecf20Sopenharmony_ci CFFPS_DEBUGFS_FRU, 618c2ecf20Sopenharmony_ci CFFPS_DEBUGFS_PN, 628c2ecf20Sopenharmony_ci CFFPS_DEBUGFS_HEADER, 638c2ecf20Sopenharmony_ci CFFPS_DEBUGFS_SN, 648c2ecf20Sopenharmony_ci CFFPS_DEBUGFS_MAX_POWER_OUT, 658c2ecf20Sopenharmony_ci CFFPS_DEBUGFS_CCIN, 668c2ecf20Sopenharmony_ci CFFPS_DEBUGFS_FW, 678c2ecf20Sopenharmony_ci CFFPS_DEBUGFS_ON_OFF_CONFIG, 688c2ecf20Sopenharmony_ci CFFPS_DEBUGFS_NUM_ENTRIES 698c2ecf20Sopenharmony_ci}; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_cienum versions { cffps1, cffps2, cffps_unknown }; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistruct ibm_cffps_input_history { 748c2ecf20Sopenharmony_ci struct mutex update_lock; 758c2ecf20Sopenharmony_ci unsigned long last_update; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci u8 byte_count; 788c2ecf20Sopenharmony_ci u8 data[CFFPS_INPUT_HISTORY_SIZE]; 798c2ecf20Sopenharmony_ci}; 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cistruct ibm_cffps { 828c2ecf20Sopenharmony_ci enum versions version; 838c2ecf20Sopenharmony_ci struct i2c_client *client; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci struct ibm_cffps_input_history input_history; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci int debugfs_entries[CFFPS_DEBUGFS_NUM_ENTRIES]; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci char led_name[32]; 908c2ecf20Sopenharmony_ci u8 led_state; 918c2ecf20Sopenharmony_ci struct led_classdev led; 928c2ecf20Sopenharmony_ci}; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_cistatic const struct i2c_device_id ibm_cffps_id[]; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci#define to_psu(x, y) container_of((x), struct ibm_cffps, debugfs_entries[(y)]) 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_cistatic ssize_t ibm_cffps_read_input_history(struct ibm_cffps *psu, 998c2ecf20Sopenharmony_ci char __user *buf, size_t count, 1008c2ecf20Sopenharmony_ci loff_t *ppos) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci int rc; 1038c2ecf20Sopenharmony_ci u8 msgbuf0[1] = { CFFPS_INPUT_HISTORY_CMD }; 1048c2ecf20Sopenharmony_ci u8 msgbuf1[CFFPS_INPUT_HISTORY_SIZE + 1] = { 0 }; 1058c2ecf20Sopenharmony_ci struct i2c_msg msg[2] = { 1068c2ecf20Sopenharmony_ci { 1078c2ecf20Sopenharmony_ci .addr = psu->client->addr, 1088c2ecf20Sopenharmony_ci .flags = psu->client->flags, 1098c2ecf20Sopenharmony_ci .len = 1, 1108c2ecf20Sopenharmony_ci .buf = msgbuf0, 1118c2ecf20Sopenharmony_ci }, { 1128c2ecf20Sopenharmony_ci .addr = psu->client->addr, 1138c2ecf20Sopenharmony_ci .flags = psu->client->flags | I2C_M_RD, 1148c2ecf20Sopenharmony_ci .len = CFFPS_INPUT_HISTORY_SIZE + 1, 1158c2ecf20Sopenharmony_ci .buf = msgbuf1, 1168c2ecf20Sopenharmony_ci }, 1178c2ecf20Sopenharmony_ci }; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci if (!*ppos) { 1208c2ecf20Sopenharmony_ci mutex_lock(&psu->input_history.update_lock); 1218c2ecf20Sopenharmony_ci if (time_after(jiffies, psu->input_history.last_update + HZ)) { 1228c2ecf20Sopenharmony_ci /* 1238c2ecf20Sopenharmony_ci * Use a raw i2c transfer, since we need more bytes 1248c2ecf20Sopenharmony_ci * than Linux I2C supports through smbus xfr (only 32). 1258c2ecf20Sopenharmony_ci */ 1268c2ecf20Sopenharmony_ci rc = i2c_transfer(psu->client->adapter, msg, 2); 1278c2ecf20Sopenharmony_ci if (rc < 0) { 1288c2ecf20Sopenharmony_ci mutex_unlock(&psu->input_history.update_lock); 1298c2ecf20Sopenharmony_ci return rc; 1308c2ecf20Sopenharmony_ci } 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci psu->input_history.byte_count = msgbuf1[0]; 1338c2ecf20Sopenharmony_ci memcpy(psu->input_history.data, &msgbuf1[1], 1348c2ecf20Sopenharmony_ci CFFPS_INPUT_HISTORY_SIZE); 1358c2ecf20Sopenharmony_ci psu->input_history.last_update = jiffies; 1368c2ecf20Sopenharmony_ci } 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci mutex_unlock(&psu->input_history.update_lock); 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci return simple_read_from_buffer(buf, count, ppos, 1428c2ecf20Sopenharmony_ci psu->input_history.data, 1438c2ecf20Sopenharmony_ci psu->input_history.byte_count); 1448c2ecf20Sopenharmony_ci} 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_cistatic ssize_t ibm_cffps_debugfs_read(struct file *file, char __user *buf, 1478c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 1488c2ecf20Sopenharmony_ci{ 1498c2ecf20Sopenharmony_ci u8 cmd; 1508c2ecf20Sopenharmony_ci int i, rc; 1518c2ecf20Sopenharmony_ci int *idxp = file->private_data; 1528c2ecf20Sopenharmony_ci int idx = *idxp; 1538c2ecf20Sopenharmony_ci struct ibm_cffps *psu = to_psu(idxp, idx); 1548c2ecf20Sopenharmony_ci char data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 }; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci pmbus_set_page(psu->client, 0, 0xff); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci switch (idx) { 1598c2ecf20Sopenharmony_ci case CFFPS_DEBUGFS_INPUT_HISTORY: 1608c2ecf20Sopenharmony_ci return ibm_cffps_read_input_history(psu, buf, count, ppos); 1618c2ecf20Sopenharmony_ci case CFFPS_DEBUGFS_FRU: 1628c2ecf20Sopenharmony_ci cmd = CFFPS_FRU_CMD; 1638c2ecf20Sopenharmony_ci break; 1648c2ecf20Sopenharmony_ci case CFFPS_DEBUGFS_PN: 1658c2ecf20Sopenharmony_ci cmd = CFFPS_PN_CMD; 1668c2ecf20Sopenharmony_ci break; 1678c2ecf20Sopenharmony_ci case CFFPS_DEBUGFS_HEADER: 1688c2ecf20Sopenharmony_ci cmd = CFFPS_HEADER_CMD; 1698c2ecf20Sopenharmony_ci break; 1708c2ecf20Sopenharmony_ci case CFFPS_DEBUGFS_SN: 1718c2ecf20Sopenharmony_ci cmd = CFFPS_SN_CMD; 1728c2ecf20Sopenharmony_ci break; 1738c2ecf20Sopenharmony_ci case CFFPS_DEBUGFS_MAX_POWER_OUT: 1748c2ecf20Sopenharmony_ci if (psu->version == cffps1) { 1758c2ecf20Sopenharmony_ci rc = i2c_smbus_read_word_swapped(psu->client, 1768c2ecf20Sopenharmony_ci CFFPS_MAX_POWER_OUT_CMD); 1778c2ecf20Sopenharmony_ci } else { 1788c2ecf20Sopenharmony_ci rc = i2c_smbus_read_word_data(psu->client, 1798c2ecf20Sopenharmony_ci CFFPS_MAX_POWER_OUT_CMD); 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci if (rc < 0) 1838c2ecf20Sopenharmony_ci return rc; 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci rc = snprintf(data, I2C_SMBUS_BLOCK_MAX, "%d", rc); 1868c2ecf20Sopenharmony_ci goto done; 1878c2ecf20Sopenharmony_ci case CFFPS_DEBUGFS_CCIN: 1888c2ecf20Sopenharmony_ci rc = i2c_smbus_read_word_swapped(psu->client, CFFPS_CCIN_CMD); 1898c2ecf20Sopenharmony_ci if (rc < 0) 1908c2ecf20Sopenharmony_ci return rc; 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci rc = snprintf(data, 5, "%04X", rc); 1938c2ecf20Sopenharmony_ci goto done; 1948c2ecf20Sopenharmony_ci case CFFPS_DEBUGFS_FW: 1958c2ecf20Sopenharmony_ci switch (psu->version) { 1968c2ecf20Sopenharmony_ci case cffps1: 1978c2ecf20Sopenharmony_ci for (i = 0; i < CFFPS1_FW_NUM_BYTES; ++i) { 1988c2ecf20Sopenharmony_ci rc = i2c_smbus_read_byte_data(psu->client, 1998c2ecf20Sopenharmony_ci CFFPS_FW_CMD + 2008c2ecf20Sopenharmony_ci i); 2018c2ecf20Sopenharmony_ci if (rc < 0) 2028c2ecf20Sopenharmony_ci return rc; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci snprintf(&data[i * 2], 3, "%02X", rc); 2058c2ecf20Sopenharmony_ci } 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci rc = i * 2; 2088c2ecf20Sopenharmony_ci break; 2098c2ecf20Sopenharmony_ci case cffps2: 2108c2ecf20Sopenharmony_ci for (i = 0; i < CFFPS2_FW_NUM_WORDS; ++i) { 2118c2ecf20Sopenharmony_ci rc = i2c_smbus_read_word_data(psu->client, 2128c2ecf20Sopenharmony_ci CFFPS_FW_CMD + 2138c2ecf20Sopenharmony_ci i); 2148c2ecf20Sopenharmony_ci if (rc < 0) 2158c2ecf20Sopenharmony_ci return rc; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci snprintf(&data[i * 4], 5, "%04X", rc); 2188c2ecf20Sopenharmony_ci } 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci rc = i * 4; 2218c2ecf20Sopenharmony_ci break; 2228c2ecf20Sopenharmony_ci default: 2238c2ecf20Sopenharmony_ci return -EOPNOTSUPP; 2248c2ecf20Sopenharmony_ci } 2258c2ecf20Sopenharmony_ci goto done; 2268c2ecf20Sopenharmony_ci case CFFPS_DEBUGFS_ON_OFF_CONFIG: 2278c2ecf20Sopenharmony_ci rc = i2c_smbus_read_byte_data(psu->client, 2288c2ecf20Sopenharmony_ci PMBUS_ON_OFF_CONFIG); 2298c2ecf20Sopenharmony_ci if (rc < 0) 2308c2ecf20Sopenharmony_ci return rc; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci rc = snprintf(data, 3, "%02x", rc); 2338c2ecf20Sopenharmony_ci goto done; 2348c2ecf20Sopenharmony_ci default: 2358c2ecf20Sopenharmony_ci return -EINVAL; 2368c2ecf20Sopenharmony_ci } 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci rc = i2c_smbus_read_block_data(psu->client, cmd, data); 2398c2ecf20Sopenharmony_ci if (rc < 0) 2408c2ecf20Sopenharmony_ci return rc; 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_cidone: 2438c2ecf20Sopenharmony_ci data[rc] = '\n'; 2448c2ecf20Sopenharmony_ci rc += 2; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci return simple_read_from_buffer(buf, count, ppos, data, rc); 2478c2ecf20Sopenharmony_ci} 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_cistatic ssize_t ibm_cffps_debugfs_write(struct file *file, 2508c2ecf20Sopenharmony_ci const char __user *buf, size_t count, 2518c2ecf20Sopenharmony_ci loff_t *ppos) 2528c2ecf20Sopenharmony_ci{ 2538c2ecf20Sopenharmony_ci u8 data; 2548c2ecf20Sopenharmony_ci ssize_t rc; 2558c2ecf20Sopenharmony_ci int *idxp = file->private_data; 2568c2ecf20Sopenharmony_ci int idx = *idxp; 2578c2ecf20Sopenharmony_ci struct ibm_cffps *psu = to_psu(idxp, idx); 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci switch (idx) { 2608c2ecf20Sopenharmony_ci case CFFPS_DEBUGFS_ON_OFF_CONFIG: 2618c2ecf20Sopenharmony_ci pmbus_set_page(psu->client, 0, 0xff); 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci rc = simple_write_to_buffer(&data, 1, ppos, buf, count); 2648c2ecf20Sopenharmony_ci if (rc <= 0) 2658c2ecf20Sopenharmony_ci return rc; 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci rc = i2c_smbus_write_byte_data(psu->client, 2688c2ecf20Sopenharmony_ci PMBUS_ON_OFF_CONFIG, data); 2698c2ecf20Sopenharmony_ci if (rc) 2708c2ecf20Sopenharmony_ci return rc; 2718c2ecf20Sopenharmony_ci 2728c2ecf20Sopenharmony_ci rc = 1; 2738c2ecf20Sopenharmony_ci break; 2748c2ecf20Sopenharmony_ci default: 2758c2ecf20Sopenharmony_ci return -EINVAL; 2768c2ecf20Sopenharmony_ci } 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci return rc; 2798c2ecf20Sopenharmony_ci} 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_cistatic const struct file_operations ibm_cffps_fops = { 2828c2ecf20Sopenharmony_ci .llseek = noop_llseek, 2838c2ecf20Sopenharmony_ci .read = ibm_cffps_debugfs_read, 2848c2ecf20Sopenharmony_ci .write = ibm_cffps_debugfs_write, 2858c2ecf20Sopenharmony_ci .open = simple_open, 2868c2ecf20Sopenharmony_ci}; 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_cistatic int ibm_cffps_read_byte_data(struct i2c_client *client, int page, 2898c2ecf20Sopenharmony_ci int reg) 2908c2ecf20Sopenharmony_ci{ 2918c2ecf20Sopenharmony_ci int rc, mfr; 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_ci switch (reg) { 2948c2ecf20Sopenharmony_ci case PMBUS_STATUS_VOUT: 2958c2ecf20Sopenharmony_ci case PMBUS_STATUS_IOUT: 2968c2ecf20Sopenharmony_ci case PMBUS_STATUS_TEMPERATURE: 2978c2ecf20Sopenharmony_ci case PMBUS_STATUS_FAN_12: 2988c2ecf20Sopenharmony_ci rc = pmbus_read_byte_data(client, page, reg); 2998c2ecf20Sopenharmony_ci if (rc < 0) 3008c2ecf20Sopenharmony_ci return rc; 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci mfr = pmbus_read_byte_data(client, page, 3038c2ecf20Sopenharmony_ci PMBUS_STATUS_MFR_SPECIFIC); 3048c2ecf20Sopenharmony_ci if (mfr < 0) 3058c2ecf20Sopenharmony_ci /* 3068c2ecf20Sopenharmony_ci * Return the status register instead of an error, 3078c2ecf20Sopenharmony_ci * since we successfully read status. 3088c2ecf20Sopenharmony_ci */ 3098c2ecf20Sopenharmony_ci return rc; 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci /* Add MFR_SPECIFIC bits to the standard pmbus status regs. */ 3128c2ecf20Sopenharmony_ci if (reg == PMBUS_STATUS_FAN_12) { 3138c2ecf20Sopenharmony_ci if (mfr & CFFPS_MFR_FAN_FAULT) 3148c2ecf20Sopenharmony_ci rc |= PB_FAN_FAN1_FAULT; 3158c2ecf20Sopenharmony_ci } else if (reg == PMBUS_STATUS_TEMPERATURE) { 3168c2ecf20Sopenharmony_ci if (mfr & CFFPS_MFR_THERMAL_FAULT) 3178c2ecf20Sopenharmony_ci rc |= PB_TEMP_OT_FAULT; 3188c2ecf20Sopenharmony_ci } else if (reg == PMBUS_STATUS_VOUT) { 3198c2ecf20Sopenharmony_ci if (mfr & (CFFPS_MFR_OV_FAULT | CFFPS_MFR_VAUX_FAULT)) 3208c2ecf20Sopenharmony_ci rc |= PB_VOLTAGE_OV_FAULT; 3218c2ecf20Sopenharmony_ci if (mfr & CFFPS_MFR_UV_FAULT) 3228c2ecf20Sopenharmony_ci rc |= PB_VOLTAGE_UV_FAULT; 3238c2ecf20Sopenharmony_ci } else if (reg == PMBUS_STATUS_IOUT) { 3248c2ecf20Sopenharmony_ci if (mfr & CFFPS_MFR_OC_FAULT) 3258c2ecf20Sopenharmony_ci rc |= PB_IOUT_OC_FAULT; 3268c2ecf20Sopenharmony_ci if (mfr & CFFPS_MFR_CURRENT_SHARE_WARNING) 3278c2ecf20Sopenharmony_ci rc |= PB_CURRENT_SHARE_FAULT; 3288c2ecf20Sopenharmony_ci } 3298c2ecf20Sopenharmony_ci break; 3308c2ecf20Sopenharmony_ci default: 3318c2ecf20Sopenharmony_ci rc = -ENODATA; 3328c2ecf20Sopenharmony_ci break; 3338c2ecf20Sopenharmony_ci } 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_ci return rc; 3368c2ecf20Sopenharmony_ci} 3378c2ecf20Sopenharmony_ci 3388c2ecf20Sopenharmony_cistatic int ibm_cffps_read_word_data(struct i2c_client *client, int page, 3398c2ecf20Sopenharmony_ci int phase, int reg) 3408c2ecf20Sopenharmony_ci{ 3418c2ecf20Sopenharmony_ci int rc, mfr; 3428c2ecf20Sopenharmony_ci 3438c2ecf20Sopenharmony_ci switch (reg) { 3448c2ecf20Sopenharmony_ci case PMBUS_STATUS_WORD: 3458c2ecf20Sopenharmony_ci rc = pmbus_read_word_data(client, page, phase, reg); 3468c2ecf20Sopenharmony_ci if (rc < 0) 3478c2ecf20Sopenharmony_ci return rc; 3488c2ecf20Sopenharmony_ci 3498c2ecf20Sopenharmony_ci mfr = pmbus_read_byte_data(client, page, 3508c2ecf20Sopenharmony_ci PMBUS_STATUS_MFR_SPECIFIC); 3518c2ecf20Sopenharmony_ci if (mfr < 0) 3528c2ecf20Sopenharmony_ci /* 3538c2ecf20Sopenharmony_ci * Return the status register instead of an error, 3548c2ecf20Sopenharmony_ci * since we successfully read status. 3558c2ecf20Sopenharmony_ci */ 3568c2ecf20Sopenharmony_ci return rc; 3578c2ecf20Sopenharmony_ci 3588c2ecf20Sopenharmony_ci if (mfr & CFFPS_MFR_PS_KILL) 3598c2ecf20Sopenharmony_ci rc |= PB_STATUS_OFF; 3608c2ecf20Sopenharmony_ci break; 3618c2ecf20Sopenharmony_ci case PMBUS_VIRT_READ_VMON: 3628c2ecf20Sopenharmony_ci rc = pmbus_read_word_data(client, page, phase, 3638c2ecf20Sopenharmony_ci CFFPS_12VCS_VOUT_CMD); 3648c2ecf20Sopenharmony_ci break; 3658c2ecf20Sopenharmony_ci default: 3668c2ecf20Sopenharmony_ci rc = -ENODATA; 3678c2ecf20Sopenharmony_ci break; 3688c2ecf20Sopenharmony_ci } 3698c2ecf20Sopenharmony_ci 3708c2ecf20Sopenharmony_ci return rc; 3718c2ecf20Sopenharmony_ci} 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_cistatic int ibm_cffps_led_brightness_set(struct led_classdev *led_cdev, 3748c2ecf20Sopenharmony_ci enum led_brightness brightness) 3758c2ecf20Sopenharmony_ci{ 3768c2ecf20Sopenharmony_ci int rc; 3778c2ecf20Sopenharmony_ci u8 next_led_state; 3788c2ecf20Sopenharmony_ci struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); 3798c2ecf20Sopenharmony_ci 3808c2ecf20Sopenharmony_ci if (brightness == LED_OFF) { 3818c2ecf20Sopenharmony_ci next_led_state = CFFPS_LED_OFF; 3828c2ecf20Sopenharmony_ci } else { 3838c2ecf20Sopenharmony_ci brightness = LED_FULL; 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_ci if (psu->led_state != CFFPS_LED_BLINK) 3868c2ecf20Sopenharmony_ci next_led_state = CFFPS_LED_ON; 3878c2ecf20Sopenharmony_ci else 3888c2ecf20Sopenharmony_ci next_led_state = CFFPS_LED_BLINK; 3898c2ecf20Sopenharmony_ci } 3908c2ecf20Sopenharmony_ci 3918c2ecf20Sopenharmony_ci dev_dbg(&psu->client->dev, "LED brightness set: %d. Command: %d.\n", 3928c2ecf20Sopenharmony_ci brightness, next_led_state); 3938c2ecf20Sopenharmony_ci 3948c2ecf20Sopenharmony_ci pmbus_set_page(psu->client, 0, 0xff); 3958c2ecf20Sopenharmony_ci 3968c2ecf20Sopenharmony_ci rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, 3978c2ecf20Sopenharmony_ci next_led_state); 3988c2ecf20Sopenharmony_ci if (rc < 0) 3998c2ecf20Sopenharmony_ci return rc; 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_ci psu->led_state = next_led_state; 4028c2ecf20Sopenharmony_ci led_cdev->brightness = brightness; 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_ci return 0; 4058c2ecf20Sopenharmony_ci} 4068c2ecf20Sopenharmony_ci 4078c2ecf20Sopenharmony_cistatic int ibm_cffps_led_blink_set(struct led_classdev *led_cdev, 4088c2ecf20Sopenharmony_ci unsigned long *delay_on, 4098c2ecf20Sopenharmony_ci unsigned long *delay_off) 4108c2ecf20Sopenharmony_ci{ 4118c2ecf20Sopenharmony_ci int rc; 4128c2ecf20Sopenharmony_ci struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led); 4138c2ecf20Sopenharmony_ci 4148c2ecf20Sopenharmony_ci dev_dbg(&psu->client->dev, "LED blink set.\n"); 4158c2ecf20Sopenharmony_ci 4168c2ecf20Sopenharmony_ci pmbus_set_page(psu->client, 0, 0xff); 4178c2ecf20Sopenharmony_ci 4188c2ecf20Sopenharmony_ci rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD, 4198c2ecf20Sopenharmony_ci CFFPS_LED_BLINK); 4208c2ecf20Sopenharmony_ci if (rc < 0) 4218c2ecf20Sopenharmony_ci return rc; 4228c2ecf20Sopenharmony_ci 4238c2ecf20Sopenharmony_ci psu->led_state = CFFPS_LED_BLINK; 4248c2ecf20Sopenharmony_ci led_cdev->brightness = LED_FULL; 4258c2ecf20Sopenharmony_ci *delay_on = CFFPS_BLINK_RATE_MS; 4268c2ecf20Sopenharmony_ci *delay_off = CFFPS_BLINK_RATE_MS; 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_ci return 0; 4298c2ecf20Sopenharmony_ci} 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_cistatic void ibm_cffps_create_led_class(struct ibm_cffps *psu) 4328c2ecf20Sopenharmony_ci{ 4338c2ecf20Sopenharmony_ci int rc; 4348c2ecf20Sopenharmony_ci struct i2c_client *client = psu->client; 4358c2ecf20Sopenharmony_ci struct device *dev = &client->dev; 4368c2ecf20Sopenharmony_ci 4378c2ecf20Sopenharmony_ci snprintf(psu->led_name, sizeof(psu->led_name), "%s-%02x", client->name, 4388c2ecf20Sopenharmony_ci client->addr); 4398c2ecf20Sopenharmony_ci psu->led.name = psu->led_name; 4408c2ecf20Sopenharmony_ci psu->led.max_brightness = LED_FULL; 4418c2ecf20Sopenharmony_ci psu->led.brightness_set_blocking = ibm_cffps_led_brightness_set; 4428c2ecf20Sopenharmony_ci psu->led.blink_set = ibm_cffps_led_blink_set; 4438c2ecf20Sopenharmony_ci 4448c2ecf20Sopenharmony_ci rc = devm_led_classdev_register(dev, &psu->led); 4458c2ecf20Sopenharmony_ci if (rc) 4468c2ecf20Sopenharmony_ci dev_warn(dev, "failed to register led class: %d\n", rc); 4478c2ecf20Sopenharmony_ci else 4488c2ecf20Sopenharmony_ci i2c_smbus_write_byte_data(client, CFFPS_SYS_CONFIG_CMD, 4498c2ecf20Sopenharmony_ci CFFPS_LED_OFF); 4508c2ecf20Sopenharmony_ci} 4518c2ecf20Sopenharmony_ci 4528c2ecf20Sopenharmony_cistatic struct pmbus_driver_info ibm_cffps_info[] = { 4538c2ecf20Sopenharmony_ci [cffps1] = { 4548c2ecf20Sopenharmony_ci .pages = 1, 4558c2ecf20Sopenharmony_ci .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | 4568c2ecf20Sopenharmony_ci PMBUS_HAVE_PIN | PMBUS_HAVE_FAN12 | PMBUS_HAVE_TEMP | 4578c2ecf20Sopenharmony_ci PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 | 4588c2ecf20Sopenharmony_ci PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | 4598c2ecf20Sopenharmony_ci PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP | 4608c2ecf20Sopenharmony_ci PMBUS_HAVE_STATUS_FAN12, 4618c2ecf20Sopenharmony_ci .read_byte_data = ibm_cffps_read_byte_data, 4628c2ecf20Sopenharmony_ci .read_word_data = ibm_cffps_read_word_data, 4638c2ecf20Sopenharmony_ci }, 4648c2ecf20Sopenharmony_ci [cffps2] = { 4658c2ecf20Sopenharmony_ci .pages = 2, 4668c2ecf20Sopenharmony_ci .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | 4678c2ecf20Sopenharmony_ci PMBUS_HAVE_PIN | PMBUS_HAVE_FAN12 | PMBUS_HAVE_TEMP | 4688c2ecf20Sopenharmony_ci PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 | 4698c2ecf20Sopenharmony_ci PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT | 4708c2ecf20Sopenharmony_ci PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP | 4718c2ecf20Sopenharmony_ci PMBUS_HAVE_STATUS_FAN12 | PMBUS_HAVE_VMON, 4728c2ecf20Sopenharmony_ci .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | 4738c2ecf20Sopenharmony_ci PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 | 4748c2ecf20Sopenharmony_ci PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT, 4758c2ecf20Sopenharmony_ci .read_byte_data = ibm_cffps_read_byte_data, 4768c2ecf20Sopenharmony_ci .read_word_data = ibm_cffps_read_word_data, 4778c2ecf20Sopenharmony_ci }, 4788c2ecf20Sopenharmony_ci}; 4798c2ecf20Sopenharmony_ci 4808c2ecf20Sopenharmony_cistatic struct pmbus_platform_data ibm_cffps_pdata = { 4818c2ecf20Sopenharmony_ci .flags = PMBUS_SKIP_STATUS_CHECK, 4828c2ecf20Sopenharmony_ci}; 4838c2ecf20Sopenharmony_ci 4848c2ecf20Sopenharmony_cistatic int ibm_cffps_probe(struct i2c_client *client) 4858c2ecf20Sopenharmony_ci{ 4868c2ecf20Sopenharmony_ci int i, rc; 4878c2ecf20Sopenharmony_ci enum versions vs = cffps_unknown; 4888c2ecf20Sopenharmony_ci struct dentry *debugfs; 4898c2ecf20Sopenharmony_ci struct dentry *ibm_cffps_dir; 4908c2ecf20Sopenharmony_ci struct ibm_cffps *psu; 4918c2ecf20Sopenharmony_ci const void *md = of_device_get_match_data(&client->dev); 4928c2ecf20Sopenharmony_ci const struct i2c_device_id *id; 4938c2ecf20Sopenharmony_ci 4948c2ecf20Sopenharmony_ci if (md) { 4958c2ecf20Sopenharmony_ci vs = (enum versions)md; 4968c2ecf20Sopenharmony_ci } else { 4978c2ecf20Sopenharmony_ci id = i2c_match_id(ibm_cffps_id, client); 4988c2ecf20Sopenharmony_ci if (id) 4998c2ecf20Sopenharmony_ci vs = (enum versions)id->driver_data; 5008c2ecf20Sopenharmony_ci } 5018c2ecf20Sopenharmony_ci 5028c2ecf20Sopenharmony_ci if (vs == cffps_unknown) { 5038c2ecf20Sopenharmony_ci u16 ccin_revision = 0; 5048c2ecf20Sopenharmony_ci u16 ccin_version = CFFPS_CCIN_VERSION_1; 5058c2ecf20Sopenharmony_ci int ccin = i2c_smbus_read_word_swapped(client, CFFPS_CCIN_CMD); 5068c2ecf20Sopenharmony_ci 5078c2ecf20Sopenharmony_ci if (ccin > 0) { 5088c2ecf20Sopenharmony_ci ccin_revision = FIELD_GET(CFFPS_CCIN_REVISION, ccin); 5098c2ecf20Sopenharmony_ci ccin_version = FIELD_GET(CFFPS_CCIN_VERSION, ccin); 5108c2ecf20Sopenharmony_ci } 5118c2ecf20Sopenharmony_ci 5128c2ecf20Sopenharmony_ci switch (ccin_version) { 5138c2ecf20Sopenharmony_ci default: 5148c2ecf20Sopenharmony_ci case CFFPS_CCIN_VERSION_1: 5158c2ecf20Sopenharmony_ci vs = cffps1; 5168c2ecf20Sopenharmony_ci break; 5178c2ecf20Sopenharmony_ci case CFFPS_CCIN_VERSION_2: 5188c2ecf20Sopenharmony_ci vs = cffps2; 5198c2ecf20Sopenharmony_ci break; 5208c2ecf20Sopenharmony_ci case CFFPS_CCIN_VERSION_3: 5218c2ecf20Sopenharmony_ci if (ccin_revision == CFFPS_CCIN_REVISION_LEGACY) 5228c2ecf20Sopenharmony_ci vs = cffps1; 5238c2ecf20Sopenharmony_ci else 5248c2ecf20Sopenharmony_ci vs = cffps2; 5258c2ecf20Sopenharmony_ci break; 5268c2ecf20Sopenharmony_ci } 5278c2ecf20Sopenharmony_ci 5288c2ecf20Sopenharmony_ci /* Set the client name to include the version number. */ 5298c2ecf20Sopenharmony_ci snprintf(client->name, I2C_NAME_SIZE, "cffps%d", vs + 1); 5308c2ecf20Sopenharmony_ci } 5318c2ecf20Sopenharmony_ci 5328c2ecf20Sopenharmony_ci client->dev.platform_data = &ibm_cffps_pdata; 5338c2ecf20Sopenharmony_ci rc = pmbus_do_probe(client, &ibm_cffps_info[vs]); 5348c2ecf20Sopenharmony_ci if (rc) 5358c2ecf20Sopenharmony_ci return rc; 5368c2ecf20Sopenharmony_ci 5378c2ecf20Sopenharmony_ci /* 5388c2ecf20Sopenharmony_ci * Don't fail the probe if there isn't enough memory for leds and 5398c2ecf20Sopenharmony_ci * debugfs. 5408c2ecf20Sopenharmony_ci */ 5418c2ecf20Sopenharmony_ci psu = devm_kzalloc(&client->dev, sizeof(*psu), GFP_KERNEL); 5428c2ecf20Sopenharmony_ci if (!psu) 5438c2ecf20Sopenharmony_ci return 0; 5448c2ecf20Sopenharmony_ci 5458c2ecf20Sopenharmony_ci psu->version = vs; 5468c2ecf20Sopenharmony_ci psu->client = client; 5478c2ecf20Sopenharmony_ci mutex_init(&psu->input_history.update_lock); 5488c2ecf20Sopenharmony_ci psu->input_history.last_update = jiffies - HZ; 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_ci ibm_cffps_create_led_class(psu); 5518c2ecf20Sopenharmony_ci 5528c2ecf20Sopenharmony_ci /* Don't fail the probe if we can't create debugfs */ 5538c2ecf20Sopenharmony_ci debugfs = pmbus_get_debugfs_dir(client); 5548c2ecf20Sopenharmony_ci if (!debugfs) 5558c2ecf20Sopenharmony_ci return 0; 5568c2ecf20Sopenharmony_ci 5578c2ecf20Sopenharmony_ci ibm_cffps_dir = debugfs_create_dir(client->name, debugfs); 5588c2ecf20Sopenharmony_ci if (!ibm_cffps_dir) 5598c2ecf20Sopenharmony_ci return 0; 5608c2ecf20Sopenharmony_ci 5618c2ecf20Sopenharmony_ci for (i = 0; i < CFFPS_DEBUGFS_NUM_ENTRIES; ++i) 5628c2ecf20Sopenharmony_ci psu->debugfs_entries[i] = i; 5638c2ecf20Sopenharmony_ci 5648c2ecf20Sopenharmony_ci debugfs_create_file("input_history", 0444, ibm_cffps_dir, 5658c2ecf20Sopenharmony_ci &psu->debugfs_entries[CFFPS_DEBUGFS_INPUT_HISTORY], 5668c2ecf20Sopenharmony_ci &ibm_cffps_fops); 5678c2ecf20Sopenharmony_ci debugfs_create_file("fru", 0444, ibm_cffps_dir, 5688c2ecf20Sopenharmony_ci &psu->debugfs_entries[CFFPS_DEBUGFS_FRU], 5698c2ecf20Sopenharmony_ci &ibm_cffps_fops); 5708c2ecf20Sopenharmony_ci debugfs_create_file("part_number", 0444, ibm_cffps_dir, 5718c2ecf20Sopenharmony_ci &psu->debugfs_entries[CFFPS_DEBUGFS_PN], 5728c2ecf20Sopenharmony_ci &ibm_cffps_fops); 5738c2ecf20Sopenharmony_ci debugfs_create_file("header", 0444, ibm_cffps_dir, 5748c2ecf20Sopenharmony_ci &psu->debugfs_entries[CFFPS_DEBUGFS_HEADER], 5758c2ecf20Sopenharmony_ci &ibm_cffps_fops); 5768c2ecf20Sopenharmony_ci debugfs_create_file("serial_number", 0444, ibm_cffps_dir, 5778c2ecf20Sopenharmony_ci &psu->debugfs_entries[CFFPS_DEBUGFS_SN], 5788c2ecf20Sopenharmony_ci &ibm_cffps_fops); 5798c2ecf20Sopenharmony_ci debugfs_create_file("max_power_out", 0444, ibm_cffps_dir, 5808c2ecf20Sopenharmony_ci &psu->debugfs_entries[CFFPS_DEBUGFS_MAX_POWER_OUT], 5818c2ecf20Sopenharmony_ci &ibm_cffps_fops); 5828c2ecf20Sopenharmony_ci debugfs_create_file("ccin", 0444, ibm_cffps_dir, 5838c2ecf20Sopenharmony_ci &psu->debugfs_entries[CFFPS_DEBUGFS_CCIN], 5848c2ecf20Sopenharmony_ci &ibm_cffps_fops); 5858c2ecf20Sopenharmony_ci debugfs_create_file("fw_version", 0444, ibm_cffps_dir, 5868c2ecf20Sopenharmony_ci &psu->debugfs_entries[CFFPS_DEBUGFS_FW], 5878c2ecf20Sopenharmony_ci &ibm_cffps_fops); 5888c2ecf20Sopenharmony_ci debugfs_create_file("on_off_config", 0644, ibm_cffps_dir, 5898c2ecf20Sopenharmony_ci &psu->debugfs_entries[CFFPS_DEBUGFS_ON_OFF_CONFIG], 5908c2ecf20Sopenharmony_ci &ibm_cffps_fops); 5918c2ecf20Sopenharmony_ci 5928c2ecf20Sopenharmony_ci return 0; 5938c2ecf20Sopenharmony_ci} 5948c2ecf20Sopenharmony_ci 5958c2ecf20Sopenharmony_cistatic const struct i2c_device_id ibm_cffps_id[] = { 5968c2ecf20Sopenharmony_ci { "ibm_cffps1", cffps1 }, 5978c2ecf20Sopenharmony_ci { "ibm_cffps2", cffps2 }, 5988c2ecf20Sopenharmony_ci { "ibm_cffps", cffps_unknown }, 5998c2ecf20Sopenharmony_ci {} 6008c2ecf20Sopenharmony_ci}; 6018c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, ibm_cffps_id); 6028c2ecf20Sopenharmony_ci 6038c2ecf20Sopenharmony_cistatic const struct of_device_id ibm_cffps_of_match[] = { 6048c2ecf20Sopenharmony_ci { 6058c2ecf20Sopenharmony_ci .compatible = "ibm,cffps1", 6068c2ecf20Sopenharmony_ci .data = (void *)cffps1 6078c2ecf20Sopenharmony_ci }, 6088c2ecf20Sopenharmony_ci { 6098c2ecf20Sopenharmony_ci .compatible = "ibm,cffps2", 6108c2ecf20Sopenharmony_ci .data = (void *)cffps2 6118c2ecf20Sopenharmony_ci }, 6128c2ecf20Sopenharmony_ci { 6138c2ecf20Sopenharmony_ci .compatible = "ibm,cffps", 6148c2ecf20Sopenharmony_ci .data = (void *)cffps_unknown 6158c2ecf20Sopenharmony_ci }, 6168c2ecf20Sopenharmony_ci {} 6178c2ecf20Sopenharmony_ci}; 6188c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, ibm_cffps_of_match); 6198c2ecf20Sopenharmony_ci 6208c2ecf20Sopenharmony_cistatic struct i2c_driver ibm_cffps_driver = { 6218c2ecf20Sopenharmony_ci .driver = { 6228c2ecf20Sopenharmony_ci .name = "ibm-cffps", 6238c2ecf20Sopenharmony_ci .of_match_table = ibm_cffps_of_match, 6248c2ecf20Sopenharmony_ci }, 6258c2ecf20Sopenharmony_ci .probe_new = ibm_cffps_probe, 6268c2ecf20Sopenharmony_ci .remove = pmbus_do_remove, 6278c2ecf20Sopenharmony_ci .id_table = ibm_cffps_id, 6288c2ecf20Sopenharmony_ci}; 6298c2ecf20Sopenharmony_ci 6308c2ecf20Sopenharmony_cimodule_i2c_driver(ibm_cffps_driver); 6318c2ecf20Sopenharmony_ci 6328c2ecf20Sopenharmony_ciMODULE_AUTHOR("Eddie James"); 6338c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("PMBus driver for IBM Common Form Factor power supplies"); 6348c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 635