* [PATCH v2 08/12] ARM: dts: microchip: sama7d65_curiosity: Enable ADC, DVFS
From: Varshini Rajendran @ 2026-06-23 10:59 UTC (permalink / raw)
To: ehristev, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt,
nicolas.ferre, alexandre.belloni, claudiu.beznea, srini,
linux-iio, devicetree, linux-arm-kernel, linux-kernel
Cc: varshini.rajendran
In-Reply-To: <20260623105944.128840-1-varshini.rajendran@microchip.com>
Add regulator, pinmux and enable ADC for sama7d65 curiosity. Add
cpu-supply regulator for DVFS.
Signed-off-by: Varshini Rajendran <varshini.rajendran@microchip.com>
---
.../dts/microchip/at91-sama7d65_curiosity.dts | 27 +++++++++++++++++++
1 file changed, 27 insertions(+)
diff --git a/arch/arm/boot/dts/microchip/at91-sama7d65_curiosity.dts b/arch/arm/boot/dts/microchip/at91-sama7d65_curiosity.dts
index 927c27260b6c..a6a44a176f56 100644
--- a/arch/arm/boot/dts/microchip/at91-sama7d65_curiosity.dts
+++ b/arch/arm/boot/dts/microchip/at91-sama7d65_curiosity.dts
@@ -97,6 +97,18 @@ &can3 {
status = "okay";
};
+&adc {
+ vddana-supply = <&vddout25>;
+ vref-supply = <&vddout25>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_adc_default &pinctrl_adtrg_default>;
+ status = "okay";
+};
+
+&cpu0 {
+ cpu-supply = <&vddcpu>;
+};
+
&dma0 {
status = "okay";
};
@@ -334,6 +346,16 @@ &main_xtal {
};
&pioa {
+ pinctrl_adc_default: adc-default {
+ pinmux = <PIN_PC5__GPIO>;
+ bias-disable;
+ };
+
+ pinctrl_adtrg_default: adtrg-default {
+ pinmux = <PIN_PB7__ADTRG>;
+ bias-pull-up;
+ };
+
pinctrl_can1_default: can1-default {
pinmux = <PIN_PD10__CANTX1>,
<PIN_PD11__CANRX1>;
@@ -457,3 +479,8 @@ input@0 {
&slow_xtal {
clock-frequency = <32768>;
};
+
+&vddout25 {
+ vin-supply = <&vdd_3v3>;
+ status = "okay";
+};
--
2.34.1
^ permalink raw reply related
* [PATCH v2 07/12] ARM: dts: microchip: sama7d65: Add ADC node
From: Varshini Rajendran @ 2026-06-23 10:59 UTC (permalink / raw)
To: ehristev, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt,
nicolas.ferre, alexandre.belloni, claudiu.beznea, srini,
linux-iio, devicetree, linux-arm-kernel, linux-kernel
Cc: varshini.rajendran
In-Reply-To: <20260623105944.128840-1-varshini.rajendran@microchip.com>
Add node for the ADC controller in sama7d65 SoC.
Signed-off-by: Varshini Rajendran <varshini.rajendran@microchip.com>
---
arch/arm/boot/dts/microchip/sama7d65.dtsi | 29 +++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/arch/arm/boot/dts/microchip/sama7d65.dtsi b/arch/arm/boot/dts/microchip/sama7d65.dtsi
index 94d49e20dc79..ba775459a816 100644
--- a/arch/arm/boot/dts/microchip/sama7d65.dtsi
+++ b/arch/arm/boot/dts/microchip/sama7d65.dtsi
@@ -11,6 +11,7 @@
#include <dt-bindings/clock/at91.h>
#include <dt-bindings/dma/at91.h>
#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/iio/adc/at91-sama5d2_adc.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/mfd/at91-usart.h>
@@ -95,6 +96,16 @@ slow_xtal: clock-slowxtal {
};
};
+ vddout25: fixed-regulator-vddout25 {
+ compatible = "regulator-fixed";
+
+ regulator-name = "VDDOUT25";
+ regulator-min-microvolt = <2500000>;
+ regulator-max-microvolt = <2500000>;
+ regulator-boot-on;
+ status = "disabled";
+ };
+
ns_sram: sram@100000 {
compatible = "mmio-sram";
reg = <0x100000 0x20000>;
@@ -296,6 +307,24 @@ can4: can@e0838000 {
status = "disabled";
};
+ adc: adc@e1000000 {
+ compatible = "microchip,sama7d65-adc";
+ reg = <0xe1000000 0x200>;
+ interrupts = <GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&pmc PMC_TYPE_GCK 25>;
+ assigned-clocks = <&pmc PMC_TYPE_GCK 25>;
+ assigned-clock-rates = <100000000>;
+ clock-names = "adc_clk";
+ dmas = <&dma0 AT91_XDMAC_DT_PERID(0)>;
+ dma-names = "rx";
+ atmel,min-sample-rate-hz = <200000>;
+ atmel,max-sample-rate-hz = <20000000>;
+ atmel,trigger-edge-type = <IRQ_TYPE_EDGE_RISING>;
+ atmel,startup-time-ms = <4>;
+ #io-channel-cells = <1>;
+ status = "disabled";
+ };
+
dma2: dma-controller@e1200000 {
compatible = "microchip,sama7d65-dma", "microchip,sama7g5-dma";
reg = <0xe1200000 0x1000>;
--
2.34.1
^ permalink raw reply related
* [PATCH v2 06/12] ARM: dts: microchip: sama7d65: add cpu opps
From: Varshini Rajendran @ 2026-06-23 10:59 UTC (permalink / raw)
To: ehristev, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt,
nicolas.ferre, alexandre.belloni, claudiu.beznea, srini,
linux-iio, devicetree, linux-arm-kernel, linux-kernel
Cc: varshini.rajendran
In-Reply-To: <20260623105944.128840-1-varshini.rajendran@microchip.com>
Add CPU OPPs table for SAMA7D65.
Signed-off-by: Varshini Rajendran <varshini.rajendran@microchip.com>
---
arch/arm/boot/dts/microchip/sama7d65.dtsi | 36 +++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/arch/arm/boot/dts/microchip/sama7d65.dtsi b/arch/arm/boot/dts/microchip/sama7d65.dtsi
index 67253bbc08df..94d49e20dc79 100644
--- a/arch/arm/boot/dts/microchip/sama7d65.dtsi
+++ b/arch/arm/boot/dts/microchip/sama7d65.dtsi
@@ -35,6 +35,7 @@ cpu0: cpu@0 {
d-cache-size = <0x8000>; // L1, 32 KB
i-cache-size = <0x8000>; // L1, 32 KB
next-level-cache = <&L2>;
+ operating-points-v2 = <&cpu_opp_table>;
L2: l2-cache {
compatible = "cache";
@@ -45,6 +46,41 @@ L2: l2-cache {
};
};
+ cpu_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+ opp-90000000 {
+ opp-hz = /bits/ 64 <90000000>;
+ opp-microvolt = <1050000 1050000 1225000>;
+ clock-latency-ns = <320000>;
+ };
+
+ opp-250000000 {
+ opp-hz = /bits/ 64 <250000000>;
+ opp-microvolt = <1050000 1050000 1225000>;
+ clock-latency-ns = <320000>;
+ };
+
+ opp-600000000 {
+ opp-hz = /bits/ 64 <600000000>;
+ opp-microvolt = <1050000 1050000 1225000>;
+ clock-latency-ns = <320000>;
+ opp-suspend;
+ };
+
+ opp-800000000 {
+ opp-hz = /bits/ 64 <800000000>;
+ opp-microvolt = <1150000 1125000 1225000>;
+ clock-latency-ns = <320000>;
+ };
+
+ opp-1000000002 {
+ opp-hz = /bits/ 64 <1000000002>;
+ opp-microvolt = <1250000 1225000 1300000>;
+ clock-latency-ns = <320000>;
+ };
+ };
+
clocks {
main_xtal: clock-mainxtal {
compatible = "fixed-clock";
--
2.34.1
^ permalink raw reply related
* [PATCH v2 05/12] nvmem: microchip-otpc: add tag-based packet lookup
From: Varshini Rajendran @ 2026-06-23 10:59 UTC (permalink / raw)
To: ehristev, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt,
nicolas.ferre, alexandre.belloni, claudiu.beznea, srini,
linux-iio, devicetree, linux-arm-kernel, linux-kernel
Cc: varshini.rajendran
In-Reply-To: <20260623105944.128840-1-varshini.rajendran@microchip.com>
Add support for accessing OTP packets by their 4-byte ASCII tag while
preserving backward compatibility with the existing ID-based lookup.
The OTP memory layout can vary across devices and may change over time,
making the packet ID approach unreliable when the memory map is not
known in advance. The packet tag provides a reliable way to identify
and access packets without prior knowledge of the OTP memory layout.
Two offset encoding are now supported:
1. Legacy ID-based: offset = OTP_PKT(id) = id * 4
Used in DT as: reg = <OTP_PKT(1) 76>;
2. TAG-based: offset = 4-byte ASCII packet tag
Used in DT as: reg = <0x41435354 0x4c>; (tag "ACST")
The driver resolves offsets matching valid legacy selectors (multiples
of 4 within the packet count) through ID lookup, falling back to tag
lookup for other values. This ensures existing device trees continue
to work while enabling new tag-based access.
During probe, packet meta data including the tag is read and cached.
The driver also validates OTP memory accessibility and emulation mode
status. When the boot packet is not configured, emulation mode allows
access to the other packets. When both are not available an
informational message is logged.
The stride of the nvmem memory is set to 1 in order to support tag based
offsets, comment in the header file is updated accordingly.
Signed-off-by: Varshini Rajendran <varshini.rajendran@microchip.com>
---
drivers/nvmem/microchip-otpc.c | 142 ++++++++++++++++--
.../nvmem/microchip,sama7g5-otpc.h | 4 +-
2 files changed, 135 insertions(+), 11 deletions(-)
diff --git a/drivers/nvmem/microchip-otpc.c b/drivers/nvmem/microchip-otpc.c
index df979e8549fd..cbb4822a97c0 100644
--- a/drivers/nvmem/microchip-otpc.c
+++ b/drivers/nvmem/microchip-otpc.c
@@ -18,16 +18,20 @@
#define MCHP_OTPC_CR_READ BIT(6)
#define MCHP_OTPC_MR (0x4)
#define MCHP_OTPC_MR_ADDR GENMASK(31, 16)
+#define MCHP_OTPC_MR_EMUL BIT(7)
#define MCHP_OTPC_AR (0x8)
#define MCHP_OTPC_SR (0xc)
#define MCHP_OTPC_SR_READ BIT(6)
#define MCHP_OTPC_HR (0x20)
#define MCHP_OTPC_HR_SIZE GENMASK(15, 8)
+#define MCHP_OTPC_HR_PACKET_TYPE GENMASK(2, 0)
#define MCHP_OTPC_DR (0x24)
#define MCHP_OTPC_NAME "mchp-otpc"
#define MCHP_OTPC_SIZE (11 * 1024)
+#define PACKET_TYPE_REGULAR 1
+
/**
* struct mchp_otpc - OTPC private data structure
* @base: base address
@@ -47,11 +51,15 @@ struct mchp_otpc {
* @list: list head
* @id: packet ID
* @offset: packet offset (in words) in OTP memory
+ * @type: type of the packet
+ * @tag: 4-byte ASCII tag of the packet
*/
struct mchp_otpc_packet {
struct list_head list;
u32 id;
u32 offset;
+ u32 type;
+ u32 tag;
};
static struct mchp_otpc_packet *mchp_otpc_id_to_packet(struct mchp_otpc *otpc,
@@ -70,6 +78,55 @@ static struct mchp_otpc_packet *mchp_otpc_id_to_packet(struct mchp_otpc *otpc,
return NULL;
}
+/**
+ * mchp_otpc_tag_to_packet() - find packet by tag
+ * @otpc: OTPC private data
+ * @tag: 4-byte ASCII tag to search for
+ *
+ * Return: pointer to packet if found, NULL otherwise
+ */
+static struct mchp_otpc_packet *mchp_otpc_tag_to_packet(struct mchp_otpc *otpc,
+ u32 tag)
+{
+ struct mchp_otpc_packet *packet;
+
+ list_for_each_entry(packet, &otpc->packets, list) {
+ if (packet->tag == tag)
+ return packet;
+ }
+
+ return NULL;
+}
+
+/**
+ * mchp_otpc_resolve_packet() - resolve offset to packet
+ * @otpc: OTPC private data
+ * @off: NVMEM offset (legacy ID-based or TAG-based)
+ *
+ * Legacy offsets (multiples of 4 within valid ID range) are resolved
+ * through ID lookup. Other offsets are treated as 4-byte ASCII tags.
+ *
+ * Return: pointer to packet if found, NULL otherwise
+ */
+static struct mchp_otpc_packet *mchp_otpc_resolve_packet(struct mchp_otpc *otpc,
+ u32 off)
+{
+ /*
+ * Legacy id based packet access: offset = id * 4
+ * Inside the driver we use continuous unsigned integer numbers
+ * for packet id, thus divide off by 4 before passing it to
+ * mchp_otpc_id_to_packet().
+ */
+
+ if (!(off % 4) && (off / 4) < otpc->npackets)
+ return mchp_otpc_id_to_packet(otpc, off / 4);
+
+ /*
+ * TAG-based packet access: offset is a 4-byte ASCII tag
+ */
+ return mchp_otpc_tag_to_packet(otpc, off);
+}
+
static int mchp_otpc_prepare_read(struct mchp_otpc *otpc,
unsigned int offset)
{
@@ -140,8 +197,29 @@ static int mchp_otpc_prepare_read(struct mchp_otpc *otpc,
* offset returned by hardware.
*
* For this, the read function will return the first requested bytes in the
- * packet. The user will have to be aware of the memory footprint before doing
- * the read request.
+ * packet.
+ *
+ * Two offset encoding are supported:
+ *
+ * 1. Legacy ID-based: offset = OTP_PKT(id) = id * 4
+ * Used in DT as: reg = <OTP_PKT(1) 76>;
+ * 2. TAG-based: offset = 4-byte ASCII packet tag
+ * Used in DT as: reg = <0x41435354 0x4c>; (tag "ACST")
+ *
+ * To use the legacy ID based packet lookup the user will have to be aware of
+ * the memory footprint before doing the read request.
+ *
+ * But by using the TAG based packet lookup, the user won't have to be aware
+ * of the memory footprint before doing the read request since this driver has
+ * it abstracted and taken care of.
+ *
+ * Practically, there is no way of knowing the mapping of the OTP memory table
+ * in advance for every device. But by using the packet tag - the identifier
+ * ASCII value, the packets can be recognized without being aware of the
+ * flashed OTP memory map table and the payload can be acquired reliably.
+ *
+ * While the legacy ID based lookup is still supported, TAG based approach is
+ * recommended.
*/
static int mchp_otpc_read(void *priv, unsigned int off, void *val,
size_t bytes)
@@ -154,12 +232,11 @@ static int mchp_otpc_read(void *priv, unsigned int off, void *val,
int ret, payload_size;
/*
- * We reach this point with off being multiple of stride = 4 to
- * be able to cross the subsystem. Inside the driver we use continuous
- * unsigned integer numbers for packet id, thus divide off by 4
- * before passing it to mchp_otpc_id_to_packet().
+ * From this point the offset has to be translated into the actual
+ * packet. For this we traverse the table of contents stored in a list
+ * "packet" based on the access type - packet id or tag.
*/
- packet = mchp_otpc_id_to_packet(otpc, off / 4);
+ packet = mchp_otpc_resolve_packet(otpc, off);
if (!packet)
return -EINVAL;
offset = packet->offset;
@@ -190,6 +267,29 @@ static int mchp_otpc_read(void *priv, unsigned int off, void *val,
return 0;
}
+/**
+ * mchp_otpc_read_packet_tag() - read tag from packet payload
+ * @otpc: OTPC private data
+ * @offset: packet offset in OTP memory
+ * @val: pointer to store the tag value
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+static int mchp_otpc_read_packet_tag(struct mchp_otpc *otpc, unsigned int offset,
+ unsigned int *val)
+{
+ int ret;
+
+ ret = mchp_otpc_prepare_read(otpc, offset);
+ if (ret)
+ return ret;
+
+ writel_relaxed(0, otpc->base + MCHP_OTPC_AR);
+ *val = readl_relaxed(otpc->base + MCHP_OTPC_DR);
+
+ return 0;
+}
+
static int mchp_otpc_init_packets_list(struct mchp_otpc *otpc, u32 *size)
{
struct mchp_otpc_packet *packet;
@@ -215,6 +315,17 @@ static int mchp_otpc_init_packets_list(struct mchp_otpc *otpc, u32 *size)
packet->id = id++;
packet->offset = word_pos;
+ packet->type = FIELD_GET(MCHP_OTPC_HR_PACKET_TYPE, word);
+
+ if (packet->type == PACKET_TYPE_REGULAR) {
+ ret = mchp_otpc_read_packet_tag(otpc, packet->offset,
+ &packet->tag);
+ if (ret)
+ return ret;
+ } else {
+ packet->tag = 0;
+ }
+
INIT_LIST_HEAD(&packet->list);
list_add_tail(&packet->list, &otpc->packets);
@@ -236,7 +347,7 @@ static struct nvmem_config mchp_nvmem_config = {
.type = NVMEM_TYPE_OTP,
.read_only = true,
.word_size = 4,
- .stride = 4,
+ .stride = 1,
.reg_read = mchp_otpc_read,
};
@@ -244,8 +355,9 @@ static int mchp_otpc_probe(struct platform_device *pdev)
{
struct nvmem_device *nvmem;
struct mchp_otpc *otpc;
- u32 size;
+ u32 size, tmp;
int ret;
+ bool emul_enable;
otpc = devm_kzalloc(&pdev->dev, sizeof(*otpc), GFP_KERNEL);
if (!otpc)
@@ -256,10 +368,22 @@ static int mchp_otpc_probe(struct platform_device *pdev)
return PTR_ERR(otpc->base);
otpc->dev = &pdev->dev;
+
+ tmp = readl_relaxed(otpc->base + MCHP_OTPC_MR);
+ emul_enable = tmp & MCHP_OTPC_MR_EMUL;
+ if (emul_enable)
+ dev_info(otpc->dev, "Emulation mode enabled\n");
+
ret = mchp_otpc_init_packets_list(otpc, &size);
if (ret)
return ret;
+ if (!size) {
+ dev_warn(otpc->dev, "Cannot access OTP memory\n");
+ if (!emul_enable)
+ dev_info(otpc->dev, "Boot packet not programmed and emulation mode disabled\n");
+ }
+
mchp_nvmem_config.dev = otpc->dev;
mchp_nvmem_config.add_legacy_fixed_of_cells = true;
mchp_nvmem_config.size = size;
diff --git a/include/dt-bindings/nvmem/microchip,sama7g5-otpc.h b/include/dt-bindings/nvmem/microchip,sama7g5-otpc.h
index f570b23165a2..5f72e75ad091 100644
--- a/include/dt-bindings/nvmem/microchip,sama7g5-otpc.h
+++ b/include/dt-bindings/nvmem/microchip,sama7g5-otpc.h
@@ -4,8 +4,8 @@
#define _DT_BINDINGS_NVMEM_MICROCHIP_OTPC_H
/*
- * Need to have it as a multiple of 4 as NVMEM memory is registered with
- * stride = 4.
+ * Need to have it as a multiple of 4 for the legacy id based packet
+ * access.
*/
#define OTP_PKT(id) ((id) * 4)
--
2.34.1
^ permalink raw reply related
* [PATCH v2 04/12] dt-bindings: nvmem: microchip,sama7g5-otpc: add sama7d65 and dt node example
From: Varshini Rajendran @ 2026-06-23 10:59 UTC (permalink / raw)
To: ehristev, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt,
nicolas.ferre, alexandre.belloni, claudiu.beznea, srini,
linux-iio, devicetree, linux-arm-kernel, linux-kernel
Cc: varshini.rajendran
In-Reply-To: <20260623105944.128840-1-varshini.rajendran@microchip.com>
Add support for sama7d65 and a dt node example that shows tag can be used
to reference a packet stored in the OTP memory.
Signed-off-by: Varshini Rajendran <varshini.rajendran@microchip.com>
---
.../nvmem/microchip,sama7g5-otpc.yaml | 28 +++++++++++++++++--
1 file changed, 25 insertions(+), 3 deletions(-)
diff --git a/Documentation/devicetree/bindings/nvmem/microchip,sama7g5-otpc.yaml b/Documentation/devicetree/bindings/nvmem/microchip,sama7g5-otpc.yaml
index cc25f2927682..3cc16b0044a6 100644
--- a/Documentation/devicetree/bindings/nvmem/microchip,sama7g5-otpc.yaml
+++ b/Documentation/devicetree/bindings/nvmem/microchip,sama7g5-otpc.yaml
@@ -20,9 +20,15 @@ allOf:
properties:
compatible:
- items:
- - const: microchip,sama7g5-otpc
- - const: syscon
+ oneOf:
+ - items:
+ - const: microchip,sama7g5-otpc
+ - const: syscon
+ - items:
+ - enum:
+ - microchip,sama7d65-otpc
+ - const: microchip,sama7g5-otpc
+ - const: syscon
reg:
maxItems: 1
@@ -48,4 +54,20 @@ examples:
};
};
+ - |
+ otp_controller: efuse@e8c00000 {
+ compatible = "microchip,sama7d65-otpc", "microchip,sama7g5-otpc", "syscon";
+ reg = <0xe8c00000 0x100>;
+
+ nvmem-layout {
+ compatible = "fixed-layout";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ temp_calib: calib@41435354 {
+ reg = <0x41435354 0x2c>; /* Temp calib data packet TAG */
+ };
+ };
+ };
+
...
--
2.34.1
^ permalink raw reply related
* [PATCH v2 03/12] iio: adc: at91-sama5d2_adc: adapt the driver for sama7d65
From: Varshini Rajendran @ 2026-06-23 10:59 UTC (permalink / raw)
To: ehristev, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt,
nicolas.ferre, alexandre.belloni, claudiu.beznea, srini,
linux-iio, devicetree, linux-arm-kernel, linux-kernel
Cc: varshini.rajendran
In-Reply-To: <20260623105944.128840-1-varshini.rajendran@microchip.com>
Add support for sama7d65 ADC. The differences are highlighted with the
compatible. The calibration data layout is the main difference.
Signed-off-by: Varshini Rajendran <varshini.rajendran@microchip.com>
---
drivers/iio/adc/at91-sama5d2_adc.c | 31 ++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c
index b569d175f4c3..237d339f342a 100644
--- a/drivers/iio/adc/at91-sama5d2_adc.c
+++ b/drivers/iio/adc/at91-sama5d2_adc.c
@@ -530,6 +530,16 @@ static const struct at91_adc_temp_calib_layout sama7g5_temp_calib = {
.p1_div = 1,
};
+static const struct at91_adc_temp_calib_layout sama7d65_temp_calib = {
+ .tag_idx = 1,
+ .p1_idx = 3,
+ .p4_idx = 2,
+ .p6_idx = 5,
+ .min_len = 11,
+ .p1_mul = 1,
+ .p1_div = 1000,
+};
+
/* Temperature sensor calibration - Vtemp voltage sensitivity to temperature. */
#define AT91_ADC_TS_VTEMP_DT (2080U)
@@ -768,6 +778,24 @@ static const struct at91_adc_platform sama7g5_platform = {
.temp_calib_layout = &sama7g5_temp_calib,
};
+static const struct at91_adc_platform sama7d65_platform = {
+ .layout = &sama7g5_layout,
+ .adc_channels = &at91_sama7g5_adc_channels,
+ .nr_channels = AT91_SAMA7G5_SINGLE_CHAN_CNT +
+ AT91_SAMA7G5_DIFF_CHAN_CNT +
+ AT91_SAMA7G5_TEMP_CHAN_CNT,
+ .max_channels = ARRAY_SIZE(at91_sama7g5_adc_channels),
+ .max_index = AT91_SAMA7G5_MAX_CHAN_IDX,
+ .hw_trig_cnt = AT91_SAMA7G5_HW_TRIG_CNT,
+ .osr_mask = GENMASK(18, 16),
+ .oversampling_avail = { 1, 4, 16, 64, 256, },
+ .oversampling_avail_no = 5,
+ .chan_realbits = 16,
+ .temp_sensor = true,
+ .temp_chan = AT91_SAMA7G5_ADC_TEMP_CHANNEL,
+ .temp_calib_layout = &sama7d65_temp_calib,
+};
+
static int at91_adc_chan_xlate(struct iio_dev *indio_dev, int chan)
{
int i;
@@ -2638,6 +2666,9 @@ static const struct of_device_id at91_adc_dt_match[] = {
}, {
.compatible = "microchip,sama7g5-adc",
.data = (const void *)&sama7g5_platform,
+ }, {
+ .compatible = "microchip,sama7d65-adc",
+ .data = (const void *)&sama7d65_platform,
}, {
/* sentinel */
}
--
2.34.1
^ permalink raw reply related
* [PATCH v2 02/12] iio: adc: at91-sama5d2_adc: rework temp calibration layout handling
From: Varshini Rajendran @ 2026-06-23 10:59 UTC (permalink / raw)
To: ehristev, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt,
nicolas.ferre, alexandre.belloni, claudiu.beznea, srini,
linux-iio, devicetree, linux-arm-kernel, linux-kernel
Cc: varshini.rajendran
In-Reply-To: <20260623105944.128840-1-varshini.rajendran@microchip.com>
Extend support to handle different temperature calibration layouts.
Add a temperature calibration data layout structure to describe indexes
of the factors P1, P4, P6, tag, minimum length of the packet and the
scaling factors for P1 (mul, div) which are SoC-specific instead of the
older non scalable id structure. This helps handle the differences in the
same function flow and prepare the calibration data to be applied. Add
additional condition to validate the calibration data read from the
NVMEM cell using the TAG of the packet.
Use cleanup helpers for NVMEM data buffer wherever applicable.
Signed-off-by: Varshini Rajendran <varshini.rajendran@microchip.com>
---
drivers/iio/adc/at91-sama5d2_adc.c | 85 ++++++++++++++++++++----------
1 file changed, 58 insertions(+), 27 deletions(-)
diff --git a/drivers/iio/adc/at91-sama5d2_adc.c b/drivers/iio/adc/at91-sama5d2_adc.c
index 255970b2e747..b569d175f4c3 100644
--- a/drivers/iio/adc/at91-sama5d2_adc.c
+++ b/drivers/iio/adc/at91-sama5d2_adc.c
@@ -445,6 +445,28 @@ static const struct at91_adc_reg_layout sama7g5_layout = {
#define at91_adc_writel(st, reg, val) \
writel_relaxed(val, (st)->base + (st)->soc_info.platform->layout->reg)
+#define AT91_TEMP_CALIB_TAG_ACST 0x41435354
+
+/**
+ * struct at91_adc_temp_calib_layout - temperature calibration packet layout
+ * @tag_idx: index of Packet tag in the NVMEM cell buffer
+ * @p1_idx: index of FT1_TEMP, equivalent to P1 in the NVMEM cell buffer
+ * @p4_idx: index of FT1_VPAT, equivalent to P4 in the NVMEM cell buffer
+ * @p6_idx: index of FT2_VBG, equivalent to P6 in the NVMEM cell buffer
+ * @min_len: minimum number of u32 words expected in the NVMEM cell buffer
+ * @p1_mul: multiplier applied to P1 to convert to millicelcius
+ * @p1_div: divider applied to P1 to convert to millicelcius
+ */
+struct at91_adc_temp_calib_layout {
+ unsigned int tag_idx;
+ unsigned int p1_idx;
+ unsigned int p4_idx;
+ unsigned int p6_idx;
+ unsigned int min_len;
+ unsigned int p1_mul;
+ unsigned int p1_div;
+};
+
/**
* struct at91_adc_platform - at91-sama5d2 platform information struct
* @layout: pointer to the reg layout struct
@@ -464,6 +486,7 @@ static const struct at91_adc_reg_layout sama7g5_layout = {
* @chan_realbits: realbits for registered channels
* @temp_chan: temperature channel index
* @temp_sensor: temperature sensor supported
+ * @temp_calib_layout: temperature calibration packet layout
*/
struct at91_adc_platform {
const struct at91_adc_reg_layout *layout;
@@ -481,6 +504,7 @@ struct at91_adc_platform {
unsigned int chan_realbits;
unsigned int temp_chan;
bool temp_sensor;
+ const struct at91_adc_temp_calib_layout *temp_calib_layout;
};
/**
@@ -496,18 +520,14 @@ struct at91_adc_temp_sensor_clb {
u32 p6;
};
-/**
- * enum at91_adc_ts_clb_idx - calibration indexes in NVMEM buffer
- * @AT91_ADC_TS_CLB_IDX_P1: index for P1
- * @AT91_ADC_TS_CLB_IDX_P4: index for P4
- * @AT91_ADC_TS_CLB_IDX_P6: index for P6
- * @AT91_ADC_TS_CLB_IDX_MAX: max index for temperature calibration packet in OTP
- */
-enum at91_adc_ts_clb_idx {
- AT91_ADC_TS_CLB_IDX_P1 = 2,
- AT91_ADC_TS_CLB_IDX_P4 = 5,
- AT91_ADC_TS_CLB_IDX_P6 = 7,
- AT91_ADC_TS_CLB_IDX_MAX = 19,
+static const struct at91_adc_temp_calib_layout sama7g5_temp_calib = {
+ .tag_idx = 1,
+ .p1_idx = 2,
+ .p4_idx = 5,
+ .p6_idx = 7,
+ .min_len = 19,
+ .p1_mul = 1000,
+ .p1_div = 1,
};
/* Temperature sensor calibration - Vtemp voltage sensitivity to temperature. */
@@ -745,6 +765,7 @@ static const struct at91_adc_platform sama7g5_platform = {
.chan_realbits = 16,
.temp_sensor = true,
.temp_chan = AT91_SAMA7G5_ADC_TEMP_CHANNEL,
+ .temp_calib_layout = &sama7g5_temp_calib,
};
static int at91_adc_chan_xlate(struct iio_dev *indio_dev, int chan)
@@ -2251,13 +2272,19 @@ static int at91_adc_temp_sensor_init(struct at91_adc_state *st,
{
struct at91_adc_temp_sensor_clb *clb = &st->soc_info.temp_sensor_clb;
struct nvmem_cell *temp_calib;
- u32 *buf;
+ const struct at91_adc_temp_calib_layout *layout;
+ void *cell_data;
+ u32 *buf __free(kfree) = NULL;
size_t len;
int ret = 0;
if (!st->soc_info.platform->temp_sensor)
return 0;
+ layout = st->soc_info.platform->temp_calib_layout;
+ if (!layout || !layout->p1_div)
+ return -EINVAL;
+
/* Get the calibration data from NVMEM. */
temp_calib = nvmem_cell_get(dev, "temperature_calib");
if (IS_ERR(temp_calib)) {
@@ -2267,31 +2294,35 @@ static int at91_adc_temp_sensor_init(struct at91_adc_state *st,
return ret;
}
- buf = nvmem_cell_read(temp_calib, &len);
+ cell_data = nvmem_cell_read(temp_calib, &len);
nvmem_cell_put(temp_calib);
- if (IS_ERR(buf)) {
+ if (IS_ERR(cell_data)) {
dev_err(dev, "Failed to read calibration data!\n");
- return PTR_ERR(buf);
+ return PTR_ERR(cell_data);
}
- if (len < AT91_ADC_TS_CLB_IDX_MAX * 4) {
+
+ buf = cell_data;
+
+ if (len < layout->min_len * sizeof(*buf) ||
+ buf[layout->tag_idx] != AT91_TEMP_CALIB_TAG_ACST) {
dev_err(dev, "Invalid calibration data!\n");
- ret = -EINVAL;
- goto free_buf;
+ return -EINVAL;
}
/* Store calibration data for later use. */
- clb->p1 = buf[AT91_ADC_TS_CLB_IDX_P1];
- clb->p4 = buf[AT91_ADC_TS_CLB_IDX_P4];
- clb->p6 = buf[AT91_ADC_TS_CLB_IDX_P6];
+ clb->p1 = buf[layout->p1_idx];
+ clb->p4 = buf[layout->p4_idx];
+ clb->p6 = buf[layout->p6_idx];
/*
- * We prepare here the conversion to milli to avoid doing it on hotpath.
+ * Here we prepare the conversion to milli to avoid doing it on hotpath.
+ * The p1 value is multiplied and divided with a scaling factor as per
+ * the SoC storage format described by per-platform calibration layout.
*/
- clb->p1 = clb->p1 * 1000;
+ clb->p1 *= layout->p1_mul;
+ clb->p1 /= layout->p1_div;
-free_buf:
- kfree(buf);
- return ret;
+ return 0;
}
static int at91_adc_probe(struct platform_device *pdev)
--
2.34.1
^ permalink raw reply related
* [PATCH v2 01/12] dt-bindings: iio: adc: at91-sama5d2: document sama7d65
From: Varshini Rajendran @ 2026-06-23 10:59 UTC (permalink / raw)
To: ehristev, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt,
nicolas.ferre, alexandre.belloni, claudiu.beznea, srini,
linux-iio, devicetree, linux-arm-kernel, linux-kernel
Cc: varshini.rajendran, Krzysztof Kozlowski
In-Reply-To: <20260623105944.128840-1-varshini.rajendran@microchip.com>
Add dt-binding documentation for sama7d65 ADC.
Signed-off-by: Varshini Rajendran <varshini.rajendran@microchip.com>
Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
---
Documentation/devicetree/bindings/iio/adc/atmel,sama5d2-adc.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/iio/adc/atmel,sama5d2-adc.yaml b/Documentation/devicetree/bindings/iio/adc/atmel,sama5d2-adc.yaml
index 4817b840977a..e8a65fdcd018 100644
--- a/Documentation/devicetree/bindings/iio/adc/atmel,sama5d2-adc.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/atmel,sama5d2-adc.yaml
@@ -15,6 +15,7 @@ properties:
- atmel,sama5d2-adc
- microchip,sam9x60-adc
- microchip,sama7g5-adc
+ - microchip,sama7d65-adc
reg:
maxItems: 1
--
2.34.1
^ permalink raw reply related
* [PATCH v2 00/12] Add thermal management support for sama7d65
From: Varshini Rajendran @ 2026-06-23 10:59 UTC (permalink / raw)
To: ehristev, jic23, dlechner, nuno.sa, andy, robh, krzk+dt, conor+dt,
nicolas.ferre, alexandre.belloni, claudiu.beznea, srini,
linux-iio, devicetree, linux-arm-kernel, linux-kernel
Cc: varshini.rajendran
Apologies for the significant delay in following up this series.
Thank you for your patience and the earlier reviews.
This v2 reworks the series based on the feedback received on v1.
The thermal management system of sama7d65 includes:
- Temperature sensor as a part of ADC channel
- Temperature calibration data retrieved from the OTP memory for
improved accuracy of the readings
- DVFS implementation
- Thermal system with DVFS as cooling cell.
This patch series adds support for the following:
- Tag-based packet lookup for the NVMEM OTPC driver while preserving
backward compatibility with existing ID-based access
- Temperature calibration layout handling in the ADC driver to support
different SoC-specific calibration data formats
- ADC driver adaptation for sama7d65
- DT nodes for OTP, ADC, temperature sensor, and thermal zones for
sama7d65
Changes in v2:
- Preserved backward compatibility with ID-based packet lookup to
avoid breaking existing users
- Removed sama7g5 DTS changes (not needed with backward compatible
driver - will be sent later to update to the new access method)
- Preserved the packet data structure returned not to break the
consumers
- Reworked ADC driver to use a calibration layout structure instead of
hardcoded indexes, for scalability
- Fixed kernel-doc Return section
- Removed stray blank line in mchp_otpc_read()
- Removed unnecessary UL suffix in writel_relaxed()
- Dropped unused packet types
- Fixed stray spaces before exclamation marks in error messages
- Added ASCII representation to TAG macro definition
- Removed odd MAX enum with trailing comma and refactored
- Moved DTS patches to the end of series
- Used cleanup.h helpers for NVMEM data buffer handling in ADC driver
- Combined multiple v1 patches into logical units
- Used correct subject prefixes for dt-bindings patches
- Used fixed-layout NVMEM syntax for sama7d65 DTS and binding
instead of deprecated syntax
- Added cpu-supply linkage for proper DVFS voltage scaling
- Updated stale stride=4 comment in dt-bindings header
Link to v1: https://lore.kernel.org/linux-arm-kernel/20250804100219.63325-1-varshini.rajendran@microchip.com/
Varshini Rajendran (12):
dt-bindings: iio: adc: at91-sama5d2: document sama7d65
iio: adc: at91-sama5d2_adc: rework temp calibration layout handling
iio: adc: at91-sama5d2_adc: adapt the driver for sama7d65
dt-bindings: nvmem: microchip,sama7g5-otpc: add sama7d65 and dt node
example
nvmem: microchip-otpc: add tag-based packet lookup
ARM: dts: microchip: sama7d65: add cpu opps
ARM: dts: microchip: sama7d65: Add ADC node
ARM: dts: microchip: sama7d65_curiosity: Enable ADC, DVFS
ARM: dts: microchip: sama7d65: add otpc node
ARM: dts: microchip: sama7d65: add cells for temperature calibration
ARM: dts: microchip: sama7d65: add temperature sensor
ARM: dts: microchip: sama7d65: add thermal zones node
.../bindings/iio/adc/atmel,sama5d2-adc.yaml | 1 +
.../nvmem/microchip,sama7g5-otpc.yaml | 28 +++-
.../dts/microchip/at91-sama7d65_curiosity.dts | 27 ++++
arch/arm/boot/dts/microchip/sama7d65.dtsi | 132 ++++++++++++++++
drivers/iio/adc/at91-sama5d2_adc.c | 116 ++++++++++----
drivers/nvmem/microchip-otpc.c | 142 ++++++++++++++++--
.../nvmem/microchip,sama7g5-otpc.h | 4 +-
7 files changed, 409 insertions(+), 41 deletions(-)
--
2.34.1
^ permalink raw reply
* Re: [PATCH v3 4/4] clk: rockchip: rk3588: add GATE_GRF clocks for I2S MCLK output to IO
From: Diederik de Haas @ 2026-06-23 10:59 UTC (permalink / raw)
To: Daniele Briguglio, Michael Turquette, Stephen Boyd, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner
Cc: Nicolas Frattaroli, linux-clk, devicetree, linux-arm-kernel,
linux-rockchip, linux-kernel, Ricardo Pardini
In-Reply-To: <20260320-rk3588-mclk-gate-grf-v3-4-980338eacd2c@superkali.me>
Hi,
On Fri Mar 20, 2026 at 11:34 AM CET, Daniele Briguglio wrote:
> The I2S MCLK outputs on RK3588 are gated by bits in the SYS_GRF
> register SOC_CON6 (offset 0x318). These gates control whether the
> internal CRU MCLK signals reach the external IO pins connected to
> audio codecs.
>
> The kernel should explicitly manage these gates so that audio
> functionality does not depend on bootloader register state. This is
> analogous to what was done for RK3576 SAI MCLK outputs [1].
>
> Register the SYS_GRF as an auxiliary GRF with grf_type_sys in the
> early clock init, and add GATE_GRF entries for all four I2S MCLK
> output gates:
>
> - I2S0_8CH_MCLKOUT_TO_IO (bit 0)
> - I2S1_8CH_MCLKOUT_TO_IO (bit 1)
> - I2S2_2CH_MCLKOUT_TO_IO (bit 2)
> - I2S3_2CH_MCLKOUT_TO_IO (bit 7)
>
> Board DTS files that need MCLK on an IO pin can reference these
> clocks, e.g.:
>
> clocks = <&cru I2S0_8CH_MCLKOUT_TO_IO>;
>
> Tested on the Youyeetoo YY3588 (RK3588) with an ES8388 codec on I2S0.
Doesn't this break audio on a lot of RK3588 based boards?
I have a kernel with this patch set and since then analog audio on my NanoPC-T6
LTS and my WIP NanoPC-T6 Plus stopped working.
Until I did s/I2S0_8CH_MCLKOUT/I2S0_8CH_MCLKOUT_TO_IO/ in my dts[i] files.
And I wouldn't be surprised if the same thing applies to other RK3588 based
boards? The same dtb file with a 7.1 kernel, without this patch set, works.
Cheers,
Diederik
> [1] https://lore.kernel.org/r/20250305-rk3576-sai-v1-2-64e6cf863e9a@collabora.com/
>
> Reviewed-by: Nicolas Frattaroli <nicolas.frattaroli@collabora.com>
> Tested-by: Ricardo Pardini <ricardo@pardini.net>
> Signed-off-by: Daniele Briguglio <hello@superkali.me>
> ---
> drivers/clk/rockchip/clk-rk3588.c | 24 ++++++++++++++++++++++++
> 1 file changed, 24 insertions(+)
>
> diff --git a/drivers/clk/rockchip/clk-rk3588.c b/drivers/clk/rockchip/clk-rk3588.c
> index 1694223f4f84..2cc85fb5b2cc 100644
> --- a/drivers/clk/rockchip/clk-rk3588.c
> +++ b/drivers/clk/rockchip/clk-rk3588.c
> @@ -5,11 +5,14 @@
> */
>
> #include <linux/clk-provider.h>
> +#include <linux/mfd/syscon.h>
> #include <linux/of.h>
> +#include <linux/slab.h>
> #include <linux/of_address.h>
> #include <linux/platform_device.h>
> #include <linux/syscore_ops.h>
> #include <dt-bindings/clock/rockchip,rk3588-cru.h>
> +#include <soc/rockchip/rk3588_grf.h>
> #include "clk.h"
>
> #define RK3588_GRF_SOC_STATUS0 0x600
> @@ -892,6 +895,8 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
> RK3588_CLKGATE_CON(8), 0, GFLAGS),
> MUX(I2S2_2CH_MCLKOUT, "i2s2_2ch_mclkout", i2s2_2ch_mclkout_p, CLK_SET_RATE_PARENT,
> RK3588_CLKSEL_CON(30), 2, 1, MFLAGS),
> + GATE_GRF(I2S2_2CH_MCLKOUT_TO_IO, "i2s2_2ch_mclkout_to_io", "i2s2_2ch_mclkout",
> + 0, RK3588_SYSGRF_SOC_CON6, 2, GFLAGS, grf_type_sys),
>
> COMPOSITE(CLK_I2S3_2CH_SRC, "clk_i2s3_2ch_src", gpll_aupll_p, 0,
> RK3588_CLKSEL_CON(30), 8, 1, MFLAGS, 3, 5, DFLAGS,
> @@ -907,6 +912,8 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
> RK3588_CLKGATE_CON(8), 4, GFLAGS),
> MUX(I2S3_2CH_MCLKOUT, "i2s3_2ch_mclkout", i2s3_2ch_mclkout_p, CLK_SET_RATE_PARENT,
> RK3588_CLKSEL_CON(32), 2, 1, MFLAGS),
> + GATE_GRF(I2S3_2CH_MCLKOUT_TO_IO, "i2s3_2ch_mclkout_to_io", "i2s3_2ch_mclkout",
> + 0, RK3588_SYSGRF_SOC_CON6, 7, GFLAGS, grf_type_sys),
> GATE(PCLK_ACDCDIG, "pclk_acdcdig", "pclk_audio_root", 0,
> RK3588_CLKGATE_CON(7), 11, GFLAGS),
> GATE(HCLK_I2S0_8CH, "hclk_i2s0_8ch", "hclk_audio_root", 0,
> @@ -935,6 +942,8 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
> RK3588_CLKGATE_CON(7), 10, GFLAGS),
> MUX(I2S0_8CH_MCLKOUT, "i2s0_8ch_mclkout", i2s0_8ch_mclkout_p, CLK_SET_RATE_PARENT,
> RK3588_CLKSEL_CON(28), 2, 2, MFLAGS),
> + GATE_GRF(I2S0_8CH_MCLKOUT_TO_IO, "i2s0_8ch_mclkout_to_io", "i2s0_8ch_mclkout",
> + 0, RK3588_SYSGRF_SOC_CON6, 0, GFLAGS, grf_type_sys),
>
> GATE(HCLK_PDM1, "hclk_pdm1", "hclk_audio_root", 0,
> RK3588_CLKGATE_CON(9), 6, GFLAGS),
> @@ -2220,6 +2229,8 @@ static struct rockchip_clk_branch rk3588_early_clk_branches[] __initdata = {
> RK3588_PMU_CLKGATE_CON(2), 13, GFLAGS),
> MUX(I2S1_8CH_MCLKOUT, "i2s1_8ch_mclkout", i2s1_8ch_mclkout_p, CLK_SET_RATE_PARENT,
> RK3588_PMU_CLKSEL_CON(9), 2, 2, MFLAGS),
> + GATE_GRF(I2S1_8CH_MCLKOUT_TO_IO, "i2s1_8ch_mclkout_to_io", "i2s1_8ch_mclkout",
> + 0, RK3588_SYSGRF_SOC_CON6, 1, GFLAGS, grf_type_sys),
> GATE(PCLK_PMU1, "pclk_pmu1", "pclk_pmu0_root", CLK_IS_CRITICAL,
> RK3588_PMU_CLKGATE_CON(1), 0, GFLAGS),
> GATE(CLK_DDR_FAIL_SAFE, "clk_ddr_fail_safe", "clk_pmu0", CLK_IGNORE_UNUSED,
> @@ -2439,6 +2450,8 @@ static struct rockchip_clk_branch rk3588_clk_branches[] = {
> static void __init rk3588_clk_early_init(struct device_node *np)
> {
> struct rockchip_clk_provider *ctx;
> + struct rockchip_aux_grf *sys_grf_e;
> + struct regmap *sys_grf;
> unsigned long clk_nr_clks, max_clk_id1, max_clk_id2;
> void __iomem *reg_base;
>
> @@ -2479,6 +2492,17 @@ static void __init rk3588_clk_early_init(struct device_node *np)
> &rk3588_cpub1clk_data, rk3588_cpub1clk_rates,
> ARRAY_SIZE(rk3588_cpub1clk_rates));
>
> + /* Register SYS_GRF for I2S MCLK output to IO gate clocks */
> + sys_grf = syscon_regmap_lookup_by_compatible("rockchip,rk3588-sys-grf");
> + if (!IS_ERR(sys_grf)) {
> + sys_grf_e = kzalloc_obj(*sys_grf_e);
> + if (sys_grf_e) {
> + sys_grf_e->grf = sys_grf;
> + sys_grf_e->type = grf_type_sys;
> + hash_add(ctx->aux_grf_table, &sys_grf_e->node, grf_type_sys);
> + }
> + }
> +
> rockchip_clk_register_branches(ctx, rk3588_early_clk_branches,
> ARRAY_SIZE(rk3588_early_clk_branches));
>
^ permalink raw reply
* Re: [PATCH v2 2/3] powercap: qcom: Add SPEL powercap driver
From: Manaf Meethalavalappu Pallikunhi @ 2026-06-23 10:58 UTC (permalink / raw)
To: Konrad Dybcio, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Rafael J. Wysocki, Bjorn Andersson, Konrad Dybcio, Daniel Lezcano
Cc: Gaurav Kohli, linux-arm-msm, devicetree, linux-kernel, linux-pm
In-Reply-To: <e203221b-5de5-4cc3-b65a-a3545986a954@oss.qualcomm.com>
Hi Konrad,
On 6/22/2026 4:31 PM, Konrad Dybcio wrote:
> On 6/19/26 10:39 PM, Manaf Meethalavalappu Pallikunhi wrote:
>> The Qualcomm SoC Power and Electrical Limits (SPEL) provides hardware
>> based power monitoring and limiting capabilities for various power
>> domains including System, SoC, CPU clusters, GPU, and various other
>> subsystems.
>>
>> The driver integrates with the Linux powercap framework, exposing SPEL
>> capabilities through powercap sysfs interfaces.
>>
>> Signed-off-by: Manaf Meethalavalappu Pallikunhi <manaf.pallikunhi@oss.qualcomm.com>
>> ---
>
> [...]
>
>> +#include <linux/bitfield.h>
>> +#include <linux/device.h>
>> +#include <linux/io.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/mod_devicetable.h>
>> +#include <linux/mutex.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/powercap.h>
>> +#include <linux/slab.h>
>> +#include <linux/types.h>
>
> Please ensure all the includes are necessary
I already checked it and removed unused headers
>
>> +
>> +/* SPEL register bitmasks */
>> +#define ENERGY_STATUS_MASK GENMASK(31, 0)
>> +
>> +#define POWER_LIMIT_MASK GENMASK(14, 0)
>> +#define POWER_LIMIT_ENABLE BIT(31)
>> +
>> +#define TIME_WINDOW_MASK_L GENMASK(14, 0)
>> +#define TIME_WINDOW_MASK_H GENMASK(22, 16)
>
> Is BIT(15) not part of this?
>
> [...]
>
>> +/* Domain configuration */
>> +static const struct spel_domain_info domain_info[SPEL_DOMAIN_MAX] = {
>> + [SPEL_DOMAIN_SYS] = { "sys", 0x40 },
>> + [SPEL_DOMAIN_SOC] = { "soc", 0x00 },
>> + [SPEL_DOMAIN_CL0] = { "cl0", 0x5c },
>> + [SPEL_DOMAIN_CL1] = { "cl1", 0x60 },
>> + [SPEL_DOMAIN_CL2] = { "cl2", 0x64 },
>> + [SPEL_DOMAIN_IGPU] = { "igpu", 0x08 },
>> + [SPEL_DOMAIN_DGPU] = { "dgpu", 0x44 },
>> + [SPEL_DOMAIN_NSP] = { "nsp", 0x0c },
>> + [SPEL_DOMAIN_MMCX] = { "mmcx", 0x10 },
>> + [SPEL_DOMAIN_INFRA] = { "infra", 0x18 },
>> + [SPEL_DOMAIN_DRAM] = { "dram", 0x1c },
>> + [SPEL_DOMAIN_MDM] = { "mdm", 0x48 },
>> + [SPEL_DOMAIN_WLAN] = { "wlan", 0x4c },
>> + [SPEL_DOMAIN_USB1] = { "usb1", 0x50 },
>> + [SPEL_DOMAIN_USB2] = { "usb2", 0x54 },
>> + [SPEL_DOMAIN_USB3] = { "usb3", 0x58 },
>> +};
>
> I would expect that the names are going to stay common, but the offsets
Names also can be different here. For example, hawi, It has only subset
of these domain and it doesn't have dgpu, it has only "gpu". cpu domain
names also different there.
> will be different. This array should probably be called
> glymur_domain_info[]. We may have another LUT just for names of indices
ACK for glymur_domain_info.
> (i.e. [SPEL_DOMAIN_xxx] = "xxx")
>
>> +
>> +/**
>> + * struct spel_constraint_info - Power limit constraint information
>> + * @limit_offset: Register offset for power limit value
>> + * @time_window_offset: Register offset for time window
>> + * @supported_mask: Bit mask in capability register
>> + * @domain_id: Domain this constraint applies to
>> + * @pl_id: Power limit ID (PL1, PL2, etc.)
>> + */
>> +struct spel_constraint_info {
>> + u32 limit_offset;
>> + u32 time_window_offset;
>> + u32 supported_mask;
>> + enum spel_domain_type domain_id;
>> + int pl_id;
>> +};
>> +
>> +/* Constraint configuration */
>> +static const struct spel_constraint_info constraints[] = {
>> + /* SYS domain constraints */
>> + { 0x10, 0x70, BIT(0), SPEL_DOMAIN_SYS, POWER_LIMIT1 },
>> + { 0x14, 0x74, BIT(1), SPEL_DOMAIN_SYS, POWER_LIMIT2 },
>> + { 0x18, 0x78, BIT(2), SPEL_DOMAIN_SYS, POWER_LIMIT3 },
>> + { 0x1c, 0x7c, BIT(3), SPEL_DOMAIN_SYS, POWER_LIMIT4 },
>> + /* SoC domain constraints */
>> + { 0x00, 0x60, BIT(4), SPEL_DOMAIN_SOC, POWER_LIMIT1 },
>> + { 0x04, 0x64, BIT(5), SPEL_DOMAIN_SOC, POWER_LIMIT2 },
>> + { 0x08, 0x68, BIT(6), SPEL_DOMAIN_SOC, POWER_LIMIT3 },
>> + { 0x0c, 0x6c, BIT(7), SPEL_DOMAIN_SOC, POWER_LIMIT4 },
>> +};
>
> Is this specific to Glymur, or SPEL-wide?
So far, current targets share common spec and offsets for constraints.
>
> [...]
>
>> +/**
>> + * struct spel_system - SPEL system
>
> odd tab after '-'
ACK
>
> [...]
>
>> + case PL_LIMIT:
>> + new_val = spel_unit_xlate(sd, POWER_UNIT, value, 1);
>> + if (new_val > FIELD_MAX(POWER_LIMIT_MASK))
>> + return -EINVAL;
>> + reg_val = (reg_val & ~POWER_LIMIT_MASK) | FIELD_PREP(POWER_LIMIT_MASK, new_val);
>
> FIELD_MODIFY()
ACK
>
>> +
>> + /*
>> + * Enable/Disable PL based on the value:
>> + * - If value is 0, disable the PL (clear enable bit)
>> + * - If value is non-zero, enable the PL (set enable bit)
>> + */
>> + if (new_val == 0)
>> + reg_val &= ~POWER_LIMIT_ENABLE;
>> + else
>> + reg_val |= POWER_LIMIT_ENABLE;
>
> Likewise
ACK
>
>
>> +
>> + writel(reg_val, reg_addr);
>> + return 0;
>> +
>> + case PL_TIME_WINDOW:
>> + /*
>> + * Encode time window: upper 7 bits to [22:16], lower 15 bits to [14:0]
>> + */
>> + new_val = spel_unit_xlate(sd, TIME_UNIT, value, 1);
>> + if (new_val > TIME_WINDOW_MAX)
>> + return -EINVAL;
>> + /* Read-modify-write to preserve other bits */
>> + reg_val = (reg_val & ~(TIME_WINDOW_MASK_H | TIME_WINDOW_MASK_L)) |
>> + FIELD_PREP(TIME_WINDOW_MASK_H, new_val >> 15) |
>> + FIELD_PREP(TIME_WINDOW_MASK_L, new_val);
>
> Also here
ACK
>
> [...]
>
>> +static void spel_detect_powerlimit(struct spel_domain *sd)
>> +{
>> + struct spel_system *sp = sd->sp;
>> + u32 capabilities;
>> + int i, j;
>> +
>> + capabilities = readl(sp->config_base + LIMITS_CAPABILITY_OFFSET);
>> +
>> + /*
>> + * Detect power limits from hardware capabilities.
>> + * Start from index 1 (POWER_LIMIT2) since PL1 is always enabled in spel_init_domains().
>> + */
>> + for (i = 1; i < ARRAY_SIZE(pl_names); i++) {
>
> int i = POWER_LIMIT2
>
> (yeah, nowadays you can finally declare the iterator inside the loop
> in the kernel)
ACK for declaring the iterator inside loop, but assigning to
POWER_LIMIT2 is removed in this revision based on V1 comment.
Thanks,
Manaf
>
> Konrad
^ permalink raw reply
* [PATCH v4 12/12] iio: dac: ad5686: add gain control support
From: Rodrigo Alencar via B4 Relay @ 2026-06-23 10:55 UTC (permalink / raw)
To: Michael Auchter, linux, linux-iio, devicetree, linux-kernel,
linux-hardening
Cc: Michael Hennerich, Jonathan Cameron, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar
In-Reply-To: <20260623-ad5686-new-features-v4-0-28962a57db0f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Most of the supported devices rely on a GAIN pin to control a 2x
multiplier applied to the output voltage. Other devices, e.g. the
single-channel ones, provides a gain control through a bit field in
the control register. Some designs might have the GAIN pin hardwired
to VDD/VLOGIC or GND, which would have no "gain-gpios" device property,
being able to set "adi,range-double" if it is hardwired to VDD. The
vref_mv field is moved down in the struct ad5686_state, so that the
overall size increase is reduced.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/dac/ad5686.c | 122 +++++++++++++++++++++++++++++++++++++++++++++--
drivers/iio/dac/ad5686.h | 12 ++++-
2 files changed, 127 insertions(+), 7 deletions(-)
diff --git a/drivers/iio/dac/ad5686.c b/drivers/iio/dac/ad5686.c
index 4dc681eb077d..bad214b89257 100644
--- a/drivers/iio/dac/ad5686.c
+++ b/drivers/iio/dac/ad5686.c
@@ -15,10 +15,13 @@
#include <linux/export.h>
#include <linux/gpio/consumer.h>
#include <linux/kstrtox.h>
+#include <linux/math64.h>
#include <linux/module.h>
+#include <linux/property.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <linux/sysfs.h>
+#include <linux/units.h>
#include <linux/wordpart.h>
#include <linux/iio/buffer.h>
@@ -41,7 +44,8 @@ static int ad5310_control_sync(struct ad5686_state *st)
return ad5686_write(st, AD5686_CMD_CONTROL_REG, 0,
FIELD_PREP(AD5310_PD_MSK, pd_val & AD5686_PD_MSK) |
- FIELD_PREP(AD5310_REF_BIT_MSK, st->use_internal_vref ? 0 : 1));
+ FIELD_PREP(AD5310_REF_BIT_MSK, st->use_internal_vref ? 0 : 1) |
+ FIELD_PREP(AD5310_GAIN_BIT_MSK, st->double_scale ? 1 : 0));
}
static int ad5683_control_sync(struct ad5686_state *st)
@@ -50,7 +54,8 @@ static int ad5683_control_sync(struct ad5686_state *st)
return ad5686_write(st, AD5686_CMD_CONTROL_REG, 0,
FIELD_PREP(AD5683_PD_MSK, pd_val & AD5686_PD_MSK) |
- FIELD_PREP(AD5683_REF_BIT_MSK, st->use_internal_vref ? 0 : 1));
+ FIELD_PREP(AD5683_REF_BIT_MSK, st->use_internal_vref ? 0 : 1) |
+ FIELD_PREP(AD5683_GAIN_BIT_MSK, st->double_scale ? 1 : 0));
}
static inline unsigned int ad5686_pd_mask_shift(const struct iio_chan_spec *chan)
@@ -193,9 +198,14 @@ static int ad5686_read_raw(struct iio_dev *indio_dev,
GENMASK(chan->scan_type.realbits - 1, 0);
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
- *val = st->vref_mv;
- *val2 = chan->scan_type.realbits;
- return IIO_VAL_FRACTIONAL_LOG2;
+ if (st->double_scale) {
+ *val = st->scale_avail[2];
+ *val2 = st->scale_avail[3];
+ } else {
+ *val = st->scale_avail[0];
+ *val2 = st->scale_avail[1];
+ }
+ return IIO_VAL_INT_PLUS_NANO;
}
return -EINVAL;
}
@@ -207,6 +217,8 @@ static int ad5686_write_raw(struct iio_dev *indio_dev,
long mask)
{
struct ad5686_state *st = iio_priv(indio_dev);
+ bool double_scale;
+ int ret;
guard(mutex)(&st->lock);
@@ -217,6 +229,84 @@ static int ad5686_write_raw(struct iio_dev *indio_dev,
return ad5686_write(st, AD5686_CMD_WRITE_INPUT_N_UPDATE_N,
chan->address, val << chan->scan_type.shift);
+ case IIO_CHAN_INFO_SCALE:
+ if (val == st->scale_avail[0] && val2 == st->scale_avail[1])
+ double_scale = false;
+ else if (val == st->scale_avail[2] && val2 == st->scale_avail[3])
+ double_scale = true;
+ else
+ return -EINVAL;
+
+ if (st->double_scale == double_scale)
+ return 0; /* no change */
+
+ st->double_scale = double_scale;
+ switch (st->chip_info->regmap_type) {
+ case AD5310_REGMAP:
+ ret = ad5310_control_sync(st);
+ break;
+ case AD5683_REGMAP:
+ ret = ad5683_control_sync(st);
+ break;
+ case AD5686_REGMAP:
+ if (!st->gain_gpio) {
+ ret = -EINVAL;
+ break;
+ }
+
+ ret = gpiod_set_value_cansleep(st->gain_gpio,
+ st->double_scale ? 1 : 0);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ if (ret)
+ st->double_scale = !double_scale; /* revert on failure */
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad5686_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad5686_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ struct ad5686_state *st = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ *type = IIO_VAL_INT_PLUS_NANO;
+
+ if (st->chip_info->regmap_type == AD5686_REGMAP && !st->gain_gpio) {
+ /*
+ * GAIN pin is board-strapped, so only the current
+ * scale is available.
+ */
+ *vals = st->double_scale ? &st->scale_avail[2] :
+ &st->scale_avail[0];
+ *length = 2;
+ return IIO_AVAIL_LIST;
+ }
+
+ *vals = st->scale_avail;
+ *length = ARRAY_SIZE(st->scale_avail);
+ return IIO_AVAIL_LIST;
default:
return -EINVAL;
}
@@ -225,6 +315,8 @@ static int ad5686_write_raw(struct iio_dev *indio_dev,
static const struct iio_info ad5686_info = {
.read_raw = ad5686_read_raw,
.write_raw = ad5686_write_raw,
+ .write_raw_get_fmt = ad5686_write_raw_get_fmt,
+ .read_avail = ad5686_read_avail,
};
static const struct iio_chan_spec_ext_info ad5686_ext_info[] = {
@@ -246,6 +338,7 @@ static const struct iio_chan_spec_ext_info ad5686_ext_info[] = {
.channel = chan, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\
+ .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE),\
.address = addr, \
.scan_index = chan, \
.scan_type = { \
@@ -472,6 +565,15 @@ const struct ad5686_chip_info ad5679r_chip_info = {
};
EXPORT_SYMBOL_NS_GPL(ad5679r_chip_info, "IIO_AD5686");
+static void ad5686_init_scale_avail(struct ad5686_state *st)
+{
+ int realbits = st->chip_info->channels[0].scan_type.realbits;
+ s64 tmp = 2ULL * st->vref_mv * NANO >> realbits;
+
+ st->scale_avail[2] = div_s64_rem(tmp, NANO, &st->scale_avail[3]);
+ st->scale_avail[0] = div_s64_rem(tmp >> 1, NANO, &st->scale_avail[1]);
+}
+
static irqreturn_t ad5686_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
@@ -585,6 +687,16 @@ int ad5686_probe(struct device *dev,
return dev_err_probe(dev, PTR_ERR(st->ldac_gpio),
"Failed to get LDAC GPIO\n");
+ st->double_scale = device_property_read_bool(dev, "adi,range-double");
+ st->gain_gpio = devm_gpiod_get_optional(dev, "gain",
+ st->double_scale ? GPIOD_OUT_HIGH :
+ GPIOD_OUT_LOW);
+ if (IS_ERR(st->gain_gpio))
+ return dev_err_probe(dev, PTR_ERR(st->gain_gpio),
+ "Failed to get GAIN GPIO\n");
+
+ ad5686_init_scale_avail(st);
+
/* Initialize masks to all ones */
st->pwr_down_mask = ~0;
st->pwr_down_mode = ~0;
diff --git a/drivers/iio/dac/ad5686.h b/drivers/iio/dac/ad5686.h
index cb884334e94a..92012e1be48f 100644
--- a/drivers/iio/dac/ad5686.h
+++ b/drivers/iio/dac/ad5686.h
@@ -39,9 +39,11 @@
#define AD5686_CMD_CONTROL_REG 0x4
#define AD5686_CMD_READBACK_ENABLE_V2 0x5
+#define AD5310_GAIN_BIT_MSK BIT(7)
#define AD5310_REF_BIT_MSK BIT(8)
#define AD5310_PD_MSK GENMASK(10, 9)
+#define AD5683_GAIN_BIT_MSK BIT(11)
#define AD5683_REF_BIT_MSK BIT(12)
#define AD5683_PD_MSK GENMASK(14, 13)
@@ -125,9 +127,12 @@ extern const struct ad5686_chip_info ad5679r_chip_info;
* @chip_info: chip model specific constants, available modes etc
* @ops: bus specific operations
* @ldac_gpio: LDAC pin GPIO descriptor
- * @vref_mv: actual reference voltage used
+ * @gain_gpio: GAIN pin GPIO descriptor
* @pwr_down_mask: power down mask
* @pwr_down_mode: current power down mode
+ * @scale_avail: pre-calculated available scale values
+ * @vref_mv: actual reference voltage used
+ * @double_scale: flag to indicate the gain multiplier is applied
* @use_internal_vref: set to true if the internal reference voltage is used
* @lock: lock to protect access to state fields, which includes
* the data buffer during regmap ops
@@ -139,9 +144,12 @@ struct ad5686_state {
const struct ad5686_chip_info *chip_info;
const struct ad5686_bus_ops *ops;
struct gpio_desc *ldac_gpio;
- unsigned short vref_mv;
+ struct gpio_desc *gain_gpio;
unsigned int pwr_down_mask;
unsigned int pwr_down_mode;
+ int scale_avail[4];
+ unsigned short vref_mv;
+ bool double_scale;
bool use_internal_vref;
struct mutex lock;
void *bus_data;
--
2.43.0
^ permalink raw reply related
* [PATCH v4 11/12] iio: dac: ad5686: add triggered buffer support
From: Rodrigo Alencar via B4 Relay @ 2026-06-23 10:55 UTC (permalink / raw)
To: Michael Auchter, linux, linux-iio, devicetree, linux-kernel,
linux-hardening
Cc: Michael Hennerich, Jonathan Cameron, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar
In-Reply-To: <20260623-ad5686-new-features-v4-0-28962a57db0f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Implement trigger handler by leveraging the LDAC gpio to update all DAC
channels at once when it is available. Also, the multiple channel writes
can be flushed at once with the sync() operation.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/dac/Kconfig | 2 ++
drivers/iio/dac/ad5686.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 68 insertions(+)
diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
index 657c68e75542..5f14fcd780e2 100644
--- a/drivers/iio/dac/Kconfig
+++ b/drivers/iio/dac/Kconfig
@@ -240,6 +240,8 @@ config LTC2688
config AD5686
tristate
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
config AD5686_SPI
tristate "Analog Devices AD5686 and similar multi-channel DACs (SPI)"
diff --git a/drivers/iio/dac/ad5686.c b/drivers/iio/dac/ad5686.c
index db175e77b0b7..4dc681eb077d 100644
--- a/drivers/iio/dac/ad5686.c
+++ b/drivers/iio/dac/ad5686.c
@@ -21,7 +21,11 @@
#include <linux/sysfs.h>
#include <linux/wordpart.h>
+#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
#include "ad5686.h"
@@ -243,6 +247,7 @@ static const struct iio_chan_spec_ext_info ad5686_ext_info[] = {
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\
.address = addr, \
+ .scan_index = chan, \
.scan_type = { \
.sign = 'u', \
.realbits = (bits), \
@@ -467,6 +472,60 @@ const struct ad5686_chip_info ad5679r_chip_info = {
};
EXPORT_SYMBOL_NS_GPL(ad5679r_chip_info, "IIO_AD5686");
+static irqreturn_t ad5686_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct iio_buffer *buffer = indio_dev->buffer;
+ struct ad5686_state *st = iio_priv(indio_dev);
+ u16 val[AD5686_MAX_CHANNELS] = { };
+ unsigned int scan_count, ch, i;
+ bool async_update;
+ int ret;
+ u8 cmd;
+
+ ret = iio_pop_from_buffer(buffer, val);
+ if (ret) {
+ iio_trigger_notify_done(indio_dev->trig);
+ return IRQ_HANDLED;
+ }
+
+ guard(mutex)(&st->lock);
+
+ scan_count = bitmap_weight(indio_dev->active_scan_mask,
+ iio_get_masklength(indio_dev));
+ async_update = st->ldac_gpio && scan_count > 1;
+ if (async_update) {
+ /* use LDAC to update all channels simultaneously */
+ cmd = AD5686_CMD_WRITE_INPUT_N;
+ gpiod_set_value_cansleep(st->ldac_gpio, 0);
+ } else {
+ cmd = AD5686_CMD_WRITE_INPUT_N_UPDATE_N;
+ }
+
+ i = 0;
+ iio_for_each_active_channel(indio_dev, ch) {
+ ret = st->ops->write(st, cmd, indio_dev->channels[ch].address, val[i++]);
+ if (ret)
+ break;
+ }
+
+ /*
+ * If sync() is available, it is called here regardless of write
+ * failure to allow bus implementation to reset. In that case, partial
+ * writes are unlikely as the write operations would just queue up
+ * the transfers.
+ */
+ if (st->ops->sync)
+ st->ops->sync(st);
+
+ if (async_update)
+ gpiod_set_value_cansleep(st->ldac_gpio, 1);
+
+ iio_trigger_notify_done(indio_dev->trig);
+ return IRQ_HANDLED;
+}
+
int ad5686_probe(struct device *dev,
const struct ad5686_chip_info *chip_info,
const char *name, const struct ad5686_bus_ops *ops,
@@ -569,6 +628,13 @@ int ad5686_probe(struct device *dev,
return -EINVAL;
}
+ ret = devm_iio_triggered_buffer_setup_ext(dev, indio_dev, NULL,
+ &ad5686_trigger_handler,
+ IIO_BUFFER_DIRECTION_OUT,
+ NULL, NULL);
+ if (ret)
+ return ret;
+
return devm_iio_device_register(dev, indio_dev);
}
EXPORT_SYMBOL_NS_GPL(ad5686_probe, "IIO_AD5686");
--
2.43.0
^ permalink raw reply related
* [PATCH v4 09/12] iio: dac: ad5686: implement new sync() op for the spi bus
From: Rodrigo Alencar via B4 Relay @ 2026-06-23 10:55 UTC (permalink / raw)
To: Michael Auchter, linux, linux-iio, devicetree, linux-kernel,
linux-hardening
Cc: Michael Hennerich, Jonathan Cameron, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar
In-Reply-To: <20260623-ad5686-new-features-v4-0-28962a57db0f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Use of local SPI bus data to manage a collection of SPI transfers and
flush them to the SPI platform driver with the sync() operation. This
allows for faster handling of multiple channel DAC writes, avoiding kernel
overhead per spi_sync() call, which will be helpful when enabling
triggered buffer support.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/dac/ad5686-spi.c | 125 ++++++++++++++++++++++++++++++++-----------
drivers/iio/dac/ad5686.c | 4 +-
drivers/iio/dac/ad5686.h | 8 ++-
drivers/iio/dac/ad5696-i2c.c | 2 +-
4 files changed, 104 insertions(+), 35 deletions(-)
diff --git a/drivers/iio/dac/ad5686-spi.c b/drivers/iio/dac/ad5686-spi.c
index 6b6ef1d7071f..4a9a365b63bc 100644
--- a/drivers/iio/dac/ad5686-spi.c
+++ b/drivers/iio/dac/ad5686-spi.c
@@ -12,59 +12,91 @@
#include <linux/errno.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
+#include <linux/overflow.h>
#include <linux/spi/spi.h>
#include <asm/byteorder.h>
#include "ad5686.h"
+/**
+ * struct ad5686_spi_data - SPI bus specific data
+ * @msg: SPI message used for transfers
+ * @size: number of transfers currently in the message
+ * @capacity: maximum number of transfers that can be added to the message
+ * @xfers: array of SPI transfers, allocated with the provided capacity
+ */
+struct ad5686_spi_data {
+ struct spi_message msg;
+ unsigned int size;
+ unsigned int capacity;
+ struct spi_transfer xfers[] __counted_by(capacity);
+};
+
static int ad5686_spi_write(struct ad5686_state *st,
u8 cmd, u8 addr, u16 val)
{
- struct spi_device *spi = to_spi_device(st->dev);
- u8 tx_len, *buf;
+ struct ad5686_spi_data *bus_data = st->bus_data;
+ struct spi_transfer *xfer;
+ if (bus_data->size >= bus_data->capacity)
+ return -E2BIG;
+
+ if (bus_data->size)
+ bus_data->xfers[bus_data->size - 1].cs_change = 1;
+ else
+ spi_message_init(&bus_data->msg);
+
+ xfer = &bus_data->xfers[bus_data->size];
switch (st->chip_info->regmap_type) {
case AD5310_REGMAP:
- st->data[0].d16 = cpu_to_be16(AD5310_CMD(cmd) |
- val);
- buf = &st->data[0].d8[0];
- tx_len = 2;
+ st->data[bus_data->size].d16 =
+ cpu_to_be16(AD5310_CMD(cmd) | val);
+ *xfer = (struct spi_transfer) {
+ .tx_buf = &st->data[bus_data->size].d16,
+ .len = sizeof(st->data[bus_data->size].d16),
+ };
break;
case AD5683_REGMAP:
- st->data[0].d32 = cpu_to_be32(AD5686_CMD(cmd) |
- AD5683_DATA(val));
- buf = &st->data[0].d8[1];
- tx_len = 3;
+ st->data[bus_data->size].d32 =
+ cpu_to_be32(AD5686_CMD(cmd) | AD5683_DATA(val));
+ *xfer = (struct spi_transfer) {
+ .tx_buf = &st->data[bus_data->size].d8[1],
+ .len = sizeof(st->data[bus_data->size].d8) - 1,
+ };
break;
case AD5686_REGMAP:
- st->data[0].d32 = cpu_to_be32(AD5686_CMD(cmd) |
- AD5686_ADDR(addr) |
- val);
- buf = &st->data[0].d8[1];
- tx_len = 3;
+ st->data[bus_data->size].d32 =
+ cpu_to_be32(AD5686_CMD(cmd) | AD5686_ADDR(addr) | val);
+ *xfer = (struct spi_transfer) {
+ .tx_buf = &st->data[bus_data->size].d8[1],
+ .len = sizeof(st->data[bus_data->size].d8) - 1,
+ };
break;
default:
return -EINVAL;
}
- return spi_write(spi, buf, tx_len);
+ spi_message_add_tail(xfer, &bus_data->msg);
+ bus_data->size++;
+
+ return 0;
+}
+
+static int ad5686_spi_sync(struct ad5686_state *st)
+{
+ struct spi_device *spi = to_spi_device(st->dev);
+ struct ad5686_spi_data *bus_data = st->bus_data;
+
+ bus_data->size = 0; /* always reset, even on sync failure */
+ return spi_sync(spi, &bus_data->msg);
}
static int ad5686_spi_read(struct ad5686_state *st, u8 addr)
{
- struct spi_transfer t[] = {
- {
- .tx_buf = &st->data[0].d8[1],
- .len = 3,
- .cs_change = 1,
- }, {
- .tx_buf = &st->data[1].d8[1],
- .rx_buf = &st->data[2].d8[1],
- .len = 3,
- },
- };
struct spi_device *spi = to_spi_device(st->dev);
+ struct ad5686_spi_data *bus_data = st->bus_data;
+ struct spi_transfer *xfer = &bus_data->xfers[0];
u8 cmd = 0;
int ret;
@@ -85,8 +117,21 @@ static int ad5686_spi_read(struct ad5686_state *st, u8 addr)
AD5686_ADDR(addr));
st->data[1].d32 = cpu_to_be32(AD5686_CMD(AD5686_CMD_NOOP));
- ret = spi_sync_transfer(spi, t, ARRAY_SIZE(t));
- if (ret < 0)
+ xfer[0] = (struct spi_transfer) {
+ .tx_buf = &st->data[0].d8[1],
+ .len = sizeof(st->data[0].d8) - 1,
+ .cs_change = 1,
+ };
+ xfer[1] = (struct spi_transfer) {
+ .tx_buf = &st->data[1].d8[1],
+ .rx_buf = &st->data[2].d8[1],
+ .len = sizeof(st->data[1].d8) - 1,
+ };
+
+ spi_message_init_with_transfers(&bus_data->msg, xfer, 2);
+
+ ret = spi_sync(spi, &bus_data->msg);
+ if (ret)
return ret;
return be32_to_cpu(st->data[2].d32);
@@ -95,12 +140,30 @@ static int ad5686_spi_read(struct ad5686_state *st, u8 addr)
static const struct ad5686_bus_ops ad5686_spi_ops = {
.write = ad5686_spi_write,
.read = ad5686_spi_read,
+ .sync = ad5686_spi_sync,
};
static int ad5686_spi_probe(struct spi_device *spi)
{
- return ad5686_probe(&spi->dev, spi_get_device_match_data(spi),
- spi->modalias, &ad5686_spi_ops);
+ const struct ad5686_chip_info *info;
+ struct ad5686_spi_data *bus_data;
+ struct device *dev = &spi->dev;
+ unsigned int capacity;
+
+ info = spi_get_device_match_data(spi);
+ if (!info)
+ return -ENODEV;
+
+ /* read operation requires at least 2 transfers */
+ capacity = max(info->num_channels, 2);
+ bus_data = devm_kzalloc(dev, struct_size(bus_data, xfers, capacity),
+ GFP_KERNEL);
+ if (!bus_data)
+ return -ENOMEM;
+
+ bus_data->capacity = capacity;
+
+ return ad5686_probe(dev, info, spi->modalias, &ad5686_spi_ops, bus_data);
}
static const struct spi_device_id ad5686_spi_id[] = {
diff --git a/drivers/iio/dac/ad5686.c b/drivers/iio/dac/ad5686.c
index 713fe71ad1e7..75dd7921c67b 100644
--- a/drivers/iio/dac/ad5686.c
+++ b/drivers/iio/dac/ad5686.c
@@ -472,7 +472,8 @@ EXPORT_SYMBOL_NS_GPL(ad5679r_chip_info, "IIO_AD5686");
int ad5686_probe(struct device *dev,
const struct ad5686_chip_info *chip_info,
- const char *name, const struct ad5686_bus_ops *ops)
+ const char *name, const struct ad5686_bus_ops *ops,
+ void *bus_data)
{
struct reset_control *rstc;
struct ad5686_state *st;
@@ -487,6 +488,7 @@ int ad5686_probe(struct device *dev,
st->dev = dev;
st->ops = ops;
+ st->bus_data = bus_data;
st->chip_info = chip_info;
rstc = devm_reset_control_get_optional_exclusive(dev, NULL);
diff --git a/drivers/iio/dac/ad5686.h b/drivers/iio/dac/ad5686.h
index 9a192b0a2d7a..cb884334e94a 100644
--- a/drivers/iio/dac/ad5686.h
+++ b/drivers/iio/dac/ad5686.h
@@ -23,6 +23,7 @@
#define AD5686_ADDR_DAC(chan) (0x1 << (chan))
#define AD5686_ADDR_ALL_DAC 0xF
+#define AD5686_MAX_CHANNELS 16
#define AD5686_CMD_NOOP 0x0
#define AD5686_CMD_WRITE_INPUT_N 0x1
@@ -130,6 +131,7 @@ extern const struct ad5686_chip_info ad5679r_chip_info;
* @use_internal_vref: set to true if the internal reference voltage is used
* @lock: lock to protect access to state fields, which includes
* the data buffer during regmap ops
+ * @bus_data: bus specific data
* @data: transfer buffers
*/
struct ad5686_state {
@@ -142,6 +144,7 @@ struct ad5686_state {
unsigned int pwr_down_mode;
bool use_internal_vref;
struct mutex lock;
+ void *bus_data;
/*
* DMA (thus cache coherency maintenance) may require the
@@ -152,13 +155,14 @@ struct ad5686_state {
__be32 d32;
__be16 d16;
u8 d8[4];
- } data[3] __aligned(IIO_DMA_MINALIGN);
+ } data[AD5686_MAX_CHANNELS] __aligned(IIO_DMA_MINALIGN);
};
int ad5686_probe(struct device *dev,
const struct ad5686_chip_info *chip_info,
- const char *name, const struct ad5686_bus_ops *ops);
+ const char *name, const struct ad5686_bus_ops *ops,
+ void *bus_data);
static inline int ad5686_write(struct ad5686_state *st, u8 cmd, u8 addr, u16 val)
{
diff --git a/drivers/iio/dac/ad5696-i2c.c b/drivers/iio/dac/ad5696-i2c.c
index 279309329b64..28c97ded43ce 100644
--- a/drivers/iio/dac/ad5696-i2c.c
+++ b/drivers/iio/dac/ad5696-i2c.c
@@ -70,7 +70,7 @@ static const struct ad5686_bus_ops ad5686_i2c_ops = {
static int ad5686_i2c_probe(struct i2c_client *i2c)
{
return ad5686_probe(&i2c->dev, i2c_get_match_data(i2c),
- i2c->name, &ad5686_i2c_ops);
+ i2c->name, &ad5686_i2c_ops, NULL);
}
static const struct i2c_device_id ad5686_i2c_id[] = {
--
2.43.0
^ permalink raw reply related
* [PATCH v4 10/12] iio: dac: ad5686: read_raw/write_raw: use guard(mutex)()
From: Rodrigo Alencar via B4 Relay @ 2026-06-23 10:55 UTC (permalink / raw)
To: Michael Auchter, linux, linux-iio, devicetree, linux-kernel,
linux-hardening
Cc: Michael Hennerich, Jonathan Cameron, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar,
Maxwell Doose, Joshua Crofts
In-Reply-To: <20260623-ad5686-new-features-v4-0-28962a57db0f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Use guarded mutex lock to facilitate code review when adding new
attributes. This will allow for early returns, avoiding error-prone
locking and unlocking in error paths. This also adds missing include
linux/cleanup.h. Gain-control support will allow the scale attribute
to be configurable.
Reviewed-by: Maxwell Doose <m32285159@gmail.com>
Reviewed-by: Joshua Crofts <joshua.crofts1@gmail.com>
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/dac/ad5686.c | 19 ++++++++-----------
1 file changed, 8 insertions(+), 11 deletions(-)
diff --git a/drivers/iio/dac/ad5686.c b/drivers/iio/dac/ad5686.c
index 75dd7921c67b..db175e77b0b7 100644
--- a/drivers/iio/dac/ad5686.c
+++ b/drivers/iio/dac/ad5686.c
@@ -8,6 +8,7 @@
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/errno.h>
@@ -177,11 +178,11 @@ static int ad5686_read_raw(struct iio_dev *indio_dev,
struct ad5686_state *st = iio_priv(indio_dev);
int ret;
+ guard(mutex)(&st->lock);
+
switch (m) {
case IIO_CHAN_INFO_RAW:
- mutex_lock(&st->lock);
ret = ad5686_read(st, chan->address);
- mutex_unlock(&st->lock);
if (ret < 0)
return ret;
*val = (ret >> chan->scan_type.shift) &
@@ -202,23 +203,19 @@ static int ad5686_write_raw(struct iio_dev *indio_dev,
long mask)
{
struct ad5686_state *st = iio_priv(indio_dev);
- int ret;
+
+ guard(mutex)(&st->lock);
switch (mask) {
case IIO_CHAN_INFO_RAW:
if (val >= (1 << chan->scan_type.realbits) || val < 0)
return -EINVAL;
- mutex_lock(&st->lock);
- ret = ad5686_write(st, AD5686_CMD_WRITE_INPUT_N_UPDATE_N,
- chan->address, val << chan->scan_type.shift);
- mutex_unlock(&st->lock);
- break;
+ return ad5686_write(st, AD5686_CMD_WRITE_INPUT_N_UPDATE_N,
+ chan->address, val << chan->scan_type.shift);
default:
- ret = -EINVAL;
+ return -EINVAL;
}
-
- return ret;
}
static const struct iio_info ad5686_info = {
--
2.43.0
^ permalink raw reply related
* [PATCH v4 07/12] iio: dac: ad5686: add ldac gpio
From: Rodrigo Alencar via B4 Relay @ 2026-06-23 10:55 UTC (permalink / raw)
To: Michael Auchter, linux, linux-iio, devicetree, linux-kernel,
linux-hardening
Cc: Michael Hennerich, Jonathan Cameron, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar
In-Reply-To: <20260623-ad5686-new-features-v4-0-28962a57db0f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
If wired LDAC, should be asserted when unused (pin is active-low), which
allows for synchronous DAC updates. This will be used to update all the
channels at the same time when adding buffer support.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/dac/ad5686.c | 6 ++++++
drivers/iio/dac/ad5686.h | 4 ++++
2 files changed, 10 insertions(+)
diff --git a/drivers/iio/dac/ad5686.c b/drivers/iio/dac/ad5686.c
index 8ad8931a3d7f..713fe71ad1e7 100644
--- a/drivers/iio/dac/ad5686.c
+++ b/drivers/iio/dac/ad5686.c
@@ -12,6 +12,7 @@
#include <linux/dev_printk.h>
#include <linux/errno.h>
#include <linux/export.h>
+#include <linux/gpio/consumer.h>
#include <linux/kstrtox.h>
#include <linux/module.h>
#include <linux/regulator/consumer.h>
@@ -521,6 +522,11 @@ int ad5686_probe(struct device *dev,
fsleep(1);
reset_control_deassert(rstc);
+ st->ldac_gpio = devm_gpiod_get_optional(dev, "ldac", GPIOD_OUT_HIGH);
+ if (IS_ERR(st->ldac_gpio))
+ return dev_err_probe(dev, PTR_ERR(st->ldac_gpio),
+ "Failed to get LDAC GPIO\n");
+
/* Initialize masks to all ones */
st->pwr_down_mask = ~0;
st->pwr_down_mode = ~0;
diff --git a/drivers/iio/dac/ad5686.h b/drivers/iio/dac/ad5686.h
index a06fe7d89305..c424720f8f72 100644
--- a/drivers/iio/dac/ad5686.h
+++ b/drivers/iio/dac/ad5686.h
@@ -60,6 +60,8 @@ enum ad5686_regmap_type {
AD5686_REGMAP,
};
+struct gpio_desc;
+
struct ad5686_state;
/**
@@ -119,6 +121,7 @@ extern const struct ad5686_chip_info ad5679r_chip_info;
* @dev: device instance
* @chip_info: chip model specific constants, available modes etc
* @ops: bus specific operations
+ * @ldac_gpio: LDAC pin GPIO descriptor
* @vref_mv: actual reference voltage used
* @pwr_down_mask: power down mask
* @pwr_down_mode: current power down mode
@@ -131,6 +134,7 @@ struct ad5686_state {
struct device *dev;
const struct ad5686_chip_info *chip_info;
const struct ad5686_bus_ops *ops;
+ struct gpio_desc *ldac_gpio;
unsigned short vref_mv;
unsigned int pwr_down_mask;
unsigned int pwr_down_mode;
--
2.43.0
^ permalink raw reply related
* [PATCH v4 08/12] iio: dac: ad5686: introduce sync operation
From: Rodrigo Alencar via B4 Relay @ 2026-06-23 10:55 UTC (permalink / raw)
To: Michael Auchter, linux, linux-iio, devicetree, linux-kernel,
linux-hardening
Cc: Michael Hennerich, Jonathan Cameron, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar
In-Reply-To: <20260623-ad5686-new-features-v4-0-28962a57db0f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add sync() to operation to ad5686_bus_ops, which can be used to flush
multiple pending data transfers at once. This is going to be used when
implementing triggered buffer support.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/dac/ad5686.h | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/drivers/iio/dac/ad5686.h b/drivers/iio/dac/ad5686.h
index c424720f8f72..9a192b0a2d7a 100644
--- a/drivers/iio/dac/ad5686.h
+++ b/drivers/iio/dac/ad5686.h
@@ -68,10 +68,12 @@ struct ad5686_state;
* struct ad5686_bus_ops - bus specific read/write operations
* @read: read a register value at the given address
* @write: write a command, address and value to the device
+ * @sync: ensure the completion of the write operation (optional)
*/
struct ad5686_bus_ops {
int (*read)(struct ad5686_state *st, u8 addr);
int (*write)(struct ad5686_state *st, u8 cmd, u8 addr, u16 val);
+ int (*sync)(struct ad5686_state *st);
};
/**
@@ -160,7 +162,13 @@ int ad5686_probe(struct device *dev,
static inline int ad5686_write(struct ad5686_state *st, u8 cmd, u8 addr, u16 val)
{
- return st->ops->write(st, cmd, addr, val);
+ int ret;
+
+ ret = st->ops->write(st, cmd, addr, val);
+ if (ret)
+ return ret;
+
+ return st->ops->sync ? st->ops->sync(st) : 0;
}
static inline int ad5686_read(struct ad5686_state *st, u8 addr)
--
2.43.0
^ permalink raw reply related
* [PATCH v4 03/12] dt-bindings: iio: dac: ad5686: add reset/ldac/gain support
From: Rodrigo Alencar via B4 Relay @ 2026-06-23 10:55 UTC (permalink / raw)
To: Michael Auchter, linux, linux-iio, devicetree, linux-kernel,
linux-hardening
Cc: Michael Hennerich, Jonathan Cameron, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar,
Conor Dooley
In-Reply-To: <20260623-ad5686-new-features-v4-0-28962a57db0f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add GPIO property for RESET, LDAC and GAIN pin. RESET is active-low, LDAC
is used to load DAC channels with values from input registers and GAIN
can double the voltage in output channels. The gain-gpios property is
not available to all supported parts. The adi,range-double property
indicates that GAIN pin is hardwired to high in case gain-gpios is not
set, otherwise it sets the initial value for the gain setting.
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
.../devicetree/bindings/iio/dac/adi,ad5686.yaml | 38 ++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/dac/adi,ad5686.yaml b/Documentation/devicetree/bindings/iio/dac/adi,ad5686.yaml
index 713f535bb33a..d781baca6a6c 100644
--- a/Documentation/devicetree/bindings/iio/dac/adi,ad5686.yaml
+++ b/Documentation/devicetree/bindings/iio/dac/adi,ad5686.yaml
@@ -35,17 +35,53 @@ properties:
vcc-supply:
description: If not supplied the internal reference is used.
+ reset-gpios:
+ description: Active-low RESET pin to reset the device.
+ maxItems: 1
+
+ ldac-gpios:
+ description:
+ Active-low LDAC pin used to asynchronously update the DAC channels.
+ maxItems: 1
+
+ gain-gpios:
+ description:
+ GAIN pin that sets a multiplier for the DAC output voltage. When high,
+ the DAC output voltage is multiplied by 2, otherwise it is unchanged.
+ maxItems: 1
+
+ adi,range-double:
+ description:
+ Sets the initial voltage output range from 0 to 2xVREF. On devices that
+ have a GAIN pin and no gain-gpios property is set, this indicates the pin
+ is hardwired high.
+ type: boolean
+
required:
- compatible
- reg
allOf:
- $ref: /schemas/spi/spi-peripheral-props.yaml#
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - adi,ad5310r
+ - adi,ad5681r
+ - adi,ad5682r
+ - adi,ad5683
+ - adi,ad5683r
+ then:
+ properties:
+ gain-gpios: false
unevaluatedProperties: false
examples:
- |
+ #include <dt-bindings/gpio/gpio.h>
spi {
#address-cells = <1>;
#size-cells = <0>;
@@ -53,6 +89,8 @@ examples:
reg = <0>;
compatible = "adi,ad5310r";
vcc-supply = <&dac_vref0>;
+ reset-gpios = <&gpio0 0 GPIO_ACTIVE_LOW>;
+ ldac-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>;
};
};
...
--
2.43.0
^ permalink raw reply related
* [PATCH v4 06/12] iio: dac: ad5686: consume optional reset signal
From: Rodrigo Alencar via B4 Relay @ 2026-06-23 10:55 UTC (permalink / raw)
To: Michael Auchter, linux, linux-iio, devicetree, linux-kernel,
linux-hardening
Cc: Michael Hennerich, Jonathan Cameron, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar
In-Reply-To: <20260623-ad5686-new-features-v4-0-28962a57db0f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add RESET pin GPIO support through an optional reset control, which is
local to the probe function. A reset pulse is manually generated after
the device is powered up.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/dac/ad5686.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/drivers/iio/dac/ad5686.c b/drivers/iio/dac/ad5686.c
index e2ebabca6887..8ad8931a3d7f 100644
--- a/drivers/iio/dac/ad5686.c
+++ b/drivers/iio/dac/ad5686.c
@@ -15,6 +15,7 @@
#include <linux/kstrtox.h>
#include <linux/module.h>
#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
#include <linux/sysfs.h>
#include <linux/wordpart.h>
@@ -472,6 +473,7 @@ int ad5686_probe(struct device *dev,
const struct ad5686_chip_info *chip_info,
const char *name, const struct ad5686_bus_ops *ops)
{
+ struct reset_control *rstc;
struct ad5686_state *st;
struct iio_dev *indio_dev;
int ret, i;
@@ -486,6 +488,11 @@ int ad5686_probe(struct device *dev,
st->ops = ops;
st->chip_info = chip_info;
+ rstc = devm_reset_control_get_optional_exclusive(dev, NULL);
+ if (IS_ERR(rstc))
+ return dev_err_probe(dev, PTR_ERR(rstc),
+ "Failed to get reset control\n");
+
ret = devm_regulator_get_enable(dev, "vdd");
if (ret)
return dev_err_probe(dev, ret, "failed to enable vdd supply\n");
@@ -509,6 +516,11 @@ int ad5686_probe(struct device *dev,
/* 4.5us power-up time: Datasheet Table 4: Timing Characteristics */
fsleep(5);
+ /* 1us >> 30ns reset pulse activation time: Datasheet Table 4 */
+ reset_control_assert(rstc);
+ fsleep(1);
+ reset_control_deassert(rstc);
+
/* Initialize masks to all ones */
st->pwr_down_mask = ~0;
st->pwr_down_mode = ~0;
--
2.43.0
^ permalink raw reply related
* [PATCH v4 05/12] iio: dac: ad5686: add support for missing power supplies
From: Rodrigo Alencar via B4 Relay @ 2026-06-23 10:55 UTC (permalink / raw)
To: Michael Auchter, linux, linux-iio, devicetree, linux-kernel,
linux-hardening
Cc: Michael Hennerich, Jonathan Cameron, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar
In-Reply-To: <20260623-ad5686-new-features-v4-0-28962a57db0f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Get and enable regulators for vdd, vlogic and vref input power pins. Vdd
is the input power supply, while vlogic powers the digital side. vref is
replacing vcc, which is being deprecated, but still supported. The value
of vref_mv is checked so that a device without internal voltage reference
cannot proceed without an explicit supply. For correct operation, vdd and
vlogic are required, then devm_regulator_get_enable() is used so the
driver can still work without them by using the stub/dummy regulators.
Error report uses dev_err_probe(), which helps debugging an init issue.
A small delay is added after the regulators are enabled to consider for
the power-up time (4.5 us).
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/dac/ad5686.c | 22 ++++++++++++++++++++--
1 file changed, 20 insertions(+), 2 deletions(-)
diff --git a/drivers/iio/dac/ad5686.c b/drivers/iio/dac/ad5686.c
index 5840fda4b011..e2ebabca6887 100644
--- a/drivers/iio/dac/ad5686.c
+++ b/drivers/iio/dac/ad5686.c
@@ -8,6 +8,8 @@
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/kstrtox.h>
@@ -484,12 +486,28 @@ int ad5686_probe(struct device *dev,
st->ops = ops;
st->chip_info = chip_info;
- ret = devm_regulator_get_enable_read_voltage(dev, "vcc");
+ ret = devm_regulator_get_enable(dev, "vdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable vdd supply\n");
+
+ ret = devm_regulator_get_enable(dev, "vlogic");
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable vlogic supply\n");
+
+ ret = devm_regulator_get_enable_read_voltage(dev, "vref");
+ if (ret == -ENODEV) /* vcc-supply is deprecated, but supported still */
+ ret = devm_regulator_get_enable_read_voltage(dev, "vcc");
if (ret < 0 && ret != -ENODEV)
- return ret;
+ return dev_err_probe(dev, ret, "failed to read vref voltage\n");
st->use_internal_vref = ret == -ENODEV;
st->vref_mv = st->use_internal_vref ? st->chip_info->int_vref_mv : ret / 1000;
+ if (!st->vref_mv)
+ return dev_err_probe(dev, -EINVAL,
+ "invalid or not provided vref voltage\n");
+
+ /* 4.5us power-up time: Datasheet Table 4: Timing Characteristics */
+ fsleep(5);
/* Initialize masks to all ones */
st->pwr_down_mask = ~0;
--
2.43.0
^ permalink raw reply related
* [PATCH v4 04/12] dt-bindings: iio: dac: ad5686: rework on power supplies
From: Rodrigo Alencar via B4 Relay @ 2026-06-23 10:55 UTC (permalink / raw)
To: Michael Auchter, linux, linux-iio, devicetree, linux-kernel,
linux-hardening
Cc: Michael Hennerich, Jonathan Cameron, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar,
Conor Dooley
In-Reply-To: <20260623-ad5686-new-features-v4-0-28962a57db0f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add supplies for VDD, VLOGIC and VREF input voltage pins. The vcc-supply
property is deprecated, once it does not really exist as none of the
devices describe any power input with that name. VCC is also misleading as
it sounds like the input power supply, but it is being used as an external
voltage reference, which should be called VREF. Certain devices require
vref-supply to be available once an internal reference voltage is absent.
For correct operation vdd and vlogic supplies are required.
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
.../devicetree/bindings/iio/dac/adi,ad5686.yaml | 34 ++++++++++++++++++++--
1 file changed, 32 insertions(+), 2 deletions(-)
diff --git a/Documentation/devicetree/bindings/iio/dac/adi,ad5686.yaml b/Documentation/devicetree/bindings/iio/dac/adi,ad5686.yaml
index d781baca6a6c..02e8c78e36d3 100644
--- a/Documentation/devicetree/bindings/iio/dac/adi,ad5686.yaml
+++ b/Documentation/devicetree/bindings/iio/dac/adi,ad5686.yaml
@@ -32,8 +32,22 @@ properties:
reg:
maxItems: 1
+ vdd-supply:
+ description: Input power supply.
+
+ vlogic-supply:
+ description:
+ Digital power supply. On some tiny package variants for single-channel
+ devices, this supply is internally connected to vdd; in that case, specify
+ this property with the same regulator as vdd.
+
+ vref-supply:
+ description:
+ Reference voltage supply. If not supplied the internal reference is used.
+
vcc-supply:
- description: If not supplied the internal reference is used.
+ deprecated: true
+ description: Use vref-supply instead.
reset-gpios:
description: Active-low RESET pin to reset the device.
@@ -60,9 +74,23 @@ properties:
required:
- compatible
- reg
+ - vdd-supply
+ - vlogic-supply
allOf:
- $ref: /schemas/spi/spi-peripheral-props.yaml#
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - adi,ad5676
+ - adi,ad5683
+ - adi,ad5684
+ - adi,ad5686
+ then:
+ required:
+ - vref-supply
- if:
properties:
compatible:
@@ -88,7 +116,9 @@ examples:
dac@0 {
reg = <0>;
compatible = "adi,ad5310r";
- vcc-supply = <&dac_vref0>;
+ vdd-supply = <&dac_vdd>;
+ vlogic-supply = <&dac_vlogic>;
+ vref-supply = <&dac_vref>;
reset-gpios = <&gpio0 0 GPIO_ACTIVE_LOW>;
ldac-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>;
};
--
2.43.0
^ permalink raw reply related
* [PATCH v4 02/12] dt-bindings: iio: dac: ad5696: rework on power supplies
From: Rodrigo Alencar via B4 Relay @ 2026-06-23 10:55 UTC (permalink / raw)
To: Michael Auchter, linux, linux-iio, devicetree, linux-kernel,
linux-hardening
Cc: Michael Hennerich, Jonathan Cameron, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar,
Conor Dooley
In-Reply-To: <20260623-ad5686-new-features-v4-0-28962a57db0f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add supplies for VDD, VLOGIC and VREF input voltage pins. The vcc-supply
property is deprecated, once it does not really exist as none of the
devices describe any power input with that name. VCC is also misleading as
it sounds like the input power supply, but it is being used as an external
voltage reference, which should be called VREF. Certain devices require
vref-supply to be available once an internal reference voltage is absent.
For correct operation vdd and vlogic supplies are required.
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
.../devicetree/bindings/iio/dac/adi,ad5696.yaml | 34 ++++++++++++++++++++--
1 file changed, 31 insertions(+), 3 deletions(-)
diff --git a/Documentation/devicetree/bindings/iio/dac/adi,ad5696.yaml b/Documentation/devicetree/bindings/iio/dac/adi,ad5696.yaml
index cc343cdf6085..e10f8596f9d3 100644
--- a/Documentation/devicetree/bindings/iio/dac/adi,ad5696.yaml
+++ b/Documentation/devicetree/bindings/iio/dac/adi,ad5696.yaml
@@ -33,9 +33,22 @@ properties:
reg:
maxItems: 1
+ vdd-supply:
+ description: Input power supply.
+
+ vlogic-supply:
+ description:
+ Digital power supply. On some tiny package variants for single-channel
+ devices, this supply is internally connected to vdd; in that case, specify
+ this property with the same regulator as vdd.
+
+ vref-supply:
+ description:
+ Reference voltage supply. If not supplied the internal reference is used.
+
vcc-supply:
- description: |
- The regulator supply for DAC reference voltage.
+ deprecated: true
+ description: Use vref-supply instead.
reset-gpios:
description: Active-low RESET pin to reset the device.
@@ -62,8 +75,21 @@ properties:
required:
- compatible
- reg
+ - vdd-supply
+ - vlogic-supply
allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - adi,ad5693
+ - adi,ad5694
+ - adi,ad5696
+ then:
+ required:
+ - vref-supply
- if:
properties:
compatible:
@@ -90,7 +116,9 @@ examples:
ad5696: dac@0 {
compatible = "adi,ad5696";
reg = <0>;
- vcc-supply = <&dac_vref>;
+ vdd-supply = <&dac_vdd>;
+ vlogic-supply = <&dac_vlogic>;
+ vref-supply = <&dac_vref>;
ldac-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>;
};
};
--
2.43.0
^ permalink raw reply related
* [PATCH v4 01/12] dt-bindings: iio: dac: ad5696: add reset/ldac/gain support
From: Rodrigo Alencar via B4 Relay @ 2026-06-23 10:55 UTC (permalink / raw)
To: Michael Auchter, linux, linux-iio, devicetree, linux-kernel,
linux-hardening
Cc: Michael Hennerich, Jonathan Cameron, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar,
Conor Dooley
In-Reply-To: <20260623-ad5686-new-features-v4-0-28962a57db0f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add GPIO property for RESET, LDAC and GAIN pin. RESET is active-low, LDAC
is used to load DAC channels with values from input registers and GAIN
can double the voltage in output channels. The gain-gpios property is
not available to all supported parts. The adi,range-double property
indicates that GAIN pin is hardwired to high in case gain-gpios is not
set, otherwise it sets the initial value for the gain setting.
Acked-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
.../devicetree/bindings/iio/dac/adi,ad5696.yaml | 39 ++++++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/dac/adi,ad5696.yaml b/Documentation/devicetree/bindings/iio/dac/adi,ad5696.yaml
index b5a88b03dc2f..cc343cdf6085 100644
--- a/Documentation/devicetree/bindings/iio/dac/adi,ad5696.yaml
+++ b/Documentation/devicetree/bindings/iio/dac/adi,ad5696.yaml
@@ -37,14 +37,52 @@ properties:
description: |
The regulator supply for DAC reference voltage.
+ reset-gpios:
+ description: Active-low RESET pin to reset the device.
+ maxItems: 1
+
+ ldac-gpios:
+ description:
+ Active-low LDAC pin used to asynchronously update the DAC channels.
+ maxItems: 1
+
+ gain-gpios:
+ description:
+ GAIN pin that sets a multiplier for the DAC output voltage. When high,
+ the DAC output voltage is multiplied by 2, otherwise it is unchanged.
+ maxItems: 1
+
+ adi,range-double:
+ description:
+ Sets the initial voltage output range from 0 to 2xVREF. On devices that
+ have a GAIN pin and no gain-gpios property is set, this indicates the pin
+ is hardwired high.
+ type: boolean
+
required:
- compatible
- reg
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - adi,ad5311r
+ - adi,ad5691r
+ - adi,ad5692r
+ - adi,ad5693
+ - adi,ad5693r
+ then:
+ properties:
+ gain-gpios: false
+
additionalProperties: false
examples:
- |
+ #include <dt-bindings/gpio/gpio.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
@@ -53,6 +91,7 @@ examples:
compatible = "adi,ad5696";
reg = <0>;
vcc-supply = <&dac_vref>;
+ ldac-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>;
};
};
...
--
2.43.0
^ permalink raw reply related
* [PATCH v4 00/12] New features for the AD5686 IIO driver
From: Rodrigo Alencar via B4 Relay @ 2026-06-23 10:55 UTC (permalink / raw)
To: Michael Auchter, linux, linux-iio, devicetree, linux-kernel,
linux-hardening
Cc: Michael Hennerich, Jonathan Cameron, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar,
Conor Dooley, Maxwell Doose, Joshua Crofts
This is the second series of three on updating the AD5686 driver.
Initially, a big patch series was sent:
https://lore.kernel.org/r/20260422-ad5313r-iio-support-v1-0-ed7dca001d1b@analog.com
Then, the first patch series added fixes and cleanups:
https://lore.kernel.org/linux-iio/20260524-ad5686-fixes-v7-0-b6bf395d08bd@analog.com/
This one is introducing new features:
- Consume optional reset and correct power supplies;
- LDAC GPIO handling (active-low, held low when unused);
- SPI bus sync() implementation for batching multiple transfers;
- Triggered buffer support, leveraging LDAC and sync() to flush
all channel writes atomically;
- Gain control support through the scale property.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
Changes in v4:
- Replace anyof+const for enum in dt-bindings.
- Address some sashiko's comments.
- Use guard(mutex)() in trigger handler and other misc changes.
- Link to v3: https://lore.kernel.org/r/20260616-ad5686-new-features-v3-0-f829fb7e9262@analog.com
Changes in v3:
- Add range-double property.
- Acquire reset control after power-up delay.
- Include cleanup.h and use guard(mutex)() in read_raw().
- Link to v2: https://lore.kernel.org/r/20260609-ad5686-new-features-v2-0-70b423f5c76d@analog.com
Changes in v2:
- Get reset control deasserted.
- Update entire spi_transfer struct rather than individual fields.
- Replace udelay() for fsleep() in probe().
- Minor changes addressing further feedback.
- Link to v1: https://lore.kernel.org/r/20260602-ad5686-new-features-v1-0-691e01883d27@analog.com
---
Rodrigo Alencar (12):
dt-bindings: iio: dac: ad5696: add reset/ldac/gain support
dt-bindings: iio: dac: ad5696: rework on power supplies
dt-bindings: iio: dac: ad5686: add reset/ldac/gain support
dt-bindings: iio: dac: ad5686: rework on power supplies
iio: dac: ad5686: add support for missing power supplies
iio: dac: ad5686: consume optional reset signal
iio: dac: ad5686: add ldac gpio
iio: dac: ad5686: introduce sync operation
iio: dac: ad5686: implement new sync() op for the spi bus
iio: dac: ad5686: read_raw/write_raw: use guard(mutex)()
iio: dac: ad5686: add triggered buffer support
iio: dac: ad5686: add gain control support
.../devicetree/bindings/iio/dac/adi,ad5686.yaml | 72 +++++-
.../devicetree/bindings/iio/dac/adi,ad5696.yaml | 73 +++++-
drivers/iio/dac/Kconfig | 2 +
drivers/iio/dac/ad5686-spi.c | 125 +++++++---
drivers/iio/dac/ad5686.c | 251 +++++++++++++++++++--
drivers/iio/dac/ad5686.h | 34 ++-
drivers/iio/dac/ad5696-i2c.c | 2 +-
7 files changed, 498 insertions(+), 61 deletions(-)
---
base-commit: a50909aa46dec46de3c73235fc15a7d6f763d996
change-id: 20260602-ad5686-new-features-e116c04bddb9
Best regards,
--
Rodrigo Alencar <rodrigo.alencar@analog.com>
^ permalink raw reply
* [PATCH 6/6] clk: qcom: gpucc: Add Nord graphics clock controller support
From: Taniya Das @ 2026-06-23 10:54 UTC (permalink / raw)
To: Bjorn Andersson, Michael Turquette, Stephen Boyd, Brian Masney,
Shawn Guo, Bartosz Golaszewski, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Neil Armstrong, Konrad Dybcio
Cc: Ajit Pandey, Imran Shaik, Jagadeesh Kona, linux-arm-msm,
linux-clk, linux-kernel, devicetree, Taniya Das
In-Reply-To: <20260623-nords_mm_v1-v1-0-860c84539804@oss.qualcomm.com>
Add support for the GPU clock controllers (GPUCC) on the Qualcomm
Nord platform.
The platform includes two GPU clock controller instances,GPUCC
and GPU2CC. Register support for both controllers, which provide
clocks required for the graphics subsystem.
Signed-off-by: Taniya Das <taniya.das@oss.qualcomm.com>
---
drivers/clk/qcom/Kconfig | 11 +
drivers/clk/qcom/Makefile | 1 +
drivers/clk/qcom/gpu2cc-nord.c | 546 +++++++++++++++++++++++++++++++++++++
drivers/clk/qcom/gpucc-nord.c | 593 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 1151 insertions(+)
diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig
index 874136a2ad9aaa117df2c7ad5c8abc5280b76339..10dcfa72a0bd3bdd70e2bee05964e8c275ceb07d 100644
--- a/drivers/clk/qcom/Kconfig
+++ b/drivers/clk/qcom/Kconfig
@@ -166,6 +166,17 @@ config CLK_NORD_GCC
SPI, I2C, USB, SD/UFS, PCIe etc. The clock controller is a combination
of GCC, SE_GCC, NE_GCC and NW_GCC.
+config CLK_NORD_GPUCC
+ tristate "Nord Graphics Clock Controller"
+ depends on ARM64 || COMPILE_TEST
+ select CLK_NORD_GCC
+ default m if ARCH_QCOM
+ help
+ Support for the graphics clock controllers on Nord devices. There are two
+ graphics clock controllers on Nord SoC.
+ Say Y if you want to support graphics controller devices and
+ functionality such as 3D graphics.
+
config CLK_X1E80100_CAMCC
tristate "X1E80100 Camera Clock Controller"
depends on ARM64 || COMPILE_TEST
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index 4282f43e7078f1fe0dde6f942040eb6bd122d7ce..fb0a5bc94e32b2dc1d56268aa87f455b20d779eb 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_CLK_KAANAPALI_TCSRCC) += tcsrcc-kaanapali.o
obj-$(CONFIG_CLK_KAANAPALI_VIDEOCC) += videocc-kaanapali.o
obj-$(CONFIG_CLK_NORD_DISPCC) += dispcc0-nord.o dispcc1-nord.o
obj-$(CONFIG_CLK_NORD_GCC) += gcc-nord.o negcc-nord.o nwgcc-nord.o segcc-nord.o
+obj-$(CONFIG_CLK_NORD_GPUCC) += gpucc-nord.o gpu2cc-nord.o
obj-$(CONFIG_CLK_NORD_TCSRCC) += tcsrcc-nord.o
obj-$(CONFIG_CLK_X1E80100_CAMCC) += camcc-x1e80100.o
obj-$(CONFIG_CLK_X1E80100_DISPCC) += dispcc-x1e80100.o
diff --git a/drivers/clk/qcom/gpu2cc-nord.c b/drivers/clk/qcom/gpu2cc-nord.c
new file mode 100644
index 0000000000000000000000000000000000000000..d1baf019704c46a9b7967031c3ee77dde3336776
--- /dev/null
+++ b/drivers/clk/qcom/gpu2cc-nord.c
@@ -0,0 +1,546 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <dt-bindings/clock/qcom,nord-gpucc.h>
+
+#include "clk-alpha-pll.h"
+#include "clk-branch.h"
+#include "clk-pll.h"
+#include "clk-rcg.h"
+#include "clk-regmap.h"
+#include "clk-regmap-divider.h"
+#include "clk-regmap-mux.h"
+#include "common.h"
+#include "gdsc.h"
+#include "reset.h"
+
+enum {
+ DT_BI_TCXO,
+ DT_GPLL0_OUT_MAIN,
+ DT_GPLL0_OUT_MAIN_DIV,
+};
+
+enum {
+ P_BI_TCXO,
+ P_GPLL0_OUT_MAIN,
+ P_GPLL0_OUT_MAIN_DIV,
+ P_GPU_2_CC_PLL0_OUT_MAIN,
+ P_GPU_2_CC_PLL1_OUT_MAIN,
+};
+
+static const struct pll_vco lucid_ole_vco[] = {
+ { 249600000, 2300000000, 0 },
+};
+
+/* 934.0 MHz Configuration */
+static const struct alpha_pll_config gpu_2_cc_pll0_config = {
+ .l = 0x30,
+ .alpha = 0xa555,
+ .config_ctl_val = 0x20485699,
+ .config_ctl_hi_val = 0x00182261,
+ .config_ctl_hi1_val = 0x82aa299c,
+ .test_ctl_val = 0x00000000,
+ .test_ctl_hi_val = 0x00000003,
+ .test_ctl_hi1_val = 0x00009000,
+ .test_ctl_hi2_val = 0x00000034,
+ .user_ctl_val = 0x00000000,
+ .user_ctl_hi_val = 0x00400005,
+};
+
+static struct clk_alpha_pll gpu_2_cc_pll0 = {
+ .offset = 0x0,
+ .config = &gpu_2_cc_pll0_config,
+ .vco_table = lucid_ole_vco,
+ .num_vco = ARRAY_SIZE(lucid_ole_vco),
+ .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
+ .clkr = {
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_pll0",
+ .parent_data = &(const struct clk_parent_data) {
+ .index = DT_BI_TCXO,
+ },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_lucid_evo_ops,
+ },
+ },
+};
+
+/* 1100.0 MHz Configuration */
+static const struct alpha_pll_config gpu_2_cc_pll1_config = {
+ .l = 0x39,
+ .alpha = 0x4aaa,
+ .config_ctl_val = 0x20485699,
+ .config_ctl_hi_val = 0x00182261,
+ .config_ctl_hi1_val = 0x82aa299c,
+ .test_ctl_val = 0x00000000,
+ .test_ctl_hi_val = 0x00000003,
+ .test_ctl_hi1_val = 0x00009000,
+ .test_ctl_hi2_val = 0x00000034,
+ .user_ctl_val = 0x00000000,
+ .user_ctl_hi_val = 0x00400005,
+};
+
+static struct clk_alpha_pll gpu_2_cc_pll1 = {
+ .offset = 0x1000,
+ .config = &gpu_2_cc_pll1_config,
+ .vco_table = lucid_ole_vco,
+ .num_vco = ARRAY_SIZE(lucid_ole_vco),
+ .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
+ .clkr = {
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_pll1",
+ .parent_data = &(const struct clk_parent_data) {
+ .index = DT_BI_TCXO,
+ },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_lucid_evo_ops,
+ },
+ },
+};
+
+static const struct parent_map gpu_2_cc_parent_map_0[] = {
+ { P_BI_TCXO, 0 },
+ { P_GPLL0_OUT_MAIN, 5 },
+ { P_GPLL0_OUT_MAIN_DIV, 6 },
+};
+
+static const struct clk_parent_data gpu_2_cc_parent_data_0[] = {
+ { .index = DT_BI_TCXO },
+ { .index = DT_GPLL0_OUT_MAIN },
+ { .index = DT_GPLL0_OUT_MAIN_DIV },
+};
+
+static const struct parent_map gpu_2_cc_parent_map_1[] = {
+ { P_BI_TCXO, 0 },
+ { P_GPU_2_CC_PLL0_OUT_MAIN, 1 },
+ { P_GPU_2_CC_PLL1_OUT_MAIN, 3 },
+ { P_GPLL0_OUT_MAIN, 5 },
+ { P_GPLL0_OUT_MAIN_DIV, 6 },
+};
+
+static const struct clk_parent_data gpu_2_cc_parent_data_1[] = {
+ { .index = DT_BI_TCXO },
+ { .hw = &gpu_2_cc_pll0.clkr.hw },
+ { .hw = &gpu_2_cc_pll1.clkr.hw },
+ { .index = DT_GPLL0_OUT_MAIN },
+ { .index = DT_GPLL0_OUT_MAIN_DIV },
+};
+
+static const struct parent_map gpu_2_cc_parent_map_2[] = {
+ { P_BI_TCXO, 0 },
+ { P_GPU_2_CC_PLL1_OUT_MAIN, 3 },
+ { P_GPLL0_OUT_MAIN, 5 },
+ { P_GPLL0_OUT_MAIN_DIV, 6 },
+};
+
+static const struct clk_parent_data gpu_2_cc_parent_data_2[] = {
+ { .index = DT_BI_TCXO },
+ { .hw = &gpu_2_cc_pll1.clkr.hw },
+ { .index = DT_GPLL0_OUT_MAIN },
+ { .index = DT_GPLL0_OUT_MAIN_DIV },
+};
+
+static const struct freq_tbl ftbl_gpu_2_cc_ff_clk_src[] = {
+ F(200000000, P_GPLL0_OUT_MAIN, 3, 0, 0),
+ { }
+};
+
+static struct clk_rcg2 gpu_2_cc_ff_clk_src = {
+ .cmd_rcgr = 0x91c4,
+ .mnd_width = 0,
+ .hid_width = 5,
+ .parent_map = gpu_2_cc_parent_map_0,
+ .freq_tbl = ftbl_gpu_2_cc_ff_clk_src,
+ .hw_clk_ctrl = true,
+ .clkr.hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_ff_clk_src",
+ .parent_data = gpu_2_cc_parent_data_0,
+ .num_parents = ARRAY_SIZE(gpu_2_cc_parent_data_0),
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_rcg2_shared_ops,
+ },
+};
+
+static const struct freq_tbl ftbl_gpu_2_cc_gmu_clk_src[] = {
+ F(550000000, P_GPU_2_CC_PLL1_OUT_MAIN, 2, 0, 0),
+ { }
+};
+
+static struct clk_rcg2 gpu_2_cc_gmu_clk_src = {
+ .cmd_rcgr = 0x9174,
+ .mnd_width = 0,
+ .hid_width = 5,
+ .parent_map = gpu_2_cc_parent_map_1,
+ .freq_tbl = ftbl_gpu_2_cc_gmu_clk_src,
+ .hw_clk_ctrl = true,
+ .clkr.hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_gmu_clk_src",
+ .parent_data = gpu_2_cc_parent_data_1,
+ .num_parents = ARRAY_SIZE(gpu_2_cc_parent_data_1),
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_rcg2_shared_ops,
+ },
+};
+
+static struct clk_rcg2 gpu_2_cc_hub_clk_src = {
+ .cmd_rcgr = 0x91a8,
+ .mnd_width = 0,
+ .hid_width = 5,
+ .parent_map = gpu_2_cc_parent_map_2,
+ .freq_tbl = ftbl_gpu_2_cc_ff_clk_src,
+ .hw_clk_ctrl = true,
+ .clkr.hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_hub_clk_src",
+ .parent_data = gpu_2_cc_parent_data_2,
+ .num_parents = ARRAY_SIZE(gpu_2_cc_parent_data_2),
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_rcg2_shared_ops,
+ },
+};
+
+static struct clk_branch gpu_2_cc_ahb_clk = {
+ .halt_reg = 0x90cc,
+ .halt_check = BRANCH_HALT_VOTED,
+ .clkr = {
+ .enable_reg = 0x90cc,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_ahb_clk",
+ .parent_hws = (const struct clk_hw*[]) {
+ &gpu_2_cc_hub_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_2_cc_crc_ahb_clk = {
+ .halt_reg = 0x90d0,
+ .halt_check = BRANCH_HALT_VOTED,
+ .clkr = {
+ .enable_reg = 0x90d0,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_crc_ahb_clk",
+ .parent_hws = (const struct clk_hw*[]) {
+ &gpu_2_cc_hub_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_2_cc_cx_accu_shift_clk = {
+ .halt_reg = 0x9114,
+ .halt_check = BRANCH_HALT_VOTED,
+ .clkr = {
+ .enable_reg = 0x9114,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_cx_accu_shift_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_2_cc_cx_ff_clk = {
+ .halt_reg = 0x9100,
+ .halt_check = BRANCH_HALT,
+ .clkr = {
+ .enable_reg = 0x9100,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_cx_ff_clk",
+ .parent_hws = (const struct clk_hw*[]) {
+ &gpu_2_cc_ff_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_2_cc_cx_gmu_clk = {
+ .halt_reg = 0x90e8,
+ .halt_check = BRANCH_HALT_VOTED,
+ .clkr = {
+ .enable_reg = 0x90e8,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_cx_gmu_clk",
+ .parent_hws = (const struct clk_hw*[]) {
+ &gpu_2_cc_gmu_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_aon_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_2_cc_cxo_clk = {
+ .halt_reg = 0x90f8,
+ .halt_check = BRANCH_HALT,
+ .clkr = {
+ .enable_reg = 0x90f8,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_cxo_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_2_cc_freq_measure_clk = {
+ .halt_reg = 0x9008,
+ .halt_check = BRANCH_HALT,
+ .clkr = {
+ .enable_reg = 0x9008,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_freq_measure_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_2_cc_gpu_smmu_vote_clk = {
+ .halt_reg = 0x7000,
+ .halt_check = BRANCH_HALT_VOTED,
+ .clkr = {
+ .enable_reg = 0x7000,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_gpu_smmu_vote_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_2_cc_hub_aon_clk = {
+ .halt_reg = 0x91a4,
+ .halt_check = BRANCH_HALT,
+ .clkr = {
+ .enable_reg = 0x91a4,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_hub_aon_clk",
+ .parent_hws = (const struct clk_hw*[]) {
+ &gpu_2_cc_hub_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_aon_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_2_cc_hub_cx_int_clk = {
+ .halt_reg = 0x90fc,
+ .halt_check = BRANCH_HALT_VOTED,
+ .clkr = {
+ .enable_reg = 0x90fc,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_hub_cx_int_clk",
+ .parent_hws = (const struct clk_hw*[]) {
+ &gpu_2_cc_hub_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_aon_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_2_cc_memnoc_gfx_clk = {
+ .halt_reg = 0x9104,
+ .halt_check = BRANCH_HALT_VOTED,
+ .clkr = {
+ .enable_reg = 0x9104,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_memnoc_gfx_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_2_cc_mnd1x_0_gfx3d_clk = {
+ .halt_reg = 0x9164,
+ .halt_check = BRANCH_HALT,
+ .clkr = {
+ .enable_reg = 0x9164,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_mnd1x_0_gfx3d_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_2_cc_mnd1x_1_gfx3d_clk = {
+ .halt_reg = 0x9168,
+ .halt_check = BRANCH_HALT,
+ .clkr = {
+ .enable_reg = 0x9168,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_mnd1x_1_gfx3d_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_2_cc_sleep_clk = {
+ .halt_reg = 0x90e0,
+ .halt_check = BRANCH_HALT_SKIP,
+ .clkr = {
+ .enable_reg = 0x90e0,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_2_cc_sleep_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct gdsc gpu_2_cc_cx_gdsc = {
+ .gdscr = 0x9090,
+ .gds_hw_ctrl = 0x90a4,
+ .en_rest_wait_val = 0x2,
+ .en_few_wait_val = 0x2,
+ .clk_dis_wait_val = 0xf,
+ .pd = {
+ .name = "gpu_2_cc_cx_gdsc",
+ },
+ .pwrsts = PWRSTS_OFF_ON,
+ .flags = VOTABLE | RETAIN_FF_ENABLE,
+};
+
+static struct gdsc gpu_2_cc_gx_gdsc = {
+ .gdscr = 0x9034,
+ .clamp_io_ctrl = 0x9504,
+ .en_rest_wait_val = 0x2,
+ .en_few_wait_val = 0x2,
+ .clk_dis_wait_val = 0xf,
+ .pd = {
+ .name = "gpu_2_cc_gx_gdsc",
+ .power_on = gdsc_gx_do_nothing_enable,
+ },
+ .pwrsts = PWRSTS_OFF_ON,
+ .flags = CLAMP_IO | POLL_CFG_GDSCR | RETAIN_FF_ENABLE,
+};
+
+static struct clk_regmap *gpu_2_cc_nord_clocks[] = {
+ [GPU_CC_AHB_CLK] = &gpu_2_cc_ahb_clk.clkr,
+ [GPU_CC_CRC_AHB_CLK] = &gpu_2_cc_crc_ahb_clk.clkr,
+ [GPU_CC_CX_ACCU_SHIFT_CLK] = &gpu_2_cc_cx_accu_shift_clk.clkr,
+ [GPU_CC_CX_FF_CLK] = &gpu_2_cc_cx_ff_clk.clkr,
+ [GPU_CC_CX_GMU_CLK] = &gpu_2_cc_cx_gmu_clk.clkr,
+ [GPU_CC_CXO_CLK] = &gpu_2_cc_cxo_clk.clkr,
+ [GPU_CC_FF_CLK_SRC] = &gpu_2_cc_ff_clk_src.clkr,
+ [GPU_CC_FREQ_MEASURE_CLK] = &gpu_2_cc_freq_measure_clk.clkr,
+ [GPU_CC_GMU_CLK_SRC] = &gpu_2_cc_gmu_clk_src.clkr,
+ [GPU_CC_GPU_SMMU_VOTE_CLK] = &gpu_2_cc_gpu_smmu_vote_clk.clkr,
+ [GPU_CC_HUB_AON_CLK] = &gpu_2_cc_hub_aon_clk.clkr,
+ [GPU_CC_HUB_CLK_SRC] = &gpu_2_cc_hub_clk_src.clkr,
+ [GPU_CC_HUB_CX_INT_CLK] = &gpu_2_cc_hub_cx_int_clk.clkr,
+ [GPU_CC_MEMNOC_GFX_CLK] = &gpu_2_cc_memnoc_gfx_clk.clkr,
+ [GPU_CC_MND1X_GFX3D_CLK] = &gpu_2_cc_mnd1x_0_gfx3d_clk.clkr,
+ [GPU_CC_MND1X_1_GFX3D_CLK] = &gpu_2_cc_mnd1x_1_gfx3d_clk.clkr,
+ [GPU_CC_PLL0] = &gpu_2_cc_pll0.clkr,
+ [GPU_CC_PLL1] = &gpu_2_cc_pll1.clkr,
+ [GPU_CC_SLEEP_CLK] = &gpu_2_cc_sleep_clk.clkr,
+};
+
+static struct gdsc *gpu_2_cc_nord_gdscs[] = {
+ [GPU_CC_CX_GDSC] = &gpu_2_cc_cx_gdsc,
+ [GPU_CC_GX_GDSC] = &gpu_2_cc_gx_gdsc,
+};
+
+static const struct qcom_reset_map gpu_2_cc_nord_resets[] = {
+ [GPU_CC_ACD_BCR] = { 0x918c },
+ [GPU_CC_CB_BCR] = { 0x9198 },
+ [GPU_CC_CX_BCR] = { 0x908c },
+ [GPU_CC_FAST_HUB_BCR] = { 0x91a0 },
+ [GPU_CC_FF_BCR] = { 0x91c0 },
+ [GPU_CC_GFX3D_AON_BCR] = { 0x9118 },
+ [GPU_CC_GMU_BCR] = { 0x9170 },
+ [GPU_CC_GX_BCR] = { 0x9030 },
+ [GPU_CC_XO_BCR] = { 0x9000 },
+};
+
+static struct clk_alpha_pll *gpu_2_cc_nord_plls[] = {
+ &gpu_2_cc_pll0,
+ &gpu_2_cc_pll1,
+};
+
+static const u32 gpu_2_cc_nord_critical_cbcrs[] = {
+ 0x9004, /* GPU_2_CC_CXO_AON_CLK */
+ 0x900c, /* GPU_2_CC_DEMET_CLK */
+};
+
+static const struct regmap_config gpu_2_cc_nord_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x9ff0,
+ .fast_io = true,
+};
+
+static const struct qcom_cc_driver_data gpu_2_cc_nord_driver_data = {
+ .alpha_plls = gpu_2_cc_nord_plls,
+ .num_alpha_plls = ARRAY_SIZE(gpu_2_cc_nord_plls),
+ .clk_cbcrs = gpu_2_cc_nord_critical_cbcrs,
+ .num_clk_cbcrs = ARRAY_SIZE(gpu_2_cc_nord_critical_cbcrs),
+};
+
+static const struct qcom_cc_desc gpu_2_cc_nord_desc = {
+ .config = &gpu_2_cc_nord_regmap_config,
+ .clks = gpu_2_cc_nord_clocks,
+ .num_clks = ARRAY_SIZE(gpu_2_cc_nord_clocks),
+ .resets = gpu_2_cc_nord_resets,
+ .num_resets = ARRAY_SIZE(gpu_2_cc_nord_resets),
+ .gdscs = gpu_2_cc_nord_gdscs,
+ .num_gdscs = ARRAY_SIZE(gpu_2_cc_nord_gdscs),
+ .driver_data = &gpu_2_cc_nord_driver_data,
+};
+
+static const struct of_device_id gpu_2_cc_nord_match_table[] = {
+ { .compatible = "qcom,nord-gpu2cc" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, gpu_2_cc_nord_match_table);
+
+static int gpu_2_cc_nord_probe(struct platform_device *pdev)
+{
+ return qcom_cc_probe(pdev, &gpu_2_cc_nord_desc);
+}
+
+static struct platform_driver gpu_2_cc_nord_driver = {
+ .probe = gpu_2_cc_nord_probe,
+ .driver = {
+ .name = "gpu2cc-nord",
+ .of_match_table = gpu_2_cc_nord_match_table,
+ },
+};
+
+module_platform_driver(gpu_2_cc_nord_driver);
+
+MODULE_DESCRIPTION("QTI GPU2CC Nord Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/qcom/gpucc-nord.c b/drivers/clk/qcom/gpucc-nord.c
new file mode 100644
index 0000000000000000000000000000000000000000..407cf7e5ad437dd87025302b50f2d34076de1a93
--- /dev/null
+++ b/drivers/clk/qcom/gpucc-nord.c
@@ -0,0 +1,593 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <dt-bindings/clock/qcom,nord-gpucc.h>
+
+#include "clk-alpha-pll.h"
+#include "clk-branch.h"
+#include "clk-pll.h"
+#include "clk-rcg.h"
+#include "clk-regmap.h"
+#include "clk-regmap-divider.h"
+#include "clk-regmap-mux.h"
+#include "common.h"
+#include "gdsc.h"
+#include "reset.h"
+
+enum {
+ DT_BI_TCXO,
+ DT_GPLL0_OUT_MAIN,
+ DT_GPLL0_OUT_MAIN_DIV,
+};
+
+enum {
+ P_BI_TCXO,
+ P_GPLL0_OUT_MAIN,
+ P_GPLL0_OUT_MAIN_DIV,
+ P_GPU_CC_PLL0_OUT_MAIN,
+ P_GPU_CC_PLL1_OUT_MAIN,
+};
+
+static const struct pll_vco lucid_ole_vco[] = {
+ { 249600000, 2300000000, 0 },
+};
+
+/* 936.0 MHz Configuration */
+static const struct alpha_pll_config gpu_cc_pll0_config = {
+ .l = 0x30,
+ .alpha = 0xc000,
+ .config_ctl_val = 0x20485699,
+ .config_ctl_hi_val = 0x00182261,
+ .config_ctl_hi1_val = 0x82aa299c,
+ .test_ctl_val = 0x00000000,
+ .test_ctl_hi_val = 0x00000003,
+ .test_ctl_hi1_val = 0x00009000,
+ .test_ctl_hi2_val = 0x00000034,
+ .user_ctl_val = 0x00000000,
+ .user_ctl_hi_val = 0x00400005,
+};
+
+static struct clk_alpha_pll gpu_cc_pll0 = {
+ .offset = 0x0,
+ .config = &gpu_cc_pll0_config,
+ .vco_table = lucid_ole_vco,
+ .num_vco = ARRAY_SIZE(lucid_ole_vco),
+ .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
+ .clkr = {
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_pll0",
+ .parent_data = &(const struct clk_parent_data) {
+ .index = DT_BI_TCXO,
+ },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_lucid_evo_ops,
+ },
+ },
+};
+
+/* 1250.0 MHz Configuration */
+static const struct alpha_pll_config gpu_cc_pll1_config = {
+ .l = 0x41,
+ .alpha = 0x1aaa,
+ .config_ctl_val = 0x20485699,
+ .config_ctl_hi_val = 0x00182261,
+ .config_ctl_hi1_val = 0x82aa299c,
+ .test_ctl_val = 0x00000000,
+ .test_ctl_hi_val = 0x00000003,
+ .test_ctl_hi1_val = 0x00009000,
+ .test_ctl_hi2_val = 0x00000034,
+ .user_ctl_val = 0x00000000,
+ .user_ctl_hi_val = 0x00400005,
+};
+
+static struct clk_alpha_pll gpu_cc_pll1 = {
+ .offset = 0x1000,
+ .config = &gpu_cc_pll1_config,
+ .vco_table = lucid_ole_vco,
+ .num_vco = ARRAY_SIZE(lucid_ole_vco),
+ .regs = clk_alpha_pll_regs[CLK_ALPHA_PLL_TYPE_LUCID_OLE],
+ .clkr = {
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_pll1",
+ .parent_data = &(const struct clk_parent_data) {
+ .index = DT_BI_TCXO,
+ },
+ .num_parents = 1,
+ .ops = &clk_alpha_pll_lucid_evo_ops,
+ },
+ },
+};
+
+static const struct parent_map gpu_cc_parent_map_0[] = {
+ { P_BI_TCXO, 0 },
+ { P_GPLL0_OUT_MAIN, 5 },
+ { P_GPLL0_OUT_MAIN_DIV, 6 },
+};
+
+static const struct clk_parent_data gpu_cc_parent_data_0[] = {
+ { .index = DT_BI_TCXO },
+ { .index = DT_GPLL0_OUT_MAIN },
+ { .index = DT_GPLL0_OUT_MAIN_DIV },
+};
+
+static const struct parent_map gpu_cc_parent_map_1[] = {
+ { P_BI_TCXO, 0 },
+ { P_GPU_CC_PLL0_OUT_MAIN, 1 },
+ { P_GPU_CC_PLL1_OUT_MAIN, 3 },
+ { P_GPLL0_OUT_MAIN, 5 },
+ { P_GPLL0_OUT_MAIN_DIV, 6 },
+};
+
+static const struct clk_parent_data gpu_cc_parent_data_1[] = {
+ { .index = DT_BI_TCXO },
+ { .hw = &gpu_cc_pll0.clkr.hw },
+ { .hw = &gpu_cc_pll1.clkr.hw },
+ { .index = DT_GPLL0_OUT_MAIN },
+ { .index = DT_GPLL0_OUT_MAIN_DIV },
+};
+
+static const struct parent_map gpu_cc_parent_map_2[] = {
+ { P_BI_TCXO, 0 },
+ { P_GPU_CC_PLL1_OUT_MAIN, 3 },
+ { P_GPLL0_OUT_MAIN, 5 },
+ { P_GPLL0_OUT_MAIN_DIV, 6 },
+};
+
+static const struct clk_parent_data gpu_cc_parent_data_2[] = {
+ { .index = DT_BI_TCXO },
+ { .hw = &gpu_cc_pll1.clkr.hw },
+ { .index = DT_GPLL0_OUT_MAIN },
+ { .index = DT_GPLL0_OUT_MAIN_DIV },
+};
+
+static const struct freq_tbl ftbl_gpu_cc_ff_clk_src[] = {
+ F(200000000, P_GPLL0_OUT_MAIN, 3, 0, 0),
+ { }
+};
+
+static struct clk_rcg2 gpu_cc_ff_clk_src = {
+ .cmd_rcgr = 0x93d4,
+ .mnd_width = 0,
+ .hid_width = 5,
+ .parent_map = gpu_cc_parent_map_0,
+ .freq_tbl = ftbl_gpu_cc_ff_clk_src,
+ .hw_clk_ctrl = true,
+ .clkr.hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_ff_clk_src",
+ .parent_data = gpu_cc_parent_data_0,
+ .num_parents = ARRAY_SIZE(gpu_cc_parent_data_0),
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_rcg2_shared_ops,
+ },
+};
+
+static const struct freq_tbl ftbl_gpu_cc_gmu_clk_src[] = {
+ F(416666667, P_GPU_CC_PLL1_OUT_MAIN, 3, 0, 0),
+ F(625000000, P_GPU_CC_PLL1_OUT_MAIN, 2, 0, 0),
+ { }
+};
+
+static struct clk_rcg2 gpu_cc_gmu_clk_src = {
+ .cmd_rcgr = 0x92b8,
+ .mnd_width = 0,
+ .hid_width = 5,
+ .parent_map = gpu_cc_parent_map_1,
+ .freq_tbl = ftbl_gpu_cc_gmu_clk_src,
+ .hw_clk_ctrl = true,
+ .clkr.hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_gmu_clk_src",
+ .parent_data = gpu_cc_parent_data_1,
+ .num_parents = ARRAY_SIZE(gpu_cc_parent_data_1),
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_rcg2_shared_ops,
+ },
+};
+
+static const struct freq_tbl ftbl_gpu_cc_hub_clk_src[] = {
+ F(300000000, P_GPLL0_OUT_MAIN, 2, 0, 0),
+ { }
+};
+
+static struct clk_rcg2 gpu_cc_hub_clk_src = {
+ .cmd_rcgr = 0x938c,
+ .mnd_width = 0,
+ .hid_width = 5,
+ .parent_map = gpu_cc_parent_map_2,
+ .freq_tbl = ftbl_gpu_cc_hub_clk_src,
+ .hw_clk_ctrl = true,
+ .clkr.hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_hub_clk_src",
+ .parent_data = gpu_cc_parent_data_2,
+ .num_parents = ARRAY_SIZE(gpu_cc_parent_data_2),
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_rcg2_shared_ops,
+ },
+};
+
+static struct clk_regmap_div gpu_cc_hub_div_clk_src = {
+ .reg = 0x93cc,
+ .shift = 0,
+ .width = 4,
+ .clkr.hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_hub_div_clk_src",
+ .parent_hws = (const struct clk_hw*[]) {
+ &gpu_cc_hub_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_regmap_div_ro_ops,
+ },
+};
+
+static struct clk_branch gpu_cc_acd_gfx3d_clk = {
+ .halt_reg = 0x92a8,
+ .halt_check = BRANCH_HALT,
+ .clkr = {
+ .enable_reg = 0x92a8,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_acd_gfx3d_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_cc_acmu_clk = {
+ .halt_reg = 0x9294,
+ .halt_check = BRANCH_HALT,
+ .clkr = {
+ .enable_reg = 0x9294,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_acmu_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_cc_ahb_clk = {
+ .halt_reg = 0x9150,
+ .halt_check = BRANCH_HALT_DELAY,
+ .clkr = {
+ .enable_reg = 0x9150,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_ahb_clk",
+ .parent_hws = (const struct clk_hw*[]) {
+ &gpu_cc_hub_div_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_cc_crc_ahb_clk = {
+ .halt_reg = 0x9154,
+ .halt_check = BRANCH_HALT_VOTED,
+ .clkr = {
+ .enable_reg = 0x9154,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_crc_ahb_clk",
+ .parent_hws = (const struct clk_hw*[]) {
+ &gpu_cc_hub_div_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_cc_cx_accu_shift_clk = {
+ .halt_reg = 0x91a4,
+ .halt_check = BRANCH_HALT_VOTED,
+ .clkr = {
+ .enable_reg = 0x91a4,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_cx_accu_shift_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_cc_cx_ff_clk = {
+ .halt_reg = 0x9184,
+ .halt_check = BRANCH_HALT,
+ .clkr = {
+ .enable_reg = 0x9184,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_cx_ff_clk",
+ .parent_hws = (const struct clk_hw*[]) {
+ &gpu_cc_ff_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_cc_cx_gmu_clk = {
+ .halt_reg = 0x916c,
+ .halt_check = BRANCH_HALT_VOTED,
+ .clkr = {
+ .enable_reg = 0x916c,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_cx_gmu_clk",
+ .parent_hws = (const struct clk_hw*[]) {
+ &gpu_cc_gmu_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_aon_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_cc_cxo_clk = {
+ .halt_reg = 0x917c,
+ .halt_check = BRANCH_HALT,
+ .clkr = {
+ .enable_reg = 0x917c,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_cxo_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_cc_dpm_clk = {
+ .halt_reg = 0x91a8,
+ .halt_check = BRANCH_HALT,
+ .clkr = {
+ .enable_reg = 0x91a8,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_dpm_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_cc_freq_measure_clk = {
+ .halt_reg = 0x9008,
+ .halt_check = BRANCH_HALT,
+ .clkr = {
+ .enable_reg = 0x9008,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_freq_measure_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_cc_gpu_smmu_vote_clk = {
+ .halt_reg = 0x7000,
+ .halt_check = BRANCH_HALT_VOTED,
+ .clkr = {
+ .enable_reg = 0x7000,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_gpu_smmu_vote_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_cc_hub_aon_clk = {
+ .halt_reg = 0x9388,
+ .halt_check = BRANCH_HALT_VOTED,
+ .clkr = {
+ .enable_reg = 0x9388,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_hub_aon_clk",
+ .parent_hws = (const struct clk_hw*[]) {
+ &gpu_cc_hub_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_aon_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_cc_hub_cx_int_clk = {
+ .halt_reg = 0x9180,
+ .halt_check = BRANCH_HALT_VOTED,
+ .clkr = {
+ .enable_reg = 0x9180,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_hub_cx_int_clk",
+ .parent_hws = (const struct clk_hw*[]) {
+ &gpu_cc_hub_clk_src.clkr.hw,
+ },
+ .num_parents = 1,
+ .flags = CLK_SET_RATE_PARENT,
+ .ops = &clk_branch2_aon_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_cc_memnoc_gfx_clk = {
+ .halt_reg = 0x9188,
+ .halt_check = BRANCH_HALT_VOTED,
+ .clkr = {
+ .enable_reg = 0x9188,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_memnoc_gfx_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_cc_mnd1x_gfx3d_clk = {
+ .halt_reg = 0x92ac,
+ .halt_check = BRANCH_HALT,
+ .clkr = {
+ .enable_reg = 0x92ac,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_mnd1x_gfx3d_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct clk_branch gpu_cc_sleep_clk = {
+ .halt_reg = 0x9164,
+ .halt_check = BRANCH_HALT_VOTED,
+ .clkr = {
+ .enable_reg = 0x9164,
+ .enable_mask = BIT(0),
+ .hw.init = &(const struct clk_init_data) {
+ .name = "gpu_cc_sleep_clk",
+ .ops = &clk_branch2_ops,
+ },
+ },
+};
+
+static struct gdsc gpu_cc_cx_gdsc = {
+ .gdscr = 0x90e8,
+ .gds_hw_ctrl = 0x9128,
+ .en_rest_wait_val = 0x2,
+ .en_few_wait_val = 0x2,
+ .clk_dis_wait_val = 0xf,
+ .pd = {
+ .name = "gpu_cc_cx_gdsc",
+ },
+ .pwrsts = PWRSTS_OFF_ON,
+ .flags = VOTABLE | RETAIN_FF_ENABLE,
+};
+
+static struct gdsc gpu_cc_gx_gdsc = {
+ .gdscr = 0x905c,
+ .clamp_io_ctrl = 0x9504,
+ .en_rest_wait_val = 0x2,
+ .en_few_wait_val = 0x2,
+ .clk_dis_wait_val = 0xf,
+ .pd = {
+ .name = "gpu_cc_gx_gdsc",
+ .power_on = gdsc_gx_do_nothing_enable,
+ },
+ .pwrsts = PWRSTS_OFF_ON,
+ .flags = CLAMP_IO | POLL_CFG_GDSCR | RETAIN_FF_ENABLE,
+};
+
+static struct clk_regmap *gpu_cc_nord_clocks[] = {
+ [GPU_CC_ACD_GFX3D_CLK] = &gpu_cc_acd_gfx3d_clk.clkr,
+ [GPU_CC_ACMU_CLK] = &gpu_cc_acmu_clk.clkr,
+ [GPU_CC_AHB_CLK] = &gpu_cc_ahb_clk.clkr,
+ [GPU_CC_CRC_AHB_CLK] = &gpu_cc_crc_ahb_clk.clkr,
+ [GPU_CC_CX_ACCU_SHIFT_CLK] = &gpu_cc_cx_accu_shift_clk.clkr,
+ [GPU_CC_CX_FF_CLK] = &gpu_cc_cx_ff_clk.clkr,
+ [GPU_CC_CX_GMU_CLK] = &gpu_cc_cx_gmu_clk.clkr,
+ [GPU_CC_CXO_CLK] = &gpu_cc_cxo_clk.clkr,
+ [GPU_CC_DPM_CLK] = &gpu_cc_dpm_clk.clkr,
+ [GPU_CC_FF_CLK_SRC] = &gpu_cc_ff_clk_src.clkr,
+ [GPU_CC_FREQ_MEASURE_CLK] = &gpu_cc_freq_measure_clk.clkr,
+ [GPU_CC_GMU_CLK_SRC] = &gpu_cc_gmu_clk_src.clkr,
+ [GPU_CC_GPU_SMMU_VOTE_CLK] = &gpu_cc_gpu_smmu_vote_clk.clkr,
+ [GPU_CC_HUB_AON_CLK] = &gpu_cc_hub_aon_clk.clkr,
+ [GPU_CC_HUB_CLK_SRC] = &gpu_cc_hub_clk_src.clkr,
+ [GPU_CC_HUB_CX_INT_CLK] = &gpu_cc_hub_cx_int_clk.clkr,
+ [GPU_CC_HUB_DIV_CLK_SRC] = &gpu_cc_hub_div_clk_src.clkr,
+ [GPU_CC_MEMNOC_GFX_CLK] = &gpu_cc_memnoc_gfx_clk.clkr,
+ [GPU_CC_MND1X_GFX3D_CLK] = &gpu_cc_mnd1x_gfx3d_clk.clkr,
+ [GPU_CC_PLL0] = &gpu_cc_pll0.clkr,
+ [GPU_CC_PLL1] = &gpu_cc_pll1.clkr,
+ [GPU_CC_SLEEP_CLK] = &gpu_cc_sleep_clk.clkr,
+};
+
+static struct gdsc *gpu_cc_nord_gdscs[] = {
+ [GPU_CC_CX_GDSC] = &gpu_cc_cx_gdsc,
+ [GPU_CC_GX_GDSC] = &gpu_cc_gx_gdsc,
+};
+
+static const struct qcom_reset_map gpu_cc_nord_resets[] = {
+ [GPU_CC_ACD_BCR] = { 0x92f8 },
+ [GPU_CC_CB_BCR] = { 0x9340 },
+ [GPU_CC_CX_BCR] = { 0x90e4 },
+ [GPU_CC_FAST_HUB_BCR] = { 0x9384 },
+ [GPU_CC_GFX3D_AON_BCR] = { 0x91ac },
+ [GPU_CC_GX_BCR] = { 0x9058 },
+ [GPU_CC_XO_BCR] = { 0x9000 },
+};
+
+static struct clk_alpha_pll *gpu_cc_nord_plls[] = {
+ &gpu_cc_pll0,
+ &gpu_cc_pll1,
+};
+
+static const u32 gpu_cc_nord_critical_cbcrs[] = {
+ 0x9004, /* GPU_CC_CXO_AON_CLK */
+ 0x900c, /* GPU_CC_DEMET_CLK */
+};
+
+static const struct regmap_config gpu_cc_nord_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = 0x9660,
+ .fast_io = true,
+};
+
+static const struct qcom_cc_driver_data gpu_cc_nord_driver_data = {
+ .alpha_plls = gpu_cc_nord_plls,
+ .num_alpha_plls = ARRAY_SIZE(gpu_cc_nord_plls),
+ .clk_cbcrs = gpu_cc_nord_critical_cbcrs,
+ .num_clk_cbcrs = ARRAY_SIZE(gpu_cc_nord_critical_cbcrs),
+};
+
+static const struct qcom_cc_desc gpu_cc_nord_desc = {
+ .config = &gpu_cc_nord_regmap_config,
+ .clks = gpu_cc_nord_clocks,
+ .num_clks = ARRAY_SIZE(gpu_cc_nord_clocks),
+ .resets = gpu_cc_nord_resets,
+ .num_resets = ARRAY_SIZE(gpu_cc_nord_resets),
+ .gdscs = gpu_cc_nord_gdscs,
+ .num_gdscs = ARRAY_SIZE(gpu_cc_nord_gdscs),
+ .driver_data = &gpu_cc_nord_driver_data,
+};
+
+static const struct of_device_id gpu_cc_nord_match_table[] = {
+ { .compatible = "qcom,nord-gpucc" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, gpu_cc_nord_match_table);
+
+static int gpu_cc_nord_probe(struct platform_device *pdev)
+{
+ return qcom_cc_probe(pdev, &gpu_cc_nord_desc);
+}
+
+static struct platform_driver gpu_cc_nord_driver = {
+ .probe = gpu_cc_nord_probe,
+ .driver = {
+ .name = "gpucc-nord",
+ .of_match_table = gpu_cc_nord_match_table,
+ },
+};
+
+module_platform_driver(gpu_cc_nord_driver);
+
+MODULE_DESCRIPTION("QTI GPUCC Nord Driver");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox