xref: /linux/drivers/hwmon/pmbus/mpq8646.c (revision 7acf90feab8b009fde7def08ff2c622d0f10e99f)
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