124be24beSVincent Jardin // SPDX-License-Identifier: GPL-2.0-or-later 224be24beSVincent Jardin /* 324be24beSVincent Jardin * Driver for MPS MPQ8646 step-down converter. 424be24beSVincent Jardin * 524be24beSVincent Jardin * Copyright (c) 2026 Free Mobile - Vincent Jardin <vjardin@free.fr> 624be24beSVincent Jardin */ 724be24beSVincent Jardin 824be24beSVincent Jardin #include <linux/bitops.h> 924be24beSVincent Jardin #include <linux/debugfs.h> 10*25ad6213SVincent Jardin #include <linux/delay.h> 1124be24beSVincent Jardin #include <linux/i2c.h> 1224be24beSVincent Jardin #include <linux/module.h> 1324be24beSVincent Jardin #include <linux/mutex.h> 1424be24beSVincent Jardin #include <linux/nvmem-provider.h> 1524be24beSVincent Jardin #include <linux/of_device.h> 1624be24beSVincent Jardin #include <linux/pmbus.h> 1724be24beSVincent Jardin #include <linux/property.h> 1824be24beSVincent Jardin #include <linux/regulator/driver.h> 1924be24beSVincent Jardin #include <linux/seq_file.h> 2024be24beSVincent Jardin #include <linux/workqueue.h> 2124be24beSVincent Jardin #include "pmbus.h" 2224be24beSVincent Jardin 2324be24beSVincent Jardin /* Default cadence for the in-driver alarm-poll fallback (ms) */ 2424be24beSVincent Jardin #define MPQ8646_ALARM_POLL_MS_DEFAULT 1000 2524be24beSVincent Jardin 2624be24beSVincent Jardin /* MPS vendor-extended command codes (NOT in PMBus 1.3 Part II) */ 27*25ad6213SVincent Jardin #define MPS_CLEAR_LAST_FAULT 0x08 28*25ad6213SVincent Jardin #define MPS_MFR_CFG_EXT 0xF5 29*25ad6213SVincent Jardin #define MPS_MFR_CFG_EXT_CLR_LAST_EN BIT(6) 3024be24beSVincent Jardin #define MPS_PROTECTION_LAST 0xFB 3124be24beSVincent Jardin 32*25ad6213SVincent Jardin /* 33*25ad6213SVincent Jardin * PMBus 1.3 NVM commit / revert commands. MPS equivalent of 34*25ad6213SVincent Jardin * STORE_ALL (15h) and RESTORE_ALL (16h). 35*25ad6213SVincent Jardin */ 36*25ad6213SVincent Jardin #define PMBUS_STORE_USER_ALL 0x15 37*25ad6213SVincent Jardin #define PMBUS_RESTORE_USER_ALL 0x16 38*25ad6213SVincent Jardin 3924be24beSVincent Jardin /* PMBus 1.3 timing / UVLO command codes */ 4024be24beSVincent Jardin #define PMBUS_VIN_ON 0x35 4124be24beSVincent Jardin #define PMBUS_VIN_OFF 0x36 4224be24beSVincent Jardin #define PMBUS_TON_DELAY 0x60 4324be24beSVincent Jardin #define PMBUS_TON_RISE 0x61 4424be24beSVincent Jardin #define PMBUS_TOFF_DELAY 0x64 4524be24beSVincent Jardin #define PMBUS_TOFF_FALL 0x65 4624be24beSVincent Jardin 4724be24beSVincent Jardin /* MPS vendor-extended observability / identity registers */ 4824be24beSVincent Jardin #define MPS_MFR_CONFIG_ID 0xC0 4924be24beSVincent Jardin #define MPS_MFR_CONFIG_CODE_REV 0xC1 50*25ad6213SVincent Jardin #define MPS_MFR_PRODUCT_REV_USER 0xC2 5124be24beSVincent Jardin #define MPS_MFR_SILICON_REV 0xC3 5224be24beSVincent Jardin #define MPS_MFR_RETRY_TIMES 0xF4 5324be24beSVincent Jardin #define MPS_MFR_VBOOT_CFG 0xFC 5424be24beSVincent Jardin 55*25ad6213SVincent Jardin /* 56*25ad6213SVincent Jardin * MPS_MFR_PMBUS_LOCK (EEh): 16-bit WORD whose low two bits gate 57*25ad6213SVincent Jardin * subsequent PMBus writes 58*25ad6213SVincent Jardin * bits[1:0] = 00 -- unlocked (POR default) 59*25ad6213SVincent Jardin * 01 -- lock all writes EXCEPT VOUT_COMMAND (0x21) 60*25ad6213SVincent Jardin * so the operator can still DVFS the rail 61*25ad6213SVincent Jardin * 11 -- lock all writes 62*25ad6213SVincent Jardin * A negative-going PG edge resets these bits to 00, the lock 63*25ad6213SVincent Jardin * is operationally reversible without a full chip POR. 64*25ad6213SVincent Jardin */ 65*25ad6213SVincent Jardin #define MPS_MFR_PMBUS_LOCK 0xEE 66*25ad6213SVincent Jardin 67*25ad6213SVincent Jardin /* 68*25ad6213SVincent Jardin * Retry parameters for the MFR_CFG_EXT gate-close write after 69*25ad6213SVincent Jardin * CLEAR_LAST_FAULT. Bench-observed NVM-busy NACK window on this 70*25ad6213SVincent Jardin * silicon is about 1 ms; the datasheet does not have information. 71*25ad6213SVincent Jardin */ 72*25ad6213SVincent Jardin #define MPQ8646_NVM_RETRY_MAX 5 73*25ad6213SVincent Jardin #define MPQ8646_NVM_RETRY_DELAY_US_MIN 2000 74*25ad6213SVincent Jardin #define MPQ8646_NVM_RETRY_DELAY_US_MAX 4000 75*25ad6213SVincent Jardin 7624be24beSVincent Jardin #define MPQ8646_DEBUG(client, fmt, ...) \ 7724be24beSVincent Jardin dev_dbg(&(client)->dev, fmt, ##__VA_ARGS__) 7824be24beSVincent Jardin 7924be24beSVincent Jardin /* 8024be24beSVincent Jardin * Maximum legal value for VOUT_SCALE_LOOP (0x29) on the MPQ8646 silicon 8124be24beSVincent Jardin * The chip uses an 11-bit VOUT feedback-divider scale register. 8224be24beSVincent Jardin */ 8324be24beSVincent Jardin #define MPQ8646_VOUT_SCALE_LOOP_MAX GENMASK(10, 0) 8424be24beSVincent Jardin 8524be24beSVincent Jardin /* Forward declaration */ 8624be24beSVincent Jardin struct mpq8646_dbg_reg_ctx; 8724be24beSVincent Jardin 8824be24beSVincent Jardin /* Per-instance state */ 8924be24beSVincent Jardin struct mpq8646_priv { 9024be24beSVincent Jardin struct pmbus_driver_info info; /* must be first, container_of target */ 9124be24beSVincent Jardin struct i2c_client *client; 9224be24beSVincent Jardin 9324be24beSVincent Jardin /* Serialises CLEAR_LAST_FAULT sequences and PROTECTION_LAST reads */ 9424be24beSVincent Jardin struct mutex mps_lock; 9524be24beSVincent Jardin 9624be24beSVincent Jardin /* Set alarm_poll_interval_ms = 0 to disable. */ 9724be24beSVincent Jardin struct delayed_work alarm_poll_work; 9824be24beSVincent Jardin u32 alarm_poll_interval_ms; 9924be24beSVincent Jardin 10024be24beSVincent Jardin #ifdef CONFIG_DEBUG_FS 10124be24beSVincent Jardin /* the only debugfs file that can re-arm the poll worker */ 10224be24beSVincent Jardin struct dentry *dbg_poll; 10324be24beSVincent Jardin struct mpq8646_dbg_reg_ctx *dbg_reg_ctx; 10424be24beSVincent Jardin #endif 10524be24beSVincent Jardin }; 10624be24beSVincent Jardin 10724be24beSVincent Jardin static inline struct mpq8646_priv *mpq8646_priv_from_client(struct i2c_client *client) 10824be24beSVincent Jardin { 10924be24beSVincent Jardin const struct pmbus_driver_info *info = pmbus_get_driver_info(client); 11024be24beSVincent Jardin 11124be24beSVincent Jardin return container_of(info, struct mpq8646_priv, info); 11224be24beSVincent Jardin } 11324be24beSVincent Jardin 11424be24beSVincent Jardin /* 11524be24beSVincent Jardin * VID-mode m/b/R coefficients, same values as the mpq8785 driver for 11624be24beSVincent Jardin * the MPS VID encoding. Per the PMBus Direct formula used by 11724be24beSVincent Jardin * pmbus_core, X = (Y * 10^-R - b) / m, so m=64 / b=0 / R=1 yields 11824be24beSVincent Jardin * 1.5625 mV per LSB. 11924be24beSVincent Jardin */ 12024be24beSVincent Jardin #define MPQ8646_VID_M 64 12124be24beSVincent Jardin #define MPQ8646_VID_B 0 12224be24beSVincent Jardin #define MPQ8646_VID_R 1 12324be24beSVincent Jardin 12424be24beSVincent Jardin static int mpq8646_identify(struct i2c_client *client, 12524be24beSVincent Jardin struct pmbus_driver_info *info) 12624be24beSVincent Jardin { 12724be24beSVincent Jardin int vout_mode; 12824be24beSVincent Jardin 12924be24beSVincent Jardin vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); 13024be24beSVincent Jardin if (vout_mode < 0) 13124be24beSVincent Jardin return vout_mode; 13224be24beSVincent Jardin 13324be24beSVincent Jardin switch (vout_mode & PB_VOUT_MODE_MODE_MASK) { 13424be24beSVincent Jardin case PB_VOUT_MODE_LINEAR: 13524be24beSVincent Jardin info->format[PSC_VOLTAGE_OUT] = linear; 13624be24beSVincent Jardin break; 13724be24beSVincent Jardin case PB_VOUT_MODE_VID: 13824be24beSVincent Jardin case PB_VOUT_MODE_DIRECT: 13924be24beSVincent Jardin info->format[PSC_VOLTAGE_OUT] = direct; 14024be24beSVincent Jardin info->m[PSC_VOLTAGE_OUT] = MPQ8646_VID_M; 14124be24beSVincent Jardin info->b[PSC_VOLTAGE_OUT] = MPQ8646_VID_B; 14224be24beSVincent Jardin info->R[PSC_VOLTAGE_OUT] = MPQ8646_VID_R; 14324be24beSVincent Jardin break; 14424be24beSVincent Jardin default: 14524be24beSVincent Jardin return -ENODEV; 14624be24beSVincent Jardin } 14724be24beSVincent Jardin 14824be24beSVincent Jardin return 0; 14924be24beSVincent Jardin }; 15024be24beSVincent Jardin 15124be24beSVincent Jardin static int mpq8646_read_byte_data(struct i2c_client *client, int page, int reg) 15224be24beSVincent Jardin { 15324be24beSVincent Jardin int ret; 15424be24beSVincent Jardin 15524be24beSVincent Jardin MPQ8646_DEBUG(client, "read_byte_data page=%d reg=0x%02x\n", page, reg); 15624be24beSVincent Jardin 15724be24beSVincent Jardin switch (reg) { 15824be24beSVincent Jardin case PMBUS_VOUT_MODE: 15924be24beSVincent Jardin ret = pmbus_read_byte_data(client, page, reg); 16024be24beSVincent Jardin MPQ8646_DEBUG(client, " VOUT_MODE raw=0x%02x ret=%d\n", ret, ret); 16124be24beSVincent Jardin if (ret < 0) 16224be24beSVincent Jardin return ret; 16324be24beSVincent Jardin 16424be24beSVincent Jardin if ((ret & PB_VOUT_MODE_MODE_MASK) == PB_VOUT_MODE_VID) 16524be24beSVincent Jardin return PB_VOUT_MODE_DIRECT; 16624be24beSVincent Jardin 16724be24beSVincent Jardin return ret; 16824be24beSVincent Jardin case PMBUS_STATUS_BYTE: 16924be24beSVincent Jardin case PMBUS_STATUS_CML: 17024be24beSVincent Jardin case PMBUS_STATUS_OTHER: 17124be24beSVincent Jardin case PMBUS_STATUS_MFR_SPECIFIC: 17224be24beSVincent Jardin case PMBUS_STATUS_FAN_12: 17324be24beSVincent Jardin case PMBUS_STATUS_FAN_34: 17424be24beSVincent Jardin return -ENXIO; 17524be24beSVincent Jardin case PMBUS_MFR_LOCATION: 17624be24beSVincent Jardin case PMBUS_MFR_DATE: 17724be24beSVincent Jardin case PMBUS_MFR_SERIAL: 17824be24beSVincent Jardin case PMBUS_IC_DEVICE_ID: 17924be24beSVincent Jardin case PMBUS_IC_DEVICE_REV: 18024be24beSVincent Jardin MPQ8646_DEBUG(client, " unsupported mfr-info reg 0x%02x -> ENXIO\n", reg); 18124be24beSVincent Jardin return -ENXIO; 18224be24beSVincent Jardin default: 18324be24beSVincent Jardin return -ENODATA; 18424be24beSVincent Jardin } 18524be24beSVincent Jardin } 18624be24beSVincent Jardin 18724be24beSVincent Jardin /* 18824be24beSVincent Jardin * Reference: ltc2978.c::ltc2978_write_word_data which uses the 18924be24beSVincent Jardin * same virtual-register channel for its real chip-side peak reset. 19024be24beSVincent Jardin */ 19124be24beSVincent Jardin static int mpq8646_write_word_data(struct i2c_client *client, int page, 19224be24beSVincent Jardin int reg, u16 word) 19324be24beSVincent Jardin { 19424be24beSVincent Jardin struct mpq8646_priv *priv = mpq8646_priv_from_client(client); 19524be24beSVincent Jardin int rc; 19624be24beSVincent Jardin 19724be24beSVincent Jardin switch (reg) { 19824be24beSVincent Jardin case PMBUS_VIRT_RESET_VIN_HISTORY: 19924be24beSVincent Jardin case PMBUS_VIRT_RESET_VOUT_HISTORY: 20024be24beSVincent Jardin case PMBUS_VIRT_RESET_IOUT_HISTORY: 20124be24beSVincent Jardin case PMBUS_VIRT_RESET_TEMP_HISTORY: 20224be24beSVincent Jardin MPQ8646_DEBUG(client, "reset_history virt reg=0x%04x -> CLEAR_FAULTS\n", 20324be24beSVincent Jardin reg); 20424be24beSVincent Jardin rc = i2c_smbus_write_byte(priv->client, PMBUS_CLEAR_FAULTS); 20524be24beSVincent Jardin if (rc < 0) 20624be24beSVincent Jardin MPQ8646_DEBUG(client, " CLEAR_FAULTS rc=%d\n", rc); 20724be24beSVincent Jardin return rc < 0 ? rc : 0; 20824be24beSVincent Jardin default: 20924be24beSVincent Jardin return -ENODATA; /* let pmbus_core do the direct write */ 21024be24beSVincent Jardin } 21124be24beSVincent Jardin } 21224be24beSVincent Jardin 21324be24beSVincent Jardin static int mpq8646_read_word_data(struct i2c_client *client, int page, 21424be24beSVincent Jardin int phase, int reg) 21524be24beSVincent Jardin { 21624be24beSVincent Jardin int rc; 21724be24beSVincent Jardin 21824be24beSVincent Jardin MPQ8646_DEBUG(client, "read_word_data page=%d phase=%d reg=0x%02x\n", 21924be24beSVincent Jardin page, phase, reg); 22024be24beSVincent Jardin 22124be24beSVincent Jardin switch (reg) { 22224be24beSVincent Jardin case PMBUS_READ_VIN: 22324be24beSVincent Jardin case PMBUS_READ_VOUT: 22424be24beSVincent Jardin case PMBUS_READ_IOUT: 22524be24beSVincent Jardin case PMBUS_READ_TEMPERATURE_1: 22624be24beSVincent Jardin case PMBUS_STATUS_WORD: 22724be24beSVincent Jardin case PMBUS_VOUT_OV_FAULT_LIMIT: 22824be24beSVincent Jardin case PMBUS_VOUT_OV_WARN_LIMIT: 22924be24beSVincent Jardin case PMBUS_VOUT_UV_WARN_LIMIT: 23024be24beSVincent Jardin case PMBUS_VOUT_UV_FAULT_LIMIT: 23124be24beSVincent Jardin case PMBUS_IOUT_OC_FAULT_LIMIT: 23224be24beSVincent Jardin case PMBUS_IOUT_OC_WARN_LIMIT: 23324be24beSVincent Jardin case PMBUS_OT_FAULT_LIMIT: 23424be24beSVincent Jardin case PMBUS_OT_WARN_LIMIT: 23524be24beSVincent Jardin case PMBUS_VIN_OV_FAULT_LIMIT: 23624be24beSVincent Jardin case PMBUS_VIN_OV_WARN_LIMIT: 23724be24beSVincent Jardin case PMBUS_VIN_UV_WARN_LIMIT: 23824be24beSVincent Jardin case PMBUS_VIN_UV_FAULT_LIMIT: 23924be24beSVincent Jardin case PMBUS_MFR_VIN_MAX: 24024be24beSVincent Jardin case PMBUS_MFR_VOUT_MAX: 24124be24beSVincent Jardin case PMBUS_MFR_IOUT_MAX: 24224be24beSVincent Jardin case PMBUS_MFR_MAX_TEMP_1: 24324be24beSVincent Jardin break; 24424be24beSVincent Jardin case PMBUS_VIRT_RESET_VIN_HISTORY: 24524be24beSVincent Jardin case PMBUS_VIRT_RESET_VOUT_HISTORY: 24624be24beSVincent Jardin case PMBUS_VIRT_RESET_IOUT_HISTORY: 24724be24beSVincent Jardin case PMBUS_VIRT_RESET_TEMP_HISTORY: 24824be24beSVincent Jardin return 0; 24924be24beSVincent Jardin default: 25024be24beSVincent Jardin return -ENODATA; 25124be24beSVincent Jardin } 25224be24beSVincent Jardin 25324be24beSVincent Jardin rc = i2c_smbus_read_word_data(client, reg); 25424be24beSVincent Jardin 25524be24beSVincent Jardin MPQ8646_DEBUG(client, " reg=0x%02x rc=%d\n", reg, rc); 25624be24beSVincent Jardin return rc; 25724be24beSVincent Jardin } 25824be24beSVincent Jardin 25924be24beSVincent Jardin static struct pmbus_driver_info mpq8646_info = { 26024be24beSVincent Jardin .pages = 1, 26124be24beSVincent Jardin .format[PSC_VOLTAGE_IN] = direct, 26224be24beSVincent Jardin .format[PSC_CURRENT_OUT] = direct, 26324be24beSVincent Jardin .format[PSC_TEMPERATURE] = direct, 26424be24beSVincent Jardin .m[PSC_VOLTAGE_IN] = 4, 26524be24beSVincent Jardin .b[PSC_VOLTAGE_IN] = 0, 26624be24beSVincent Jardin .R[PSC_VOLTAGE_IN] = 1, 26724be24beSVincent Jardin .m[PSC_CURRENT_OUT] = 16, 26824be24beSVincent Jardin .b[PSC_CURRENT_OUT] = 0, 26924be24beSVincent Jardin .R[PSC_CURRENT_OUT] = 0, 27024be24beSVincent Jardin .m[PSC_TEMPERATURE] = 1, 27124be24beSVincent Jardin .b[PSC_TEMPERATURE] = 0, 27224be24beSVincent Jardin .R[PSC_TEMPERATURE] = 0, 27324be24beSVincent Jardin .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | 27424be24beSVincent Jardin PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP | 27524be24beSVincent Jardin PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_VOUT | 27624be24beSVincent Jardin PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_TEMP, 27724be24beSVincent Jardin }; 27824be24beSVincent Jardin 27924be24beSVincent Jardin #if IS_ENABLED(CONFIG_REGULATOR) 28024be24beSVincent Jardin static const struct regulator_desc mpq8646_reg_desc[] = { 28124be24beSVincent Jardin PMBUS_REGULATOR("vout", 0), 28224be24beSVincent Jardin }; 28324be24beSVincent Jardin #endif /* CONFIG_REGULATOR */ 28424be24beSVincent Jardin 28524be24beSVincent Jardin #if IS_ENABLED(CONFIG_NVMEM) 28624be24beSVincent Jardin /* 28724be24beSVincent Jardin * Expose using 28824be24beSVincent Jardin * /sys/bus/nvmem/devices/<i2c-name>/nvmem 28924be24beSVincent Jardin * 29024be24beSVincent Jardin * Layout (16 bytes): 29124be24beSVincent Jardin * offset 0..1 PROTECTION_LAST (0xFB) word, LE -- NVM 29224be24beSVincent Jardin * offset 2..3 MFR_RETRY_TIMES (0xF4) word, LE -- NVM 29324be24beSVincent Jardin * offset 4..5 MFR_CONFIG_ID (0xC0) word, LE -- NVM 29424be24beSVincent Jardin * offset 6..7 MFR_VBOOT_CFG (0xFC) word, LE -- NVM 29524be24beSVincent Jardin * offset 8 MFR_SILICON_REV (0xC3) byte -- NVM 29624be24beSVincent Jardin * offset 9..15 reserved (zero-fill, leaves room for additions) 29724be24beSVincent Jardin */ 29824be24beSVincent Jardin #define MPQ8646_NVMEM_SIZE 16 29924be24beSVincent Jardin 30024be24beSVincent Jardin struct mpq8646_nvmem_entry { 30124be24beSVincent Jardin unsigned int off; 30224be24beSVincent Jardin u8 reg; 30324be24beSVincent Jardin bool is_word; 30424be24beSVincent Jardin }; 30524be24beSVincent Jardin 30624be24beSVincent Jardin static const struct mpq8646_nvmem_entry mpq8646_nvmem_map[] = { 30724be24beSVincent Jardin { 0, MPS_PROTECTION_LAST, true }, 30824be24beSVincent Jardin { 2, MPS_MFR_RETRY_TIMES, true }, 30924be24beSVincent Jardin { 4, MPS_MFR_CONFIG_ID, true }, 31024be24beSVincent Jardin { 6, MPS_MFR_VBOOT_CFG, true }, 31124be24beSVincent Jardin { 8, MPS_MFR_SILICON_REV, false }, 31224be24beSVincent Jardin }; 31324be24beSVincent Jardin 31424be24beSVincent Jardin static int mpq8646_nvmem_read(void *data, unsigned int offset, void *val, 31524be24beSVincent Jardin size_t bytes) 31624be24beSVincent Jardin { 31724be24beSVincent Jardin struct mpq8646_priv *priv = data; 31824be24beSVincent Jardin u8 *out = val; 31924be24beSVincent Jardin size_t i; 32024be24beSVincent Jardin 32124be24beSVincent Jardin if (offset >= MPQ8646_NVMEM_SIZE) 32224be24beSVincent Jardin return -EINVAL; 32324be24beSVincent Jardin if (offset + bytes > MPQ8646_NVMEM_SIZE) 32424be24beSVincent Jardin bytes = MPQ8646_NVMEM_SIZE - offset; 32524be24beSVincent Jardin 32624be24beSVincent Jardin memset(out, 0, bytes); 32724be24beSVincent Jardin 32824be24beSVincent Jardin guard(mutex)(&priv->mps_lock); 32924be24beSVincent Jardin for (i = 0; i < ARRAY_SIZE(mpq8646_nvmem_map); i++) { 33024be24beSVincent Jardin const struct mpq8646_nvmem_entry *e = &mpq8646_nvmem_map[i]; 33124be24beSVincent Jardin unsigned int e_start = e->off; 33224be24beSVincent Jardin unsigned int e_end = e_start + (e->is_word ? 2 : 1); 33324be24beSVincent Jardin u8 raw[2]; 33424be24beSVincent Jardin int rc; 33524be24beSVincent Jardin unsigned int j; 33624be24beSVincent Jardin 33724be24beSVincent Jardin if (e_end <= offset || e_start >= offset + bytes) 33824be24beSVincent Jardin continue; /* outside requested slice */ 33924be24beSVincent Jardin 34024be24beSVincent Jardin if (e->is_word) 34124be24beSVincent Jardin rc = i2c_smbus_read_word_data(priv->client, e->reg); 34224be24beSVincent Jardin else 34324be24beSVincent Jardin rc = i2c_smbus_read_byte_data(priv->client, e->reg); 34424be24beSVincent Jardin if (rc < 0) 34524be24beSVincent Jardin continue; /* leave the zero-fill in place */ 34624be24beSVincent Jardin 34724be24beSVincent Jardin raw[0] = rc & 0xff; 34824be24beSVincent Jardin raw[1] = (rc >> 8) & 0xff; 34924be24beSVincent Jardin 35024be24beSVincent Jardin for (j = 0; j < e_end - e_start; j++) { 35124be24beSVincent Jardin unsigned int abs = e_start + j; 35224be24beSVincent Jardin 35324be24beSVincent Jardin if (abs >= offset && abs < offset + bytes) 35424be24beSVincent Jardin out[abs - offset] = raw[j]; 35524be24beSVincent Jardin } 35624be24beSVincent Jardin } 35724be24beSVincent Jardin return 0; 35824be24beSVincent Jardin } 35924be24beSVincent Jardin #endif /* CONFIG_NVMEM */ 36024be24beSVincent Jardin 36124be24beSVincent Jardin static const struct i2c_device_id mpq8646_id[] = { 36224be24beSVincent Jardin { .name = "mpq8646" }, 36324be24beSVincent Jardin { }, 36424be24beSVincent Jardin }; 36524be24beSVincent Jardin MODULE_DEVICE_TABLE(i2c, mpq8646_id); 36624be24beSVincent Jardin 36724be24beSVincent Jardin static const struct of_device_id __maybe_unused mpq8646_of_match[] = { 36824be24beSVincent Jardin { .compatible = "mps,mpq8646" }, 36924be24beSVincent Jardin {} 37024be24beSVincent Jardin }; 37124be24beSVincent Jardin MODULE_DEVICE_TABLE(of, mpq8646_of_match); 37224be24beSVincent Jardin 37324be24beSVincent Jardin static struct pmbus_platform_data mpq8646_no_pec_pdata = { 37424be24beSVincent Jardin .flags = PMBUS_NO_CAPABILITY, 37524be24beSVincent Jardin }; 37624be24beSVincent Jardin 37724be24beSVincent Jardin #ifdef CONFIG_DEBUG_FS 37824be24beSVincent Jardin /* 37924be24beSVincent Jardin * Read-only decode cases in the client's pmbus debugfs directory: 38024be24beSVincent Jardin * the MPS-specific STATUS_WORD and PROTECTION_LAST bit decode plus the 38124be24beSVincent Jardin * identity/timing registers. 38224be24beSVincent Jardin */ 38324be24beSVincent Jardin 38424be24beSVincent Jardin struct mpq_status_bit { 38524be24beSVincent Jardin u16 mask; 38624be24beSVincent Jardin const char *name; 38724be24beSVincent Jardin }; 38824be24beSVincent Jardin 38924be24beSVincent Jardin static const struct mpq_status_bit mpq8646_status_word_bits[] = { 39024be24beSVincent Jardin { PB_STATUS_VOUT, "VOUT" }, 39124be24beSVincent Jardin { PB_STATUS_IOUT_POUT, "IOUT_POUT" }, 39224be24beSVincent Jardin { PB_STATUS_INPUT, "INPUT" }, 39324be24beSVincent Jardin { PB_STATUS_WORD_MFR, "NVM_SUMMARY" }, 39424be24beSVincent Jardin { PB_STATUS_POWER_GOOD_N, "POWER_GOOD#" }, 39524be24beSVincent Jardin { PB_STATUS_FANS, "FANS" }, 39624be24beSVincent Jardin { PB_STATUS_OTHER, "OTHER" }, 39724be24beSVincent Jardin { PB_STATUS_UNKNOWN, "WATCH_DOG" }, 39824be24beSVincent Jardin { PB_STATUS_BUSY, "BUSY" }, 39924be24beSVincent Jardin { PB_STATUS_OFF, "OFF" }, 40024be24beSVincent Jardin { PB_STATUS_VOUT_OV, "VOUT_OV_FAULT" }, 40124be24beSVincent Jardin { PB_STATUS_IOUT_OC, "IOUT_OC_FAULT" }, 40224be24beSVincent Jardin { PB_STATUS_VIN_UV, "VIN_UV_FAULT" }, 40324be24beSVincent Jardin { PB_STATUS_TEMPERATURE, "TEMP" }, 40424be24beSVincent Jardin { PB_STATUS_CML, "CML" }, 40524be24beSVincent Jardin { PB_STATUS_NONE_ABOVE, "DRMOS_FAULT" }, 40624be24beSVincent Jardin { /* sentinel */ } 40724be24beSVincent Jardin }; 40824be24beSVincent Jardin 40924be24beSVincent Jardin /* PROTECTION_LAST (0xFB) bit names, it survives chip POR */ 41024be24beSVincent Jardin static const struct mpq_status_bit mpq8646_protection_last_bits[] = { 41124be24beSVincent Jardin { BIT(15), "INIT_FAULT" }, 41224be24beSVincent Jardin { BIT(14), "NVM_CRC_ERROR" }, 41324be24beSVincent Jardin { BIT(13), "NVM_FAULT" }, 41424be24beSVincent Jardin { BIT(12), "OC_PHASE_FAULT" }, 41524be24beSVincent Jardin { BIT(11), "OTP_SELF_FAULT" }, 41624be24beSVincent Jardin { BIT(9), "SWITCH_PRD_FAULT" }, 41724be24beSVincent Jardin { BIT(8), "VIN_OV_FAULT" }, 41824be24beSVincent Jardin { BIT(7), "VOUT_OV_FAULT" }, 41924be24beSVincent Jardin { BIT(6), "VOUT_UV_FAULT" }, 42024be24beSVincent Jardin { BIT(5), "OC_TOT_FAULT" }, 42124be24beSVincent Jardin { BIT(4), "VIN_UVLO_FAULT" }, 42224be24beSVincent Jardin { BIT(3), "DRMOS_OTP" }, 42324be24beSVincent Jardin { /* sentinel */ } 42424be24beSVincent Jardin }; 42524be24beSVincent Jardin 42624be24beSVincent Jardin static void mpq8646_print_bits(struct seq_file *s, u16 v, 42724be24beSVincent Jardin const struct mpq_status_bit *tab) 42824be24beSVincent Jardin { 42924be24beSVincent Jardin const struct mpq_status_bit *t; 43024be24beSVincent Jardin bool first = true; 43124be24beSVincent Jardin 43224be24beSVincent Jardin seq_printf(s, "0x%04x", v); 43324be24beSVincent Jardin if (!v) { 43424be24beSVincent Jardin seq_puts(s, " [clean]\n"); 43524be24beSVincent Jardin return; 43624be24beSVincent Jardin } 43724be24beSVincent Jardin seq_puts(s, " ["); 43824be24beSVincent Jardin for (t = tab; t->mask; t++) { 43924be24beSVincent Jardin if (v & t->mask) { 44024be24beSVincent Jardin if (!first) 44124be24beSVincent Jardin seq_putc(s, ' '); 44224be24beSVincent Jardin seq_puts(s, t->name); 44324be24beSVincent Jardin first = false; 44424be24beSVincent Jardin } 44524be24beSVincent Jardin } 44624be24beSVincent Jardin seq_puts(s, "]\n"); 44724be24beSVincent Jardin } 44824be24beSVincent Jardin 44924be24beSVincent Jardin static int mpq8646_dbg_status_decoded_show(struct seq_file *s, void *unused) 45024be24beSVincent Jardin { 45124be24beSVincent Jardin struct mpq8646_priv *priv = s->private; 45224be24beSVincent Jardin int rc; 45324be24beSVincent Jardin 45424be24beSVincent Jardin scoped_guard(pmbus_lock, priv->client) 45524be24beSVincent Jardin rc = i2c_smbus_read_word_data(priv->client, PMBUS_STATUS_WORD); 45624be24beSVincent Jardin if (rc < 0) { 45724be24beSVincent Jardin seq_printf(s, "ERROR: STATUS_WORD read failed (%d)\n", rc); 45824be24beSVincent Jardin return 0; 45924be24beSVincent Jardin } 46024be24beSVincent Jardin seq_puts(s, "STATUS_WORD: "); 46124be24beSVincent Jardin mpq8646_print_bits(s, (u16)rc, mpq8646_status_word_bits); 46224be24beSVincent Jardin seq_puts(s, "(MPS extensions: bit12=NVM_SUMMARY, bit8=WATCH_DOG, bit0=DRMOS_FAULT)\n"); 46324be24beSVincent Jardin return 0; 46424be24beSVincent Jardin } 46524be24beSVincent Jardin DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_status_decoded); 46624be24beSVincent Jardin 46724be24beSVincent Jardin static int mpq8646_dbg_protection_last_show(struct seq_file *s, void *unused) 46824be24beSVincent Jardin { 46924be24beSVincent Jardin struct mpq8646_priv *priv = s->private; 47024be24beSVincent Jardin int rc; 47124be24beSVincent Jardin 47224be24beSVincent Jardin guard(pmbus_lock)(priv->client); 47324be24beSVincent Jardin scoped_guard(mutex, &priv->mps_lock) 47424be24beSVincent Jardin rc = i2c_smbus_read_word_data(priv->client, MPS_PROTECTION_LAST); 47524be24beSVincent Jardin 47624be24beSVincent Jardin if (rc < 0) { 47724be24beSVincent Jardin seq_printf(s, "ERROR: PROTECTION_LAST read failed (%d)\n", rc); 47824be24beSVincent Jardin return 0; 47924be24beSVincent Jardin } 48024be24beSVincent Jardin seq_puts(s, "PROTECTION_LAST: "); 48124be24beSVincent Jardin mpq8646_print_bits(s, (u16)rc, mpq8646_protection_last_bits); 48224be24beSVincent Jardin seq_puts(s, "(NVM-backed, survives chip POR)\n"); 48324be24beSVincent Jardin return 0; 48424be24beSVincent Jardin } 48524be24beSVincent Jardin DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_protection_last); 48624be24beSVincent Jardin 48724be24beSVincent Jardin struct mpq8646_dbg_reg { 48824be24beSVincent Jardin u8 reg; 48924be24beSVincent Jardin bool is_word; 49024be24beSVincent Jardin const char *name; 49124be24beSVincent Jardin }; 49224be24beSVincent Jardin 49324be24beSVincent Jardin static const struct mpq8646_dbg_reg mpq8646_dbg_regs[] = { 49424be24beSVincent Jardin /* MPS vendor extensions (read-only identity / observability) */ 49524be24beSVincent Jardin { MPS_MFR_CONFIG_ID, true, "mfr_config_id" }, 49624be24beSVincent Jardin { MPS_MFR_CONFIG_CODE_REV, true, "mfr_config_code_rev" }, 49724be24beSVincent Jardin { MPS_MFR_SILICON_REV, false, "mfr_silicon_rev" }, 49824be24beSVincent Jardin { MPS_MFR_RETRY_TIMES, true, "mfr_retry_times" }, 49924be24beSVincent Jardin { MPS_MFR_VBOOT_CFG, true, "mfr_vboot_cfg" }, 50024be24beSVincent Jardin /* PMBus 1.3 standard timing / UVLO (read-only introspection) */ 50124be24beSVincent Jardin { PMBUS_VIN_ON, true, "vin_on" }, 50224be24beSVincent Jardin { PMBUS_VIN_OFF, true, "vin_off" }, 50324be24beSVincent Jardin { PMBUS_TON_DELAY, true, "ton_delay" }, 50424be24beSVincent Jardin { PMBUS_TON_RISE, true, "ton_rise" }, 50524be24beSVincent Jardin { PMBUS_TOFF_DELAY, true, "toff_delay" }, 50624be24beSVincent Jardin { PMBUS_TOFF_FALL, true, "toff_fall" }, 50724be24beSVincent Jardin }; 50824be24beSVincent Jardin 50924be24beSVincent Jardin struct mpq8646_dbg_reg_ctx { 51024be24beSVincent Jardin struct mpq8646_priv *priv; 51124be24beSVincent Jardin const struct mpq8646_dbg_reg *desc; 51224be24beSVincent Jardin }; 51324be24beSVincent Jardin 51424be24beSVincent Jardin static int mpq8646_dbg_reg_show(struct seq_file *s, void *unused) 51524be24beSVincent Jardin { 51624be24beSVincent Jardin struct mpq8646_dbg_reg_ctx *ctx = s->private; 51724be24beSVincent Jardin int rc; 51824be24beSVincent Jardin 51924be24beSVincent Jardin guard(pmbus_lock)(ctx->priv->client); 52024be24beSVincent Jardin if (ctx->desc->is_word) 52124be24beSVincent Jardin rc = i2c_smbus_read_word_data(ctx->priv->client, 52224be24beSVincent Jardin ctx->desc->reg); 52324be24beSVincent Jardin else 52424be24beSVincent Jardin rc = i2c_smbus_read_byte_data(ctx->priv->client, 52524be24beSVincent Jardin ctx->desc->reg); 52624be24beSVincent Jardin 52724be24beSVincent Jardin if (rc < 0) { 52824be24beSVincent Jardin seq_printf(s, "ERROR: reg 0x%02x (%s) read failed (%d)\n", 52924be24beSVincent Jardin ctx->desc->reg, ctx->desc->name, rc); 53024be24beSVincent Jardin return 0; 53124be24beSVincent Jardin } 53224be24beSVincent Jardin seq_printf(s, "0x%0*x\n", ctx->desc->is_word ? 4 : 2, rc); 53324be24beSVincent Jardin return 0; 53424be24beSVincent Jardin } 53524be24beSVincent Jardin DEFINE_SHOW_ATTRIBUTE(mpq8646_dbg_reg); 53624be24beSVincent Jardin 537*25ad6213SVincent Jardin #ifdef CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE 538*25ad6213SVincent Jardin /* 539*25ad6213SVincent Jardin * Write/provisioning data, disabled by default: NVM commit and 540*25ad6213SVincent Jardin * revert, the CLEAR_LAST_FAULT sequences and a small set of named 541*25ad6213SVincent Jardin * writable registers. 542*25ad6213SVincent Jardin */ 543*25ad6213SVincent Jardin 544*25ad6213SVincent Jardin static void mpq8646_unsafe_banner(struct mpq8646_priv *priv) 545*25ad6213SVincent Jardin { 546*25ad6213SVincent Jardin dev_warn(&priv->client->dev, 547*25ad6213SVincent Jardin "**********************************************************\n" 548*25ad6213SVincent Jardin "** WARNING WARNING WARNING WARNING WARNING WARNING **\n" 549*25ad6213SVincent Jardin "** **\n" 550*25ad6213SVincent Jardin "** The MPQ8646 provisioning debugfs writes are enabled. **\n" 551*25ad6213SVincent Jardin "** Wrong register writes can and likely will physically **\n" 552*25ad6213SVincent Jardin "** damage or destroy the chip and/or the board. **\n" 553*25ad6213SVincent Jardin "** **\n" 554*25ad6213SVincent Jardin "** If you see this message and you are not debugging **\n" 555*25ad6213SVincent Jardin "** the kernel, report this immediately to your system **\n" 556*25ad6213SVincent Jardin "** administrator! **\n" 557*25ad6213SVincent Jardin "** **\n" 558*25ad6213SVincent Jardin "** WARNING WARNING WARNING WARNING WARNING WARNING **\n" 559*25ad6213SVincent Jardin "**********************************************************\n"); 560*25ad6213SVincent Jardin } 561*25ad6213SVincent Jardin 562*25ad6213SVincent Jardin static int mpq8646_dbg_clear_protection_last(void *data, u64 val) 563*25ad6213SVincent Jardin { 564*25ad6213SVincent Jardin struct mpq8646_priv *priv = data; 565*25ad6213SVincent Jardin int rc; 566*25ad6213SVincent Jardin 567*25ad6213SVincent Jardin if (!val) 568*25ad6213SVincent Jardin return 0; 569*25ad6213SVincent Jardin 570*25ad6213SVincent Jardin guard(pmbus_lock)(priv->client); 571*25ad6213SVincent Jardin scoped_guard(mutex, &priv->mps_lock) 572*25ad6213SVincent Jardin rc = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT); 573*25ad6213SVincent Jardin if (rc < 0) 574*25ad6213SVincent Jardin dev_warn(&priv->client->dev, 575*25ad6213SVincent Jardin "clear_protection_last: CLEAR_LAST_FAULT write failed (%d)\n", 576*25ad6213SVincent Jardin rc); 577*25ad6213SVincent Jardin return rc; 578*25ad6213SVincent Jardin } 579*25ad6213SVincent Jardin DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_fops, 580*25ad6213SVincent Jardin NULL, mpq8646_dbg_clear_protection_last, "%llu\n"); 581*25ad6213SVincent Jardin 582*25ad6213SVincent Jardin static int mpq8646_dbg_clear_protection_last_force(void *data, u64 val) 583*25ad6213SVincent Jardin { 584*25ad6213SVincent Jardin struct mpq8646_priv *priv = data; 585*25ad6213SVincent Jardin int rc, ret; 586*25ad6213SVincent Jardin int wp_orig, cfg_orig; 587*25ad6213SVincent Jardin 588*25ad6213SVincent Jardin if (!val) 589*25ad6213SVincent Jardin return 0; 590*25ad6213SVincent Jardin 591*25ad6213SVincent Jardin guard(pmbus_lock)(priv->client); 592*25ad6213SVincent Jardin guard(mutex)(&priv->mps_lock); 593*25ad6213SVincent Jardin 594*25ad6213SVincent Jardin wp_orig = i2c_smbus_read_byte_data(priv->client, PMBUS_WRITE_PROTECT); 595*25ad6213SVincent Jardin if (wp_orig < 0) { 596*25ad6213SVincent Jardin dev_warn(&priv->client->dev, 597*25ad6213SVincent Jardin "clear_protection_last_force: WRITE_PROTECT read failed (%d), aborting\n", 598*25ad6213SVincent Jardin wp_orig); 599*25ad6213SVincent Jardin return wp_orig; 600*25ad6213SVincent Jardin } 601*25ad6213SVincent Jardin cfg_orig = i2c_smbus_read_word_data(priv->client, MPS_MFR_CFG_EXT); 602*25ad6213SVincent Jardin if (cfg_orig < 0) { 603*25ad6213SVincent Jardin dev_warn(&priv->client->dev, 604*25ad6213SVincent Jardin "clear_protection_last_force: MFR_CFG_EXT read failed (%d), aborting\n", 605*25ad6213SVincent Jardin cfg_orig); 606*25ad6213SVincent Jardin return cfg_orig; 607*25ad6213SVincent Jardin } 608*25ad6213SVincent Jardin 609*25ad6213SVincent Jardin if (wp_orig != 0) { 610*25ad6213SVincent Jardin rc = i2c_smbus_write_byte_data(priv->client, 611*25ad6213SVincent Jardin PMBUS_WRITE_PROTECT, 0); 612*25ad6213SVincent Jardin if (rc < 0) { 613*25ad6213SVincent Jardin dev_warn(&priv->client->dev, 614*25ad6213SVincent Jardin "clear_protection_last_force: WP clear failed (%d), aborting\n", 615*25ad6213SVincent Jardin rc); 616*25ad6213SVincent Jardin return rc; 617*25ad6213SVincent Jardin } 618*25ad6213SVincent Jardin } 619*25ad6213SVincent Jardin 620*25ad6213SVincent Jardin ret = i2c_smbus_write_word_data(priv->client, MPS_MFR_CFG_EXT, 621*25ad6213SVincent Jardin (u16)cfg_orig | MPS_MFR_CFG_EXT_CLR_LAST_EN); 622*25ad6213SVincent Jardin if (ret < 0) { 623*25ad6213SVincent Jardin dev_warn(&priv->client->dev, 624*25ad6213SVincent Jardin "clear_protection_last_force: gate open failed (%d)\n", 625*25ad6213SVincent Jardin ret); 626*25ad6213SVincent Jardin } else { 627*25ad6213SVincent Jardin ret = i2c_smbus_write_byte(priv->client, MPS_CLEAR_LAST_FAULT); 628*25ad6213SVincent Jardin if (ret < 0) 629*25ad6213SVincent Jardin dev_warn(&priv->client->dev, 630*25ad6213SVincent Jardin "clear_protection_last_force: CLEAR_LAST_FAULT failed (%d) even with gate open\n", 631*25ad6213SVincent Jardin ret); 632*25ad6213SVincent Jardin 633*25ad6213SVincent Jardin for (int attempt = 0; attempt < MPQ8646_NVM_RETRY_MAX; attempt++) { 634*25ad6213SVincent Jardin rc = i2c_smbus_write_word_data(priv->client, 635*25ad6213SVincent Jardin MPS_MFR_CFG_EXT, 636*25ad6213SVincent Jardin (u16)cfg_orig); 637*25ad6213SVincent Jardin if (rc >= 0) 638*25ad6213SVincent Jardin break; 639*25ad6213SVincent Jardin usleep_range(MPQ8646_NVM_RETRY_DELAY_US_MIN, 640*25ad6213SVincent Jardin MPQ8646_NVM_RETRY_DELAY_US_MAX); 641*25ad6213SVincent Jardin } 642*25ad6213SVincent Jardin if (rc < 0) { 643*25ad6213SVincent Jardin dev_warn(&priv->client->dev, 644*25ad6213SVincent Jardin "clear_protection_last_force: MFR_CFG_EXT restore failed after retries (%d): gate may stay open until POR\n", 645*25ad6213SVincent Jardin rc); 646*25ad6213SVincent Jardin if (ret == 0) 647*25ad6213SVincent Jardin ret = rc; 648*25ad6213SVincent Jardin } 649*25ad6213SVincent Jardin } 650*25ad6213SVincent Jardin 651*25ad6213SVincent Jardin if (wp_orig != 0) { 652*25ad6213SVincent Jardin rc = i2c_smbus_write_byte_data(priv->client, 653*25ad6213SVincent Jardin PMBUS_WRITE_PROTECT, 654*25ad6213SVincent Jardin (u8)wp_orig); 655*25ad6213SVincent Jardin if (rc < 0) { 656*25ad6213SVincent Jardin dev_warn(&priv->client->dev, 657*25ad6213SVincent Jardin "clear_protection_last_force: WP restore failed (%d)\n", 658*25ad6213SVincent Jardin rc); 659*25ad6213SVincent Jardin if (ret == 0) 660*25ad6213SVincent Jardin ret = rc; 661*25ad6213SVincent Jardin } 662*25ad6213SVincent Jardin } 663*25ad6213SVincent Jardin return ret; 664*25ad6213SVincent Jardin } 665*25ad6213SVincent Jardin DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_clear_protection_last_force_fops, 666*25ad6213SVincent Jardin NULL, mpq8646_dbg_clear_protection_last_force, "%llu\n"); 667*25ad6213SVincent Jardin 668*25ad6213SVincent Jardin static int mpq8646_dbg_store_all(void *data, u64 val) 669*25ad6213SVincent Jardin { 670*25ad6213SVincent Jardin struct mpq8646_priv *priv = data; 671*25ad6213SVincent Jardin int rc; 672*25ad6213SVincent Jardin 673*25ad6213SVincent Jardin if (!val) 674*25ad6213SVincent Jardin return 0; 675*25ad6213SVincent Jardin 676*25ad6213SVincent Jardin guard(pmbus_lock)(priv->client); 677*25ad6213SVincent Jardin scoped_guard(mutex, &priv->mps_lock) 678*25ad6213SVincent Jardin rc = i2c_smbus_write_byte(priv->client, PMBUS_STORE_USER_ALL); 679*25ad6213SVincent Jardin if (rc < 0) 680*25ad6213SVincent Jardin dev_warn(&priv->client->dev, 681*25ad6213SVincent Jardin "store_all: STORE_USER_ALL (0x15) write failed (%d)\n", 682*25ad6213SVincent Jardin rc); 683*25ad6213SVincent Jardin return rc; 684*25ad6213SVincent Jardin } 685*25ad6213SVincent Jardin DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_store_all_fops, 686*25ad6213SVincent Jardin NULL, mpq8646_dbg_store_all, "%llu\n"); 687*25ad6213SVincent Jardin 688*25ad6213SVincent Jardin static int mpq8646_dbg_restore_all(void *data, u64 val) 689*25ad6213SVincent Jardin { 690*25ad6213SVincent Jardin struct mpq8646_priv *priv = data; 691*25ad6213SVincent Jardin int rc; 692*25ad6213SVincent Jardin 693*25ad6213SVincent Jardin if (!val) 694*25ad6213SVincent Jardin return 0; 695*25ad6213SVincent Jardin 696*25ad6213SVincent Jardin guard(pmbus_lock)(priv->client); 697*25ad6213SVincent Jardin scoped_guard(mutex, &priv->mps_lock) 698*25ad6213SVincent Jardin rc = i2c_smbus_write_byte(priv->client, PMBUS_RESTORE_USER_ALL); 699*25ad6213SVincent Jardin if (rc < 0) 700*25ad6213SVincent Jardin dev_warn(&priv->client->dev, 701*25ad6213SVincent Jardin "restore_all: RESTORE_USER_ALL (0x16) write failed (%d)\n", 702*25ad6213SVincent Jardin rc); 703*25ad6213SVincent Jardin return rc; 704*25ad6213SVincent Jardin } 705*25ad6213SVincent Jardin DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_restore_all_fops, 706*25ad6213SVincent Jardin NULL, mpq8646_dbg_restore_all, "%llu\n"); 707*25ad6213SVincent Jardin 708*25ad6213SVincent Jardin static const struct mpq8646_dbg_reg mpq8646_dbg_regs_unsafe[] = { 709*25ad6213SVincent Jardin /* PMBus 1.3 control / margin */ 710*25ad6213SVincent Jardin { PMBUS_ON_OFF_CONFIG, false, "on_off_config" }, 711*25ad6213SVincent Jardin { PMBUS_VOUT_MARGIN_HIGH, true, "vout_margin_high" }, 712*25ad6213SVincent Jardin { PMBUS_VOUT_MARGIN_LOW, true, "vout_margin_low" }, 713*25ad6213SVincent Jardin /* MPS PMBus-level write-protect */ 714*25ad6213SVincent Jardin { MPS_MFR_PMBUS_LOCK, true, "mfr_pmbus_lock" }, 715*25ad6213SVincent Jardin /* MPS user-writable product revision */ 716*25ad6213SVincent Jardin { MPS_MFR_PRODUCT_REV_USER, true, "mfr_product_rev_user" }, 717*25ad6213SVincent Jardin }; 718*25ad6213SVincent Jardin 719*25ad6213SVincent Jardin static int mpq8646_dbg_reg_get(void *data, u64 *val) 720*25ad6213SVincent Jardin { 721*25ad6213SVincent Jardin struct mpq8646_dbg_reg_ctx *ctx = data; 722*25ad6213SVincent Jardin int rc; 723*25ad6213SVincent Jardin 724*25ad6213SVincent Jardin guard(pmbus_lock)(ctx->priv->client); 725*25ad6213SVincent Jardin if (ctx->desc->is_word) 726*25ad6213SVincent Jardin rc = i2c_smbus_read_word_data(ctx->priv->client, 727*25ad6213SVincent Jardin ctx->desc->reg); 728*25ad6213SVincent Jardin else 729*25ad6213SVincent Jardin rc = i2c_smbus_read_byte_data(ctx->priv->client, 730*25ad6213SVincent Jardin ctx->desc->reg); 731*25ad6213SVincent Jardin if (rc < 0) 732*25ad6213SVincent Jardin return rc; 733*25ad6213SVincent Jardin *val = rc; 734*25ad6213SVincent Jardin return 0; 735*25ad6213SVincent Jardin } 736*25ad6213SVincent Jardin 737*25ad6213SVincent Jardin static int mpq8646_dbg_reg_set(void *data, u64 val) 738*25ad6213SVincent Jardin { 739*25ad6213SVincent Jardin struct mpq8646_dbg_reg_ctx *ctx = data; 740*25ad6213SVincent Jardin int rc; 741*25ad6213SVincent Jardin 742*25ad6213SVincent Jardin guard(pmbus_lock)(ctx->priv->client); 743*25ad6213SVincent Jardin if (ctx->desc->is_word) 744*25ad6213SVincent Jardin rc = i2c_smbus_write_word_data(ctx->priv->client, 745*25ad6213SVincent Jardin ctx->desc->reg, (u16)val); 746*25ad6213SVincent Jardin else 747*25ad6213SVincent Jardin rc = i2c_smbus_write_byte_data(ctx->priv->client, 748*25ad6213SVincent Jardin ctx->desc->reg, (u8)val); 749*25ad6213SVincent Jardin return rc < 0 ? rc : 0; 750*25ad6213SVincent Jardin } 751*25ad6213SVincent Jardin DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_reg_rw_fops, 752*25ad6213SVincent Jardin mpq8646_dbg_reg_get, mpq8646_dbg_reg_set, "0x%llx\n"); 753*25ad6213SVincent Jardin 754*25ad6213SVincent Jardin static void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv, 755*25ad6213SVincent Jardin struct dentry *root) 756*25ad6213SVincent Jardin { 757*25ad6213SVincent Jardin struct mpq8646_dbg_reg_ctx *ctx; 758*25ad6213SVincent Jardin size_t i; 759*25ad6213SVincent Jardin 760*25ad6213SVincent Jardin mpq8646_unsafe_banner(priv); 761*25ad6213SVincent Jardin 762*25ad6213SVincent Jardin debugfs_create_file_unsafe("clear_protection_last", 0200, root, priv, 763*25ad6213SVincent Jardin &mpq8646_dbg_clear_protection_last_fops); 764*25ad6213SVincent Jardin debugfs_create_file_unsafe("clear_protection_last_force", 0200, root, 765*25ad6213SVincent Jardin priv, 766*25ad6213SVincent Jardin &mpq8646_dbg_clear_protection_last_force_fops); 767*25ad6213SVincent Jardin debugfs_create_file_unsafe("store_all", 0200, root, priv, 768*25ad6213SVincent Jardin &mpq8646_dbg_store_all_fops); 769*25ad6213SVincent Jardin debugfs_create_file_unsafe("restore_all", 0200, root, priv, 770*25ad6213SVincent Jardin &mpq8646_dbg_restore_all_fops); 771*25ad6213SVincent Jardin 772*25ad6213SVincent Jardin ctx = devm_kcalloc(&priv->client->dev, 773*25ad6213SVincent Jardin ARRAY_SIZE(mpq8646_dbg_regs_unsafe), 774*25ad6213SVincent Jardin sizeof(*ctx), GFP_KERNEL); 775*25ad6213SVincent Jardin if (!ctx) 776*25ad6213SVincent Jardin return; 777*25ad6213SVincent Jardin for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs_unsafe); i++) { 778*25ad6213SVincent Jardin ctx[i].priv = priv; 779*25ad6213SVincent Jardin ctx[i].desc = &mpq8646_dbg_regs_unsafe[i]; 780*25ad6213SVincent Jardin debugfs_create_file_unsafe(mpq8646_dbg_regs_unsafe[i].name, 781*25ad6213SVincent Jardin 0600, root, &ctx[i], 782*25ad6213SVincent Jardin &mpq8646_dbg_reg_rw_fops); 783*25ad6213SVincent Jardin } 784*25ad6213SVincent Jardin } 785*25ad6213SVincent Jardin #else 786*25ad6213SVincent Jardin static inline void mpq8646_debugfs_register_unsafe(struct mpq8646_priv *priv, 787*25ad6213SVincent Jardin struct dentry *root) {} 788*25ad6213SVincent Jardin #endif /* CONFIG_SENSORS_MPQ8646_DEBUG_UNSAFE */ 789*25ad6213SVincent Jardin 79024be24beSVincent Jardin static int mpq8646_dbg_poll_interval_get(void *data, u64 *val) 79124be24beSVincent Jardin { 79224be24beSVincent Jardin struct mpq8646_priv *priv = data; 79324be24beSVincent Jardin 79424be24beSVincent Jardin *val = priv->alarm_poll_interval_ms; 79524be24beSVincent Jardin return 0; 79624be24beSVincent Jardin } 79724be24beSVincent Jardin 79824be24beSVincent Jardin static int mpq8646_dbg_poll_interval_set(void *data, u64 val) 79924be24beSVincent Jardin { 80024be24beSVincent Jardin struct mpq8646_priv *priv = data; 80124be24beSVincent Jardin bool was_off = !priv->alarm_poll_interval_ms; 80224be24beSVincent Jardin 80324be24beSVincent Jardin priv->alarm_poll_interval_ms = (u32)val; 80424be24beSVincent Jardin 80524be24beSVincent Jardin if (val && was_off && !priv->client->irq) 80624be24beSVincent Jardin schedule_delayed_work(&priv->alarm_poll_work, 80724be24beSVincent Jardin msecs_to_jiffies((u32)val)); 80824be24beSVincent Jardin return 0; 80924be24beSVincent Jardin } 81024be24beSVincent Jardin DEFINE_DEBUGFS_ATTRIBUTE(mpq8646_dbg_poll_interval_fops, 81124be24beSVincent Jardin mpq8646_dbg_poll_interval_get, 81224be24beSVincent Jardin mpq8646_dbg_poll_interval_set, "%llu\n"); 81324be24beSVincent Jardin 81424be24beSVincent Jardin static void mpq8646_debugfs_register(struct mpq8646_priv *priv) 81524be24beSVincent Jardin { 81624be24beSVincent Jardin struct dentry *root; 81724be24beSVincent Jardin size_t i; 81824be24beSVincent Jardin 81924be24beSVincent Jardin root = pmbus_get_debugfs_dir(priv->client); 82024be24beSVincent Jardin if (!root) 82124be24beSVincent Jardin return; 82224be24beSVincent Jardin 82324be24beSVincent Jardin /* MPS extensions: status decode + NVM-backed PROTECTION_LAST */ 82424be24beSVincent Jardin debugfs_create_file("status_decoded", 0400, root, priv, 82524be24beSVincent Jardin &mpq8646_dbg_status_decoded_fops); 82624be24beSVincent Jardin debugfs_create_file("protection_last", 0400, root, priv, 82724be24beSVincent Jardin &mpq8646_dbg_protection_last_fops); 82824be24beSVincent Jardin priv->dbg_poll = 82924be24beSVincent Jardin debugfs_create_file_unsafe("alarm_poll_interval_ms", 0600, 83024be24beSVincent Jardin root, priv, 83124be24beSVincent Jardin &mpq8646_dbg_poll_interval_fops); 83224be24beSVincent Jardin 83324be24beSVincent Jardin priv->dbg_reg_ctx = devm_kcalloc(&priv->client->dev, 83424be24beSVincent Jardin ARRAY_SIZE(mpq8646_dbg_regs), 83524be24beSVincent Jardin sizeof(*priv->dbg_reg_ctx), 83624be24beSVincent Jardin GFP_KERNEL); 83724be24beSVincent Jardin if (!priv->dbg_reg_ctx) 83824be24beSVincent Jardin return; 83924be24beSVincent Jardin for (i = 0; i < ARRAY_SIZE(mpq8646_dbg_regs); i++) { 84024be24beSVincent Jardin priv->dbg_reg_ctx[i].priv = priv; 84124be24beSVincent Jardin priv->dbg_reg_ctx[i].desc = &mpq8646_dbg_regs[i]; 84224be24beSVincent Jardin debugfs_create_file(mpq8646_dbg_regs[i].name, 0400, 84324be24beSVincent Jardin root, &priv->dbg_reg_ctx[i], 84424be24beSVincent Jardin &mpq8646_dbg_reg_fops); 84524be24beSVincent Jardin } 846*25ad6213SVincent Jardin 847*25ad6213SVincent Jardin mpq8646_debugfs_register_unsafe(priv, root); 84824be24beSVincent Jardin } 84924be24beSVincent Jardin 85024be24beSVincent Jardin static void mpq8646_debugfs_unregister(struct mpq8646_priv *priv) 85124be24beSVincent Jardin { 85224be24beSVincent Jardin debugfs_remove(priv->dbg_poll); 85324be24beSVincent Jardin } 85424be24beSVincent Jardin #else 85524be24beSVincent Jardin static inline void mpq8646_debugfs_register(struct mpq8646_priv *priv) {} 85624be24beSVincent Jardin static inline void mpq8646_debugfs_unregister(struct mpq8646_priv *priv) {} 85724be24beSVincent Jardin #endif /* CONFIG_DEBUG_FS */ 85824be24beSVincent Jardin 85924be24beSVincent Jardin static void mpq8646_alarm_poll_work(struct work_struct *work) 86024be24beSVincent Jardin { 86124be24beSVincent Jardin struct mpq8646_priv *priv = container_of(to_delayed_work(work), 86224be24beSVincent Jardin struct mpq8646_priv, 86324be24beSVincent Jardin alarm_poll_work); 86424be24beSVincent Jardin 86524be24beSVincent Jardin if (priv->client->irq) 86624be24beSVincent Jardin return; /* SMBALERT# wired; polling not needed */ 86724be24beSVincent Jardin 86824be24beSVincent Jardin if (!priv->alarm_poll_interval_ms) 86924be24beSVincent Jardin return; /* polling disabled; don't re-arm */ 87024be24beSVincent Jardin 87124be24beSVincent Jardin pmbus_check_and_notify_faults(priv->client); 87224be24beSVincent Jardin 87324be24beSVincent Jardin schedule_delayed_work(&priv->alarm_poll_work, 87424be24beSVincent Jardin msecs_to_jiffies(priv->alarm_poll_interval_ms)); 87524be24beSVincent Jardin } 87624be24beSVincent Jardin 87724be24beSVincent Jardin static int mpq8646_probe(struct i2c_client *client) 87824be24beSVincent Jardin { 87924be24beSVincent Jardin struct device *dev = &client->dev; 88024be24beSVincent Jardin struct pmbus_driver_info *info; 88124be24beSVincent Jardin struct mpq8646_priv *priv; 88224be24beSVincent Jardin u32 voltage_scale; 88324be24beSVincent Jardin int ret; 88424be24beSVincent Jardin 88524be24beSVincent Jardin priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 88624be24beSVincent Jardin if (!priv) 88724be24beSVincent Jardin return -ENOMEM; 88824be24beSVincent Jardin priv->client = client; 88924be24beSVincent Jardin mutex_init(&priv->mps_lock); 89024be24beSVincent Jardin memcpy(&priv->info, &mpq8646_info, sizeof(priv->info)); 89124be24beSVincent Jardin info = &priv->info; 89224be24beSVincent Jardin 89324be24beSVincent Jardin info->identify = mpq8646_identify; 89424be24beSVincent Jardin info->read_byte_data = mpq8646_read_byte_data; 89524be24beSVincent Jardin info->read_word_data = mpq8646_read_word_data; 89624be24beSVincent Jardin info->write_word_data = mpq8646_write_word_data; 89724be24beSVincent Jardin dev->platform_data = &mpq8646_no_pec_pdata; 89824be24beSVincent Jardin 89924be24beSVincent Jardin #if IS_ENABLED(CONFIG_REGULATOR) 90024be24beSVincent Jardin info->reg_desc = mpq8646_reg_desc; 90124be24beSVincent Jardin info->num_regulators = ARRAY_SIZE(mpq8646_reg_desc); 90224be24beSVincent Jardin #endif 90324be24beSVincent Jardin 90424be24beSVincent Jardin INIT_DELAYED_WORK(&priv->alarm_poll_work, mpq8646_alarm_poll_work); 90524be24beSVincent Jardin priv->alarm_poll_interval_ms = MPQ8646_ALARM_POLL_MS_DEFAULT; 90624be24beSVincent Jardin 90724be24beSVincent Jardin if (!device_property_read_u32(dev, "mps,vout-fb-divider-ratio-permille", 90824be24beSVincent Jardin &voltage_scale)) { 90924be24beSVincent Jardin if (voltage_scale > MPQ8646_VOUT_SCALE_LOOP_MAX) 91024be24beSVincent Jardin return -EINVAL; 91124be24beSVincent Jardin 91224be24beSVincent Jardin ret = i2c_smbus_write_word_data(client, PMBUS_VOUT_SCALE_LOOP, 91324be24beSVincent Jardin voltage_scale); 91424be24beSVincent Jardin if (ret) 91524be24beSVincent Jardin return ret; 91624be24beSVincent Jardin } 91724be24beSVincent Jardin 91824be24beSVincent Jardin ret = pmbus_do_probe(client, info); 91924be24beSVincent Jardin if (ret) 92024be24beSVincent Jardin return ret; 92124be24beSVincent Jardin 92224be24beSVincent Jardin mpq8646_debugfs_register(priv); 92324be24beSVincent Jardin 92424be24beSVincent Jardin if (!client->irq) 92524be24beSVincent Jardin schedule_delayed_work(&priv->alarm_poll_work, 92624be24beSVincent Jardin msecs_to_jiffies(priv->alarm_poll_interval_ms)); 92724be24beSVincent Jardin 92824be24beSVincent Jardin #if IS_ENABLED(CONFIG_NVMEM) 92924be24beSVincent Jardin { 93024be24beSVincent Jardin struct nvmem_config cfg = { 93124be24beSVincent Jardin .dev = &client->dev, 93224be24beSVincent Jardin .name = dev_name(&client->dev), 93324be24beSVincent Jardin .owner = THIS_MODULE, 93424be24beSVincent Jardin .read_only = true, 93524be24beSVincent Jardin .root_only = true, 93624be24beSVincent Jardin .word_size = 1, 93724be24beSVincent Jardin .stride = 1, 93824be24beSVincent Jardin .size = MPQ8646_NVMEM_SIZE, 93924be24beSVincent Jardin .reg_read = mpq8646_nvmem_read, 94024be24beSVincent Jardin .priv = priv, 94124be24beSVincent Jardin }; 94224be24beSVincent Jardin struct nvmem_device *nv = devm_nvmem_register(&client->dev, &cfg); 94324be24beSVincent Jardin 94424be24beSVincent Jardin if (IS_ERR(nv)) 94524be24beSVincent Jardin dev_warn(&client->dev, 94624be24beSVincent Jardin "nvmem snapshot register failed (%pe)\n", 94724be24beSVincent Jardin nv); 94824be24beSVincent Jardin } 94924be24beSVincent Jardin #endif 95024be24beSVincent Jardin return 0; 95124be24beSVincent Jardin }; 95224be24beSVincent Jardin 95324be24beSVincent Jardin static void mpq8646_remove(struct i2c_client *client) 95424be24beSVincent Jardin { 95524be24beSVincent Jardin struct mpq8646_priv *priv = mpq8646_priv_from_client(client); 95624be24beSVincent Jardin 95724be24beSVincent Jardin mpq8646_debugfs_unregister(priv); 95824be24beSVincent Jardin cancel_delayed_work_sync(&priv->alarm_poll_work); 95924be24beSVincent Jardin } 96024be24beSVincent Jardin 96124be24beSVincent Jardin static struct i2c_driver mpq8646_driver = { 96224be24beSVincent Jardin .driver = { 96324be24beSVincent Jardin .name = "mpq8646", 96424be24beSVincent Jardin .of_match_table = of_match_ptr(mpq8646_of_match), 96524be24beSVincent Jardin }, 96624be24beSVincent Jardin .probe = mpq8646_probe, 96724be24beSVincent Jardin .remove = mpq8646_remove, 96824be24beSVincent Jardin .id_table = mpq8646_id, 96924be24beSVincent Jardin }; 97024be24beSVincent Jardin 97124be24beSVincent Jardin module_i2c_driver(mpq8646_driver); 97224be24beSVincent Jardin 97324be24beSVincent Jardin MODULE_AUTHOR("Vincent Jardin <vjardin@free.fr>"); 97424be24beSVincent Jardin MODULE_DESCRIPTION("PMBus driver for MPS MPQ8646 (extended observability)"); 97524be24beSVincent Jardin MODULE_LICENSE("GPL"); 97624be24beSVincent Jardin MODULE_IMPORT_NS("PMBUS"); 977