Linux Documentation
 help / color / mirror / Atom feed
* [PATCH v4 05/27] mtd: spi-nor: debugfs: Align variable access with the rest of the file
From: Miquel Raynal @ 2026-04-03 16:09 UTC (permalink / raw)
  To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
	Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet
  Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
	linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260403-winbond-v6-18-rc1-spi-nor-swp-v4-0-833dab5e7288@bootlin.com>

The "params" variable is used everywhere else, align this particular
line of the file to use "params" directly rather than the "nor" pointer.

Reviewed-by: Michael Walle <mwalle@kernel.org>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
 drivers/mtd/spi-nor/debugfs.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c
index d700e0b27182..69830ad43990 100644
--- a/drivers/mtd/spi-nor/debugfs.c
+++ b/drivers/mtd/spi-nor/debugfs.c
@@ -139,7 +139,7 @@ static int spi_nor_params_show(struct seq_file *s, void *data)
 
 	if (!(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
 		string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf));
-		seq_printf(s, " %02x (%s)\n", nor->params->die_erase_opcode, buf);
+		seq_printf(s, " %02x (%s)\n", params->die_erase_opcode, buf);
 	}
 
 	seq_puts(s, "\nsector map\n");

-- 
2.53.0


^ permalink raw reply related

* [PATCH v4 04/27] mtd: spi-nor: Improve opcodes documentation
From: Miquel Raynal @ 2026-04-03 16:09 UTC (permalink / raw)
  To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
	Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet
  Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
	linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260403-winbond-v6-18-rc1-spi-nor-swp-v4-0-833dab5e7288@bootlin.com>

There are two status registers, named 1 and 2. The current wording is
misleading as "1" may refer to the status register ID as well as the
number of bytes required (which, in this case can be 1 or 2).

Clarify the comments by aligning them on the same pattern:
"{read,write} status {1,2} register"

Reviewed-by: Michael Walle <mwalle@kernel.org>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
 include/linux/mtd/spi-nor.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index cdcfe0fd2e7d..90a0cf583512 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -21,8 +21,8 @@
 /* Flash opcodes. */
 #define SPINOR_OP_WRDI		0x04	/* Write disable */
 #define SPINOR_OP_WREN		0x06	/* Write enable */
-#define SPINOR_OP_RDSR		0x05	/* Read status register */
-#define SPINOR_OP_WRSR		0x01	/* Write status register 1 byte */
+#define SPINOR_OP_RDSR		0x05	/* Read status register 1 */
+#define SPINOR_OP_WRSR		0x01	/* Write status register 1 */
 #define SPINOR_OP_RDSR2		0x3f	/* Read status register 2 */
 #define SPINOR_OP_WRSR2		0x3e	/* Write status register 2 */
 #define SPINOR_OP_READ		0x03	/* Read data bytes (low frequency) */

-- 
2.53.0


^ permalink raw reply related

* [PATCH v4 03/27] mtd: spi-nor: swp: Improve locking user experience
From: Miquel Raynal @ 2026-04-03 16:09 UTC (permalink / raw)
  To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
	Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet
  Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
	linux-kernel, linux-doc, Miquel Raynal, stable
In-Reply-To: <20260403-winbond-v6-18-rc1-spi-nor-swp-v4-0-833dab5e7288@bootlin.com>

In the case of the first block being locked (or the few first blocks),
if the user want to fully unlock the device it has two possibilities:
- either it asks to unlock the entire device, and this works;
- or it asks to unlock just the block(s) that are currently locked,
  which fails.

It fails because the conditions "can_be_top" and "can_be_bottom" are
true. Indeed, in this case, we unlock everything, so the TB bit does not
matter. However in the current implementation, use_top would be true (as
this is the favourite option) and lock_len, which in practice should be
reduced down to 0, is set to "nor->params->size - (ofs + len)" which is
a positive number. This is wrong.

An easy way is to simply add an extra condition. In the unlock() path,
if we can achieve the same result from both sides, it means we unlock
everything and lock_len must simply be 0. A comment is added to clarify
that logic.

Fixes: 3dd8012a8eeb ("mtd: spi-nor: add TB (Top/Bottom) protect support")
Cc: stable@kernel.org
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Reviewed-by: Michael Walle <mwalle@kernel.org>
---
 drivers/mtd/spi-nor/swp.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
index 9b07f83aeac7..1d50db1ef1a0 100644
--- a/drivers/mtd/spi-nor/swp.c
+++ b/drivers/mtd/spi-nor/swp.c
@@ -280,8 +280,15 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
 	/* Prefer top, if both are valid */
 	use_top = can_be_top;
 
-	/* lock_len: length of region that should remain locked */
-	if (use_top)
+	/*
+	 * lock_len: length of region that should remain locked.
+	 *
+	 * When can_be_top and can_be_bottom booleans are true, both adjacent
+	 * regions are unlocked, thus the entire flash can be unlocked.
+	 */
+	if (can_be_top && can_be_bottom)
+		lock_len = 0;
+	else if (use_top)
 		lock_len = nor->params->size - (ofs + len);
 	else
 		lock_len = ofs;

-- 
2.53.0


^ permalink raw reply related

* [PATCH v4 02/27] mtd: spi-nor: debugfs: Fix the flags list
From: Miquel Raynal @ 2026-04-03 16:09 UTC (permalink / raw)
  To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
	Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet
  Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
	linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260403-winbond-v6-18-rc1-spi-nor-swp-v4-0-833dab5e7288@bootlin.com>

As mentioned above the spi_nor_option_flags enumeration in core.h, this
list should be kept in sync with the one in the core.

Add the missing flag.

Fixes: 6a42bc97ccda ("mtd: spi-nor: core: Allow specifying the byte order in Octal DTR mode")
Reviewed-by: Michael Walle <mwalle@kernel.org>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
 drivers/mtd/spi-nor/debugfs.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c
index fa6956144d2e..d700e0b27182 100644
--- a/drivers/mtd/spi-nor/debugfs.c
+++ b/drivers/mtd/spi-nor/debugfs.c
@@ -28,6 +28,7 @@ static const char *const snor_f_names[] = {
 	SNOR_F_NAME(RWW),
 	SNOR_F_NAME(ECC),
 	SNOR_F_NAME(NO_WP),
+	SNOR_F_NAME(SWAP16),
 };
 #undef SNOR_F_NAME
 

-- 
2.53.0


^ permalink raw reply related

* [PATCH v4 01/27] mtd: spi-nor: Drop duplicate Kconfig dependency
From: Miquel Raynal @ 2026-04-03 16:09 UTC (permalink / raw)
  To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
	Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet
  Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
	linux-kernel, linux-doc, Miquel Raynal
In-Reply-To: <20260403-winbond-v6-18-rc1-spi-nor-swp-v4-0-833dab5e7288@bootlin.com>

I do not think the MTD dependency is needed twice. This is likely a
duplicate coming from a former rebase when the spi-nor core got cleaned
up a while ago. Remove the extra line.

Fixes: b35b9a10362d ("mtd: spi-nor: Move m25p80 code in spi-nor.c")
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Reviewed-by: Michael Walle <mwalle@kernel.org>
---
 drivers/mtd/spi-nor/Kconfig | 1 -
 1 file changed, 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
index 24cd25de2b8b..fd05a24d64a9 100644
--- a/drivers/mtd/spi-nor/Kconfig
+++ b/drivers/mtd/spi-nor/Kconfig
@@ -1,7 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0-only
 menuconfig MTD_SPI_NOR
 	tristate "SPI NOR device support"
-	depends on MTD
 	depends on MTD && SPI_MASTER
 	select SPI_MEM
 	help

-- 
2.53.0


^ permalink raw reply related

* [PATCH v4 00/27] mtd: spi-nor: Enhance software protection
From: Miquel Raynal @ 2026-04-03 16:09 UTC (permalink / raw)
  To: Pratyush Yadav, Michael Walle, Takahiro Kuwano,
	Richard Weinberger, Vignesh Raghavendra, Jonathan Corbet
  Cc: Sean Anderson, Thomas Petazzoni, Steam Lin, linux-mtd,
	linux-kernel, linux-doc, Miquel Raynal, stable

Hello,

As recently raised on the mailing-list (link below), it seems that the
"locking" support in SPI NOR could benefit from some enhancements. As I
myself had to dig into it recently, here is a proposal.

First issue that I see, the MEMLOCK ioctl is not behaving correctly
in some cases, as addressed in:

    mtd: spi-nor: swp: Improve locking user experience

Then there is no clear explanation of the shortcuts taken by the kernel
in terms of uAPI, so there is an attempt to list them in:

    mtd: spi-nor: swp: Explain the MEMLOCK ioctl implementation behaviour

Plus, Tudor also asked if we could cover locking in the testing
procedure, which is done in:

    mtd: spi-nor: Add steps for testing locking support

In order to simplify this procedure, and because it got very helpful
during my testing/development, I want to propose additions to the
debugfs output:

    mtd: spi-nor: debugfs: Add locking support TODO: make the captures again

Finally, I am providing an implementation for the complement (CMP)
feature in order to allow finer control of the regions locked. This
feature is for instance available on Winbond chips:

    [core] mtd: spi-nor: swp: Add support for the complement feature
    [doc]  mtd: spi-nor: Add steps for testing locking with CMP
    [use]  mtd: spi-nor: winbond: Add CMP locking support

Disclaimer: it was much less straightforward than I initially thought to
get the CMP feature working correctly. I tested it with as much focus as
I could, and I am improving the test coverage for the new cases, I am
also providing extra test cases in the metadata of the commit (which do
not make sense to test for chip additions, but may be sensible show when
making core additions like this one), but honestly there are so many
possibilities, I may still be missing corner cases. I hope this will
anyway be helpful to others!

All the other patches are misc improvements or style fixes which I faced
and fixed during my development.

Link: https://lore.kernel.org/linux-mtd/92e99a96-5582-48a5-a4f9-e9b33fcff171@linux.dev/

Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
---
Changes in v4:
- Make sure we don't try to show the (SR specific) debugfs info if the
  chip does not support an SR based locking scheme. For this, add a new
  helper to derive whether we are using the default ops or not.
- Fix compilation issue on arm32, by using div_u64.
- Link to v3: https://lore.kernel.org/r/20260317-winbond-v6-18-rc1-spi-nor-swp-v3-0-2ca9ea4e7b9b@bootlin.com

Changes in v3:
- No change at all, just rebased on top of v7.0-rc1.
- Collected 2 R-by from M. Walle.
- Link to v2: https://lore.kernel.org/r/20260108-winbond-v6-18-rc1-spi-nor-swp-v2-0-c462ef806130@bootlin.com

Changes in v2:
- Collect tags.
- Add missing Fixes/Cc: stable tags.
- Add a comment explaining why can_be_top && can_be_bottom is a specific
  condition.
- Fix commit logs following Michael Walle's reviews.
- Amend the documentation following our discussion with Michael Walle as
  well.
- Cache the SR register for debugfs use.
- Create a locked sector map file instead of dumping it as part of the
  `params` file output.
- Improved greatly the output of the map as suggested by Michael.
- Add a patch fixing a duplicate dependency in Kconfig.
- Add an important comment in the doc about the small 4kiB erase size
  choice.
- Add test runs for each and every chip for which the CMP feature is
  added. This prove me that testing of each and every chip was needed,
  as some of them seem to feature a broken BFPT table which does not
  advertise a working 35h (Read CR) command.
- Added a condition on which the CMP feature is enabled: RDCR must be
  possible.
- Link to v1: https://lore.kernel.org/r/20251114-winbond-v6-18-rc1-spi-nor-swp-v1-0-487bc7129931@bootlin.com

---
Miquel Raynal (27):
      mtd: spi-nor: Drop duplicate Kconfig dependency
      mtd: spi-nor: debugfs: Fix the flags list
      mtd: spi-nor: swp: Improve locking user experience
      mtd: spi-nor: Improve opcodes documentation
      mtd: spi-nor: debugfs: Align variable access with the rest of the file
      mtd: spi-nor: debugfs: Enhance output
      mtd: spi-nor: swp: Explain the MEMLOCK ioctl implementation behaviour
      mtd: spi-nor: swp: Clarify a comment
      mtd: spi-nor: swp: Use a pointer for SR instead of a single byte
      mtd: spi-nor: swp: Create a helper that writes SR, CR and checks
      mtd: spi-nor: swp: Rename a mask
      mtd: spi-nor: swp: Create a TB intermediate variable
      mtd: spi-nor: swp: Create helpers for building the SR register
      mtd: spi-nor: swp: Simplify checking the locked/unlocked range
      mtd: spi-nor: swp: Cosmetic changes
      mtd: spi-nor: Create a local SR cache
      mtd: spi-nor: debugfs: Add locking support
      mtd: spi-nor: debugfs: Add a locked sectors map
      mtd: spi-nor: Add steps for testing locking support
      mtd: spi-nor: swp: Add support for the complement feature
      mtd: spi-nor: Add steps for testing locking with CMP
      mtd: spi-nor: winbond: Add W25H512NWxxAM CMP locking support
      mtd: spi-nor: winbond: Add W25H01NWxxAM CMP locking support
      mtd: spi-nor: winbond: Add W25H02NWxxAM CMP locking support
      mtd: spi-nor: winbond: Add W25H01NWxxIQ CMP locking support
      mtd: spi-nor: winbond: Add W25Q01NWxxIM CMP locking support
      mtd: spi-nor: winbond: Add W25Q02NWxxIM CMP locking support

 Documentation/driver-api/mtd/spi-nor.rst | 164 ++++++++++++++
 drivers/mtd/spi-nor/Kconfig              |   1 -
 drivers/mtd/spi-nor/core.c               |  74 ++++++-
 drivers/mtd/spi-nor/core.h               |  11 +
 drivers/mtd/spi-nor/debugfs.c            |  69 +++++-
 drivers/mtd/spi-nor/swp.c                | 364 ++++++++++++++++++++++++-------
 drivers/mtd/spi-nor/winbond.c            |  41 +++-
 include/linux/mtd/spi-nor.h              |   7 +-
 8 files changed, 643 insertions(+), 88 deletions(-)
---
base-commit: 2cd9033836346e630afaf847c4f1ced08c4d1846
change-id: 20251114-winbond-v6-18-rc1-spi-nor-swp-865d36f4f695

Best regards,
-- 
Miquel Raynal <miquel.raynal@bootlin.com>


^ permalink raw reply

* Re: [PATCH v3 17/27] mtd: spi-nor: debugfs: Add locking support
From: Miquel Raynal @ 2026-04-03 16:06 UTC (permalink / raw)
  To: Takahiro.Kuwano
  Cc: pratyush, mwalle, richard, vigneshr, corbet, tudor.ambarus,
	sean.anderson, thomas.petazzoni, STLin2, linux-mtd, linux-kernel,
	linux-doc
In-Reply-To: <ee2057a104d44d3dbfbedcf5b1ed80b2@infineon.com>

Hi Takahiro,

>> +u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor);
>> +void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_t *ofs, u64 *len);
>> +bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, const u8 *sr);
>> +
>
> It would be better to have generic helper functions rather than using SR-based
> functions directly. The locking_ops is vendor/chip specific and can provide
> other locking mechanism than SR-based block protection. For instance, Infineon,
> Micron, Macronix (and maybe other vendors) offer protection mechanisms that can
> protect sectors individually with volatile or non-volatile manner.

I get what you mean, thanks for pointing this out. Unfortunately it's
not that straightforward.

As of today, there are the "default" locking ops (SR based) and a few
others, chip specific. For debugging purposes, these "other" ops do not
provide any kind of useful feedback regarding what has actually been
locked. Only SR based chips provide this, it is useful, I want to use it.

So I'm going to expose a new boolean to filter out whether we have
locking support *and* if yes, whether we can use SR based functions, this
should also address this concern:

> Don't we need to check 'SNOR_F_HAS_LOCK' flag here?

We will still need to expose the SR specific helpers, though, but at
least we will no longer get inconsistencies with chips not featuring the
default SR approach.

>> +       seq_puts(s, "\nlocked sectors\n");
>> +       seq_puts(s, " region (in hex)   | status   | #blocks\n");
>> +       seq_puts(s, " ------------------+----------+--------\n");
>> +
>> +       spi_nor_get_locked_range_sr(nor, nor->dfs_sr_cache, &lock_start, &lock_length);
>> +       if (!lock_length || lock_length == params->size) {
>> +               seq_printf(s, " %08llx-%08llx | %s | %llu\n", 0ULL, params->size - 1,
>> +                          lock_length ? "  locked" : "unlocked", params->size / min_prot_len);
>
> div_u64() is needed. 
> I got undefined reference to `__aeabi_uldivmod' for my 32-bit ARM platform.
> Same for following four seq_printf().

Good catch, I will fix this too.

Thanks,
Miquèl

^ permalink raw reply

* Re: [PATCH v2 2/3] Documentation: explain how to find maintainers addresses for security reports
From: Kees Cook @ 2026-04-03 15:48 UTC (permalink / raw)
  To: Willy Tarreau
  Cc: greg, edumazet, rdunlap, Jonathan Corbet, skhan, workflows,
	linux-doc, linux-kernel
In-Reply-To: <20260403062018.31080-3-w@1wt.eu>

On Fri, Apr 03, 2026 at 08:20:17AM +0200, Willy Tarreau wrote:
> [...]
> +One difficulty for most first-time reporters is to figure the right list of
> +recipients to send a report to.  In the Linux kernel, all official maintainers
> +are trusted, so the consequences of accidentally including the wrong maintainer
> +are essentially a bit more noise for that person, i.e. nothing dramatic.  As

Yeah, this is the central point: we already trust maintainers; there is
nothing "special" about security@kernel.org.

> [...]
> +single line suitable for use in the To: field of a mailer like this::
> +
> +  $ ./scripts/get_maintainer.pl --no-tree --no-l --no-r --no-n --m \
> +    --no-git-fallback --no-substatus --no-rolestats --no-multiline \
> +    --pattern-depth 1 drivers/example.c
> +  dev1@example.com, dev2@example.org

To echo Greg, yeah, this is great, and has been an implicit action we've
done for years, so there's every reason to delegate it to the reporter
to avoid the round-trip.

Though I guess we'll see if these new instructions actually change
anything -- we still have people asking for CVE assignments. :P

Reviewed-by: Kees Cook <kees@kernel.org>

-- 
Kees Cook

^ permalink raw reply

* Re: [net-next,13/15] ice: add support for unmanaged DPLL on E830 NIC
From: Tony Nguyen @ 2026-04-03 15:44 UTC (permalink / raw)
  To: Paolo Abeni
  Cc: andriy.shevchenko, pmenzel, aleksandr.loktionov, mika.westerberg,
	mbloch, saeedm, piotr.kwapulinski, sx.rinitha, kuba,
	grzegorz.nitka, netdev, przemyslaw.kitszel, arkadiusz.kubalewski,
	tariqt, davem, dima.ruinskiy, bhelgaas, andrew+netdev, lukas,
	jacob.e.keller, richardcochran, takkozu, enjuk,
	maximilianpezzullo, joe, przemyslaw.korba, leon, vgrinber,
	linux-pci, sunithax.d.mekala, corbet, vinicius.gomes,
	ilpo.jarvinen, edumazet, linux-doc, kohei.enju, kohei, horms,
	avigailx.dahan
In-Reply-To: <20260402102616.177883-1-pabeni@redhat.com>



On 4/2/2026 3:26 AM, Paolo Abeni wrote:
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> 
> For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
> ---
> ice: add support for unmanaged DPLL on E830 NIC
> 
> Hardware variants of E830 may support an unmanaged DPLL where the
> configuration is hardcoded within the hardware and firmware, meaning
> users cannot modify settings. However, users are able to check the DPLL
> lock status and obtain configuration information through the Linux DPLL
> and devlink health subsystem.
> 
>> diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.c b/drivers/net/ethernet/intel/ice/ice_dpll.c
>> index 62f75701d652..2a9eb233dbf4 100644
>> --- a/drivers/net/ethernet/intel/ice/ice_dpll.c
>> +++ b/drivers/net/ethernet/intel/ice/ice_dpll.c
> 
> [ ... ]
> 
>> @@ -3148,12 +3176,15 @@ ice_dpll_init_direct_pins(struct ice_pf *pf, bool cgu,
>>   	int ret;
>>
>>   	ret = ice_dpll_get_pins(pf, pins, start_idx, count, pf->dplls.clock_id);
>> -	if (ret)
>> +	if (!cgu || ret)
>>   		return ret;
>> -	if (cgu) {
>> +
>> +	if (first) {
>>   		ret = ice_dpll_register_pins(first, pins, ops, count);
>>   		if (ret)
>>   			goto release_pins;
>> +	}
>> +	if (second) {
>>   		ret = ice_dpll_register_pins(second, pins, ops, count);
>>   		if (ret)
>>   			goto unregister_first;
>> @@ -3162,7 +3193,8 @@ ice_dpll_init_direct_pins(struct ice_pf *pf, bool cgu,
>>   	return 0;
>>
>>   unregister_first:
>> -	ice_dpll_unregister_pins(first, pins, ops, count);
>> +	if (first)
>> +		ice_dpll_unregister_pins(first, pins, ops, count);
>>   release_pins:
>>   	ice_dpll_release_pins(pins, count);
>>   	return ret;
> 
> NULL checks were added to ice_dpll_init_direct_pins for handling unmanaged
> DPLL initialization where only one DPLL device is active. However, is the
> corresponding cleanup function ice_dpll_deinit_direct_pins missing the same
> NULL checks?
> 
> In unmanaged mode, only pf->dplls.pps.dpll is initialized while
> pf->dplls.eec.dpll remains NULL. When ice_dpll_init_pins calls
> ice_dpll_init_direct_pins(pf, true, outputs, count, num_outputs,
> output_ops, eec.dpll, pps.dpll), if this second call (for output pins)
> fails, the error path calls ice_dpll_deinit_direct_pins(pf, true, ...,
> pps.dpll, eec.dpll).
> 
> Looking at ice_dpll_deinit_direct_pins:
> 
> static void ice_dpll_deinit_direct_pins(struct ice_pf *pf, bool cgu,
> 					struct ice_dpll_pin *pins, int count,
> 					const struct dpll_pin_ops *ops,
> 					struct dpll_device *first,
> 					struct dpll_device *second)
> {
> 	if (cgu) {
> 		ice_dpll_unregister_pins(first, pins, ops, count);
> 		ice_dpll_unregister_pins(second, pins, ops, count);
> 	}
> 	ice_dpll_release_pins(pins, count);
> }
> 
> When cgu=true and second=NULL, ice_dpll_unregister_pins is called with
> dpll=NULL. Then dpll_pin_unregister(dpll, pin) dereferences dpll with
> xa_empty(&dpll->pin_refs), causing a NULL pointer dereference.
> 
> Shouldn't ice_dpll_deinit_direct_pins add NULL checks for first and second,
> matching the guards you added in ice_dpll_init_direct_pins?
> 
>> diff --git a/drivers/net/ethernet/intel/ice/ice_dpll.h b/drivers/net/ethernet/intel/ice/ice_dpll.h
>> index ae42cdea0ee1..2c98b6c6deb0 100644
>> --- a/drivers/net/ethernet/intel/ice/ice_dpll.h
>> +++ b/drivers/net/ethernet/intel/ice/ice_dpll.h
> 
> [ ... ]
> 
>> @@ -173,3 +181,5 @@ static inline void ice_dpll_deinit(struct ice_pf *pf) { }
>>   #define ICE_CGU_R11_SYNCE_S_BYP_CLK		GENMASK(6, 1)
>>
>>   #define ICE_CGU_BYPASS_MUX_OFFSET_E825C		3
>> +#define ICE_DPLL_UNMANAGED_PIN_NUM		4
>> +#define ICE_DPLL_IN_ESYNC_ENABLED	ICE_AQC_GET_CGU_IN_CFG_FLG2_ESYNC_EN
> 
> This isn't a bug, but ICE_DPLL_UNMANAGED_PIN_NUM is defined but never
> used. Looking at the unmanaged pin descriptors, ice_e830_unmanaged_inputs
> has 1 entry and ice_e830_unmanaged_outputs has 2 entries (totaling 3 pins,
> not 4).

Hi Paolo,

We're working on changes to resolve these issues.

Thanks,
Tony


^ permalink raw reply

* [PATCH v8 0/3] Support runtime configuration for per-VM's HGATP mode
From: fangyu.yu @ 2026-04-03 15:30 UTC (permalink / raw)
  To: pbonzini, corbet, anup, atish.patra, pjw, palmer, aou, alex,
	skhan
  Cc: guoren, radim.krcmar, andrew.jones, linux-doc, kvm, kvm-riscv,
	linux-riscv, linux-kernel, Fangyu Yu

From: Fangyu Yu <fangyu.yu@linux.alibaba.com>

Currently, RISC-V KVM hardcodes the G-stage page table format (HGATP mode)
to the maximum mode detected at boot time (e.g., SV57x4 if supported). but
often such a wide GPA is unnecessary, just as a host sometimes doesn't need
sv57.

This patch reuse KVM_CAP_VM_GPA_BITS to select HGATP.MODE. User-space can
now explicitly request a specific HGATP mode (SV39x4, SV48x4, SV57x4 or
SV32x4) during VM creation.

---
Changes in v8:
    - Reuse KVM_CAP_VM_GPA_BITS to advertise and select the effective GPA width
      for a VM (Anup's suggestion).
    - Handle the kvm == NULL case and task kvm->lock and kvm->slots_lock to serialize
      against concurrent vCPU creation and memslot updates (Radim's suggestion).
    - Link to v7:
      https://lore.kernel.org/linux-riscv/20260402132303.6252-1-fangyu.yu@linux.alibaba.com/
---
Changes in v7 (Anup's suggestions):
    - Keep the original HGATP mode probing logic.
    - Link to v6:
      https://lore.kernel.org/linux-riscv/20260330122601.22140-1-fangyu.yu@linux.alibaba.com/
---
Changes in v6 (Anup's suggestions):
    - Reworked kvm_riscv_gstage_gpa_bits() and kvm_riscv_gstage_gpa_size() to
      take "unsigned long pgd_levels" instead of "struct kvm_arch *".
    - Moved kvm_riscv_gstage_mode() helper from kvm_host.h to kvm_gstage.h.
    - Renamed kvm->arch.kvm_riscv_gstage_pgd_levels to kvm->arch.pgd_levels.
    - Added pgd_levels to struct kvm_gstage to avoid repeated
      gstage->kvm->arch pointer chasing.
    - Link to v5:
      https://lore.kernel.org/linux-riscv/20260204134507.33912-1-fangyu.yu@linux.alibaba.com/
---
Changes in v5:
    - Use architectural HGATP.MODE encodings as the bit index for the supported-mode
      bitmap and for the VM-mode selection UAPI; no new UAPI mode/bit defines are
      introduced(per Radim).
    - Allow KVM_CAP_RISCV_SET_HGATP_MODE on RV32 as well(per Drew).
    - Link to v4:
      https://lore.kernel.org/linux-riscv/20260202140716.34323-1-fangyu.yu@linux.alibaba.com/
---
Changes in v4:
    - Extend kvm_riscv_gstage_mode_detect() to probe all HGATP.MODE values
      supported by the host and record them in a bitmask.
    - Treat unexpected pgd_levels in kvm_riscv_gstage_mode() as an internal error
      (e.g. WARN_ON_ONCE())(per Radim).
    - Move kvm_riscv_gstage_gpa_bits() and kvm_riscv_gstage_gpa_size() to header
      as static inline helpers(per Radim).
    - Drop gstage_mode_user_initialized and Remove the kvm_debug() message from
      KVM_CAP_RISCV_SET_HGATP_MODE(per Radim).
    - Link to v3:
      https://lore.kernel.org/linux-riscv/20260125150450.27068-1-fangyu.yu@linux.alibaba.com/
---
Changes in v3:
    - Reworked the patch formatting (per Drew).
    - Dropped kvm->arch.kvm_riscv_gstage_mode and derive HGATP.MODE from
      kvm_riscv_gstage_pgd_levels via a helper, avoiding redundant per-VM state(per Drew).
    - Removed kvm_riscv_gstage_max_mode and keep only kvm_riscv_gstage_max_pgd_levels
      for host capability detection(per Drew).
    - Other initialization and return value issues(per Drew).
    - Enforce that KVM_CAP_RISCV_SET_HGATP_MODE can only be enabled before any vCPUs
      are created by rejecting the ioctl once kvm->created_vcpus is non-zero(per Radim).
    - Add a memslot safety check and reject the capability unless
      kvm_are_all_memslots_empty(kvm) is true, ensuring the G-stage format is not
      changed after any memslots have been installed(per Radim).
    - Link to v2:
      https://lore.kernel.org/linux-riscv/20260105143232.76715-1-fangyu.yu@linux.alibaba.com/

Fangyu Yu (3):
  RISC-V: KVM: Support runtime configuration for per-VM's HGATP mode
  RISC-V: KVM: Cache gstage pgd_levels in struct kvm_gstage
  RISC-V: KVM: Reuse KVM_CAP_VM_GPA_BITS to select HGATP.MODE

 arch/riscv/include/asm/kvm_gstage.h | 47 ++++++++++++++++---
 arch/riscv/include/asm/kvm_host.h   |  1 +
 arch/riscv/kvm/gstage.c             | 65 +++++++++++++--------------
 arch/riscv/kvm/main.c               | 12 ++---
 arch/riscv/kvm/mmu.c                | 70 +++++++++--------------------
 arch/riscv/kvm/vm.c                 | 49 ++++++++++++++++++--
 arch/riscv/kvm/vmid.c               |  3 +-
 7 files changed, 148 insertions(+), 99 deletions(-)

-- 
2.50.1


^ permalink raw reply

* [PATCH v8 2/3] RISC-V: KVM: Cache gstage pgd_levels in struct kvm_gstage
From: fangyu.yu @ 2026-04-03 15:30 UTC (permalink / raw)
  To: pbonzini, corbet, anup, atish.patra, pjw, palmer, aou, alex,
	skhan
  Cc: guoren, radim.krcmar, andrew.jones, linux-doc, kvm, kvm-riscv,
	linux-riscv, linux-kernel, Fangyu Yu
In-Reply-To: <20260403153019.9916-1-fangyu.yu@linux.alibaba.com>

From: Fangyu Yu <fangyu.yu@linux.alibaba.com>

Gstage page-table helpers frequently chase gstage->kvm->arch to
fetch pgd_levels. This adds noise and repeats the same dereference
chain in hot paths.

Add pgd_levels to struct kvm_gstage and initialize it from kvm->arch
when setting up a gstage instance. Introduce kvm_riscv_gstage_init()
to centralize initialization and switch gstage code to use
gstage->pgd_levels.

Suggested-by: Anup Patel <anup@brainfault.org>
Signed-off-by: Fangyu Yu <fangyu.yu@linux.alibaba.com>
Reviewed-by: Anup Patel <anup@brainfault.org>
---
 arch/riscv/include/asm/kvm_gstage.h | 10 ++++++
 arch/riscv/kvm/gstage.c             | 10 +++---
 arch/riscv/kvm/mmu.c                | 50 ++++++-----------------------
 3 files changed, 25 insertions(+), 45 deletions(-)

diff --git a/arch/riscv/include/asm/kvm_gstage.h b/arch/riscv/include/asm/kvm_gstage.h
index 5aa58d1f692a..70d9d483365e 100644
--- a/arch/riscv/include/asm/kvm_gstage.h
+++ b/arch/riscv/include/asm/kvm_gstage.h
@@ -15,6 +15,7 @@ struct kvm_gstage {
 #define KVM_GSTAGE_FLAGS_LOCAL		BIT(0)
 	unsigned long vmid;
 	pgd_t *pgd;
+	unsigned long pgd_levels;
 };
 
 struct kvm_gstage_mapping {
@@ -92,4 +93,13 @@ static inline unsigned long kvm_riscv_gstage_mode(unsigned long pgd_levels)
 	}
 }
 
+static inline void kvm_riscv_gstage_init(struct kvm_gstage *gstage, struct kvm *kvm)
+{
+	gstage->kvm = kvm;
+	gstage->flags = 0;
+	gstage->vmid = READ_ONCE(kvm->arch.vmid.vmid);
+	gstage->pgd = kvm->arch.pgd;
+	gstage->pgd_levels = kvm->arch.pgd_levels;
+}
+
 #endif
diff --git a/arch/riscv/kvm/gstage.c b/arch/riscv/kvm/gstage.c
index 4beb9322fe76..7c4c34bc191b 100644
--- a/arch/riscv/kvm/gstage.c
+++ b/arch/riscv/kvm/gstage.c
@@ -26,7 +26,7 @@ static inline unsigned long gstage_pte_index(struct kvm_gstage *gstage,
 	unsigned long mask;
 	unsigned long shift = HGATP_PAGE_SHIFT + (kvm_riscv_gstage_index_bits * level);
 
-	if (level == gstage->kvm->arch.pgd_levels - 1)
+	if (level == gstage->pgd_levels - 1)
 		mask = (PTRS_PER_PTE * (1UL << kvm_riscv_gstage_pgd_xbits)) - 1;
 	else
 		mask = PTRS_PER_PTE - 1;
@@ -45,7 +45,7 @@ static int gstage_page_size_to_level(struct kvm_gstage *gstage, unsigned long pa
 	u32 i;
 	unsigned long psz = 1UL << 12;
 
-	for (i = 0; i < gstage->kvm->arch.pgd_levels; i++) {
+	for (i = 0; i < gstage->pgd_levels; i++) {
 		if (page_size == (psz << (i * kvm_riscv_gstage_index_bits))) {
 			*out_level = i;
 			return 0;
@@ -58,7 +58,7 @@ static int gstage_page_size_to_level(struct kvm_gstage *gstage, unsigned long pa
 static int gstage_level_to_page_order(struct kvm_gstage *gstage, u32 level,
 				      unsigned long *out_pgorder)
 {
-	if (gstage->kvm->arch.pgd_levels < level)
+	if (gstage->pgd_levels < level)
 		return -EINVAL;
 
 	*out_pgorder = 12 + (level * kvm_riscv_gstage_index_bits);
@@ -83,7 +83,7 @@ bool kvm_riscv_gstage_get_leaf(struct kvm_gstage *gstage, gpa_t addr,
 			       pte_t **ptepp, u32 *ptep_level)
 {
 	pte_t *ptep;
-	u32 current_level = gstage->kvm->arch.pgd_levels - 1;
+	u32 current_level = gstage->pgd_levels - 1;
 
 	*ptep_level = current_level;
 	ptep = (pte_t *)gstage->pgd;
@@ -127,7 +127,7 @@ int kvm_riscv_gstage_set_pte(struct kvm_gstage *gstage,
 			     struct kvm_mmu_memory_cache *pcache,
 			     const struct kvm_gstage_mapping *map)
 {
-	u32 current_level = gstage->kvm->arch.pgd_levels - 1;
+	u32 current_level = gstage->pgd_levels - 1;
 	pte_t *next_ptep = (pte_t *)gstage->pgd;
 	pte_t *ptep = &next_ptep[gstage_pte_index(gstage, map->addr, current_level)];
 
diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
index fbcdd75cb9af..2d3def024270 100644
--- a/arch/riscv/kvm/mmu.c
+++ b/arch/riscv/kvm/mmu.c
@@ -24,10 +24,7 @@ static void mmu_wp_memory_region(struct kvm *kvm, int slot)
 	phys_addr_t end = (memslot->base_gfn + memslot->npages) << PAGE_SHIFT;
 	struct kvm_gstage gstage;
 
-	gstage.kvm = kvm;
-	gstage.flags = 0;
-	gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
-	gstage.pgd = kvm->arch.pgd;
+	kvm_riscv_gstage_init(&gstage, kvm);
 
 	spin_lock(&kvm->mmu_lock);
 	kvm_riscv_gstage_wp_range(&gstage, start, end);
@@ -49,10 +46,7 @@ int kvm_riscv_mmu_ioremap(struct kvm *kvm, gpa_t gpa, phys_addr_t hpa,
 	struct kvm_gstage_mapping map;
 	struct kvm_gstage gstage;
 
-	gstage.kvm = kvm;
-	gstage.flags = 0;
-	gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
-	gstage.pgd = kvm->arch.pgd;
+	kvm_riscv_gstage_init(&gstage, kvm);
 
 	end = (gpa + size + PAGE_SIZE - 1) & PAGE_MASK;
 	pfn = __phys_to_pfn(hpa);
@@ -89,10 +83,7 @@ void kvm_riscv_mmu_iounmap(struct kvm *kvm, gpa_t gpa, unsigned long size)
 {
 	struct kvm_gstage gstage;
 
-	gstage.kvm = kvm;
-	gstage.flags = 0;
-	gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
-	gstage.pgd = kvm->arch.pgd;
+	kvm_riscv_gstage_init(&gstage, kvm);
 
 	spin_lock(&kvm->mmu_lock);
 	kvm_riscv_gstage_unmap_range(&gstage, gpa, size, false);
@@ -109,10 +100,7 @@ void kvm_arch_mmu_enable_log_dirty_pt_masked(struct kvm *kvm,
 	phys_addr_t end = (base_gfn + __fls(mask) + 1) << PAGE_SHIFT;
 	struct kvm_gstage gstage;
 
-	gstage.kvm = kvm;
-	gstage.flags = 0;
-	gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
-	gstage.pgd = kvm->arch.pgd;
+	kvm_riscv_gstage_init(&gstage, kvm);
 
 	kvm_riscv_gstage_wp_range(&gstage, start, end);
 }
@@ -141,10 +129,7 @@ void kvm_arch_flush_shadow_memslot(struct kvm *kvm,
 	phys_addr_t size = slot->npages << PAGE_SHIFT;
 	struct kvm_gstage gstage;
 
-	gstage.kvm = kvm;
-	gstage.flags = 0;
-	gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
-	gstage.pgd = kvm->arch.pgd;
+	kvm_riscv_gstage_init(&gstage, kvm);
 
 	spin_lock(&kvm->mmu_lock);
 	kvm_riscv_gstage_unmap_range(&gstage, gpa, size, false);
@@ -250,10 +235,7 @@ bool kvm_unmap_gfn_range(struct kvm *kvm, struct kvm_gfn_range *range)
 	if (!kvm->arch.pgd)
 		return false;
 
-	gstage.kvm = kvm;
-	gstage.flags = 0;
-	gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
-	gstage.pgd = kvm->arch.pgd;
+	kvm_riscv_gstage_init(&gstage, kvm);
 	mmu_locked = spin_trylock(&kvm->mmu_lock);
 	kvm_riscv_gstage_unmap_range(&gstage, range->start << PAGE_SHIFT,
 				     (range->end - range->start) << PAGE_SHIFT,
@@ -275,10 +257,7 @@ bool kvm_age_gfn(struct kvm *kvm, struct kvm_gfn_range *range)
 
 	WARN_ON(size != PAGE_SIZE && size != PMD_SIZE && size != PUD_SIZE);
 
-	gstage.kvm = kvm;
-	gstage.flags = 0;
-	gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
-	gstage.pgd = kvm->arch.pgd;
+	kvm_riscv_gstage_init(&gstage, kvm);
 	if (!kvm_riscv_gstage_get_leaf(&gstage, range->start << PAGE_SHIFT,
 				       &ptep, &ptep_level))
 		return false;
@@ -298,10 +277,7 @@ bool kvm_test_age_gfn(struct kvm *kvm, struct kvm_gfn_range *range)
 
 	WARN_ON(size != PAGE_SIZE && size != PMD_SIZE && size != PUD_SIZE);
 
-	gstage.kvm = kvm;
-	gstage.flags = 0;
-	gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
-	gstage.pgd = kvm->arch.pgd;
+	kvm_riscv_gstage_init(&gstage, kvm);
 	if (!kvm_riscv_gstage_get_leaf(&gstage, range->start << PAGE_SHIFT,
 				       &ptep, &ptep_level))
 		return false;
@@ -463,10 +439,7 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
 	struct kvm_gstage gstage;
 	struct page *page;
 
-	gstage.kvm = kvm;
-	gstage.flags = 0;
-	gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
-	gstage.pgd = kvm->arch.pgd;
+	kvm_riscv_gstage_init(&gstage, kvm);
 
 	/* Setup initial state of output mapping */
 	memset(out_map, 0, sizeof(*out_map));
@@ -587,10 +560,7 @@ void kvm_riscv_mmu_free_pgd(struct kvm *kvm)
 
 	spin_lock(&kvm->mmu_lock);
 	if (kvm->arch.pgd) {
-		gstage.kvm = kvm;
-		gstage.flags = 0;
-		gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
-		gstage.pgd = kvm->arch.pgd;
+		kvm_riscv_gstage_init(&gstage, kvm);
 		kvm_riscv_gstage_unmap_range(&gstage, 0UL,
 			kvm_riscv_gstage_gpa_size(kvm->arch.pgd_levels), false);
 		pgd = READ_ONCE(kvm->arch.pgd);
-- 
2.50.1


^ permalink raw reply related

* [PATCH v8 3/3] RISC-V: KVM: Reuse KVM_CAP_VM_GPA_BITS to select HGATP.MODE
From: fangyu.yu @ 2026-04-03 15:30 UTC (permalink / raw)
  To: pbonzini, corbet, anup, atish.patra, pjw, palmer, aou, alex,
	skhan
  Cc: guoren, radim.krcmar, andrew.jones, linux-doc, kvm, kvm-riscv,
	linux-riscv, linux-kernel, Fangyu Yu
In-Reply-To: <20260403153019.9916-1-fangyu.yu@linux.alibaba.com>

From: Fangyu Yu <fangyu.yu@linux.alibaba.com>

Reuse KVM_CAP_VM_GPA_BITS to advertise and select the effective
G-stage GPA width for a VM.

KVM_CHECK_EXTENSION(KVM_CAP_VM_GPA_BITS) returns the effective GPA
bits for a VM, KVM_ENABLE_CAP(KVM_CAP_VM_GPA_BITS) allows userspace
to downsize the effective GPA width by selecting a smaller G-stage
page table format:
  - gpa_bits <= 41 selects Sv39x4 (pgd_levels=3)
  - gpa_bits <= 50 selects Sv48x4 (pgd_levels=4)
  - gpa_bits <= 59 selects Sv57x4 (pgd_levels=5)

Reject the request with -EINVAL for unsupported values and with -EBUSY
if vCPUs have been created or any memslot is populated.

Signed-off-by: Fangyu Yu <fangyu.yu@linux.alibaba.com>
Reviewed-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
Reviewed-by: Guo Ren <guoren@kernel.org>
---
 arch/riscv/kvm/vm.c | 44 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 42 insertions(+), 2 deletions(-)

diff --git a/arch/riscv/kvm/vm.c b/arch/riscv/kvm/vm.c
index fb7c4e07961f..a9f083feeb76 100644
--- a/arch/riscv/kvm/vm.c
+++ b/arch/riscv/kvm/vm.c
@@ -214,12 +214,52 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
 
 int kvm_vm_ioctl_enable_cap(struct kvm *kvm, struct kvm_enable_cap *cap)
 {
+	if (cap->flags)
+		return -EINVAL;
+
 	switch (cap->cap) {
 	case KVM_CAP_RISCV_MP_STATE_RESET:
-		if (cap->flags)
-			return -EINVAL;
 		kvm->arch.mp_state_reset = true;
 		return 0;
+	case KVM_CAP_VM_GPA_BITS: {
+		unsigned long gpa_bits = cap->args[0];
+		unsigned long new_levels;
+		int r = 0;
+
+		/* Decide target pgd levels from requested gpa_bits */
+#ifdef CONFIG_64BIT
+		if (gpa_bits <= 41)
+			new_levels = 3;        /* Sv39x4 */
+		else if (gpa_bits <= 50)
+			new_levels = 4;        /* Sv48x4 */
+		else if (gpa_bits <= 59)
+			new_levels = 5;        /* Sv57x4 */
+		else
+			return -EINVAL;
+#else
+		/* 32-bit: only Sv32x4*/
+		if (gpa_bits <= 34)
+			new_levels = 2;
+		else
+			return -EINVAL;
+#endif
+		if (new_levels > kvm_riscv_gstage_max_pgd_levels)
+			return -EINVAL;
+
+		/* Follow KVM's lock ordering: kvm->lock -> kvm->slots_lock. */
+		mutex_lock(&kvm->lock);
+		mutex_lock(&kvm->slots_lock);
+
+		if (kvm->created_vcpus || !kvm_are_all_memslots_empty(kvm))
+			r = -EBUSY;
+		else
+			kvm->arch.pgd_levels = new_levels;
+
+		mutex_unlock(&kvm->slots_lock);
+		mutex_unlock(&kvm->lock);
+
+		return r;
+	}
 	default:
 		return -EINVAL;
 	}
-- 
2.50.1


^ permalink raw reply related

* [PATCH v8 1/3] RISC-V: KVM: Support runtime configuration for per-VM's HGATP mode
From: fangyu.yu @ 2026-04-03 15:30 UTC (permalink / raw)
  To: pbonzini, corbet, anup, atish.patra, pjw, palmer, aou, alex,
	skhan
  Cc: guoren, radim.krcmar, andrew.jones, linux-doc, kvm, kvm-riscv,
	linux-riscv, linux-kernel, Fangyu Yu
In-Reply-To: <20260403153019.9916-1-fangyu.yu@linux.alibaba.com>

From: Fangyu Yu <fangyu.yu@linux.alibaba.com>

Introduces one per-VM architecture-specific fields to support runtime
configuration of the G-stage page table format:

- kvm->arch.pgd_levels: the corresponding number of page table levels
  for the selected mode.

These fields replace the previous global variables
kvm_riscv_gstage_mode and kvm_riscv_gstage_pgd_levels, enabling different
virtual machines to independently select their G-stage page table format
instead of being forced to share the maximum mode detected by the kernel
at boot time.

Signed-off-by: Fangyu Yu <fangyu.yu@linux.alibaba.com>
Reviewed-by: Andrew Jones <andrew.jones@oss.qualcomm.com>
Reviewed-by: Anup Patel <anup@brainfault.org>
Reviewed-by: Guo Ren <guoren@kernel.org>
---
 arch/riscv/include/asm/kvm_gstage.h | 37 ++++++++++++----
 arch/riscv/include/asm/kvm_host.h   |  1 +
 arch/riscv/kvm/gstage.c             | 65 ++++++++++++++---------------
 arch/riscv/kvm/main.c               | 12 +++---
 arch/riscv/kvm/mmu.c                | 20 +++++----
 arch/riscv/kvm/vm.c                 |  5 ++-
 arch/riscv/kvm/vmid.c               |  3 +-
 7 files changed, 86 insertions(+), 57 deletions(-)

diff --git a/arch/riscv/include/asm/kvm_gstage.h b/arch/riscv/include/asm/kvm_gstage.h
index 595e2183173e..5aa58d1f692a 100644
--- a/arch/riscv/include/asm/kvm_gstage.h
+++ b/arch/riscv/include/asm/kvm_gstage.h
@@ -29,16 +29,22 @@ struct kvm_gstage_mapping {
 #define kvm_riscv_gstage_index_bits	10
 #endif
 
-extern unsigned long kvm_riscv_gstage_mode;
-extern unsigned long kvm_riscv_gstage_pgd_levels;
+extern unsigned long kvm_riscv_gstage_max_pgd_levels;
 
 #define kvm_riscv_gstage_pgd_xbits	2
 #define kvm_riscv_gstage_pgd_size	(1UL << (HGATP_PAGE_SHIFT + kvm_riscv_gstage_pgd_xbits))
-#define kvm_riscv_gstage_gpa_bits	(HGATP_PAGE_SHIFT + \
-					 (kvm_riscv_gstage_pgd_levels * \
-					  kvm_riscv_gstage_index_bits) + \
-					 kvm_riscv_gstage_pgd_xbits)
-#define kvm_riscv_gstage_gpa_size	((gpa_t)(1ULL << kvm_riscv_gstage_gpa_bits))
+
+static inline unsigned long kvm_riscv_gstage_gpa_bits(unsigned long pgd_levels)
+{
+	return (HGATP_PAGE_SHIFT +
+		pgd_levels * kvm_riscv_gstage_index_bits +
+		kvm_riscv_gstage_pgd_xbits);
+}
+
+static inline gpa_t kvm_riscv_gstage_gpa_size(unsigned long pgd_levels)
+{
+	return BIT_ULL(kvm_riscv_gstage_gpa_bits(pgd_levels));
+}
 
 bool kvm_riscv_gstage_get_leaf(struct kvm_gstage *gstage, gpa_t addr,
 			       pte_t **ptepp, u32 *ptep_level);
@@ -69,4 +75,21 @@ void kvm_riscv_gstage_wp_range(struct kvm_gstage *gstage, gpa_t start, gpa_t end
 
 void kvm_riscv_gstage_mode_detect(void);
 
+static inline unsigned long kvm_riscv_gstage_mode(unsigned long pgd_levels)
+{
+	switch (pgd_levels) {
+	case 2:
+		return HGATP_MODE_SV32X4;
+	case 3:
+		return HGATP_MODE_SV39X4;
+	case 4:
+		return HGATP_MODE_SV48X4;
+	case 5:
+		return HGATP_MODE_SV57X4;
+	default:
+		WARN_ON_ONCE(1);
+		return HGATP_MODE_OFF;
+	}
+}
+
 #endif
diff --git a/arch/riscv/include/asm/kvm_host.h b/arch/riscv/include/asm/kvm_host.h
index 24585304c02b..478f699e9dec 100644
--- a/arch/riscv/include/asm/kvm_host.h
+++ b/arch/riscv/include/asm/kvm_host.h
@@ -94,6 +94,7 @@ struct kvm_arch {
 	/* G-stage page table */
 	pgd_t *pgd;
 	phys_addr_t pgd_phys;
+	unsigned long pgd_levels;
 
 	/* Guest Timer */
 	struct kvm_guest_timer timer;
diff --git a/arch/riscv/kvm/gstage.c b/arch/riscv/kvm/gstage.c
index b67d60d722c2..4beb9322fe76 100644
--- a/arch/riscv/kvm/gstage.c
+++ b/arch/riscv/kvm/gstage.c
@@ -12,22 +12,21 @@
 #include <asm/kvm_gstage.h>
 
 #ifdef CONFIG_64BIT
-unsigned long kvm_riscv_gstage_mode __ro_after_init = HGATP_MODE_SV39X4;
-unsigned long kvm_riscv_gstage_pgd_levels __ro_after_init = 3;
+unsigned long kvm_riscv_gstage_max_pgd_levels __ro_after_init = 3;
 #else
-unsigned long kvm_riscv_gstage_mode __ro_after_init = HGATP_MODE_SV32X4;
-unsigned long kvm_riscv_gstage_pgd_levels __ro_after_init = 2;
+unsigned long kvm_riscv_gstage_max_pgd_levels __ro_after_init = 2;
 #endif
 
 #define gstage_pte_leaf(__ptep)	\
 	(pte_val(*(__ptep)) & (_PAGE_READ | _PAGE_WRITE | _PAGE_EXEC))
 
-static inline unsigned long gstage_pte_index(gpa_t addr, u32 level)
+static inline unsigned long gstage_pte_index(struct kvm_gstage *gstage,
+					     gpa_t addr, u32 level)
 {
 	unsigned long mask;
 	unsigned long shift = HGATP_PAGE_SHIFT + (kvm_riscv_gstage_index_bits * level);
 
-	if (level == (kvm_riscv_gstage_pgd_levels - 1))
+	if (level == gstage->kvm->arch.pgd_levels - 1)
 		mask = (PTRS_PER_PTE * (1UL << kvm_riscv_gstage_pgd_xbits)) - 1;
 	else
 		mask = PTRS_PER_PTE - 1;
@@ -40,12 +39,13 @@ static inline unsigned long gstage_pte_page_vaddr(pte_t pte)
 	return (unsigned long)pfn_to_virt(__page_val_to_pfn(pte_val(pte)));
 }
 
-static int gstage_page_size_to_level(unsigned long page_size, u32 *out_level)
+static int gstage_page_size_to_level(struct kvm_gstage *gstage, unsigned long page_size,
+				     u32 *out_level)
 {
 	u32 i;
 	unsigned long psz = 1UL << 12;
 
-	for (i = 0; i < kvm_riscv_gstage_pgd_levels; i++) {
+	for (i = 0; i < gstage->kvm->arch.pgd_levels; i++) {
 		if (page_size == (psz << (i * kvm_riscv_gstage_index_bits))) {
 			*out_level = i;
 			return 0;
@@ -55,21 +55,23 @@ static int gstage_page_size_to_level(unsigned long page_size, u32 *out_level)
 	return -EINVAL;
 }
 
-static int gstage_level_to_page_order(u32 level, unsigned long *out_pgorder)
+static int gstage_level_to_page_order(struct kvm_gstage *gstage, u32 level,
+				      unsigned long *out_pgorder)
 {
-	if (kvm_riscv_gstage_pgd_levels < level)
+	if (gstage->kvm->arch.pgd_levels < level)
 		return -EINVAL;
 
 	*out_pgorder = 12 + (level * kvm_riscv_gstage_index_bits);
 	return 0;
 }
 
-static int gstage_level_to_page_size(u32 level, unsigned long *out_pgsize)
+static int gstage_level_to_page_size(struct kvm_gstage *gstage, u32 level,
+				     unsigned long *out_pgsize)
 {
 	int rc;
 	unsigned long page_order = PAGE_SHIFT;
 
-	rc = gstage_level_to_page_order(level, &page_order);
+	rc = gstage_level_to_page_order(gstage, level, &page_order);
 	if (rc)
 		return rc;
 
@@ -81,11 +83,11 @@ bool kvm_riscv_gstage_get_leaf(struct kvm_gstage *gstage, gpa_t addr,
 			       pte_t **ptepp, u32 *ptep_level)
 {
 	pte_t *ptep;
-	u32 current_level = kvm_riscv_gstage_pgd_levels - 1;
+	u32 current_level = gstage->kvm->arch.pgd_levels - 1;
 
 	*ptep_level = current_level;
 	ptep = (pte_t *)gstage->pgd;
-	ptep = &ptep[gstage_pte_index(addr, current_level)];
+	ptep = &ptep[gstage_pte_index(gstage, addr, current_level)];
 	while (ptep && pte_val(ptep_get(ptep))) {
 		if (gstage_pte_leaf(ptep)) {
 			*ptep_level = current_level;
@@ -97,7 +99,7 @@ bool kvm_riscv_gstage_get_leaf(struct kvm_gstage *gstage, gpa_t addr,
 			current_level--;
 			*ptep_level = current_level;
 			ptep = (pte_t *)gstage_pte_page_vaddr(ptep_get(ptep));
-			ptep = &ptep[gstage_pte_index(addr, current_level)];
+			ptep = &ptep[gstage_pte_index(gstage, addr, current_level)];
 		} else {
 			ptep = NULL;
 		}
@@ -110,7 +112,7 @@ static void gstage_tlb_flush(struct kvm_gstage *gstage, u32 level, gpa_t addr)
 {
 	unsigned long order = PAGE_SHIFT;
 
-	if (gstage_level_to_page_order(level, &order))
+	if (gstage_level_to_page_order(gstage, level, &order))
 		return;
 	addr &= ~(BIT(order) - 1);
 
@@ -125,9 +127,9 @@ int kvm_riscv_gstage_set_pte(struct kvm_gstage *gstage,
 			     struct kvm_mmu_memory_cache *pcache,
 			     const struct kvm_gstage_mapping *map)
 {
-	u32 current_level = kvm_riscv_gstage_pgd_levels - 1;
+	u32 current_level = gstage->kvm->arch.pgd_levels - 1;
 	pte_t *next_ptep = (pte_t *)gstage->pgd;
-	pte_t *ptep = &next_ptep[gstage_pte_index(map->addr, current_level)];
+	pte_t *ptep = &next_ptep[gstage_pte_index(gstage, map->addr, current_level)];
 
 	if (current_level < map->level)
 		return -EINVAL;
@@ -151,7 +153,7 @@ int kvm_riscv_gstage_set_pte(struct kvm_gstage *gstage,
 		}
 
 		current_level--;
-		ptep = &next_ptep[gstage_pte_index(map->addr, current_level)];
+		ptep = &next_ptep[gstage_pte_index(gstage, map->addr, current_level)];
 	}
 
 	if (pte_val(*ptep) != pte_val(map->pte)) {
@@ -175,7 +177,7 @@ int kvm_riscv_gstage_map_page(struct kvm_gstage *gstage,
 	out_map->addr = gpa;
 	out_map->level = 0;
 
-	ret = gstage_page_size_to_level(page_size, &out_map->level);
+	ret = gstage_page_size_to_level(gstage, page_size, &out_map->level);
 	if (ret)
 		return ret;
 
@@ -217,7 +219,7 @@ void kvm_riscv_gstage_op_pte(struct kvm_gstage *gstage, gpa_t addr,
 	u32 next_ptep_level;
 	unsigned long next_page_size, page_size;
 
-	ret = gstage_level_to_page_size(ptep_level, &page_size);
+	ret = gstage_level_to_page_size(gstage, ptep_level, &page_size);
 	if (ret)
 		return;
 
@@ -229,7 +231,7 @@ void kvm_riscv_gstage_op_pte(struct kvm_gstage *gstage, gpa_t addr,
 	if (ptep_level && !gstage_pte_leaf(ptep)) {
 		next_ptep = (pte_t *)gstage_pte_page_vaddr(ptep_get(ptep));
 		next_ptep_level = ptep_level - 1;
-		ret = gstage_level_to_page_size(next_ptep_level, &next_page_size);
+		ret = gstage_level_to_page_size(gstage, next_ptep_level, &next_page_size);
 		if (ret)
 			return;
 
@@ -263,7 +265,7 @@ void kvm_riscv_gstage_unmap_range(struct kvm_gstage *gstage,
 
 	while (addr < end) {
 		found_leaf = kvm_riscv_gstage_get_leaf(gstage, addr, &ptep, &ptep_level);
-		ret = gstage_level_to_page_size(ptep_level, &page_size);
+		ret = gstage_level_to_page_size(gstage, ptep_level, &page_size);
 		if (ret)
 			break;
 
@@ -297,7 +299,7 @@ void kvm_riscv_gstage_wp_range(struct kvm_gstage *gstage, gpa_t start, gpa_t end
 
 	while (addr < end) {
 		found_leaf = kvm_riscv_gstage_get_leaf(gstage, addr, &ptep, &ptep_level);
-		ret = gstage_level_to_page_size(ptep_level, &page_size);
+		ret = gstage_level_to_page_size(gstage, ptep_level, &page_size);
 		if (ret)
 			break;
 
@@ -319,39 +321,34 @@ void __init kvm_riscv_gstage_mode_detect(void)
 	/* Try Sv57x4 G-stage mode */
 	csr_write(CSR_HGATP, HGATP_MODE_SV57X4 << HGATP_MODE_SHIFT);
 	if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV57X4) {
-		kvm_riscv_gstage_mode = HGATP_MODE_SV57X4;
-		kvm_riscv_gstage_pgd_levels = 5;
+		kvm_riscv_gstage_max_pgd_levels = 5;
 		goto done;
 	}
 
 	/* Try Sv48x4 G-stage mode */
 	csr_write(CSR_HGATP, HGATP_MODE_SV48X4 << HGATP_MODE_SHIFT);
 	if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV48X4) {
-		kvm_riscv_gstage_mode = HGATP_MODE_SV48X4;
-		kvm_riscv_gstage_pgd_levels = 4;
+		kvm_riscv_gstage_max_pgd_levels = 4;
 		goto done;
 	}
 
 	/* Try Sv39x4 G-stage mode */
 	csr_write(CSR_HGATP, HGATP_MODE_SV39X4 << HGATP_MODE_SHIFT);
 	if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV39X4) {
-		kvm_riscv_gstage_mode = HGATP_MODE_SV39X4;
-		kvm_riscv_gstage_pgd_levels = 3;
+		kvm_riscv_gstage_max_pgd_levels = 3;
 		goto done;
 	}
 #else /* CONFIG_32BIT */
 	/* Try Sv32x4 G-stage mode */
 	csr_write(CSR_HGATP, HGATP_MODE_SV32X4 << HGATP_MODE_SHIFT);
 	if ((csr_read(CSR_HGATP) >> HGATP_MODE_SHIFT) == HGATP_MODE_SV32X4) {
-		kvm_riscv_gstage_mode = HGATP_MODE_SV32X4;
-		kvm_riscv_gstage_pgd_levels = 2;
+		kvm_riscv_gstage_max_pgd_levels = 2;
 		goto done;
 	}
 #endif
 
 	/* KVM depends on !HGATP_MODE_OFF */
-	kvm_riscv_gstage_mode = HGATP_MODE_OFF;
-	kvm_riscv_gstage_pgd_levels = 0;
+	kvm_riscv_gstage_max_pgd_levels = 0;
 
 done:
 	csr_write(CSR_HGATP, 0);
diff --git a/arch/riscv/kvm/main.c b/arch/riscv/kvm/main.c
index 0f3fe3986fc0..90ee0a032b9a 100644
--- a/arch/riscv/kvm/main.c
+++ b/arch/riscv/kvm/main.c
@@ -105,17 +105,17 @@ static int __init riscv_kvm_init(void)
 		return rc;
 
 	kvm_riscv_gstage_mode_detect();
-	switch (kvm_riscv_gstage_mode) {
-	case HGATP_MODE_SV32X4:
+	switch (kvm_riscv_gstage_max_pgd_levels) {
+	case 2:
 		str = "Sv32x4";
 		break;
-	case HGATP_MODE_SV39X4:
+	case 3:
 		str = "Sv39x4";
 		break;
-	case HGATP_MODE_SV48X4:
+	case 4:
 		str = "Sv48x4";
 		break;
-	case HGATP_MODE_SV57X4:
+	case 5:
 		str = "Sv57x4";
 		break;
 	default:
@@ -164,7 +164,7 @@ static int __init riscv_kvm_init(void)
 			 (rc) ? slist : "no features");
 	}
 
-	kvm_info("using %s G-stage page table format\n", str);
+	kvm_info("highest G-stage page table mode is %s\n", str);
 
 	kvm_info("VMID %ld bits available\n", kvm_riscv_gstage_vmid_bits());
 
diff --git a/arch/riscv/kvm/mmu.c b/arch/riscv/kvm/mmu.c
index 088d33ba90ed..fbcdd75cb9af 100644
--- a/arch/riscv/kvm/mmu.c
+++ b/arch/riscv/kvm/mmu.c
@@ -67,7 +67,7 @@ int kvm_riscv_mmu_ioremap(struct kvm *kvm, gpa_t gpa, phys_addr_t hpa,
 		if (!writable)
 			map.pte = pte_wrprotect(map.pte);
 
-		ret = kvm_mmu_topup_memory_cache(&pcache, kvm_riscv_gstage_pgd_levels);
+		ret = kvm_mmu_topup_memory_cache(&pcache, kvm->arch.pgd_levels);
 		if (ret)
 			goto out;
 
@@ -186,7 +186,7 @@ int kvm_arch_prepare_memory_region(struct kvm *kvm,
 	 * space addressable by the KVM guest GPA space.
 	 */
 	if ((new->base_gfn + new->npages) >=
-	    (kvm_riscv_gstage_gpa_size >> PAGE_SHIFT))
+	     kvm_riscv_gstage_gpa_size(kvm->arch.pgd_levels) >> PAGE_SHIFT)
 		return -EFAULT;
 
 	hva = new->userspace_addr;
@@ -472,7 +472,7 @@ int kvm_riscv_mmu_map(struct kvm_vcpu *vcpu, struct kvm_memory_slot *memslot,
 	memset(out_map, 0, sizeof(*out_map));
 
 	/* We need minimum second+third level pages */
-	ret = kvm_mmu_topup_memory_cache(pcache, kvm_riscv_gstage_pgd_levels);
+	ret = kvm_mmu_topup_memory_cache(pcache, kvm->arch.pgd_levels);
 	if (ret) {
 		kvm_err("Failed to topup G-stage cache\n");
 		return ret;
@@ -575,6 +575,7 @@ int kvm_riscv_mmu_alloc_pgd(struct kvm *kvm)
 		return -ENOMEM;
 	kvm->arch.pgd = page_to_virt(pgd_page);
 	kvm->arch.pgd_phys = page_to_phys(pgd_page);
+	kvm->arch.pgd_levels = kvm_riscv_gstage_max_pgd_levels;
 
 	return 0;
 }
@@ -590,10 +591,12 @@ void kvm_riscv_mmu_free_pgd(struct kvm *kvm)
 		gstage.flags = 0;
 		gstage.vmid = READ_ONCE(kvm->arch.vmid.vmid);
 		gstage.pgd = kvm->arch.pgd;
-		kvm_riscv_gstage_unmap_range(&gstage, 0UL, kvm_riscv_gstage_gpa_size, false);
+		kvm_riscv_gstage_unmap_range(&gstage, 0UL,
+			kvm_riscv_gstage_gpa_size(kvm->arch.pgd_levels), false);
 		pgd = READ_ONCE(kvm->arch.pgd);
 		kvm->arch.pgd = NULL;
 		kvm->arch.pgd_phys = 0;
+		kvm->arch.pgd_levels = 0;
 	}
 	spin_unlock(&kvm->mmu_lock);
 
@@ -603,11 +606,12 @@ void kvm_riscv_mmu_free_pgd(struct kvm *kvm)
 
 void kvm_riscv_mmu_update_hgatp(struct kvm_vcpu *vcpu)
 {
-	unsigned long hgatp = kvm_riscv_gstage_mode << HGATP_MODE_SHIFT;
-	struct kvm_arch *k = &vcpu->kvm->arch;
+	struct kvm_arch *ka = &vcpu->kvm->arch;
+	unsigned long hgatp = kvm_riscv_gstage_mode(ka->pgd_levels)
+			      << HGATP_MODE_SHIFT;
 
-	hgatp |= (READ_ONCE(k->vmid.vmid) << HGATP_VMID_SHIFT) & HGATP_VMID;
-	hgatp |= (k->pgd_phys >> PAGE_SHIFT) & HGATP_PPN;
+	hgatp |= (READ_ONCE(ka->vmid.vmid) << HGATP_VMID_SHIFT) & HGATP_VMID;
+	hgatp |= (ka->pgd_phys >> PAGE_SHIFT) & HGATP_PPN;
 
 	ncsr_write(CSR_HGATP, hgatp);
 
diff --git a/arch/riscv/kvm/vm.c b/arch/riscv/kvm/vm.c
index 13c63ae1a78b..fb7c4e07961f 100644
--- a/arch/riscv/kvm/vm.c
+++ b/arch/riscv/kvm/vm.c
@@ -199,7 +199,10 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
 		r = KVM_USER_MEM_SLOTS;
 		break;
 	case KVM_CAP_VM_GPA_BITS:
-		r = kvm_riscv_gstage_gpa_bits;
+		if (!kvm)
+			r = kvm_riscv_gstage_gpa_bits(kvm_riscv_gstage_max_pgd_levels);
+		else
+			r = kvm_riscv_gstage_gpa_bits(kvm->arch.pgd_levels);
 		break;
 	default:
 		r = 0;
diff --git a/arch/riscv/kvm/vmid.c b/arch/riscv/kvm/vmid.c
index cf34d448289d..c15bdb1dd8be 100644
--- a/arch/riscv/kvm/vmid.c
+++ b/arch/riscv/kvm/vmid.c
@@ -26,7 +26,8 @@ static DEFINE_SPINLOCK(vmid_lock);
 void __init kvm_riscv_gstage_vmid_detect(void)
 {
 	/* Figure-out number of VMID bits in HW */
-	csr_write(CSR_HGATP, (kvm_riscv_gstage_mode << HGATP_MODE_SHIFT) | HGATP_VMID);
+	csr_write(CSR_HGATP, (kvm_riscv_gstage_mode(kvm_riscv_gstage_max_pgd_levels) <<
+			      HGATP_MODE_SHIFT) | HGATP_VMID);
 	vmid_bits = csr_read(CSR_HGATP);
 	vmid_bits = (vmid_bits & HGATP_VMID) >> HGATP_VMID_SHIFT;
 	vmid_bits = fls_long(vmid_bits);
-- 
2.50.1


^ permalink raw reply related

* Re: [PATCH v9 01/10] x86/bhi: x86/vmscape: Move LFENCE out of clear_bhb_loop()
From: Borislav Petkov @ 2026-04-03 15:16 UTC (permalink / raw)
  To: Pawan Gupta
  Cc: x86, Jon Kohler, Nikolay Borisov, H. Peter Anvin, Josh Poimboeuf,
	David Kaplan, Sean Christopherson, Dave Hansen, Peter Zijlstra,
	Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, KP Singh,
	Jiri Olsa, David S. Miller, David Laight, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, David Ahern, Martin KaFai Lau,
	Eduard Zingerman, Song Liu, Yonghong Song, John Fastabend,
	Stanislav Fomichev, Hao Luo, Paolo Bonzini, Jonathan Corbet,
	linux-kernel, kvm, Asit Mallick, Tao Zhang, bpf, netdev,
	linux-doc
In-Reply-To: <20260402-vmscape-bhb-v9-1-94d16bc29774@linux.intel.com>

On Thu, Apr 02, 2026 at 05:30:47PM -0700, Pawan Gupta wrote:
> Currently, the BHB clearing sequence is followed by an LFENCE to prevent
> transient execution of subsequent indirect branches prematurely. However,
> the LFENCE barrier could be unnecessary in certain cases. For example, when
> the kernel is using the BHI_DIS_S mitigation, and BHB clearing is only
> needed for userspace. In such cases, the LFENCE is redundant because ring
> transitions would provide the necessary serialization.
> 
> Below is a quick recap of BHI mitigation options:
> 
> On Alder Lake and newer
> 
>     BHI_DIS_S: Hardware control to mitigate BHI in ring0. This has low
>     performance overhead.
> 
>     Long loop: Alternatively, a longer version of the BHB clearing sequence
>     can be used to mitigate BHI. It can also be used to mitigate the BHI
>     variant of VMSCAPE. This is not yet implemented in Linux.
> 
> On older CPUs
> 
>     Short loop: Clears BHB at kernel entry and VMexit. The "Long loop" is
>     effective on older CPUs as well, but should be avoided because of
>     unnecessary overhead.
> 
> On Alder Lake and newer CPUs, eIBRS isolates the indirect targets between
> guest and host. But when affected by the BHI variant of VMSCAPE, a guest's
> branch history may still influence indirect branches in userspace. This
> also means the big hammer IBPB could be replaced with a cheaper option that
> clears the BHB at exit-to-userspace after a VMexit.
> 
> In preparation for adding the support for the BHB sequence (without LFENCE)
> on newer CPUs, move the LFENCE to the caller side after clear_bhb_loop() is
> executed. Allow callers to decide whether they need the LFENCE or not. This
> adds a few extra bytes to the call sites, but it obviates the need for
> multiple variants of clear_bhb_loop().
> 
> Suggested-by: Dave Hansen <dave.hansen@linux.intel.com>
> Tested-by: Jon Kohler <jon@nutanix.com>
> Reviewed-by: Nikolay Borisov <nik.borisov@suse.com>
> Signed-off-by: Pawan Gupta <pawan.kumar.gupta@linux.intel.com>
> ---
>  arch/x86/entry/entry_64.S            | 5 ++++-
>  arch/x86/include/asm/nospec-branch.h | 4 ++--
>  arch/x86/net/bpf_jit_comp.c          | 2 ++
>  3 files changed, 8 insertions(+), 3 deletions(-)

Acked-by: Borislav Petkov (AMD) <bp@alien8.de>

-- 
Regards/Gruss,
    Boris.

https://people.kernel.org/tglx/notes-about-netiquette

^ permalink raw reply

* Re: [PATCH v9 07/10] x86/vmscape: Use static_call() for predictor flush
From: Sean Christopherson @ 2026-04-03 14:52 UTC (permalink / raw)
  To: Pawan Gupta
  Cc: x86, Jon Kohler, Nikolay Borisov, H. Peter Anvin, Josh Poimboeuf,
	David Kaplan, Borislav Petkov, Dave Hansen, Peter Zijlstra,
	Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, KP Singh,
	Jiri Olsa, David S. Miller, David Laight, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, David Ahern, Martin KaFai Lau,
	Eduard Zingerman, Song Liu, Yonghong Song, John Fastabend,
	Stanislav Fomichev, Hao Luo, Paolo Bonzini, Jonathan Corbet,
	linux-kernel, kvm, Asit Mallick, Tao Zhang, bpf, netdev,
	linux-doc
In-Reply-To: <20260402-vmscape-bhb-v9-7-94d16bc29774@linux.intel.com>

On Thu, Apr 02, 2026, Pawan Gupta wrote:
> Adding more mitigation options at exit-to-userspace for VMSCAPE would
> usually require a series of checks to decide which mitigation to use. In
> this case, the mitigation is done by calling a function, which is decided
> at boot. So, adding more feature flags and multiple checks can be avoided
> by using static_call() to the mitigating function.
> 
> Replace the flag-based mitigation selector with a static_call(). This also
> frees the existing X86_FEATURE_IBPB_EXIT_TO_USER.

...

> @@ -3133,8 +3139,14 @@ static void __init vmscape_update_mitigation(void)
>  static void __init vmscape_apply_mitigation(void)
>  {
>  	if (vmscape_mitigation == VMSCAPE_MITIGATION_IBPB_EXIT_TO_USER)
> -		setup_force_cpu_cap(X86_FEATURE_IBPB_EXIT_TO_USER);
> +		static_call_update(vmscape_predictor_flush, write_ibpb);
> +}
> +
> +bool vmscape_mitigation_enabled(void)
> +{
> +	return !!static_call_query(vmscape_predictor_flush);
>  }
> +EXPORT_SYMBOL_FOR_KVM(vmscape_mitigation_enabled);
>  
>  #undef pr_fmt
>  #define pr_fmt(fmt) fmt
> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 45d7cfedc507..e204482e64f3 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
> @@ -11463,7 +11463,7 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu)
>  	 * set for the CPU that actually ran the guest, and not the CPU that it
>  	 * may migrate to.
>  	 */
> -	if (cpu_feature_enabled(X86_FEATURE_IBPB_EXIT_TO_USER))
> +	if (vmscape_mitigation_enabled())

This is pretty lame.  It turns a statically patched MOV

  11548		if (cpu_feature_enabled(X86_FEATURE_IBPB_EXIT_TO_USER))
  11549			this_cpu_write(x86_ibpb_exit_to_user, true);
     0x000000000003c57a <+858>:	movb   $0x1,%gs:0x0(%rip)        # 0x3c582 <vcpu_enter_guest+866>

into a function call and two sets of conditional branches.  And with mitigations
enabled, that function call may trigger the wonderful unret insanity

  11548		if (vmscape_mitigation_enabled())
     0x000000000003c575 <+853>:	call   0x3c57a <vcpu_enter_guest+858>
     0x000000000003c57a <+858>:	test   %al,%al
     0x000000000003c57c <+860>:	je     0x3c586 <vcpu_enter_guest+870>

  11549			this_cpu_write(x86_predictor_flush_exit_to_user, true);
     0x000000000003c57e <+862>:	movb   $0x1,%gs:0x0(%rip)        # 0x3c586 <vcpu_enter_guest+870>


  3166	{
     0xffffffff81285320 <+0>:	endbr64
     0xffffffff81285324 <+4>:	call   0xffffffff812aa5a0 <__fentry__>

  3167		return !!static_call_query(vmscape_predictor_flush);
     0xffffffff81285329 <+9>:	mov    0x13a4f30(%rip),%rax        # 0xffffffff8262a260 <__SCK__vmscape_predictor_flush>
     0xffffffff81285330 <+16>:	test   %rax,%rax
     0xffffffff81285333 <+19>:	setne  %al

  3168	}
     0xffffffff81285336 <+22>:	jmp    0xffffffff81db1e30 <__x86_return_thunk>

While this isn't KVM's super hot inner run loop, it's still very much a hot path.
Even more annoying, KVM will eat the function call on kernels with CPU_MITIGATIONS=n.

I'd like to at least do something like the below to make the common case of
multiple guest entry/exits more or less free, and to avoid the CALL+(UN)RET
overhead, but trying to include linux/static_call.h in processor.h (or any other
core x86 header) creates a cyclical dependency :-/

diff --git a/arch/x86/include/asm/processor.h b/arch/x86/include/asm/processor.h
index 20ab4dd588c6..0dc0680a80f8 100644
--- a/arch/x86/include/asm/processor.h
+++ b/arch/x86/include/asm/processor.h
@@ -36,6 +36,7 @@ struct vm86;
 #include <linux/err.h>
 #include <linux/irqflags.h>
 #include <linux/mem_encrypt.h>
+#include <linux/static_call.h>
 
 /*
  * We handle most unaligned accesses in hardware.  On the other hand
@@ -753,7 +754,11 @@ enum mds_mitigations {
 };
 
 extern bool gds_ucode_mitigated(void);
-extern bool vmscape_mitigation_enabled(void);
+
+static inline bool vmscape_mitigation_enabled(void)
+{
+       return !!static_call_query(vmscape_predictor_flush);
+}
 
 /*
  * Make previous memory operations globally visible before
diff --git a/arch/x86/kernel/cpu/bugs.c b/arch/x86/kernel/cpu/bugs.c
index 366ebe1e1fb9..02bf626f0773 100644
--- a/arch/x86/kernel/cpu/bugs.c
+++ b/arch/x86/kernel/cpu/bugs.c
@@ -148,6 +148,7 @@ DEFINE_STATIC_KEY_FALSE(switch_mm_cond_l1d_flush);
  * sequence. This defaults to no mitigation.
  */
 DEFINE_STATIC_CALL_NULL(vmscape_predictor_flush, write_ibpb);
+EXPORT_STATIC_CALL_GPL(vmscape_predictor_flush);
 
 #undef pr_fmt
 #define pr_fmt(fmt)    "mitigations: " fmt
@@ -3162,12 +3163,6 @@ static void __init vmscape_apply_mitigation(void)
                static_call_update(vmscape_predictor_flush, clear_bhb_loop_nofence);
 }
 
-bool vmscape_mitigation_enabled(void)
-{
-       return !!static_call_query(vmscape_predictor_flush);
-}
-EXPORT_SYMBOL_FOR_KVM(vmscape_mitigation_enabled);
-
 #undef pr_fmt
 #define pr_fmt(fmt) fmt
 
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index a1fbbab08291..117c60d00758 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -11545,7 +11545,9 @@ static noinline int vcpu_enter_guest(struct kvm_vcpu *vcpu)
         * set for the CPU that actually ran the guest, and not the CPU that it
         * may migrate to.
         */
-       if (vmscape_mitigation_enabled())
+       if (IS_ENABLED(CONFIG_CPU_MITIGATIONS) &&
+           !this_cpu_read(x86_predictor_flush_exit_to_user) &&
+           vmscape_mitigation_enabled())
                this_cpu_write(x86_predictor_flush_exit_to_user, true);
 
        /*

^ permalink raw reply related

* Re: [PATCH RFC v4 10/44] KVM: guest_memfd: Add support for KVM_SET_MEMORY_ATTRIBUTES2
From: Ackerley Tng @ 2026-04-03 14:50 UTC (permalink / raw)
  To: Michael Roth
  Cc: aik, andrew.jones, binbin.wu, brauner, chao.p.peng, david,
	ira.weiny, jmattson, jthoughton, oupton, pankaj.gupta, qperret,
	rick.p.edgecombe, rientjes, shivankg, steven.price, tabba, willy,
	wyihan, yan.y.zhao, forkloop, pratyush, suzuki.poulose,
	aneesh.kumar, Paolo Bonzini, Sean Christopherson, Thomas Gleixner,
	Ingo Molnar, Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
	Steven Rostedt, Masami Hiramatsu, Mathieu Desnoyers,
	Jonathan Corbet, Shuah Khan, Shuah Khan, Vishal Annapurve,
	Andrew Morton, Chris Li, Kairui Song, Kemeng Shi, Nhat Pham,
	Baoquan He, Barry Song, Axel Rasmussen, Yuanchu Xie, Wei Xu,
	Jason Gunthorpe, Vlastimil Babka, kvm, linux-kernel,
	linux-trace-kernel, linux-doc, linux-kselftest, linux-mm
In-Reply-To: <CAEvNRgGm9icDK8sK5ZfqHEOEqSbvjwtihE4p9d3vpBq-NfVjmw@mail.gmail.com>

Ackerley Tng <ackerleytng@google.com> writes:

>
> [...snip...]
>
> guest_memfd's populate will first check that the memory is shared, then
> also set the memory to private after the populate.
>
> [...snip...]
>
Looking at this again, the above basically means that the entire
conversion process needs to be performed within populate.

In addition to setting the attributes in guest_memfd as private, for
consistency, populate will also have to do all the associated
operations, especially unmapping from the host, checking refcounts,
and the list of work in conversion will only increase in future with
direct map removal/restoration and huge page merging.

The complexity of conversion also means possible errors (EAGAIN for
elevated refcounts and ENOMEM when we're out of memory), and error
information like the offset where the elevated refcount was.

It doesn't look like there's room for this information to be plumbed out
through the platform-specific ioctls, and even if we make space, it
seems odd to have conversion-related error information returned through
the platform-specific call.


I agree with the goal of not having KVM touch private memory contents as
tracked by guest_memfd, so I'd like to propose that we distinguish:

1. private as tracked by KVM (guest_memfd/vm_memory_attributes)
2. private as tracked by trusted entity


Currently, in TDX's populate flow, KVM doesn't do any copying, it only
instructs TDX to do the copying.

"TDH.MEM.PAGE.ADD Operands Information Definition" in the TDX module
ABI spec says the source is accessed as shared, the destination is
accessed as private, and in the case that source and destination are
the same, the "In-Place Add" section says the source will be converted
to private.

In SNP's populate flow, SNP-specific code in KVM does the copying from
shared memory into the destination, then makes the destination address
private in the RMP table before telling the firmware to do the
in-place encryption.


I think we should think of the populate ioctls as being
platform-specific ioctls that, when called, accept

+ destination address: private (as tracked by guest_memfd)
+ source address: shared (as tracked by guest_memfd) or NULL

KVM doesn't touch private memory contents, even in this case, because
it's really a platform-specific ioctl that handles loading, and the
platform does expect the destination is private for both TDX and SNP
at the firmware boundary.

Since SNP (platform-specific) only allows in-place launch update, and
KVM had to provide an interface that allows a different source address
for support before in-place conversion, then SNP has to continue
supporting the to-be-deprecated path by doing the copying within
platform-specific code.

For consistency, guest_memfd can continue to check that it tracks the
destination address as private, and sev_gmem_populate will then hide
the copying code away just to support the legacy case.


The flow before in-place conversion is

1. Load memory (shared or non-guest_memfd memory)
2. KVM_SEV_SNP_LAUNCH_UPDATE or KVM_TDX_INIT_MEM_REGION (destination:
   gfn for separate private memory, source: shared memory)

The proposed flow for in-place conversion is

1. INIT_SHARED or convert to shared
2. Load memory while it is shared
3. Convert to private (PRESERVE, or new flag?)
4. KVM_SEV_SNP_LAUNCH_UPDATE or KVM_TDX_INIT_MEM_REGION (destination:
   gfn for converted private memory, source: NULL)


TLDR:

+ Think of populate ioctls not as KVM touching memory, but platform
  handling population.
+ KVM code (kvm_gmem_populate) still doesn't touch memory contents
+ post_populate is platform-specific code that handles loading into
  private destination memory just to support legacy non-in-place
  conversion.
+ Don't complicate populate ioctls by doing conversion just to support
  legacy use-cases where platform-specific code has to do copying on
  the host.

>>>
>>> [...snip...]
>>>

^ permalink raw reply

* [PATCH v12 1/2] dt-bindings: hwmon: add support for MCP998X
From: Victor Duicu @ 2026-04-03 13:32 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, marius.cristea,
	victor.duicu
In-Reply-To: <20260403-add-mcp9982-hwmon-v12-0-b3bfb26ff136@microchip.com>

Add devicetree schema for Microchip MCP998X/33 and MCP998XD/33D
Multichannel Automotive Temperature Monitor Family.

Signed-off-by: Victor Duicu <victor.duicu@microchip.com>
---
 .../bindings/hwmon/microchip,mcp9982.yaml          | 237 +++++++++++++++++++++
 MAINTAINERS                                        |   6 +
 2 files changed, 243 insertions(+)

diff --git a/Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml b/Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
new file mode 100644
index 000000000000..83dd2bf37e27
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
@@ -0,0 +1,237 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/hwmon/microchip,mcp9982.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip MCP998X/33 and MCP998XD/33D Temperature Monitor
+
+maintainers:
+  - Victor Duicu <victor.duicu@microchip.com>
+
+description: |
+  The MCP998X/33 and MCP998XD/33D family is a high-accuracy 2-wire
+  multichannel automotive temperature monitor.
+  The datasheet can be found here:
+    https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf
+
+properties:
+  compatible:
+    enum:
+      - microchip,mcp9933
+      - microchip,mcp9933d
+      - microchip,mcp9982
+      - microchip,mcp9982d
+      - microchip,mcp9983
+      - microchip,mcp9983d
+      - microchip,mcp9984
+      - microchip,mcp9984d
+      - microchip,mcp9985
+      - microchip,mcp9985d
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    minItems: 1
+    maxItems: 2
+
+  interrupt-names:
+    description:
+      The chip family has three different interrupt pins divided among them.
+      The chips without "D" have alert-therm and therm-addr.
+      The chips with "D" have alert-therm and sys-shtdwn.
+    minItems: 1
+    items:
+      - enum: [alert-therm, therm-addr, sys-shtdwn]
+      - enum: [therm-addr, sys-shtdwn]
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+  microchip,enable-anti-parallel:
+    description:
+      Enable anti-parallel diode mode operation.
+      MCP9984/84D/85/85D and MCP9933/33D support reading two external diodes
+      in anti-parallel connection on the same set of pins.
+    type: boolean
+
+  microchip,parasitic-res-on-channel1-2:
+    description:
+      Indicates that the chip and the diodes/transistors are sufficiently far
+      apart that a parasitic resistance is added to the wires, which can affect
+      the measurements. Due to the anti-parallel diode connections, channels
+      1 and 2 are affected together.
+    type: boolean
+
+  microchip,parasitic-res-on-channel3-4:
+    description:
+      Indicates that the chip and the diodes/transistors are sufficiently far
+      apart that a parasitic resistance is added to the wires, which can affect
+      the measurements. Due to the anti-parallel diode connections, channels
+      3 and 4 are affected together.
+    type: boolean
+
+  microchip,power-state:
+    description:
+      The chip can be set in Run state or Standby state. In Run state the ADC
+      is converting on all channels at the programmed conversion rate.
+      In Standby state the host must initiate a conversion cycle by writing
+      to the One-Shot register.
+      True value sets Run state.
+      Chips with "D" in the name can only be set in Run mode.
+    type: boolean
+
+  vdd-supply: true
+
+patternProperties:
+  "^channel@[1-4]$":
+    description:
+      Represents the external temperature channels to which
+      a remote diode is connected.
+    type: object
+
+    properties:
+      reg:
+        items:
+          maxItems: 1
+
+      label:
+        description: Unique name to identify which channel this is.
+
+    required:
+      - reg
+
+    additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - vdd-supply
+
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - microchip,mcp9982d
+              - microchip,mcp9983d
+              - microchip,mcp9984d
+              - microchip,mcp9985d
+              - microchip,mcp9933d
+    then:
+      properties:
+        interrupt-names:
+          items:
+            enum:
+              - alert-therm
+              - sys-shtdwn
+      required:
+        - microchip,power-state
+        - microchip,parasitic-res-on-channel1-2
+    else:
+      properties:
+        microchip,power-state: true
+        interrupt-names:
+          items:
+            enum:
+              - alert-therm
+              - therm-addr
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - microchip,mcp9983d
+              - microchip,mcp9984d
+              - microchip,mcp9985d
+    then:
+      required:
+        - microchip,parasitic-res-on-channel3-4
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - microchip,mcp9982
+              - microchip,mcp9982d
+    then:
+      properties:
+        microchip,enable-anti-parallel: false
+      patternProperties:
+        "^channel@[2-4]$": false
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - microchip,mcp9983
+              - microchip,mcp9983d
+    then:
+      properties:
+        microchip,enable-anti-parallel: false
+      patternProperties:
+        "^channel@[3-4]$": false
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - microchip,mcp9933
+              - microchip,mcp9933d
+    then:
+      patternProperties:
+        "^channel@[3-4]$": false
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - microchip,mcp9984
+              - microchip,mcp9984d
+    then:
+      properties:
+        channel@4: false
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        temperature-sensor@4c {
+            compatible = "microchip,mcp9985";
+            reg = <0x4c>;
+
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            microchip,enable-anti-parallel;
+            microchip,parasitic-res-on-channel1-2;
+            microchip,parasitic-res-on-channel3-4;
+            vdd-supply = <&vdd>;
+
+            channel@1 {
+                reg = <1>;
+                label = "Room Temperature";
+            };
+
+            channel@2 {
+                reg = <2>;
+                label = "GPU Temperature";
+            };
+        };
+    };
+
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index e08767323763..026510a4129c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17160,6 +17160,12 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/iio/adc/microchip,mcp3911.yaml
 F:	drivers/iio/adc/mcp3911.c
 
+MICROCHIP MCP9982 TEMPERATURE DRIVER
+M:	Victor Duicu <victor.duicu@microchip.com>
+L:	linux-hwmon@vger.kernel.org
+S:	Supported
+F:	Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
+
 MICROCHIP MMC/SD/SDIO MCI DRIVER
 M:	Aubin Constans <aubin.constans@microchip.com>
 S:	Maintained

-- 
2.51.0


^ permalink raw reply related

* [PATCH v12 2/2] hwmon: add support for MCP998X
From: Victor Duicu @ 2026-04-03 13:32 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, marius.cristea,
	victor.duicu
In-Reply-To: <20260403-add-mcp9982-hwmon-v12-0-b3bfb26ff136@microchip.com>

Add driver for Microchip MCP998X/33 and MCP998XD/33D
Multichannel Automotive Temperature Monitor Family.

Signed-off-by: Victor Duicu <victor.duicu@microchip.com>
---
 Documentation/hwmon/index.rst   |   1 +
 Documentation/hwmon/mcp9982.rst | 111 +++++
 MAINTAINERS                     |   2 +
 drivers/hwmon/Kconfig           |  11 +
 drivers/hwmon/Makefile          |   1 +
 drivers/hwmon/mcp9982.c         | 997 ++++++++++++++++++++++++++++++++++++++++
 6 files changed, 1123 insertions(+)

diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 85d7a686883e..b02709fc216e 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -173,6 +173,7 @@ Hardware Monitoring Kernel Drivers
    mc33xs2410_hwmon
    mc34vr500
    mcp3021
+   mcp9982
    menf21bmc
    mlxreg-fan
    mp2856
diff --git a/Documentation/hwmon/mcp9982.rst b/Documentation/hwmon/mcp9982.rst
new file mode 100644
index 000000000000..790ee1697b45
--- /dev/null
+++ b/Documentation/hwmon/mcp9982.rst
@@ -0,0 +1,111 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+Kernel driver MCP998X
+=====================
+
+Supported chips:
+
+  * Microchip Technology MCP998X/MCP9933 and MCP998XD/MCP9933D
+
+    Prefix: 'mcp9982'
+
+    Datasheet:
+    https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf
+
+Authors:
+
+   - Victor Duicu <victor.duicu@microchip.com>
+
+Description
+-----------
+
+This driver implements support for the MCP998X family containing: MCP9982,
+MCP9982D, MCP9983, MCP9983D, MCP9984, MCP9984D, MCP9985, MCP9985D,
+MCP9933 and MCP9933D.
+
+The MCP998X Family is a high accuracy 2-wire multichannel automotive
+temperature monitor.
+
+The chips in the family have different numbers of external channels,
+ranging from 1 (MCP9982) to 4 channels (MCP9985). Reading diodes in
+anti-parallel connection is supported by MCP9984/85/33 and
+MCP9984D/85D/33D. Dedicated hardware shutdown circuitry is present
+only in MCP998XD and MCP9933D.
+
+Temperatures are read in millidegrees Celsius, ranging from -64 to
+191.875 with 0.125 precision.
+
+Each channel has a minimum, maximum, and critical limit alongside associated alarms.
+The chips also implement a hysteresis mechanism which applies only to the maximum
+and critical limits. The relative difference between a limit and its hysteresis
+is the same for both and the value is kept in a single register.
+
+The chips measure temperatures with a variable conversion rate.
+Update_interval = Conversion/Second, so the available options are:
+- 16000 (ms) = 1 conv/16 sec
+- 8000 (ms) = 1 conv/8 sec
+- 4000 (ms) = 1 conv/4 sec
+- 2000 (ms) = 1 conv/2 sec
+- 1000 (ms) = 1 conv/sec
+- 500 (ms) = 2 conv/sec
+- 250 (ms) = 4 conv/sec
+- 125 (ms) = 8 conv/sec
+- 64 (ms) = 16 conv/sec
+- 32 (ms) = 32 conv/sec
+- 16 (ms) = 64 conv/sec
+
+Usage Notes
+-----------
+
+Parameters that can be configured in devicetree:
+- anti-parallel diode mode operation
+- resistance error correction on channels 1 and 2
+- resistance error correction on channels 3 and 4
+- power state
+
+Chips 82/83 and 82D/83D do not support anti-parallel diode mode.
+For chips with "D" in the name resistance error correction must be on.
+Please see Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
+for details.
+
+There are two power states:
+- Active state: in which the chip is converting on all channels at the
+programmed rate.
+
+- Standby state: in which the host must initiate a conversion cycle.
+
+Chips with "D" in the name work in Active state only and those without
+can work in either state.
+
+Chips with "D" in the name can't set update interval slower than 1 second.
+
+Among the hysteresis attributes, only the tempX_crit_hyst ones are writeable
+while the others are read only. Setting tempX_crit_hyst writes the difference
+between tempX_crit and tempX_crit_hyst in the hysteresis register. The new value
+applies automatically  to the other limits. At power up the device starts with
+a 10 degree hysteresis.
+
+Sysfs entries
+-------------
+
+The following attributes are supported. The temperature limits and
+update_interval are read-write. The attribute tempX_crit_hyst is read-write,
+while tempX_max_hyst is read only. All other attributes are read only.
+
+======================= ==================================================
+temp[1-5]_label		User name for channel.
+temp[1-5]_input		Measured temperature for channel.
+
+temp[1-5]_crit		Critical temperature limit.
+temp[1-5]_crit_alarm	Critical temperature limit alarm.
+temp[1-5]_crit_hyst	Critical temperature limit hysteresis.
+
+temp[1-5]_max		High temperature limit.
+temp[1-5]_max_alarm	High temperature limit alarm.
+temp[1-5]_max_hyst	High temperature limit hysteresis.
+
+temp[1-5]_min		Low temperature limit.
+temp[1-5]_min_alarm	Low temperature limit alarm.
+
+update_interval		The interval at which the chip will update readings.
+======================= ==================================================
diff --git a/MAINTAINERS b/MAINTAINERS
index 026510a4129c..5c6662e10b04 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17165,6 +17165,8 @@ M:	Victor Duicu <victor.duicu@microchip.com>
 L:	linux-hwmon@vger.kernel.org
 S:	Supported
 F:	Documentation/devicetree/bindings/hwmon/microchip,mcp9982.yaml
+F:	Documentation/hwmon/mcp9982.rst
+F:	drivers/hwmon/mcp9982.c
 
 MICROCHIP MMC/SD/SDIO MCI DRIVER
 M:	Aubin Constans <aubin.constans@microchip.com>
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 157678b821fc..c758ab2d5fdf 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1388,6 +1388,17 @@ config SENSORS_MCP3021
 	  This driver can also be built as a module. If so, the module
 	  will be called mcp3021.
 
+config SENSORS_MCP9982
+       tristate "Microchip Technology MCP9982 driver"
+       depends on I2C
+       select REGMAP_I2C
+       help
+         Say yes here to include support for Microchip Technology's MCP998X/33
+         and MCP998XD/33D Multichannel Automotive Temperature Monitor Family.
+
+         This driver can also be built as a module. If so, the module
+         will be called mcp9982.
+
 config SENSORS_MLXREG_FAN
 	tristate "Mellanox FAN driver"
 	depends on MELLANOX_PLATFORM
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index eade8e3b1bde..cec33da29a68 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -170,6 +170,7 @@ obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o
 obj-$(CONFIG_SENSORS_MC33XS2410) += mc33xs2410_hwmon.o
 obj-$(CONFIG_SENSORS_MC34VR500)	+= mc34vr500.o
 obj-$(CONFIG_SENSORS_MCP3021)	+= mcp3021.o
+obj-$(CONFIG_SENSORS_MCP9982)	+= mcp9982.o
 obj-$(CONFIG_SENSORS_TC654)	+= tc654.o
 obj-$(CONFIG_SENSORS_TPS23861)	+= tps23861.o
 obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o
diff --git a/drivers/hwmon/mcp9982.c b/drivers/hwmon/mcp9982.c
new file mode 100644
index 000000000000..f0b836a3f256
--- /dev/null
+++ b/drivers/hwmon/mcp9982.c
@@ -0,0 +1,997 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * HWMON driver for MCP998X/33 and MCP998XD/33D Multichannel Automotive
+ * Temperature Monitor Family
+ *
+ * Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Victor Duicu <victor.duicu@microchip.com>
+ *
+ * Datasheet can be found here:
+ * https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/MCP998X-Family-Data-Sheet-DS20006827.pdf
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/byteorder/generic.h>
+#include <linux/delay.h>
+#include <linux/device/devres.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/math.h>
+#include <linux/minmax.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/time64.h>
+#include <linux/util_macros.h>
+
+/* MCP9982 Registers */
+#define MCP9982_HIGH_BYTE_ADDR(index)		(2 * (index))
+#define MCP9982_ONE_SHOT_ADDR			0x0A
+#define MCP9982_INTERNAL_HIGH_LIMIT_ADDR	0x0B
+#define MCP9982_INTERNAL_LOW_LIMIT_ADDR		0x0C
+#define MCP9982_EXT_HIGH_LIMIT_ADDR(index)	(4 * ((index) - 1) + 0x0D)
+#define MCP9982_EXT_LOW_LIMIT_ADDR(index)	(4 * ((index) - 1) + 0x0F)
+#define MCP9982_THERM_LIMIT_ADDR(index)		((index) + 0x1D)
+#define MCP9982_CFG_ADDR			0x22
+#define MCP9982_CONV_ADDR			0x24
+#define MCP9982_HYS_ADDR			0x25
+#define MCP9982_CONSEC_ALRT_ADDR		0x26
+#define MCP9982_ALRT_CFG_ADDR			0x27
+#define MCP9982_RUNNING_AVG_ADDR		0x28
+#define MCP9982_HOTTEST_CFG_ADDR		0x29
+#define MCP9982_STATUS_ADDR			0x2A
+#define MCP9982_EXT_FAULT_STATUS_ADDR		0x2B
+#define MCP9982_HIGH_LIMIT_STATUS_ADDR		0x2C
+#define MCP9982_LOW_LIMIT_STATUS_ADDR		0x2D
+#define MCP9982_THERM_LIMIT_STATUS_ADDR		0x2E
+#define MCP9982_HOTTEST_HIGH_BYTE_ADDR		0x2F
+#define MCP9982_HOTTEST_LOW_BYTE_ADDR		0x30
+#define MCP9982_HOTTEST_STATUS_ADDR		0x31
+#define MCP9982_THERM_SHTDWN_CFG_ADDR		0x32
+#define MCP9982_HRDW_THERM_SHTDWN_LIMIT_ADDR	0x33
+#define MCP9982_EXT_BETA_CFG_ADDR(index)	((index) + 0x33)
+#define MCP9982_EXT_IDEAL_ADDR(index)		((index) + 0x35)
+
+/* MCP9982 Bits */
+#define MCP9982_CFG_MSKAL			BIT(7)
+#define MCP9982_CFG_RS				BIT(6)
+#define MCP9982_CFG_ATTHM			BIT(5)
+#define MCP9982_CFG_RECD12			BIT(4)
+#define MCP9982_CFG_RECD34			BIT(3)
+#define MCP9982_CFG_RANGE			BIT(2)
+#define MCP9982_CFG_DA_ENA			BIT(1)
+#define MCP9982_CFG_APDD			BIT(0)
+
+#define MCP9982_STATUS_BUSY			BIT(5)
+
+/* Constants and default values */
+#define MCP9982_MAX_NUM_CHANNELS		5
+#define MCP9982_BETA_AUTODETECT			16
+#define MCP9982_IDEALITY_DEFAULT		18
+#define MCP9982_OFFSET				64
+#define MCP9982_DEFAULT_CONSEC_ALRT_VAL		112
+#define MCP9982_DEFAULT_HYS_VAL			10
+#define MCP9982_DEFAULT_CONV_VAL		6
+#define MCP9982_WAKE_UP_TIME_US			125000
+#define MCP9982_WAKE_UP_TIME_MAX_US		130000
+#define MCP9982_HIGH_LIMIT_DEFAULT		85000
+#define MCP9982_LOW_LIMIT_DEFAULT		0
+
+static const struct hwmon_channel_info * const mcp9985_info[] = {
+	HWMON_CHANNEL_INFO(temp,
+			   HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
+			   HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
+			   HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
+			   HWMON_T_CRIT_HYST,
+			   HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
+			   HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
+			   HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
+			   HWMON_T_CRIT_HYST,
+			   HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
+			   HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
+			   HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
+			   HWMON_T_CRIT_HYST,
+			   HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
+			   HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
+			   HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
+			   HWMON_T_CRIT_HYST,
+			   HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MIN |
+			   HWMON_T_MIN_ALARM | HWMON_T_MAX | HWMON_T_MAX_ALARM |
+			   HWMON_T_MAX_HYST | HWMON_T_CRIT | HWMON_T_CRIT_ALARM |
+			   HWMON_T_CRIT_HYST),
+	HWMON_CHANNEL_INFO(chip,
+			   HWMON_C_UPDATE_INTERVAL),
+	NULL
+};
+
+/**
+ * struct mcp9982_features - features of a mcp9982 instance
+ * @name:			chip's name
+ * @phys_channels:		number of physical channels supported by the chip
+ * @hw_thermal_shutdown:	presence of hardware thermal shutdown circuitry
+ * @allow_apdd:			whether the chip supports enabling APDD
+ * @has_recd34:			whether the chip has the channels that are affected by recd34
+ */
+struct mcp9982_features {
+	const char	*name;
+	u8		phys_channels;
+	bool		hw_thermal_shutdown;
+	bool		allow_apdd;
+	bool		has_recd34;
+};
+
+static const struct mcp9982_features mcp9933_chip_config = {
+	.name = "mcp9933",
+	.phys_channels = 3,
+	.hw_thermal_shutdown = false,
+	.allow_apdd = true,
+	.has_recd34 = false,
+};
+
+static const struct mcp9982_features mcp9933d_chip_config = {
+	.name = "mcp9933d",
+	.phys_channels = 3,
+	.hw_thermal_shutdown = true,
+	.allow_apdd = true,
+	.has_recd34 = false,
+};
+
+static const struct mcp9982_features mcp9982_chip_config = {
+	.name = "mcp9982",
+	.phys_channels = 2,
+	.hw_thermal_shutdown = false,
+	.allow_apdd = false,
+	.has_recd34 = false,
+};
+
+static const struct mcp9982_features mcp9982d_chip_config = {
+	.name = "mcp9982d",
+	.phys_channels = 2,
+	.hw_thermal_shutdown = true,
+	.allow_apdd = false,
+	.has_recd34 = false,
+};
+
+static const struct mcp9982_features mcp9983_chip_config = {
+	.name = "mcp9983",
+	.phys_channels = 3,
+	.hw_thermal_shutdown = false,
+	.allow_apdd = false,
+	.has_recd34 = true,
+};
+
+static const struct mcp9982_features mcp9983d_chip_config = {
+	.name = "mcp9983d",
+	.phys_channels = 3,
+	.hw_thermal_shutdown = true,
+	.allow_apdd = false,
+	.has_recd34 = true,
+};
+
+static const struct mcp9982_features mcp9984_chip_config = {
+	.name = "mcp9984",
+	.phys_channels = 4,
+	.hw_thermal_shutdown = false,
+	.allow_apdd = true,
+	.has_recd34 = true,
+};
+
+static const struct mcp9982_features mcp9984d_chip_config = {
+	.name = "mcp9984d",
+	.phys_channels = 4,
+	.hw_thermal_shutdown = true,
+	.allow_apdd = true,
+	.has_recd34 = true,
+};
+
+static const struct mcp9982_features mcp9985_chip_config = {
+	.name = "mcp9985",
+	.phys_channels = 5,
+	.hw_thermal_shutdown = false,
+	.allow_apdd = true,
+	.has_recd34 = true,
+};
+
+static const struct mcp9982_features mcp9985d_chip_config = {
+	.name = "mcp9985d",
+	.phys_channels = 5,
+	.hw_thermal_shutdown = true,
+	.allow_apdd = true,
+	.has_recd34 = true,
+};
+
+static const unsigned int mcp9982_update_interval[11] = {
+	16000, 8000, 4000, 2000, 1000, 500, 250, 125, 64, 32, 16
+};
+
+/* MCP9982 regmap configuration */
+static const struct regmap_range mcp9982_regmap_wr_ranges[] = {
+	regmap_reg_range(MCP9982_ONE_SHOT_ADDR, MCP9982_CFG_ADDR),
+	regmap_reg_range(MCP9982_CONV_ADDR, MCP9982_HOTTEST_CFG_ADDR),
+	regmap_reg_range(MCP9982_THERM_SHTDWN_CFG_ADDR, MCP9982_THERM_SHTDWN_CFG_ADDR),
+	regmap_reg_range(MCP9982_EXT_BETA_CFG_ADDR(1), MCP9982_EXT_IDEAL_ADDR(4)),
+};
+
+static const struct regmap_access_table mcp9982_regmap_wr_table = {
+	.yes_ranges = mcp9982_regmap_wr_ranges,
+	.n_yes_ranges = ARRAY_SIZE(mcp9982_regmap_wr_ranges),
+};
+
+static const struct regmap_range mcp9982_regmap_rd_ranges[] = {
+	regmap_reg_range(MCP9982_HIGH_BYTE_ADDR(0), MCP9982_CFG_ADDR),
+	regmap_reg_range(MCP9982_CONV_ADDR, MCP9982_EXT_IDEAL_ADDR(4)),
+};
+
+static const struct regmap_access_table mcp9982_regmap_rd_table = {
+	.yes_ranges = mcp9982_regmap_rd_ranges,
+	.n_yes_ranges = ARRAY_SIZE(mcp9982_regmap_rd_ranges),
+};
+
+static bool mcp9982_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case MCP9982_ONE_SHOT_ADDR:
+	case MCP9982_INTERNAL_HIGH_LIMIT_ADDR:
+	case MCP9982_INTERNAL_LOW_LIMIT_ADDR:
+	case MCP9982_EXT_LOW_LIMIT_ADDR(1):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(1) + 1:
+	case MCP9982_EXT_LOW_LIMIT_ADDR(2):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(2) + 1:
+	case MCP9982_EXT_LOW_LIMIT_ADDR(3):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(3) + 1:
+	case MCP9982_EXT_LOW_LIMIT_ADDR(4):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(4) + 1:
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(1):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(1) + 1:
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(2):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(2) + 1:
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(3):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(3) + 1:
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(4):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(4) + 1:
+	case MCP9982_THERM_LIMIT_ADDR(0):
+	case MCP9982_THERM_LIMIT_ADDR(1):
+	case MCP9982_THERM_LIMIT_ADDR(2):
+	case MCP9982_THERM_LIMIT_ADDR(3):
+	case MCP9982_THERM_LIMIT_ADDR(4):
+	case MCP9982_CFG_ADDR:
+	case MCP9982_CONV_ADDR:
+	case MCP9982_HYS_ADDR:
+	case MCP9982_CONSEC_ALRT_ADDR:
+	case MCP9982_ALRT_CFG_ADDR:
+	case MCP9982_RUNNING_AVG_ADDR:
+	case MCP9982_HOTTEST_CFG_ADDR:
+	case MCP9982_THERM_SHTDWN_CFG_ADDR:
+		return false;
+	default:
+		return true;
+	}
+}
+
+static const struct regmap_config mcp9982_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.rd_table = &mcp9982_regmap_rd_table,
+	.wr_table = &mcp9982_regmap_wr_table,
+	.volatile_reg = mcp9982_is_volatile_reg,
+	.max_register = MCP9982_EXT_IDEAL_ADDR(4),
+	.cache_type = REGCACHE_MAPLE,
+};
+
+/**
+ * struct mcp9982_priv - information about chip parameters
+ * @regmap:			device register map
+ * @chip:			pointer to structure holding chip features
+ * @labels:			labels of the channels
+ * @interval_idx:		index representing the current update interval
+ * @enabled_channel_mask:	mask containing which channels should be enabled
+ * @num_channels:		number of active physical channels
+ * @recd34_enable:		state of Resistance Error Correction(REC) on channels 3 and 4
+ * @recd12_enable:		state of Resistance Error Correction(REC) on channels 1 and 2
+ * @apdd_enable:		state of anti-parallel diode mode
+ * @run_state:			chip is in Run state, otherwise is in Standby state
+ */
+struct mcp9982_priv {
+	struct regmap *regmap;
+	const struct mcp9982_features *chip;
+	const char *labels[MCP9982_MAX_NUM_CHANNELS];
+	unsigned int interval_idx;
+	unsigned long enabled_channel_mask;
+	u8 num_channels;
+	bool recd34_enable;
+	bool recd12_enable;
+	bool apdd_enable;
+	bool run_state;
+};
+
+static int mcp9982_read_limit(struct mcp9982_priv *priv, u8 address, long *val)
+{
+	unsigned int limit, reg_high, reg_low;
+	int ret;
+
+	switch (address) {
+	case MCP9982_INTERNAL_HIGH_LIMIT_ADDR:
+	case MCP9982_INTERNAL_LOW_LIMIT_ADDR:
+	case MCP9982_THERM_LIMIT_ADDR(0):
+	case MCP9982_THERM_LIMIT_ADDR(1):
+	case MCP9982_THERM_LIMIT_ADDR(2):
+	case MCP9982_THERM_LIMIT_ADDR(3):
+	case MCP9982_THERM_LIMIT_ADDR(4):
+		ret = regmap_read(priv->regmap, address, &limit);
+		if (ret)
+			return ret;
+
+		*val = ((int)limit - MCP9982_OFFSET) * 1000;
+
+		return 0;
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(1):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(2):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(3):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(4):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(1):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(2):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(3):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(4):
+		/*
+		 * In order to keep consistency with reading temperature memory region we will use
+		 * single byte I2C read.
+		 */
+		ret = regmap_read(priv->regmap, address, &reg_high);
+		if (ret)
+			return ret;
+
+		ret = regmap_read(priv->regmap, address + 1, &reg_low);
+		if (ret)
+			return ret;
+
+		*val = ((reg_high << 8) + reg_low) >> 5;
+		*val = (*val - (MCP9982_OFFSET << 3)) * 125;
+
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int mcp9982_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+			long *val)
+{
+	struct mcp9982_priv *priv = dev_get_drvdata(dev);
+	unsigned int reg_high, reg_low, hyst, reg_status;
+	int ret;
+	u8 addr;
+
+	/*
+	 * In Standby State the conversion cycle must be initated manually in
+	 * order to read fresh temperature values and the status of the alarms.
+	 */
+	if (!priv->run_state) {
+		switch (type) {
+		case hwmon_temp:
+			switch (attr) {
+			case hwmon_temp_input:
+			case hwmon_temp_max_alarm:
+			case hwmon_temp_min_alarm:
+			case hwmon_temp_crit_alarm:
+				ret = regmap_write(priv->regmap, MCP9982_ONE_SHOT_ADDR, 1);
+				if (ret)
+					return ret;
+				/*
+				 * When the device is in Standby mode, 125 ms need
+				 * to pass from writing in One Shot register before
+				 * the conversion cycle begins.
+				 */
+				usleep_range(MCP9982_WAKE_UP_TIME_US, MCP9982_WAKE_UP_TIME_MAX_US);
+				ret = regmap_read_poll_timeout
+					       (priv->regmap, MCP9982_STATUS_ADDR,
+					       reg_status, !(reg_status & MCP9982_STATUS_BUSY),
+					       MCP9982_WAKE_UP_TIME_US,
+					       MCP9982_WAKE_UP_TIME_US * 10);
+				break;
+			}
+		default:
+			break;
+		}
+	}
+
+	switch (type) {
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_input:
+			/*
+			 * The only areas of memory that support SMBus block read are 80h->89h
+			 * (temperature memory block) and 90h->97h(status memory block).
+			 * In this context the read operation uses SMBus protocol and the first
+			 * value returned will be the number of addresses that can be read.
+			 * Temperature memory block is 10 bytes long and status memory block is 8
+			 * bytes long.
+			 *
+			 * Depending on the read instruction used, the chip behaves differently:
+			 * - regmap_bulk_read() when applied to the temperature memory block
+			 * (80h->89h), the chip replies with SMBus block read, including count,
+			 * additionally to the high and the low bytes. This function cannot be
+			 * applied on the memory region 00h->09h(memory area which does not support
+			 * block reads, returns wrong data) unless use_single_read is set in
+			 * regmap_config.
+			 *
+			 * - regmap_multi_reg_read() when applied to the 00h->09h area uses I2C
+			 * and returns only the high and low temperature bytes. When applied to
+			 * the temperature memory block (80h->89h) returns the count till the end of
+			 * the temperature memory block(aka SMBus count).
+			 *
+			 * - i2c_smbus_read_block_data() is not supported by all drivers.
+			 *
+			 * In order to keep consistency with reading limit memory region we will
+			 * use single byte I2C read.
+			 *
+			 * Low register is latched when high temperature register is read.
+			 */
+			ret = regmap_read(priv->regmap, MCP9982_HIGH_BYTE_ADDR(channel), &reg_high);
+			if (ret)
+				return ret;
+
+			ret = regmap_read(priv->regmap, MCP9982_HIGH_BYTE_ADDR(channel) + 1,
+					  &reg_low);
+			if (ret)
+				return ret;
+
+			*val = ((reg_high << 8) + reg_low) >> 5;
+			*val = (*val - (MCP9982_OFFSET << 3)) * 125;
+
+			return 0;
+		case hwmon_temp_max:
+			if (channel)
+				addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel);
+			else
+				addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR;
+
+			return mcp9982_read_limit(priv, addr, val);
+		case hwmon_temp_max_alarm:
+			*val = regmap_test_bits(priv->regmap, MCP9982_HIGH_LIMIT_STATUS_ADDR,
+						BIT(channel));
+			if (*val < 0)
+				return *val;
+
+			return 0;
+		case hwmon_temp_max_hyst:
+			if (channel)
+				addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel);
+			else
+				addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR;
+			ret = mcp9982_read_limit(priv, addr, val);
+			if (ret)
+				return ret;
+
+			ret = regmap_read(priv->regmap, MCP9982_HYS_ADDR, &hyst);
+			if (ret)
+				return ret;
+
+			*val -= hyst * 1000;
+
+			return 0;
+		case hwmon_temp_min:
+			if (channel)
+				addr = MCP9982_EXT_LOW_LIMIT_ADDR(channel);
+			else
+				addr = MCP9982_INTERNAL_LOW_LIMIT_ADDR;
+
+			return mcp9982_read_limit(priv, addr, val);
+		case hwmon_temp_min_alarm:
+			*val = regmap_test_bits(priv->regmap, MCP9982_LOW_LIMIT_STATUS_ADDR,
+						BIT(channel));
+			if (*val < 0)
+				return *val;
+
+			return 0;
+		case hwmon_temp_crit:
+			return mcp9982_read_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val);
+		case hwmon_temp_crit_alarm:
+			*val = regmap_test_bits(priv->regmap, MCP9982_THERM_LIMIT_STATUS_ADDR,
+						BIT(channel));
+			if (*val < 0)
+				return *val;
+
+			return 0;
+		case hwmon_temp_crit_hyst:
+			ret = mcp9982_read_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val);
+			if (ret)
+				return ret;
+
+			ret = regmap_read(priv->regmap, MCP9982_HYS_ADDR, &hyst);
+			if (ret)
+				return ret;
+
+			*val -= hyst * 1000;
+
+			return 0;
+		default:
+			return -EINVAL;
+		}
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+			*val = mcp9982_update_interval[priv->interval_idx];
+			return 0;
+		default:
+			return -EINVAL;
+		}
+	default:
+		return -EINVAL;
+	}
+}
+
+static int mcp9982_read_label(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+			      int channel, const char **str)
+{
+	struct mcp9982_priv *priv = dev_get_drvdata(dev);
+
+	switch (type) {
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_label:
+			*str = priv->labels[channel];
+			return 0;
+		default:
+			return -EOPNOTSUPP;
+		}
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int mcp9982_write_limit(struct mcp9982_priv *priv, u8 address, long val)
+{
+	int ret;
+	unsigned int regh, regl;
+
+	switch (address) {
+	case MCP9982_INTERNAL_HIGH_LIMIT_ADDR:
+	case MCP9982_INTERNAL_LOW_LIMIT_ADDR:
+	case MCP9982_THERM_LIMIT_ADDR(0):
+	case MCP9982_THERM_LIMIT_ADDR(1):
+	case MCP9982_THERM_LIMIT_ADDR(2):
+	case MCP9982_THERM_LIMIT_ADDR(3):
+	case MCP9982_THERM_LIMIT_ADDR(4):
+		regh = DIV_ROUND_CLOSEST(val, 1000);
+		regh = clamp_val(regh, 0, 255);
+
+		return regmap_write(priv->regmap, address, regh);
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(1):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(2):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(3):
+	case MCP9982_EXT_HIGH_LIMIT_ADDR(4):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(1):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(2):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(3):
+	case MCP9982_EXT_LOW_LIMIT_ADDR(4):
+		val = DIV_ROUND_CLOSEST(val, 125);
+		regh = (val >> 3) & 0xff;
+		regl = (val & 0x07) << 5;
+		/* Block writing is not supported by the chip. */
+		ret = regmap_write(priv->regmap, address, regh);
+		if (ret)
+			return ret;
+
+		return regmap_write(priv->regmap, address + 1, regl);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int mcp9982_write_hyst(struct mcp9982_priv *priv, int channel, long val)
+{
+	int hyst, ret;
+	int limit;
+
+	val = DIV_ROUND_CLOSEST(val, 1000);
+	val = clamp_val(val, 0, 255);
+
+	/* Therm register is 8 bits and so it keeps only the integer part of the temperature. */
+	ret = regmap_read(priv->regmap, MCP9982_THERM_LIMIT_ADDR(channel), &limit);
+	if (ret)
+		return ret;
+
+	hyst = clamp_val(limit - val, 0, 255);
+
+	return regmap_write(priv->regmap, MCP9982_HYS_ADDR, hyst);
+}
+
+static int mcp9982_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
+			 long val)
+{
+	struct mcp9982_priv *priv = dev_get_drvdata(dev);
+	unsigned int idx;
+	u8 addr;
+
+	switch (type) {
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+
+			/*
+			 * For MCP998XD and MCP9933D update interval
+			 * can't be longer than 1 second.
+			 */
+			if (priv->chip->hw_thermal_shutdown)
+				val = clamp_val(val, 0, 1000);
+
+			idx = find_closest_descending(val, mcp9982_update_interval,
+						      ARRAY_SIZE(mcp9982_update_interval));
+			priv->interval_idx = idx;
+
+			return regmap_write(priv->regmap, MCP9982_CONV_ADDR, idx);
+		default:
+			return -EINVAL;
+		}
+	case hwmon_temp:
+		val = clamp_val(val, -64000, 191875);
+		val = val + (MCP9982_OFFSET * 1000);
+		switch (attr) {
+		case hwmon_temp_max:
+			if (channel)
+				addr = MCP9982_EXT_HIGH_LIMIT_ADDR(channel);
+			else
+				addr = MCP9982_INTERNAL_HIGH_LIMIT_ADDR;
+
+			return mcp9982_write_limit(priv, addr, val);
+		case hwmon_temp_min:
+			if (channel)
+				addr = MCP9982_EXT_LOW_LIMIT_ADDR(channel);
+			else
+				addr = MCP9982_INTERNAL_LOW_LIMIT_ADDR;
+
+			return mcp9982_write_limit(priv, addr, val);
+		case hwmon_temp_crit:
+			return mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(channel), val);
+		case hwmon_temp_crit_hyst:
+			return mcp9982_write_hyst(priv, channel, val);
+		default:
+			return -EINVAL;
+		}
+	default:
+		return -EINVAL;
+	}
+}
+
+static umode_t mcp9982_is_visible(const void *_data, enum hwmon_sensor_types type, u32 attr,
+				  int channel)
+{
+	const struct mcp9982_priv *priv = _data;
+
+	if (!test_bit(channel, &priv->enabled_channel_mask))
+		return 0;
+
+	switch (type) {
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_label:
+			if (priv->labels[channel])
+				return 0444;
+			else
+				return 0;
+		case hwmon_temp_input:
+		case hwmon_temp_min_alarm:
+		case hwmon_temp_max_alarm:
+		case hwmon_temp_max_hyst:
+		case hwmon_temp_crit_alarm:
+			return 0444;
+		case hwmon_temp_min:
+		case hwmon_temp_max:
+		case hwmon_temp_crit:
+		case hwmon_temp_crit_hyst:
+			return 0644;
+		default:
+			return 0;
+		}
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+			return 0644;
+		default:
+			return 0;
+		}
+	default:
+		return 0;
+	}
+}
+
+static const struct hwmon_ops mcp9982_hwmon_ops = {
+	.is_visible = mcp9982_is_visible,
+	.read = mcp9982_read,
+	.read_string = mcp9982_read_label,
+	.write = mcp9982_write,
+};
+
+static int mcp9982_init(struct device *dev, struct mcp9982_priv *priv)
+{
+	long high_limit, low_limit;
+	unsigned int i;
+	int ret;
+	u8 val;
+
+	/* Chips 82/83 and 82D/83D do not support anti-parallel diode mode. */
+	if (!priv->chip->allow_apdd && priv->apdd_enable == 1)
+		return dev_err_probe(dev, -EINVAL, "Incorrect setting of APDD.\n");
+
+	/* Chips with "D" work only in Run state. */
+	if (priv->chip->hw_thermal_shutdown && !priv->run_state)
+		return dev_err_probe(dev, -EINVAL, "Incorrect setting of Power State.\n");
+
+	/* All chips with "D" in the name must have RECD12 enabled. */
+	if (priv->chip->hw_thermal_shutdown && !priv->recd12_enable)
+		return dev_err_probe(dev, -EINVAL, "Incorrect setting of RECD12.\n");
+	/* Chips 83D/84D/85D must have RECD34 enabled. */
+	if (priv->chip->hw_thermal_shutdown)
+		if ((priv->chip->has_recd34 && !priv->recd34_enable))
+			return dev_err_probe(dev, -EINVAL, "Incorrect setting of RECD34.\n");
+
+	/*
+	 * Set default values in registers.
+	 * APDD, RECD12 and RECD34 are active on 0.
+	 */
+	val = FIELD_PREP(MCP9982_CFG_MSKAL, 1) |
+	      FIELD_PREP(MCP9982_CFG_RS, !priv->run_state) |
+	      FIELD_PREP(MCP9982_CFG_ATTHM, 1) |
+	      FIELD_PREP(MCP9982_CFG_RECD12, !priv->recd12_enable) |
+	      FIELD_PREP(MCP9982_CFG_RECD34, !priv->recd34_enable) |
+	      FIELD_PREP(MCP9982_CFG_RANGE, 1) | FIELD_PREP(MCP9982_CFG_DA_ENA, 0) |
+	      FIELD_PREP(MCP9982_CFG_APDD, !priv->apdd_enable);
+
+	ret = regmap_write(priv->regmap, MCP9982_CFG_ADDR, val);
+	if (ret)
+		return ret;
+
+	/*
+	 * Read initial value from register.
+	 * The convert register utilises only 4 out of 8 bits.
+	 * Numerical values 0->10 set their respective update intervals,
+	 * while numerical values 11->15 default to 1 second.
+	 */
+	ret = regmap_read(priv->regmap, MCP9982_CONV_ADDR, &priv->interval_idx);
+	if (ret)
+		return ret;
+	if (priv->interval_idx >= 11)
+		priv->interval_idx = 4;
+
+	ret = regmap_write(priv->regmap, MCP9982_HYS_ADDR, MCP9982_DEFAULT_HYS_VAL);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->regmap, MCP9982_CONSEC_ALRT_ADDR, MCP9982_DEFAULT_CONSEC_ALRT_VAL);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->regmap, MCP9982_ALRT_CFG_ADDR, 0);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->regmap, MCP9982_RUNNING_AVG_ADDR, 0);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->regmap, MCP9982_HOTTEST_CFG_ADDR, 0);
+	if (ret)
+		return ret;
+
+	/*
+	 * Only external channels 1 and 2 support beta compensation.
+	 * Set beta auto-detection.
+	 */
+	for (i = 1; i < 3; i++)
+		if (test_bit(i, &priv->enabled_channel_mask)) {
+			ret = regmap_write(priv->regmap, MCP9982_EXT_BETA_CFG_ADDR(i),
+					   MCP9982_BETA_AUTODETECT);
+			if (ret)
+				return ret;
+		}
+
+	high_limit = MCP9982_HIGH_LIMIT_DEFAULT + (MCP9982_OFFSET * 1000);
+	low_limit = MCP9982_LOW_LIMIT_DEFAULT + (MCP9982_OFFSET * 1000);
+
+	/* Set default values for internal channel limits. */
+	if (test_bit(0, &priv->enabled_channel_mask)) {
+		ret = mcp9982_write_limit(priv, MCP9982_INTERNAL_HIGH_LIMIT_ADDR, high_limit);
+		if (ret)
+			return ret;
+
+		ret = mcp9982_write_limit(priv, MCP9982_INTERNAL_LOW_LIMIT_ADDR, low_limit);
+		if (ret)
+			return ret;
+
+		ret = mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(0), high_limit);
+		if (ret)
+			return ret;
+	}
+
+	/* Set ideality factor and limits to default for external channels. */
+	for (i = 1; i < MCP9982_MAX_NUM_CHANNELS; i++)
+		if (test_bit(i, &priv->enabled_channel_mask)) {
+			ret = regmap_write(priv->regmap, MCP9982_EXT_IDEAL_ADDR(i),
+					   MCP9982_IDEALITY_DEFAULT);
+			if (ret)
+				return ret;
+
+			ret = mcp9982_write_limit(priv, MCP9982_EXT_HIGH_LIMIT_ADDR(i), high_limit);
+			if (ret)
+				return ret;
+
+			ret = mcp9982_write_limit(priv, MCP9982_EXT_LOW_LIMIT_ADDR(i), low_limit);
+			if (ret)
+				return ret;
+
+			ret = mcp9982_write_limit(priv, MCP9982_THERM_LIMIT_ADDR(i), high_limit);
+			if (ret)
+				return ret;
+		}
+
+	return 0;
+}
+
+static int mcp9982_parse_fw_config(struct device *dev, int device_nr_channels)
+{
+	struct mcp9982_priv *priv = dev_get_drvdata(dev);
+	unsigned int reg_nr;
+	int ret;
+
+	/* Initialise internal channel( which is always present ). */
+	priv->labels[0] = "internal diode";
+	priv->enabled_channel_mask = 1;
+
+	/* Default values to work on systems without devicetree or firmware nodes. */
+	if (!dev_fwnode(dev)) {
+		priv->num_channels = device_nr_channels;
+		priv->enabled_channel_mask = BIT(priv->num_channels) - 1;
+		priv->apdd_enable = false;
+		priv->recd12_enable = true;
+		priv->recd34_enable = true;
+		priv->run_state = true;
+		return 0;
+	}
+
+	priv->apdd_enable =
+		device_property_read_bool(dev, "microchip,enable-anti-parallel");
+
+	priv->recd12_enable =
+		device_property_read_bool(dev, "microchip,parasitic-res-on-channel1-2");
+
+	priv->recd34_enable =
+		device_property_read_bool(dev, "microchip,parasitic-res-on-channel3-4");
+
+	priv->run_state =
+		device_property_read_bool(dev, "microchip,power-state");
+
+	priv->num_channels = device_get_child_node_count(dev) + 1;
+
+	if (priv->num_channels > device_nr_channels)
+		return dev_err_probe(dev, -EINVAL,
+				     "More channels than the chip supports.\n");
+
+	/* Read information about the external channels. */
+	device_for_each_named_child_node_scoped(dev, child, "channel") {
+		reg_nr = 0;
+		ret = fwnode_property_read_u32(child, "reg", &reg_nr);
+		if (ret || !reg_nr || reg_nr >= device_nr_channels)
+			return dev_err_probe(dev, -EINVAL,
+			  "Channel reg is incorrectly set.\n");
+
+		fwnode_property_read_string(child, "label", &priv->labels[reg_nr]);
+		set_bit(reg_nr, &priv->enabled_channel_mask);
+	}
+
+	return 0;
+}
+
+static const struct hwmon_chip_info mcp998x_chip_info = {
+	.ops = &mcp9982_hwmon_ops,
+	.info = mcp9985_info,
+};
+
+static int mcp9982_probe(struct i2c_client *client)
+{
+	const struct mcp9982_features *chip;
+	struct device *dev = &client->dev;
+	struct mcp9982_priv *priv;
+	struct device *hwmon_dev;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(struct mcp9982_priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->regmap = devm_regmap_init_i2c(client, &mcp9982_regmap_config);
+
+	if (IS_ERR(priv->regmap))
+		return dev_err_probe(dev, PTR_ERR(priv->regmap),
+				     "Cannot initialize register map.\n");
+
+	dev_set_drvdata(dev, priv);
+
+	chip = i2c_get_match_data(client);
+	if (!chip)
+		return -EINVAL;
+	priv->chip = chip;
+
+	ret = mcp9982_parse_fw_config(dev, chip->phys_channels);
+	if (ret)
+		return ret;
+
+	ret = mcp9982_init(dev, priv);
+	if (ret)
+		return ret;
+
+	hwmon_dev = devm_hwmon_device_register_with_info(dev, chip->name, priv,
+							 &mcp998x_chip_info, NULL);
+
+	return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct i2c_device_id mcp9982_id[] = {
+	{ .name = "mcp9933", .driver_data = (kernel_ulong_t)&mcp9933_chip_config },
+	{ .name = "mcp9933d", .driver_data = (kernel_ulong_t)&mcp9933d_chip_config },
+	{ .name = "mcp9982", .driver_data = (kernel_ulong_t)&mcp9982_chip_config },
+	{ .name = "mcp9982d", .driver_data = (kernel_ulong_t)&mcp9982d_chip_config },
+	{ .name = "mcp9983", .driver_data = (kernel_ulong_t)&mcp9983_chip_config },
+	{ .name = "mcp9983d", .driver_data = (kernel_ulong_t)&mcp9983d_chip_config },
+	{ .name = "mcp9984", .driver_data = (kernel_ulong_t)&mcp9984_chip_config },
+	{ .name = "mcp9984d", .driver_data = (kernel_ulong_t)&mcp9984d_chip_config },
+	{ .name = "mcp9985", .driver_data = (kernel_ulong_t)&mcp9985_chip_config },
+	{ .name = "mcp9985d", .driver_data = (kernel_ulong_t)&mcp9985d_chip_config },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, mcp9982_id);
+
+static const struct of_device_id mcp9982_of_match[] = {
+	{
+		.compatible = "microchip,mcp9933",
+		.data = &mcp9933_chip_config,
+	}, {
+		.compatible = "microchip,mcp9933d",
+		.data = &mcp9933d_chip_config,
+	}, {
+		.compatible = "microchip,mcp9982",
+		.data = &mcp9982_chip_config,
+	}, {
+		.compatible = "microchip,mcp9982d",
+		.data = &mcp9982d_chip_config,
+	}, {
+		.compatible = "microchip,mcp9983",
+		.data = &mcp9983_chip_config,
+	}, {
+		.compatible = "microchip,mcp9983d",
+		.data = &mcp9983d_chip_config,
+	}, {
+		.compatible = "microchip,mcp9984",
+		.data = &mcp9984_chip_config,
+	}, {
+		.compatible = "microchip,mcp9984d",
+		.data = &mcp9984d_chip_config,
+	}, {
+		.compatible = "microchip,mcp9985",
+		.data = &mcp9985_chip_config,
+	}, {
+		.compatible = "microchip,mcp9985d",
+		.data = &mcp9985d_chip_config,
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, mcp9982_of_match);
+
+static struct i2c_driver mcp9982_driver = {
+	.driver	 = {
+		.name = "mcp9982",
+		.of_match_table = mcp9982_of_match,
+	},
+	.probe = mcp9982_probe,
+	.id_table = mcp9982_id,
+};
+module_i2c_driver(mcp9982_driver);
+
+MODULE_AUTHOR("Victor Duicu <victor.duicu@microchip.com>");
+MODULE_DESCRIPTION("MCP998X/33 and MCP998XD/33D Multichannel Automotive Temperature Monitor Driver");
+MODULE_LICENSE("GPL");

-- 
2.51.0


^ permalink raw reply related

* [PATCH v12 0/2] add support in hwmon for MCP998X
From: Victor Duicu @ 2026-04-03 13:32 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, marius.cristea,
	victor.duicu

Add support in hwmon for Microchip MCP998X/33 and MCP998XD/33D
Multichannel Automotive Temperature Monitor Family.

The chips in the family have different numbers of external channels,
ranging from 1(MCP9982) to 4 channels (MCP9985).
Reading diodes in anti-parallel connection is supported by MCP9984/85/33
and MCP9984D/85D/33D.
Dedicated hardware shutdown circuitry is present only in MCP998XD
and MCP9933D.

The driver supports reading the temperature channels, the temperature
limits, the limit hysteresis and their corresponding alarms. The user can
set the limits, the update interval and the hysteresis value through
the critical limit hysteresis.

Differences related to previous patch:
v12:
- in devicetree list items in enum.
- make microchip,parasitic-res-on-channel3-4
  required only for the "D" chips that are
  affected by it.
- in mcp9982.rst rephrase to "device starts
  with a 10 degree hysteresis".
- in driver check recd34 is enabled only for
  the "D" chips that are affected by it.
- in mcp9982_read() initiate a conversion cycle
  only when reading temperature values and alarms.
  Use poll function to check the BUSY bit.
- define hyst as unsigned int.
- add comment to clarify that in Standby mode,
  after writing in OneShot register 125ms need
  to pass before the conversion cycle begins.
- add comment to clarify the behavior of the chip
  regarding block read.
- rework the way temperature limits and hysteresis
  are written in order to round the result instead
  of truncating.
- return regmap_write() directly where applicable.
- in mcp9982_write() use clamp_val().
- in mcp9982_init(), if update interval value is 
  outside mcp9982_update_interval[] set default
  value of 1 second.
- use device_for_each_named_child_node_scoped().
- rephrase some comments.
- link to v11: https://lore.kernel.org/r/20260305-add-mcp9982-hwmon-v11-0-6e914ba63239@microchip.com

v11:
- in devicetree set minItems to interrupts.
- in interrupt-names define both items and remove maxItems.
- in conditionals list the items that must be present.
- in driver in mcp9982_write_limit() calculate regh and regl
  at the start of the function.
- link to v10: https://lore.kernel.org/all/20260217-add-mcp9982-hwmon-v10-0-5e0aaae6f289@microchip.com/

v10:
- in devicetree rework interrupt-names.
- rework conditionals to disable the channels that are not used.
- in patternProperties remove minItems from reg and change maxItems to 1.
- in mcp9982.rst fix indentation errors and clarify sysfs entries
  regarding limit hysteresis.
- add power state in the list of devicetree parameters.
- in driver change default high and critical temperature limit
  value to 85000.
- rework driver to not force wait until the next conversion is done.
  Only in Standby state wait the wake up time.
- fix typo in comment about mcp9982_priv.
- remove bitwise operations with mask FF.
- remove clamp_val() from limit hysteresis calculation.
- edit comments regarding block read and write.
- in mcp9982_write_limit() replace bulk write with two writes.
- in mcp9982_write() remove pointless assignment to ret.
- in mcp9982_init() do not override update interval.
- define mcp998x_chip_info as static const struct.
- link to v9: https://lore.kernel.org/all/20260127151823.9728-1-victor.duicu@microchip.com/

v9:
- update copyright year.
- add tempX_max_hyst and tempX_crit_hyst attributes and document
  them in mcp9982.rst.
- in include list add byteorder/generic.h and remove unaligned.h.
- remove definitions for temperature memory block
  and status memory block.
- remove individual definitions for register addresses 1Dh->21h.
- add constants MCP9982_WAKE_UP_TIME_MAX_US and
  MCP9982_TIMER_BUFFER_US.
- add checks to ensure that values read from registers are on 8 bits.
- in mcp9982_read_limit() simplify calculation, replace bulk read
  with individual operations and add comment.
- in mcp9982_read_limit() add explicit case branches for limits
  that are on 16 bits.
- in mcp9982_read() replace mdelay() with usleep_range().
- in mcp9982_read() replace block reading for temperature values with
  individual operations, add comment and remove unnecessary
  mask variable.
- in regmap_read_poll_timeout() add final timeout.
- in mcp9982_read_label() remove label check.
- in mcp9982_write_limit() replace put_unaligned_be16() with cpu_to_be16().
- in mcp9982_write_limit() add explicit case branches for limits
  that are on 16 bits.
- in mcp9982_init() write default value for diode alert mask register.
- in mcp9982_parse_fw_config() replace E2BIG with EINVAL.
- link to v8: https://lore.kernel.org/all/20251120071248.3767-1-victor.duicu@microchip.com/

v8:
- in Kconfig add select REGMAP_I2C.
- in yaml add power state attribute. For chips with "D" in the name
  check that Run mode is set in yaml and driver.
- in the include list: remove cleanup.h, add math.h, minmax.h and
  util_macros.h.
- add min, max and crit limits for all channels. These attributes can
  be read and written. In mcp9982_init() set default values for limits.
- add alarms for limits.
- edit regmap ranges to add the limit registers.
- when writing update interval, don't force the user to set exact value.
  Search for closest valid value.
- in mcp9982_parse_fw_config() check value from fwnode_property_read_u32().
- edit coding style and comments.
- remove constant MCP9982_SCALE.
- rename variable sampl_idx from mcp9982_priv to interval_idx.
- in mcp9982_write() rename variable use_previous_freq
  to use_previous_interval.
- link to v7: https://lore.kernel.org/all/20251031155831.42763-1-victor.duicu@microchip.com/

v7:
- send driver to hwmon subsystem.
- include index.rst and mcp9982.rst.
- in microchip,mcp9982.yaml set microchip,parasitic-res-on-channel1-2
  and 3-4 to required.
- in mcp9982.c rework read raw, read label and write raw functions.
- remove avail parameters.
- rework sampling frequency to update interval.
- lock running average filter to off.
- rework definition of channels.
- add cache type Maple to mcp9982_regmap_config().
- define constants for the numerical values used.
- in include list add: bitops.h, cleanup.h, device.h, hwmon.h,
  time64.h, unaligned.h. Remove iio.h, math64.h, string.h, units.h.
- add explicit definitions for beta and ideality registers.
- add definitions for status memory block registers.
- add mcp9982_is_visible() and set visible only the channels
  that are enabled.
- in mcp9982_parse_fw_config() add branch with default values for
  systems that do not have devicetree or firmware nodes.
- remove mutex.
- link to v6: https://lore.kernel.org/all/20250930133131.13797-1-victor.duicu@microchip.com/

Differences related to the IIO patch:

v6:
- in yaml first condition list part numbers instead
  of regular expression. Add ^ to regular expression.
- edit coding style and comments.
- use hex values in defines.
- remove MCP9982_TEMP_MEM_BLOCK_LOW and
  MCP9982_TEMP_MEM_BLOCK_HIGH.
- in MCP9982_CHAN() place macro parameters in ().
- move all variable definitions at the start of functions.
- in mcp9982_parse_fw_config() initialise iio_idx to 0.
- remove bit flags.
- in MCP9982_CHAN remove outer ().
- remove variable start in mcp9982_write_raw().
- replace constant in .max_register.
- use get_unaligned_be16 in mcp9982_read_raw().
- link to v5: https://lore.kernel.org/all/20250918111937.5150-1-victor.duicu@microchip.com/

v5:
- in yaml edit description of interrupts.
- add min and maxItems to reg.
- remove ideality parameter.
- use pattern recognition in conditionals.
- group conditions based on the chip.
- correct microchip,parasitic-res-on-channel3-4 to true.
- in driver include bitops.h.
- change name of some variables.
- rename mcp9982_parse_of_config() to mcp9982_parse_fw_config().
- implement bulk reading of temp registers.
- lock ideality parameter to default value.
- implement bit flags.
- add compound literal to MCP9982_CHAN.
- remove hysteresis parameter.
- edit comments.
- change values from int to bool in mcp9982_features.
- remove mcp9982_calc_all_3db_values() and hardcode values.
  When filter is OFF the 3db value is equal to frequency.
- add .max_register to regmap_config.
- remove devm_kcalloc().
- in mcp9982_read_avail() add an else branch to hw_thermal_shutdown
  check.
- in mcp9982_read_raw use USEC_PER_MSEC and set regmap_read_poll_timeout
  to never timeout.
  Replace switch with bitmap_weight.
- in mcp9982_read_label() remove unnecessary if.
- in mcp9982_write_raw() remove duplicated code.
- in mcp9982_init add error messages when APDD and RECD are incorrectly
  set.
- in mcp9982_parse_fw_config() add default for reg_nr.
- link to v4: https://lore.kernel.org/all/20250829143447.18893-1-victor.duicu@microchip.com/

v4:
- lock beta parameters to default value of beta-autodetect.
  Remove beta parameters and checks from devicetree.
- lock temperature range to extended.
  This change avoids the issue of the average filter using raw values
  with different scales when changing the range.
- change driver to wait an amount of time before reading a raw value
  to ensure it is valid.
- change driver to stop calculating the physical temp when reading
  in_tempx_raw. Reading from in_tempx_raw will return the raw value.
  The physical temp will be calculated with in_tempx_raw, scale and
  offset parameters.
- add scale parameter to channel definition.
- initialise chips with "D" to work in Run state and those without
  in Standby state.
- when activating the low pass filter for chips without "D",
  set the power state to RUN to ensure fresh values for the average.
- add minimum and maximum to microchip,beta1 and microchip,beta2 in yaml.
- rename microchip,resistance-comp-ch1-2-enable and
  microchip,resistance-comp-ch3-4-enable to
  microchip,parasitic-res-on-channel1-2
  and microchip,parasitic-res-on-channel3-4
  and edit description in yaml.
- add conditional logic to check if the chip supports APDD
  and force default values where necessary in yaml.
- edit comments and coding style.
- replace asm/div64.h with linux/math64.h.
- add delay.h to includes.
- redefine mcp9982_sampl_fr with new structure division.
- in mcp9982_priv remove dev_name,extended_temp_range and beta_values.
  Add run_state, wait_before_read, time_limit and pointer to chip
  structure to remove all instances of matching strings.
  Reorder parameters for memory optimization.
- in mcp9982_features add flags to know if the chip has thermal shutdown
  circuitry and supports APDD.
- in mcp9982_read_avail() rework verification of chip type in sampling
  frequency case.
- in mcp9982_read_raw() rework switch in low pass filter case.
- in mcp9982_parse_of_config() replace generic -EINVAL code
  with -E2BIG and -EOVERFLOW.
- link to v3: https://lore.kernel.org/all/20250613130207.8560-1-victor.duicu@microchip.com/

v3:
- move beta parameters to devicetree.
- change the name of the interrupts and add
  check to match them to the device in yaml.
- remove label for device and remove "0x" from
  channel registers in example in yaml.
- edit comments in yaml and driver.
- add minItems to interrupts in yaml.
- rename microchip,recd12 and microchip,recd34 to
  microchip,resistance-comp-ch1-2-enable
  and microchip,resistance-comp-ch3-4-enable.
- rename microchip,apdd-state to microchip,enable-anti-parallel.
- add static to mcp9982_3db_values_map_tbl to fix
  kernel test robot warning.
- in mcp9982_init() add check to ensure that hardware
  shutdown feature can't be overridden.
- replace div_u64_rem with do_div and add
  asm/div64.h to includes.
- remove unused includes.
- add iio_chan_spec in the macro definition of MCP9982_CHAN.
- remove MCP9982_EXT_BETA_ENBL.
- in mcp9982_init() replace regmap_assign_bits
  with regmap_write when setting beta compensation.
- remove custom attribute enable_extended_temp_range and
  map it to IIO_CHAN_INFO_OFFSET.
- add unsigned to int variables that allow it.
- reorder parameters in mcp9982_priv, change some
  from int to bool, add const to labels and add dev_name.
- add check for chips with "D" in the name to not
  allow sampling frequencies lower than 1 to
  prevent overriding of hardware shutdown.
- remove mcp9982_attributes.
- move mcp9982_calc_all_3db_values() to before
  mcp9982_init().
- use MICRO instead of number constant.
- in mcp9982_write_raw replace ">=" with "==".
- rename index2 to idx in mcp9982_read_raw().
- remove i2c_set_clientdata() in mcp9982_probe().
- since there are no more custom ABI attributes
  the testing file was removed.
- link to v2: https://lore.kernel.org/all/20250529093628.15042-1-victor.duicu@microchip.com/

v2:
- move hysteresis, extended temperature range and beta parameters
  from devicetree into user space.
- edit comments in yaml and driver.
- remove "|" in descpriptions, remove "+" from PatternProperties in yaml.
- add default to microchip,ideality-factor, delete blank lines and wrap to
  80 chars in yaml.
- remove variables with upper case.
- add check for microchip,apdd-state and microchip,recd34 in yaml.
- improve coding style in driver code.
- add includes for all functions used.
- rename MCP9982_INT_HIGH_BYTE_ADDR to MCP9982_INT_VALUE_ADDR and
  MCP9982_INT_LOW_BYTE_ADDR to MCP9982_FRAC_VALUE_ADDR.
- remove custom attribute running_average_window and
  running_average_window_available and map them to a low pass filter.
- update sysfs-bus-iio-temperature-mcp9982 to reflect current
  driver attributes and point to next kernel version (6.17).
- use compound literal to define driver channels.
- replace device_property_read_string() with i2c_get_match_data() to read
  chip name from devicetree.
- remove MCP9982_DEV_ATTR and mcp9982_prep_custom_attributes().
- remove client, chip_name, iio_info from mcp9982_priv.
- replace sprintf() with sysfs_emit().
- remove error messages which are triggered by keyboard input.
- replace devm_kzalloc() with devm_kcalloc(), array mcp9982_chip_config[]
  with individual structures, device_property_present() with
  device_property_read_bool().
- reordered parameters in mcp9982_features and mcp9982_priv to optimize
  memory allocation.
- remove .endianness from channel properties.
- change name of some parameters in mcp9982_priv.
- add check for reg value 0 from devicetree (channel 0 is for internal
  temperature and can't be disabled).
- link to v1: https://lore.kernel.org/all/20250415132623.14913-1-victor.duicu@microchip.com/

v1:
- initial version.

Signed-off-by: Victor Duicu <victor.duicu@microchip.com>
---
Victor Duicu (2):
      dt-bindings: hwmon: add support for MCP998X
      hwmon: add support for MCP998X

 .../bindings/hwmon/microchip,mcp9982.yaml          | 237 +++++
 Documentation/hwmon/index.rst                      |   1 +
 Documentation/hwmon/mcp9982.rst                    | 111 +++
 MAINTAINERS                                        |   8 +
 drivers/hwmon/Kconfig                              |  11 +
 drivers/hwmon/Makefile                             |   1 +
 drivers/hwmon/mcp9982.c                            | 997 +++++++++++++++++++++
 7 files changed, 1366 insertions(+)
---
base-commit: 05f7e89ab9731565d8a62e3b5d1ec206485eeb0b
change-id: 20260305-add-mcp9982-hwmon-9ac964ca3191

Best regards,
-- 
Victor Duicu <victor.duicu@microchip.com>


^ permalink raw reply

* [PATCH v2] Documentation: gpio: update the preferred method for using software node lookup
From: Bartosz Golaszewski @ 2026-04-03 13:04 UTC (permalink / raw)
  To: Linus Walleij, Bartosz Golaszewski, Jonathan Corbet, Shuah Khan,
	Dmitry Torokhov
  Cc: linux-gpio, linux-doc, linux-kernel, Bartosz Golaszewski

In its current version, the manual for converting of board files from
using GPIO lookup tables to software nodes recommends leaving the
software nodes representing GPIO controllers as "free-floating", not
attached objects and relying on the matching of their names against the
GPIO controller's name. This is an abuse of the software node API and
makes it impossible to create fw_devlinks between GPIO suppliers and
consumers in this case. We want to remove this behavior from GPIOLIB and
to this end, work on converting all existing drivers to using "attached"
software nodes.

Except for a few corner-cases where board files define consumers
depending on GPIO controllers described in firmware - where we need to
reference a real firmware node from a software node - which requires a
more complex approach, most board files can easily be converted to using
propert firmware node lookup.

Update the documentation to recommend attaching the GPIO chip's software
nodes to the actual platform devices and show how to do it.

Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
---
Changes in v2:
- Use the new .swnode field of struct platform_device_info in examples
- Fix whitespaces
- Link to v1: https://patch.msgid.link/20260331-doc-gpio-swnodes-v1-1-3f84c268999b@oss.qualcomm.com
---
 Documentation/driver-api/gpio/board.rst         | 20 +++++++++++---
 Documentation/driver-api/gpio/legacy-boards.rst | 36 ++++++++++++++++++-------
 2 files changed, 44 insertions(+), 12 deletions(-)

diff --git a/Documentation/driver-api/gpio/board.rst b/Documentation/driver-api/gpio/board.rst
index 0993cac891fb5e4887a1aee6deae273197c6aae1..b306c21481d7c191201d81d228a290a908cc82ab 100644
--- a/Documentation/driver-api/gpio/board.rst
+++ b/Documentation/driver-api/gpio/board.rst
@@ -108,9 +108,8 @@ macro, which ties a software node representing the GPIO controller with
 consumer device. It allows consumers to use regular gpiolib APIs, such as
 gpiod_get(), gpiod_get_optional().
 
-The software node representing a GPIO controller need not be attached to the
-GPIO controller device. The only requirement is that the node must be
-registered and its name must match the GPIO controller's label.
+The software node representing a GPIO controller must be attached to the
+GPIO controller device - either as the primary or the secondary firmware node.
 
 For example, here is how to describe a single GPIO-connected LED. This is an
 alternative to using platform_data on legacy systems.
@@ -153,6 +152,21 @@ alternative to using platform_data on legacy systems.
 	};
 	software_node_register_node_group(swnodes);
 
+	/*
+	 * 5. Attach the GPIO controller's software node to the device and
+	 *    register it.
+	 */
+	 static void gpio_foo_register(void)
+	 {
+		struct platform_device_info pdev_info = {
+			.name = "gpio-foo",
+			.id = PLATFORM_DEVID_NONE,
+			.swnode = &gpio_controller_node
+		};
+
+		platform_device_register_full(&pdev_info);
+	 }
+
 	// Then register a platform_device for "leds-gpio" and associate
 	// it with &led_device_swnode via .fwnode.
 
diff --git a/Documentation/driver-api/gpio/legacy-boards.rst b/Documentation/driver-api/gpio/legacy-boards.rst
index 46e3a26dba772e5e5117866b5d202e76c8e2adf2..a9d33bcbb176b5df99838bd03e43ec2ebf4d9db6 100644
--- a/Documentation/driver-api/gpio/legacy-boards.rst
+++ b/Documentation/driver-api/gpio/legacy-boards.rst
@@ -36,12 +36,10 @@ Requirements for GPIO Properties
 When using software nodes to describe GPIO connections, the following
 requirements must be met for the GPIO core to correctly resolve the reference:
 
-1.  **The GPIO controller's software node "name" must match the controller's
-    "label".** The gpiolib core uses this name to find the corresponding
-    struct gpio_chip at runtime.
-    This software node has to be registered, but need not be attached to the
-    device representing the GPIO controller that is providing the GPIO in
-    question. It may be left as a "free floating" node.
+1.  **The GPIO controller's software node must be registered and attached to
+    the controller's ``struct device`` either as its primary or secondary
+    firmware node.** The gpiolib core uses the address of the firmware node to
+    find the corresponding ``struct gpio_chip`` at runtime.
 
 2.  **The GPIO property must be a reference.** The ``PROPERTY_ENTRY_GPIO()``
     macro handles this as it is an alias for ``PROPERTY_ENTRY_REF()``.
@@ -121,13 +119,21 @@ A typical legacy board file might look like this:
   /* Device registration */
   static int __init myboard_init(void)
   {
+  	struct platform_device_info pdev_info = {
+  		.name = MYBOARD_GPIO_CONTROLLER,
+  		.id = PLATFORM_DEVID_NONE,
+  		.swnode = &gpio_controller_node
+  	};
+
   	gpiod_add_lookup_table(&myboard_leds_gpios);
   	gpiod_add_lookup_table(&myboard_buttons_gpios);
 
+  	platform_device_register_full(&pdev_info);
   	platform_device_register_data(NULL, "leds-gpio", -1,
   				      &myboard_leds_pdata, sizeof(myboard_leds_pdata));
   	platform_device_register_data(NULL, "gpio-keys", -1,
-  				      &myboard_buttons_pdata, sizeof(myboard_buttons_pdata));
+  				      &myboard_buttons_pdata,
+  				      sizeof(myboard_buttons_pdata));
 
   	return 0;
   }
@@ -141,8 +147,7 @@ Step 1: Define the GPIO Controller Node
 ***************************************
 
 First, define a software node that represents the GPIO controller that the
-LEDs and buttons are connected to. The ``name`` of this node must match the
-name of the driver for the GPIO controller (e.g., "gpio-foo").
+LEDs and buttons are connected to. The ``name`` of this node is optional.
 
 .. code-block:: c
 
@@ -257,6 +262,16 @@ software nodes using the ``fwnode`` field in struct platform_device_info.
   	if (error)
   		return error;
 
+  	memset(&pdev_info, 0, sizeof(pdev_info));
+  	pdev_info.name = MYBOARD_GPIO_CONTROLLER;
+  	pdev_info.id = PLATFORM_DEVID_NONE;
+  	pdev_info.swnode = &myboard_gpio_controller_node;
+  	gpio_pdev = platform_device_register_full(&pdev_info);
+  	if (IS_ERR(gpio_pdev)) {
+  		error = PTR_ERR(gpio_pdev);
+  		goto err_unregister_nodes;
+  	}
+
   	memset(&pdev_info, 0, sizeof(pdev_info));
   	pdev_info.name = "leds-gpio";
   	pdev_info.id = PLATFORM_DEVID_NONE;
@@ -264,6 +279,7 @@ software nodes using the ``fwnode`` field in struct platform_device_info.
   	leds_pdev = platform_device_register_full(&pdev_info);
   	if (IS_ERR(leds_pdev)) {
   		error = PTR_ERR(leds_pdev);
+  		platform_device_unregister(gpio_pdev);
   		goto err_unregister_nodes;
   	}
 
@@ -274,6 +290,7 @@ software nodes using the ``fwnode`` field in struct platform_device_info.
   	keys_pdev = platform_device_register_full(&pdev_info);
   	if (IS_ERR(keys_pdev)) {
   		error = PTR_ERR(keys_pdev);
+  		platform_device_unregister(gpio_pdev);
   		platform_device_unregister(leds_pdev);
   		goto err_unregister_nodes;
   	}
@@ -289,6 +306,7 @@ software nodes using the ``fwnode`` field in struct platform_device_info.
   {
   	platform_device_unregister(keys_pdev);
   	platform_device_unregister(leds_pdev);
+  	platform_device_unregister(gpio_pdev);
   	software_node_unregister_node_group(myboard_swnodes);
   }
 

---
base-commit: cc13002a9f984d37906e9476f3e532a8cdd126f5
change-id: 20260331-doc-gpio-swnodes-fc3ddf59b8dc

Best regards,
-- 
Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>


^ permalink raw reply related

* [PATCH v9 0/2] Add support for Microchip EMC1812
From: Marius Cristea @ 2026-04-03 12:39 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, Marius Cristea,
	Conor Dooley

This is the hwmon driver for EMC1812/13/14/15/33 multichannel Low-Voltage
Remote Diode Sensor Family. The chips in the family have one internal
and different numbers of external channels, ranging from 1 (EMC1812) to
4 channels (EMC1815).
Reading diodes in anti-parallel connection is supported by EMC1814, EMC1815
and EMC1833.

Signed-off-by: Marius Cristea <marius.cristea@microchip.com>
---
Changes in v9:
- improve the wording in the Documentation/hwmon/emc1812.rst file
- add const to variables in the driver
- initialize the EXT2_BETA_CONFIG only for the pats that support it
- update the writeble regmap table to exclude read-only registers
- Link to v8: https://lore.kernel.org/r/20260310-hw_mon-emc1812-v8-0-bc155727e0d2@microchip.com

Changes in v8:
- remove "address scan" from emc1812.rst documentation
- change the second dimension of emc1812_limit_regs_low[][] to 2
- clamp input value before doing math on it to avoid overflow
- use rounding instead of truncation for 8 bits limit registers
- fix misleading comment when HW ID is not recognized
- Link to v7: https://lore.kernel.org/r/20260223-hw_mon-emc1812-v7-0-51e2676f4e20@microchip.com

Changes in v7:
- driver
  - fix an overflow emc1812_set_hyst
  - remove unused parameter in emc1812_set_temp
- devicetree binding:
  - remove unneeded restrictions not to bloating the binding
- Link to v6: https://lore.kernel.org/r/20260212-hw_mon-emc1812-v6-0-e37e9b38d898@microchip.com

Changes in v6:
- driver
  - fix an overflow when writing more then 191875 to limits stored on 8
    bits register
  - remove "i2c_set_clientdata" from probe
  - fix discrepancy where writing 16ms and reading it back returns 15ms
    at update interval
  - skip setting the ideality factor for channels that are not available
    on the device
- devicetree binding:
  - change the way interrupts are described/used
  - add "microchip,enable-anti-parallel"
  - rewrite "allOf" section to be more clear
- Link to v5: https://lore.kernel.org/r/20260205-hw_mon-emc1812-v5-0-232835aefe8f@microchip.com

Changes in v5:
- fix calculation in emc1812_get_limit_temp 
- use i2c_get_match_data cover the case when the driver is instantiated
  via I2C ID table.
- replace dev_info with dev_warn
- remove some unnecessary truncation on 8 bits
- remove clamping when reading the temerature with hyst
- not change the conversion rate at probe time
- use a generic define to remove duplicate channel_info entries
- Link to v4: https://lore.kernel.org/r/20260127-hw_mon-emc1812-v4-0-6bf636b54847@microchip.com

Changes in v4:
- fix file permissions for read only properties
- fix calculation when the limits are written
- remove the temp_min_hyst because the part doesn't support it
- Link to v3: https://lore.kernel.org/r/20251218-hw_mon-emc1812-v3-0-a123ada7b859@microchip.com

Changes in v3:
- remove mesages that are not helpfull
- fix an issue related to NULL labels
- fix sign/unsign calculation
- replace E2BIG with EINVAL
- use BIT() to create mask
- Link to v2: https://lore.kernel.org/r/20251121-hw_mon-emc1812-v2-0-5b2070f8b778@microchip.com

Changes in v2:
- update the interrupt section from yaml file
- update index.rst
- remove fault condition from internal sensor
- remove unused members from structures
- update the driver to work on systems without device tree or
  firmware nodes
- add missing include files
- make NULL labels to be not visible
- corect sign/unsign calculations
- corect possible underflow for limits
- Link to v1: https://lore.kernel.org/r/20251029-hw_mon-emc1812-v1-0-be4fd8af016a@microchip.com

---
Marius Cristea (2):
      dt-bindings: hwmon: temperature: add support for EMC1812
      hwmon: temperature: add support for EMC1812

 .../bindings/hwmon/microchip,emc1812.yaml          | 184 ++++
 Documentation/hwmon/emc1812.rst                    |  67 ++
 Documentation/hwmon/index.rst                      |   1 +
 MAINTAINERS                                        |   8 +
 drivers/hwmon/Kconfig                              |  11 +
 drivers/hwmon/Makefile                             |   1 +
 drivers/hwmon/emc1812.c                            | 965 +++++++++++++++++++++
 7 files changed, 1237 insertions(+)
---
base-commit: d2b2fea3503e5e12b2e28784152937e48bcca6ff
change-id: 20251002-hw_mon-emc1812-f1b806487d10

Best regards,
-- 
Marius Cristea <marius.cristea@microchip.com>


^ permalink raw reply

* [PATCH v9 2/2] hwmon: temperature: add support for EMC1812
From: Marius Cristea @ 2026-04-03 12:39 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, Marius Cristea
In-Reply-To: <20260403-hw_mon-emc1812-v9-0-1a798f31cf2e@microchip.com>

This is the hwmon driver for Microchip EMC1812/13/14/15/33
Multichannel Low-Voltage Remote Diode Sensor Family.

EMC1812 has one external remote temperature monitoring channel.
EMC1813 has two external remote temperature monitoring channels.
EMC1814 has three external remote temperature monitoring channels and
channels 2 and 3 support anti parallel diode.
EMC1815 has four external remote temperature monitoring channels and
channels 1/2  and 3/4 support anti parallel diode.
EMC1833 has two external remote temperature monitoring channels and
channels 1 and 2 support anti parallel diode.

Signed-off-by: Marius Cristea <marius.cristea@microchip.com>
---
 Documentation/hwmon/emc1812.rst |  67 +++
 Documentation/hwmon/index.rst   |   1 +
 MAINTAINERS                     |   2 +
 drivers/hwmon/Kconfig           |  11 +
 drivers/hwmon/Makefile          |   1 +
 drivers/hwmon/emc1812.c         | 965 ++++++++++++++++++++++++++++++++++++++++
 6 files changed, 1047 insertions(+)

diff --git a/Documentation/hwmon/emc1812.rst b/Documentation/hwmon/emc1812.rst
new file mode 100644
index 0000000000000000000000000000000000000000..0b4fbcaaea71a42ef4db1be25182ab363a136a5d
--- /dev/null
+++ b/Documentation/hwmon/emc1812.rst
@@ -0,0 +1,67 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Kernel driver emc1812
+=====================
+
+Supported chips:
+
+  * Microchip EMC1812, EMC1813, EMC1814, EMC1815, EMC1833
+
+    Prefix: 'emc1812'
+
+    Datasheets:
+
+	- https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/EMC1812-3-4-5-33-Data-Sheet-DS20005751.pdf
+
+Author:
+    Marius Cristea <marius.cristea@microchip.com>
+
+
+Description
+-----------
+
+The Microchip EMC181x/33 chips contain up to 4 remote temperature sensors
+and one internal.
+- The EMC1812 is a single channel remote temperature sensor.
+- The EMC1813 and EMC1833 are dual channel remote temperature sensor. The
+remote channels for this selection of devices can support substrate diodes,
+discrete diode-connected transistors or CPU/GPU thermal diodes.
+- The EMC1814 is a three channel remote temperature sensor that supports
+Anti-Parallel Diode (APD) only on one channel. For the channel that does not
+support APD functionality, substrate diodes, discrete diode-connected
+transistors or CPU/GPU thermal diodes are supported. For the channel that
+supports APD, only discrete diode-connected transistors may be implemented.
+However, if APD is disabled on the EMC1814, then the channel that supports
+APD will be functional with substrate diodes, discrete diode-connected
+transistors and CPU/GPU thermal diodes.
+- The EMC1815 is a four channel remote temperature sensor.
+
+The EMC1815 and EMC1833 support APD on all channels. When APD is enabled,
+the channels support only diode-connected transistors. If APD is disabled,
+then the channels will support substrate transistors, discrete diode-connected
+transistors and CPU/GPU thermal diodes.
+
+Note: Disabling APD functionality to implement substrate diodes on devices
+that support APD eliminates the benefit of APD (two diodes on one channel).
+
+The chips implement three limits for each sensor: low (tempX_min), high
+(tempX_max) and critical (tempX_crit). The chips also implement an
+hysteresis mechanism which applies to all limits. The relative difference
+is stored in a single register on the chip, which means that the relative
+difference between the limit and its hysteresis is always the same for
+all three limits.
+
+This implementation detail implies the following:
+
+* When setting a limit, its hysteresis will automatically follow, the
+  difference staying unchanged. For example, if the old critical limit was
+  80 degrees C, and the hysteresis was 75 degrees C, and you change the
+  critical limit to 90 degrees C, then the hysteresis will automatically
+  change to 85 degrees C.
+* The hysteresis values can't be set independently. We decided to make
+  only tempX_crit_hyst writable, while all other hysteresis attributes
+  are read-only. Setting tempX_crit_hyst writes the difference between
+  tempX_crit_hyst and tempX_crit into the chip, and the same relative
+  hysteresis applies automatically to all other limits.
+* The limits should be set before the hysteresis. At power up the device
+  starts with 10 degree hysteresis.
diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 51a5bdf75b08656ee6499c6b5c50a51fc4d7c210..a03e97f9a97f4d3edf7bcd1e8d1b73a21d5f0ab5 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -69,6 +69,7 @@ Hardware Monitoring Kernel Drivers
    ds1621
    ds620
    emc1403
+   emc1812
    emc2103
    emc2305
    emc6w201
diff --git a/MAINTAINERS b/MAINTAINERS
index 85c236df781e47c78deeb7ef4d80bc94bba604c4..fcb712549ea679d49fde8c97840af9528b52d52b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16651,6 +16651,8 @@ M:	Marius Cristea <marius.cristea@microchip.com>
 L:	linux-hwmon@vger.kernel.org
 S:	Supported
 F:	Documentation/devicetree/bindings/hwmon/microchip,emc1812.yaml
+F:	Documentation/hwmon/emc1812.rst
+F:	drivers/hwmon/emc1812.c
 
 MICROCHIP I2C DRIVER
 M:	Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 2760feb9f83b5d3b990b27acff572e587b373e9d..3b53572fd8bfbd752c2235ca429c4f74b1db3095 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -2042,6 +2042,17 @@ config SENSORS_EMC1403
 	  Threshold values can be configured using sysfs.
 	  Data from the different diodes are accessible via sysfs.
 
+config SENSORS_EMC1812
+	tristate "Microchip Technology EMC1812 driver"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	  If you say yes here to build support for Microchip Technology's
+	  EMC181X/33  Multichannel Low-Voltage Remote Diode Sensor Family.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called emc1812.
+
 config SENSORS_EMC2103
 	tristate "SMSC EMC2103"
 	depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 73b2abdcc6dd9cfae4c84b350febc5d8c191e385..e93e4051e99db698dbaae97ac4841e6d810ee8c4 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -73,6 +73,7 @@ obj-$(CONFIG_SENSORS_DRIVETEMP)	+= drivetemp.o
 obj-$(CONFIG_SENSORS_DS620)	+= ds620.o
 obj-$(CONFIG_SENSORS_DS1621)	+= ds1621.o
 obj-$(CONFIG_SENSORS_EMC1403)	+= emc1403.o
+obj-$(CONFIG_SENSORS_EMC1812)	+= emc1812.o
 obj-$(CONFIG_SENSORS_EMC2103)	+= emc2103.o
 obj-$(CONFIG_SENSORS_EMC2305)	+= emc2305.o
 obj-$(CONFIG_SENSORS_EMC6W201)	+= emc6w201.o
diff --git a/drivers/hwmon/emc1812.c b/drivers/hwmon/emc1812.c
new file mode 100644
index 0000000000000000000000000000000000000000..1ba8feac93b5f078d4e544c72711e69f0be06238
--- /dev/null
+++ b/drivers/hwmon/emc1812.c
@@ -0,0 +1,965 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * HWMON driver for Microchip EMC1812/13/14/15/33 Multichannel high-accuracy
+ * 2-wire low-voltage remote diode temperature monitor family.
+ *
+ * Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Marius Cristea <marius.cristea@microchip.com>
+ *
+ * Datasheet can be found here:
+ * https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/EMC1812-3-4-5-33-Data-Sheet-DS20005751.pdf
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/string.h>
+#include <linux/units.h>
+#include <linux/util_macros.h>
+
+/* EMC1812 Registers Addresses */
+#define EMC1812_STATUS_ADDR				0x02
+#define EMC1812_CONFIG_LO_ADDR				0x03
+
+#define EMC1812_CFG_ADDR				0x09
+#define EMC1812_CONV_ADDR				0x0A
+#define EMC1812_INT_DIODE_HIGH_LIMIT_ADDR		0x0B
+#define EMC1812_INT_DIODE_LOW_LIMIT_ADDR		0x0C
+#define EMC1812_EXT1_HIGH_LIMIT_HIGH_BYTE_ADDR		0x0D
+#define EMC1812_EXT1_LOW_LIMIT_HIGH_BYTE_ADDR		0x0E
+#define EMC1812_ONE_SHOT_ADDR				0x0F
+
+#define EMC1812_EXT1_HIGH_LIMIT_LOW_BYTE_ADDR		0x13
+#define EMC1812_EXT1_LOW_LIMIT_LOW_BYTE_ADDR		0x14
+#define EMC1812_EXT2_HIGH_LIMIT_HIGH_BYTE_ADDR		0x15
+#define EMC1812_EXT2_LOW_LIMIT_HIGH_BYTE_ADDR		0x16
+#define EMC1812_EXT2_HIGH_LIMIT_LOW_BYTE_ADDR		0x17
+#define EMC1812_EXT2_LOW_LIMIT_LOW_BYTE_ADDR		0x18
+#define EMC1812_EXT1_THERM_LIMIT_ADDR			0x19
+#define EMC1812_EXT2_THERM_LIMIT_ADDR			0x1A
+#define EMC1812_EXT_DIODE_FAULT_STATUS_ADDR		0x1B
+
+#define EMC1812_DIODE_FAULT_MASK_ADDR			0x1F
+#define EMC1812_INT_DIODE_THERM_LIMIT_ADDR		0x20
+#define EMC1812_THRM_HYS_ADDR				0x21
+#define EMC1812_CONSEC_ALERT_ADDR			0x22
+
+#define EMC1812_EXT1_BETA_CONFIG_ADDR			0x25
+#define EMC1812_EXT2_BETA_CONFIG_ADDR			0x26
+#define EMC1812_EXT1_IDEALITY_FACTOR_ADDR		0x27
+#define EMC1812_EXT2_IDEALITY_FACTOR_ADDR		0x28
+
+#define EMC1812_EXT3_HIGH_LIMIT_HIGH_BYTE_ADDR		0x2C
+#define EMC1812_EXT3_LOW_LIMIT_HIGH_BYTE_ADDR		0x2D
+#define EMC1812_EXT3_HIGH_LIMIT_LOW_BYTE_ADDR		0x2E
+#define EMC1812_EXT3_LOW_LIMIT_LOW_BYTE_ADDR		0x2F
+#define EMC1812_EXT3_THERM_LIMIT_ADDR			0x30
+#define EMC1812_EXT3_IDEALITY_FACTOR_ADDR		0x31
+
+#define EMC1812_EXT4_HIGH_LIMIT_HIGH_BYTE_ADDR		0x34
+#define EMC1812_EXT4_LOW_LIMIT_HIGH_BYTE_ADDR		0x35
+#define EMC1812_EXT4_HIGH_LIMIT_LOW_BYTE_ADDR		0x36
+#define EMC1812_EXT4_LOW_LIMIT_LOW_BYTE_ADDR		0x37
+#define EMC1812_EXT4_THERM_LIMIT_ADDR			0x38
+#define EMC1812_EXT4_IDEALITY_FACTOR_ADDR		0x39
+#define EMC1812_HIGH_LIMIT_STATUS_ADDR			0x3A
+#define EMC1812_LOW_LIMIT_STATUS_ADDR			0x3B
+#define EMC1812_THERM_LIMIT_STATUS_ADDR			0x3C
+#define EMC1812_ROC_GAIN_ADDR				0x3D
+#define EMC1812_ROC_CONFIG_ADDR				0x3E
+#define EMC1812_ROC_STATUS_ADDR				0x3F
+#define EMC1812_R1_RESH_ADDR				0x40
+#define EMC1812_R1_LIMH_ADDR				0x41
+#define EMC1812_R1_LIML_ADDR				0x42
+#define EMC1812_R1_SMPL_ADDR				0x43
+#define EMC1812_R2_RESH_ADDR				0x44
+#define EMC1812_R2_3_RESL_ADDR				0x45
+#define EMC1812_R2_LIMH_ADDR				0x46
+#define EMC1812_R2_LIML_ADDR				0x47
+#define EMC1812_R2_SMPL_ADDR				0x48
+#define EMC1812_PER_MAXTH_1_ADDR			0x49
+#define EMC1812_PER_MAXT1L_ADDR				0x4A
+#define EMC1812_PER_MAXTH_2_ADDR			0x4B
+#define EMC1812_PER_MAXT2_3L_ADDR			0x4C
+#define EMC1812_GBL_MAXT1H_ADDR				0x4D
+#define EMC1812_GBL_MAXT1L_ADDR				0x4E
+#define EMC1812_GBL_MAXT2H_ADDR				0x4F
+#define EMC1812_GBL_MAXT2L_ADDR				0x50
+#define EMC1812_FILTER_SEL_ADDR				0x51
+
+#define EMC1812_INT_HIGH_BYTE_ADDR		0x60
+#define EMC1812_INT_LOW_BYTE_ADDR		0x61
+#define EMC1812_EXT1_HIGH_BYTE_ADDR		0x62
+#define EMC1812_EXT1_LOW_BYTE_ADDR		0x63
+#define EMC1812_EXT2_HIGH_BYTE_ADDR		0x64
+#define EMC1812_EXT2_LOW_BYTE_ADDR		0x65
+#define EMC1812_EXT3_HIGH_BYTE_ADDR		0x66
+#define EMC1812_EXT3_LOW_BYTE_ADDR		0x67
+#define EMC1812_EXT4_HIGH_BYTE_ADDR		0x68
+#define EMC1812_EXT4_LOW_BYTE_ADDR		0x69
+#define EMC1812_HOTTEST_DIODE_HIGH_BYTE_ADDR	0x6A
+#define EMC1812_HOTTEST_DIODE_LOW_BYTE_ADDR	0x6B
+#define EMC1812_HOTTEST_STATUS_ADDR		0x6C
+#define EMC1812_HOTTEST_CFG_ADDR		0x6D
+
+#define EMC1812_PRODUCT_ID_ADDR		0xFD
+#define EMC1812_MANUFACTURER_ID_ADDR	0xFE
+#define EMC1812_REVISION_ADDR		0xFF
+
+/* EMC1812 Config Bits */
+#define EMC1812_CFG_MSKAL		BIT(7)
+#define EMC1812_CFG_RS			BIT(6)
+#define EMC1812_CFG_ATTHM		BIT(5)
+#define EMC1812_CFG_RECD12		BIT(4)
+#define EMC1812_CFG_RECD34		BIT(3)
+#define EMC1812_CFG_RANGE		BIT(2)
+#define EMC1812_CFG_DA_ENA		BIT(1)
+#define EMC1812_CFG_APDD		BIT(0)
+
+/* EMC1812 Status Bits */
+#define EMC1812_STATUS_ROCF		BIT(7)
+#define EMC1812_STATUS_HOTCHG		BIT(6)
+#define EMC1812_STATUS_BUSY		BIT(5)
+#define EMC1812_STATUS_HIGH		BIT(4)
+#define EMC1812_STATUS_LOW		BIT(3)
+#define EMC1812_STATUS_FAULT		BIT(2)
+#define EMC1812_STATUS_ETHRM		BIT(1)
+#define EMC1812_STATUS_ITHRM		BIT(0)
+
+#define EMC1812_BETA_LOCK_VAL		0x0F
+
+#define EMC1812_TEMP_CH_ADDR(index)	(EMC1812_INT_HIGH_BYTE_ADDR + 2 * (index))
+
+#define EMC1812_FILTER_MASK_LEN		2
+
+#define EMC1812_PID			0x81
+#define EMC1813_PID			0x87
+#define EMC1814_PID			0x84
+#define EMC1815_PID			0x85
+#define EMC1833_PID			0x83
+
+/* The maximum number of channels a member of the family can have */
+#define EMC1812_MAX_NUM_CHANNELS		5
+#define EMC1812_TEMP_OFFSET			64
+
+#define EMC1812_DEFAULT_IDEALITY_FACTOR		0x12
+
+#define EMC1812_TEMP_MASK (HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | \
+			   HWMON_T_CRIT | HWMON_T_MAX_HYST | HWMON_T_CRIT_HYST | \
+			   HWMON_T_MIN_ALARM | HWMON_T_MAX_ALARM | \
+			   HWMON_T_CRIT_ALARM | HWMON_T_LABEL)
+
+static const struct hwmon_channel_info * const emc1812_info[] = {
+	HWMON_CHANNEL_INFO(chip, HWMON_C_UPDATE_INTERVAL),
+	HWMON_CHANNEL_INFO(temp,
+			   EMC1812_TEMP_MASK,
+			   EMC1812_TEMP_MASK | HWMON_T_FAULT,
+			   EMC1812_TEMP_MASK | HWMON_T_FAULT,
+			   EMC1812_TEMP_MASK | HWMON_T_FAULT,
+			   EMC1812_TEMP_MASK | HWMON_T_FAULT
+			   ),
+	NULL
+};
+
+/**
+ * struct emc1812_features - features of a emc1812 instance
+ * @name:		chip's name
+ * @phys_channels:	number of physical channels supported by the chip
+ * @has_ext2_beta_reg:	the EXT2_BETA register is available on the chip
+ */
+struct emc1812_features {
+	const char	*name;
+	u8		phys_channels;
+	bool		has_ext2_beta_reg;
+};
+
+static const struct emc1812_features emc1833_chip_config = {
+	.name = "emc1833",
+	.phys_channels = 3,
+	.has_ext2_beta_reg = true,
+};
+
+static const struct emc1812_features emc1812_chip_config = {
+	.name = "emc1812",
+	.phys_channels = 2,
+	.has_ext2_beta_reg = false,
+};
+
+static const struct emc1812_features emc1813_chip_config = {
+	.name = "emc1813",
+	.phys_channels = 3,
+	.has_ext2_beta_reg = true,
+};
+
+static const struct emc1812_features emc1814_chip_config = {
+	.name = "emc1814",
+	.phys_channels = 4,
+	.has_ext2_beta_reg = false,
+};
+
+static const struct emc1812_features emc1815_chip_config = {
+	.name = "emc1815",
+	.phys_channels = 5,
+	.has_ext2_beta_reg = false,
+};
+
+enum emc1812_limit_type {temp_min, temp_max};
+
+static const u8 emc1812_temp_map[] = {
+	[hwmon_temp_min] = temp_min,
+	[hwmon_temp_max] = temp_max,
+};
+
+static const u8 emc1812_temp_crit_regs[] = {
+	[0] = EMC1812_INT_DIODE_THERM_LIMIT_ADDR,
+	[1] = EMC1812_EXT1_THERM_LIMIT_ADDR,
+	[2] = EMC1812_EXT2_THERM_LIMIT_ADDR,
+	[3] = EMC1812_EXT3_THERM_LIMIT_ADDR,
+	[4] = EMC1812_EXT4_THERM_LIMIT_ADDR,
+};
+
+static const u8 emc1812_limit_regs[][2] = {
+	[0] = {
+		[temp_min] = EMC1812_INT_DIODE_LOW_LIMIT_ADDR,
+		[temp_max] = EMC1812_INT_DIODE_HIGH_LIMIT_ADDR,
+	},
+	[1] = {
+		[temp_min] = EMC1812_EXT1_LOW_LIMIT_HIGH_BYTE_ADDR,
+		[temp_max] = EMC1812_EXT1_HIGH_LIMIT_HIGH_BYTE_ADDR,
+	},
+	[2] = {
+		[temp_min] = EMC1812_EXT2_LOW_LIMIT_HIGH_BYTE_ADDR,
+		[temp_max] = EMC1812_EXT2_HIGH_LIMIT_HIGH_BYTE_ADDR,
+	},
+	[3] = {
+		[temp_min] = EMC1812_EXT3_LOW_LIMIT_HIGH_BYTE_ADDR,
+		[temp_max] = EMC1812_EXT3_HIGH_LIMIT_HIGH_BYTE_ADDR,
+	},
+	[4] = {
+		[temp_min] = EMC1812_EXT4_LOW_LIMIT_HIGH_BYTE_ADDR,
+		[temp_max] = EMC1812_EXT4_HIGH_LIMIT_HIGH_BYTE_ADDR,
+	},
+};
+
+static const u8 emc1812_limit_regs_low[][2] = {
+	[0] = {
+		[temp_min] = 0xff,
+		[temp_max] = 0xff,
+	},
+	[1] = {
+		[temp_min] = EMC1812_EXT1_LOW_LIMIT_LOW_BYTE_ADDR,
+		[temp_max] = EMC1812_EXT1_HIGH_LIMIT_LOW_BYTE_ADDR,
+	},
+	[2] = {
+		[temp_min] = EMC1812_EXT2_LOW_LIMIT_LOW_BYTE_ADDR,
+		[temp_max] = EMC1812_EXT2_HIGH_LIMIT_LOW_BYTE_ADDR,
+	},
+	[3] = {
+		[temp_min] = EMC1812_EXT3_LOW_LIMIT_LOW_BYTE_ADDR,
+		[temp_max] = EMC1812_EXT3_HIGH_LIMIT_LOW_BYTE_ADDR,
+	},
+	[4] = {
+		[temp_min] = EMC1812_EXT4_LOW_LIMIT_LOW_BYTE_ADDR,
+		[temp_max] = EMC1812_EXT4_HIGH_LIMIT_LOW_BYTE_ADDR,
+	},
+};
+
+/* Lookup table for temperature conversion times in msec */
+static const u16 emc1812_conv_time[] = {
+	16000, 8000, 4000, 2000, 1000, 500, 250, 125, 62, 31, 16
+};
+
+/**
+ * struct emc1812_data - information about chip parameters
+ * @labels:		labels of the channels
+ * @active_ch_mask:	active channels
+ * @chip:		pointer to structure holding chip features
+ * @regmap:		device register map
+ * @recd34_en:		state of Resistance Error Correction (REC) on channels 3 and 4
+ * @recd12_en:		state of Resistance Error Correction (REC) on channels 1 and 2
+ * @apdd_en:		state of anti-parallel diode mode
+ */
+struct emc1812_data {
+	const char *labels[EMC1812_MAX_NUM_CHANNELS];
+	unsigned long active_ch_mask;
+	const struct emc1812_features *chip;
+	struct regmap *regmap;
+	bool recd34_en;
+	bool recd12_en;
+	bool apdd_en;
+};
+
+/* emc1812 regmap configuration */
+static const struct regmap_range emc1812_regmap_writable_ranges[] = {
+	regmap_reg_range(EMC1812_CFG_ADDR, EMC1812_ONE_SHOT_ADDR),
+	regmap_reg_range(EMC1812_EXT1_HIGH_LIMIT_LOW_BYTE_ADDR, EMC1812_EXT2_THERM_LIMIT_ADDR),
+	regmap_reg_range(EMC1812_DIODE_FAULT_MASK_ADDR, EMC1812_CONSEC_ALERT_ADDR),
+	regmap_reg_range(EMC1812_EXT1_BETA_CONFIG_ADDR, EMC1812_EXT4_IDEALITY_FACTOR_ADDR),
+	regmap_reg_range(EMC1812_ROC_GAIN_ADDR, EMC1812_ROC_CONFIG_ADDR),
+	regmap_reg_range(EMC1812_R1_LIMH_ADDR, EMC1812_R1_SMPL_ADDR),
+	regmap_reg_range(EMC1812_R2_LIMH_ADDR, EMC1812_R2_SMPL_ADDR),
+	regmap_reg_range(EMC1812_FILTER_SEL_ADDR, EMC1812_FILTER_SEL_ADDR),
+	regmap_reg_range(EMC1812_HOTTEST_CFG_ADDR, EMC1812_HOTTEST_CFG_ADDR),
+};
+
+static const struct regmap_access_table emc1812_regmap_wr_table = {
+	.yes_ranges = emc1812_regmap_writable_ranges,
+	.n_yes_ranges = ARRAY_SIZE(emc1812_regmap_writable_ranges),
+};
+
+static const struct regmap_range emc1812_regmap_rd_ranges[] = {
+	regmap_reg_range(EMC1812_STATUS_ADDR, EMC1812_CONFIG_LO_ADDR),
+	regmap_reg_range(EMC1812_CFG_ADDR, EMC1812_ONE_SHOT_ADDR),
+	regmap_reg_range(EMC1812_EXT1_HIGH_LIMIT_LOW_BYTE_ADDR,
+			 EMC1812_EXT_DIODE_FAULT_STATUS_ADDR),
+	regmap_reg_range(EMC1812_DIODE_FAULT_MASK_ADDR, EMC1812_CONSEC_ALERT_ADDR),
+	regmap_reg_range(EMC1812_EXT1_BETA_CONFIG_ADDR, EMC1812_FILTER_SEL_ADDR),
+	regmap_reg_range(EMC1812_INT_HIGH_BYTE_ADDR, EMC1812_HOTTEST_CFG_ADDR),
+	regmap_reg_range(EMC1812_PRODUCT_ID_ADDR, EMC1812_REVISION_ADDR),
+};
+
+static const struct regmap_access_table emc1812_regmap_rd_table = {
+	.yes_ranges = emc1812_regmap_rd_ranges,
+	.n_yes_ranges = ARRAY_SIZE(emc1812_regmap_rd_ranges),
+};
+
+static bool emc1812_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case EMC1812_STATUS_ADDR:
+	case EMC1812_EXT_DIODE_FAULT_STATUS_ADDR:
+	case EMC1812_DIODE_FAULT_MASK_ADDR:
+	case EMC1812_EXT1_BETA_CONFIG_ADDR:
+	case EMC1812_EXT2_BETA_CONFIG_ADDR:
+	case EMC1812_HIGH_LIMIT_STATUS_ADDR:
+	case EMC1812_LOW_LIMIT_STATUS_ADDR:
+	case EMC1812_THERM_LIMIT_STATUS_ADDR:
+	case EMC1812_ROC_STATUS_ADDR:
+	case EMC1812_PER_MAXTH_1_ADDR:
+	case EMC1812_PER_MAXT1L_ADDR:
+	case EMC1812_PER_MAXTH_2_ADDR:
+	case EMC1812_PER_MAXT2_3L_ADDR:
+	case EMC1812_GBL_MAXT1H_ADDR:
+	case EMC1812_GBL_MAXT1L_ADDR:
+	case EMC1812_GBL_MAXT2H_ADDR:
+	case EMC1812_GBL_MAXT2L_ADDR:
+	case EMC1812_INT_HIGH_BYTE_ADDR:
+	case EMC1812_INT_LOW_BYTE_ADDR:
+	case EMC1812_EXT1_HIGH_BYTE_ADDR:
+	case EMC1812_EXT1_LOW_BYTE_ADDR:
+	case EMC1812_EXT2_HIGH_BYTE_ADDR:
+	case EMC1812_EXT2_LOW_BYTE_ADDR:
+	case EMC1812_EXT3_HIGH_BYTE_ADDR:
+	case EMC1812_EXT3_LOW_BYTE_ADDR:
+	case EMC1812_EXT4_HIGH_BYTE_ADDR:
+	case EMC1812_EXT4_LOW_BYTE_ADDR:
+	case EMC1812_HOTTEST_DIODE_HIGH_BYTE_ADDR:
+	case EMC1812_HOTTEST_DIODE_LOW_BYTE_ADDR:
+	case EMC1812_HOTTEST_STATUS_ADDR:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config emc1812_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.rd_table = &emc1812_regmap_rd_table,
+	.wr_table = &emc1812_regmap_wr_table,
+	.volatile_reg = emc1812_is_volatile_reg,
+	.max_register = EMC1812_REVISION_ADDR,
+	.cache_type = REGCACHE_MAPLE,
+};
+
+static umode_t emc1812_is_visible(const void *_data, enum hwmon_sensor_types type,
+				  u32 attr, int channel)
+{
+	const struct emc1812_data *data = _data;
+
+	switch (type) {
+	case hwmon_temp:
+		/* Don't show channels which are not described into the device tree */
+		if (!(data->active_ch_mask & BIT(channel)))
+			return 0;
+
+		/* Don't show channels which are not physically connected */
+		if (channel >= data->chip->phys_channels)
+			return 0;
+
+		switch (attr) {
+		case hwmon_temp_min:
+		case hwmon_temp_max:
+		case hwmon_temp_crit:
+		case hwmon_temp_crit_hyst:
+			return 0644;
+		case hwmon_temp_crit_alarm:
+		case hwmon_temp_input:
+		case hwmon_temp_fault:
+		case hwmon_temp_max_alarm:
+		case hwmon_temp_max_hyst:
+		case hwmon_temp_min_alarm:
+			return 0444;
+		case hwmon_temp_label:
+			if (data->labels[channel])
+				return 0444;
+			return 0;
+		default:
+			return 0;
+		}
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+			return 0644;
+		default:
+			return 0;
+		}
+	default:
+		return 0;
+	}
+};
+
+static int emc1812_get_temp(struct emc1812_data *data, int channel, long *val)
+{
+	__be16 tmp_be16;
+	int ret;
+
+	ret = regmap_bulk_read(data->regmap, EMC1812_TEMP_CH_ADDR(channel),
+			       &tmp_be16, sizeof(tmp_be16));
+	if (ret)
+		return ret;
+
+	/* Range is always -64 to 191.875°C */
+	*val = ((be16_to_cpu(tmp_be16) >> 5) - (EMC1812_TEMP_OFFSET << 3)) * 125;
+
+	return 0;
+}
+
+static int emc1812_get_crit_limit_temp(struct emc1812_data *data, int channel, long *val)
+{
+	unsigned int tmp;
+	int ret;
+
+	/* Critical register is 8bits long and keeps only integer part of temperature */
+	ret = regmap_read(data->regmap, emc1812_temp_crit_regs[channel], &tmp);
+	if (ret)
+		return ret;
+
+	*val = tmp;
+	/* Range is always -64 to 191°C */
+	*val = (*val - EMC1812_TEMP_OFFSET) * 1000;
+
+	return 0;
+}
+
+static int emc1812_get_limit_temp(struct emc1812_data *data, int ch,
+				  enum emc1812_limit_type type, long *val)
+{
+	unsigned int regvalh;
+	unsigned int regvall = 0;
+	int ret;
+
+	ret = regmap_read(data->regmap, emc1812_limit_regs[ch][type], &regvalh);
+	if (ret < 0)
+		return ret;
+
+	if (ch) {
+		ret = regmap_read(data->regmap, emc1812_limit_regs_low[ch][type], &regvall);
+		if (ret < 0)
+			return ret;
+	}
+
+	/* Range is always -64 to 191.875°C */
+	*val = ((regvalh << 3) | (regvall >> 5));
+	*val = (*val - (EMC1812_TEMP_OFFSET << 3)) * 125;
+
+	return 0;
+}
+
+static int emc1812_read_reg(struct device *dev, struct emc1812_data *data, u32 attr,
+			    int channel, long *val)
+{
+	int hyst, ret;
+
+	switch (attr) {
+	case hwmon_temp_min:
+	case hwmon_temp_max:
+		return emc1812_get_limit_temp(data, channel, emc1812_temp_map[attr], val);
+	case hwmon_temp_crit:
+		return emc1812_get_crit_limit_temp(data, channel, val);
+	case hwmon_temp_input:
+		return emc1812_get_temp(data, channel, val);
+	case hwmon_temp_max_hyst:
+		ret = emc1812_get_limit_temp(data, channel, temp_max, val);
+		if (ret < 0)
+			return ret;
+
+		ret = regmap_read(data->regmap, EMC1812_THRM_HYS_ADDR, &hyst);
+		if (ret < 0)
+			return ret;
+
+		*val -= hyst * 1000;
+
+		return 0;
+	case hwmon_temp_crit_hyst:
+		ret = emc1812_get_crit_limit_temp(data, channel, val);
+		if (ret < 0)
+			return ret;
+
+		ret = regmap_read(data->regmap, EMC1812_THRM_HYS_ADDR, &hyst);
+		if (ret < 0)
+			return ret;
+
+		*val -= hyst * 1000;
+
+		return 0;
+	case hwmon_temp_min_alarm:
+		*val = regmap_test_bits(data->regmap, EMC1812_LOW_LIMIT_STATUS_ADDR,
+					BIT(channel));
+		if (*val < 0)
+			return *val;
+
+		return 0;
+	case hwmon_temp_max_alarm:
+		*val = regmap_test_bits(data->regmap, EMC1812_HIGH_LIMIT_STATUS_ADDR,
+					BIT(channel));
+		if (*val < 0)
+			return *val;
+
+		return 0;
+	case hwmon_temp_crit_alarm:
+		*val = regmap_test_bits(data->regmap, EMC1812_THERM_LIMIT_STATUS_ADDR,
+					BIT(channel));
+		if (*val < 0)
+			return *val;
+
+		return 0;
+	case hwmon_temp_fault:
+		*val = regmap_test_bits(data->regmap, EMC1812_EXT_DIODE_FAULT_STATUS_ADDR,
+					BIT(channel));
+		if (*val < 0)
+			return *val;
+
+		return 0;
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int emc1812_read(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+			int channel, long *val)
+{
+	struct emc1812_data *data = dev_get_drvdata(dev);
+	unsigned int convrate;
+	int ret;
+
+	switch (type) {
+	case hwmon_temp:
+		return emc1812_read_reg(dev, data, attr, channel, val);
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+			ret = regmap_read(data->regmap, EMC1812_CONV_ADDR, &convrate);
+			if (ret < 0)
+				return ret;
+
+			if (convrate > 10)
+				convrate = 4;
+
+			*val = DIV_ROUND_CLOSEST(16000, 1 << convrate);
+			return 0;
+		default:
+			return -EOPNOTSUPP;
+		}
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int emc1812_read_string(struct device *dev, enum hwmon_sensor_types type,
+			       u32 attr, int channel, const char **str)
+{
+	struct emc1812_data *data = dev_get_drvdata(dev);
+
+	if (channel >= data->chip->phys_channels)
+		return 0;
+
+	switch (type) {
+	case hwmon_temp:
+		switch (attr) {
+		case hwmon_temp_label:
+			*str = data->labels[channel];
+			return 0;
+		default:
+			return -EOPNOTSUPP;
+		}
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int emc1812_set_hyst(struct emc1812_data *data, int channel, int val)
+{
+	int hyst, ret;
+	int limit;
+
+	/* Critical register is 8bits long and keeps only integer part of temperature */
+	ret = regmap_read(data->regmap, emc1812_temp_crit_regs[channel], &limit);
+	if (ret)
+		return ret;
+
+	hyst = clamp_val(limit - val, 0, 255);
+
+	ret = regmap_write(data->regmap, EMC1812_THRM_HYS_ADDR, hyst);
+
+	return ret;
+}
+
+static int emc1812_set_temp(struct emc1812_data *data, int channel,
+			    enum emc1812_limit_type map, int val)
+{
+	long valh, vall;
+	u8 regh, regl;
+	int ret;
+
+	regh = emc1812_limit_regs[channel][map];
+	regl = emc1812_limit_regs_low[channel][map];
+
+	if (channel) {
+		val = DIV_ROUND_CLOSEST(val, 125);
+		valh = (val >> 3) & 0xff;
+		vall = (val & 0x07) << 5;
+	} else {
+		/* Temperature limit for internal channel is stored on 8bits */
+		valh = DIV_ROUND_CLOSEST(val, 1000);
+		valh = clamp_val(valh, 0, 255);
+	}
+
+	ret = regmap_write(data->regmap, regh, valh);
+	if (ret < 0)
+		return ret;
+
+	if (channel)
+		ret = regmap_write(data->regmap, regl, vall);
+
+	return ret;
+}
+
+static int emc1812_write(struct device *dev, enum hwmon_sensor_types type, u32 attr,
+			 int channel, long val)
+{
+	struct emc1812_data *data = dev_get_drvdata(dev);
+	unsigned int interval;
+	int convrate;
+
+	switch (type) {
+	case hwmon_temp:
+		/* Range should be -64000 to 191875°C + (EMC1812_TEMP_OFFSET * 1000) */
+		val = clamp_val(val, -64000, 191875);
+		val = val + (EMC1812_TEMP_OFFSET * 1000);
+
+		switch (attr) {
+		case hwmon_temp_min:
+		case hwmon_temp_max:
+			return emc1812_set_temp(data, channel, emc1812_temp_map[attr], val);
+		case hwmon_temp_crit:
+			/* Critical temperature limit is stored on 8bits */
+			val = DIV_ROUND_CLOSEST(val, 1000);
+			val = clamp_val(val, 0, 255);
+			return regmap_write(data->regmap, emc1812_temp_crit_regs[channel], val);
+		case hwmon_temp_crit_hyst:
+			/* Critical temperature hysteresis is stored on 8bits */
+			val = DIV_ROUND_CLOSEST(val, 1000);
+			val = clamp_val(val, 0, 255);
+			return emc1812_set_hyst(data, channel, val);
+		default:
+			return -EOPNOTSUPP;
+		}
+	case hwmon_chip:
+		switch (attr) {
+		case hwmon_chip_update_interval:
+			interval = clamp_val(val, 0, 16000);
+			convrate = find_closest_descending(interval, emc1812_conv_time,
+							   ARRAY_SIZE(emc1812_conv_time));
+			return regmap_write(data->regmap, EMC1812_CONV_ADDR, convrate);
+		default:
+			return -EOPNOTSUPP;
+		}
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int emc1812_init(struct emc1812_data *priv)
+{
+	int ret;
+	u8 val;
+
+	/*
+	 * Set default values in registers. APDD, RECD12 and RECD34 are active
+	 * on 0. Set ALERT pin to be in comparator mode.
+	 * Set the device to be in Run (Active) state and converting on all
+	 * channels.
+	 * Don't change conversion rate. After reset, default is 4 conversions/seconds.
+	 * The temperature measurement range is -64°C to +191.875°C.
+	 */
+	val = FIELD_PREP(EMC1812_CFG_MSKAL, 0) |
+	      FIELD_PREP(EMC1812_CFG_RS, 0) |
+	      FIELD_PREP(EMC1812_CFG_ATTHM, 1) |
+	      FIELD_PREP(EMC1812_CFG_RECD12, !priv->recd12_en) |
+	      FIELD_PREP(EMC1812_CFG_RECD34, !priv->recd34_en) |
+	      FIELD_PREP(EMC1812_CFG_RANGE, 1) |
+	      FIELD_PREP(EMC1812_CFG_DA_ENA, 0) |
+	      FIELD_PREP(EMC1812_CFG_APDD, !priv->apdd_en);
+
+	ret = regmap_write(priv->regmap, EMC1812_CFG_ADDR, val);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->regmap, EMC1812_THRM_HYS_ADDR, 0x0A);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->regmap, EMC1812_CONSEC_ALERT_ADDR, 0x70);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->regmap, EMC1812_FILTER_SEL_ADDR, 0);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(priv->regmap, EMC1812_HOTTEST_CFG_ADDR, 0);
+	if (ret)
+		return ret;
+
+	/* Enables the beta compensation factor auto-detection function for beta1 and beta2 */
+	ret = regmap_write(priv->regmap, EMC1812_EXT1_BETA_CONFIG_ADDR,
+			   EMC1812_BETA_LOCK_VAL);
+	if (ret)
+		return ret;
+
+	if (priv->chip->has_ext2_beta_reg) {
+		ret = regmap_write(priv->regmap, EMC1812_EXT2_BETA_CONFIG_ADDR,
+				   EMC1812_BETA_LOCK_VAL);
+		if (ret)
+			return ret;
+	}
+
+	/* Set ideality factor for all external channels */
+	ret = regmap_write(priv->regmap, EMC1812_EXT1_IDEALITY_FACTOR_ADDR,
+			   EMC1812_DEFAULT_IDEALITY_FACTOR);
+	if (ret)
+		return ret;
+
+	/* Set the ideality factor only for the channels supported by the chip */
+	if (priv->chip->phys_channels < 3)
+		return 0;
+
+	ret = regmap_write(priv->regmap, EMC1812_EXT2_IDEALITY_FACTOR_ADDR,
+			   EMC1812_DEFAULT_IDEALITY_FACTOR);
+	if (ret)
+		return ret;
+
+	if (priv->chip->phys_channels < 4)
+		return 0;
+
+	ret = regmap_write(priv->regmap, EMC1812_EXT3_IDEALITY_FACTOR_ADDR,
+			   EMC1812_DEFAULT_IDEALITY_FACTOR);
+	if (ret)
+		return ret;
+
+	if (priv->chip->phys_channels < 5)
+		return 0;
+
+	return regmap_write(priv->regmap, EMC1812_EXT4_IDEALITY_FACTOR_ADDR,
+			    EMC1812_DEFAULT_IDEALITY_FACTOR);
+}
+
+static int emc1812_parse_fw_config(struct emc1812_data *data, struct device *dev)
+{
+	unsigned int reg_nr = 0;
+	int num_channels, ret;
+
+	/* To be able to load the driver in case we don't have device tree */
+	if (!dev_fwnode(dev)) {
+		data->active_ch_mask = BIT(data->chip->phys_channels) - 1;
+		return 0;
+	}
+
+	data->apdd_en = device_property_read_bool(dev, "microchip,enable-anti-parallel");
+	data->recd12_en = device_property_read_bool(dev, "microchip,parasitic-res-on-channel1-2");
+	data->recd34_en = device_property_read_bool(dev, "microchip,parasitic-res-on-channel3-4");
+
+	num_channels = device_get_child_node_count(dev) + 1;
+
+	if (num_channels > data->chip->phys_channels)
+		return dev_err_probe(dev, -EINVAL, "More channels than the chip supports\n");
+
+	/* Internal temperature channel is always active */
+	data->labels[reg_nr] = "internal_diode";
+	set_bit(reg_nr, &data->active_ch_mask);
+
+	device_for_each_child_node_scoped(dev, child) {
+		ret = fwnode_property_read_u32(child, "reg", &reg_nr);
+		if (ret || reg_nr >= data->chip->phys_channels)
+			return dev_err_probe(dev, -EINVAL,
+				     "The index of the channels does not match the chip\n");
+		/* Mark external channel as active */
+		set_bit(reg_nr, &data->active_ch_mask);
+
+		fwnode_property_read_string(child, "label", &data->labels[reg_nr]);
+	}
+
+	return 0;
+}
+
+static int emc1812_chip_identify(struct emc1812_data *data, struct i2c_client *client)
+{
+	const struct emc1812_features *chip;
+	struct device *dev = &client->dev;
+	int ret, tmp;
+
+	ret = regmap_read(data->regmap, EMC1812_PRODUCT_ID_ADDR, &tmp);
+	if (ret)
+		return ret;
+
+	switch (tmp) {
+	case EMC1812_PID:
+		data->chip = &emc1812_chip_config;
+		break;
+	case EMC1813_PID:
+		data->chip = &emc1813_chip_config;
+		break;
+	case EMC1814_PID:
+		data->chip = &emc1814_chip_config;
+		break;
+	case EMC1815_PID:
+		data->chip = &emc1815_chip_config;
+		break;
+	case EMC1833_PID:
+		data->chip = &emc1833_chip_config;
+		break;
+	default:
+		/*
+		 * If failed to identify the hardware based on internal registers,
+		 * try using fallback compatible in device tree to deal with some
+		 * newer part number.
+		 */
+		chip = i2c_get_match_data(client);
+		if (!chip)
+			return -ENODEV;
+
+		dev_warn(dev, "Unrecognized hardware ID 0x%x, using %s from devicetree data\n",
+			 tmp, chip->name);
+
+		data->chip = chip;
+
+		return 0;
+	}
+
+	return 0;
+}
+
+static const struct hwmon_ops emc1812_ops = {
+	.is_visible = emc1812_is_visible,
+	.read = emc1812_read,
+	.read_string = emc1812_read_string,
+	.write = emc1812_write,
+};
+
+static const struct hwmon_chip_info emc1812_chip_info = {
+	.ops = &emc1812_ops,
+	.info = emc1812_info,
+};
+
+static int emc1812_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct emc1812_data *data;
+	struct device *hwmon_dev;
+	int ret;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->regmap = devm_regmap_init_i2c(client, &emc1812_regmap_config);
+	if (IS_ERR(data->regmap))
+		return dev_err_probe(dev, PTR_ERR(data->regmap),
+				     "Cannot initialize register map\n");
+
+	ret = emc1812_chip_identify(data, client);
+	if (ret)
+		return dev_err_probe(dev, ret, "Chip identification fails\n");
+
+	ret = emc1812_parse_fw_config(data, dev);
+	if (ret)
+		return ret;
+
+	ret = emc1812_init(data);
+	if (ret)
+		return dev_err_probe(dev, ret, "Cannot initialize device\n");
+
+	hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
+							 &emc1812_chip_info, NULL);
+
+	return PTR_ERR_OR_ZERO(hwmon_dev);
+}
+
+static const struct i2c_device_id emc1812_id[] = {
+	{ .name = "emc1812", .driver_data = (kernel_ulong_t)&emc1812_chip_config },
+	{ .name = "emc1813", .driver_data = (kernel_ulong_t)&emc1813_chip_config },
+	{ .name = "emc1814", .driver_data = (kernel_ulong_t)&emc1814_chip_config },
+	{ .name = "emc1815", .driver_data = (kernel_ulong_t)&emc1815_chip_config },
+	{ .name = "emc1833", .driver_data = (kernel_ulong_t)&emc1833_chip_config },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, emc1812_id);
+
+static const struct of_device_id emc1812_of_match[] = {
+	{
+		.compatible = "microchip,emc1812",
+		.data = &emc1812_chip_config
+	},
+	{
+		.compatible = "microchip,emc1813",
+		.data = &emc1813_chip_config
+	},
+	{
+		.compatible = "microchip,emc1814",
+		.data = &emc1814_chip_config
+	},
+	{
+		.compatible = "microchip,emc1815",
+		.data = &emc1815_chip_config
+	},
+	{
+		.compatible = "microchip,emc1833",
+		.data = &emc1833_chip_config
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, emc1812_of_match);
+
+static struct i2c_driver emc1812_driver = {
+	.driver	 = {
+		.name = "emc1812",
+		.of_match_table = emc1812_of_match,
+	},
+	.probe = emc1812_probe,
+	.id_table = emc1812_id,
+};
+module_i2c_driver(emc1812_driver);
+
+MODULE_AUTHOR("Marius Cristea <marius.cristea@microchip.com>");
+MODULE_DESCRIPTION("EMC1812/13/14/15/33 high-accuracy remote diode temperature monitor Driver");
+MODULE_LICENSE("GPL");

-- 
2.51.0


^ permalink raw reply related

* [PATCH v9 1/2] dt-bindings: hwmon: temperature: add support for EMC1812
From: Marius Cristea @ 2026-04-03 12:39 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, Marius Cristea,
	Conor Dooley
In-Reply-To: <20260403-hw_mon-emc1812-v9-0-1a798f31cf2e@microchip.com>

This is the devicetree schema for Microchip EMC1812/13/14/15/33
Multichannel Low-Voltage Remote Diode Sensor Family.

EMC1812 has one external remote temperature monitoring channel.
EMC1813 has two external remote temperature monitoring channels.
EMC1814 has three external remote temperature monitoring channels and
channels 2 and 3 support anti parallel diode.
EMC1815 has four external remote temperature monitoring channels and
channels 1/2  and 3/4 support anti parallel diode.
EMC1833 has two external remote temperature monitoring channels and
channels 1 and 2 support anti parallel diode.

Signed-off-by: Marius Cristea <marius.cristea@microchip.com>
Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
---
 .../bindings/hwmon/microchip,emc1812.yaml          | 184 +++++++++++++++++++++
 MAINTAINERS                                        |   6 +
 2 files changed, 190 insertions(+)

diff --git a/Documentation/devicetree/bindings/hwmon/microchip,emc1812.yaml b/Documentation/devicetree/bindings/hwmon/microchip,emc1812.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..5f766a3b6a297cdc9bdfff2215fce6fa41434f0a
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/microchip,emc1812.yaml
@@ -0,0 +1,184 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/hwmon/microchip,emc1812.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip EMC1812/13/14/15/33 multichannel temperature sensor
+
+maintainers:
+  - Marius Cristea <marius.cristea@microchip.com>
+
+description: |
+  The Microchip EMC1812/13/14/15/33 is a high-accuracy 2-wire multichannel
+  low-voltage remote diode temperature monitor.
+
+  The datasheet can be found here:
+    https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/EMC1812-3-4-5-33-Data-Sheet-DS20005751.pdf
+
+  EMC1812 has one external remote temperature monitoring channel
+  EMC1813 has two external remote temperature monitoring channels
+  EMC1814 has three external remote temperature monitoring channels and
+    channels 2 and 3 support anti parallel diode
+  EMC1815 has four external remote temperature monitoring channels and
+    channels 1/2 and 3/4 support anti parallel diode
+  EMC1833 has two external remote temperature monitoring channels and
+    channels 1 and 2 support anti parallel diode
+
+properties:
+  compatible:
+    enum:
+      - microchip,emc1812
+      - microchip,emc1813
+      - microchip,emc1814
+      - microchip,emc1815
+      - microchip,emc1833
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    items:
+      - description: alert-therm2 asserts when the ALERT limit is exceeded.
+      - description: therm-addr asserts when the THERM limit is exceeded.
+    minItems: 1
+
+  interrupt-names:
+    items:
+      enum: [alert-therm2, therm-addr]
+    minItems: 1
+    maxItems: 2
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+  microchip,enable-anti-parallel:
+    description:
+      Enable anti-parallel diode mode operation. EMC1814, EMC1815 and EMC1833
+      support reading two external diodes in anti-parallel connection on the
+      same set of pins. Disabling APD functionality to implement substrate
+      diodes on devices that support APD eliminates the benefit of APD
+      (two diodes on one channel).
+    type: boolean
+
+  microchip,parasitic-res-on-channel1-2:
+    description:
+      Indicates that the chip and the diodes/transistors are sufficiently
+      far apart that a parasitic resistance is added to the wires, which can
+      affect the measurements. Due to the anti-parallel diode connections,
+      channels 1 and 2 are affected together.
+    type: boolean
+
+  microchip,parasitic-res-on-channel3-4:
+    description:
+      Indicates that the chip and the diodes/transistors are sufficiently far
+      apart that a parasitic resistance is added to the wires, which can affect
+      the measurements. Due to the anti-parallel diode connections, channels
+      3 and 4 are affected together.
+    type: boolean
+
+  vdd-supply: true
+
+patternProperties:
+  "^channel@[1-4]$":
+    description:
+      Represents the external temperature channels to which
+      a remote diode is connected.
+    type: object
+
+    properties:
+      reg:
+        maxItems: 1
+
+      label:
+        description: Unique name to identify which channel this is.
+
+    required:
+      - reg
+
+    additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - vdd-supply
+
+allOf:
+  # EMC1812: 1 Channel, No APD, REC on channel 1
+  - if:
+      properties:
+        compatible:
+          const: microchip,emc1812
+    then:
+      properties:
+        microchip,enable-anti-parallel: false
+        microchip,parasitic-res-on-channel3-4: false
+      patternProperties:
+        "^channel@[2-4]$": false
+
+  # EMC1813: 2 Channels, No APD, REC on both channel 1 & 2
+  - if:
+      properties:
+        compatible:
+          const: microchip,emc1813
+    then:
+      properties:
+        microchip,enable-anti-parallel: false
+        microchip,parasitic-res-on-channel3-4: false
+      patternProperties:
+        "^channel@[3-4]$": false
+
+  # EMC1833: 2 Channels, Supports APD, REC on both channel 1 & 2
+  - if:
+      properties:
+        compatible:
+          const: microchip,emc1833
+    then:
+      properties:
+        microchip,parasitic-res-on-channel3-4: false
+      patternProperties:
+        "^channel@[3-4]$": false
+
+  # EMC1814: 3 Channels, Supports APD,
+  # REC on both channel 1 & 2 and channel 3
+  - if:
+      properties:
+        compatible:
+          const: microchip,emc1814
+    then:
+      properties:
+        channel@4: false
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        temperature-sensor@4c {
+            compatible = "microchip,emc1813";
+            reg = <0x4c>;
+
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            microchip,parasitic-res-on-channel1-2;
+
+            vdd-supply = <&vdd>;
+
+            channel@1 {
+                reg = <1>;
+                label = "External CH1 Temperature";
+            };
+
+            channel@2 {
+                reg = <2>;
+                label = "External CH2 Temperature";
+            };
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 6d7b697bfdba16e4f0ee5f4f0195b9d7da06dae5..85c236df781e47c78deeb7ef4d80bc94bba604c4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16646,6 +16646,12 @@ S:	Supported
 F:	Documentation/devicetree/bindings/interrupt-controller/microchip,sama7g5-eic.yaml
 F:	drivers/irqchip/irq-mchp-eic.c
 
+MICROCHIP EMC1812 DRIVER
+M:	Marius Cristea <marius.cristea@microchip.com>
+L:	linux-hwmon@vger.kernel.org
+S:	Supported
+F:	Documentation/devicetree/bindings/hwmon/microchip,emc1812.yaml
+
 MICROCHIP I2C DRIVER
 M:	Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
 L:	linux-i2c@vger.kernel.org

-- 
2.51.0


^ permalink raw reply related

* Re: [PATCH v3] doc: Add CPU Isolation documentation
From: Frederic Weisbecker @ 2026-04-03 12:06 UTC (permalink / raw)
  To: Sebastian Andrzej Siewior
  Cc: LKML, Anna-Maria Behnsen, Gabriele Monaco, Ingo Molnar,
	Jonathan Corbet, Marcelo Tosatti, Marco Crivellari, Michal Hocko,
	Paul E . McKenney, Peter Zijlstra, Phil Auld, Steven Rostedt,
	Thomas Gleixner, Valentin Schneider, Vlastimil Babka, Waiman Long,
	linux-doc, Bagas Sanjaya
In-Reply-To: <20260402110122.2gkDqQ7Q@linutronix.de>

Le Thu, Apr 02, 2026 at 01:01:22PM +0200, Sebastian Andrzej Siewior a écrit :
> On 2026-04-02 11:47:49 [+0200], Frederic Weisbecker wrote:
> > nohz_full was introduced in v3.10 in 2013, which means this
> > documentation is overdue for 13 years.
> > 
> > Fortunately Paul wrote a part of the needed documentation a while ago,
> > especially concerning nohz_full in Documentation/timers/no_hz.rst and
> > also about per-CPU kthreads in
> > Documentation/admin-guide/kernel-per-CPU-kthreads.rst
> > 
> > Introduce a new page that gives an overview of CPU isolation in general.
> > 
> > Acked-by: Waiman Long <longman@redhat.com>
> > Reviewed-by: Valentin Schneider <vschneid@redhat.com>
> > Reviewed-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
> > Signed-off-by: Frederic Weisbecker <frederic@kernel.org>
> 
> This documents also isolcpus= boot argument. The only thing that this
> argument does and runtime can not do is the managed_irq sub argument.
> This sub argument is a story of its own and it is of quite limited for
> me taste.
> 
> However, isolcpus= is marked as deprecated. I suggest to remove the
> "Deprecated - use cpusets instead" note as the static configuration is
> fine if the system is partitioned once never changed within its
> lifetime.
> Are there any objections and if so why needs this boot argument be
> removed (assuming we have a runtime equivalent knob for managed_irq)?

No objections! Thanks.

> 
> Sebastian

-- 
Frederic Weisbecker
SUSE Labs

^ permalink raw reply

* Re: [PATCH v2 0/3] Documentation: clarify required info in security reports
From: Willy Tarreau @ 2026-04-03 11:51 UTC (permalink / raw)
  To: Greg KH
  Cc: edumazet, rdunlap, Jonathan Corbet, skhan, workflows, linux-doc,
	linux-kernel
In-Reply-To: <2026040324-coping-vacation-7d64@gregkh>

On Fri, Apr 03, 2026 at 01:11:47PM +0200, Greg KH wrote:
> On Fri, Apr 03, 2026 at 08:20:15AM +0200, Willy Tarreau wrote:
> > Hi Greg,
> > 
> > I'm sending you the doc clarifications we discussed for the process of
> > reporting security issues. It's cut into the 3 patches I shared this
> > morning on the security list (plus two typos fixed and a paragraph
> > asking for one single issue per report):
> > 
> >   - one patch that reminds our need for a valid e-mail address
> >   - one that explains to reporters how to proceed to find maintainers
> >     addresses, hoping we won't have to do it for 90% of reports anymore
> >   - one that enumerates basic requirements for every report
> > 
> > I think it covers the difficulties we've faced this week. As always,
> > we might possibly find tiny adjustments to add, but my goal would be
> > for such updates to be merged in time to update the public page ASAP
> > so that we can redirect incomplete reports in an attempt to lower the
> > team's current load.
> 
> Looks great, thanks.  I've applied these to one of my trees and will get
> them to Linus in time for 7.0-final.

Thank you!
Willy

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox