Add support for ltc4286 driver Signed-off-by: Delphine CC Chiu <Delphine_CC_Chiu@xxxxxxxxxx> --- drivers/hwmon/pmbus/Kconfig | 9 +++ drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/ltc4286.c | 142 ++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 drivers/hwmon/pmbus/ltc4286.c diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 59d9a7430499..1230d910d681 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -218,6 +218,15 @@ config SENSORS_LTC3815 This driver can also be built as a module. If so, the module will be called ltc3815. +config SENSORS_LTC4286 + bool "Linear Technologies LTC4286" + help + If you say yes here you get hardware monitoring support for Linear + Technology LTC4286. + + This driver can also be built as a module. If so, the module will + be called ltc4286. + config SENSORS_MAX15301 tristate "Maxim MAX15301" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 3ae019916267..540265539580 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_SENSORS_LM25066) += lm25066.o obj-$(CONFIG_SENSORS_LT7182S) += lt7182s.o obj-$(CONFIG_SENSORS_LTC2978) += ltc2978.o obj-$(CONFIG_SENSORS_LTC3815) += ltc3815.o +obj-$(CONFIG_SENSORS_LTC4286) += ltc4286.o obj-$(CONFIG_SENSORS_MAX15301) += max15301.o obj-$(CONFIG_SENSORS_MAX16064) += max16064.o obj-$(CONFIG_SENSORS_MAX16601) += max16601.o diff --git a/drivers/hwmon/pmbus/ltc4286.c b/drivers/hwmon/pmbus/ltc4286.c new file mode 100644 index 000000000000..474f4ec9107c --- /dev/null +++ b/drivers/hwmon/pmbus/ltc4286.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for LTC4286 Hot-Swap Controller + * + * Copyright (c) 2023 Linear Technology + */ + +#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 "pmbus.h" + +// LTC4286 register +#define LTC4286_MFR_CONFIG1 (0xF2) + +// LTC4286 configuration +#define VRANGE_SELECT (1 << 1) + +#define LTC4286_MFR_ID_SIZE 3 + +enum chips { ltc4286, ltc4287 }; + +static struct pmbus_driver_info ltc4286_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 32, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 1, + .m[PSC_VOLTAGE_OUT] = 32, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 1, + .m[PSC_CURRENT_OUT] = 1024, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 3 - 6, + .m[PSC_POWER] = 1, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = 4 - 6, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 273.15, + .R[PSC_TEMPERATURE] = 0, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | + PMBUS_HAVE_PIN | PMBUS_HAVE_TEMP, +}; + +static int ltc4286_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; + struct device *dev = &client->dev; + struct pmbus_driver_info *info; + u32 rsense; + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, block_buffer); + if (ret < 0) { + dev_err(&client->dev, "failed to read manufacturer id\n"); + return ret; + } + + /* Refer to ltc4286 datasheet page 20 + * the default manufacturer id is LTC + */ + if (ret != LTC4286_MFR_ID_SIZE || + strncmp(block_buffer, "LTC", LTC4286_MFR_ID_SIZE)) { + dev_err(&client->dev, "unsupported manufacturer id\n"); + return -ENODEV; + } + + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_MODEL, block_buffer); + if (ret < 0) { + dev_err(&client->dev, "failed to read manufacturer model\n"); + return ret; + } + + ret = of_property_read_u32(client->dev.of_node, "rsense-micro-ohms", + &rsense); + if (ret < 0) + return ret; + + if (rsense == 0) + return -EINVAL; + + info = <c4286_info; + + /* Default of VRANGE_SELECT = 1 */ + if (device_property_read_bool(dev, "vrange_select_25p6")) { + /* Setup MFR1 CONFIG register bit 1 VRANGE_SELECT */ + ret = i2c_smbus_read_word_data(client, LTC4286_MFR_CONFIG1); + if (ret < 0) { + dev_err(&client->dev, + "failed to read manufacturer configuration one\n"); + return ret; + } + + ret &= ~VRANGE_SELECT; /* VRANGE_SELECT = 0, 25.6V */ + i2c_smbus_write_word_data(client, LTC4286_MFR_CONFIG1, ret); + + info->m[PSC_VOLTAGE_IN] = 128; + info->m[PSC_VOLTAGE_OUT] = 128; + info->m[PSC_POWER] = 4 * rsense; + } else { + info->m[PSC_POWER] = rsense; + } + + info->m[PSC_CURRENT_OUT] = 1024 * rsense; + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id ltc4286_id[] = { { "ltc4286", ltc4286 }, + { "ltc4287", ltc4287 }, + {} }; +MODULE_DEVICE_TABLE(i2c, ltc4286_id); + +static const struct of_device_id ltc4286_of_match[] = { + { .compatible = "lltc,ltc4286" }, + { .compatible = "lltc,ltc4287" }, + {} +}; + +/* This is the driver that will be inserted */ +static struct i2c_driver ltc4286_driver = { + .driver = { + .name = "ltc4286", + .of_match_table = ltc4286_of_match, + }, + .probe = ltc4286_probe, + .id_table = ltc4286_id, +}; + +module_i2c_driver(ltc4286_driver); + +MODULE_AUTHOR("Delphine CC Chiu <Delphine_CC_Chiu@xxxxxxxxxx>"); +MODULE_DESCRIPTION("PMBUS driver for LTC4286 and compatibles"); +MODULE_LICENSE("GPL"); -- 2.17.1