162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2017 IBM Corp.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/bitfield.h>
762306a36Sopenharmony_ci#include <linux/bitops.h>
862306a36Sopenharmony_ci#include <linux/debugfs.h>
962306a36Sopenharmony_ci#include <linux/device.h>
1062306a36Sopenharmony_ci#include <linux/fs.h>
1162306a36Sopenharmony_ci#include <linux/i2c.h>
1262306a36Sopenharmony_ci#include <linux/jiffies.h>
1362306a36Sopenharmony_ci#include <linux/leds.h>
1462306a36Sopenharmony_ci#include <linux/module.h>
1562306a36Sopenharmony_ci#include <linux/mutex.h>
1662306a36Sopenharmony_ci#include <linux/of.h>
1762306a36Sopenharmony_ci#include <linux/pmbus.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include "pmbus.h"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#define CFFPS_CCIN_CMD				0xBD
2262306a36Sopenharmony_ci#define CFFPS_FW_CMD				0xFA
2362306a36Sopenharmony_ci#define CFFPS1_FW_NUM_BYTES			4
2462306a36Sopenharmony_ci#define CFFPS2_FW_NUM_WORDS			3
2562306a36Sopenharmony_ci#define CFFPS_SYS_CONFIG_CMD			0xDA
2662306a36Sopenharmony_ci#define CFFPS_12VCS_VOUT_CMD			0xDE
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#define CFFPS_INPUT_HISTORY_CMD			0xD6
2962306a36Sopenharmony_ci#define CFFPS_INPUT_HISTORY_SIZE		101
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#define CFFPS_CCIN_REVISION			GENMASK(7, 0)
3262306a36Sopenharmony_ci#define CFFPS_CCIN_REVISION_LEGACY		 0xde
3362306a36Sopenharmony_ci#define CFFPS_CCIN_VERSION			GENMASK(15, 8)
3462306a36Sopenharmony_ci#define CFFPS_CCIN_VERSION_1			 0x2b
3562306a36Sopenharmony_ci#define CFFPS_CCIN_VERSION_2			 0x2e
3662306a36Sopenharmony_ci#define CFFPS_CCIN_VERSION_3			 0x51
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci/* STATUS_MFR_SPECIFIC bits */
3962306a36Sopenharmony_ci#define CFFPS_MFR_FAN_FAULT			BIT(0)
4062306a36Sopenharmony_ci#define CFFPS_MFR_THERMAL_FAULT			BIT(1)
4162306a36Sopenharmony_ci#define CFFPS_MFR_OV_FAULT			BIT(2)
4262306a36Sopenharmony_ci#define CFFPS_MFR_UV_FAULT			BIT(3)
4362306a36Sopenharmony_ci#define CFFPS_MFR_PS_KILL			BIT(4)
4462306a36Sopenharmony_ci#define CFFPS_MFR_OC_FAULT			BIT(5)
4562306a36Sopenharmony_ci#define CFFPS_MFR_VAUX_FAULT			BIT(6)
4662306a36Sopenharmony_ci#define CFFPS_MFR_CURRENT_SHARE_WARNING		BIT(7)
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci#define CFFPS_LED_BLINK				(BIT(0) | BIT(6))
4962306a36Sopenharmony_ci#define CFFPS_LED_ON				(BIT(1) | BIT(6))
5062306a36Sopenharmony_ci#define CFFPS_LED_OFF				(BIT(2) | BIT(6))
5162306a36Sopenharmony_ci#define CFFPS_BLINK_RATE_MS			250
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cienum {
5462306a36Sopenharmony_ci	CFFPS_DEBUGFS_MAX_POWER_OUT = 0,
5562306a36Sopenharmony_ci	CFFPS_DEBUGFS_CCIN,
5662306a36Sopenharmony_ci	CFFPS_DEBUGFS_FW,
5762306a36Sopenharmony_ci	CFFPS_DEBUGFS_ON_OFF_CONFIG,
5862306a36Sopenharmony_ci	CFFPS_DEBUGFS_NUM_ENTRIES
5962306a36Sopenharmony_ci};
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cienum versions { cffps1, cffps2, cffps_unknown };
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_cistruct ibm_cffps {
6462306a36Sopenharmony_ci	enum versions version;
6562306a36Sopenharmony_ci	struct i2c_client *client;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	u8 input_history[CFFPS_INPUT_HISTORY_SIZE];
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	int debugfs_entries[CFFPS_DEBUGFS_NUM_ENTRIES];
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	char led_name[32];
7262306a36Sopenharmony_ci	u8 led_state;
7362306a36Sopenharmony_ci	struct led_classdev led;
7462306a36Sopenharmony_ci};
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci#define to_psu(x, y) container_of((x), struct ibm_cffps, debugfs_entries[(y)])
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistatic ssize_t ibm_cffps_debugfs_read_input_history(struct file *file, char __user *buf,
7962306a36Sopenharmony_ci						    size_t count, loff_t *ppos)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	int rc;
8262306a36Sopenharmony_ci	u8 cmd = CFFPS_INPUT_HISTORY_CMD;
8362306a36Sopenharmony_ci	struct ibm_cffps *psu = file->private_data;
8462306a36Sopenharmony_ci	struct i2c_msg msg[2] = {
8562306a36Sopenharmony_ci		{
8662306a36Sopenharmony_ci			.addr = psu->client->addr,
8762306a36Sopenharmony_ci			.flags = psu->client->flags,
8862306a36Sopenharmony_ci			.len = 1,
8962306a36Sopenharmony_ci			.buf = &cmd,
9062306a36Sopenharmony_ci		}, {
9162306a36Sopenharmony_ci			.addr = psu->client->addr,
9262306a36Sopenharmony_ci			.flags = psu->client->flags | I2C_M_RD,
9362306a36Sopenharmony_ci			.len = CFFPS_INPUT_HISTORY_SIZE,
9462306a36Sopenharmony_ci			.buf = psu->input_history,
9562306a36Sopenharmony_ci		},
9662306a36Sopenharmony_ci	};
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	if (!*ppos) {
9962306a36Sopenharmony_ci		rc = pmbus_lock_interruptible(psu->client);
10062306a36Sopenharmony_ci		if (rc)
10162306a36Sopenharmony_ci			return rc;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci		rc = pmbus_set_page(psu->client, 0, 0xff);
10462306a36Sopenharmony_ci		if (rc) {
10562306a36Sopenharmony_ci			pmbus_unlock(psu->client);
10662306a36Sopenharmony_ci			return rc;
10762306a36Sopenharmony_ci		}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci		/*
11062306a36Sopenharmony_ci		 * Use a raw i2c transfer, since we need more bytes
11162306a36Sopenharmony_ci		 * than Linux I2C supports through smbus xfr (only 32).
11262306a36Sopenharmony_ci		 */
11362306a36Sopenharmony_ci		rc = i2c_transfer(psu->client->adapter, msg, 2);
11462306a36Sopenharmony_ci		pmbus_unlock(psu->client);
11562306a36Sopenharmony_ci		if (rc < 0)
11662306a36Sopenharmony_ci			return rc;
11762306a36Sopenharmony_ci	}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	return simple_read_from_buffer(buf, count, ppos,
12062306a36Sopenharmony_ci				       psu->input_history + 1,
12162306a36Sopenharmony_ci				       psu->input_history[0]);
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic const struct file_operations ibm_cffps_input_history_fops = {
12562306a36Sopenharmony_ci	.llseek = noop_llseek,
12662306a36Sopenharmony_ci	.read = ibm_cffps_debugfs_read_input_history,
12762306a36Sopenharmony_ci	.open = simple_open,
12862306a36Sopenharmony_ci};
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_cistatic ssize_t ibm_cffps_debugfs_read(struct file *file, char __user *buf,
13162306a36Sopenharmony_ci				      size_t count, loff_t *ppos)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	int i, rc;
13462306a36Sopenharmony_ci	int *idxp = file->private_data;
13562306a36Sopenharmony_ci	int idx = *idxp;
13662306a36Sopenharmony_ci	struct ibm_cffps *psu = to_psu(idxp, idx);
13762306a36Sopenharmony_ci	char data[I2C_SMBUS_BLOCK_MAX + 2] = { 0 };
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	rc = pmbus_lock_interruptible(psu->client);
14062306a36Sopenharmony_ci	if (rc)
14162306a36Sopenharmony_ci		return rc;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	rc = pmbus_set_page(psu->client, 0, 0xff);
14462306a36Sopenharmony_ci	if (rc)
14562306a36Sopenharmony_ci		goto unlock;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	switch (idx) {
14862306a36Sopenharmony_ci	case CFFPS_DEBUGFS_MAX_POWER_OUT:
14962306a36Sopenharmony_ci		if (psu->version == cffps1)
15062306a36Sopenharmony_ci			rc = i2c_smbus_read_word_swapped(psu->client, PMBUS_MFR_POUT_MAX);
15162306a36Sopenharmony_ci		else
15262306a36Sopenharmony_ci			rc = i2c_smbus_read_word_data(psu->client, PMBUS_MFR_POUT_MAX);
15362306a36Sopenharmony_ci		if (rc >= 0)
15462306a36Sopenharmony_ci			rc = snprintf(data, I2C_SMBUS_BLOCK_MAX, "%d", rc);
15562306a36Sopenharmony_ci		break;
15662306a36Sopenharmony_ci	case CFFPS_DEBUGFS_CCIN:
15762306a36Sopenharmony_ci		rc = i2c_smbus_read_word_swapped(psu->client, CFFPS_CCIN_CMD);
15862306a36Sopenharmony_ci		if (rc >= 0)
15962306a36Sopenharmony_ci			rc = snprintf(data, 5, "%04X", rc);
16062306a36Sopenharmony_ci		break;
16162306a36Sopenharmony_ci	case CFFPS_DEBUGFS_FW:
16262306a36Sopenharmony_ci		switch (psu->version) {
16362306a36Sopenharmony_ci		case cffps1:
16462306a36Sopenharmony_ci			for (i = 0; i < CFFPS1_FW_NUM_BYTES; ++i) {
16562306a36Sopenharmony_ci				rc = i2c_smbus_read_byte_data(psu->client, CFFPS_FW_CMD + i);
16662306a36Sopenharmony_ci				if (rc < 0)
16762306a36Sopenharmony_ci					goto unlock;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci				snprintf(&data[i * 2], 3, "%02X", rc);
17062306a36Sopenharmony_ci			}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci			rc = i * 2;
17362306a36Sopenharmony_ci			break;
17462306a36Sopenharmony_ci		case cffps2:
17562306a36Sopenharmony_ci			for (i = 0; i < CFFPS2_FW_NUM_WORDS; ++i) {
17662306a36Sopenharmony_ci				rc = i2c_smbus_read_word_data(psu->client, CFFPS_FW_CMD + i);
17762306a36Sopenharmony_ci				if (rc < 0)
17862306a36Sopenharmony_ci					goto unlock;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci				snprintf(&data[i * 4], 5, "%04X", rc);
18162306a36Sopenharmony_ci			}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci			rc = i * 4;
18462306a36Sopenharmony_ci			break;
18562306a36Sopenharmony_ci		default:
18662306a36Sopenharmony_ci			rc = -EOPNOTSUPP;
18762306a36Sopenharmony_ci			break;
18862306a36Sopenharmony_ci		}
18962306a36Sopenharmony_ci		break;
19062306a36Sopenharmony_ci	case CFFPS_DEBUGFS_ON_OFF_CONFIG:
19162306a36Sopenharmony_ci		rc = i2c_smbus_read_byte_data(psu->client, PMBUS_ON_OFF_CONFIG);
19262306a36Sopenharmony_ci		if (rc >= 0)
19362306a36Sopenharmony_ci			rc = snprintf(data, 3, "%02x", rc);
19462306a36Sopenharmony_ci		break;
19562306a36Sopenharmony_ci	default:
19662306a36Sopenharmony_ci		rc = -EINVAL;
19762306a36Sopenharmony_ci		break;
19862306a36Sopenharmony_ci	}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ciunlock:
20162306a36Sopenharmony_ci	pmbus_unlock(psu->client);
20262306a36Sopenharmony_ci	if (rc < 0)
20362306a36Sopenharmony_ci		return rc;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	data[rc] = '\n';
20662306a36Sopenharmony_ci	rc += 2;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	return simple_read_from_buffer(buf, count, ppos, data, rc);
20962306a36Sopenharmony_ci}
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_cistatic ssize_t ibm_cffps_debugfs_write(struct file *file,
21262306a36Sopenharmony_ci				       const char __user *buf, size_t count,
21362306a36Sopenharmony_ci				       loff_t *ppos)
21462306a36Sopenharmony_ci{
21562306a36Sopenharmony_ci	u8 data;
21662306a36Sopenharmony_ci	ssize_t rc;
21762306a36Sopenharmony_ci	int *idxp = file->private_data;
21862306a36Sopenharmony_ci	int idx = *idxp;
21962306a36Sopenharmony_ci	struct ibm_cffps *psu = to_psu(idxp, idx);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	switch (idx) {
22262306a36Sopenharmony_ci	case CFFPS_DEBUGFS_ON_OFF_CONFIG:
22362306a36Sopenharmony_ci		rc = simple_write_to_buffer(&data, 1, ppos, buf, count);
22462306a36Sopenharmony_ci		if (rc <= 0)
22562306a36Sopenharmony_ci			return rc;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci		rc = pmbus_lock_interruptible(psu->client);
22862306a36Sopenharmony_ci		if (rc)
22962306a36Sopenharmony_ci			return rc;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci		rc = pmbus_set_page(psu->client, 0, 0xff);
23262306a36Sopenharmony_ci		if (rc) {
23362306a36Sopenharmony_ci			pmbus_unlock(psu->client);
23462306a36Sopenharmony_ci			return rc;
23562306a36Sopenharmony_ci		}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci		rc = i2c_smbus_write_byte_data(psu->client, PMBUS_ON_OFF_CONFIG, data);
23862306a36Sopenharmony_ci		pmbus_unlock(psu->client);
23962306a36Sopenharmony_ci		if (rc)
24062306a36Sopenharmony_ci			return rc;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci		rc = 1;
24362306a36Sopenharmony_ci		break;
24462306a36Sopenharmony_ci	default:
24562306a36Sopenharmony_ci		return -EINVAL;
24662306a36Sopenharmony_ci	}
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	return rc;
24962306a36Sopenharmony_ci}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_cistatic const struct file_operations ibm_cffps_fops = {
25262306a36Sopenharmony_ci	.llseek = noop_llseek,
25362306a36Sopenharmony_ci	.read = ibm_cffps_debugfs_read,
25462306a36Sopenharmony_ci	.write = ibm_cffps_debugfs_write,
25562306a36Sopenharmony_ci	.open = simple_open,
25662306a36Sopenharmony_ci};
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_cistatic int ibm_cffps_read_byte_data(struct i2c_client *client, int page,
25962306a36Sopenharmony_ci				    int reg)
26062306a36Sopenharmony_ci{
26162306a36Sopenharmony_ci	int rc, mfr;
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	switch (reg) {
26462306a36Sopenharmony_ci	case PMBUS_STATUS_VOUT:
26562306a36Sopenharmony_ci	case PMBUS_STATUS_IOUT:
26662306a36Sopenharmony_ci	case PMBUS_STATUS_TEMPERATURE:
26762306a36Sopenharmony_ci	case PMBUS_STATUS_FAN_12:
26862306a36Sopenharmony_ci		rc = pmbus_read_byte_data(client, page, reg);
26962306a36Sopenharmony_ci		if (rc < 0)
27062306a36Sopenharmony_ci			return rc;
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci		mfr = pmbus_read_byte_data(client, page,
27362306a36Sopenharmony_ci					   PMBUS_STATUS_MFR_SPECIFIC);
27462306a36Sopenharmony_ci		if (mfr < 0)
27562306a36Sopenharmony_ci			/*
27662306a36Sopenharmony_ci			 * Return the status register instead of an error,
27762306a36Sopenharmony_ci			 * since we successfully read status.
27862306a36Sopenharmony_ci			 */
27962306a36Sopenharmony_ci			return rc;
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci		/* Add MFR_SPECIFIC bits to the standard pmbus status regs. */
28262306a36Sopenharmony_ci		if (reg == PMBUS_STATUS_FAN_12) {
28362306a36Sopenharmony_ci			if (mfr & CFFPS_MFR_FAN_FAULT)
28462306a36Sopenharmony_ci				rc |= PB_FAN_FAN1_FAULT;
28562306a36Sopenharmony_ci		} else if (reg == PMBUS_STATUS_TEMPERATURE) {
28662306a36Sopenharmony_ci			if (mfr & CFFPS_MFR_THERMAL_FAULT)
28762306a36Sopenharmony_ci				rc |= PB_TEMP_OT_FAULT;
28862306a36Sopenharmony_ci		} else if (reg == PMBUS_STATUS_VOUT) {
28962306a36Sopenharmony_ci			if (mfr & (CFFPS_MFR_OV_FAULT | CFFPS_MFR_VAUX_FAULT))
29062306a36Sopenharmony_ci				rc |= PB_VOLTAGE_OV_FAULT;
29162306a36Sopenharmony_ci			if (mfr & CFFPS_MFR_UV_FAULT)
29262306a36Sopenharmony_ci				rc |= PB_VOLTAGE_UV_FAULT;
29362306a36Sopenharmony_ci		} else if (reg == PMBUS_STATUS_IOUT) {
29462306a36Sopenharmony_ci			if (mfr & CFFPS_MFR_OC_FAULT)
29562306a36Sopenharmony_ci				rc |= PB_IOUT_OC_FAULT;
29662306a36Sopenharmony_ci			if (mfr & CFFPS_MFR_CURRENT_SHARE_WARNING)
29762306a36Sopenharmony_ci				rc |= PB_CURRENT_SHARE_FAULT;
29862306a36Sopenharmony_ci		}
29962306a36Sopenharmony_ci		break;
30062306a36Sopenharmony_ci	default:
30162306a36Sopenharmony_ci		rc = -ENODATA;
30262306a36Sopenharmony_ci		break;
30362306a36Sopenharmony_ci	}
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	return rc;
30662306a36Sopenharmony_ci}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_cistatic int ibm_cffps_read_word_data(struct i2c_client *client, int page,
30962306a36Sopenharmony_ci				    int phase, int reg)
31062306a36Sopenharmony_ci{
31162306a36Sopenharmony_ci	int rc, mfr;
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	switch (reg) {
31462306a36Sopenharmony_ci	case PMBUS_STATUS_WORD:
31562306a36Sopenharmony_ci		rc = pmbus_read_word_data(client, page, phase, reg);
31662306a36Sopenharmony_ci		if (rc < 0)
31762306a36Sopenharmony_ci			return rc;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci		mfr = pmbus_read_byte_data(client, page,
32062306a36Sopenharmony_ci					   PMBUS_STATUS_MFR_SPECIFIC);
32162306a36Sopenharmony_ci		if (mfr < 0)
32262306a36Sopenharmony_ci			/*
32362306a36Sopenharmony_ci			 * Return the status register instead of an error,
32462306a36Sopenharmony_ci			 * since we successfully read status.
32562306a36Sopenharmony_ci			 */
32662306a36Sopenharmony_ci			return rc;
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci		if (mfr & CFFPS_MFR_PS_KILL)
32962306a36Sopenharmony_ci			rc |= PB_STATUS_OFF;
33062306a36Sopenharmony_ci		break;
33162306a36Sopenharmony_ci	case PMBUS_VIRT_READ_VMON:
33262306a36Sopenharmony_ci		rc = pmbus_read_word_data(client, page, phase,
33362306a36Sopenharmony_ci					  CFFPS_12VCS_VOUT_CMD);
33462306a36Sopenharmony_ci		break;
33562306a36Sopenharmony_ci	default:
33662306a36Sopenharmony_ci		rc = -ENODATA;
33762306a36Sopenharmony_ci		break;
33862306a36Sopenharmony_ci	}
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	return rc;
34162306a36Sopenharmony_ci}
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_cistatic int ibm_cffps_led_brightness_set(struct led_classdev *led_cdev,
34462306a36Sopenharmony_ci					enum led_brightness brightness)
34562306a36Sopenharmony_ci{
34662306a36Sopenharmony_ci	int rc;
34762306a36Sopenharmony_ci	u8 next_led_state;
34862306a36Sopenharmony_ci	struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led);
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	if (brightness == LED_OFF) {
35162306a36Sopenharmony_ci		next_led_state = CFFPS_LED_OFF;
35262306a36Sopenharmony_ci	} else {
35362306a36Sopenharmony_ci		brightness = LED_FULL;
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci		if (psu->led_state != CFFPS_LED_BLINK)
35662306a36Sopenharmony_ci			next_led_state = CFFPS_LED_ON;
35762306a36Sopenharmony_ci		else
35862306a36Sopenharmony_ci			next_led_state = CFFPS_LED_BLINK;
35962306a36Sopenharmony_ci	}
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	dev_dbg(&psu->client->dev, "LED brightness set: %d. Command: %d.\n",
36262306a36Sopenharmony_ci		brightness, next_led_state);
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	rc = pmbus_lock_interruptible(psu->client);
36562306a36Sopenharmony_ci	if (rc)
36662306a36Sopenharmony_ci		return rc;
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	rc = pmbus_set_page(psu->client, 0, 0xff);
36962306a36Sopenharmony_ci	if (rc) {
37062306a36Sopenharmony_ci		pmbus_unlock(psu->client);
37162306a36Sopenharmony_ci		return rc;
37262306a36Sopenharmony_ci	}
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD,
37562306a36Sopenharmony_ci				       next_led_state);
37662306a36Sopenharmony_ci	pmbus_unlock(psu->client);
37762306a36Sopenharmony_ci	if (rc < 0)
37862306a36Sopenharmony_ci		return rc;
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci	psu->led_state = next_led_state;
38162306a36Sopenharmony_ci	led_cdev->brightness = brightness;
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci	return 0;
38462306a36Sopenharmony_ci}
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_cistatic int ibm_cffps_led_blink_set(struct led_classdev *led_cdev,
38762306a36Sopenharmony_ci				   unsigned long *delay_on,
38862306a36Sopenharmony_ci				   unsigned long *delay_off)
38962306a36Sopenharmony_ci{
39062306a36Sopenharmony_ci	int rc;
39162306a36Sopenharmony_ci	struct ibm_cffps *psu = container_of(led_cdev, struct ibm_cffps, led);
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	dev_dbg(&psu->client->dev, "LED blink set.\n");
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	rc = pmbus_lock_interruptible(psu->client);
39662306a36Sopenharmony_ci	if (rc)
39762306a36Sopenharmony_ci		return rc;
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	rc = pmbus_set_page(psu->client, 0, 0xff);
40062306a36Sopenharmony_ci	if (rc) {
40162306a36Sopenharmony_ci		pmbus_unlock(psu->client);
40262306a36Sopenharmony_ci		return rc;
40362306a36Sopenharmony_ci	}
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci	rc = i2c_smbus_write_byte_data(psu->client, CFFPS_SYS_CONFIG_CMD,
40662306a36Sopenharmony_ci				       CFFPS_LED_BLINK);
40762306a36Sopenharmony_ci	pmbus_unlock(psu->client);
40862306a36Sopenharmony_ci	if (rc < 0)
40962306a36Sopenharmony_ci		return rc;
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_ci	psu->led_state = CFFPS_LED_BLINK;
41262306a36Sopenharmony_ci	led_cdev->brightness = LED_FULL;
41362306a36Sopenharmony_ci	*delay_on = CFFPS_BLINK_RATE_MS;
41462306a36Sopenharmony_ci	*delay_off = CFFPS_BLINK_RATE_MS;
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci	return 0;
41762306a36Sopenharmony_ci}
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_cistatic void ibm_cffps_create_led_class(struct ibm_cffps *psu)
42062306a36Sopenharmony_ci{
42162306a36Sopenharmony_ci	int rc;
42262306a36Sopenharmony_ci	struct i2c_client *client = psu->client;
42362306a36Sopenharmony_ci	struct device *dev = &client->dev;
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	snprintf(psu->led_name, sizeof(psu->led_name), "%s-%02x", client->name,
42662306a36Sopenharmony_ci		 client->addr);
42762306a36Sopenharmony_ci	psu->led.name = psu->led_name;
42862306a36Sopenharmony_ci	psu->led.max_brightness = LED_FULL;
42962306a36Sopenharmony_ci	psu->led.brightness_set_blocking = ibm_cffps_led_brightness_set;
43062306a36Sopenharmony_ci	psu->led.blink_set = ibm_cffps_led_blink_set;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	rc = devm_led_classdev_register(dev, &psu->led);
43362306a36Sopenharmony_ci	if (rc)
43462306a36Sopenharmony_ci		dev_warn(dev, "failed to register led class: %d\n", rc);
43562306a36Sopenharmony_ci	else
43662306a36Sopenharmony_ci		i2c_smbus_write_byte_data(client, CFFPS_SYS_CONFIG_CMD,
43762306a36Sopenharmony_ci					  CFFPS_LED_OFF);
43862306a36Sopenharmony_ci}
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_cistatic struct pmbus_driver_info ibm_cffps_info[] = {
44162306a36Sopenharmony_ci	[cffps1] = {
44262306a36Sopenharmony_ci		.pages = 1,
44362306a36Sopenharmony_ci		.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
44462306a36Sopenharmony_ci			PMBUS_HAVE_PIN | PMBUS_HAVE_FAN12 | PMBUS_HAVE_TEMP |
44562306a36Sopenharmony_ci			PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 |
44662306a36Sopenharmony_ci			PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT |
44762306a36Sopenharmony_ci			PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP |
44862306a36Sopenharmony_ci			PMBUS_HAVE_STATUS_FAN12,
44962306a36Sopenharmony_ci		.read_byte_data = ibm_cffps_read_byte_data,
45062306a36Sopenharmony_ci		.read_word_data = ibm_cffps_read_word_data,
45162306a36Sopenharmony_ci	},
45262306a36Sopenharmony_ci	[cffps2] = {
45362306a36Sopenharmony_ci		.pages = 2,
45462306a36Sopenharmony_ci		.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
45562306a36Sopenharmony_ci			PMBUS_HAVE_PIN | PMBUS_HAVE_FAN12 | PMBUS_HAVE_TEMP |
45662306a36Sopenharmony_ci			PMBUS_HAVE_TEMP2 | PMBUS_HAVE_TEMP3 |
45762306a36Sopenharmony_ci			PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT |
45862306a36Sopenharmony_ci			PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP |
45962306a36Sopenharmony_ci			PMBUS_HAVE_STATUS_FAN12 | PMBUS_HAVE_VMON,
46062306a36Sopenharmony_ci		.func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
46162306a36Sopenharmony_ci			PMBUS_HAVE_PIN | PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 |
46262306a36Sopenharmony_ci			PMBUS_HAVE_TEMP3 | PMBUS_HAVE_STATUS_VOUT |
46362306a36Sopenharmony_ci			PMBUS_HAVE_STATUS_IOUT | PMBUS_HAVE_STATUS_INPUT |
46462306a36Sopenharmony_ci			PMBUS_HAVE_STATUS_TEMP,
46562306a36Sopenharmony_ci		.read_byte_data = ibm_cffps_read_byte_data,
46662306a36Sopenharmony_ci		.read_word_data = ibm_cffps_read_word_data,
46762306a36Sopenharmony_ci	},
46862306a36Sopenharmony_ci};
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_cistatic struct pmbus_platform_data ibm_cffps_pdata = {
47162306a36Sopenharmony_ci	.flags = PMBUS_SKIP_STATUS_CHECK | PMBUS_NO_CAPABILITY,
47262306a36Sopenharmony_ci};
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_cistatic const struct i2c_device_id ibm_cffps_id[] = {
47562306a36Sopenharmony_ci	{ "ibm_cffps1", cffps1 },
47662306a36Sopenharmony_ci	{ "ibm_cffps2", cffps2 },
47762306a36Sopenharmony_ci	{ "ibm_cffps", cffps_unknown },
47862306a36Sopenharmony_ci	{}
47962306a36Sopenharmony_ci};
48062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, ibm_cffps_id);
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_cistatic int ibm_cffps_probe(struct i2c_client *client)
48362306a36Sopenharmony_ci{
48462306a36Sopenharmony_ci	int i, rc;
48562306a36Sopenharmony_ci	enum versions vs = cffps_unknown;
48662306a36Sopenharmony_ci	struct dentry *debugfs;
48762306a36Sopenharmony_ci	struct ibm_cffps *psu;
48862306a36Sopenharmony_ci	const void *md = of_device_get_match_data(&client->dev);
48962306a36Sopenharmony_ci	const struct i2c_device_id *id;
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci	if (md) {
49262306a36Sopenharmony_ci		vs = (uintptr_t)md;
49362306a36Sopenharmony_ci	} else {
49462306a36Sopenharmony_ci		id = i2c_match_id(ibm_cffps_id, client);
49562306a36Sopenharmony_ci		if (id)
49662306a36Sopenharmony_ci			vs = (enum versions)id->driver_data;
49762306a36Sopenharmony_ci	}
49862306a36Sopenharmony_ci
49962306a36Sopenharmony_ci	if (vs == cffps_unknown) {
50062306a36Sopenharmony_ci		u16 ccin_revision = 0;
50162306a36Sopenharmony_ci		u16 ccin_version = CFFPS_CCIN_VERSION_1;
50262306a36Sopenharmony_ci		int ccin = i2c_smbus_read_word_swapped(client, CFFPS_CCIN_CMD);
50362306a36Sopenharmony_ci		char mfg_id[I2C_SMBUS_BLOCK_MAX + 2] = { 0 };
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci		if (ccin > 0) {
50662306a36Sopenharmony_ci			ccin_revision = FIELD_GET(CFFPS_CCIN_REVISION, ccin);
50762306a36Sopenharmony_ci			ccin_version = FIELD_GET(CFFPS_CCIN_VERSION, ccin);
50862306a36Sopenharmony_ci		}
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci		rc = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, mfg_id);
51162306a36Sopenharmony_ci		if (rc < 0) {
51262306a36Sopenharmony_ci			dev_err(&client->dev, "Failed to read Manufacturer ID\n");
51362306a36Sopenharmony_ci			return rc;
51462306a36Sopenharmony_ci		}
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_ci		switch (ccin_version) {
51762306a36Sopenharmony_ci		default:
51862306a36Sopenharmony_ci		case CFFPS_CCIN_VERSION_1:
51962306a36Sopenharmony_ci			if ((strncmp(mfg_id, "ACBE", 4) == 0) ||
52062306a36Sopenharmony_ci				     (strncmp(mfg_id, "ARTE", 4) == 0))
52162306a36Sopenharmony_ci				vs = cffps1;
52262306a36Sopenharmony_ci			else
52362306a36Sopenharmony_ci				vs = cffps2;
52462306a36Sopenharmony_ci			break;
52562306a36Sopenharmony_ci		case CFFPS_CCIN_VERSION_2:
52662306a36Sopenharmony_ci			vs = cffps2;
52762306a36Sopenharmony_ci			break;
52862306a36Sopenharmony_ci		case CFFPS_CCIN_VERSION_3:
52962306a36Sopenharmony_ci			if (ccin_revision == CFFPS_CCIN_REVISION_LEGACY)
53062306a36Sopenharmony_ci				vs = cffps1;
53162306a36Sopenharmony_ci			else
53262306a36Sopenharmony_ci				vs = cffps2;
53362306a36Sopenharmony_ci			break;
53462306a36Sopenharmony_ci		}
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_ci		/* Set the client name to include the version number. */
53762306a36Sopenharmony_ci		snprintf(client->name, I2C_NAME_SIZE, "cffps%d", vs + 1);
53862306a36Sopenharmony_ci	}
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci	client->dev.platform_data = &ibm_cffps_pdata;
54162306a36Sopenharmony_ci	rc = pmbus_do_probe(client, &ibm_cffps_info[vs]);
54262306a36Sopenharmony_ci	if (rc)
54362306a36Sopenharmony_ci		return rc;
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci	/*
54662306a36Sopenharmony_ci	 * Don't fail the probe if there isn't enough memory for leds and
54762306a36Sopenharmony_ci	 * debugfs.
54862306a36Sopenharmony_ci	 */
54962306a36Sopenharmony_ci	psu = devm_kzalloc(&client->dev, sizeof(*psu), GFP_KERNEL);
55062306a36Sopenharmony_ci	if (!psu)
55162306a36Sopenharmony_ci		return 0;
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_ci	psu->version = vs;
55462306a36Sopenharmony_ci	psu->client = client;
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci	ibm_cffps_create_led_class(psu);
55762306a36Sopenharmony_ci
55862306a36Sopenharmony_ci	/* Don't fail the probe if we can't create debugfs */
55962306a36Sopenharmony_ci	debugfs = pmbus_get_debugfs_dir(client);
56062306a36Sopenharmony_ci	if (!debugfs)
56162306a36Sopenharmony_ci		return 0;
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ci	for (i = 0; i < CFFPS_DEBUGFS_NUM_ENTRIES; ++i)
56462306a36Sopenharmony_ci		psu->debugfs_entries[i] = i;
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_ci	debugfs_create_file("input_history", 0444, debugfs, psu, &ibm_cffps_input_history_fops);
56762306a36Sopenharmony_ci	debugfs_create_file("max_power_out", 0444, debugfs,
56862306a36Sopenharmony_ci			    &psu->debugfs_entries[CFFPS_DEBUGFS_MAX_POWER_OUT],
56962306a36Sopenharmony_ci			    &ibm_cffps_fops);
57062306a36Sopenharmony_ci	debugfs_create_file("ccin", 0444, debugfs,
57162306a36Sopenharmony_ci			    &psu->debugfs_entries[CFFPS_DEBUGFS_CCIN],
57262306a36Sopenharmony_ci			    &ibm_cffps_fops);
57362306a36Sopenharmony_ci	debugfs_create_file("fw_version", 0444, debugfs,
57462306a36Sopenharmony_ci			    &psu->debugfs_entries[CFFPS_DEBUGFS_FW],
57562306a36Sopenharmony_ci			    &ibm_cffps_fops);
57662306a36Sopenharmony_ci	debugfs_create_file("on_off_config", 0644, debugfs,
57762306a36Sopenharmony_ci			    &psu->debugfs_entries[CFFPS_DEBUGFS_ON_OFF_CONFIG],
57862306a36Sopenharmony_ci			    &ibm_cffps_fops);
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_ci	/* For compatibility with users of the old naming scheme. */
58162306a36Sopenharmony_ci	debugfs_create_symlink(client->name, debugfs, ".");
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci	return 0;
58462306a36Sopenharmony_ci}
58562306a36Sopenharmony_ci
58662306a36Sopenharmony_cistatic const struct of_device_id ibm_cffps_of_match[] = {
58762306a36Sopenharmony_ci	{
58862306a36Sopenharmony_ci		.compatible = "ibm,cffps1",
58962306a36Sopenharmony_ci		.data = (void *)cffps1
59062306a36Sopenharmony_ci	},
59162306a36Sopenharmony_ci	{
59262306a36Sopenharmony_ci		.compatible = "ibm,cffps2",
59362306a36Sopenharmony_ci		.data = (void *)cffps2
59462306a36Sopenharmony_ci	},
59562306a36Sopenharmony_ci	{
59662306a36Sopenharmony_ci		.compatible = "ibm,cffps",
59762306a36Sopenharmony_ci		.data = (void *)cffps_unknown
59862306a36Sopenharmony_ci	},
59962306a36Sopenharmony_ci	{}
60062306a36Sopenharmony_ci};
60162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, ibm_cffps_of_match);
60262306a36Sopenharmony_ci
60362306a36Sopenharmony_cistatic struct i2c_driver ibm_cffps_driver = {
60462306a36Sopenharmony_ci	.driver = {
60562306a36Sopenharmony_ci		.name = "ibm-cffps",
60662306a36Sopenharmony_ci		.of_match_table = ibm_cffps_of_match,
60762306a36Sopenharmony_ci	},
60862306a36Sopenharmony_ci	.probe = ibm_cffps_probe,
60962306a36Sopenharmony_ci	.id_table = ibm_cffps_id,
61062306a36Sopenharmony_ci};
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_cimodule_i2c_driver(ibm_cffps_driver);
61362306a36Sopenharmony_ci
61462306a36Sopenharmony_ciMODULE_AUTHOR("Eddie James");
61562306a36Sopenharmony_ciMODULE_DESCRIPTION("PMBus driver for IBM Common Form Factor power supplies");
61662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
61762306a36Sopenharmony_ciMODULE_IMPORT_NS(PMBUS);
618