Commit 317f9d80 authored by Erik Rosen's avatar Erik Rosen Committed by Guenter Roeck
Browse files

hwmon: (pmbus/pim4328) Add PMBus driver for PIM4006, PIM4328 and PIM4820



Add hardware monitoring support for Flex power interface modules PIM4006,
PIM4328 and PIM4820.

Signed-off-by: default avatarErik Rosen <erik.rosen@metormote.com>
Signed-off-by: default avatarGuenter Roeck <linux@roeck-us.net>
parent 5e86f128
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -267,6 +267,15 @@ config SENSORS_MP2975
	  This driver can also be built as a module. If so, the module will
	  be called mp2975.

config SENSORS_PIM4328
	tristate "Flex PIM4328 and compatibles"
	help
	  If you say yes here you get hardware monitoring support for Flex
	  PIM4328, PIM4820 and PIM4006 Power Interface Modules.

	  This driver can also be built as a module. If so, the module will
	  be called pim4328.

config SENSORS_PM6764TR
	tristate "ST PM6764TR"
	help
+1 −0
Original line number Diff line number Diff line
@@ -40,3 +40,4 @@ obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o
obj-$(CONFIG_SENSORS_UCD9200)	+= ucd9200.o
obj-$(CONFIG_SENSORS_XDPE122)	+= xdpe12284.o
obj-$(CONFIG_SENSORS_ZL6100)	+= zl6100.o
obj-$(CONFIG_SENSORS_PIM4328)	+= pim4328.o
+233 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Hardware monitoring driver for PIM4006, PIM4328 and PIM4820
 *
 * Copyright (c) 2021 Flextronics International Sweden AB
 */

#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pmbus.h>
#include <linux/slab.h>
#include "pmbus.h"

enum chips { pim4006, pim4328, pim4820 };

struct pim4328_data {
	enum chips id;
	struct pmbus_driver_info info;
};

#define to_pim4328_data(x)  container_of(x, struct pim4328_data, info)

/* PIM4006 and PIM4328 */
#define PIM4328_MFR_READ_VINA		0xd3
#define PIM4328_MFR_READ_VINB		0xd4

/* PIM4006 */
#define PIM4328_MFR_READ_IINA		0xd6
#define PIM4328_MFR_READ_IINB		0xd7
#define PIM4328_MFR_FET_CHECKSTATUS	0xd9

/* PIM4328 */
#define PIM4328_MFR_STATUS_BITS		0xd5

/* PIM4820 */
#define PIM4328_MFR_READ_STATUS		0xd0

static const struct i2c_device_id pim4328_id[] = {
	{"bmr455", pim4328},
	{"pim4006", pim4006},
	{"pim4106", pim4006},
	{"pim4206", pim4006},
	{"pim4306", pim4006},
	{"pim4328", pim4328},
	{"pim4406", pim4006},
	{"pim4820", pim4820},
	{}
};
MODULE_DEVICE_TABLE(i2c, pim4328_id);

static int pim4328_read_word_data(struct i2c_client *client, int page,
				  int phase, int reg)
{
	int ret;

	if (page > 0)
		return -ENXIO;

	if (phase == 0xff)
		return -ENODATA;

	switch (reg) {
	case PMBUS_READ_VIN:
		ret = pmbus_read_word_data(client, page, phase,
					   phase == 0 ? PIM4328_MFR_READ_VINA
						      : PIM4328_MFR_READ_VINB);
		break;
	case PMBUS_READ_IIN:
		ret = pmbus_read_word_data(client, page, phase,
					   phase == 0 ? PIM4328_MFR_READ_IINA
						      : PIM4328_MFR_READ_IINB);
		break;
	default:
		ret = -ENODATA;
	}

	return ret;
}

static int pim4328_read_byte_data(struct i2c_client *client, int page, int reg)
{
	const struct pmbus_driver_info *info = pmbus_get_driver_info(client);
	struct pim4328_data *data = to_pim4328_data(info);
	int ret, status;

	if (page > 0)
		return -ENXIO;

	switch (reg) {
	case PMBUS_STATUS_BYTE:
		ret = pmbus_read_byte_data(client, page, PMBUS_STATUS_BYTE);
		if (ret < 0)
			return ret;
		if (data->id == pim4006) {
			status = pmbus_read_word_data(client, page, 0xff,
						      PIM4328_MFR_FET_CHECKSTATUS);
			if (status < 0)
				return status;
			if (status & 0x0630) /* Input UV */
				ret |= PB_STATUS_VIN_UV;
		} else if (data->id == pim4328) {
			status = pmbus_read_byte_data(client, page,
						      PIM4328_MFR_STATUS_BITS);
			if (status < 0)
				return status;
			if (status & 0x04) /* Input UV */
				ret |= PB_STATUS_VIN_UV;
			if (status & 0x40) /* Output UV */
				ret |= PB_STATUS_NONE_ABOVE;
		} else if (data->id == pim4820) {
			status = pmbus_read_byte_data(client, page,
						      PIM4328_MFR_READ_STATUS);
			if (status < 0)
				return status;
			if (status & 0x05) /* Input OV or OC */
				ret |= PB_STATUS_NONE_ABOVE;
			if (status & 0x1a) /* Input UV */
				ret |= PB_STATUS_VIN_UV;
			if (status & 0x40) /* OT */
				ret |= PB_STATUS_TEMPERATURE;
		}
		break;
	default:
		ret = -ENODATA;
	}

	return ret;
}

static int pim4328_probe(struct i2c_client *client)
{
	int status;
	u8 device_id[I2C_SMBUS_BLOCK_MAX + 1];
	const struct i2c_device_id *mid;
	struct pim4328_data *data;
	struct pmbus_driver_info *info;
	struct pmbus_platform_data *pdata;
	struct device *dev = &client->dev;

	if (!i2c_check_functionality(client->adapter,
				     I2C_FUNC_SMBUS_READ_BYTE_DATA
				     | I2C_FUNC_SMBUS_BLOCK_DATA))
		return -ENODEV;

	data = devm_kzalloc(&client->dev, sizeof(struct pim4328_data),
			    GFP_KERNEL);
	if (!data)
		return -ENOMEM;

	status = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, device_id);
	if (status < 0) {
		dev_err(&client->dev, "Failed to read Manufacturer Model\n");
		return status;
	}
	for (mid = pim4328_id; mid->name[0]; mid++) {
		if (!strncasecmp(mid->name, device_id, strlen(mid->name)))
			break;
	}
	if (!mid->name[0]) {
		dev_err(&client->dev, "Unsupported device\n");
		return -ENODEV;
	}

	if (strcmp(client->name, mid->name))
		dev_notice(&client->dev,
			   "Device mismatch: Configured %s, detected %s\n",
			   client->name, mid->name);

	data->id = mid->driver_data;
	info = &data->info;
	info->pages = 1;
	info->read_byte_data = pim4328_read_byte_data;
	info->read_word_data = pim4328_read_word_data;

	pdata = devm_kzalloc(dev, sizeof(struct pmbus_platform_data),
			     GFP_KERNEL);
	if (!pdata)
		return -ENOMEM;
	dev->platform_data = pdata;
	pdata->flags = PMBUS_NO_CAPABILITY | PMBUS_NO_WRITE_PROTECT;

	switch (data->id) {
	case pim4006:
		info->phases[0] = 2;
		info->func[0] = PMBUS_PHASE_VIRTUAL | PMBUS_HAVE_VIN
			| PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT;
		info->pfunc[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN;
		info->pfunc[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_IIN;
		break;
	case pim4328:
		info->phases[0] = 2;
		info->func[0] = PMBUS_PHASE_VIRTUAL
			| PMBUS_HAVE_VCAP | PMBUS_HAVE_VIN
			| PMBUS_HAVE_TEMP | PMBUS_HAVE_IOUT;
		info->pfunc[0] = PMBUS_HAVE_VIN;
		info->pfunc[1] = PMBUS_HAVE_VIN;
		info->format[PSC_VOLTAGE_IN] = direct;
		info->format[PSC_TEMPERATURE] = direct;
		info->format[PSC_CURRENT_OUT] = direct;
		pdata->flags |= PMBUS_USE_COEFFICIENTS_CMD;
		break;
	case pim4820:
		info->func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_TEMP
			| PMBUS_HAVE_IIN;
		info->format[PSC_VOLTAGE_IN] = direct;
		info->format[PSC_TEMPERATURE] = direct;
		info->format[PSC_CURRENT_IN] = direct;
		pdata->flags |= PMBUS_USE_COEFFICIENTS_CMD;
		break;
	default:
		return -ENODEV;
	}

	return pmbus_do_probe(client, info);
}

static struct i2c_driver pim4328_driver = {
	.driver = {
		   .name = "pim4328",
		   },
	.probe_new = pim4328_probe,
	.id_table = pim4328_id,
};

module_i2c_driver(pim4328_driver);

MODULE_AUTHOR("Erik Rosen <erik.rosen@metormote.com>");
MODULE_DESCRIPTION("PMBus driver for PIM4006, PIM4328, PIM4820 power interface modules");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS(PMBUS);