Re: [PATCH 3/3] hwmon: (pmbus/fs1412) Add TDK FS1412 driver
From: Guenter Roeck
Date: Mon Oct 19 2026 - 16:29:00 EST
On Thu, Oct 08, 2026 at 05:56:04PM -0700, Abdurrahman Hussain wrote:
> The TDK FS1412 is a 12A uPOL regulator with PMBus telemetry for input
> voltage, output voltage and temperature, plus the matching status
> registers. It has over-current and over-temperature protection with
> programmable fault limits but no current telemetry: READ_IOUT is not in
> its command table. Its MFR_ID reads "KDT", which the driver checks
> before binding.
>
> VOUT_MODE reads 0x98: LINEAR mode with exponent -8 and bit 7 set. The
> datasheet defines the output voltage limits relative to the output
> voltage with a resolution of 1/256, so 0x0133 against a 0.6V command is
> a 120% over-voltage limit. The PMBus core takes bits [7:5] as the mode
> and rejects the device, so the driver reports VOUT_MODE with bit 7
> cleared and converts the four VOUT limits to and from absolute voltages
> against VOUT_COMMAND.
>
> OT_WARN_LIMIT is not in the command table either. The register answers,
> but the part has no warning threshold and reports OT_WARNING at room
> temperature, so the driver hides the register from the core and only
> the OT_FAULT_LIMIT based attributes are provided.
>
> READ_VOUT reports the voltage at the feedback pin, so boards that set
> the output with a resistor divider scale in2_input in user space.
>
> Signed-off-by: Abdurrahman Hussain <abdurrahman@xxxxxxxxxx>
> ---
> Documentation/hwmon/fs1412.rst | 87 ++++++++++++++++++++
> Documentation/hwmon/index.rst | 1 +
> MAINTAINERS | 7 ++
> drivers/hwmon/pmbus/Kconfig | 9 +++
> drivers/hwmon/pmbus/Makefile | 1 +
> drivers/hwmon/pmbus/fs1412.c | 179 +++++++++++++++++++++++++++++++++++++++++
> 6 files changed, 284 insertions(+)
>
> diff --git a/Documentation/hwmon/fs1412.rst b/Documentation/hwmon/fs1412.rst
> new file mode 100644
> index 000000000000..b9b12fcbc5d9
> --- /dev/null
> +++ b/Documentation/hwmon/fs1412.rst
> @@ -0,0 +1,87 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +Kernel driver fs1412
> +====================
> +
> +Supported chips:
> +
> + * TDK FS1412
> +
> + Prefix: 'fs1412'
> +
> + Addresses scanned: -
> +
> + Datasheet: https://product.tdk.com/system/files/dam/doc/product/power/switching-power/micro-pol/data_sheet/fs1412_datasheet.pdf
> +
> +Authors:
> + Abdurrahman Hussain <abdurrahman@xxxxxxxxxx>
> +
> +
> +Description
> +-----------
> +
> +This driver implements support for the TDK FS1412, a 12A uPOL DC/DC
> +regulator with PMBus monitoring of its input voltage, output voltage and
> +temperature. The part has over-current and over-temperature protection with
> +programmable fault limits but no current telemetry, so no current attributes
> +are provided.
> +
> +The device answers on two I2C addresses: a register interface at the I2C
> +base address (0x08 by default) and the PMBus interface at the PMBus base
> +address (0x70 by default), each plus the offset strapped on the ADDR pin.
> +This driver binds to the PMBus address.
> +
> +The driver is a client driver to the core PMBus driver.
> +Please see Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
> +
> +
> +Usage Notes
> +-----------
> +
> +This driver does not auto-detect devices. You will have to instantiate the
> +devices explicitly. Please see Documentation/i2c/instantiating-devices.rst for
> +details.
> +
> +READ_VOUT reports the voltage at the feedback (VFB) pin, not the regulated
> +output. When the output is set with an external resistor divider, scale
> +in2_input and the in2 limits by the divider ratio in user space.
> +
> +The device stores its output voltage limits relative to VOUT_COMMAND
> +(VOUT_MODE bit 7 set). The driver converts them to and from absolute
> +voltages, so the in2 limits read and write in the same units as in2_input.
> +A written limit is stored as a 1/256 fraction of VOUT_COMMAND, so it may
> +read back rounded to that resolution. The over-voltage protection itself
> +engages at one of four thresholds the device rounds the fault limit to; see
> +the datasheet.
> +
> +The part reports an over-temperature warning at room temperature and has no
> +OT_WARN_LIMIT in its command table, so temp1_max and its alarm are not
> +provided; temp1_crit comes from OT_FAULT_LIMIT.
> +
> +
> +Sysfs entries
> +-------------
> +
> +======================= ======================================================
> +in1_label "vin"
> +in1_input Measured input voltage
> +in1_min Minimum input voltage
> +in1_min_alarm Input voltage low alarm
> +in1_crit Critical high input voltage
> +in1_crit_alarm Input voltage critical high alarm
> +
> +in2_label "vout1"
> +in2_input Measured voltage at the feedback pin
> +in2_min Minimum output voltage
> +in2_min_alarm Output voltage low alarm
> +in2_max Maximum output voltage
> +in2_max_alarm Output voltage high alarm
> +in2_lcrit Critical low output voltage
> +in2_lcrit_alarm Output voltage critical low alarm
> +in2_crit Critical high output voltage
> +in2_crit_alarm Output voltage critical high alarm
> +
> +temp1_input Measured temperature
> +temp1_crit Critical high temperature
> +temp1_crit_alarm Temperature critical high alarm
> +======================= ======================================================
> diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
> index 9955a525436a..625833374c94 100644
> --- a/Documentation/hwmon/index.rst
> +++ b/Documentation/hwmon/index.rst
> @@ -79,6 +79,7 @@ Hardware Monitoring Kernel Drivers
> f71805f
> f71882fg
> fam15h_power
> + fs1412
> fsp-3y
> ftsteutates
> g760a
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 65e8a4b5c90b..05461a380bf7 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -26778,6 +26778,13 @@ W: https://linuxtv.org
> T: git git://linuxtv.org/media.git
> F: drivers/media/i2c/tda9840*
>
> +TDK FS1412 MICRO POL DRIVER
> +M: Abdurrahman Hussain <abdurrahman@xxxxxxxxxx>
> +L: linux-hwmon@xxxxxxxxxxxxxxx
> +S: Maintained
> +F: Documentation/hwmon/fs1412.rst
> +F: drivers/hwmon/pmbus/fs1412.c
> +
> TEA5761 TUNER DRIVER
> M: Mauro Carvalho Chehab <mchehab@xxxxxxxxxx>
> L: linux-media@xxxxxxxxxxxxxxx
> diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
> index eb71a0ac044b..030ef72415fe 100644
> --- a/drivers/hwmon/pmbus/Kconfig
> +++ b/drivers/hwmon/pmbus/Kconfig
> @@ -132,6 +132,15 @@ config SENSORS_DELTA_AHE50DC_FAN
> This driver can also be built as a module. If so, the module
> will be called delta-ahe50dc-fan.
>
> +config SENSORS_FS1412
> + tristate "TDK FS1412"
> + help
> + If you say yes here you get hardware monitoring support for the TDK
> + FS1412 uPOL regulator.
> +
> + This driver can also be built as a module. If so, the module will
> + be called fs1412.
> +
> config SENSORS_FSP_3Y
> tristate "FSP/3Y-Power power supplies"
> help
> diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
> index e288fe72a437..7bbc0d02bfad 100644
> --- a/drivers/hwmon/pmbus/Makefile
> +++ b/drivers/hwmon/pmbus/Makefile
> @@ -13,6 +13,7 @@ obj-$(CONFIG_SENSORS_APS_379) += aps-379.o
> obj-$(CONFIG_SENSORS_BEL_PFE) += bel-pfe.o
> obj-$(CONFIG_SENSORS_BPA_RS600) += bpa-rs600.o
> obj-$(CONFIG_SENSORS_DELTA_AHE50DC_FAN) += delta-ahe50dc-fan.o
> +obj-$(CONFIG_SENSORS_FS1412) += fs1412.o
> obj-$(CONFIG_SENSORS_FSP_3Y) += fsp-3y.o
> obj-$(CONFIG_SENSORS_HAC300S) += hac300s.o
> obj-$(CONFIG_SENSORS_IBM_CFFPS) += ibm-cffps.o
> diff --git a/drivers/hwmon/pmbus/fs1412.c b/drivers/hwmon/pmbus/fs1412.c
> new file mode 100644
> index 000000000000..90a72b23967e
> --- /dev/null
> +++ b/drivers/hwmon/pmbus/fs1412.c
> @@ -0,0 +1,179 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Hardware monitoring driver for the TDK FS1412 uPOL regulator
> + *
> + * Copyright 2026 Nexthop Systems.
> + */
> +
> +#include <linux/bits.h>
> +#include <linux/i2c.h>
> +#include <linux/math.h>
> +#include <linux/minmax.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/pmbus.h>
> +#include <linux/string.h>
> +
> +#include "pmbus.h"
> +
> +#define FS1412_MFR_ID "KDT"
> +#define FS1412_MFR_ID_LEN (sizeof(FS1412_MFR_ID) - 1)
> +
> +/* VOUT_MODE: bit 7 selects relative VOUT limits, bits [4:0] the exponent. */
> +#define FS1412_VOUT_MODE_RELATIVE BIT(7)
> +#define FS1412_VOUT_MODE_EXP_MASK 0x1f
> +#define FS1412_VOUT_EXP_SHIFT 8 /* exponent -8: 1/256 V per LSB */
> +
> +/*
> + * VOUT_MODE reads 0x98: LINEAR mode with exponent -8 and bit 7 set, which
> + * makes the VOUT limit commands relative to VOUT_COMMAND. The PMBus core
> + * treats bits [7:5] as the mode, so report the mode and exponent with bit
> + * 7 cleared and convert the relative limits in the word accessors below.
> + */
> +static int fs1412_read_byte_data(struct i2c_client *client, int page, int reg)
> +{
> + int ret;
> +
> + if (reg != PMBUS_VOUT_MODE)
> + return -ENODATA;
> +
> + ret = pmbus_read_byte_data(client, page, reg);
> + if (ret < 0 || ret == 0xff)
> + return ret;
> +
> + return ret & ~FS1412_VOUT_MODE_RELATIVE;
This implementation is wrong. Support for relative mode should instead
be added to the PMBus core. Or, rather, this driver should be tested with
https://patchwork.kernel.org/project/linux-hwmon/patch/20240315151855.377627-2-lars.petter.mostad@xxxxxxxxxx/
in place (with appropriate fixes for that patch if necessary), and the
patches should be applied together.
Thanks,
Guenter
> +}
> +
> +static bool fs1412_vout_limit(int reg)
> +{
> + switch (reg) {
> + case PMBUS_VOUT_OV_FAULT_LIMIT:
> + case PMBUS_VOUT_OV_WARN_LIMIT:
> + case PMBUS_VOUT_UV_WARN_LIMIT:
> + case PMBUS_VOUT_UV_FAULT_LIMIT:
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> +/*
> + * The VOUT limits hold the limit as a fraction of VOUT_COMMAND in the same
> + * 1/256 units, so 0x0133 on a 0.6 V command is a 120% limit. Report them
> + * as absolute voltages: limit = fraction * VOUT_COMMAND.
> + */
> +static int fs1412_read_word_data(struct i2c_client *client, int page,
> + int phase, int reg)
> +{
> + int rel, cmd;
> +
> + /*
> + * OT_WARN_LIMIT is not in the command table. The register answers,
> + * but the part has no warning threshold and reports OT_WARNING at
> + * room temperature, so keep the core from exposing either.
> + */
> + if (reg == PMBUS_OT_WARN_LIMIT)
> + return -ENXIO;
> + if (!fs1412_vout_limit(reg))
> + return -ENODATA;
> +
> + rel = pmbus_read_word_data(client, page, phase, reg);
> + if (rel < 0)
> + return rel;
> +
> + cmd = pmbus_read_word_data(client, page, 0xff, PMBUS_VOUT_COMMAND);
> + if (cmd < 0)
> + return cmd;
> +
> + return min_t(u32, DIV_ROUND_CLOSEST((u32)rel * cmd,
> + 1 << FS1412_VOUT_EXP_SHIFT), 0xffff);
> +}
> +
> +static int fs1412_write_word_data(struct i2c_client *client, int page,
> + int reg, u16 word)
> +{
> + int cmd;
> +
> + if (reg == PMBUS_OT_WARN_LIMIT)
> + return -ENXIO;
> + if (!fs1412_vout_limit(reg))
> + return -ENODATA;
> +
> + cmd = pmbus_read_word_data(client, page, 0xff, PMBUS_VOUT_COMMAND);
> + if (cmd < 0)
> + return cmd;
> + if (!cmd)
> + return -EINVAL;
> +
> + word = clamp_val(DIV_ROUND_CLOSEST(word << FS1412_VOUT_EXP_SHIFT, cmd),
> + 0, 0xffff);
> +
> + return pmbus_write_word_data(client, page, reg, word);
> +}
> +
> +static struct pmbus_driver_info fs1412_info = {
> + .pages = 1,
> + .format[PSC_VOLTAGE_IN] = linear,
> + .format[PSC_VOLTAGE_OUT] = linear,
> + .format[PSC_TEMPERATURE] = linear,
> + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_STATUS_INPUT |
> + PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT |
> + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP,
> + .read_byte_data = fs1412_read_byte_data,
> + .read_word_data = fs1412_read_word_data,
> + .write_word_data = fs1412_write_word_data,
> +};
> +
> +static int fs1412_probe(struct i2c_client *client)
> +{
> + char buf[I2C_SMBUS_BLOCK_MAX + 1] = { 0 };
> + struct device *dev = &client->dev;
> + int ret;
> +
> + ret = i2c_smbus_read_block_data(client, PMBUS_MFR_ID, buf);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "Failed to read PMBUS_MFR_ID\n");
> +
> + if (ret != FS1412_MFR_ID_LEN ||
> + strncmp(buf, FS1412_MFR_ID, FS1412_MFR_ID_LEN))
> + return dev_err_probe(dev, -ENODEV,
> + "Unsupported Manufacturer ID '%s'\n", buf);
> +
> + /* The relative limit conversion assumes the 1/256 V exponent. */
> + ret = i2c_smbus_read_byte_data(client, PMBUS_VOUT_MODE);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "Failed to read VOUT_MODE\n");
> + if ((ret & FS1412_VOUT_MODE_EXP_MASK) != 0x18)
> + return dev_err_probe(dev, -ENODEV,
> + "Unsupported VOUT_MODE 0x%02x\n", ret);
> +
> + return pmbus_do_probe(client, &fs1412_info);
> +}
> +
> +static const struct i2c_device_id fs1412_id[] = {
> + { .name = "fs1412" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(i2c, fs1412_id);
> +
> +static const struct of_device_id fs1412_of_match[] = {
> + { .compatible = "tdk,fs1412" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, fs1412_of_match);
> +
> +static struct i2c_driver fs1412_driver = {
> + .driver = {
> + .name = "fs1412",
> + .of_match_table = fs1412_of_match,
> + },
> + .probe = fs1412_probe,
> + .id_table = fs1412_id,
> +};
> +
> +module_i2c_driver(fs1412_driver);
> +
> +MODULE_AUTHOR("Abdurrahman Hussain <abdurrahman@xxxxxxxxxx>");
> +MODULE_DESCRIPTION("PMBus driver for the TDK FS1412 uPOL regulator");
> +MODULE_LICENSE("GPL");
> +MODULE_IMPORT_NS("PMBUS");