* Re: [PATCH v11 1/2] dt-bindings: hwmon: temperature: add support for EMC1812
From: sashiko-bot @ 2026-06-10 15:24 UTC (permalink / raw)
To: Marius Cristea; +Cc: robh, linux-hwmon, devicetree, conor+dt
In-Reply-To: <20260610-hw_mon-emc1812-v11-1-cef809af5c19@microchip.com>
> This is the devicetree schema for Microchip EMC1812/13/14/15/33
> Multichannel Low-Voltage Remote Diode Sensor Family. It also
> updates the MAINTAINERS file to include the new driver.
>
> 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.
> Resistance Error Correction is supported on channels 1/2 and 3/4.
>
> Signed-off-by: Marius Cristea <marius.cristea@microchip.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260610-hw_mon-emc1812-v11-0-cef809af5c19@microchip.com?part=1
^ permalink raw reply
* [PATCH v5 phy-next 16/16] MAINTAINERS: expand Lynx 28G entry to cover Lynx 10G SerDes
From: Vladimir Oltean @ 2026-06-10 15:19 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel, devicetree, Conor Dooley, Krzysztof Kozlowski,
Rob Herring
In-Reply-To: <20260610151952.2141019-1-vladimir.oltean@nxp.com>
The lynx-28g and lynx-10g drivers share code and hardware architecture,
so let them be covered by a single MAINTAINERS entry.
Add myself as a second maintainer alongside Ioana Ciornei.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
Cc: devicetree@vger.kernel.org
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Rob Herring <robh@kernel.org>
v1->v5: none
---
MAINTAINERS | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index a727d4920fae..ecc2f4a0baed 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15415,12 +15415,18 @@ S: Maintained
F: Documentation/devicetree/bindings/iio/light/liteon,ltr390.yaml
F: drivers/iio/light/ltr390.c
-LYNX 28G SERDES PHY DRIVER
+LYNX SERDES PHY DRIVERS
M: Ioana Ciornei <ioana.ciornei@nxp.com>
+M: Vladimir Oltean <vladimir.oltean@nxp.com>
L: netdev@vger.kernel.org
S: Supported
+F: Documentation/devicetree/bindings/phy/fsl,lynx-10g.yaml
F: Documentation/devicetree/bindings/phy/fsl,lynx-28g.yaml
+F: drivers/phy/freescale/phy-fsl-lynx-10g.c
F: drivers/phy/freescale/phy-fsl-lynx-28g.c
+F: drivers/phy/freescale/phy-fsl-lynx-core.c
+F: drivers/phy/freescale/phy-fsl-lynx-core.h
+F: include/soc/fsl/phy-fsl-lynx.h
LYNX PCS MODULE
M: Ioana Ciornei <ioana.ciornei@nxp.com>
--
2.34.1
^ permalink raw reply related
* [PATCH v5 phy-next 15/16] phy: lynx-10g: new driver
From: Vladimir Oltean @ 2026-06-10 15:19 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel, devicetree, Conor Dooley, Krzysztof Kozlowski,
Rob Herring, linux-arm-kernel, chleroy, linuxppc-dev
In-Reply-To: <20260610151952.2141019-1-vladimir.oltean@nxp.com>
Introduce a driver for the networking lanes of the 10G Lynx SerDes
block, present on the majority of Layerscape and QorIQ (Freescale/NXP)
SoCs.
As with the 28G Lynx, the SerDes lanes come pre-initialized out of
reset and the consumers use them that way outside the Generic PHY
framework (for networking, the static configuration remains for the
entire SoC lifetime, whereas for SATA and PCIe, the hardware
reconfigures itself automatically for other link speeds).
The need for the Generic PHY framework comes specifically for networking
use cases where a static lane configuration is not sufficient. For
example a network MAC is connected to an SFP cage, where various SFP or
SFP+ modules can be connected. Each of them may require a different
SerDes protocol (SGMII, 1000Base-X, 10GBase-R), which phylink + sfp-bus
are responsible of figuring out. The phylink drivers are:
- enetc
- felix
- dpaa_eth (fman_memac)
- dpaa2-eth
- dpaa2-switch
and they all need to reconfigure the SerDes for the requested link mode,
using phy_set_mode_ext() (and phy_validate() to see if it is supported
in the first place).
Note that SerDes 2 on LS1088A is exclusively non-networking, so there is
currently no need for this driver. Therefore we skip matching on its
compatible string and do not probe on that device.
Co-developed-by: Ioana Ciornei <ioana.ciornei@nxp.com>
Signed-off-by: Ioana Ciornei <ioana.ciornei@nxp.com>
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
---
Cc: devicetree@vger.kernel.org
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Rob Herring <robh@kernel.org>
Cc: linux-arm-kernel@lists.infradead.org
Cc: chleroy@kernel.org
Cc: linuxppc-dev@lists.ozlabs.org
v4->v5:
- add comments around default_pccr so that Sashiko understands what is
the idea and why the code is correct (and the assumptions being made)
- replace testing of the non-zero quality of lane->default_pccr[mode]
with the lynx_10g_pccr_val_enabled() helper which ignores the KX bit.
A lane starting with PCCR8_SGMIIa_KX=1 and PCCR8_SGMIIa_CFG=0 is
disabled, not enabled (although this is unusual and never a hardware
reset value).
- express PCCR8_SGMIIa_CFG(), PCCR9_QSGMIIa_CFG(), PCCR9_QXGMIIa_CFG(),
PCCRB_XFIa_CFG(), PCCRB_SXGMIIa_CFG() using GENMASK(2, 0) to make it
clear that the field width is not 1
v3->v4:
- none
v2->v3:
- fix lynx_10g_power_on() procedure
- include <linux/of.h> instead of <linux/of_device.h>
- fix build warning introduced in v2 in lynx_10g_lane_set_nrate()
v1->v2:
- move lynx_lane_restrict_fixed_mode_change() to lynx-core, even though
the 28G Lynx as instantiated in LX2 does not have QSGMII.
- lynx_10g_validate() now calls the new lynx_phy_mode_to_lane_mode()
which does verify that the current lane mode is supported
- avoid line size checkpatch warnings in lynx_10g_lane_set_nrate() by
saving the nrate to a variable and calling lynx_lane_rmw() only once
- remove redundant "if (!lane->powered_up)" checks from
lynx_10g_lane_halt() and lynx_10g_lane_reset() - also checked at
the only call site, lynx_10g_set_mode(), as in lynx-28g
- expand CC list (flagged by Patchwork)
---
drivers/phy/freescale/Kconfig | 10 +
drivers/phy/freescale/Makefile | 1 +
drivers/phy/freescale/phy-fsl-lynx-10g.c | 1321 +++++++++++++++++++++
drivers/phy/freescale/phy-fsl-lynx-core.c | 38 +
drivers/phy/freescale/phy-fsl-lynx-core.h | 4 +
include/soc/fsl/phy-fsl-lynx.h | 27 +
6 files changed, 1401 insertions(+)
create mode 100644 drivers/phy/freescale/phy-fsl-lynx-10g.c
diff --git a/drivers/phy/freescale/Kconfig b/drivers/phy/freescale/Kconfig
index ac575d531db7..5bf3864fbe64 100644
--- a/drivers/phy/freescale/Kconfig
+++ b/drivers/phy/freescale/Kconfig
@@ -54,6 +54,16 @@ endif
config PHY_FSL_LYNX_CORE
tristate
+config PHY_FSL_LYNX_10G
+ tristate "Freescale Layerscape Lynx 10G SerDes PHY support"
+ depends on OF
+ depends on ARCH_LAYERSCAPE || COMPILE_TEST
+ select GENERIC_PHY
+ select PHY_FSL_LYNX_CORE
+ help
+ Enable this to add support for the Lynx 10G SerDes PHY as found on
+ NXP's Layerscape platform such as LS1088A or LS1028A.
+
config PHY_FSL_LYNX_28G
tristate "Freescale Layerscape Lynx 28G SerDes PHY support"
depends on OF
diff --git a/drivers/phy/freescale/Makefile b/drivers/phy/freescale/Makefile
index d7aa62cdeb39..5b0e180d6972 100644
--- a/drivers/phy/freescale/Makefile
+++ b/drivers/phy/freescale/Makefile
@@ -5,5 +5,6 @@ obj-$(CONFIG_PHY_MIXEL_MIPI_DPHY) += phy-fsl-imx8-mipi-dphy.o
obj-$(CONFIG_PHY_FSL_IMX8M_PCIE) += phy-fsl-imx8m-pcie.o
obj-$(CONFIG_PHY_FSL_IMX8QM_HSIO) += phy-fsl-imx8qm-hsio.o
obj-$(CONFIG_PHY_FSL_LYNX_CORE) += phy-fsl-lynx-core.o
+obj-$(CONFIG_PHY_FSL_LYNX_10G) += phy-fsl-lynx-10g.o
obj-$(CONFIG_PHY_FSL_LYNX_28G) += phy-fsl-lynx-28g.o
obj-$(CONFIG_PHY_FSL_SAMSUNG_HDMI_PHY) += phy-fsl-samsung-hdmi.o
diff --git a/drivers/phy/freescale/phy-fsl-lynx-10g.c b/drivers/phy/freescale/phy-fsl-lynx-10g.c
new file mode 100644
index 000000000000..38def160ef1a
--- /dev/null
+++ b/drivers/phy/freescale/phy-fsl-lynx-10g.c
@@ -0,0 +1,1321 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright 2021-2026 NXP */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+
+#include "phy-fsl-lynx-core.h"
+
+/* SoC IP wrapper for protocol converters */
+#define PCCR8 0x220
+#define PCCR8_SGMIIa_KX BIT(3)
+#define PCCR8_SGMIIa_CFG GENMASK(2, 0)
+
+#define PCCR9 0x224
+#define PCCR9_QSGMIIa_CFG GENMASK(2, 0)
+#define PCCR9_QXGMIIa_CFG GENMASK(2, 0)
+
+#define PCCRB 0x22c
+#define PCCRB_XFIa_CFG GENMASK(2, 0)
+#define PCCRB_SXGMIIa_CFG GENMASK(2, 0)
+
+#define SGMII_CFG(id) (28 - (id) * 4)
+#define QSGMII_CFG(id) (28 - (id) * 4)
+#define SXGMII_CFG(id) (28 - (id) * 4)
+#define QXGMII_CFG(id) (12 - (id) * 4)
+#define XFI_CFG(id) (28 - (id) * 4)
+
+#define CR(x) ((x) * 4)
+
+#define A 0
+#define B 1
+#define C 2
+#define D 3
+#define E 4
+#define F 5
+#define G 6
+#define H 7
+
+#define SGMIIaCR0(id) (0x1800 + (id) * 0x10)
+#define QSGMIIaCR0(id) (0x1880 + (id) * 0x10)
+#define XAUIaCR0(id) (0x1900 + (id) * 0x10)
+#define XFIaCR0(id) (0x1980 + (id) * 0x10)
+#define SXGMIIaCR0(id) (0x1a80 + (id) * 0x10)
+#define QXGMIIaCR0(id) (0x1b00 + (id) * 0x20)
+
+#define SGMIIaCR0_RST_SGM BIT(31)
+#define SGMIIaCR0_RST_SGM_OFF SGMIIaCR0_RST_SGM
+#define SGMIIaCR0_RST_SGM_ON 0
+#define SGMIIaCR0_PD_SGM BIT(30)
+#define SGMIIaCR1_SGPCS_EN BIT(11)
+#define SGMIIaCR1_SGPCS_DIS 0x0
+
+#define QSGMIIaCR0_RST_QSGM BIT(31)
+#define QSGMIIaCR0_RST_QSGM_OFF QSGMIIaCR0_RST_QSGM
+#define QSGMIIaCR0_RST_QSGM_ON 0
+#define QSGMIIaCR0_PD_QSGM BIT(30)
+
+/* Per PLL registers */
+#define PLLnCR0(pll) ((pll) * 0x20 + 0x4)
+
+#define PLLnCR0_POFF BIT(31)
+
+#define PLLnCR0_REFCLK_SEL GENMASK(30, 28)
+#define PLLnCR0_REFCLK_SEL_100MHZ 0x0
+#define PLLnCR0_REFCLK_SEL_125MHZ 0x1
+#define PLLnCR0_REFCLK_SEL_156MHZ 0x2
+#define PLLnCR0_REFCLK_SEL_150MHZ 0x3
+#define PLLnCR0_REFCLK_SEL_161MHZ 0x4
+#define PLLnCR0_PLL_LCK BIT(23)
+#define PLLnCR0_FRATE_SEL GENMASK(19, 16)
+#define PLLnCR0_FRATE_5G 0x0
+#define PLLnCR0_FRATE_5_15625G 0x6
+#define PLLnCR0_FRATE_4G 0x7
+#define PLLnCR0_FRATE_3_125G 0x9
+#define PLLnCR0_FRATE_3G 0xa
+
+/* Per SerDes lane registers */
+
+/* Lane a Protocol Select status register */
+#define LNaPSSR0(lane) (0x100 + (lane) * 0x20)
+#define LNaPSSR0_TYPE GENMASK(30, 26)
+#define LNaPSSR0_IS_QUAD GENMASK(25, 24)
+#define LNaPSSR0_MAC GENMASK(19, 16)
+#define LNaPSSR0_PCS GENMASK(10, 8)
+#define LNaPSSR0_LANE GENMASK(2, 0)
+
+/* Lane a General Control Register */
+#define LNaGCR0(lane) (0x800 + (lane) * 0x40 + 0x0)
+#define LNaGCR0_RPLL_PLLF BIT(31)
+#define LNaGCR0_RPLL_PLLS 0x0
+#define LNaGCR0_RPLL_MSK BIT(31)
+#define LNaGCR0_RRAT_SEL GENMASK(29, 28)
+#define LNaGCR0_TRAT_SEL GENMASK(25, 24)
+#define LNaGCR0_TPLL_PLLF BIT(27)
+#define LNaGCR0_TPLL_PLLS 0x0
+#define LNaGCR0_TPLL_MSK BIT(27)
+#define LNaGCR0_RRST_OFF LNaGCR0_RRST
+#define LNaGCR0_TRST_OFF LNaGCR0_TRST
+#define LNaGCR0_RRST_ON 0x0
+#define LNaGCR0_TRST_ON 0x0
+#define LNaGCR0_RRST BIT(22)
+#define LNaGCR0_TRST BIT(21)
+#define LNaGCR0_RX_PD BIT(20)
+#define LNaGCR0_TX_PD BIT(19)
+#define LNaGCR0_IF20BIT_EN BIT(18)
+#define LNaGCR0_PROTS GENMASK(11, 7)
+
+#define LNaGCR1(lane) (0x800 + (lane) * 0x40 + 0x4)
+#define LNaGCR1_RDAT_INV BIT(31)
+#define LNaGCR1_TDAT_INV BIT(30)
+#define LNaGCR1_OPAD_CTL BIT(26)
+#define LNaGCR1_REIDL_TH GENMASK(22, 20)
+#define LNaGCR1_REIDL_EX_SEL GENMASK(19, 18)
+#define LNaGCR1_REIDL_ET_SEL GENMASK(17, 16)
+#define LNaGCR1_REIDL_EX_MSB BIT(15)
+#define LNaGCR1_REIDL_ET_MSB BIT(14)
+#define LNaGCR1_REQ_CTL_SNP BIT(13)
+#define LNaGCR1_REQ_CDR_SNP BIT(12)
+#define LNaGCR1_TRSTDIR BIT(7)
+#define LNaGCR1_REQ_BIN_SNP BIT(6)
+#define LNaGCR1_ISLEW_RCTL GENMASK(5, 4)
+#define LNaGCR1_OSLEW_RCTL GENMASK(1, 0)
+
+#define LNaRECR0(lane) (0x800 + (lane) * 0x40 + 0x10)
+#define LNaRECR0_RXEQ_BST BIT(28)
+#define LNaRECR0_GK2OVD GENMASK(27, 24)
+#define LNaRECR0_GK3OVD GENMASK(19, 16)
+#define LNaRECR0_GK2OVD_EN BIT(15)
+#define LNaRECR0_GK3OVD_EN BIT(14)
+#define LNaRECR0_OSETOVD_EN BIT(13)
+#define LNaRECR0_BASE_WAND GENMASK(11, 10)
+#define LNaRECR0_OSETOVD GENMASK(6, 0)
+
+#define LNaTECR0(lane) (0x800 + (lane) * 0x40 + 0x18)
+#define LNaTECR0_TEQ_TYPE GENMASK(29, 28)
+#define LNaTECR0_SGN_PREQ BIT(26)
+#define LNaTECR0_RATIO_PREQ GENMASK(25, 22)
+#define LNaTECR0_SGN_POST1Q BIT(21)
+#define LNaTECR0_RATIO_PST1Q GENMASK(20, 16)
+#define LNaTECR0_ADPT_EQ GENMASK(13, 8)
+#define LNaTECR0_AMP_RED GENMASK(5, 0)
+
+#define LNaTTLCR0(lane) (0x800 + (lane) * 0x40 + 0x20)
+#define LNaTTLCR1(lane) (0x800 + (lane) * 0x40 + 0x24)
+#define LNaTTLCR2(lane) (0x800 + (lane) * 0x40 + 0x28)
+
+#define LNaTCSR3(lane) (0x800 + (lane) * 0x40 + 0x3C)
+#define LNaTCSR3_CDR_LCK BIT(27)
+
+enum lynx_10g_rat_sel {
+ RAT_SEL_FULL = 0x0,
+ RAT_SEL_HALF = 0x1,
+ RAT_SEL_QUARTER = 0x2,
+ RAT_SEL_DOUBLE = 0x3,
+};
+
+enum lynx_10g_eq_type {
+ EQ_TYPE_NO_EQ = 0,
+ EQ_TYPE_2TAP = 1,
+ EQ_TYPE_3TAP = 2,
+};
+
+enum lynx_10g_proto_sel {
+ PROTO_SEL_PCIE = 0,
+ PROTO_SEL_SGMII_BASEX_KX_QSGMII = 1,
+ PROTO_SEL_SATA = 2,
+ PROTO_SEL_XAUI = 4,
+ PROTO_SEL_XFI_10GBASER_KR_SXGMII = 0xa,
+};
+
+struct lynx_10g_proto_conf {
+ int proto_sel;
+ int if20bit_en;
+ int reidl_th;
+ int reidl_et_msb;
+ int reidl_et_sel;
+ int reidl_ex_msb;
+ int reidl_ex_sel;
+ int islew_rctl;
+ int oslew_rctl;
+ int rxeq_bst;
+ int gk2ovd;
+ int gk3ovd;
+ int gk2ovd_en;
+ int gk3ovd_en;
+ int base_wand;
+ int teq_type;
+ int sgn_preq;
+ int ratio_preq;
+ int sgn_post1q;
+ int ratio_post1q;
+ int adpt_eq;
+ int amp_red;
+ int ttlcr0;
+};
+
+static const struct lynx_10g_proto_conf lynx_10g_proto_conf[LANE_MODE_MAX] = {
+ [LANE_MODE_1000BASEX_SGMII] = {
+ .proto_sel = PROTO_SEL_SGMII_BASEX_KX_QSGMII,
+ .reidl_th = 1,
+ .reidl_ex_sel = 3,
+ .reidl_et_msb = 1,
+ .islew_rctl = 1,
+ .oslew_rctl = 1,
+ .gk2ovd = 15,
+ .gk3ovd = 15,
+ .gk2ovd_en = 1,
+ .gk3ovd_en = 1,
+ .teq_type = EQ_TYPE_NO_EQ,
+ .adpt_eq = 48,
+ .amp_red = 6,
+ .ttlcr0 = 0x39000400,
+ },
+ [LANE_MODE_2500BASEX] = {
+ .proto_sel = PROTO_SEL_SGMII_BASEX_KX_QSGMII,
+ .islew_rctl = 2,
+ .oslew_rctl = 2,
+ .teq_type = EQ_TYPE_2TAP,
+ .sgn_post1q = 1,
+ .ratio_post1q = 6,
+ .adpt_eq = 48,
+ .ttlcr0 = 0x00000400,
+ },
+ [LANE_MODE_QSGMII] = {
+ .proto_sel = PROTO_SEL_SGMII_BASEX_KX_QSGMII,
+ .islew_rctl = 1,
+ .oslew_rctl = 1,
+ .teq_type = EQ_TYPE_2TAP,
+ .sgn_post1q = 1,
+ .ratio_post1q = 6,
+ .adpt_eq = 48,
+ .amp_red = 2,
+ .ttlcr0 = 0x00000400,
+ },
+ [LANE_MODE_10G_QXGMII] = {
+ .proto_sel = PROTO_SEL_XFI_10GBASER_KR_SXGMII,
+ .if20bit_en = 1,
+ .islew_rctl = 1,
+ .oslew_rctl = 1,
+ .base_wand = 1,
+ .teq_type = EQ_TYPE_NO_EQ,
+ .adpt_eq = 48,
+ .ttlcr0 = 0x00000400,
+ },
+ [LANE_MODE_USXGMII] = {
+ .proto_sel = PROTO_SEL_XFI_10GBASER_KR_SXGMII,
+ .if20bit_en = 1,
+ .islew_rctl = 1,
+ .oslew_rctl = 1,
+ .base_wand = 1,
+ .teq_type = EQ_TYPE_NO_EQ,
+ .sgn_post1q = 1,
+ .adpt_eq = 48,
+ .ttlcr0 = 0x00000400,
+ },
+ [LANE_MODE_10GBASER] = {
+ .proto_sel = PROTO_SEL_XFI_10GBASER_KR_SXGMII,
+ .if20bit_en = 1,
+ .islew_rctl = 2,
+ .oslew_rctl = 2,
+ .rxeq_bst = 1,
+ .base_wand = 1,
+ .teq_type = EQ_TYPE_2TAP,
+ .sgn_post1q = 1,
+ .ratio_post1q = 3,
+ .adpt_eq = 48,
+ .amp_red = 7,
+ .ttlcr0 = 0x00000400,
+ },
+};
+
+static void lynx_10g_cdr_lock_check(struct lynx_lane *lane)
+{
+ u32 tcsr3 = lynx_lane_read(lane, LNaTCSR3);
+
+ if (tcsr3 & LNaTCSR3_CDR_LCK)
+ return;
+
+ dev_dbg(&lane->phy->dev,
+ "Lane %c CDR unlocked, resetting receiver...\n",
+ 'A' + lane->id);
+
+ lynx_lane_rmw(lane, LNaGCR0, LNaGCR0_RRST_ON, LNaGCR0_RRST);
+ usleep_range(1, 2);
+ lynx_lane_rmw(lane, LNaGCR0, LNaGCR0_RRST_OFF, LNaGCR0_RRST);
+
+ usleep_range(1, 2);
+}
+
+static void lynx_10g_pll_read_configuration(struct lynx_pll *pll)
+{
+ u32 val;
+
+ val = lynx_pll_read(pll, PLLnCR0);
+ pll->frate_sel = FIELD_GET(PLLnCR0_FRATE_SEL, val);
+ pll->refclk_sel = FIELD_GET(PLLnCR0_REFCLK_SEL, val);
+ pll->enabled = !(val & PLLnCR0_POFF);
+ pll->locked = !!(val & PLLnCR0_PLL_LCK);
+
+ if (!pll->enabled)
+ return;
+
+ switch (pll->frate_sel) {
+ case PLLnCR0_FRATE_5G:
+ /* 5GHz clock net */
+ __set_bit(LANE_MODE_1000BASEX_SGMII, pll->supported);
+ __set_bit(LANE_MODE_QSGMII, pll->supported);
+ break;
+ case PLLnCR0_FRATE_3_125G:
+ __set_bit(LANE_MODE_2500BASEX, pll->supported);
+ break;
+ case PLLnCR0_FRATE_5_15625G:
+ /* 10.3125GHz clock net */
+ __set_bit(LANE_MODE_10GBASER, pll->supported);
+ __set_bit(LANE_MODE_USXGMII, pll->supported);
+ __set_bit(LANE_MODE_10G_QXGMII, pll->supported);
+ break;
+ default:
+ break;
+ }
+}
+
+/* On LS1028A, SGMIIA_CFG, SGMIIB_CFG, and SGMIIC_CFG from PCCR8 have the
+ * ability to map either an ENETC PCS (PCCR8_SGMIIa_CFG=2) or a Felix switch
+ * PCS (PCCR8_SGMIIa_CFG=1) to the same lane.
+ *
+ * On LS1088A, the same QSGMII PCS B can be connected to SerDes lane 1
+ * (PCCR9_QSGMIIa_CFG=1) or to lane 3 (PCCR9_QSGMIIa_CFG=2).
+ *
+ * The PHY API lacks the capability to distinguish anything about the consumer,
+ * so we don't support changing the initial muxing done by the RCW.
+ *
+ * However, after disabling a PCS through PCCR8, we need to properly restore
+ * the original value to keep the same muxing, and for that we need to back
+ * it up (here).
+ */
+static void lynx_10g_backup_pccr_val(struct lynx_lane *lane)
+{
+ u32 val;
+ int err;
+
+ if (lane->mode == LANE_MODE_UNKNOWN)
+ return;
+
+ err = lynx_pccr_read(lane, lane->mode, &val);
+ if (err) {
+ dev_warn(&lane->phy->dev,
+ "The driver doesn't know how to access the PCCR for lane mode %s\n",
+ lynx_lane_mode_str(lane->mode));
+ lane->mode = LANE_MODE_UNKNOWN;
+ return;
+ }
+
+ lane->default_pccr[lane->mode] = val;
+
+ /* 1000Base-X, 1000Base-KX, 2500Base-KX and SGMII use the same PCCR8.
+ * Only the KX bit differs (set for 1000Base-KX). Since we back up PCCR
+ * values per lane mode, make sure to not back up the PCCR8 value with
+ * the KX bit set for the non-KX modes, if the lane was in KX mode at
+ * boot time. Just preserve bits 2:0, which tell whether the (and
+ * which) 1G PCS was enabled.
+ */
+ switch (lane->mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ lane->default_pccr[LANE_MODE_1000BASEX_SGMII] = val & ~PCCR8_SGMIIa_KX;
+ lane->default_pccr[LANE_MODE_2500BASEX] = val & ~PCCR8_SGMIIa_KX;
+ break;
+ default:
+ break;
+ }
+}
+
+/* Is the PCS enabled, according to the value backed up from the PCCR register
+ * for this lane mode?
+ *
+ * Normally we'd need to ask "what lane mode are we talking about?", but the
+ * answer is invariably the same regardless - PCCR8_SGMIIa_CFG has the same
+ * layout as PCCR9_QSGMIIa_CFG, PCCRB_XFIa_CFG etc etc, and the value 0
+ * universally means "PCS disabled". So this is just a shorthand answer.
+ */
+static bool lynx_10g_pccr_val_enabled(u32 pccr)
+{
+ return FIELD_PREP(PCCR8_SGMIIa_CFG, pccr) != 0;
+}
+
+static bool lynx_10g_lane_is_3_125g(struct lynx_lane *lane)
+{
+ struct lynx_priv *priv = lane->priv;
+ struct lynx_pll *pll;
+ u32 gcr0;
+
+ gcr0 = lynx_lane_read(lane, LNaGCR0);
+
+ if (gcr0 & LNaGCR0_TPLL_PLLF)
+ pll = &priv->pll[0];
+ else
+ pll = &priv->pll[1];
+
+ if (pll->frate_sel != PLLnCR0_FRATE_3_125G)
+ return false;
+
+ if (FIELD_GET(LNaGCR0_TRAT_SEL, gcr0) != RAT_SEL_FULL ||
+ FIELD_GET(LNaGCR0_RRAT_SEL, gcr0) != RAT_SEL_FULL)
+ return false;
+
+ return true;
+}
+
+static void lynx_10g_lane_read_configuration(struct lynx_lane *lane)
+{
+ u32 pssr0 = lynx_lane_read(lane, LNaPSSR0);
+ struct lynx_priv *priv = lane->priv;
+ int proto;
+
+ proto = FIELD_GET(LNaPSSR0_TYPE, pssr0);
+ switch (proto) {
+ case PROTO_SEL_SGMII_BASEX_KX_QSGMII:
+ if (lynx_10g_lane_is_3_125g(lane))
+ lane->mode = LANE_MODE_2500BASEX;
+ else if (FIELD_GET(LNaPSSR0_IS_QUAD, pssr0))
+ lane->mode = LANE_MODE_QSGMII;
+ else
+ lane->mode = LANE_MODE_1000BASEX_SGMII;
+ break;
+ case PROTO_SEL_XFI_10GBASER_KR_SXGMII:
+ if (FIELD_GET(LNaPSSR0_IS_QUAD, pssr0))
+ lane->mode = LANE_MODE_10G_QXGMII;
+ else if (priv->info->quirks & LYNX_QUIRK_HAS_HARDCODED_USXGMII)
+ lane->mode = LANE_MODE_USXGMII;
+ else
+ lane->mode = LANE_MODE_10GBASER;
+ break;
+ case PROTO_SEL_PCIE:
+ case PROTO_SEL_SATA:
+ case PROTO_SEL_XAUI:
+ break;
+ default:
+ dev_warn(&lane->phy->dev, "Unknown lane protocol 0x%x\n",
+ proto);
+ }
+
+ lynx_10g_backup_pccr_val(lane);
+}
+
+static int ls1028a_get_pccr(enum lynx_lane_mode lane_mode, int lane,
+ struct lynx_pccr *pccr)
+{
+ switch (lane_mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ pccr->offset = PCCR8;
+ pccr->width = 4;
+ pccr->shift = SGMII_CFG(lane);
+ break;
+ case LANE_MODE_QSGMII:
+ if (lane != 1)
+ return -EINVAL;
+
+ pccr->offset = PCCR9;
+ pccr->width = 3;
+ pccr->shift = QSGMII_CFG(A);
+ break;
+ case LANE_MODE_10G_QXGMII:
+ if (lane != 1)
+ return -EINVAL;
+
+ pccr->offset = PCCR9;
+ pccr->width = 3;
+ pccr->shift = QXGMII_CFG(A);
+ break;
+ case LANE_MODE_USXGMII:
+ if (lane != 0)
+ return -EINVAL;
+
+ pccr->offset = PCCRB;
+ pccr->width = 3;
+ pccr->shift = SXGMII_CFG(A);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ls1028a_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
+{
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ return SGMIIaCR0(lane);
+ case LANE_MODE_QSGMII:
+ return lane == 1 ? QSGMIIaCR0(A) : -EINVAL;
+ case LANE_MODE_USXGMII:
+ return lane == 0 ? SXGMIIaCR0(A) : -EINVAL;
+ case LANE_MODE_10G_QXGMII:
+ return lane == 1 ? QXGMIIaCR0(A) : -EINVAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct lynx_info lynx_info_ls1028a = {
+ .get_pccr = ls1028a_get_pccr,
+ .get_pcvt_offset = ls1028a_get_pcvt_offset,
+ .pll_read_configuration = lynx_10g_pll_read_configuration,
+ .lane_read_configuration = lynx_10g_lane_read_configuration,
+ .cdr_lock_check = lynx_10g_cdr_lock_check,
+ .num_lanes = 4,
+ .index = 1,
+ .quirks = LYNX_QUIRK_HAS_HARDCODED_USXGMII,
+};
+
+static int ls1046a_serdes1_get_pccr(enum lynx_lane_mode lane_mode, int lane,
+ struct lynx_pccr *pccr)
+{
+ switch (lane_mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ pccr->offset = PCCR8;
+ pccr->width = 4;
+ pccr->shift = SGMII_CFG(lane);
+ break;
+ case LANE_MODE_QSGMII:
+ if (lane != 1)
+ return -EINVAL;
+
+ pccr->offset = PCCR9;
+ pccr->width = 3;
+ pccr->shift = QSGMII_CFG(B);
+ break;
+ case LANE_MODE_10GBASER:
+ switch (lane) {
+ case 2:
+ pccr->shift = XFI_CFG(A);
+ break;
+ case 3:
+ pccr->shift = XFI_CFG(B);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ pccr->offset = PCCRB;
+ pccr->width = 3;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ls1046a_serdes1_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
+{
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ return SGMIIaCR0(lane);
+ case LANE_MODE_QSGMII:
+ if (lane != 1)
+ return -EINVAL;
+
+ return QSGMIIaCR0(B);
+ case LANE_MODE_10GBASER:
+ switch (lane) {
+ case 2:
+ return XFIaCR0(A);
+ case 3:
+ return XFIaCR0(B);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct lynx_info lynx_info_ls1046a_serdes1 = {
+ .get_pccr = ls1046a_serdes1_get_pccr,
+ .get_pcvt_offset = ls1046a_serdes1_get_pcvt_offset,
+ .pll_read_configuration = lynx_10g_pll_read_configuration,
+ .lane_read_configuration = lynx_10g_lane_read_configuration,
+ .cdr_lock_check = lynx_10g_cdr_lock_check,
+ .num_lanes = 4,
+ .index = 1,
+};
+
+static int ls1046a_serdes2_get_pccr(enum lynx_lane_mode lane_mode, int lane,
+ struct lynx_pccr *pccr)
+{
+ switch (lane_mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ if (lane != 1)
+ return -EINVAL;
+
+ pccr->offset = PCCR8;
+ pccr->width = 4;
+ pccr->shift = SGMII_CFG(B);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ls1046a_serdes2_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
+{
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ if (lane != 1)
+ return -EINVAL;
+
+ return SGMIIaCR0(B);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct lynx_info lynx_info_ls1046a_serdes2 = {
+ .get_pccr = ls1046a_serdes2_get_pccr,
+ .get_pcvt_offset = ls1046a_serdes2_get_pcvt_offset,
+ .pll_read_configuration = lynx_10g_pll_read_configuration,
+ .lane_read_configuration = lynx_10g_lane_read_configuration,
+ .cdr_lock_check = lynx_10g_cdr_lock_check,
+ .num_lanes = 4,
+ .index = 2,
+};
+
+static int ls1088a_serdes1_get_pccr(enum lynx_lane_mode lane_mode, int lane,
+ struct lynx_pccr *pccr)
+{
+ switch (lane_mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ pccr->offset = PCCR8;
+ pccr->width = 4;
+ pccr->shift = SGMII_CFG(lane);
+ break;
+ case LANE_MODE_QSGMII:
+ switch (lane) {
+ case 0:
+ pccr->shift = QSGMII_CFG(A);
+ break;
+ case 1:
+ case 3:
+ pccr->shift = QSGMII_CFG(B);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ pccr->offset = PCCR9;
+ pccr->width = 3;
+ break;
+ case LANE_MODE_10GBASER:
+ switch (lane) {
+ case 2:
+ pccr->shift = XFI_CFG(A);
+ break;
+ case 3:
+ pccr->shift = XFI_CFG(B);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ pccr->offset = PCCRB;
+ pccr->width = 3;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ls1088a_serdes1_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
+{
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ return SGMIIaCR0(lane);
+ case LANE_MODE_QSGMII:
+ switch (lane) {
+ case 0:
+ return QSGMIIaCR0(A);
+ case 1:
+ case 3:
+ return QSGMIIaCR0(B);
+ default:
+ return -EINVAL;
+ }
+ case LANE_MODE_10GBASER:
+ switch (lane) {
+ case 2:
+ return XFIaCR0(A);
+ case 3:
+ return XFIaCR0(B);
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct lynx_info lynx_info_ls1088a_serdes1 = {
+ .get_pccr = ls1088a_serdes1_get_pccr,
+ .get_pcvt_offset = ls1088a_serdes1_get_pcvt_offset,
+ .pll_read_configuration = lynx_10g_pll_read_configuration,
+ .lane_read_configuration = lynx_10g_lane_read_configuration,
+ .cdr_lock_check = lynx_10g_cdr_lock_check,
+ .num_lanes = 4,
+ .index = 1,
+};
+
+static int ls2088a_serdes1_get_pccr(enum lynx_lane_mode lane_mode, int lane,
+ struct lynx_pccr *pccr)
+{
+ switch (lane_mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ pccr->offset = PCCR8;
+ pccr->width = 4;
+ pccr->shift = SGMII_CFG(lane);
+ break;
+ case LANE_MODE_QSGMII:
+ switch (lane) {
+ case 2:
+ case 6:
+ pccr->shift = QSGMII_CFG(A);
+ break;
+ case 7:
+ pccr->shift = QSGMII_CFG(B);
+ break;
+ case 0:
+ case 4:
+ pccr->shift = QSGMII_CFG(C);
+ break;
+ case 1:
+ case 5:
+ pccr->shift = QSGMII_CFG(D);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ pccr->offset = PCCR9;
+ pccr->width = 3;
+ break;
+ case LANE_MODE_10GBASER:
+ pccr->offset = PCCRB;
+ pccr->width = 3;
+ pccr->shift = XFI_CFG(lane);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ls2088a_serdes1_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
+{
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ return SGMIIaCR0(lane);
+ case LANE_MODE_QSGMII:
+ switch (lane) {
+ case 2:
+ case 6:
+ return QSGMIIaCR0(A);
+ case 7:
+ return QSGMIIaCR0(B);
+ case 0:
+ case 4:
+ return QSGMIIaCR0(C);
+ case 1:
+ case 5:
+ return QSGMIIaCR0(D);
+ default:
+ return -EINVAL;
+ }
+ case LANE_MODE_10GBASER:
+ return XFIaCR0(lane);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct lynx_info lynx_info_ls2088a_serdes1 = {
+ .get_pccr = ls2088a_serdes1_get_pccr,
+ .get_pcvt_offset = ls2088a_serdes1_get_pcvt_offset,
+ .pll_read_configuration = lynx_10g_pll_read_configuration,
+ .lane_read_configuration = lynx_10g_lane_read_configuration,
+ .cdr_lock_check = lynx_10g_cdr_lock_check,
+ .num_lanes = 8,
+ .index = 1,
+};
+
+static int ls2088a_serdes2_get_pccr(enum lynx_lane_mode lane_mode, int lane,
+ struct lynx_pccr *pccr)
+{
+ switch (lane_mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ pccr->offset = PCCR8;
+ pccr->width = 4;
+ pccr->shift = SGMII_CFG(lane);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ls2088a_serdes2_get_pcvt_offset(int lane, enum lynx_lane_mode mode)
+{
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ return SGMIIaCR0(lane);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct lynx_info lynx_info_ls2088a_serdes2 = {
+ .get_pccr = ls2088a_serdes2_get_pccr,
+ .get_pcvt_offset = ls2088a_serdes2_get_pcvt_offset,
+ .pll_read_configuration = lynx_10g_pll_read_configuration,
+ .lane_read_configuration = lynx_10g_lane_read_configuration,
+ .cdr_lock_check = lynx_10g_cdr_lock_check,
+ .num_lanes = 8,
+ .index = 2,
+};
+
+/* Halting puts the lane in a mode in which it can be reconfigured */
+static void lynx_10g_lane_halt(struct phy *phy)
+{
+ struct lynx_lane *lane = phy_get_drvdata(phy);
+
+ /* Issue a reset request */
+ lynx_lane_rmw(lane, LNaGCR0,
+ LNaGCR0_RRST_ON | LNaGCR0_TRST_ON,
+ LNaGCR0_RRST | LNaGCR0_TRST);
+
+ /* The RM says to wait for at least 50ns */
+ usleep_range(1, 2);
+}
+
+static void lynx_10g_lane_reset(struct phy *phy)
+{
+ struct lynx_lane *lane = phy_get_drvdata(phy);
+
+ /* Finalize the reset request */
+ lynx_lane_rmw(lane, LNaGCR0,
+ LNaGCR0_RRST_OFF | LNaGCR0_TRST_OFF,
+ LNaGCR0_RRST | LNaGCR0_TRST);
+}
+
+static int lynx_10g_power_off(struct phy *phy)
+{
+ struct lynx_lane *lane = phy_get_drvdata(phy);
+
+ if (!lane->powered_up)
+ return 0;
+
+ /* Issue a reset request with the power down bits set */
+ lynx_lane_rmw(lane, LNaGCR0,
+ LNaGCR0_RRST_ON | LNaGCR0_TRST_ON |
+ LNaGCR0_RX_PD | LNaGCR0_TX_PD,
+ LNaGCR0_RRST | LNaGCR0_TRST |
+ LNaGCR0_RX_PD | LNaGCR0_TX_PD);
+
+ /* The RM says to wait for at least 50ns */
+ usleep_range(1, 2);
+
+ lane->powered_up = false;
+
+ return 0;
+}
+
+static int lynx_10g_power_on(struct phy *phy)
+{
+ struct lynx_lane *lane = phy_get_drvdata(phy);
+
+ if (lane->powered_up)
+ return 0;
+
+ /* RM says that to enable a previously powered down lane, set
+ * LNmGCR0[{R,T}X_PD]=0, wait 15 us, then set LNmGCR0[{R,T}RST]=1.
+ */
+ lynx_lane_rmw(lane, LNaGCR0, 0, LNaGCR0_RX_PD | LNaGCR0_TX_PD);
+ usleep_range(150, 300);
+ lynx_10g_lane_reset(phy);
+
+ lane->powered_up = true;
+
+ return 0;
+}
+
+static void lynx_10g_lane_set_nrate(struct lynx_lane *lane,
+ struct lynx_pll *pll,
+ enum lynx_lane_mode mode)
+{
+ enum lynx_10g_rat_sel nrate;
+
+ switch (pll->frate_sel) {
+ case PLLnCR0_FRATE_5G:
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ nrate = RAT_SEL_QUARTER;
+ break;
+ case LANE_MODE_QSGMII:
+ nrate = RAT_SEL_FULL;
+ break;
+ default:
+ return;
+ }
+ break;
+ case PLLnCR0_FRATE_3_125G:
+ switch (mode) {
+ case LANE_MODE_2500BASEX:
+ nrate = RAT_SEL_FULL;
+ break;
+ default:
+ return;
+ }
+ break;
+ case PLLnCR0_FRATE_5_15625G:
+ switch (mode) {
+ case LANE_MODE_10GBASER:
+ case LANE_MODE_USXGMII:
+ case LANE_MODE_10G_QXGMII:
+ nrate = RAT_SEL_DOUBLE;
+ break;
+ default:
+ return;
+ }
+ break;
+ default:
+ return;
+ }
+
+ lynx_lane_rmw(lane, LNaGCR0,
+ FIELD_PREP(LNaGCR0_TRAT_SEL, nrate) |
+ FIELD_PREP(LNaGCR0_RRAT_SEL, nrate),
+ LNaGCR0_RRAT_SEL | LNaGCR0_TRAT_SEL);
+}
+
+static void lynx_10g_lane_set_pll(struct lynx_lane *lane,
+ struct lynx_pll *pll)
+{
+ if (pll->id == 0) {
+ lynx_lane_rmw(lane, LNaGCR0,
+ LNaGCR0_RPLL_PLLF | LNaGCR0_TPLL_PLLF,
+ LNaGCR0_RPLL_MSK | LNaGCR0_TPLL_MSK);
+ } else {
+ lynx_lane_rmw(lane, LNaGCR0,
+ LNaGCR0_RPLL_PLLS | LNaGCR0_TPLL_PLLS,
+ LNaGCR0_RPLL_MSK | LNaGCR0_TPLL_MSK);
+ }
+}
+
+static void lynx_10g_lane_remap_pll(struct lynx_lane *lane,
+ enum lynx_lane_mode lane_mode)
+{
+ struct lynx_priv *priv = lane->priv;
+ struct lynx_pll *pll;
+
+ /* Switch to the PLL that works with this interface type */
+ pll = lynx_pll_get(priv, lane_mode);
+ if (unlikely(!pll))
+ return;
+
+ lynx_10g_lane_set_pll(lane, pll);
+
+ /* Choose the portion of clock net to be used on this lane */
+ lynx_10g_lane_set_nrate(lane, pll, lane_mode);
+}
+
+static void lynx_10g_lane_change_proto_conf(struct lynx_lane *lane,
+ enum lynx_lane_mode mode)
+{
+ const struct lynx_10g_proto_conf *conf = &lynx_10g_proto_conf[mode];
+
+ lynx_lane_rmw(lane, LNaGCR0,
+ FIELD_PREP(LNaGCR0_PROTS, conf->proto_sel) |
+ FIELD_PREP(LNaGCR0_IF20BIT_EN, conf->if20bit_en),
+ LNaGCR0_PROTS | LNaGCR0_IF20BIT_EN);
+ lynx_lane_rmw(lane, LNaGCR1,
+ FIELD_PREP(LNaGCR1_REIDL_TH, conf->reidl_th) |
+ FIELD_PREP(LNaGCR1_REIDL_ET_MSB, conf->reidl_et_msb) |
+ FIELD_PREP(LNaGCR1_REIDL_ET_SEL, conf->reidl_et_sel) |
+ FIELD_PREP(LNaGCR1_REIDL_EX_MSB, conf->reidl_ex_msb) |
+ FIELD_PREP(LNaGCR1_REIDL_EX_SEL, conf->reidl_ex_sel) |
+ FIELD_PREP(LNaGCR1_ISLEW_RCTL, conf->islew_rctl) |
+ FIELD_PREP(LNaGCR1_OSLEW_RCTL, conf->oslew_rctl),
+ LNaGCR1_REIDL_TH |
+ LNaGCR1_REIDL_ET_MSB | LNaGCR1_REIDL_ET_SEL |
+ LNaGCR1_REIDL_EX_MSB | LNaGCR1_REIDL_EX_SEL |
+ LNaGCR1_ISLEW_RCTL | LNaGCR1_OSLEW_RCTL);
+ lynx_lane_rmw(lane, LNaRECR0,
+ FIELD_PREP(LNaRECR0_RXEQ_BST, conf->rxeq_bst) |
+ FIELD_PREP(LNaRECR0_GK2OVD, conf->gk2ovd) |
+ FIELD_PREP(LNaRECR0_GK3OVD, conf->gk3ovd) |
+ FIELD_PREP(LNaRECR0_GK2OVD_EN, conf->gk2ovd_en) |
+ FIELD_PREP(LNaRECR0_GK3OVD_EN, conf->gk3ovd_en) |
+ FIELD_PREP(LNaRECR0_BASE_WAND, conf->base_wand),
+ LNaRECR0_RXEQ_BST | LNaRECR0_GK2OVD | LNaRECR0_GK3OVD |
+ LNaRECR0_GK2OVD_EN | LNaRECR0_GK3OVD_EN |
+ LNaRECR0_BASE_WAND);
+ lynx_lane_rmw(lane, LNaTECR0,
+ FIELD_PREP(LNaTECR0_TEQ_TYPE, conf->teq_type) |
+ FIELD_PREP(LNaTECR0_SGN_PREQ, conf->sgn_preq) |
+ FIELD_PREP(LNaTECR0_RATIO_PREQ, conf->ratio_preq) |
+ FIELD_PREP(LNaTECR0_SGN_POST1Q, conf->sgn_post1q) |
+ FIELD_PREP(LNaTECR0_RATIO_PST1Q, conf->ratio_post1q) |
+ FIELD_PREP(LNaTECR0_ADPT_EQ, conf->adpt_eq) |
+ FIELD_PREP(LNaTECR0_AMP_RED, conf->amp_red),
+ LNaTECR0_TEQ_TYPE | LNaTECR0_SGN_PREQ |
+ LNaTECR0_RATIO_PREQ | LNaTECR0_SGN_POST1Q |
+ LNaTECR0_RATIO_PST1Q | LNaTECR0_ADPT_EQ |
+ LNaTECR0_AMP_RED);
+ lynx_lane_write(lane, LNaTTLCR0, conf->ttlcr0);
+}
+
+static int lynx_10g_lane_disable_pcvt(struct lynx_lane *lane,
+ enum lynx_lane_mode mode)
+{
+ struct lynx_priv *priv = lane->priv;
+ int err;
+
+ spin_lock(&priv->pcc_lock);
+
+ err = lynx_pccr_write(lane, mode, 0);
+ if (err)
+ goto out;
+
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ err = lynx_pcvt_rmw(lane, mode, CR(1), SGMIIaCR1_SGPCS_DIS,
+ SGMIIaCR1_SGPCS_EN);
+ if (err)
+ goto out;
+
+ lynx_pcvt_rmw(lane, mode, CR(0),
+ SGMIIaCR0_RST_SGM_ON | SGMIIaCR0_PD_SGM,
+ SGMIIaCR0_RST_SGM | SGMIIaCR0_PD_SGM);
+ break;
+ case LANE_MODE_QSGMII:
+ err = lynx_pcvt_rmw(lane, mode, CR(0),
+ QSGMIIaCR0_RST_QSGM_ON | QSGMIIaCR0_PD_QSGM,
+ QSGMIIaCR0_RST_QSGM | QSGMIIaCR0_PD_QSGM);
+ if (err)
+ goto out;
+ break;
+ default:
+ err = 0;
+ }
+
+out:
+ spin_unlock(&priv->pcc_lock);
+
+ return err;
+}
+
+static int lynx_10g_lane_enable_pcvt(struct lynx_lane *lane,
+ enum lynx_lane_mode mode)
+{
+ struct lynx_priv *priv = lane->priv;
+ u32 val;
+ int err;
+
+ spin_lock(&priv->pcc_lock);
+
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ err = lynx_pcvt_rmw(lane, mode, CR(1), SGMIIaCR1_SGPCS_EN,
+ SGMIIaCR1_SGPCS_EN);
+ if (err)
+ goto out;
+
+ lynx_pcvt_rmw(lane, mode, CR(0), SGMIIaCR0_RST_SGM_OFF,
+ SGMIIaCR0_RST_SGM | SGMIIaCR0_PD_SGM);
+ break;
+ case LANE_MODE_QSGMII:
+ err = lynx_pcvt_rmw(lane, mode, CR(0), QSGMIIaCR0_RST_QSGM_OFF,
+ QSGMIIaCR0_RST_QSGM | QSGMIIaCR0_PD_QSGM);
+ if (err)
+ goto out;
+ break;
+ default:
+ err = 0;
+ }
+
+ /* If the PCS was enabled at boot time, use the backed up PCCR value to
+ * re-enable it here, to preserve the muxing.
+ */
+ if (lynx_10g_pccr_val_enabled(lane->default_pccr[mode])) {
+ err = lynx_pccr_write(lane, mode, lane->default_pccr[mode]);
+ goto out;
+ }
+
+ /* If the PCS was not enabled, set the PCCR to a default value which
+ * enables it (1). The assumption is that this is the only PCS <->
+ * SerDes lane muxing value possible.
+ *
+ * This is mostly useful for SGMII <-> 10GBase-R major protocol
+ * reconfiguration, where at boot time, either the SGMII or the
+ * 10GBase-R PCS is enabled for the lane, but not both.
+ *
+ * In fact, if there are multiple lane muxing options, this function
+ * will most likely not choose the right one. For correct functionality
+ * there, we assume that the PCS we are enabling here was found enabled
+ * at boot time (reset default, or through PBL, or...), and we preserve
+ * its muxing through the default_pccr branch above.
+ */
+ val = 0;
+
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ val |= FIELD_PREP(PCCR8_SGMIIa_CFG, 1);
+ break;
+ case LANE_MODE_QSGMII:
+ val |= FIELD_PREP(PCCR9_QSGMIIa_CFG, 1);
+ break;
+ case LANE_MODE_10G_QXGMII:
+ val |= FIELD_PREP(PCCR9_QXGMIIa_CFG, 1);
+ break;
+ case LANE_MODE_10GBASER:
+ val |= FIELD_PREP(PCCRB_XFIa_CFG, 1);
+ break;
+ case LANE_MODE_USXGMII:
+ val |= FIELD_PREP(PCCRB_SXGMIIa_CFG, 1);
+ break;
+ default:
+ err = 0;
+ goto out;
+ }
+
+ err = lynx_pccr_write(lane, mode, val);
+out:
+ spin_unlock(&priv->pcc_lock);
+
+ return err;
+}
+
+static bool lynx_10g_lane_mode_needs_rcw_override(struct lynx_lane *lane,
+ enum lynx_lane_mode new)
+{
+ enum lynx_lane_mode curr = lane->mode;
+
+ /* Major protocol changes, which involve changing the PCS connection to
+ * the GMII MAC with the one to the XGMII MAC, require an RCW override
+ * procedure to reconfigure an internal mux, as documented here:
+ * https://lore.kernel.org/linux-phy/20230810102631.bvozjer3t67r67iy@skbuf/
+ * This is SoC-specific, and not yet implemented in drivers/soc/fsl/guts.c.
+ *
+ * So the supported set of protocols depends on the initial lane mode.
+ *
+ * Minor protocol changes (SGMII <-> 1000Base-X <-> 2500Base-X or
+ * 10GBase-R <-> USXGMII) are supported.
+ */
+ if ((lynx_lane_mode_uses_gmii_mac(curr) &&
+ lynx_lane_mode_uses_xgmii_mac(new)) ||
+ (lynx_lane_mode_uses_xgmii_mac(curr) &&
+ lynx_lane_mode_uses_gmii_mac(new)))
+ return true;
+
+ return false;
+}
+
+static int lynx_10g_validate(struct phy *phy, enum phy_mode mode, int submode,
+ union phy_configure_opts *opts)
+{
+ struct lynx_lane *lane = phy_get_drvdata(phy);
+ enum lynx_lane_mode lane_mode;
+ int err;
+
+ err = lynx_phy_mode_to_lane_mode(phy, mode, submode, &lane_mode);
+ if (err)
+ return err;
+
+ if (lynx_10g_lane_mode_needs_rcw_override(lane, lane_mode))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int lynx_10g_set_mode(struct phy *phy, enum phy_mode mode, int submode)
+{
+ struct lynx_lane *lane = phy_get_drvdata(phy);
+ bool powered_up = lane->powered_up;
+ enum lynx_lane_mode lane_mode;
+ int err;
+
+ err = lynx_10g_validate(phy, mode, submode, NULL);
+ if (err)
+ return err;
+
+ lane_mode = phy_interface_to_lane_mode(submode);
+ /* lynx_10g_validate() already made sure the lane_mode is supported */
+
+ if (lane_mode == lane->mode)
+ return 0;
+
+ /* If the lane is powered up, put the lane into the halt state while
+ * the reconfiguration is being done.
+ */
+ if (powered_up)
+ lynx_10g_lane_halt(phy);
+
+ err = lynx_10g_lane_disable_pcvt(lane, lane->mode);
+ if (err)
+ goto out;
+
+ lynx_10g_lane_change_proto_conf(lane, lane_mode);
+ lynx_10g_lane_remap_pll(lane, lane_mode);
+ WARN_ON(lynx_10g_lane_enable_pcvt(lane, lane_mode));
+
+ lane->mode = lane_mode;
+
+out:
+ if (powered_up) {
+ /* The RM says to wait for at least 120 ns */
+ usleep_range(1, 2);
+ lynx_10g_lane_reset(phy);
+ }
+
+ return err;
+}
+
+static int lynx_10g_init(struct phy *phy)
+{
+ struct lynx_lane *lane = phy_get_drvdata(phy);
+
+ /* Mark the fact that the lane was init */
+ lane->init = true;
+
+ /* SerDes lanes are powered on at boot time. Any lane that is
+ * managed by this driver will get powered off when its consumer
+ * calls phy_init().
+ */
+ lane->powered_up = true;
+ lynx_10g_power_off(phy);
+
+ return 0;
+}
+
+static int lynx_10g_exit(struct phy *phy)
+{
+ struct lynx_lane *lane = phy_get_drvdata(phy);
+
+ /* The lane returns to the state where it isn't managed by the
+ * consumer, so we must treat is as if it isn't initialized, and always
+ * powered on.
+ */
+ lane->init = false;
+ lane->powered_up = false;
+ lynx_10g_power_on(phy);
+
+ return 0;
+}
+
+static const struct phy_ops lynx_10g_ops = {
+ .init = lynx_10g_init,
+ .exit = lynx_10g_exit,
+ .power_on = lynx_10g_power_on,
+ .power_off = lynx_10g_power_off,
+ .set_mode = lynx_10g_set_mode,
+ .validate = lynx_10g_validate,
+ .owner = THIS_MODULE,
+};
+
+static int lynx_10g_probe(struct platform_device *pdev)
+{
+ return lynx_probe(pdev, of_device_get_match_data(&pdev->dev),
+ &lynx_10g_ops);
+}
+
+static const struct of_device_id lynx_10g_of_match_table[] = {
+ { .compatible = "fsl,ls1028a-serdes", .data = &lynx_info_ls1028a },
+ { .compatible = "fsl,ls1046a-serdes1", .data = &lynx_info_ls1046a_serdes1 },
+ { .compatible = "fsl,ls1046a-serdes2", .data = &lynx_info_ls1046a_serdes2 },
+ { .compatible = "fsl,ls1088a-serdes1", .data = &lynx_info_ls1088a_serdes1 },
+ { .compatible = "fsl,ls2088a-serdes1", .data = &lynx_info_ls2088a_serdes1 },
+ { .compatible = "fsl,ls2088a-serdes2", .data = &lynx_info_ls2088a_serdes2 },
+ {}
+};
+MODULE_DEVICE_TABLE(of, lynx_10g_of_match_table);
+
+static struct platform_driver lynx_10g_driver = {
+ .probe = lynx_10g_probe,
+ .remove = lynx_remove,
+ .driver = {
+ .name = "lynx-10g",
+ .of_match_table = lynx_10g_of_match_table,
+ },
+};
+module_platform_driver(lynx_10g_driver);
+
+MODULE_IMPORT_NS("PHY_FSL_LYNX");
+MODULE_AUTHOR("Ioana Ciornei <ioana.ciornei@nxp.com>");
+MODULE_AUTHOR("Vladimir Oltean <vladimir.oltean@nxp.com>");
+MODULE_DESCRIPTION("Lynx 10G SerDes PHY driver for Layerscape SoCs");
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.c b/drivers/phy/freescale/phy-fsl-lynx-core.c
index 1e411bfab404..2cfe9236ffc5 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.c
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.c
@@ -11,6 +11,12 @@ const char *lynx_lane_mode_str(enum lynx_lane_mode lane_mode)
switch (lane_mode) {
case LANE_MODE_1000BASEX_SGMII:
return "1000Base-X/SGMII";
+ case LANE_MODE_2500BASEX:
+ return "2500Base-X";
+ case LANE_MODE_QSGMII:
+ return "QSGMII";
+ case LANE_MODE_10G_QXGMII:
+ return "10G-QXGMII";
case LANE_MODE_10GBASER:
return "10GBase-R";
case LANE_MODE_USXGMII:
@@ -29,6 +35,12 @@ enum lynx_lane_mode phy_interface_to_lane_mode(phy_interface_t intf)
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_1000BASEX:
return LANE_MODE_1000BASEX_SGMII;
+ case PHY_INTERFACE_MODE_2500BASEX:
+ return LANE_MODE_2500BASEX;
+ case PHY_INTERFACE_MODE_QSGMII:
+ return LANE_MODE_QSGMII;
+ case PHY_INTERFACE_MODE_10G_QXGMII:
+ return LANE_MODE_10G_QXGMII;
case PHY_INTERFACE_MODE_10GBASER:
return LANE_MODE_10GBASER;
case PHY_INTERFACE_MODE_USXGMII:
@@ -89,6 +101,29 @@ bool lynx_lane_supports_mode(struct lynx_lane *lane, enum lynx_lane_mode mode)
}
EXPORT_SYMBOL_NS_GPL(lynx_lane_supports_mode, "PHY_FSL_LYNX");
+/* The quad protocols are fixed because the lane has multiple consumers, and
+ * one phy_set_mode_ext() affects the other consumers as well. We have no use
+ * case for dynamic protocol changing here, so disallow it.
+ */
+static enum lynx_lane_mode lynx_fixed_protocols[] = {
+ LANE_MODE_QSGMII,
+ LANE_MODE_10G_QXGMII,
+};
+
+static bool lynx_lane_restrict_fixed_mode_change(struct lynx_lane *lane,
+ enum lynx_lane_mode new)
+{
+ enum lynx_lane_mode curr = lane->mode;
+
+ for (int i = 0; i < ARRAY_SIZE(lynx_fixed_protocols); i++)
+ if ((curr == lynx_fixed_protocols[i] ||
+ new == lynx_fixed_protocols[i]) &&
+ curr != new)
+ return true;
+
+ return false;
+}
+
/* Translate the mode/submode from phy_validate() and phy_set_mode_ext() to a
* lane_mode and return 0 if it is supported and we can transition to it from
* the current lane mode, or return negative error otherwise.
@@ -112,6 +147,9 @@ int lynx_phy_mode_to_lane_mode(struct phy *phy, enum phy_mode mode,
if (!lynx_lane_supports_mode(lane, tmp_lane_mode))
return -EINVAL;
+ if (lynx_lane_restrict_fixed_mode_change(lane, tmp_lane_mode))
+ return -EINVAL;
+
if (lane_mode)
*lane_mode = tmp_lane_mode;
diff --git a/drivers/phy/freescale/phy-fsl-lynx-core.h b/drivers/phy/freescale/phy-fsl-lynx-core.h
index 37fa4b544faa..a60429ba9324 100644
--- a/drivers/phy/freescale/phy-fsl-lynx-core.h
+++ b/drivers/phy/freescale/phy-fsl-lynx-core.h
@@ -9,6 +9,7 @@
#include <soc/fsl/phy-fsl-lynx.h>
#define LYNX_NUM_PLL 2
+#define LYNX_QUIRK_HAS_HARDCODED_USXGMII BIT(0)
struct lynx_priv;
struct lynx_lane;
@@ -36,6 +37,7 @@ struct lynx_lane {
bool init;
unsigned int id;
enum lynx_lane_mode mode;
+ u32 default_pccr[LANE_MODE_MAX];
};
struct lynx_info {
@@ -48,6 +50,8 @@ struct lynx_info {
void (*cdr_lock_check)(struct lynx_lane *lane);
int first_lane;
int num_lanes;
+ int index;
+ unsigned long quirks;
};
struct lynx_priv {
diff --git a/include/soc/fsl/phy-fsl-lynx.h b/include/soc/fsl/phy-fsl-lynx.h
index 92e8272d5ae1..ff5a7d1835b5 100644
--- a/include/soc/fsl/phy-fsl-lynx.h
+++ b/include/soc/fsl/phy-fsl-lynx.h
@@ -7,10 +7,37 @@
enum lynx_lane_mode {
LANE_MODE_UNKNOWN,
LANE_MODE_1000BASEX_SGMII,
+ LANE_MODE_2500BASEX,
+ LANE_MODE_QSGMII,
+ LANE_MODE_10G_QXGMII,
LANE_MODE_10GBASER,
LANE_MODE_USXGMII,
LANE_MODE_25GBASER,
LANE_MODE_MAX,
};
+static inline bool lynx_lane_mode_uses_gmii_mac(enum lynx_lane_mode mode)
+{
+ switch (mode) {
+ case LANE_MODE_1000BASEX_SGMII:
+ case LANE_MODE_2500BASEX:
+ case LANE_MODE_QSGMII:
+ case LANE_MODE_10G_QXGMII:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static inline bool lynx_lane_mode_uses_xgmii_mac(enum lynx_lane_mode mode)
+{
+ switch (mode) {
+ case LANE_MODE_10GBASER:
+ case LANE_MODE_USXGMII:
+ return true;
+ default:
+ return false;
+ }
+}
+
#endif /* __PHY_FSL_LYNX_H_ */
--
2.34.1
^ permalink raw reply related
* [PATCH v5 phy-next 14/16] dt-bindings: phy: lynx-10g: initial document
From: Vladimir Oltean @ 2026-06-10 15:19 UTC (permalink / raw)
To: linux-phy
Cc: Ioana Ciornei, Vinod Koul, Neil Armstrong, Tanjeff Moos,
linux-kernel, Rob Herring, devicetree, Conor Dooley,
Krzysztof Kozlowski
In-Reply-To: <20260610151952.2141019-1-vladimir.oltean@nxp.com>
Add a schema for the 10G Lynx SerDes. This is very similar to the modern
form of the 28G Lynx SerDes, which is very much the intention.
There is intentionally no generic fsl,lynx-10g compatible string due to
the hardware inability to report its capabilities, despite having a
common register map.
We allow both forms of #phy-cells = <1> in the top-level provider
and #phy-cells = <0> in the per-lane provider for more flexibility to
consumers, and because the kernel code is shared with the 28G Lynx which
already has that support for compatibility reasons.
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
Cc: devicetree@vger.kernel.org
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Rob Herring <robh@kernel.org>
v4->v5:
- return "big-endian: true" as a global constraint, add "big-endian:
false" in case the compatible is not LS1046A (Conor)
v3->v4:
- avoid relying on dtschema fixup to turn "properties: compatible: enum:"
into valid json-schema syntax ("properties: compatible: items: enum:"),
instead use "properties: compatible: contains: enum:" (Rob)
- move "big-endian: true" under allOf constraint for LS1046A, same place
as "required: big-endian"
- add Rob's review tag
v2->v3:
- move fsl,lynx-10g compatible comment to commit message from schema
property description
- make big-endian required for LS1046A
v1->v2:
- move patch later in series, right before driver
- deliberately ignoring this Sashiko feedback:
https://lore.kernel.org/linux-phy/20260529125017.ifqunh52gdzhthdg@skbuf/
---
.../devicetree/bindings/phy/fsl,lynx-10g.yaml | 139 ++++++++++++++++++
1 file changed, 139 insertions(+)
create mode 100644 Documentation/devicetree/bindings/phy/fsl,lynx-10g.yaml
diff --git a/Documentation/devicetree/bindings/phy/fsl,lynx-10g.yaml b/Documentation/devicetree/bindings/phy/fsl,lynx-10g.yaml
new file mode 100644
index 000000000000..2e7d0abfa71a
--- /dev/null
+++ b/Documentation/devicetree/bindings/phy/fsl,lynx-10g.yaml
@@ -0,0 +1,139 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/phy/fsl,lynx-10g.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Freescale Lynx 10G SerDes PHY
+
+maintainers:
+ - Vladimir Oltean <vladimir.oltean@nxp.com>
+
+description:
+ The 10G Lynx is a multi-protocol SerDes block which handles networking, PCIe,
+ SATA and other high-speed interfaces. It is present on most QorIQ and
+ Layerscape SoCs. The register map is common, but the integration is
+ SoC-specific, with the differences consisting in register endianness, the
+ number of lanes, protocol converters available per lane and their location in
+ the PCCR registers. Some SoCs have multiple SerDes blocks and those differ in
+ their protocol capabilities per lane.
+
+properties:
+ compatible:
+ enum:
+ - fsl,ls1028a-serdes
+ - fsl,ls1046a-serdes1
+ - fsl,ls1046a-serdes2
+ - fsl,ls1088a-serdes1
+ - fsl,ls1088a-serdes2
+ - fsl,ls2088a-serdes1
+ - fsl,ls2088a-serdes2
+
+ reg:
+ maxItems: 1
+
+ big-endian: true
+
+ "#phy-cells":
+ const: 1
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+patternProperties:
+ "^phy@[0-7]$":
+ type: object
+ description: SerDes lane (single RX/TX differential pair)
+
+ properties:
+ reg:
+ minimum: 0
+ maximum: 7
+ description: Lane index as seen in register map
+
+ "#phy-cells":
+ const: 0
+
+ required:
+ - reg
+ - "#phy-cells"
+
+ additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - "#phy-cells"
+ - "#address-cells"
+ - "#size-cells"
+
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - fsl,ls1028a-serdes
+ - fsl,ls1046a-serdes1
+ - fsl,ls1046a-serdes2
+ - fsl,ls1088a-serdes1
+ - fsl,ls1088a-serdes2
+ then:
+ patternProperties:
+ "^phy@[0-7]$":
+ properties:
+ reg:
+ minimum: 0
+ maximum: 3
+ - if:
+ properties:
+ compatible:
+ enum:
+ - fsl,ls1046a-serdes1
+ - fsl,ls1046a-serdes2
+ then:
+ required:
+ - big-endian
+ else:
+ properties:
+ big-endian: false
+
+additionalProperties: false
+
+examples:
+ - |
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ serdes@1ea0000 {
+ compatible = "fsl,ls1028a-serdes";
+ reg = <0x0 0x1ea0000 0x0 0xffff>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ #phy-cells = <1>;
+
+ phy@0 {
+ reg = <0>;
+ #phy-cells = <0>;
+ };
+
+ phy@1 {
+ reg = <1>;
+ #phy-cells = <0>;
+ };
+
+ phy@2 {
+ reg = <2>;
+ #phy-cells = <0>;
+ };
+
+ phy@3 {
+ reg = <3>;
+ #phy-cells = <0>;
+ };
+ };
+ };
--
2.34.1
^ permalink raw reply related
* [PATCH v11 2/2] hwmon: temperature: add support for EMC1812
From: Marius Cristea @ 2026-06-10 15:19 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: <20260610-hw_mon-emc1812-v11-0-cef809af5c19@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,
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.
Resistance Error Correction is supported on channels 1/2 and 3/4.
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 000000000000..0b4fbcaaea71
--- /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 51a5bdf75b08..a03e97f9a97f 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 85c236df781e..fcb712549ea6 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 2760feb9f83b..3b53572fd8bf 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 73b2abdcc6dd..e93e4051e99d 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 000000000000..68575c27d090
--- /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
+
+/* Constants and default values */
+#define EMC1812_HIGH_LIMIT_DEFAULT (85 + EMC1812_TEMP_OFFSET)
+
+#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_ideality_regs[] = {
+ [0] = 0xff,
+ [1] = EMC1812_EXT1_IDEALITY_FACTOR_ADDR,
+ [2] = EMC1812_EXT2_IDEALITY_FACTOR_ADDR,
+ [3] = EMC1812_EXT3_IDEALITY_FACTOR_ADDR,
+ [4] = EMC1812_EXT4_IDEALITY_FACTOR_ADDR,
+};
+
+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 enabled */
+ if (!(data->active_ch_mask & BIT(channel)))
+ 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], ®valh);
+ if (ret < 0)
+ return ret;
+
+ if (ch) {
+ ret = regmap_read(data->regmap, emc1812_limit_regs_low[ch][type], ®vall);
+ 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)
+{
+ unsigned int hyst;
+ int 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 -= (long)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 -= (long)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 -EOPNOTSUPP;
+
+ 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)
+{
+ unsigned int limit;
+ int hyst, ret;
+
+ /* 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((int)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)
+{
+ unsigned int 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, tmp;
+
+ 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);
+ tmp = clamp_val(val, 0, 255);
+ return regmap_write(data->regmap, emc1812_temp_crit_regs[channel], tmp);
+ case hwmon_temp_crit_hyst:
+ /* Critical temperature hysteresis is stored on 8bits */
+ val = DIV_ROUND_CLOSEST(val, 1000);
+ tmp = clamp_val(val, 0, 255);
+ return emc1812_set_hyst(data, channel, tmp);
+ default:
+ return -EOPNOTSUPP;
+ }
+ case hwmon_chip:
+ switch (attr) {
+ case hwmon_chip_update_interval:
+ interval = clamp_val(val, 0, 16000);
+ tmp = find_closest_descending(interval, emc1812_conv_time,
+ ARRAY_SIZE(emc1812_conv_time));
+ return regmap_write(data->regmap, EMC1812_CONV_ADDR, tmp);
+ default:
+ return -EOPNOTSUPP;
+ }
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int emc1812_init(struct emc1812_data *priv)
+{
+ int i, ret;
+ u8 val;
+
+ 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;
+ }
+
+ for (i = 0; i < priv->chip->phys_channels; i++) {
+ if (!test_bit(i, &priv->active_ch_mask))
+ continue;
+
+ /* Update the max temperature limit for extended temperature range. */
+ ret = emc1812_set_temp(priv, i, emc1812_temp_map[hwmon_temp_max],
+ EMC1812_HIGH_LIMIT_DEFAULT * 1000);
+ if (ret)
+ return ret;
+
+ /* Update the critical temperature limit for extended temperature range. */
+ ret = regmap_write(priv->regmap, emc1812_temp_crit_regs[i],
+ EMC1812_HIGH_LIMIT_DEFAULT);
+ if (ret)
+ return ret;
+
+ /* Set the ideality factor */
+ if (i > 0) {
+ ret = regmap_write(priv->regmap, emc1812_ideality_regs[i],
+ EMC1812_DEFAULT_IDEALITY_FACTOR);
+ if (ret)
+ return ret;
+ }
+ }
+
+ /*
+ * Set default values in registers. APDD, RECD12 and RECD34 are active on 0.
+ * 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.
+ * Set ALERT/THERM2 pin to be in comparator mode (When the ALERT/THERM2 pin is
+ * asserted in comparator mode, the corresponding High Limit Status bits are set.
+ * Reading these bits does not clear them until the ALERT/THERM2 pin is deasserted.
+ * Once the ALERT/THERM2 pin is deasserted, the status bits are automatically
+ * cleared.).
+ */
+ 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);
+
+ return regmap_write(priv->regmap, EMC1812_CFG_ADDR, val);
+}
+
+static int emc1812_parse_fw_config(struct emc1812_data *data, struct device *dev)
+{
+ unsigned int reg_nr = 0;
+ int 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");
+
+ /* 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", ®_nr);
+ if (ret || reg_nr >= data->chip->phys_channels)
+ return dev_err_probe(dev, -EINVAL,
+ "The index is higher then the chip supports\n");
+ /* Mark 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;
+ unsigned int tmp;
+ int ret;
+
+ 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.53.0
^ permalink raw reply related
* [PATCH v11 1/2] dt-bindings: hwmon: temperature: add support for EMC1812
From: Marius Cristea @ 2026-06-10 15:19 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: <20260610-hw_mon-emc1812-v11-0-cef809af5c19@microchip.com>
This is the devicetree schema for Microchip EMC1812/13/14/15/33
Multichannel Low-Voltage Remote Diode Sensor Family. It also
updates the MAINTAINERS file to include the new driver.
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.
Resistance Error Correction is supported on channels 1/2 and 3/4.
Signed-off-by: Marius Cristea <marius.cristea@microchip.com>
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
---
.../bindings/hwmon/microchip,emc1812.yaml | 193 +++++++++++++++++++++
MAINTAINERS | 6 +
2 files changed, 199 insertions(+)
diff --git a/Documentation/devicetree/bindings/hwmon/microchip,emc1812.yaml b/Documentation/devicetree/bindings/hwmon/microchip,emc1812.yaml
new file mode 100644
index 000000000000..1a273621db82
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/microchip,emc1812.yaml
@@ -0,0 +1,193 @@
+# 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:
+ - const: alert-therm2
+ - const: therm-addr
+ minItems: 1
+
+ "#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 availability of only a single
+ configuration bit in hardware, channels 1 and 2 are affected together.
+ If channel 2 is not available in hardware, this setting affects only
+ channel 1.
+ 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 availability of only a single
+ configuration bit in hardware, channels 3 and 4 are affected together.
+ If channel 4 is not available in hardware, this setting affects only
+ channel 3.
+ type: boolean
+
+ vdd-supply: true
+
+patternProperties:
+ "^channel@[0-4]$":
+ description: |
+ Represents the temperature channels.
+ 0: Internal sensor
+ 1-4: External remote diodes
+ 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 Internal, 1 External Channels, No APD,
+ # parasitic-res-on-channel1-2: for 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: 1 Internal, 2 External Channels, No APD,
+ # parasitic-res-on-channel1-2: 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: 1 Internal, 2 External Channels, Supports APD,
+ # parasitic-res-on-channel1-2: 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: 1 Internal, 3 External Channels, Supports APD,
+ # parasitic-res-on-channel1-2: on both channel 1 & 2
+ # parasitic-res-on-channel3-4: for channel 3
+ - if:
+ properties:
+ compatible:
+ const: microchip,emc1814
+ then:
+ properties:
+ channel@4: false
+
+unevaluatedProperties: 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 6d7b697bfdba..85c236df781e 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.53.0
^ permalink raw reply related
* [PATCH v11 0/2] Add support for Microchip EMC1812
From: Marius Cristea @ 2026-06-10 15:19 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Jonathan Corbet
Cc: linux-hwmon, devicetree, linux-kernel, linux-doc, Marius Cristea
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 v11:
- remove unnecessary check for channels which are not physically available
- fix pointer signedness mismatch warning
- fix off-by-one misalignment when setting IDEALITY_FACTOR
- update the max temperature and critical temperature limit to match the
extended temperature range
- Link to v10: https://lore.kernel.org/r/20260429-hw_mon-emc1812-v10-0-a8ca1d779502@microchip.com
Changes in v10:
- made comments more clear into the devicetree binding
- allow channel 0 (internal channel) into devicetree binding
- allow the default name for Channel 0 to be overridden by the Device Tree property
- translate temperature limits to support the hardware's extended temperature range
- update channel count validation to properly account for the internal channel
- return -EOPNOTSUPP if channel is greater than or equal to phys_channels
- Link to v9: https://lore.kernel.org/r/20260403-hw_mon-emc1812-v9-0-1a798f31cf2e@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 | 193 +++++
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, 1246 insertions(+)
---
base-commit: d2b2fea3503e5e12b2e28784152937e48bcca6ff
change-id: 20251002-hw_mon-emc1812-f1b806487d10
Best regards,
--
Marius Cristea <marius.cristea@microchip.com>
^ permalink raw reply
* Re: [PATCH v3 0/9] ARM: dts: aspeed: anacapa: restructure devicetree for development-phase
From: Colin Huang @ 2026-06-10 15:16 UTC (permalink / raw)
To: Andrew Jeffery
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Joel Stanley,
devicetree, linux-arm-kernel, linux-aspeed, linux-kernel,
colin.huang2, Carl Lee, Rex Fu, Andy Chung, Peter Shen
In-Reply-To: <2d1095b342fe0f4b1b4b99b22bb3af410d9aa60e.camel@codeconstruct.com.au>
Andrew Jeffery <andrew@codeconstruct.com.au> 於 2026年6月10日週三 下午9:04寫道:
>
> On Tue, 2026-06-02 at 21:24 +0800, Colin Huang via B4 Relay wrote:
> > This series refactors the Anacapa BMC devicetree layout to better support
> > development-phase hardware revisions (EVT1/EVT2/DVT) while keeping a platform
> > entrypoint.
> >
> > Signed-off-by: Colin Huang <u8813345@gmail.com>
> > ---
> > Changes in v3:
> > - Restructure the EVT2 devicetree to inherit from the EVT1 devicetree, making it incremental rather than standalone.
> > - Add the DVT devicetree, inheriting from the EVT2 devicetree.
> > - Enable MCTP and FRU support for the NIC.
> > - Align PDB fan GPIO numbering.
> > - Add an EEPROM device node for the NFC adaptor board.
> > - Add an additional EEPROM device node for the SCM.
> > - Add shunt resistor values for HSC monitors
> > - Link to v2: https://lore.kernel.org/r/20260409-anacapa-devlop-phase-devicetree-v2-0-68f328671653@gmail.com
> >
>
> So just to check, the changes in patches 5-8 inclusive are applicable
> to all of EVT1, EVT2 and DVT (given the way you've structured the
> includes)?
Yes, these patch apply to all development phase.
>
> > Changes in v2:
> > - Fix dtbs_check fail.
> > Validated by following command:
> > make dt_binding_check DT_SCHEMA_FILES=arm/aspeed/aspeed.yaml
> > make CHECK_DTBS=y DT_SCHEMA_FILES=arm/aspeed/aspeed.yaml aspeed/aspeed-bmc-facebook-anacapa.dtb
> > make CHECK_DTBS=y DT_SCHEMA_FILES=arm/aspeed/aspeed.yaml aspeed/aspeed-bmc-facebook-anacapa-evt1.dtb
> > make CHECK_DTBS=y DT_SCHEMA_FILES=arm/aspeed/aspeed.yaml aspeed/aspeed-bmc-facebook-anacapa-evt2.dtb
> > - Link to v1: https://lore.kernel.org/r/20260407-anacapa-devlop-phase-devicetree-v1-0-97b96367cac3@gmail.com
> >
> > ---
> > Andy Chung (1):
> > ARM: dts: aspeed: anacapa: Enable MCTP and FRU for NIC
> >
> > Carl Lee (1):
> > ARM: dts: aspeed: anacapa: Add eeprom device node for NFC adaptor board
> >
> > Colin Huang (5):
> > dt-bindings: arm: aspeed: add Anacapa EVT1 EVT2 DVT board
> > ARM: dts: aspeed: anacapa: add EVT1 devicetree and point wrapper to it
> > ARM: dts: aspeed: anacapa: add EVT2 devicetree inheriting EVT1
> > ARM: dts: aspeed: anacapa: add DVT devicetree inheriting EVT2
> > ARM: dts: aspeed: anacapa: add additional EEPROM node for SCM
>
> If you need to respin this series for some reason, can you please
> capitalise the first word of the short description (the bit after the
> last ':') for the commits above and the one below?
>
Got it. Capitalise the first word.of the short description.
> >
> > Peter Shen (1):
> > ARM: dts: aspeed: anacapa: evt2: add shunt resistor values for HSC monitors
> >
> > Rex Fu (1):
> > ARM: dts: aspeed: anacapa: Align PDB fan GPIO numbering
> >
> > .../devicetree/bindings/arm/aspeed/aspeed.yaml | 3 +
> > .../dts/aspeed/aspeed-bmc-facebook-anacapa-dvt.dts | 178 +++
> > .../aspeed/aspeed-bmc-facebook-anacapa-evt1.dts | 1179 ++++++++++++++++++++
> > .../aspeed/aspeed-bmc-facebook-anacapa-evt2.dts | 228 ++++
> > .../dts/aspeed/aspeed-bmc-facebook-anacapa.dts | 1077 +-----------------
> > 5 files changed, 1589 insertions(+), 1076 deletions(-)
> > ---
> > base-commit: 7ca1caf017d34396397b19fb4de9ecef256f4acc
> > change-id: 20260407-anacapa-devlop-phase-devicetree-4101d3f312c0
> >
> > Best regards,
>
> Andrew
^ permalink raw reply
* [PATCH 2/2] iio: adc: Add TI ADS1220 driver
From: Nguyen Minh Tien @ 2026-06-10 15:13 UTC (permalink / raw)
To: Jonathan Cameron
Cc: linux-iio, devicetree, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, David Lechner, Nuno Sá, Andy Shevchenko,
linux-kernel, zizuzacker
In-Reply-To: <20260610151342.44274-1-zizuzacker@gmail.com>
Add an IIO driver for the Texas Instruments ADS1220 24-bit delta-sigma
SPI ADC. The driver supports single-ended and differential voltage
channels described as device-tree child nodes, per-channel programmable
gain (exposed through scale) and data rate (exposed through sampling
frequency), the internal 2.048V reference, an external reference via a
regulator, or the analog supply (AVDD) as a ratiometric reference,
single-shot conversions and a DRDY-interrupt-driven triggered buffer.
Conversions are gated either on the DRDY interrupt or, when no interrupt
is wired, on a data-rate-derived delay. Runtime PM powers the device down
between conversions.
Signed-off-by: Nguyen Minh Tien <zizuzacker@gmail.com>
---
MAINTAINERS | 7 +
drivers/iio/adc/Kconfig | 12 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ti-ads1220.c | 835 +++++++++++++++++++++++++++++++++++
4 files changed, 855 insertions(+)
create mode 100644 drivers/iio/adc/ti-ads1220.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 396d4e76d..1797af05c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26661,6 +26661,13 @@ S: Maintained
F: Documentation/devicetree/bindings/iio/adc/ti,ads1119.yaml
F: drivers/iio/adc/ti-ads1119.c
+TI ADS1220 ADC DRIVER
+M: Nguyen Minh Tien <zizuzacker@gmail.com>
+L: linux-iio@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/iio/adc/ti,ads1220.yaml
+F: drivers/iio/adc/ti-ads1220.c
+
TI ADS1018 ADC DRIVER
M: Kurt Borja <kuurtb@gmail.com>
L: linux-iio@vger.kernel.org
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index a3a93a47b..cb17a89f0 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1784,6 +1784,18 @@ config TI_ADS1119
This driver can also be built as a module. If so, the module will be
called ti-ads1119.
+config TI_ADS1220
+ tristate "Texas Instruments ADS1220 ADC"
+ depends on SPI
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+ help
+ If you say yes here you get support for Texas Instruments ADS1220
+ 24-bit, 4-channel, delta-sigma ADC chip.
+
+ This driver can also be built as a module. If so, the module will be
+ called ti-ads1220.
+
config TI_ADS124S08
tristate "Texas Instruments ADS124S08"
depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 707dd7089..076cd51ed 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -153,6 +153,7 @@ obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o
obj-$(CONFIG_TI_ADS1018) += ti-ads1018.o
obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o
obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o
+obj-$(CONFIG_TI_ADS1220) += ti-ads1220.o
obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o
obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o
obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o
diff --git a/drivers/iio/adc/ti-ads1220.c b/drivers/iio/adc/ti-ads1220.c
new file mode 100644
index 000000000..06bcc8841
--- /dev/null
+++ b/drivers/iio/adc/ti-ads1220.c
@@ -0,0 +1,835 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Texas Instruments ADS1220 ADC driver
+ *
+ * Datasheet: https://www.ti.com/lit/gpn/ads1220
+ *
+ * Copyright (C) 2026 Nguyen Minh Tien <zizuzacker@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/log2.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm_runtime.h>
+#include <linux/spi/spi.h>
+#include <linux/units.h>
+#include <linux/unaligned.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+
+/* SPI commands (Table 8-7) */
+#define ADS1220_CMD_RESET 0x06
+#define ADS1220_CMD_START 0x08
+#define ADS1220_CMD_POWERDOWN 0x02
+#define ADS1220_CMD_RDATA 0x10
+#define ADS1220_CMD_RREG 0x20
+#define ADS1220_CMD_WREG 0x40
+/* RREG/WREG operate on one register (nn = 0 => 1 byte) at address rr */
+#define ADS1220_CMD_RREG_REG(reg) (ADS1220_CMD_RREG | ((reg) << 2))
+#define ADS1220_CMD_WREG_REG(reg) (ADS1220_CMD_WREG | ((reg) << 2))
+
+/* Configuration registers (Table 8-8) */
+#define ADS1220_REG_CONFIG0 0x00
+#define ADS1220_REG_CONFIG1 0x01
+#define ADS1220_REG_CONFIG2 0x02
+#define ADS1220_REG_CONFIG3 0x03
+#define ADS1220_MAX_REG ADS1220_REG_CONFIG3
+
+/* CONFIG0 */
+#define ADS1220_CFG0_MUX GENMASK(7, 4)
+#define ADS1220_CFG0_GAIN GENMASK(3, 1)
+#define ADS1220_CFG0_PGA_BYPASS BIT(0)
+
+/* CONFIG1 */
+#define ADS1220_CFG1_DR GENMASK(7, 5)
+#define ADS1220_CFG1_MODE GENMASK(4, 3)
+#define ADS1220_CFG1_CM BIT(2)
+#define ADS1220_CFG1_TS BIT(1)
+#define ADS1220_CFG1_BCS BIT(0)
+
+/* CONFIG2 */
+#define ADS1220_CFG2_VREF GENMASK(7, 6)
+#define ADS1220_CFG2_FILTER GENMASK(5, 4)
+#define ADS1220_CFG2_PSW BIT(3)
+#define ADS1220_CFG2_IDAC GENMASK(2, 0)
+
+/* CONFIG3 */
+#define ADS1220_CFG3_I1MUX GENMASK(7, 5)
+#define ADS1220_CFG3_I2MUX GENMASK(4, 2)
+#define ADS1220_CFG3_DRDYM BIT(1)
+
+/* VREF[1:0] sources */
+#define ADS1220_VREF_INTERNAL 0
+#define ADS1220_VREF_REFP0_REFN0 1
+#define ADS1220_VREF_AIN0_AIN3 2
+#define ADS1220_VREF_AVDD 3
+#define ADS1220_INTERNAL_VREF_uV 2048000
+
+/* Input multiplexer codes (Table 8-10) */
+#define ADS1220_MUX_SINGLE(ain) (0x8 | (ain)) /* AINx vs AVSS */
+#define ADS1220_MUX_SHORTED 0x0e /* (AVDD + AVSS) / 2 */
+
+#define ADS1220_DATA_BYTES 3
+#define ADS1220_DATA_BITS 24
+
+#define ADS1220_NUM_GAINS 8 /* 1, 2, 4, 8, 16, 32, 64, 128 */
+#define ADS1220_MAX_SE_GAIN 4 /* single-ended forces PGA bypass */
+
+#define ADS1220_MAX_CHANNELS 7 /* 4 single-ended + 3 differential-ish */
+#define ADS1220_MAX_AIN 4
+
+/* Worst-case single conversion: 20 SPS => 50 ms, plus margin. */
+#define ADS1220_CONV_TIMEOUT_MS 100
+#define ADS1220_CONV_MARGIN_US 2000
+
+#define ADS1220_SUSPEND_DELAY_MS 2000
+
+/* Data rate (samples per second) in normal mode, indexed by DR[2:0]. */
+static const int ads1220_datarates[] = {
+ 20, 45, 90, 175, 330, 600, 1000,
+};
+
+/*
+ * Available scales expressed as gain reciprocals (val / val2), matching the
+ * convention used by the sibling ti-ads1119 driver: writing 0.25 selects a
+ * gain of 4. The full list is used for differential channels; single-ended
+ * channels (which force the PGA into bypass) are limited to the first three
+ * entries (gains 1, 2, 4).
+ */
+static const int ads1220_scale_avail[] = {
+ 1, 1,
+ 1, 2,
+ 1, 4,
+ 1, 8,
+ 1, 16,
+ 1, 32,
+ 1, 64,
+ 1, 128,
+};
+
+#define ADS1220_SE_SCALE_AVAIL_LEN (3 * 2)
+#define ADS1220_SCALE_AVAIL_LEN ARRAY_SIZE(ads1220_scale_avail)
+
+struct ads1220_channel_config {
+ unsigned int mux;
+ unsigned int gain;
+ unsigned int datarate;
+ bool single_ended;
+};
+
+struct ads1220_state {
+ struct spi_device *spi;
+ struct completion completion;
+ struct iio_trigger *trig;
+ struct ads1220_channel_config *channels_cfg;
+ unsigned int num_channels_cfg;
+ int vref_uV;
+ unsigned int vref_source;
+
+ /*
+ * DMA-safe buffers. tx is used for command/register writes, rx for
+ * register and conversion-result reads. scan holds one sample plus a
+ * timestamp for the triggered buffer.
+ */
+ u8 tx[2] __aligned(IIO_DMA_MINALIGN);
+ u8 rx[ADS1220_DATA_BYTES];
+ struct {
+ s32 sample;
+ aligned_s64 timestamp;
+ } scan;
+};
+
+static int ads1220_command(struct ads1220_state *st, u8 cmd)
+{
+ st->tx[0] = cmd;
+
+ return spi_write(st->spi, st->tx, 1);
+}
+
+static int ads1220_write_reg(struct ads1220_state *st, u8 reg, u8 val)
+{
+ st->tx[0] = ADS1220_CMD_WREG_REG(reg);
+ st->tx[1] = val;
+
+ return spi_write(st->spi, st->tx, 2);
+}
+
+static int ads1220_read_reg(struct ads1220_state *st, u8 reg, u8 *val)
+{
+ int ret;
+
+ st->tx[0] = ADS1220_CMD_RREG_REG(reg);
+
+ ret = spi_write_then_read(st->spi, st->tx, 1, st->rx, 1);
+ if (ret)
+ return ret;
+
+ *val = st->rx[0];
+
+ return 0;
+}
+
+static int ads1220_reset(struct ads1220_state *st)
+{
+ int ret;
+
+ ret = ads1220_command(st, ADS1220_CMD_RESET);
+ if (ret)
+ return ret;
+
+ /* Wait at least 50us + 32 x tCLK after RESET before any command. */
+ fsleep(100);
+
+ return 0;
+}
+
+static unsigned int ads1220_datarate_to_code(unsigned int datarate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ads1220_datarates); i++)
+ if (ads1220_datarates[i] == datarate)
+ return i;
+
+ return 0;
+}
+
+static int ads1220_configure(struct ads1220_state *st, unsigned int mux,
+ unsigned int gain, unsigned int datarate,
+ bool single_ended, bool continuous)
+{
+ u8 reg0, reg1;
+ int ret;
+
+ reg0 = FIELD_PREP(ADS1220_CFG0_MUX, mux) |
+ FIELD_PREP(ADS1220_CFG0_GAIN, ilog2(gain));
+ /*
+ * For single-ended inputs (AINN = AVSS) the PGA must be bypassed; the
+ * datasheet only allows gains 1, 2 and 4 in that case.
+ */
+ if (single_ended)
+ reg0 |= ADS1220_CFG0_PGA_BYPASS;
+
+ ret = ads1220_write_reg(st, ADS1220_REG_CONFIG0, reg0);
+ if (ret)
+ return ret;
+
+ reg1 = FIELD_PREP(ADS1220_CFG1_DR, ads1220_datarate_to_code(datarate));
+ if (continuous)
+ reg1 |= ADS1220_CFG1_CM;
+
+ return ads1220_write_reg(st, ADS1220_REG_CONFIG1, reg1);
+}
+
+static int ads1220_read_sample(struct ads1220_state *st, unsigned int datarate,
+ int *val)
+{
+ int ret;
+
+ if (st->spi->irq) {
+ unsigned long timeout = msecs_to_jiffies(ADS1220_CONV_TIMEOUT_MS);
+
+ if (!wait_for_completion_timeout(&st->completion, timeout))
+ return -ETIMEDOUT;
+ } else {
+ /*
+ * No DRDY interrupt: wait for the conversion to finish. In
+ * single-shot mode the result stays latched until the next
+ * START, so waiting longer than one conversion is harmless;
+ * wait two periods plus a margin to comfortably cover the
+ * oscillator start-up and its tolerance.
+ */
+ fsleep(2 * DIV_ROUND_UP(MICRO, datarate) + ADS1220_CONV_MARGIN_US);
+ }
+
+ /*
+ * Once DRDY is low the result can be clocked out directly, MSB first,
+ * without an RDATA command (datasheet section 8.5.4).
+ */
+ ret = spi_read(st->spi, st->rx, ADS1220_DATA_BYTES);
+ if (ret)
+ return ret;
+
+ *val = sign_extend32(get_unaligned_be24(st->rx), ADS1220_DATA_BITS - 1);
+
+ return 0;
+}
+
+static int ads1220_single_conversion(struct ads1220_state *st,
+ const struct iio_chan_spec *chan,
+ int *val, bool calib_offset)
+{
+ struct device *dev = &st->spi->dev;
+ struct ads1220_channel_config *cfg = &st->channels_cfg[chan->address];
+ unsigned int mux = cfg->mux;
+ bool single_ended = cfg->single_ended;
+ int ret;
+
+ if (calib_offset) {
+ mux = ADS1220_MUX_SHORTED;
+ single_ended = false;
+ }
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ ret = ads1220_configure(st, mux, cfg->gain, cfg->datarate,
+ single_ended, false);
+ if (ret)
+ goto out;
+
+ if (st->spi->irq)
+ reinit_completion(&st->completion);
+
+ ret = ads1220_command(st, ADS1220_CMD_START);
+ if (ret)
+ goto out;
+
+ ret = ads1220_read_sample(st, cfg->datarate, val);
+ if (ret)
+ goto out;
+
+ ret = IIO_VAL_INT;
+out:
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
+ return ret;
+}
+
+static int ads1220_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long mask)
+{
+ struct ads1220_state *st = iio_priv(indio_dev);
+ struct ads1220_channel_config *cfg = &st->channels_cfg[chan->address];
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+ ret = ads1220_single_conversion(st, chan, val, false);
+ iio_device_release_direct(indio_dev);
+ return ret;
+ case IIO_CHAN_INFO_OFFSET:
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
+ ret = ads1220_single_conversion(st, chan, val, true);
+ iio_device_release_direct(indio_dev);
+ return ret;
+ case IIO_CHAN_INFO_SCALE:
+ /* scale [mV] = vref / (gain * 2^23); gain is a power of two. */
+ *val = st->vref_uV / MILLI;
+ *val2 = (chan->scan_type.realbits - 1) + ilog2(cfg->gain);
+ return IIO_VAL_FRACTIONAL_LOG2;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = cfg->datarate;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ads1220_read_avail(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct ads1220_state *st = iio_priv(indio_dev);
+ struct ads1220_channel_config *cfg = &st->channels_cfg[chan->address];
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ *type = IIO_VAL_FRACTIONAL;
+ *vals = ads1220_scale_avail;
+ *length = cfg->single_ended ? ADS1220_SE_SCALE_AVAIL_LEN :
+ ADS1220_SCALE_AVAIL_LEN;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *type = IIO_VAL_INT;
+ *vals = ads1220_datarates;
+ *length = ARRAY_SIZE(ads1220_datarates);
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ads1220_write_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int val, int val2, long mask)
+{
+ struct ads1220_state *st = iio_priv(indio_dev);
+ struct ads1220_channel_config *cfg = &st->channels_cfg[chan->address];
+ unsigned int gain;
+ int i;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ /* The available scales are the gain reciprocals (e.g. 1/4). */
+ if (val == 0 && val2 == 0)
+ return -EINVAL;
+
+ gain = MICRO / (val * MICRO + val2);
+ if (!is_power_of_2(gain) || gain > BIT(ADS1220_NUM_GAINS - 1))
+ return -EINVAL;
+ if (cfg->single_ended && gain > ADS1220_MAX_SE_GAIN)
+ return -EINVAL;
+
+ cfg->gain = gain;
+ return 0;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ for (i = 0; i < ARRAY_SIZE(ads1220_datarates); i++) {
+ if (ads1220_datarates[i] == val) {
+ cfg->datarate = val;
+ return 0;
+ }
+ }
+ return -EINVAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ads1220_debugfs_reg_access(struct iio_dev *indio_dev,
+ unsigned int reg, unsigned int writeval,
+ unsigned int *readval)
+{
+ struct ads1220_state *st = iio_priv(indio_dev);
+ u8 val;
+ int ret;
+
+ if (reg > ADS1220_MAX_REG)
+ return -EINVAL;
+
+ if (readval) {
+ ret = ads1220_read_reg(st, reg, &val);
+ if (ret)
+ return ret;
+ *readval = val;
+ return 0;
+ }
+
+ return ads1220_write_reg(st, reg, writeval);
+}
+
+static const struct iio_info ads1220_info = {
+ .read_raw = ads1220_read_raw,
+ .read_avail = ads1220_read_avail,
+ .write_raw = ads1220_write_raw,
+ .debugfs_reg_access = ads1220_debugfs_reg_access,
+};
+
+static int ads1220_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct ads1220_state *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ struct ads1220_channel_config *cfg;
+ unsigned int index;
+ int ret;
+
+ index = find_first_bit(indio_dev->active_scan_mask,
+ iio_get_masklength(indio_dev));
+ cfg = &st->channels_cfg[index];
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ ret = ads1220_configure(st, cfg->mux, cfg->gain, cfg->datarate,
+ cfg->single_ended, true);
+ if (ret)
+ goto err;
+
+ ret = ads1220_command(st, ADS1220_CMD_START);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+ return ret;
+}
+
+static int ads1220_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct ads1220_state *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops ads1220_buffer_setup_ops = {
+ .preenable = ads1220_buffer_preenable,
+ .postdisable = ads1220_buffer_postdisable,
+ .validate_scan_mask = &iio_validate_scan_mask_onehot,
+};
+
+static const struct iio_trigger_ops ads1220_trigger_ops = {
+ .validate_device = &iio_trigger_validate_own_device,
+};
+
+static irqreturn_t ads1220_irq_handler(int irq, void *dev_id)
+{
+ struct iio_dev *indio_dev = dev_id;
+ struct ads1220_state *st = iio_priv(indio_dev);
+
+ if (iio_buffer_enabled(indio_dev) && iio_trigger_using_own(indio_dev))
+ iio_trigger_poll(indio_dev->trig);
+ else
+ complete(&st->completion);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t ads1220_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct ads1220_state *st = iio_priv(indio_dev);
+ int ret;
+
+ ret = spi_read(st->spi, st->rx, ADS1220_DATA_BYTES);
+ if (ret) {
+ dev_err(&st->spi->dev, "Failed to read sample: %d\n", ret);
+ goto done;
+ }
+
+ st->scan.sample = sign_extend32(get_unaligned_be24(st->rx),
+ ADS1220_DATA_BITS - 1);
+
+ iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
+ iio_get_time_ns(indio_dev));
+done:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int ads1220_map_mux(struct device *dev, u32 ain_pos, u32 ain_neg,
+ bool differential, unsigned int *mux,
+ bool *single_ended)
+{
+ static const u8 diff_mux[ADS1220_MAX_AIN][ADS1220_MAX_AIN] = {
+ [0][1] = 0x0, [0][2] = 0x1, [0][3] = 0x2,
+ [1][2] = 0x3, [1][3] = 0x4, [1][0] = 0x6,
+ [2][3] = 0x5,
+ [3][2] = 0x7,
+ };
+
+ if (!differential) {
+ if (ain_pos >= ADS1220_MAX_AIN)
+ return -EINVAL;
+ *mux = ADS1220_MUX_SINGLE(ain_pos);
+ *single_ended = true;
+ return 0;
+ }
+
+ if (ain_pos >= ADS1220_MAX_AIN || ain_neg >= ADS1220_MAX_AIN)
+ return -EINVAL;
+
+ /* Only the input pairs the multiplexer can route are valid. */
+ if (ain_pos == ain_neg || (diff_mux[ain_pos][ain_neg] == 0 &&
+ !(ain_pos == 0 && ain_neg == 1)))
+ return -EINVAL;
+
+ *mux = diff_mux[ain_pos][ain_neg];
+ *single_ended = false;
+
+ return 0;
+}
+
+static int ads1220_alloc_channels(struct iio_dev *indio_dev)
+{
+ const struct iio_chan_spec ads1220_channel = {
+ .type = IIO_VOLTAGE,
+ .indexed = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .scan_type = {
+ .sign = 's',
+ .realbits = ADS1220_DATA_BITS,
+ .storagebits = 32,
+ .endianness = IIO_CPU,
+ },
+ };
+ const struct iio_chan_spec ads1220_ts = IIO_CHAN_SOFT_TIMESTAMP(0);
+ struct ads1220_state *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ struct iio_chan_spec *channels, *chan;
+ unsigned int num_channels, i = 0;
+ int ret;
+
+ st->num_channels_cfg = device_get_child_node_count(dev);
+ if (st->num_channels_cfg == 0 ||
+ st->num_channels_cfg > ADS1220_MAX_CHANNELS)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid channel count %u (max %u)\n",
+ st->num_channels_cfg, ADS1220_MAX_CHANNELS);
+
+ st->channels_cfg = devm_kcalloc(dev, st->num_channels_cfg,
+ sizeof(*st->channels_cfg), GFP_KERNEL);
+ if (!st->channels_cfg)
+ return -ENOMEM;
+
+ /* One extra channel for the timestamp. */
+ num_channels = st->num_channels_cfg + 1;
+ channels = devm_kcalloc(dev, num_channels, sizeof(*channels),
+ GFP_KERNEL);
+ if (!channels)
+ return -ENOMEM;
+
+ device_for_each_child_node_scoped(dev, child) {
+ struct ads1220_channel_config *cfg = &st->channels_cfg[i];
+ bool differential;
+ u32 ain[2];
+
+ differential = fwnode_property_present(child, "diff-channels");
+ if (differential)
+ ret = fwnode_property_read_u32_array(child,
+ "diff-channels",
+ ain, 2);
+ else
+ ret = fwnode_property_read_u32(child, "single-channel",
+ &ain[0]);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to read channel property\n");
+
+ ret = ads1220_map_mux(dev, ain[0], ain[1], differential,
+ &cfg->mux, &cfg->single_ended);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Invalid input combination\n");
+
+ cfg->gain = 1;
+ cfg->datarate = ads1220_datarates[0];
+
+ chan = &channels[i];
+ *chan = ads1220_channel;
+ chan->channel = ain[0];
+ chan->address = i;
+ chan->scan_index = i;
+ if (differential) {
+ chan->channel2 = ain[1];
+ chan->differential = 1;
+ }
+
+ i++;
+ }
+
+ channels[i] = ads1220_ts;
+ channels[i].scan_index = i;
+
+ indio_dev->channels = channels;
+ indio_dev->num_channels = num_channels;
+
+ return 0;
+}
+
+static int ads1220_init(struct ads1220_state *st)
+{
+ u8 reg2;
+ int ret;
+
+ ret = ads1220_reset(st);
+ if (ret)
+ return ret;
+
+ reg2 = FIELD_PREP(ADS1220_CFG2_VREF, st->vref_source);
+
+ ret = ads1220_write_reg(st, ADS1220_REG_CONFIG2, reg2);
+ if (ret)
+ return ret;
+
+ /* DRDY only on the dedicated pin (DRDYM = 0). */
+ return ads1220_write_reg(st, ADS1220_REG_CONFIG3, 0);
+}
+
+static void ads1220_powerdown(void *data)
+{
+ struct ads1220_state *st = data;
+
+ ads1220_command(st, ADS1220_CMD_POWERDOWN);
+}
+
+static int ads1220_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct ads1220_state *st;
+ int avdd_uV;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->spi = spi;
+ spi_set_drvdata(spi, indio_dev);
+
+ /* The ADS1220 uses SPI mode 1 (CPOL = 0, CPHA = 1). */
+ spi->mode |= SPI_CPHA;
+ spi->bits_per_word = 8;
+ ret = spi_setup(spi);
+ if (ret)
+ return dev_err_probe(dev, ret, "SPI setup failed\n");
+
+ indio_dev->name = "ads1220";
+ indio_dev->info = &ads1220_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = devm_regulator_get_enable(dev, "dvdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable dvdd\n");
+
+ avdd_uV = devm_regulator_get_enable_read_voltage(dev, "avdd");
+ if (avdd_uV < 0)
+ return dev_err_probe(dev, avdd_uV, "Failed to get avdd\n");
+
+ /*
+ * Reference source, in priority order:
+ * - external reference on REFP0/REFN0 if a "vref" regulator is given;
+ * - the analog supply (AVDD) for ratiometric single-supply setups if
+ * "ti,vref-avdd" is set - no extra pins, full 0..AVDD input range;
+ * - otherwise the internal 2.048V reference.
+ */
+ st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "vref");
+ if (st->vref_uV >= 0) {
+ st->vref_source = ADS1220_VREF_REFP0_REFN0;
+ } else if (st->vref_uV != -ENODEV) {
+ return dev_err_probe(dev, st->vref_uV, "Failed to get vref\n");
+ } else if (device_property_read_bool(dev, "ti,vref-avdd")) {
+ st->vref_source = ADS1220_VREF_AVDD;
+ st->vref_uV = avdd_uV;
+ } else {
+ st->vref_source = ADS1220_VREF_INTERNAL;
+ st->vref_uV = ADS1220_INTERNAL_VREF_uV;
+ }
+
+ ret = ads1220_alloc_channels(indio_dev);
+ if (ret)
+ return ret;
+
+ init_completion(&st->completion);
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL,
+ ads1220_trigger_handler,
+ &ads1220_buffer_setup_ops);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to set up IIO buffer\n");
+
+ if (spi->irq > 0) {
+ ret = devm_request_irq(dev, spi->irq, ads1220_irq_handler,
+ IRQF_NO_THREAD, "ads1220", indio_dev);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to request irq\n");
+
+ st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!st->trig)
+ return -ENOMEM;
+
+ st->trig->ops = &ads1220_trigger_ops;
+ iio_trigger_set_drvdata(st->trig, indio_dev);
+
+ ret = devm_iio_trigger_register(dev, st->trig);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to register trigger\n");
+ }
+
+ ret = ads1220_init(st);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to initialize device\n");
+
+ pm_runtime_set_autosuspend_delay(dev, ADS1220_SUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_set_active(dev);
+
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable pm runtime\n");
+
+ ret = devm_add_action_or_reset(dev, ads1220_powerdown, st);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static int ads1220_runtime_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct ads1220_state *st = iio_priv(indio_dev);
+
+ return ads1220_command(st, ADS1220_CMD_POWERDOWN);
+}
+
+static int ads1220_runtime_resume(struct device *dev)
+{
+ /*
+ * A START/SYNC command wakes the analog parts from power-down; it is
+ * issued by the conversion path, so there is nothing to do here beyond
+ * letting the device settle after the supplies are active again.
+ */
+ fsleep(100);
+
+ return 0;
+}
+
+static DEFINE_RUNTIME_DEV_PM_OPS(ads1220_pm_ops, ads1220_runtime_suspend,
+ ads1220_runtime_resume, NULL);
+
+static const struct spi_device_id ads1220_id[] = {
+ { "ads1220" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ads1220_id);
+
+static const struct of_device_id ads1220_of_match[] = {
+ { .compatible = "ti,ads1220" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ads1220_of_match);
+
+static struct spi_driver ads1220_driver = {
+ .driver = {
+ .name = "ads1220",
+ .of_match_table = ads1220_of_match,
+ .pm = pm_ptr(&ads1220_pm_ops),
+ },
+ .probe = ads1220_probe,
+ .id_table = ads1220_id,
+};
+module_spi_driver(ads1220_driver);
+
+MODULE_DESCRIPTION("Texas Instruments ADS1220 ADC Driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Nguyen Minh Tien <zizuzacker@gmail.com>");
--
2.34.1
^ permalink raw reply related
* [PATCH 1/2] dt-bindings: iio: adc: Add TI ADS1220
From: Nguyen Minh Tien @ 2026-06-10 15:13 UTC (permalink / raw)
To: Jonathan Cameron
Cc: linux-iio, devicetree, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, David Lechner, Nuno Sá, Andy Shevchenko,
linux-kernel, zizuzacker
In-Reply-To: <20260610151342.44274-1-zizuzacker@gmail.com>
The ADS1220 is a 24-bit, 2-kSPS, 4-channel delta-sigma ADC from Texas
Instruments with an SPI (mode 1) interface, a programmable gain amplifier,
an internal 2.048V reference and a dedicated DRDY data-ready output.
Add a device tree binding describing the SPI device and its per-input
channel child nodes (single-ended AINx or the multiplexer's differential
pairs), the optional external/AVDD reference selection and the DRDY
interrupt.
Signed-off-by: Nguyen Minh Tien <zizuzacker@gmail.com>
---
.../bindings/iio/adc/ti,ads1220.yaml | 146 ++++++++++++++++++
1 file changed, 146 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads1220.yaml
diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1220.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1220.yaml
new file mode 100644
index 000000000..1fedffc2a
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1220.yaml
@@ -0,0 +1,146 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/ti,ads1220.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Texas Instruments ADS1220 ADC
+
+maintainers:
+ - Nguyen Minh Tien <zizuzacker@gmail.com>
+
+description:
+ The TI ADS1220 is a precision 24-bit, 2-kSPS, delta-sigma ADC with an SPI
+ (mode 1) interface. It provides two differential or four single-ended inputs
+ through a multiplexer, a programmable gain amplifier (gain 1 to 128), an
+ internal 2.048V reference and oscillator, two programmable excitation current
+ sources and a 50/60Hz rejection filter. A dedicated DRDY output signals when a
+ new conversion result is available.
+
+properties:
+ compatible:
+ const: ti,ads1220
+
+ reg:
+ maxItems: 1
+
+ spi-cpha: true
+
+ interrupts:
+ description: DRDY pin, signals that a new conversion result is ready.
+ maxItems: 1
+
+ avdd-supply:
+ description: Analog power supply (AVDD/AVSS).
+
+ dvdd-supply:
+ description: Digital power supply (DVDD/DGND).
+
+ vref-supply:
+ description:
+ External reference voltage (REFP0/REFN0). If omitted, the internal
+ 2.048V reference is used unless ti,vref-avdd is set.
+
+ ti,vref-avdd:
+ type: boolean
+ description:
+ Use the analog supply (AVDD/AVSS) as the conversion reference instead of
+ the internal 2.048V reference. Suited to ratiometric single-supply
+ measurements (for example a potentiometer wired across AVDD), giving a
+ full 0..AVDD input range without an external reference. Ignored when
+ vref-supply is present.
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+ "#io-channel-cells":
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - "#address-cells"
+ - "#size-cells"
+ - avdd-supply
+ - dvdd-supply
+
+patternProperties:
+ "^channel@[0-6]$":
+ $ref: adc.yaml
+ type: object
+ description: Represents one ADC input configuration (channel).
+
+ properties:
+ reg:
+ minimum: 0
+ maximum: 6
+
+ diff-channels:
+ description:
+ Differential input pair routable by the ADS1220 multiplexer.
+ oneOf:
+ - items: [const: 0, const: 1]
+ - items: [const: 0, const: 2]
+ - items: [const: 0, const: 3]
+ - items: [const: 1, const: 2]
+ - items: [const: 1, const: 3]
+ - items: [const: 2, const: 3]
+ - items: [const: 1, const: 0]
+ - items: [const: 3, const: 2]
+
+ single-channel:
+ description:
+ Single-ended input channel AINx measured against AVSS.
+ minimum: 0
+ maximum: 3
+
+ oneOf:
+ - required: [diff-channels]
+ - required: [single-channel]
+
+ required:
+ - reg
+
+ unevaluatedProperties: false
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adc@0 {
+ compatible = "ti,ads1220";
+ reg = <0>;
+ spi-max-frequency = <2500000>;
+ spi-cpha;
+ interrupt-parent = <&pio>;
+ interrupts = <4 4 IRQ_TYPE_EDGE_FALLING>;
+ avdd-supply = <®_vcc3v3>;
+ dvdd-supply = <®_vcc3v3>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ #io-channel-cells = <1>;
+
+ channel@0 {
+ reg = <0>;
+ single-channel = <0>;
+ };
+
+ channel@1 {
+ reg = <1>;
+ diff-channels = <0 1>;
+ };
+ };
+ };
+...
--
2.34.1
^ permalink raw reply related
* [PATCH 0/2] iio: adc: Add Texas Instruments ADS1220 ADC
From: Nguyen Minh Tien @ 2026-06-10 15:13 UTC (permalink / raw)
To: Jonathan Cameron
Cc: linux-iio, devicetree, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, David Lechner, Nuno Sá, Andy Shevchenko,
linux-kernel, zizuzacker
This series adds support for the Texas Instruments ADS1220, a 24-bit,
2-kSPS, 4-channel delta-sigma ADC with an SPI (mode 1) interface, a
programmable gain amplifier (1 to 128), an internal 2.048V reference and
a dedicated DRDY data-ready output.
The driver supports:
- single-ended and differential voltage channels described as
device-tree child nodes;
- per-channel programmable gain (via _scale) and data rate (via
_sampling_frequency), with the matching *_available attributes;
- the internal 2.048V reference, an external reference on REFP0/REFN0
via a regulator, or the analog supply (AVDD) as a ratiometric
reference for single-supply measurements;
- single-shot conversions gated on the DRDY interrupt, or on a
data-rate-derived delay when no interrupt is wired;
- a DRDY-interrupt-driven triggered buffer for streaming;
- runtime PM (power-down between conversions).
I tested this on a Lichee Pi Nano (Allwinner F1C100s) running Linux 7.0:
with a potentiometer on AIN0 (single-ended against AVSS, AVDD as the
reference), in_voltage0_raw tracks the wiper linearly across the full
0..3.3V range (0 to 0x7fffff). Nothing in the driver is board-specific -
it only uses the SPI and IIO frameworks - so it should work on any SPI
host.
This is my first kernel contribution. I modelled the driver on the
existing TI ADS-family IIO drivers - ti-ads1119 for the structure and
ti-ads124s08 for the SPI side - so I'd welcome any feedback on things
I've missed.
Nguyen Minh Tien (2):
dt-bindings: iio: adc: Add TI ADS1220
iio: adc: Add TI ADS1220 driver
.../bindings/iio/adc/ti,ads1220.yaml | 146 +++
MAINTAINERS | 7 +
drivers/iio/adc/Kconfig | 12 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ti-ads1220.c | 835 ++++++++++++++++++
5 files changed, 1001 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/adc/ti,ads1220.yaml
create mode 100644 drivers/iio/adc/ti-ads1220.c
base-commit: ae696dfa47c30016cd429b9db5e70b259b8f509e
--
2.34.1
^ permalink raw reply
* RE: [PATCH net v4 4/4] dt-bindings: net: updated interrupt type to be active low, level triggered
From: Selvamani Rajagopal @ 2026-06-10 15:09 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Parthiban Veerasooran, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Andrew Lunn, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, Conor Dooley,
devicetree@vger.kernel.org, Piergiorgio Beruto
In-Reply-To: <20260610-glittering-rose-squirrel-b0ccf6@quoll>
> Subject: Re: [PATCH net v4 4/4] dt-bindings: net: updated interrupt type to be active low,
> level triggered
>
> On Tue, Jun 09, 2026 at 12:50:27PM -0700, Selvamani Rajagopal wrote:
> > According to OPEN Alliance 10BASE-T1x MACPHY Serial Interface (TC6)
> > specification, interrupt type is active low, level triggered interrupt.
> >
> > Fixes: ac49b950bea9 ("dt-bindings: net: add Microchip's LAN865X 10BASE-T1S
> MACPHY")
>
> This to be a fix, you would need to describe the issue, e.g. impact. I
> already asked last time and commit msg did not improve.
Have added some details in the source change. I should have added here too. I will add here in the next submission.
>
> > Signed-off-by: Selvamani Rajagopal <Selvamani.Rajagopal@onsemi.com>
> >
>
> Messed patch - missing separator.
I didn't know, therefore didn't realize. Will add in the next submission.
>
> > changes in v4:
> > no change
> > changes in v3
> > interrupts entry changed to level triggered from edge triggered
>
> Best regards,
> Krzysztof
^ permalink raw reply
* Re: [PATCH v13 17/22] media: i2c: maxim-serdes: add MAX96724 driver
From: Dan Carpenter @ 2026-06-10 15:06 UTC (permalink / raw)
To: Niklas Söderlund
Cc: dumitru.ceclan, Tomi Valkeinen, Mauro Carvalho Chehab,
Sakari Ailus, Laurent Pinchart, Julien Massot, Rob Herring,
Greg Kroah-Hartman, mitrutzceclan, linux-media, linux-kernel,
devicetree, linux-staging, linux-gpio, Martin Hecht,
Cosmin Tanislav, Cory Keitz
In-Reply-To: <20260610144242.GF2948@ragnatech.se>
On Wed, Jun 10, 2026 at 04:42:42PM +0200, Niklas Söderlund wrote:
> Hi,
>
> Thanks for your work.
>
> This patch gives me new compiler warnings, can they be avoided?
>
> .../max96724.c:402 max96724_log_phy_status() warn: subtract is higher precedence than shift
> .../max96724.c:409 max96724_log_phy_status() warn: subtract is higher precedence than shift
> .../max96724.c:588 max96724_init_phy() warn: subtract is higher precedence than shift
> .../max96724.c:756 max96724_set_pipe_remap() warn: subtract is higher precedence than shift
> .../max96724.c:796 max96724_set_pipe_phy() warn: subtract is higher precedence than shift
> .../max96724.c:818 max96724_set_pipe_stream_id() warn: subtract is higher precedence than shift
> .../max96724.c:830 max96724_set_pipe_link() warn: subtract is higher precedence than shift
> .../max96724.c:942 max96724_set_link_version() warn: subtract is higher precedence than shift
>
These are Smatch warnings. I appologize for them. I know about them
but I haven't looked at them. I'll fix them by the end of the week.
regards,
dan carpenter
^ permalink raw reply
* [PATCH v3 net-next 1/1] dt-bindings: net: dsa: Convert lan9303.txt to yaml format
From: Frank.Li @ 2026-06-10 15:05 UTC (permalink / raw)
To: Andrew Lunn, Vladimir Oltean, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Simon Horman, Jonathan Corbet, Shuah Khan, Frank Li,
open list:NETWORKING DRIVERS,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
open list, open list:DOCUMENTATION
Cc: imx
From: Frank Li <Frank.Li@nxp.com>
Convert lan9303.txt to yaml format to fix below CHECK_DTBS warnings:
arch/arm/boot/dts/nxp/imx/imx53-kp-hsc.dtb: /soc/bus@50000000/i2c@53fec000/switch@a: failed to match any schema with compatible: ['smsc,lan9303-i2c']
Additional changes:
- rename switch-phy to switch in example.
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Frank Li <Frank.Li@nxp.com>
---
change in v3
- rebase to net-next
change in v2:
- fix typo Additional in commit message
- add rob's reviewed-by tags
- fix doc ref problem
---
.../devicetree/bindings/net/dsa/lan9303.txt | 100 --------------
.../bindings/net/dsa/smsc,lan9303.yaml | 123 ++++++++++++++++++
Documentation/networking/dsa/lan9303.rst | 2 +-
3 files changed, 124 insertions(+), 101 deletions(-)
delete mode 100644 Documentation/devicetree/bindings/net/dsa/lan9303.txt
create mode 100644 Documentation/devicetree/bindings/net/dsa/smsc,lan9303.yaml
diff --git a/Documentation/devicetree/bindings/net/dsa/lan9303.txt b/Documentation/devicetree/bindings/net/dsa/lan9303.txt
deleted file mode 100644
index 0337c2ccfa9a7..0000000000000
--- a/Documentation/devicetree/bindings/net/dsa/lan9303.txt
+++ /dev/null
@@ -1,100 +0,0 @@
-SMSC/MicroChip LAN9303 three port ethernet switch
--------------------------------------------------
-
-Required properties:
-
-- compatible: should be
- - "smsc,lan9303-i2c" for I2C managed mode
- or
- - "smsc,lan9303-mdio" for mdio managed mode
-
-Optional properties:
-
-- reset-gpios: GPIO to be used to reset the whole device
-- reset-duration: reset duration in milliseconds, defaults to 200 ms
-
-Subnodes:
-
-The integrated switch subnode should be specified according to the binding
-described in dsa/dsa.yaml. The CPU port of this switch is always port 0.
-
-Note: always use 'reg = <0/1/2>;' for the three DSA ports, even if the device is
-configured to use 1/2/3 instead. This hardware configuration will be
-auto-detected and mapped accordingly.
-
-Example:
-
-I2C managed mode:
-
- master: masterdevice@X {
-
- fixed-link { /* RMII fixed link to LAN9303 */
- speed = <100>;
- full-duplex;
- };
- };
-
- switch: switch@a {
- compatible = "smsc,lan9303-i2c";
- reg = <0xa>;
- reset-gpios = <&gpio7 6 GPIO_ACTIVE_LOW>;
- reset-duration = <200>;
-
- ports {
- #address-cells = <1>;
- #size-cells = <0>;
-
- port@0 { /* RMII fixed link to master */
- reg = <0>;
- ethernet = <&master>;
- };
-
- port@1 { /* external port 1 */
- reg = <1>;
- label = "lan1";
- };
-
- port@2 { /* external port 2 */
- reg = <2>;
- label = "lan2";
- };
- };
- };
-
-MDIO managed mode:
-
- master: masterdevice@X {
- phy-handle = <&switch>;
-
- mdio {
- #address-cells = <1>;
- #size-cells = <0>;
-
- switch: switch-phy@0 {
- compatible = "smsc,lan9303-mdio";
- reg = <0>;
- reset-gpios = <&gpio7 6 GPIO_ACTIVE_LOW>;
- reset-duration = <100>;
-
- ports {
- #address-cells = <1>;
- #size-cells = <0>;
-
- port@0 {
- reg = <0>;
- ethernet = <&master>;
- };
-
- port@1 { /* external port 1 */
- reg = <1>;
- label = "lan1";
- };
-
- port@2 { /* external port 2 */
- reg = <2>;
- label = "lan2";
- };
- };
- };
- };
- };
diff --git a/Documentation/devicetree/bindings/net/dsa/smsc,lan9303.yaml b/Documentation/devicetree/bindings/net/dsa/smsc,lan9303.yaml
new file mode 100644
index 0000000000000..42f8473538a07
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/dsa/smsc,lan9303.yaml
@@ -0,0 +1,123 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/dsa/smsc,lan9303.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: SMSC/MicroChip LAN9303 three port ethernet switch
+
+maintainers:
+ - Frank Li <Frank.Li@nxp.com>
+
+description:
+ The LAN9303 is a three port ethernet switch with integrated PHYs for the
+ two external ports. The third port is an RMII/MII interface to a host
+ processor. The device can be managed via I2C or MDIO.
+
+ Note - always use 'reg = <0/1/2>;' for the three DSA ports, even if the
+ device is configured to use 1/2/3 instead. This hardware configuration
+ will be auto-detected and mapped accordingly.
+
+properties:
+ compatible:
+ enum:
+ - smsc,lan9303-i2c
+ - smsc,lan9303-mdio
+
+ reg:
+ maxItems: 1
+
+ reset-gpios:
+ description:
+ GPIO to be used to reset the whole device
+ maxItems: 1
+
+ reset-duration:
+ description:
+ Reset duration in milliseconds
+ default: 200
+ $ref: /schemas/types.yaml#/definitions/uint32
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+allOf:
+ - $ref: dsa.yaml#
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ /* I2C managed mode */
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ switch@a {
+ compatible = "smsc,lan9303-i2c";
+ reg = <0xa>;
+ reset-gpios = <&gpio7 6 GPIO_ACTIVE_LOW>;
+ reset-duration = <200>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ label = "cpu";
+ ethernet = <&master>;
+ };
+
+ port@1 {
+ reg = <1>;
+ label = "lan1";
+ };
+
+ port@2 {
+ reg = <2>;
+ label = "lan2";
+ };
+ };
+ };
+ };
+
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ /* MDIO managed mode */
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ switch@0 {
+ compatible = "smsc,lan9303-mdio";
+ reg = <0>;
+ reset-gpios = <&gpio7 6 GPIO_ACTIVE_LOW>;
+ reset-duration = <100>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ label = "cpu";
+ ethernet = <&master>;
+ };
+
+ port@1 {
+ reg = <1>;
+ label = "lan1";
+ };
+
+ port@2 {
+ reg = <2>;
+ label = "lan2";
+ };
+ };
+ };
+ };
diff --git a/Documentation/networking/dsa/lan9303.rst b/Documentation/networking/dsa/lan9303.rst
index ab81b4e0139e3..776572be265e1 100644
--- a/Documentation/networking/dsa/lan9303.rst
+++ b/Documentation/networking/dsa/lan9303.rst
@@ -12,7 +12,7 @@ Driver details
The driver is implemented as a DSA driver, see ``Documentation/networking/dsa/dsa.rst``.
-See ``Documentation/devicetree/bindings/net/dsa/lan9303.txt`` for device tree
+See ``Documentation/devicetree/bindings/net/dsa/smsc,lan9303.yaml`` for device tree
binding.
The LAN9303 can be managed both via MDIO and I2C, both supported by this driver.
--
2.43.0
^ permalink raw reply related
* Re: [PATCH V7 2/6] power: supply: sgm41542: Add SG Micro sgm41542 charger
From: sashiko-bot @ 2026-06-10 14:59 UTC (permalink / raw)
To: Chris Morgan; +Cc: devicetree, robh, conor+dt
In-Reply-To: <20260610144407.438846-3-macroalpha82@gmail.com>
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Hardware watchdog resets charger configuration during system suspend due to missing PM suspend/resume handlers.
- [High] The IRQ handler unconditionally overwrites the input current limit whenever VBUS is present, silently destroying userspace-configured limits.
- [Medium] Missing cleanup for device_init_wakeup() on driver unbind and probe error paths.
- [Medium] Lack of synchronization in the sysfs property setter for ONLINE state allows hardware/software state desynchronization.
- [Medium] The IRQ handler returns early on an I2C failure, skipping the critical `power_supply_changed()` uevent notification.
--
commit 518a06e67b4771b8e50e9e0705ace20eca6acede
Author: Chris Morgan <macromorgan@hotmail.com>
power: supply: sgm41542: Add SG Micro sgm41542 charger
Add support for the SG Micro SGM41542 charger/boost converter.
Driver was adapted from Rockchip BSP driver [1] and confirmed
with vendor datasheet [2].
Link: https://github.com/rockchip-linux/kernel/blob/develop-6.6/drivers/power/supply/sgm41542_charger.c
Link: https://www.sg-micro.de/rect/assets/1e8de70b-657e-4156-be68-a64fdbe8e418/SGM41541_SGM41542.pdf
> diff --git a/drivers/power/supply/sgm41542_charger.c b/drivers/power/supply/sgm41542_charger.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/power/supply/sgm41542_charger.c
[ ... ]
> +static int sgm4154x_charger_set_property(struct power_supply *psy,
> + enum power_supply_property prop,
> + const union power_supply_propval *val)
> +{
> + struct sgm4154x_device *sgm = power_supply_get_drvdata(psy);
> + int ret = -EINVAL;
> +
> + switch (prop) {
> + case POWER_SUPPLY_PROP_ONLINE:
> + if (val->intval) {
> + ret = sgm4154x_enable_charger(sgm);
> + sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_40S);
> + } else {
> + sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE);
> + ret = sgm4154x_disable_charger(sgm);
> + }
> + break;
[Severity: Medium]
Is there a need for a mutex in this sysfs property setter?
When modifying POWER_SUPPLY_PROP_ONLINE, the code performs multiple
operations without holding any locks.
Could concurrent sysfs writes interleave these operations? This might lead
to the hardware charger being disabled while the watchdog timer remains
enabled, causing the software sgm->watchdog_enable flag to become
desynchronized with the actual hardware state.
[ ... ]
> +static irqreturn_t sgm4154x_irq_handler_thread(int irq, void *private)
> +{
> + struct sgm4154x_device *sgm4154x = private;
> + struct sgm4154x_state state;
> + int ret;
> +
> + ret = sgm4154x_get_state(sgm4154x, &state);
> + if (ret) {
> + dev_err(sgm4154x->dev, "get state error!\n");
> + return IRQ_NONE;
> + }
> + sgm4154x->state = state;
> + if (state.vbus_gd) {
> + if (sgm4154x->init_data.ilim >= SGM4154X_DEFAULT_INPUT_CUR) {
> + ret = sgm4154x_set_input_curr_lim(sgm4154x, sgm4154x->init_data.ilim);
[Severity: High]
Will this unconditionally overwrite the input current limit configured
by userspace?
This checks if VBUS is good, but doesn't check if VBUS just transitioned
to being good.
Since it unconditionally applies init_data.ilim, won't any subsequent
interrupt while VBUS is present (such as a battery thermal event) silently
overwrite custom limits previously set via sysfs?
> + if (ret) {
> + dev_err(sgm4154x->dev, "set input current error!\n");
> + /* Reading IRQ clears interrupt, so return handled */
> + return IRQ_HANDLED;
> + }
> + }
> + }
> + power_supply_changed(sgm4154x->charger);
> +
> + return IRQ_HANDLED;
> +}
[Severity: Medium]
Should this early return be avoided so that power_supply_changed()
is still called?
If the I2C write for the input current limit fails, the code immediately
returns.
Will this cause userspace to silently miss important hardware state changes,
like cable insertion or fault conditions, that triggered the interrupt?
[ ... ]
> +static int sgm4154x_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct sgm4154x_device *sgm;
> + int ret;
> +
> + sgm = devm_kzalloc(dev, sizeof(*sgm), GFP_KERNEL);
> + if (!sgm)
> + return -ENOMEM;
> +
> + sgm->dev = dev;
> +
> + sgm->regmap = devm_regmap_init_i2c(client, &sgm4154x_regmap_config);
> + if (IS_ERR(sgm->regmap))
> + return dev_err_probe(dev, PTR_ERR(sgm->regmap),
> + "Failed to allocate register map\n");
> +
> + i2c_set_clientdata(client, sgm);
> +
> + ret = sgm4154x_hw_chipid_detect(sgm);
> + if (ret)
> + return dev_err_probe(dev, ret, "Unable to read HW ID\n");
> +
> + device_init_wakeup(dev, 1);
[Severity: Medium]
Is there a missing cleanup step for device_init_wakeup()?
The device is marked as wakeup-capable here, but there doesn't seem to be
a corresponding device_init_wakeup(dev, 0) during probe error paths or in
a driver remove callback.
Could this leave the system PM subsystem in an inconsistent state if the
driver unbinds or the probe fails later?
> +
> + sgm->sgm_monitor_wq = devm_alloc_ordered_workqueue(dev, "sgm-monitor-wq",
> + WQ_MEM_RECLAIM | WQ_FREEZABLE);
[Severity: High]
How is the hardware watchdog handled during system suspend?
The workqueue feeding the hardware watchdog is allocated with WQ_FREEZABLE,
meaning it stops feeding the watchdog during suspend.
[ ... ]
> +static struct i2c_driver sgm4154x_driver = {
> + .driver = {
> + .name = "sgm4154x-charger",
> + .of_match_table = sgm4154x_of_match,
> + },
> + .probe = sgm4154x_probe,
> + .id_table = sgm4154x_i2c_ids,
> +};
Because there are no PM suspend/resume handlers defined in the driver, the
hardware watchdog is never disabled before sleep.
If the system sleeps longer than the 40-second watchdog timeout, will the
charger IC expire the watchdog and reset all registers (such as charge
current and voltage limits) to their hardware defaults?
Upon resume, the frozen workqueue thaws and continues resetting the
watchdog, but the custom charging parameters are never re-applied.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260610144407.438846-1-macroalpha82@gmail.com?part=2
^ permalink raw reply
* Re: [PATCH V7 4/6] drm/panel: anbernic-td4310: Add RG Vita Pro panel
From: sashiko-bot @ 2026-06-10 14:58 UTC (permalink / raw)
To: Chris Morgan; +Cc: conor+dt, devicetree, robh
In-Reply-To: <20260610144407.438846-5-macroalpha82@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Incorrect power-down sequencing in the error path and unprepare function risks backpowering the panel.
- [Low] The driver calls `devm_drm_panel_add()`, which does not exist in the kernel API, causing a build failure.
--
commit ab0aaf4898e0640c3a73b98bb981eeb9023b2012
Author: Chris Morgan <macromorgan@hotmail.com>
drm/panel: anbernic-td4310: Add RG Vita Pro panel
The panel used by Anbernic in the RG Vita-Pro is a DSI panel based
on the TD4310 controller IC. It measures approximately 5.5 inches
diagonally and is 1080x1920 in resolution.
> diff --git a/drivers/gpu/drm/panel/panel-anbernic-td4310.c b/drivers/gpu/drm/panel/panel-anbernic-td4310.c
> new file mode 100644
> index 0000000000000..9a1b4525423c4
> --- /dev/null
> +++ b/drivers/gpu/drm/panel/panel-anbernic-td4310.c
[ ... ]
> +static int panel_anbernic_td4310_prepare(struct drm_panel *panel)
> +{
[ ... ]
> + ret = regulator_enable(ctx->vdd);
> + if (ret)
> + return ret;
> +
> + ret = gpiod_set_value_cansleep(ctx->enable_gpio, 1);
> + if (ret)
> + goto err_enable;
> +
> + if (ctx->panel_info->enable_delay)
> + mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->enable_delay);
> +
> + ret = gpiod_set_value_cansleep(ctx->reset_gpio, 1);
[ ... ]
> +err_reset:
> + gpiod_set_value_cansleep(ctx->enable_gpio, 0);
> +err_enable:
> + regulator_disable(ctx->vdd);
> + return ret;
> +}
[Severity: Medium]
Does this error path miss asserting the reset gpio before disabling the enable
gpio and the vdd regulator?
The power-up sequence enables vdd, asserts the enable gpio, and then cycles
the reset gpio. To prevent backpowering or latch-up, shouldn't the shutdown
sequence strictly reverse this order by asserting the reset gpio first?
> +
> +static int panel_anbernic_td4310_unprepare(struct drm_panel *panel)
> +{
> + struct anbernic_panel_td4310 *ctx = panel_to_anbernic_panel_td4310(panel);
> + struct mipi_dsi_device *dsi = ctx->dsi;
> + struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
> +
> + mipi_dsi_dcs_set_display_off_multi(&dsi_ctx);
> + mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->unprepare_delay);
> + mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx);
> + mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->disable_delay);
> +
> + gpiod_set_value_cansleep(ctx->enable_gpio, 0);
> +
> + gpiod_set_value_cansleep(ctx->reset_gpio, 1);
[Severity: Medium]
Will this sequence risk backpowering the panel?
By disabling the enable gpio before asserting the reset gpio, it violates the
reverse power-up sequence mentioned above. Should the reset gpio be asserted
first before turning off the enable gpio?
> +
> + regulator_disable(ctx->vdd);
> +
> + return 0;
> +}
[ ... ]
> +static int panel_anbernic_td4310_probe(struct mipi_dsi_device *dsi)
> +{
[ ... ]
> + dsi->lanes = ctx->panel_info->lanes;
> + dsi->format = ctx->panel_info->format;
> + dsi->mode_flags = ctx->panel_info->mode_flags;
> +
> + ret = drm_panel_of_backlight(&ctx->panel);
> + if (ret)
> + return ret;
> +
> + devm_drm_panel_add(dev, &ctx->panel);
[Severity: Low]
Is this call to devm_drm_panel_add() correct?
The function devm_drm_panel_add() doesn't appear to exist in the DRM panel
API (only drm_panel_add() does). Will this result in a build failure?
> +
> + ret = devm_mipi_dsi_attach(dev, dsi);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "Failed to attach to DSI host\n");
> +
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260610144407.438846-1-macroalpha82@gmail.com?part=4
^ permalink raw reply
* [PATCH V7 6/6] arm64: dts: rockchip: Add Anbernic RG Vita-Pro
From: Chris Morgan @ 2026-06-10 14:44 UTC (permalink / raw)
To: linux-rockchip
Cc: devicetree, xsf, sre, simona, airlied, tzimmermann, mripard,
maarten.lankhorst, jesszhan0024, neil.armstrong, heiko, conor+dt,
krzk+dt, robh, Chris Morgan
In-Reply-To: <20260610144407.438846-1-macroalpha82@gmail.com>
From: Chris Morgan <macromorgan@hotmail.com>
Add device tree for the Anbernic RG Vita-Pro, based on the Rockchip
RK3576. All hardware is currently working except for the accelerometer,
the touchscreen, the battery monitor, and DisplayPort over USB-C. PWM
is also missing currently, and as a result the LEDs and panel backlight
are currently controlled via GPIO but will be changed to PWM once
supported.
The Anbernic RG Vita-Pro includes the following hardware:
- A 1080x1920 DSI display with touch.
- 21 buttons.
- 3 LEDs.
- 64GB eMMC, 2 SDMMC slots.
- RTL8852BE WiFi (with Bluetooth via UART)
- 1 USB 3.0 USB-C port in OTG mode.
- 3.5mm headphone jack with play button support.
- 5000mAH battery
The following hardware has incomplete driver support and is not yet
working:
- An Invensense icm42607p accelerometer.
- A Cellwise cw221x battery monitor.
- A Synaptics i2c touchscreen.
- DisplayPort over USB-C alt-mode.
- PWM controller.
- 2 Analog joysticks with LED controllers.
Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
---
arch/arm64/boot/dts/rockchip/Makefile | 1 +
.../rockchip/rk3576-anbernic-rg-vita-pro.dts | 1327 +++++++++++++++++
2 files changed, 1328 insertions(+)
create mode 100644 arch/arm64/boot/dts/rockchip/rk3576-anbernic-rg-vita-pro.dts
diff --git a/arch/arm64/boot/dts/rockchip/Makefile b/arch/arm64/boot/dts/rockchip/Makefile
index cb55c6b70d0e..7d86676f98e8 100644
--- a/arch/arm64/boot/dts/rockchip/Makefile
+++ b/arch/arm64/boot/dts/rockchip/Makefile
@@ -165,6 +165,7 @@ dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3568-wolfvision-pf5.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3568-wolfvision-pf5-display-vz.dtbo
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3568-wolfvision-pf5-io-expander.dtbo
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-100ask-dshanpi-a1.dtb
+dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-anbernic-rg-vita-pro.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-armsom-sige5.dtb
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-armsom-sige5-v1.2-wifibt.dtbo
dtb-$(CONFIG_ARCH_ROCKCHIP) += rk3576-evb1-v10.dtb
diff --git a/arch/arm64/boot/dts/rockchip/rk3576-anbernic-rg-vita-pro.dts b/arch/arm64/boot/dts/rockchip/rk3576-anbernic-rg-vita-pro.dts
new file mode 100644
index 000000000000..344ba1870533
--- /dev/null
+++ b/arch/arm64/boot/dts/rockchip/rk3576-anbernic-rg-vita-pro.dts
@@ -0,0 +1,1327 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright (c) 2026 Chris Morgan <macromorgan@hotmail.com>
+ */
+
+/dts-v1/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/input/linux-event-codes.h>
+#include <dt-bindings/leds/common.h>
+#include <dt-bindings/pinctrl/rockchip.h>
+#include <dt-bindings/pwm/pwm.h>
+#include <dt-bindings/soc/rockchip,vop2.h>
+#include <dt-bindings/usb/pd.h>
+
+#include "rk3576.dtsi"
+
+/ {
+ model = "Anbernic RG Vita Pro";
+ chassis-type = "handset";
+ compatible = "anbernic,rg-vita-pro", "rockchip,rk3576";
+
+ aliases {
+ mmc0 = &sdhci;
+ mmc1 = &sdmmc;
+ mmc2 = &sdio;
+ };
+
+ chosen {
+ stdout-path = "serial0:1500000n8";
+ };
+
+ adc_keys_home: adc-keys-home {
+ compatible = "adc-keys";
+ io-channel-names = "buttons";
+ io-channels = <&saradc 1>;
+ keyup-threshold-microvolt = <1800000>;
+ poll-interval = <60>;
+
+ button-home {
+ label = "HOME";
+ linux,code = <KEY_HOME>;
+ press-threshold-microvolt = <1750>;
+ };
+ };
+
+ adc_keys_play: adc-keys-play {
+ compatible = "adc-keys";
+ io-channel-names = "buttons";
+ io-channels = <&saradc 3>;
+ keyup-threshold-microvolt = <1300000>;
+ poll-interval = <60>;
+
+ button-play {
+ label = "PLAY";
+ linux,code = <KEY_PLAYPAUSE>;
+ press-threshold-microvolt = <1750>;
+ };
+ };
+
+ /*
+ * Battery values from BSP except for the following:
+ *
+ * factory-internal-resistance-micro-ohms - This was set to 80 which
+ * is likely incorrect and changed to 80000.
+ *
+ * charge-full-design-microamp-hours - Was set to 8000000 but the
+ * physical battery says 5000000.
+ *
+ * constant-charge-current-max-microamp - Was set to 10000000 which
+ * both seems high and exceeds what the charger hardware can do.
+ * Setting to 3800000 which is the maximum input current the charger
+ * can handle.
+ */
+ battery: battery {
+ compatible = "simple-battery";
+ charge-full-design-microamp-hours = <5000000>;
+ charge-term-current-microamp = <300000>;
+ constant-charge-current-max-microamp = <3800000>;
+ constant-charge-voltage-max-microvolt = <4350000>;
+ factory-internal-resistance-micro-ohms = <80000>;
+ precharge-current-microamp = <180000>;
+ precharge-upper-limit-microvolt = <3600000>;
+ voltage-max-design-microvolt = <4350000>;
+ voltage-min-design-microvolt = <3000000>;
+ };
+
+ hp_amp: audio-amplifier {
+ compatible = "simple-audio-amplifier";
+ enable-gpios = <&gpio0 RK_PC3 GPIO_ACTIVE_HIGH>;
+ pinctrl-0 = <&hp_amp_enable_h>;
+ pinctrl-names = "default";
+ sound-name-prefix = "Headphone Amp";
+ };
+
+ /*
+ * LEDs and Backlights can be controlled as a PWM, but PWM
+ * driver is not yet available.
+ */
+ gpio_backlight: backlight {
+ compatible = "gpio-backlight";
+ gpios = <&gpio2 RK_PC4 GPIO_ACTIVE_HIGH>;
+ pinctrl-0 = <&led_backlight_h>;
+ pinctrl-names = "default";
+ };
+
+ gpio_leds: leds {
+ compatible = "gpio-leds";
+ pinctrl-0 = <&led_amber_h>, <&led_green_h>, <&led_red_h>;
+ pinctrl-names = "default";
+
+ charging-led {
+ color = <LED_COLOR_ID_AMBER>;
+ function = LED_FUNCTION_CHARGING;
+ gpios = <&gpio3 RK_PA4 GPIO_ACTIVE_HIGH>;
+ };
+
+ full-led {
+ color = <LED_COLOR_ID_GREEN>;
+ function = LED_FUNCTION_POWER;
+ gpios = <&gpio3 RK_PA5 GPIO_ACTIVE_HIGH>;
+ };
+
+ bat-low-led {
+ color = <LED_COLOR_ID_RED>;
+ function = LED_FUNCTION_ALARM;
+ gpios = <&gpio3 RK_PA6 GPIO_ACTIVE_HIGH>;
+ };
+ };
+
+ gpio_keys_control: gpio-keys-control {
+ compatible = "gpio-keys";
+ pinctrl-0 = <&gamepad_keys_l>;
+ pinctrl-names = "default";
+
+ button-a {
+ gpios = <&gpio3 RK_PB6 GPIO_ACTIVE_LOW>;
+ label = "EAST";
+ linux,code = <BTN_EAST>;
+ };
+
+ button-b {
+ gpios = <&gpio3 RK_PB7 GPIO_ACTIVE_LOW>;
+ label = "SOUTH";
+ linux,code = <BTN_SOUTH>;
+ };
+
+ button-down {
+ gpios = <&gpio3 RK_PB1 GPIO_ACTIVE_LOW>;
+ label = "DPAD-DOWN";
+ linux,code = <BTN_DPAD_DOWN>;
+ };
+
+ button-l1 {
+ gpios = <&gpio3 RK_PD7 GPIO_ACTIVE_LOW>;
+ label = "TL";
+ linux,code = <BTN_TL>;
+ };
+
+ button-l2 {
+ gpios = <&gpio3 RK_PC1 GPIO_ACTIVE_LOW>;
+ label = "TL2";
+ linux,code = <BTN_TL2>;
+ };
+
+ button-left {
+ gpios = <&gpio3 RK_PB2 GPIO_ACTIVE_LOW>;
+ label = "DPAD-LEFT";
+ linux,code = <BTN_DPAD_LEFT>;
+ };
+
+ button-menu {
+ gpios = <&gpio3 RK_PA7 GPIO_ACTIVE_LOW>;
+ label = "MENU";
+ linux,code = <BTN_MODE>;
+ };
+
+ button-right {
+ gpios = <&gpio3 RK_PB3 GPIO_ACTIVE_LOW>;
+ label = "DPAD-RIGHT";
+ linux,code = <BTN_DPAD_RIGHT>;
+ };
+
+ button-r1 {
+ gpios = <&gpio3 RK_PC2 GPIO_ACTIVE_LOW>;
+ label = "T2";
+ linux,code = <BTN_TR>;
+ };
+
+ button-r2 {
+ gpios = <&gpio3 RK_PC3 GPIO_ACTIVE_LOW>;
+ label = "TR2";
+ linux,code = <BTN_TR2>;
+ };
+
+ button-select {
+ gpios = <&gpio3 RK_PD6 GPIO_ACTIVE_LOW>;
+ label = "SELECT";
+ linux,code = <BTN_SELECT>;
+ };
+
+ button-start {
+ gpios = <&gpio3 RK_PD3 GPIO_ACTIVE_LOW>;
+ label = "START";
+ linux,code = <BTN_START>;
+ };
+
+ button-thumbl {
+ gpios = <&gpio3 RK_PC0 GPIO_ACTIVE_LOW>;
+ label = "THUMBL";
+ linux,code = <BTN_THUMBL>;
+ };
+
+ button-thumbr {
+ gpios = <&gpio3 RK_PC4 GPIO_ACTIVE_LOW>;
+ label = "THUMBR";
+ linux,code = <BTN_THUMBR>;
+ };
+
+ button-up {
+ gpios = <&gpio3 RK_PB0 GPIO_ACTIVE_LOW>;
+ label = "DPAD-UP";
+ linux,code = <BTN_DPAD_UP>;
+ };
+
+ button-x {
+ gpios = <&gpio3 RK_PB4 GPIO_ACTIVE_LOW>;
+ label = "NORTH";
+ linux,code = <BTN_NORTH>;
+ };
+
+ button-y {
+ gpios = <&gpio3 RK_PB5 GPIO_ACTIVE_LOW>;
+ label = "WEST";
+ linux,code = <BTN_WEST>;
+ };
+ };
+
+ gpio_keys_volume: gpio-keys-volume {
+ compatible = "gpio-keys";
+ autorepeat;
+ pinctrl-0 = <&vol_keys_l>;
+ pinctrl-names = "default";
+
+ vol-down-key {
+ gpios = <&gpio3 RK_PD1 GPIO_ACTIVE_LOW>;
+ label = "VOLUMEDOWN";
+ linux,code = <KEY_VOLUMEDOWN>;
+ };
+
+ vol-up-key {
+ gpios = <&gpio3 RK_PD2 GPIO_ACTIVE_LOW>;
+ label = "VOLUMEUP";
+ linux,code = <KEY_VOLUMEUP>;
+ };
+ };
+
+ hdmi-con {
+ compatible = "hdmi-connector";
+ type = "c";
+
+ port {
+ hdmi_con_in: endpoint {
+ remote-endpoint = <&hdmi_out_con>;
+ };
+ };
+ };
+
+ rfkill {
+ compatible = "rfkill-gpio";
+ pinctrl-names = "default";
+ pinctrl-0 = <&wifi_en_h>;
+ radio-type = "wlan";
+ shutdown-gpios = <&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>;
+ };
+
+ sound: sound {
+ compatible = "simple-audio-card";
+ pinctrl-0 = <&hp_det>;
+ pinctrl-names = "default";
+ simple-audio-card,aux-devs = <&aw87391_pa_l>, <&aw87391_pa_r>,
+ <&hp_amp>;
+ simple-audio-card,format = "i2s";
+ simple-audio-card,hp-det-gpios = <&gpio4 RK_PA4 GPIO_ACTIVE_HIGH>;
+ simple-audio-card,mclk-fs = <256>;
+ simple-audio-card,name = "rockchip,es8388-codec";
+ simple-audio-card,pin-switches = "Headphones", "Internal Speakers";
+ simple-audio-card,routing =
+ "LINPUT1", "Mic Jack",
+ "LINPUT2", "Mic Jack",
+ "RINPUT1", "Onboard Microphone",
+ "RINPUT2", "Onboard Microphone",
+ "Headphones", "Headphone Amp OUTL",
+ "Headphones", "Headphone Amp OUTR",
+ "Headphone Amp INL", "LOUT1",
+ "Headphone Amp INR", "ROUT1",
+ "Internal Speakers", "Left Amp OUT",
+ "Internal Speakers", "Right Amp OUT",
+ "Left Amp IN", "LOUT2",
+ "Right Amp IN", "ROUT2";
+ simple-audio-card,widgets =
+ "Microphone", "Mic Jack",
+ "Microphone", "Onboard Microphone",
+ "Headphone", "Headphones",
+ "Speaker", "Internal Speakers";
+ status = "okay";
+
+ simple-audio-card,codec {
+ sound-dai = <&es8388>;
+ system-clock-frequency = <12288000>;
+ };
+
+ simple-audio-card,cpu {
+ sound-dai = <&sai1>;
+ };
+ };
+
+ vcc_1v1_nldo_s3: regulator-vcc-1v1-nldo-s3 {
+ compatible = "regulator-fixed";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-max-microvolt = <1100000>;
+ regulator-min-microvolt = <1100000>;
+ regulator-name = "vcc_1v1_nldo_s3";
+ vin-supply = <&vcc_3v8_sys>;
+ };
+
+ vcc_2v0_pldo_s3: regulator-vcc-2v0-pldo-s3 {
+ compatible = "regulator-fixed";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-max-microvolt = <2000000>;
+ regulator-min-microvolt = <2000000>;
+ regulator-name = "vcc_2v0_pldo_s3";
+ vin-supply = <&vcc_3v8_sys>;
+ };
+
+ vcc_3v8_sys: regulator-vcc-3v8-sys {
+ compatible = "regulator-fixed";
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-max-microvolt = <3800000>;
+ regulator-min-microvolt = <3800000>;
+ regulator-name = "vcc_3v8_sys";
+ };
+
+ vcc3v3_sd_s0: regulator-vcc3v3-sd-s0 {
+ compatible = "regulator-fixed";
+ enable-active-high;
+ gpio = <&gpio0 RK_PB6 GPIO_ACTIVE_HIGH>;
+ pinctrl-0 = <&vcc_3v3_sd_s0_h>;
+ pinctrl-names = "default";
+ regulator-max-microvolt = <3300000>;
+ regulator-min-microvolt = <3300000>;
+ regulator-name = "vcc3v3_sd_s0";
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vcc_sdio: regulator-vcc-sdio {
+ compatible = "regulator-gpio";
+ regulator-name = "vcc_sdio";
+ gpios = <&gpio0 RK_PB4 GPIO_ACTIVE_HIGH>;
+ pinctrl-0 = <&vcc_sdio_h>;
+ pinctrl-names = "default";
+ regulator-max-microvolt = <3300000>;
+ regulator-min-microvolt = <1800000>;
+ states = <1800000 0>, <3300000 1>;
+ };
+
+ vcc_wifi: regulator-vcc-wifi {
+ compatible = "regulator-fixed";
+ regulator-name = "vcc_wifi";
+ enable-active-high;
+ gpios = <&gpio1 RK_PD2 GPIO_ACTIVE_HIGH>;
+ pinctrl-0 = <&vcc_wifi_h>;
+ pinctrl-names = "default";
+ regulator-max-microvolt = <3300000>;
+ regulator-min-microvolt = <3300000>;
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ };
+ };
+
+ vdd_lcd: regulator-vdd-lcd {
+ compatible = "regulator-fixed";
+ regulator-name = "vdd_lcd";
+ regulator-always-on;
+ enable-active-high;
+ gpios = <&gpio0 RK_PB0 GPIO_ACTIVE_HIGH>;
+ pinctrl-0 = <&vdd_lcd_h>;
+ pinctrl-names = "default";
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+};
+
+&combphy0_ps {
+ status = "okay";
+};
+
+&cpu_b0 {
+ cpu-supply = <&vdd_cpu_big_s0>;
+};
+
+&cpu_b1 {
+ cpu-supply = <&vdd_cpu_big_s0>;
+};
+
+&cpu_b2 {
+ cpu-supply = <&vdd_cpu_big_s0>;
+};
+
+&cpu_b3 {
+ cpu-supply = <&vdd_cpu_big_s0>;
+};
+
+&cpu_l0 {
+ cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&cpu_l1 {
+ cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&cpu_l2 {
+ cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&cpu_l3 {
+ cpu-supply = <&vdd_cpu_lit_s0>;
+};
+
+&dsi {
+ status = "okay";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ panel: panel@0 {
+ compatible = "anbernic,panel-vita-pro", "anbernic,td4310";
+ reg = <0>;
+ backlight = <&gpio_backlight>;
+ enable-gpios = <&gpio0 RK_PB1 GPIO_ACTIVE_HIGH>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&lcd_enable>, <&lcd_rst>;
+ reset-gpios = <&gpio0 RK_PB5 GPIO_ACTIVE_LOW>;
+ rotation = <270>;
+ vdd-supply = <&vdd_lcd>;
+
+ port {
+ mipi_in_panel: endpoint {
+ remote-endpoint = <&mipi_out_panel>;
+ };
+ };
+ };
+};
+
+&dsi_in {
+ dsi_in_vp1: endpoint {
+ remote-endpoint = <&vp1_out_dsi>;
+ };
+};
+
+&dsi_out {
+ mipi_out_panel: endpoint {
+ remote-endpoint = <&mipi_in_panel>;
+ };
+};
+
+&gpu {
+ mali-supply = <&vdd_gpu_s0>;
+ status = "okay";
+};
+
+&hdmi {
+ status = "okay";
+};
+
+&hdmi_in {
+ hdmi_in_vp0: endpoint {
+ remote-endpoint = <&vp0_out_hdmi>;
+ };
+};
+
+&hdmi_out {
+ hdmi_out_con: endpoint {
+ remote-endpoint = <&hdmi_con_in>;
+ };
+};
+
+&hdmi_sound {
+ status = "okay";
+};
+
+&hdptxphy {
+ status = "okay";
+};
+
+&i2c0 {
+ pinctrl-0 = <&i2c0m1_xfer>;
+ pinctrl-names = "default";
+ status = "okay";
+
+ /* synaptics,dsx-i2c touchscreen at 0x70 */
+};
+
+&i2c1 {
+ status = "okay";
+
+ pmic@23 {
+ compatible = "rockchip,rk806";
+ reg = <0x23>;
+ #gpio-cells = <2>;
+ gpio-controller;
+ interrupt-parent = <&gpio0>;
+ interrupts = <RK_PA6 IRQ_TYPE_LEVEL_LOW>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pmic_pins
+ &rk806_dvs1_null
+ &rk806_dvs2_null
+ &rk806_dvs3_null>;
+ system-power-controller;
+ vcc1-supply = <&vcc_3v8_sys>;
+ vcc2-supply = <&vcc_3v8_sys>;
+ vcc3-supply = <&vcc_3v8_sys>;
+ vcc4-supply = <&vcc_3v8_sys>;
+ vcc5-supply = <&vcc_3v8_sys>;
+ vcc6-supply = <&vcc_3v8_sys>;
+ vcc7-supply = <&vcc_3v8_sys>;
+ vcc8-supply = <&vcc_3v8_sys>;
+ vcc9-supply = <&vcc_3v8_sys>;
+ vcc10-supply = <&vcc_3v8_sys>;
+ vcc11-supply = <&vcc_2v0_pldo_s3>;
+ vcc12-supply = <&vcc_3v8_sys>;
+ vcc13-supply = <&vcc_1v1_nldo_s3>;
+ vcc14-supply = <&vcc_1v1_nldo_s3>;
+ vcca-supply = <&vcc_3v8_sys>;
+
+ rk806_dvs1_null: dvs1-null-pins {
+ pins = "gpio_pwrctrl1";
+ function = "pin_fun0";
+ };
+
+ rk806_dvs1_pwrdn: dvs1-pwrdn-pins {
+ pins = "gpio_pwrctrl1";
+ function = "pin_fun2";
+ };
+
+ rk806_dvs1_rst: dvs1-rst-pins {
+ pins = "gpio_pwrctrl1";
+ function = "pin_fun3";
+ };
+
+ rk806_dvs1_slp: dvs1-slp-pins {
+ pins = "gpio_pwrctrl1";
+ function = "pin_fun1";
+ };
+
+ rk806_dvs2_dvs: dvs2-dvs-pins {
+ pins = "gpio_pwrctrl2";
+ function = "pin_fun4";
+ };
+
+ rk806_dvs2_gpio: dvs2-gpio-pins {
+ pins = "gpio_pwrctrl2";
+ function = "pin_fun5";
+ };
+
+ rk806_dvs2_null: dvs2-null-pins {
+ pins = "gpio_pwrctrl2";
+ function = "pin_fun0";
+ };
+
+ rk806_dvs2_pwrdn: dvs2-pwrdn-pins {
+ pins = "gpio_pwrctrl2";
+ function = "pin_fun2";
+ };
+
+ rk806_dvs2_rst: dvs2-rst-pins {
+ pins = "gpio_pwrctrl2";
+ function = "pin_fun3";
+ };
+
+ rk806_dvs2_slp: dvs2-slp-pins {
+ pins = "gpio_pwrctrl2";
+ function = "pin_fun1";
+ };
+
+ rk806_dvs3_dvs: dvs3-dvs-pins {
+ pins = "gpio_pwrctrl3";
+ function = "pin_fun4";
+ };
+
+ rk806_dvs3_gpio: dvs3-gpio-pins {
+ pins = "gpio_pwrctrl3";
+ function = "pin_fun5";
+ };
+
+ rk806_dvs3_null: dvs3-null-pins {
+ pins = "gpio_pwrctrl3";
+ function = "pin_fun0";
+ };
+
+ rk806_dvs3_pwrdn: dvs3-pwrdn-pins {
+ pins = "gpio_pwrctrl3";
+ function = "pin_fun2";
+ };
+
+ rk806_dvs3_rst: dvs3-rst-pins {
+ pins = "gpio_pwrctrl3";
+ function = "pin_fun3";
+ };
+
+ rk806_dvs3_slp: dvs3-slp-pins {
+ pins = "gpio_pwrctrl3";
+ function = "pin_fun1";
+ };
+
+ regulators {
+ vdd_cpu_big_s0: dcdc-reg1 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-enable-ramp-delay = <400>;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <950000>;
+ regulator-name = "vdd_cpu_big_s0";
+ regulator-ramp-delay = <12500>;
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_npu_s0: dcdc-reg2 {
+ regulator-boot-on;
+ regulator-enable-ramp-delay = <400>;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <950000>;
+ regulator-name = "vdd_npu_s0";
+ regulator-ramp-delay = <12500>;
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_cpu_lit_s0: dcdc-reg3 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <950000>;
+ regulator-name = "vdd_cpu_lit_s0";
+ regulator-ramp-delay = <12500>;
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ regulator-suspend-microvolt = <750000>;
+ };
+ };
+
+ vcc_3v3_s3: dcdc-reg4 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-name = "vcc_3v3_s3";
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <3300000>;
+ };
+ };
+
+ vdd_gpu_s0: dcdc-reg5 {
+ regulator-boot-on;
+ regulator-enable-ramp-delay = <400>;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <900000>;
+ regulator-name = "vdd_gpu_s0";
+ regulator-ramp-delay = <12500>;
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ regulator-suspend-microvolt = <850000>;
+ };
+ };
+
+ vddq_ddr_s0: dcdc-reg6 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-name = "vddq_ddr_s0";
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdd_logic_s0: dcdc-reg7 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <800000>;
+ regulator-name = "vdd_logic_s0";
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vcc_1v8_s3: dcdc-reg8 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-name = "vcc_1v8_s3";
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <1800000>;
+ };
+ };
+
+ vdd2_ddr_s3: dcdc-reg9 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-name = "vdd2_ddr_s3";
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ };
+ };
+
+ vdd_ddr_s0: dcdc-reg10 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <550000>;
+ regulator-max-microvolt = <1200000>;
+ regulator-name = "vdd_ddr_s0";
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vcca_1v8_s0: pldo-reg1 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-name = "vcca_1v8_s0";
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vcca1v8_pldo2_s0: pldo-reg2 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-name = "vcca1v8_pldo2_s0";
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdda_1v2_s0: pldo-reg3 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1200000>;
+ regulator-max-microvolt = <1200000>;
+ regulator-name = "vdda_1v2_s0";
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vcca_3v3_s0: pldo-reg4 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-name = "vcca_3v3_s0";
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vccio_sd_s0: pldo-reg5 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-name = "vccio_sd_s0";
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ };
+ };
+
+ vcca1v8_pldo6_s3: pldo-reg6 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-name = "vcca1v8_pldo6_s3";
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <1800000>;
+ };
+ };
+
+ vdd_0v75_s3: nldo-reg1 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <750000>;
+ regulator-max-microvolt = <750000>;
+ regulator-name = "vdd_0v75_s3";
+ regulator-state-mem {
+ regulator-on-in-suspend;
+ regulator-suspend-microvolt = <750000>;
+ };
+ };
+
+ vdda_ddr_pll_s0: nldo-reg2 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <850000>;
+ regulator-max-microvolt = <850000>;
+ regulator-name = "vdda_ddr_pll_s0";
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdda0v75_hdmi_s0: nldo-reg3 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <750000>;
+ regulator-max-microvolt = <750000>;
+ regulator-name = "vdda0v75_hdmi_s0";
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdda_0v85_s0: nldo-reg4 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <850000>;
+ regulator-max-microvolt = <850000>;
+ regulator-name = "vdda_0v85_s0";
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+
+ vdda_0v75_s0: nldo-reg5 {
+ regulator-always-on;
+ regulator-boot-on;
+ regulator-min-microvolt = <750000>;
+ regulator-max-microvolt = <750000>;
+ regulator-name = "vdda_0v75_s0";
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+ };
+ };
+};
+
+&i2c2 {
+ status = "okay";
+
+ /* inv,icm42607 IMU at 0x68 */
+};
+
+&i2c3 {
+ status = "okay";
+
+ es8388: audio-codec@10 {
+ compatible = "everest,es8388", "everest,es8328";
+ reg = <0x10>;
+ assigned-clock-rates = <12288000>;
+ assigned-clocks = <&cru CLK_SAI1_MCLKOUT_TO_IO>;
+ AVDD-supply = <&vcca_3v3_s0>;
+ clocks = <&cru CLK_SAI1_MCLKOUT_TO_IO>;
+ DVDD-supply = <&vcc_3v3_s3>;
+ HPVDD-supply = <&vcca_3v3_s0>;
+ pinctrl-0 = <&sai1m0_mclk>;
+ pinctrl-names = "default";
+ PVDD-supply = <&vcc_3v3_s3>;
+ #sound-dai-cells = <0>;
+ };
+
+ aw87391_pa_l: audio-codec@58 {
+ compatible = "anbernic,rgds-amp", "awinic,aw87391";
+ reg = <0x58>;
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "Left Amp";
+ };
+
+ aw87391_pa_r: audio-codec@5b {
+ compatible = "anbernic,rgds-amp", "awinic,aw87391";
+ reg = <0x5b>;
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "Right Amp";
+ };
+};
+
+&i2c6 {
+ pinctrl-0 = <&i2c6m3_xfer>;
+ pinctrl-names = "default";
+ status = "okay";
+
+ sgm41542: charger@3b {
+ compatible = "sgmicro,sgm41542";
+ reg = <0x3b>;
+ input-current-limit-microamp = <3000000>;
+ input-voltage-limit-microvolt = <4500000>;
+ interrupt-parent = <&gpio0>;
+ interrupts = <RK_PD2 IRQ_TYPE_EDGE_FALLING>;
+ pinctrl-0 = <&charger_irq>;
+ pinctrl-names = "default";
+ monitored-battery = <&battery>;
+ status = "okay";
+
+ regulators {
+ vbus5v0_typec: otg-vbus {
+ regulator-name = "vbus5v0_typec";
+ regulator-max-microvolt = <5000000>;
+ regulator-min-microvolt = <5000000>;
+
+ regulator-state-mem {
+ regulator-off-in-suspend;
+ };
+ };
+ };
+ };
+
+ /* Unused iSmartWare SW2001 encryption device at 0x3c */
+
+ husb311: typec-portc@4e {
+ compatible = "hynetek,husb311", "richtek,rt1711h";
+ reg = <0x4e>;
+ interrupt-parent = <&gpio0>;
+ interrupts = <RK_PD1 IRQ_TYPE_LEVEL_LOW>;
+ pinctrl-0 = <&usbc_int>;
+ pinctrl-names = "default";
+ status = "okay";
+ vbus-supply = <&vbus5v0_typec>;
+
+ connector {
+ compatible = "usb-c-connector";
+ label = "USB-C";
+ data-role = "dual";
+ op-sink-microwatt = <10000000>;
+ power-role = "dual";
+ try-power-role = "sink";
+
+ sink-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)
+ PDO_FIXED(9000, 2000, PDO_FIXED_USB_COMM)>;
+ source-pdos = <PDO_FIXED(5000, 3000, PDO_FIXED_USB_COMM)>;
+
+ altmodes {
+ displayport {
+ svid = /bits/ 16 <0xff01>;
+ vdo = <0xffffffff>;
+ };
+ };
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ usbc0_hs_ep: endpoint {
+ remote-endpoint = <&usb_drd0_hs_ep>;
+ };
+ };
+ port@1 {
+ reg = <1>;
+ usbc0_ss_ep: endpoint {
+ remote-endpoint = <&usb_drd0_ss_ep>;
+ };
+ };
+ port@2 {
+ reg = <2>;
+ usbc0_dp_ep: endpoint {
+ remote-endpoint = <&usbdp_phy_ep>;
+ };
+ };
+ };
+ };
+ };
+
+ hym8563: rtc@51 {
+ compatible = "haoyu,hym8563";
+ reg = <0x51>;
+ #clock-cells = <0>;
+ clock-output-names = "hym8563";
+ interrupt-parent = <&gpio0>;
+ interrupts = <RK_PA0 IRQ_TYPE_LEVEL_LOW>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&hym8563_int>;
+ wakeup-source;
+ };
+
+ /* cellwise,cw221X battery manager at 0x64 */
+};
+
+&mipidcphy {
+ status = "okay";
+};
+
+&pcie0 {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pcie0_perstn>;
+ reset-gpios = <&gpio2 RK_PB4 GPIO_ACTIVE_HIGH>;
+ vpcie3v3-supply = <&vcc_wifi>;
+ status = "okay";
+};
+
+&pinctrl {
+ audio {
+ hp_amp_enable_h: hp-amp-enable {
+ rockchip,pins = <0 RK_PC3 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+
+ hp_det: hp-det {
+ rockchip,pins = <4 RK_PA4 RK_FUNC_GPIO &pcfg_pull_up>;
+ };
+ };
+
+ bluetooth {
+ bt_reset_gpio: bt-reset-pin {
+ rockchip,pins = <2 RK_PB5 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+
+ bt_wake_gpio: bt-wake-pin {
+ rockchip,pins = <2 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+
+ bt_wake_host_irq: bt-wake-host-irq {
+ rockchip,pins = <2 RK_PC1 RK_FUNC_GPIO &pcfg_pull_up>;
+ };
+ };
+
+ charger {
+ charger_irq: charger-irq {
+ rockchip,pins = <0 RK_PD2 RK_FUNC_GPIO &pcfg_pull_up>;
+ };
+ };
+
+ display {
+ lcd_enable: lcd-enable {
+ rockchip,pins = <0 RK_PB1 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+
+ lcd_rst: lcd-rst {
+ rockchip,pins = <0 RK_PB5 RK_FUNC_GPIO &pcfg_pull_down>;
+ };
+ };
+
+ gpio-keys {
+ vol_keys_l: vol-keys_l {
+ rockchip,pins =
+ <3 RK_PD1 RK_FUNC_GPIO &pcfg_pull_up>,
+ <3 RK_PD2 RK_FUNC_GPIO &pcfg_pull_up>;
+ };
+
+ gamepad_keys_l: gamepad-keys-l {
+ rockchip,pins =
+ <3 RK_PA7 RK_FUNC_GPIO &pcfg_pull_up>,
+ <3 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>,
+ <3 RK_PB1 RK_FUNC_GPIO &pcfg_pull_up>,
+ <3 RK_PB2 RK_FUNC_GPIO &pcfg_pull_up>,
+ <3 RK_PB3 RK_FUNC_GPIO &pcfg_pull_up>,
+ <3 RK_PB4 RK_FUNC_GPIO &pcfg_pull_up>,
+ <3 RK_PB5 RK_FUNC_GPIO &pcfg_pull_up>,
+ <3 RK_PB6 RK_FUNC_GPIO &pcfg_pull_up>,
+ <3 RK_PB7 RK_FUNC_GPIO &pcfg_pull_up>,
+ <3 RK_PC0 RK_FUNC_GPIO &pcfg_pull_up>,
+ <3 RK_PC1 RK_FUNC_GPIO &pcfg_pull_up>,
+ <3 RK_PC2 RK_FUNC_GPIO &pcfg_pull_up>,
+ <3 RK_PC3 RK_FUNC_GPIO &pcfg_pull_up>,
+ <3 RK_PC4 RK_FUNC_GPIO &pcfg_pull_up>,
+ <3 RK_PD3 RK_FUNC_GPIO &pcfg_pull_up>,
+ <3 RK_PD6 RK_FUNC_GPIO &pcfg_pull_up>,
+ <3 RK_PD7 RK_FUNC_GPIO &pcfg_pull_up>;
+ };
+ };
+
+ gpio-leds {
+ led_amber_h: led-amber-h {
+ rockchip,pins = <3 RK_PA4 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+
+ led_backlight_h: led-backlight-h {
+ rockchip,pins = <2 RK_PC4 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+
+ led_green_h: led-green-h {
+ rockchip,pins = <3 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+
+ led_red_h: led-red-h {
+ rockchip,pins = <3 RK_PA6 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+ };
+
+ pcie {
+ pcie0_perstn: pcie0-perstn {
+ rockchip,pins = <2 RK_PB4 RK_FUNC_GPIO &pcfg_pull_up>;
+ };
+ };
+
+ regulator {
+ vcc_3v3_sd_s0_h: vcc-3v3-sd-s0-h {
+ rockchip,pins = <0 RK_PB6 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+
+ vcc_sdio_h: vcc-sdio-h {
+ rockchip,pins = <0 RK_PB4 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+
+ vcc_wifi_h: vcc-wifi-h {
+ rockchip,pins = <1 RK_PD2 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+
+ vdd_lcd_h: vdd-lcd-h {
+ rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+ };
+
+ rtc {
+ hym8563_int: hym8563-int {
+ rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_up>;
+ };
+ };
+
+ touch {
+ touch_int: touch-int {
+ rockchip,pins = <0 RK_PC5 RK_FUNC_GPIO &pcfg_pull_up>;
+ };
+ };
+
+ usb {
+ usbc_int: usbc-int {
+ rockchip,pins = <0 RK_PD1 RK_FUNC_GPIO &pcfg_pull_up>;
+ };
+
+ usbc0_sbu1: usbc0-sbu1 {
+ rockchip,pins = <4 RK_PB6 RK_FUNC_GPIO &pcfg_pull_down>;
+ };
+
+ usbc0_sbu2: usbc0-sbu2 {
+ rockchip,pins = <4 RK_PB7 RK_FUNC_GPIO &pcfg_pull_down>;
+ };
+ };
+
+ wifi {
+ wifi_en_h: wifi-en-h {
+ rockchip,pins = <0 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+ };
+};
+
+&sai1 {
+ pinctrl-0 = <&sai1m0_lrck
+ &sai1m0_sclk
+ &sai1m0_sdi0
+ &sai1m0_sdo0>;
+ pinctrl-names = "default";
+ status = "okay";
+};
+
+&sai6 {
+ status = "okay";
+};
+
+&saradc {
+ vref-supply = <&vcca1v8_pldo2_s0>;
+ status = "okay";
+};
+
+&sdio {
+ bus-width = <4>;
+ cap-mmc-highspeed;
+ cap-sd-highspeed;
+ disable-wp;
+ max-frequency = <150000000>;
+ no-mmc;
+ no-sdio;
+ pinctrl-0 = <&sdmmc1m0_bus4>, <&sdmmc1m0_clk>,
+ <&sdmmc1m0_cmd>, <&sdmmc1m0_det>;
+ pinctrl-names = "default";
+ sd-uhs-sdr12;
+ sd-uhs-sdr25;
+ sd-uhs-sdr50;
+ sd-uhs-sdr104;
+ vmmc-supply = <&vcc3v3_sd_s0>;
+ vqmmc-supply = <&vcc_sdio>;
+ status = "okay";
+};
+
+&sdhci {
+ bus-width = <8>;
+ full-pwr-cycle-in-suspend;
+ no-sd;
+ no-sdio;
+ non-removable;
+ mmc-hs400-1_8v;
+ mmc-hs400-enhanced-strobe;
+ status = "okay";
+};
+
+&sdmmc {
+ bus-width = <4>;
+ cap-mmc-highspeed;
+ cap-sd-highspeed;
+ disable-wp;
+ max-frequency = <150000000>;
+ no-sdio;
+ no-mmc;
+ pinctrl-0 = <&sdmmc0_bus4>, <&sdmmc0_clk>,
+ <&sdmmc0_cmd>, <&sdmmc0_det>;
+ pinctrl-names = "default";
+ sd-uhs-sdr12;
+ sd-uhs-sdr25;
+ sd-uhs-sdr50;
+ sd-uhs-sdr104;
+ vmmc-supply = <&vcc3v3_sd_s0>;
+ vqmmc-supply = <&vccio_sd_s0>;
+ status = "okay";
+};
+
+&spi1 {
+ num-cs = <1>;
+ pinctrl-0 = <&spi1m2_pins>, <&spi1m2_csn0>;
+ status = "okay";
+
+ /* SPI controlled MCU for joystick and joystick LEDs. */
+};
+
+&vop {
+ status = "okay";
+};
+
+&vop_mmu {
+ status = "okay";
+};
+
+&vp0 {
+ vp0_out_hdmi: endpoint@ROCKCHIP_VOP2_EP_HDMI0 {
+ reg = <ROCKCHIP_VOP2_EP_HDMI0>;
+ remote-endpoint = <&hdmi_in_vp0>;
+ };
+};
+
+&vp1 {
+ vp1_out_dsi: endpoint@ROCKCHIP_VOP2_EP_MIPI0 {
+ reg = <ROCKCHIP_VOP2_EP_MIPI0>;
+ remote-endpoint = <&dsi_in_vp1>;
+ };
+};
+
+&u2phy0 {
+ status = "okay";
+};
+
+&u2phy0_otg {
+ status = "okay";
+};
+
+&uart0 {
+ status = "okay";
+};
+
+&uart6 {
+ pinctrl-0 = <&uart6m1_xfer>, <&uart6m1_ctsn>, <&uart6m1_rtsn>;
+ pinctrl-names = "default";
+ uart-has-rtscts;
+ status = "okay";
+
+ bluetooth {
+ compatible = "realtek,rtl8822cs-bt";
+ device-wake-gpios = <&gpio2 RK_PB6 GPIO_ACTIVE_HIGH>;
+ enable-gpios = <&gpio2 RK_PB5 GPIO_ACTIVE_HIGH>;
+ host-wake-gpios = <&gpio2 RK_PC1 GPIO_ACTIVE_HIGH>;
+ pinctrl-0 = <&bt_reset_gpio>, <&bt_wake_gpio>, <&bt_wake_host_irq>;
+ pinctrl-names = "default";
+ };
+};
+
+&usbdp_phy {
+ mode-switch;
+ orientation-switch;
+ pinctrl-names = "default";
+ pinctrl-0 = <&usbc0_sbu1 &usbc0_sbu2>;
+ sbu1-dc-gpios = <&gpio4 RK_PB6 GPIO_ACTIVE_HIGH>;
+ sbu2-dc-gpios = <&gpio4 RK_PB7 GPIO_ACTIVE_HIGH>;
+ status = "okay";
+
+ port {
+ usbdp_phy_ep: endpoint {
+ remote-endpoint = <&usbc0_dp_ep>;
+ };
+ };
+};
+
+&usb_drd0_dwc3 {
+ usb-role-switch;
+ dr_mode = "otg";
+ status = "okay";
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ usb_drd0_hs_ep: endpoint {
+ remote-endpoint = <&usbc0_hs_ep>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ usb_drd0_ss_ep: endpoint {
+ remote-endpoint = <&usbc0_ss_ep>;
+ };
+ };
+ };
+};
--
2.43.0
^ permalink raw reply related
* [PATCH V7 5/6] dt-bindings: arm: rockchip: Add Anbernic RG Vita-Pro
From: Chris Morgan @ 2026-06-10 14:44 UTC (permalink / raw)
To: linux-rockchip
Cc: devicetree, xsf, sre, simona, airlied, tzimmermann, mripard,
maarten.lankhorst, jesszhan0024, neil.armstrong, heiko, conor+dt,
krzk+dt, robh, Chris Morgan, Krzysztof Kozlowski
In-Reply-To: <20260610144407.438846-1-macroalpha82@gmail.com>
From: Chris Morgan <macromorgan@hotmail.com>
Add compatible string for the Anbernic RG Vita-Pro.
Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
Documentation/devicetree/bindings/arm/rockchip.yaml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Documentation/devicetree/bindings/arm/rockchip.yaml b/Documentation/devicetree/bindings/arm/rockchip.yaml
index 1a9dde18626d..b8babe9d3a26 100644
--- a/Documentation/devicetree/bindings/arm/rockchip.yaml
+++ b/Documentation/devicetree/bindings/arm/rockchip.yaml
@@ -66,6 +66,12 @@ properties:
- anbernic,rg-ds
- const: rockchip,rk3568
+ - description: Anbernic RK3576 Handheld Gaming Console
+ items:
+ - enum:
+ - anbernic,rg-vita-pro
+ - const: rockchip,rk3576
+
- description: Ariaboard Photonicat
items:
- const: ariaboard,photonicat
--
2.43.0
^ permalink raw reply related
* [PATCH V7 4/6] drm/panel: anbernic-td4310: Add RG Vita Pro panel
From: Chris Morgan @ 2026-06-10 14:44 UTC (permalink / raw)
To: linux-rockchip
Cc: devicetree, xsf, sre, simona, airlied, tzimmermann, mripard,
maarten.lankhorst, jesszhan0024, neil.armstrong, heiko, conor+dt,
krzk+dt, robh, Chris Morgan
In-Reply-To: <20260610144407.438846-1-macroalpha82@gmail.com>
From: Chris Morgan <macromorgan@hotmail.com>
The panel used by Anbernic in the RG Vita-Pro is a DSI panel based
on the TD4310 controller IC. It measures approximately 5.5 inches
diagonally and is 1080x1920 in resolution.
Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
---
drivers/gpu/drm/panel/Kconfig | 10 +
drivers/gpu/drm/panel/Makefile | 1 +
drivers/gpu/drm/panel/panel-anbernic-td4310.c | 257 ++++++++++++++++++
3 files changed, 268 insertions(+)
create mode 100644 drivers/gpu/drm/panel/panel-anbernic-td4310.c
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index d592f4f4b939..61dd00297ecc 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -17,6 +17,16 @@ config DRM_PANEL_ABT_Y030XX067A
Y030XX067A 320x480 3.0" panel as found in the YLM RG-280M, RG-300
and RG-99 handheld gaming consoles.
+config DRM_PANEL_ANBERNIC_TD4310
+ tristate "Anbernic TD4310 LCD panel"
+ depends on GPIOLIB && OF
+ depends on DRM_MIPI_DSI
+ depends on BACKLIGHT_CLASS_DEVICE
+ help
+ Say Y here to enable support for Anbernic designed panels with the
+ TD4310 panel controller such as the ones used on the Anbernic RG
+ Vita Pro.
+
config DRM_PANEL_ARM_VERSATILE
tristate "ARM Versatile panel driver"
depends on OF
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index a4291dc3905b..9d8f70c9de3e 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_DRM_PANEL_ABT_Y030XX067A) += panel-abt-y030xx067a.o
+obj-$(CONFIG_DRM_PANEL_ANBERNIC_TD4310) += panel-anbernic-td4310.o
obj-$(CONFIG_DRM_PANEL_ARM_VERSATILE) += panel-arm-versatile.o
obj-$(CONFIG_DRM_PANEL_ASUS_Z00T_TM5P5_NT35596) += panel-asus-z00t-tm5p5-n35596.o
obj-$(CONFIG_DRM_PANEL_AUO_A030JTN01) += panel-auo-a030jtn01.o
diff --git a/drivers/gpu/drm/panel/panel-anbernic-td4310.c b/drivers/gpu/drm/panel/panel-anbernic-td4310.c
new file mode 100644
index 000000000000..9a1b4525423c
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-anbernic-td4310.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for Anbernic panels with TD4310 panel controller.
+ *
+ * Copyright (C) 2026 Chris Morgan <macromorgan@hotmail.com>
+ *
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+
+#include <video/mipi_display.h>
+
+struct anbernic_panel_td4310_info {
+ const struct drm_display_mode *display_mode;
+ u16 width_mm;
+ u16 height_mm;
+ u32 bus_flags;
+ unsigned long mode_flags;
+ u32 format;
+ u32 lanes;
+ u16 prepare_delay;
+ u16 reset_delay;
+ u16 init_delay;
+ u16 enable_delay;
+ u16 disable_delay;
+ u16 unprepare_delay;
+};
+
+struct anbernic_panel_td4310 {
+ struct device *dev;
+ struct mipi_dsi_device *dsi;
+ struct drm_panel panel;
+ const struct anbernic_panel_td4310_info *panel_info;
+ struct gpio_desc *reset_gpio;
+ struct gpio_desc *enable_gpio;
+ struct regulator *vdd;
+ enum drm_panel_orientation orientation;
+};
+
+static inline struct anbernic_panel_td4310 *panel_to_anbernic_panel_td4310(struct drm_panel *panel)
+{
+ return container_of(panel, struct anbernic_panel_td4310, panel);
+}
+
+static int panel_anbernic_td4310_prepare(struct drm_panel *panel)
+{
+ struct anbernic_panel_td4310 *ctx = panel_to_anbernic_panel_td4310(panel);
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
+ int ret;
+
+ ret = regulator_enable(ctx->vdd);
+ if (ret)
+ return ret;
+
+ ret = gpiod_set_value_cansleep(ctx->enable_gpio, 1);
+ if (ret)
+ goto err_enable;
+
+ if (ctx->panel_info->enable_delay)
+ mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->enable_delay);
+
+ ret = gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+ if (ret)
+ goto err_reset;
+
+ mipi_dsi_msleep(&dsi_ctx, 10);
+
+ ret = gpiod_set_value_cansleep(ctx->reset_gpio, 0);
+ if (ret)
+ goto err_reset;
+
+ if (ctx->panel_info->reset_delay)
+ mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->reset_delay);
+
+ mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx);
+ mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->prepare_delay);
+ mipi_dsi_dcs_set_display_on_multi(&dsi_ctx);
+ mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->prepare_delay);
+
+ if (dsi_ctx.accum_err) {
+ ret = dsi_ctx.accum_err;
+ goto err_reset;
+ }
+
+ return 0;
+
+err_reset:
+ gpiod_set_value_cansleep(ctx->enable_gpio, 0);
+err_enable:
+ regulator_disable(ctx->vdd);
+ return ret;
+}
+
+static int panel_anbernic_td4310_unprepare(struct drm_panel *panel)
+{
+ struct anbernic_panel_td4310 *ctx = panel_to_anbernic_panel_td4310(panel);
+ struct mipi_dsi_device *dsi = ctx->dsi;
+ struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
+
+ mipi_dsi_dcs_set_display_off_multi(&dsi_ctx);
+ mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->unprepare_delay);
+ mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx);
+ mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->disable_delay);
+
+ gpiod_set_value_cansleep(ctx->enable_gpio, 0);
+
+ gpiod_set_value_cansleep(ctx->reset_gpio, 1);
+
+ regulator_disable(ctx->vdd);
+
+ return 0;
+}
+
+static int panel_anbernic_td4310_get_mode(struct drm_panel *panel,
+ struct drm_connector *connector)
+{
+ struct anbernic_panel_td4310 *ctx = panel_to_anbernic_panel_td4310(panel);
+ const struct anbernic_panel_td4310_info *panel_info = ctx->panel_info;
+
+ connector->display_info.bpc = 8;
+ connector->display_info.width_mm = panel_info->width_mm;
+ connector->display_info.height_mm = panel_info->height_mm;
+ connector->display_info.bus_flags = panel_info->bus_flags;
+
+ return drm_connector_helper_get_modes_fixed(connector, panel_info->display_mode);
+}
+
+static enum drm_panel_orientation panel_anbernic_td4310_get_orientation(struct drm_panel *panel)
+{
+ struct anbernic_panel_td4310 *ctx = panel_to_anbernic_panel_td4310(panel);
+
+ return ctx->orientation;
+}
+
+static const struct drm_panel_funcs panel_anbernic_td4310_funcs = {
+ .prepare = panel_anbernic_td4310_prepare,
+ .unprepare = panel_anbernic_td4310_unprepare,
+ .get_modes = panel_anbernic_td4310_get_mode,
+ .get_orientation = panel_anbernic_td4310_get_orientation,
+};
+
+static int panel_anbernic_td4310_probe(struct mipi_dsi_device *dsi)
+{
+ struct device *dev = &dsi->dev;
+ struct anbernic_panel_td4310 *ctx;
+ int ret;
+
+ ctx = devm_drm_panel_alloc(dev, struct anbernic_panel_td4310, panel,
+ &panel_anbernic_td4310_funcs,
+ DRM_MODE_CONNECTOR_DSI);
+ if (IS_ERR(ctx))
+ return PTR_ERR(ctx);
+
+ ctx->dev = dev;
+
+ ctx->panel_info = of_device_get_match_data(dev);
+ if (!ctx->panel_info)
+ return -EINVAL;
+
+ ret = of_drm_get_panel_orientation(dev->of_node, &ctx->orientation);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to get panel orientation\n");
+
+ ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->reset_gpio))
+ return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio),
+ "Cannot get reset gpio\n");
+
+ ctx->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(ctx->enable_gpio))
+ return dev_err_probe(dev, PTR_ERR(ctx->enable_gpio),
+ "Cannot get enable gpio\n");
+
+ ctx->vdd = devm_regulator_get(dev, "vdd");
+ if (IS_ERR(ctx->vdd))
+ return dev_err_probe(dev, PTR_ERR(ctx->vdd),
+ "Failed to request vdd regulator\n");
+
+ ctx->dsi = dsi;
+ mipi_dsi_set_drvdata(dsi, ctx);
+
+ dsi->lanes = ctx->panel_info->lanes;
+ dsi->format = ctx->panel_info->format;
+ dsi->mode_flags = ctx->panel_info->mode_flags;
+
+ ret = drm_panel_of_backlight(&ctx->panel);
+ if (ret)
+ return ret;
+
+ devm_drm_panel_add(dev, &ctx->panel);
+
+ ret = devm_mipi_dsi_attach(dev, dsi);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to attach to DSI host\n");
+
+ return 0;
+}
+
+static const struct drm_display_mode anbernic_vitapro_mode = {
+ .clock = 140020,
+ .hdisplay = 1080,
+ .hsync_start = 1080 + 50,
+ .hsync_end = 1080 + 50 + 4,
+ .htotal = 1080 + 50 + 4 + 50,
+ .vdisplay = 1920,
+ .vsync_start = 1920 + 15,
+ .vsync_end = 1920 + 15 + 4,
+ .vtotal = 1920 + 15 + 4 + 32,
+ .flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+};
+
+static const struct anbernic_panel_td4310_info anbernic_vitapro_info = {
+ .display_mode = &anbernic_vitapro_mode,
+ .width_mm = 69,
+ .height_mm = 121,
+ .bus_flags = DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE,
+ .mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
+ MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET |
+ MIPI_DSI_CLOCK_NON_CONTINUOUS,
+ .format = MIPI_DSI_FMT_RGB888,
+ .lanes = 4,
+ .prepare_delay = 50,
+ .reset_delay = 220,
+ .enable_delay = 120,
+ .disable_delay = 50,
+ .unprepare_delay = 20,
+};
+
+static const struct of_device_id panel_anbernic_td4310_of_match[] = {
+ {
+ .compatible = "anbernic,panel-vita-pro",
+ .data = &anbernic_vitapro_info,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(of, panel_anbernic_td4310_of_match);
+
+static struct mipi_dsi_driver anbernic_panel_td4310_driver = {
+ .driver = {
+ .name = "panel-anbernic-td4310",
+ .of_match_table = panel_anbernic_td4310_of_match,
+ },
+ .probe = panel_anbernic_td4310_probe,
+};
+module_mipi_dsi_driver(anbernic_panel_td4310_driver);
+
+MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>");
+MODULE_DESCRIPTION("DRM driver for Anbernic TD4310 MIPI DSI panels");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH V7 3/6] dt-bindings: display: panel: Add Anbernic TD4310 panel
From: Chris Morgan @ 2026-06-10 14:44 UTC (permalink / raw)
To: linux-rockchip
Cc: devicetree, xsf, sre, simona, airlied, tzimmermann, mripard,
maarten.lankhorst, jesszhan0024, neil.armstrong, heiko, conor+dt,
krzk+dt, robh, Chris Morgan, Conor Dooley
In-Reply-To: <20260610144407.438846-1-macroalpha82@gmail.com>
From: Chris Morgan <macromorgan@hotmail.com>
The panel used by Anbernic in the RG Vita-Pro is a DSI panel based
on the TD4310 controller IC. It measures approximately 5.5 inches
diagonally and is 1080x1920 in resolution.
Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
---
.../display/panel/anbernic,td4310.yaml | 66 +++++++++++++++++++
1 file changed, 66 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/panel/anbernic,td4310.yaml
diff --git a/Documentation/devicetree/bindings/display/panel/anbernic,td4310.yaml b/Documentation/devicetree/bindings/display/panel/anbernic,td4310.yaml
new file mode 100644
index 000000000000..f820dc2f732b
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/panel/anbernic,td4310.yaml
@@ -0,0 +1,66 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/panel/anbernic,td4310.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Anbernic TD4310 Based Panels
+
+maintainers:
+ - Chris Morgan <macromorgan@hotmail.com>
+
+description:
+ Anbernic TD4310 Based Panels, such as the RG-Vita-Pro panel
+ (a 1080x1920 5.5 inch panel).
+
+allOf:
+ - $ref: panel-common.yaml#
+
+properties:
+ compatible:
+ items:
+ - enum:
+ - anbernic,panel-vita-pro
+ - const: anbernic,td4310
+
+ reg:
+ maxItems: 1
+
+ vdd-supply:
+ description: Panel power supply
+
+required:
+ - compatible
+ - port
+ - reg
+ - reset-gpios
+ - vdd-supply
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ dsi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ panel@0 {
+ compatible = "anbernic,panel-vita-pro", "anbernic,td4310";
+ reg = <0>;
+ backlight = <&backlight>;
+ enable-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>;
+ reset-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
+ rotation = <270>;
+ vdd-supply = <&vdd_lcd>;
+
+ port {
+ endpoint {
+ remote-endpoint = <&dsi_out>;
+ };
+ };
+ };
+ };
+
+...
--
2.43.0
^ permalink raw reply related
* [PATCH V7 2/6] power: supply: sgm41542: Add SG Micro sgm41542 charger
From: Chris Morgan @ 2026-06-10 14:44 UTC (permalink / raw)
To: linux-rockchip
Cc: devicetree, xsf, sre, simona, airlied, tzimmermann, mripard,
maarten.lankhorst, jesszhan0024, neil.armstrong, heiko, conor+dt,
krzk+dt, robh, Chris Morgan
In-Reply-To: <20260610144407.438846-1-macroalpha82@gmail.com>
From: Chris Morgan <macromorgan@hotmail.com>
Add support for the SG Micro SGM41542 charger/boost converter.
Driver was adapted from Rockchip BSP driver [1] and confirmed
with vendor datasheet [2].
[1] https://github.com/rockchip-linux/kernel/blob/develop-6.6/drivers/power/supply/sgm41542_charger.c
[2] https://www.sg-micro.de/rect/assets/1e8de70b-657e-4156-be68-a64fdbe8e418/SGM41541_SGM41542.pdf
Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
---
drivers/power/supply/Kconfig | 8 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/sgm41542_charger.c | 1067 +++++++++++++++++++++++
3 files changed, 1076 insertions(+)
create mode 100644 drivers/power/supply/sgm41542_charger.c
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 83392ed6a8da..57dae0913472 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -856,6 +856,14 @@ config CHARGER_RK817
help
Say Y to include support for Rockchip RK817 Battery Charger.
+config CHARGER_SGM41542
+ tristate "SGM41542 charger driver"
+ depends on I2C
+ depends on GPIOLIB || COMPILE_TEST
+ select REGMAP_I2C
+ help
+ Say Y to enable support for the SGM41542 battery charger.
+
config CHARGER_SMB347
tristate "Summit Microelectronics SMB3XX Battery Charger"
depends on I2C
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 7ee839dca7f3..c376889db317 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -107,6 +107,7 @@ obj-$(CONFIG_CHARGER_BQ25890) += bq25890_charger.o
obj-$(CONFIG_CHARGER_BQ25980) += bq25980_charger.o
obj-$(CONFIG_CHARGER_BQ256XX) += bq256xx_charger.o
obj-$(CONFIG_CHARGER_RK817) += rk817_charger.o
+obj-$(CONFIG_CHARGER_SGM41542) += sgm41542_charger.o
obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o
obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o
diff --git a/drivers/power/supply/sgm41542_charger.c b/drivers/power/supply/sgm41542_charger.c
new file mode 100644
index 000000000000..3e8c50b6a134
--- /dev/null
+++ b/drivers/power/supply/sgm41542_charger.c
@@ -0,0 +1,1067 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Charger driver for SGM4154x
+ *
+ * Copyright (c) 2026 Rockchip Electronics Co., Ltd.
+ *
+ * Author: Xu Shengfei <xsf@rock-chips.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/devm-helpers.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/types.h>
+
+#define SGM4154X_MANUFACTURER "SGMICRO"
+#define SGM4154X_NAME "sgm41542"
+
+#define SGM4154X_CHRG_CTRL_0 0x00
+#define SGM4154X_HIZ_EN BIT(7)
+#define SGM4154X_IINDPM_I_MASK GENMASK(4, 0)
+#define SGM4154X_IINDPM_I_MIN_UA 100000
+#define SGM4154X_IINDPM_I_MAX_UA 3800000
+#define SGM4154X_IINDPM_STEP_UA 100000
+#define SGM4154X_IINDPM_DEF_UA 2400000
+
+#define SGM4154X_CHRG_CTRL_1 0x01
+#define SGM4154X_WDT_RST BIT(6)
+#define SGM4154X_OTG_EN BIT(5)
+#define SGM4154X_CHRG_EN BIT(4)
+
+#define SGM4154X_CHRG_CTRL_2 0x02
+#define SGM4154X_BOOST_LIM BIT(7)
+#define SGM4154X_ICHRG_CUR_MASK GENMASK(5, 0)
+#define SGM4154X_ICHRG_I_STEP_UA 60000
+#define SGM4154X_ICHRG_I_MIN_UA 0
+#define SGM4154X_ICHRG_I_MAX_UA 3780000
+#define SGM4154X_ICHRG_I_DEF_UA 2040000
+
+#define SGM4154X_CHRG_CTRL_3 0x03
+#define SGM4154X_PRECHRG_CUR_MASK GENMASK(7, 4)
+#define SGM4154X_PRECHRG_CURRENT_STEP_UA 60000
+#define SGM4154X_PRECHRG_I_MIN_UA 60000
+#define SGM4154X_PRECHRG_I_MAX_UA 780000
+#define SGM4154X_PRECHRG_I_DEF_UA 180000
+#define SGM4154X_TERMCHRG_CUR_MASK GENMASK(3, 0)
+#define SGM4154X_TERMCHRG_CURRENT_STEP_UA 60000
+#define SGM4154X_TERMCHRG_I_MIN_UA 60000
+#define SGM4154X_TERMCHRG_I_MAX_UA 960000
+#define SGM4154X_TERMCHRG_I_DEF_UA 180000
+
+#define SGM4154X_CHRG_CTRL_4 0x04
+#define SGM4154X_VREG_V_MASK GENMASK(7, 3)
+#define SGM4154X_VREG_V_MAX_UV 4624000
+#define SGM4154X_VREG_V_MIN_UV 3856000
+#define SGM4154X_VREG_V_DEF_UV 4208000
+#define SGM4154X_VREG_V_STEP_UV 32000
+#define SGM4154X_VRECHARGE BIT(0)
+#define SGM4154X_VRECHRG_STEP_UV 100000
+#define SGM4154X_VRECHRG_OFFSET_UV 100000
+
+#define SGM4154X_CHRG_CTRL_5 0x05
+#define SGM4154X_TERM_EN BIT(7)
+#define SGM4154X_WDT_TIMER_MASK GENMASK(5, 4)
+
+#define SGM4154X_CHRG_CTRL_6 0x06
+#define SGM4154X_VAC_OVP_MASK GENMASK(7, 6)
+#define SGM4154X_OVP_14V (BIT(7) | BIT(6))
+#define SGM4154X_OVP_10_5V BIT(7)
+#define SGM4154X_OVP_6_5V BIT(6)
+#define SGM4154X_OVP_5_5V 0
+#define SGM4154X_OVP_DEFAULT SGM4154X_OVP_14V
+#define SGM4154X_BOOSTV GENMASK(5, 4)
+#define SGM4154X_VINDPM_V_MASK GENMASK(3, 0)
+#define SGM4154X_VINDPM_V_MIN_UV 3900000
+#define SGM4154X_VINDPM_V_MAX_UV 12000000
+#define SGM4154X_VINDPM_STEP_UV 100000
+#define SGM4154X_VINDPM_DEF_UV 4500000
+
+#define SGM4154X_CHRG_CTRL_7 0x07
+
+#define SGM4154X_CHRG_STAT 0x08
+#define SGM4154X_VBUS_STAT_MASK GENMASK(7, 5)
+#define SGM4154X_OTG_MODE (BIT(7) | BIT(6) | BIT(5))
+#define SGM4154X_NON_STANDARD (BIT(7) | BIT(6))
+#define SGM4154X_UNKNOWN (BIT(7) | BIT(5))
+#define SGM4154X_USB_DCP (BIT(6) | BIT(5))
+#define SGM4154X_USB_CDP BIT(6)
+#define SGM4154X_USB_SDP BIT(5)
+#define SGM4154X_NOT_CHRGING 0
+#define SGM4154X_CHG_STAT_MASK GENMASK(4, 3)
+#define SGM4154X_TERM_CHRG (BIT(4) | BIT(3))
+#define SGM4154X_FAST_CHRG BIT(4)
+#define SGM4154X_PRECHRG BIT(3)
+#define SGM4154X_PG_STAT BIT(2)
+#define SGM4154X_THERM_STAT BIT(1)
+#define SGM4154X_VSYS_STAT BIT(0)
+
+#define SGM4154X_CHRG_FAULT 0x09
+#define SGM4154X_TEMP_MASK GENMASK(2, 0)
+#define SGM4154X_TEMP_HOT (BIT(2) | BIT(1))
+#define SGM4154X_TEMP_COLD (BIT(2) | BIT(0))
+#define SGM4154X_TEMP_COOL (BIT(1) | BIT(0))
+#define SGM4154X_TEMP_WARM BIT(1)
+#define SGM4154X_TEMP_NORMAL BIT(0)
+
+#define SGM4154X_CHRG_CTRL_A 0x0a
+#define SGM4154X_VBUS_GOOD BIT(7)
+#define SGM4154X_VINDPM_INT_MASK BIT(1)
+#define SGM4154X_IINDPM_INT_MASK BIT(0)
+
+#define SGM4154X_CHRG_CTRL_B 0x0b
+#define SGM4154X_PN_ID (BIT(6) | BIT(5) | BIT(3))
+#define SGM4154X_PN_MASK GENMASK(6, 3)
+
+#define SGM4154X_CHRG_CTRL_C 0x0c
+
+#define SGM4154X_CHRG_CTRL_D 0x0d
+#define SGM4154X_JEITA_EN BIT(0)
+
+#define SGM4154X_INPUT_DET 0x0e
+#define SGM4154X_DPDM_ONGOING BIT(7)
+
+#define SGM4154X_CHRG_CTRL_F 0x0f
+#define SGM4154X_VINDPM_OS_MASK GENMASK(1, 0)
+
+#define SGM4154X_DEFAULT_INPUT_CUR (500 * 1000)
+
+struct sgm4154x_init_data {
+ int ilim; /* input current limit */
+ int vlim; /* minimum system voltage limit */
+ int iterm; /* termination current */
+ int iprechg; /* precharge current */
+ int max_ichg; /* maximum charge current */
+ int max_vreg; /* maximum charge voltage */
+};
+
+struct sgm4154x_state {
+ bool vsys_stat;
+ bool therm_stat;
+ bool online;
+ u8 chrg_stat;
+ bool chrg_en;
+ bool vbus_gd;
+ u8 chrg_type;
+ u8 health;
+ u8 chrg_fault;
+ u8 ntc_fault;
+};
+
+struct sgm4154x_device {
+ struct device *dev;
+ struct power_supply *charger;
+ struct regmap *regmap;
+ struct sgm4154x_init_data init_data;
+ struct sgm4154x_state state;
+ struct regulator_dev *otg_rdev;
+ bool watchdog_enable;
+ struct workqueue_struct *sgm_monitor_wq;
+ struct delayed_work sgm_delay_work;
+};
+
+enum SGM4154X_VINDPM_OS {
+ VINDPM_OS_3900MV,
+ VINDPM_OS_5900MV,
+ VINDPM_OS_7500MV,
+ VINDPM_OS_10500MV,
+};
+
+enum sgm4154x_wdt_values {
+ SGM4154X_WDT_TIMER_DISABLE = 0,
+ SGM4154X_WDT_TIMER_40S = 1,
+ SGM4154X_WDT_TIMER_80S = 2,
+ SGM4154X_WDT_TIMER_160S = 3,
+};
+
+static int sgm4154x_set_term_curr(struct sgm4154x_device *sgm, int cur_ua)
+{
+ int reg_val;
+ int ret;
+
+ cur_ua = clamp(cur_ua, SGM4154X_TERMCHRG_I_MIN_UA, SGM4154X_TERMCHRG_I_MAX_UA);
+ reg_val = (cur_ua - SGM4154X_TERMCHRG_I_MIN_UA) / SGM4154X_TERMCHRG_CURRENT_STEP_UA;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_3,
+ SGM4154X_TERMCHRG_CUR_MASK,
+ reg_val);
+ if (ret)
+ dev_err(sgm->dev, "set term current error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_set_prechrg_curr(struct sgm4154x_device *sgm, int cur_ua)
+{
+ int reg_val;
+ int ret;
+
+ cur_ua = clamp(cur_ua, SGM4154X_PRECHRG_I_MIN_UA, SGM4154X_PRECHRG_I_MAX_UA);
+ reg_val = (cur_ua - SGM4154X_PRECHRG_I_MIN_UA) / SGM4154X_PRECHRG_CURRENT_STEP_UA;
+
+ reg_val = FIELD_PREP(SGM4154X_PRECHRG_CUR_MASK, reg_val);
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_3,
+ SGM4154X_PRECHRG_CUR_MASK,
+ reg_val);
+ if (ret)
+ dev_err(sgm->dev, "set precharge current error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_set_ichrg_curr(struct sgm4154x_device *sgm, int cur_ua)
+{
+ int reg_val;
+ int ret;
+
+ cur_ua = clamp(cur_ua, SGM4154X_ICHRG_I_MIN_UA, sgm->init_data.max_ichg);
+ reg_val = cur_ua / SGM4154X_ICHRG_I_STEP_UA;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_2,
+ SGM4154X_ICHRG_CUR_MASK,
+ reg_val);
+ if (ret)
+ dev_err(sgm->dev, "set icharge current error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_get_ichrg_curr(struct sgm4154x_device *sgm)
+{
+ u32 reg;
+ int ret, val;
+
+ ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_2, ®);
+ if (ret) {
+ dev_err(sgm->dev, "get charge current error!\n");
+ return ret;
+ }
+
+ val = FIELD_GET(SGM4154X_ICHRG_CUR_MASK, reg);
+
+ return val * SGM4154X_ICHRG_I_STEP_UA;
+}
+
+static int sgm4154x_set_chrg_volt(struct sgm4154x_device *sgm, int chrg_volt)
+{
+ int reg_val;
+ int ret;
+
+ /*
+ * Note that the value of 0x01111 represents a "special value"
+ * corresponding to 4352000uV instead of the expected 4336000uV,
+ * per the datasheet. All other values are as expected. So not
+ * only do we need to clamp between max and min values, but
+ * also clamp anything below 4352000uv to 4304000uv to prevent
+ * overcharging.
+ */
+ chrg_volt = clamp(chrg_volt, SGM4154X_VREG_V_MIN_UV, sgm->init_data.max_vreg);
+ if (chrg_volt < 4352000)
+ chrg_volt = clamp(chrg_volt, SGM4154X_VREG_V_MIN_UV, 4304000);
+ reg_val = (chrg_volt - SGM4154X_VREG_V_MIN_UV) / SGM4154X_VREG_V_STEP_UV;
+ reg_val = FIELD_PREP(SGM4154X_VREG_V_MASK, reg_val);
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_4,
+ SGM4154X_VREG_V_MASK,
+ reg_val);
+ if (ret)
+ dev_err(sgm->dev, "set charge voltage error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_get_chrg_volt(struct sgm4154x_device *sgm)
+{
+ u32 reg;
+ int ret, val;
+
+ ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_4, ®);
+ if (ret) {
+ dev_err(sgm->dev, "get charge voltage error!\n");
+ return ret;
+ }
+
+ val = FIELD_GET(SGM4154X_VREG_V_MASK, reg);
+
+ /*
+ * 0x01111 is a special value meaning 4352000uV, all other
+ * values are as expected based on the offset and step values.
+ */
+ if (val == 0x0f)
+ return 4352000;
+
+ return val * SGM4154X_VREG_V_STEP_UV + SGM4154X_VREG_V_MIN_UV;
+}
+
+static int sgm4154x_set_input_volt_lim(struct sgm4154x_device *sgm,
+ unsigned int vindpm)
+{
+ enum SGM4154X_VINDPM_OS os_val;
+ unsigned int offset;
+ u8 reg_val;
+ int ret;
+
+
+ if (vindpm < SGM4154X_VINDPM_V_MIN_UV ||
+ vindpm > SGM4154X_VINDPM_V_MAX_UV) {
+ dev_err(sgm->dev, "input voltage limit %u outside range\n", vindpm);
+ return -EINVAL;
+ }
+
+ /*
+ * Supported ranges per the datasheet are as follows:
+ * 3.9v - 5.4v with a 3.9v offset
+ * 5.9v - 7.4v with a 5.9v offset
+ * 7.5v - 9.0v with a 7.5v offset
+ * 10.5v - 12.0v with a 10.5v offset
+ * Step size is a constant 100mv
+ */
+ if (vindpm < 5900000) {
+ offset = 3900000;
+ vindpm = clamp(vindpm, offset, 5400000);
+ os_val = VINDPM_OS_3900MV;
+ } else if (vindpm < 7500000) {
+ offset = 5900000;
+ vindpm = clamp(vindpm, offset, 7400000);
+ os_val = VINDPM_OS_5900MV;
+ } else if (vindpm < 10500000) {
+ offset = 7500000;
+ vindpm = clamp(vindpm, offset, 9000000);
+ os_val = VINDPM_OS_7500MV;
+ } else {
+ offset = 10500000;
+ vindpm = clamp(vindpm, offset, 12000000);
+ os_val = VINDPM_OS_10500MV;
+ }
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_F,
+ SGM4154X_VINDPM_OS_MASK,
+ os_val);
+ if (ret) {
+ dev_err(sgm->dev, "set vin dpm error!\n");
+ return ret;
+ }
+
+ reg_val = (vindpm - offset) / SGM4154X_VINDPM_STEP_UV;
+
+ ret = regmap_update_bits(sgm->regmap, SGM4154X_CHRG_CTRL_6,
+ SGM4154X_VINDPM_V_MASK, reg_val);
+ if (ret)
+ dev_err(sgm->dev, "input voltage error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_set_input_curr_lim(struct sgm4154x_device *sgm, int iindpm)
+{
+ int reg_val;
+ int ret;
+
+ if (iindpm < SGM4154X_IINDPM_I_MIN_UA)
+ return -EINVAL;
+
+ /*
+ * Protect against a timing-issue edge case if a sysfs write occurs in the middle
+ * of a probe (very unlikely).
+ */
+ if (sgm->init_data.ilim < SGM4154X_IINDPM_I_MIN_UA)
+ return -EINVAL;
+
+ /*
+ * Per the datasheet, values between 100000uA and 3100000uA work
+ * as expected with the register defined as having a step of
+ * 100000 and a min/max of 100000 (0x00) through 3100000 (0x1e).
+ * The register value of 0x1f however corresponds to 3800000uA not
+ * 3200000uA as one would expect.
+ */
+ if ((iindpm > SGM4154X_IINDPM_I_MAX_UA) || (iindpm > sgm->init_data.ilim))
+ iindpm = min(SGM4154X_IINDPM_I_MAX_UA, sgm->init_data.ilim);
+
+ if (iindpm > 3100000 && iindpm < SGM4154X_IINDPM_I_MAX_UA)
+ iindpm = 3100000;
+
+ if (iindpm == SGM4154X_IINDPM_I_MAX_UA)
+ reg_val = 0x1f;
+ else
+ reg_val = (iindpm - SGM4154X_IINDPM_I_MIN_UA) / SGM4154X_IINDPM_STEP_UA;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_0,
+ SGM4154X_IINDPM_I_MASK,
+ reg_val);
+ if (ret)
+ dev_err(sgm->dev, "set input current limit error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_get_input_curr_lim(struct sgm4154x_device *sgm)
+{
+ int ret;
+ int ilim;
+
+ ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_0, &ilim);
+ if (ret) {
+ dev_err(sgm->dev, "get input current limit error!\n");
+ return ret;
+ }
+
+ ilim &= SGM4154X_IINDPM_I_MASK;
+
+ /* Max value is not 3200000uA as expected but is 3800000uA */
+ if (ilim == SGM4154X_IINDPM_I_MASK)
+ return SGM4154X_IINDPM_I_MAX_UA;
+
+ ilim = ilim * SGM4154X_IINDPM_STEP_UA + SGM4154X_IINDPM_I_MIN_UA;
+
+ return ilim;
+}
+
+static int sgm4154x_watchdog_timer_reset(struct sgm4154x_device *sgm)
+{
+ int ret;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_1,
+ SGM4154X_WDT_RST,
+ SGM4154X_WDT_RST);
+
+ if (ret)
+ dev_err(sgm->dev, "set watchdog timer error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_set_watchdog_timer(struct sgm4154x_device *sgm, u8 time)
+{
+ int ret;
+
+ if (time > SGM4154X_WDT_TIMER_160S)
+ return -EINVAL;
+
+ time = FIELD_PREP(SGM4154X_WDT_TIMER_MASK, time);
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_5,
+ SGM4154X_WDT_TIMER_MASK,
+ time);
+
+ if (ret) {
+ dev_err(sgm->dev, "set watchdog timer error!\n");
+ return ret;
+ }
+
+ if (time) {
+ if (!sgm->watchdog_enable)
+ queue_delayed_work(sgm->sgm_monitor_wq,
+ &sgm->sgm_delay_work,
+ msecs_to_jiffies(1000 * 5));
+ sgm->watchdog_enable = true;
+ } else {
+ sgm->watchdog_enable = false;
+ sgm4154x_watchdog_timer_reset(sgm);
+ }
+
+ return ret;
+}
+
+static int sgm4154x_enable_charger(struct sgm4154x_device *sgm)
+{
+ int ret;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_1,
+ SGM4154X_CHRG_EN,
+ SGM4154X_CHRG_EN);
+ if (ret)
+ dev_err(sgm->dev, "enable charger error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_disable_charger(struct sgm4154x_device *sgm)
+{
+ int ret;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_1,
+ SGM4154X_CHRG_EN,
+ 0);
+ if (ret)
+ dev_err(sgm->dev, "disable charger error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_set_vac_ovp(struct sgm4154x_device *sgm)
+{
+ int reg_val;
+ int ret;
+
+ reg_val = SGM4154X_OVP_DEFAULT & SGM4154X_VAC_OVP_MASK;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_6,
+ SGM4154X_VAC_OVP_MASK,
+ reg_val);
+ if (ret)
+ dev_err(sgm->dev, "set vac ovp error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_set_recharge_volt_ua(struct sgm4154x_device *sgm, int recharge_volt)
+{
+ int reg_val;
+ int ret;
+
+ reg_val = (recharge_volt - SGM4154X_VRECHRG_OFFSET_UV) / SGM4154X_VRECHRG_STEP_UV;
+
+ ret = regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_4,
+ SGM4154X_VRECHARGE,
+ reg_val);
+ if (ret)
+ dev_err(sgm->dev, "set recharger error!\n");
+
+ return ret;
+}
+
+static int sgm4154x_get_state(struct sgm4154x_device *sgm,
+ struct sgm4154x_state *state)
+{
+ int reg, ret;
+
+ ret = regmap_read(sgm->regmap, SGM4154X_CHRG_STAT, ®);
+ if (ret) {
+ dev_err(sgm->dev, "read SGM4154X_CHRG_STAT fail\n");
+ return ret;
+ }
+ state->chrg_type = reg & SGM4154X_VBUS_STAT_MASK;
+ state->chrg_stat = reg & SGM4154X_CHG_STAT_MASK;
+ state->online = !!(reg & SGM4154X_PG_STAT);
+ state->therm_stat = !!(reg & SGM4154X_THERM_STAT);
+ state->vsys_stat = !!(reg & SGM4154X_VSYS_STAT);
+
+ ret = regmap_read(sgm->regmap, SGM4154X_CHRG_FAULT, ®);
+ if (ret) {
+ dev_err(sgm->dev, "read SGM4154X_CHRG_FAULT fail\n");
+ return ret;
+ }
+ state->chrg_fault = reg;
+ state->ntc_fault = reg & SGM4154X_TEMP_MASK;
+ state->health = state->ntc_fault;
+
+ ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_A, ®);
+ if (ret) {
+ dev_err(sgm->dev, "read SGM4154X_CHRG_CTRL_A fail\n");
+ return ret;
+ }
+ state->vbus_gd = !!(reg & SGM4154X_VBUS_GOOD);
+
+ return ret;
+}
+
+static int sgm4154x_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ switch (prop) {
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ case POWER_SUPPLY_PROP_ONLINE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int sgm4154x_charger_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct sgm4154x_device *sgm = power_supply_get_drvdata(psy);
+ int ret = -EINVAL;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (val->intval) {
+ ret = sgm4154x_enable_charger(sgm);
+ sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_40S);
+ } else {
+ sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE);
+ ret = sgm4154x_disable_charger(sgm);
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ ret = sgm4154x_set_input_curr_lim(sgm, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ ret = sgm4154x_set_ichrg_curr(sgm, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ ret = sgm4154x_set_chrg_volt(sgm, val->intval);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int sgm4154x_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct sgm4154x_device *sgm = power_supply_get_drvdata(psy);
+ struct sgm4154x_state state;
+ int ret;
+
+ ret = sgm4154x_get_state(sgm, &state);
+ if (ret) {
+ dev_err(sgm->dev, "get state error!\n");
+ return ret;
+ }
+ sgm->state = state;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (!state.chrg_type || (state.chrg_type == SGM4154X_OTG_MODE))
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (!state.chrg_stat)
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else if (state.chrg_stat == SGM4154X_TERM_CHRG)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ switch (state.chrg_stat) {
+ case SGM4154X_PRECHRG:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case SGM4154X_FAST_CHRG:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ case SGM4154X_TERM_CHRG:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case SGM4154X_NOT_CHRGING:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+ }
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = SGM4154X_MANUFACTURER;
+ break;
+
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = SGM4154X_NAME;
+ break;
+
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = state.online;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = state.vbus_gd;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ val->intval = sgm4154x_get_chrg_volt(sgm);
+ if (val->intval < 0)
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ val->intval = sgm4154x_get_ichrg_curr(sgm);
+ if (val->intval < 0)
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
+ val->intval = sgm->init_data.vlim;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ val->intval = sgm4154x_get_input_curr_lim(sgm);
+ if (val->intval < 0)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static irqreturn_t sgm4154x_irq_handler_thread(int irq, void *private)
+{
+ struct sgm4154x_device *sgm4154x = private;
+ struct sgm4154x_state state;
+ int ret;
+
+ ret = sgm4154x_get_state(sgm4154x, &state);
+ if (ret) {
+ dev_err(sgm4154x->dev, "get state error!\n");
+ return IRQ_NONE;
+ }
+ sgm4154x->state = state;
+ if (state.vbus_gd) {
+ if (sgm4154x->init_data.ilim >= SGM4154X_DEFAULT_INPUT_CUR) {
+ ret = sgm4154x_set_input_curr_lim(sgm4154x, sgm4154x->init_data.ilim);
+ if (ret) {
+ dev_err(sgm4154x->dev, "set input current error!\n");
+ /* Reading IRQ clears interrupt, so return handled */
+ return IRQ_HANDLED;
+ }
+ }
+ }
+ power_supply_changed(sgm4154x->charger);
+
+ return IRQ_HANDLED;
+}
+
+static enum power_supply_property sgm4154x_power_supply_props[] = {
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ POWER_SUPPLY_PROP_PRESENT
+};
+
+static struct power_supply_desc sgm4154x_power_supply_desc = {
+ .name = "sgm4154x-charger",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = sgm4154x_power_supply_props,
+ .num_properties = ARRAY_SIZE(sgm4154x_power_supply_props),
+ .get_property = sgm4154x_charger_get_property,
+ .set_property = sgm4154x_charger_set_property,
+ .property_is_writeable = sgm4154x_property_is_writeable,
+};
+
+static const struct regmap_config sgm4154x_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = SGM4154X_CHRG_CTRL_F,
+ .cache_type = REGCACHE_NONE,
+};
+
+static int sgm4154x_power_supply_init(struct sgm4154x_device *sgm,
+ struct device *dev)
+{
+ struct power_supply_config psy_cfg = { .drv_data = sgm,
+ .fwnode = dev->fwnode, };
+
+ sgm->charger = devm_power_supply_register(sgm->dev,
+ &sgm4154x_power_supply_desc,
+ &psy_cfg);
+ if (IS_ERR(sgm->charger))
+ return PTR_ERR(sgm->charger);
+
+ return 0;
+}
+
+static int sgm4154x_hw_init(struct sgm4154x_device *sgm)
+{
+ struct power_supply_battery_info *bat_info;
+ int ret;
+ u32 val;
+
+ /*
+ * If unable to read devicetree info, use default/reset
+ * values from hardware. If the devicetree info has data out
+ * of range for any of the values, use the default value.
+ */
+ sgm->init_data.iprechg = SGM4154X_PRECHRG_I_DEF_UA;
+ sgm->init_data.iterm = SGM4154X_TERMCHRG_I_DEF_UA;
+ sgm->init_data.max_ichg = SGM4154X_ICHRG_I_DEF_UA;
+ sgm->init_data.max_vreg = SGM4154X_VREG_V_DEF_UV;
+ sgm->init_data.vlim = SGM4154X_VINDPM_DEF_UV;
+ sgm->init_data.ilim = SGM4154X_IINDPM_DEF_UA;
+
+ ret = power_supply_get_battery_info(sgm->charger, &bat_info);
+ if (ret)
+ dev_warn(sgm->dev, "sgm4154x: cannot read battery info\n");
+ else {
+ if ((bat_info->constant_charge_current_max_ua >= SGM4154X_ICHRG_I_MIN_UA) &&
+ (bat_info->constant_charge_current_max_ua <= SGM4154X_ICHRG_I_MAX_UA))
+ sgm->init_data.max_ichg = bat_info->constant_charge_current_max_ua;
+ if ((bat_info->constant_charge_voltage_max_uv >= SGM4154X_VREG_V_MIN_UV) &&
+ (bat_info->constant_charge_voltage_max_uv <= SGM4154X_VREG_V_MAX_UV))
+ sgm->init_data.max_vreg = bat_info->constant_charge_voltage_max_uv;
+ if ((bat_info->charge_term_current_ua >= SGM4154X_TERMCHRG_I_MIN_UA) &&
+ (bat_info->charge_term_current_ua <= SGM4154X_TERMCHRG_I_MAX_UA))
+ sgm->init_data.iterm = bat_info->charge_term_current_ua;
+ if ((bat_info->precharge_current_ua >= SGM4154X_PRECHRG_I_MIN_UA) &&
+ (bat_info->precharge_current_ua <= SGM4154X_PRECHRG_I_MAX_UA))
+ sgm->init_data.iprechg = bat_info->precharge_current_ua;
+
+ power_supply_put_battery_info(sgm->charger, bat_info);
+ }
+
+ ret = device_property_read_u32(sgm->dev,
+ "input-voltage-limit-microvolt",
+ &val);
+ if (!ret)
+ sgm->init_data.vlim = clamp(val, SGM4154X_VINDPM_V_MIN_UV,
+ SGM4154X_VINDPM_V_MAX_UV);
+
+ ret = device_property_read_u32(sgm->dev,
+ "input-current-limit-microamp",
+ &val);
+ if (!ret)
+ sgm->init_data.ilim = clamp(val, SGM4154X_IINDPM_I_MIN_UA,
+ SGM4154X_IINDPM_I_MAX_UA);
+
+ ret = sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE);
+ if (ret)
+ return ret;
+
+ ret = sgm4154x_set_prechrg_curr(sgm, sgm->init_data.iprechg);
+ if (ret)
+ return ret;
+
+ ret = sgm4154x_set_chrg_volt(sgm, sgm->init_data.max_vreg);
+ if (ret)
+ return ret;
+
+ ret = sgm4154x_set_term_curr(sgm, sgm->init_data.iterm);
+ if (ret)
+ return ret;
+
+ ret = sgm4154x_set_ichrg_curr(sgm, sgm->init_data.max_ichg);
+ if (ret)
+ return ret;
+
+ ret = sgm4154x_set_input_volt_lim(sgm, sgm->init_data.vlim);
+ if (ret)
+ return ret;
+
+ ret = sgm4154x_set_input_curr_lim(sgm, sgm->init_data.ilim);
+ if (ret)
+ return ret;
+
+ ret = sgm4154x_set_vac_ovp(sgm);
+ if (ret)
+ return ret;
+
+ regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_D,
+ SGM4154X_JEITA_EN,
+ 0);
+
+ regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_A,
+ SGM4154X_IINDPM_INT_MASK,
+ SGM4154X_IINDPM_INT_MASK);
+
+ regmap_update_bits(sgm->regmap,
+ SGM4154X_CHRG_CTRL_A,
+ SGM4154X_VINDPM_INT_MASK,
+ SGM4154X_VINDPM_INT_MASK);
+
+ /*
+ * Recharge microvolt set to 200000 by BSP driver instead of hardware
+ * default value of 100000.
+ */
+ ret = sgm4154x_set_recharge_volt_ua(sgm, 200000);
+
+ return ret;
+}
+
+static const u32 sgm4154x_chg_otg_cur_ua[] = {
+ 1200000, 2000000,
+};
+
+static const struct regulator_ops sgm4154x_vbus_ops = {
+ .list_voltage = regulator_list_voltage_linear,
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .is_enabled = regulator_is_enabled_regmap,
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .set_current_limit = regulator_set_current_limit_regmap,
+ .get_current_limit = regulator_get_current_limit_regmap,
+};
+
+static const struct regulator_desc sgm4154x_otg_rdesc = {
+ .of_match = "otg-vbus",
+ .name = "otg-vbus",
+ .regulators_node = of_match_ptr("regulators"),
+ .ops = &sgm4154x_vbus_ops,
+ .owner = THIS_MODULE,
+ .type = REGULATOR_VOLTAGE,
+ .min_uV = 4850000,
+ .uV_step = 150000,
+ .n_voltages = 4,
+ .vsel_reg = SGM4154X_CHRG_CTRL_6,
+ .vsel_mask = SGM4154X_BOOSTV,
+ .enable_reg = SGM4154X_CHRG_CTRL_1,
+ .enable_mask = SGM4154X_OTG_EN,
+ .curr_table = sgm4154x_chg_otg_cur_ua,
+ .n_current_limits = ARRAY_SIZE(sgm4154x_chg_otg_cur_ua),
+ .csel_reg = SGM4154X_CHRG_CTRL_2,
+ .csel_mask = SGM4154X_BOOST_LIM,
+};
+
+static int sgm4154x_vbus_regulator_register(struct sgm4154x_device *sgm)
+{
+ struct regulator_config config = {
+ .dev = sgm->dev,
+ .regmap = sgm->regmap,
+ .driver_data = sgm,
+ };
+
+ sgm->otg_rdev = devm_regulator_register(sgm->dev,
+ &sgm4154x_otg_rdesc,
+ &config);
+
+ return PTR_ERR_OR_ZERO(sgm->otg_rdev);
+}
+
+static int sgm4154x_hw_chipid_detect(struct sgm4154x_device *sgm)
+{
+ int ret;
+ int val;
+
+ ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_B, &val);
+ if (ret)
+ return ret;
+
+ if ((val & SGM4154X_PN_MASK) != SGM4154X_PN_ID)
+ dev_warn(sgm->dev, "sgm4154x device ID mismatch\n");
+
+ return 0;
+}
+
+static void sgm_charger_work(struct work_struct *work)
+{
+ struct sgm4154x_device *sgm =
+ container_of(work,
+ struct sgm4154x_device,
+ sgm_delay_work.work);
+
+ sgm4154x_watchdog_timer_reset(sgm);
+ if (sgm->watchdog_enable)
+ queue_delayed_work(sgm->sgm_monitor_wq,
+ &sgm->sgm_delay_work,
+ msecs_to_jiffies(1000 * 5));
+}
+
+static void sgm4154x_disable_irq_wake(void *data)
+{
+ struct sgm4154x_device *sgm = data;
+ struct i2c_client *client = to_i2c_client(sgm->dev);
+
+ disable_irq_wake(client->irq);
+}
+
+static int sgm4154x_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct sgm4154x_device *sgm;
+ int ret;
+
+ sgm = devm_kzalloc(dev, sizeof(*sgm), GFP_KERNEL);
+ if (!sgm)
+ return -ENOMEM;
+
+ sgm->dev = dev;
+
+ sgm->regmap = devm_regmap_init_i2c(client, &sgm4154x_regmap_config);
+ if (IS_ERR(sgm->regmap))
+ return dev_err_probe(dev, PTR_ERR(sgm->regmap),
+ "Failed to allocate register map\n");
+
+ i2c_set_clientdata(client, sgm);
+
+ ret = sgm4154x_hw_chipid_detect(sgm);
+ if (ret)
+ return dev_err_probe(dev, ret, "Unable to read HW ID\n");
+
+ device_init_wakeup(dev, 1);
+
+ sgm->sgm_monitor_wq = devm_alloc_ordered_workqueue(dev, "sgm-monitor-wq",
+ WQ_MEM_RECLAIM | WQ_FREEZABLE);
+ if (!sgm->sgm_monitor_wq)
+ return -EINVAL;
+
+ ret = devm_delayed_work_autocancel(dev, &sgm->sgm_delay_work,
+ sgm_charger_work);
+ if (ret)
+ return dev_err_probe(dev, ret, "Unable to register delayed work\n");
+
+ ret = sgm4154x_power_supply_init(sgm, dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register power supply\n");
+
+ ret = sgm4154x_hw_init(sgm);
+ if (ret)
+ return dev_err_probe(dev, ret, "Cannot initialize the chip.\n");
+
+ if (client->irq) {
+ ret = devm_request_threaded_irq(dev, client->irq, NULL,
+ sgm4154x_irq_handler_thread,
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ "sgm41542-irq", sgm);
+ if (ret)
+ return ret;
+
+ ret = enable_irq_wake(client->irq);
+ if (!ret) {
+ ret = devm_add_action_or_reset(dev, sgm4154x_disable_irq_wake, sgm);
+ if (ret)
+ return ret;
+ }
+ }
+
+ ret = sgm4154x_vbus_regulator_register(sgm);
+ if (ret) {
+ return dev_err_probe(dev, ret,
+ "Unable to register VBUS regulator\n");
+ }
+
+ return 0;
+}
+
+static const struct i2c_device_id sgm4154x_i2c_ids[] = {
+ { "sgm41542" },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, sgm4154x_i2c_ids);
+
+static const struct of_device_id sgm4154x_of_match[] = {
+ { .compatible = "sgmicro,sgm41542", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, sgm4154x_of_match);
+
+static struct i2c_driver sgm4154x_driver = {
+ .driver = {
+ .name = "sgm4154x-charger",
+ .of_match_table = sgm4154x_of_match,
+ },
+ .probe = sgm4154x_probe,
+ .id_table = sgm4154x_i2c_ids,
+};
+
+module_i2c_driver(sgm4154x_driver);
+
+MODULE_AUTHOR("Xu Shengfei <xsf@rock-chips.com>");
+MODULE_DESCRIPTION("sgm4154x charger driver");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH V7 1/6] dt-bindings: power: supply: sgm41542: document sgm41542
From: Chris Morgan @ 2026-06-10 14:44 UTC (permalink / raw)
To: linux-rockchip
Cc: devicetree, xsf, sre, simona, airlied, tzimmermann, mripard,
maarten.lankhorst, jesszhan0024, neil.armstrong, heiko, conor+dt,
krzk+dt, robh, Chris Morgan
In-Reply-To: <20260610144407.438846-1-macroalpha82@gmail.com>
From: Chris Morgan <macromorgan@hotmail.com>
Document the SG Micro sgm41542 battery charger/boost converter.
The parameters of input-current-limit-microamp and
input-voltage-limit-microvolt are defined as such since they are in
common use among multiple bindings currently.
Signed-off-by: Chris Morgan <macromorgan@hotmail.com>
---
.../power/supply/sgmicro,sgm41542.yaml | 96 +++++++++++++++++++
1 file changed, 96 insertions(+)
create mode 100644 Documentation/devicetree/bindings/power/supply/sgmicro,sgm41542.yaml
diff --git a/Documentation/devicetree/bindings/power/supply/sgmicro,sgm41542.yaml b/Documentation/devicetree/bindings/power/supply/sgmicro,sgm41542.yaml
new file mode 100644
index 000000000000..af80fe528505
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/sgmicro,sgm41542.yaml
@@ -0,0 +1,96 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/power/supply/sgmicro,sgm41542.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: SGM41542 Battery Charger
+
+description:
+ The SGMicro SGM41542 is a single cell battery charger/boost controller.
+
+maintainers:
+ - Chris Morgan <macromorgan@hotmail.com>
+ - Xu Shengfei <xsf@rock-chips.com>
+
+allOf:
+ - $ref: power-supply.yaml#
+
+properties:
+ compatible:
+ const: sgmicro,sgm41542
+
+ input-current-limit-microamp:
+ default: 2400000
+ minimum: 100000
+ maximum: 3800000
+
+ input-voltage-limit-microvolt:
+ default: 4500000
+ minimum: 3900000
+ maximum: 12000000
+
+ interrupts:
+ maxItems: 1
+
+ monitored-battery:
+ description: |
+ The charger uses the following battery properties
+ constant-charge-current-max-microamp (default 2040000)
+ constant-charge-voltage-max-microvolt (default 4208000)
+ charge-term-current-microamp (default 180000)
+ precharge-current-microamp (default 180000)
+
+ reg:
+ maxItems: 1
+
+ regulators:
+ type: object
+ properties:
+ otg-vbus:
+ type: object
+ description: OTG boost regulator
+ $ref: /schemas/regulator/regulator.yaml
+ unevaluatedProperties: false
+ additionalProperties: false
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ battery: battery {
+ compatible = "simple-battery";
+ constant-charge-current-max-microamp = <10000000>;
+ constant-charge-voltage-max-microvolt = <4350000>;
+ precharge-current-microamp = <180000>;
+ charge-term-current-microamp = <300000>;
+ };
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ charger@3b {
+ compatible = "sgmicro,sgm41542";
+ reg = <0x3b>;
+ input-current-limit-microamp = <3000000>;
+ input-voltage-limit-microvolt = <4500000>;
+ interrupt-parent = <&gpio0>;
+ interrupts = <26 IRQ_TYPE_EDGE_FALLING>;
+ monitored-battery = <&battery>;
+
+ regulators {
+ otg-vbus {
+ regulator-max-microvolt = <5000000>;
+ regulator-min-microvolt = <5000000>;
+ };
+ };
+ };
+ };
--
2.43.0
^ permalink raw reply related
* [PATCH V7 0/6] Add Anbernic RG Vita-Pro
From: Chris Morgan @ 2026-06-10 14:44 UTC (permalink / raw)
To: linux-rockchip
Cc: devicetree, xsf, sre, simona, airlied, tzimmermann, mripard,
maarten.lankhorst, jesszhan0024, neil.armstrong, heiko, conor+dt,
krzk+dt, robh, Chris Morgan
From: Chris Morgan <macromorgan@hotmail.com>
Add support for the Anbernic RG Vita-Pro, based on the Rockchip RK3576 SoC.
Changes Since V1:
- Updated documentation filename to matche compatible string.
- Corrected order of allocating IRQ in charger driver and switched
to using devm managed function to allocate work queue.
Changes Since V2:
- Corrected issue with naming of panel binding document and added
fallback compatible string.
- Switched to devm helpers in panel driver. Note that
devm_drm_panel_add() is currently only in the drm-next branch.
- Optimized setting GPIOs for panel driver.
Changes Since V3:
- Fixed numerous issues identifed by Sashiko bot, mainly relating
to unclamped or improperly clamped values from within the
charger driver.
- Corrected hard-coded values in the panel driver unprepare routine
as well as correcting an improperly checked return condition.
Changes Since V4:
- Fixed errors identified by Sashiko bot on charger driver that could
result in an improper value being written to the registers.
- Corrected some error handling in the panel driver.
Changes Since V5:
- Corrected minor errors in documentation and added default values.
- Further corrected clamping behavior in charger driver.
Changes Since V6:
- Additional corrections as suggested by Sashiko bot to fix potential
edge cases during probe for sgm41542.
- Corrected an improperly shifted value being written to register for
sgm41542.
- Changed a MV value to UV in sgm41542 driver to simplify function.
Chris Morgan (6):
dt-bindings: power: supply: sgm41542: document sgm41542
power: supply: sgm41542: Add SG Micro sgm41542 charger
dt-bindings: display: panel: Add Anbernic TD4310 panel
drm/panel: anbernic-td4310: Add RG Vita Pro panel
dt-bindings: arm: rockchip: Add Anbernic RG Vita-Pro
arm64: dts: rockchip: Add Anbernic RG Vita-Pro
.../devicetree/bindings/arm/rockchip.yaml | 6 +
.../display/panel/anbernic,td4310.yaml | 66 +
.../power/supply/sgmicro,sgm41542.yaml | 96 ++
arch/arm64/boot/dts/rockchip/Makefile | 1 +
.../rockchip/rk3576-anbernic-rg-vita-pro.dts | 1327 +++++++++++++++++
drivers/gpu/drm/panel/Kconfig | 10 +
drivers/gpu/drm/panel/Makefile | 1 +
drivers/gpu/drm/panel/panel-anbernic-td4310.c | 257 ++++
drivers/power/supply/Kconfig | 8 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/sgm41542_charger.c | 1067 +++++++++++++
11 files changed, 2840 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/panel/anbernic,td4310.yaml
create mode 100644 Documentation/devicetree/bindings/power/supply/sgmicro,sgm41542.yaml
create mode 100644 arch/arm64/boot/dts/rockchip/rk3576-anbernic-rg-vita-pro.dts
create mode 100644 drivers/gpu/drm/panel/panel-anbernic-td4310.c
create mode 100644 drivers/power/supply/sgm41542_charger.c
--
2.43.0
^ permalink raw reply
* Re: [PATCH v4 14/14] video: leds: backlight: lm3533: Support getting LED sources from DT
From: Svyatoslav Ryhel @ 2026-06-10 14:45 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Helge Deller,
Johan Hovold, dri-devel, linux-leds, devicetree, linux-kernel,
linux-iio, linux-fbdev
In-Reply-To: <aihoH9Pmzufa9pnE@ashevche-desk.local>
вт, 9 черв. 2026 р. о 22:23 Andy Shevchenko <andriy.shevchenko@intel.com> пише:
>
> On Sat, Jun 06, 2026 at 07:57:38AM +0300, Svyatoslav Ryhel wrote:
> > Add Control Bank to HVLED/LVLED muxing support based on the led-sources
> > defined in the device tree.
>
> ...
>
> > static int lm3533_led_setup(struct lm3533_led *led)
> > {
> > - int ret;
> > + u32 output_cfg_shift = 0;
>
> No need to assign the default to this.
>
This is a personal preference, compiler will optimize it if this is not needed.
> > + u32 output_cfg_val = 0;
> > + u32 output_cfg_mask = 0;
> > + int ret, i;
>
> No need to add 'i'.
>
This is personal preference as well. There is no strict rule that
iteration variable must be defined strictly in the for loop.
> > + if (led->num_leds) {
> > + for (i = 0; i < led->num_leds; i++) {
>
> for (unsigned int i = 0; i < led->num_leds; i++) {
>
> > + if (led->leds[i] >= LM3533_LVCTRLBANK_MAX)
> > + continue;
> > +
> > + output_cfg_shift = led->leds[i] * 2;
> > + output_cfg_val |= led->id << output_cfg_shift;
> > + output_cfg_mask |= OUTPUT_LVLED_MASK << output_cfg_shift;
> > + }
> > +
> > + /* LVLED1, LVLED2 and LVLED3 */
> > + ret = regmap_update_bits(led->regmap, LM3533_REG_OUTPUT_CONF1,
> > + output_cfg_mask << OUTPUT_CONF1_SHIFT,
> > + output_cfg_val << OUTPUT_CONF1_SHIFT);
> > + if (ret)
> > + return ret;
> > +
> > + /* LVLED4 and LVLED5 */
> > + ret = regmap_update_bits(led->regmap, LM3533_REG_OUTPUT_CONF2,
> > + output_cfg_mask >> OUTPUT_CONF2_SHIFT,
> > + output_cfg_val >> OUTPUT_CONF2_SHIFT);
> > + if (ret)
> > + return ret;
> > + }
>
> ...
>
> > + if (led->num_leds > 0) {
> > + ret = device_property_read_u32_array(&pdev->dev, "led-sources",
> > + led->leds, led->num_leds);
> > + if (ret) {
> > + dev_err(&pdev->dev, "failed to get led-sources\n");
> > + goto err_deregister;
> > + }
> > + }
>
> This and other pieces may benefit from local variable
>
> struct device *dev = &pdev->dev;
>
> defined at the top of the function.
>
Yes, but this would require an additional patch, which I will not add
to this already overinflated series.
> ...
>
> > static int lm3533_bl_setup(struct lm3533_bl *bl)
>
> As per above.
>
> --
> With Best Regards,
> Andy Shevchenko
>
>
^ permalink raw reply
* Re: [PATCH v13 17/22] media: i2c: maxim-serdes: add MAX96724 driver
From: Niklas Söderlund @ 2026-06-10 14:42 UTC (permalink / raw)
To: dumitru.ceclan
Cc: Tomi Valkeinen, Mauro Carvalho Chehab, Sakari Ailus,
Laurent Pinchart, Julien Massot, Rob Herring, Greg Kroah-Hartman,
mitrutzceclan, linux-media, linux-kernel, devicetree,
linux-staging, linux-gpio, Martin Hecht, Cosmin Tanislav,
Cory Keitz
In-Reply-To: <20260604-gmsl2-3_serdes-v13-17-9d8a4919983b@analog.com>
Hi,
Thanks for your work.
This patch gives me new compiler warnings, can they be avoided?
.../max96724.c:402 max96724_log_phy_status() warn: subtract is higher precedence than shift
.../max96724.c:409 max96724_log_phy_status() warn: subtract is higher precedence than shift
.../max96724.c:588 max96724_init_phy() warn: subtract is higher precedence than shift
.../max96724.c:756 max96724_set_pipe_remap() warn: subtract is higher precedence than shift
.../max96724.c:796 max96724_set_pipe_phy() warn: subtract is higher precedence than shift
.../max96724.c:818 max96724_set_pipe_stream_id() warn: subtract is higher precedence than shift
.../max96724.c:830 max96724_set_pipe_link() warn: subtract is higher precedence than shift
.../max96724.c:942 max96724_set_link_version() warn: subtract is higher precedence than shift
On 2026-06-04 17:14:04 +0300, Dumitru Ceclan via B4 Relay wrote:
> From: Cosmin Tanislav <demonsingur@gmail.com>
>
> Add a new MAX96724 driver that also supports MAX96712, MAX96724F
> and MAX96724R.
>
> Integrate it with the common deserializer framework, while keeping
> compatibility with existing usecases, avoiding code duplication, and
> also enabling more features across all chips.
>
> Signed-off-by: Cosmin Tanislav <demonsingur@gmail.com>
> Tested-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> Tested-by: Cory Keitz <ckeitz@amazon.com>
> ---
> drivers/media/i2c/maxim-serdes/Kconfig | 13 +
> drivers/media/i2c/maxim-serdes/Makefile | 1 +
> drivers/media/i2c/maxim-serdes/max96724.c | 1279 +++++++++++++++++++++++++++++
> 3 files changed, 1293 insertions(+)
>
> diff --git a/drivers/media/i2c/maxim-serdes/Kconfig b/drivers/media/i2c/maxim-serdes/Kconfig
> index c811790c09b9..9d3621ae8d90 100644
> --- a/drivers/media/i2c/maxim-serdes/Kconfig
> +++ b/drivers/media/i2c/maxim-serdes/Kconfig
> @@ -34,3 +34,16 @@ config VIDEO_MAX96717
>
> To compile this driver as a module, choose M here: the module
> will be called max96717.
> +
> +config VIDEO_MAX96724
> + tristate "Maxim MAX96724 Quad Deserializer support"
> + depends on I2C
> + depends on VIDEO_DEV
> + select VIDEO_MAXIM_SERDES
> + help
> + This driver supports the Maxim MAX96712, MAX96724, MAX96724F,
> + MAX96724R Quad Deserializers, which convert from four GMSL2
> + links to up to four MIPI D-PHY or C-PHY outputs.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called max96724.
> diff --git a/drivers/media/i2c/maxim-serdes/Makefile b/drivers/media/i2c/maxim-serdes/Makefile
> index 04abda6a5437..b6d5aebfaee1 100644
> --- a/drivers/media/i2c/maxim-serdes/Makefile
> +++ b/drivers/media/i2c/maxim-serdes/Makefile
> @@ -2,3 +2,4 @@
> max-serdes-objs := max_serdes.o max_ser.o max_des.o
> obj-$(CONFIG_VIDEO_MAXIM_SERDES) += max-serdes.o
> obj-$(CONFIG_VIDEO_MAX96717) += max96717.o
> +obj-$(CONFIG_VIDEO_MAX96724) += max96724.o
> diff --git a/drivers/media/i2c/maxim-serdes/max96724.c b/drivers/media/i2c/maxim-serdes/max96724.c
> new file mode 100644
> index 000000000000..7fc51254e1ef
> --- /dev/null
> +++ b/drivers/media/i2c/maxim-serdes/max96724.c
> @@ -0,0 +1,1279 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Maxim MAX96724 Quad GMSL2 Deserializer Driver
> + *
> + * Copyright (C) 2025 Analog Devices Inc.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/delay.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/i2c.h>
> +#include <linux/iopoll.h>
> +#include <linux/module.h>
> +#include <linux/of_graph.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +
> +#include "max_des.h"
> +
> +#define MAX96724_REG0 0x0
> +
> +#define MAX96724_REG3 0x3
> +#define MAX96724_REG3_CC_PORT_SEL(n) GENMASK((n) * 2 + 1, (n) * 2)
> +#define MAX96724_REG3_CC_PORT_SEL_MASK (MAX96724_REG3_CC_PORT_SEL(0) | \
> + MAX96724_REG3_CC_PORT_SEL(1) | \
> + MAX96724_REG3_CC_PORT_SEL(2) | \
> + MAX96724_REG3_CC_PORT_SEL(3))
> +#define MAX96724_REG3_CC_PORT_SEL_PORT0 0x2
> +#define MAX96724_REG3_CC_PORT_SEL_PORT1 0x1
> +#define MAX96724_REG3_CC_PORT_CFG(sel) \
> + (FIELD_PREP(MAX96724_REG3_CC_PORT_SEL(0), (sel)) | \
> + FIELD_PREP(MAX96724_REG3_CC_PORT_SEL(1), (sel)) | \
> + FIELD_PREP(MAX96724_REG3_CC_PORT_SEL(2), (sel)) | \
> + FIELD_PREP(MAX96724_REG3_CC_PORT_SEL(3), (sel)))
> +#define MAX96724_REG3_CC_PORT_CFG_PORT0 \
> + MAX96724_REG3_CC_PORT_CFG(MAX96724_REG3_CC_PORT_SEL_PORT0)
> +#define MAX96724_REG3_CC_PORT_CFG_PORT1 \
> + MAX96724_REG3_CC_PORT_CFG(MAX96724_REG3_CC_PORT_SEL_PORT1)
> +
> +#define MAX96724_REG6 0x6
> +#define MAX96724_REG6_LINK_EN GENMASK(3, 0)
> +
> +#define MAX96724_REG7 0x7
> +#define MAX96724_REG7_CC_CROSSOVER_SEL GENMASK(7, 4)
> +
> +#define MAX96724_DEBUG_EXTRA 0x9
> +#define MAX96724_DEBUG_EXTRA_PCLK_SRC GENMASK(1, 0)
> +#define MAX96724_DEBUG_EXTRA_PCLK_SRC_25MHZ 0b00
> +#define MAX96724_DEBUG_EXTRA_PCLK_SRC_75MHZ 0b01
> +#define MAX96724_DEBUG_EXTRA_PCLK_SRC_USE_PIPE 0b10
> +
> +#define MAX96724_REG26(x) (0x10 + (x) / 2)
> +#define MAX96724_REG26_RX_RATE_PHY(x) (GENMASK(1, 0) << (4 * ((x) % 2)))
> +#define MAX96724_REG26_RX_RATE_3GBPS 0b01
> +#define MAX96724_REG26_RX_RATE_6GBPS 0b10
> +
> +#define MAX96724_PWR1 0x13
> +#define MAX96724_PWR1_RESET_ALL BIT(6)
> +
> +#define MAX96724_CTRL1 0x18
> +#define MAX96724_CTRL1_RESET_ONESHOT GENMASK(3, 0)
> +
> +#define MAX96724_VIDEO_PIPE_SEL(p) (0xf0 + (p) / 2)
> +#define MAX96724_VIDEO_PIPE_SEL_STREAM(p) (GENMASK(1, 0) << (4 * ((p) % 2)))
> +#define MAX96724_VIDEO_PIPE_SEL_LINK(p) (GENMASK(3, 2) << (4 * ((p) % 2)))
> +
> +#define MAX96724_VIDEO_PIPE_EN 0xf4
> +#define MAX96724_VIDEO_PIPE_EN_MASK(p) BIT(p)
> +#define MAX96724_VIDEO_PIPE_EN_STREAM_SEL_ALL BIT(4)
> +
> +#define MAX96724_VPRBS(p) (0x1dc + (p) * 0x20)
> +#define MAX96724_VPRBS_VIDEO_LOCK BIT(0)
> +#define MAX96724_VPRBS_PATGEN_CLK_SRC BIT(7)
> +#define MAX96724_VPRBS_PATGEN_CLK_SRC_150MHZ 0b0
> +#define MAX96724_VPRBS_PATGEN_CLK_SRC_375MHZ 0b1
> +
> +#define MAX96724_BACKTOP12 0x40b
> +#define MAX96724_BACKTOP12_CSI_OUT_EN BIT(1)
> +
> +#define MAX96724_BACKTOP21(p) (0x414 + (p) / 4 * 0x20)
> +#define MAX96724_BACKTOP21_BPP8DBL(p) BIT(4 + (p) % 4)
> +
> +#define MAX96724_BACKTOP22(x) (0x415 + (x) * 0x3)
> +#define MAX96724_BACKTOP22_PHY_CSI_TX_DPLL GENMASK(4, 0)
> +#define MAX96724_BACKTOP22_PHY_CSI_TX_DPLL_EN BIT(5)
> +
> +#define MAX96724_BACKTOP24(p) (0x417 + (p) / 4 * 0x20)
> +#define MAX96724_BACKTOP24_BPP8DBL_MODE(p) BIT(4 + (p) % 4)
> +
> +#define MAX96724_BACKTOP30(p) (0x41d + (p) / 4 * 0x20)
> +#define MAX96724_BACKTOP30_BPP10DBL3 BIT(4)
> +#define MAX96724_BACKTOP30_BPP10DBL3_MODE BIT(5)
> +
> +#define MAX96724_BACKTOP31(p) (0x41e + (p) / 4 * 0x20)
> +#define MAX96724_BACKTOP31_BPP10DBL2 BIT(6)
> +#define MAX96724_BACKTOP31_BPP10DBL2_MODE BIT(7)
> +
> +#define MAX96724_BACKTOP32(p) (0x41f + (p) / 4 * 0x20)
> +#define MAX96724_BACKTOP32_BPP12(p) BIT((p) % 4)
> +#define MAX96724_BACKTOP32_BPP10DBL0 BIT(4)
> +#define MAX96724_BACKTOP32_BPP10DBL0_MODE BIT(5)
> +#define MAX96724_BACKTOP32_BPP10DBL1 BIT(6)
> +#define MAX96724_BACKTOP32_BPP10DBL1_MODE BIT(7)
> +
> +#define MAX96724_MIPI_PHY0 0x8a0
> +#define MAX96724_MIPI_PHY0_PHY_CONFIG GENMASK(4, 0)
> +#define MAX96724_MIPI_PHY0_PHY_4X2 BIT(0)
> +#define MAX96724_MIPI_PHY0_PHY_2X4 BIT(2)
> +#define MAX96724_MIPI_PHY0_PHY_1X4A_2X2 BIT(3)
> +#define MAX96724_MIPI_PHY0_PHY_1X4B_2X2 BIT(4)
> +#define MAX96724_MIPI_PHY0_FORCE_CSI_OUT_EN BIT(7)
> +
> +#define MAX96724_MIPI_PHY2 0x8a2
> +#define MAX96724_MIPI_PHY2_PHY_STDB_N_4(x) (GENMASK(5, 4) << ((x) / 2 * 2))
> +#define MAX96724_MIPI_PHY2_PHY_STDB_N_2(x) (BIT(4 + (x)))
> +
> +#define MAX96724_MIPI_PHY3(x) (0x8a3 + (x) / 2)
> +#define MAX96724_MIPI_PHY3_PHY_LANE_MAP_4 GENMASK(7, 0)
> +#define MAX96724_MIPI_PHY3_PHY_LANE_MAP_2(x) (GENMASK(3, 0) << (4 * ((x) % 2)))
> +
> +#define MAX96724_MIPI_PHY5(x) (0x8a5 + (x) / 2)
> +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_4_0_1 GENMASK(1, 0)
> +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_4_2_3 GENMASK(4, 3)
> +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_4_CLK BIT(5)
> +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_2(x) (GENMASK(1, 0) << (3 * ((x) % 2)))
> +#define MAX96724_MIPI_PHY5_PHY_POL_MAP_2_CLK(x) BIT(2 + 3 * ((x) % 2))
> +
> +#define MAX96724_MIPI_PHY13 0x8ad
> +#define MAX96724_MIPI_PHY13_T_T3_PREBEGIN GENMASK(5, 0)
> +#define MAX96724_MIPI_PHY13_T_T3_PREBEGIN_64X7 FIELD_PREP(MAX96724_MIPI_PHY13_T_T3_PREBEGIN, 63)
> +
> +#define MAX96724_MIPI_PHY14 0x8ae
> +#define MAX96724_MIPI_PHY14_T_T3_PREP GENMASK(1, 0)
> +#define MAX96724_MIPI_PHY14_T_T3_PREP_55NS FIELD_PREP(MAX96724_MIPI_PHY14_T_T3_PREP, 0b01)
> +#define MAX96724_MIPI_PHY14_T_T3_POST GENMASK(6, 2)
> +#define MAX96724_MIPI_PHY14_T_T3_POST_32X7 FIELD_PREP(MAX96724_MIPI_PHY14_T_T3_POST, 31)
> +
> +#define MAX96724_MIPI_CTRL_SEL 0x8ca
> +#define MAX96724_MIPI_CTRL_SEL_MASK(p) (GENMASK(1, 0) << ((p) * 2))
> +
> +#define MAX96724_MIPI_PHY25(x) (0x8d0 + (x) / 2)
> +#define MAX96724_MIPI_PHY25_CSI2_TX_PKT_CNT(x) (GENMASK(3, 0) << (4 * ((x) % 2)))
> +
> +#define MAX96724_MIPI_PHY27(x) (0x8d2 + (x) / 2)
> +#define MAX96724_MIPI_PHY27_PHY_PKT_CNT(x) (GENMASK(3, 0) << (4 * ((x) % 2)))
> +
> +#define MAX96724_MIPI_TX3(x) (0x903 + (x) * 0x40)
> +#define MAX96724_MIPI_TX3_DESKEW_INIT_8X32K FIELD_PREP(GENMASK(2, 0), 0b001)
> +#define MAX96724_MIPI_TX3_DESKEW_INIT_AUTO BIT(7)
> +
> +#define MAX96724_MIPI_TX4(x) (0x904 + (x) * 0x40)
> +#define MAX96724_MIPI_TX4_DESKEW_PER_2K FIELD_PREP(GENMASK(2, 0), 0b001)
> +#define MAX96724_MIPI_TX4_DESKEW_PER_AUTO BIT(7)
> +
> +#define MAX96724_MIPI_TX10(x) (0x90a + (x) * 0x40)
> +#define MAX96724_MIPI_TX10_CSI2_CPHY_EN BIT(5)
> +#define MAX96724_MIPI_TX10_CSI2_LANE_CNT GENMASK(7, 6)
> +
> +#define MAX96724_MIPI_TX11(p) (0x90b + (p) * 0x40)
> +#define MAX96724_MIPI_TX12(p) (0x90c + (p) * 0x40)
> +
> +#define MAX96724_MIPI_TX13(p, x) (0x90d + (p) * 0x40 + (x) * 0x2)
> +#define MAX96724_MIPI_TX13_MAP_SRC_DT GENMASK(5, 0)
> +#define MAX96724_MIPI_TX13_MAP_SRC_VC GENMASK(7, 6)
> +
> +#define MAX96724_MIPI_TX14(p, x) (0x90e + (p) * 0x40 + (x) * 0x2)
> +#define MAX96724_MIPI_TX14_MAP_DST_DT GENMASK(5, 0)
> +#define MAX96724_MIPI_TX14_MAP_DST_VC GENMASK(7, 6)
> +
> +#define MAX96724_MIPI_TX45(p, x) (0x92d + (p) * 0x40 + (x) / 4)
> +#define MAX96724_MIPI_TX45_MAP_DPHY_DEST(x) (GENMASK(1, 0) << (2 * ((x) % 4)))
> +
> +#define MAX96724_MIPI_TX51(x) (0x933 + (x) * 0x40)
> +#define MAX96724_MIPI_TX51_ALT_MEM_MAP_12 BIT(0)
> +#define MAX96724_MIPI_TX51_ALT_MEM_MAP_8 BIT(1)
> +#define MAX96724_MIPI_TX51_ALT_MEM_MAP_10 BIT(2)
> +#define MAX96724_MIPI_TX51_ALT2_MEM_MAP_8 BIT(4)
> +
> +#define MAX96724_MIPI_TX54(x) (0x936 + (x) * 0x40)
> +#define MAX96724_MIPI_TX54_TUN_EN BIT(0)
> +
> +#define MAX96724_MIPI_TX57(x) (0x939 + (x) * 0x40)
> +#define MAX96724_MIPI_TX57_TUN_DEST GENMASK(5, 4)
> +#define MAX96724_MIPI_TX57_DIS_AUTO_TUN_DET BIT(6)
> +#define MAX96724_DET(p) BIT(p)
> +
> +#define MAX96724_PATGEN_0 0x1050
> +#define MAX96724_PATGEN_0_VTG_MODE GENMASK(1, 0)
> +#define MAX96724_PATGEN_0_VTG_MODE_FREE_RUNNING 0b11
> +#define MAX96724_PATGEN_0_DE_INV BIT(2)
> +#define MAX96724_PATGEN_0_HS_INV BIT(3)
> +#define MAX96724_PATGEN_0_VS_INV BIT(4)
> +#define MAX96724_PATGEN_0_GEN_DE BIT(5)
> +#define MAX96724_PATGEN_0_GEN_HS BIT(6)
> +#define MAX96724_PATGEN_0_GEN_VS BIT(7)
> +
> +#define MAX96724_PATGEN_1 0x1051
> +#define MAX96724_PATGEN_1_PATGEN_MODE GENMASK(5, 4)
> +#define MAX96724_PATGEN_1_PATGEN_MODE_DISABLED 0b00
> +#define MAX96724_PATGEN_1_PATGEN_MODE_CHECKER 0b01
> +#define MAX96724_PATGEN_1_PATGEN_MODE_GRADIENT 0b10
> +
> +#define MAX96724_VS_DLY_2 0x1052
> +#define MAX96724_VS_HIGH_2 0x1055
> +#define MAX96724_VS_LOW_2 0x1058
> +#define MAX96724_V2H_2 0x105b
> +#define MAX96724_HS_HIGH_1 0x105e
> +#define MAX96724_HS_LOW_1 0x1060
> +#define MAX96724_HS_CNT_1 0x1062
> +#define MAX96724_V2D_2 0x1064
> +#define MAX96724_DE_HIGH_1 0x1067
> +#define MAX96724_DE_LOW_1 0x1069
> +#define MAX96724_DE_CNT_1 0x106b
> +#define MAX96724_GRAD_INCR 0x106d
> +#define MAX96724_CHKR_COLOR_A_L 0x106e
> +#define MAX96724_CHKR_COLOR_B_L 0x1071
> +#define MAX96724_CHKR_RPT_A 0x1074
> +#define MAX96724_CHKR_RPT_B 0x1075
> +#define MAX96724_CHKR_ALT 0x1076
> +
> +#define MAX96724_DE_DET 0x11f0
> +#define MAX96724_HS_DET 0x11f1
> +#define MAX96724_VS_DET 0x11f2
> +#define MAX96724_HS_POL 0x11f3
> +#define MAX96724_VS_POL 0x11f4
> +#define MAX96724_DET(p) BIT(p)
> +
> +#define MAX96724_DPLL_0(x) (0x1c00 + (x) * 0x100)
> +#define MAX96724_DPLL_0_CONFIG_SOFT_RST_N BIT(0)
> +
> +#define MAX96724_PHY1_ALT_CLOCK 5
> +
> +static const struct regmap_config max96724_i2c_regmap = {
> + .reg_bits = 16,
> + .val_bits = 8,
> + .max_register = 0x1f00,
> +};
> +
> +struct max96724_priv {
> + struct max_des des;
> + const struct max96724_chip_info *info;
> +
> + struct device *dev;
> + struct i2c_client *client;
> + struct regmap *regmap;
> +
> + struct gpio_desc *gpiod_enable;
> + unsigned int cc_port_cfg;
> +};
> +
> +struct max96724_chip_info {
> + unsigned int versions;
> + unsigned int modes;
> + bool supports_pipe_stream_autoselect;
> + unsigned int num_pipes;
> +
> + int (*set_pipe_phy)(struct max_des *des, struct max_des_pipe *pipe,
> + struct max_des_phy *phy);
> + int (*set_pipe_tunnel_phy)(struct max_des *des, struct max_des_pipe *pipe,
> + struct max_des_phy *phy);
> + int (*set_pipe_tunnel_enable)(struct max_des *des, struct max_des_pipe *pipe,
> + bool enable);
> +};
> +
> +#define des_to_priv(_des) \
> + container_of(_des, struct max96724_priv, des)
> +
> +static int max96724_wait_for_device(struct max96724_priv *priv)
> +{
> + unsigned int val;
> + int ret, err;
> +
> + err = read_poll_timeout(regmap_read, ret,
> + !ret && val,
> + 100 * USEC_PER_MSEC,
> + 1 * USEC_PER_SEC, false,
> + priv->regmap, MAX96724_REG0, &val);
> + if (err)
> + dev_err(priv->dev, "Timeout waiting for deserializer: %d\n", ret);
> +
> + return err;
> +}
> +
> +static int max96724_reset(struct max96724_priv *priv)
> +{
> + int ret;
> +
> + ret = max96724_wait_for_device(priv);
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(priv->regmap, MAX96724_PWR1,
> + MAX96724_PWR1_RESET_ALL,
> + FIELD_PREP(MAX96724_PWR1_RESET_ALL, 1));
> + if (ret)
> + return ret;
> +
> + fsleep(10000);
> +
> + ret = max96724_wait_for_device(priv);
> + if (ret)
> + return ret;
> +
> + /* Restore I2C control-channel access after a reset. */
> + return regmap_update_bits(priv->regmap, MAX96724_REG3,
> + MAX96724_REG3_CC_PORT_SEL_MASK,
> + priv->cc_port_cfg);
> +}
> +
> +static int __maybe_unused max96724_reg_read(struct max_des *des, unsigned int reg,
> + unsigned int *val)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> +
> + return regmap_read(priv->regmap, reg, val);
> +}
> +
> +static int __maybe_unused max96724_reg_write(struct max_des *des, unsigned int reg,
> + unsigned int val)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> +
> + return regmap_write(priv->regmap, reg, val);
> +}
> +
> +static unsigned int max96724_phy_id(struct max_des *des, struct max_des_phy *phy)
> +{
> + unsigned int num_hw_data_lanes = max_des_phy_hw_data_lanes(des, phy);
> +
> + /* PHY 1 is the master PHY when combining PHY 0 and PHY 1. */
> + if (phy->index == 0 && num_hw_data_lanes == 4)
> + return 1;
> +
> + if (phy->index == 1 && !des->phys[1].enabled)
> + return 0;
> +
> + return phy->index;
> +}
> +
> +static int max96724_log_pipe_status(struct max_des *des,
> + struct max_des_pipe *pipe)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = pipe->index;
> + unsigned int val, mask;
> + int ret;
> +
> + ret = regmap_read(priv->regmap, MAX96724_VPRBS(index), &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\tvideo_lock: %u\n",
> + !!(val & MAX96724_VPRBS_VIDEO_LOCK));
> +
> + mask = MAX96724_DET(index);
> +
> + ret = regmap_read(priv->regmap, MAX96724_DE_DET, &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\tde_det: %u\n", !!(val & mask));
> +
> + ret = regmap_read(priv->regmap, MAX96724_HS_DET, &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\ths_det: %u\n", !!(val & mask));
> +
> + ret = regmap_read(priv->regmap, MAX96724_VS_DET, &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\tvs_det: %u\n", !!(val & mask));
> +
> + ret = regmap_read(priv->regmap, MAX96724_HS_POL, &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\ths_pol: %u\n", !!(val & mask));
> +
> + ret = regmap_read(priv->regmap, MAX96724_VS_POL, &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\tvs_pol: %u\n", !!(val & mask));
> +
> + return 0;
> +}
> +
> +static int max96724_log_phy_status(struct max_des *des,
> + struct max_des_phy *phy)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = max96724_phy_id(des, phy);
> + unsigned int val;
> + int ret;
> +
> + ret = regmap_read(priv->regmap, MAX96724_MIPI_PHY25(index), &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\tcsi2_pkt_cnt: %lu\n",
> + field_get(MAX96724_MIPI_PHY25_CSI2_TX_PKT_CNT(index), val));
> +
> + ret = regmap_read(priv->regmap, MAX96724_MIPI_PHY27(index), &val);
> + if (ret)
> + return ret;
> +
> + dev_info(priv->dev, "\tphy_pkt_cnt: %lu\n",
> + field_get(MAX96724_MIPI_PHY27_PHY_PKT_CNT(index), val));
> +
> + return 0;
> +}
> +
> +static int max96724_set_enable(struct max_des *des, bool enable)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> +
> + return regmap_assign_bits(priv->regmap, MAX96724_BACKTOP12,
> + MAX96724_BACKTOP12_CSI_OUT_EN, enable);
> +}
> +
> +static const unsigned int max96724_phys_configs_reg_val[] = {
> + MAX96724_MIPI_PHY0_PHY_1X4A_2X2,
> + MAX96724_MIPI_PHY0_PHY_2X4,
> +
> + MAX96724_MIPI_PHY0_PHY_4X2,
> + MAX96724_MIPI_PHY0_PHY_1X4A_2X2,
> + MAX96724_MIPI_PHY0_PHY_1X4B_2X2,
> + MAX96724_MIPI_PHY0_PHY_2X4,
> +};
> +
> +static const struct max_serdes_phys_config max96724_phys_configs[] = {
> + /*
> + * PHY 1 can be in 4-lane mode (combining lanes of PHY 0 and PHY 1)
> + * but only use the data lanes of PHY0, while continuing to use the
> + * clock lane of PHY 1.
> + * Specifying clock-lanes as 5 turns on alternate clocking mode.
> + */
> + { { 2, 0, 2, 2 }, { MAX96724_PHY1_ALT_CLOCK, 0, 0, 0 } },
> + { { 2, 0, 4, 0 }, { MAX96724_PHY1_ALT_CLOCK, 0, 0, 0 } },
> +
> + /*
> + * When combining PHY 0 and PHY 1 to make them function in 4-lane mode,
> + * PHY 1 is the master PHY, but we use PHY 0 here to maintain
> + * compatibility.
> + */
> + { { 2, 2, 2, 2 } },
> + { { 4, 0, 2, 2 } },
> + { { 2, 2, 4, 0 } },
> + { { 4, 0, 4, 0 } },
> +};
> +
> +static int max96724_init_tpg(struct max_des *des)
> +{
> + const struct reg_sequence regs[] = {
> + { MAX96724_GRAD_INCR, MAX_SERDES_GRAD_INCR },
> + REG_SEQUENCE_3_LE(MAX96724_CHKR_COLOR_A_L,
> + MAX_SERDES_CHECKER_COLOR_A),
> + REG_SEQUENCE_3_LE(MAX96724_CHKR_COLOR_B_L,
> + MAX_SERDES_CHECKER_COLOR_B),
> + { MAX96724_CHKR_RPT_A, MAX_SERDES_CHECKER_SIZE },
> + { MAX96724_CHKR_RPT_B, MAX_SERDES_CHECKER_SIZE },
> + { MAX96724_CHKR_ALT, MAX_SERDES_CHECKER_SIZE },
> + };
> + struct max96724_priv *priv = des_to_priv(des);
> +
> + return regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs));
> +}
> +
> +static int max96724_init(struct max_des *des)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int i;
> + int ret;
> +
> + if (priv->info->set_pipe_tunnel_enable) {
> + for (i = 0; i < des->info->num_pipes; i++) {
> + ret = regmap_set_bits(priv->regmap, MAX96724_MIPI_TX57(i),
> + MAX96724_MIPI_TX57_DIS_AUTO_TUN_DET);
> + if (ret)
> + return ret;
> + }
> + }
> +
> + if (priv->info->supports_pipe_stream_autoselect) {
> + /* Enable stream autoselect. */
> + ret = regmap_set_bits(priv->regmap, MAX96724_VIDEO_PIPE_EN,
> + MAX96724_VIDEO_PIPE_EN_STREAM_SEL_ALL);
> + if (ret)
> + return ret;
> + }
> +
> + /* Enable I2C control ports crossover. */
> + ret = regmap_set_bits(priv->regmap, MAX96724_REG7,
> + MAX96724_REG7_CC_CROSSOVER_SEL);
> + if (ret)
> + return ret;
> +
> + /* Set PHY mode. */
> + ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_PHY0,
> + MAX96724_MIPI_PHY0_PHY_CONFIG,
> + max96724_phys_configs_reg_val[des->phys_config]);
> + if (ret)
> + return ret;
> +
> + return max96724_init_tpg(des);
> +}
> +
> +static int max96724_init_phy(struct max_des *des, struct max_des_phy *phy)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + bool is_cphy = phy->bus_type == V4L2_MBUS_CSI2_CPHY;
> + unsigned int num_data_lanes = phy->mipi.num_data_lanes;
> + unsigned int num_hw_data_lanes;
> + unsigned int index;
> + unsigned int used_data_lanes = 0;
> + unsigned int val, mask;
> + unsigned int i;
> + u64 dpll_freq;
> + int ret;
> +
> + index = max96724_phy_id(des, phy);
> + num_hw_data_lanes = max_des_phy_hw_data_lanes(des, phy);
> + dpll_freq = is_cphy ? phy->link_frequency
> + : phy->link_frequency * 2;
> +
> + ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_TX10(index),
> + MAX96724_MIPI_TX10_CSI2_LANE_CNT,
> + FIELD_PREP(MAX96724_MIPI_TX10_CSI2_LANE_CNT,
> + num_data_lanes - 1));
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX10(index),
> + MAX96724_MIPI_TX10_CSI2_CPHY_EN, is_cphy);
> + if (ret)
> + return ret;
> +
> + /* Configure lane mapping. */
> + val = 0;
> + for (i = 0; i < num_hw_data_lanes ; i++) {
> + unsigned int map;
> +
> + if (i < num_data_lanes)
> + map = phy->mipi.data_lanes[i] - 1;
> + else
> + map = ffz(used_data_lanes);
> +
> + val |= map << (i * 2);
> + used_data_lanes |= BIT(map);
> + }
> +
> + if (num_hw_data_lanes == 4)
> + mask = MAX96724_MIPI_PHY3_PHY_LANE_MAP_4;
> + else
> + mask = MAX96724_MIPI_PHY3_PHY_LANE_MAP_2(index);
> +
> + ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_PHY3(index),
> + mask, field_prep(mask, val));
> + if (ret)
> + return ret;
> +
> + /* Configure lane polarity. */
> + for (i = 0, val = 0; i < num_data_lanes; i++)
> + if (phy->mipi.lane_polarities[i + 1])
> + val |= BIT(i);
> +
> + if (num_hw_data_lanes == 4) {
> + ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_PHY5(index),
> + MAX96724_MIPI_PHY5_PHY_POL_MAP_4_0_1 |
> + MAX96724_MIPI_PHY5_PHY_POL_MAP_4_2_3,
> + FIELD_PREP(MAX96724_MIPI_PHY5_PHY_POL_MAP_4_0_1,
> + val) |
> + FIELD_PREP(MAX96724_MIPI_PHY5_PHY_POL_MAP_4_2_3,
> + val >> 2));
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_PHY5(index),
> + MAX96724_MIPI_PHY5_PHY_POL_MAP_4_CLK,
> + phy->mipi.lane_polarities[0]);
> + if (ret)
> + return ret;
> + } else {
> + ret = regmap_update_bits(priv->regmap, MAX96724_MIPI_PHY5(index),
> + MAX96724_MIPI_PHY5_PHY_POL_MAP_2(index),
> + field_prep(MAX96724_MIPI_PHY5_PHY_POL_MAP_2(index), val));
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_PHY5(index),
> + MAX96724_MIPI_PHY5_PHY_POL_MAP_2_CLK(index),
> + phy->mipi.lane_polarities[0]);
> + if (ret)
> + return ret;
> + }
> +
> + if (!is_cphy && dpll_freq > 1500000000ull) {
> + /* Enable initial deskew with 2 x 32k UI. */
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX3(index),
> + MAX96724_MIPI_TX3_DESKEW_INIT_AUTO |
> + MAX96724_MIPI_TX3_DESKEW_INIT_8X32K);
> + if (ret)
> + return ret;
> +
> + /* Enable periodic deskew with 2 x 1k UI.. */
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX4(index),
> + MAX96724_MIPI_TX4_DESKEW_PER_AUTO |
> + MAX96724_MIPI_TX4_DESKEW_PER_2K);
> + if (ret)
> + return ret;
> + } else {
> + /* Disable initial deskew. */
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX3(index), 0x0);
> + if (ret)
> + return ret;
> +
> + /* Disable periodic deskew. */
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX4(index), 0x0);
> + if (ret)
> + return ret;
> + }
> +
> + if (is_cphy) {
> + /* Configure C-PHY timings. */
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_PHY13,
> + MAX96724_MIPI_PHY13_T_T3_PREBEGIN_64X7);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_PHY14,
> + MAX96724_MIPI_PHY14_T_T3_PREP_55NS |
> + MAX96724_MIPI_PHY14_T_T3_POST_32X7);
> + if (ret)
> + return ret;
> + }
> +
> + /* Put DPLL block into reset. */
> + ret = regmap_clear_bits(priv->regmap, MAX96724_DPLL_0(index),
> + MAX96724_DPLL_0_CONFIG_SOFT_RST_N);
> + if (ret)
> + return ret;
> +
> + /* Set DPLL frequency. */
> + ret = regmap_update_bits(priv->regmap, MAX96724_BACKTOP22(index),
> + MAX96724_BACKTOP22_PHY_CSI_TX_DPLL,
> + FIELD_PREP(MAX96724_BACKTOP22_PHY_CSI_TX_DPLL,
> + div_u64(dpll_freq, 100000000)));
> + if (ret)
> + return ret;
> +
> + /* Enable DPLL frequency. */
> + ret = regmap_set_bits(priv->regmap, MAX96724_BACKTOP22(index),
> + MAX96724_BACKTOP22_PHY_CSI_TX_DPLL_EN);
> + if (ret)
> + return ret;
> +
> + /* Pull DPLL block out of reset. */
> + return regmap_set_bits(priv->regmap, MAX96724_DPLL_0(index),
> + MAX96724_DPLL_0_CONFIG_SOFT_RST_N);
> +}
> +
> +static int max96724_set_phy_mode(struct max_des *des, struct max_des_phy *phy,
> + struct max_des_phy_mode *mode)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = max96724_phy_id(des, phy);
> + int ret;
> +
> + /* Set alternate memory map modes. */
> + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index),
> + MAX96724_MIPI_TX51_ALT_MEM_MAP_12,
> + mode->alt_mem_map12);
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index),
> + MAX96724_MIPI_TX51_ALT_MEM_MAP_8,
> + mode->alt_mem_map8);
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index),
> + MAX96724_MIPI_TX51_ALT_MEM_MAP_10,
> + mode->alt_mem_map10);
> + if (ret)
> + return ret;
> +
> + return regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX51(index),
> + MAX96724_MIPI_TX51_ALT2_MEM_MAP_8,
> + mode->alt2_mem_map8);
> +}
> +
> +static int max96724_set_phy_enable(struct max_des *des, struct max_des_phy *phy,
> + bool enable)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = max96724_phy_id(des, phy);
> + unsigned int num_hw_data_lanes;
> + unsigned int mask;
> +
> + num_hw_data_lanes = max_des_phy_hw_data_lanes(des, phy);
> +
> + /*
> + * Some configurations merge two logical PHYs into one hardware PHY.
> + * Skip writes for absorbed PHYs to avoid clobbering the master's bits.
> + */
> + if (!num_hw_data_lanes)
> + return 0;
> +
> + if (num_hw_data_lanes == 4)
> + /* PHY 1 -> bits [5:4] */
> + /* PHY 2 -> bits [7:6] */
> + mask = MAX96724_MIPI_PHY2_PHY_STDB_N_4(index);
> + else
> + mask = MAX96724_MIPI_PHY2_PHY_STDB_N_2(index);
> +
> + return regmap_assign_bits(priv->regmap, MAX96724_MIPI_PHY2, mask, enable);
> +}
> +
> +static int max96724_set_pipe_remap(struct max_des *des,
> + struct max_des_pipe *pipe,
> + unsigned int i,
> + struct max_des_remap *remap)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + struct max_des_phy *phy = &des->phys[remap->phy];
> + unsigned int phy_id = max96724_phy_id(des, phy);
> + unsigned int index = pipe->index;
> + int ret;
> +
> + /* Set source Data Type and Virtual Channel. */
> + /* TODO: implement extended Virtual Channel. */
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX13(index, i),
> + FIELD_PREP(MAX96724_MIPI_TX13_MAP_SRC_DT,
> + remap->from_dt) |
> + FIELD_PREP(MAX96724_MIPI_TX13_MAP_SRC_VC,
> + remap->from_vc));
> + if (ret)
> + return ret;
> +
> + /* Set destination Data Type and Virtual Channel. */
> + /* TODO: implement extended Virtual Channel. */
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX14(index, i),
> + FIELD_PREP(MAX96724_MIPI_TX14_MAP_DST_DT,
> + remap->to_dt) |
> + FIELD_PREP(MAX96724_MIPI_TX14_MAP_DST_VC,
> + remap->to_vc));
> + if (ret)
> + return ret;
> +
> + /* Set destination PHY. */
> + return regmap_update_bits(priv->regmap, MAX96724_MIPI_TX45(index, i),
> + MAX96724_MIPI_TX45_MAP_DPHY_DEST(i),
> + field_prep(MAX96724_MIPI_TX45_MAP_DPHY_DEST(i),
> + phy_id));
> +}
> +
> +static int max96724_set_pipe_remaps_enable(struct max_des *des,
> + struct max_des_pipe *pipe,
> + unsigned int mask)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = pipe->index;
> + int ret;
> +
> + ret = regmap_write(priv->regmap, MAX96724_MIPI_TX11(index), mask);
> + if (ret)
> + return ret;
> +
> + return regmap_write(priv->regmap, MAX96724_MIPI_TX12(index), mask >> 8);
> +}
> +
> +static int max96724_set_pipe_tunnel_phy(struct max_des *des,
> + struct max_des_pipe *pipe,
> + struct max_des_phy *phy)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int phy_index = max96724_phy_id(des, phy);
> +
> + return regmap_update_bits(priv->regmap, MAX96724_MIPI_TX57(pipe->index),
> + MAX96724_MIPI_TX57_TUN_DEST,
> + FIELD_PREP(MAX96724_MIPI_TX57_TUN_DEST,
> + phy_index));
> +}
> +
> +static int max96724_set_pipe_phy(struct max_des *des, struct max_des_pipe *pipe,
> + struct max_des_phy *phy)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int phy_index = max96724_phy_id(des, phy);
> +
> + return regmap_update_bits(priv->regmap, MAX96724_MIPI_CTRL_SEL,
> + MAX96724_MIPI_CTRL_SEL_MASK(pipe->index),
> + field_prep(MAX96724_MIPI_CTRL_SEL_MASK(pipe->index),
> + phy_index));
> +}
> +
> +static int max96724_set_pipe_enable(struct max_des *des, struct max_des_pipe *pipe,
> + bool enable)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = pipe->index;
> +
> + return regmap_assign_bits(priv->regmap, MAX96724_VIDEO_PIPE_EN,
> + MAX96724_VIDEO_PIPE_EN_MASK(index), enable);
> +}
> +
> +static int max96724_set_pipe_stream_id(struct max_des *des, struct max_des_pipe *pipe,
> + unsigned int stream_id)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = pipe->index;
> +
> + return regmap_update_bits(priv->regmap, MAX96724_VIDEO_PIPE_SEL(index),
> + MAX96724_VIDEO_PIPE_SEL_STREAM(index),
> + field_prep(MAX96724_VIDEO_PIPE_SEL_STREAM(index),
> + stream_id));
> +}
> +
> +static int max96724_set_pipe_link(struct max_des *des, struct max_des_pipe *pipe,
> + struct max_des_link *link)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = pipe->index;
> +
> + return regmap_update_bits(priv->regmap, MAX96724_VIDEO_PIPE_SEL(index),
> + MAX96724_VIDEO_PIPE_SEL_LINK(index),
> + field_prep(MAX96724_VIDEO_PIPE_SEL_LINK(index),
> + link->index));
> +}
> +
> +static int max96724_set_pipe_mode(struct max_des *des,
> + struct max_des_pipe *pipe,
> + struct max_des_pipe_mode *mode)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = pipe->index;
> + unsigned int reg, mask, mode_mask;
> + int ret;
> +
> + /* Set 8bit double mode. */
> + ret = regmap_assign_bits(priv->regmap, MAX96724_BACKTOP21(index),
> + MAX96724_BACKTOP21_BPP8DBL(index), mode->dbl8);
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, MAX96724_BACKTOP24(index),
> + MAX96724_BACKTOP24_BPP8DBL_MODE(index),
> + mode->dbl8mode);
> + if (ret)
> + return ret;
> +
> + /* Set 10bit double mode. */
> + if (index % 4 == 3) {
> + reg = MAX96724_BACKTOP30(index);
> + mask = MAX96724_BACKTOP30_BPP10DBL3;
> + mode_mask = MAX96724_BACKTOP30_BPP10DBL3_MODE;
> + } else if (index % 4 == 2) {
> + reg = MAX96724_BACKTOP31(index);
> + mask = MAX96724_BACKTOP31_BPP10DBL2;
> + mode_mask = MAX96724_BACKTOP31_BPP10DBL2_MODE;
> + } else if (index % 4 == 1) {
> + reg = MAX96724_BACKTOP32(index);
> + mask = MAX96724_BACKTOP32_BPP10DBL1;
> + mode_mask = MAX96724_BACKTOP32_BPP10DBL1_MODE;
> + } else {
> + reg = MAX96724_BACKTOP32(index);
> + mask = MAX96724_BACKTOP32_BPP10DBL0;
> + mode_mask = MAX96724_BACKTOP32_BPP10DBL0_MODE;
> + }
> +
> + ret = regmap_assign_bits(priv->regmap, reg, mask, mode->dbl10);
> + if (ret)
> + return ret;
> +
> + ret = regmap_assign_bits(priv->regmap, reg, mode_mask, mode->dbl10mode);
> + if (ret)
> + return ret;
> +
> + /* Set 12bit double mode. */
> + return regmap_assign_bits(priv->regmap, MAX96724_BACKTOP32(index),
> + MAX96724_BACKTOP32_BPP12(index), mode->dbl12);
> +}
> +
> +static int max96724_set_pipe_tunnel_enable(struct max_des *des,
> + struct max_des_pipe *pipe, bool enable)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> +
> + return regmap_assign_bits(priv->regmap, MAX96724_MIPI_TX54(pipe->index),
> + MAX96724_MIPI_TX54_TUN_EN, enable);
> +}
> +
> +static int max96724_select_links(struct max_des *des, unsigned int mask)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int val = priv->cc_port_cfg;
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < des->info->num_links; i++) {
> + if (!(mask & BIT(i)))
> + val |= MAX96724_REG3_CC_PORT_SEL(i);
> + }
> +
> + ret = regmap_write(priv->regmap, MAX96724_REG3, val);
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(priv->regmap, MAX96724_REG6, MAX96724_REG6_LINK_EN,
> + field_prep(MAX96724_REG6_LINK_EN, mask));
> + if (ret)
> + return ret;
> +
> + ret = regmap_set_bits(priv->regmap, MAX96724_CTRL1,
> + MAX96724_CTRL1_RESET_ONESHOT);
> + if (ret)
> + return ret;
> +
> + msleep(60);
> +
> + return 0;
> +}
> +
> +static int max96724_set_link_version(struct max_des *des,
> + struct max_des_link *link,
> + enum max_serdes_gmsl_version version)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + unsigned int index = link->index;
> + unsigned int val;
> +
> + if (version == MAX_SERDES_GMSL_2_6GBPS)
> + val = MAX96724_REG26_RX_RATE_6GBPS;
> + else
> + val = MAX96724_REG26_RX_RATE_3GBPS;
> +
> + return regmap_update_bits(priv->regmap, MAX96724_REG26(index),
> + MAX96724_REG26_RX_RATE_PHY(index),
> + field_prep(MAX96724_REG26_RX_RATE_PHY(index), val));
> +}
> +
> +static int max96724_set_tpg_timings(struct max96724_priv *priv,
> + const struct max_serdes_tpg_timings *tm)
> +{
> + const struct reg_sequence regs[] = {
> + REG_SEQUENCE_3(MAX96724_VS_DLY_2, tm->vs_dly),
> + REG_SEQUENCE_3(MAX96724_VS_HIGH_2, tm->vs_high),
> + REG_SEQUENCE_3(MAX96724_VS_LOW_2, tm->vs_low),
> + REG_SEQUENCE_3(MAX96724_V2H_2, tm->v2h),
> + REG_SEQUENCE_2(MAX96724_HS_HIGH_1, tm->hs_high),
> + REG_SEQUENCE_2(MAX96724_HS_LOW_1, tm->hs_low),
> + REG_SEQUENCE_2(MAX96724_HS_CNT_1, tm->hs_cnt),
> + REG_SEQUENCE_3(MAX96724_V2D_2, tm->v2d),
> + REG_SEQUENCE_2(MAX96724_DE_HIGH_1, tm->de_high),
> + REG_SEQUENCE_2(MAX96724_DE_LOW_1, tm->de_low),
> + REG_SEQUENCE_2(MAX96724_DE_CNT_1, tm->de_cnt),
> + };
> + int ret;
> +
> + ret = regmap_multi_reg_write(priv->regmap, regs, ARRAY_SIZE(regs));
> + if (ret)
> + return ret;
> +
> + return regmap_write(priv->regmap, MAX96724_PATGEN_0,
> + FIELD_PREP(MAX96724_PATGEN_0_VTG_MODE,
> + MAX96724_PATGEN_0_VTG_MODE_FREE_RUNNING) |
> + FIELD_PREP(MAX96724_PATGEN_0_DE_INV, tm->de_inv) |
> + FIELD_PREP(MAX96724_PATGEN_0_HS_INV, tm->hs_inv) |
> + FIELD_PREP(MAX96724_PATGEN_0_VS_INV, tm->vs_inv) |
> + FIELD_PREP(MAX96724_PATGEN_0_GEN_DE, tm->gen_de) |
> + FIELD_PREP(MAX96724_PATGEN_0_GEN_HS, tm->gen_hs) |
> + FIELD_PREP(MAX96724_PATGEN_0_GEN_VS, tm->gen_vs));
> +}
> +
> +static int max96724_set_tpg_clk(struct max96724_priv *priv, u32 clock)
> +{
> + bool patgen_clk_src = 0;
> + u8 pclk_src;
> + int ret;
> +
> + switch (clock) {
> + case 25000000:
> + pclk_src = MAX96724_DEBUG_EXTRA_PCLK_SRC_25MHZ;
> + break;
> + case 75000000:
> + pclk_src = MAX96724_DEBUG_EXTRA_PCLK_SRC_75MHZ;
> + break;
> + case 150000000:
> + pclk_src = MAX96724_DEBUG_EXTRA_PCLK_SRC_USE_PIPE;
> + patgen_clk_src = MAX96724_VPRBS_PATGEN_CLK_SRC_150MHZ;
> + break;
> + case 375000000:
> + pclk_src = MAX96724_DEBUG_EXTRA_PCLK_SRC_USE_PIPE;
> + patgen_clk_src = MAX96724_VPRBS_PATGEN_CLK_SRC_375MHZ;
> + break;
> + case 0:
> + return 0;
> + default:
> + return -EINVAL;
> + }
> +
> + /*
> + * TPG data is always injected on link 0, which is always routed to
> + * pipe 0.
> + */
> + ret = regmap_update_bits(priv->regmap, MAX96724_VPRBS(0),
> + MAX96724_VPRBS_PATGEN_CLK_SRC,
> + FIELD_PREP(MAX96724_VPRBS_PATGEN_CLK_SRC,
> + patgen_clk_src));
> + if (ret)
> + return ret;
> +
> + return regmap_update_bits(priv->regmap, MAX96724_DEBUG_EXTRA,
> + MAX96724_DEBUG_EXTRA_PCLK_SRC,
> + FIELD_PREP(MAX96724_DEBUG_EXTRA_PCLK_SRC,
> + pclk_src));
> +}
> +
> +static int max96724_set_tpg_mode(struct max96724_priv *priv, bool enable)
> +{
> + unsigned int patgen_mode;
> +
> + switch (priv->des.tpg_pattern) {
> + case MAX_SERDES_TPG_PATTERN_GRADIENT:
> + patgen_mode = MAX96724_PATGEN_1_PATGEN_MODE_GRADIENT;
> + break;
> + case MAX_SERDES_TPG_PATTERN_CHECKERBOARD:
> + patgen_mode = MAX96724_PATGEN_1_PATGEN_MODE_CHECKER;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + return regmap_update_bits(priv->regmap, MAX96724_PATGEN_1,
> + MAX96724_PATGEN_1_PATGEN_MODE,
> + FIELD_PREP(MAX96724_PATGEN_1_PATGEN_MODE,
> + enable ? patgen_mode
> + : MAX96724_PATGEN_1_PATGEN_MODE_DISABLED));
> +}
> +
> +static int max96724_set_tpg(struct max_des *des,
> + const struct max_serdes_tpg_entry *entry)
> +{
> + struct max96724_priv *priv = des_to_priv(des);
> + struct max_serdes_tpg_timings timings = { 0 };
> + int ret;
> +
> + ret = max_serdes_get_tpg_timings(entry, &timings);
> + if (ret)
> + return ret;
> +
> + ret = max96724_set_tpg_timings(priv, &timings);
> + if (ret)
> + return ret;
> +
> + ret = max96724_set_tpg_clk(priv, timings.clock);
> + if (ret)
> + return ret;
> +
> + ret = max96724_set_tpg_mode(priv, entry);
> + if (ret)
> + return ret;
> +
> + return regmap_assign_bits(priv->regmap, MAX96724_MIPI_PHY0,
> + MAX96724_MIPI_PHY0_FORCE_CSI_OUT_EN, !!entry);
> +}
> +
> +static const struct max_serdes_tpg_entry max96724_tpg_entries[] = {
> + MAX_TPG_ENTRY_640X480P60_RGB888,
> + MAX_TPG_ENTRY_1920X1080P30_RGB888,
> + MAX_TPG_ENTRY_1920X1080P60_RGB888,
> +};
> +
> +static const struct max_des_info max96724_des_info = {
> + .num_phys = 4,
> + .num_links = 4,
> + .num_remaps_per_pipe = 16,
> + .phys_configs = {
> + .num_configs = ARRAY_SIZE(max96724_phys_configs),
> + .configs = max96724_phys_configs,
> + },
> + .tpg_entries = {
> + .num_entries = ARRAY_SIZE(max96724_tpg_entries),
> + .entries = max96724_tpg_entries,
> + },
> + .tpg_mode = MAX_SERDES_GMSL_PIXEL_MODE,
> + .tpg_patterns = BIT(MAX_SERDES_TPG_PATTERN_CHECKERBOARD) |
> + BIT(MAX_SERDES_TPG_PATTERN_GRADIENT),
> + .use_atr = true,
> +};
> +
> +static const struct max_des_ops max96724_des_ops = {
> +#ifdef CONFIG_VIDEO_ADV_DEBUG
> + .reg_read = max96724_reg_read,
> + .reg_write = max96724_reg_write,
> +#endif
> + .log_pipe_status = max96724_log_pipe_status,
> + .log_phy_status = max96724_log_phy_status,
> + .set_enable = max96724_set_enable,
> + .init = max96724_init,
> + .init_phy = max96724_init_phy,
> + .set_phy_mode = max96724_set_phy_mode,
> + .set_phy_enable = max96724_set_phy_enable,
> + .set_pipe_stream_id = max96724_set_pipe_stream_id,
> + .set_pipe_link = max96724_set_pipe_link,
> + .set_pipe_enable = max96724_set_pipe_enable,
> + .set_pipe_remap = max96724_set_pipe_remap,
> + .set_pipe_remaps_enable = max96724_set_pipe_remaps_enable,
> + .set_pipe_mode = max96724_set_pipe_mode,
> + .set_tpg = max96724_set_tpg,
> + .select_links = max96724_select_links,
> + .set_link_version = max96724_set_link_version,
> +};
> +
> +static const struct max96724_chip_info max96724_info = {
> + .versions = BIT(MAX_SERDES_GMSL_2_3GBPS) |
> + BIT(MAX_SERDES_GMSL_2_6GBPS),
> + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) |
> + BIT(MAX_SERDES_GMSL_TUNNEL_MODE),
> + .set_pipe_tunnel_enable = max96724_set_pipe_tunnel_enable,
> + .set_pipe_phy = max96724_set_pipe_phy,
> + .set_pipe_tunnel_phy = max96724_set_pipe_tunnel_phy,
> + .supports_pipe_stream_autoselect = true,
> + .num_pipes = 4,
> +};
> +
> +static const struct max96724_chip_info max96724f_info = {
> + .versions = BIT(MAX_SERDES_GMSL_2_3GBPS),
> + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE) |
> + BIT(MAX_SERDES_GMSL_TUNNEL_MODE),
> + .set_pipe_tunnel_enable = max96724_set_pipe_tunnel_enable,
> + .set_pipe_phy = max96724_set_pipe_phy,
> + .set_pipe_tunnel_phy = max96724_set_pipe_tunnel_phy,
> + .supports_pipe_stream_autoselect = true,
> + .num_pipes = 4,
> +};
> +
> +static const struct max96724_chip_info max96712_info = {
> + .versions = BIT(MAX_SERDES_GMSL_2_3GBPS) |
> + BIT(MAX_SERDES_GMSL_2_6GBPS),
> + .modes = BIT(MAX_SERDES_GMSL_PIXEL_MODE),
> + .num_pipes = 8,
> +};
> +
> +static void max96724_power_off(void *data)
> +{
> + struct max96724_priv *priv = data;
> +
> + gpiod_set_value_cansleep(priv->gpiod_enable, 0);
> +}
> +
> +static int max96724_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct max96724_priv *priv;
> + struct max_des_info *info;
> + struct max_des_ops *ops;
> + u32 cc_port;
> + int ret;
> +
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
> + if (!info)
> + return -ENOMEM;
> +
> + ops = devm_kzalloc(dev, sizeof(*ops), GFP_KERNEL);
> + if (!ops)
> + return -ENOMEM;
> +
> + priv->info = device_get_match_data(dev);
> + if (!priv->info) {
> + dev_err(dev, "Failed to get match data\n");
> + return -ENODEV;
> + }
> +
> + priv->dev = dev;
> + priv->client = client;
> + i2c_set_clientdata(client, priv);
> +
> + priv->regmap = devm_regmap_init_i2c(client, &max96724_i2c_regmap);
> + if (IS_ERR(priv->regmap))
> + return PTR_ERR(priv->regmap);
> +
> + priv->gpiod_enable = devm_gpiod_get_optional(&client->dev, "enable",
> + GPIOD_OUT_LOW);
> + if (IS_ERR(priv->gpiod_enable))
> + return PTR_ERR(priv->gpiod_enable);
> +
> + if (priv->gpiod_enable) {
> + /* PWDN must be held for 1us for reset */
> + udelay(1);
> +
> + gpiod_set_value_cansleep(priv->gpiod_enable, 1);
> +
> + /* Maximum power-up time (tLOCK) 4ms */
> + usleep_range(4000, 5000);
> +
> + ret = devm_add_action_or_reset(dev, max96724_power_off,
> + priv);
> + if (ret)
> + return ret;
> + }
> +
> + priv->cc_port_cfg = MAX96724_REG3_CC_PORT_CFG_PORT0;
> +
> + ret = device_property_read_u32(dev, "maxim,control-channel-port",
> + &cc_port);
> + if (!ret) {
> + switch (cc_port) {
> + case 0:
> + priv->cc_port_cfg = MAX96724_REG3_CC_PORT_CFG_PORT0;
> + break;
> + case 1:
> + priv->cc_port_cfg = MAX96724_REG3_CC_PORT_CFG_PORT1;
> + break;
> + default:
> + dev_err(dev, "Invalid control-channel port %u\n", cc_port);
> + return -EINVAL;
> + }
> + } else if (ret != -ENODATA && ret != -ENOENT && ret != -EINVAL) {
> + return ret;
> + }
> +
> + *info = max96724_des_info;
> + info->versions = priv->info->versions;
> + info->modes = priv->info->modes;
> + info->num_pipes = priv->info->num_pipes;
> + priv->des.info = info;
> +
> + *ops = max96724_des_ops;
> + ops->set_pipe_tunnel_enable = priv->info->set_pipe_tunnel_enable;
> + ops->set_pipe_phy = priv->info->set_pipe_phy;
> + ops->set_pipe_tunnel_phy = priv->info->set_pipe_tunnel_phy;
> + priv->des.ops = ops;
> +
> + ret = max96724_reset(priv);
> + if (ret)
> + return ret;
> +
> + return max_des_probe(client, &priv->des);
> +}
> +
> +static void max96724_remove(struct i2c_client *client)
> +{
> + struct max96724_priv *priv = i2c_get_clientdata(client);
> +
> + max_des_remove(&priv->des);
> +}
> +
> +static const struct of_device_id max96724_of_table[] = {
> + { .compatible = "maxim,max96712", .data = &max96712_info },
> + { .compatible = "maxim,max96724", .data = &max96724_info },
> + { .compatible = "maxim,max96724f", .data = &max96724f_info },
> + { .compatible = "maxim,max96724r", .data = &max96724f_info },
> + { },
> +};
> +MODULE_DEVICE_TABLE(of, max96724_of_table);
> +
> +static struct i2c_driver max96724_i2c_driver = {
> + .driver = {
> + .name = "max96724",
> + .of_match_table = max96724_of_table,
> + },
> + .probe = max96724_probe,
> + .remove = max96724_remove,
> +};
> +
> +module_i2c_driver(max96724_i2c_driver);
> +
> +MODULE_IMPORT_NS("MAX_SERDES");
> +MODULE_DESCRIPTION("Maxim MAX96724 Quad GMSL2 Deserializer Driver");
> +MODULE_AUTHOR("Cosmin Tanislav <cosmin.tanislav@analog.com>");
> +MODULE_LICENSE("GPL");
>
> --
> 2.53.0
>
>
--
Kind Regards,
Niklas Söderlund
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox