Devicetree
 help / color / mirror / Atom feed
* [PATCH RFC 3/4] net: ipa: add new QMI request for HW filter stats info
From: Alexander Koskovich @ 2026-04-16 22:41 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Alex Elder
  Cc: linux-arm-msm, netdev, devicetree, linux-kernel,
	Alexander Koskovich
In-Reply-To: <20260416-eliza-ipa-v1-0-f4109a8e43c4@pm.me>

Some IPA firmware versions on IPA 5.5 require ipa_filter_stats_info in
the init_modem_driver QMI request. For example on Eliza, if this is not
passed then shortly after IPA init the system will halt and reboot
shortly after.

Downstream this is marked as optional but does not seem to be the case
on newer IPA firmware versions.

Signed-off-by: Alexander Koskovich <akoskovich@pm.me>
---
 drivers/net/ipa/data/ipa_data-v4.5.c |  1 +
 drivers/net/ipa/data/ipa_data-v4.7.c |  1 +
 drivers/net/ipa/data/ipa_data-v4.9.c |  1 +
 drivers/net/ipa/data/ipa_data-v5.0.c |  1 +
 drivers/net/ipa/data/ipa_data-v5.5.c |  1 +
 drivers/net/ipa/ipa.h                |  3 ++
 drivers/net/ipa/ipa_data.h           |  3 ++
 drivers/net/ipa/ipa_mem.c            |  2 ++
 drivers/net/ipa/ipa_qmi.c            | 18 +++++++++++
 drivers/net/ipa/ipa_qmi_msg.c        | 58 ++++++++++++++++++++++++++++++++++++
 drivers/net/ipa/ipa_qmi_msg.h        | 15 +++++++++-
 11 files changed, 103 insertions(+), 1 deletion(-)

diff --git a/drivers/net/ipa/data/ipa_data-v4.5.c b/drivers/net/ipa/data/ipa_data-v4.5.c
index 730d8c43a45c..25eceffdd9f7 100644
--- a/drivers/net/ipa/data/ipa_data-v4.5.c
+++ b/drivers/net/ipa/data/ipa_data-v4.5.c
@@ -419,6 +419,7 @@ static const struct ipa_mem_data ipa_mem_data = {
 	.imem_addr	= 0x14688000,
 	.imem_size	= 0x00003000,
 	.smem_size	= 0x00009000,
+	.fnr_idx_cnt	= 52,
 };
 
 /* Interconnect rates are in 1000 byte/second units */
diff --git a/drivers/net/ipa/data/ipa_data-v4.7.c b/drivers/net/ipa/data/ipa_data-v4.7.c
index 5e1d9049c62b..ef4695ee1b2d 100644
--- a/drivers/net/ipa/data/ipa_data-v4.7.c
+++ b/drivers/net/ipa/data/ipa_data-v4.7.c
@@ -361,6 +361,7 @@ static const struct ipa_mem_data ipa_mem_data = {
 	.imem_addr	= 0x146a8000,
 	.imem_size	= 0x00002000,
 	.smem_size	= 0x00009000,
+	.fnr_idx_cnt	= 52,
 };
 
 /* Interconnect rates are in 1000 byte/second units */
diff --git a/drivers/net/ipa/data/ipa_data-v4.9.c b/drivers/net/ipa/data/ipa_data-v4.9.c
index da472a2a2e29..0e2e521d98bb 100644
--- a/drivers/net/ipa/data/ipa_data-v4.9.c
+++ b/drivers/net/ipa/data/ipa_data-v4.9.c
@@ -417,6 +417,7 @@ static const struct ipa_mem_data ipa_mem_data = {
 	.imem_addr	= 0x146bd000,
 	.imem_size	= 0x00002000,
 	.smem_size	= 0x00009000,
+	.fnr_idx_cnt	= 52,
 };
 
 /* Interconnect rates are in 1000 byte/second units */
diff --git a/drivers/net/ipa/data/ipa_data-v5.0.c b/drivers/net/ipa/data/ipa_data-v5.0.c
index bc5722e4b053..9f7aaf37b8fd 100644
--- a/drivers/net/ipa/data/ipa_data-v5.0.c
+++ b/drivers/net/ipa/data/ipa_data-v5.0.c
@@ -443,6 +443,7 @@ static const struct ipa_mem_data ipa_mem_data = {
 	.imem_addr	= 0x14688000,
 	.imem_size	= 0x00003000,
 	.smem_size	= 0x00009000,
+	.fnr_idx_cnt    = 52,
 };
 
 /* Interconnect rates are in 1000 byte/second units */
diff --git a/drivers/net/ipa/data/ipa_data-v5.5.c b/drivers/net/ipa/data/ipa_data-v5.5.c
index f6ba3b944700..44a9df7346b7 100644
--- a/drivers/net/ipa/data/ipa_data-v5.5.c
+++ b/drivers/net/ipa/data/ipa_data-v5.5.c
@@ -449,6 +449,7 @@ static const struct ipa_mem_data ipa_mem_data = {
 	.imem_addr	= 0x14688000,
 	.imem_size	= 0x00002000,
 	.smem_size	= 0x0000b000,
+	.fnr_idx_cnt	= 52,
 };
 
 /* Interconnect rates are in 1000 byte/second units */
diff --git a/drivers/net/ipa/ipa.h b/drivers/net/ipa/ipa.h
index 7ef10a4ff35e..d2ceaea31635 100644
--- a/drivers/net/ipa/ipa.h
+++ b/drivers/net/ipa/ipa.h
@@ -69,6 +69,7 @@ struct ipa_smp2p;
  * @modem_state:	State of modem (stopped, running)
  * @modem_netdev:	Network device structure used for modem
  * @qmi:		QMI information
+ * @fnr_idx_cnt:	Number of FnR counters
  */
 struct ipa {
 	struct gsi gsi;
@@ -129,6 +130,8 @@ struct ipa {
 	atomic_t modem_state;		/* enum ipa_modem_state */
 	struct net_device *modem_netdev;
 	struct ipa_qmi qmi;
+
+	u8 fnr_idx_cnt;
 };
 
 /**
diff --git a/drivers/net/ipa/ipa_data.h b/drivers/net/ipa/ipa_data.h
index 3eb9dc2ce339..f7566c8edabd 100644
--- a/drivers/net/ipa/ipa_data.h
+++ b/drivers/net/ipa/ipa_data.h
@@ -181,6 +181,7 @@ struct ipa_resource_data {
  * @imem_addr:		physical address of IPA region within IMEM
  * @imem_size:		size in bytes of IPA IMEM region
  * @smem_size:		size in bytes of the IPA SMEM region
+ * @fnr_idx_cnt:	Number of FnR counters
  */
 struct ipa_mem_data {
 	u32 local_count;
@@ -193,6 +194,8 @@ struct ipa_mem_data {
 	u32 imem_size; /* DEPRECATED */
 
 	u32 smem_size;
+
+	u8 fnr_idx_cnt;
 };
 
 /**
diff --git a/drivers/net/ipa/ipa_mem.c b/drivers/net/ipa/ipa_mem.c
index 078d32a18dbf..fb04f953bf7a 100644
--- a/drivers/net/ipa/ipa_mem.c
+++ b/drivers/net/ipa/ipa_mem.c
@@ -631,6 +631,8 @@ int ipa_mem_init(struct ipa *ipa, struct platform_device *pdev,
 	ipa->mem_count = mem_data->local_count;
 	ipa->mem = mem_data->local;
 
+	ipa->fnr_idx_cnt = mem_data->fnr_idx_cnt;
+
 	/* Check the route and filter table memory regions */
 	if (!ipa_table_mem_valid(ipa, false))
 		return -EINVAL;
diff --git a/drivers/net/ipa/ipa_qmi.c b/drivers/net/ipa/ipa_qmi.c
index d771f3a71f94..a5a5572c9ccd 100644
--- a/drivers/net/ipa/ipa_qmi.c
+++ b/drivers/net/ipa/ipa_qmi.c
@@ -74,6 +74,8 @@
 #define IPA_MODEM_SERVICE_INS_ID	2
 #define IPA_MODEM_SVC_VERS		1
 
+#define IPA_MODEM_FNR_IDX_START		128
+
 #define QMI_INIT_DRIVER_TIMEOUT		60000	/* A minute in milliseconds */
 
 /* Send an INIT_COMPLETE indication message to the modem */
@@ -394,6 +396,22 @@ init_modem_driver_req(struct ipa_qmi *ipa_qmi)
 		}
 	}
 
+	if (ipa->version >= IPA_VERSION_4_5 && ipa->fnr_idx_cnt) {
+		mem = ipa_mem_find(ipa, IPA_MEM_STATS_FILTER_ROUTE);
+		if (mem && mem->size) {
+			req.hw_stats_filter_info_valid = 1;
+			req.hw_stats_filter_info.start_addr =
+				ipa->mem_offset + mem->offset;
+			req.hw_stats_filter_info.size =
+				ipa->fnr_idx_cnt * 16;
+			req.hw_stats_filter_info.start_index =
+				IPA_MODEM_FNR_IDX_START;
+			req.hw_stats_filter_info.end_index =
+				IPA_MODEM_FNR_IDX_START +
+				ipa->fnr_idx_cnt - 1;
+		}
+	}
+
 	return &req;
 }
 
diff --git a/drivers/net/ipa/ipa_qmi_msg.c b/drivers/net/ipa/ipa_qmi_msg.c
index 51dc13a577a5..160c0d207691 100644
--- a/drivers/net/ipa/ipa_qmi_msg.c
+++ b/drivers/net/ipa/ipa_qmi_msg.c
@@ -250,6 +250,43 @@ const struct qmi_elem_info ipa_mem_range_ei[] = {
 	},
 };
 
+/* QMI message structure definition for struct ipa_stats_filter */
+const struct qmi_elem_info ipa_stats_filter_ei[] = {
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_stats_filter, start_addr),
+		.offset		= offsetof(struct ipa_stats_filter,
+					   start_addr),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_4_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_stats_filter, size),
+		.offset		= offsetof(struct ipa_stats_filter, size),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_1_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_stats_filter, start_index),
+		.offset		= offsetof(struct ipa_stats_filter,
+					   start_index),
+	},
+	{
+		.data_type	= QMI_UNSIGNED_1_BYTE,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_stats_filter, end_index),
+		.offset		= offsetof(struct ipa_stats_filter, end_index),
+	},
+	{
+		.data_type	= QMI_EOTI,
+	},
+};
+
 /* QMI message structure definition for struct ipa_init_modem_driver_req */
 const struct qmi_elem_info ipa_init_modem_driver_req_ei[] = {
 	{
@@ -640,6 +677,27 @@ const struct qmi_elem_info ipa_init_modem_driver_req_ei[] = {
 		.offset		= offsetof(struct ipa_init_modem_driver_req,
 					   hw_stats_drop_size),
 	},
+	{
+		.data_type	= QMI_OPT_FLAG,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     hw_stats_filter_info_valid),
+		.tlv_type	= 0x23,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   hw_stats_filter_info_valid),
+	},
+	{
+		.data_type	= QMI_STRUCT,
+		.elem_len	= 1,
+		.elem_size	=
+			sizeof_field(struct ipa_init_modem_driver_req,
+				     hw_stats_filter_info),
+		.tlv_type	= 0x23,
+		.offset		= offsetof(struct ipa_init_modem_driver_req,
+					   hw_stats_filter_info),
+		.ei_array	= ipa_stats_filter_ei,
+	},
 	{
 		.data_type	= QMI_EOTI,
 	},
diff --git a/drivers/net/ipa/ipa_qmi_msg.h b/drivers/net/ipa/ipa_qmi_msg.h
index 644b8c27108b..3a2205c213d1 100644
--- a/drivers/net/ipa/ipa_qmi_msg.h
+++ b/drivers/net/ipa/ipa_qmi_msg.h
@@ -27,7 +27,7 @@
  */
 #define IPA_QMI_INDICATION_REGISTER_REQ_SZ	20	/* -> server handle */
 #define IPA_QMI_INDICATION_REGISTER_RSP_SZ	7	/* <- server handle */
-#define IPA_QMI_INIT_DRIVER_REQ_SZ		162	/* client handle -> */
+#define IPA_QMI_INIT_DRIVER_REQ_SZ		186	/* client handle -> */
 #define IPA_QMI_INIT_DRIVER_RSP_SZ		25	/* client handle <- */
 #define IPA_QMI_INIT_COMPLETE_IND_SZ		7	/* <- server handle */
 #define IPA_QMI_DRIVER_INIT_COMPLETE_REQ_SZ	4	/* -> server handle */
@@ -119,6 +119,13 @@ struct ipa_mem_range {
 	u32 size;
 };
 
+struct ipa_stats_filter {
+	u32 start_addr;
+	u32 size;
+	u8 start_index;
+	u8 end_index;
+};
+
 /* The message for the IPA_QMI_INIT_DRIVER request contains information
  * from the AP that affects modem initialization.
  */
@@ -216,6 +223,11 @@ struct ipa_init_modem_driver_req {
 	u32			hw_stats_drop_base_addr;
 	u8			hw_stats_drop_size_valid;
 	u32			hw_stats_drop_size;
+
+	/* Hardware filter statistics information. (IPA v4.5 and above)
+	 */
+	u8			hw_stats_filter_info_valid;
+	struct ipa_stats_filter	hw_stats_filter_info;
 };
 
 /* The response to a IPA_QMI_INIT_DRIVER request begins with a standard
@@ -256,6 +268,7 @@ extern const struct qmi_elem_info ipa_init_complete_ind_ei[];
 extern const struct qmi_elem_info ipa_mem_bounds_ei[];
 extern const struct qmi_elem_info ipa_mem_array_ei[];
 extern const struct qmi_elem_info ipa_mem_range_ei[];
+extern const struct qmi_elem_info ipa_stats_filter_ei[];
 extern const struct qmi_elem_info ipa_init_modem_driver_req_ei[];
 extern const struct qmi_elem_info ipa_init_modem_driver_rsp_ei[];
 

-- 
2.53.0



^ permalink raw reply related

* [PATCH RFC 4/4] net: ipa: add Eliza configuration data
From: Alexander Koskovich @ 2026-04-16 22:41 UTC (permalink / raw)
  To: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Alex Elder
  Cc: linux-arm-msm, netdev, devicetree, linux-kernel,
	Alexander Koskovich
In-Reply-To: <20260416-eliza-ipa-v1-0-f4109a8e43c4@pm.me>

Add the configuration data required for Eliza, which uses IPA v5.5. The
difference over other platforms that use IPA 5.5 is the Q6 FnR counters
have been increased on the firmware version it uses, which results in a
different memory layout.

Signed-off-by: Alexander Koskovich <akoskovich@pm.me>
---
 drivers/net/ipa/data/ipa_data-v5.5.c | 163 +++++++++++++++++++++++++++++++++++
 drivers/net/ipa/ipa_data.h           |   1 +
 drivers/net/ipa/ipa_main.c           |   4 +
 3 files changed, 168 insertions(+)

diff --git a/drivers/net/ipa/data/ipa_data-v5.5.c b/drivers/net/ipa/data/ipa_data-v5.5.c
index 44a9df7346b7..e1454454bde9 100644
--- a/drivers/net/ipa/data/ipa_data-v5.5.c
+++ b/drivers/net/ipa/data/ipa_data-v5.5.c
@@ -288,6 +288,148 @@ static const struct ipa_resource_data ipa_resource_data = {
 	.resource_dst		= ipa_resource_dst,
 };
 
+/* IPA-resident memory region data for the Eliza SoC */
+static const struct ipa_mem ipa_mem_local_data_eliza[] = {
+	{
+		.id		= IPA_MEM_UC_EVENT_RING,
+		.offset		= 0x0000,
+		.size		= 0x1000,
+		.canary_count	= 0,
+	},
+	{
+		.id		= IPA_MEM_UC_SHARED,
+		.offset		= 0x1000,
+		.size		= 0x0080,
+		.canary_count	= 0,
+	},
+	{
+		.id		= IPA_MEM_UC_INFO,
+		.offset		= 0x1080,
+		.size		= 0x0200,
+		.canary_count	= 0,
+	},
+	{
+		.id		= IPA_MEM_V4_FILTER_HASHED,
+		.offset		= 0x1288,
+		.size		= 0x0078,
+		.canary_count	= 2,
+	},
+	{
+		.id		= IPA_MEM_V4_FILTER,
+		.offset		= 0x1308,
+		.size		= 0x0078,
+		.canary_count	= 2,
+	},
+	{
+		.id		= IPA_MEM_V6_FILTER_HASHED,
+		.offset		= 0x1388,
+		.size		= 0x0078,
+		.canary_count	= 2,
+	},
+	{
+		.id		= IPA_MEM_V6_FILTER,
+		.offset		= 0x1408,
+		.size		= 0x0078,
+		.canary_count	= 2,
+	},
+	{
+		.id		= IPA_MEM_V4_ROUTE_HASHED,
+		.offset		= 0x1488,
+		.size		= 0x0098,
+		.canary_count	= 2,
+	},
+	{
+		.id		= IPA_MEM_V4_ROUTE,
+		.offset		= 0x1528,
+		.size		= 0x0098,
+		.canary_count	= 2,
+	},
+	{
+		.id		= IPA_MEM_V6_ROUTE_HASHED,
+		.offset		= 0x15c8,
+		.size		= 0x0098,
+		.canary_count	= 2,
+	},
+	{
+		.id		= IPA_MEM_V6_ROUTE,
+		.offset		= 0x1668,
+		.size		= 0x0098,
+		.canary_count	= 2,
+	},
+	{
+		.id		= IPA_MEM_MODEM_HEADER,
+		.offset		= 0x1708,
+		.size		= 0x0240,
+		.canary_count	= 2,
+	},
+	{
+		.id		= IPA_MEM_AP_HEADER,
+		.offset		= 0x1948,
+		.size		= 0x01e0,
+		.canary_count	= 0,
+	},
+	{
+		.id		= IPA_MEM_MODEM_PROC_CTX,
+		.offset		= 0x1b40,
+		.size		= 0x0b20,
+		.canary_count	= 2,
+	},
+	{
+		.id		= IPA_MEM_AP_PROC_CTX,
+		.offset		= 0x2660,
+		.size		= 0x0200,
+		.canary_count	= 0,
+	},
+	{
+		.id		= IPA_MEM_STATS_QUOTA_MODEM,
+		.offset		= 0x2868,
+		.size		= 0x0060,
+		.canary_count	= 2,
+	},
+	{
+		.id		= IPA_MEM_STATS_QUOTA_AP,
+		.offset		= 0x28c8,
+		.size		= 0x0048,
+		.canary_count	= 0,
+	},
+	{
+		.id		= IPA_MEM_STATS_TETHERING,
+		.offset		= 0x2910,
+		.size		= 0x03c0,
+		.canary_count	= 0,
+	},
+	{
+		.id		= IPA_MEM_STATS_FILTER_ROUTE,
+		.offset		= 0x2cd0,
+		.size		= 0x0c40,
+		.canary_count	= 2,
+	},
+	{
+		.id		= IPA_MEM_STATS_DROP,
+		.offset		= 0x3910,
+		.size		= 0x0020,
+		.canary_count	= 0,
+	},
+	{
+		.id		= IPA_MEM_MODEM,
+		.offset		= 0x3938,
+		.size		= 0x0d48,
+		.canary_count	= 2,
+	},
+	{
+		.id		= IPA_MEM_NAT_TABLE,
+		.offset		= 0x4680,
+		.size		= 0x0900,
+		.canary_count	= 0,
+	},
+	{
+		.id		= IPA_MEM_PDN_CONFIG,
+		.offset		= 0x4f88,
+		.size		= 0x0050,
+		.canary_count	= 2,
+	},
+};
+
 /* IPA-resident memory region data for an SoC having IPA v5.5 */
 static const struct ipa_mem ipa_mem_local_data[] = {
 	{
@@ -442,6 +584,14 @@ static const struct ipa_mem ipa_mem_local_data[] = {
 	},
 };
 
+/* Memory configuration data for the Eliza SoC */
+static const struct ipa_mem_data ipa_mem_data_eliza = {
+	.local_count	= ARRAY_SIZE(ipa_mem_local_data_eliza),
+	.local		= ipa_mem_local_data_eliza,
+	.smem_size	= 0x0000b000,
+	.fnr_idx_cnt	= 68,
+};
+
 /* Memory configuration data for an SoC having IPA v5.5 */
 static const struct ipa_mem_data ipa_mem_data = {
 	.local_count	= ARRAY_SIZE(ipa_mem_local_data),
@@ -486,3 +636,16 @@ const struct ipa_data ipa_data_v5_5 = {
 	.mem_data		= &ipa_mem_data,
 	.power_data		= &ipa_power_data,
 };
+
+/* Configuration data for the Eliza SoC (IPA v5.5). */
+const struct ipa_data ipa_data_v5_5_eliza = {
+	.version		= IPA_VERSION_5_5,
+	.qsb_count		= ARRAY_SIZE(ipa_qsb_data),
+	.qsb_data		= ipa_qsb_data,
+	.modem_route_count	= 11,
+	.endpoint_count		= ARRAY_SIZE(ipa_gsi_endpoint_data),
+	.endpoint_data		= ipa_gsi_endpoint_data,
+	.resource_data		= &ipa_resource_data,
+	.mem_data		= &ipa_mem_data_eliza,
+	.power_data		= &ipa_power_data,
+};
diff --git a/drivers/net/ipa/ipa_data.h b/drivers/net/ipa/ipa_data.h
index f7566c8edabd..da01fc84edac 100644
--- a/drivers/net/ipa/ipa_data.h
+++ b/drivers/net/ipa/ipa_data.h
@@ -258,5 +258,6 @@ extern const struct ipa_data ipa_data_v4_11;
 extern const struct ipa_data ipa_data_v5_0;
 extern const struct ipa_data ipa_data_v5_2;
 extern const struct ipa_data ipa_data_v5_5;
+extern const struct ipa_data ipa_data_v5_5_eliza;
 
 #endif /* _IPA_DATA_H_ */
diff --git a/drivers/net/ipa/ipa_main.c b/drivers/net/ipa/ipa_main.c
index 788dd99af2a4..981be8b538d0 100644
--- a/drivers/net/ipa/ipa_main.c
+++ b/drivers/net/ipa/ipa_main.c
@@ -637,6 +637,10 @@ static int ipa_firmware_load(struct device *dev)
 }
 
 static const struct of_device_id ipa_match[] = {
+	{
+		.compatible	= "qcom,eliza-ipa",
+		.data		= &ipa_data_v5_5_eliza,
+	},
 	{
 		.compatible	= "qcom,msm8998-ipa",
 		.data		= &ipa_data_v3_1,

-- 
2.53.0



^ permalink raw reply related

* [PATCH v6 1/4] ASoC: dt-bindings: Add ti,tas67524
From: Sen Wang @ 2026-04-16 23:26 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood
  Cc: Krzysztof Kozlowski, Conor Dooley, Rob Herring, Jaroslav Kysela,
	Takashi Iwai, Shenghao Ding, Kevin Lu, Baojun Xu, linux-sound,
	devicetree, linux-kernel, Sen Wang, Krzysztof Kozlowski
In-Reply-To: <20260416232640.3084132-1-sen@ti.com>

Add device tree binding for the Texas Instruments TAS67524 family
of four-channel Class-D audio amplifiers with integrated DSP.

Signed-off-by: Sen Wang <sen@ti.com>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
---
Changes in v6:
 - None

Changes in v5:
 - None

Changes in v4:
 - Correct ti,tas6754 compatible with a fallback of ti,tas67524
 - Correct comment spacing

Changes in v3:
 - Rename ti,tas675x to ti,tas67524.yaml
 - Remove tas6754 compatible instance
 - Change pd-gpios to powerdown-gpios
 - Cleanup unnessary "|" formatting

Changes in v2:
 - None

 .../bindings/sound/ti,tas67524.yaml           | 280 ++++++++++++++++++
 1 file changed, 280 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/ti,tas67524.yaml

diff --git a/Documentation/devicetree/bindings/sound/ti,tas67524.yaml b/Documentation/devicetree/bindings/sound/ti,tas67524.yaml
new file mode 100644
index 000000000000..812a4d39e2a5
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/ti,tas67524.yaml
@@ -0,0 +1,280 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/ti,tas67524.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Texas Instruments TAS67524 Audio Amplifier
+
+maintainers:
+  - Sen Wang <sen@ti.com>
+
+description:
+  The TAS67524 is a four-channel, digital-input, automotive
+  Class-D audio amplifier with load diagnostics and an integrated
+  DSP for audio processing.
+
+allOf:
+  - $ref: dai-common.yaml#
+
+properties:
+  compatible:
+    oneOf:
+      - items:
+          - enum:
+              - ti,tas6754
+          - const: ti,tas67524
+      - const: ti,tas67524
+
+  reg:
+    maxItems: 1
+
+  '#sound-dai-cells':
+    const: 1
+    description: |
+      The device exposes three DAIs, selected by index.
+        0 - Standard Audio Path (Playback)
+        1 - Low-Latency Playback Path (Playback)
+        2 - Sensory Feedback (Capture - Vpredict and Isense)
+      By default, all four channels of each DAI are active.
+
+  interrupts:
+    maxItems: 1
+    description:
+      Active-low falling-edge interrupt from the FAULT pin. When provided,
+      the driver uses IRQ-driven fault reporting instead of polling.
+
+  powerdown-gpios:
+    maxItems: 1
+    description:
+      GPIO connected to the PD pin, active low. Controls the internal
+      digital circuitry power state. When asserted the device enters
+      full power-down mode and all register state is lost. Can be omitted if
+      PD pin is hardwired or externally controlled.
+
+  standby-gpios:
+    maxItems: 1
+    description:
+      GPIO connected to the STBY pin, active low. Controls the analog
+      power stage. When asserted the device enters Deep Sleep mode but
+      remains I2C-accessible with registers retained. Can be omitted if
+      STBY pin is tied to PD or hardwired.
+
+  dvdd-supply:
+    description:
+      Digital logic supply (1.62 V to 3.6 V). All three supply rails must
+      be within their recommended operating ranges before the PD pin is
+      released.
+
+  pvdd-supply:
+    description:
+      Output FET power supply (4.5 V to 19 V). All three supply rails must
+      be within their recommended operating ranges before the PD pin is
+      released.
+
+  vbat-supply:
+    description:
+      Battery supply for the Class-D output stage (4.5 V to 19 V). Optional
+      when PVDD and VBAT are connected to the same supply rail. When absent,
+      VBAT is assumed hardwired to PVDD.
+
+  ti,fast-boot:
+    type: boolean
+    description:
+      Skip DC load diagnostic sweep at power-on to reduce boot latency.
+      Automatic diagnostics after fault conditions remain enabled. Hardware
+      overcurrent protection is always active.
+
+  ti,audio-slot-no:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      TDM slot offset for the standard audio playback path via SDIN1. A value
+      of 4 maps to slot 4. If omitted, slot assignment is derived from the
+      tx_mask provided via set_tdm_slot(). Without either property, no slot
+      mapping is configured.
+
+  ti,llp-slot-no:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      TDM slot offset for the low-latency playback path via SDIN1. If omitted,
+      slot assignment is derived from the tx_mask provided via set_tdm_slot().
+      Without either property, no slot mapping is configured. Disabled outside
+      of LLP mode, and only relevant for TDM formats.
+
+  ti,vpredict-slot-no:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: |
+      In TDM mode, enables Vpredict output and assigns its starting slot;
+      four consecutive slots carry Vpredict Ch1-4 on SDOUT1. May coexist
+      with ti,isense-slot-no using separate non-overlapping slots.
+
+      In I2S mode, enables Vpredict output on SDOUT1 (Ch1/Ch2) and SDOUT2
+      (Ch3/Ch4). The slot value is unused. Requires a GPIO configured as
+      sdout2 for Ch3/Ch4; without it only Ch1/Ch2 are output. Mutually
+      exclusive with ti,isense-slot-no; if both are set, Vpredict takes
+      priority.
+
+      Irrelevant in Left-J and Right-J modes.
+
+  ti,isense-slot-no:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: |
+      In TDM mode, enables Isense output and assigns its starting slot;
+      four consecutive slots carry Isense Ch1-4 on SDOUT1. May coexist
+      with ti,vpredict-slot-no using separate non-overlapping slots.
+
+      In I2S mode, enables Isense output on SDOUT1 (Ch1/Ch2) and SDOUT2
+      (Ch3/Ch4). The slot value is unused. Requires a GPIO configured as
+      SDOUT2 for Ch3/Ch4; without it only Ch1/Ch2 are output. Mutually
+      exclusive with ti,vpredict-slot-no; Vpredict takes priority if both
+      are set.
+
+      Irrelevant in Left-J and Right-J modes.
+
+  ti,gpio1-function:
+    $ref: /schemas/types.yaml#/definitions/string
+    description:
+      Function for the GPIO_1 pin. When omitted, GPIO_1 remains in its
+      power-on default state.
+    enum:
+      - low            # Output: driven low
+      - auto-mute      # Output: high when all channels are auto-muted
+      - auto-mute-ch4  # Output: high when channel 4 is auto-muted
+      - auto-mute-ch3  # Output: high when channel 3 is auto-muted
+      - auto-mute-ch2  # Output: high when channel 2 is auto-muted
+      - auto-mute-ch1  # Output: high when channel 1 is auto-muted
+      - sdout2         # Output: Routes secondary serial data output 2
+      - sdout1         # Output: Re-routes secondary serial data output 1
+      - warn           # Output: warning signal (OTW, CBC)
+      - fault          # Output: fault signal (OTSD, OC, DC)
+      - clock-sync     # Output: clock synchronisation
+      - invalid-clock  # Output: high when clock is invalid
+      - high           # Output: driven high
+      - mute           # Input: external mute control
+      - phase-sync     # Input: phase synchronisation
+      - sdin2          # Input: secondary SDIN2 for I2S/LJ/RJ ch3/ch4
+      - deep-sleep     # Input: asserted transitions device to Deep Sleep
+      - hiz            # Input: asserted transitions device to Hi-Z
+      - play           # Input: asserted transitions device to Play
+      - sleep          # Input: asserted transitions device to Sleep
+
+  ti,gpio2-function:
+    $ref: /schemas/types.yaml#/definitions/string
+    description:
+      Function for the GPIO_2 pin. When omitted, GPIO_2 remains in its
+      power-on default state.
+    enum:
+      - low            # Output: driven low
+      - auto-mute      # Output: high when all channels are auto-muted
+      - auto-mute-ch4  # Output: high when channel 4 is auto-muted
+      - auto-mute-ch3  # Output: high when channel 3 is auto-muted
+      - auto-mute-ch2  # Output: high when channel 2 is auto-muted
+      - auto-mute-ch1  # Output: high when channel 1 is auto-muted
+      - sdout2         # Output: Routes secondary serial data output 2
+      - sdout1         # Output: Re-routes secondary serial data output 1
+      - warn           # Output: warning signal (OTW, CBC)
+      - fault          # Output: fault signal (OTSD, OC, DC)
+      - clock-sync     # Output: clock synchronisation
+      - invalid-clock  # Output: high when clock is invalid
+      - high           # Output: driven high
+      - mute           # Input: external mute control
+      - phase-sync     # Input: phase synchronisation
+      - sdin2          # Input: secondary SDIN2 for I2S/LJ/RJ ch3/ch4
+      - deep-sleep     # Input: asserted transitions device to Deep Sleep
+      - hiz            # Input: asserted transitions device to Hi-Z
+      - play           # Input: asserted transitions device to Play
+      - sleep          # Input: asserted transitions device to Sleep
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+    properties:
+      port@0:
+        $ref: audio-graph-port.yaml#
+        unevaluatedProperties: false
+        description: Standard audio playback port (DAI 0).
+
+      port@1:
+        $ref: audio-graph-port.yaml#
+        unevaluatedProperties: false
+        description: Low-latency playback port (LLP) (DAI 1).
+
+      port@2:
+        $ref: audio-graph-port.yaml#
+        unevaluatedProperties: false
+        description: Sensory feedback capture port (DAI 2).
+
+  port:
+    $ref: audio-graph-port.yaml#
+    unevaluatedProperties: false
+
+required:
+  - compatible
+  - reg
+  - '#sound-dai-cells'
+  - dvdd-supply
+  - pvdd-supply
+
+anyOf:
+  - required: [powerdown-gpios]
+  - required: [standby-gpios]
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        amplifier@70 {
+            compatible = "ti,tas67524";
+            reg = <0x70>;
+            #sound-dai-cells = <1>;
+            sound-name-prefix = "TAS0";
+
+            standby-gpios = <&main_gpio0 33 GPIO_ACTIVE_LOW>;
+
+            dvdd-supply = <&dvdd_1v8>;
+            pvdd-supply = <&pvdd_12v>;
+            vbat-supply = <&vbat_12v>;
+
+            ti,audio-slot-no = <0>;
+            ti,llp-slot-no = <4>;
+            ti,vpredict-slot-no = <0>;
+            ti,isense-slot-no = <4>;
+
+            ti,gpio2-function = "warn";
+
+            ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                    reg = <0>;
+
+                    tas0_audio_ep: endpoint {
+                        dai-format = "dsp_b";
+                        remote-endpoint = <&be_tas0_audio_ep>;
+                    };
+                };
+
+                port@1 {
+                    reg = <1>;
+
+                    tas0_anc_ep: endpoint {
+                        remote-endpoint = <&be_tas0_anc_ep>;
+                    };
+                };
+
+                port@2 {
+                    reg = <2>;
+
+                    tas0_fb_ep: endpoint {
+                        remote-endpoint = <&be_tas0_fb_ep>;
+                    };
+                };
+            };
+        };
+    };
-- 
2.43.0


^ permalink raw reply related

* [PATCH v6 2/4] ASoC: codecs: Add TAS67524 quad-channel audio amplifier driver
From: Sen Wang @ 2026-04-16 23:26 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood
  Cc: Krzysztof Kozlowski, Conor Dooley, Rob Herring, Jaroslav Kysela,
	Takashi Iwai, Shenghao Ding, Kevin Lu, Baojun Xu, linux-sound,
	devicetree, linux-kernel, Sen Wang
In-Reply-To: <20260416232640.3084132-1-sen@ti.com>

The TAS675x (TAS6754, TAS67524) are quad-channel, digital-input
Class-D amplifiers with an integrated DSP, controlled over I2C.
They support I2S and TDM serial audio interfaces.

Signed-off-by: Sen Wang <sen@ti.com>
---
Changes in v6:
 - Cancel fault_check_work and disable IRQ in i2c_remove
 - Use regmap_bulk_read for RTLDG impedance registers to prevent data
   tearing from split I2C reads (Reported by Sashiko)

Changes in v5:
 - Drop ti,tas6754 device id reference
 - Restrict RTLDG threshold max to 24bit, remove zero-padding in dsp_mem funcs
 - Complete error checking for set_dcldg_trigger
 - Add runtime PM reference in IRQ handler

Changes in v4:
 - Reverted filename change from tas67524.c back to tas675x.c
 - Removed improper kernel doc syntax

Changes in v3:
 - Use disable delayed_work and re-enable on runtime suspend/resume
 - Similarly, use disable/enable IRQ on system suspend/resume
 - Include IRQ_NONE on ISR returns
 - Clarify _check_faults() which now returns need_clear boolean

Changes in v2:
 - Remove redundant DAPM event function
 - Move IRQ request past power_on(2/4)
 - Add delayed_work at probe time to accomdate no PM config
 - Change .set_fmt and .dapm_routes callbacks to the same tas675x_set_fmt name

 sound/soc/codecs/Kconfig   |   12 +
 sound/soc/codecs/Makefile  |    2 +
 sound/soc/codecs/tas675x.c | 2194 ++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/tas675x.h |  367 ++++++
 4 files changed, 2575 insertions(+)
 create mode 100644 sound/soc/codecs/tas675x.c
 create mode 100644 sound/soc/codecs/tas675x.h

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index f9e6a83e55c6..0ca7c7e2283b 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -275,6 +275,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_TAS571X
 	imply SND_SOC_TAS5720
 	imply SND_SOC_TAS6424
+	imply SND_SOC_TAS675X
 	imply SND_SOC_TDA7419
 	imply SND_SOC_TFA9879
 	imply SND_SOC_TFA989X
@@ -2240,6 +2241,17 @@ config SND_SOC_TAS6424
 	  Enable support for Texas Instruments TAS6424 high-efficiency
 	  digital input quad-channel Class-D audio power amplifiers.
 
+config SND_SOC_TAS675X
+	tristate "Texas Instruments TAS675x Quad-Channel Audio Amplifier"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	  Enable support for Texas Instruments TAS675x quad-channel Class-D
+	  audio power amplifiers (TAS6754, TAS67524). The devices support I2S
+	  and TDM interfaces with real-time voltage and current sense feedback
+	  via SDOUT, and provide DC/AC/real-time load diagnostics for speaker
+	  monitoring.
+
 config SND_SOC_TDA7419
 	tristate "ST TDA7419 audio processor"
 	depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 172861d17cfd..106fdc140d42 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -318,6 +318,7 @@ snd-soc-tas571x-y := tas571x.o
 snd-soc-tas5720-y := tas5720.o
 snd-soc-tas5805m-y := tas5805m.o
 snd-soc-tas6424-y := tas6424.o
+snd-soc-tas675x-y := tas675x.o
 snd-soc-tda7419-y := tda7419.o
 snd-soc-tas2770-y := tas2770.o
 snd-soc-tas2781-comlib-y := tas2781-comlib.o
@@ -760,6 +761,7 @@ obj-$(CONFIG_SND_SOC_TAS571X)	+= snd-soc-tas571x.o
 obj-$(CONFIG_SND_SOC_TAS5720)	+= snd-soc-tas5720.o
 obj-$(CONFIG_SND_SOC_TAS5805M)	+= snd-soc-tas5805m.o
 obj-$(CONFIG_SND_SOC_TAS6424)	+= snd-soc-tas6424.o
+obj-$(CONFIG_SND_SOC_TAS675X)	+= snd-soc-tas675x.o
 obj-$(CONFIG_SND_SOC_TDA7419)	+= snd-soc-tda7419.o
 obj-$(CONFIG_SND_SOC_TAS2770) += snd-soc-tas2770.o
 obj-$(CONFIG_SND_SOC_TFA9879)	+= snd-soc-tfa9879.o
diff --git a/sound/soc/codecs/tas675x.c b/sound/soc/codecs/tas675x.c
new file mode 100644
index 000000000000..6f89a422f3c6
--- /dev/null
+++ b/sound/soc/codecs/tas675x.c
@@ -0,0 +1,2194 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ALSA SoC Texas Instruments TAS67524 Quad-Channel Audio Amplifier
+ *
+ * Copyright (C) 2026 Texas Instruments Incorporated - https://www.ti.com/
+ *	Author: Sen Wang <sen@ti.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+#include <linux/delay.h>
+#include <linux/property.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/pm_runtime.h>
+#include <linux/iopoll.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/pcm_params.h>
+
+#include "tas675x.h"
+
+#define TAS675X_FAULT_CHECK_INTERVAL_MS  200
+
+enum tas675x_type {
+	TAS67524,
+};
+
+struct tas675x_reg_param {
+	u8  page;
+	u8  reg;
+	u32 val;
+};
+
+struct tas675x_priv {
+	struct device *dev;
+	struct regmap *regmap;
+	enum tas675x_type dev_type;
+	/* Custom regmap lock; protects writes across books */
+	struct mutex io_lock;
+
+	struct gpio_desc *pd_gpio;
+	struct gpio_desc *stby_gpio;
+	struct regulator_bulk_data supplies[2];
+	struct regulator *vbat;
+	bool fast_boot;
+
+	int audio_slot;
+	int llp_slot;
+	int vpredict_slot;
+	int isense_slot;
+	int bclk_offset;
+	int slot_width;
+	unsigned int tx_mask;
+
+	int gpio1_func;
+	int gpio2_func;
+
+	unsigned long active_playback_dais;
+	unsigned long active_capture_dais;
+	unsigned int rate;
+	unsigned int saved_rtldg_en;
+#define TAS675X_DSP_PARAM_NUM	2
+	struct tas675x_reg_param dsp_params[TAS675X_DSP_PARAM_NUM];
+
+	/* Fault monitor, disabled when Fault IRQ is used */
+	struct delayed_work fault_check_work;
+#define TAS675X_FAULT_REGS_NUM	9
+	unsigned int last_status[TAS675X_FAULT_REGS_NUM];
+};
+
+static const char * const tas675x_supply_names[] = {
+	"dvdd",		/* Digital power supply */
+	"pvdd",		/* Output powerstage supply */
+};
+
+/* Page 1 setup initialization defaults */
+static const struct reg_sequence tas675x_page1_init[] = {
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0xC8), 0x20),	/* Charge pump clock */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0x2F), 0x90),	/* VBAT idle */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0x29), 0x40),	/* OC/CBC threshold */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0x2E), 0x0C),	/* OC/CBC config */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0xC5), 0x02),	/* OC/CBC config */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0xC6), 0x10),	/* OC/CBC config */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0x1F), 0x20),	/* OC/CBC config */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0x16), 0x01),	/* OC/CBC config */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0x1E), 0x04),	/* OC/CBC config */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0xC1), 0x00),	/* CH1 DC fault */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0xC2), 0x04),	/* CH2 DC fault */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0xC3), 0x00),	/* CH3 DC fault */
+	REG_SEQ0(TAS675X_PAGE_REG(1, 0xC4), 0x00),	/* CH4 DC fault */
+};
+
+static inline const char *tas675x_state_name(unsigned int state)
+{
+	switch (state & 0x0F) {
+	case TAS675X_STATE_DEEPSLEEP:	return "DEEPSLEEP";
+	case TAS675X_STATE_LOAD_DIAG:	return "LOAD_DIAG";
+	case TAS675X_STATE_SLEEP:	return "SLEEP";
+	case TAS675X_STATE_HIZ:		return "HIZ";
+	case TAS675X_STATE_PLAY:	return "PLAY";
+	case TAS675X_STATE_FAULT:	return "FAULT";
+	case TAS675X_STATE_AUTOREC:	return "AUTOREC";
+	default:			return "UNKNOWN";
+	}
+}
+
+static inline int tas675x_set_state_all(struct tas675x_priv *tas, u8 state)
+{
+	const struct reg_sequence seq[] = {
+		REG_SEQ0(TAS675X_STATE_CTRL_CH1_CH2_REG, state),
+		REG_SEQ0(TAS675X_STATE_CTRL_CH3_CH4_REG, state),
+	};
+
+	return regmap_multi_reg_write(tas->regmap, seq, ARRAY_SIZE(seq));
+}
+
+static inline int tas675x_select_book(struct regmap *regmap, u8 book)
+{
+	int ret;
+
+	/* Reset page to 0 before switching books */
+	ret = regmap_write(regmap, TAS675X_PAGE_CTRL_REG, 0x00);
+	if (!ret)
+		ret = regmap_write(regmap, TAS675X_BOOK_CTRL_REG, book);
+
+	return ret;
+}
+
+/* Raw I2C version of tas675x_select_book, must be called with io_lock held */
+static inline int __tas675x_select_book(struct tas675x_priv *tas, u8 book)
+{
+	struct i2c_client *client = to_i2c_client(tas->dev);
+	int ret;
+
+	/* Reset page to 0 before switching books */
+	ret = i2c_smbus_write_byte_data(client, TAS675X_PAGE_CTRL_REG, 0x00);
+	if (ret)
+		return ret;
+
+	return i2c_smbus_write_byte_data(client, TAS675X_BOOK_CTRL_REG, book);
+}
+
+static int tas675x_dsp_mem_write(struct tas675x_priv *tas, u8 page, u8 reg, u32 val)
+{
+	struct i2c_client *client = to_i2c_client(tas->dev);
+	u8 buf[4];
+	int ret;
+
+	/* DSP registers are 32 bit big-endian */
+	buf[0] = (val >> 24) & 0xFF;
+	buf[1] = (val >> 16) & 0xFF;
+	buf[2] = (val >> 8) & 0xFF;
+	buf[3] = val & 0xFF;
+
+	/*
+	 * DSP regs in a different book, therefore block
+	 * regmap access before completion.
+	 */
+	mutex_lock(&tas->io_lock);
+
+	ret = __tas675x_select_book(tas, TAS675X_BOOK_DSP);
+	if (ret)
+		goto out;
+
+	ret = i2c_smbus_write_byte_data(client, TAS675X_PAGE_CTRL_REG, page);
+	if (ret)
+		goto out;
+
+	ret = i2c_smbus_write_i2c_block_data(client, reg, sizeof(buf), buf);
+
+out:
+	__tas675x_select_book(tas, TAS675X_BOOK_DEFAULT);
+	mutex_unlock(&tas->io_lock);
+
+	return ret;
+}
+
+static int tas675x_dsp_mem_read(struct tas675x_priv *tas, u8 page, u8 reg, u32 *val)
+{
+	struct i2c_client *client = to_i2c_client(tas->dev);
+	u8 buf[4];
+	int ret;
+
+	/*
+	 * DSP regs in a different book, therefore block
+	 * regmap access before completion.
+	 */
+	mutex_lock(&tas->io_lock);
+
+	ret = __tas675x_select_book(tas, TAS675X_BOOK_DSP);
+	if (ret)
+		goto out;
+
+	ret = i2c_smbus_write_byte_data(client, TAS675X_PAGE_CTRL_REG, page);
+	if (ret)
+		goto out;
+
+	ret = i2c_smbus_read_i2c_block_data(client, reg, sizeof(buf), buf);
+	if (ret == sizeof(buf)) {
+		*val = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
+		ret = 0;
+	} else if (ret >= 0) {
+		ret = -EIO;
+	}
+
+out:
+	__tas675x_select_book(tas, TAS675X_BOOK_DEFAULT);
+	mutex_unlock(&tas->io_lock);
+
+	return ret;
+}
+
+static const struct {
+	const char *name;
+	int val;
+} tas675x_gpio_func_map[] = {
+	/* Output functions */
+	{ "low",            TAS675X_GPIO_SEL_LOW },
+	{ "auto-mute",      TAS675X_GPIO_SEL_AUTO_MUTE_ALL },
+	{ "auto-mute-ch4",  TAS675X_GPIO_SEL_AUTO_MUTE_CH4 },
+	{ "auto-mute-ch3",  TAS675X_GPIO_SEL_AUTO_MUTE_CH3 },
+	{ "auto-mute-ch2",  TAS675X_GPIO_SEL_AUTO_MUTE_CH2 },
+	{ "auto-mute-ch1",  TAS675X_GPIO_SEL_AUTO_MUTE_CH1 },
+	{ "sdout2",         TAS675X_GPIO_SEL_SDOUT2 },
+	{ "sdout1",         TAS675X_GPIO_SEL_SDOUT1 },
+	{ "warn",           TAS675X_GPIO_SEL_WARN },
+	{ "fault",          TAS675X_GPIO_SEL_FAULT },
+	{ "clock-sync",     TAS675X_GPIO_SEL_CLOCK_SYNC },
+	{ "invalid-clock",  TAS675X_GPIO_SEL_INVALID_CLK },
+	{ "high",           TAS675X_GPIO_SEL_HIGH },
+	/* Input functions */
+	{ "mute",           TAS675X_GPIO_IN_MUTE },
+	{ "phase-sync",     TAS675X_GPIO_IN_PHASE_SYNC },
+	{ "sdin2",          TAS675X_GPIO_IN_SDIN2 },
+	{ "deep-sleep",     TAS675X_GPIO_IN_DEEP_SLEEP },
+	{ "hiz",            TAS675X_GPIO_IN_HIZ },
+	{ "play",           TAS675X_GPIO_IN_PLAY },
+	{ "sleep",          TAS675X_GPIO_IN_SLEEP },
+};
+
+static int tas675x_gpio_func_parse(struct device *dev, const char *propname)
+{
+	const char *str;
+	int i, ret;
+
+	ret = device_property_read_string(dev, propname, &str);
+	if (ret)
+		return -1;
+
+	for (i = 0; i < ARRAY_SIZE(tas675x_gpio_func_map); i++) {
+		if (!strcmp(str, tas675x_gpio_func_map[i].name))
+			return tas675x_gpio_func_map[i].val;
+	}
+
+	dev_warn(dev, "Invalid %s value '%s'\n", propname, str);
+	return -1;
+}
+
+static const struct {
+	unsigned int reg;
+	unsigned int mask;
+} tas675x_gpio_input_table[TAS675X_GPIO_IN_NUM] = {
+	[TAS675X_GPIO_IN_ID_MUTE] = {
+		TAS675X_GPIO_INPUT_MUTE_REG, TAS675X_GPIO_IN_MUTE_MASK },
+	[TAS675X_GPIO_IN_ID_PHASE_SYNC] = {
+		TAS675X_GPIO_INPUT_SYNC_REG, TAS675X_GPIO_IN_SYNC_MASK },
+	[TAS675X_GPIO_IN_ID_SDIN2] = {
+		TAS675X_GPIO_INPUT_SDIN2_REG, TAS675X_GPIO_IN_SDIN2_MASK },
+	[TAS675X_GPIO_IN_ID_DEEP_SLEEP] = {
+		TAS675X_GPIO_INPUT_SLEEP_HIZ_REG, TAS675X_GPIO_IN_DEEP_SLEEP_MASK },
+	[TAS675X_GPIO_IN_ID_HIZ] = {
+		TAS675X_GPIO_INPUT_SLEEP_HIZ_REG, TAS675X_GPIO_IN_HIZ_MASK },
+	[TAS675X_GPIO_IN_ID_PLAY] = {
+		TAS675X_GPIO_INPUT_PLAY_SLEEP_REG, TAS675X_GPIO_IN_PLAY_MASK },
+	[TAS675X_GPIO_IN_ID_SLEEP] = {
+		TAS675X_GPIO_INPUT_PLAY_SLEEP_REG, TAS675X_GPIO_IN_SLEEP_MASK },
+};
+
+static void tas675x_config_gpio_pin(struct regmap *regmap, int func_id,
+				    unsigned int out_sel_reg,
+				    unsigned int pin_idx,
+				    unsigned int *gpio_ctrl)
+{
+	int id;
+
+	if (func_id < 0)
+		return;
+
+	if (func_id & TAS675X_GPIO_FUNC_INPUT) {
+		/* 3-bit mux: 0 = disabled, 0b1 = GPIO1, 0b10 = GPIO2 */
+		id = func_id & ~TAS675X_GPIO_FUNC_INPUT;
+		regmap_update_bits(regmap,
+				   tas675x_gpio_input_table[id].reg,
+				   tas675x_gpio_input_table[id].mask,
+				   (pin_idx + 1) << __ffs(tas675x_gpio_input_table[id].mask));
+	} else {
+		/* Output GPIO, update selection register and enable bit */
+		regmap_write(regmap, out_sel_reg, func_id);
+		*gpio_ctrl |= pin_idx ? TAS675X_GPIO2_OUTPUT_EN : TAS675X_GPIO1_OUTPUT_EN;
+	}
+}
+
+static int tas675x_rtldg_thresh_info(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	/* threshold reg ranges up to 24bit */
+	uinfo->value.integer.max = 0x00FFFFFF;
+	return 0;
+}
+
+static int tas675x_set_rtldg_thresh(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
+	const struct tas675x_reg_param *t =
+		(const struct tas675x_reg_param *)kcontrol->private_value;
+	u32 val = ucontrol->value.integer.value[0];
+	int ret;
+
+	ret = tas675x_dsp_mem_write(tas, t->page, t->reg, val);
+
+	/* Cache the value */
+	if (!ret) {
+		int i;
+
+		for (i = 0; i < ARRAY_SIZE(tas->dsp_params); i++) {
+			if (tas->dsp_params[i].page == t->page &&
+			    tas->dsp_params[i].reg == t->reg) {
+				tas->dsp_params[i].val = val;
+				break;
+			}
+		}
+	}
+
+	/* Return 1 to notify change, or propagate error */
+	return ret ? ret : 1;
+}
+
+static int tas675x_get_rtldg_thresh(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
+	const struct tas675x_reg_param *t =
+		(const struct tas675x_reg_param *)kcontrol->private_value;
+	u32 val = 0;
+	int ret;
+
+	ret = tas675x_dsp_mem_read(tas, t->page, t->reg, &val);
+	if (!ret)
+		ucontrol->value.integer.value[0] = val;
+
+	return ret;
+}
+
+static const struct tas675x_reg_param tas675x_dsp_defaults[] = {
+	[TAS675X_DSP_PARAM_ID_OL_THRESH] = {
+		TAS675X_DSP_PAGE_RTLDG, TAS675X_DSP_RTLDG_OL_THRESH_REG },
+	[TAS675X_DSP_PARAM_ID_SL_THRESH] = {
+		TAS675X_DSP_PAGE_RTLDG, TAS675X_DSP_RTLDG_SL_THRESH_REG },
+};
+
+static_assert(ARRAY_SIZE(tas675x_dsp_defaults) == TAS675X_DSP_PARAM_NUM);
+
+static int tas675x_set_dcldg_trigger(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
+	unsigned int state, state34;
+	int ret;
+
+	if (!ucontrol->value.integer.value[0])
+		return 0;
+
+	if (snd_soc_component_active(comp))
+		return -EBUSY;
+
+	ret = pm_runtime_resume_and_get(tas->dev);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Abort automatic DC LDG retry loops (startup or init-after-fault)
+	 * and clear faults before manual diagnostics.
+	 */
+	regmap_update_bits(tas->regmap, TAS675X_DC_LDG_CTRL_REG,
+			   TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT,
+			   TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT);
+	regmap_write(tas->regmap, TAS675X_RESET_REG, TAS675X_FAULT_CLEAR);
+
+	/* Wait for LOAD_DIAG to exit */
+	ret = regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH1_CH2_REG,
+				       state, (state & 0x0F) != TAS675X_STATE_LOAD_DIAG &&
+					      (state >> 4) != TAS675X_STATE_LOAD_DIAG,
+				       TAS675X_POLL_INTERVAL_US,
+				       TAS675X_STATE_TRANSITION_TIMEOUT_US);
+	ret |= regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH3_CH4_REG,
+					state34, (state34 & 0x0F) != TAS675X_STATE_LOAD_DIAG &&
+						 (state34 >> 4) != TAS675X_STATE_LOAD_DIAG,
+					TAS675X_POLL_INTERVAL_US,
+					TAS675X_STATE_TRANSITION_TIMEOUT_US);
+	if (ret) {
+		dev_err(tas->dev,
+			"DC LDG: abort timeout (CH1/2=0x%02x [%s/%s], CH3/4=0x%02x [%s/%s])\n",
+			state, tas675x_state_name(state), tas675x_state_name(state >> 4),
+			state34, tas675x_state_name(state34), tas675x_state_name(state34 >> 4));
+		goto out_restore_ldg_ctrl;
+	}
+
+	/* Transition to HIZ state */
+	ret = tas675x_set_state_all(tas, TAS675X_STATE_HIZ_BOTH);
+	if (ret)
+		goto out_restore_ldg_ctrl;
+
+	/* Set LOAD_DIAG state for manual DC LDG */
+	ret = tas675x_set_state_all(tas, TAS675X_STATE_LOAD_DIAG_BOTH);
+	if (ret)
+		goto out_restore_ldg_ctrl;
+
+	/* Wait for device to transition to LOAD_DIAG state */
+	ret = regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH1_CH2_REG,
+				       state, state == TAS675X_STATE_LOAD_DIAG_BOTH,
+				       TAS675X_POLL_INTERVAL_US,
+				       TAS675X_STATE_TRANSITION_TIMEOUT_US);
+	ret |= regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH3_CH4_REG,
+					state34, state34 == TAS675X_STATE_LOAD_DIAG_BOTH,
+					TAS675X_POLL_INTERVAL_US,
+					TAS675X_STATE_TRANSITION_TIMEOUT_US);
+	if (ret) {
+		dev_err(tas->dev,
+			"DC LDG: LOAD_DIAG timeout (CH1/2=0x%02x [%s/%s], CH3/4=0x%02x [%s/%s])\n",
+			state, tas675x_state_name(state), tas675x_state_name(state >> 4),
+			state34, tas675x_state_name(state34), tas675x_state_name(state34 >> 4));
+		goto out_restore_hiz;
+	}
+
+	/* Clear ABORT and BYPASS bits to enable manual DC LDG */
+	ret = regmap_update_bits(tas->regmap, TAS675X_DC_LDG_CTRL_REG,
+				 TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT,
+				 0);
+	if (ret)
+		goto out_restore_hiz;
+
+	dev_dbg(tas->dev, "DC LDG: Started\n");
+
+	/* Poll all channels for SLEEP state */
+	ret = regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH1_CH2_REG,
+				       state, state == TAS675X_STATE_SLEEP_BOTH,
+				       TAS675X_POLL_INTERVAL_US,
+				       TAS675X_DC_LDG_TIMEOUT_US);
+	ret |= regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH3_CH4_REG,
+				       state34, state34 == TAS675X_STATE_SLEEP_BOTH,
+				       TAS675X_POLL_INTERVAL_US,
+				       TAS675X_DC_LDG_TIMEOUT_US);
+	if (ret) {
+		dev_err(tas->dev,
+			"DC LDG: SLEEP timeout (CH1/2=0x%02x [%s/%s], CH3/4=0x%02x [%s/%s])\n",
+			state, tas675x_state_name(state), tas675x_state_name(state >> 4),
+			state34, tas675x_state_name(state34), tas675x_state_name(state34 >> 4));
+		goto out_restore_hiz;
+	}
+
+	dev_dbg(tas->dev, "DC LDG: Completed successfully (CH1/2=0x%02x, CH3/4=0x%02x)\n",
+		state, state34);
+
+out_restore_hiz:
+	tas675x_set_state_all(tas, TAS675X_STATE_HIZ_BOTH);
+
+out_restore_ldg_ctrl:
+	regmap_update_bits(tas->regmap, TAS675X_DC_LDG_CTRL_REG,
+			   TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT,
+			   0);
+
+	pm_runtime_mark_last_busy(tas->dev);
+	pm_runtime_put_autosuspend(tas->dev);
+
+	return ret;
+}
+
+static int tas675x_set_acldg_trigger(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
+	unsigned int state, state34;
+	int ret;
+
+	if (!ucontrol->value.integer.value[0])
+		return 0;
+
+	if (snd_soc_component_active(comp))
+		return -EBUSY;
+
+	ret = pm_runtime_resume_and_get(tas->dev);
+	if (ret < 0)
+		return ret;
+
+	/* AC Load Diagnostics requires SLEEP state */
+	ret = tas675x_set_state_all(tas, TAS675X_STATE_SLEEP_BOTH);
+	if (ret) {
+		dev_err(tas->dev, "AC LDG: Failed to set SLEEP state: %d\n", ret);
+		goto out;
+	}
+
+	/* Start AC LDG on all 4 channels (0x0F) */
+	ret = regmap_write(tas->regmap, TAS675X_AC_LDG_CTRL_REG, 0x0F);
+	if (ret) {
+		dev_err(tas->dev, "AC LDG: Failed to start: %d\n", ret);
+		goto out;
+	}
+
+	dev_dbg(tas->dev, "AC LDG: Started\n");
+
+	/* Poll all channels for SLEEP state */
+	ret = regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH1_CH2_REG,
+				       state, (state == TAS675X_STATE_SLEEP_BOTH),
+				       TAS675X_POLL_INTERVAL_US,
+				       TAS675X_AC_LDG_TIMEOUT_US);
+	if (ret) {
+		dev_err(tas->dev,
+			"AC LDG: CH1/CH2 timeout: %d (state=0x%02x [%s/%s])\n",
+			ret, state, tas675x_state_name(state),
+			tas675x_state_name(state >> 4));
+		regmap_write(tas->regmap, TAS675X_AC_LDG_CTRL_REG, 0x00);
+		goto out;
+	}
+
+	ret = regmap_read_poll_timeout(tas->regmap, TAS675X_STATE_REPORT_CH3_CH4_REG,
+				       state34, (state34 == TAS675X_STATE_SLEEP_BOTH),
+				       TAS675X_POLL_INTERVAL_US,
+				       TAS675X_AC_LDG_TIMEOUT_US);
+	if (ret) {
+		dev_err(tas->dev,
+			"AC LDG: CH3/CH4 timeout: %d (state=0x%02x [%s/%s])\n",
+			ret, state34, tas675x_state_name(state34),
+			tas675x_state_name(state34 >> 4));
+		regmap_write(tas->regmap, TAS675X_AC_LDG_CTRL_REG, 0x00);
+		goto out;
+	}
+
+	dev_dbg(tas->dev, "AC LDG: Completed successfully (CH1/2=0x%02x, CH3/4=0x%02x)\n",
+		state, state34);
+	regmap_write(tas->regmap, TAS675X_AC_LDG_CTRL_REG, 0x00);
+
+out:
+	pm_runtime_mark_last_busy(tas->dev);
+	pm_runtime_put_autosuspend(tas->dev);
+
+	return ret;
+}
+
+static int tas675x_rtldg_impedance_info(struct snd_kcontrol *kcontrol,
+					struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 0xFFFF;
+	return 0;
+}
+
+static int tas675x_get_rtldg_impedance(struct snd_kcontrol *kcontrol,
+				       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
+	unsigned int msb_reg = (unsigned int)kcontrol->private_value;
+	u8 buf[2];
+	int ret;
+
+	ret = regmap_bulk_read(tas->regmap, msb_reg, buf, 2);
+	if (ret)
+		return ret;
+
+	ucontrol->value.integer.value[0] = (buf[0] << 8) | buf[1];
+	return 0;
+}
+
+static int tas675x_dc_resistance_info(struct snd_kcontrol *kcontrol,
+				      struct snd_ctl_elem_info *uinfo)
+{
+	/* 10-bit: 2-bit MSB + 8-bit LSB, 0.1 ohm/code, 0-102.3 ohm */
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1023;
+	return 0;
+}
+
+static int tas675x_get_dc_resistance(struct snd_kcontrol *kcontrol,
+				     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *comp = snd_kcontrol_chip(kcontrol);
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(comp);
+	unsigned int lsb_reg = (unsigned int)kcontrol->private_value;
+	unsigned int msb, lsb, shift;
+	int ret;
+
+	ret = regmap_read(tas->regmap, TAS675X_DC_LDG_DCR_MSB_REG, &msb);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(tas->regmap, lsb_reg, &lsb);
+	if (ret)
+		return ret;
+
+	/* 2-bit MSB: CH1=[7:6], CH2=[5:4], CH3=[3:2], CH4=[1:0] */
+	shift = 6 - (lsb_reg - TAS675X_CH1_DC_LDG_DCR_LSB_REG) * 2;
+	msb = (msb >> shift) & 0x3;
+
+	ucontrol->value.integer.value[0] = (msb << 8) | lsb;
+	return 0;
+}
+
+/* Counterparts with read-only access */
+#define SOC_SINGLE_RO(xname, xreg, xshift, xmax) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = xname, \
+	.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+	.info = snd_soc_info_volsw, \
+	.get = snd_soc_get_volsw, \
+	.private_value = SOC_SINGLE_VALUE(xreg, xshift, 0, xmax, 0, 0) }
+#define SOC_DC_RESIST_RO(xname, xlsb_reg) \
+{	.name = xname, \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+	.info = tas675x_dc_resistance_info, \
+	.get = tas675x_get_dc_resistance, \
+	.private_value = (xlsb_reg) }
+#define SOC_RTLDG_IMP_RO(xname, xreg) \
+{	.name = xname, \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+	.info = tas675x_rtldg_impedance_info, \
+	.get = tas675x_get_rtldg_impedance, \
+	.private_value = (xreg) }
+
+#define SOC_DSP_THRESH_EXT(xname, xthresh) \
+{	.name = xname, \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.info = tas675x_rtldg_thresh_info, \
+	.get = tas675x_get_rtldg_thresh, \
+	.put = tas675x_set_rtldg_thresh, \
+	.private_value = (unsigned long)&(xthresh) }
+
+/*
+ * DAC digital volumes. From -103 to 0 dB in 0.5 dB steps, -103.5 dB means mute.
+ * DAC analog gain. From -15.5 to 0 dB in 0.5 dB steps, no mute.
+ */
+static const DECLARE_TLV_DB_SCALE(tas675x_dig_vol_tlv, -10350, 50, 1);
+static const DECLARE_TLV_DB_SCALE(tas675x_ana_gain_tlv, -1550, 50, 0);
+
+static const char * const tas675x_ss_texts[] = {
+	"Disabled", "Triangle", "Random", "Triangle and Random"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ss_enum, TAS675X_SS_CTRL_REG, 0, tas675x_ss_texts);
+
+static const char * const tas675x_ss_tri_range_texts[] = {
+	"6.5%", "13.5%", "5%", "10%"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ss_tri_range_enum,
+			    TAS675X_SS_RANGE_CTRL_REG, 0,
+			    tas675x_ss_tri_range_texts);
+
+static const char * const tas675x_ss_rdm_range_texts[] = {
+	"0.83%", "2.50%", "5.83%", "12.50%", "25.83%"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ss_rdm_range_enum,
+			    TAS675X_SS_RANGE_CTRL_REG, 4,
+			    tas675x_ss_rdm_range_texts);
+
+static const char * const tas675x_ss_rdm_dwell_texts[] = {
+	"1/FSS to 2/FSS", "1/FSS to 4/FSS", "1/FSS to 8/FSS", "1/FSS to 15/FSS"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ss_rdm_dwell_enum,
+			    TAS675X_SS_RANGE_CTRL_REG, 2,
+			    tas675x_ss_rdm_dwell_texts);
+
+static const char * const tas675x_oc_limit_texts[] = {
+	"Level 4", "Level 3", "Level 2", "Level 1"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_oc_limit_enum, TAS675X_CURRENT_LIMIT_CTRL_REG,
+			    0, tas675x_oc_limit_texts);
+
+static const char * const tas675x_otw_texts[] = {
+	"Disabled", ">95C", ">110C", ">125C", ">135C", ">145C", ">155C", ">165C"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ch1_otw_enum,
+			    TAS675X_OTW_CTRL_CH1_CH2_REG, 4,
+			    tas675x_otw_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch2_otw_enum,
+			    TAS675X_OTW_CTRL_CH1_CH2_REG, 0,
+			    tas675x_otw_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch3_otw_enum,
+			    TAS675X_OTW_CTRL_CH3_CH4_REG, 4,
+			    tas675x_otw_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch4_otw_enum,
+			    TAS675X_OTW_CTRL_CH3_CH4_REG, 0,
+			    tas675x_otw_texts);
+
+static const char * const tas675x_dc_ldg_sl_texts[] = {
+	"0.5 Ohm", "1 Ohm", "1.5 Ohm", "2 Ohm", "2.5 Ohm",
+	"3 Ohm", "3.5 Ohm", "4 Ohm", "4.5 Ohm", "5 Ohm"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ch1_dc_ldg_sl_enum,
+			    TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG, 4,
+			    tas675x_dc_ldg_sl_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch2_dc_ldg_sl_enum,
+			    TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG, 0,
+			    tas675x_dc_ldg_sl_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch3_dc_ldg_sl_enum,
+			    TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG, 4,
+			    tas675x_dc_ldg_sl_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch4_dc_ldg_sl_enum,
+			    TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG, 0,
+			    tas675x_dc_ldg_sl_texts);
+
+static const char * const tas675x_dc_slol_ramp_texts[] = {
+	"15 ms", "30 ms", "10 ms", "20 ms"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_dc_slol_ramp_enum,
+			    TAS675X_DC_LDG_TIME_CTRL_REG, 6,
+			    tas675x_dc_slol_ramp_texts);
+
+static const char * const tas675x_dc_slol_settling_texts[] = {
+	"10 ms", "5 ms", "20 ms", "15 ms"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_dc_slol_settling_enum,
+			    TAS675X_DC_LDG_TIME_CTRL_REG, 4,
+			    tas675x_dc_slol_settling_texts);
+
+static const char * const tas675x_dc_s2pg_ramp_texts[] = {
+	"5 ms", "2.5 ms", "10 ms", "15 ms"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_dc_s2pg_ramp_enum,
+			    TAS675X_DC_LDG_TIME_CTRL_REG, 2,
+			    tas675x_dc_s2pg_ramp_texts);
+
+static const char * const tas675x_dc_s2pg_settling_texts[] = {
+	"10 ms", "5 ms", "20 ms", "30 ms"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_dc_s2pg_settling_enum,
+			    TAS675X_DC_LDG_TIME_CTRL_REG, 0,
+			    tas675x_dc_s2pg_settling_texts);
+
+static const char * const tas675x_dsp_mode_texts[] = {
+	"Normal", "LLP", "FFLP"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_dsp_mode_enum,
+			    TAS675X_LL_EN_REG, 0,
+			    tas675x_dsp_mode_texts);
+
+static const char * const tas675x_ana_ramp_texts[] = {
+	"15us", "60us", "200us", "400us"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ana_ramp_enum,
+			    TAS675X_ANALOG_GAIN_RAMP_CTRL_REG, 2,
+			    tas675x_ana_ramp_texts);
+
+static const char * const tas675x_ramp_rate_texts[] = {
+	"4 FS", "16 FS", "32 FS", "Instant"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ramp_down_rate_enum,
+			    TAS675X_DIG_VOL_RAMP_CTRL_REG, 6,
+			    tas675x_ramp_rate_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ramp_up_rate_enum,
+			    TAS675X_DIG_VOL_RAMP_CTRL_REG, 2,
+			    tas675x_ramp_rate_texts);
+
+static const char * const tas675x_ramp_step_texts[] = {
+	"4dB", "2dB", "1dB", "0.5dB"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ramp_down_step_enum,
+			    TAS675X_DIG_VOL_RAMP_CTRL_REG, 4,
+			    tas675x_ramp_step_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ramp_up_step_enum,
+			    TAS675X_DIG_VOL_RAMP_CTRL_REG, 0,
+			    tas675x_ramp_step_texts);
+
+static const char * const tas675x_vol_combine_ch12_texts[] = {
+	"Independent", "CH2 follows CH1", "CH1 follows CH2"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_vol_combine_ch12_enum,
+			    TAS675X_DIG_VOL_COMBINE_CTRL_REG, 0,
+			    tas675x_vol_combine_ch12_texts);
+
+static const char * const tas675x_vol_combine_ch34_texts[] = {
+	"Independent", "CH4 follows CH3", "CH3 follows CH4"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_vol_combine_ch34_enum,
+			    TAS675X_DIG_VOL_COMBINE_CTRL_REG, 2,
+			    tas675x_vol_combine_ch34_texts);
+
+static const char * const tas675x_auto_mute_time_texts[] = {
+	"11.5ms", "53ms", "106.5ms", "266.5ms",
+	"535ms", "1065ms", "2665ms", "5330ms"
+};
+
+static SOC_ENUM_SINGLE_DECL(tas675x_ch1_mute_time_enum,
+			    TAS675X_AUTO_MUTE_TIMING_CH1_CH2_REG, 4,
+			    tas675x_auto_mute_time_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch2_mute_time_enum,
+			    TAS675X_AUTO_MUTE_TIMING_CH1_CH2_REG, 0,
+			    tas675x_auto_mute_time_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch3_mute_time_enum,
+			    TAS675X_AUTO_MUTE_TIMING_CH3_CH4_REG, 4,
+			    tas675x_auto_mute_time_texts);
+static SOC_ENUM_SINGLE_DECL(tas675x_ch4_mute_time_enum,
+			    TAS675X_AUTO_MUTE_TIMING_CH3_CH4_REG, 0,
+			    tas675x_auto_mute_time_texts);
+
+/*
+ * ALSA Mixer Controls
+ *
+ * For detailed documentation of each control see:
+ * Documentation/sound/codecs/tas675x.rst
+ */
+static const struct snd_kcontrol_new tas675x_snd_controls[] = {
+	/* Volume & Gain Control */
+	SOC_DOUBLE_R_TLV("Analog Playback Volume", TAS675X_ANALOG_GAIN_CH1_CH2_REG,
+			 TAS675X_ANALOG_GAIN_CH3_CH4_REG, 1, 0x1F, 1, tas675x_ana_gain_tlv),
+	SOC_ENUM("Analog Gain Ramp Step", tas675x_ana_ramp_enum),
+	SOC_SINGLE_RANGE_TLV("CH1 Digital Playback Volume",
+			     TAS675X_DIG_VOL_CH1_REG, 0, 0x30, 0xFF, 1,
+			     tas675x_dig_vol_tlv),
+	SOC_SINGLE_RANGE_TLV("CH2 Digital Playback Volume",
+			     TAS675X_DIG_VOL_CH2_REG, 0, 0x30, 0xFF, 1,
+			     tas675x_dig_vol_tlv),
+	SOC_SINGLE_RANGE_TLV("CH3 Digital Playback Volume",
+			     TAS675X_DIG_VOL_CH3_REG, 0, 0x30, 0xFF, 1,
+			     tas675x_dig_vol_tlv),
+	SOC_SINGLE_RANGE_TLV("CH4 Digital Playback Volume",
+			     TAS675X_DIG_VOL_CH4_REG, 0, 0x30, 0xFF, 1,
+			     tas675x_dig_vol_tlv),
+	SOC_ENUM("Volume Ramp Down Rate", tas675x_ramp_down_rate_enum),
+	SOC_ENUM("Volume Ramp Down Step", tas675x_ramp_down_step_enum),
+	SOC_ENUM("Volume Ramp Up Rate", tas675x_ramp_up_rate_enum),
+	SOC_ENUM("Volume Ramp Up Step", tas675x_ramp_up_step_enum),
+	SOC_ENUM("CH1/2 Volume Combine", tas675x_vol_combine_ch12_enum),
+	SOC_ENUM("CH3/4 Volume Combine", tas675x_vol_combine_ch34_enum),
+
+	/* Auto Mute & Silence Detection */
+	SOC_SINGLE("CH1 Auto Mute Switch", TAS675X_AUTO_MUTE_EN_REG, 0, 1, 0),
+	SOC_SINGLE("CH2 Auto Mute Switch", TAS675X_AUTO_MUTE_EN_REG, 1, 1, 0),
+	SOC_SINGLE("CH3 Auto Mute Switch", TAS675X_AUTO_MUTE_EN_REG, 2, 1, 0),
+	SOC_SINGLE("CH4 Auto Mute Switch", TAS675X_AUTO_MUTE_EN_REG, 3, 1, 0),
+	SOC_SINGLE("Auto Mute Combine Switch", TAS675X_AUTO_MUTE_EN_REG, 4, 1, 0),
+	SOC_ENUM("CH1 Auto Mute Time", tas675x_ch1_mute_time_enum),
+	SOC_ENUM("CH2 Auto Mute Time", tas675x_ch2_mute_time_enum),
+	SOC_ENUM("CH3 Auto Mute Time", tas675x_ch3_mute_time_enum),
+	SOC_ENUM("CH4 Auto Mute Time", tas675x_ch4_mute_time_enum),
+
+	/* Clock & EMI Management */
+	SOC_ENUM("Spread Spectrum Mode", tas675x_ss_enum),
+	SOC_ENUM("SS Triangle Range", tas675x_ss_tri_range_enum),
+	SOC_ENUM("SS Random Range", tas675x_ss_rdm_range_enum),
+	SOC_ENUM("SS Random Dwell Range", tas675x_ss_rdm_dwell_enum),
+	SOC_SINGLE("SS Triangle Dwell Min", TAS675X_SS_DWELL_CTRL_REG, 4, 15, 0),
+	SOC_SINGLE("SS Triangle Dwell Max", TAS675X_SS_DWELL_CTRL_REG, 0, 15, 0),
+
+	/* Hardware Protection */
+	SOC_SINGLE("OTSD Auto Recovery Switch", TAS675X_OTSD_RECOVERY_EN_REG, 1, 1, 0),
+	SOC_ENUM("Overcurrent Limit Level", tas675x_oc_limit_enum),
+	SOC_ENUM("CH1 OTW Threshold", tas675x_ch1_otw_enum),
+	SOC_ENUM("CH2 OTW Threshold", tas675x_ch2_otw_enum),
+	SOC_ENUM("CH3 OTW Threshold", tas675x_ch3_otw_enum),
+	SOC_ENUM("CH4 OTW Threshold", tas675x_ch4_otw_enum),
+
+	/* DSP Signal Path & Mode */
+	SOC_ENUM("DSP Signal Path Mode", tas675x_dsp_mode_enum),
+
+	/* DC Load Diagnostics */
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "DC LDG Trigger",
+		.access = SNDRV_CTL_ELEM_ACCESS_WRITE,
+		.info = snd_ctl_boolean_mono_info,
+		.put = tas675x_set_dcldg_trigger,
+	},
+	SOC_SINGLE("DC LDG Auto Diagnostics Switch", TAS675X_DC_LDG_CTRL_REG, 0, 1, 1),
+	SOC_SINGLE("CH1 LO LDG Switch", TAS675X_DC_LDG_LO_CTRL_REG, 3, 1, 0),
+	SOC_SINGLE("CH2 LO LDG Switch", TAS675X_DC_LDG_LO_CTRL_REG, 2, 1, 0),
+	SOC_SINGLE("CH3 LO LDG Switch", TAS675X_DC_LDG_LO_CTRL_REG, 1, 1, 0),
+	SOC_SINGLE("CH4 LO LDG Switch", TAS675X_DC_LDG_LO_CTRL_REG, 0, 1, 0),
+	SOC_ENUM("DC LDG SLOL Ramp Time", tas675x_dc_slol_ramp_enum),
+	SOC_ENUM("DC LDG SLOL Settling Time", tas675x_dc_slol_settling_enum),
+	SOC_ENUM("DC LDG S2PG Ramp Time", tas675x_dc_s2pg_ramp_enum),
+	SOC_ENUM("DC LDG S2PG Settling Time", tas675x_dc_s2pg_settling_enum),
+	SOC_ENUM("CH1 DC LDG SL Threshold", tas675x_ch1_dc_ldg_sl_enum),
+	SOC_ENUM("CH2 DC LDG SL Threshold", tas675x_ch2_dc_ldg_sl_enum),
+	SOC_ENUM("CH3 DC LDG SL Threshold", tas675x_ch3_dc_ldg_sl_enum),
+	SOC_ENUM("CH4 DC LDG SL Threshold", tas675x_ch4_dc_ldg_sl_enum),
+	SOC_SINGLE_RO("DC LDG Result", TAS675X_DC_LDG_RESULT_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH1 DC LDG Report", TAS675X_DC_LDG_REPORT_CH1_CH2_REG, 4, 0x0F),
+	SOC_SINGLE_RO("CH2 DC LDG Report", TAS675X_DC_LDG_REPORT_CH1_CH2_REG, 0, 0x0F),
+	SOC_SINGLE_RO("CH3 DC LDG Report", TAS675X_DC_LDG_REPORT_CH3_CH4_REG, 4, 0x0F),
+	SOC_SINGLE_RO("CH4 DC LDG Report", TAS675X_DC_LDG_REPORT_CH3_CH4_REG, 0, 0x0F),
+	SOC_SINGLE_RO("CH1 LO LDG Report", TAS675X_DC_LDG_RESULT_REG, 7, 1),
+	SOC_SINGLE_RO("CH2 LO LDG Report", TAS675X_DC_LDG_RESULT_REG, 6, 1),
+	SOC_SINGLE_RO("CH3 LO LDG Report", TAS675X_DC_LDG_RESULT_REG, 5, 1),
+	SOC_SINGLE_RO("CH4 LO LDG Report", TAS675X_DC_LDG_RESULT_REG, 4, 1),
+	SOC_DC_RESIST_RO("CH1 DC Resistance", TAS675X_CH1_DC_LDG_DCR_LSB_REG),
+	SOC_DC_RESIST_RO("CH2 DC Resistance", TAS675X_CH2_DC_LDG_DCR_LSB_REG),
+	SOC_DC_RESIST_RO("CH3 DC Resistance", TAS675X_CH3_DC_LDG_DCR_LSB_REG),
+	SOC_DC_RESIST_RO("CH4 DC Resistance", TAS675X_CH4_DC_LDG_DCR_LSB_REG),
+
+	/* AC Load Diagnostics */
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "AC LDG Trigger",
+		.access = SNDRV_CTL_ELEM_ACCESS_WRITE,
+		.info = snd_ctl_boolean_mono_info,
+		.put = tas675x_set_acldg_trigger,
+	},
+	SOC_SINGLE("AC LDG Gain", TAS675X_AC_LDG_CTRL_REG, 4, 1, 0),
+	SOC_SINGLE("AC LDG Test Frequency", TAS675X_AC_LDG_FREQ_CTRL_REG, 0, 0xFF, 0),
+	SOC_SINGLE_RO("CH1 AC LDG Real", TAS675X_AC_LDG_REPORT_CH1_R_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH1 AC LDG Imag", TAS675X_AC_LDG_REPORT_CH1_I_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH2 AC LDG Real", TAS675X_AC_LDG_REPORT_CH2_R_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH2 AC LDG Imag", TAS675X_AC_LDG_REPORT_CH2_I_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH3 AC LDG Real", TAS675X_AC_LDG_REPORT_CH3_R_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH3 AC LDG Imag", TAS675X_AC_LDG_REPORT_CH3_I_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH4 AC LDG Real", TAS675X_AC_LDG_REPORT_CH4_R_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH4 AC LDG Imag", TAS675X_AC_LDG_REPORT_CH4_I_REG, 0, 0xFF),
+
+	/* Temperature and Voltage Monitoring */
+	SOC_SINGLE_RO("PVDD Sense", TAS675X_PVDD_SENSE_REG, 0, 0xFF),
+	SOC_SINGLE_RO("Global Temperature", TAS675X_TEMP_GLOBAL_REG, 0, 0xFF),
+	SOC_SINGLE_RO("CH1 Temperature Range", TAS675X_TEMP_CH1_CH2_REG, 6, 3),
+	SOC_SINGLE_RO("CH2 Temperature Range", TAS675X_TEMP_CH1_CH2_REG, 4, 3),
+	SOC_SINGLE_RO("CH3 Temperature Range", TAS675X_TEMP_CH3_CH4_REG, 2, 3),
+	SOC_SINGLE_RO("CH4 Temperature Range", TAS675X_TEMP_CH3_CH4_REG, 0, 3),
+
+	/* Speaker Protection & Detection */
+	SOC_SINGLE("Tweeter Detection Switch", TAS675X_TWEETER_DETECT_CTRL_REG, 0, 1, 1),
+	SOC_SINGLE("Tweeter Detect Threshold", TAS675X_TWEETER_DETECT_THRESH_REG, 0, 0xFF, 0),
+	SOC_SINGLE_RO("CH1 Tweeter Detect Report", TAS675X_TWEETER_REPORT_REG, 3, 1),
+	SOC_SINGLE_RO("CH2 Tweeter Detect Report", TAS675X_TWEETER_REPORT_REG, 2, 1),
+	SOC_SINGLE_RO("CH3 Tweeter Detect Report", TAS675X_TWEETER_REPORT_REG, 1, 1),
+	SOC_SINGLE_RO("CH4 Tweeter Detect Report", TAS675X_TWEETER_REPORT_REG, 0, 1),
+
+	/*
+	 * Unavailable in LLP, available in Normal & FFLP
+	 */
+	SOC_SINGLE("Thermal Foldback Switch", TAS675X_DSP_CTRL_REG, 0, 1, 0),
+	SOC_SINGLE("PVDD Foldback Switch", TAS675X_DSP_CTRL_REG, 4, 1, 0),
+	SOC_SINGLE("DC Blocker Bypass Switch", TAS675X_DC_BLOCK_BYP_REG, 0, 1, 0),
+	SOC_SINGLE("Clip Detect Switch", TAS675X_CLIP_DETECT_CTRL_REG, 6, 1, 0),
+	SOC_SINGLE("Audio SDOUT Switch", TAS675X_DSP_CTRL_REG, 5, 1, 0),
+
+	/*
+	 * Unavailable in both FFLP and LLP, Normal mode only
+	 */
+	/* Real-Time Load Diagnostics */
+	SOC_SINGLE("CH1 RTLDG Switch", TAS675X_RTLDG_EN_REG, 3, 1, 0),
+	SOC_SINGLE("CH2 RTLDG Switch", TAS675X_RTLDG_EN_REG, 2, 1, 0),
+	SOC_SINGLE("CH3 RTLDG Switch", TAS675X_RTLDG_EN_REG, 1, 1, 0),
+	SOC_SINGLE("CH4 RTLDG Switch", TAS675X_RTLDG_EN_REG, 0, 1, 0),
+	SOC_SINGLE("RTLDG Clip Mask Switch", TAS675X_RTLDG_EN_REG, 4, 1, 0),
+	SOC_SINGLE("ISENSE Calibration Switch", TAS675X_ISENSE_CAL_REG, 3, 1, 0),
+	SOC_DSP_THRESH_EXT("RTLDG Open Load Threshold",
+			   tas675x_dsp_defaults[TAS675X_DSP_PARAM_ID_OL_THRESH]),
+	SOC_DSP_THRESH_EXT("RTLDG Short Load Threshold",
+			   tas675x_dsp_defaults[TAS675X_DSP_PARAM_ID_SL_THRESH]),
+	SOC_RTLDG_IMP_RO("CH1 RTLDG Impedance", TAS675X_CH1_RTLDG_IMP_MSB_REG),
+	SOC_RTLDG_IMP_RO("CH2 RTLDG Impedance", TAS675X_CH2_RTLDG_IMP_MSB_REG),
+	SOC_RTLDG_IMP_RO("CH3 RTLDG Impedance", TAS675X_CH3_RTLDG_IMP_MSB_REG),
+	SOC_RTLDG_IMP_RO("CH4 RTLDG Impedance", TAS675X_CH4_RTLDG_IMP_MSB_REG),
+};
+
+static const struct snd_kcontrol_new tas675x_audio_path_switch =
+	SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 1);
+
+static const struct snd_kcontrol_new tas675x_anc_path_switch =
+	SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 1);
+
+static const struct snd_soc_dapm_widget tas675x_dapm_widgets[] = {
+	SND_SOC_DAPM_SUPPLY("Analog Core", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("SDOUT Vpredict", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("SDOUT Isense", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_DAC("Audio DAC", "Playback", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("ANC DAC", "ANC Playback", SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_ADC("Feedback ADC", "Feedback Capture", SND_SOC_NOPM, 0, 0),
+
+	SND_SOC_DAPM_SWITCH("Audio Path", SND_SOC_NOPM, 0, 0,
+			    &tas675x_audio_path_switch),
+	SND_SOC_DAPM_SWITCH("ANC Path", SND_SOC_NOPM, 0, 0,
+			    &tas675x_anc_path_switch),
+
+	/*
+	 * Even though all channels are coupled in terms of power control,
+	 * use logical outputs for each channel to allow independent routing
+	 * and DAPM controls if needed.
+	 */
+	SND_SOC_DAPM_OUTPUT("OUT_CH1"),
+	SND_SOC_DAPM_OUTPUT("OUT_CH2"),
+	SND_SOC_DAPM_OUTPUT("OUT_CH3"),
+	SND_SOC_DAPM_OUTPUT("OUT_CH4"),
+	SND_SOC_DAPM_INPUT("SPEAKER_LOAD"),
+};
+
+static const struct snd_soc_dapm_route tas675x_dapm_routes[] = {
+	{ "Audio DAC", NULL, "Analog Core" },
+	{ "Audio Path", "Switch", "Audio DAC" },
+	{ "OUT_CH1", NULL, "Audio Path" },
+	{ "OUT_CH2", NULL, "Audio Path" },
+	{ "OUT_CH3", NULL, "Audio Path" },
+	{ "OUT_CH4", NULL, "Audio Path" },
+
+	{ "ANC DAC", NULL, "Analog Core" },
+	{ "ANC Path", "Switch", "ANC DAC" },
+	{ "OUT_CH1", NULL, "ANC Path" },
+	{ "OUT_CH2", NULL, "ANC Path" },
+	{ "OUT_CH3", NULL, "ANC Path" },
+	{ "OUT_CH4", NULL, "ANC Path" },
+
+	{ "Feedback ADC", NULL, "Analog Core" },
+	{ "Feedback ADC", NULL, "SDOUT Vpredict" },
+	{ "Feedback ADC", NULL, "SDOUT Isense" },
+	{ "Feedback ADC", NULL, "SPEAKER_LOAD" },
+};
+
+static void tas675x_program_slot_offsets(struct tas675x_priv *tas,
+					 int dai_id, int slot_width)
+{
+	int offset = 0;
+
+	switch (dai_id) {
+	case 0:
+	/* Standard Audio on SDIN */
+		if (tas->audio_slot >= 0)
+			offset = tas->audio_slot * slot_width;
+		else if (tas->tx_mask)
+			offset = __ffs(tas->tx_mask) * slot_width;
+		else
+			return;
+		offset += tas->bclk_offset;
+		regmap_update_bits(tas->regmap, TAS675X_SDIN_OFFSET_MSB_REG,
+				   TAS675X_SDIN_AUDIO_OFF_MSB_MASK,
+				   FIELD_PREP(TAS675X_SDIN_AUDIO_OFF_MSB_MASK, offset >> 8));
+		regmap_write(tas->regmap, TAS675X_SDIN_AUDIO_OFFSET_REG,
+			     offset & 0xFF);
+		break;
+	case 1:
+	/*
+	 * Low-Latency Playback on SDIN, **only** enabled in LLP mode
+	 * and to be mixed with main audio before output amplification
+	 * to achieve ANC/RNC.
+	 */
+		if (tas->llp_slot >= 0)
+			offset = tas->llp_slot * slot_width;
+		else if (tas->tx_mask)
+			offset = __ffs(tas->tx_mask) * slot_width;
+		else
+			return;
+		offset += tas->bclk_offset;
+		regmap_update_bits(tas->regmap, TAS675X_SDIN_OFFSET_MSB_REG,
+				   TAS675X_SDIN_LL_OFF_MSB_MASK,
+				   FIELD_PREP(TAS675X_SDIN_LL_OFF_MSB_MASK, offset >> 8));
+		regmap_write(tas->regmap, TAS675X_SDIN_LL_OFFSET_REG,
+			     offset & 0xFF);
+		break;
+	case 2:
+	/* SDOUT Data Output (Vpredict + Isense feedback) */
+		if (!tas->slot_width)
+			break;
+		if (tas->vpredict_slot >= 0) {
+			offset = tas->vpredict_slot * slot_width;
+			offset += tas->bclk_offset;
+			regmap_update_bits(tas->regmap, TAS675X_SDOUT_OFFSET_MSB_REG,
+					   TAS675X_SDOUT_VP_OFF_MSB_MASK,
+					   FIELD_PREP(TAS675X_SDOUT_VP_OFF_MSB_MASK, offset >> 8));
+			regmap_write(tas->regmap, TAS675X_VPREDICT_OFFSET_REG,
+				     offset & 0xFF);
+		}
+		if (tas->isense_slot >= 0) {
+			offset = tas->isense_slot * slot_width;
+			offset += tas->bclk_offset;
+			regmap_update_bits(tas->regmap, TAS675X_SDOUT_OFFSET_MSB_REG,
+					   TAS675X_SDOUT_IS_OFF_MSB_MASK,
+					   FIELD_PREP(TAS675X_SDOUT_IS_OFF_MSB_MASK, offset >> 8));
+			regmap_write(tas->regmap, TAS675X_ISENSE_OFFSET_REG,
+				     offset & 0xFF);
+		}
+		break;
+	}
+
+	if (offset > 511)
+		dev_warn(tas->dev,
+			 "DAI %d slot offset %d exceeds 511 SCLK limit\n",
+			 dai_id, offset);
+}
+
+static int tas675x_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *params,
+			     struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(component);
+	unsigned int rate = params_rate(params);
+	u8 word_length;
+
+	/*
+	 * Single clock domain: SDIN and SDOUT share one SCLK/FSYNC pair,
+	 * so all active DAIs must use the same sample rate.
+	 */
+	if ((tas->active_playback_dais || tas->active_capture_dais) &&
+	    tas->rate && tas->rate != rate) {
+		dev_err(component->dev,
+			"Rate %u conflicts with active rate %u\n",
+			rate, tas->rate);
+		return -EINVAL;
+	}
+
+	switch (params_width(params)) {
+	case 16:
+		word_length = TAS675X_WL_16BIT;
+		break;
+	case 20:
+		word_length = TAS675X_WL_20BIT;
+		break;
+	case 24:
+		word_length = TAS675X_WL_24BIT;
+		break;
+	case 32:
+		word_length = TAS675X_WL_32BIT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		/*
+		 * RTLDG is not supported above 96kHz. Auto-disable to
+		 * prevent DSP overload and restore when rate drops back.
+		 */
+		if (rate > 96000) {
+			unsigned int val;
+
+			regmap_read(component->regmap, TAS675X_RTLDG_EN_REG,
+				    &val);
+			if (val & TAS675X_RTLDG_CH_EN_MASK) {
+				tas->saved_rtldg_en = val;
+				dev_dbg(component->dev,
+					"Sample rate %dHz > 96kHz: Auto-disabling RTLDG\n",
+					rate);
+				regmap_update_bits(component->regmap,
+						   TAS675X_RTLDG_EN_REG,
+						   TAS675X_RTLDG_CH_EN_MASK,
+						   0x00);
+			}
+		} else if (tas->saved_rtldg_en) {
+			unsigned int cur;
+
+			/*
+			 * Respect overrides and only restore if RTLDG is still auto-disabled
+			 */
+			regmap_read(component->regmap, TAS675X_RTLDG_EN_REG,
+				    &cur);
+			if (!(cur & TAS675X_RTLDG_CH_EN_MASK)) {
+				dev_dbg(component->dev,
+					"Restoring RTLDG config after high-rate stream\n");
+				regmap_update_bits(component->regmap,
+						   TAS675X_RTLDG_EN_REG,
+						   TAS675X_RTLDG_CH_EN_MASK,
+						   TAS675X_RTLDG_CH_EN_MASK &
+							tas->saved_rtldg_en);
+			}
+			tas->saved_rtldg_en = 0;
+		}
+
+		/* Set SDIN word length (audio path + low-latency path) */
+		regmap_update_bits(component->regmap, TAS675X_SDIN_CTRL_REG,
+				   TAS675X_SDIN_WL_MASK,
+				   FIELD_PREP(TAS675X_SDIN_AUDIO_WL_MASK, word_length) |
+				   FIELD_PREP(TAS675X_SDIN_LL_WL_MASK, word_length));
+	} else {
+		/* Set SDOUT word length (VPREDICT + ISENSE) for capture */
+		regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
+				   TAS675X_SDOUT_WL_MASK,
+				   FIELD_PREP(TAS675X_SDOUT_VP_WL_MASK, word_length) |
+				   FIELD_PREP(TAS675X_SDOUT_IS_WL_MASK, word_length));
+	}
+
+	tas675x_program_slot_offsets(tas, dai->id,
+				     tas->slot_width ?: params_width(params));
+
+	tas->rate = rate;
+
+	return 0;
+}
+
+static int tas675x_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_component *component = dai->component;
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(component);
+	bool tdm_mode = false, i2s_mode = false;
+
+	/* Enforce Clocking Direction (Codec is strictly a consumer) */
+	switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+	case SND_SOC_DAIFMT_BC_FC:
+		break;
+	default:
+		dev_err(component->dev, "Unsupported clock provider format\n");
+		return -EINVAL;
+	}
+
+	/* SCLK polarity: NB_NF or IB_NF only (no FSYNC inversion support) */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		regmap_update_bits(component->regmap, TAS675X_SCLK_INV_CTRL_REG,
+				   TAS675X_SCLK_INV_MASK, 0x00);
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		regmap_update_bits(component->regmap, TAS675X_SCLK_INV_CTRL_REG,
+				   TAS675X_SCLK_INV_MASK, TAS675X_SCLK_INV_MASK);
+		break;
+	default:
+		dev_err(component->dev, "Unsupported clock inversion\n");
+		return -EINVAL;
+	}
+
+	/* Configure Audio Format and TDM Enable */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		i2s_mode = true;
+		tas->bclk_offset = 0;
+		regmap_update_bits(component->regmap, TAS675X_AUDIO_IF_CTRL_REG,
+				   TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_MASK |
+				   TAS675X_FS_PULSE_MASK,
+				   TAS675X_SAP_FMT_I2S);
+		regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
+				   TAS675X_SDOUT_SELECT_MASK,
+				   TAS675X_SDOUT_SELECT_NON_TDM);
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		tas->bclk_offset = 0;
+		regmap_update_bits(component->regmap, TAS675X_AUDIO_IF_CTRL_REG,
+				   TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_MASK |
+				   TAS675X_FS_PULSE_MASK,
+				   TAS675X_SAP_FMT_RIGHT_J);
+		regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
+				   TAS675X_SDOUT_SELECT_MASK,
+				   TAS675X_SDOUT_SELECT_NON_TDM);
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		tas->bclk_offset = 0;
+		regmap_update_bits(component->regmap, TAS675X_AUDIO_IF_CTRL_REG,
+				   TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_MASK |
+				   TAS675X_FS_PULSE_MASK,
+				   TAS675X_SAP_FMT_LEFT_J);
+		regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
+				   TAS675X_SDOUT_SELECT_MASK,
+				   TAS675X_SDOUT_SELECT_NON_TDM);
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		tdm_mode = true;
+		tas->bclk_offset = 1;
+		regmap_update_bits(component->regmap, TAS675X_AUDIO_IF_CTRL_REG,
+				   TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_MASK |
+				   TAS675X_FS_PULSE_MASK,
+				   TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_TDM |
+				   TAS675X_FS_PULSE_SHORT);
+		regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
+				   TAS675X_SDOUT_SELECT_MASK,
+				   TAS675X_SDOUT_SELECT_TDM_SDOUT1);
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		tdm_mode = true;
+		tas->bclk_offset = 0;
+		regmap_update_bits(component->regmap, TAS675X_AUDIO_IF_CTRL_REG,
+				   TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_MASK |
+				   TAS675X_FS_PULSE_MASK,
+				   TAS675X_TDM_EN_BIT | TAS675X_SAP_FMT_TDM |
+				   TAS675X_FS_PULSE_SHORT);
+		regmap_update_bits(component->regmap, TAS675X_SDOUT_CTRL_REG,
+				   TAS675X_SDOUT_SELECT_MASK,
+				   TAS675X_SDOUT_SELECT_TDM_SDOUT1);
+		break;
+	default:
+		dev_err(component->dev, "Unsupported DAI format\n");
+		return -EINVAL;
+	}
+
+	/* Setup Vpredict and Isense outputs */
+	if (dai->id == 2) {
+		unsigned int sdout_en = 0;
+
+		if (tdm_mode) {
+			/* TDM: Vpredict and Isense may coexist on separate slots */
+			if (tas->vpredict_slot >= 0)
+				sdout_en |= TAS675X_SDOUT_EN_VPREDICT;
+			if (tas->isense_slot >= 0)
+				sdout_en |= TAS675X_SDOUT_EN_ISENSE;
+			regmap_update_bits(component->regmap,
+					   TAS675X_SDOUT_EN_REG,
+					   TAS675X_SDOUT_EN_VPREDICT |
+					   TAS675X_SDOUT_EN_ISENSE,
+					   sdout_en);
+			if (tas->vpredict_slot >= 0 && tas->isense_slot >= 0 &&
+			    abs(tas->vpredict_slot - tas->isense_slot) < 4)
+				dev_warn(component->dev,
+					 "ti,vpredict-slot-no and ti,isense-slot-no overlaps (each occupies 4 consecutive slots)\n");
+		} else if (i2s_mode) {
+			/* I2S: only one source at a time; Vpredict takes priority */
+			if (tas->vpredict_slot >= 0)
+				sdout_en = TAS675X_SDOUT_NON_TDM_SEL_VPREDICT |
+					   TAS675X_SDOUT_EN_NON_TDM_ALL;
+			else if (tas->isense_slot >= 0)
+				sdout_en = TAS675X_SDOUT_NON_TDM_SEL_ISENSE |
+					   TAS675X_SDOUT_EN_NON_TDM_ALL;
+			regmap_update_bits(component->regmap,
+					   TAS675X_SDOUT_EN_REG,
+					   TAS675X_SDOUT_NON_TDM_SEL_MASK |
+					   TAS675X_SDOUT_EN_NON_TDM_ALL,
+					   sdout_en);
+			if (sdout_en &&
+			    tas->gpio1_func != TAS675X_GPIO_SEL_SDOUT2 &&
+			    tas->gpio2_func != TAS675X_GPIO_SEL_SDOUT2)
+				dev_warn(component->dev,
+					 "sdout enabled in I2S mode but no GPIO configured as SDOUT2; Ch3/Ch4 will be absent\n");
+		}
+	}
+
+	return 0;
+}
+
+static int tas675x_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+				unsigned int rx_mask, int slots, int slot_width)
+{
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(dai->component);
+
+	if (slots == 0) {
+		tas->slot_width = 0;
+		tas->tx_mask = 0;
+		return 0;
+	}
+
+	/* No rx_mask as hardware does not support channel muxing for capture */
+	tas->slot_width = slot_width;
+	tas->tx_mask = tx_mask;
+	return 0;
+}
+
+static int tas675x_mute_stream(struct snd_soc_dai *dai, int mute, int direction)
+{
+	struct snd_soc_component *component = dai->component;
+	struct tas675x_priv *tas = snd_soc_component_get_drvdata(component);
+	unsigned int discard;
+	int ret;
+
+	if (direction == SNDRV_PCM_STREAM_CAPTURE) {
+		if (mute)
+			clear_bit(dai->id, &tas->active_capture_dais);
+		else
+			set_bit(dai->id, &tas->active_capture_dais);
+		return 0;
+	}
+
+	/*
+	 * Track which playback DAIs are active.
+	 * The TAS675x has two playback DAIs (main audio and LLP).
+	 * Only transition to SLEEP when ALL are muted.
+	 */
+	if (mute)
+		clear_bit(dai->id, &tas->active_playback_dais);
+	else
+		set_bit(dai->id, &tas->active_playback_dais);
+
+	/* Last playback stream */
+	if (mute && !tas->active_playback_dais) {
+		ret = tas675x_set_state_all(tas, TAS675X_STATE_SLEEP_BOTH);
+		regmap_read(tas->regmap, TAS675X_CLK_FAULT_LATCHED_REG, &discard);
+		return ret;
+	}
+
+	return tas675x_set_state_all(tas,
+				     tas->active_playback_dais ?
+					TAS675X_STATE_PLAY_BOTH :
+					TAS675X_STATE_SLEEP_BOTH);
+}
+
+static const struct snd_soc_dai_ops tas675x_dai_ops = {
+	.hw_params	= tas675x_hw_params,
+	.set_fmt	= tas675x_set_fmt,
+	.set_tdm_slot	= tas675x_set_tdm_slot,
+	.mute_stream	= tas675x_mute_stream,
+};
+
+static struct snd_soc_dai_driver tas675x_dais[] = {
+	{
+		.name = "tas675x-audio",
+		.id = 0,
+		.playback = {
+			.stream_name = "Playback",
+			.channels_min = 2,
+			.channels_max = 4,
+			.rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
+				 SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_LE |
+				   SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
+		},
+		.ops = &tas675x_dai_ops,
+	},
+	/* Only available when Low Latency Path (LLP) is enabled */
+	{
+		.name = "tas675x-anc",
+		.id = 1,
+		.playback = {
+			.stream_name = "ANC Playback",
+			.channels_min = 2,
+			.channels_max = 4,
+			.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_LE |
+				   SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
+		},
+		.ops = &tas675x_dai_ops,
+	},
+	{
+		.name = "tas675x-feedback",
+		.id = 2,
+		.capture = {
+			.stream_name = "Feedback Capture",
+			.channels_min = 2,
+			.channels_max = 8,
+			.rates = SNDRV_PCM_RATE_48000,
+			.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_LE |
+				   SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
+		},
+		.ops = &tas675x_dai_ops,
+	}
+};
+
+/*
+ * Enable regulators and release hardware reset GPIOs.
+ * The device is not I2C-accessible until this returns.
+ */
+static int tas675x_hw_enable(struct tas675x_priv *tas)
+{
+	int ret;
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(tas->supplies), tas->supplies);
+	if (ret) {
+		dev_err(tas->dev, "Failed to enable regulators: %d\n", ret);
+		return ret;
+	}
+
+	if (!IS_ERR(tas->vbat)) {
+		ret = regulator_enable(tas->vbat);
+		if (ret) {
+			dev_err(tas->dev, "Failed to enable vbat: %d\n", ret);
+			regulator_bulk_disable(ARRAY_SIZE(tas->supplies), tas->supplies);
+			return ret;
+		}
+	}
+
+	if (tas->pd_gpio && tas->stby_gpio) {
+		/*
+		 * Independent Pin Control
+		 * Deassert PD first to boot digital, then STBY for analog.
+		 */
+		/* Min 4ms digital boot wait */
+		gpiod_set_value_cansleep(tas->pd_gpio, 0);
+		usleep_range(4000, 5000);
+
+		/* ~2ms analog stabilization */
+		gpiod_set_value_cansleep(tas->stby_gpio, 0);
+		usleep_range(2000, 3000);
+	} else if (tas->pd_gpio) {
+		/*
+		 * Simultaneous Pin Release
+		 * STBY tied to PD or hardwired HIGH.
+		 */
+		/* 6ms wait for simultaneous release transition */
+		gpiod_set_value_cansleep(tas->pd_gpio, 0);
+		usleep_range(6000, 7000);
+	} else {
+		/*
+		 * PD hardwired, device in DEEP_SLEEP.
+		 * Digital core already booted, I2C active. Deassert STBY
+		 * to bring up the analog output stage.
+		 */
+		/* ~2ms analog stabilization */
+		gpiod_set_value_cansleep(tas->stby_gpio, 0);
+		usleep_range(2000, 3000);
+	}
+
+	return 0;
+}
+
+static void tas675x_hw_disable(struct tas675x_priv *tas)
+{
+	if (tas->stby_gpio)
+		gpiod_set_value_cansleep(tas->stby_gpio, 1);
+
+	if (tas->pd_gpio)
+		gpiod_set_value_cansleep(tas->pd_gpio, 1);
+
+	/*
+	 * Hold PD/STBY asserted for at least 10ms
+	 * before removing PVDD, VBAT or DVDD.
+	 */
+	usleep_range(10000, 11000);
+
+	if (!IS_ERR(tas->vbat))
+		regulator_disable(tas->vbat);
+
+	regulator_bulk_disable(ARRAY_SIZE(tas->supplies), tas->supplies);
+}
+
+/*
+ * Write device start-up defaults.
+ * Must be called after tas675x_hw_enable() and after regcache is enabled.
+ */
+static int tas675x_init_device(struct tas675x_priv *tas)
+{
+	struct regmap *regmap = tas->regmap;
+	unsigned int val;
+	int ret, i;
+
+	/* Clear POR fault flag to prevent IRQ storm */
+	regmap_read(regmap, TAS675X_POWER_FAULT_LATCHED_REG, &val);
+
+	/* Bypass DC Load Diagnostics for fast boot */
+	if (tas->fast_boot)
+		regmap_update_bits(regmap, TAS675X_DC_LDG_CTRL_REG,
+				   TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT,
+				   TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT);
+
+	tas675x_select_book(regmap, TAS675X_BOOK_DEFAULT);
+
+	/* Enter setup mode */
+	ret = regmap_write(regmap, TAS675X_SETUP_REG1, TAS675X_SETUP_ENTER_VAL1);
+	if (ret)
+		goto err;
+	ret = regmap_write(regmap, TAS675X_SETUP_REG2, TAS675X_SETUP_ENTER_VAL2);
+	if (ret)
+		goto err;
+
+	/* Set all channels to Sleep (required before Page 1 config) */
+	tas675x_set_state_all(tas, TAS675X_STATE_SLEEP_BOTH);
+
+	/* Set DAC clock per TRM startup script */
+	regmap_write(regmap, TAS675X_DAC_CLK_REG, 0x00);
+
+	/*
+	 * Switch to Page 1 for safety-critical OC/CBC configuration,
+	 * while bypassing regcache. (Page 1 not accessible post setup)
+	 */
+	regcache_cache_bypass(regmap, true);
+	ret = regmap_multi_reg_write(regmap, tas675x_page1_init,
+				     ARRAY_SIZE(tas675x_page1_init));
+	regcache_cache_bypass(regmap, false);
+	if (ret)
+		goto err_setup;
+
+	/* Resync regmap's cached page selector */
+	regmap_write(regmap, TAS675X_PAGE_CTRL_REG, 0x00);
+
+	/* Exit setup mode */
+	regmap_write(regmap, TAS675X_SETUP_REG1, TAS675X_SETUP_EXIT_VAL);
+	regmap_write(regmap, TAS675X_SETUP_REG2, TAS675X_SETUP_EXIT_VAL);
+
+	/* Write DSP parameters if cached */
+	for (i = 0; i < ARRAY_SIZE(tas->dsp_params); i++) {
+		if (tas->dsp_params[i].val)
+			tas675x_dsp_mem_write(tas,
+					      tas->dsp_params[i].page,
+					      tas->dsp_params[i].reg,
+					      tas->dsp_params[i].val);
+	}
+
+	/*
+	 * Configure fault and warning event routing:
+	 *
+	 * ROUTING_1: CP fault/UVLO latch, OUTM soft short latch
+	 * ROUTING_2: CBC latch, OTSD latch, OTSD, power fault
+	 * ROUTING_3: CBC latch, OTSD latch, power latch, DC LDG,
+	 *            OTSD, power warnings
+	 * ROUTING_4: OC latch, DC latch, protection shutdown
+	 *            OTW latch, OTW, clip latch
+	 * ROUTING_5: clock latch+non-latch, RTLDG latch
+	 *            CBC warning, clip warning
+	 */
+	regmap_write(regmap, TAS675X_REPORT_ROUTING_1_REG, 0x70);
+	regmap_write(regmap, TAS675X_REPORT_ROUTING_2_REG, 0xA3);
+	regmap_write(regmap, TAS675X_REPORT_ROUTING_3_REG, 0xBB);
+	regmap_write(regmap, TAS675X_REPORT_ROUTING_4_REG, 0x7E);
+	regmap_write(regmap, TAS675X_REPORT_ROUTING_5_REG, 0xF3);
+
+	/* Configure GPIO pins if specified in DT */
+	if (tas->gpio1_func >= 0 || tas->gpio2_func >= 0) {
+		unsigned int gpio_ctrl = TAS675X_GPIO_CTRL_RSTVAL;
+
+		tas675x_config_gpio_pin(regmap, tas->gpio1_func,
+					TAS675X_GPIO1_OUTPUT_SEL_REG,
+					0, &gpio_ctrl);
+		tas675x_config_gpio_pin(regmap, tas->gpio2_func,
+					TAS675X_GPIO2_OUTPUT_SEL_REG,
+					1, &gpio_ctrl);
+		regmap_write(regmap, TAS675X_GPIO_CTRL_REG, gpio_ctrl);
+	}
+
+	/* Clear fast boot bits */
+	if (tas->fast_boot)
+		regmap_update_bits(regmap, TAS675X_DC_LDG_CTRL_REG,
+				   TAS675X_LDG_ABORT_BIT | TAS675X_LDG_BYPASS_BIT,
+				   0);
+
+	/* Clear any stale faults from the boot sequence */
+	regmap_read(regmap, TAS675X_POWER_FAULT_STATUS_1_REG, &val);
+	regmap_read(regmap, TAS675X_POWER_FAULT_LATCHED_REG, &val);
+	regmap_read(regmap, TAS675X_CLK_FAULT_LATCHED_REG, &val);
+	regmap_write(regmap, TAS675X_RESET_REG, TAS675X_FAULT_CLEAR);
+
+	return 0;
+
+err_setup:
+	regmap_write(regmap, TAS675X_SETUP_REG1, TAS675X_SETUP_EXIT_VAL);
+	regmap_write(regmap, TAS675X_SETUP_REG2, TAS675X_SETUP_EXIT_VAL);
+err:
+	dev_err(tas->dev, "Init device failed: %d\n", ret);
+	return ret;
+}
+
+static void tas675x_power_off(struct tas675x_priv *tas)
+{
+	regcache_cache_only(tas->regmap, true);
+	regcache_mark_dirty(tas->regmap);
+	tas675x_hw_disable(tas);
+}
+
+static int tas675x_power_on(struct tas675x_priv *tas)
+{
+	int ret;
+
+	ret = tas675x_hw_enable(tas);
+	if (ret)
+		return ret;
+
+	regcache_cache_only(tas->regmap, false);
+	regcache_mark_dirty(tas->regmap);
+
+	ret = tas675x_init_device(tas);
+	if (ret)
+		goto err_disable;
+
+	ret = regcache_sync(tas->regmap);
+	if (ret) {
+		dev_err(tas->dev, "Failed to sync regcache: %d\n", ret);
+		goto err_disable;
+	}
+
+	/* Reset fault tracking */
+	memset(tas->last_status, 0, sizeof(tas->last_status));
+
+	return 0;
+
+err_disable:
+	tas675x_power_off(tas);
+	return ret;
+}
+
+static int tas675x_runtime_suspend(struct device *dev)
+{
+	struct tas675x_priv *tas = dev_get_drvdata(dev);
+
+	disable_delayed_work_sync(&tas->fault_check_work);
+	tas675x_set_state_all(tas, TAS675X_STATE_SLEEP_BOTH);
+
+	return 0;
+}
+
+static int tas675x_runtime_resume(struct device *dev)
+{
+	struct tas675x_priv *tas = dev_get_drvdata(dev);
+
+	tas675x_set_state_all(tas, TAS675X_STATE_SLEEP_BOTH);
+
+	if (!to_i2c_client(dev)->irq) {
+		enable_delayed_work(&tas->fault_check_work);
+		schedule_delayed_work(&tas->fault_check_work,
+				      msecs_to_jiffies(TAS675X_FAULT_CHECK_INTERVAL_MS));
+	}
+
+	return 0;
+}
+
+static int tas675x_system_suspend(struct device *dev)
+{
+	struct tas675x_priv *tas = dev_get_drvdata(dev);
+	int ret;
+
+	ret = tas675x_runtime_suspend(dev);
+	if (ret)
+		return ret;
+
+	if (to_i2c_client(dev)->irq)
+		disable_irq(to_i2c_client(dev)->irq);
+
+	tas675x_power_off(tas);
+	return 0;
+}
+
+static int tas675x_system_resume(struct device *dev)
+{
+	struct tas675x_priv *tas = dev_get_drvdata(dev);
+	int ret;
+
+	ret = tas675x_power_on(tas);
+	if (ret)
+		return ret;
+
+	if (to_i2c_client(dev)->irq)
+		enable_irq(to_i2c_client(dev)->irq);
+
+	return tas675x_runtime_resume(dev);
+}
+
+static const struct snd_soc_component_driver soc_codec_dev_tas675x = {
+	.controls		= tas675x_snd_controls,
+	.num_controls		= ARRAY_SIZE(tas675x_snd_controls),
+	.dapm_widgets		= tas675x_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(tas675x_dapm_widgets),
+	.dapm_routes		= tas675x_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(tas675x_dapm_routes),
+	.endianness		= 1,
+};
+
+/* Fault register flags */
+#define TAS675X_FAULT_CRITICAL	BIT(0)	/* causes FAULT state, FAULT_CLEAR required */
+#define TAS675X_FAULT_TRACK	BIT(1)	/* track last value, only log on change */
+#define TAS675X_FAULT_ACTIVE	BIT(2)	/* skip when no stream is active */
+
+struct tas675x_fault_reg {
+	unsigned int reg;
+	unsigned int flags;
+	const char *name;
+};
+
+static const struct tas675x_fault_reg tas675x_fault_table[] = {
+	/* Critical */
+	{ TAS675X_OTSD_LATCHED_REG,              TAS675X_FAULT_CRITICAL | TAS675X_FAULT_TRACK,
+	  "Overtemperature Shutdown" },
+	{ TAS675X_OC_DC_FAULT_LATCHED_REG,       TAS675X_FAULT_CRITICAL | TAS675X_FAULT_TRACK,
+	  "Overcurrent / DC Fault" },
+	{ TAS675X_RTLDG_OL_SL_FAULT_LATCHED_REG, TAS675X_FAULT_CRITICAL | TAS675X_FAULT_TRACK,
+	  "Real-Time Load Diagnostic Fault" },
+	{ TAS675X_CBC_FAULT_WARN_LATCHED_REG,    TAS675X_FAULT_CRITICAL | TAS675X_FAULT_TRACK,
+	  "CBC Fault/Warning" },
+	/* Warning */
+	{ TAS675X_POWER_FAULT_STATUS_1_REG,      TAS675X_FAULT_TRACK,
+	  "CP / OUTM Fault" },
+	{ TAS675X_POWER_FAULT_LATCHED_REG,       TAS675X_FAULT_TRACK,
+	  "Power Fault" },
+	{ TAS675X_CLK_FAULT_LATCHED_REG,         TAS675X_FAULT_TRACK | TAS675X_FAULT_ACTIVE,
+	  "Clock Fault" },
+	{ TAS675X_OTW_LATCHED_REG,               TAS675X_FAULT_TRACK,
+	  "Overtemperature Warning" },
+	{ TAS675X_CLIP_WARN_LATCHED_REG,         TAS675X_FAULT_ACTIVE,
+	  "Clip Warning" },
+};
+
+static_assert(ARRAY_SIZE(tas675x_fault_table) == TAS675X_FAULT_REGS_NUM);
+
+/*
+ * Read and log all latched fault registers.
+ * Shared by both the polled fault_check_work and IRQ handler paths
+ * (which are mutually exclusive, only one is active per device).
+ * Returns true if any fault register needs to be cleared.
+ *
+ * For deciphering fault messages, see "Fault Monitoring" in
+ * Documentation/sound/codecs/tas675x.rst
+ */
+static bool tas675x_check_faults(struct tas675x_priv *tas)
+{
+	struct device *dev = tas->dev;
+	bool needs_clear = false;
+	unsigned int reg;
+	int i, ret;
+
+	for (i = 0; i < ARRAY_SIZE(tas675x_fault_table); i++) {
+		const struct tas675x_fault_reg *f = &tas675x_fault_table[i];
+
+		ret = regmap_read(tas->regmap, f->reg, &reg);
+		if (ret) {
+			if (f->flags & TAS675X_FAULT_CRITICAL) {
+				dev_err(dev, "failed to read %s: %d\n", f->name, ret);
+				return needs_clear;
+			}
+			continue;
+		}
+
+		if (reg)
+			needs_clear = true;
+
+		/* Skip logging stream-dependent events when no stream is active */
+		if ((f->flags & TAS675X_FAULT_ACTIVE) &&
+		    !READ_ONCE(tas->active_playback_dais) &&
+		    !READ_ONCE(tas->active_capture_dais))
+			continue;
+
+		/* Log on change or on every non-zero read */
+		if (reg && (!(f->flags & TAS675X_FAULT_TRACK) ||
+			    reg != tas->last_status[i])) {
+			if (f->flags & TAS675X_FAULT_CRITICAL)
+				dev_crit(dev, "%s Latched: 0x%02x\n", f->name, reg);
+			else
+				dev_warn(dev, "%s Latched: 0x%02x\n", f->name, reg);
+		}
+
+		if (f->flags & TAS675X_FAULT_TRACK)
+			tas->last_status[i] = reg;
+	}
+
+	return needs_clear;
+}
+
+static void tas675x_fault_check_work(struct work_struct *work)
+{
+	struct tas675x_priv *tas = container_of(work, struct tas675x_priv,
+						fault_check_work.work);
+
+	if (tas675x_check_faults(tas))
+		regmap_write(tas->regmap, TAS675X_RESET_REG, TAS675X_FAULT_CLEAR);
+
+	schedule_delayed_work(&tas->fault_check_work,
+			      msecs_to_jiffies(TAS675X_FAULT_CHECK_INTERVAL_MS));
+}
+
+static irqreturn_t tas675x_irq_handler(int irq, void *data)
+{
+	struct tas675x_priv *tas = data;
+	irqreturn_t ret = IRQ_NONE;
+
+	if (pm_runtime_resume_and_get(tas->dev) < 0)
+		return IRQ_NONE;
+
+	if (tas675x_check_faults(tas)) {
+		regmap_write(tas->regmap, TAS675X_RESET_REG, TAS675X_FAULT_CLEAR);
+		ret = IRQ_HANDLED;
+	}
+
+	pm_runtime_mark_last_busy(tas->dev);
+	pm_runtime_put_autosuspend(tas->dev);
+	return ret;
+}
+
+static const struct reg_default tas675x_reg_defaults[] = {
+	{ TAS675X_PAGE_CTRL_REG,           0x00 },
+	{ TAS675X_OUTPUT_CTRL_REG,         0x00 },
+	{ TAS675X_STATE_CTRL_CH1_CH2_REG,  TAS675X_STATE_SLEEP_BOTH },
+	{ TAS675X_STATE_CTRL_CH3_CH4_REG,  TAS675X_STATE_SLEEP_BOTH },
+	{ TAS675X_ISENSE_CTRL_REG,         0x0F },
+	{ TAS675X_DC_DETECT_CTRL_REG,      0x00 },
+	{ TAS675X_SCLK_INV_CTRL_REG,       0x00 },
+	{ TAS675X_AUDIO_IF_CTRL_REG,       0x00 },
+	{ TAS675X_SDIN_CTRL_REG,           0x0A },
+	{ TAS675X_SDOUT_CTRL_REG,          0x1A },
+	{ TAS675X_SDIN_OFFSET_MSB_REG,     0x00 },
+	{ TAS675X_SDIN_AUDIO_OFFSET_REG,   0x00 },
+	{ TAS675X_SDIN_LL_OFFSET_REG,      0x60 },
+	{ TAS675X_SDIN_CH_SWAP_REG,        0x00 },
+	{ TAS675X_SDOUT_OFFSET_MSB_REG,    0xCF },
+	{ TAS675X_VPREDICT_OFFSET_REG,     0xFF },
+	{ TAS675X_ISENSE_OFFSET_REG,       0x00 },
+	{ TAS675X_SDOUT_EN_REG,            0x00 },
+	{ TAS675X_LL_EN_REG,               0x00 },
+	{ TAS675X_RTLDG_EN_REG,            0x10 },
+	{ TAS675X_DC_BLOCK_BYP_REG,        0x00 },
+	{ TAS675X_DSP_CTRL_REG,            0x00 },
+	{ TAS675X_PAGE_AUTO_INC_REG,       0x00 },
+	{ TAS675X_DIG_VOL_CH1_REG,         0x30 },
+	{ TAS675X_DIG_VOL_CH2_REG,         0x30 },
+	{ TAS675X_DIG_VOL_CH3_REG,         0x30 },
+	{ TAS675X_DIG_VOL_CH4_REG,         0x30 },
+	{ TAS675X_DIG_VOL_RAMP_CTRL_REG,   0x77 },
+	{ TAS675X_DIG_VOL_COMBINE_CTRL_REG, 0x00 },
+	{ TAS675X_AUTO_MUTE_EN_REG,        0x00 },
+	{ TAS675X_AUTO_MUTE_TIMING_CH1_CH2_REG, 0x00 },
+	{ TAS675X_AUTO_MUTE_TIMING_CH3_CH4_REG, 0x00 },
+	{ TAS675X_ANALOG_GAIN_CH1_CH2_REG, 0x00 },
+	{ TAS675X_ANALOG_GAIN_CH3_CH4_REG, 0x00 },
+	{ TAS675X_ANALOG_GAIN_RAMP_CTRL_REG, 0x00 },
+	{ TAS675X_PULSE_INJECTION_EN_REG,  0x03 },
+	{ TAS675X_CBC_CTRL_REG,            0x07 },
+	{ TAS675X_CURRENT_LIMIT_CTRL_REG,  0x00 },
+	{ TAS675X_ISENSE_CAL_REG,          0x00 },
+	{ TAS675X_PWM_PHASE_CTRL_REG,      0x00 },
+	{ TAS675X_SS_CTRL_REG,             0x00 },
+	{ TAS675X_SS_RANGE_CTRL_REG,       0x00 },
+	{ TAS675X_SS_DWELL_CTRL_REG,       0x00 },
+	{ TAS675X_RAMP_PHASE_CTRL_GPO_REG, 0x00 },
+	{ TAS675X_PWM_PHASE_M_CTRL_CH1_REG, 0x00 },
+	{ TAS675X_PWM_PHASE_M_CTRL_CH2_REG, 0x00 },
+	{ TAS675X_PWM_PHASE_M_CTRL_CH3_REG, 0x00 },
+	{ TAS675X_PWM_PHASE_M_CTRL_CH4_REG, 0x00 },
+	{ TAS675X_DC_LDG_CTRL_REG,         0x00 },
+	{ TAS675X_DC_LDG_LO_CTRL_REG,      0x00 },
+	{ TAS675X_DC_LDG_TIME_CTRL_REG,    0x00 },
+	{ TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG, 0x11 },
+	{ TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG, 0x11 },
+	{ TAS675X_AC_LDG_CTRL_REG,         0x10 },
+	{ TAS675X_TWEETER_DETECT_CTRL_REG, 0x08 },
+	{ TAS675X_TWEETER_DETECT_THRESH_REG, 0x00 },
+	{ TAS675X_AC_LDG_FREQ_CTRL_REG,    0xC8 },
+	{ TAS675X_REPORT_ROUTING_1_REG,    0x00 },
+	{ TAS675X_OTSD_RECOVERY_EN_REG,    0x00 },
+	{ TAS675X_REPORT_ROUTING_2_REG,    0xA2 },
+	{ TAS675X_REPORT_ROUTING_3_REG,    0x00 },
+	{ TAS675X_REPORT_ROUTING_4_REG,    0x06 },
+	{ TAS675X_CLIP_DETECT_CTRL_REG,    0x00 },
+	{ TAS675X_REPORT_ROUTING_5_REG,    0x00 },
+	{ TAS675X_GPIO1_OUTPUT_SEL_REG,    0x00 },
+	{ TAS675X_GPIO2_OUTPUT_SEL_REG,    0x00 },
+	{ TAS675X_GPIO_CTRL_REG,           TAS675X_GPIO_CTRL_RSTVAL },
+	{ TAS675X_OTW_CTRL_CH1_CH2_REG,    0x11 },
+	{ TAS675X_OTW_CTRL_CH3_CH4_REG,    0x11 },
+};
+
+static bool tas675x_is_readable_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case TAS675X_RESET_REG:
+		return false;
+	default:
+		return true;
+	}
+}
+
+static bool tas675x_is_volatile_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case TAS675X_RESET_REG:
+	case TAS675X_BOOK_CTRL_REG:
+	case TAS675X_AUTO_MUTE_STATUS_REG:
+	case TAS675X_STATE_REPORT_CH1_CH2_REG:
+	case TAS675X_STATE_REPORT_CH3_CH4_REG:
+	case TAS675X_PVDD_SENSE_REG:
+	case TAS675X_TEMP_GLOBAL_REG:
+	case TAS675X_TEMP_CH1_CH2_REG:
+	case TAS675X_TEMP_CH3_CH4_REG:
+	case TAS675X_FS_MON_REG:
+	case TAS675X_SCLK_MON_REG:
+	case TAS675X_POWER_FAULT_STATUS_1_REG:
+	case TAS675X_POWER_FAULT_STATUS_2_REG:
+	case TAS675X_OT_FAULT_REG:
+	case TAS675X_OTW_STATUS_REG:
+	case TAS675X_CLIP_WARN_STATUS_REG:
+	case TAS675X_CBC_WARNING_STATUS_REG:
+	case TAS675X_POWER_FAULT_LATCHED_REG:
+	case TAS675X_OTSD_LATCHED_REG:
+	case TAS675X_OTW_LATCHED_REG:
+	case TAS675X_CLIP_WARN_LATCHED_REG:
+	case TAS675X_CLK_FAULT_LATCHED_REG:
+	case TAS675X_RTLDG_OL_SL_FAULT_LATCHED_REG:
+	case TAS675X_CBC_FAULT_WARN_LATCHED_REG:
+	case TAS675X_OC_DC_FAULT_LATCHED_REG:
+	case TAS675X_WARN_OT_MAX_FLAG_REG:
+	case TAS675X_DC_LDG_REPORT_CH1_CH2_REG ... TAS675X_TWEETER_REPORT_REG:
+	case TAS675X_CH1_RTLDG_IMP_MSB_REG ... TAS675X_CH4_DC_LDG_DCR_LSB_REG:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_range_cfg tas675x_ranges[] = {
+	{
+		.name = "Pages",
+		.range_min = 0,
+		.range_max = TAS675X_PAGE_SIZE * TAS675X_PAGE_SIZE - 1,
+		.selector_reg = TAS675X_PAGE_CTRL_REG,
+		.selector_mask = 0xff,
+		.selector_shift = 0,
+		.window_start = 0,
+		.window_len = TAS675X_PAGE_SIZE,
+	},
+};
+
+static void tas675x_regmap_lock(void *lock_arg)
+{
+	struct tas675x_priv *tas = lock_arg;
+
+	mutex_lock(&tas->io_lock);
+}
+
+static void tas675x_regmap_unlock(void *lock_arg)
+{
+	struct tas675x_priv *tas = lock_arg;
+
+	mutex_unlock(&tas->io_lock);
+}
+
+static const struct regmap_config tas675x_regmap_config = {
+	.reg_bits         = 8,
+	.val_bits         = 8,
+	.max_register     = TAS675X_PAGE_SIZE * TAS675X_PAGE_SIZE - 1,
+	.ranges           = tas675x_ranges,
+	.num_ranges       = ARRAY_SIZE(tas675x_ranges),
+	.cache_type       = REGCACHE_MAPLE,
+	.reg_defaults     = tas675x_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(tas675x_reg_defaults),
+	.readable_reg     = tas675x_is_readable_register,
+	.volatile_reg     = tas675x_is_volatile_register,
+};
+
+static int tas675x_i2c_probe(struct i2c_client *client)
+{
+	struct regmap_config cfg = tas675x_regmap_config;
+	struct tas675x_priv *tas;
+	u32 val;
+	int i, ret;
+
+	tas = devm_kzalloc(&client->dev, sizeof(*tas), GFP_KERNEL);
+	if (!tas)
+		return -ENOMEM;
+
+	tas->dev = &client->dev;
+	i2c_set_clientdata(client, tas);
+
+	mutex_init(&tas->io_lock);
+	cfg.lock     = tas675x_regmap_lock;
+	cfg.unlock   = tas675x_regmap_unlock;
+	cfg.lock_arg = tas;
+
+	memcpy(tas->dsp_params, tas675x_dsp_defaults, sizeof(tas->dsp_params));
+	INIT_DELAYED_WORK(&tas->fault_check_work, tas675x_fault_check_work);
+
+	tas->regmap = devm_regmap_init_i2c(client, &cfg);
+	if (IS_ERR(tas->regmap))
+		return PTR_ERR(tas->regmap);
+
+	/* Keep regmap cache-only until hardware is powered on */
+	regcache_cache_only(tas->regmap, true);
+
+	tas->dev_type = (enum tas675x_type)(unsigned long)device_get_match_data(tas->dev);
+	tas->fast_boot = device_property_read_bool(tas->dev, "ti,fast-boot");
+
+	tas->audio_slot = -1;
+	tas->llp_slot = -1;
+	tas->vpredict_slot = -1;
+	tas->isense_slot = -1;
+	if (!device_property_read_u32(tas->dev, "ti,audio-slot-no", &val))
+		tas->audio_slot = val;
+	if (!device_property_read_u32(tas->dev, "ti,llp-slot-no", &val))
+		tas->llp_slot = val;
+	if (!device_property_read_u32(tas->dev, "ti,vpredict-slot-no", &val))
+		tas->vpredict_slot = val;
+	if (!device_property_read_u32(tas->dev, "ti,isense-slot-no", &val))
+		tas->isense_slot = val;
+
+	tas->gpio1_func = tas675x_gpio_func_parse(tas->dev, "ti,gpio1-function");
+	tas->gpio2_func = tas675x_gpio_func_parse(tas->dev, "ti,gpio2-function");
+
+	for (i = 0; i < ARRAY_SIZE(tas675x_supply_names); i++)
+		tas->supplies[i].supply = tas675x_supply_names[i];
+
+	ret = devm_regulator_bulk_get(tas->dev, ARRAY_SIZE(tas->supplies), tas->supplies);
+	if (ret)
+		return dev_err_probe(tas->dev, ret, "Failed to request supplies\n");
+
+	tas->vbat = devm_regulator_get_optional(tas->dev, "vbat");
+	if (IS_ERR(tas->vbat) && PTR_ERR(tas->vbat) != -ENODEV)
+		return dev_err_probe(tas->dev, PTR_ERR(tas->vbat),
+				     "Failed to get vbat supply\n");
+
+	tas->pd_gpio = devm_gpiod_get_optional(tas->dev, "powerdown", GPIOD_OUT_HIGH);
+	if (IS_ERR(tas->pd_gpio))
+		return dev_err_probe(tas->dev, PTR_ERR(tas->pd_gpio), "Failed powerdown-gpios\n");
+
+	tas->stby_gpio = devm_gpiod_get_optional(tas->dev, "standby", GPIOD_OUT_HIGH);
+	if (IS_ERR(tas->stby_gpio))
+		return dev_err_probe(tas->dev, PTR_ERR(tas->stby_gpio), "Failed standby-gpios\n");
+
+	if (!tas->pd_gpio && !tas->stby_gpio)
+		return dev_err_probe(tas->dev, -EINVAL,
+				     "At least one of powerdown-gpios or standby-gpios is required\n");
+
+	ret = tas675x_power_on(tas);
+	if (ret)
+		return ret;
+
+	if (client->irq) {
+		ret = devm_request_threaded_irq(tas->dev, client->irq, NULL,
+						tas675x_irq_handler,
+						IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
+						"tas675x-fault", tas);
+		if (ret) {
+			tas675x_power_off(tas);
+			return dev_err_probe(tas->dev, ret, "Failed to request IRQ\n");
+		}
+	} else {
+		/* Schedule delayed work for fault checking at probe and runtime resume */
+		schedule_delayed_work(&tas->fault_check_work,
+				      msecs_to_jiffies(TAS675X_FAULT_CHECK_INTERVAL_MS));
+	}
+
+	/* Enable runtime PM with 2s autosuspend */
+	pm_runtime_set_autosuspend_delay(tas->dev, 2000);
+	pm_runtime_use_autosuspend(tas->dev);
+	pm_runtime_set_active(tas->dev);
+	pm_runtime_mark_last_busy(tas->dev);
+	pm_runtime_enable(tas->dev);
+
+	ret = devm_snd_soc_register_component(tas->dev, &soc_codec_dev_tas675x,
+					      tas675x_dais, ARRAY_SIZE(tas675x_dais));
+	if (ret)
+		goto err_pm_disable;
+
+	return 0;
+
+err_pm_disable:
+	pm_runtime_force_suspend(tas->dev);
+	pm_runtime_disable(tas->dev);
+	tas675x_power_off(tas);
+	return ret;
+}
+
+static void tas675x_i2c_remove(struct i2c_client *client)
+{
+	struct tas675x_priv *tas = dev_get_drvdata(&client->dev);
+
+	disable_delayed_work_sync(&tas->fault_check_work);
+	if (client->irq)
+		disable_irq(client->irq);
+
+	pm_runtime_force_suspend(&client->dev);
+	pm_runtime_disable(&client->dev);
+	tas675x_power_off(tas);
+}
+
+static const struct dev_pm_ops tas675x_pm_ops = {
+	SYSTEM_SLEEP_PM_OPS(tas675x_system_suspend, tas675x_system_resume)
+	RUNTIME_PM_OPS(tas675x_runtime_suspend, tas675x_runtime_resume, NULL)
+};
+
+static const struct of_device_id tas675x_of_match[] = {
+	{ .compatible = "ti,tas67524", .data = (void *)TAS67524 },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, tas675x_of_match);
+
+static const struct i2c_device_id tas675x_i2c_id[] = {
+	{ "tas67524",  TAS67524 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, tas675x_i2c_id);
+
+static struct i2c_driver tas675x_i2c_driver = {
+	.driver = {
+		.name = "tas675x",
+		.of_match_table = tas675x_of_match,
+		.pm = pm_ptr(&tas675x_pm_ops),
+	},
+	.probe = tas675x_i2c_probe,
+	.remove = tas675x_i2c_remove,
+	.id_table = tas675x_i2c_id,
+};
+
+module_i2c_driver(tas675x_i2c_driver);
+
+MODULE_AUTHOR("Sen Wang <sen@ti.com>");
+MODULE_DESCRIPTION("ASoC TAS675x Audio Amplifier Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/tas675x.h b/sound/soc/codecs/tas675x.h
new file mode 100644
index 000000000000..db29bb377336
--- /dev/null
+++ b/sound/soc/codecs/tas675x.h
@@ -0,0 +1,367 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ALSA SoC Texas Instruments TAS675x Quad-Channel Audio Amplifier
+ *
+ * Copyright (C) 2026 Texas Instruments Incorporated - https://www.ti.com/
+ *	Author: Sen Wang <sen@ti.com>
+ */
+
+#ifndef __TAS675X_H__
+#define __TAS675X_H__
+
+/*
+ * Book 0, Page 0 — Register Addresses
+ */
+
+#define TAS675X_PAGE_SIZE                    256
+#define TAS675X_PAGE_REG(page, reg)  ((page) * TAS675X_PAGE_SIZE + (reg))
+
+/* Page Control & Basic Config */
+#define TAS675X_PAGE_CTRL_REG                0x00
+#define TAS675X_RESET_REG                    0x01
+#define TAS675X_OUTPUT_CTRL_REG              0x02
+#define TAS675X_STATE_CTRL_CH1_CH2_REG       0x03
+#define TAS675X_STATE_CTRL_CH3_CH4_REG       0x04
+#define TAS675X_ISENSE_CTRL_REG              0x05
+#define TAS675X_DC_DETECT_CTRL_REG           0x06
+
+/* Serial Audio Port */
+#define TAS675X_SCLK_INV_CTRL_REG            0x20
+#define TAS675X_AUDIO_IF_CTRL_REG            0x21
+#define TAS675X_SDIN_CTRL_REG                0x23
+#define TAS675X_SDOUT_CTRL_REG               0x25
+#define TAS675X_SDIN_OFFSET_MSB_REG          0x27
+#define TAS675X_SDIN_AUDIO_OFFSET_REG        0x28
+#define TAS675X_SDIN_LL_OFFSET_REG           0x29
+#define TAS675X_SDIN_CH_SWAP_REG             0x2A
+#define TAS675X_SDOUT_OFFSET_MSB_REG         0x2C
+#define TAS675X_VPREDICT_OFFSET_REG          0x2D
+#define TAS675X_ISENSE_OFFSET_REG            0x2E
+#define TAS675X_SDOUT_EN_REG                 0x31
+#define TAS675X_LL_EN_REG                    0x32
+
+/* DSP & Core Audio Control */
+#define TAS675X_RTLDG_EN_REG                 0x37
+#define TAS675X_DC_BLOCK_BYP_REG             0x39
+#define TAS675X_DSP_CTRL_REG                 0x3A
+#define TAS675X_PAGE_AUTO_INC_REG            0x3B
+
+/* Volume & Mute */
+#define TAS675X_DIG_VOL_CH1_REG              0x40
+#define TAS675X_DIG_VOL_CH2_REG              0x41
+#define TAS675X_DIG_VOL_CH3_REG              0x42
+#define TAS675X_DIG_VOL_CH4_REG              0x43
+#define TAS675X_DIG_VOL_RAMP_CTRL_REG        0x44
+#define TAS675X_DIG_VOL_COMBINE_CTRL_REG     0x46
+#define TAS675X_AUTO_MUTE_EN_REG             0x47
+#define TAS675X_AUTO_MUTE_TIMING_CH1_CH2_REG 0x48
+#define TAS675X_AUTO_MUTE_TIMING_CH3_CH4_REG 0x49
+
+/* Analog Gain & Power Stage */
+#define TAS675X_ANALOG_GAIN_CH1_CH2_REG      0x4A
+#define TAS675X_ANALOG_GAIN_CH3_CH4_REG      0x4B
+#define TAS675X_ANALOG_GAIN_RAMP_CTRL_REG    0x4E
+#define TAS675X_PULSE_INJECTION_EN_REG       0x52
+#define TAS675X_CBC_CTRL_REG                 0x54
+#define TAS675X_CURRENT_LIMIT_CTRL_REG       0x55
+#define TAS675X_DAC_CLK_REG                  0x5A
+#define TAS675X_ISENSE_CAL_REG               0x5B
+
+/* Spread Spectrum & PWM Phase */
+#define TAS675X_PWM_PHASE_CTRL_REG           0x60
+#define TAS675X_SS_CTRL_REG                  0x61
+#define TAS675X_SS_RANGE_CTRL_REG            0x62
+#define TAS675X_SS_DWELL_CTRL_REG            0x66
+#define TAS675X_RAMP_PHASE_CTRL_GPO_REG      0x68
+#define TAS675X_PWM_PHASE_M_CTRL_CH1_REG     0x69
+#define TAS675X_PWM_PHASE_M_CTRL_CH2_REG     0x6A
+#define TAS675X_PWM_PHASE_M_CTRL_CH3_REG     0x6B
+#define TAS675X_PWM_PHASE_M_CTRL_CH4_REG     0x6C
+
+/* Status & Reporting */
+#define TAS675X_AUTO_MUTE_STATUS_REG         0x71
+#define TAS675X_STATE_REPORT_CH1_CH2_REG     0x72
+#define TAS675X_STATE_REPORT_CH3_CH4_REG     0x73
+#define TAS675X_PVDD_SENSE_REG               0x74
+#define TAS675X_TEMP_GLOBAL_REG              0x75
+#define TAS675X_FS_MON_REG                   0x76
+#define TAS675X_SCLK_MON_REG                 0x77
+#define TAS675X_REPORT_ROUTING_1_REG         0x7C
+
+/* Memory Paging & Book Control */
+#define TAS675X_SETUP_REG1                   0x7D
+#define TAS675X_SETUP_REG2                   0x7E
+#define TAS675X_BOOK_CTRL_REG                0x7F
+
+/* Fault Status */
+#define TAS675X_POWER_FAULT_STATUS_1_REG     0x7D
+#define TAS675X_POWER_FAULT_STATUS_2_REG     0x80
+#define TAS675X_OT_FAULT_REG                 0x81
+#define TAS675X_OTW_STATUS_REG               0x82
+#define TAS675X_CLIP_WARN_STATUS_REG         0x83
+#define TAS675X_CBC_WARNING_STATUS_REG       0x85
+
+/* Latched Fault Registers */
+#define TAS675X_POWER_FAULT_LATCHED_REG      0x86
+#define TAS675X_OTSD_LATCHED_REG             0x87
+#define TAS675X_OTW_LATCHED_REG              0x88
+#define TAS675X_CLIP_WARN_LATCHED_REG        0x89
+#define TAS675X_CLK_FAULT_LATCHED_REG        0x8A
+#define TAS675X_RTLDG_OL_SL_FAULT_LATCHED_REG 0x8B
+#define TAS675X_CBC_FAULT_WARN_LATCHED_REG   0x8D
+#define TAS675X_OC_DC_FAULT_LATCHED_REG      0x8E
+#define TAS675X_OTSD_RECOVERY_EN_REG         0x8F
+
+/* Protection & Routing Controls */
+#define TAS675X_REPORT_ROUTING_2_REG         0x90
+#define TAS675X_REPORT_ROUTING_3_REG         0x91
+#define TAS675X_REPORT_ROUTING_4_REG         0x92
+#define TAS675X_CLIP_DETECT_CTRL_REG         0x93
+#define TAS675X_REPORT_ROUTING_5_REG         0x94
+
+/* GPIO Pin Configuration */
+#define TAS675X_GPIO1_OUTPUT_SEL_REG         0x95
+#define TAS675X_GPIO2_OUTPUT_SEL_REG         0x96
+#define TAS675X_GPIO_INPUT_SLEEP_HIZ_REG     0x9B
+#define TAS675X_GPIO_INPUT_PLAY_SLEEP_REG    0x9C
+#define TAS675X_GPIO_INPUT_MUTE_REG          0x9D
+#define TAS675X_GPIO_INPUT_SYNC_REG          0x9E
+#define TAS675X_GPIO_INPUT_SDIN2_REG         0x9F
+#define TAS675X_GPIO_CTRL_REG                0xA0
+#define TAS675X_GPIO_INVERT_REG              0xA1
+
+/* Load Diagnostics Config */
+#define TAS675X_DC_LDG_CTRL_REG              0xB0
+#define TAS675X_DC_LDG_LO_CTRL_REG           0xB1
+#define TAS675X_DC_LDG_TIME_CTRL_REG         0xB2
+#define TAS675X_DC_LDG_SL_CH1_CH2_CTRL_REG   0xB3
+#define TAS675X_DC_LDG_SL_CH3_CH4_CTRL_REG   0xB4
+#define TAS675X_AC_LDG_CTRL_REG              0xB5
+#define TAS675X_TWEETER_DETECT_CTRL_REG      0xB6
+#define TAS675X_TWEETER_DETECT_THRESH_REG    0xB7
+#define TAS675X_AC_LDG_FREQ_CTRL_REG         0xB8
+#define TAS675X_TEMP_CH1_CH2_REG             0xBB
+#define TAS675X_TEMP_CH3_CH4_REG             0xBC
+#define TAS675X_WARN_OT_MAX_FLAG_REG         0xBD
+
+/* DC Load Diagnostic Reports */
+#define TAS675X_DC_LDG_REPORT_CH1_CH2_REG    0xC0
+#define TAS675X_DC_LDG_REPORT_CH3_CH4_REG    0xC1
+#define TAS675X_DC_LDG_RESULT_REG            0xC2
+#define TAS675X_AC_LDG_REPORT_CH1_R_REG      0xC3
+#define TAS675X_AC_LDG_REPORT_CH1_I_REG      0xC4
+#define TAS675X_AC_LDG_REPORT_CH2_R_REG      0xC5
+#define TAS675X_AC_LDG_REPORT_CH2_I_REG      0xC6
+#define TAS675X_AC_LDG_REPORT_CH3_R_REG      0xC7
+#define TAS675X_AC_LDG_REPORT_CH3_I_REG      0xC8
+#define TAS675X_AC_LDG_REPORT_CH4_R_REG      0xC9
+#define TAS675X_AC_LDG_REPORT_CH4_I_REG      0xCA
+#define TAS675X_TWEETER_REPORT_REG           0xCB
+
+/* RTLDG Impedance */
+#define TAS675X_CH1_RTLDG_IMP_MSB_REG        0xD1
+#define TAS675X_CH1_RTLDG_IMP_LSB_REG        0xD2
+#define TAS675X_CH2_RTLDG_IMP_MSB_REG        0xD3
+#define TAS675X_CH2_RTLDG_IMP_LSB_REG        0xD4
+#define TAS675X_CH3_RTLDG_IMP_MSB_REG        0xD5
+#define TAS675X_CH3_RTLDG_IMP_LSB_REG        0xD6
+#define TAS675X_CH4_RTLDG_IMP_MSB_REG        0xD7
+#define TAS675X_CH4_RTLDG_IMP_LSB_REG        0xD8
+
+/* DC Load Diagnostic Resistance */
+#define TAS675X_DC_LDG_DCR_MSB_REG           0xD9
+#define TAS675X_CH1_DC_LDG_DCR_LSB_REG       0xDA
+#define TAS675X_CH2_DC_LDG_DCR_LSB_REG       0xDB
+#define TAS675X_CH3_DC_LDG_DCR_LSB_REG       0xDC
+#define TAS675X_CH4_DC_LDG_DCR_LSB_REG       0xDD
+
+/* Over-Temperature Warning */
+#define TAS675X_OTW_CTRL_CH1_CH2_REG         0xE2
+#define TAS675X_OTW_CTRL_CH3_CH4_REG         0xE3
+
+/* RESET_REG (all bits auto-clear) */
+#define TAS675X_DEVICE_RESET                 BIT(4)
+#define TAS675X_FAULT_CLEAR                  BIT(3)
+#define TAS675X_REGISTER_RESET               BIT(0)
+
+/* STATE_CTRL and STATE_REPORT — Channel state values */
+#define TAS675X_STATE_DEEPSLEEP              0x00
+#define TAS675X_STATE_LOAD_DIAG              0x01
+#define TAS675X_STATE_SLEEP                  0x02
+#define TAS675X_STATE_HIZ                    0x03
+#define TAS675X_STATE_PLAY                   0x04
+
+/* Additional STATE_REPORT values */
+#define TAS675X_STATE_FAULT                  0x05
+#define TAS675X_STATE_AUTOREC                0x06
+
+/* Combined values for both channel pairs in one register */
+#define TAS675X_STATE_DEEPSLEEP_BOTH \
+	(TAS675X_STATE_DEEPSLEEP | (TAS675X_STATE_DEEPSLEEP << 4))
+#define TAS675X_STATE_LOAD_DIAG_BOTH \
+	(TAS675X_STATE_LOAD_DIAG | (TAS675X_STATE_LOAD_DIAG << 4))
+#define TAS675X_STATE_SLEEP_BOTH \
+	(TAS675X_STATE_SLEEP | (TAS675X_STATE_SLEEP << 4))
+#define TAS675X_STATE_HIZ_BOTH \
+	(TAS675X_STATE_HIZ | (TAS675X_STATE_HIZ << 4))
+#define TAS675X_STATE_PLAY_BOTH \
+	(TAS675X_STATE_PLAY | (TAS675X_STATE_PLAY << 4))
+#define TAS675X_STATE_FAULT_BOTH \
+	(TAS675X_STATE_FAULT | (TAS675X_STATE_FAULT << 4))
+
+/* STATE_CTRL_CH1_CH2 / STATE_CTRL_CH3_CH4 — mute bits */
+#define TAS675X_CH1_MUTE_BIT                 BIT(7)
+#define TAS675X_CH2_MUTE_BIT                 BIT(3)
+#define TAS675X_CH_MUTE_BOTH                 (TAS675X_CH1_MUTE_BIT | TAS675X_CH2_MUTE_BIT)
+
+/* SCLK_INV_CTRL_REG */
+#define TAS675X_SCLK_INV_TX_BIT             BIT(5)
+#define TAS675X_SCLK_INV_RX_BIT             BIT(4)
+#define TAS675X_SCLK_INV_MASK               (TAS675X_SCLK_INV_TX_BIT | TAS675X_SCLK_INV_RX_BIT)
+
+/* AUDIO_IF_CTRL_REG */
+#define TAS675X_TDM_EN_BIT                   BIT(4)
+#define TAS675X_SAP_FMT_MASK                 GENMASK(3, 2)
+#define TAS675X_SAP_FMT_I2S                  (0x00 << 2)
+#define TAS675X_SAP_FMT_TDM                  (0x01 << 2)
+#define TAS675X_SAP_FMT_RIGHT_J              (0x02 << 2)
+#define TAS675X_SAP_FMT_LEFT_J               (0x03 << 2)
+#define TAS675X_FS_PULSE_MASK                GENMASK(1, 0)
+#define TAS675X_FS_PULSE_SHORT               0x01
+
+/* SDIN_CTRL_REG */
+#define TAS675X_SDIN_AUDIO_WL_MASK           GENMASK(3, 2)
+#define TAS675X_SDIN_LL_WL_MASK              GENMASK(1, 0)
+#define TAS675X_SDIN_WL_MASK                 (TAS675X_SDIN_AUDIO_WL_MASK | TAS675X_SDIN_LL_WL_MASK)
+
+/* SDOUT_CTRL_REG */
+#define TAS675X_SDOUT_SELECT_MASK            GENMASK(7, 4)
+#define TAS675X_SDOUT_SELECT_TDM_SDOUT1      0x00
+#define TAS675X_SDOUT_SELECT_NON_TDM         0x10
+#define TAS675X_SDOUT_VP_WL_MASK             GENMASK(3, 2)
+#define TAS675X_SDOUT_IS_WL_MASK             GENMASK(1, 0)
+#define TAS675X_SDOUT_WL_MASK                (TAS675X_SDOUT_VP_WL_MASK | TAS675X_SDOUT_IS_WL_MASK)
+
+/* SDOUT_EN_REG */
+#define TAS675X_SDOUT_NON_TDM_SEL_MASK      GENMASK(5, 4)
+#define TAS675X_SDOUT_NON_TDM_SEL_VPREDICT  (0x0 << 4)
+#define TAS675X_SDOUT_NON_TDM_SEL_ISENSE    (0x1 << 4)
+#define TAS675X_SDOUT_EN_VPREDICT           BIT(0)
+#define TAS675X_SDOUT_EN_ISENSE             BIT(1)
+#define TAS675X_SDOUT_EN_NON_TDM_ALL        GENMASK(1, 0)
+
+/* Word length values (shared by SDIN_CTRL and SDOUT_CTRL) */
+#define TAS675X_WL_16BIT                     0x00
+#define TAS675X_WL_20BIT                     0x01
+#define TAS675X_WL_24BIT                     0x02
+#define TAS675X_WL_32BIT                     0x03
+
+/* SDIN_OFFSET_MSB_REG */
+#define TAS675X_SDIN_AUDIO_OFF_MSB_MASK      GENMASK(7, 6)
+#define TAS675X_SDIN_LL_OFF_MSB_MASK         GENMASK(5, 4)
+
+/* SDOUT_OFFSET_MSB_REG */
+#define TAS675X_SDOUT_VP_OFF_MSB_MASK        GENMASK(7, 6)
+#define TAS675X_SDOUT_IS_OFF_MSB_MASK        GENMASK(5, 4)
+
+/* RTLDG_EN_REG */
+#define TAS675X_RTLDG_CLIP_MASK_BIT          BIT(4)
+#define TAS675X_RTLDG_CH_EN_MASK             GENMASK(3, 0)
+
+/* DC_LDG_CTRL_REG */
+#define TAS675X_LDG_ABORT_BIT                BIT(7)
+#define TAS675X_LDG_BUFFER_WAIT_MASK         GENMASK(6, 5)
+#define TAS675X_LDG_WAIT_BYPASS_BIT          BIT(2)
+#define TAS675X_SLOL_DISABLE_BIT             BIT(1)
+#define TAS675X_LDG_BYPASS_BIT               BIT(0)
+
+/* DC_LDG_TIME_CTRL_REG */
+#define TAS675X_LDG_RAMP_SLOL_MASK           GENMASK(7, 6)
+#define TAS675X_LDG_SETTLING_SLOL_MASK       GENMASK(5, 4)
+#define TAS675X_LDG_RAMP_S2PG_MASK           GENMASK(3, 2)
+#define TAS675X_LDG_SETTLING_S2PG_MASK       GENMASK(1, 0)
+
+/* AC_LDG_CTRL_REG */
+#define TAS675X_AC_DIAG_GAIN_BIT             BIT(4)
+#define TAS675X_AC_DIAG_START_MASK           GENMASK(3, 0)
+
+/* DC_LDG_RESULT_REG */
+#define TAS675X_DC_LDG_LO_RESULT_MASK        GENMASK(7, 4)
+#define TAS675X_DC_LDG_PASS_MASK             GENMASK(3, 0)
+
+/* Load Diagnostics Timing Constants */
+#define TAS675X_POLL_INTERVAL_US             10000
+#define TAS675X_STATE_TRANSITION_TIMEOUT_US  50000
+#define TAS675X_DC_LDG_TIMEOUT_US            300000
+#define TAS675X_AC_LDG_TIMEOUT_US            400000
+
+/* GPIO_CTRL_REG */
+#define TAS675X_GPIO1_OUTPUT_EN              BIT(7)
+#define TAS675X_GPIO2_OUTPUT_EN              BIT(6)
+#define TAS675X_GPIO_CTRL_RSTVAL             0x22
+
+/* GPIO output select values */
+#define TAS675X_GPIO_SEL_LOW                 0x00
+#define TAS675X_GPIO_SEL_AUTO_MUTE_ALL       0x02
+#define TAS675X_GPIO_SEL_AUTO_MUTE_CH4       0x03
+#define TAS675X_GPIO_SEL_AUTO_MUTE_CH3       0x04
+#define TAS675X_GPIO_SEL_AUTO_MUTE_CH2       0x05
+#define TAS675X_GPIO_SEL_AUTO_MUTE_CH1       0x06
+#define TAS675X_GPIO_SEL_SDOUT2              0x08
+#define TAS675X_GPIO_SEL_SDOUT1              0x09
+#define TAS675X_GPIO_SEL_WARN                0x0A
+#define TAS675X_GPIO_SEL_FAULT               0x0B
+#define TAS675X_GPIO_SEL_CLOCK_SYNC          0x0E
+#define TAS675X_GPIO_SEL_INVALID_CLK         0x0F
+#define TAS675X_GPIO_SEL_HIGH                0x13
+
+/* GPIO input function encoding (flag bit | function ID) */
+#define TAS675X_GPIO_FUNC_INPUT              0x100
+
+/* Input Function IDs */
+#define TAS675X_GPIO_IN_ID_MUTE              0
+#define TAS675X_GPIO_IN_ID_PHASE_SYNC        1
+#define TAS675X_GPIO_IN_ID_SDIN2             2
+#define TAS675X_GPIO_IN_ID_DEEP_SLEEP        3
+#define TAS675X_GPIO_IN_ID_HIZ               4
+#define TAS675X_GPIO_IN_ID_PLAY              5
+#define TAS675X_GPIO_IN_ID_SLEEP             6
+#define TAS675X_GPIO_IN_NUM                  7
+
+#define TAS675X_GPIO_IN_MUTE                 (TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_MUTE)
+#define TAS675X_GPIO_IN_PHASE_SYNC \
+	(TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_PHASE_SYNC)
+#define TAS675X_GPIO_IN_SDIN2                (TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_SDIN2)
+#define TAS675X_GPIO_IN_DEEP_SLEEP \
+	(TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_DEEP_SLEEP)
+#define TAS675X_GPIO_IN_HIZ                  (TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_HIZ)
+#define TAS675X_GPIO_IN_PLAY                 (TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_PLAY)
+#define TAS675X_GPIO_IN_SLEEP                (TAS675X_GPIO_FUNC_INPUT | TAS675X_GPIO_IN_ID_SLEEP)
+
+/* GPIO input 3-bit mux field masks */
+#define TAS675X_GPIO_IN_MUTE_MASK            GENMASK(2, 0)
+#define TAS675X_GPIO_IN_SYNC_MASK            GENMASK(2, 0)
+#define TAS675X_GPIO_IN_SDIN2_MASK           GENMASK(6, 4)
+#define TAS675X_GPIO_IN_DEEP_SLEEP_MASK      GENMASK(6, 4)
+#define TAS675X_GPIO_IN_HIZ_MASK             GENMASK(2, 0)
+#define TAS675X_GPIO_IN_PLAY_MASK            GENMASK(6, 4)
+#define TAS675X_GPIO_IN_SLEEP_MASK           GENMASK(2, 0)
+
+/* Book addresses for tas675x_select_book() */
+#define TAS675X_BOOK_DEFAULT                 0x00
+#define TAS675X_BOOK_DSP                     0x8C
+
+/* DSP memory addresses (DSP Book) */
+#define TAS675X_DSP_PAGE_RTLDG               0x22
+#define TAS675X_DSP_RTLDG_OL_THRESH_REG      0x98
+#define TAS675X_DSP_RTLDG_SL_THRESH_REG      0x9C
+
+#define TAS675X_DSP_PARAM_ID_OL_THRESH       0
+#define TAS675X_DSP_PARAM_ID_SL_THRESH       1
+
+/* Setup Mode Entry/Exit*/
+#define TAS675X_SETUP_ENTER_VAL1             0x11
+#define TAS675X_SETUP_ENTER_VAL2             0xFF
+#define TAS675X_SETUP_EXIT_VAL               0x00
+
+#endif /* __TAS675X_H__ */
-- 
2.43.0


^ permalink raw reply related

* [PATCH v6 4/4] MAINTAINERS: add entry for TAS67524 audio amplifier
From: Sen Wang @ 2026-04-16 23:26 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood
  Cc: Krzysztof Kozlowski, Conor Dooley, Rob Herring, Jaroslav Kysela,
	Takashi Iwai, Shenghao Ding, Kevin Lu, Baojun Xu, linux-sound,
	devicetree, linux-kernel, Sen Wang
In-Reply-To: <20260416232640.3084132-1-sen@ti.com>

Add Sen Wang as maintainer for the TAS67524 codec driver, DT binding,
and documentation.

Signed-off-by: Sen Wang <sen@ti.com>
---
 MAINTAINERS | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index a626dee5c106..a78b6cb9b907 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26191,17 +26191,20 @@ TEXAS INSTRUMENTS AUDIO (ASoC/HDA) DRIVERS
 M:	Shenghao Ding <shenghao-ding@ti.com>
 M:	Kevin Lu <kevin-lu@ti.com>
 M:	Baojun Xu <baojun.xu@ti.com>
+M:	Sen Wang <sen@ti.com>
 L:	linux-sound@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/sound/ti,tas2552.yaml
 F:	Documentation/devicetree/bindings/sound/ti,tas2562.yaml
 F:	Documentation/devicetree/bindings/sound/ti,tas2770.yaml
 F:	Documentation/devicetree/bindings/sound/ti,tas27xx.yaml
+F:	Documentation/devicetree/bindings/sound/ti,tas67524.yaml
 F:	Documentation/devicetree/bindings/sound/ti,tpa6130a2.yaml
 F:	Documentation/devicetree/bindings/sound/ti,pcm1681.yaml
 F:	Documentation/devicetree/bindings/sound/ti,pcm3168a.yaml
 F:	Documentation/devicetree/bindings/sound/ti,tlv320*.yaml
 F:	Documentation/devicetree/bindings/sound/ti,tlv320adcx140.yaml
+F:	Documentation/sound/codecs/tas675x*
 F:	include/sound/tas2*.h
 F:	include/sound/tlv320*.h
 F:	sound/hda/codecs/side-codecs/tas2781_hda_i2c.c
@@ -26215,6 +26218,7 @@ F:	sound/soc/codecs/pcm3168a*.*
 F:	sound/soc/codecs/pcm5102a.c
 F:	sound/soc/codecs/pcm512x*.*
 F:	sound/soc/codecs/tas2*.*
+F:	sound/soc/codecs/tas675x*.*
 F:	sound/soc/codecs/tlv320*.*
 F:	sound/soc/codecs/tpa6130a2.*
 
-- 
2.43.0


^ permalink raw reply related

* [PATCH v6 3/4] Documentation: sound: Add TAS675x codec mixer controls documentation
From: Sen Wang @ 2026-04-16 23:26 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood
  Cc: Krzysztof Kozlowski, Conor Dooley, Rob Herring, Jaroslav Kysela,
	Takashi Iwai, Shenghao Ding, Kevin Lu, Baojun Xu, linux-sound,
	devicetree, linux-kernel, Sen Wang
In-Reply-To: <20260416232640.3084132-1-sen@ti.com>

Add documentation for the TAS675x mixer controls, fault monitoring,
and driver known limitations.

Signed-off-by: Sen Wang <sen@ti.com>
---
Changes in v6:
 - Add Fault Monitoring section with register decoding tables for all
   monitored registers

Changes in v5:
 - None

Changes in v4:
 - None

Changes in v3:
 - Add register column
 - Add few addintional notes for clarification

Changes in v2:
 - None

 Documentation/sound/codecs/tas675x.rst | 820 +++++++++++++++++++++++++
 1 file changed, 820 insertions(+)
 create mode 100644 Documentation/sound/codecs/tas675x.rst

diff --git a/Documentation/sound/codecs/tas675x.rst b/Documentation/sound/codecs/tas675x.rst
new file mode 100644
index 000000000000..36bcdf18d238
--- /dev/null
+++ b/Documentation/sound/codecs/tas675x.rst
@@ -0,0 +1,820 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+======================================
+TAS675x Codec Mixer Controls
+======================================
+
+This document describes the ALSA mixer controls for the TAS675x
+4-channel amplifier driver.
+
+For device tree bindings, see:
+Documentation/devicetree/bindings/sound/ti,tas67524.yaml
+
+DSP Signal Path Mode
+====================
+
+DSP Signal Path Mode
+--------------------
+
+:Description: Signal processing mode selection.
+:Type:        Enumerated
+:Default:     Normal
+:Options:     Normal, LLP, FFLP
+:Register:    0x32 bits [1:0]
+
+Normal
+  Full DSP with all features available.
+
+LLP (Low Latency Path)
+  Bypasses DSP processing. DSP protection features (Thermal Foldback,
+  PVDD Foldback, Clip Detect) and Real-Time Load Diagnostics unavailable.
+
+FFLP (Full Feature Low Latency Path)
+  Reduced latency. Real-Time Load Diagnostics unavailable.
+
+The following controls are unavailable in LLP mode:
+``Thermal Foldback Switch``, ``PVDD Foldback Switch``,
+``DC Blocker Bypass Switch``, ``Clip Detect Switch``, ``Audio SDOUT Switch``.
+
+The following controls require Normal mode (unavailable in FFLP and LLP):
+``CHx RTLDG Switch``, ``RTLDG Clip Mask Switch``, ``ISENSE Calibration Switch``,
+``RTLDG Open Load Threshold``, ``RTLDG Short Load Threshold``,
+``CHx RTLDG Impedance``.
+
+Volume Controls
+===============
+
+Analog Playback Volume
+----------------------
+
+:Description: Analog output gain for all channels (CH1/CH2 and CH3/CH4 pairs).
+:Type:        Volume (TLV)
+:Default:     0 dB
+:Range:       -15.5 dB to 0 dB (0.5 dB steps)
+:Register:    0x4A (CH1/CH2), 0x4B (CH3/CH4)
+
+Analog Gain Ramp Step
+---------------------
+
+:Description: Anti-pop ramp step duration for analog gain transitions.
+:Type:        Enumerated
+:Default:     15us
+:Options:     15us, 60us, 200us, 400us
+:Register:    0x4E bits [3:2]
+
+CHx Digital Playback Volume
+---------------------------
+
+:Description: Per-channel digital volume control (x = 1, 2, 3, 4).
+:Type:        Volume (TLV)
+:Default:     0 dB
+:Range:       -103 dB to 0 dB (0.5 dB steps)
+:Bounds:      0x30 (min/mute) to 0xFF (max)
+:Register:    0x40 (CH1), 0x41 (CH2), 0x42 (CH3), 0x43 (CH4)
+
+Volume Ramp Down Rate
+---------------------
+
+:Description: Update frequency during mute transition.
+:Type:        Enumerated
+:Default:     16 FS
+:Options:     4 FS, 16 FS, 32 FS, Instant
+:Register:    0x44 bits [7:6]
+
+Volume Ramp Down Step
+---------------------
+
+:Description: dB change per update during mute.
+:Type:        Enumerated
+:Default:     0.5dB
+:Options:     4dB, 2dB, 1dB, 0.5dB
+:Register:    0x44 bits [5:4]
+
+Volume Ramp Up Rate
+-------------------
+
+:Description: Update frequency during unmute transition.
+:Type:        Enumerated
+:Default:     16 FS
+:Options:     4 FS, 16 FS, 32 FS, Instant
+:Register:    0x44 bits [3:2]
+
+Volume Ramp Up Step
+-------------------
+
+:Description: dB change per update during unmute.
+:Type:        Enumerated
+:Default:     0.5dB
+:Options:     4dB, 2dB, 1dB, 0.5dB
+:Register:    0x44 bits [1:0]
+
+CH1/2 Volume Combine
+--------------------
+
+:Description: Links digital volume controls for CH1 and CH2.
+:Type:        Enumerated
+:Default:     Independent
+:Options:     Independent, CH2 follows CH1, CH1 follows CH2
+:Register:    0x46 bits [1:0]
+
+CH3/4 Volume Combine
+--------------------
+
+:Description: Links digital volume controls for CH3 and CH4.
+:Type:        Enumerated
+:Default:     Independent
+:Options:     Independent, CH4 follows CH3, CH3 follows CH4
+:Register:    0x46 bits [3:2]
+
+Auto Mute & Silence Detection
+==============================
+
+CHx Auto Mute Switch
+--------------------
+
+:Description: Enables automatic muting on zero-signal detection (x = 1, 2, 3, 4).
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+:Register:    0x47 bit [0] (CH1), bit [1] (CH2), bit [2] (CH3), bit [3] (CH4)
+
+Auto Mute Combine Switch
+------------------------
+
+:Description: Coordinated muting behaviour across all channels.
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+:Behavior:    Disabled: channels mute independently when their signal is zero.
+              Enabled: all channels mute together only when all detect zero
+              signal; unmute when any channel has non-zero signal.
+:Register:    0x47 bit [4]
+
+CHx Auto Mute Time
+------------------
+
+:Description: Duration of zero signal before muting triggers (x = 1, 2, 3, 4).
+:Type:        Enumerated
+:Default:     11.5ms
+:Options:     11.5ms, 53ms, 106.5ms, 266.5ms, 535ms, 1065ms, 2665ms, 5330ms
+:Register:    0x48 bits [7:4] (CH1), bits [3:0] (CH2),
+              0x49 bits [7:4] (CH3), bits [3:0] (CH4)
+:Note:        Values are at 96 kHz. At 48 kHz, times are doubled.
+
+Clock & EMI Management
+======================
+
+Spread Spectrum Mode
+--------------------
+
+:Description: Frequency dithering mode to reduce peak EMI.
+:Type:        Enumerated
+:Default:     Disabled
+:Options:     Disabled, Triangle, Random, Triangle and Random
+:Register:    0x61 bits [1:0]
+
+SS Triangle Range
+-----------------
+
+:Description: Frequency deviation range for Triangle spread spectrum.
+:Type:        Enumerated
+:Default:     6.5%
+:Options:     6.5%, 13.5%, 5%, 10%
+:Register:    0x62 bits [1:0]
+:Note:        Applies only when Spread Spectrum Mode includes Triangle.
+
+SS Random Range
+---------------
+
+:Description: Frequency deviation range for Random spread spectrum.
+:Type:        Enumerated
+:Default:     0.83%
+:Options:     0.83%, 2.50%, 5.83%, 12.50%, 25.83%
+:Register:    0x62 bits [6:4]
+:Note:        Applies only when Spread Spectrum Mode includes Random.
+
+SS Random Dwell Range
+---------------------
+
+:Description: Dwell time range for Random spread spectrum (FSS = spread
+              spectrum modulation frequency).
+:Type:        Enumerated
+:Default:     1/FSS to 2/FSS
+:Options:     1/FSS to 2/FSS, 1/FSS to 4/FSS, 1/FSS to 8/FSS, 1/FSS to 15/FSS
+:Register:    0x62 bits [3:2]
+:Note:        Applies only when Spread Spectrum Mode includes Random.
+
+SS Triangle Dwell Min
+---------------------
+
+:Description: Minimum dwell time at Triangle spread spectrum frequency extremes.
+:Type:        Integer
+:Default:     0
+:Range:       0 to 15 (0 = feature disabled)
+:Register:    0x66 bits [7:4]
+:Note:        Counts in FSS clock cycles. The modulator holds the extreme
+              frequency for at least this many FSS cycles before reversing.
+              When Dwell Min equals Dwell Max, the dwell feature is inactive.
+              For FSS values at each PWM frequency refer to the "Spread
+              Spectrum" section of the TRM.
+
+SS Triangle Dwell Max
+---------------------
+
+:Description: Maximum dwell time at Triangle spread spectrum frequency extremes.
+:Type:        Integer
+:Default:     0
+:Range:       0 to 15 (0 = feature disabled)
+:Register:    0x66 bits [3:0]
+:Note:        Counts in FSS clock cycles. Must be >= Dwell Min. When Dwell Max
+              equals Dwell Min, the dwell feature is inactive.
+
+Hardware Protection
+===================
+
+OTSD Auto Recovery Switch
+--------------------------
+
+:Description: Enables automatic recovery from over-temperature shutdown.
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+:Register:    0x8F bit [1]
+:Note:        When disabled, manual fault clearing is required after OTSD events.
+
+Overcurrent Limit Level
+-----------------------
+
+:Description: Current-limit trip point sensitivity.
+:Type:        Enumerated
+:Default:     Level 4
+:Options:     Level 4, Level 3, Level 2, Level 1
+:Register:    0x55 bits [1:0]
+:Note:        Level 4 is the least sensitive (highest trip current); Level 1 is
+              the most sensitive. The exact ILIM values depend on operating
+              conditions (PVDD voltage, switching frequency, and temperature).
+              Refer to the Electrical Characteristics table and the
+              "Overcurrent Limit (Cycle-By-Cycle)" section of the TRM.
+
+CHx OTW Threshold
+-----------------
+
+:Description: Over-temperature warning threshold per channel (x = 1, 2, 3, 4).
+:Type:        Enumerated
+:Default:     >95C
+:Options:     Disabled, >95C, >110C, >125C, >135C, >145C, >155C, >165C
+:Register:    0xE2 bits [6:4] (CH1), bits [2:0] (CH2),
+              0xE3 bits [6:4] (CH3), bits [2:0] (CH4)
+
+Temperature and Voltage Monitoring
+===================================
+
+PVDD Sense
+----------
+
+:Description: Supply voltage sense register.
+:Type:        Integer (read-only)
+:Range:       0 to 255
+:Conversion:  value × 0.19 V
+:Register:    0x74
+
+Global Temperature
+------------------
+
+:Description: Global die temperature sense register.
+:Type:        Integer (read-only)
+:Range:       0 to 255
+:Conversion:  (value × 0.5 °C) − 50 °C
+:Register:    0x75
+
+CHx Temperature Range
+---------------------
+
+:Description: Per-channel coarse temperature range indicator (x = 1, 2, 3, 4).
+:Type:        Integer (read-only)
+:Range:       0 to 3
+:Mapping:     0 = <80 °C, 1 = 80–100 °C, 2 = 100–120 °C, 3 = >120 °C
+:Register:    0xBB bits [7:6] (CH1), bits [5:4] (CH2),
+              0xBC bits [3:2] (CH3), bits [1:0] (CH4)
+
+Load Diagnostics
+================
+
+The TAS675x provides three load diagnostic modes:
+
+DC Load Diagnostics (DC LDG)
+  Measures DC resistance to detect S2G (short-to-ground), S2P
+  (short-to-power), OL (open load), and SL (shorted load) faults.
+
+AC Load Diagnostics (AC LDG)
+  Measures complex AC impedance at a configurable frequency. Detects
+  capacitive loads and tweeter configurations.
+
+Real-Time Load Diagnostics (RTLDG)
+  Monitors impedance continuously during playback using a pilot tone.
+  Normal DSP mode only, at 48 kHz or 96 kHz.
+
+Fast Boot Mode
+--------------
+
+By default the device runs DC load diagnostics at initialization before
+accepting audio. Setting ``ti,fast-boot`` in the device tree bypasses this
+initial diagnostic run for faster startup. Automatic diagnostics after
+fault recovery remain enabled.
+
+DC Load Diagnostics
+-------------------
+
+The ``CHx DC LDG Report`` 4-bit fault field uses the following encoding:
+
+  ======  ===========  ===================================================
+  Bit     Fault        Description
+  ======  ===========  ===================================================
+  [3]     S2G          Short-to-Ground
+  [2]     S2P          Short-to-Power
+  [1]     OL           Open Load
+  [0]     SL           Shorted Load
+  ======  ===========  ===================================================
+
+DC LDG Trigger
+~~~~~~~~~~~~~~
+
+:Description: Triggers manual DC load diagnostics on all channels.
+:Type:        Boolean (write-only)
+:Note:        Returns -EBUSY if any DAI stream (playback or capture) is active.
+              The driver manages all channel state transitions. Blocks until
+              diagnostics complete or time out (300 ms).
+
+DC LDG Auto Diagnostics Switch
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Enables automatic DC diagnostics after fault recovery.
+:Type:        Boolean Switch
+:Default:     Enabled (1)
+:Register:    0xB0 bit [0]
+:Note:        Active-low, when enabled, affected channels re-run diagnostics after
+              fault recovery and retry approximately every 750 ms until resolved.
+
+CHx LO LDG Switch
+~~~~~~~~~~~~~~~~~
+
+:Description: Enables line output load detection per channel (x = 1, 2, 3, 4).
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+:Register:    0xB1 bit [3] (CH1), bit [2] (CH2), bit [1] (CH3), bit [0] (CH4)
+:Note:        When enabled and DC diagnostics report OL, the device tests for
+              a high-impedance line output load.
+
+DC LDG SLOL Ramp Time
+~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Voltage ramp time for shorted-load and open-load detection.
+:Type:        Enumerated
+:Default:     15 ms
+:Options:     15 ms, 30 ms, 10 ms, 20 ms
+:Register:    0xB2 bits [7:6]
+
+DC LDG SLOL Settling Time
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Settling time for shorted-load and open-load detection.
+:Type:        Enumerated
+:Default:     10 ms
+:Options:     10 ms, 5 ms, 20 ms, 15 ms
+:Register:    0xB2 bits [5:4]
+
+DC LDG S2PG Ramp Time
+~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Voltage ramp time for short-to-power and short-to-ground detection.
+:Type:        Enumerated
+:Default:     5 ms
+:Options:     5 ms, 2.5 ms, 10 ms, 15 ms
+:Register:    0xB2 bits [3:2]
+
+DC LDG S2PG Settling Time
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Settling time for short-to-power and short-to-ground detection.
+:Type:        Enumerated
+:Default:     10 ms
+:Options:     10 ms, 5 ms, 20 ms, 30 ms
+:Register:    0xB2 bits [1:0]
+
+CHx DC LDG SL Threshold
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Shorted-load detection threshold per channel (x = 1, 2, 3, 4).
+:Type:        Enumerated
+:Default:     1 Ohm
+:Options:     0.5 Ohm, 1 Ohm, 1.5 Ohm, 2 Ohm, 2.5 Ohm,
+              3 Ohm, 3.5 Ohm, 4 Ohm, 4.5 Ohm, 5 Ohm
+:Register:    0xB3 bits [7:4] (CH1), bits [3:0] (CH2),
+              0xB4 bits [7:4] (CH3), bits [3:0] (CH4)
+
+DC LDG Result
+~~~~~~~~~~~~~
+
+:Description: Overall DC diagnostic result register.
+:Type:        Integer (read-only)
+:Range:       0x00 to 0xFF
+:Register:    0xC2
+:Bit Encoding:
+
+  ========  =====================================================
+  Bits      Description
+  ========  =====================================================
+  [7:4]     Line output detection result, one bit per channel
+  [3:0]     DC diagnostic pass/fail per channel (1=pass, 0=fail)
+  ========  =====================================================
+
+CHx DC LDG Report
+~~~~~~~~~~~~~~~~~
+
+:Description: DC diagnostic fault status per channel (x = 1, 2, 3, 4).
+:Type:        Integer (read-only)
+:Range:       0x0 to 0xF
+:Register:    0xC0 bits [7:4] (CH1), bits [3:0] (CH2),
+              0xC1 bits [7:4] (CH3), bits [3:0] (CH4)
+:Note:        See fault bit encoding table at the start of this section.
+
+CHx LO LDG Report
+~~~~~~~~~~~~~~~~~
+
+:Description: Line output load detection result per channel (x = 1, 2, 3, 4).
+:Type:        Boolean (read-only)
+:Values:      0 = not detected, 1 = line output load detected
+:Register:    0xC2 bit [7] (CH1), bit [6] (CH2), bit [5] (CH3), bit [4] (CH4)
+
+CHx DC Resistance
+~~~~~~~~~~~~~~~~~
+
+:Description: Measured DC load resistance per channel (x = 1, 2, 3, 4).
+:Type:        Float (read-only, displayed in ohms)
+:Resolution:  0.1 ohm per code (10-bit value)
+:Range:       0.0 to 102.3 ohms
+:Register:    0xD9 bits [7:6]/[5:4]/[3:2]/[1:0] (MSB for CH1–CH4),
+              0xDA (CH1 LSB), 0xDB (CH2 LSB), 0xDC (CH3 LSB), 0xDD (CH4 LSB)
+
+AC Load Diagnostics
+-------------------
+
+AC LDG Trigger
+~~~~~~~~~~~~~~
+
+:Description: Triggers AC impedance measurement on all channels.
+:Type:        Boolean (write-only)
+:Note:        Returns -EBUSY if any DAI stream (playback or capture) is active.
+              The driver transitions all channels to SLEEP state before starting
+              the measurement. Blocks until diagnostics complete or time out.
+
+AC LDG Gain
+~~~~~~~~~~~
+
+:Description: Measurement resolution for AC diagnostics.
+:Type:        Boolean Switch
+:Default:     1 (Gain 8)
+:Values:      0 = 0.8 ohm/code (Gain 1), 1 = 0.1 ohm/code (Gain 8)
+:Register:    0xB5 bit [4]
+:Note:        Gain 8 recommended for load impedances below 8 ohms.
+
+AC LDG Test Frequency
+~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Test signal frequency for AC impedance measurement.
+:Type:        Integer
+:Default:     200 (0xC8 = 18.75 kHz)
+:Range:       0x01 to 0xFF (0x00 reserved)
+:Formula:     Frequency = 93.75 Hz × register value
+:Register:    0xB8
+
+CHx AC LDG Real / CHx AC LDG Imag
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Real and imaginary AC impedance components per channel
+              (x = 1, 2, 3, 4).
+:Type:        Integer (read-only)
+:Range:       0x00 to 0xFF (8-bit signed)
+:Register:    0xC3 (CH1 Real), 0xC4 (CH1 Imag), 0xC5 (CH2 Real), 0xC6 (CH2 Imag),
+              0xC7 (CH3 Real), 0xC8 (CH3 Imag), 0xC9 (CH4 Real), 0xCA (CH4 Imag)
+:Note:        Scale set by AC LDG Gain.
+
+Speaker Protection & Detection
+-------------------------------
+
+Tweeter Detection Switch
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Enables tweeter detection using the AC impedance magnitude comparator.
+:Type:        Boolean Switch
+:Default:     Enabled (1)
+:Register:    0xB6 bit [0]
+:Note:        The underlying register bit is TWEETER DETECT DISABLE (active-low).
+              Control value 1 = detection enabled (register bit 0), 0 = disabled
+              (register bit 1).
+
+Tweeter Detect Threshold
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Magnitude threshold for tweeter detection.
+:Type:        Integer
+:Default:     0
+:Range:       0x00 to 0xFF
+:Resolution:  0.8 ohm/code (AC LDG Gain=0) or 0.1 ohm/code (AC LDG Gain=1)
+:Register:    0xB7
+
+CHx Tweeter Detect Report
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Description: Tweeter detection result per channel (x = 1, 2, 3, 4).
+:Type:        Boolean (read-only)
+:Values:      0 = no tweeter detected, 1 = tweeter detected
+:Register:    0xCB bit [3] (CH1), bit [2] (CH2), bit [1] (CH3), bit [0] (CH4)
+
+DSP Protection Features
+=======================
+
+These controls are unavailable in LLP mode.
+
+Thermal Foldback Switch
+-----------------------
+
+:Description: Enables dynamic gain reduction based on die temperature.
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+:Register:    0x3A bit [0]
+
+PVDD Foldback Switch
+--------------------
+
+:Description: Enables automatic gain limiting when supply voltage drops
+              (Automatic Gain Limiter).
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+:Register:    0x3A bit [4]
+
+DC Blocker Bypass Switch
+------------------------
+
+:Description: Bypasses the DC-blocking high-pass filter.
+:Type:        Boolean Switch
+:Default:     Not bypassed (0)
+:Register:    0x39 bit [0]
+
+Clip Detect Switch
+------------------
+
+:Description: Enables DSP-based clip detection (Pseudo-Analog Clip Detect).
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+:Register:    0x93 bit [6]
+
+Audio SDOUT Switch
+------------------
+
+:Description: Routes post-processed audio to the SDOUT pin instead of
+              Vpredict data.
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+:Register:    0x3A bit [5]
+:Note:        When enabled, replaces Vpredict data on SDOUT with post-processed
+              SDIN. All SDOUT configurations that apply to Vpredict also apply
+              to SDIN-to-SDOUT transmission.
+
+Real-Time Load Diagnostics
+===========================
+
+These controls require Normal DSP mode at 48 kHz or 96 kHz. They are
+unavailable at 192 kHz and in FFLP and LLP modes.
+
+
+CHx RTLDG Switch
+----------------
+
+:Description: Enables real-time impedance monitoring during playback
+              (x = 1, 2, 3, 4).
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+:Register:    0x37 bit [3] (CH1), bit [2] (CH2), bit [1] (CH3), bit [0] (CH4)
+
+RTLDG Clip Mask Switch
+----------------------
+
+:Description: Suppresses impedance updates during clipping events.
+:Type:        Boolean Switch
+:Default:     Enabled (1)
+:Register:    0x37 bit [4]
+
+ISENSE Calibration Switch
+--------------------------
+
+:Description: Enables current sense calibration for accurate impedance
+              measurements.
+:Type:        Boolean Switch
+:Default:     Disabled (0)
+:Register:    0x5B bit [3]
+
+RTLDG Open Load Threshold
+--------------------------
+
+:Description: DSP coefficient for open load fault detection threshold.
+:Type:        DSP coefficient (extended control)
+:Register:    DSP Book 0x8C, page 0x22, 0x98
+
+RTLDG Short Load Threshold
+---------------------------
+
+:Description: DSP coefficient for shorted load fault detection threshold.
+:Type:        DSP coefficient (extended control)
+:Register:    DSP Book 0x8C, page 0x22, 0x9C
+
+CHx RTLDG Impedance
+-------------------
+
+:Description: Real-time load impedance per channel (x = 1, 2, 3, 4).
+:Type:        Float (read-only, displayed in ohms)
+:Register:    0xD1–0xD2 (CH1), 0xD3–0xD4 (CH2), 0xD5–0xD6 (CH3), 0xD7–0xD8 (CH4)
+:Note:        Valid only during PLAY state with RTLDG enabled at 48 or
+              96 kHz. Holds stale data in SLEEP, MUTE, or Hi-Z states.
+
+Fault Monitoring
+================
+
+The driver monitors faults via IRQ or periodic polling (fallback when
+no ``interrupts`` DT property is present). Detected faults are logged
+and cleared so affected channels can recover.
+
+Kernel log messages use the format::
+
+  tas675x <addr>: <Name> Latched: 0x<value>
+
+Critical faults are logged at ``CRIT`` level; warnings at ``WARN``.
+
+The following fault registers are monitored:
+
+Critical Faults
+---------------
+
+These faults place affected channels into the FAULT state. The driver
+issues fault clear (register 0x01 bit 3) to allow recovery.
+
+Overtemperature Shutdown (0x87)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+  ========  ==========================================
+  Bits      Description
+  ========  ==========================================
+  [4]       Global OTSD
+  [3:0]     CH1-CH4 OTSD respectively
+  ========  ==========================================
+
+Fires when die temperature exceeds the OTSD threshold. If auto-recovery
+is enabled (``OTSD Auto Recovery Switch``), channels return to their
+previous state after cooling. Otherwise fault clear is required.
+
+Overcurrent / DC Fault (0x8E)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+  ========  ==========================================
+  Bits      Description
+  ========  ==========================================
+  [7:4]     Overcurrent Shutdown, CH1-CH4 respectively
+  [3:0]     DC Fault, CH1-CH4 respectively
+  ========  ==========================================
+
+Overcurrent shutdown occurs when output current reaches the shutdown
+threshold (e.g. output short to GND). DC fault fires when DC offset
+exceeds the DCFAULT threshold.
+
+Real-Time Load Diagnostic Fault (0x8B)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+  ========  ==========================================
+  Bits      Description
+  ========  ==========================================
+  [7:4]     Shorted Load faults, CH1-CH4 respectively
+  [3:0]     Open Load faults, CH1-CH4 respectively
+  ========  ==========================================
+
+Reported during playback when RTLDG detects impedance outside the
+configured open/short thresholds. Requires RTLDG and current sense to
+be enabled, and a sampling rate of 48 kHz or 96 kHz.
+
+CBC Fault/Warning (0x8D)
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+  ========  ==========================================
+  Bits      Description
+  ========  ==========================================
+  [7:4]     Load current warning, CH1-CH4 respectively
+  [3:0]     Load current fault, CH1-CH4 respectively
+  ========  ==========================================
+
+Cycle-by-cycle current limiting events. A warning fires when the
+current limit is active for at least 25% of a 21.3 ms window. A fault
+fires if the warning persists for 170.4 ms continuously.
+
+Warnings
+--------
+
+These conditions are logged but do not place channels into FAULT state.
+Corresponding register bits are cleared when read. Nonetheless, the driver
+still clears all events as warnings also latch the (mapped) FAULT pin.
+
+Power Fault (0x86)
+~~~~~~~~~~~~~~~~~~
+
+  ========  ==========================================
+  Bits      Description
+  ========  ==========================================
+  [7]       DVDD Power-On-Reset
+  [4]       DVDD Undervoltage
+  [3]       PVDD Overvoltage
+  [2]       VBAT Overvoltage
+  [1]       PVDD Undervoltage
+  [0]       VBAT Undervoltage
+  ========  ==========================================
+
+Supply-related events. DVDD POR at bit 7 is expected after every
+power-up and can be ignored.
+
+CP / OUTM Soft Short Fault (0x7D)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+  ========  ==========================================
+  Bits      Description
+  ========  ==========================================
+  [5:2]     OUTM soft short, CH4-CH1 respectively
+  [1]       Charge Pump UVLO (latched)
+  [0]       Charge Pump UVLO (unlatched/live status)
+  ========  ==========================================
+
+Per-channel out_minus soft short faults and charge pump undervoltage lockout.
+
+Clock Fault (0x8A)
+~~~~~~~~~~~~~~~~~~
+
+  ========  ==========================================
+  Bits      Description
+  ========  ==========================================
+  [0]       Clock error
+  ========  ==========================================
+
+Fires on clock halt or unsupported SCLK/FSYNC ratio. Only logged when
+a stream is active (suppressed during idle to avoid noise from normal
+stream stop/start transitions). See `Clock Fault Behaviour`_ below.
+
+Overtemperature Warning (0x88)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+  ========  ==========================================
+  Bits      Description
+  ========  ==========================================
+  [4]       Global OTW
+  [3:0]     CH1-CH4 OTW respectively
+  ========  ==========================================
+
+Fires when temperature crosses the OTW threshold. The device continues
+to operate. Only logged on change to avoid log spam.
+
+Clip Warning (0x89)
+~~~~~~~~~~~~~~~~~~~
+
+  ========  ==========================================
+  Bits      Description
+  ========  ==========================================
+  [3:0]     CH1-CH4 clip detected respectively
+  ========  ==========================================
+
+Fires when the audio signal magnitude exceeds the clip detection
+threshold. Requires Clip Detect to be enabled. Only logged when a
+stream is active.
+
+Driver Known Limitations
+========================
+
+Clock Fault Behaviour
+---------------------
+
+On Stream Stop
+~~~~~~~~~~~~~~
+
+Every time a playback stream stops the FAULT pin briefly asserts. The
+ASoC PCM trigger stop sequence calls DAIs in reverse order, so the CPU
+DAI stops SCLK before the codec can transition to sleep. The device
+detects the clock halt and latches ``CLK_FAULT_LATCHED``, which asserts
+the FAULT pin. The driver clears the latch in the ``mute_stream``
+callback that follows, so the FAULT pin flicker lasts only a few
+milliseconds. Audio output is not affected and no kernel log message
+is produced.
+
+On Rapid Rate Switching
+~~~~~~~~~~~~~~~~~~~~~~~
+
+When streams are started in rapid succession, an intermittent
+``Clock Fault Latched: 0x01`` message may appear in the kernel log.
+A (conservative) ~0.5 second gap between sessions eliminates this.
+
+References
+==========
+
+- TAS675x Technical Reference Manual: SLOU589A
+- Device Tree Bindings: Documentation/devicetree/bindings/sound/ti,tas67524.yaml
+- ALSA Control Name Conventions: Documentation/sound/designs/control-names.rst
-- 
2.43.0


^ permalink raw reply related

* [PATCH v6 0/4] ASoC: Add TAS67524 quad-channel Class-D amplifier driver
From: Sen Wang @ 2026-04-16 23:26 UTC (permalink / raw)
  To: Mark Brown, Liam Girdwood
  Cc: Krzysztof Kozlowski, Conor Dooley, Rob Herring, Jaroslav Kysela,
	Takashi Iwai, Shenghao Ding, Kevin Lu, Baojun Xu, linux-sound,
	devicetree, linux-kernel, Sen Wang

This series adds support for the TI TAS675x (TAS6754, TAS67524)
quad-channel automotive Class-D amplifiers. The devices have an
integrated DSP and load diagnostics, and are controlled over I2C.

Patch 1 adds the dt-binding, patch 2 the codec driver, patch 3 the
ALSA mixer controls documentation, and patch 4 adds the MAINTAINERS
entry.

Tested on AM62D-EVM with a TAS67CD-AEC daughter card. For setup &
test procedures, refer to the GitHub repository.

GitHub: https://github.com/SenWang125/tas67-linux

Changes in v6:
 - Cancel fault_check_work and disable IRQ in i2c_remove (2/4)
 - (Report by Sashiko) Use regmap_bulk_read for RTLDG impedance to prevent data tearing (2/4)
 - Add Fault Monitoring documentation section with register decoding (3/4)
 - Links to v5: https://lore.kernel.org/all/20260409220607.686146-1-sen@ti.com/

Changes in v5:
 - Drop ti,tas6754 device id reference (2/4)
 - Restrict RTLDG threshold max to 24bit (2/4)
 - Complete error checking for set_dcldg_trigger (2/4)
 - Add runtime PM reference in IRQ handler (2/4)
 - Links to v4: https://lore.kernel.org/all/20260408053149.1369350-1-sen@ti.com/

Changes in v4:
 - Correct dt-binding compatibles (1/4)
 - Revert v3's change and made tas67524.c back to tas675x.c (2/4)
 - Links to v3: https://lore.kernel.org/all/20260403050627.635591-1-sen@ti.com/

Changes in v3:
 - Rename ALL tas675x filenames to tas67524, removed tas6754 compatible instance
 - Change pd-gpios to powerdown-gpios, cleanup unnessary .yaml formatting (1/4)
 - Opt to use disable delayed_work and re-enable on runtime suspend/resume,
 similarly, use disable/enable IRQ on system suspend/resume. (2/4)
 - Include IRQ_NONE on ISR returns. (2/4)
 - Clarify _check_faults() function which now returns need_clear boolean (2/4)
 - Add register section (3/4)
 - Add addintional notes: for clarification (3/4)
 - Links to v2: https://lore.kernel.org/all/20260401223239.1638881-1-sen@ti.com/

Changes in v2:
 - Remove redundant DAPM event function (2/4)
 - Move IRQ request past power_on, so regs can be set in a clean state (2/4)
 - Add delayed_work at probe time to accomdate no PM configs (2/4)
 - Change .set_fmt and .dapm_routes callbacks to the same tas675x_set_fmt name (2/4)
 - Links to v1: https://lore.kernel.org/all/20260401024210.28542-1-sen@ti.com/

Sen Wang (4):
  ASoC: dt-bindings: Add ti,tas67524
  ASoC: codecs: Add TAS67524 quad-channel audio amplifier driver
  Documentation: sound: Add TAS675x codec mixer controls documentation
  MAINTAINERS: add entry for TAS67524 audio amplifier

 .../bindings/sound/ti,tas67524.yaml           |  280 +++
 Documentation/sound/codecs/tas675x.rst        |  820 ++++++
 MAINTAINERS                                   |    4 +
 sound/soc/codecs/Kconfig                      |   12 +
 sound/soc/codecs/Makefile                     |    2 +
 sound/soc/codecs/tas675x.c                    | 2194 +++++++++++++++++
 sound/soc/codecs/tas675x.h                    |  367 +++
 7 files changed, 3679 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/ti,tas67524.yaml
 create mode 100644 Documentation/sound/codecs/tas675x.rst
 create mode 100644 sound/soc/codecs/tas675x.c
 create mode 100644 sound/soc/codecs/tas675x.h

--
2.43.0


^ permalink raw reply

* Re: [PATCH v2 2/6] ASoC: renesas: fsi: Fix hang by enabling SPU clock
From: Kuninori Morimoto @ 2026-04-17  0:42 UTC (permalink / raw)
  To: Bui Duc Phuc
  Cc: broonie, lgirdwood, robh, krzk+dt, conor+dt, geert+renesas,
	magnus.damm, perex, tiwai, linux-sound, linux-renesas-soc,
	devicetree, linux-kernel
In-Reply-To: <CAABR9nFN9C4CGsaZoWzrHEjibBLqQ1KmM8o5oG2-pSMcrgMsAw@mail.gmail.com>


Hi Bui

> Regarding spu_count, I’m not entirely sure, but if we increment it
> even on failure,
> the counter might become unbalanced and clk_prepare_enable() may not
> be retried on the next call.
> Would it be better to increment spu_count only on success to keep the
> state consistent?

It depens on how you create the error case :)
I don't mind super much about detail of it.

> Since fsi_hw_startup() and fsi_hw_shutdown() are called from fsi_dai_trigger(),
> I think this runs in an atomic context, but please correct me if I'm wrong.
> If so, is it safe to call clk_prepare_enable() under guard(spinlock_irqsave)?
> Since clk_prepare() can sleep, I’m wondering if this could potentially
> cause a "scheduling while atomic" issue.
>
> Would it make more sense to move clk_prepare() to init time (in new
> fsi_clk_init() ),
> and only use clk_enable() / clk_disable() in the trigger path?

I don't remember detail of SH-Mobile clock driver, but yes.

Thank you for your help !!

Best regards
---
Kuninori Morimoto

^ permalink raw reply

* RE: [PATCH v2 1/2] dt-bindings: hwmon: pmbus: add max20830
From: Torreno, Alexis Czezar @ 2026-04-17  1:04 UTC (permalink / raw)
  To: Conor Dooley, Guenter Roeck
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
	Shuah Khan, linux-hwmon@vger.kernel.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-doc@vger.kernel.org
In-Reply-To: <20260416-scoring-secluding-c7a7235b181a@spud>


> > >
> > > On the previous version, you got an LLM comment about not having the
> > > interrupts property amongst other things.
> > > I think the other things got implemented, but I didn't see any reply
> > > to the bot about that?

I wasn't sure if it was that type of bot. I'll try replying on the other patch review.
I just added a note in the cover letter change log about the lacking smbalert. 

> > > I think the answer is that it shouldn't because the pin it
> > > referenced doesn't exist, but when looking at the schematic I have
> > > to wonder if
> >
> > I had to look this up in the datasheet. A SMBus chip with no alert pin
> > is a bit odd, but you are correct.
> >
> > > there should be an interrupts property for dealing with "pgood"?
> > >
> > FWIW, I have never seen that. Normally such pins are used to take
> > devices out of reset.
> 
> It's an output on this device seemingly. I don't care if the driver ignores it, but
> for completeness (and we like completeness with
> bindings) I think it should be documented as an interrupt or gpio.

Alright, I'll add it as an interrupt: optional power-good signal

Regards,
Alexis

^ permalink raw reply

* [PATCH] dt-bindings: Remove the redundant 'type: boolean'
From: phucduc.bui @ 2026-04-17  2:18 UTC (permalink / raw)
  To: robh, krzk+dt, conor+dt
  Cc: nick, dmitry.torokhov, nicolas.ferre, alexandre.belloni,
	claudiu.beznea, lee, heiko, gregkh, linusw, zyw, zhangqing,
	gene_chen, linux-input, devicetree, linux-arm-kernel, linux-usb,
	bui duc phuc

From: bui duc phuc <phucduc.bui@gmail.com>

The 'wakeup-source' property already has its type defined in the core
schema. Remove the redundant 'type: boolean' from the binding file to
clean up the binding files.

Signed-off-by: bui duc phuc <phucduc.bui@gmail.com>
---
 Documentation/devicetree/bindings/input/atmel,maxtouch.yaml | 3 +--
 Documentation/devicetree/bindings/mfd/rockchip,rk816.yaml   | 3 +--
 Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml  | 3 +--
 3 files changed, 3 insertions(+), 6 deletions(-)

diff --git a/Documentation/devicetree/bindings/input/atmel,maxtouch.yaml b/Documentation/devicetree/bindings/input/atmel,maxtouch.yaml
index 9bf07acea599..26ea78df27c4 100644
--- a/Documentation/devicetree/bindings/input/atmel,maxtouch.yaml
+++ b/Documentation/devicetree/bindings/input/atmel,maxtouch.yaml
@@ -88,8 +88,7 @@ properties:
       - 2 # ATMEL_MXT_WAKEUP_GPIO
     default: 0
 
-  wakeup-source:
-    type: boolean
+  wakeup-source: true
 
 required:
   - compatible
diff --git a/Documentation/devicetree/bindings/mfd/rockchip,rk816.yaml b/Documentation/devicetree/bindings/mfd/rockchip,rk816.yaml
index 0676890f101e..a58d9455a1a5 100644
--- a/Documentation/devicetree/bindings/mfd/rockchip,rk816.yaml
+++ b/Documentation/devicetree/bindings/mfd/rockchip,rk816.yaml
@@ -44,8 +44,7 @@ properties:
     description:
       Telling whether or not this PMIC is controlling the system power.
 
-  wakeup-source:
-    type: boolean
+  wakeup-source: true
 
   vcc1-supply:
     description:
diff --git a/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml b/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml
index ae611f7e57ca..ec0d83220527 100644
--- a/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml
+++ b/Documentation/devicetree/bindings/usb/richtek,rt1711h.yaml
@@ -33,8 +33,7 @@ properties:
   vbus-supply:
     description: VBUS power supply
 
-  wakeup-source:
-    type: boolean
+  wakeup-source: true
 
   connector:
     type: object
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v7 1/3] dt-bindings: pinctrl: Add aspeed,ast2700-soc0-pinctrl
From: Billy Tsai @ 2026-04-17  2:20 UTC (permalink / raw)
  To: Conor Dooley
  Cc: Lee Jones, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Joel Stanley, Andrew Jeffery, Linus Walleij, Bartosz Golaszewski,
	Ryan Chen, Andrew Jeffery, devicetree@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org,
	linux-aspeed@lists.ozlabs.org, linux-kernel@vger.kernel.org,
	openbmc@lists.ozlabs.org, linux-gpio@vger.kernel.org,
	linux-clk@vger.kernel.org
In-Reply-To: <20260416-brutishly-saga-ba7168a4cd14@spud>

> > +    properties:
> > +      function:
> > +        enum:
> > +          - EMMC
> > +          - JTAGDDR
> > +          - JTAGM0
> > +          - JTAGPCIEA
> > +          - JTAGPCIEB
> > +          - JTAGPSP
> > +          - JTAGSSP
> > +          - JTAGTSP
> > +          - JTAGUSB3A
> > +          - JTAGUSB3B
> > +          - PCIERC0PERST
> > +          - PCIERC1PERST
> > +          - TSPRSTN
> > +          - UFSCLKI
> > +          - USB2AD0
> > +          - USB2AD1
> > +          - USB2AH
> > +          - USB2AHP
> > +          - USB2AHPD0
> > +          - USB2AXH
> > +          - USB2AXH2B
> > +          - USB2AXHD1
> > +          - USB2AXHP
> > +          - USB2AXHP2B
> > +          - USB2AXHPD1
> > +          - USB2BD0
> > +          - USB2BD1
> > +          - USB2BH
> > +          - USB2BHP
> > +          - USB2BHPD0
> > +          - USB2BXH
> > +          - USB2BXH2A
> > +          - USB2BXHD1
> > +          - USB2BXHP
> > +          - USB2BXHP2A
> > +          - USB2BXHPD1
> > +          - USB3AXH
> > +          - USB3AXH2B
> > +          - USB3AXHD
> > +          - USB3AXHP
> > +          - USB3AXHP2B
> > +          - USB3AXHPD
> > +          - USB3BXH
> > +          - USB3BXH2A
> > +          - USB3BXHD
> > +          - USB3BXHP
> > +          - USB3BXHP2A
> > +          - USB3BXHPD
> > +          - VB
> > +          - VGADDC
> > +
> > +      groups:
> > +        enum:
> > +          - EMMCCDN
> > +          - EMMCG1
> > +          - EMMCG4
> > +          - EMMCG8
> > +          - EMMCWPN
> > +          - JTAG0
> > +          - PCIERC0PERST
> > +          - PCIERC1PERST
> > +          - TSPRSTN
> > +          - UFSCLKI
> > +          - USB2A
> > +          - USB2AAP
> > +          - USB2ABP
> > +          - USB2ADAP
> > +          - USB2AH
> > +          - USB2AHAP
> > +          - USB2B
> > +          - USB2BAP
> > +          - USB2BBP
> > +          - USB2BDBP
> > +          - USB2BH
> > +          - USB2BHBP
> > +          - USB3A
> > +          - USB3AAP
> > +          - USB3ABP
> > +          - USB3B
> > +          - USB3BAP
> > +          - USB3BBP
> > +          - VB0
> > +          - VB1
> > +          - VGADDC
> > +      pins:
> > +        enum:
> > +          - AB13
> > +          - AB14
> > +          - AC13
> > +          - AC14
> > +          - AD13
> > +          - AD14
> > +          - AE13
> > +          - AE14
> > +          - AE15
> > +          - AF13
> > +          - AF14
> > +          - AF15

> Why do you have groups and pins?

> Is it valid in your device to have groups and pins in the same node?

The intent is to support both group-based mux selection and
configuration, as well as per-pin configuration.

In our hardware:

- `function` + `groups` are used for pinmux selection.
- `pins` is used for per-pin configuration (e.g. drive strength,
  bias settings).
- `groups` may also be used for group-level configuration.

As a result, both `groups` and `pins` may appear in the same node,
but they serve different purposes and do not conflict:

- `groups` selects the mux function and may apply configuration to
  the entire group.
- `pins` allows overriding or specifying configuration for individual
  pins.

In most cases, only one of them is needed, but both are allowed when
both group-level and per-pin configuration are required.

Thanks
Billy Tsai

^ permalink raw reply

* [PATCH] PCI: amd-mdb: Assert PERST# on shutdown
From: Sai Krishna Musham @ 2026-04-17  2:38 UTC (permalink / raw)
  To: bhelgaas, lpieralisi, kw, mani, robh, krzk+dt, conor+dt, cassel
  Cc: linux-pci, devicetree, linux-kernel, michal.simek,
	bharat.kumar.gogada, thippeswamy.havalige, sai.krishna.musham

Add a shutdown handler for the AMD MDB PCIe host controller that
asserts the PERST# signal via GPIO before the system powers off or
reboots. This ensures the connected PCIe endpoint is held in reset
during shutdown.

Signed-off-by: Sai Krishna Musham <sai.krishna.musham@amd.com>
---
 drivers/pci/controller/dwc/pcie-amd-mdb.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/drivers/pci/controller/dwc/pcie-amd-mdb.c b/drivers/pci/controller/dwc/pcie-amd-mdb.c
index 7e50e11fbffd..69d21b4727e4 100644
--- a/drivers/pci/controller/dwc/pcie-amd-mdb.c
+++ b/drivers/pci/controller/dwc/pcie-amd-mdb.c
@@ -507,6 +507,14 @@ static int amd_mdb_pcie_probe(struct platform_device *pdev)
 	return amd_mdb_add_pcie_port(pcie, pdev);
 }
 
+static void amd_mdb_pcie_shutdown(struct platform_device *pdev)
+{
+	struct amd_mdb_pcie *pcie = platform_get_drvdata(pdev);
+
+	if (pcie->perst_gpio)
+		gpiod_set_value_cansleep(pcie->perst_gpio, 1);
+}
+
 static const struct of_device_id amd_mdb_pcie_of_match[] = {
 	{
 		.compatible = "amd,versal2-mdb-host",
@@ -521,6 +529,7 @@ static struct platform_driver amd_mdb_pcie_driver = {
 		.suppress_bind_attrs = true,
 	},
 	.probe = amd_mdb_pcie_probe,
+	.shutdown = amd_mdb_pcie_shutdown,
 };
 
 builtin_platform_driver(amd_mdb_pcie_driver);
-- 
2.43.0


^ permalink raw reply related

* [net-next v2 0/5] Add StarFive JHB100 soc SGMII GMAC support
From: Minda Chen @ 2026-04-17  2:45 UTC (permalink / raw)
  To: Alexandre Torgue, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Maxime Coquelin,
	Emil Renner Berthing, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, netdev
  Cc: linux-kernel, linux-stm32, devicetree, Minda Chen

JHB100 is a Starfive new RISC-V SoC for datacenter BMC (BaseBoard
Managent Controller). Similar with Aspeed 27x0.

The JHB100 minimal system upstream is in progress:
https://patchwork.kernel.org/project/linux-riscv/cover/20260403054945.467700-1-changhuang.liang@starfivetech.com/

JHB100 GMAC still using designware GMAC core like JH7100 and JH7110,
and contains 2 SGMII interfaces, 1 RGMII/RMII interface, 1 RMII
interface. In JH7100/JH7110 dwmac-starfive.c have supported RGMII/RMII
interface. So require to add SGMII support to dwmac-starfive.c for JHB100.

SGMII serdes PHY has been intergrated in JHB100 and do not have driver
setting.

In JHB100 EVB board, SGMII connect with motorcomm YT8531s external PHY
and support RJ45 ethernet port.

The patch base in 7.0-rc5

changes
v2:
1. patch1 Add the remove reason
2. patch2 rename rx clock to sgmii_rx
3. patch4 confirm sgmii rx clock exist, or will probe error
   sgmii will not call starfive_dwmac_set_mode() 

Minda Chen (5):
  dt-bindings: net: starfive,jh7110-dwmac: Remove JH8100
  dt-bindings: net: starfive,jh7110-dwmac: Add JHB100 support
  dt-bindings: net: starfive,jh7110-dwmac: Add JHB100 sgmii rx clk
  net: stmmac: starfive: Add JHB100 SGMII interface
  net: stmmac: starfive: Add STMMAC_FLAG_SPH_DISABLE flag

 .../devicetree/bindings/net/snps,dwmac.yaml   |  1 +
 .../bindings/net/starfive,jh7110-dwmac.yaml   | 89 +++++++++++++------
 .../ethernet/stmicro/stmmac/dwmac-starfive.c  | 56 +++++++++---
 3 files changed, 106 insertions(+), 40 deletions(-)


base-commit: c369299895a591d96745d6492d4888259b004a9e
-- 
2.17.1


^ permalink raw reply

* [net-next v2 2/5] dt-bindings: net: starfive,jh7110-dwmac: Add JHB100 support
From: Minda Chen @ 2026-04-17  2:45 UTC (permalink / raw)
  To: Alexandre Torgue, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Maxime Coquelin,
	Emil Renner Berthing, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, netdev
  Cc: linux-kernel, linux-stm32, devicetree, Minda Chen
In-Reply-To: <20260417024523.107786-1-minda.chen@starfivetech.com>

Add StarFive JHB100 dwmac support and compatible.
The JHB100 dwmac shares the same driver code as the JH7110 dwmac,
which contains 2 SGMII interfaces, 1 RGMII/RMII interface and
1 RMII interface.
JHB100 dwmac has only one reset signal and one main interrupt
line.

Please refer to below:

JHB100: reset-names = "stmmaceth";

Example usage of JHB100 in the device tree:

gmac0: ethernet@11b80000 {
        compatible = "starfive,jhb100-dwmac",
                     "snps,dwmac-5.20";
        interrupts = <225>;
        interrupt-names = "macirq";
        ...
};

Signed-off-by: Minda Chen <minda.chen@starfivetech.com>
---
 .../devicetree/bindings/net/snps,dwmac.yaml   |  1 +
 .../bindings/net/starfive,jh7110-dwmac.yaml   | 23 +++++++++++++++++++
 2 files changed, 24 insertions(+)

diff --git a/Documentation/devicetree/bindings/net/snps,dwmac.yaml b/Documentation/devicetree/bindings/net/snps,dwmac.yaml
index 38bc34dc4f09..85cd3252e8b1 100644
--- a/Documentation/devicetree/bindings/net/snps,dwmac.yaml
+++ b/Documentation/devicetree/bindings/net/snps,dwmac.yaml
@@ -115,6 +115,7 @@ properties:
         - sophgo,sg2044-dwmac
         - starfive,jh7100-dwmac
         - starfive,jh7110-dwmac
+        - starfive,jhb100-dwmac
         - tesla,fsd-ethqos
         - thead,th1520-gmac
 
diff --git a/Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml b/Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml
index 0d1962980f57..edc246a71ce3 100644
--- a/Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml
+++ b/Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml
@@ -18,6 +18,7 @@ select:
         enum:
           - starfive,jh7100-dwmac
           - starfive,jh7110-dwmac
+          - starfive,jhb100-dwmac
   required:
     - compatible
 
@@ -30,6 +31,9 @@ properties:
       - items:
           - const: starfive,jh7110-dwmac
           - const: snps,dwmac-5.20
+      - items:
+          - const: starfive,jhb100-dwmac
+          - const: snps,dwmac-5.20
 
   reg:
     maxItems: 1
@@ -122,6 +126,25 @@ allOf:
         reset-names:
           minItems: 2
 
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: starfive,jhb100-dwmac
+    then:
+      properties:
+        interrupts:
+          maxItems: 1
+
+        interrupt-names:
+          const: macirq
+
+        resets:
+          maxItems: 1
+
+        reset-names:
+          const: stmmaceth
+
 unevaluatedProperties: false
 
 examples:
-- 
2.17.1


^ permalink raw reply related

* [net-next v2 3/5] dt-bindings: net: starfive,jh7110-dwmac: Add JHB100 sgmii rx clk
From: Minda Chen @ 2026-04-17  2:45 UTC (permalink / raw)
  To: Alexandre Torgue, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Maxime Coquelin,
	Emil Renner Berthing, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, netdev
  Cc: linux-kernel, linux-stm32, devicetree, Minda Chen
In-Reply-To: <20260417024523.107786-1-minda.chen@starfivetech.com>

JHB100 SGMII interface tx/rx mac clock is split and require to
set clock rate in 10M/100M/1000M speed. So dts need to add a
new rx clock in code, dts and dt binding doc.

Signed-off-by: Minda Chen <minda.chen@starfivetech.com>
---
 .../bindings/net/starfive,jh7110-dwmac.yaml   | 42 ++++++++++++++++---
 1 file changed, 36 insertions(+), 6 deletions(-)

diff --git a/Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml b/Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml
index edc246a71ce3..3802cdbf1848 100644
--- a/Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml
+++ b/Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml
@@ -39,20 +39,26 @@ properties:
     maxItems: 1
 
   clocks:
+    minItems: 5
     items:
       - description: GMAC main clock
       - description: GMAC AHB clock
       - description: PTP clock
       - description: TX clock
       - description: GTX clock
+      - description: SGMII RX clock
 
   clock-names:
-    items:
-      - const: stmmaceth
-      - const: pclk
-      - const: ptp_ref
-      - const: tx
-      - const: gtx
+    minItems: 5
+    maxItems: 6
+    contains:
+      enum:
+       - stmmaceth
+       - pclk
+       - ptp_ref
+       - tx
+       - gtx
+       - sgmii_rx
 
   starfive,tx-use-rgmii-clk:
     description:
@@ -99,6 +105,14 @@ allOf:
           minItems: 2
           maxItems: 2
 
+        clocks:
+          minItems: 5
+          maxItems: 5
+
+        clock-names:
+          minItems: 5
+          maxItems: 5
+
         resets:
           maxItems: 1
 
@@ -120,6 +134,14 @@ allOf:
           minItems: 3
           maxItems: 3
 
+        clocks:
+          minItems: 5
+          maxItems: 5
+
+        clock-names:
+          minItems: 5
+          maxItems: 5
+
         resets:
           minItems: 2
 
@@ -139,6 +161,14 @@ allOf:
         interrupt-names:
           const: macirq
 
+        clocks:
+          minItems: 5
+          maxItems: 6
+
+        clock-names:
+          minItems: 5
+          maxItems: 6
+
         resets:
           maxItems: 1
 
-- 
2.17.1


^ permalink raw reply related

* [net-next v2 4/5] net: stmmac: starfive: Add JHB100 SGMII interface
From: Minda Chen @ 2026-04-17  2:45 UTC (permalink / raw)
  To: Alexandre Torgue, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Maxime Coquelin,
	Emil Renner Berthing, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, netdev
  Cc: linux-kernel, linux-stm32, devicetree, Minda Chen
In-Reply-To: <20260417024523.107786-1-minda.chen@starfivetech.com>

Add JHB100 compatible and SGMII support. JHB100 soc contains
2 SGMII interfaces and integrated with serdes PHY. SGMII with
split TX/RX MAC clock and need to set 2.5M/25M/125M TX/RX clock
rate in 10M/100M/1000M speed mode.

Signed-off-by: Minda Chen <minda.chen@starfivetech.com>
---
 .../ethernet/stmicro/stmmac/dwmac-starfive.c  | 54 ++++++++++++++-----
 1 file changed, 42 insertions(+), 12 deletions(-)

diff --git a/drivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c b/drivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c
index 16b955a6d77b..91698c763dac 100644
--- a/drivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c
+++ b/drivers/net/ethernet/stmicro/stmmac/dwmac-starfive.c
@@ -26,6 +26,7 @@ struct starfive_dwmac_data {
 struct starfive_dwmac {
 	struct device *dev;
 	const struct starfive_dwmac_data *data;
+	struct clk *sgmii_rx;
 };
 
 static int starfive_dwmac_set_mode(struct plat_stmmacenet_data *plat_dat)
@@ -68,6 +69,24 @@ static int starfive_dwmac_set_mode(struct plat_stmmacenet_data *plat_dat)
 	return 0;
 }
 
+static int stmmac_starfive_sgmii_set_clk_rate(void *bsp_priv, struct clk *clk_tx_i,
+					      phy_interface_t interface, int speed)
+{
+	struct starfive_dwmac *dwmac = (void *)bsp_priv;
+	long rate = rgmii_clock(speed);
+	int ret;
+
+	/* MAC clock rate the same as RGMII */
+	if (rate < 0)
+		return 0;
+
+	ret = clk_set_rate(clk_tx_i, rate);
+	if (ret)
+		return ret;
+
+	return clk_set_rate(dwmac->sgmii_rx, rate);
+}
+
 static int starfive_dwmac_probe(struct platform_device *pdev)
 {
 	struct plat_stmmacenet_data *plat_dat;
@@ -102,24 +121,34 @@ static int starfive_dwmac_probe(struct platform_device *pdev)
 		return dev_err_probe(&pdev->dev, PTR_ERR(clk_gtx),
 				     "error getting gtx clock\n");
 
-	/* Generally, the rgmii_tx clock is provided by the internal clock,
-	 * which needs to match the corresponding clock frequency according
-	 * to different speeds. If the rgmii_tx clock is provided by the
-	 * external rgmii_rxin, there is no need to configure the clock
-	 * internally, because rgmii_rxin will be adaptively adjusted.
-	 */
-	if (!device_property_read_bool(&pdev->dev, "starfive,tx-use-rgmii-clk"))
-		plat_dat->set_clk_tx_rate = stmmac_set_clk_tx_rate;
+	if (plat_dat->phy_interface == PHY_INTERFACE_MODE_SGMII) {
+		dwmac->sgmii_rx = devm_clk_get_enabled(&pdev->dev, "sgmii_rx");
+		if (IS_ERR(dwmac->sgmii_rx))
+			return dev_err_probe(&pdev->dev,
+					     PTR_ERR(dwmac->sgmii_rx),
+					     "error getting sgmii rx clock\n");
+		plat_dat->set_clk_tx_rate = stmmac_starfive_sgmii_set_clk_rate;
+	} else {
+		/*
+		 * Generally, the rgmii_tx clock is provided by the internal clock,
+		 * which needs to match the corresponding clock frequency according
+		 * to different speeds. If the rgmii_tx clock is provided by the
+		 * external rgmii_rxin, there is no need to configure the clock
+		 * internally, because rgmii_rxin will be adaptively adjusted.
+		 */
+		if (!device_property_read_bool(&pdev->dev, "starfive,tx-use-rgmii-clk"))
+			plat_dat->set_clk_tx_rate = stmmac_set_clk_tx_rate;
+
+		err = starfive_dwmac_set_mode(plat_dat);
+		if (err)
+			return err;
+	}
 
 	dwmac->dev = &pdev->dev;
 	plat_dat->flags |= STMMAC_FLAG_EN_TX_LPI_CLK_PHY_CAP;
 	plat_dat->bsp_priv = dwmac;
 	plat_dat->dma_cfg->dche = true;
 
-	err = starfive_dwmac_set_mode(plat_dat);
-	if (err)
-		return err;
-
 	return stmmac_dvr_probe(&pdev->dev, plat_dat, &stmmac_res);
 }
 
@@ -130,6 +159,7 @@ static const struct starfive_dwmac_data jh7100_data = {
 static const struct of_device_id starfive_dwmac_match[] = {
 	{ .compatible = "starfive,jh7100-dwmac", .data = &jh7100_data },
 	{ .compatible = "starfive,jh7110-dwmac" },
+	{ .compatible = "starfive,jhb100-dwmac" },
 	{ /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, starfive_dwmac_match);
-- 
2.17.1


^ permalink raw reply related

* Re: [PATCH 1/2] drivers/of: validate live-tree string properties before string use
From: Pengpeng Hou @ 2026-04-17  3:06 UTC (permalink / raw)
  To: Rob Herring; +Cc: Saravana Kannan, devicetree, linux-kernel, pengpeng
In-Reply-To: <20260403183501.1-drivers-of-live-tree-pengpeng@iscas.ac.cn>

Hi Rob,

Thanks, I'll respin this.

I'll switch the `device_type` match to `of_property_match_string()`,
rework `of_prop_next_string()` so the `cur == NULL` case flows through a
cleaner iterator path without validating each returned string twice, and
add a unittest for that first-iteration case.

Thanks,
Pengpeng



^ permalink raw reply

* Re: Re: Re: [PATCH v4 1/2] dt-bindings: pwm: dwc: add reset optional
From: Xuyang Dong @ 2026-04-17  3:11 UTC (permalink / raw)
  To: Conor Dooley
  Cc: Krzysztof Kozlowski, ukleinek, robh, krzk+dt, conor+dt, ben-linux,
	ben.dooks, p.zabel, linux-pwm, devicetree, linux-kernel, ningyu,
	linmin, xuxiang, wangguosheng, pinkesh.vaghela
In-Reply-To: <20260416-flashcard-shadily-a0ddd2f12ff8@spud>

> > > > > 
> > > > > The DesignWare PWM includes separate reset signals dedicated to each clock
> > > > > domain:
> > > > > The presetn signal resets logic in pclk domain.
> > > > > The timer_N_resetn signal resets logic in the timer_N_clk domain.
> > > > > The resets are active-low.
> > > > > 
> > > > > Signed-off-by: Xuyang Dong <dongxuyang@eswincomputing.com>
> > > > 
> > > > This commit implies that your hardware differs from existing devices,
> > > > I think you should add a device-specific compatible.
> > > > 
> > 
> > Hi Conor and Krzysztof,
> > 
> > The DesignWare PWM Databook for 2.13a says: "The DW_apb_timers includes 
> > separate reset signals dedicated to each clock domain". They are:
> > The presetn signal resets logic in pclk domain (i.e., the bus clock in DT).
> > The timer_N_resetn signal resets logic in the timer_N_clk domain (i.e.,
> > the timer clock in DT).
> > 
> > These reset signals are optional; it is up to the designer's 
> > implementation.
> 
> Right, and it's that "designer's implementation" that warrants a
> device-specific compatible.
> 

Hi Conor,

The YAML update for the new device-specific compatible is as follows:

properties:
  compatible:
    oneOf:
      - const: snps,dw-apb-timers-pwm2
      - items:
          - enum:
              - snps,dw-apb-timers-pwm-2.13a
          - const: snps,dw-apb-timers-pwm2

  reg:
    maxItems: 1

  "#pwm-cells":
    const: 3

  clocks:
    items:
      - description: Interface bus clock
      - description: PWM reference clock

  clock-names:
    items:
      - const: bus
      - const: timer

  resets:
    items:
      - description: Interface bus reset
      - description: PWM timer logic reset

  snps,pwm-number:
    $ref: /schemas/types.yaml#/definitions/uint32
    description: The number of PWM channels configured for this instance
    enum: [1, 2, 3, 4, 5, 6, 7, 8]

required:
  - compatible
  - reg
  - clocks
  - clock-names

allOf:
  - $ref: pwm.yaml#

  - if:
      properties:
        compatible:
          contains:
            const: snps,dw-apb-timers-pwm-2.13a

    then:
      required:
        - resets

In your opinion, is this modification accurate?

Best regards,
Xuyang Dong

> > 
> > According to [1], the applied YAML is also based on 2.13a, so our 
> > hardware is the same as the existing devices. It's just that these two 
> > reset signals were missing from the original YAML binding.
> > 
> > [1] https://lore.kernel.org/linux-pwm/8bb5103d-803e-90d2-fd93-132bb2aac2d6@sifive.com/
> > 
> > > > > ---
> > > > >  .../devicetree/bindings/pwm/snps,dw-apb-timers-pwm2.yaml       | 3 +++
> > > > >  1 file changed, 3 insertions(+)
> > > > > 
> > > > > diff --git a/Documentation/devicetree/bindings/pwm/snps,dw-apb-timers-pwm2.yaml b/Documentation/devicetree/bindings/pwm/snps,dw-apb-timers-pwm2.yaml
> > > > > index 7523a89a1773..a8bbad0360f8 100644
> > > > > --- a/Documentation/devicetree/bindings/pwm/snps,dw-apb-timers-pwm2.yaml
> > > > > +++ b/Documentation/devicetree/bindings/pwm/snps,dw-apb-timers-pwm2.yaml
> > > > > @@ -43,6 +43,9 @@ properties:
> > > > >        - const: bus
> > > > >        - const: timer
> > > > >  
> > > > > +  resets:
> > > > > +    maxItems: 2
> > > 
> > > And this should really be listed with description, because order is
> > > fixed.
> > > 
> > 
> > The description of resets will be listed in next version.
> > 
> > Best regards,
> > Xuyang Dong

^ permalink raw reply

* Re: Re: [PATCH v4 1/2] dt-bindings: pwm: dwc: add reset optional
From: Xuyang Dong @ 2026-04-17  3:13 UTC (permalink / raw)
  To: Uwe Kleine-König
  Cc: robh, krzk+dt, conor+dt, ben-linux, ben.dooks, p.zabel, linux-pwm,
	devicetree, linux-kernel, ningyu, linmin, xuxiang, wangguosheng,
	pinkesh.vaghela
In-Reply-To: <aeE-D5vC453uqtm6@monoceros>

> 
> Hello,
> 
> I suggest
> 
> 	dt-bindings: pwm: dwc: Add optional reset
> 
> as shortlog.
> 

Hi Uwe,

Thanks, I'll address this in the next version.

Best regards,
Xuyang Dong

^ permalink raw reply

* RE: [PATCH V13 02/12] PCI: host-generic: Add common helpers for parsing Root Port properties
From: Sherry Sun @ 2026-04-17  3:17 UTC (permalink / raw)
  To: Bjorn Helgaas
  Cc: robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
	Frank Li, s.hauer@pengutronix.de, kernel@pengutronix.de,
	festevam@gmail.com, lpieralisi@kernel.org, kwilczynski@kernel.org,
	mani@kernel.org, bhelgaas@google.com, Hongxing Zhu,
	l.stach@pengutronix.de, imx@lists.linux.dev,
	linux-pci@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <20260416203905.GA29913@bhelgaas>

> On Thu, Apr 16, 2026 at 07:14:12PM +0800, Sherry Sun wrote:
> > Introduce generic helper functions to parse Root Port device tree
> > nodes and extract common properties like reset GPIOs. This allows
> > multiple PCI host controller drivers to share the same parsing logic.
> >
> > Define struct pci_host_port to hold common Root Port properties
> > (currently only reset GPIO descriptor) and add
> > pci_host_common_parse_ports() to parse Root Port nodes from device
> tree.
> 
> Are the Root Port and the RC the only possible places for 'reset' GPIO
> descriptions in DT?  I think PERST# routing is outside the PCIe spec, so it
> seems like a system could provide a PERST# GPIO routed to any Switch
> Upstream Port or Endpoint (I assume a PERST# connected to a switch would
> apply to both the upstream port and the downstream ports).

Hi Bjorn,

Thanks for the feedback. You're right that PERST# routing could theoretically be
connected to any device in the hierarchy. However, for this patch series, I've focused
on the most common use case in practice: use Root Port level PERST# instead of the
legacy Root Complex level PERST#.

Root Port level PERST# - This is the primary target, where each Root Port has individual
control over devices connected to it.
RC level PERST# - Legacy binding support, where a single GPIO controls all ports.

We can extend this framework later if real hardware emerges that needs Switch or
EP-level PERST# control. I can add a comment documenting this limitation if needed.

BTW, Mani and Rob had some great discussions in dt-schema about PERST# and WAKE#
sideband signals settings.
You can check here:
https://github.com/devicetree-org/dt-schema/issues/168
https://github.com/devicetree-org/dt-schema/pull/126
https://github.com/devicetree-org/dt-schema/pull/170

Best Regards
Sherry

^ permalink raw reply

* [net-next v2 1/5] dt-bindings: net: starfive,jh7110-dwmac: Remove JH8100
From: Minda Chen @ 2026-04-17  2:45 UTC (permalink / raw)
  To: Alexandre Torgue, Andrew Lunn, David S . Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Maxime Coquelin,
	Emil Renner Berthing, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, netdev
  Cc: linux-kernel, linux-stm32, devicetree, Minda Chen
In-Reply-To: <20260417024523.107786-1-minda.chen@starfivetech.com>

Remove JH8100 dt-bindings because do not support it now.
StarFive have stopped JH8100 developing and will release it
outside.

Signed-off-by: Minda Chen <minda.chen@starfivetech.com>
---
 .../bindings/net/starfive,jh7110-dwmac.yaml   | 28 ++++---------------
 1 file changed, 5 insertions(+), 23 deletions(-)

diff --git a/Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml b/Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml
index 313a15331661..0d1962980f57 100644
--- a/Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml
+++ b/Documentation/devicetree/bindings/net/starfive,jh7110-dwmac.yaml
@@ -30,10 +30,6 @@ properties:
       - items:
           - const: starfive,jh7110-dwmac
           - const: snps,dwmac-5.20
-      - items:
-          - const: starfive,jh8100-dwmac
-          - const: starfive,jh7110-dwmac
-          - const: snps,dwmac-5.20
 
   reg:
     maxItems: 1
@@ -120,25 +116,11 @@ allOf:
           minItems: 3
           maxItems: 3
 
-      if:
-        properties:
-          compatible:
-            contains:
-              const: starfive,jh8100-dwmac
-      then:
-        properties:
-          resets:
-            maxItems: 1
-
-          reset-names:
-            const: stmmaceth
-      else:
-        properties:
-          resets:
-            minItems: 2
-
-          reset-names:
-            minItems: 2
+        resets:
+          minItems: 2
+
+        reset-names:
+          minItems: 2
 
 unevaluatedProperties: false
 
-- 
2.17.1


^ permalink raw reply related

* Re: [PATCH v2 1/2] dt-bindings: hwmon: pmbus: add max20830
From: Guenter Roeck @ 2026-04-17  3:22 UTC (permalink / raw)
  To: Torreno, Alexis Czezar, Conor Dooley
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
	Shuah Khan, linux-hwmon@vger.kernel.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-doc@vger.kernel.org
In-Reply-To: <PH0PR03MB635166088B7C473CF59F17D1F1202@PH0PR03MB6351.namprd03.prod.outlook.com>

On 4/16/26 18:04, Torreno, Alexis Czezar wrote:
> 
>>>>
>>>> On the previous version, you got an LLM comment about not having the
>>>> interrupts property amongst other things.
>>>> I think the other things got implemented, but I didn't see any reply
>>>> to the bot about that?
> 
> I wasn't sure if it was that type of bot. I'll try replying on the other patch review.
> I just added a note in the cover letter change log about the lacking smbalert.
> 
>>>> I think the answer is that it shouldn't because the pin it
>>>> referenced doesn't exist, but when looking at the schematic I have
>>>> to wonder if
>>>
>>> I had to look this up in the datasheet. A SMBus chip with no alert pin
>>> is a bit odd, but you are correct.
>>>
>>>> there should be an interrupts property for dealing with "pgood"?
>>>>
>>> FWIW, I have never seen that. Normally such pins are used to take
>>> devices out of reset.
>>
>> It's an output on this device seemingly. I don't care if the driver ignores it, but
>> for completeness (and we like completeness with
>> bindings) I think it should be documented as an interrupt or gpio.
> 
> Alright, I'll add it as an interrupt: optional power-good signal
> 

Uuh, that really doesn't make any sense. Please at least make it a gpio pin,
matching pwr-good-gpios of ti,tps65185.yaml.

Guenter


^ permalink raw reply

* Re: [PATCH v5 11/14] ASoC: rsnd: adg: Add per-SSI ADG and SSIF supply clock management
From: Kuninori Morimoto @ 2026-04-17  3:32 UTC (permalink / raw)
  To: John Madieu
  Cc: Mark Brown, Liam Girdwood, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Jaroslav Kysela, Takashi Iwai, Geert Uytterhoeven,
	Magnus Damm, Philipp Zabel, Claudiu Beznea, Biju Das, linux-sound,
	linux-renesas-soc, devicetree, linux-kernel, John Madieu
In-Reply-To: <20260415124731.3684773-12-john.madieu.xa@bp.renesas.com>


Hi John

Thank you for the patch

> RZ/G3E's ADG module requires explicit clock management for SSI audio
> interfaces that differs from R-Car Gen2/Gen3/Gen4:
> 
>  - Per-SSI ADG clocks (adg.ssi.N) for each SSI module
>  - A shared SSIF supply clock for the SSI subsystem
> 
> These clocks are acquired using optional APIs, making them transparent
> to platforms that do not require them.
> 
> Clock prepare/unprepare is handled in rsnd_adg_clk_control(), which
> is called from probe, remove, suspend and resume (all sleepable
> contexts). The trigger path (atomic context) only calls
> clk_enable/clk_disable, which is atomic-safe and requires no
> additional splitting.
> 
> Signed-off-by: John Madieu <john.madieu.xa@bp.renesas.com>
> ---
(snip)
> @@ -424,9 +455,35 @@ int rsnd_adg_clk_control(struct rsnd_priv *priv, int enable)
>  	if (ret < 0)
>  		rsnd_adg_clk_disable(priv);
>  
> +	/* RZ/G3E: per-SSI ADG and SSIF supply clocks */
> +	if (enable) {
> +		for (i = 0; i < ADG_SSI_MAX; i++) {
> +			ret = clk_prepare(adg->clk_adg_ssi[i]);
> +			if (ret < 0) {
> +				while (--i >= 0)
> +					clk_unprepare(adg->clk_adg_ssi[i]);
> +				rsnd_adg_clk_disable(priv);
> +				return ret;
> +			}
> +		}
> +		ret = clk_prepare(adg->clk_ssif_supply);
> +		if (ret < 0) {
> +			for (i = 0; i < ADG_SSI_MAX; i++)
> +				clk_unprepare(adg->clk_adg_ssi[i]);
> +			rsnd_adg_clk_disable(priv);
> +			return ret;
> +		}
> +	}
> +
>  	/* disable adg */
> -	if (!enable)
> +	if (!enable) {
> +		/* RZ/G3E: unprepare per-SSI and supply clocks */
> +		clk_unprepare(adg->clk_ssif_supply);
> +		for (i = 0; i < ADG_SSI_MAX; i++)
> +			clk_unprepare(adg->clk_adg_ssi[i]);
> +
>  		clk_disable_unprepare(adg->adg);
> +	}
>  
>  	return ret;
>  }

As mentioned in comment, to avoid duplicate code (like above), it will
call rsnd_adg_clk_disable() in case of rollback, too.

	/*
	 * rsnd_adg_clk_enable() might return error (_disable() will not).
	 * We need to rollback in such case
	 */
	if (ret < 0)
		rsnd_adg_clk_disable(priv);

Because of this style, I guess "enable" need to call clk_prepare() for *all*
clk without loop break, even though it returuns error. And call clk_unprepare()
for *all* clk when "rollback" and/or "disable".

Enable
	for (i = 0; i < ADG_SSI_MAX; i++)
		ret |= clk_prepare(...);
		    ^^
	ret |= clk_prepare(...);
	    ^^
	...
	if (ret < 0)
		rsnd_adg_clk_disable(priv);

Disable
	clk_unprepare(...);
	for (i = 0; i < ADG_SSI_MAX; i++)
		clk_unprepare(...);

Thank you for your help !!

Best regards
---
Kuninori Morimoto

^ permalink raw reply

* RE: [PATCH v2 1/2] dt-bindings: hwmon: pmbus: add max20830
From: Torreno, Alexis Czezar @ 2026-04-17  3:32 UTC (permalink / raw)
  To: Guenter Roeck, Conor Dooley
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet,
	Shuah Khan, linux-hwmon@vger.kernel.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-doc@vger.kernel.org
In-Reply-To: <a142d5ce-e4a3-4f50-8009-f796609fb13c@roeck-us.net>


> >>
> >> It's an output on this device seemingly. I don't care if the driver
> >> ignores it, but for completeness (and we like completeness with
> >> bindings) I think it should be documented as an interrupt or gpio.
> >
> > Alright, I'll add it as an interrupt: optional power-good signal
> >
> 
> Uuh, that really doesn't make any sense. Please at least make it a gpio pin,
> matching pwr-good-gpios of ti,tps65185.yaml.
> 

I see, will do.

^ permalink raw reply

* Re: [PATCH v2 6/6] ASoC: dt-bindings: renesas,fsi: add support for multiple clocks
From: Bui Duc Phuc @ 2026-04-17  3:34 UTC (permalink / raw)
  To: Geert Uytterhoeven
  Cc: Krzysztof Kozlowski, kuninori.morimoto.gx, broonie, lgirdwood,
	robh, krzk+dt, conor+dt, geert+renesas, magnus.damm, perex, tiwai,
	linux-sound, linux-renesas-soc, devicetree, linux-kernel
In-Reply-To: <CAMuHMdVPbcz4rF8ojEcvxp1NaM2mbQ2o+HZLwnnjNnX8uHf4HA@mail.gmail.com>

Hi Geert,

Thanks for your feedback.

> Where does this match the driver?
> Usually the functional clock is called "fck".

Regarding the clock name "own", I used it because of the following
implementation in the current driver:

   clock->own = devm_clk_get(dev, NULL);
   if (IS_ERR(clock->own))
      return -EINVAL;

The driver currently fetches the first clock in the list (index 0) and
stores it in a variable named own.
That is why I named it "own" in the DT bindings to match.

However, I have noticed that other DTS files commonly use "fck" for
the functional clock.
Are you suggesting that I should also rename the variable from "own"
to "fck" in the driver code ??
For example:

    clocks = <&mstp2_clks R8A7740_CLK_SCIFA1>;
    clock-names = "fck";

But from the hardware manual, MSTP refers to a "Module Stop Clock",
not a "functional clock".
So I'm not sure if using "fck" here is appropriate. Could you explain
the reasoning behind calling this clock "fck"?

Regarding the FSI clocks, they can be categorized into two types:
audio clocks and module clocks (which may include bus/bridge clocks).
The driver itself does not explicitly handle the enabling/disabling of
the module clock; it only manages the audio clocks.
From my code tracing:
    At boot: The kernel automatically attaches the PM domain and
prepares the clocks during device initialization.
    During playback (aplay): The FSI driver doesn't enable the module
clock directly. Instead, it is handled via:
    genpd_runtime_resume -> pm_clk_resume -> clk_core_enable ->
cpg_mstp_clock_endisable.
Since this module clock is essential for register access, it must
always be the first entry in the clocks property (index 0) so
devm_clk_get(dev, NULL)
can fetch it correctly, right?
Please let me know if my understanding is incorrect.

Best regards,
Phuc

^ permalink raw reply


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