* [PATCH 0/3] Add Xilinx AMS Driver
@ 2018-08-30 10:22 Manish Narani
2018-08-30 10:22 ` [PATCH 1/3] dt-bindings: iio: adc: Add Xilinx AMS binding documentation Manish Narani
` (2 more replies)
0 siblings, 3 replies; 13+ messages in thread
From: Manish Narani @ 2018-08-30 10:22 UTC (permalink / raw)
To: jic23, knaack.h, lars, pmeerw, robh+dt, mark.rutland,
michal.simek, manish.narani, leoyang.li, sudeep.holla,
amit.kucheria, broonie, arnaud.pouliquen, geert, eugen.hristev,
rdunlap, lukas, freeman.liu, vilhelm.gray, tglx, baolin.wang,
gregkh
Cc: sgoud, anirudh, linux-iio, devicetree, linux-arm-kernel,
linux-kernel
Add Xilinx AMS driver which is used for Xilinx's ZynqMP AMS controller.
This AMS driver is used to report various interface voltages and
temperatures across the system.
This driver handles AMS module including PS-Sysmon & PL-Sysmon. The
binding documentation is added for understanding of AMS, PS, PL Sysmon
Channels.
Manish Narani (3):
dt-bindings: iio: adc: Add Xilinx AMS binding documentation
iio: adc: Add Xilinx AMS driver
arm64: zynqmp: DT: Add Xilinx AMS node
.../devicetree/bindings/iio/adc/xilinx-ams.txt | 159 +++
arch/arm64/boot/dts/xilinx/zynqmp.dtsi | 26 +
drivers/iio/adc/Kconfig | 10 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/xilinx-ams.c | 1081 ++++++++++++++++++++
drivers/iio/adc/xilinx-ams.h | 281 +++++
6 files changed, 1558 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
create mode 100644 drivers/iio/adc/xilinx-ams.c
create mode 100644 drivers/iio/adc/xilinx-ams.h
--
2.1.1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 1/3] dt-bindings: iio: adc: Add Xilinx AMS binding documentation
2018-08-30 10:22 [PATCH 0/3] Add Xilinx AMS Driver Manish Narani
@ 2018-08-30 10:22 ` Manish Narani
2018-09-02 18:14 ` Jonathan Cameron
2018-09-04 1:17 ` Rob Herring
2018-08-30 10:22 ` [PATCH 2/3] iio: adc: Add Xilinx AMS driver Manish Narani
2018-08-30 10:22 ` [PATCH 3/3] arm64: zynqmp: DT: Add Xilinx AMS node Manish Narani
2 siblings, 2 replies; 13+ messages in thread
From: Manish Narani @ 2018-08-30 10:22 UTC (permalink / raw)
To: jic23, knaack.h, lars, pmeerw, robh+dt, mark.rutland,
michal.simek, manish.narani, leoyang.li, sudeep.holla,
amit.kucheria, broonie, arnaud.pouliquen, geert, eugen.hristev,
rdunlap, lukas, freeman.liu, vilhelm.gray, tglx, baolin.wang,
gregkh
Cc: sgoud, anirudh, linux-iio, devicetree, linux-arm-kernel,
linux-kernel
Xilinx AMS have several ADC channels that can be used for measurement of
different voltages and temperatures. Document the same in the bindings.
Signed-off-by: Manish Narani <manish.narani@xilinx.com>
---
.../devicetree/bindings/iio/adc/xilinx-ams.txt | 159 +++++++++++++++++++++
1 file changed, 159 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
diff --git a/Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt b/Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
new file mode 100644
index 0000000..8cc96f0
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
@@ -0,0 +1,159 @@
+Xilinx AMS controller
+
+The AMS includes an ADC as well as on-chip sensors that can be used to
+sample external voltages and monitor on-die operating conditions, such as
+temperature and supply voltage levels. The AMS has two SYSMON blocks.
+PL-SYSMON block is capable of monitoring off chip voltage and temperature.
+PL-SYSMON block has DRP, JTAG and I2C interface to enable monitoring from
+external master. Out of this interface currently only DRP is supported.
+Other block PS-SYSMON is memory mapped to PS. Both of block has built-in
+alarm generation logic that is used to interrupt the processor based on
+condition set.
+
+All designs should have AMS registers, but PS and PL are optional. The
+AMS controller can work with only PS, only PL and both PS and PL
+configurations. Please specify registers according to your design. Devicetree
+should always have AMS module property. Providing PS & PL module is optional.
+
+Required properties:
+ - compatible: Should be "xlnx,zynqmp-ams"
+ - reg: Should specify AMS register space
+ - interrupts: Interrupt number for the AMS control interface
+ - interrupt-names: Interrupt name, must be "ams-irq"
+ - clocks: Should contain a clock specifier for the device
+ - ranges: keep the property empty to map child address space
+ (for PS and/or PL) nodes 1:1 onto the parent address
+ space
+
+AMS device tree subnode:
+ - compatible: Should be "xlnx,zynqmp-ams-ps" or "xlnx,zynqmp-ams-pl"
+ - reg: Register space for PS or PL
+
+Optional properties:
+
+Following optional property only valid for PL.
+ - xlnx,ext-channels: List of external channels that are connected to the
+ AMS PL module.
+
+ The child nodes of this node represent the external channels which are
+ connected to the AMS Module. If the property is not present
+ no external channels will be assumed to be connected.
+
+ Each child node represents one channel and has the following
+ properties:
+ Required properties:
+ * reg: Pair of pins the channel is connected to.
+ 0: VP/VN
+ 1: VUSER0
+ 2: VUSER1
+ 3: VUSER3
+ 4: VUSER4
+ 5: VAUXP[0]/VAUXN[0]
+ 6: VAUXP[1]/VAUXN[1]
+ ...
+ 20: VAUXP[15]/VAUXN[15]
+ Note each channel number should only be used at most
+ once.
+ Optional properties:
+ * xlnx,bipolar: If set the channel is used in bipolar
+ mode.
+
+
+Example:
+ xilinx_ams: ams@ffa50000 {
+ compatible = "xlnx,zynqmp-ams";
+ interrupt-parent = <&gic>;
+ interrupts = <0 56 4>;
+ interrupt-names = "ams-irq";
+ clocks = <&clkc 70>;
+ reg = <0x0 0xffa50000 0x0 0x800>;
+ reg-names = "ams-base";
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+
+ ams_ps: ams_ps@ffa50800 {
+ compatible = "xlnx,zynqmp-ams-ps";
+ reg = <0x0 0xffa50800 0x0 0x400>;
+ };
+
+ ams_pl: ams_pl@ffa50c00 {
+ compatible = "xlnx,zynqmp-ams-pl";
+ reg = <0x0 0xffa50c00 0x0 0x400>;
+ xlnx,ext-channels {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ channel@0 {
+ reg = <0>;
+ xlnx,bipolar;
+ };
+ channel@1 {
+ reg = <1>;
+ };
+ channel@8 {
+ reg = <8>;
+ xlnx,bipolar;
+ };
+ };
+ };
+ };
+
+AMS Channels Details:
+
+Sysmon Block |Channel| Details |Measurement
+ Number Type
+---------------------------------------------------------------------------------------------------------
+AMS CTRL |0 |System PLLs voltage measurement, VCC_PSPLL. |Voltage
+ |1 |Battery voltage measurement, VCC_PSBATT. |Voltage
+ |2 |PL Internal voltage measurement, VCCINT. |Voltage
+ |3 |Block RAM voltage measurement, VCCBRAM. |Voltage
+ |4 |PL Aux voltage measurement, VCCAUX. |Voltage
+ |5 |Voltage measurement for six DDR I/O PLLs, VCC_PSDDR_PLL. |Voltage
+ |6 |VCC_PSINTFP_DDR voltage measurement. |Voltage
+---------------------------------------------------------------------------------------------------------
+PS Sysmon |7 |LPD temperature measurement. |Temperature
+ |8 |FPD Temperature Measurment (REMOTE). |Temperature
+ |9 |VCC PS LPD voltage measurement (supply1). |Voltage
+ |10 |VCC PS FPD voltage measurement (supply2). |Voltage
+ |11 |PS Aux voltage reference (supply3). |Voltage
+ |12 |DDR I/O VCC voltage measurement. |Voltage
+ |13 |PS IO Bank 503 voltage measurement (supply5). |Voltage
+ |14 |PS IO Bank 500 voltage measurement (supply6). |Voltage
+ |15 |VCCO_PSIO1 voltage measurement. |Voltage
+ |16 |VCCO_PSIO2 voltage measurement. |Voltage
+ |17 |VCC_PS_GTR voltage measurement (VPS_MGTRAVCC). |Voltage
+ |18 |VTT_PS_GTR voltage measurement (VPS_MGTRAVTT). |Voltage
+ |19 |VCC_PSADC voltage measurement. |Voltage
+---------------------------------------------------------------------------------------------------------
+PL Sysmon |20 |PL Temperature measurement. |Temperature
+ |21 |PL Internal Voltage Voltage measurement, VCCINT. |Voltage
+ |22 |PL Auxiliary Voltage measurement, VCCAUX. |Voltage
+ |23 |ADC Reference P+ Voltage measurement. |Voltage
+ |24 |ADC Reference N- Voltage measurement. |Voltage
+ |25 |PL Block RAM Voltage measurement, VCCBRAM. |Voltage
+ |26 |LPD Internal Voltage measurement, VCC_PSINTLP (supply4). |Voltage
+ |27 |FPD Internal Voltage measurement, VCC_PSINTFP (supply5). |Voltage
+ |28 |PS Auxiliary Voltage measurement (supply6). |Voltage
+ |29 |PL VCCADC Voltage measurement (vccams). |Voltage
+ |30 |Differencial analog input signal Voltage measurment. |Voltage
+ |31 |VUser0 Voltage measurement (supply7). |Voltage
+ |32 |VUser1 Voltage measurement (supply8). |Voltage
+ |33 |VUser2 Voltage measurement (supply9). |Voltage
+ |34 |VUser3 Voltage measurement (supply10). |Voltage
+ |35 |Auxiliary ch 0 Voltage measurement (VAux0). |Voltage
+ |36 |Auxiliary ch 1 Voltage measurement (VAux1). |Voltage
+ |37 |Auxiliary ch 2 Voltage measurement (VAux2). |Voltage
+ |38 |Auxiliary ch 3 Voltage measurement (VAux3). |Voltage
+ |39 |Auxiliary ch 4 Voltage measurement (VAux4). |Voltage
+ |40 |Auxiliary ch 5 Voltage measurement (VAux5). |Voltage
+ |41 |Auxiliary ch 6 Voltage measurement (VAux6). |Voltage
+ |42 |Auxiliary ch 7 Voltage measurement (VAux7). |Voltage
+ |43 |Auxiliary ch 8 Voltage measurement (VAux8). |Voltage
+ |44 |Auxiliary ch 9 Voltage measurement (VAux9). |Voltage
+ |45 |Auxiliary ch 10 Voltage measurement (VAux10). |Voltage
+ |46 |Auxiliary ch 11 Voltage measurement (VAux11). |Voltage
+ |47 |Auxiliary ch 12 Voltage measurement (VAux12). |Voltage
+ |48 |Auxiliary ch 13 Voltage measurement (VAux13). |Voltage
+ |49 |Auxiliary ch 14 Voltage measurement (VAux14). |Voltage
+ |50 |Auxiliary ch 15 Voltage measurement (VAux15). |Voltage
+---------------------------------------------------------------------------------------------------------
--
2.1.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 2/3] iio: adc: Add Xilinx AMS driver
2018-08-30 10:22 [PATCH 0/3] Add Xilinx AMS Driver Manish Narani
2018-08-30 10:22 ` [PATCH 1/3] dt-bindings: iio: adc: Add Xilinx AMS binding documentation Manish Narani
@ 2018-08-30 10:22 ` Manish Narani
2018-08-30 15:36 ` Randy Dunlap
2018-09-02 19:56 ` Jonathan Cameron
2018-08-30 10:22 ` [PATCH 3/3] arm64: zynqmp: DT: Add Xilinx AMS node Manish Narani
2 siblings, 2 replies; 13+ messages in thread
From: Manish Narani @ 2018-08-30 10:22 UTC (permalink / raw)
To: jic23, knaack.h, lars, pmeerw, robh+dt, mark.rutland,
michal.simek, manish.narani, leoyang.li, sudeep.holla,
amit.kucheria, broonie, arnaud.pouliquen, geert, eugen.hristev,
rdunlap, lukas, freeman.liu, vilhelm.gray, tglx, baolin.wang,
gregkh
Cc: sgoud, anirudh, linux-iio, devicetree, linux-arm-kernel,
linux-kernel
The AMS includes an ADC as well as on-chip sensors that can be used to
sample external voltages and monitor on-die operating conditions, such
as temperature and supply voltage levels. The AMS has two SYSMON blocks.
PL-SYSMON block is capable of monitoring off chip voltage and
temperature.
PL-SYSMON block has DRP, JTAG and I2C interface to enable monitoring
from external master. Out of these interface currently only DRP is
supported.
Other block PS-SYSMON is memory mapped to PS.
The AMS can use internal channels to monitor voltage and temperature as
well as one primary and up to 16 auxiliary channels for measuring
external voltages.
The voltage and temperature monitoring channels also have event
capability which allows to generate an interrupt when their value falls
below or raises above a set threshold.
Signed-off-by: Manish Narani <manish.narani@xilinx.com>
---
drivers/iio/adc/Kconfig | 10 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/xilinx-ams.c | 1081 ++++++++++++++++++++++++++++++++++++++++++
drivers/iio/adc/xilinx-ams.h | 281 +++++++++++
4 files changed, 1373 insertions(+)
create mode 100644 drivers/iio/adc/xilinx-ams.c
create mode 100644 drivers/iio/adc/xilinx-ams.h
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 4a75492..405ea00 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -941,4 +941,14 @@ config XILINX_XADC
The driver can also be build as a module. If so, the module will be called
xilinx-xadc.
+config XILINX_AMS
+ tristate "Xilinx AMS driver"
+ depends on ARCH_ZYNQMP || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+ Say yes here to have support for the Xilinx AMS.
+
+ The driver can also be build as a module. If so, the module will be called
+ xilinx-ams.
+
endmenu
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 03db7b5..fbfcc45 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -85,4 +85,5 @@ obj-$(CONFIG_VF610_ADC) += vf610_adc.o
obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
xilinx-xadc-y := xilinx-xadc-core.o xilinx-xadc-events.o
obj-$(CONFIG_XILINX_XADC) += xilinx-xadc.o
+obj-$(CONFIG_XILINX_AMS) += xilinx-ams.o
obj-$(CONFIG_SD_ADC_MODULATOR) += sd_adc_modulator.o
diff --git a/drivers/iio/adc/xilinx-ams.c b/drivers/iio/adc/xilinx-ams.c
new file mode 100644
index 0000000..10bcc52
--- /dev/null
+++ b/drivers/iio/adc/xilinx-ams.c
@@ -0,0 +1,1081 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xilinx AMS driver
+ *
+ * Copyright (C) 2017-2018 Xilinx, Inc.
+ *
+ * Manish Narani <mnarani@xilinx.com>
+ * Rajnikant Bhojani <rajnikant.bhojani@xilinx.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/of_address.h>
+#include <linux/iopoll.h>
+#include <linux/delay.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/events.h>
+#include <linux/io.h>
+
+#include "xilinx-ams.h"
+
+static const unsigned int AMS_UNMASK_TIMEOUT_MS = 500;
+
+static inline void ams_ps_update_reg(struct ams *ams, unsigned int offset,
+ u32 mask, u32 data)
+{
+ u32 val;
+
+ val = readl(ams->ps_base + offset);
+ writel((val & ~mask) | (data & mask), ams->ps_base + offset);
+}
+
+static inline void ams_pl_write_reg(struct ams *ams, unsigned int offset,
+ u32 data)
+{
+ writel(data, ams->pl_base + offset);
+}
+
+static inline void ams_pl_update_reg(struct ams *ams, unsigned int offset,
+ u32 mask, u32 data)
+{
+ u32 val;
+
+ val = readl(ams->pl_base + offset);
+ writel((val & ~mask) | (data & mask), ams->pl_base + offset);
+}
+
+static void ams_update_intrmask(struct ams *ams, u64 mask, u64 val)
+{
+ ams->intr_mask &= ~mask;
+ ams->intr_mask |= (val & mask);
+
+ writel(~(ams->intr_mask | ams->masked_alarm), ams->base + AMS_IER_0);
+ writel(~(ams->intr_mask >> AMS_ISR1_INTR_MASK_SHIFT),
+ ams->base + AMS_IER_1);
+ writel(ams->intr_mask | ams->masked_alarm, ams->base + AMS_IDR_0);
+ writel(ams->intr_mask >> AMS_ISR1_INTR_MASK_SHIFT,
+ ams->base + AMS_IDR_1);
+}
+
+static void ams_disable_all_alarms(struct ams *ams)
+{
+ /* disable PS module alarm */
+ if (ams->ps_base) {
+ ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_REGCFG1_ALARM_MASK,
+ AMS_REGCFG1_ALARM_MASK);
+ ams_ps_update_reg(ams, AMS_REG_CONFIG3, AMS_REGCFG3_ALARM_MASK,
+ AMS_REGCFG3_ALARM_MASK);
+ }
+
+ /* disable PL module alarm */
+ if (ams->pl_base) {
+ ams_pl_update_reg(ams, AMS_REG_CONFIG1,
+ AMS_REGCFG1_ALARM_MASK,
+ AMS_REGCFG1_ALARM_MASK);
+ ams_pl_update_reg(ams, AMS_REG_CONFIG3,
+ AMS_REGCFG3_ALARM_MASK,
+ AMS_REGCFG3_ALARM_MASK);
+ }
+}
+
+static void iio_ams_update_alarm(struct ams *ams, unsigned long alarm_mask)
+{
+ u32 cfg;
+ unsigned long flags;
+ unsigned long pl_alarm_mask;
+
+ if (ams->ps_base) {
+ /* Configuring PS alarm enable */
+ cfg = ~((alarm_mask & AMS_ISR0_ALARM_2_TO_0_MASK) <<
+ AMS_CONF1_ALARM_2_TO_0_SHIFT);
+ cfg &= ~((alarm_mask & AMS_ISR0_ALARM_6_TO_3_MASK) <<
+ AMS_CONF1_ALARM_6_TO_3_SHIFT);
+ ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_REGCFG1_ALARM_MASK,
+ cfg);
+
+ cfg = ~((alarm_mask >> AMS_CONF3_ALARM_12_TO_7_SHIFT) &
+ AMS_ISR0_ALARM_12_TO_7_MASK);
+ ams_ps_update_reg(ams, AMS_REG_CONFIG3, AMS_REGCFG3_ALARM_MASK,
+ cfg);
+ }
+
+ if (ams->pl_base) {
+ pl_alarm_mask = (alarm_mask >> AMS_PL_ALARM_START);
+ /* Configuring PL alarm enable */
+ cfg = ~((pl_alarm_mask & AMS_ISR0_ALARM_2_TO_0_MASK) <<
+ AMS_CONF1_ALARM_2_TO_0_SHIFT);
+ cfg &= ~((pl_alarm_mask & AMS_ISR0_ALARM_6_TO_3_MASK) <<
+ AMS_CONF1_ALARM_6_TO_3_SHIFT);
+ ams_pl_update_reg(ams, AMS_REG_CONFIG1,
+ AMS_REGCFG1_ALARM_MASK, cfg);
+
+ cfg = ~((pl_alarm_mask >> AMS_CONF3_ALARM_12_TO_7_SHIFT) &
+ AMS_ISR0_ALARM_12_TO_7_MASK);
+ ams_pl_update_reg(ams, AMS_REG_CONFIG3,
+ AMS_REGCFG3_ALARM_MASK, cfg);
+ }
+
+ spin_lock_irqsave(&ams->lock, flags);
+ ams_update_intrmask(ams, AMS_ISR0_ALARM_MASK, ~alarm_mask);
+ spin_unlock_irqrestore(&ams->lock, flags);
+}
+
+static void ams_enable_channel_sequence(struct ams *ams)
+{
+ int i;
+ unsigned long long scan_mask;
+ struct iio_dev *indio_dev = iio_priv_to_dev(ams);
+
+ /*
+ * Enable channel sequence. First 22 bit of scan_mask represent
+ * PS channels, and next remaining bit represents PL channels.
+ */
+
+ /* Run calibration of PS & PL as part of the sequence */
+ scan_mask = 0x1 | BIT(PS_SEQ_MAX);
+ for (i = 0; i < indio_dev->num_channels; i++)
+ scan_mask |= BIT(indio_dev->channels[i].scan_index);
+
+ if (ams->ps_base) {
+ /* put sysmon in a soft reset to change the sequence */
+ ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK,
+ AMS_CONF1_SEQ_DEFAULT);
+
+ /* configure basic channels */
+ writel(scan_mask & AMS_REG_SEQ0_MASK,
+ ams->ps_base + AMS_REG_SEQ_CH0);
+ writel(AMS_REG_SEQ2_MASK &
+ (scan_mask >> AMS_REG_SEQ2_MASK_SHIFT),
+ ams->ps_base + AMS_REG_SEQ_CH2);
+
+ /* set continuous sequence mode */
+ ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK,
+ AMS_CONF1_SEQ_CONTINUOUS);
+ }
+
+ if (ams->pl_base) {
+ /* put sysmon in a soft reset to change the sequence */
+ ams_pl_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK,
+ AMS_CONF1_SEQ_DEFAULT);
+
+ /* configure basic channels */
+ scan_mask = scan_mask >> PS_SEQ_MAX;
+ writel(scan_mask & AMS_REG_SEQ0_MASK,
+ ams->pl_base + AMS_REG_SEQ_CH0);
+ writel(AMS_REG_SEQ2_MASK &
+ (scan_mask >> AMS_REG_SEQ2_MASK_SHIFT),
+ ams->pl_base + AMS_REG_SEQ_CH2);
+ writel(AMS_REG_SEQ1_MASK &
+ (scan_mask >> AMS_REG_SEQ1_MASK_SHIFT),
+ ams->pl_base + AMS_REG_SEQ_CH1);
+
+ /* set continuous sequence mode */
+ ams_pl_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK,
+ AMS_CONF1_SEQ_CONTINUOUS);
+ }
+}
+
+static int iio_ams_init_device(struct ams *ams)
+{
+ u32 reg;
+ int ret;
+
+ /* reset AMS */
+ if (ams->ps_base) {
+ writel(AMS_PS_RESET_VALUE, ams->ps_base + AMS_VP_VN);
+
+ ret = readl_poll_timeout(ams->base + AMS_PS_CSTS, reg,
+ (reg & AMS_PS_CSTS_PS_READY) ==
+ AMS_PS_CSTS_PS_READY, 0,
+ AMS_INIT_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* put sysmon in a default state */
+ ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK,
+ AMS_CONF1_SEQ_DEFAULT);
+ }
+
+ if (ams->pl_base) {
+ writel(AMS_PL_RESET_VALUE, ams->pl_base + AMS_VP_VN);
+
+ ret = readl_poll_timeout(ams->base + AMS_PL_CSTS, reg,
+ (reg & AMS_PL_CSTS_ACCESS_MASK) ==
+ AMS_PL_CSTS_ACCESS_MASK, 0,
+ AMS_INIT_TIMEOUT);
+ if (ret)
+ return ret;
+
+ /* put sysmon in a default state */
+ ams_pl_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK,
+ AMS_CONF1_SEQ_DEFAULT);
+ }
+
+ ams_disable_all_alarms(ams);
+
+ /* Disable interrupt */
+ ams_update_intrmask(ams, ~0, ~0);
+
+ /* Clear any pending interrupt */
+ writel(AMS_ISR0_ALARM_MASK, ams->base + AMS_ISR_0);
+ writel(AMS_ISR1_ALARM_MASK, ams->base + AMS_ISR_1);
+
+ return 0;
+}
+
+static int ams_enable_single_channel(struct ams *ams, unsigned int offset)
+{
+ u8 channel_num = 0;
+
+ switch (offset) {
+ case AMS_VCC_PSPLL0:
+ channel_num = AMS_VCC_PSPLL0_CH;
+ break;
+ case AMS_VCC_PSPLL3:
+ channel_num = AMS_VCC_PSPLL3_CH;
+ break;
+ case AMS_VCCINT:
+ channel_num = AMS_VCCINT_CH;
+ break;
+ case AMS_VCCBRAM:
+ channel_num = AMS_VCCBRAM_CH;
+ break;
+ case AMS_VCCAUX:
+ channel_num = AMS_VCCAUX_CH;
+ break;
+ case AMS_PSDDRPLL:
+ channel_num = AMS_PSDDRPLL_CH;
+ break;
+ case AMS_PSINTFPDDR:
+ channel_num = AMS_PSINTFPDDR_CH;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set single channel, sequencer off mode */
+ ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK,
+ AMS_CONF1_SEQ_SINGLE_CHANNEL);
+
+ /* write the channel number */
+ ams_ps_update_reg(ams, AMS_REG_CONFIG0, AMS_CONF0_CHANNEL_NUM_MASK,
+ channel_num);
+ mdelay(1);
+
+ return 0;
+}
+
+static int ams_read_vcc_reg(struct ams *ams, unsigned int offset, u32 *data)
+{
+ int ret;
+
+ ret = ams_enable_single_channel(ams, offset);
+ if (ret)
+ return ret;
+
+ *data = readl(ams->base + offset);
+ ams_enable_channel_sequence(ams);
+
+ return 0;
+}
+
+static int ams_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct ams *ams = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&ams->mutex);
+ if (chan->scan_index >= (PS_SEQ_MAX * 3)) {
+ ret = ams_read_vcc_reg(ams, chan->address, val);
+ if (ret)
+ goto read_raw_err;
+ } else if (chan->scan_index >= PS_SEQ_MAX)
+ *val = readl(ams->pl_base + chan->address);
+ else
+ *val = readl(ams->ps_base + chan->address);
+ mutex_unlock(&ams->mutex);
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ switch (chan->address) {
+ case AMS_SUPPLY1:
+ case AMS_SUPPLY2:
+ case AMS_SUPPLY3:
+ case AMS_SUPPLY4:
+ *val = AMS_SUPPLY_SCALE_3VOLT;
+ break;
+ case AMS_SUPPLY5:
+ case AMS_SUPPLY6:
+ if (chan->scan_index < PS_SEQ_MAX)
+ *val = AMS_SUPPLY_SCALE_6VOLT;
+ else
+ *val = AMS_SUPPLY_SCALE_3VOLT;
+ break;
+ case AMS_SUPPLY7:
+ case AMS_SUPPLY8:
+ *val = AMS_SUPPLY_SCALE_6VOLT;
+ break;
+ case AMS_SUPPLY9:
+ case AMS_SUPPLY10:
+ if (chan->scan_index < PS_SEQ_MAX)
+ *val = AMS_SUPPLY_SCALE_3VOLT;
+ else
+ *val = AMS_SUPPLY_SCALE_6VOLT;
+ break;
+ case AMS_VCC_PSPLL0:
+ case AMS_VCC_PSPLL3:
+ case AMS_VCCINT:
+ case AMS_VCCBRAM:
+ case AMS_VCCAUX:
+ case AMS_PSDDRPLL:
+ case AMS_PSINTFPDDR:
+ *val = AMS_SUPPLY_SCALE_3VOLT;
+ break;
+ default:
+ *val = AMS_SUPPLY_SCALE_1VOLT;
+ break;
+ }
+ *val2 = AMS_SUPPLY_SCALE_DIV_BIT;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ case IIO_TEMP:
+ *val = AMS_TEMP_SCALE;
+ *val2 = AMS_TEMP_SCALE_DIV_BIT;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OFFSET:
+ /* Only the temperature channel has an offset */
+ *val = AMS_TEMP_OFFSET;
+ return IIO_VAL_INT;
+ }
+
+read_raw_err:
+ return -EINVAL;
+}
+
+static int ams_get_alarm_offset(int scan_index, enum iio_event_direction dir)
+{
+ int offset = 0;
+
+ if (scan_index >= PS_SEQ_MAX)
+ scan_index -= PS_SEQ_MAX;
+
+ if (dir == IIO_EV_DIR_FALLING) {
+ if (scan_index < AMS_SEQ_SUPPLY7)
+ offset = AMS_ALARM_THRESHOLD_OFF_10;
+ else
+ offset = AMS_ALARM_THRESHOLD_OFF_20;
+ }
+
+ switch (scan_index) {
+ case AMS_SEQ_TEMP:
+ return AMS_ALARM_TEMP + offset;
+ case AMS_SEQ_SUPPLY1:
+ return AMS_ALARM_SUPPLY1 + offset;
+ case AMS_SEQ_SUPPLY2:
+ return AMS_ALARM_SUPPLY2 + offset;
+ case AMS_SEQ_SUPPLY3:
+ return AMS_ALARM_SUPPLY3 + offset;
+ case AMS_SEQ_SUPPLY4:
+ return AMS_ALARM_SUPPLY4 + offset;
+ case AMS_SEQ_SUPPLY5:
+ return AMS_ALARM_SUPPLY5 + offset;
+ case AMS_SEQ_SUPPLY6:
+ return AMS_ALARM_SUPPLY6 + offset;
+ case AMS_SEQ_SUPPLY7:
+ return AMS_ALARM_SUPPLY7 + offset;
+ case AMS_SEQ_SUPPLY8:
+ return AMS_ALARM_SUPPLY8 + offset;
+ case AMS_SEQ_SUPPLY9:
+ return AMS_ALARM_SUPPLY9 + offset;
+ case AMS_SEQ_SUPPLY10:
+ return AMS_ALARM_SUPPLY10 + offset;
+ case AMS_SEQ_VCCAMS:
+ return AMS_ALARM_VCCAMS + offset;
+ case AMS_SEQ_TEMP_REMOTE:
+ return AMS_ALARM_TEMP_REMOTE + offset;
+ }
+
+ return 0;
+}
+
+static const struct iio_chan_spec *ams_event_to_channel(
+ struct iio_dev *indio_dev, u32 event)
+{
+ int scan_index = 0, i;
+
+ if (event >= AMS_PL_ALARM_START) {
+ event -= AMS_PL_ALARM_START;
+ scan_index = PS_SEQ_MAX;
+ }
+
+ switch (event) {
+ case AMS_ALARM_BIT_TEMP:
+ scan_index += AMS_SEQ_TEMP;
+ break;
+ case AMS_ALARM_BIT_SUPPLY1:
+ scan_index += AMS_SEQ_SUPPLY1;
+ break;
+ case AMS_ALARM_BIT_SUPPLY2:
+ scan_index += AMS_SEQ_SUPPLY2;
+ break;
+ case AMS_ALARM_BIT_SUPPLY3:
+ scan_index += AMS_SEQ_SUPPLY3;
+ break;
+ case AMS_ALARM_BIT_SUPPLY4:
+ scan_index += AMS_SEQ_SUPPLY4;
+ break;
+ case AMS_ALARM_BIT_SUPPLY5:
+ scan_index += AMS_SEQ_SUPPLY5;
+ break;
+ case AMS_ALARM_BIT_SUPPLY6:
+ scan_index += AMS_SEQ_SUPPLY6;
+ break;
+ case AMS_ALARM_BIT_SUPPLY7:
+ scan_index += AMS_SEQ_SUPPLY7;
+ break;
+ case AMS_ALARM_BIT_SUPPLY8:
+ scan_index += AMS_SEQ_SUPPLY8;
+ break;
+ case AMS_ALARM_BIT_SUPPLY9:
+ scan_index += AMS_SEQ_SUPPLY9;
+ break;
+ case AMS_ALARM_BIT_SUPPLY10:
+ scan_index += AMS_SEQ_SUPPLY10;
+ break;
+ case AMS_ALARM_BIT_VCCAMS:
+ scan_index += AMS_SEQ_VCCAMS;
+ break;
+ case AMS_ALARM_BIT_TEMP_REMOTE:
+ scan_index += AMS_SEQ_TEMP_REMOTE;
+ break;
+ }
+
+ for (i = 0; i < indio_dev->num_channels; i++)
+ if (indio_dev->channels[i].scan_index == scan_index)
+ break;
+
+ return &indio_dev->channels[i];
+}
+
+static int ams_get_alarm_mask(int scan_index)
+{
+ int bit = 0;
+
+ if (scan_index >= PS_SEQ_MAX) {
+ bit = AMS_PL_ALARM_START;
+ scan_index -= PS_SEQ_MAX;
+ }
+
+ switch (scan_index) {
+ case AMS_SEQ_TEMP:
+ return BIT(AMS_ALARM_BIT_TEMP + bit);
+ case AMS_SEQ_SUPPLY1:
+ return BIT(AMS_ALARM_BIT_SUPPLY1 + bit);
+ case AMS_SEQ_SUPPLY2:
+ return BIT(AMS_ALARM_BIT_SUPPLY2 + bit);
+ case AMS_SEQ_SUPPLY3:
+ return BIT(AMS_ALARM_BIT_SUPPLY3 + bit);
+ case AMS_SEQ_SUPPLY4:
+ return BIT(AMS_ALARM_BIT_SUPPLY4 + bit);
+ case AMS_SEQ_SUPPLY5:
+ return BIT(AMS_ALARM_BIT_SUPPLY5 + bit);
+ case AMS_SEQ_SUPPLY6:
+ return BIT(AMS_ALARM_BIT_SUPPLY6 + bit);
+ case AMS_SEQ_SUPPLY7:
+ return BIT(AMS_ALARM_BIT_SUPPLY7 + bit);
+ case AMS_SEQ_SUPPLY8:
+ return BIT(AMS_ALARM_BIT_SUPPLY8 + bit);
+ case AMS_SEQ_SUPPLY9:
+ return BIT(AMS_ALARM_BIT_SUPPLY9 + bit);
+ case AMS_SEQ_SUPPLY10:
+ return BIT(AMS_ALARM_BIT_SUPPLY10 + bit);
+ case AMS_SEQ_VCCAMS:
+ return BIT(AMS_ALARM_BIT_VCCAMS + bit);
+ case AMS_SEQ_TEMP_REMOTE:
+ return BIT(AMS_ALARM_BIT_TEMP_REMOTE + bit);
+ }
+
+ return 0;
+}
+
+static int ams_read_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir)
+{
+ struct ams *ams = iio_priv(indio_dev);
+
+ return (ams->alarm_mask & ams_get_alarm_mask(chan->scan_index)) ? 1 : 0;
+}
+
+static int ams_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ int state)
+{
+ struct ams *ams = iio_priv(indio_dev);
+ unsigned int alarm;
+
+ alarm = ams_get_alarm_mask(chan->scan_index);
+
+ mutex_lock(&ams->mutex);
+
+ if (state)
+ ams->alarm_mask |= alarm;
+ else
+ ams->alarm_mask &= ~alarm;
+
+ iio_ams_update_alarm(ams, ams->alarm_mask);
+
+ mutex_unlock(&ams->mutex);
+
+ return 0;
+}
+
+static int ams_read_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info, int *val, int *val2)
+{
+ struct ams *ams = iio_priv(indio_dev);
+ unsigned int offset = ams_get_alarm_offset(chan->scan_index, dir);
+
+ mutex_lock(&ams->mutex);
+
+ if (chan->scan_index >= PS_SEQ_MAX)
+ *val = readl(ams->pl_base + offset);
+ else
+ *val = readl(ams->ps_base + offset);
+
+ mutex_unlock(&ams->mutex);
+
+ return IIO_VAL_INT;
+}
+
+static int ams_write_event_value(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir,
+ enum iio_event_info info, int val, int val2)
+{
+ struct ams *ams = iio_priv(indio_dev);
+ unsigned int offset;
+
+ mutex_lock(&ams->mutex);
+
+ /* Set temperature channel threshold to direct threshold */
+ if (chan->type == IIO_TEMP) {
+ offset = ams_get_alarm_offset(chan->scan_index,
+ IIO_EV_DIR_FALLING);
+
+ if (chan->scan_index >= PS_SEQ_MAX)
+ ams_pl_update_reg(ams, offset,
+ AMS_ALARM_THR_DIRECT_MASK,
+ AMS_ALARM_THR_DIRECT_MASK);
+ else
+ ams_ps_update_reg(ams, offset,
+ AMS_ALARM_THR_DIRECT_MASK,
+ AMS_ALARM_THR_DIRECT_MASK);
+ }
+
+ offset = ams_get_alarm_offset(chan->scan_index, dir);
+ if (chan->scan_index >= PS_SEQ_MAX)
+ writel(val, ams->pl_base + offset);
+ else
+ writel(val, ams->ps_base + offset);
+
+ mutex_unlock(&ams->mutex);
+
+ return 0;
+}
+
+static void ams_handle_event(struct iio_dev *indio_dev, u32 event)
+{
+ const struct iio_chan_spec *chan;
+
+ chan = ams_event_to_channel(indio_dev, event);
+
+ if (chan->type == IIO_TEMP) {
+ /* The temperature channel only supports over-temperature
+ * events
+ */
+ iio_push_event(indio_dev,
+ IIO_UNMOD_EVENT_CODE(chan->type, chan->channel,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_RISING),
+ iio_get_time_ns(indio_dev));
+ } else {
+ /* For other channels we don't know whether it is a upper or
+ * lower threshold event. Userspace will have to check the
+ * channel value if it wants to know.
+ */
+ iio_push_event(indio_dev,
+ IIO_UNMOD_EVENT_CODE(chan->type, chan->channel,
+ IIO_EV_TYPE_THRESH,
+ IIO_EV_DIR_EITHER),
+ iio_get_time_ns(indio_dev));
+ }
+}
+
+static void ams_handle_events(struct iio_dev *indio_dev, unsigned long events)
+{
+ unsigned int bit;
+
+ for_each_set_bit(bit, &events, AMS_NO_OF_ALARMS)
+ ams_handle_event(indio_dev, bit);
+}
+
+/**
+ * ams_unmask_worker - ams alarm interrupt unmask worker
+ * @work : work to be done
+ *
+ * The ZynqMP threshold interrupts are level sensitive. Since we can't make the
+ * threshold condition go way from within the interrupt handler, this means as
+ * soon as a threshold condition is present we would enter the interrupt handler
+ * again and again. To work around this we mask all active threshold interrupts
+ * in the interrupt handler and start a timer. In this timer we poll the
+ * interrupt status and only if the interrupt is inactive we unmask it again.
+ */
+static void ams_unmask_worker(struct work_struct *work)
+{
+ struct ams *ams = container_of(work, struct ams, ams_unmask_work.work);
+ unsigned int status, unmask;
+
+ spin_lock_irq(&ams->lock);
+
+ status = readl(ams->base + AMS_ISR_0);
+
+ /* Clear those bits which are not active anymore */
+ unmask = (ams->masked_alarm ^ status) & ams->masked_alarm;
+
+ /* clear status of disabled alarm */
+ unmask |= ams->intr_mask;
+
+ ams->masked_alarm &= status;
+
+ /* Also clear those which are masked out anyway */
+ ams->masked_alarm &= ~ams->intr_mask;
+
+ /* Clear the interrupts before we unmask them */
+ writel(unmask, ams->base + AMS_ISR_0);
+
+ ams_update_intrmask(ams, 0, 0);
+
+ spin_unlock_irq(&ams->lock);
+
+ /* if still pending some alarm re-trigger the timer */
+ if (ams->masked_alarm)
+ schedule_delayed_work(&ams->ams_unmask_work,
+ msecs_to_jiffies(AMS_UNMASK_TIMEOUT_MS));
+}
+
+static irqreturn_t ams_iio_irq(int irq, void *data)
+{
+ unsigned int isr0, isr1;
+ struct iio_dev *indio_dev = data;
+ struct ams *ams = iio_priv(indio_dev);
+
+ spin_lock(&ams->lock);
+
+ isr0 = readl(ams->base + AMS_ISR_0);
+ isr1 = readl(ams->base + AMS_ISR_1);
+
+ /* only process alarms that are not masked */
+ isr0 &= ~((ams->intr_mask & AMS_ISR0_ALARM_MASK) | ams->masked_alarm);
+ isr1 &= ~(ams->intr_mask >> AMS_ISR1_INTR_MASK_SHIFT);
+
+ /* clear interrupt */
+ writel(isr0, ams->base + AMS_ISR_0);
+ writel(isr1, ams->base + AMS_ISR_1);
+
+ if (isr0) {
+ /* Once the alarm interrupt occurred, mask until get cleared */
+ ams->masked_alarm |= isr0;
+ ams_update_intrmask(ams, 0, 0);
+
+ ams_handle_events(indio_dev, isr0);
+
+ schedule_delayed_work(&ams->ams_unmask_work,
+ msecs_to_jiffies(AMS_UNMASK_TIMEOUT_MS));
+ }
+
+ spin_unlock(&ams->lock);
+
+ return IRQ_HANDLED;
+}
+
+static const struct iio_event_spec ams_temp_events[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE),
+ },
+};
+
+static const struct iio_event_spec ams_voltage_events[] = {
+ {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE),
+ }, {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_separate = BIT(IIO_EV_INFO_VALUE),
+ }, {
+ .type = IIO_EV_TYPE_THRESH,
+ .dir = IIO_EV_DIR_EITHER,
+ .mask_separate = BIT(IIO_EV_INFO_ENABLE),
+ },
+};
+
+static const struct iio_chan_spec ams_ps_channels[] = {
+ AMS_PS_CHAN_TEMP(AMS_SEQ_TEMP, AMS_TEMP, "ps_temp"),
+ AMS_PS_CHAN_TEMP(AMS_SEQ_TEMP_REMOTE, AMS_TEMP_REMOTE, "remote_temp"),
+ AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY1, AMS_SUPPLY1, "vccpsintlp"),
+ AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY2, AMS_SUPPLY2, "vccpsintfp"),
+ AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY3, AMS_SUPPLY3, "vccpsaux"),
+ AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY4, AMS_SUPPLY4, "vccpsddr"),
+ AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY5, AMS_SUPPLY5, "vccpsio3"),
+ AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY6, AMS_SUPPLY6, "vccpsio0"),
+ AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY7, AMS_SUPPLY7, "vccpsio1"),
+ AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY8, AMS_SUPPLY8, "vccpsio2"),
+ AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY9, AMS_SUPPLY9, "psmgtravcc"),
+ AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY10, AMS_SUPPLY10, "psmgtravtt"),
+ AMS_PS_CHAN_VOLTAGE(AMS_SEQ_VCCAMS, AMS_VCCAMS, "vccams"),
+};
+
+static const struct iio_chan_spec ams_pl_channels[] = {
+ AMS_PL_CHAN_TEMP(AMS_SEQ_TEMP, AMS_TEMP, "pl_temp"),
+ AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY1, AMS_SUPPLY1, "vccint", true),
+ AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY2, AMS_SUPPLY2, "vccaux", true),
+ AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VREFP, AMS_VREFP, "vccvrefp", false),
+ AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VREFN, AMS_VREFN, "vccvrefn", false),
+ AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY3, AMS_SUPPLY3, "vccbram", true),
+ AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY4, AMS_SUPPLY4, "vccplintlp", true),
+ AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY5, AMS_SUPPLY5, "vccplintfp", true),
+ AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY6, AMS_SUPPLY6, "vccplaux", true),
+ AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VCCAMS, AMS_VCCAMS, "vccams", true),
+ AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VP_VN, AMS_VP_VN, "vccvpvn", false),
+ AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY7, AMS_SUPPLY7, "vuser0", true),
+ AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY8, AMS_SUPPLY8, "vuser1", true),
+ AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY9, AMS_SUPPLY9, "vuser2", true),
+ AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY10, AMS_SUPPLY10, "vuser3", true),
+ AMS_PL_AUX_CHAN_VOLTAGE(0, "vccaux0"),
+ AMS_PL_AUX_CHAN_VOLTAGE(1, "vccaux1"),
+ AMS_PL_AUX_CHAN_VOLTAGE(2, "vccaux2"),
+ AMS_PL_AUX_CHAN_VOLTAGE(3, "vccaux3"),
+ AMS_PL_AUX_CHAN_VOLTAGE(4, "vccaux4"),
+ AMS_PL_AUX_CHAN_VOLTAGE(5, "vccaux5"),
+ AMS_PL_AUX_CHAN_VOLTAGE(6, "vccaux6"),
+ AMS_PL_AUX_CHAN_VOLTAGE(7, "vccaux7"),
+ AMS_PL_AUX_CHAN_VOLTAGE(8, "vccaux8"),
+ AMS_PL_AUX_CHAN_VOLTAGE(9, "vccaux9"),
+ AMS_PL_AUX_CHAN_VOLTAGE(10, "vccaux10"),
+ AMS_PL_AUX_CHAN_VOLTAGE(11, "vccaux11"),
+ AMS_PL_AUX_CHAN_VOLTAGE(12, "vccaux12"),
+ AMS_PL_AUX_CHAN_VOLTAGE(13, "vccaux13"),
+ AMS_PL_AUX_CHAN_VOLTAGE(14, "vccaux14"),
+ AMS_PL_AUX_CHAN_VOLTAGE(15, "vccaux15"),
+};
+
+static const struct iio_chan_spec ams_ctrl_channels[] = {
+ AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCC_PSPLL, AMS_VCC_PSPLL0, "vcc_pspll0"),
+ AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCC_PSBATT, AMS_VCC_PSPLL3, "vcc_psbatt"),
+ AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCCINT, AMS_VCCINT, "vccint"),
+ AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCCBRAM, AMS_VCCBRAM, "vccbram"),
+ AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCCAUX, AMS_VCCAUX, "vccaux"),
+ AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_PSDDRPLL, AMS_PSDDRPLL, "psddrpll"),
+ AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_INTDDR, AMS_PSINTFPDDR, "psintfpddr"),
+};
+
+static int ams_init_module(struct iio_dev *indio_dev, struct device_node *np,
+ struct iio_chan_spec *channels)
+{
+ struct ams *ams = iio_priv(indio_dev);
+ struct device_node *chan_node, *child;
+ int ret, num_channels = 0;
+ unsigned int reg;
+
+ if (of_device_is_compatible(np, "xlnx,zynqmp-ams-ps")) {
+ ams->ps_base = of_iomap(np, 0);
+ if (!ams->ps_base)
+ return -ENXIO;
+
+ /* add PS channels to iio device channels */
+ memcpy(channels + num_channels, ams_ps_channels,
+ sizeof(ams_ps_channels));
+ num_channels += ARRAY_SIZE(ams_ps_channels);
+ } else if (of_device_is_compatible(np, "xlnx,zynqmp-ams-pl")) {
+ ams->pl_base = of_iomap(np, 0);
+ if (!ams->pl_base)
+ return -ENXIO;
+
+ /* Copy only first 10 fix channels */
+ memcpy(channels + num_channels, ams_pl_channels,
+ AMS_PL_MAX_FIXED_CHANNEL * sizeof(*channels));
+ num_channels += AMS_PL_MAX_FIXED_CHANNEL;
+
+ chan_node = of_get_child_by_name(np, "xlnx,ext-channels");
+ if (chan_node) {
+ for_each_child_of_node(chan_node, child) {
+ ret = of_property_read_u32(child, "reg", ®);
+ if (ret || reg > AMS_PL_MAX_EXT_CHANNEL)
+ continue;
+
+ memcpy(&channels[num_channels],
+ &ams_pl_channels[reg +
+ AMS_PL_MAX_FIXED_CHANNEL],
+ sizeof(*channels));
+
+ if (of_property_read_bool(child,
+ "xlnx,bipolar"))
+ channels[num_channels].scan_type.sign =
+ 's';
+
+ num_channels += 1;
+ }
+ }
+ of_node_put(chan_node);
+ } else if (of_device_is_compatible(np, "xlnx,zynqmp-ams")) {
+ /* add AMS channels to iio device channels */
+ memcpy(channels + num_channels, ams_ctrl_channels,
+ sizeof(ams_ctrl_channels));
+ num_channels += ARRAY_SIZE(ams_ctrl_channels);
+ } else {
+ return -EINVAL;
+ }
+
+ return num_channels;
+}
+
+static int ams_parse_dt(struct iio_dev *indio_dev, struct platform_device *pdev)
+{
+ struct ams *ams = iio_priv(indio_dev);
+ struct iio_chan_spec *ams_channels, *dev_channels;
+ struct device_node *child_node = NULL, *np = pdev->dev.of_node;
+ int ret, chan_vol = 0, chan_temp = 0, i, rising_off, falling_off;
+ unsigned int num_channels = 0;
+
+ /* Initialize buffer for channel specification */
+ ams_channels = kzalloc(sizeof(ams_ps_channels) +
+ sizeof(ams_pl_channels) +
+ sizeof(ams_ctrl_channels), GFP_KERNEL);
+ if (!ams_channels)
+ return -ENOMEM;
+
+ if (of_device_is_available(np)) {
+ ret = ams_init_module(indio_dev, np, ams_channels);
+ if (ret < 0) {
+ kfree(ams_channels);
+ return ret;
+ }
+
+ num_channels += ret;
+ }
+
+ for_each_child_of_node(np, child_node) {
+ if (of_device_is_available(child_node)) {
+ ret = ams_init_module(indio_dev, child_node,
+ ams_channels + num_channels);
+ if (ret < 0) {
+ kfree(ams_channels);
+ return ret;
+ }
+
+ num_channels += ret;
+ }
+ }
+
+ for (i = 0; i < num_channels; i++) {
+ if (ams_channels[i].type == IIO_VOLTAGE)
+ ams_channels[i].channel = chan_vol++;
+ else
+ ams_channels[i].channel = chan_temp++;
+
+ if (ams_channels[i].scan_index < (PS_SEQ_MAX * 3)) {
+ /* set threshold to max and min for each channel */
+ falling_off = ams_get_alarm_offset(
+ ams_channels[i].scan_index,
+ IIO_EV_DIR_FALLING);
+ rising_off = ams_get_alarm_offset(
+ ams_channels[i].scan_index,
+ IIO_EV_DIR_RISING);
+ if (ams_channels[i].scan_index >= PS_SEQ_MAX) {
+ writel(AMS_ALARM_THR_MIN,
+ ams->pl_base + falling_off);
+ writel(AMS_ALARM_THR_MAX,
+ ams->pl_base + rising_off);
+ } else {
+ writel(AMS_ALARM_THR_MIN,
+ ams->ps_base + falling_off);
+ writel(AMS_ALARM_THR_MAX,
+ ams->ps_base + rising_off);
+ }
+ }
+ }
+
+ dev_channels = devm_kzalloc(&pdev->dev, sizeof(*dev_channels) *
+ num_channels, GFP_KERNEL);
+ if (!dev_channels) {
+ kfree(ams_channels);
+ return -ENOMEM;
+ }
+
+ memcpy(dev_channels, ams_channels,
+ sizeof(*ams_channels) * num_channels);
+ kfree(ams_channels);
+ indio_dev->channels = dev_channels;
+ indio_dev->num_channels = num_channels;
+
+ return 0;
+}
+
+static const struct iio_info iio_pl_info = {
+ .read_raw = &ams_read_raw,
+ .read_event_config = &ams_read_event_config,
+ .write_event_config = &ams_write_event_config,
+ .read_event_value = &ams_read_event_value,
+ .write_event_value = &ams_write_event_value,
+};
+
+static const struct of_device_id ams_of_match_table[] = {
+ { .compatible = "xlnx,zynqmp-ams" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ams_of_match_table);
+
+static int ams_probe(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev;
+ struct ams *ams;
+ struct resource *res;
+ int ret;
+
+ if (!pdev->dev.of_node)
+ return -ENODEV;
+
+ indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*ams));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ ams = iio_priv(indio_dev);
+ mutex_init(&ams->mutex);
+ spin_lock_init(&ams->lock);
+
+ indio_dev->dev.parent = &pdev->dev;
+ indio_dev->dev.of_node = pdev->dev.of_node;
+ indio_dev->name = "ams";
+
+ indio_dev->info = &iio_pl_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ams-base");
+ ams->base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(ams->base))
+ return PTR_ERR(ams->base);
+
+ INIT_DELAYED_WORK(&ams->ams_unmask_work, ams_unmask_worker);
+
+ ams->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(ams->clk))
+ return PTR_ERR(ams->clk);
+ clk_prepare_enable(ams->clk);
+
+ ret = iio_ams_init_device(ams);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to initialize AMS\n");
+ goto clk_disable;
+ }
+
+ ret = ams_parse_dt(indio_dev, pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "failure in parsing DT\n");
+ goto clk_disable;
+ }
+
+ ams_enable_channel_sequence(ams);
+
+ ams->irq = platform_get_irq_byname(pdev, "ams-irq");
+ ret = devm_request_irq(&pdev->dev, ams->irq, &ams_iio_irq, 0, "ams-irq",
+ indio_dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to register interrupt\n");
+ goto clk_disable;
+ }
+
+ platform_set_drvdata(pdev, indio_dev);
+
+ return iio_device_register(indio_dev);
+
+clk_disable:
+ clk_disable_unprepare(ams->clk);
+
+ return ret;
+}
+
+static int ams_remove(struct platform_device *pdev)
+{
+ struct iio_dev *indio_dev = platform_get_drvdata(pdev);
+ struct ams *ams = iio_priv(indio_dev);
+
+ cancel_delayed_work(&ams->ams_unmask_work);
+
+ /* Unregister the device */
+ iio_device_unregister(indio_dev);
+ clk_disable_unprepare(ams->clk);
+
+ return 0;
+}
+
+static int __maybe_unused ams_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct ams *ams = iio_priv(indio_dev);
+
+ clk_disable_unprepare(ams->clk);
+
+ return 0;
+}
+
+static int __maybe_unused ams_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct ams *ams = iio_priv(indio_dev);
+
+ clk_prepare_enable(ams->clk);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(ams_pm_ops, ams_suspend, ams_resume);
+
+static struct platform_driver ams_driver = {
+ .probe = ams_probe,
+ .remove = ams_remove,
+ .driver = {
+ .name = "ams",
+ .pm = &ams_pm_ops,
+ .of_match_table = ams_of_match_table,
+ },
+};
+module_platform_driver(ams_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Xilinx, Inc.");
diff --git a/drivers/iio/adc/xilinx-ams.h b/drivers/iio/adc/xilinx-ams.h
new file mode 100644
index 0000000..b9fa262
--- /dev/null
+++ b/drivers/iio/adc/xilinx-ams.h
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xilinx AMS driver
+ *
+ * Copyright (C) 2017-2018 Xilinx, Inc.
+ *
+ * Manish Narani <mnarani@xilinx.com>
+ * Rajnikant Bhojani <rajnikant.bhojani@xilinx.com>
+ */
+
+#ifndef __XILINX_AMS_H__
+#define __XILINX_AMS_H__
+
+#define AMS_MISC_CTRL 0x000
+#define AMS_ISR_0 0x010
+#define AMS_ISR_1 0x014
+#define AMS_IMR_0 0x018
+#define AMS_IMR_1 0x01c
+#define AMS_IER_0 0x020
+#define AMS_IER_1 0x024
+#define AMS_IDR_0 0x028
+#define AMS_IDR_1 0x02c
+#define AMS_PS_CSTS 0x040
+#define AMS_PL_CSTS 0x044
+#define AMS_MON_CSTS 0x050
+
+#define AMS_VCC_PSPLL0 0x060
+#define AMS_VCC_PSPLL3 0x06C
+#define AMS_VCCINT 0x078
+#define AMS_VCCBRAM 0x07C
+#define AMS_VCCAUX 0x080
+#define AMS_PSDDRPLL 0x084
+#define AMS_PSINTFPDDR 0x09C
+
+#define AMS_VCC_PSPLL0_CH 48
+#define AMS_VCC_PSPLL3_CH 51
+#define AMS_VCCINT_CH 54
+#define AMS_VCCBRAM_CH 55
+#define AMS_VCCAUX_CH 56
+#define AMS_PSDDRPLL_CH 57
+#define AMS_PSINTFPDDR_CH 63
+
+#define AMS_REG_CONFIG0 0x100
+#define AMS_REG_CONFIG1 0x104
+#define AMS_REG_CONFIG2 0x108
+#define AMS_REG_CONFIG3 0x10C
+#define AMS_REG_CONFIG4 0x110
+#define AMS_REG_SEQ_CH0 0x120
+#define AMS_REG_SEQ_CH1 0x124
+#define AMS_REG_SEQ_CH2 0x118
+
+#define AMS_TEMP 0x000
+#define AMS_SUPPLY1 0x004
+#define AMS_SUPPLY2 0x008
+#define AMS_VP_VN 0x00c
+#define AMS_VREFP 0x010
+#define AMS_VREFN 0x014
+#define AMS_SUPPLY3 0x018
+#define AMS_SUPPLY4 0x034
+#define AMS_SUPPLY5 0x038
+#define AMS_SUPPLY6 0x03c
+#define AMS_SUPPLY7 0x200
+#define AMS_SUPPLY8 0x204
+#define AMS_SUPPLY9 0x208
+#define AMS_SUPPLY10 0x20c
+#define AMS_VCCAMS 0x210
+#define AMS_TEMP_REMOTE 0x214
+
+#define AMS_REG_VAUX(x) (0x40 + (4*(x)))
+#define AMS_REG_VUSER(x) (0x200 + (4*(x)))
+
+#define AMS_PS_RESET_VALUE 0xFFFFU
+#define AMS_PL_RESET_VALUE 0xFFFFU
+
+#define AMS_CONF0_CHANNEL_NUM_MASK (0x3f << 0)
+
+#define AMS_CONF1_SEQ_MASK (0xf << 12)
+#define AMS_CONF1_SEQ_DEFAULT (0 << 12)
+#define AMS_CONF1_SEQ_SINGLE_PASS (1 << 12)
+#define AMS_CONF1_SEQ_CONTINUOUS (2 << 12)
+#define AMS_CONF1_SEQ_SINGLE_CHANNEL (3 << 12)
+
+#define AMS_REG_SEQ0_MASK 0xFFFF
+#define AMS_REG_SEQ2_MASK 0x3F
+#define AMS_REG_SEQ1_MASK 0xFFFF
+#define AMS_REG_SEQ2_MASK_SHIFT 16
+#define AMS_REG_SEQ1_MASK_SHIFT 22
+
+#define AMS_REGCFG1_ALARM_MASK 0xF0F
+#define AMS_REGCFG3_ALARM_MASK 0x3F
+
+#define AMS_ALARM_TEMP 0x140
+#define AMS_ALARM_SUPPLY1 0x144
+#define AMS_ALARM_SUPPLY2 0x148
+#define AMS_ALARM_OT 0x14c
+
+#define AMS_ALARM_SUPPLY3 0x160
+#define AMS_ALARM_SUPPLY4 0x164
+#define AMS_ALARM_SUPPLY5 0x168
+#define AMS_ALARM_SUPPLY6 0x16c
+#define AMS_ALARM_SUPPLY7 0x180
+#define AMS_ALARM_SUPPLY8 0x184
+#define AMS_ALARM_SUPPLY9 0x188
+#define AMS_ALARM_SUPPLY10 0x18c
+#define AMS_ALARM_VCCAMS 0x190
+#define AMS_ALARM_TEMP_REMOTE 0x194
+#define AMS_ALARM_THRESHOLD_OFF_10 0x10
+#define AMS_ALARM_THRESHOLD_OFF_20 0x20
+
+#define AMS_ALARM_THR_DIRECT_MASK 0x01
+#define AMS_ALARM_THR_MIN 0x0000
+#define AMS_ALARM_THR_MAX 0xffff
+
+#define AMS_NO_OF_ALARMS 32
+#define AMS_PL_ALARM_START 16
+#define AMS_ISR0_ALARM_MASK 0xFFFFFFFFU
+#define AMS_ISR1_ALARM_MASK 0xE000001FU
+#define AMS_ISR1_INTR_MASK_SHIFT 32
+#define AMS_ISR0_ALARM_2_TO_0_MASK 0x07
+#define AMS_ISR0_ALARM_6_TO_3_MASK 0x78
+#define AMS_ISR0_ALARM_12_TO_7_MASK 0x3F
+#define AMS_CONF1_ALARM_2_TO_0_SHIFT 1
+#define AMS_CONF1_ALARM_6_TO_3_SHIFT 5
+#define AMS_CONF3_ALARM_12_TO_7_SHIFT 8
+
+#define AMS_PS_CSTS_PS_READY 0x08010000U
+#define AMS_PL_CSTS_ACCESS_MASK 0x00000001U
+
+#define AMS_PL_MAX_FIXED_CHANNEL 10
+#define AMS_PL_MAX_EXT_CHANNEL 20
+
+#define AMS_INIT_TIMEOUT 10000
+
+/*
+ * Following scale and offset value is derived from
+ * UG580 (v1.7) December 20, 2016
+ */
+#define AMS_SUPPLY_SCALE_1VOLT 1000
+#define AMS_SUPPLY_SCALE_3VOLT 3000
+#define AMS_SUPPLY_SCALE_6VOLT 6000
+#define AMS_SUPPLY_SCALE_DIV_BIT 16
+
+#define AMS_TEMP_SCALE 509314
+#define AMS_TEMP_SCALE_DIV_BIT 16
+#define AMS_TEMP_OFFSET -((280230L << 16) / 509314)
+
+enum ams_alarm_bit {
+ AMS_ALARM_BIT_TEMP,
+ AMS_ALARM_BIT_SUPPLY1,
+ AMS_ALARM_BIT_SUPPLY2,
+ AMS_ALARM_BIT_SUPPLY3,
+ AMS_ALARM_BIT_SUPPLY4,
+ AMS_ALARM_BIT_SUPPLY5,
+ AMS_ALARM_BIT_SUPPLY6,
+ AMS_ALARM_BIT_RESERVED,
+ AMS_ALARM_BIT_SUPPLY7,
+ AMS_ALARM_BIT_SUPPLY8,
+ AMS_ALARM_BIT_SUPPLY9,
+ AMS_ALARM_BIT_SUPPLY10,
+ AMS_ALARM_BIT_VCCAMS,
+ AMS_ALARM_BIT_TEMP_REMOTE
+};
+
+enum ams_seq {
+ AMS_SEQ_VCC_PSPLL,
+ AMS_SEQ_VCC_PSBATT,
+ AMS_SEQ_VCCINT,
+ AMS_SEQ_VCCBRAM,
+ AMS_SEQ_VCCAUX,
+ AMS_SEQ_PSDDRPLL,
+ AMS_SEQ_INTDDR
+};
+
+enum ams_ps_pl_seq {
+ AMS_SEQ_CALIB,
+ AMS_SEQ_RSVD_1,
+ AMS_SEQ_RSVD_2,
+ AMS_SEQ_TEST,
+ AMS_SEQ_RSVD_4,
+ AMS_SEQ_SUPPLY4,
+ AMS_SEQ_SUPPLY5,
+ AMS_SEQ_SUPPLY6,
+ AMS_SEQ_TEMP,
+ AMS_SEQ_SUPPLY2,
+ AMS_SEQ_SUPPLY1,
+ AMS_SEQ_VP_VN,
+ AMS_SEQ_VREFP,
+ AMS_SEQ_VREFN,
+ AMS_SEQ_SUPPLY3,
+ AMS_SEQ_CURRENT_MON,
+ AMS_SEQ_SUPPLY7,
+ AMS_SEQ_SUPPLY8,
+ AMS_SEQ_SUPPLY9,
+ AMS_SEQ_SUPPLY10,
+ AMS_SEQ_VCCAMS,
+ AMS_SEQ_TEMP_REMOTE,
+ AMS_SEQ_MAX
+};
+
+#define AMS_SEQ(x) (AMS_SEQ_MAX + (x))
+#define AMS_VAUX_SEQ(x) (AMS_SEQ_MAX + (x))
+
+#define PS_SEQ_MAX AMS_SEQ_MAX
+#define PS_SEQ(x) (x)
+#define PL_SEQ(x) (PS_SEQ_MAX + x)
+
+#define AMS_CHAN_TEMP(_scan_index, _addr, _ext) { \
+ .type = IIO_TEMP, \
+ .indexed = 1, \
+ .address = (_addr), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_OFFSET), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .event_spec = ams_temp_events, \
+ .num_event_specs = ARRAY_SIZE(ams_temp_events), \
+ .scan_index = (_scan_index), \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 12, \
+ .storagebits = 16, \
+ .shift = 4, \
+ .endianness = IIO_CPU, \
+ }, \
+ .extend_name = _ext, \
+}
+
+#define AMS_CHAN_VOLTAGE(_scan_index, _addr, _ext, _alarm) { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .address = (_addr), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .event_spec = (_alarm) ? ams_voltage_events : NULL, \
+ .num_event_specs = (_alarm) ? ARRAY_SIZE(ams_voltage_events) : 0, \
+ .scan_index = (_scan_index), \
+ .scan_type = { \
+ .realbits = 10, \
+ .storagebits = 16, \
+ .shift = 6, \
+ .endianness = IIO_CPU, \
+ }, \
+ .extend_name = _ext, \
+}
+
+#define AMS_PS_CHAN_TEMP(_scan_index, _addr, _ext) \
+ AMS_CHAN_TEMP(PS_SEQ(_scan_index), _addr, _ext)
+#define AMS_PS_CHAN_VOLTAGE(_scan_index, _addr, _ext) \
+ AMS_CHAN_VOLTAGE(PS_SEQ(_scan_index), _addr, _ext, true)
+
+#define AMS_PL_CHAN_TEMP(_scan_index, _addr, _ext) \
+ AMS_CHAN_TEMP(PL_SEQ(_scan_index), _addr, _ext)
+#define AMS_PL_CHAN_VOLTAGE(_scan_index, _addr, _ext, _alarm) \
+ AMS_CHAN_VOLTAGE(PL_SEQ(_scan_index), _addr, _ext, _alarm)
+#define AMS_PL_AUX_CHAN_VOLTAGE(_auxno, _ext) \
+ AMS_CHAN_VOLTAGE(PL_SEQ(AMS_VAUX_SEQ(_auxno)), \
+ AMS_REG_VAUX(_auxno), _ext, false)
+#define AMS_CTRL_CHAN_VOLTAGE(_scan_index, _addr, _ext) \
+ AMS_CHAN_VOLTAGE(PL_SEQ(AMS_VAUX_SEQ(AMS_SEQ(_scan_index))), \
+ _addr, _ext, false)
+
+struct ams {
+ void __iomem *base;
+ void __iomem *ps_base;
+ void __iomem *pl_base;
+ struct clk *clk;
+ struct device *dev;
+
+ struct mutex mutex;
+ spinlock_t lock;
+
+ unsigned int alarm_mask;
+ unsigned int masked_alarm;
+ u64 intr_mask;
+ int irq;
+
+ struct delayed_work ams_unmask_work;
+};
+
+#endif /* __XILINX_AMS_H__ */
--
2.1.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* [PATCH 3/3] arm64: zynqmp: DT: Add Xilinx AMS node
2018-08-30 10:22 [PATCH 0/3] Add Xilinx AMS Driver Manish Narani
2018-08-30 10:22 ` [PATCH 1/3] dt-bindings: iio: adc: Add Xilinx AMS binding documentation Manish Narani
2018-08-30 10:22 ` [PATCH 2/3] iio: adc: Add Xilinx AMS driver Manish Narani
@ 2018-08-30 10:22 ` Manish Narani
2 siblings, 0 replies; 13+ messages in thread
From: Manish Narani @ 2018-08-30 10:22 UTC (permalink / raw)
To: jic23, knaack.h, lars, pmeerw, robh+dt, mark.rutland,
michal.simek, manish.narani, leoyang.li, sudeep.holla,
amit.kucheria, broonie, arnaud.pouliquen, geert, eugen.hristev,
rdunlap, lukas, freeman.liu, vilhelm.gray, tglx, baolin.wang,
gregkh
Cc: sgoud, anirudh, linux-iio, devicetree, linux-arm-kernel,
linux-kernel
The Xilinx AMS includes an ADC as well as on-chip sensors that can be
used to sample external and monitor on-die operating conditions, such as
temperature and supply voltage levels.
Signed-off-by: Manish Narani <manish.narani@xilinx.com>
---
arch/arm64/boot/dts/xilinx/zynqmp.dtsi | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/arch/arm64/boot/dts/xilinx/zynqmp.dtsi b/arch/arm64/boot/dts/xilinx/zynqmp.dtsi
index 29ce234..6e42ca2 100644
--- a/arch/arm64/boot/dts/xilinx/zynqmp.dtsi
+++ b/arch/arm64/boot/dts/xilinx/zynqmp.dtsi
@@ -617,5 +617,31 @@
reg = <0x0 0xfd4d0000 0x0 0x1000>;
timeout-sec = <10>;
};
+
+ xilinx_ams: ams@ffa50000 {
+ compatible = "xlnx,zynqmp-ams";
+ status = "disabled";
+ interrupt-parent = <&gic>;
+ interrupts = <0 56 4>;
+ interrupt-names = "ams-irq";
+ reg = <0x0 0xffa50000 0x0 0x800>;
+ reg-names = "ams-base";
+ #address-cells = <2>;
+ #size-cells = <2>;
+ #io-channel-cells = <1>;
+ ranges;
+
+ ams_ps: ams_ps@ffa50800 {
+ compatible = "xlnx,zynqmp-ams-ps";
+ status = "disabled";
+ reg = <0x0 0xffa50800 0x0 0x400>;
+ };
+
+ ams_pl: ams_pl@ffa50c00 {
+ compatible = "xlnx,zynqmp-ams-pl";
+ status = "disabled";
+ reg = <0x0 0xffa50c00 0x0 0x400>;
+ };
+ };
};
};
--
2.1.1
^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH 2/3] iio: adc: Add Xilinx AMS driver
2018-08-30 10:22 ` [PATCH 2/3] iio: adc: Add Xilinx AMS driver Manish Narani
@ 2018-08-30 15:36 ` Randy Dunlap
2018-09-02 19:56 ` Jonathan Cameron
1 sibling, 0 replies; 13+ messages in thread
From: Randy Dunlap @ 2018-08-30 15:36 UTC (permalink / raw)
To: Manish Narani, jic23, knaack.h, lars, pmeerw, robh+dt,
mark.rutland, michal.simek, leoyang.li, sudeep.holla,
amit.kucheria, broonie, arnaud.pouliquen, geert, eugen.hristev,
lukas, freeman.liu, vilhelm.gray, tglx, baolin.wang, gregkh
Cc: sgoud, anirudh, linux-iio, devicetree, linux-arm-kernel,
linux-kernel
On 08/30/2018 03:22 AM, Manish Narani wrote:
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 4a75492..405ea00 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -941,4 +941,14 @@ config XILINX_XADC
> The driver can also be build as a module. If so, the module will be called
> xilinx-xadc.
>
> +config XILINX_AMS
> + tristate "Xilinx AMS driver"
> + depends on ARCH_ZYNQMP || COMPILE_TEST
> + depends on HAS_IOMEM
> + help
> + Say yes here to have support for the Xilinx AMS.
> +
> + The driver can also be build as a module. If so, the module will be called
built
> + xilinx-ams.
> +
> endmenu
--
~Randy
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/3] dt-bindings: iio: adc: Add Xilinx AMS binding documentation
2018-08-30 10:22 ` [PATCH 1/3] dt-bindings: iio: adc: Add Xilinx AMS binding documentation Manish Narani
@ 2018-09-02 18:14 ` Jonathan Cameron
2018-09-06 13:27 ` Manish Narani
2018-09-04 1:17 ` Rob Herring
1 sibling, 1 reply; 13+ messages in thread
From: Jonathan Cameron @ 2018-09-02 18:14 UTC (permalink / raw)
To: Manish Narani
Cc: knaack.h, lars, pmeerw, robh+dt, mark.rutland, michal.simek,
leoyang.li, sudeep.holla, amit.kucheria, broonie,
arnaud.pouliquen, geert, eugen.hristev, rdunlap, lukas,
freeman.liu, vilhelm.gray, tglx, baolin.wang, gregkh, sgoud,
anirudh, linux-iio, devicetree, linux-arm-kernel, linux-kernel
On Thu, 30 Aug 2018 15:52:17 +0530
Manish Narani <manish.narani@xilinx.com> wrote:
> Xilinx AMS have several ADC channels that can be used for measurement of
> different voltages and temperatures. Document the same in the bindings.
>
> Signed-off-by: Manish Narani <manish.narani@xilinx.com>
> ---
> .../devicetree/bindings/iio/adc/xilinx-ams.txt | 159 +++++++++++++++++++++
> 1 file changed, 159 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
>
> diff --git a/Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt b/Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
> new file mode 100644
> index 0000000..8cc96f0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
> @@ -0,0 +1,159 @@
> +Xilinx AMS controller
> +
> +The AMS includes an ADC as well as on-chip sensors that can be used to
> +sample external voltages and monitor on-die operating conditions, such as
> +temperature and supply voltage levels. The AMS has two SYSMON blocks.
> +PL-SYSMON block is capable of monitoring off chip voltage and temperature.
> +PL-SYSMON block has DRP, JTAG and I2C interface to enable monitoring from
> +external master. Out of this interface currently only DRP is supported.
> +Other block PS-SYSMON is memory mapped to PS. Both of block has built-in
> +alarm generation logic that is used to interrupt the processor based on
> +condition set.
I guess anyone reading this because they have the hardware would know what
PS and PL are, but it would still be nice to define those acronyms!
Google suggest, Programmable Logic (FPGA bit I guess) and Process Space
(Arm core bit?) As I read this, from a driver point of view it doesn't
really matter - these are just blocks of channels that are there?
This binding is complex enough I definitely want some DT binding
review.
> +
> +All designs should have AMS registers, but PS and PL are optional. The
> +AMS controller can work with only PS, only PL and both PS and PL
> +configurations. Please specify registers according to your design. Devicetree
> +should always have AMS module property. Providing PS & PL module is optional.
> +
> +Required properties:
> + - compatible: Should be "xlnx,zynqmp-ams"
> + - reg: Should specify AMS register space
> + - interrupts: Interrupt number for the AMS control interface
> + - interrupt-names: Interrupt name, must be "ams-irq"
> + - clocks: Should contain a clock specifier for the device
> + - ranges: keep the property empty to map child address space
> + (for PS and/or PL) nodes 1:1 onto the parent address
> + space
> +
> +AMS device tree subnode:
> + - compatible: Should be "xlnx,zynqmp-ams-ps" or "xlnx,zynqmp-ams-pl"
> + - reg: Register space for PS or PL
> +
> +Optional properties:
> +
> +Following optional property only valid for PL.
> + - xlnx,ext-channels: List of external channels that are connected to the
> + AMS PL module.
> +
> + The child nodes of this node represent the external channels which are
> + connected to the AMS Module. If the property is not present
> + no external channels will be assumed to be connected.
> +
> + Each child node represents one channel and has the following
> + properties:
> + Required properties:
> + * reg: Pair of pins the channel is connected to.
> + 0: VP/VN
Hmm. So we have this table here of the ones that may or may not be connected
with different numbering from the overall table. Given these indexes
are arbitrary can we unify them?
I.e. use the index from below but state here which values can be used?
> + 1: VUSER0
> + 2: VUSER1
> + 3: VUSER3
> + 4: VUSER4
> + 5: VAUXP[0]/VAUXN[0]
> + 6: VAUXP[1]/VAUXN[1]
> + ...
> + 20: VAUXP[15]/VAUXN[15]
> + Note each channel number should only be used at most
> + once.
> + Optional properties:
> + * xlnx,bipolar: If set the channel is used in bipolar
> + mode.
> +
> +
> +Example:
> + xilinx_ams: ams@ffa50000 {
> + compatible = "xlnx,zynqmp-ams";
> + interrupt-parent = <&gic>;
> + interrupts = <0 56 4>;
> + interrupt-names = "ams-irq";
> + clocks = <&clkc 70>;
> + reg = <0x0 0xffa50000 0x0 0x800>;
> + reg-names = "ams-base";
> + #address-cells = <2>;
> + #size-cells = <2>;
> + ranges;
> +
> + ams_ps: ams_ps@ffa50800 {
> + compatible = "xlnx,zynqmp-ams-ps";
> + reg = <0x0 0xffa50800 0x0 0x400>;
> + };
> +
> + ams_pl: ams_pl@ffa50c00 {
> + compatible = "xlnx,zynqmp-ams-pl";
> + reg = <0x0 0xffa50c00 0x0 0x400>;
> + xlnx,ext-channels {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + channel@0 {
> + reg = <0>;
> + xlnx,bipolar;
> + };
> + channel@1 {
> + reg = <1>;
> + };
> + channel@8 {
> + reg = <8>;
> + xlnx,bipolar;
> + };
> + };
> + };
> + };
> +
> +AMS Channels Details:
> +
> +Sysmon Block |Channel| Details |Measurement
> + Number Type
> +---------------------------------------------------------------------------------------------------------
> +AMS CTRL |0 |System PLLs voltage measurement, VCC_PSPLL. |Voltage
> + |1 |Battery voltage measurement, VCC_PSBATT. |Voltage
> + |2 |PL Internal voltage measurement, VCCINT. |Voltage
> + |3 |Block RAM voltage measurement, VCCBRAM. |Voltage
> + |4 |PL Aux voltage measurement, VCCAUX. |Voltage
> + |5 |Voltage measurement for six DDR I/O PLLs, VCC_PSDDR_PLL. |Voltage
> + |6 |VCC_PSINTFP_DDR voltage measurement. |Voltage
> +---------------------------------------------------------------------------------------------------------
> +PS Sysmon |7 |LPD temperature measurement. |Temperature
> + |8 |FPD Temperature Measurment (REMOTE). |Temperature
> + |9 |VCC PS LPD voltage measurement (supply1). |Voltage
> + |10 |VCC PS FPD voltage measurement (supply2). |Voltage
> + |11 |PS Aux voltage reference (supply3). |Voltage
> + |12 |DDR I/O VCC voltage measurement. |Voltage
> + |13 |PS IO Bank 503 voltage measurement (supply5). |Voltage
> + |14 |PS IO Bank 500 voltage measurement (supply6). |Voltage
> + |15 |VCCO_PSIO1 voltage measurement. |Voltage
> + |16 |VCCO_PSIO2 voltage measurement. |Voltage
> + |17 |VCC_PS_GTR voltage measurement (VPS_MGTRAVCC). |Voltage
> + |18 |VTT_PS_GTR voltage measurement (VPS_MGTRAVTT). |Voltage
> + |19 |VCC_PSADC voltage measurement. |Voltage
> +---------------------------------------------------------------------------------------------------------
> +PL Sysmon |20 |PL Temperature measurement. |Temperature
> + |21 |PL Internal Voltage Voltage measurement, VCCINT. |Voltage
> + |22 |PL Auxiliary Voltage measurement, VCCAUX. |Voltage
> + |23 |ADC Reference P+ Voltage measurement. |Voltage
> + |24 |ADC Reference N- Voltage measurement. |Voltage
> + |25 |PL Block RAM Voltage measurement, VCCBRAM. |Voltage
> + |26 |LPD Internal Voltage measurement, VCC_PSINTLP (supply4). |Voltage
> + |27 |FPD Internal Voltage measurement, VCC_PSINTFP (supply5). |Voltage
> + |28 |PS Auxiliary Voltage measurement (supply6). |Voltage
> + |29 |PL VCCADC Voltage measurement (vccams). |Voltage
> + |30 |Differencial analog input signal Voltage measurment. |Voltage
> + |31 |VUser0 Voltage measurement (supply7). |Voltage
> + |32 |VUser1 Voltage measurement (supply8). |Voltage
> + |33 |VUser2 Voltage measurement (supply9). |Voltage
> + |34 |VUser3 Voltage measurement (supply10). |Voltage
> + |35 |Auxiliary ch 0 Voltage measurement (VAux0). |Voltage
> + |36 |Auxiliary ch 1 Voltage measurement (VAux1). |Voltage
> + |37 |Auxiliary ch 2 Voltage measurement (VAux2). |Voltage
> + |38 |Auxiliary ch 3 Voltage measurement (VAux3). |Voltage
> + |39 |Auxiliary ch 4 Voltage measurement (VAux4). |Voltage
> + |40 |Auxiliary ch 5 Voltage measurement (VAux5). |Voltage
> + |41 |Auxiliary ch 6 Voltage measurement (VAux6). |Voltage
> + |42 |Auxiliary ch 7 Voltage measurement (VAux7). |Voltage
> + |43 |Auxiliary ch 8 Voltage measurement (VAux8). |Voltage
> + |44 |Auxiliary ch 9 Voltage measurement (VAux9). |Voltage
> + |45 |Auxiliary ch 10 Voltage measurement (VAux10). |Voltage
> + |46 |Auxiliary ch 11 Voltage measurement (VAux11). |Voltage
> + |47 |Auxiliary ch 12 Voltage measurement (VAux12). |Voltage
> + |48 |Auxiliary ch 13 Voltage measurement (VAux13). |Voltage
> + |49 |Auxiliary ch 14 Voltage measurement (VAux14). |Voltage
> + |50 |Auxiliary ch 15 Voltage measurement (VAux15). |Voltage
> +---------------------------------------------------------------------------------------------------------
Thanks,
Jonathan
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 2/3] iio: adc: Add Xilinx AMS driver
2018-08-30 10:22 ` [PATCH 2/3] iio: adc: Add Xilinx AMS driver Manish Narani
2018-08-30 15:36 ` Randy Dunlap
@ 2018-09-02 19:56 ` Jonathan Cameron
2018-09-06 15:19 ` Manish Narani
1 sibling, 1 reply; 13+ messages in thread
From: Jonathan Cameron @ 2018-09-02 19:56 UTC (permalink / raw)
To: Manish Narani
Cc: knaack.h, lars, pmeerw, robh+dt, mark.rutland, michal.simek,
leoyang.li, sudeep.holla, amit.kucheria, broonie,
arnaud.pouliquen, geert, eugen.hristev, rdunlap, lukas,
freeman.liu, vilhelm.gray, tglx, baolin.wang, gregkh, sgoud,
anirudh, linux-iio, devicetree, linux-arm-kernel, linux-kernel
On Thu, 30 Aug 2018 15:52:18 +0530
Manish Narani <manish.narani@xilinx.com> wrote:
> The AMS includes an ADC as well as on-chip sensors that can be used to
> sample external voltages and monitor on-die operating conditions, such
> as temperature and supply voltage levels. The AMS has two SYSMON blocks.
> PL-SYSMON block is capable of monitoring off chip voltage and
> temperature.
> PL-SYSMON block has DRP, JTAG and I2C interface to enable monitoring
> from external master. Out of these interface currently only DRP is
> supported.
> Other block PS-SYSMON is memory mapped to PS.
> The AMS can use internal channels to monitor voltage and temperature as
> well as one primary and up to 16 auxiliary channels for measuring
> external voltages.
> The voltage and temperature monitoring channels also have event
> capability which allows to generate an interrupt when their value falls
> below or raises above a set threshold.
>
> Signed-off-by: Manish Narani <manish.narani@xilinx.com>
Given the use of extended_name in here, there is a whole lot of
undocumented userspace ABI. Please add to
Documentation/ABI/testing/sysfs-bus-iio-xilinx-ams or similar.
I am a little concerned at introducing quite so many of these.
Perhaps we need to revisit how we represent this sort of information...
Otherwise, a few minor things inline but looking fairly good overall.
Thanks,
Jonathan
> ---
> drivers/iio/adc/Kconfig | 10 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/xilinx-ams.c | 1081 ++++++++++++++++++++++++++++++++++++++++++
> drivers/iio/adc/xilinx-ams.h | 281 +++++++++++
> 4 files changed, 1373 insertions(+)
> create mode 100644 drivers/iio/adc/xilinx-ams.c
> create mode 100644 drivers/iio/adc/xilinx-ams.h
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 4a75492..405ea00 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -941,4 +941,14 @@ config XILINX_XADC
> The driver can also be build as a module. If so, the module will be called
> xilinx-xadc.
>
> +config XILINX_AMS
> + tristate "Xilinx AMS driver"
> + depends on ARCH_ZYNQMP || COMPILE_TEST
> + depends on HAS_IOMEM
> + help
> + Say yes here to have support for the Xilinx AMS.
> +
> + The driver can also be build as a module. If so, the module will be called
> + xilinx-ams.
> +
> endmenu
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index 03db7b5..fbfcc45 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -85,4 +85,5 @@ obj-$(CONFIG_VF610_ADC) += vf610_adc.o
> obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o
> xilinx-xadc-y := xilinx-xadc-core.o xilinx-xadc-events.o
> obj-$(CONFIG_XILINX_XADC) += xilinx-xadc.o
> +obj-$(CONFIG_XILINX_AMS) += xilinx-ams.o
> obj-$(CONFIG_SD_ADC_MODULATOR) += sd_adc_modulator.o
> diff --git a/drivers/iio/adc/xilinx-ams.c b/drivers/iio/adc/xilinx-ams.c
> new file mode 100644
> index 0000000..10bcc52
> --- /dev/null
> +++ b/drivers/iio/adc/xilinx-ams.c
> @@ -0,0 +1,1081 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Xilinx AMS driver
> + *
> + * Copyright (C) 2017-2018 Xilinx, Inc.
> + *
> + * Manish Narani <mnarani@xilinx.com>
> + * Rajnikant Bhojani <rajnikant.bhojani@xilinx.com>
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/module.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +#include <linux/clk.h>
> +#include <linux/of_address.h>
> +#include <linux/iopoll.h>
> +#include <linux/delay.h>
There is a rough convention of alphabetical order if there isn't
a reason to do otherwise.
Here you might keep the iio headers for their own block at the end, but
the rest should be in order.
> +#include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
> +#include <linux/iio/events.h>
> +#include <linux/io.h>
> +
> +#include "xilinx-ams.h"
> +
> +static const unsigned int AMS_UNMASK_TIMEOUT_MS = 500;
> +
> +static inline void ams_ps_update_reg(struct ams *ams, unsigned int offset,
> + u32 mask, u32 data)
> +{
> + u32 val;
> +
> + val = readl(ams->ps_base + offset);
> + writel((val & ~mask) | (data & mask), ams->ps_base + offset);
> +}
> +
> +static inline void ams_pl_write_reg(struct ams *ams, unsigned int offset,
> + u32 data)
> +{
> + writel(data, ams->pl_base + offset);
> +}
I'm always anti wrappers that don't add much. In this driver you also
only use them 'sometimes'.
I'd prefer just having the writel inline all the time. The update
functions have a little more purpose and given they are called
quite often, perhaps are worth keeping.
Another option is to use regmap for the whole thing getting you update
functions and caching etc. Might not be worth it here. Up to you.
> +
> +static inline void ams_pl_update_reg(struct ams *ams, unsigned int offset,
> + u32 mask, u32 data)
> +{
> + u32 val;
> +
> + val = readl(ams->pl_base + offset);
> + writel((val & ~mask) | (data & mask), ams->pl_base + offset);
> +}
> +
> +static void ams_update_intrmask(struct ams *ams, u64 mask, u64 val)
> +{
> + ams->intr_mask &= ~mask;
> + ams->intr_mask |= (val & mask);
> +
> + writel(~(ams->intr_mask | ams->masked_alarm), ams->base + AMS_IER_0);
> + writel(~(ams->intr_mask >> AMS_ISR1_INTR_MASK_SHIFT),
> + ams->base + AMS_IER_1);
> + writel(ams->intr_mask | ams->masked_alarm, ams->base + AMS_IDR_0);
> + writel(ams->intr_mask >> AMS_ISR1_INTR_MASK_SHIFT,
> + ams->base + AMS_IDR_1);
> +}
> +
> +static void ams_disable_all_alarms(struct ams *ams)
> +{
> + /* disable PS module alarm */
> + if (ams->ps_base) {
> + ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_REGCFG1_ALARM_MASK,
> + AMS_REGCFG1_ALARM_MASK);
> + ams_ps_update_reg(ams, AMS_REG_CONFIG3, AMS_REGCFG3_ALARM_MASK,
> + AMS_REGCFG3_ALARM_MASK);
> + }
> +
> + /* disable PL module alarm */
> + if (ams->pl_base) {
> + ams_pl_update_reg(ams, AMS_REG_CONFIG1,
> + AMS_REGCFG1_ALARM_MASK,
> + AMS_REGCFG1_ALARM_MASK);
> + ams_pl_update_reg(ams, AMS_REG_CONFIG3,
> + AMS_REGCFG3_ALARM_MASK,
> + AMS_REGCFG3_ALARM_MASK);
> + }
> +}
> +
> +static void iio_ams_update_alarm(struct ams *ams, unsigned long alarm_mask)
> +{
> + u32 cfg;
> + unsigned long flags;
> + unsigned long pl_alarm_mask;
> +
> + if (ams->ps_base) {
> + /* Configuring PS alarm enable */
> + cfg = ~((alarm_mask & AMS_ISR0_ALARM_2_TO_0_MASK) <<
> + AMS_CONF1_ALARM_2_TO_0_SHIFT);
> + cfg &= ~((alarm_mask & AMS_ISR0_ALARM_6_TO_3_MASK) <<
> + AMS_CONF1_ALARM_6_TO_3_SHIFT);
> + ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_REGCFG1_ALARM_MASK,
> + cfg);
> +
> + cfg = ~((alarm_mask >> AMS_CONF3_ALARM_12_TO_7_SHIFT) &
> + AMS_ISR0_ALARM_12_TO_7_MASK);
> + ams_ps_update_reg(ams, AMS_REG_CONFIG3, AMS_REGCFG3_ALARM_MASK,
> + cfg);
> + }
> +
> + if (ams->pl_base) {
> + pl_alarm_mask = (alarm_mask >> AMS_PL_ALARM_START);
> + /* Configuring PL alarm enable */
> + cfg = ~((pl_alarm_mask & AMS_ISR0_ALARM_2_TO_0_MASK) <<
> + AMS_CONF1_ALARM_2_TO_0_SHIFT);
> + cfg &= ~((pl_alarm_mask & AMS_ISR0_ALARM_6_TO_3_MASK) <<
> + AMS_CONF1_ALARM_6_TO_3_SHIFT);
> + ams_pl_update_reg(ams, AMS_REG_CONFIG1,
> + AMS_REGCFG1_ALARM_MASK, cfg);
> +
> + cfg = ~((pl_alarm_mask >> AMS_CONF3_ALARM_12_TO_7_SHIFT) &
> + AMS_ISR0_ALARM_12_TO_7_MASK);
> + ams_pl_update_reg(ams, AMS_REG_CONFIG3,
> + AMS_REGCFG3_ALARM_MASK, cfg);
> + }
> +
> + spin_lock_irqsave(&ams->lock, flags);
> + ams_update_intrmask(ams, AMS_ISR0_ALARM_MASK, ~alarm_mask);
> + spin_unlock_irqrestore(&ams->lock, flags);
> +}
> +
> +static void ams_enable_channel_sequence(struct ams *ams)
> +{
> + int i;
> + unsigned long long scan_mask;
> + struct iio_dev *indio_dev = iio_priv_to_dev(ams);
> +
> + /*
> + * Enable channel sequence. First 22 bit of scan_mask represent
> + * PS channels, and next remaining bit represents PL channels.
> + */
> +
> + /* Run calibration of PS & PL as part of the sequence */
> + scan_mask = 0x1 | BIT(PS_SEQ_MAX);
> + for (i = 0; i < indio_dev->num_channels; i++)
> + scan_mask |= BIT(indio_dev->channels[i].scan_index);
> +
> + if (ams->ps_base) {
> + /* put sysmon in a soft reset to change the sequence */
> + ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK,
> + AMS_CONF1_SEQ_DEFAULT);
> +
> + /* configure basic channels */
> + writel(scan_mask & AMS_REG_SEQ0_MASK,
> + ams->ps_base + AMS_REG_SEQ_CH0);
> + writel(AMS_REG_SEQ2_MASK &
> + (scan_mask >> AMS_REG_SEQ2_MASK_SHIFT),
> + ams->ps_base + AMS_REG_SEQ_CH2);
> +
> + /* set continuous sequence mode */
> + ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK,
> + AMS_CONF1_SEQ_CONTINUOUS);
> + }
> +
> + if (ams->pl_base) {
> + /* put sysmon in a soft reset to change the sequence */
> + ams_pl_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK,
> + AMS_CONF1_SEQ_DEFAULT);
> +
> + /* configure basic channels */
> + scan_mask = scan_mask >> PS_SEQ_MAX;
> + writel(scan_mask & AMS_REG_SEQ0_MASK,
> + ams->pl_base + AMS_REG_SEQ_CH0);
> + writel(AMS_REG_SEQ2_MASK &
> + (scan_mask >> AMS_REG_SEQ2_MASK_SHIFT),
> + ams->pl_base + AMS_REG_SEQ_CH2);
> + writel(AMS_REG_SEQ1_MASK &
> + (scan_mask >> AMS_REG_SEQ1_MASK_SHIFT),
> + ams->pl_base + AMS_REG_SEQ_CH1);
> +
> + /* set continuous sequence mode */
> + ams_pl_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK,
> + AMS_CONF1_SEQ_CONTINUOUS);
> + }
> +}
> +
> +static int iio_ams_init_device(struct ams *ams)
> +{
> + u32 reg;
> + int ret;
> +
> + /* reset AMS */
> + if (ams->ps_base) {
> + writel(AMS_PS_RESET_VALUE, ams->ps_base + AMS_VP_VN);
> +
> + ret = readl_poll_timeout(ams->base + AMS_PS_CSTS, reg,
> + (reg & AMS_PS_CSTS_PS_READY) ==
> + AMS_PS_CSTS_PS_READY, 0,
> + AMS_INIT_TIMEOUT);
> + if (ret)
> + return ret;
> +
> + /* put sysmon in a default state */
> + ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK,
> + AMS_CONF1_SEQ_DEFAULT);
> + }
> +
> + if (ams->pl_base) {
> + writel(AMS_PL_RESET_VALUE, ams->pl_base + AMS_VP_VN);
> +
> + ret = readl_poll_timeout(ams->base + AMS_PL_CSTS, reg,
> + (reg & AMS_PL_CSTS_ACCESS_MASK) ==
> + AMS_PL_CSTS_ACCESS_MASK, 0,
> + AMS_INIT_TIMEOUT);
> + if (ret)
> + return ret;
> +
> + /* put sysmon in a default state */
> + ams_pl_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK,
> + AMS_CONF1_SEQ_DEFAULT);
> + }
> +
> + ams_disable_all_alarms(ams);
> +
> + /* Disable interrupt */
> + ams_update_intrmask(ams, ~0, ~0);
> +
> + /* Clear any pending interrupt */
> + writel(AMS_ISR0_ALARM_MASK, ams->base + AMS_ISR_0);
> + writel(AMS_ISR1_ALARM_MASK, ams->base + AMS_ISR_1);
> +
> + return 0;
> +}
> +
> +static int ams_enable_single_channel(struct ams *ams, unsigned int offset)
> +{
> + u8 channel_num = 0;
> +
> + switch (offset) {
> + case AMS_VCC_PSPLL0:
> + channel_num = AMS_VCC_PSPLL0_CH;
> + break;
> + case AMS_VCC_PSPLL3:
> + channel_num = AMS_VCC_PSPLL3_CH;
> + break;
> + case AMS_VCCINT:
> + channel_num = AMS_VCCINT_CH;
> + break;
> + case AMS_VCCBRAM:
> + channel_num = AMS_VCCBRAM_CH;
> + break;
> + case AMS_VCCAUX:
> + channel_num = AMS_VCCAUX_CH;
> + break;
> + case AMS_PSDDRPLL:
> + channel_num = AMS_PSDDRPLL_CH;
> + break;
> + case AMS_PSINTFPDDR:
> + channel_num = AMS_PSINTFPDDR_CH;
> + break;
> + default:
> + return -EINVAL;
> + }
> +
> + /* set single channel, sequencer off mode */
> + ams_ps_update_reg(ams, AMS_REG_CONFIG1, AMS_CONF1_SEQ_MASK,
> + AMS_CONF1_SEQ_SINGLE_CHANNEL);
> +
> + /* write the channel number */
> + ams_ps_update_reg(ams, AMS_REG_CONFIG0, AMS_CONF0_CHANNEL_NUM_MASK,
> + channel_num);
> + mdelay(1);
This delay should be documented.. Preferably with a reference to the datasheet
to justify the particular value.
> +
> + return 0;
> +}
> +
> +static int ams_read_vcc_reg(struct ams *ams, unsigned int offset, u32 *data)
> +{
> + int ret;
> +
> + ret = ams_enable_single_channel(ams, offset);
> + if (ret)
> + return ret;
> +
> + *data = readl(ams->base + offset);
> + ams_enable_channel_sequence(ams);
> +
> + return 0;
> +}
> +
> +static int ams_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int *val, int *val2, long mask)
> +{
> + struct ams *ams = iio_priv(indio_dev);
> + int ret;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + mutex_lock(&ams->mutex);
> + if (chan->scan_index >= (PS_SEQ_MAX * 3)) {
> + ret = ams_read_vcc_reg(ams, chan->address, val);
> + if (ret)
> + goto read_raw_err;
Missing mutex_unlock?
> + } else if (chan->scan_index >= PS_SEQ_MAX)
> + *val = readl(ams->pl_base + chan->address);
> + else
> + *val = readl(ams->ps_base + chan->address);
> + mutex_unlock(&ams->mutex);
> +
> + return IIO_VAL_INT;
> + case IIO_CHAN_INFO_SCALE:
> + switch (chan->type) {
> + case IIO_VOLTAGE:
> + switch (chan->address) {
> + case AMS_SUPPLY1:
> + case AMS_SUPPLY2:
> + case AMS_SUPPLY3:
> + case AMS_SUPPLY4:
> + *val = AMS_SUPPLY_SCALE_3VOLT;
> + break;
> + case AMS_SUPPLY5:
> + case AMS_SUPPLY6:
> + if (chan->scan_index < PS_SEQ_MAX)
> + *val = AMS_SUPPLY_SCALE_6VOLT;
> + else
> + *val = AMS_SUPPLY_SCALE_3VOLT;
> + break;
> + case AMS_SUPPLY7:
> + case AMS_SUPPLY8:
> + *val = AMS_SUPPLY_SCALE_6VOLT;
> + break;
> + case AMS_SUPPLY9:
> + case AMS_SUPPLY10:
> + if (chan->scan_index < PS_SEQ_MAX)
> + *val = AMS_SUPPLY_SCALE_3VOLT;
> + else
> + *val = AMS_SUPPLY_SCALE_6VOLT;
> + break;
> + case AMS_VCC_PSPLL0:
> + case AMS_VCC_PSPLL3:
> + case AMS_VCCINT:
> + case AMS_VCCBRAM:
> + case AMS_VCCAUX:
> + case AMS_PSDDRPLL:
> + case AMS_PSINTFPDDR:
> + *val = AMS_SUPPLY_SCALE_3VOLT;
> + break;
> + default:
> + *val = AMS_SUPPLY_SCALE_1VOLT;
> + break;
> + }
> + *val2 = AMS_SUPPLY_SCALE_DIV_BIT;
> + return IIO_VAL_FRACTIONAL_LOG2;
> + case IIO_TEMP:
> + *val = AMS_TEMP_SCALE;
> + *val2 = AMS_TEMP_SCALE_DIV_BIT;
> + return IIO_VAL_FRACTIONAL_LOG2;
> + default:
> + return -EINVAL;
> + }
> + case IIO_CHAN_INFO_OFFSET:
> + /* Only the temperature channel has an offset */
> + *val = AMS_TEMP_OFFSET;
> + return IIO_VAL_INT;
> + }
> +
> +read_raw_err:
Given we only return an error code here, are we not better just doing
that inline?
> + return -EINVAL;
> +}
> +
> +static int ams_get_alarm_offset(int scan_index, enum iio_event_direction dir)
> +{
> + int offset = 0;
> +
> + if (scan_index >= PS_SEQ_MAX)
> + scan_index -= PS_SEQ_MAX;
> +
> + if (dir == IIO_EV_DIR_FALLING) {
> + if (scan_index < AMS_SEQ_SUPPLY7)
> + offset = AMS_ALARM_THRESHOLD_OFF_10;
> + else
> + offset = AMS_ALARM_THRESHOLD_OFF_20;
> + }
> +
> + switch (scan_index) {
> + case AMS_SEQ_TEMP:
> + return AMS_ALARM_TEMP + offset;
> + case AMS_SEQ_SUPPLY1:
> + return AMS_ALARM_SUPPLY1 + offset;
> + case AMS_SEQ_SUPPLY2:
> + return AMS_ALARM_SUPPLY2 + offset;
> + case AMS_SEQ_SUPPLY3:
> + return AMS_ALARM_SUPPLY3 + offset;
> + case AMS_SEQ_SUPPLY4:
> + return AMS_ALARM_SUPPLY4 + offset;
> + case AMS_SEQ_SUPPLY5:
> + return AMS_ALARM_SUPPLY5 + offset;
> + case AMS_SEQ_SUPPLY6:
> + return AMS_ALARM_SUPPLY6 + offset;
> + case AMS_SEQ_SUPPLY7:
> + return AMS_ALARM_SUPPLY7 + offset;
> + case AMS_SEQ_SUPPLY8:
> + return AMS_ALARM_SUPPLY8 + offset;
> + case AMS_SEQ_SUPPLY9:
> + return AMS_ALARM_SUPPLY9 + offset;
> + case AMS_SEQ_SUPPLY10:
> + return AMS_ALARM_SUPPLY10 + offset;
> + case AMS_SEQ_VCCAMS:
> + return AMS_ALARM_VCCAMS + offset;
> + case AMS_SEQ_TEMP_REMOTE:
> + return AMS_ALARM_TEMP_REMOTE + offset;
> + }
> +
> + return 0;
> +}
> +
> +static const struct iio_chan_spec *ams_event_to_channel(
> + struct iio_dev *indio_dev, u32 event)
> +{
> + int scan_index = 0, i;
> +
> + if (event >= AMS_PL_ALARM_START) {
> + event -= AMS_PL_ALARM_START;
> + scan_index = PS_SEQ_MAX;
> + }
> +
> + switch (event) {
> + case AMS_ALARM_BIT_TEMP:
> + scan_index += AMS_SEQ_TEMP;
> + break;
> + case AMS_ALARM_BIT_SUPPLY1:
> + scan_index += AMS_SEQ_SUPPLY1;
> + break;
> + case AMS_ALARM_BIT_SUPPLY2:
> + scan_index += AMS_SEQ_SUPPLY2;
> + break;
> + case AMS_ALARM_BIT_SUPPLY3:
> + scan_index += AMS_SEQ_SUPPLY3;
> + break;
> + case AMS_ALARM_BIT_SUPPLY4:
> + scan_index += AMS_SEQ_SUPPLY4;
> + break;
> + case AMS_ALARM_BIT_SUPPLY5:
> + scan_index += AMS_SEQ_SUPPLY5;
> + break;
> + case AMS_ALARM_BIT_SUPPLY6:
> + scan_index += AMS_SEQ_SUPPLY6;
> + break;
> + case AMS_ALARM_BIT_SUPPLY7:
> + scan_index += AMS_SEQ_SUPPLY7;
> + break;
> + case AMS_ALARM_BIT_SUPPLY8:
> + scan_index += AMS_SEQ_SUPPLY8;
> + break;
> + case AMS_ALARM_BIT_SUPPLY9:
> + scan_index += AMS_SEQ_SUPPLY9;
> + break;
> + case AMS_ALARM_BIT_SUPPLY10:
> + scan_index += AMS_SEQ_SUPPLY10;
> + break;
> + case AMS_ALARM_BIT_VCCAMS:
> + scan_index += AMS_SEQ_VCCAMS;
> + break;
> + case AMS_ALARM_BIT_TEMP_REMOTE:
> + scan_index += AMS_SEQ_TEMP_REMOTE;
> + break;
> + }
> +
> + for (i = 0; i < indio_dev->num_channels; i++)
> + if (indio_dev->channels[i].scan_index == scan_index)
> + break;
> +
> + return &indio_dev->channels[i];
> +}
> +
> +static int ams_get_alarm_mask(int scan_index)
> +{
> + int bit = 0;
> +
> + if (scan_index >= PS_SEQ_MAX) {
> + bit = AMS_PL_ALARM_START;
> + scan_index -= PS_SEQ_MAX;
> + }
> +
> + switch (scan_index) {
> + case AMS_SEQ_TEMP:
> + return BIT(AMS_ALARM_BIT_TEMP + bit);
> + case AMS_SEQ_SUPPLY1:
> + return BIT(AMS_ALARM_BIT_SUPPLY1 + bit);
> + case AMS_SEQ_SUPPLY2:
> + return BIT(AMS_ALARM_BIT_SUPPLY2 + bit);
> + case AMS_SEQ_SUPPLY3:
> + return BIT(AMS_ALARM_BIT_SUPPLY3 + bit);
> + case AMS_SEQ_SUPPLY4:
> + return BIT(AMS_ALARM_BIT_SUPPLY4 + bit);
> + case AMS_SEQ_SUPPLY5:
> + return BIT(AMS_ALARM_BIT_SUPPLY5 + bit);
> + case AMS_SEQ_SUPPLY6:
> + return BIT(AMS_ALARM_BIT_SUPPLY6 + bit);
> + case AMS_SEQ_SUPPLY7:
> + return BIT(AMS_ALARM_BIT_SUPPLY7 + bit);
> + case AMS_SEQ_SUPPLY8:
> + return BIT(AMS_ALARM_BIT_SUPPLY8 + bit);
> + case AMS_SEQ_SUPPLY9:
> + return BIT(AMS_ALARM_BIT_SUPPLY9 + bit);
> + case AMS_SEQ_SUPPLY10:
> + return BIT(AMS_ALARM_BIT_SUPPLY10 + bit);
> + case AMS_SEQ_VCCAMS:
> + return BIT(AMS_ALARM_BIT_VCCAMS + bit);
> + case AMS_SEQ_TEMP_REMOTE:
> + return BIT(AMS_ALARM_BIT_TEMP_REMOTE + bit);
> + }
> +
> + return 0;
> +}
> +
> +static int ams_read_event_config(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir)
> +{
> + struct ams *ams = iio_priv(indio_dev);
> +
> + return (ams->alarm_mask & ams_get_alarm_mask(chan->scan_index)) ? 1 : 0;
> +}
> +
> +static int ams_write_event_config(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir,
> + int state)
> +{
> + struct ams *ams = iio_priv(indio_dev);
> + unsigned int alarm;
> +
> + alarm = ams_get_alarm_mask(chan->scan_index);
> +
> + mutex_lock(&ams->mutex);
> +
> + if (state)
> + ams->alarm_mask |= alarm;
> + else
> + ams->alarm_mask &= ~alarm;
> +
> + iio_ams_update_alarm(ams, ams->alarm_mask);
> +
> + mutex_unlock(&ams->mutex);
> +
> + return 0;
> +}
> +
> +static int ams_read_event_value(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir,
> + enum iio_event_info info, int *val, int *val2)
> +{
> + struct ams *ams = iio_priv(indio_dev);
> + unsigned int offset = ams_get_alarm_offset(chan->scan_index, dir);
> +
> + mutex_lock(&ams->mutex);
> +
> + if (chan->scan_index >= PS_SEQ_MAX)
> + *val = readl(ams->pl_base + offset);
> + else
> + *val = readl(ams->ps_base + offset);
> +
> + mutex_unlock(&ams->mutex);
> +
> + return IIO_VAL_INT;
> +}
> +
> +static int ams_write_event_value(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + enum iio_event_type type,
> + enum iio_event_direction dir,
> + enum iio_event_info info, int val, int val2)
> +{
> + struct ams *ams = iio_priv(indio_dev);
> + unsigned int offset;
> +
> + mutex_lock(&ams->mutex);
> +
> + /* Set temperature channel threshold to direct threshold */
> + if (chan->type == IIO_TEMP) {
> + offset = ams_get_alarm_offset(chan->scan_index,
> + IIO_EV_DIR_FALLING);
> +
> + if (chan->scan_index >= PS_SEQ_MAX)
> + ams_pl_update_reg(ams, offset,
> + AMS_ALARM_THR_DIRECT_MASK,
> + AMS_ALARM_THR_DIRECT_MASK);
> + else
> + ams_ps_update_reg(ams, offset,
> + AMS_ALARM_THR_DIRECT_MASK,
> + AMS_ALARM_THR_DIRECT_MASK);
> + }
> +
> + offset = ams_get_alarm_offset(chan->scan_index, dir);
> + if (chan->scan_index >= PS_SEQ_MAX)
> + writel(val, ams->pl_base + offset);
> + else
> + writel(val, ams->ps_base + offset);
> +
> + mutex_unlock(&ams->mutex);
> +
> + return 0;
> +}
> +
> +static void ams_handle_event(struct iio_dev *indio_dev, u32 event)
> +{
> + const struct iio_chan_spec *chan;
> +
> + chan = ams_event_to_channel(indio_dev, event);
> +
> + if (chan->type == IIO_TEMP) {
> + /* The temperature channel only supports over-temperature
In IIO we use the standard kernel syntax for comments of
/*
* The temperature...
*/
This form is only used in a few subsystems such as net.
It's minor but nice to be consistent...
> + * events
> + */
> + iio_push_event(indio_dev,
> + IIO_UNMOD_EVENT_CODE(chan->type, chan->channel,
> + IIO_EV_TYPE_THRESH,
> + IIO_EV_DIR_RISING),
> + iio_get_time_ns(indio_dev));
> + } else {
> + /* For other channels we don't know whether it is a upper or
> + * lower threshold event. Userspace will have to check the
> + * channel value if it wants to know.
> + */
> + iio_push_event(indio_dev,
> + IIO_UNMOD_EVENT_CODE(chan->type, chan->channel,
> + IIO_EV_TYPE_THRESH,
> + IIO_EV_DIR_EITHER),
> + iio_get_time_ns(indio_dev));
> + }
> +}
> +
> +static void ams_handle_events(struct iio_dev *indio_dev, unsigned long events)
> +{
> + unsigned int bit;
> +
> + for_each_set_bit(bit, &events, AMS_NO_OF_ALARMS)
> + ams_handle_event(indio_dev, bit);
> +}
> +
> +/**
> + * ams_unmask_worker - ams alarm interrupt unmask worker
> + * @work : work to be done
> + *
> + * The ZynqMP threshold interrupts are level sensitive. Since we can't make the
> + * threshold condition go way from within the interrupt handler, this means as
> + * soon as a threshold condition is present we would enter the interrupt handler
> + * again and again. To work around this we mask all active threshold interrupts
> + * in the interrupt handler and start a timer. In this timer we poll the
> + * interrupt status and only if the interrupt is inactive we unmask it again.
> + */
> +static void ams_unmask_worker(struct work_struct *work)
> +{
> + struct ams *ams = container_of(work, struct ams, ams_unmask_work.work);
> + unsigned int status, unmask;
> +
> + spin_lock_irq(&ams->lock);
> +
> + status = readl(ams->base + AMS_ISR_0);
> +
> + /* Clear those bits which are not active anymore */
> + unmask = (ams->masked_alarm ^ status) & ams->masked_alarm;
> +
> + /* clear status of disabled alarm */
> + unmask |= ams->intr_mask;
> +
> + ams->masked_alarm &= status;
> +
> + /* Also clear those which are masked out anyway */
> + ams->masked_alarm &= ~ams->intr_mask;
> +
> + /* Clear the interrupts before we unmask them */
> + writel(unmask, ams->base + AMS_ISR_0);
> +
> + ams_update_intrmask(ams, 0, 0);
> +
> + spin_unlock_irq(&ams->lock);
> +
> + /* if still pending some alarm re-trigger the timer */
> + if (ams->masked_alarm)
> + schedule_delayed_work(&ams->ams_unmask_work,
> + msecs_to_jiffies(AMS_UNMASK_TIMEOUT_MS));
> +}
> +
> +static irqreturn_t ams_iio_irq(int irq, void *data)
> +{
> + unsigned int isr0, isr1;
> + struct iio_dev *indio_dev = data;
> + struct ams *ams = iio_priv(indio_dev);
> +
> + spin_lock(&ams->lock);
Do these need to be handled in interrupt context? Would we be better
off doing it in an interrupt thread where we can sleep (and hence
not use a spin lock for example)?
Perhaps not, but this doesn't immediately feel time critical...
> +
> + isr0 = readl(ams->base + AMS_ISR_0);
> + isr1 = readl(ams->base + AMS_ISR_1);
> +
> + /* only process alarms that are not masked */
> + isr0 &= ~((ams->intr_mask & AMS_ISR0_ALARM_MASK) | ams->masked_alarm);
> + isr1 &= ~(ams->intr_mask >> AMS_ISR1_INTR_MASK_SHIFT);
> +
> + /* clear interrupt */
> + writel(isr0, ams->base + AMS_ISR_0);
> + writel(isr1, ams->base + AMS_ISR_1);
If we have interrupts here that aren't ours we should a) not be clearing them
b) be returning IRQ_NONE so it can be handled as an incorrect interrupt
and reported as such.
> +
> + if (isr0) {
> + /* Once the alarm interrupt occurred, mask until get cleared */
> + ams->masked_alarm |= isr0;
> + ams_update_intrmask(ams, 0, 0);
> +
> + ams_handle_events(indio_dev, isr0);
> +
> + schedule_delayed_work(&ams->ams_unmask_work,
> + msecs_to_jiffies(AMS_UNMASK_TIMEOUT_MS));
> + }
> +
> + spin_unlock(&ams->lock);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static const struct iio_event_spec ams_temp_events[] = {
> + {
> + .type = IIO_EV_TYPE_THRESH,
> + .dir = IIO_EV_DIR_RISING,
> + .mask_separate = BIT(IIO_EV_INFO_ENABLE) |
> + BIT(IIO_EV_INFO_VALUE),
> + },
> +};
> +
> +static const struct iio_event_spec ams_voltage_events[] = {
> + {
> + .type = IIO_EV_TYPE_THRESH,
> + .dir = IIO_EV_DIR_RISING,
> + .mask_separate = BIT(IIO_EV_INFO_VALUE),
> + }, {
> + .type = IIO_EV_TYPE_THRESH,
> + .dir = IIO_EV_DIR_FALLING,
> + .mask_separate = BIT(IIO_EV_INFO_VALUE),
> + }, {
> + .type = IIO_EV_TYPE_THRESH,
> + .dir = IIO_EV_DIR_EITHER,
> + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> + },
> +};
> +
> +static const struct iio_chan_spec ams_ps_channels[] = {
> + AMS_PS_CHAN_TEMP(AMS_SEQ_TEMP, AMS_TEMP, "ps_temp"),
> + AMS_PS_CHAN_TEMP(AMS_SEQ_TEMP_REMOTE, AMS_TEMP_REMOTE, "remote_temp"),
> + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY1, AMS_SUPPLY1, "vccpsintlp"),
> + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY2, AMS_SUPPLY2, "vccpsintfp"),
> + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY3, AMS_SUPPLY3, "vccpsaux"),
> + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY4, AMS_SUPPLY4, "vccpsddr"),
> + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY5, AMS_SUPPLY5, "vccpsio3"),
> + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY6, AMS_SUPPLY6, "vccpsio0"),
> + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY7, AMS_SUPPLY7, "vccpsio1"),
> + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY8, AMS_SUPPLY8, "vccpsio2"),
> + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY9, AMS_SUPPLY9, "psmgtravcc"),
> + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY10, AMS_SUPPLY10, "psmgtravtt"),
> + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_VCCAMS, AMS_VCCAMS, "vccams"),
> +};
> +
> +static const struct iio_chan_spec ams_pl_channels[] = {
> + AMS_PL_CHAN_TEMP(AMS_SEQ_TEMP, AMS_TEMP, "pl_temp"),
> + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY1, AMS_SUPPLY1, "vccint", true),
> + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY2, AMS_SUPPLY2, "vccaux", true),
> + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VREFP, AMS_VREFP, "vccvrefp", false),
> + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VREFN, AMS_VREFN, "vccvrefn", false),
> + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY3, AMS_SUPPLY3, "vccbram", true),
> + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY4, AMS_SUPPLY4, "vccplintlp", true),
> + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY5, AMS_SUPPLY5, "vccplintfp", true),
> + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY6, AMS_SUPPLY6, "vccplaux", true),
> + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VCCAMS, AMS_VCCAMS, "vccams", true),
> + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VP_VN, AMS_VP_VN, "vccvpvn", false),
> + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY7, AMS_SUPPLY7, "vuser0", true),
> + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY8, AMS_SUPPLY8, "vuser1", true),
> + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY9, AMS_SUPPLY9, "vuser2", true),
> + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY10, AMS_SUPPLY10, "vuser3", true),
> + AMS_PL_AUX_CHAN_VOLTAGE(0, "vccaux0"),
Extended_name is a two edged sword. It lets you make nice specifically
named channels, but it also breaks almost all userspace code as it
doesn't know how to handle the extra bit on the end of the name.
As such we tend to only specify it for very special use non standard
channels (such as the power supplies)
For the aux channels, it has no particular meaning. So I would prefer
that you dropped it and just left them with the indexes.
> + AMS_PL_AUX_CHAN_VOLTAGE(1, "vccaux1"),
> + AMS_PL_AUX_CHAN_VOLTAGE(2, "vccaux2"),
> + AMS_PL_AUX_CHAN_VOLTAGE(3, "vccaux3"),
> + AMS_PL_AUX_CHAN_VOLTAGE(4, "vccaux4"),
> + AMS_PL_AUX_CHAN_VOLTAGE(5, "vccaux5"),
> + AMS_PL_AUX_CHAN_VOLTAGE(6, "vccaux6"),
> + AMS_PL_AUX_CHAN_VOLTAGE(7, "vccaux7"),
> + AMS_PL_AUX_CHAN_VOLTAGE(8, "vccaux8"),
> + AMS_PL_AUX_CHAN_VOLTAGE(9, "vccaux9"),
> + AMS_PL_AUX_CHAN_VOLTAGE(10, "vccaux10"),
> + AMS_PL_AUX_CHAN_VOLTAGE(11, "vccaux11"),
> + AMS_PL_AUX_CHAN_VOLTAGE(12, "vccaux12"),
> + AMS_PL_AUX_CHAN_VOLTAGE(13, "vccaux13"),
> + AMS_PL_AUX_CHAN_VOLTAGE(14, "vccaux14"),
> + AMS_PL_AUX_CHAN_VOLTAGE(15, "vccaux15"),
> +};
> +
> +static const struct iio_chan_spec ams_ctrl_channels[] = {
> + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCC_PSPLL, AMS_VCC_PSPLL0, "vcc_pspll0"),
> + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCC_PSBATT, AMS_VCC_PSPLL3, "vcc_psbatt"),
> + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCCINT, AMS_VCCINT, "vccint"),
> + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCCBRAM, AMS_VCCBRAM, "vccbram"),
> + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCCAUX, AMS_VCCAUX, "vccaux"),
> + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_PSDDRPLL, AMS_PSDDRPLL, "psddrpll"),
> + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_INTDDR, AMS_PSINTFPDDR, "psintfpddr"),
> +};
> +
> +static int ams_init_module(struct iio_dev *indio_dev, struct device_node *np,
> + struct iio_chan_spec *channels)
> +{
> + struct ams *ams = iio_priv(indio_dev);
> + struct device_node *chan_node, *child;
> + int ret, num_channels = 0;
> + unsigned int reg;
> +
> + if (of_device_is_compatible(np, "xlnx,zynqmp-ams-ps")) {
> + ams->ps_base = of_iomap(np, 0);
> + if (!ams->ps_base)
> + return -ENXIO;
> +
> + /* add PS channels to iio device channels */
> + memcpy(channels + num_channels, ams_ps_channels,
> + sizeof(ams_ps_channels));
> + num_channels += ARRAY_SIZE(ams_ps_channels);
> + } else if (of_device_is_compatible(np, "xlnx,zynqmp-ams-pl")) {
> + ams->pl_base = of_iomap(np, 0);
> + if (!ams->pl_base)
> + return -ENXIO;
> +
> + /* Copy only first 10 fix channels */
> + memcpy(channels + num_channels, ams_pl_channels,
> + AMS_PL_MAX_FIXED_CHANNEL * sizeof(*channels));
> + num_channels += AMS_PL_MAX_FIXED_CHANNEL;
> +
> + chan_node = of_get_child_by_name(np, "xlnx,ext-channels");
> + if (chan_node) {
> + for_each_child_of_node(chan_node, child) {
> + ret = of_property_read_u32(child, "reg", ®);
> + if (ret || reg > AMS_PL_MAX_EXT_CHANNEL)
> + continue;
> +
> + memcpy(&channels[num_channels],
> + &ams_pl_channels[reg +
> + AMS_PL_MAX_FIXED_CHANNEL],
> + sizeof(*channels));
> +
> + if (of_property_read_bool(child,
> + "xlnx,bipolar"))
> + channels[num_channels].scan_type.sign =
> + 's';
> +
> + num_channels += 1;
num_channels++;
> + }
> + }
> + of_node_put(chan_node);
> + } else if (of_device_is_compatible(np, "xlnx,zynqmp-ams")) {
> + /* add AMS channels to iio device channels */
> + memcpy(channels + num_channels, ams_ctrl_channels,
> + sizeof(ams_ctrl_channels));
> + num_channels += ARRAY_SIZE(ams_ctrl_channels);
> + } else {
> + return -EINVAL;
> + }
> +
> + return num_channels;
> +}
> +
> +static int ams_parse_dt(struct iio_dev *indio_dev, struct platform_device *pdev)
> +{
> + struct ams *ams = iio_priv(indio_dev);
> + struct iio_chan_spec *ams_channels, *dev_channels;
> + struct device_node *child_node = NULL, *np = pdev->dev.of_node;
> + int ret, chan_vol = 0, chan_temp = 0, i, rising_off, falling_off;
chan_vol and chan_temp are counts? Perhaps their names can make that clearer.
> + unsigned int num_channels = 0;
> +
> + /* Initialize buffer for channel specification */
> + ams_channels = kzalloc(sizeof(ams_ps_channels) +
> + sizeof(ams_pl_channels) +
> + sizeof(ams_ctrl_channels), GFP_KERNEL);
> + if (!ams_channels)
> + return -ENOMEM;
> +
> + if (of_device_is_available(np)) {
> + ret = ams_init_module(indio_dev, np, ams_channels);
> + if (ret < 0) {
> + kfree(ams_channels);
> + return ret;
> + }
> +
> + num_channels += ret;
> + }
> +
> + for_each_child_of_node(np, child_node) {
> + if (of_device_is_available(child_node)) {
> + ret = ams_init_module(indio_dev, child_node,
> + ams_channels + num_channels);
> + if (ret < 0) {
> + kfree(ams_channels);
> + return ret;
> + }
> +
> + num_channels += ret;
> + }
> + }
> +
> + for (i = 0; i < num_channels; i++) {
> + if (ams_channels[i].type == IIO_VOLTAGE)
> + ams_channels[i].channel = chan_vol++;
> + else
> + ams_channels[i].channel = chan_temp++;
> +
> + if (ams_channels[i].scan_index < (PS_SEQ_MAX * 3)) {
> + /* set threshold to max and min for each channel */
> + falling_off = ams_get_alarm_offset(
> + ams_channels[i].scan_index,
> + IIO_EV_DIR_FALLING);
> + rising_off = ams_get_alarm_offset(
> + ams_channels[i].scan_index,
> + IIO_EV_DIR_RISING);
> + if (ams_channels[i].scan_index >= PS_SEQ_MAX) {
> + writel(AMS_ALARM_THR_MIN,
> + ams->pl_base + falling_off);
> + writel(AMS_ALARM_THR_MAX,
> + ams->pl_base + rising_off);
> + } else {
> + writel(AMS_ALARM_THR_MIN,
> + ams->ps_base + falling_off);
> + writel(AMS_ALARM_THR_MAX,
> + ams->ps_base + rising_off);
> + }
> + }
> + }
> +
> + dev_channels = devm_kzalloc(&pdev->dev, sizeof(*dev_channels) *
> + num_channels, GFP_KERNEL);
> + if (!dev_channels) {
> + kfree(ams_channels);
> + return -ENOMEM;
> + }
> +
> + memcpy(dev_channels, ams_channels,
> + sizeof(*ams_channels) * num_channels);
> + kfree(ams_channels);
If you were to reorder this so the ams_channels cleanup was last you could use
the nice clean pattern of
error:
kfree(ams_channels);
return ret;
To make it clear that the ams_channels gets freed in all paths.
> + indio_dev->channels = dev_channels;
> + indio_dev->num_channels = num_channels;
> +
> + return 0;
> +}
> +
> +static const struct iio_info iio_pl_info = {
> + .read_raw = &ams_read_raw,
> + .read_event_config = &ams_read_event_config,
> + .write_event_config = &ams_write_event_config,
> + .read_event_value = &ams_read_event_value,
> + .write_event_value = &ams_write_event_value,
> +};
> +
> +static const struct of_device_id ams_of_match_table[] = {
> + { .compatible = "xlnx,zynqmp-ams" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, ams_of_match_table);
> +
> +static int ams_probe(struct platform_device *pdev)
> +{
> + struct iio_dev *indio_dev;
> + struct ams *ams;
> + struct resource *res;
> + int ret;
> +
> + if (!pdev->dev.of_node)
> + return -ENODEV;
> +
> + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*ams));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + ams = iio_priv(indio_dev);
> + mutex_init(&ams->mutex);
> + spin_lock_init(&ams->lock);
> +
> + indio_dev->dev.parent = &pdev->dev;
> + indio_dev->dev.of_node = pdev->dev.of_node;
> + indio_dev->name = "ams";
As names go, that one is a too little generic. Particularly
with a common manufacturer of ADCs called Austrian
Micro Systems ;) No possibility of confusion!
It's fine to use ams within the driver, but for external
interfaces, perhaps prefix with xilinx-?
> +
> + indio_dev->info = &iio_pl_info;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> +
> + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ams-base");
> + ams->base = devm_ioremap_resource(&pdev->dev, res);
> + if (IS_ERR(ams->base))
> + return PTR_ERR(ams->base);
> +
> + INIT_DELAYED_WORK(&ams->ams_unmask_work, ams_unmask_worker);
> +
> + ams->clk = devm_clk_get(&pdev->dev, NULL);
> + if (IS_ERR(ams->clk))
> + return PTR_ERR(ams->clk);
> + clk_prepare_enable(ams->clk);
> +
> + ret = iio_ams_init_device(ams);
> + if (ret) {
> + dev_err(&pdev->dev, "failed to initialize AMS\n");
> + goto clk_disable;
> + }
> +
> + ret = ams_parse_dt(indio_dev, pdev);
> + if (ret) {
> + dev_err(&pdev->dev, "failure in parsing DT\n");
> + goto clk_disable;
> + }
> +
> + ams_enable_channel_sequence(ams);
> +
> + ams->irq = platform_get_irq_byname(pdev, "ams-irq");
Why store this in the ams structure? It's not used anywhere other than in the
next line.
> + ret = devm_request_irq(&pdev->dev, ams->irq, &ams_iio_irq, 0, "ams-irq",
> + indio_dev);
This mixing of devm and non devm functions makes it tricky to
be sure there aren't an races.
One easy solution is to use devm_add_action to put the
clk_disable_unprepare into the cleanup list.
> + if (ret < 0) {
> + dev_err(&pdev->dev, "failed to register interrupt\n");
> + goto clk_disable;
> + }
> +
> + platform_set_drvdata(pdev, indio_dev);
> +
> + return iio_device_register(indio_dev);
What if device register returns an error? You leave the clock enabled.
That way everything is using the managed cleanup and the ordering will
be correct.
The alternative is to not use devm forms for everything after the
clk_prepare_enable.
> +
> +clk_disable:
> + clk_disable_unprepare(ams->clk);
> +
> + return ret;
> +}
> +
> +static int ams_remove(struct platform_device *pdev)
> +{
> + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> + struct ams *ams = iio_priv(indio_dev);
> +
> + cancel_delayed_work(&ams->ams_unmask_work);
> +
> + /* Unregister the device */
> + iio_device_unregister(indio_dev);
> + clk_disable_unprepare(ams->clk);
> +
> + return 0;
> +}
> +
> +static int __maybe_unused ams_suspend(struct device *dev)
> +{
> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
> + struct ams *ams = iio_priv(indio_dev);
> +
> + clk_disable_unprepare(ams->clk);
> +
> + return 0;
> +}
> +
> +static int __maybe_unused ams_resume(struct device *dev)
> +{
> + struct iio_dev *indio_dev = dev_get_drvdata(dev);
> + struct ams *ams = iio_priv(indio_dev);
You could streamline this without loss of clarity as.
struct ams *ams = iio_priv(dev_get_drvdata(dev));
(very minor point!)
> +
> + clk_prepare_enable(ams->clk);
> +
> + return 0;
> +}
> +
> +static SIMPLE_DEV_PM_OPS(ams_pm_ops, ams_suspend, ams_resume);
> +
> +static struct platform_driver ams_driver = {
> + .probe = ams_probe,
> + .remove = ams_remove,
> + .driver = {
> + .name = "ams",
> + .pm = &ams_pm_ops,
Spacing here seems a little odd. Just have a single space after pm
> + .of_match_table = ams_of_match_table,
> + },
> +};
> +module_platform_driver(ams_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Xilinx, Inc.");
> diff --git a/drivers/iio/adc/xilinx-ams.h b/drivers/iio/adc/xilinx-ams.h
> new file mode 100644
> index 0000000..b9fa262
> --- /dev/null
> +++ b/drivers/iio/adc/xilinx-ams.h
I can't immediately see why a lot of this wants to be in a header.
Please move it inline with the C file.
> @@ -0,0 +1,281 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Xilinx AMS driver
> + *
> + * Copyright (C) 2017-2018 Xilinx, Inc.
> + *
> + * Manish Narani <mnarani@xilinx.com>
> + * Rajnikant Bhojani <rajnikant.bhojani@xilinx.com>
> + */
> +
> +#ifndef __XILINX_AMS_H__
> +#define __XILINX_AMS_H__
> +
> +#define AMS_MISC_CTRL 0x000
> +#define AMS_ISR_0 0x010
> +#define AMS_ISR_1 0x014
> +#define AMS_IMR_0 0x018
> +#define AMS_IMR_1 0x01c
> +#define AMS_IER_0 0x020
> +#define AMS_IER_1 0x024
> +#define AMS_IDR_0 0x028
> +#define AMS_IDR_1 0x02c
> +#define AMS_PS_CSTS 0x040
> +#define AMS_PL_CSTS 0x044
> +#define AMS_MON_CSTS 0x050
> +
> +#define AMS_VCC_PSPLL0 0x060
> +#define AMS_VCC_PSPLL3 0x06C
> +#define AMS_VCCINT 0x078
> +#define AMS_VCCBRAM 0x07C
> +#define AMS_VCCAUX 0x080
> +#define AMS_PSDDRPLL 0x084
> +#define AMS_PSINTFPDDR 0x09C
> +
> +#define AMS_VCC_PSPLL0_CH 48
> +#define AMS_VCC_PSPLL3_CH 51
> +#define AMS_VCCINT_CH 54
> +#define AMS_VCCBRAM_CH 55
> +#define AMS_VCCAUX_CH 56
> +#define AMS_PSDDRPLL_CH 57
> +#define AMS_PSINTFPDDR_CH 63
> +
> +#define AMS_REG_CONFIG0 0x100
> +#define AMS_REG_CONFIG1 0x104
> +#define AMS_REG_CONFIG2 0x108
> +#define AMS_REG_CONFIG3 0x10C
> +#define AMS_REG_CONFIG4 0x110
> +#define AMS_REG_SEQ_CH0 0x120
> +#define AMS_REG_SEQ_CH1 0x124
> +#define AMS_REG_SEQ_CH2 0x118
> +
> +#define AMS_TEMP 0x000
> +#define AMS_SUPPLY1 0x004
> +#define AMS_SUPPLY2 0x008
> +#define AMS_VP_VN 0x00c
> +#define AMS_VREFP 0x010
> +#define AMS_VREFN 0x014
> +#define AMS_SUPPLY3 0x018
> +#define AMS_SUPPLY4 0x034
> +#define AMS_SUPPLY5 0x038
> +#define AMS_SUPPLY6 0x03c
> +#define AMS_SUPPLY7 0x200
> +#define AMS_SUPPLY8 0x204
> +#define AMS_SUPPLY9 0x208
> +#define AMS_SUPPLY10 0x20c
> +#define AMS_VCCAMS 0x210
> +#define AMS_TEMP_REMOTE 0x214
> +
> +#define AMS_REG_VAUX(x) (0x40 + (4*(x)))
> +#define AMS_REG_VUSER(x) (0x200 + (4*(x)))
> +
> +#define AMS_PS_RESET_VALUE 0xFFFFU
> +#define AMS_PL_RESET_VALUE 0xFFFFU
> +
> +#define AMS_CONF0_CHANNEL_NUM_MASK (0x3f << 0)
> +
> +#define AMS_CONF1_SEQ_MASK (0xf << 12)
> +#define AMS_CONF1_SEQ_DEFAULT (0 << 12)
> +#define AMS_CONF1_SEQ_SINGLE_PASS (1 << 12)
> +#define AMS_CONF1_SEQ_CONTINUOUS (2 << 12)
> +#define AMS_CONF1_SEQ_SINGLE_CHANNEL (3 << 12)
> +
> +#define AMS_REG_SEQ0_MASK 0xFFFF
> +#define AMS_REG_SEQ2_MASK 0x3F
> +#define AMS_REG_SEQ1_MASK 0xFFFF
> +#define AMS_REG_SEQ2_MASK_SHIFT 16
> +#define AMS_REG_SEQ1_MASK_SHIFT 22
> +
> +#define AMS_REGCFG1_ALARM_MASK 0xF0F
> +#define AMS_REGCFG3_ALARM_MASK 0x3F
> +
> +#define AMS_ALARM_TEMP 0x140
> +#define AMS_ALARM_SUPPLY1 0x144
> +#define AMS_ALARM_SUPPLY2 0x148
> +#define AMS_ALARM_OT 0x14c
> +
> +#define AMS_ALARM_SUPPLY3 0x160
> +#define AMS_ALARM_SUPPLY4 0x164
> +#define AMS_ALARM_SUPPLY5 0x168
> +#define AMS_ALARM_SUPPLY6 0x16c
> +#define AMS_ALARM_SUPPLY7 0x180
> +#define AMS_ALARM_SUPPLY8 0x184
> +#define AMS_ALARM_SUPPLY9 0x188
> +#define AMS_ALARM_SUPPLY10 0x18c
> +#define AMS_ALARM_VCCAMS 0x190
> +#define AMS_ALARM_TEMP_REMOTE 0x194
> +#define AMS_ALARM_THRESHOLD_OFF_10 0x10
> +#define AMS_ALARM_THRESHOLD_OFF_20 0x20
> +
> +#define AMS_ALARM_THR_DIRECT_MASK 0x01
> +#define AMS_ALARM_THR_MIN 0x0000
> +#define AMS_ALARM_THR_MAX 0xffff
> +
> +#define AMS_NO_OF_ALARMS 32
> +#define AMS_PL_ALARM_START 16
> +#define AMS_ISR0_ALARM_MASK 0xFFFFFFFFU
> +#define AMS_ISR1_ALARM_MASK 0xE000001FU
> +#define AMS_ISR1_INTR_MASK_SHIFT 32
> +#define AMS_ISR0_ALARM_2_TO_0_MASK 0x07
> +#define AMS_ISR0_ALARM_6_TO_3_MASK 0x78
> +#define AMS_ISR0_ALARM_12_TO_7_MASK 0x3F
> +#define AMS_CONF1_ALARM_2_TO_0_SHIFT 1
> +#define AMS_CONF1_ALARM_6_TO_3_SHIFT 5
> +#define AMS_CONF3_ALARM_12_TO_7_SHIFT 8
> +
> +#define AMS_PS_CSTS_PS_READY 0x08010000U
> +#define AMS_PL_CSTS_ACCESS_MASK 0x00000001U
> +
> +#define AMS_PL_MAX_FIXED_CHANNEL 10
> +#define AMS_PL_MAX_EXT_CHANNEL 20
> +
> +#define AMS_INIT_TIMEOUT 10000
> +
> +/*
> + * Following scale and offset value is derived from
> + * UG580 (v1.7) December 20, 2016
> + */
> +#define AMS_SUPPLY_SCALE_1VOLT 1000
> +#define AMS_SUPPLY_SCALE_3VOLT 3000
> +#define AMS_SUPPLY_SCALE_6VOLT 6000
> +#define AMS_SUPPLY_SCALE_DIV_BIT 16
> +
> +#define AMS_TEMP_SCALE 509314
> +#define AMS_TEMP_SCALE_DIV_BIT 16
> +#define AMS_TEMP_OFFSET -((280230L << 16) / 509314)
> +
> +enum ams_alarm_bit {
> + AMS_ALARM_BIT_TEMP,
> + AMS_ALARM_BIT_SUPPLY1,
> + AMS_ALARM_BIT_SUPPLY2,
> + AMS_ALARM_BIT_SUPPLY3,
> + AMS_ALARM_BIT_SUPPLY4,
> + AMS_ALARM_BIT_SUPPLY5,
> + AMS_ALARM_BIT_SUPPLY6,
> + AMS_ALARM_BIT_RESERVED,
> + AMS_ALARM_BIT_SUPPLY7,
> + AMS_ALARM_BIT_SUPPLY8,
> + AMS_ALARM_BIT_SUPPLY9,
> + AMS_ALARM_BIT_SUPPLY10,
> + AMS_ALARM_BIT_VCCAMS,
> + AMS_ALARM_BIT_TEMP_REMOTE
> +};
> +
> +enum ams_seq {
> + AMS_SEQ_VCC_PSPLL,
> + AMS_SEQ_VCC_PSBATT,
> + AMS_SEQ_VCCINT,
> + AMS_SEQ_VCCBRAM,
> + AMS_SEQ_VCCAUX,
> + AMS_SEQ_PSDDRPLL,
> + AMS_SEQ_INTDDR
> +};
> +
> +enum ams_ps_pl_seq {
> + AMS_SEQ_CALIB,
> + AMS_SEQ_RSVD_1,
> + AMS_SEQ_RSVD_2,
> + AMS_SEQ_TEST,
> + AMS_SEQ_RSVD_4,
> + AMS_SEQ_SUPPLY4,
> + AMS_SEQ_SUPPLY5,
> + AMS_SEQ_SUPPLY6,
> + AMS_SEQ_TEMP,
> + AMS_SEQ_SUPPLY2,
> + AMS_SEQ_SUPPLY1,
> + AMS_SEQ_VP_VN,
> + AMS_SEQ_VREFP,
> + AMS_SEQ_VREFN,
> + AMS_SEQ_SUPPLY3,
> + AMS_SEQ_CURRENT_MON,
> + AMS_SEQ_SUPPLY7,
> + AMS_SEQ_SUPPLY8,
> + AMS_SEQ_SUPPLY9,
> + AMS_SEQ_SUPPLY10,
> + AMS_SEQ_VCCAMS,
> + AMS_SEQ_TEMP_REMOTE,
> + AMS_SEQ_MAX
> +};
> +
> +#define AMS_SEQ(x) (AMS_SEQ_MAX + (x))
> +#define AMS_VAUX_SEQ(x) (AMS_SEQ_MAX + (x))
> +
> +#define PS_SEQ_MAX AMS_SEQ_MAX
> +#define PS_SEQ(x) (x)
> +#define PL_SEQ(x) (PS_SEQ_MAX + x)
> +
> +#define AMS_CHAN_TEMP(_scan_index, _addr, _ext) { \
> + .type = IIO_TEMP, \
> + .indexed = 1, \
> + .address = (_addr), \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
> + BIT(IIO_CHAN_INFO_SCALE) | \
> + BIT(IIO_CHAN_INFO_OFFSET), \
> + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> + .event_spec = ams_temp_events, \
> + .num_event_specs = ARRAY_SIZE(ams_temp_events), \
> + .scan_index = (_scan_index), \
> + .scan_type = { \
> + .sign = 'u', \
> + .realbits = 12, \
> + .storagebits = 16, \
> + .shift = 4, \
> + .endianness = IIO_CPU, \
> + }, \
> + .extend_name = _ext, \
> +}
> +
> +#define AMS_CHAN_VOLTAGE(_scan_index, _addr, _ext, _alarm) { \
> + .type = IIO_VOLTAGE, \
> + .indexed = 1, \
> + .address = (_addr), \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
> + BIT(IIO_CHAN_INFO_SCALE), \
> + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> + .event_spec = (_alarm) ? ams_voltage_events : NULL, \
> + .num_event_specs = (_alarm) ? ARRAY_SIZE(ams_voltage_events) : 0, \
> + .scan_index = (_scan_index), \
> + .scan_type = { \
> + .realbits = 10, \
> + .storagebits = 16, \
> + .shift = 6, \
> + .endianness = IIO_CPU, \
> + }, \
> + .extend_name = _ext, \
> +}
> +
> +#define AMS_PS_CHAN_TEMP(_scan_index, _addr, _ext) \
> + AMS_CHAN_TEMP(PS_SEQ(_scan_index), _addr, _ext)
> +#define AMS_PS_CHAN_VOLTAGE(_scan_index, _addr, _ext) \
> + AMS_CHAN_VOLTAGE(PS_SEQ(_scan_index), _addr, _ext, true)
> +
> +#define AMS_PL_CHAN_TEMP(_scan_index, _addr, _ext) \
> + AMS_CHAN_TEMP(PL_SEQ(_scan_index), _addr, _ext)
> +#define AMS_PL_CHAN_VOLTAGE(_scan_index, _addr, _ext, _alarm) \
> + AMS_CHAN_VOLTAGE(PL_SEQ(_scan_index), _addr, _ext, _alarm)
> +#define AMS_PL_AUX_CHAN_VOLTAGE(_auxno, _ext) \
> + AMS_CHAN_VOLTAGE(PL_SEQ(AMS_VAUX_SEQ(_auxno)), \
> + AMS_REG_VAUX(_auxno), _ext, false)
> +#define AMS_CTRL_CHAN_VOLTAGE(_scan_index, _addr, _ext) \
> + AMS_CHAN_VOLTAGE(PL_SEQ(AMS_VAUX_SEQ(AMS_SEQ(_scan_index))), \
> + _addr, _ext, false)
> +
> +struct ams {
> + void __iomem *base;
> + void __iomem *ps_base;
> + void __iomem *pl_base;
> + struct clk *clk;
> + struct device *dev;
> +
> + struct mutex mutex;
Pleases give clear comments on what these locks are protecting.
It's certainly curious to see a mutex and a spinlock with such
generic names right next to each other.
> + spinlock_t lock;
> +
> + unsigned int alarm_mask;
> + unsigned int masked_alarm;
> + u64 intr_mask;
> + int irq;
As noted above, this is only used next to where it is first set.
No advantage in having it here.
> +
> + struct delayed_work ams_unmask_work;
> +};
> +
> +#endif /* __XILINX_AMS_H__ */
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/3] dt-bindings: iio: adc: Add Xilinx AMS binding documentation
2018-08-30 10:22 ` [PATCH 1/3] dt-bindings: iio: adc: Add Xilinx AMS binding documentation Manish Narani
2018-09-02 18:14 ` Jonathan Cameron
@ 2018-09-04 1:17 ` Rob Herring
2018-09-06 13:42 ` Manish Narani
1 sibling, 1 reply; 13+ messages in thread
From: Rob Herring @ 2018-09-04 1:17 UTC (permalink / raw)
To: Manish Narani
Cc: jic23, knaack.h, lars, pmeerw, mark.rutland, michal.simek,
leoyang.li, sudeep.holla, amit.kucheria, broonie,
arnaud.pouliquen, geert, eugen.hristev, rdunlap, lukas,
freeman.liu, vilhelm.gray, tglx, baolin.wang, gregkh, sgoud,
anirudh, linux-iio, devicetree, linux-arm-kernel, linux-kernel
On Thu, Aug 30, 2018 at 03:52:17PM +0530, Manish Narani wrote:
> Xilinx AMS have several ADC channels that can be used for measurement of
> different voltages and temperatures. Document the same in the bindings.
>
> Signed-off-by: Manish Narani <manish.narani@xilinx.com>
> ---
> .../devicetree/bindings/iio/adc/xilinx-ams.txt | 159 +++++++++++++++++++++
> 1 file changed, 159 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
>
> diff --git a/Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt b/Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
> new file mode 100644
> index 0000000..8cc96f0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
> @@ -0,0 +1,159 @@
> +Xilinx AMS controller
> +
> +The AMS includes an ADC as well as on-chip sensors that can be used to
> +sample external voltages and monitor on-die operating conditions, such as
> +temperature and supply voltage levels. The AMS has two SYSMON blocks.
> +PL-SYSMON block is capable of monitoring off chip voltage and temperature.
> +PL-SYSMON block has DRP, JTAG and I2C interface to enable monitoring from
> +external master. Out of this interface currently only DRP is supported.
> +Other block PS-SYSMON is memory mapped to PS. Both of block has built-in
> +alarm generation logic that is used to interrupt the processor based on
> +condition set.
> +
> +All designs should have AMS registers, but PS and PL are optional. The
> +AMS controller can work with only PS, only PL and both PS and PL
> +configurations. Please specify registers according to your design. Devicetree
> +should always have AMS module property. Providing PS & PL module is optional.
> +
> +Required properties:
> + - compatible: Should be "xlnx,zynqmp-ams"
> + - reg: Should specify AMS register space
> + - interrupts: Interrupt number for the AMS control interface
> + - interrupt-names: Interrupt name, must be "ams-irq"
> + - clocks: Should contain a clock specifier for the device
> + - ranges: keep the property empty to map child address space
> + (for PS and/or PL) nodes 1:1 onto the parent address
> + space
> +
> +AMS device tree subnode:
> + - compatible: Should be "xlnx,zynqmp-ams-ps" or "xlnx,zynqmp-ams-pl"
> + - reg: Register space for PS or PL
Please split each child node into its own section.
> +
> +Optional properties:
> +
> +Following optional property only valid for PL.
property or...
> + - xlnx,ext-channels: List of external channels that are connected to the
> + AMS PL module.
> +
> + The child nodes of this node represent the external channels which are
node?
> + connected to the AMS Module. If the property is not present
> + no external channels will be assumed to be connected.
> +
> + Each child node represents one channel and has the following
> + properties:
> + Required properties:
> + * reg: Pair of pins the channel is connected to.
> + 0: VP/VN
> + 1: VUSER0
> + 2: VUSER1
> + 3: VUSER3
> + 4: VUSER4
> + 5: VAUXP[0]/VAUXN[0]
> + 6: VAUXP[1]/VAUXN[1]
> + ...
> + 20: VAUXP[15]/VAUXN[15]
> + Note each channel number should only be used at most
> + once.
> + Optional properties:
> + * xlnx,bipolar: If set the channel is used in bipolar
> + mode.
> +
> +
> +Example:
> + xilinx_ams: ams@ffa50000 {
> + compatible = "xlnx,zynqmp-ams";
> + interrupt-parent = <&gic>;
> + interrupts = <0 56 4>;
> + interrupt-names = "ams-irq";
> + clocks = <&clkc 70>;
> + reg = <0x0 0xffa50000 0x0 0x800>;
> + reg-names = "ams-base";
> + #address-cells = <2>;
> + #size-cells = <2>;
> + ranges;
There's no need for 64-bits of addresses and sizes here. Use ranges.
> +
> + ams_ps: ams_ps@ffa50800 {
> + compatible = "xlnx,zynqmp-ams-ps";
> + reg = <0x0 0xffa50800 0x0 0x400>;
> + };
> +
> + ams_pl: ams_pl@ffa50c00 {
> + compatible = "xlnx,zynqmp-ams-pl";
> + reg = <0x0 0xffa50c00 0x0 0x400>;
> + xlnx,ext-channels {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + channel@0 {
> + reg = <0>;
> + xlnx,bipolar;
> + };
> + channel@1 {
> + reg = <1>;
> + };
> + channel@8 {
> + reg = <8>;
> + xlnx,bipolar;
> + };
> + };
> + };
> + };
> +
> +AMS Channels Details:
> +
> +Sysmon Block |Channel| Details |Measurement
> + Number Type
> +---------------------------------------------------------------------------------------------------------
> +AMS CTRL |0 |System PLLs voltage measurement, VCC_PSPLL. |Voltage
> + |1 |Battery voltage measurement, VCC_PSBATT. |Voltage
> + |2 |PL Internal voltage measurement, VCCINT. |Voltage
> + |3 |Block RAM voltage measurement, VCCBRAM. |Voltage
> + |4 |PL Aux voltage measurement, VCCAUX. |Voltage
> + |5 |Voltage measurement for six DDR I/O PLLs, VCC_PSDDR_PLL. |Voltage
> + |6 |VCC_PSINTFP_DDR voltage measurement. |Voltage
> +---------------------------------------------------------------------------------------------------------
> +PS Sysmon |7 |LPD temperature measurement. |Temperature
> + |8 |FPD Temperature Measurment (REMOTE). |Temperature
> + |9 |VCC PS LPD voltage measurement (supply1). |Voltage
> + |10 |VCC PS FPD voltage measurement (supply2). |Voltage
> + |11 |PS Aux voltage reference (supply3). |Voltage
> + |12 |DDR I/O VCC voltage measurement. |Voltage
> + |13 |PS IO Bank 503 voltage measurement (supply5). |Voltage
> + |14 |PS IO Bank 500 voltage measurement (supply6). |Voltage
> + |15 |VCCO_PSIO1 voltage measurement. |Voltage
> + |16 |VCCO_PSIO2 voltage measurement. |Voltage
> + |17 |VCC_PS_GTR voltage measurement (VPS_MGTRAVCC). |Voltage
> + |18 |VTT_PS_GTR voltage measurement (VPS_MGTRAVTT). |Voltage
> + |19 |VCC_PSADC voltage measurement. |Voltage
> +---------------------------------------------------------------------------------------------------------
> +PL Sysmon |20 |PL Temperature measurement. |Temperature
> + |21 |PL Internal Voltage Voltage measurement, VCCINT. |Voltage
> + |22 |PL Auxiliary Voltage measurement, VCCAUX. |Voltage
> + |23 |ADC Reference P+ Voltage measurement. |Voltage
> + |24 |ADC Reference N- Voltage measurement. |Voltage
> + |25 |PL Block RAM Voltage measurement, VCCBRAM. |Voltage
> + |26 |LPD Internal Voltage measurement, VCC_PSINTLP (supply4). |Voltage
> + |27 |FPD Internal Voltage measurement, VCC_PSINTFP (supply5). |Voltage
> + |28 |PS Auxiliary Voltage measurement (supply6). |Voltage
> + |29 |PL VCCADC Voltage measurement (vccams). |Voltage
> + |30 |Differencial analog input signal Voltage measurment. |Voltage
> + |31 |VUser0 Voltage measurement (supply7). |Voltage
> + |32 |VUser1 Voltage measurement (supply8). |Voltage
> + |33 |VUser2 Voltage measurement (supply9). |Voltage
> + |34 |VUser3 Voltage measurement (supply10). |Voltage
> + |35 |Auxiliary ch 0 Voltage measurement (VAux0). |Voltage
> + |36 |Auxiliary ch 1 Voltage measurement (VAux1). |Voltage
> + |37 |Auxiliary ch 2 Voltage measurement (VAux2). |Voltage
> + |38 |Auxiliary ch 3 Voltage measurement (VAux3). |Voltage
> + |39 |Auxiliary ch 4 Voltage measurement (VAux4). |Voltage
> + |40 |Auxiliary ch 5 Voltage measurement (VAux5). |Voltage
> + |41 |Auxiliary ch 6 Voltage measurement (VAux6). |Voltage
> + |42 |Auxiliary ch 7 Voltage measurement (VAux7). |Voltage
> + |43 |Auxiliary ch 8 Voltage measurement (VAux8). |Voltage
> + |44 |Auxiliary ch 9 Voltage measurement (VAux9). |Voltage
> + |45 |Auxiliary ch 10 Voltage measurement (VAux10). |Voltage
> + |46 |Auxiliary ch 11 Voltage measurement (VAux11). |Voltage
> + |47 |Auxiliary ch 12 Voltage measurement (VAux12). |Voltage
> + |48 |Auxiliary ch 13 Voltage measurement (VAux13). |Voltage
> + |49 |Auxiliary ch 14 Voltage measurement (VAux14). |Voltage
> + |50 |Auxiliary ch 15 Voltage measurement (VAux15). |Voltage
> +---------------------------------------------------------------------------------------------------------
> --
> 2.1.1
>
^ permalink raw reply [flat|nested] 13+ messages in thread
* RE: [PATCH 1/3] dt-bindings: iio: adc: Add Xilinx AMS binding documentation
2018-09-02 18:14 ` Jonathan Cameron
@ 2018-09-06 13:27 ` Manish Narani
2018-09-08 13:42 ` Jonathan Cameron
0 siblings, 1 reply; 13+ messages in thread
From: Manish Narani @ 2018-09-06 13:27 UTC (permalink / raw)
To: Jonathan Cameron
Cc: knaack.h@gmx.de, lars@metafoo.de, pmeerw@pmeerw.net,
robh+dt@kernel.org, mark.rutland@arm.com, Michal Simek,
leoyang.li@nxp.com, sudeep.holla@arm.com,
amit.kucheria@linaro.org, broonie@kernel.org,
arnaud.pouliquen@st.com, geert@linux-m68k.org,
eugen.hristev@microchip.com, rdunlap@infradead.org,
lukas@wunner.de, freeman.liu@spreadtrum.com,
vilhelm.gray@gmail.com, tglx@linutronix.de,
baolin.wang@linaro.org, gregkh@linuxfoundation.org, Srinivas Goud,
Anirudha Sarangi, linux-iio@vger.kernel.org,
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org
SGkgSm9uYXRoYW4sDQoNClRoYW5rcyBmb3IgdGhlIHJldmlldyENCg0KPiAtLS0tLU9yaWdpbmFs
IE1lc3NhZ2UtLS0tLQ0KPiBGcm9tOiBKb25hdGhhbiBDYW1lcm9uIFttYWlsdG86amljMjNAa2Vy
bmVsLm9yZ10NCj4gU2VudDogU3VuZGF5LCBTZXB0ZW1iZXIgMiwgMjAxOCAxMTo0NSBQTQ0KPiBU
bzogTWFuaXNoIE5hcmFuaSA8TU5BUkFOSUB4aWxpbnguY29tPg0KPiBDYzoga25hYWNrLmhAZ214
LmRlOyBsYXJzQG1ldGFmb28uZGU7IHBtZWVyd0BwbWVlcncubmV0Ow0KPiByb2JoK2R0QGtlcm5l
bC5vcmc7IG1hcmsucnV0bGFuZEBhcm0uY29tOyBNaWNoYWwgU2ltZWsNCj4gPG1pY2hhbHNAeGls
aW54LmNvbT47IGxlb3lhbmcubGlAbnhwLmNvbTsgc3VkZWVwLmhvbGxhQGFybS5jb207DQo+IGFt
aXQua3VjaGVyaWFAbGluYXJvLm9yZzsgYnJvb25pZUBrZXJuZWwub3JnOyBhcm5hdWQucG91bGlx
dWVuQHN0LmNvbTsNCj4gZ2VlcnRAbGludXgtbTY4ay5vcmc7IGV1Z2VuLmhyaXN0ZXZAbWljcm9j
aGlwLmNvbTsgcmR1bmxhcEBpbmZyYWRlYWQub3JnOw0KPiBsdWthc0B3dW5uZXIuZGU7IGZyZWVt
YW4ubGl1QHNwcmVhZHRydW0uY29tOyB2aWxoZWxtLmdyYXlAZ21haWwuY29tOw0KPiB0Z2x4QGxp
bnV0cm9uaXguZGU7IGJhb2xpbi53YW5nQGxpbmFyby5vcmc7IGdyZWdraEBsaW51eGZvdW5kYXRp
b24ub3JnOw0KPiBTcmluaXZhcyBHb3VkIDxzZ291ZEB4aWxpbnguY29tPjsgQW5pcnVkaGEgU2Fy
YW5naSA8YW5pcnVkaEB4aWxpbnguY29tPjsNCj4gbGludXgtaWlvQHZnZXIua2VybmVsLm9yZzsg
ZGV2aWNldHJlZUB2Z2VyLmtlcm5lbC5vcmc7IGxpbnV4LWFybS0NCj4ga2VybmVsQGxpc3RzLmlu
ZnJhZGVhZC5vcmc7IGxpbnV4LWtlcm5lbEB2Z2VyLmtlcm5lbC5vcmcNCj4gU3ViamVjdDogUmU6
IFtQQVRDSCAxLzNdIGR0LWJpbmRpbmdzOiBpaW86IGFkYzogQWRkIFhpbGlueCBBTVMgYmluZGlu
Zw0KPiBkb2N1bWVudGF0aW9uDQo+IA0KPiBPbiBUaHUsIDMwIEF1ZyAyMDE4IDE1OjUyOjE3ICsw
NTMwDQo+IE1hbmlzaCBOYXJhbmkgPG1hbmlzaC5uYXJhbmlAeGlsaW54LmNvbT4gd3JvdGU6DQo+
IA0KPiA+IFhpbGlueCBBTVMgaGF2ZSBzZXZlcmFsIEFEQyBjaGFubmVscyB0aGF0IGNhbiBiZSB1
c2VkIGZvciBtZWFzdXJlbWVudA0KPiA+IG9mIGRpZmZlcmVudCB2b2x0YWdlcyBhbmQgdGVtcGVy
YXR1cmVzLiBEb2N1bWVudCB0aGUgc2FtZSBpbiB0aGUgYmluZGluZ3MuDQo+ID4NCj4gPiBTaWdu
ZWQtb2ZmLWJ5OiBNYW5pc2ggTmFyYW5pIDxtYW5pc2gubmFyYW5pQHhpbGlueC5jb20+DQo+ID4g
LS0tDQo+ID4gIC4uLi9kZXZpY2V0cmVlL2JpbmRpbmdzL2lpby9hZGMveGlsaW54LWFtcy50eHQg
ICAgIHwgMTU5DQo+ICsrKysrKysrKysrKysrKysrKysrKw0KPiA+ICAxIGZpbGUgY2hhbmdlZCwg
MTU5IGluc2VydGlvbnMoKykNCj4gPiAgY3JlYXRlIG1vZGUgMTAwNjQ0DQo+ID4gRG9jdW1lbnRh
dGlvbi9kZXZpY2V0cmVlL2JpbmRpbmdzL2lpby9hZGMveGlsaW54LWFtcy50eHQNCj4gPg0KPiA+
IGRpZmYgLS1naXQgYS9Eb2N1bWVudGF0aW9uL2RldmljZXRyZWUvYmluZGluZ3MvaWlvL2FkYy94
aWxpbngtYW1zLnR4dA0KPiA+IGIvRG9jdW1lbnRhdGlvbi9kZXZpY2V0cmVlL2JpbmRpbmdzL2lp
by9hZGMveGlsaW54LWFtcy50eHQNCj4gPiBuZXcgZmlsZSBtb2RlIDEwMDY0NA0KPiA+IGluZGV4
IDAwMDAwMDAuLjhjYzk2ZjANCj4gPiAtLS0gL2Rldi9udWxsDQo+ID4gKysrIGIvRG9jdW1lbnRh
dGlvbi9kZXZpY2V0cmVlL2JpbmRpbmdzL2lpby9hZGMveGlsaW54LWFtcy50eHQNCj4gPiBAQCAt
MCwwICsxLDE1OSBAQA0KPiA+ICtYaWxpbnggQU1TIGNvbnRyb2xsZXINCj4gPiArDQo+ID4gK1Ro
ZSBBTVMgaW5jbHVkZXMgYW4gQURDIGFzIHdlbGwgYXMgb24tY2hpcCBzZW5zb3JzIHRoYXQgY2Fu
IGJlIHVzZWQNCj4gPiArdG8gc2FtcGxlIGV4dGVybmFsIHZvbHRhZ2VzIGFuZCBtb25pdG9yIG9u
LWRpZSBvcGVyYXRpbmcgY29uZGl0aW9ucywNCj4gPiArc3VjaCBhcyB0ZW1wZXJhdHVyZSBhbmQg
c3VwcGx5IHZvbHRhZ2UgbGV2ZWxzLiBUaGUgQU1TIGhhcyB0d28gU1lTTU9ODQo+IGJsb2Nrcy4N
Cj4gPiArUEwtU1lTTU9OIGJsb2NrIGlzIGNhcGFibGUgb2YgbW9uaXRvcmluZyBvZmYgY2hpcCB2
b2x0YWdlIGFuZA0KPiB0ZW1wZXJhdHVyZS4NCj4gPiArUEwtU1lTTU9OIGJsb2NrIGhhcyBEUlAs
IEpUQUcgYW5kIEkyQyBpbnRlcmZhY2UgdG8gZW5hYmxlIG1vbml0b3JpbmcNCj4gPiArZnJvbSBl
eHRlcm5hbCBtYXN0ZXIuIE91dCBvZiB0aGlzIGludGVyZmFjZSBjdXJyZW50bHkgb25seSBEUlAg
aXMgc3VwcG9ydGVkLg0KPiA+ICtPdGhlciBibG9jayBQUy1TWVNNT04gaXMgbWVtb3J5IG1hcHBl
ZCB0byBQUy4gQm90aCBvZiBibG9jayBoYXMNCj4gPiArYnVpbHQtaW4gYWxhcm0gZ2VuZXJhdGlv
biBsb2dpYyB0aGF0IGlzIHVzZWQgdG8gaW50ZXJydXB0IHRoZQ0KPiA+ICtwcm9jZXNzb3IgYmFz
ZWQgb24gY29uZGl0aW9uIHNldC4NCj4gDQo+IEkgZ3Vlc3MgYW55b25lIHJlYWRpbmcgdGhpcyBi
ZWNhdXNlIHRoZXkgaGF2ZSB0aGUgaGFyZHdhcmUgd291bGQga25vdyB3aGF0DQo+IFBTIGFuZCBQ
TCBhcmUsIGJ1dCBpdCB3b3VsZCBzdGlsbCBiZSBuaWNlIHRvIGRlZmluZSB0aG9zZSBhY3Jvbnlt
cyENCg0KT2theSwgSSB3aWxsIHJlY3RpZnkgdGhpcyBpbiB2Mi4NCg0KPiANCj4gR29vZ2xlIHN1
Z2dlc3QsIFByb2dyYW1tYWJsZSBMb2dpYyAoRlBHQSBiaXQgSSBndWVzcykgYW5kIFByb2Nlc3Mg
U3BhY2UNCj4gKEFybSBjb3JlIGJpdD8pICBBcyBJIHJlYWQgdGhpcywgZnJvbSBhIGRyaXZlciBw
b2ludCBvZiB2aWV3IGl0IGRvZXNuJ3QgcmVhbGx5DQo+IG1hdHRlciAtIHRoZXNlIGFyZSBqdXN0
IGJsb2NrcyBvZiBjaGFubmVscyB0aGF0IGFyZSB0aGVyZT8NCg0KWWVzLCB0aGVzZSBhcmUganVz
dCBibG9ja3Mgb2YgY2hhbm5lbHMsIHdoaWNoIGFyZSBlbmFibGVkL2Rpc2FibGVkIHZpYSBkZXZp
Y2V0cmVlIGNoaWxkIG5vZGVzICJ4bG54LHp5bnFtcC1hbXMtcHMiIGFuZCAieGxueCx6eW5xbXAt
YW1zLXBsIi4NCg0KPiANCj4gVGhpcyBiaW5kaW5nIGlzIGNvbXBsZXggZW5vdWdoIEkgZGVmaW5p
dGVseSB3YW50IHNvbWUgRFQgYmluZGluZyByZXZpZXcuDQoNClN1cmUhDQoNCj4gDQo+IA0KPiAN
Cj4gPiArDQo+ID4gK0FsbCBkZXNpZ25zIHNob3VsZCBoYXZlIEFNUyByZWdpc3RlcnMsIGJ1dCBQ
UyBhbmQgUEwgYXJlIG9wdGlvbmFsLg0KPiA+ICtUaGUgQU1TIGNvbnRyb2xsZXIgY2FuIHdvcmsg
d2l0aCBvbmx5IFBTLCBvbmx5IFBMIGFuZCBib3RoIFBTIGFuZCBQTA0KPiA+ICtjb25maWd1cmF0
aW9ucy4gUGxlYXNlIHNwZWNpZnkgcmVnaXN0ZXJzIGFjY29yZGluZyB0byB5b3VyIGRlc2lnbi4N
Cj4gPiArRGV2aWNldHJlZSBzaG91bGQgYWx3YXlzIGhhdmUgQU1TIG1vZHVsZSBwcm9wZXJ0eS4g
UHJvdmlkaW5nIFBTICYgUEwNCj4gbW9kdWxlIGlzIG9wdGlvbmFsLg0KPiA+ICsNCj4gPiArUmVx
dWlyZWQgcHJvcGVydGllczoNCj4gPiArCS0gY29tcGF0aWJsZTogU2hvdWxkIGJlICJ4bG54LHp5
bnFtcC1hbXMiDQo+ID4gKwktIHJlZzogIFNob3VsZCBzcGVjaWZ5IEFNUyByZWdpc3RlciBzcGFj
ZQ0KPiA+ICsJLSBpbnRlcnJ1cHRzOiBJbnRlcnJ1cHQgbnVtYmVyIGZvciB0aGUgQU1TIGNvbnRy
b2wgaW50ZXJmYWNlDQo+ID4gKwktIGludGVycnVwdC1uYW1lczogSW50ZXJydXB0IG5hbWUsIG11
c3QgYmUgImFtcy1pcnEiDQo+ID4gKwktIGNsb2NrczogU2hvdWxkIGNvbnRhaW4gYSBjbG9jayBz
cGVjaWZpZXIgZm9yIHRoZSBkZXZpY2UNCj4gPiArCS0gcmFuZ2VzOiBrZWVwIHRoZSBwcm9wZXJ0
eSBlbXB0eSB0byBtYXAgY2hpbGQgYWRkcmVzcyBzcGFjZQ0KPiA+ICsJICAgICAgICAgIChmb3Ig
UFMgYW5kL29yIFBMKSBub2RlcyAxOjEgb250byB0aGUgcGFyZW50IGFkZHJlc3MNCj4gPiArCSAg
ICAgICAgICBzcGFjZQ0KPiA+ICsNCj4gPiArQU1TIGRldmljZSB0cmVlIHN1Ym5vZGU6DQo+ID4g
KwktIGNvbXBhdGlibGU6IFNob3VsZCBiZSAieGxueCx6eW5xbXAtYW1zLXBzIiBvciAieGxueCx6
eW5xbXAtYW1zLXBsIg0KPiA+ICsJLSByZWc6ICBSZWdpc3RlciBzcGFjZSBmb3IgUFMgb3IgUEwN
Cj4gPiArDQo+ID4gK09wdGlvbmFsIHByb3BlcnRpZXM6DQo+ID4gKw0KPiA+ICtGb2xsb3dpbmcg
b3B0aW9uYWwgcHJvcGVydHkgb25seSB2YWxpZCBmb3IgUEwuDQo+ID4gKwktIHhsbngsZXh0LWNo
YW5uZWxzOiBMaXN0IG9mIGV4dGVybmFsIGNoYW5uZWxzIHRoYXQgYXJlIGNvbm5lY3RlZCB0byB0
aGUNCj4gPiArCSAgICAgICAgICAgICAgICAgICAgIEFNUyBQTCBtb2R1bGUuDQo+ID4gKw0KPiA+
ICsJICBUaGUgY2hpbGQgbm9kZXMgb2YgdGhpcyBub2RlIHJlcHJlc2VudCB0aGUgZXh0ZXJuYWwg
Y2hhbm5lbHMgd2hpY2gNCj4gYXJlDQo+ID4gKwkgIGNvbm5lY3RlZCB0byB0aGUgQU1TIE1vZHVs
ZS4gSWYgdGhlIHByb3BlcnR5IGlzIG5vdCBwcmVzZW50DQo+ID4gKwkgIG5vIGV4dGVybmFsIGNo
YW5uZWxzIHdpbGwgYmUgYXNzdW1lZCB0byBiZSBjb25uZWN0ZWQuDQo+ID4gKw0KPiA+ICsJICBF
YWNoIGNoaWxkIG5vZGUgcmVwcmVzZW50cyBvbmUgY2hhbm5lbCBhbmQgaGFzIHRoZSBmb2xsb3dp
bmcNCj4gPiArCSAgcHJvcGVydGllczoNCj4gPiArCQlSZXF1aXJlZCBwcm9wZXJ0aWVzOg0KPiA+
ICsJCQkqIHJlZzogUGFpciBvZiBwaW5zIHRoZSBjaGFubmVsIGlzIGNvbm5lY3RlZCB0by4NCj4g
PiArCQkJCTA6IFZQL1ZODQo+IEhtbS4gU28gd2UgaGF2ZSB0aGlzIHRhYmxlIGhlcmUgb2YgdGhl
IG9uZXMgdGhhdCBtYXkgb3IgbWF5IG5vdCBiZSBjb25uZWN0ZWQNCj4gd2l0aCBkaWZmZXJlbnQg
bnVtYmVyaW5nIGZyb20gdGhlIG92ZXJhbGwgdGFibGUuICBHaXZlbiB0aGVzZSBpbmRleGVzIGFy
ZQ0KPiBhcmJpdHJhcnkgY2FuIHdlIHVuaWZ5IHRoZW0/DQo+IA0KPiBJLmUuIHVzZSB0aGUgaW5k
ZXggZnJvbSBiZWxvdyBidXQgc3RhdGUgaGVyZSB3aGljaCB2YWx1ZXMgY2FuIGJlIHVzZWQ/DQoN
Ck9rYXkuIEkgZ3Vlc3MgeW91IGFyZSBzdWdnZXN0aW5nIHRvIHVzZSB0aGUgY2hhbm5lbCBpbmRl
eCBmcm9tIHRoZSBjaGFubmVsIGRldGFpbHMgdGFibGUgZ2l2ZW4gaW4gdGhlIGVuZCBvZiB0aGlz
IGZpbGUgYW5kIHN0YXRlIGhlcmUgd2hpY2ggbnVtYmVyIChmb3IgJ3JlZycgdmFsdWUpIHNob3Vs
ZCBiZSB1c2VkIGZvciBnaXZlbiBjaGFubmVsLiBJcyB0aGlzIGNvcnJlY3Q/DQoNCj4gDQo+ID4g
KwkJCQkxOiBWVVNFUjANCj4gPiArCQkJCTI6IFZVU0VSMQ0KPiA+ICsJCQkJMzogVlVTRVIzDQo+
ID4gKwkJCQk0OiBWVVNFUjQNCj4gPiArCQkJCTU6IFZBVVhQWzBdL1ZBVVhOWzBdDQo+ID4gKwkJ
CQk2OiBWQVVYUFsxXS9WQVVYTlsxXQ0KPiA+ICsJCQkJLi4uDQo+ID4gKwkJCQkyMDogVkFVWFBb
MTVdL1ZBVVhOWzE1XQ0KPiA+ICsJCQkgIE5vdGUgZWFjaCBjaGFubmVsIG51bWJlciBzaG91bGQg
b25seSBiZSB1c2VkIGF0DQo+IG1vc3QNCj4gPiArCQkJICBvbmNlLg0KPiA+ICsJCU9wdGlvbmFs
IHByb3BlcnRpZXM6DQo+ID4gKwkJCSogeGxueCxiaXBvbGFyOiBJZiBzZXQgdGhlIGNoYW5uZWwg
aXMgdXNlZCBpbiBiaXBvbGFyDQo+ID4gKwkJCSAgbW9kZS4NCj4gPiArDQo+ID4gKw0KPiA+ICtF
eGFtcGxlOg0KPiA+ICsJeGlsaW54X2FtczogYW1zQGZmYTUwMDAwIHsNCj4gPiArCQljb21wYXRp
YmxlID0gInhsbngsenlucW1wLWFtcyI7DQo+ID4gKwkJaW50ZXJydXB0LXBhcmVudCA9IDwmZ2lj
PjsNCj4gPiArCQlpbnRlcnJ1cHRzID0gPDAgNTYgND47DQo+ID4gKwkJaW50ZXJydXB0LW5hbWVz
ID0gImFtcy1pcnEiOw0KPiA+ICsJCWNsb2NrcyA9IDwmY2xrYyA3MD47DQo+ID4gKwkJcmVnID0g
PDB4MCAweGZmYTUwMDAwIDB4MCAweDgwMD47DQo+ID4gKwkJcmVnLW5hbWVzID0gImFtcy1iYXNl
IjsNCj4gPiArCQkjYWRkcmVzcy1jZWxscyA9IDwyPjsNCj4gPiArCQkjc2l6ZS1jZWxscyA9IDwy
PjsNCj4gPiArCQlyYW5nZXM7DQo+ID4gKw0KPiA+ICsJCWFtc19wczogYW1zX3BzQGZmYTUwODAw
IHsNCj4gPiArCQkJY29tcGF0aWJsZSA9ICJ4bG54LHp5bnFtcC1hbXMtcHMiOw0KPiA+ICsJCQly
ZWcgPSA8MHgwIDB4ZmZhNTA4MDAgMHgwIDB4NDAwPjsNCj4gPiArCQl9Ow0KPiA+ICsNCj4gPiAr
CQlhbXNfcGw6IGFtc19wbEBmZmE1MGMwMCB7DQo+ID4gKwkJCWNvbXBhdGlibGUgPSAieGxueCx6
eW5xbXAtYW1zLXBsIjsNCj4gPiArCQkJcmVnID0gPDB4MCAweGZmYTUwYzAwIDB4MCAweDQwMD47
DQo+ID4gKwkJCXhsbngsZXh0LWNoYW5uZWxzIHsNCj4gPiArCQkJCSNhZGRyZXNzLWNlbGxzID0g
PDE+Ow0KPiA+ICsJCQkJI3NpemUtY2VsbHMgPSA8MD47DQo+ID4gKwkJCQljaGFubmVsQDAgew0K
PiA+ICsJCQkJCXJlZyA9IDwwPjsNCj4gPiArCQkJCQl4bG54LGJpcG9sYXI7DQo+ID4gKwkJCQl9
Ow0KPiA+ICsJCQkJY2hhbm5lbEAxIHsNCj4gPiArCQkJCQlyZWcgPSA8MT47DQo+ID4gKwkJCQl9
Ow0KPiA+ICsJCQkJY2hhbm5lbEA4IHsNCj4gPiArCQkJCQlyZWcgPSA8OD47DQo+ID4gKwkJCQkJ
eGxueCxiaXBvbGFyOw0KPiA+ICsJCQkJfTsNCj4gPiArCQkJfTsNCj4gPiArCQl9Ow0KPiA+ICsJ
fTsNCj4gPiArDQo+ID4gK0FNUyBDaGFubmVscyBEZXRhaWxzOg0KPiA+ICsNCj4gPiArU3lzbW9u
IEJsb2NrCXxDaGFubmVsfAkJCURldGFpbHMNCj4gCQkJfE1lYXN1cmVtZW50DQo+ID4gKwkJIE51
bWJlcg0KPiAJCQkgVHlwZQ0KPiA+ICstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t
LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t
LS0tLS0tLS0NCj4gLS0tLS0tDQo+ID4gK0FNUyBDVFJMCXwwCXxTeXN0ZW0gUExMcyB2b2x0YWdl
IG1lYXN1cmVtZW50LCBWQ0NfUFNQTEwuDQo+IAkJfFZvbHRhZ2UNCj4gPiArCQl8MQl8QmF0dGVy
eSB2b2x0YWdlIG1lYXN1cmVtZW50LCBWQ0NfUFNCQVRULg0KPiAJCXxWb2x0YWdlDQo+ID4gKwkJ
fDIJfFBMIEludGVybmFsIHZvbHRhZ2UgbWVhc3VyZW1lbnQsIFZDQ0lOVC4NCj4gCQl8Vm9sdGFn
ZQ0KPiA+ICsJCXwzCXxCbG9jayBSQU0gdm9sdGFnZSBtZWFzdXJlbWVudCwgVkNDQlJBTS4NCj4g
CQl8Vm9sdGFnZQ0KPiA+ICsJCXw0CXxQTCBBdXggdm9sdGFnZSBtZWFzdXJlbWVudCwgVkNDQVVY
Lg0KPiAJCXxWb2x0YWdlDQo+ID4gKwkJfDUJfFZvbHRhZ2UgbWVhc3VyZW1lbnQgZm9yIHNpeCBE
RFIgSS9PIFBMTHMsDQo+IFZDQ19QU0REUl9QTEwuCXxWb2x0YWdlDQo+ID4gKwkJfDYJfFZDQ19Q
U0lOVEZQX0REUiB2b2x0YWdlIG1lYXN1cmVtZW50Lg0KPiAJCQl8Vm9sdGFnZQ0KPiA+ICstLS0t
LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t
LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCj4gLS0tLS0tDQo+ID4gK1BT
IFN5c21vbgl8Nwl8TFBEIHRlbXBlcmF0dXJlIG1lYXN1cmVtZW50Lg0KPiAJCQl8VGVtcGVyYXR1
cmUNCj4gPiArCQl8OAl8RlBEIFRlbXBlcmF0dXJlIE1lYXN1cm1lbnQgKFJFTU9URSkuDQo+IAkJ
CXxUZW1wZXJhdHVyZQ0KPiA+ICsJCXw5CXxWQ0MgUFMgTFBEIHZvbHRhZ2UgbWVhc3VyZW1lbnQg
KHN1cHBseTEpLg0KPiAJCXxWb2x0YWdlDQo+ID4gKwkJfDEwCXxWQ0MgUFMgRlBEIHZvbHRhZ2Ug
bWVhc3VyZW1lbnQgKHN1cHBseTIpLg0KPiAJCXxWb2x0YWdlDQo+ID4gKwkJfDExCXxQUyBBdXgg
dm9sdGFnZSByZWZlcmVuY2UgKHN1cHBseTMpLg0KPiAJCXxWb2x0YWdlDQo+ID4gKwkJfDEyCXxE
RFIgSS9PIFZDQyB2b2x0YWdlIG1lYXN1cmVtZW50Lg0KPiAJCXxWb2x0YWdlDQo+ID4gKwkJfDEz
CXxQUyBJTyBCYW5rIDUwMyB2b2x0YWdlIG1lYXN1cmVtZW50IChzdXBwbHk1KS4NCj4gCQkJfFZv
bHRhZ2UNCj4gPiArCQl8MTQJfFBTIElPIEJhbmsgNTAwIHZvbHRhZ2UgbWVhc3VyZW1lbnQgKHN1
cHBseTYpLg0KPiAJCQl8Vm9sdGFnZQ0KPiA+ICsJCXwxNQl8VkNDT19QU0lPMSB2b2x0YWdlIG1l
YXN1cmVtZW50Lg0KPiAJCXxWb2x0YWdlDQo+ID4gKwkJfDE2CXxWQ0NPX1BTSU8yIHZvbHRhZ2Ug
bWVhc3VyZW1lbnQuDQo+IAkJfFZvbHRhZ2UNCj4gPiArCQl8MTcJfFZDQ19QU19HVFIgdm9sdGFn
ZSBtZWFzdXJlbWVudA0KPiAoVlBTX01HVFJBVkNDKS4JCQl8Vm9sdGFnZQ0KPiA+ICsJCXwxOAl8
VlRUX1BTX0dUUiB2b2x0YWdlIG1lYXN1cmVtZW50DQo+IChWUFNfTUdUUkFWVFQpLgkJCXxWb2x0
YWdlDQo+ID4gKwkJfDE5CXxWQ0NfUFNBREMgdm9sdGFnZSBtZWFzdXJlbWVudC4NCj4gCQkJfFZv
bHRhZ2UNCj4gPiArLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t
LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQo+
IC0tLS0tLQ0KPiA+ICtQTCBTeXNtb24JfDIwCXxQTCBUZW1wZXJhdHVyZSBtZWFzdXJlbWVudC4N
Cj4gCQkJfFRlbXBlcmF0dXJlDQo+ID4gKwkJfDIxCXxQTCBJbnRlcm5hbCBWb2x0YWdlIFZvbHRh
Z2UgbWVhc3VyZW1lbnQsIFZDQ0lOVC4NCj4gCQl8Vm9sdGFnZQ0KPiA+ICsJCXwyMgl8UEwgQXV4
aWxpYXJ5IFZvbHRhZ2UgbWVhc3VyZW1lbnQsIFZDQ0FVWC4NCj4gCQl8Vm9sdGFnZQ0KPiA+ICsJ
CXwyMwl8QURDIFJlZmVyZW5jZSBQKyBWb2x0YWdlIG1lYXN1cmVtZW50Lg0KPiAJCQl8Vm9sdGFn
ZQ0KPiA+ICsJCXwyNAl8QURDIFJlZmVyZW5jZSBOLSBWb2x0YWdlIG1lYXN1cmVtZW50Lg0KPiAJ
CQl8Vm9sdGFnZQ0KPiA+ICsJCXwyNQl8UEwgQmxvY2sgUkFNIFZvbHRhZ2UgbWVhc3VyZW1lbnQs
IFZDQ0JSQU0uDQo+IAkJCXxWb2x0YWdlDQo+ID4gKwkJfDI2CXxMUEQgSW50ZXJuYWwgVm9sdGFn
ZSBtZWFzdXJlbWVudCwgVkNDX1BTSU5UTFANCj4gKHN1cHBseTQpLgl8Vm9sdGFnZQ0KPiA+ICsJ
CXwyNwl8RlBEIEludGVybmFsIFZvbHRhZ2UgbWVhc3VyZW1lbnQsIFZDQ19QU0lOVEZQDQo+IChz
dXBwbHk1KS4JfFZvbHRhZ2UNCj4gPiArCQl8MjgJfFBTIEF1eGlsaWFyeSBWb2x0YWdlIG1lYXN1
cmVtZW50IChzdXBwbHk2KS4NCj4gCQl8Vm9sdGFnZQ0KPiA+ICsJCXwyOQl8UEwgVkNDQURDIFZv
bHRhZ2UgbWVhc3VyZW1lbnQgKHZjY2FtcykuDQo+IAkJfFZvbHRhZ2UNCj4gPiArCQl8MzAJfERp
ZmZlcmVuY2lhbCBhbmFsb2cgaW5wdXQgc2lnbmFsIFZvbHRhZ2UgbWVhc3VybWVudC4NCj4gCQl8
Vm9sdGFnZQ0KPiA+ICsJCXwzMQl8VlVzZXIwIFZvbHRhZ2UgbWVhc3VyZW1lbnQgKHN1cHBseTcp
Lg0KPiAJCQl8Vm9sdGFnZQ0KPiA+ICsJCXwzMgl8VlVzZXIxIFZvbHRhZ2UgbWVhc3VyZW1lbnQg
KHN1cHBseTgpLg0KPiAJCQl8Vm9sdGFnZQ0KPiA+ICsJCXwzMwl8VlVzZXIyIFZvbHRhZ2UgbWVh
c3VyZW1lbnQgKHN1cHBseTkpLg0KPiAJCQl8Vm9sdGFnZQ0KPiA+ICsJCXwzNAl8VlVzZXIzIFZv
bHRhZ2UgbWVhc3VyZW1lbnQgKHN1cHBseTEwKS4NCj4gCQkJfFZvbHRhZ2UNCj4gPiArCQl8MzUJ
fEF1eGlsaWFyeSBjaCAwIFZvbHRhZ2UgbWVhc3VyZW1lbnQgKFZBdXgwKS4NCj4gCQl8Vm9sdGFn
ZQ0KPiA+ICsJCXwzNgl8QXV4aWxpYXJ5IGNoIDEgVm9sdGFnZSBtZWFzdXJlbWVudCAoVkF1eDEp
Lg0KPiAJCXxWb2x0YWdlDQo+ID4gKwkJfDM3CXxBdXhpbGlhcnkgY2ggMiBWb2x0YWdlIG1lYXN1
cmVtZW50IChWQXV4MikuDQo+IAkJfFZvbHRhZ2UNCj4gPiArCQl8MzgJfEF1eGlsaWFyeSBjaCAz
IFZvbHRhZ2UgbWVhc3VyZW1lbnQgKFZBdXgzKS4NCj4gCQl8Vm9sdGFnZQ0KPiA+ICsJCXwzOQl8
QXV4aWxpYXJ5IGNoIDQgVm9sdGFnZSBtZWFzdXJlbWVudCAoVkF1eDQpLg0KPiAJCXxWb2x0YWdl
DQo+ID4gKwkJfDQwCXxBdXhpbGlhcnkgY2ggNSBWb2x0YWdlIG1lYXN1cmVtZW50IChWQXV4NSku
DQo+IAkJfFZvbHRhZ2UNCj4gPiArCQl8NDEJfEF1eGlsaWFyeSBjaCA2IFZvbHRhZ2UgbWVhc3Vy
ZW1lbnQgKFZBdXg2KS4NCj4gCQl8Vm9sdGFnZQ0KPiA+ICsJCXw0Mgl8QXV4aWxpYXJ5IGNoIDcg
Vm9sdGFnZSBtZWFzdXJlbWVudCAoVkF1eDcpLg0KPiAJCXxWb2x0YWdlDQo+ID4gKwkJfDQzCXxB
dXhpbGlhcnkgY2ggOCBWb2x0YWdlIG1lYXN1cmVtZW50IChWQXV4OCkuDQo+IAkJfFZvbHRhZ2UN
Cj4gPiArCQl8NDQJfEF1eGlsaWFyeSBjaCA5IFZvbHRhZ2UgbWVhc3VyZW1lbnQgKFZBdXg5KS4N
Cj4gCQl8Vm9sdGFnZQ0KPiA+ICsJCXw0NQl8QXV4aWxpYXJ5IGNoIDEwIFZvbHRhZ2UgbWVhc3Vy
ZW1lbnQgKFZBdXgxMCkuDQo+IAkJCXxWb2x0YWdlDQo+ID4gKwkJfDQ2CXxBdXhpbGlhcnkgY2gg
MTEgVm9sdGFnZSBtZWFzdXJlbWVudCAoVkF1eDExKS4NCj4gCQkJfFZvbHRhZ2UNCj4gPiArCQl8
NDcJfEF1eGlsaWFyeSBjaCAxMiBWb2x0YWdlIG1lYXN1cmVtZW50IChWQXV4MTIpLg0KPiAJCQl8
Vm9sdGFnZQ0KPiA+ICsJCXw0OAl8QXV4aWxpYXJ5IGNoIDEzIFZvbHRhZ2UgbWVhc3VyZW1lbnQg
KFZBdXgxMykuDQo+IAkJCXxWb2x0YWdlDQo+ID4gKwkJfDQ5CXxBdXhpbGlhcnkgY2ggMTQgVm9s
dGFnZSBtZWFzdXJlbWVudCAoVkF1eDE0KS4NCj4gCQkJfFZvbHRhZ2UNCj4gPiArCQl8NTAJfEF1
eGlsaWFyeSBjaCAxNSBWb2x0YWdlIG1lYXN1cmVtZW50IChWQXV4MTUpLg0KPiAJCQl8Vm9sdGFn
ZQ0KPiA+ICstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t
LS0tLS0tLS0tLS0tLS0tLS0tLS0NCj4gPiArLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t
LS0tLS0tDQo+IA0KPiBUaGFua3MsDQo+IA0KPiBKb25hdGhhbg0KDQpUaGFua3MsDQpNYW5pc2gg
TmFyYW5pDQo=
^ permalink raw reply [flat|nested] 13+ messages in thread
* RE: [PATCH 1/3] dt-bindings: iio: adc: Add Xilinx AMS binding documentation
2018-09-04 1:17 ` Rob Herring
@ 2018-09-06 13:42 ` Manish Narani
0 siblings, 0 replies; 13+ messages in thread
From: Manish Narani @ 2018-09-06 13:42 UTC (permalink / raw)
To: Rob Herring
Cc: jic23@kernel.org, knaack.h@gmx.de, lars@metafoo.de,
pmeerw@pmeerw.net, mark.rutland@arm.com, Michal Simek,
leoyang.li@nxp.com, sudeep.holla@arm.com,
amit.kucheria@linaro.org, broonie@kernel.org,
arnaud.pouliquen@st.com, geert@linux-m68k.org,
eugen.hristev@microchip.com, rdunlap@infradead.org,
lukas@wunner.de, freeman.liu@spreadtrum.com,
vilhelm.gray@gmail.com, tglx@linutronix.de,
baolin.wang@linaro.org, gregkh@linuxfoundation.org, Srinivas Goud,
Anirudha Sarangi, linux-iio@vger.kernel.org,
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org
Hi Rob,
Thanks for the review. Please see my comments inline.
> From: Rob Herring [mailto:robh@kernel.org]
> Sent: Tuesday, September 4, 2018 6:48 AM
> To: Manish Narani <MNARANI@xilinx.com>
>=20
> On Thu, Aug 30, 2018 at 03:52:17PM +0530, Manish Narani wrote:
> > Xilinx AMS have several ADC channels that can be used for measurement
> > of different voltages and temperatures. Document the same in the bindin=
gs.
> >
> > Signed-off-by: Manish Narani <manish.narani@xilinx.com>
> > ---
> > .../devicetree/bindings/iio/adc/xilinx-ams.txt | 159
> +++++++++++++++++++++
> > 1 file changed, 159 insertions(+)
> > create mode 100644
> > Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
> >
> > diff --git a/Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
> > b/Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
> > new file mode 100644
> > index 0000000..8cc96f0
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
> > +AMS device tree subnode:
> > + - compatible: Should be "xlnx,zynqmp-ams-ps" or "xlnx,zynqmp-ams-pl"
> > + - reg: Register space for PS or PL
>=20
> Please split each child node into its own section.
Okay. I will update this in v2.
>=20
> > +
> > +Optional properties:
> > +
> > +Following optional property only valid for PL.
>=20
> property or...
It should be node. I will update this in v2.
>=20
> > + - xlnx,ext-channels: List of external channels that are connected to =
the
> > + AMS PL module.
> > +
> > + The child nodes of this node represent the external channels which
> > +are
>=20
> node?
>=20
> > + connected to the AMS Module. If the property is not present
> > + no external channels will be assumed to be connected.
> > +
> > + Each child node represents one channel and has the following
> > + properties:
> > + Required properties:
> > + * reg: Pair of pins the channel is connected to.
> > + 0: VP/VN
> > + 1: VUSER0
> > + 2: VUSER1
> > + 3: VUSER3
> > + 4: VUSER4
> > + 5: VAUXP[0]/VAUXN[0]
> > + 6: VAUXP[1]/VAUXN[1]
> > + ...
> > + 20: VAUXP[15]/VAUXN[15]
> > + Note each channel number should only be used at
> most
> > + once.
> > + Optional properties:
> > + * xlnx,bipolar: If set the channel is used in bipolar
> > + mode.
> > +
> > +
> > +Example:
> > + xilinx_ams: ams@ffa50000 {
> > + compatible =3D "xlnx,zynqmp-ams";
> > + interrupt-parent =3D <&gic>;
> > + interrupts =3D <0 56 4>;
> > + interrupt-names =3D "ams-irq";
> > + clocks =3D <&clkc 70>;
> > + reg =3D <0x0 0xffa50000 0x0 0x800>;
> > + reg-names =3D "ams-base";
> > + #address-cells =3D <2>;
> > + #size-cells =3D <2>;
> > + ranges;
>=20
> There's no need for 64-bits of addresses and sizes here. Use ranges.
Okay.
> > +
> > + ams_ps: ams_ps@ffa50800 {
> > + compatible =3D "xlnx,zynqmp-ams-ps";
> > + reg =3D <0x0 0xffa50800 0x0 0x400>;
> > + };
> > +
> > + ams_pl: ams_pl@ffa50c00 {
> > + compatible =3D "xlnx,zynqmp-ams-pl";
> > + reg =3D <0x0 0xffa50c00 0x0 0x400>;
> > + xlnx,ext-channels {
> > + #address-cells =3D <1>;
> > + #size-cells =3D <0>;
> > + channel@0 {
> > + reg =3D <0>;
> > + xlnx,bipolar;
> > + };
> > + channel@1 {
> > + reg =3D <1>;
> > + };
> > + channel@8 {
> > + reg =3D <8>;
> > + xlnx,bipolar;
> > + };
> > + };
> > + };
> > + };
> > +
> > +AMS Channels Details:
> > +
> > +Sysmon Block |Channel| Details
> |Measurement
> > + Number
> Type
> > +----------------------------------------------------------------------=
-----------------------------
> ------
> > +AMS CTRL |0 |System PLLs voltage measurement, VCC_PSPLL.
> |Voltage
> > + |1 |Battery voltage measurement, VCC_PSBATT.
> |Voltage
> > + |2 |PL Internal voltage measurement, VCCINT.
> |Voltage
> > + |3 |Block RAM voltage measurement, VCCBRAM.
> |Voltage
> > + |4 |PL Aux voltage measurement, VCCAUX.
> |Voltage
> > + |5 |Voltage measurement for six DDR I/O PLLs,
> VCC_PSDDR_PLL. |Voltage
> > + |6 |VCC_PSINTFP_DDR voltage measurement.
> |Voltage
> > +----------------------------------------------------------------------=
-----------------------------
> ------
> > +PS Sysmon |7 |LPD temperature measurement.
> |Temperature
> > + |8 |FPD Temperature Measurment (REMOTE).
> |Temperature
> > + |9 |VCC PS LPD voltage measurement (supply1).
> |Voltage
> > + |10 |VCC PS FPD voltage measurement (supply2).
> |Voltage
> > + |11 |PS Aux voltage reference (supply3).
> |Voltage
> > + |12 |DDR I/O VCC voltage measurement.
> |Voltage
> > + |13 |PS IO Bank 503 voltage measurement (supply5).
> |Voltage
> > + |14 |PS IO Bank 500 voltage measurement (supply6).
> |Voltage
> > + |15 |VCCO_PSIO1 voltage measurement.
> |Voltage
> > + |16 |VCCO_PSIO2 voltage measurement.
> |Voltage
> > + |17 |VCC_PS_GTR voltage measurement
> (VPS_MGTRAVCC). |Voltage
> > + |18 |VTT_PS_GTR voltage measurement
> (VPS_MGTRAVTT). |Voltage
> > + |19 |VCC_PSADC voltage measurement.
> |Voltage
> > +----------------------------------------------------------------------=
-----------------------------
> ------
> > +PL Sysmon |20 |PL Temperature measurement.
> |Temperature
> > + |21 |PL Internal Voltage Voltage measurement, VCCINT.
> |Voltage
> > + |22 |PL Auxiliary Voltage measurement, VCCAUX.
> |Voltage
> > + |23 |ADC Reference P+ Voltage measurement.
> |Voltage
> > + |24 |ADC Reference N- Voltage measurement.
> |Voltage
> > + |25 |PL Block RAM Voltage measurement, VCCBRAM.
> |Voltage
> > + |26 |LPD Internal Voltage measurement, VCC_PSINTLP
> (supply4). |Voltage
> > + |27 |FPD Internal Voltage measurement, VCC_PSINTFP
> (supply5). |Voltage
> > + |28 |PS Auxiliary Voltage measurement (supply6).
> |Voltage
> > + |29 |PL VCCADC Voltage measurement (vccams).
> |Voltage
> > + |30 |Differencial analog input signal Voltage measurment.
> |Voltage
> > + |31 |VUser0 Voltage measurement (supply7).
> |Voltage
> > + |32 |VUser1 Voltage measurement (supply8).
> |Voltage
> > + |33 |VUser2 Voltage measurement (supply9).
> |Voltage
> > + |34 |VUser3 Voltage measurement (supply10).
> |Voltage
> > + |35 |Auxiliary ch 0 Voltage measurement (VAux0).
> |Voltage
> > + |36 |Auxiliary ch 1 Voltage measurement (VAux1).
> |Voltage
> > + |37 |Auxiliary ch 2 Voltage measurement (VAux2).
> |Voltage
> > + |38 |Auxiliary ch 3 Voltage measurement (VAux3).
> |Voltage
> > + |39 |Auxiliary ch 4 Voltage measurement (VAux4).
> |Voltage
> > + |40 |Auxiliary ch 5 Voltage measurement (VAux5).
> |Voltage
> > + |41 |Auxiliary ch 6 Voltage measurement (VAux6).
> |Voltage
> > + |42 |Auxiliary ch 7 Voltage measurement (VAux7).
> |Voltage
> > + |43 |Auxiliary ch 8 Voltage measurement (VAux8).
> |Voltage
> > + |44 |Auxiliary ch 9 Voltage measurement (VAux9).
> |Voltage
> > + |45 |Auxiliary ch 10 Voltage measurement (VAux10).
> |Voltage
> > + |46 |Auxiliary ch 11 Voltage measurement (VAux11).
> |Voltage
> > + |47 |Auxiliary ch 12 Voltage measurement (VAux12).
> |Voltage
> > + |48 |Auxiliary ch 13 Voltage measurement (VAux13).
> |Voltage
> > + |49 |Auxiliary ch 14 Voltage measurement (VAux14).
> |Voltage
> > + |50 |Auxiliary ch 15 Voltage measurement (VAux15).
> |Voltage
> > +---------------------------------------------------------------------
> > +------------------------------------
> > --
> > 2.1.1
> >
Thanks,
Manish Narani
^ permalink raw reply [flat|nested] 13+ messages in thread
* RE: [PATCH 2/3] iio: adc: Add Xilinx AMS driver
2018-09-02 19:56 ` Jonathan Cameron
@ 2018-09-06 15:19 ` Manish Narani
2018-09-08 13:50 ` Jonathan Cameron
0 siblings, 1 reply; 13+ messages in thread
From: Manish Narani @ 2018-09-06 15:19 UTC (permalink / raw)
To: Jonathan Cameron
Cc: knaack.h@gmx.de, lars@metafoo.de, pmeerw@pmeerw.net,
robh+dt@kernel.org, mark.rutland@arm.com, Michal Simek,
leoyang.li@nxp.com, sudeep.holla@arm.com,
amit.kucheria@linaro.org, broonie@kernel.org,
arnaud.pouliquen@st.com, geert@linux-m68k.org,
eugen.hristev@microchip.com, rdunlap@infradead.org,
lukas@wunner.de, freeman.liu@spreadtrum.com,
vilhelm.gray@gmail.com, tglx@linutronix.de,
baolin.wang@linaro.org, gregkh@linuxfoundation.org, Srinivas Goud,
Anirudha Sarangi, linux-iio@vger.kernel.org,
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org
Hi Jonathan,
Thanks a lot for the review. Please see my response inline.
> -----Original Message-----
> From: Jonathan Cameron [mailto:jic23@kernel.org]
> Sent: Monday, September 3, 2018 1:27 AM
> To: Manish Narani <MNARANI@xilinx.com>
> Cc: knaack.h@gmx.de; lars@metafoo.de; pmeerw@pmeerw.net;
> robh+dt@kernel.org; mark.rutland@arm.com; Michal Simek
> <michals@xilinx.com>; leoyang.li@nxp.com; sudeep.holla@arm.com;
> amit.kucheria@linaro.org; broonie@kernel.org; arnaud.pouliquen@st.com;
> geert@linux-m68k.org; eugen.hristev@microchip.com; rdunlap@infradead.org;
> lukas@wunner.de; freeman.liu@spreadtrum.com; vilhelm.gray@gmail.com;
> tglx@linutronix.de; baolin.wang@linaro.org; gregkh@linuxfoundation.org;
> Srinivas Goud <sgoud@xilinx.com>; Anirudha Sarangi <anirudh@xilinx.com>;
> linux-iio@vger.kernel.org; devicetree@vger.kernel.org; linux-arm-
> kernel@lists.infradead.org; linux-kernel@vger.kernel.org
> Subject: Re: [PATCH 2/3] iio: adc: Add Xilinx AMS driver
>=20
> On Thu, 30 Aug 2018 15:52:18 +0530
> Manish Narani <manish.narani@xilinx.com> wrote:
>=20
> > The AMS includes an ADC as well as on-chip sensors that can be used to
> > sample external voltages and monitor on-die operating conditions, such
> > as temperature and supply voltage levels. The AMS has two SYSMON blocks=
.
> > PL-SYSMON block is capable of monitoring off chip voltage and
> > temperature.
> > PL-SYSMON block has DRP, JTAG and I2C interface to enable monitoring
> > from external master. Out of these interface currently only DRP is
> > supported.
> > Other block PS-SYSMON is memory mapped to PS.
> > The AMS can use internal channels to monitor voltage and temperature
> > as well as one primary and up to 16 auxiliary channels for measuring
> > external voltages.
> > The voltage and temperature monitoring channels also have event
> > capability which allows to generate an interrupt when their value
> > falls below or raises above a set threshold.
> >
> > Signed-off-by: Manish Narani <manish.narani@xilinx.com>
>=20
> Given the use of extended_name in here, there is a whole lot of undocumen=
ted
> userspace ABI. Please add to
>=20
> Documentation/ABI/testing/sysfs-bus-iio-xilinx-ams or similar.
Okay sure.
>=20
> I am a little concerned at introducing quite so many of these.
> Perhaps we need to revisit how we represent this sort of information...
>=20
> Otherwise, a few minor things inline but looking fairly good overall.
>=20
> Thanks,
>=20
> Jonathan
>=20
> > ---
> > drivers/iio/adc/Kconfig | 10 +
> > drivers/iio/adc/Makefile | 1 +
> > drivers/iio/adc/xilinx-ams.c | 1081
> > ++++++++++++++++++++++++++++++++++++++++++
> > drivers/iio/adc/xilinx-ams.h | 281 +++++++++++
> > 4 files changed, 1373 insertions(+)
> > create mode 100644 drivers/iio/adc/xilinx-ams.c create mode 100644
> > drivers/iio/adc/xilinx-ams.h
> >
> > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index
> > 4a75492..405ea00 100644
> > --- a/drivers/iio/adc/Kconfig
> > +++ b/drivers/iio/adc/Kconfig
> > @@ -941,4 +941,14 @@ config XILINX_XADC
> > The driver can also be build as a module. If so, the module will be
> called
> > xilinx-xadc.
> >
> > +config XILINX_AMS
> > + tristate "Xilinx AMS driver"
> > + depends on ARCH_ZYNQMP || COMPILE_TEST
> > + depends on HAS_IOMEM
> > + help
> > + Say yes here to have support for the Xilinx AMS.
> > +
> > + The driver can also be build as a module. If so, the module will be
> called
> > + xilinx-ams.
> > +
> > endmenu
> > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index
> > 03db7b5..fbfcc45 100644
> > --- a/drivers/iio/adc/Makefile
> > +++ b/drivers/iio/adc/Makefile
> > @@ -85,4 +85,5 @@ obj-$(CONFIG_VF610_ADC) +=3D vf610_adc.o
> > obj-$(CONFIG_VIPERBOARD_ADC) +=3D viperboard_adc.o xilinx-xadc-y :=3D
> > xilinx-xadc-core.o xilinx-xadc-events.o
> > obj-$(CONFIG_XILINX_XADC) +=3D xilinx-xadc.o
> > +obj-$(CONFIG_XILINX_AMS) +=3D xilinx-ams.o
> > obj-$(CONFIG_SD_ADC_MODULATOR) +=3D sd_adc_modulator.o diff --git
> > a/drivers/iio/adc/xilinx-ams.c b/drivers/iio/adc/xilinx-ams.c new file
> > mode 100644 index 0000000..10bcc52
> > --- /dev/null
> > +++ b/drivers/iio/adc/xilinx-ams.c
> > @@ -0,0 +1,1081 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Xilinx AMS driver
> > + *
> > + * Copyright (C) 2017-2018 Xilinx, Inc.
> > + *
> > + * Manish Narani <mnarani@xilinx.com>
> > + * Rajnikant Bhojani <rajnikant.bhojani@xilinx.com> */
> > +
> > +#include <linux/kernel.h>
> > +#include <linux/slab.h>
> > +#include <linux/module.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/clk.h>
> > +#include <linux/of_address.h>
> > +#include <linux/iopoll.h>
> > +#include <linux/delay.h>
> There is a rough convention of alphabetical order if there isn't a reason=
to do
> otherwise.
>=20
> Here you might keep the iio headers for their own block at the end, but t=
he rest
> should be in order.
Okay I will try to follow the same and update in v2.
>=20
> > +#include <linux/iio/iio.h>
> > +#include <linux/iio/sysfs.h>
> > +#include <linux/iio/events.h>
> > +#include <linux/io.h>
> > +
> > +#include "xilinx-ams.h"
> > +
> > +static const unsigned int AMS_UNMASK_TIMEOUT_MS =3D 500;
> > +
> > +static inline void ams_ps_update_reg(struct ams *ams, unsigned int off=
set,
> > + u32 mask, u32 data)
> > +{
> > + u32 val;
> > +
> > + val =3D readl(ams->ps_base + offset);
> > + writel((val & ~mask) | (data & mask), ams->ps_base + offset); }
> > +
> > +static inline void ams_pl_write_reg(struct ams *ams, unsigned int offs=
et,
> > + u32 data)
> > +{
> > + writel(data, ams->pl_base + offset); }
> I'm always anti wrappers that don't add much. In this driver you also on=
ly use
> them 'sometimes'.
Okay, I will use writel inline in v2
>=20
> I'd prefer just having the writel inline all the time. The update functi=
ons have a
> little more purpose and given they are called quite often, perhaps are wo=
rth
> keeping.
>=20
> Another option is to use regmap for the whole thing getting you update
> functions and caching etc. Might not be worth it here. Up to you.
Sure. I will try to explore this.
>=20
> > +
> > +static inline void ams_pl_update_reg(struct ams *ams, unsigned int off=
set,
> > + u32 mask, u32 data)
> > +{
> > + u32 val;
> > +
> > + val =3D readl(ams->pl_base + offset);
> > + writel((val & ~mask) | (data & mask), ams->pl_base + offset); }
> > +
> > +static void ams_update_intrmask(struct ams *ams, u64 mask, u64 val) {
> > + ams->intr_mask &=3D ~mask;
> > + ams->intr_mask |=3D (val & mask);
> > +
> > + writel(~(ams->intr_mask | ams->masked_alarm), ams->base +
> AMS_IER_0);
> > + writel(~(ams->intr_mask >> AMS_ISR1_INTR_MASK_SHIFT),
> > + ams->base + AMS_IER_1);
> > + writel(ams->intr_mask | ams->masked_alarm, ams->base +
> AMS_IDR_0);
> > + writel(ams->intr_mask >> AMS_ISR1_INTR_MASK_SHIFT,
> > + ams->base + AMS_IDR_1);
> > +}
> > +
> > +static void ams_disable_all_alarms(struct ams *ams) {
> > + /* disable PS module alarm */
> > + if (ams->ps_base) {
> > + ams_ps_update_reg(ams, AMS_REG_CONFIG1,
> AMS_REGCFG1_ALARM_MASK,
> > + AMS_REGCFG1_ALARM_MASK);
> > + ams_ps_update_reg(ams, AMS_REG_CONFIG3,
> AMS_REGCFG3_ALARM_MASK,
> > + AMS_REGCFG3_ALARM_MASK);
> > + }
> > +
> > + /* disable PL module alarm */
> > + if (ams->pl_base) {
> > + ams_pl_update_reg(ams, AMS_REG_CONFIG1,
> > + AMS_REGCFG1_ALARM_MASK,
> > + AMS_REGCFG1_ALARM_MASK);
> > + ams_pl_update_reg(ams, AMS_REG_CONFIG3,
> > + AMS_REGCFG3_ALARM_MASK,
> > + AMS_REGCFG3_ALARM_MASK);
> > + }
> > +}
> > +
> > +static void iio_ams_update_alarm(struct ams *ams, unsigned long
> > +alarm_mask) {
> > + u32 cfg;
> > + unsigned long flags;
> > + unsigned long pl_alarm_mask;
> > +
> > + if (ams->ps_base) {
> > + /* Configuring PS alarm enable */
> > + cfg =3D ~((alarm_mask & AMS_ISR0_ALARM_2_TO_0_MASK) <<
> > + AMS_CONF1_ALARM_2_TO_0_SHIFT);
> > + cfg &=3D ~((alarm_mask & AMS_ISR0_ALARM_6_TO_3_MASK)
> <<
> > + AMS_CONF1_ALARM_6_TO_3_SHIFT);
> > + ams_ps_update_reg(ams, AMS_REG_CONFIG1,
> AMS_REGCFG1_ALARM_MASK,
> > + cfg);
> > +
> > + cfg =3D ~((alarm_mask >>
> AMS_CONF3_ALARM_12_TO_7_SHIFT) &
> > + AMS_ISR0_ALARM_12_TO_7_MASK);
> > + ams_ps_update_reg(ams, AMS_REG_CONFIG3,
> AMS_REGCFG3_ALARM_MASK,
> > + cfg);
> > + }
> > +
> > + if (ams->pl_base) {
> > + pl_alarm_mask =3D (alarm_mask >> AMS_PL_ALARM_START);
> > + /* Configuring PL alarm enable */
> > + cfg =3D ~((pl_alarm_mask & AMS_ISR0_ALARM_2_TO_0_MASK)
> <<
> > + AMS_CONF1_ALARM_2_TO_0_SHIFT);
> > + cfg &=3D ~((pl_alarm_mask &
> AMS_ISR0_ALARM_6_TO_3_MASK) <<
> > + AMS_CONF1_ALARM_6_TO_3_SHIFT);
> > + ams_pl_update_reg(ams, AMS_REG_CONFIG1,
> > + AMS_REGCFG1_ALARM_MASK, cfg);
> > +
> > + cfg =3D ~((pl_alarm_mask >>
> AMS_CONF3_ALARM_12_TO_7_SHIFT) &
> > + AMS_ISR0_ALARM_12_TO_7_MASK);
> > + ams_pl_update_reg(ams, AMS_REG_CONFIG3,
> > + AMS_REGCFG3_ALARM_MASK, cfg);
> > + }
> > +
> > + spin_lock_irqsave(&ams->lock, flags);
> > + ams_update_intrmask(ams, AMS_ISR0_ALARM_MASK, ~alarm_mask);
> > + spin_unlock_irqrestore(&ams->lock, flags); }
> > +
> > +static void ams_enable_channel_sequence(struct ams *ams) {
> > + int i;
> > + unsigned long long scan_mask;
> > + struct iio_dev *indio_dev =3D iio_priv_to_dev(ams);
> > +
> > + /*
> > + * Enable channel sequence. First 22 bit of scan_mask represent
> > + * PS channels, and next remaining bit represents PL channels.
> > + */
> > +
> > + /* Run calibration of PS & PL as part of the sequence */
> > + scan_mask =3D 0x1 | BIT(PS_SEQ_MAX);
> > + for (i =3D 0; i < indio_dev->num_channels; i++)
> > + scan_mask |=3D BIT(indio_dev->channels[i].scan_index);
> > +
> > + if (ams->ps_base) {
> > + /* put sysmon in a soft reset to change the sequence */
> > + ams_ps_update_reg(ams, AMS_REG_CONFIG1,
> AMS_CONF1_SEQ_MASK,
> > + AMS_CONF1_SEQ_DEFAULT);
> > +
> > + /* configure basic channels */
> > + writel(scan_mask & AMS_REG_SEQ0_MASK,
> > + ams->ps_base + AMS_REG_SEQ_CH0);
> > + writel(AMS_REG_SEQ2_MASK &
> > + (scan_mask >> AMS_REG_SEQ2_MASK_SHIFT),
> > + ams->ps_base + AMS_REG_SEQ_CH2);
> > +
> > + /* set continuous sequence mode */
> > + ams_ps_update_reg(ams, AMS_REG_CONFIG1,
> AMS_CONF1_SEQ_MASK,
> > + AMS_CONF1_SEQ_CONTINUOUS);
> > + }
> > +
> > + if (ams->pl_base) {
> > + /* put sysmon in a soft reset to change the sequence */
> > + ams_pl_update_reg(ams, AMS_REG_CONFIG1,
> AMS_CONF1_SEQ_MASK,
> > + AMS_CONF1_SEQ_DEFAULT);
> > +
> > + /* configure basic channels */
> > + scan_mask =3D scan_mask >> PS_SEQ_MAX;
> > + writel(scan_mask & AMS_REG_SEQ0_MASK,
> > + ams->pl_base + AMS_REG_SEQ_CH0);
> > + writel(AMS_REG_SEQ2_MASK &
> > + (scan_mask >> AMS_REG_SEQ2_MASK_SHIFT),
> > + ams->pl_base + AMS_REG_SEQ_CH2);
> > + writel(AMS_REG_SEQ1_MASK &
> > + (scan_mask >> AMS_REG_SEQ1_MASK_SHIFT),
> > + ams->pl_base + AMS_REG_SEQ_CH1);
> > +
> > + /* set continuous sequence mode */
> > + ams_pl_update_reg(ams, AMS_REG_CONFIG1,
> AMS_CONF1_SEQ_MASK,
> > + AMS_CONF1_SEQ_CONTINUOUS);
> > + }
> > +}
> > +
> > +static int iio_ams_init_device(struct ams *ams) {
> > + u32 reg;
> > + int ret;
> > +
> > + /* reset AMS */
> > + if (ams->ps_base) {
> > + writel(AMS_PS_RESET_VALUE, ams->ps_base + AMS_VP_VN);
> > +
> > + ret =3D readl_poll_timeout(ams->base + AMS_PS_CSTS, reg,
> > + (reg & AMS_PS_CSTS_PS_READY) =3D=3D
> > + AMS_PS_CSTS_PS_READY, 0,
> > + AMS_INIT_TIMEOUT);
> > + if (ret)
> > + return ret;
> > +
> > + /* put sysmon in a default state */
> > + ams_ps_update_reg(ams, AMS_REG_CONFIG1,
> AMS_CONF1_SEQ_MASK,
> > + AMS_CONF1_SEQ_DEFAULT);
> > + }
> > +
> > + if (ams->pl_base) {
> > + writel(AMS_PL_RESET_VALUE, ams->pl_base + AMS_VP_VN);
> > +
> > + ret =3D readl_poll_timeout(ams->base + AMS_PL_CSTS, reg,
> > + (reg & AMS_PL_CSTS_ACCESS_MASK)
> =3D=3D
> > + AMS_PL_CSTS_ACCESS_MASK, 0,
> > + AMS_INIT_TIMEOUT);
> > + if (ret)
> > + return ret;
> > +
> > + /* put sysmon in a default state */
> > + ams_pl_update_reg(ams, AMS_REG_CONFIG1,
> AMS_CONF1_SEQ_MASK,
> > + AMS_CONF1_SEQ_DEFAULT);
> > + }
> > +
> > + ams_disable_all_alarms(ams);
> > +
> > + /* Disable interrupt */
> > + ams_update_intrmask(ams, ~0, ~0);
> > +
> > + /* Clear any pending interrupt */
> > + writel(AMS_ISR0_ALARM_MASK, ams->base + AMS_ISR_0);
> > + writel(AMS_ISR1_ALARM_MASK, ams->base + AMS_ISR_1);
> > +
> > + return 0;
> > +}
> > +
> > +static int ams_enable_single_channel(struct ams *ams, unsigned int
> > +offset) {
> > + u8 channel_num =3D 0;
> > +
> > + switch (offset) {
> > + case AMS_VCC_PSPLL0:
> > + channel_num =3D AMS_VCC_PSPLL0_CH;
> > + break;
> > + case AMS_VCC_PSPLL3:
> > + channel_num =3D AMS_VCC_PSPLL3_CH;
> > + break;
> > + case AMS_VCCINT:
> > + channel_num =3D AMS_VCCINT_CH;
> > + break;
> > + case AMS_VCCBRAM:
> > + channel_num =3D AMS_VCCBRAM_CH;
> > + break;
> > + case AMS_VCCAUX:
> > + channel_num =3D AMS_VCCAUX_CH;
> > + break;
> > + case AMS_PSDDRPLL:
> > + channel_num =3D AMS_PSDDRPLL_CH;
> > + break;
> > + case AMS_PSINTFPDDR:
> > + channel_num =3D AMS_PSINTFPDDR_CH;
> > + break;
> > + default:
> > + return -EINVAL;
> > + }
> > +
> > + /* set single channel, sequencer off mode */
> > + ams_ps_update_reg(ams, AMS_REG_CONFIG1,
> AMS_CONF1_SEQ_MASK,
> > + AMS_CONF1_SEQ_SINGLE_CHANNEL);
> > +
> > + /* write the channel number */
> > + ams_ps_update_reg(ams, AMS_REG_CONFIG0,
> AMS_CONF0_CHANNEL_NUM_MASK,
> > + channel_num);
> > + mdelay(1);
> This delay should be documented.. Preferably with a reference to the
> datasheet to justify the particular value.
There is an option to poll for EOC (End of Conversion) to be set in a regis=
ter. I will replace this delay will that polling.
>=20
> > +
> > + return 0;
> > +}
> > +
> > +static int ams_read_vcc_reg(struct ams *ams, unsigned int offset, u32
> > +*data) {
> > + int ret;
> > +
> > + ret =3D ams_enable_single_channel(ams, offset);
> > + if (ret)
> > + return ret;
> > +
> > + *data =3D readl(ams->base + offset);
> > + ams_enable_channel_sequence(ams);
> > +
> > + return 0;
> > +}
> > +
> > +static int ams_read_raw(struct iio_dev *indio_dev,
> > + struct iio_chan_spec const *chan,
> > + int *val, int *val2, long mask)
> > +{
> > + struct ams *ams =3D iio_priv(indio_dev);
> > + int ret;
> > +
> > + switch (mask) {
> > + case IIO_CHAN_INFO_RAW:
> > + mutex_lock(&ams->mutex);
> > + if (chan->scan_index >=3D (PS_SEQ_MAX * 3)) {
> > + ret =3D ams_read_vcc_reg(ams, chan->address, val);
> > + if (ret)
> > + goto read_raw_err;
>=20
> Missing mutex_unlock?
Missed it. This will be corrected in v2.
>=20
> > + } else if (chan->scan_index >=3D PS_SEQ_MAX)
> > + *val =3D readl(ams->pl_base + chan->address);
> > + else
> > + *val =3D readl(ams->ps_base + chan->address);
> > + mutex_unlock(&ams->mutex);
> > +
> > + return IIO_VAL_INT;
> > + case IIO_CHAN_INFO_SCALE:
> > + switch (chan->type) {
> > + case IIO_VOLTAGE:
> > + switch (chan->address) {
> > + case AMS_SUPPLY1:
> > + case AMS_SUPPLY2:
> > + case AMS_SUPPLY3:
> > + case AMS_SUPPLY4:
> > + *val =3D AMS_SUPPLY_SCALE_3VOLT;
> > + break;
> > + case AMS_SUPPLY5:
> > + case AMS_SUPPLY6:
> > + if (chan->scan_index < PS_SEQ_MAX)
> > + *val =3D AMS_SUPPLY_SCALE_6VOLT;
> > + else
> > + *val =3D AMS_SUPPLY_SCALE_3VOLT;
> > + break;
> > + case AMS_SUPPLY7:
> > + case AMS_SUPPLY8:
> > + *val =3D AMS_SUPPLY_SCALE_6VOLT;
> > + break;
> > + case AMS_SUPPLY9:
> > + case AMS_SUPPLY10:
> > + if (chan->scan_index < PS_SEQ_MAX)
> > + *val =3D AMS_SUPPLY_SCALE_3VOLT;
> > + else
> > + *val =3D AMS_SUPPLY_SCALE_6VOLT;
> > + break;
> > + case AMS_VCC_PSPLL0:
> > + case AMS_VCC_PSPLL3:
> > + case AMS_VCCINT:
> > + case AMS_VCCBRAM:
> > + case AMS_VCCAUX:
> > + case AMS_PSDDRPLL:
> > + case AMS_PSINTFPDDR:
> > + *val =3D AMS_SUPPLY_SCALE_3VOLT;
> > + break;
> > + default:
> > + *val =3D AMS_SUPPLY_SCALE_1VOLT;
> > + break;
> > + }
> > + *val2 =3D AMS_SUPPLY_SCALE_DIV_BIT;
> > + return IIO_VAL_FRACTIONAL_LOG2;
> > + case IIO_TEMP:
> > + *val =3D AMS_TEMP_SCALE;
> > + *val2 =3D AMS_TEMP_SCALE_DIV_BIT;
> > + return IIO_VAL_FRACTIONAL_LOG2;
> > + default:
> > + return -EINVAL;
> > + }
> > + case IIO_CHAN_INFO_OFFSET:
> > + /* Only the temperature channel has an offset */
> > + *val =3D AMS_TEMP_OFFSET;
> > + return IIO_VAL_INT;
> > + }
> > +
> > +read_raw_err:
>=20
> Given we only return an error code here, are we not better just doing tha=
t
> inline?
Yes. You are right. Will update this in v2.
>=20
> > + return -EINVAL;
> > +}
> > +
> > +static int ams_get_alarm_offset(int scan_index, enum
> > +iio_event_direction dir) {
> > + int offset =3D 0;
> > +
> > + if (scan_index >=3D PS_SEQ_MAX)
> > + scan_index -=3D PS_SEQ_MAX;
> > +
> > + if (dir =3D=3D IIO_EV_DIR_FALLING) {
> > + if (scan_index < AMS_SEQ_SUPPLY7)
> > + offset =3D AMS_ALARM_THRESHOLD_OFF_10;
> > + else
> > + offset =3D AMS_ALARM_THRESHOLD_OFF_20;
> > + }
> > +
> > + switch (scan_index) {
> > + case AMS_SEQ_TEMP:
> > + return AMS_ALARM_TEMP + offset;
> > + case AMS_SEQ_SUPPLY1:
> > + return AMS_ALARM_SUPPLY1 + offset;
> > + case AMS_SEQ_SUPPLY2:
> > + return AMS_ALARM_SUPPLY2 + offset;
> > + case AMS_SEQ_SUPPLY3:
> > + return AMS_ALARM_SUPPLY3 + offset;
> > + case AMS_SEQ_SUPPLY4:
> > + return AMS_ALARM_SUPPLY4 + offset;
> > + case AMS_SEQ_SUPPLY5:
> > + return AMS_ALARM_SUPPLY5 + offset;
> > + case AMS_SEQ_SUPPLY6:
> > + return AMS_ALARM_SUPPLY6 + offset;
> > + case AMS_SEQ_SUPPLY7:
> > + return AMS_ALARM_SUPPLY7 + offset;
> > + case AMS_SEQ_SUPPLY8:
> > + return AMS_ALARM_SUPPLY8 + offset;
> > + case AMS_SEQ_SUPPLY9:
> > + return AMS_ALARM_SUPPLY9 + offset;
> > + case AMS_SEQ_SUPPLY10:
> > + return AMS_ALARM_SUPPLY10 + offset;
> > + case AMS_SEQ_VCCAMS:
> > + return AMS_ALARM_VCCAMS + offset;
> > + case AMS_SEQ_TEMP_REMOTE:
> > + return AMS_ALARM_TEMP_REMOTE + offset;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static const struct iio_chan_spec *ams_event_to_channel(
> > + struct iio_dev *indio_dev, u32 event) {
> > + int scan_index =3D 0, i;
> > +
> > + if (event >=3D AMS_PL_ALARM_START) {
> > + event -=3D AMS_PL_ALARM_START;
> > + scan_index =3D PS_SEQ_MAX;
> > + }
> > +
> > + switch (event) {
> > + case AMS_ALARM_BIT_TEMP:
> > + scan_index +=3D AMS_SEQ_TEMP;
> > + break;
> > + case AMS_ALARM_BIT_SUPPLY1:
> > + scan_index +=3D AMS_SEQ_SUPPLY1;
> > + break;
> > + case AMS_ALARM_BIT_SUPPLY2:
> > + scan_index +=3D AMS_SEQ_SUPPLY2;
> > + break;
> > + case AMS_ALARM_BIT_SUPPLY3:
> > + scan_index +=3D AMS_SEQ_SUPPLY3;
> > + break;
> > + case AMS_ALARM_BIT_SUPPLY4:
> > + scan_index +=3D AMS_SEQ_SUPPLY4;
> > + break;
> > + case AMS_ALARM_BIT_SUPPLY5:
> > + scan_index +=3D AMS_SEQ_SUPPLY5;
> > + break;
> > + case AMS_ALARM_BIT_SUPPLY6:
> > + scan_index +=3D AMS_SEQ_SUPPLY6;
> > + break;
> > + case AMS_ALARM_BIT_SUPPLY7:
> > + scan_index +=3D AMS_SEQ_SUPPLY7;
> > + break;
> > + case AMS_ALARM_BIT_SUPPLY8:
> > + scan_index +=3D AMS_SEQ_SUPPLY8;
> > + break;
> > + case AMS_ALARM_BIT_SUPPLY9:
> > + scan_index +=3D AMS_SEQ_SUPPLY9;
> > + break;
> > + case AMS_ALARM_BIT_SUPPLY10:
> > + scan_index +=3D AMS_SEQ_SUPPLY10;
> > + break;
> > + case AMS_ALARM_BIT_VCCAMS:
> > + scan_index +=3D AMS_SEQ_VCCAMS;
> > + break;
> > + case AMS_ALARM_BIT_TEMP_REMOTE:
> > + scan_index +=3D AMS_SEQ_TEMP_REMOTE;
> > + break;
> > + }
> > +
> > + for (i =3D 0; i < indio_dev->num_channels; i++)
> > + if (indio_dev->channels[i].scan_index =3D=3D scan_index)
> > + break;
> > +
> > + return &indio_dev->channels[i];
> > +}
> > +
> > +static int ams_get_alarm_mask(int scan_index) {
> > + int bit =3D 0;
> > +
> > + if (scan_index >=3D PS_SEQ_MAX) {
> > + bit =3D AMS_PL_ALARM_START;
> > + scan_index -=3D PS_SEQ_MAX;
> > + }
> > +
> > + switch (scan_index) {
> > + case AMS_SEQ_TEMP:
> > + return BIT(AMS_ALARM_BIT_TEMP + bit);
> > + case AMS_SEQ_SUPPLY1:
> > + return BIT(AMS_ALARM_BIT_SUPPLY1 + bit);
> > + case AMS_SEQ_SUPPLY2:
> > + return BIT(AMS_ALARM_BIT_SUPPLY2 + bit);
> > + case AMS_SEQ_SUPPLY3:
> > + return BIT(AMS_ALARM_BIT_SUPPLY3 + bit);
> > + case AMS_SEQ_SUPPLY4:
> > + return BIT(AMS_ALARM_BIT_SUPPLY4 + bit);
> > + case AMS_SEQ_SUPPLY5:
> > + return BIT(AMS_ALARM_BIT_SUPPLY5 + bit);
> > + case AMS_SEQ_SUPPLY6:
> > + return BIT(AMS_ALARM_BIT_SUPPLY6 + bit);
> > + case AMS_SEQ_SUPPLY7:
> > + return BIT(AMS_ALARM_BIT_SUPPLY7 + bit);
> > + case AMS_SEQ_SUPPLY8:
> > + return BIT(AMS_ALARM_BIT_SUPPLY8 + bit);
> > + case AMS_SEQ_SUPPLY9:
> > + return BIT(AMS_ALARM_BIT_SUPPLY9 + bit);
> > + case AMS_SEQ_SUPPLY10:
> > + return BIT(AMS_ALARM_BIT_SUPPLY10 + bit);
> > + case AMS_SEQ_VCCAMS:
> > + return BIT(AMS_ALARM_BIT_VCCAMS + bit);
> > + case AMS_SEQ_TEMP_REMOTE:
> > + return BIT(AMS_ALARM_BIT_TEMP_REMOTE + bit);
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int ams_read_event_config(struct iio_dev *indio_dev,
> > + const struct iio_chan_spec *chan,
> > + enum iio_event_type type,
> > + enum iio_event_direction dir)
> > +{
> > + struct ams *ams =3D iio_priv(indio_dev);
> > +
> > + return (ams->alarm_mask & ams_get_alarm_mask(chan->scan_index))
> ? 1
> > +: 0; }
> > +
> > +static int ams_write_event_config(struct iio_dev *indio_dev,
> > + const struct iio_chan_spec *chan,
> > + enum iio_event_type type,
> > + enum iio_event_direction dir,
> > + int state)
> > +{
> > + struct ams *ams =3D iio_priv(indio_dev);
> > + unsigned int alarm;
> > +
> > + alarm =3D ams_get_alarm_mask(chan->scan_index);
> > +
> > + mutex_lock(&ams->mutex);
> > +
> > + if (state)
> > + ams->alarm_mask |=3D alarm;
> > + else
> > + ams->alarm_mask &=3D ~alarm;
> > +
> > + iio_ams_update_alarm(ams, ams->alarm_mask);
> > +
> > + mutex_unlock(&ams->mutex);
> > +
> > + return 0;
> > +}
> > +
> > +static int ams_read_event_value(struct iio_dev *indio_dev,
> > + const struct iio_chan_spec *chan,
> > + enum iio_event_type type,
> > + enum iio_event_direction dir,
> > + enum iio_event_info info, int *val, int *val2) {
> > + struct ams *ams =3D iio_priv(indio_dev);
> > + unsigned int offset =3D ams_get_alarm_offset(chan->scan_index, dir);
> > +
> > + mutex_lock(&ams->mutex);
> > +
> > + if (chan->scan_index >=3D PS_SEQ_MAX)
> > + *val =3D readl(ams->pl_base + offset);
> > + else
> > + *val =3D readl(ams->ps_base + offset);
> > +
> > + mutex_unlock(&ams->mutex);
> > +
> > + return IIO_VAL_INT;
> > +}
> > +
> > +static int ams_write_event_value(struct iio_dev *indio_dev,
> > + const struct iio_chan_spec *chan,
> > + enum iio_event_type type,
> > + enum iio_event_direction dir,
> > + enum iio_event_info info, int val, int val2) {
> > + struct ams *ams =3D iio_priv(indio_dev);
> > + unsigned int offset;
> > +
> > + mutex_lock(&ams->mutex);
> > +
> > + /* Set temperature channel threshold to direct threshold */
> > + if (chan->type =3D=3D IIO_TEMP) {
> > + offset =3D ams_get_alarm_offset(chan->scan_index,
> > + IIO_EV_DIR_FALLING);
> > +
> > + if (chan->scan_index >=3D PS_SEQ_MAX)
> > + ams_pl_update_reg(ams, offset,
> > + AMS_ALARM_THR_DIRECT_MASK,
> > + AMS_ALARM_THR_DIRECT_MASK);
> > + else
> > + ams_ps_update_reg(ams, offset,
> > + AMS_ALARM_THR_DIRECT_MASK,
> > + AMS_ALARM_THR_DIRECT_MASK);
> > + }
> > +
> > + offset =3D ams_get_alarm_offset(chan->scan_index, dir);
> > + if (chan->scan_index >=3D PS_SEQ_MAX)
> > + writel(val, ams->pl_base + offset);
> > + else
> > + writel(val, ams->ps_base + offset);
> > +
> > + mutex_unlock(&ams->mutex);
> > +
> > + return 0;
> > +}
> > +
> > +static void ams_handle_event(struct iio_dev *indio_dev, u32 event) {
> > + const struct iio_chan_spec *chan;
> > +
> > + chan =3D ams_event_to_channel(indio_dev, event);
> > +
> > + if (chan->type =3D=3D IIO_TEMP) {
> > + /* The temperature channel only supports over-temperature
> In IIO we use the standard kernel syntax for comments of
> /*
> * The temperature...
> */
>=20
> This form is only used in a few subsystems such as net.
>=20
> It's minor but nice to be consistent...
Okay. Will update this in v2.
>=20
> > + * events
> > + */
> > + iio_push_event(indio_dev,
> > + IIO_UNMOD_EVENT_CODE(chan->type, chan-
> >channel,
> > + IIO_EV_TYPE_THRESH,
> > + IIO_EV_DIR_RISING),
> > + iio_get_time_ns(indio_dev));
> > + } else {
> > + /* For other channels we don't know whether it is a upper or
> > + * lower threshold event. Userspace will have to check the
> > + * channel value if it wants to know.
> > + */
> > + iio_push_event(indio_dev,
> > + IIO_UNMOD_EVENT_CODE(chan->type, chan-
> >channel,
> > + IIO_EV_TYPE_THRESH,
> > + IIO_EV_DIR_EITHER),
> > + iio_get_time_ns(indio_dev));
> > + }
> > +}
> > +
> > +static void ams_handle_events(struct iio_dev *indio_dev, unsigned
> > +long events) {
> > + unsigned int bit;
> > +
> > + for_each_set_bit(bit, &events, AMS_NO_OF_ALARMS)
> > + ams_handle_event(indio_dev, bit);
> > +}
> > +
> > +/**
> > + * ams_unmask_worker - ams alarm interrupt unmask worker
> > + * @work : work to be done
> > + *
> > + * The ZynqMP threshold interrupts are level sensitive. Since we
> > +can't make the
> > + * threshold condition go way from within the interrupt handler, this
> > +means as
> > + * soon as a threshold condition is present we would enter the
> > +interrupt handler
> > + * again and again. To work around this we mask all active threshold
> > +interrupts
> > + * in the interrupt handler and start a timer. In this timer we poll
> > +the
> > + * interrupt status and only if the interrupt is inactive we unmask it=
again.
> > + */
> > +static void ams_unmask_worker(struct work_struct *work) {
> > + struct ams *ams =3D container_of(work, struct ams,
> ams_unmask_work.work);
> > + unsigned int status, unmask;
> > +
> > + spin_lock_irq(&ams->lock);
> > +
> > + status =3D readl(ams->base + AMS_ISR_0);
> > +
> > + /* Clear those bits which are not active anymore */
> > + unmask =3D (ams->masked_alarm ^ status) & ams->masked_alarm;
> > +
> > + /* clear status of disabled alarm */
> > + unmask |=3D ams->intr_mask;
> > +
> > + ams->masked_alarm &=3D status;
> > +
> > + /* Also clear those which are masked out anyway */
> > + ams->masked_alarm &=3D ~ams->intr_mask;
> > +
> > + /* Clear the interrupts before we unmask them */
> > + writel(unmask, ams->base + AMS_ISR_0);
> > +
> > + ams_update_intrmask(ams, 0, 0);
> > +
> > + spin_unlock_irq(&ams->lock);
> > +
> > + /* if still pending some alarm re-trigger the timer */
> > + if (ams->masked_alarm)
> > + schedule_delayed_work(&ams->ams_unmask_work,
> > +
> msecs_to_jiffies(AMS_UNMASK_TIMEOUT_MS));
> > +}
> > +
> > +static irqreturn_t ams_iio_irq(int irq, void *data) {
> > + unsigned int isr0, isr1;
> > + struct iio_dev *indio_dev =3D data;
> > + struct ams *ams =3D iio_priv(indio_dev);
> > +
> > + spin_lock(&ams->lock);
>=20
> Do these need to be handled in interrupt context? Would we be better off
> doing it in an interrupt thread where we can sleep (and hence not use a s=
pin
> lock for example)?
>=20
> Perhaps not, but this doesn't immediately feel time critical...
This is not time critical. I will remove spin lock here.
>=20
> > +
> > + isr0 =3D readl(ams->base + AMS_ISR_0);
> > + isr1 =3D readl(ams->base + AMS_ISR_1);
> > +
> > + /* only process alarms that are not masked */
> > + isr0 &=3D ~((ams->intr_mask & AMS_ISR0_ALARM_MASK) | ams-
> >masked_alarm);
> > + isr1 &=3D ~(ams->intr_mask >> AMS_ISR1_INTR_MASK_SHIFT);
> > +
> > + /* clear interrupt */
> > + writel(isr0, ams->base + AMS_ISR_0);
> > + writel(isr1, ams->base + AMS_ISR_1);
>=20
> If we have interrupts here that aren't ours we should a) not be clearing =
them
> b) be returning IRQ_NONE so it can be handled as an incorrect interrupt a=
nd
> reported as such.
Right. Will update this in v2.
>=20
> > +
> > + if (isr0) {
> > + /* Once the alarm interrupt occurred, mask until get cleared
> */
> > + ams->masked_alarm |=3D isr0;
> > + ams_update_intrmask(ams, 0, 0);
> > +
> > + ams_handle_events(indio_dev, isr0);
> > +
> > + schedule_delayed_work(&ams->ams_unmask_work,
> > +
> msecs_to_jiffies(AMS_UNMASK_TIMEOUT_MS));
> > + }
> > +
> > + spin_unlock(&ams->lock);
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +static const struct iio_event_spec ams_temp_events[] =3D {
> > + {
> > + .type =3D IIO_EV_TYPE_THRESH,
> > + .dir =3D IIO_EV_DIR_RISING,
> > + .mask_separate =3D BIT(IIO_EV_INFO_ENABLE) |
> > + BIT(IIO_EV_INFO_VALUE),
> > + },
> > +};
> > +
> > +static const struct iio_event_spec ams_voltage_events[] =3D {
> > + {
> > + .type =3D IIO_EV_TYPE_THRESH,
> > + .dir =3D IIO_EV_DIR_RISING,
> > + .mask_separate =3D BIT(IIO_EV_INFO_VALUE),
> > + }, {
> > + .type =3D IIO_EV_TYPE_THRESH,
> > + .dir =3D IIO_EV_DIR_FALLING,
> > + .mask_separate =3D BIT(IIO_EV_INFO_VALUE),
> > + }, {
> > + .type =3D IIO_EV_TYPE_THRESH,
> > + .dir =3D IIO_EV_DIR_EITHER,
> > + .mask_separate =3D BIT(IIO_EV_INFO_ENABLE),
> > + },
> > +};
> > +
> > +static const struct iio_chan_spec ams_ps_channels[] =3D {
> > + AMS_PS_CHAN_TEMP(AMS_SEQ_TEMP, AMS_TEMP, "ps_temp"),
> > + AMS_PS_CHAN_TEMP(AMS_SEQ_TEMP_REMOTE,
> AMS_TEMP_REMOTE, "remote_temp"),
> > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY1, AMS_SUPPLY1,
> "vccpsintlp"),
> > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY2, AMS_SUPPLY2,
> "vccpsintfp"),
> > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY3, AMS_SUPPLY3,
> "vccpsaux"),
> > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY4, AMS_SUPPLY4,
> "vccpsddr"),
> > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY5, AMS_SUPPLY5,
> "vccpsio3"),
> > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY6, AMS_SUPPLY6,
> "vccpsio0"),
> > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY7, AMS_SUPPLY7,
> "vccpsio1"),
> > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY8, AMS_SUPPLY8,
> "vccpsio2"),
> > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY9, AMS_SUPPLY9,
> "psmgtravcc"),
> > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY10, AMS_SUPPLY10,
> "psmgtravtt"),
> > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_VCCAMS, AMS_VCCAMS,
> "vccams"), };
> > +
> > +static const struct iio_chan_spec ams_pl_channels[] =3D {
> > + AMS_PL_CHAN_TEMP(AMS_SEQ_TEMP, AMS_TEMP, "pl_temp"),
> > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY1, AMS_SUPPLY1, "vccint",
> true),
> > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY2, AMS_SUPPLY2,
> "vccaux", true),
> > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VREFP, AMS_VREFP, "vccvrefp",
> false),
> > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VREFN, AMS_VREFN, "vccvrefn",
> false),
> > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY3, AMS_SUPPLY3,
> "vccbram", true),
> > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY4, AMS_SUPPLY4,
> "vccplintlp", true),
> > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY5, AMS_SUPPLY5,
> "vccplintfp", true),
> > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY6, AMS_SUPPLY6,
> "vccplaux", true),
> > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VCCAMS, AMS_VCCAMS,
> "vccams", true),
> > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VP_VN, AMS_VP_VN, "vccvpvn",
> false),
> > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY7, AMS_SUPPLY7,
> "vuser0", true),
> > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY8, AMS_SUPPLY8,
> "vuser1", true),
> > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY9, AMS_SUPPLY9,
> "vuser2", true),
> > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY10, AMS_SUPPLY10,
> "vuser3", true),
> > + AMS_PL_AUX_CHAN_VOLTAGE(0, "vccaux0"),
>=20
> Extended_name is a two edged sword. It lets you make nice specifically n=
amed
> channels, but it also breaks almost all userspace code as it doesn't know=
how to
> handle the extra bit on the end of the name.
>=20
> As such we tend to only specify it for very special use non standard chan=
nels
> (such as the power supplies)
>=20
> For the aux channels, it has no particular meaning. So I would prefer tha=
t you
> dropped it and just left them with the indexes.
Okay. I will keep the extended names for AMS_SUPPLY* and remove them for th=
e aux channels.
>=20
> > + AMS_PL_AUX_CHAN_VOLTAGE(1, "vccaux1"),
> > + AMS_PL_AUX_CHAN_VOLTAGE(2, "vccaux2"),
> > + AMS_PL_AUX_CHAN_VOLTAGE(3, "vccaux3"),
> > + AMS_PL_AUX_CHAN_VOLTAGE(4, "vccaux4"),
> > + AMS_PL_AUX_CHAN_VOLTAGE(5, "vccaux5"),
> > + AMS_PL_AUX_CHAN_VOLTAGE(6, "vccaux6"),
> > + AMS_PL_AUX_CHAN_VOLTAGE(7, "vccaux7"),
> > + AMS_PL_AUX_CHAN_VOLTAGE(8, "vccaux8"),
> > + AMS_PL_AUX_CHAN_VOLTAGE(9, "vccaux9"),
> > + AMS_PL_AUX_CHAN_VOLTAGE(10, "vccaux10"),
> > + AMS_PL_AUX_CHAN_VOLTAGE(11, "vccaux11"),
> > + AMS_PL_AUX_CHAN_VOLTAGE(12, "vccaux12"),
> > + AMS_PL_AUX_CHAN_VOLTAGE(13, "vccaux13"),
> > + AMS_PL_AUX_CHAN_VOLTAGE(14, "vccaux14"),
> > + AMS_PL_AUX_CHAN_VOLTAGE(15, "vccaux15"), };
> > +
> > +static const struct iio_chan_spec ams_ctrl_channels[] =3D {
> > + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCC_PSPLL, AMS_VCC_PSPLL0,
> "vcc_pspll0"),
> > + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCC_PSBATT,
> AMS_VCC_PSPLL3, "vcc_psbatt"),
> > + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCCINT, AMS_VCCINT,
> "vccint"),
> > + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCCBRAM, AMS_VCCBRAM,
> "vccbram"),
> > + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCCAUX, AMS_VCCAUX,
> "vccaux"),
> > + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_PSDDRPLL, AMS_PSDDRPLL,
> "psddrpll"),
> > + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_INTDDR, AMS_PSINTFPDDR,
> "psintfpddr"),
> > +};
> > +
> > +static int ams_init_module(struct iio_dev *indio_dev, struct device_no=
de
> *np,
> > + struct iio_chan_spec *channels) {
> > + struct ams *ams =3D iio_priv(indio_dev);
> > + struct device_node *chan_node, *child;
> > + int ret, num_channels =3D 0;
> > + unsigned int reg;
> > +
> > + if (of_device_is_compatible(np, "xlnx,zynqmp-ams-ps")) {
> > + ams->ps_base =3D of_iomap(np, 0);
> > + if (!ams->ps_base)
> > + return -ENXIO;
> > +
> > + /* add PS channels to iio device channels */
> > + memcpy(channels + num_channels, ams_ps_channels,
> > + sizeof(ams_ps_channels));
> > + num_channels +=3D ARRAY_SIZE(ams_ps_channels);
> > + } else if (of_device_is_compatible(np, "xlnx,zynqmp-ams-pl")) {
> > + ams->pl_base =3D of_iomap(np, 0);
> > + if (!ams->pl_base)
> > + return -ENXIO;
> > +
> > + /* Copy only first 10 fix channels */
> > + memcpy(channels + num_channels, ams_pl_channels,
> > + AMS_PL_MAX_FIXED_CHANNEL * sizeof(*channels));
> > + num_channels +=3D AMS_PL_MAX_FIXED_CHANNEL;
> > +
> > + chan_node =3D of_get_child_by_name(np, "xlnx,ext-channels");
> > + if (chan_node) {
> > + for_each_child_of_node(chan_node, child) {
> > + ret =3D of_property_read_u32(child, "reg",
> ®);
> > + if (ret || reg > AMS_PL_MAX_EXT_CHANNEL)
> > + continue;
> > +
> > + memcpy(&channels[num_channels],
> > + &ams_pl_channels[reg +
> > + AMS_PL_MAX_FIXED_CHANNEL],
> > + sizeof(*channels));
> > +
> > + if (of_property_read_bool(child,
> > + "xlnx,bipolar"))
> > +
> channels[num_channels].scan_type.sign =3D
> > + 's';
> > +
> > + num_channels +=3D 1;
>=20
> num_channels++;
Okay.
>=20
> > + }
> > + }
> > + of_node_put(chan_node);
> > + } else if (of_device_is_compatible(np, "xlnx,zynqmp-ams")) {
> > + /* add AMS channels to iio device channels */
> > + memcpy(channels + num_channels, ams_ctrl_channels,
> > + sizeof(ams_ctrl_channels));
> > + num_channels +=3D ARRAY_SIZE(ams_ctrl_channels);
> > + } else {
> > + return -EINVAL;
> > + }
> > +
> > + return num_channels;
> > +}
> > +
> > +static int ams_parse_dt(struct iio_dev *indio_dev, struct
> > +platform_device *pdev) {
> > + struct ams *ams =3D iio_priv(indio_dev);
> > + struct iio_chan_spec *ams_channels, *dev_channels;
> > + struct device_node *child_node =3D NULL, *np =3D pdev->dev.of_node;
> > + int ret, chan_vol =3D 0, chan_temp =3D 0, i, rising_off, falling_off;
>=20
> chan_vol and chan_temp are counts? Perhaps their names can make that
> clearer.
Okay. I will change their name to 'vol_ch_cnt' and 'temp_ch_cnt' in v2.
>=20
> > + unsigned int num_channels =3D 0;
> > +
> > + /* Initialize buffer for channel specification */
> > + ams_channels =3D kzalloc(sizeof(ams_ps_channels) +
> > + sizeof(ams_pl_channels) +
> > + sizeof(ams_ctrl_channels), GFP_KERNEL);
> > + if (!ams_channels)
> > + return -ENOMEM;
> > +
> > + if (of_device_is_available(np)) {
> > + ret =3D ams_init_module(indio_dev, np, ams_channels);
> > + if (ret < 0) {
> > + kfree(ams_channels);
> > + return ret;
> > + }
> > +
> > + num_channels +=3D ret;
> > + }
> > +
> > + for_each_child_of_node(np, child_node) {
> > + if (of_device_is_available(child_node)) {
> > + ret =3D ams_init_module(indio_dev, child_node,
> > + ams_channels + num_channels);
> > + if (ret < 0) {
> > + kfree(ams_channels);
> > + return ret;
> > + }
> > +
> > + num_channels +=3D ret;
> > + }
> > + }
> > +
> > + for (i =3D 0; i < num_channels; i++) {
> > + if (ams_channels[i].type =3D=3D IIO_VOLTAGE)
> > + ams_channels[i].channel =3D chan_vol++;
> > + else
> > + ams_channels[i].channel =3D chan_temp++;
> > +
> > + if (ams_channels[i].scan_index < (PS_SEQ_MAX * 3)) {
> > + /* set threshold to max and min for each channel */
> > + falling_off =3D ams_get_alarm_offset(
> > + ams_channels[i].scan_index,
> > + IIO_EV_DIR_FALLING);
> > + rising_off =3D ams_get_alarm_offset(
> > + ams_channels[i].scan_index,
> > + IIO_EV_DIR_RISING);
> > + if (ams_channels[i].scan_index >=3D PS_SEQ_MAX) {
> > + writel(AMS_ALARM_THR_MIN,
> > + ams->pl_base + falling_off);
> > + writel(AMS_ALARM_THR_MAX,
> > + ams->pl_base + rising_off);
> > + } else {
> > + writel(AMS_ALARM_THR_MIN,
> > + ams->ps_base + falling_off);
> > + writel(AMS_ALARM_THR_MAX,
> > + ams->ps_base + rising_off);
> > + }
> > + }
> > + }
> > +
> > + dev_channels =3D devm_kzalloc(&pdev->dev, sizeof(*dev_channels) *
> > + num_channels, GFP_KERNEL);
> > + if (!dev_channels) {
> > + kfree(ams_channels);
> > + return -ENOMEM;
> > + }
> > +
> > + memcpy(dev_channels, ams_channels,
> > + sizeof(*ams_channels) * num_channels);
> > + kfree(ams_channels);
> If you were to reorder this so the ams_channels cleanup was last you coul=
d use
> the nice clean pattern of
>=20
> error:
> kfree(ams_channels);
>=20
> return ret;
>=20
> To make it clear that the ams_channels gets freed in all paths.
Fair enough. I will update this in v2.
>=20
> > + indio_dev->channels =3D dev_channels;
> > + indio_dev->num_channels =3D num_channels;
> > +
> > + return 0;
> > +}
> > +
> > +static const struct iio_info iio_pl_info =3D {
> > + .read_raw =3D &ams_read_raw,
> > + .read_event_config =3D &ams_read_event_config,
> > + .write_event_config =3D &ams_write_event_config,
> > + .read_event_value =3D &ams_read_event_value,
> > + .write_event_value =3D &ams_write_event_value, };
> > +
> > +static const struct of_device_id ams_of_match_table[] =3D {
> > + { .compatible =3D "xlnx,zynqmp-ams" },
> > + { }
> > +};
> > +MODULE_DEVICE_TABLE(of, ams_of_match_table);
> > +
> > +static int ams_probe(struct platform_device *pdev) {
> > + struct iio_dev *indio_dev;
> > + struct ams *ams;
> > + struct resource *res;
> > + int ret;
> > +
> > + if (!pdev->dev.of_node)
> > + return -ENODEV;
> > +
> > + indio_dev =3D devm_iio_device_alloc(&pdev->dev, sizeof(*ams));
> > + if (!indio_dev)
> > + return -ENOMEM;
> > +
> > + ams =3D iio_priv(indio_dev);
> > + mutex_init(&ams->mutex);
> > + spin_lock_init(&ams->lock);
> > +
> > + indio_dev->dev.parent =3D &pdev->dev;
> > + indio_dev->dev.of_node =3D pdev->dev.of_node;
> > + indio_dev->name =3D "ams";
> As names go, that one is a too little generic. Particularly with a commo=
n
> manufacturer of ADCs called Austrian Micro Systems ;) No possibility of
> confusion!
>=20
> It's fine to use ams within the driver, but for external interfaces, perh=
aps prefix
> with xilinx-?
Okay. I will use xilinx-ams.
>=20
> > +
> > + indio_dev->info =3D &iio_pl_info;
> > + indio_dev->modes =3D INDIO_DIRECT_MODE;
> > +
> > + res =3D platform_get_resource_byname(pdev, IORESOURCE_MEM,
> "ams-base");
> > + ams->base =3D devm_ioremap_resource(&pdev->dev, res);
> > + if (IS_ERR(ams->base))
> > + return PTR_ERR(ams->base);
> > +
> > + INIT_DELAYED_WORK(&ams->ams_unmask_work,
> ams_unmask_worker);
> > +
> > + ams->clk =3D devm_clk_get(&pdev->dev, NULL);
> > + if (IS_ERR(ams->clk))
> > + return PTR_ERR(ams->clk);
> > + clk_prepare_enable(ams->clk);
> > +
> > + ret =3D iio_ams_init_device(ams);
> > + if (ret) {
> > + dev_err(&pdev->dev, "failed to initialize AMS\n");
> > + goto clk_disable;
> > + }
> > +
> > + ret =3D ams_parse_dt(indio_dev, pdev);
> > + if (ret) {
> > + dev_err(&pdev->dev, "failure in parsing DT\n");
> > + goto clk_disable;
> > + }
> > +
> > + ams_enable_channel_sequence(ams);
> > +
> > + ams->irq =3D platform_get_irq_byname(pdev, "ams-irq");
>=20
> Why store this in the ams structure? It's not used anywhere other than i=
n the
> next line.
Okay. Will update this in v2.
>=20
> > + ret =3D devm_request_irq(&pdev->dev, ams->irq, &ams_iio_irq, 0, "ams-
> irq",
> > + indio_dev);
> This mixing of devm and non devm functions makes it tricky to be sure the=
re
> aren't an races.
>=20
> One easy solution is to use devm_add_action to put the clk_disable_unprep=
are
> into the cleanup list.
Okay.
>=20
> > + if (ret < 0) {
> > + dev_err(&pdev->dev, "failed to register interrupt\n");
> > + goto clk_disable;
> > + }
> > +
> > + platform_set_drvdata(pdev, indio_dev);
> > +
> > + return iio_device_register(indio_dev);
>=20
> What if device register returns an error? You leave the clock enabled.
>=20
> That way everything is using the managed cleanup and the ordering will be
> correct.
>=20
> The alternative is to not use devm forms for everything after the
> clk_prepare_enable.
Yes. This is better.
>=20
> > +
> > +clk_disable:
> > + clk_disable_unprepare(ams->clk);
> > +
> > + return ret;
> > +}
> > +
> > +static int ams_remove(struct platform_device *pdev) {
> > + struct iio_dev *indio_dev =3D platform_get_drvdata(pdev);
> > + struct ams *ams =3D iio_priv(indio_dev);
> > +
> > + cancel_delayed_work(&ams->ams_unmask_work);
> > +
> > + /* Unregister the device */
> > + iio_device_unregister(indio_dev);
> > + clk_disable_unprepare(ams->clk);
> > +
> > + return 0;
> > +}
> > +
> > +static int __maybe_unused ams_suspend(struct device *dev) {
> > + struct iio_dev *indio_dev =3D dev_get_drvdata(dev);
> > + struct ams *ams =3D iio_priv(indio_dev);
> > +
> > + clk_disable_unprepare(ams->clk);
> > +
> > + return 0;
> > +}
> > +
> > +static int __maybe_unused ams_resume(struct device *dev) {
> > + struct iio_dev *indio_dev =3D dev_get_drvdata(dev);
> > + struct ams *ams =3D iio_priv(indio_dev);
> You could streamline this without loss of clarity as.
>=20
> struct ams *ams =3D iio_priv(dev_get_drvdata(dev));
>=20
> (very minor point!)
Okay.
> > +
> > + clk_prepare_enable(ams->clk);
> > +
> > + return 0;
> > +}
> > +
> > +static SIMPLE_DEV_PM_OPS(ams_pm_ops, ams_suspend, ams_resume);
> > +
> > +static struct platform_driver ams_driver =3D {
> > + .probe =3D ams_probe,
> > + .remove =3D ams_remove,
> > + .driver =3D {
> > + .name =3D "ams",
> > + .pm =3D &ams_pm_ops,
>=20
> Spacing here seems a little odd. Just have a single space after pm
Okay.
>=20
> > + .of_match_table =3D ams_of_match_table,
> > + },
> > +};
> > +module_platform_driver(ams_driver);
> > +
> > +MODULE_LICENSE("GPL");
> > +MODULE_AUTHOR("Xilinx, Inc.");
> > diff --git a/drivers/iio/adc/xilinx-ams.h
> > b/drivers/iio/adc/xilinx-ams.h new file mode 100644 index
> > 0000000..b9fa262
> > --- /dev/null
> > +++ b/drivers/iio/adc/xilinx-ams.h
>=20
> I can't immediately see why a lot of this wants to be in a header.
>=20
> Please move it inline with the C file.
Okay. I will update that in v2.
>=20
>=20
> > @@ -0,0 +1,281 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Xilinx AMS driver
> > + *
> > + * Copyright (C) 2017-2018 Xilinx, Inc.
> > + *
> > + * Manish Narani <mnarani@xilinx.com>
> > + * Rajnikant Bhojani <rajnikant.bhojani@xilinx.com> */
> > +
> > +#ifndef __XILINX_AMS_H__
> > +#define __XILINX_AMS_H__
> > +
> > +#define AMS_MISC_CTRL 0x000
> > +#define AMS_ISR_0 0x010
> > +#define AMS_ISR_1 0x014
> > +#define AMS_IMR_0 0x018
> > +#define AMS_IMR_1 0x01c
> > +#define AMS_IER_0 0x020
> > +#define AMS_IER_1 0x024
> > +#define AMS_IDR_0 0x028
> > +#define AMS_IDR_1 0x02c
> > +#define AMS_PS_CSTS 0x040
> > +#define AMS_PL_CSTS 0x044
> > +#define AMS_MON_CSTS 0x050
> > +
> > +#define AMS_VCC_PSPLL0 0x060
> > +#define AMS_VCC_PSPLL3 0x06C
> > +#define AMS_VCCINT 0x078
> > +#define AMS_VCCBRAM 0x07C
> > +#define AMS_VCCAUX 0x080
> > +#define AMS_PSDDRPLL 0x084
> > +#define AMS_PSINTFPDDR 0x09C
> > +
> > +#define AMS_VCC_PSPLL0_CH 48
> > +#define AMS_VCC_PSPLL3_CH 51
> > +#define AMS_VCCINT_CH 54
> > +#define AMS_VCCBRAM_CH 55
> > +#define AMS_VCCAUX_CH 56
> > +#define AMS_PSDDRPLL_CH 57
> > +#define AMS_PSINTFPDDR_CH 63
> > +
> > +#define AMS_REG_CONFIG0 0x100
> > +#define AMS_REG_CONFIG1 0x104
> > +#define AMS_REG_CONFIG2 0x108
> > +#define AMS_REG_CONFIG3 0x10C
> > +#define AMS_REG_CONFIG4 0x110
> > +#define AMS_REG_SEQ_CH0 0x120
> > +#define AMS_REG_SEQ_CH1 0x124
> > +#define AMS_REG_SEQ_CH2 0x118
> > +
> > +#define AMS_TEMP 0x000
> > +#define AMS_SUPPLY1 0x004
> > +#define AMS_SUPPLY2 0x008
> > +#define AMS_VP_VN 0x00c
> > +#define AMS_VREFP 0x010
> > +#define AMS_VREFN 0x014
> > +#define AMS_SUPPLY3 0x018
> > +#define AMS_SUPPLY4 0x034
> > +#define AMS_SUPPLY5 0x038
> > +#define AMS_SUPPLY6 0x03c
> > +#define AMS_SUPPLY7 0x200
> > +#define AMS_SUPPLY8 0x204
> > +#define AMS_SUPPLY9 0x208
> > +#define AMS_SUPPLY10 0x20c
> > +#define AMS_VCCAMS 0x210
> > +#define AMS_TEMP_REMOTE 0x214
> > +
> > +#define AMS_REG_VAUX(x) (0x40 + (4*(x)))
> > +#define AMS_REG_VUSER(x) (0x200 + (4*(x)))
> > +
> > +#define AMS_PS_RESET_VALUE 0xFFFFU
> > +#define AMS_PL_RESET_VALUE 0xFFFFU
> > +
> > +#define AMS_CONF0_CHANNEL_NUM_MASK (0x3f << 0)
> > +
> > +#define AMS_CONF1_SEQ_MASK (0xf << 12)
> > +#define AMS_CONF1_SEQ_DEFAULT (0 << 12)
> > +#define AMS_CONF1_SEQ_SINGLE_PASS (1 << 12)
> > +#define AMS_CONF1_SEQ_CONTINUOUS (2 << 12)
> > +#define AMS_CONF1_SEQ_SINGLE_CHANNEL (3 << 12)
> > +
> > +#define AMS_REG_SEQ0_MASK 0xFFFF
> > +#define AMS_REG_SEQ2_MASK 0x3F
> > +#define AMS_REG_SEQ1_MASK 0xFFFF
> > +#define AMS_REG_SEQ2_MASK_SHIFT 16
> > +#define AMS_REG_SEQ1_MASK_SHIFT 22
> > +
> > +#define AMS_REGCFG1_ALARM_MASK 0xF0F
> > +#define AMS_REGCFG3_ALARM_MASK 0x3F
> > +
> > +#define AMS_ALARM_TEMP 0x140
> > +#define AMS_ALARM_SUPPLY1 0x144
> > +#define AMS_ALARM_SUPPLY2 0x148
> > +#define AMS_ALARM_OT 0x14c
> > +
> > +#define AMS_ALARM_SUPPLY3 0x160
> > +#define AMS_ALARM_SUPPLY4 0x164
> > +#define AMS_ALARM_SUPPLY5 0x168
> > +#define AMS_ALARM_SUPPLY6 0x16c
> > +#define AMS_ALARM_SUPPLY7 0x180
> > +#define AMS_ALARM_SUPPLY8 0x184
> > +#define AMS_ALARM_SUPPLY9 0x188
> > +#define AMS_ALARM_SUPPLY10 0x18c
> > +#define AMS_ALARM_VCCAMS 0x190
> > +#define AMS_ALARM_TEMP_REMOTE 0x194
> > +#define AMS_ALARM_THRESHOLD_OFF_10 0x10 #define
> > +AMS_ALARM_THRESHOLD_OFF_20 0x20
> > +
> > +#define AMS_ALARM_THR_DIRECT_MASK 0x01
> > +#define AMS_ALARM_THR_MIN 0x0000
> > +#define AMS_ALARM_THR_MAX 0xffff
> > +
> > +#define AMS_NO_OF_ALARMS 32
> > +#define AMS_PL_ALARM_START 16
> > +#define AMS_ISR0_ALARM_MASK 0xFFFFFFFFU
> > +#define AMS_ISR1_ALARM_MASK 0xE000001FU
> > +#define AMS_ISR1_INTR_MASK_SHIFT 32
> > +#define AMS_ISR0_ALARM_2_TO_0_MASK 0x07
> > +#define AMS_ISR0_ALARM_6_TO_3_MASK 0x78
> > +#define AMS_ISR0_ALARM_12_TO_7_MASK 0x3F
> > +#define AMS_CONF1_ALARM_2_TO_0_SHIFT 1
> > +#define AMS_CONF1_ALARM_6_TO_3_SHIFT 5
> > +#define AMS_CONF3_ALARM_12_TO_7_SHIFT 8
> > +
> > +#define AMS_PS_CSTS_PS_READY 0x08010000U
> > +#define AMS_PL_CSTS_ACCESS_MASK 0x00000001U
> > +
> > +#define AMS_PL_MAX_FIXED_CHANNEL 10
> > +#define AMS_PL_MAX_EXT_CHANNEL 20
> > +
> > +#define AMS_INIT_TIMEOUT 10000
> > +
> > +/*
> > + * Following scale and offset value is derived from
> > + * UG580 (v1.7) December 20, 2016
> > + */
> > +#define AMS_SUPPLY_SCALE_1VOLT 1000
> > +#define AMS_SUPPLY_SCALE_3VOLT 3000
> > +#define AMS_SUPPLY_SCALE_6VOLT 6000
> > +#define AMS_SUPPLY_SCALE_DIV_BIT 16
> > +
> > +#define AMS_TEMP_SCALE 509314
> > +#define AMS_TEMP_SCALE_DIV_BIT 16
> > +#define AMS_TEMP_OFFSET -((280230L << 16) / 509314)
> > +
> > +enum ams_alarm_bit {
> > + AMS_ALARM_BIT_TEMP,
> > + AMS_ALARM_BIT_SUPPLY1,
> > + AMS_ALARM_BIT_SUPPLY2,
> > + AMS_ALARM_BIT_SUPPLY3,
> > + AMS_ALARM_BIT_SUPPLY4,
> > + AMS_ALARM_BIT_SUPPLY5,
> > + AMS_ALARM_BIT_SUPPLY6,
> > + AMS_ALARM_BIT_RESERVED,
> > + AMS_ALARM_BIT_SUPPLY7,
> > + AMS_ALARM_BIT_SUPPLY8,
> > + AMS_ALARM_BIT_SUPPLY9,
> > + AMS_ALARM_BIT_SUPPLY10,
> > + AMS_ALARM_BIT_VCCAMS,
> > + AMS_ALARM_BIT_TEMP_REMOTE
> > +};
> > +
> > +enum ams_seq {
> > + AMS_SEQ_VCC_PSPLL,
> > + AMS_SEQ_VCC_PSBATT,
> > + AMS_SEQ_VCCINT,
> > + AMS_SEQ_VCCBRAM,
> > + AMS_SEQ_VCCAUX,
> > + AMS_SEQ_PSDDRPLL,
> > + AMS_SEQ_INTDDR
> > +};
> > +
> > +enum ams_ps_pl_seq {
> > + AMS_SEQ_CALIB,
> > + AMS_SEQ_RSVD_1,
> > + AMS_SEQ_RSVD_2,
> > + AMS_SEQ_TEST,
> > + AMS_SEQ_RSVD_4,
> > + AMS_SEQ_SUPPLY4,
> > + AMS_SEQ_SUPPLY5,
> > + AMS_SEQ_SUPPLY6,
> > + AMS_SEQ_TEMP,
> > + AMS_SEQ_SUPPLY2,
> > + AMS_SEQ_SUPPLY1,
> > + AMS_SEQ_VP_VN,
> > + AMS_SEQ_VREFP,
> > + AMS_SEQ_VREFN,
> > + AMS_SEQ_SUPPLY3,
> > + AMS_SEQ_CURRENT_MON,
> > + AMS_SEQ_SUPPLY7,
> > + AMS_SEQ_SUPPLY8,
> > + AMS_SEQ_SUPPLY9,
> > + AMS_SEQ_SUPPLY10,
> > + AMS_SEQ_VCCAMS,
> > + AMS_SEQ_TEMP_REMOTE,
> > + AMS_SEQ_MAX
> > +};
> > +
> > +#define AMS_SEQ(x) (AMS_SEQ_MAX + (x))
> > +#define AMS_VAUX_SEQ(x) (AMS_SEQ_MAX + (x))
> > +
> > +#define PS_SEQ_MAX AMS_SEQ_MAX
> > +#define PS_SEQ(x) (x)
> > +#define PL_SEQ(x) (PS_SEQ_MAX + x)
> > +
> > +#define AMS_CHAN_TEMP(_scan_index, _addr, _ext) { \
> > + .type =3D IIO_TEMP, \
> > + .indexed =3D 1, \
> > + .address =3D (_addr), \
> > + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) | \
> > + BIT(IIO_CHAN_INFO_SCALE) | \
> > + BIT(IIO_CHAN_INFO_OFFSET), \
> > + .info_mask_shared_by_all =3D BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> > + .event_spec =3D ams_temp_events, \
> > + .num_event_specs =3D ARRAY_SIZE(ams_temp_events), \
> > + .scan_index =3D (_scan_index), \
> > + .scan_type =3D { \
> > + .sign =3D 'u', \
> > + .realbits =3D 12, \
> > + .storagebits =3D 16, \
> > + .shift =3D 4, \
> > + .endianness =3D IIO_CPU, \
> > + }, \
> > + .extend_name =3D _ext, \
> > +}
> > +
> > +#define AMS_CHAN_VOLTAGE(_scan_index, _addr, _ext, _alarm) { \
> > + .type =3D IIO_VOLTAGE, \
> > + .indexed =3D 1, \
> > + .address =3D (_addr), \
> > + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) | \
> > + BIT(IIO_CHAN_INFO_SCALE), \
> > + .info_mask_shared_by_all =3D BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> > + .event_spec =3D (_alarm) ? ams_voltage_events : NULL, \
> > + .num_event_specs =3D (_alarm) ? ARRAY_SIZE(ams_voltage_events) : 0, \
> > + .scan_index =3D (_scan_index), \
> > + .scan_type =3D { \
> > + .realbits =3D 10, \
> > + .storagebits =3D 16, \
> > + .shift =3D 6, \
> > + .endianness =3D IIO_CPU, \
> > + }, \
> > + .extend_name =3D _ext, \
> > +}
> > +
> > +#define AMS_PS_CHAN_TEMP(_scan_index, _addr, _ext) \
> > + AMS_CHAN_TEMP(PS_SEQ(_scan_index), _addr, _ext) #define
> > +AMS_PS_CHAN_VOLTAGE(_scan_index, _addr, _ext) \
> > + AMS_CHAN_VOLTAGE(PS_SEQ(_scan_index), _addr, _ext, true)
> > +
> > +#define AMS_PL_CHAN_TEMP(_scan_index, _addr, _ext) \
> > + AMS_CHAN_TEMP(PL_SEQ(_scan_index), _addr, _ext) #define
> > +AMS_PL_CHAN_VOLTAGE(_scan_index, _addr, _ext, _alarm) \
> > + AMS_CHAN_VOLTAGE(PL_SEQ(_scan_index), _addr, _ext, _alarm)
> #define
> > +AMS_PL_AUX_CHAN_VOLTAGE(_auxno, _ext) \
> > + AMS_CHAN_VOLTAGE(PL_SEQ(AMS_VAUX_SEQ(_auxno)), \
> > + AMS_REG_VAUX(_auxno), _ext, false) #define
> > +AMS_CTRL_CHAN_VOLTAGE(_scan_index, _addr, _ext) \
> > +
> AMS_CHAN_VOLTAGE(PL_SEQ(AMS_VAUX_SEQ(AMS_SEQ(_scan_inde
> x))), \
> > + _addr, _ext, false)
> > +
> > +struct ams {
> > + void __iomem *base;
> > + void __iomem *ps_base;
> > + void __iomem *pl_base;
> > + struct clk *clk;
> > + struct device *dev;
> > +
> > + struct mutex mutex;
> Pleases give clear comments on what these locks are protecting.
> It's certainly curious to see a mutex and a spinlock with such generic na=
mes
> right next to each other.
Okay. I will update with the comments in v2.
>=20
> > + spinlock_t lock;
> > +
> > + unsigned int alarm_mask;
> > + unsigned int masked_alarm;
> > + u64 intr_mask;
> > + int irq;
> As noted above, this is only used next to where it is first set.
> No advantage in having it here.
I will remove this in v2.
>=20
> > +
> > + struct delayed_work ams_unmask_work; };
> > +
> > +#endif /* __XILINX_AMS_H__ */
Thanks,
Manish Narani
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 1/3] dt-bindings: iio: adc: Add Xilinx AMS binding documentation
2018-09-06 13:27 ` Manish Narani
@ 2018-09-08 13:42 ` Jonathan Cameron
0 siblings, 0 replies; 13+ messages in thread
From: Jonathan Cameron @ 2018-09-08 13:42 UTC (permalink / raw)
To: Manish Narani
Cc: knaack.h@gmx.de, lars@metafoo.de, pmeerw@pmeerw.net,
robh+dt@kernel.org, mark.rutland@arm.com, Michal Simek,
leoyang.li@nxp.com, sudeep.holla@arm.com,
amit.kucheria@linaro.org, broonie@kernel.org,
arnaud.pouliquen@st.com, geert@linux-m68k.org,
eugen.hristev@microchip.com, rdunlap@infradead.org,
lukas@wunner.de, freeman.liu@spreadtrum.com,
vilhelm.gray@gmail.com, tglx@linutronix.de,
baolin.wang@linaro.org, gregkh@linuxfoundation.org, Srinivas Goud,
Anirudha Sarangi, linux-iio@vger.kernel.org,
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org
On Thu, 6 Sep 2018 13:27:34 +0000
Manish Narani <MNARANI@xilinx.com> wrote:
> Hi Jonathan,
>
> Thanks for the review!
>
> > -----Original Message-----
> > From: Jonathan Cameron [mailto:jic23@kernel.org]
> > Sent: Sunday, September 2, 2018 11:45 PM
> > To: Manish Narani <MNARANI@xilinx.com>
> > Cc: knaack.h@gmx.de; lars@metafoo.de; pmeerw@pmeerw.net;
> > robh+dt@kernel.org; mark.rutland@arm.com; Michal Simek
> > <michals@xilinx.com>; leoyang.li@nxp.com; sudeep.holla@arm.com;
> > amit.kucheria@linaro.org; broonie@kernel.org; arnaud.pouliquen@st.com;
> > geert@linux-m68k.org; eugen.hristev@microchip.com; rdunlap@infradead.org;
> > lukas@wunner.de; freeman.liu@spreadtrum.com; vilhelm.gray@gmail.com;
> > tglx@linutronix.de; baolin.wang@linaro.org; gregkh@linuxfoundation.org;
> > Srinivas Goud <sgoud@xilinx.com>; Anirudha Sarangi <anirudh@xilinx.com>;
> > linux-iio@vger.kernel.org; devicetree@vger.kernel.org; linux-arm-
> > kernel@lists.infradead.org; linux-kernel@vger.kernel.org
> > Subject: Re: [PATCH 1/3] dt-bindings: iio: adc: Add Xilinx AMS binding
> > documentation
> >
> > On Thu, 30 Aug 2018 15:52:17 +0530
> > Manish Narani <manish.narani@xilinx.com> wrote:
> >
> > > Xilinx AMS have several ADC channels that can be used for measurement
> > > of different voltages and temperatures. Document the same in the bindings.
> > >
> > > Signed-off-by: Manish Narani <manish.narani@xilinx.com>
> > > ---
> > > .../devicetree/bindings/iio/adc/xilinx-ams.txt | 159
> > +++++++++++++++++++++
> > > 1 file changed, 159 insertions(+)
> > > create mode 100644
> > > Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
> > >
> > > diff --git a/Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
> > > b/Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
> > > new file mode 100644
> > > index 0000000..8cc96f0
> > > --- /dev/null
> > > +++ b/Documentation/devicetree/bindings/iio/adc/xilinx-ams.txt
> > > @@ -0,0 +1,159 @@
> > > +Xilinx AMS controller
> > > +
> > > +The AMS includes an ADC as well as on-chip sensors that can be used
> > > +to sample external voltages and monitor on-die operating conditions,
> > > +such as temperature and supply voltage levels. The AMS has two SYSMON
> > blocks.
> > > +PL-SYSMON block is capable of monitoring off chip voltage and
> > temperature.
> > > +PL-SYSMON block has DRP, JTAG and I2C interface to enable monitoring
> > > +from external master. Out of this interface currently only DRP is supported.
> > > +Other block PS-SYSMON is memory mapped to PS. Both of block has
> > > +built-in alarm generation logic that is used to interrupt the
> > > +processor based on condition set.
> >
> > I guess anyone reading this because they have the hardware would know what
> > PS and PL are, but it would still be nice to define those acronyms!
>
> Okay, I will rectify this in v2.
>
> >
> > Google suggest, Programmable Logic (FPGA bit I guess) and Process Space
> > (Arm core bit?) As I read this, from a driver point of view it doesn't really
> > matter - these are just blocks of channels that are there?
>
> Yes, these are just blocks of channels, which are enabled/disabled
> via devicetree child nodes "xlnx,zynqmp-ams-ps" and
> "xlnx,zynqmp-ams-pl".
Great, put please expand in here on the naming. This may not
matter for people using this particular driver, but having clear
human readable descriptions is useful when we are looking for
similarities between this an future drivers etc.
>
> >
> > This binding is complex enough I definitely want some DT binding
> > review.
>
> Sure!
>
> >
> >
> >
> > > +
> > > +All designs should have AMS registers, but PS and PL are
> > > optional. +The AMS controller can work with only PS, only PL and
> > > both PS and PL +configurations. Please specify registers
> > > according to your design. +Devicetree should always have AMS
> > > module property. Providing PS & PL
> > module is optional.
> > > +
> > > +Required properties:
> > > + - compatible: Should be "xlnx,zynqmp-ams"
> > > + - reg: Should specify AMS register space
> > > + - interrupts: Interrupt number for the AMS control
> > > interface
> > > + - interrupt-names: Interrupt name, must be "ams-irq"
> > > + - clocks: Should contain a clock specifier for the device
> > > + - ranges: keep the property empty to map child address
> > > space
> > > + (for PS and/or PL) nodes 1:1 onto the parent
> > > address
> > > + space
> > > +
> > > +AMS device tree subnode:
> > > + - compatible: Should be "xlnx,zynqmp-ams-ps" or
> > > "xlnx,zynqmp-ams-pl"
> > > + - reg: Register space for PS or PL
> > > +
> > > +Optional properties:
> > > +
> > > +Following optional property only valid for PL.
> > > + - xlnx,ext-channels: List of external channels that are
> > > connected to the
> > > + AMS PL module.
> > > +
> > > + The child nodes of this node represent the external
> > > channels which
> > are
> > > + connected to the AMS Module. If the property is not
> > > present
> > > + no external channels will be assumed to be connected.
> > > +
> > > + Each child node represents one channel and has the
> > > following
> > > + properties:
> > > + Required properties:
> > > + * reg: Pair of pins the channel is
> > > connected to.
> > > + 0: VP/VN
> > Hmm. So we have this table here of the ones that may or may not be
> > connected with different numbering from the overall table. Given
> > these indexes are arbitrary can we unify them?
> >
> > I.e. use the index from below but state here which values can be
> > used?
>
> Okay. I guess you are suggesting to use the channel index from the
> channel details table given in the end of this file and state here
> which number (for 'reg' value) should be used for given channel. Is
> this correct?
Exactly right.
>
> >
> > > + 1: VUSER0
> > > + 2: VUSER1
> > > + 3: VUSER3
> > > + 4: VUSER4
> > > + 5: VAUXP[0]/VAUXN[0]
> > > + 6: VAUXP[1]/VAUXN[1]
> > > + ...
> > > + 20: VAUXP[15]/VAUXN[15]
> > > + Note each channel number should only be used at
> > most
> > > + once.
> > > + Optional properties:
> > > + * xlnx,bipolar: If set the channel is used in bipolar
> > > + mode.
> > > +
> > > +
> > > +Example:
> > > + xilinx_ams: ams@ffa50000 {
> > > + compatible = "xlnx,zynqmp-ams";
> > > + interrupt-parent = <&gic>;
> > > + interrupts = <0 56 4>;
> > > + interrupt-names = "ams-irq";
> > > + clocks = <&clkc 70>;
> > > + reg = <0x0 0xffa50000 0x0 0x800>;
> > > + reg-names = "ams-base";
> > > + #address-cells = <2>;
> > > + #size-cells = <2>;
> > > + ranges;
> > > +
> > > + ams_ps: ams_ps@ffa50800 {
> > > + compatible = "xlnx,zynqmp-ams-ps";
> > > + reg = <0x0 0xffa50800 0x0 0x400>;
> > > + };
> > > +
> > > + ams_pl: ams_pl@ffa50c00 {
> > > + compatible = "xlnx,zynqmp-ams-pl";
> > > + reg = <0x0 0xffa50c00 0x0 0x400>;
> > > + xlnx,ext-channels {
> > > + #address-cells = <1>;
> > > + #size-cells = <0>;
> > > + channel@0 {
> > > + reg = <0>;
> > > + xlnx,bipolar;
> > > + };
> > > + channel@1 {
> > > + reg = <1>;
> > > + };
> > > + channel@8 {
> > > + reg = <8>;
> > > + xlnx,bipolar;
> > > + };
> > > + };
> > > + };
> > > + };
> > > +
> > > +AMS Channels Details:
> > > +
> > > +Sysmon Block |Channel| Details
> > |Measurement
> > > + Number
> > Type
> > > +---------------------------------------------------------------------------------------------------
> > ------
> > > +AMS CTRL |0 |System PLLs voltage measurement, VCC_PSPLL.
> > |Voltage
> > > + |1 |Battery voltage measurement, VCC_PSBATT.
> > |Voltage
> > > + |2 |PL Internal voltage measurement, VCCINT.
> > |Voltage
> > > + |3 |Block RAM voltage measurement, VCCBRAM.
> > |Voltage
> > > + |4 |PL Aux voltage measurement, VCCAUX.
> > |Voltage
> > > + |5 |Voltage measurement for six DDR I/O PLLs,
> > VCC_PSDDR_PLL. |Voltage
> > > + |6 |VCC_PSINTFP_DDR voltage measurement.
> > |Voltage
> > > +---------------------------------------------------------------------------------------------------
> > ------
> > > +PS Sysmon |7 |LPD temperature measurement.
> > |Temperature
> > > + |8 |FPD Temperature Measurment (REMOTE).
> > |Temperature
> > > + |9 |VCC PS LPD voltage measurement (supply1).
> > |Voltage
> > > + |10 |VCC PS FPD voltage measurement (supply2).
> > |Voltage
> > > + |11 |PS Aux voltage reference (supply3).
> > |Voltage
> > > + |12 |DDR I/O VCC voltage measurement.
> > |Voltage
> > > + |13 |PS IO Bank 503 voltage measurement (supply5).
> > |Voltage
> > > + |14 |PS IO Bank 500 voltage measurement (supply6).
> > |Voltage
> > > + |15 |VCCO_PSIO1 voltage measurement.
> > |Voltage
> > > + |16 |VCCO_PSIO2 voltage measurement.
> > |Voltage
> > > + |17 |VCC_PS_GTR voltage measurement
> > (VPS_MGTRAVCC). |Voltage
> > > + |18 |VTT_PS_GTR voltage measurement
> > (VPS_MGTRAVTT). |Voltage
> > > + |19 |VCC_PSADC voltage measurement.
> > |Voltage
> > > +---------------------------------------------------------------------------------------------------
> > ------
> > > +PL Sysmon |20 |PL Temperature measurement.
> > |Temperature
> > > + |21 |PL Internal Voltage Voltage measurement, VCCINT.
> > |Voltage
> > > + |22 |PL Auxiliary Voltage measurement, VCCAUX.
> > |Voltage
> > > + |23 |ADC Reference P+ Voltage measurement.
> > |Voltage
> > > + |24 |ADC Reference N- Voltage measurement.
> > |Voltage
> > > + |25 |PL Block RAM Voltage measurement, VCCBRAM.
> > |Voltage
> > > + |26 |LPD Internal Voltage measurement, VCC_PSINTLP
> > (supply4). |Voltage
> > > + |27 |FPD Internal Voltage measurement, VCC_PSINTFP
> > (supply5). |Voltage
> > > + |28 |PS Auxiliary Voltage measurement (supply6).
> > |Voltage
> > > + |29 |PL VCCADC Voltage measurement (vccams).
> > |Voltage
> > > + |30 |Differencial analog input signal Voltage measurment.
> > |Voltage
> > > + |31 |VUser0 Voltage measurement (supply7).
> > |Voltage
> > > + |32 |VUser1 Voltage measurement (supply8).
> > |Voltage
> > > + |33 |VUser2 Voltage measurement (supply9).
> > |Voltage
> > > + |34 |VUser3 Voltage measurement (supply10).
> > |Voltage
> > > + |35 |Auxiliary ch 0 Voltage measurement (VAux0).
> > |Voltage
> > > + |36 |Auxiliary ch 1 Voltage measurement (VAux1).
> > |Voltage
> > > + |37 |Auxiliary ch 2 Voltage measurement (VAux2).
> > |Voltage
> > > + |38 |Auxiliary ch 3 Voltage measurement (VAux3).
> > |Voltage
> > > + |39 |Auxiliary ch 4 Voltage measurement (VAux4).
> > |Voltage
> > > + |40 |Auxiliary ch 5 Voltage measurement (VAux5).
> > |Voltage
> > > + |41 |Auxiliary ch 6 Voltage measurement (VAux6).
> > |Voltage
> > > + |42 |Auxiliary ch 7 Voltage measurement (VAux7).
> > |Voltage
> > > + |43 |Auxiliary ch 8 Voltage measurement (VAux8).
> > |Voltage
> > > + |44 |Auxiliary ch 9 Voltage measurement (VAux9).
> > |Voltage
> > > + |45 |Auxiliary ch 10 Voltage measurement (VAux10).
> > |Voltage
> > > + |46 |Auxiliary ch 11 Voltage measurement (VAux11).
> > |Voltage
> > > + |47 |Auxiliary ch 12 Voltage measurement (VAux12).
> > |Voltage
> > > + |48 |Auxiliary ch 13 Voltage measurement (VAux13).
> > |Voltage
> > > + |49 |Auxiliary ch 14 Voltage measurement (VAux14).
> > |Voltage
> > > + |50 |Auxiliary ch 15 Voltage measurement (VAux15).
> > |Voltage
> > > +---------------------------------------------------------------------
> > > +------------------------------------
> >
> > Thanks,
> >
> > Jonathan
>
> Thanks,
> Manish Narani
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 2/3] iio: adc: Add Xilinx AMS driver
2018-09-06 15:19 ` Manish Narani
@ 2018-09-08 13:50 ` Jonathan Cameron
0 siblings, 0 replies; 13+ messages in thread
From: Jonathan Cameron @ 2018-09-08 13:50 UTC (permalink / raw)
To: Manish Narani
Cc: knaack.h@gmx.de, lars@metafoo.de, pmeerw@pmeerw.net,
robh+dt@kernel.org, mark.rutland@arm.com, Michal Simek,
leoyang.li@nxp.com, sudeep.holla@arm.com,
amit.kucheria@linaro.org, broonie@kernel.org,
arnaud.pouliquen@st.com, geert@linux-m68k.org,
eugen.hristev@microchip.com, rdunlap@infradead.org,
lukas@wunner.de, freeman.liu@spreadtrum.com,
vilhelm.gray@gmail.com, tglx@linutronix.de,
baolin.wang@linaro.org, gregkh@linuxfoundation.org, Srinivas Goud,
Anirudha Sarangi, linux-iio@vger.kernel.org,
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org
On Thu, 6 Sep 2018 15:19:29 +0000
Manish Narani <MNARANI@xilinx.com> wrote:
> Hi Jonathan,
>
> Thanks a lot for the review. Please see my response inline.
Hi Manish,
Just one thing to think about. (and I'm not that great about this
myself :) Please look to crop out irrelevant text. It saves
time when people are checking to see if there are discussion
points ongoing.
If you are replying to a review and agree with the suggestion then
feel free to cut that chunk out too. If you agree entirely don't
bother replying at all. I'll assume it will all be fixed in the
next version. I know this may seem impolite, but it's better
to save everyone time!
I'm lazy and like to minimize the time it takes me to look through
responses to reviews ;)
Thanks,
Jonathan
>
> > -----Original Message-----
> > From: Jonathan Cameron [mailto:jic23@kernel.org]
> > Sent: Monday, September 3, 2018 1:27 AM
> > To: Manish Narani <MNARANI@xilinx.com>
> > Cc: knaack.h@gmx.de; lars@metafoo.de; pmeerw@pmeerw.net;
> > robh+dt@kernel.org; mark.rutland@arm.com; Michal Simek
> > <michals@xilinx.com>; leoyang.li@nxp.com; sudeep.holla@arm.com;
> > amit.kucheria@linaro.org; broonie@kernel.org; arnaud.pouliquen@st.com;
> > geert@linux-m68k.org; eugen.hristev@microchip.com; rdunlap@infradead.org;
> > lukas@wunner.de; freeman.liu@spreadtrum.com; vilhelm.gray@gmail.com;
> > tglx@linutronix.de; baolin.wang@linaro.org; gregkh@linuxfoundation.org;
> > Srinivas Goud <sgoud@xilinx.com>; Anirudha Sarangi <anirudh@xilinx.com>;
> > linux-iio@vger.kernel.org; devicetree@vger.kernel.org; linux-arm-
> > kernel@lists.infradead.org; linux-kernel@vger.kernel.org
> > Subject: Re: [PATCH 2/3] iio: adc: Add Xilinx AMS driver
> >
> > On Thu, 30 Aug 2018 15:52:18 +0530
> > Manish Narani <manish.narani@xilinx.com> wrote:
> >
> > > The AMS includes an ADC as well as on-chip sensors that can be used to
> > > sample external voltages and monitor on-die operating conditions, such
> > > as temperature and supply voltage levels. The AMS has two SYSMON blocks.
> > > PL-SYSMON block is capable of monitoring off chip voltage and
> > > temperature.
> > > PL-SYSMON block has DRP, JTAG and I2C interface to enable monitoring
> > > from external master. Out of these interface currently only DRP is
> > > supported.
> > > Other block PS-SYSMON is memory mapped to PS.
> > > The AMS can use internal channels to monitor voltage and temperature
> > > as well as one primary and up to 16 auxiliary channels for measuring
> > > external voltages.
> > > The voltage and temperature monitoring channels also have event
> > > capability which allows to generate an interrupt when their value
> > > falls below or raises above a set threshold.
> > >
> > > Signed-off-by: Manish Narani <manish.narani@xilinx.com>
> >
> > Given the use of extended_name in here, there is a whole lot of undocumented
> > userspace ABI. Please add to
> >
> > Documentation/ABI/testing/sysfs-bus-iio-xilinx-ams or similar.
>
> Okay sure.
>
> >
> > I am a little concerned at introducing quite so many of these.
> > Perhaps we need to revisit how we represent this sort of information...
> >
> > Otherwise, a few minor things inline but looking fairly good overall.
> >
> > Thanks,
> >
> > Jonathan
> >
> > > ---
> > > drivers/iio/adc/Kconfig | 10 +
> > > drivers/iio/adc/Makefile | 1 +
> > > drivers/iio/adc/xilinx-ams.c | 1081
> > > ++++++++++++++++++++++++++++++++++++++++++
> > > drivers/iio/adc/xilinx-ams.h | 281 +++++++++++
> > > 4 files changed, 1373 insertions(+)
> > > create mode 100644 drivers/iio/adc/xilinx-ams.c create mode 100644
> > > drivers/iio/adc/xilinx-ams.h
> > >
> > > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index
> > > 4a75492..405ea00 100644
> > > --- a/drivers/iio/adc/Kconfig
> > > +++ b/drivers/iio/adc/Kconfig
> > > @@ -941,4 +941,14 @@ config XILINX_XADC
> > > The driver can also be build as a module. If so, the module will be
> > called
> > > xilinx-xadc.
> > >
> > > +config XILINX_AMS
> > > + tristate "Xilinx AMS driver"
> > > + depends on ARCH_ZYNQMP || COMPILE_TEST
> > > + depends on HAS_IOMEM
> > > + help
> > > + Say yes here to have support for the Xilinx AMS.
> > > +
> > > + The driver can also be build as a module. If so, the module will be
> > called
> > > + xilinx-ams.
> > > +
> > > endmenu
> > > diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index
> > > 03db7b5..fbfcc45 100644
> > > --- a/drivers/iio/adc/Makefile
> > > +++ b/drivers/iio/adc/Makefile
> > > @@ -85,4 +85,5 @@ obj-$(CONFIG_VF610_ADC) += vf610_adc.o
> > > obj-$(CONFIG_VIPERBOARD_ADC) += viperboard_adc.o xilinx-xadc-y :=
> > > xilinx-xadc-core.o xilinx-xadc-events.o
> > > obj-$(CONFIG_XILINX_XADC) += xilinx-xadc.o
> > > +obj-$(CONFIG_XILINX_AMS) += xilinx-ams.o
> > > obj-$(CONFIG_SD_ADC_MODULATOR) += sd_adc_modulator.o diff --git
> > > a/drivers/iio/adc/xilinx-ams.c b/drivers/iio/adc/xilinx-ams.c new file
> > > mode 100644 index 0000000..10bcc52
> > > --- /dev/null
> > > +++ b/drivers/iio/adc/xilinx-ams.c
> > > @@ -0,0 +1,1081 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Xilinx AMS driver
> > > + *
> > > + * Copyright (C) 2017-2018 Xilinx, Inc.
> > > + *
> > > + * Manish Narani <mnarani@xilinx.com>
> > > + * Rajnikant Bhojani <rajnikant.bhojani@xilinx.com> */
> > > +
> > > +#include <linux/kernel.h>
> > > +#include <linux/slab.h>
> > > +#include <linux/module.h>
> > > +#include <linux/interrupt.h>
> > > +#include <linux/platform_device.h>
> > > +#include <linux/clk.h>
> > > +#include <linux/of_address.h>
> > > +#include <linux/iopoll.h>
> > > +#include <linux/delay.h>
> > There is a rough convention of alphabetical order if there isn't a reason to do
> > otherwise.
> >
> > Here you might keep the iio headers for their own block at the end, but the rest
> > should be in order.
>
> Okay I will try to follow the same and update in v2.
>
> >
> > > +#include <linux/iio/iio.h>
> > > +#include <linux/iio/sysfs.h>
> > > +#include <linux/iio/events.h>
> > > +#include <linux/io.h>
> > > +
> > > +#include "xilinx-ams.h"
> > > +
> > > +static const unsigned int AMS_UNMASK_TIMEOUT_MS = 500;
> > > +
> > > +static inline void ams_ps_update_reg(struct ams *ams, unsigned int offset,
> > > + u32 mask, u32 data)
> > > +{
> > > + u32 val;
> > > +
> > > + val = readl(ams->ps_base + offset);
> > > + writel((val & ~mask) | (data & mask), ams->ps_base + offset); }
> > > +
> > > +static inline void ams_pl_write_reg(struct ams *ams, unsigned int offset,
> > > + u32 data)
> > > +{
> > > + writel(data, ams->pl_base + offset); }
> > I'm always anti wrappers that don't add much. In this driver you also only use
> > them 'sometimes'.
>
> Okay, I will use writel inline in v2
>
> >
> > I'd prefer just having the writel inline all the time. The update functions have a
> > little more purpose and given they are called quite often, perhaps are worth
> > keeping.
> >
> > Another option is to use regmap for the whole thing getting you update
> > functions and caching etc. Might not be worth it here. Up to you.
>
> Sure. I will try to explore this.
>
> >
> > > +
> > > +static inline void ams_pl_update_reg(struct ams *ams, unsigned int offset,
> > > + u32 mask, u32 data)
> > > +{
> > > + u32 val;
> > > +
> > > + val = readl(ams->pl_base + offset);
> > > + writel((val & ~mask) | (data & mask), ams->pl_base + offset); }
> > > +
> > > +static void ams_update_intrmask(struct ams *ams, u64 mask, u64 val) {
> > > + ams->intr_mask &= ~mask;
> > > + ams->intr_mask |= (val & mask);
> > > +
> > > + writel(~(ams->intr_mask | ams->masked_alarm), ams->base +
> > AMS_IER_0);
> > > + writel(~(ams->intr_mask >> AMS_ISR1_INTR_MASK_SHIFT),
> > > + ams->base + AMS_IER_1);
> > > + writel(ams->intr_mask | ams->masked_alarm, ams->base +
> > AMS_IDR_0);
> > > + writel(ams->intr_mask >> AMS_ISR1_INTR_MASK_SHIFT,
> > > + ams->base + AMS_IDR_1);
> > > +}
> > > +
> > > +static void ams_disable_all_alarms(struct ams *ams) {
> > > + /* disable PS module alarm */
> > > + if (ams->ps_base) {
> > > + ams_ps_update_reg(ams, AMS_REG_CONFIG1,
> > AMS_REGCFG1_ALARM_MASK,
> > > + AMS_REGCFG1_ALARM_MASK);
> > > + ams_ps_update_reg(ams, AMS_REG_CONFIG3,
> > AMS_REGCFG3_ALARM_MASK,
> > > + AMS_REGCFG3_ALARM_MASK);
> > > + }
> > > +
> > > + /* disable PL module alarm */
> > > + if (ams->pl_base) {
> > > + ams_pl_update_reg(ams, AMS_REG_CONFIG1,
> > > + AMS_REGCFG1_ALARM_MASK,
> > > + AMS_REGCFG1_ALARM_MASK);
> > > + ams_pl_update_reg(ams, AMS_REG_CONFIG3,
> > > + AMS_REGCFG3_ALARM_MASK,
> > > + AMS_REGCFG3_ALARM_MASK);
> > > + }
> > > +}
> > > +
> > > +static void iio_ams_update_alarm(struct ams *ams, unsigned long
> > > +alarm_mask) {
> > > + u32 cfg;
> > > + unsigned long flags;
> > > + unsigned long pl_alarm_mask;
> > > +
> > > + if (ams->ps_base) {
> > > + /* Configuring PS alarm enable */
> > > + cfg = ~((alarm_mask & AMS_ISR0_ALARM_2_TO_0_MASK) <<
> > > + AMS_CONF1_ALARM_2_TO_0_SHIFT);
> > > + cfg &= ~((alarm_mask & AMS_ISR0_ALARM_6_TO_3_MASK)
> > <<
> > > + AMS_CONF1_ALARM_6_TO_3_SHIFT);
> > > + ams_ps_update_reg(ams, AMS_REG_CONFIG1,
> > AMS_REGCFG1_ALARM_MASK,
> > > + cfg);
> > > +
> > > + cfg = ~((alarm_mask >>
> > AMS_CONF3_ALARM_12_TO_7_SHIFT) &
> > > + AMS_ISR0_ALARM_12_TO_7_MASK);
> > > + ams_ps_update_reg(ams, AMS_REG_CONFIG3,
> > AMS_REGCFG3_ALARM_MASK,
> > > + cfg);
> > > + }
> > > +
> > > + if (ams->pl_base) {
> > > + pl_alarm_mask = (alarm_mask >> AMS_PL_ALARM_START);
> > > + /* Configuring PL alarm enable */
> > > + cfg = ~((pl_alarm_mask & AMS_ISR0_ALARM_2_TO_0_MASK)
> > <<
> > > + AMS_CONF1_ALARM_2_TO_0_SHIFT);
> > > + cfg &= ~((pl_alarm_mask &
> > AMS_ISR0_ALARM_6_TO_3_MASK) <<
> > > + AMS_CONF1_ALARM_6_TO_3_SHIFT);
> > > + ams_pl_update_reg(ams, AMS_REG_CONFIG1,
> > > + AMS_REGCFG1_ALARM_MASK, cfg);
> > > +
> > > + cfg = ~((pl_alarm_mask >>
> > AMS_CONF3_ALARM_12_TO_7_SHIFT) &
> > > + AMS_ISR0_ALARM_12_TO_7_MASK);
> > > + ams_pl_update_reg(ams, AMS_REG_CONFIG3,
> > > + AMS_REGCFG3_ALARM_MASK, cfg);
> > > + }
> > > +
> > > + spin_lock_irqsave(&ams->lock, flags);
> > > + ams_update_intrmask(ams, AMS_ISR0_ALARM_MASK, ~alarm_mask);
> > > + spin_unlock_irqrestore(&ams->lock, flags); }
> > > +
> > > +static void ams_enable_channel_sequence(struct ams *ams) {
> > > + int i;
> > > + unsigned long long scan_mask;
> > > + struct iio_dev *indio_dev = iio_priv_to_dev(ams);
> > > +
> > > + /*
> > > + * Enable channel sequence. First 22 bit of scan_mask represent
> > > + * PS channels, and next remaining bit represents PL channels.
> > > + */
> > > +
> > > + /* Run calibration of PS & PL as part of the sequence */
> > > + scan_mask = 0x1 | BIT(PS_SEQ_MAX);
> > > + for (i = 0; i < indio_dev->num_channels; i++)
> > > + scan_mask |= BIT(indio_dev->channels[i].scan_index);
> > > +
> > > + if (ams->ps_base) {
> > > + /* put sysmon in a soft reset to change the sequence */
> > > + ams_ps_update_reg(ams, AMS_REG_CONFIG1,
> > AMS_CONF1_SEQ_MASK,
> > > + AMS_CONF1_SEQ_DEFAULT);
> > > +
> > > + /* configure basic channels */
> > > + writel(scan_mask & AMS_REG_SEQ0_MASK,
> > > + ams->ps_base + AMS_REG_SEQ_CH0);
> > > + writel(AMS_REG_SEQ2_MASK &
> > > + (scan_mask >> AMS_REG_SEQ2_MASK_SHIFT),
> > > + ams->ps_base + AMS_REG_SEQ_CH2);
> > > +
> > > + /* set continuous sequence mode */
> > > + ams_ps_update_reg(ams, AMS_REG_CONFIG1,
> > AMS_CONF1_SEQ_MASK,
> > > + AMS_CONF1_SEQ_CONTINUOUS);
> > > + }
> > > +
> > > + if (ams->pl_base) {
> > > + /* put sysmon in a soft reset to change the sequence */
> > > + ams_pl_update_reg(ams, AMS_REG_CONFIG1,
> > AMS_CONF1_SEQ_MASK,
> > > + AMS_CONF1_SEQ_DEFAULT);
> > > +
> > > + /* configure basic channels */
> > > + scan_mask = scan_mask >> PS_SEQ_MAX;
> > > + writel(scan_mask & AMS_REG_SEQ0_MASK,
> > > + ams->pl_base + AMS_REG_SEQ_CH0);
> > > + writel(AMS_REG_SEQ2_MASK &
> > > + (scan_mask >> AMS_REG_SEQ2_MASK_SHIFT),
> > > + ams->pl_base + AMS_REG_SEQ_CH2);
> > > + writel(AMS_REG_SEQ1_MASK &
> > > + (scan_mask >> AMS_REG_SEQ1_MASK_SHIFT),
> > > + ams->pl_base + AMS_REG_SEQ_CH1);
> > > +
> > > + /* set continuous sequence mode */
> > > + ams_pl_update_reg(ams, AMS_REG_CONFIG1,
> > AMS_CONF1_SEQ_MASK,
> > > + AMS_CONF1_SEQ_CONTINUOUS);
> > > + }
> > > +}
> > > +
> > > +static int iio_ams_init_device(struct ams *ams) {
> > > + u32 reg;
> > > + int ret;
> > > +
> > > + /* reset AMS */
> > > + if (ams->ps_base) {
> > > + writel(AMS_PS_RESET_VALUE, ams->ps_base + AMS_VP_VN);
> > > +
> > > + ret = readl_poll_timeout(ams->base + AMS_PS_CSTS, reg,
> > > + (reg & AMS_PS_CSTS_PS_READY) ==
> > > + AMS_PS_CSTS_PS_READY, 0,
> > > + AMS_INIT_TIMEOUT);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + /* put sysmon in a default state */
> > > + ams_ps_update_reg(ams, AMS_REG_CONFIG1,
> > AMS_CONF1_SEQ_MASK,
> > > + AMS_CONF1_SEQ_DEFAULT);
> > > + }
> > > +
> > > + if (ams->pl_base) {
> > > + writel(AMS_PL_RESET_VALUE, ams->pl_base + AMS_VP_VN);
> > > +
> > > + ret = readl_poll_timeout(ams->base + AMS_PL_CSTS, reg,
> > > + (reg & AMS_PL_CSTS_ACCESS_MASK)
> > ==
> > > + AMS_PL_CSTS_ACCESS_MASK, 0,
> > > + AMS_INIT_TIMEOUT);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + /* put sysmon in a default state */
> > > + ams_pl_update_reg(ams, AMS_REG_CONFIG1,
> > AMS_CONF1_SEQ_MASK,
> > > + AMS_CONF1_SEQ_DEFAULT);
> > > + }
> > > +
> > > + ams_disable_all_alarms(ams);
> > > +
> > > + /* Disable interrupt */
> > > + ams_update_intrmask(ams, ~0, ~0);
> > > +
> > > + /* Clear any pending interrupt */
> > > + writel(AMS_ISR0_ALARM_MASK, ams->base + AMS_ISR_0);
> > > + writel(AMS_ISR1_ALARM_MASK, ams->base + AMS_ISR_1);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int ams_enable_single_channel(struct ams *ams, unsigned int
> > > +offset) {
> > > + u8 channel_num = 0;
> > > +
> > > + switch (offset) {
> > > + case AMS_VCC_PSPLL0:
> > > + channel_num = AMS_VCC_PSPLL0_CH;
> > > + break;
> > > + case AMS_VCC_PSPLL3:
> > > + channel_num = AMS_VCC_PSPLL3_CH;
> > > + break;
> > > + case AMS_VCCINT:
> > > + channel_num = AMS_VCCINT_CH;
> > > + break;
> > > + case AMS_VCCBRAM:
> > > + channel_num = AMS_VCCBRAM_CH;
> > > + break;
> > > + case AMS_VCCAUX:
> > > + channel_num = AMS_VCCAUX_CH;
> > > + break;
> > > + case AMS_PSDDRPLL:
> > > + channel_num = AMS_PSDDRPLL_CH;
> > > + break;
> > > + case AMS_PSINTFPDDR:
> > > + channel_num = AMS_PSINTFPDDR_CH;
> > > + break;
> > > + default:
> > > + return -EINVAL;
> > > + }
> > > +
> > > + /* set single channel, sequencer off mode */
> > > + ams_ps_update_reg(ams, AMS_REG_CONFIG1,
> > AMS_CONF1_SEQ_MASK,
> > > + AMS_CONF1_SEQ_SINGLE_CHANNEL);
> > > +
> > > + /* write the channel number */
> > > + ams_ps_update_reg(ams, AMS_REG_CONFIG0,
> > AMS_CONF0_CHANNEL_NUM_MASK,
> > > + channel_num);
> > > + mdelay(1);
> > This delay should be documented.. Preferably with a reference to the
> > datasheet to justify the particular value.
>
> There is an option to poll for EOC (End of Conversion) to be set in a register. I will replace this delay will that polling.
>
> >
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int ams_read_vcc_reg(struct ams *ams, unsigned int offset, u32
> > > +*data) {
> > > + int ret;
> > > +
> > > + ret = ams_enable_single_channel(ams, offset);
> > > + if (ret)
> > > + return ret;
> > > +
> > > + *data = readl(ams->base + offset);
> > > + ams_enable_channel_sequence(ams);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int ams_read_raw(struct iio_dev *indio_dev,
> > > + struct iio_chan_spec const *chan,
> > > + int *val, int *val2, long mask)
> > > +{
> > > + struct ams *ams = iio_priv(indio_dev);
> > > + int ret;
> > > +
> > > + switch (mask) {
> > > + case IIO_CHAN_INFO_RAW:
> > > + mutex_lock(&ams->mutex);
> > > + if (chan->scan_index >= (PS_SEQ_MAX * 3)) {
> > > + ret = ams_read_vcc_reg(ams, chan->address, val);
> > > + if (ret)
> > > + goto read_raw_err;
> >
> > Missing mutex_unlock?
>
> Missed it. This will be corrected in v2.
>
> >
> > > + } else if (chan->scan_index >= PS_SEQ_MAX)
> > > + *val = readl(ams->pl_base + chan->address);
> > > + else
> > > + *val = readl(ams->ps_base + chan->address);
> > > + mutex_unlock(&ams->mutex);
> > > +
> > > + return IIO_VAL_INT;
> > > + case IIO_CHAN_INFO_SCALE:
> > > + switch (chan->type) {
> > > + case IIO_VOLTAGE:
> > > + switch (chan->address) {
> > > + case AMS_SUPPLY1:
> > > + case AMS_SUPPLY2:
> > > + case AMS_SUPPLY3:
> > > + case AMS_SUPPLY4:
> > > + *val = AMS_SUPPLY_SCALE_3VOLT;
> > > + break;
> > > + case AMS_SUPPLY5:
> > > + case AMS_SUPPLY6:
> > > + if (chan->scan_index < PS_SEQ_MAX)
> > > + *val = AMS_SUPPLY_SCALE_6VOLT;
> > > + else
> > > + *val = AMS_SUPPLY_SCALE_3VOLT;
> > > + break;
> > > + case AMS_SUPPLY7:
> > > + case AMS_SUPPLY8:
> > > + *val = AMS_SUPPLY_SCALE_6VOLT;
> > > + break;
> > > + case AMS_SUPPLY9:
> > > + case AMS_SUPPLY10:
> > > + if (chan->scan_index < PS_SEQ_MAX)
> > > + *val = AMS_SUPPLY_SCALE_3VOLT;
> > > + else
> > > + *val = AMS_SUPPLY_SCALE_6VOLT;
> > > + break;
> > > + case AMS_VCC_PSPLL0:
> > > + case AMS_VCC_PSPLL3:
> > > + case AMS_VCCINT:
> > > + case AMS_VCCBRAM:
> > > + case AMS_VCCAUX:
> > > + case AMS_PSDDRPLL:
> > > + case AMS_PSINTFPDDR:
> > > + *val = AMS_SUPPLY_SCALE_3VOLT;
> > > + break;
> > > + default:
> > > + *val = AMS_SUPPLY_SCALE_1VOLT;
> > > + break;
> > > + }
> > > + *val2 = AMS_SUPPLY_SCALE_DIV_BIT;
> > > + return IIO_VAL_FRACTIONAL_LOG2;
> > > + case IIO_TEMP:
> > > + *val = AMS_TEMP_SCALE;
> > > + *val2 = AMS_TEMP_SCALE_DIV_BIT;
> > > + return IIO_VAL_FRACTIONAL_LOG2;
> > > + default:
> > > + return -EINVAL;
> > > + }
> > > + case IIO_CHAN_INFO_OFFSET:
> > > + /* Only the temperature channel has an offset */
> > > + *val = AMS_TEMP_OFFSET;
> > > + return IIO_VAL_INT;
> > > + }
> > > +
> > > +read_raw_err:
> >
> > Given we only return an error code here, are we not better just doing that
> > inline?
>
> Yes. You are right. Will update this in v2.
>
> >
> > > + return -EINVAL;
> > > +}
> > > +
> > > +static int ams_get_alarm_offset(int scan_index, enum
> > > +iio_event_direction dir) {
> > > + int offset = 0;
> > > +
> > > + if (scan_index >= PS_SEQ_MAX)
> > > + scan_index -= PS_SEQ_MAX;
> > > +
> > > + if (dir == IIO_EV_DIR_FALLING) {
> > > + if (scan_index < AMS_SEQ_SUPPLY7)
> > > + offset = AMS_ALARM_THRESHOLD_OFF_10;
> > > + else
> > > + offset = AMS_ALARM_THRESHOLD_OFF_20;
> > > + }
> > > +
> > > + switch (scan_index) {
> > > + case AMS_SEQ_TEMP:
> > > + return AMS_ALARM_TEMP + offset;
> > > + case AMS_SEQ_SUPPLY1:
> > > + return AMS_ALARM_SUPPLY1 + offset;
> > > + case AMS_SEQ_SUPPLY2:
> > > + return AMS_ALARM_SUPPLY2 + offset;
> > > + case AMS_SEQ_SUPPLY3:
> > > + return AMS_ALARM_SUPPLY3 + offset;
> > > + case AMS_SEQ_SUPPLY4:
> > > + return AMS_ALARM_SUPPLY4 + offset;
> > > + case AMS_SEQ_SUPPLY5:
> > > + return AMS_ALARM_SUPPLY5 + offset;
> > > + case AMS_SEQ_SUPPLY6:
> > > + return AMS_ALARM_SUPPLY6 + offset;
> > > + case AMS_SEQ_SUPPLY7:
> > > + return AMS_ALARM_SUPPLY7 + offset;
> > > + case AMS_SEQ_SUPPLY8:
> > > + return AMS_ALARM_SUPPLY8 + offset;
> > > + case AMS_SEQ_SUPPLY9:
> > > + return AMS_ALARM_SUPPLY9 + offset;
> > > + case AMS_SEQ_SUPPLY10:
> > > + return AMS_ALARM_SUPPLY10 + offset;
> > > + case AMS_SEQ_VCCAMS:
> > > + return AMS_ALARM_VCCAMS + offset;
> > > + case AMS_SEQ_TEMP_REMOTE:
> > > + return AMS_ALARM_TEMP_REMOTE + offset;
> > > + }
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static const struct iio_chan_spec *ams_event_to_channel(
> > > + struct iio_dev *indio_dev, u32 event) {
> > > + int scan_index = 0, i;
> > > +
> > > + if (event >= AMS_PL_ALARM_START) {
> > > + event -= AMS_PL_ALARM_START;
> > > + scan_index = PS_SEQ_MAX;
> > > + }
> > > +
> > > + switch (event) {
> > > + case AMS_ALARM_BIT_TEMP:
> > > + scan_index += AMS_SEQ_TEMP;
> > > + break;
> > > + case AMS_ALARM_BIT_SUPPLY1:
> > > + scan_index += AMS_SEQ_SUPPLY1;
> > > + break;
> > > + case AMS_ALARM_BIT_SUPPLY2:
> > > + scan_index += AMS_SEQ_SUPPLY2;
> > > + break;
> > > + case AMS_ALARM_BIT_SUPPLY3:
> > > + scan_index += AMS_SEQ_SUPPLY3;
> > > + break;
> > > + case AMS_ALARM_BIT_SUPPLY4:
> > > + scan_index += AMS_SEQ_SUPPLY4;
> > > + break;
> > > + case AMS_ALARM_BIT_SUPPLY5:
> > > + scan_index += AMS_SEQ_SUPPLY5;
> > > + break;
> > > + case AMS_ALARM_BIT_SUPPLY6:
> > > + scan_index += AMS_SEQ_SUPPLY6;
> > > + break;
> > > + case AMS_ALARM_BIT_SUPPLY7:
> > > + scan_index += AMS_SEQ_SUPPLY7;
> > > + break;
> > > + case AMS_ALARM_BIT_SUPPLY8:
> > > + scan_index += AMS_SEQ_SUPPLY8;
> > > + break;
> > > + case AMS_ALARM_BIT_SUPPLY9:
> > > + scan_index += AMS_SEQ_SUPPLY9;
> > > + break;
> > > + case AMS_ALARM_BIT_SUPPLY10:
> > > + scan_index += AMS_SEQ_SUPPLY10;
> > > + break;
> > > + case AMS_ALARM_BIT_VCCAMS:
> > > + scan_index += AMS_SEQ_VCCAMS;
> > > + break;
> > > + case AMS_ALARM_BIT_TEMP_REMOTE:
> > > + scan_index += AMS_SEQ_TEMP_REMOTE;
> > > + break;
> > > + }
> > > +
> > > + for (i = 0; i < indio_dev->num_channels; i++)
> > > + if (indio_dev->channels[i].scan_index == scan_index)
> > > + break;
> > > +
> > > + return &indio_dev->channels[i];
> > > +}
> > > +
> > > +static int ams_get_alarm_mask(int scan_index) {
> > > + int bit = 0;
> > > +
> > > + if (scan_index >= PS_SEQ_MAX) {
> > > + bit = AMS_PL_ALARM_START;
> > > + scan_index -= PS_SEQ_MAX;
> > > + }
> > > +
> > > + switch (scan_index) {
> > > + case AMS_SEQ_TEMP:
> > > + return BIT(AMS_ALARM_BIT_TEMP + bit);
> > > + case AMS_SEQ_SUPPLY1:
> > > + return BIT(AMS_ALARM_BIT_SUPPLY1 + bit);
> > > + case AMS_SEQ_SUPPLY2:
> > > + return BIT(AMS_ALARM_BIT_SUPPLY2 + bit);
> > > + case AMS_SEQ_SUPPLY3:
> > > + return BIT(AMS_ALARM_BIT_SUPPLY3 + bit);
> > > + case AMS_SEQ_SUPPLY4:
> > > + return BIT(AMS_ALARM_BIT_SUPPLY4 + bit);
> > > + case AMS_SEQ_SUPPLY5:
> > > + return BIT(AMS_ALARM_BIT_SUPPLY5 + bit);
> > > + case AMS_SEQ_SUPPLY6:
> > > + return BIT(AMS_ALARM_BIT_SUPPLY6 + bit);
> > > + case AMS_SEQ_SUPPLY7:
> > > + return BIT(AMS_ALARM_BIT_SUPPLY7 + bit);
> > > + case AMS_SEQ_SUPPLY8:
> > > + return BIT(AMS_ALARM_BIT_SUPPLY8 + bit);
> > > + case AMS_SEQ_SUPPLY9:
> > > + return BIT(AMS_ALARM_BIT_SUPPLY9 + bit);
> > > + case AMS_SEQ_SUPPLY10:
> > > + return BIT(AMS_ALARM_BIT_SUPPLY10 + bit);
> > > + case AMS_SEQ_VCCAMS:
> > > + return BIT(AMS_ALARM_BIT_VCCAMS + bit);
> > > + case AMS_SEQ_TEMP_REMOTE:
> > > + return BIT(AMS_ALARM_BIT_TEMP_REMOTE + bit);
> > > + }
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int ams_read_event_config(struct iio_dev *indio_dev,
> > > + const struct iio_chan_spec *chan,
> > > + enum iio_event_type type,
> > > + enum iio_event_direction dir)
> > > +{
> > > + struct ams *ams = iio_priv(indio_dev);
> > > +
> > > + return (ams->alarm_mask & ams_get_alarm_mask(chan->scan_index))
> > ? 1
> > > +: 0; }
> > > +
> > > +static int ams_write_event_config(struct iio_dev *indio_dev,
> > > + const struct iio_chan_spec *chan,
> > > + enum iio_event_type type,
> > > + enum iio_event_direction dir,
> > > + int state)
> > > +{
> > > + struct ams *ams = iio_priv(indio_dev);
> > > + unsigned int alarm;
> > > +
> > > + alarm = ams_get_alarm_mask(chan->scan_index);
> > > +
> > > + mutex_lock(&ams->mutex);
> > > +
> > > + if (state)
> > > + ams->alarm_mask |= alarm;
> > > + else
> > > + ams->alarm_mask &= ~alarm;
> > > +
> > > + iio_ams_update_alarm(ams, ams->alarm_mask);
> > > +
> > > + mutex_unlock(&ams->mutex);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int ams_read_event_value(struct iio_dev *indio_dev,
> > > + const struct iio_chan_spec *chan,
> > > + enum iio_event_type type,
> > > + enum iio_event_direction dir,
> > > + enum iio_event_info info, int *val, int *val2) {
> > > + struct ams *ams = iio_priv(indio_dev);
> > > + unsigned int offset = ams_get_alarm_offset(chan->scan_index, dir);
> > > +
> > > + mutex_lock(&ams->mutex);
> > > +
> > > + if (chan->scan_index >= PS_SEQ_MAX)
> > > + *val = readl(ams->pl_base + offset);
> > > + else
> > > + *val = readl(ams->ps_base + offset);
> > > +
> > > + mutex_unlock(&ams->mutex);
> > > +
> > > + return IIO_VAL_INT;
> > > +}
> > > +
> > > +static int ams_write_event_value(struct iio_dev *indio_dev,
> > > + const struct iio_chan_spec *chan,
> > > + enum iio_event_type type,
> > > + enum iio_event_direction dir,
> > > + enum iio_event_info info, int val, int val2) {
> > > + struct ams *ams = iio_priv(indio_dev);
> > > + unsigned int offset;
> > > +
> > > + mutex_lock(&ams->mutex);
> > > +
> > > + /* Set temperature channel threshold to direct threshold */
> > > + if (chan->type == IIO_TEMP) {
> > > + offset = ams_get_alarm_offset(chan->scan_index,
> > > + IIO_EV_DIR_FALLING);
> > > +
> > > + if (chan->scan_index >= PS_SEQ_MAX)
> > > + ams_pl_update_reg(ams, offset,
> > > + AMS_ALARM_THR_DIRECT_MASK,
> > > + AMS_ALARM_THR_DIRECT_MASK);
> > > + else
> > > + ams_ps_update_reg(ams, offset,
> > > + AMS_ALARM_THR_DIRECT_MASK,
> > > + AMS_ALARM_THR_DIRECT_MASK);
> > > + }
> > > +
> > > + offset = ams_get_alarm_offset(chan->scan_index, dir);
> > > + if (chan->scan_index >= PS_SEQ_MAX)
> > > + writel(val, ams->pl_base + offset);
> > > + else
> > > + writel(val, ams->ps_base + offset);
> > > +
> > > + mutex_unlock(&ams->mutex);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static void ams_handle_event(struct iio_dev *indio_dev, u32 event) {
> > > + const struct iio_chan_spec *chan;
> > > +
> > > + chan = ams_event_to_channel(indio_dev, event);
> > > +
> > > + if (chan->type == IIO_TEMP) {
> > > + /* The temperature channel only supports over-temperature
> > In IIO we use the standard kernel syntax for comments of
> > /*
> > * The temperature...
> > */
> >
> > This form is only used in a few subsystems such as net.
> >
> > It's minor but nice to be consistent...
>
> Okay. Will update this in v2.
>
> >
> > > + * events
> > > + */
> > > + iio_push_event(indio_dev,
> > > + IIO_UNMOD_EVENT_CODE(chan->type, chan-
> > >channel,
> > > + IIO_EV_TYPE_THRESH,
> > > + IIO_EV_DIR_RISING),
> > > + iio_get_time_ns(indio_dev));
> > > + } else {
> > > + /* For other channels we don't know whether it is a upper or
> > > + * lower threshold event. Userspace will have to check the
> > > + * channel value if it wants to know.
> > > + */
> > > + iio_push_event(indio_dev,
> > > + IIO_UNMOD_EVENT_CODE(chan->type, chan-
> > >channel,
> > > + IIO_EV_TYPE_THRESH,
> > > + IIO_EV_DIR_EITHER),
> > > + iio_get_time_ns(indio_dev));
> > > + }
> > > +}
> > > +
> > > +static void ams_handle_events(struct iio_dev *indio_dev, unsigned
> > > +long events) {
> > > + unsigned int bit;
> > > +
> > > + for_each_set_bit(bit, &events, AMS_NO_OF_ALARMS)
> > > + ams_handle_event(indio_dev, bit);
> > > +}
> > > +
> > > +/**
> > > + * ams_unmask_worker - ams alarm interrupt unmask worker
> > > + * @work : work to be done
> > > + *
> > > + * The ZynqMP threshold interrupts are level sensitive. Since we
> > > +can't make the
> > > + * threshold condition go way from within the interrupt handler, this
> > > +means as
> > > + * soon as a threshold condition is present we would enter the
> > > +interrupt handler
> > > + * again and again. To work around this we mask all active threshold
> > > +interrupts
> > > + * in the interrupt handler and start a timer. In this timer we poll
> > > +the
> > > + * interrupt status and only if the interrupt is inactive we unmask it again.
> > > + */
> > > +static void ams_unmask_worker(struct work_struct *work) {
> > > + struct ams *ams = container_of(work, struct ams,
> > ams_unmask_work.work);
> > > + unsigned int status, unmask;
> > > +
> > > + spin_lock_irq(&ams->lock);
> > > +
> > > + status = readl(ams->base + AMS_ISR_0);
> > > +
> > > + /* Clear those bits which are not active anymore */
> > > + unmask = (ams->masked_alarm ^ status) & ams->masked_alarm;
> > > +
> > > + /* clear status of disabled alarm */
> > > + unmask |= ams->intr_mask;
> > > +
> > > + ams->masked_alarm &= status;
> > > +
> > > + /* Also clear those which are masked out anyway */
> > > + ams->masked_alarm &= ~ams->intr_mask;
> > > +
> > > + /* Clear the interrupts before we unmask them */
> > > + writel(unmask, ams->base + AMS_ISR_0);
> > > +
> > > + ams_update_intrmask(ams, 0, 0);
> > > +
> > > + spin_unlock_irq(&ams->lock);
> > > +
> > > + /* if still pending some alarm re-trigger the timer */
> > > + if (ams->masked_alarm)
> > > + schedule_delayed_work(&ams->ams_unmask_work,
> > > +
> > msecs_to_jiffies(AMS_UNMASK_TIMEOUT_MS));
> > > +}
> > > +
> > > +static irqreturn_t ams_iio_irq(int irq, void *data) {
> > > + unsigned int isr0, isr1;
> > > + struct iio_dev *indio_dev = data;
> > > + struct ams *ams = iio_priv(indio_dev);
> > > +
> > > + spin_lock(&ams->lock);
> >
> > Do these need to be handled in interrupt context? Would we be better off
> > doing it in an interrupt thread where we can sleep (and hence not use a spin
> > lock for example)?
> >
> > Perhaps not, but this doesn't immediately feel time critical...
>
> This is not time critical. I will remove spin lock here.
>
> >
> > > +
> > > + isr0 = readl(ams->base + AMS_ISR_0);
> > > + isr1 = readl(ams->base + AMS_ISR_1);
> > > +
> > > + /* only process alarms that are not masked */
> > > + isr0 &= ~((ams->intr_mask & AMS_ISR0_ALARM_MASK) | ams-
> > >masked_alarm);
> > > + isr1 &= ~(ams->intr_mask >> AMS_ISR1_INTR_MASK_SHIFT);
> > > +
> > > + /* clear interrupt */
> > > + writel(isr0, ams->base + AMS_ISR_0);
> > > + writel(isr1, ams->base + AMS_ISR_1);
> >
> > If we have interrupts here that aren't ours we should a) not be clearing them
> > b) be returning IRQ_NONE so it can be handled as an incorrect interrupt and
> > reported as such.
>
> Right. Will update this in v2.
>
> >
> > > +
> > > + if (isr0) {
> > > + /* Once the alarm interrupt occurred, mask until get cleared
> > */
> > > + ams->masked_alarm |= isr0;
> > > + ams_update_intrmask(ams, 0, 0);
> > > +
> > > + ams_handle_events(indio_dev, isr0);
> > > +
> > > + schedule_delayed_work(&ams->ams_unmask_work,
> > > +
> > msecs_to_jiffies(AMS_UNMASK_TIMEOUT_MS));
> > > + }
> > > +
> > > + spin_unlock(&ams->lock);
> > > +
> > > + return IRQ_HANDLED;
> > > +}
> > > +
> > > +static const struct iio_event_spec ams_temp_events[] = {
> > > + {
> > > + .type = IIO_EV_TYPE_THRESH,
> > > + .dir = IIO_EV_DIR_RISING,
> > > + .mask_separate = BIT(IIO_EV_INFO_ENABLE) |
> > > + BIT(IIO_EV_INFO_VALUE),
> > > + },
> > > +};
> > > +
> > > +static const struct iio_event_spec ams_voltage_events[] = {
> > > + {
> > > + .type = IIO_EV_TYPE_THRESH,
> > > + .dir = IIO_EV_DIR_RISING,
> > > + .mask_separate = BIT(IIO_EV_INFO_VALUE),
> > > + }, {
> > > + .type = IIO_EV_TYPE_THRESH,
> > > + .dir = IIO_EV_DIR_FALLING,
> > > + .mask_separate = BIT(IIO_EV_INFO_VALUE),
> > > + }, {
> > > + .type = IIO_EV_TYPE_THRESH,
> > > + .dir = IIO_EV_DIR_EITHER,
> > > + .mask_separate = BIT(IIO_EV_INFO_ENABLE),
> > > + },
> > > +};
> > > +
> > > +static const struct iio_chan_spec ams_ps_channels[] = {
> > > + AMS_PS_CHAN_TEMP(AMS_SEQ_TEMP, AMS_TEMP, "ps_temp"),
> > > + AMS_PS_CHAN_TEMP(AMS_SEQ_TEMP_REMOTE,
> > AMS_TEMP_REMOTE, "remote_temp"),
> > > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY1, AMS_SUPPLY1,
> > "vccpsintlp"),
> > > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY2, AMS_SUPPLY2,
> > "vccpsintfp"),
> > > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY3, AMS_SUPPLY3,
> > "vccpsaux"),
> > > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY4, AMS_SUPPLY4,
> > "vccpsddr"),
> > > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY5, AMS_SUPPLY5,
> > "vccpsio3"),
> > > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY6, AMS_SUPPLY6,
> > "vccpsio0"),
> > > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY7, AMS_SUPPLY7,
> > "vccpsio1"),
> > > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY8, AMS_SUPPLY8,
> > "vccpsio2"),
> > > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY9, AMS_SUPPLY9,
> > "psmgtravcc"),
> > > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_SUPPLY10, AMS_SUPPLY10,
> > "psmgtravtt"),
> > > + AMS_PS_CHAN_VOLTAGE(AMS_SEQ_VCCAMS, AMS_VCCAMS,
> > "vccams"), };
> > > +
> > > +static const struct iio_chan_spec ams_pl_channels[] = {
> > > + AMS_PL_CHAN_TEMP(AMS_SEQ_TEMP, AMS_TEMP, "pl_temp"),
> > > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY1, AMS_SUPPLY1, "vccint",
> > true),
> > > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY2, AMS_SUPPLY2,
> > "vccaux", true),
> > > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VREFP, AMS_VREFP, "vccvrefp",
> > false),
> > > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VREFN, AMS_VREFN, "vccvrefn",
> > false),
> > > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY3, AMS_SUPPLY3,
> > "vccbram", true),
> > > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY4, AMS_SUPPLY4,
> > "vccplintlp", true),
> > > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY5, AMS_SUPPLY5,
> > "vccplintfp", true),
> > > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY6, AMS_SUPPLY6,
> > "vccplaux", true),
> > > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VCCAMS, AMS_VCCAMS,
> > "vccams", true),
> > > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_VP_VN, AMS_VP_VN, "vccvpvn",
> > false),
> > > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY7, AMS_SUPPLY7,
> > "vuser0", true),
> > > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY8, AMS_SUPPLY8,
> > "vuser1", true),
> > > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY9, AMS_SUPPLY9,
> > "vuser2", true),
> > > + AMS_PL_CHAN_VOLTAGE(AMS_SEQ_SUPPLY10, AMS_SUPPLY10,
> > "vuser3", true),
> > > + AMS_PL_AUX_CHAN_VOLTAGE(0, "vccaux0"),
> >
> > Extended_name is a two edged sword. It lets you make nice specifically named
> > channels, but it also breaks almost all userspace code as it doesn't know how to
> > handle the extra bit on the end of the name.
> >
> > As such we tend to only specify it for very special use non standard channels
> > (such as the power supplies)
> >
> > For the aux channels, it has no particular meaning. So I would prefer that you
> > dropped it and just left them with the indexes.
>
> Okay. I will keep the extended names for AMS_SUPPLY* and remove them for the aux channels.
>
> >
> > > + AMS_PL_AUX_CHAN_VOLTAGE(1, "vccaux1"),
> > > + AMS_PL_AUX_CHAN_VOLTAGE(2, "vccaux2"),
> > > + AMS_PL_AUX_CHAN_VOLTAGE(3, "vccaux3"),
> > > + AMS_PL_AUX_CHAN_VOLTAGE(4, "vccaux4"),
> > > + AMS_PL_AUX_CHAN_VOLTAGE(5, "vccaux5"),
> > > + AMS_PL_AUX_CHAN_VOLTAGE(6, "vccaux6"),
> > > + AMS_PL_AUX_CHAN_VOLTAGE(7, "vccaux7"),
> > > + AMS_PL_AUX_CHAN_VOLTAGE(8, "vccaux8"),
> > > + AMS_PL_AUX_CHAN_VOLTAGE(9, "vccaux9"),
> > > + AMS_PL_AUX_CHAN_VOLTAGE(10, "vccaux10"),
> > > + AMS_PL_AUX_CHAN_VOLTAGE(11, "vccaux11"),
> > > + AMS_PL_AUX_CHAN_VOLTAGE(12, "vccaux12"),
> > > + AMS_PL_AUX_CHAN_VOLTAGE(13, "vccaux13"),
> > > + AMS_PL_AUX_CHAN_VOLTAGE(14, "vccaux14"),
> > > + AMS_PL_AUX_CHAN_VOLTAGE(15, "vccaux15"), };
> > > +
> > > +static const struct iio_chan_spec ams_ctrl_channels[] = {
> > > + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCC_PSPLL, AMS_VCC_PSPLL0,
> > "vcc_pspll0"),
> > > + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCC_PSBATT,
> > AMS_VCC_PSPLL3, "vcc_psbatt"),
> > > + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCCINT, AMS_VCCINT,
> > "vccint"),
> > > + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCCBRAM, AMS_VCCBRAM,
> > "vccbram"),
> > > + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_VCCAUX, AMS_VCCAUX,
> > "vccaux"),
> > > + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_PSDDRPLL, AMS_PSDDRPLL,
> > "psddrpll"),
> > > + AMS_CTRL_CHAN_VOLTAGE(AMS_SEQ_INTDDR, AMS_PSINTFPDDR,
> > "psintfpddr"),
> > > +};
> > > +
> > > +static int ams_init_module(struct iio_dev *indio_dev, struct device_node
> > *np,
> > > + struct iio_chan_spec *channels) {
> > > + struct ams *ams = iio_priv(indio_dev);
> > > + struct device_node *chan_node, *child;
> > > + int ret, num_channels = 0;
> > > + unsigned int reg;
> > > +
> > > + if (of_device_is_compatible(np, "xlnx,zynqmp-ams-ps")) {
> > > + ams->ps_base = of_iomap(np, 0);
> > > + if (!ams->ps_base)
> > > + return -ENXIO;
> > > +
> > > + /* add PS channels to iio device channels */
> > > + memcpy(channels + num_channels, ams_ps_channels,
> > > + sizeof(ams_ps_channels));
> > > + num_channels += ARRAY_SIZE(ams_ps_channels);
> > > + } else if (of_device_is_compatible(np, "xlnx,zynqmp-ams-pl")) {
> > > + ams->pl_base = of_iomap(np, 0);
> > > + if (!ams->pl_base)
> > > + return -ENXIO;
> > > +
> > > + /* Copy only first 10 fix channels */
> > > + memcpy(channels + num_channels, ams_pl_channels,
> > > + AMS_PL_MAX_FIXED_CHANNEL * sizeof(*channels));
> > > + num_channels += AMS_PL_MAX_FIXED_CHANNEL;
> > > +
> > > + chan_node = of_get_child_by_name(np, "xlnx,ext-channels");
> > > + if (chan_node) {
> > > + for_each_child_of_node(chan_node, child) {
> > > + ret = of_property_read_u32(child, "reg",
> > ®);
> > > + if (ret || reg > AMS_PL_MAX_EXT_CHANNEL)
> > > + continue;
> > > +
> > > + memcpy(&channels[num_channels],
> > > + &ams_pl_channels[reg +
> > > + AMS_PL_MAX_FIXED_CHANNEL],
> > > + sizeof(*channels));
> > > +
> > > + if (of_property_read_bool(child,
> > > + "xlnx,bipolar"))
> > > +
> > channels[num_channels].scan_type.sign =
> > > + 's';
> > > +
> > > + num_channels += 1;
> >
> > num_channels++;
>
> Okay.
>
> >
> > > + }
> > > + }
> > > + of_node_put(chan_node);
> > > + } else if (of_device_is_compatible(np, "xlnx,zynqmp-ams")) {
> > > + /* add AMS channels to iio device channels */
> > > + memcpy(channels + num_channels, ams_ctrl_channels,
> > > + sizeof(ams_ctrl_channels));
> > > + num_channels += ARRAY_SIZE(ams_ctrl_channels);
> > > + } else {
> > > + return -EINVAL;
> > > + }
> > > +
> > > + return num_channels;
> > > +}
> > > +
> > > +static int ams_parse_dt(struct iio_dev *indio_dev, struct
> > > +platform_device *pdev) {
> > > + struct ams *ams = iio_priv(indio_dev);
> > > + struct iio_chan_spec *ams_channels, *dev_channels;
> > > + struct device_node *child_node = NULL, *np = pdev->dev.of_node;
> > > + int ret, chan_vol = 0, chan_temp = 0, i, rising_off, falling_off;
> >
> > chan_vol and chan_temp are counts? Perhaps their names can make that
> > clearer.
>
> Okay. I will change their name to 'vol_ch_cnt' and 'temp_ch_cnt' in v2.
>
> >
> > > + unsigned int num_channels = 0;
> > > +
> > > + /* Initialize buffer for channel specification */
> > > + ams_channels = kzalloc(sizeof(ams_ps_channels) +
> > > + sizeof(ams_pl_channels) +
> > > + sizeof(ams_ctrl_channels), GFP_KERNEL);
> > > + if (!ams_channels)
> > > + return -ENOMEM;
> > > +
> > > + if (of_device_is_available(np)) {
> > > + ret = ams_init_module(indio_dev, np, ams_channels);
> > > + if (ret < 0) {
> > > + kfree(ams_channels);
> > > + return ret;
> > > + }
> > > +
> > > + num_channels += ret;
> > > + }
> > > +
> > > + for_each_child_of_node(np, child_node) {
> > > + if (of_device_is_available(child_node)) {
> > > + ret = ams_init_module(indio_dev, child_node,
> > > + ams_channels + num_channels);
> > > + if (ret < 0) {
> > > + kfree(ams_channels);
> > > + return ret;
> > > + }
> > > +
> > > + num_channels += ret;
> > > + }
> > > + }
> > > +
> > > + for (i = 0; i < num_channels; i++) {
> > > + if (ams_channels[i].type == IIO_VOLTAGE)
> > > + ams_channels[i].channel = chan_vol++;
> > > + else
> > > + ams_channels[i].channel = chan_temp++;
> > > +
> > > + if (ams_channels[i].scan_index < (PS_SEQ_MAX * 3)) {
> > > + /* set threshold to max and min for each channel */
> > > + falling_off = ams_get_alarm_offset(
> > > + ams_channels[i].scan_index,
> > > + IIO_EV_DIR_FALLING);
> > > + rising_off = ams_get_alarm_offset(
> > > + ams_channels[i].scan_index,
> > > + IIO_EV_DIR_RISING);
> > > + if (ams_channels[i].scan_index >= PS_SEQ_MAX) {
> > > + writel(AMS_ALARM_THR_MIN,
> > > + ams->pl_base + falling_off);
> > > + writel(AMS_ALARM_THR_MAX,
> > > + ams->pl_base + rising_off);
> > > + } else {
> > > + writel(AMS_ALARM_THR_MIN,
> > > + ams->ps_base + falling_off);
> > > + writel(AMS_ALARM_THR_MAX,
> > > + ams->ps_base + rising_off);
> > > + }
> > > + }
> > > + }
> > > +
> > > + dev_channels = devm_kzalloc(&pdev->dev, sizeof(*dev_channels) *
> > > + num_channels, GFP_KERNEL);
> > > + if (!dev_channels) {
> > > + kfree(ams_channels);
> > > + return -ENOMEM;
> > > + }
> > > +
> > > + memcpy(dev_channels, ams_channels,
> > > + sizeof(*ams_channels) * num_channels);
> > > + kfree(ams_channels);
> > If you were to reorder this so the ams_channels cleanup was last you could use
> > the nice clean pattern of
> >
> > error:
> > kfree(ams_channels);
> >
> > return ret;
> >
> > To make it clear that the ams_channels gets freed in all paths.
>
> Fair enough. I will update this in v2.
>
> >
> > > + indio_dev->channels = dev_channels;
> > > + indio_dev->num_channels = num_channels;
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static const struct iio_info iio_pl_info = {
> > > + .read_raw = &ams_read_raw,
> > > + .read_event_config = &ams_read_event_config,
> > > + .write_event_config = &ams_write_event_config,
> > > + .read_event_value = &ams_read_event_value,
> > > + .write_event_value = &ams_write_event_value, };
> > > +
> > > +static const struct of_device_id ams_of_match_table[] = {
> > > + { .compatible = "xlnx,zynqmp-ams" },
> > > + { }
> > > +};
> > > +MODULE_DEVICE_TABLE(of, ams_of_match_table);
> > > +
> > > +static int ams_probe(struct platform_device *pdev) {
> > > + struct iio_dev *indio_dev;
> > > + struct ams *ams;
> > > + struct resource *res;
> > > + int ret;
> > > +
> > > + if (!pdev->dev.of_node)
> > > + return -ENODEV;
> > > +
> > > + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*ams));
> > > + if (!indio_dev)
> > > + return -ENOMEM;
> > > +
> > > + ams = iio_priv(indio_dev);
> > > + mutex_init(&ams->mutex);
> > > + spin_lock_init(&ams->lock);
> > > +
> > > + indio_dev->dev.parent = &pdev->dev;
> > > + indio_dev->dev.of_node = pdev->dev.of_node;
> > > + indio_dev->name = "ams";
> > As names go, that one is a too little generic. Particularly with a common
> > manufacturer of ADCs called Austrian Micro Systems ;) No possibility of
> > confusion!
> >
> > It's fine to use ams within the driver, but for external interfaces, perhaps prefix
> > with xilinx-?
>
> Okay. I will use xilinx-ams.
>
> >
> > > +
> > > + indio_dev->info = &iio_pl_info;
> > > + indio_dev->modes = INDIO_DIRECT_MODE;
> > > +
> > > + res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> > "ams-base");
> > > + ams->base = devm_ioremap_resource(&pdev->dev, res);
> > > + if (IS_ERR(ams->base))
> > > + return PTR_ERR(ams->base);
> > > +
> > > + INIT_DELAYED_WORK(&ams->ams_unmask_work,
> > ams_unmask_worker);
> > > +
> > > + ams->clk = devm_clk_get(&pdev->dev, NULL);
> > > + if (IS_ERR(ams->clk))
> > > + return PTR_ERR(ams->clk);
> > > + clk_prepare_enable(ams->clk);
> > > +
> > > + ret = iio_ams_init_device(ams);
> > > + if (ret) {
> > > + dev_err(&pdev->dev, "failed to initialize AMS\n");
> > > + goto clk_disable;
> > > + }
> > > +
> > > + ret = ams_parse_dt(indio_dev, pdev);
> > > + if (ret) {
> > > + dev_err(&pdev->dev, "failure in parsing DT\n");
> > > + goto clk_disable;
> > > + }
> > > +
> > > + ams_enable_channel_sequence(ams);
> > > +
> > > + ams->irq = platform_get_irq_byname(pdev, "ams-irq");
> >
> > Why store this in the ams structure? It's not used anywhere other than in the
> > next line.
>
> Okay. Will update this in v2.
>
> >
> > > + ret = devm_request_irq(&pdev->dev, ams->irq, &ams_iio_irq, 0, "ams-
> > irq",
> > > + indio_dev);
> > This mixing of devm and non devm functions makes it tricky to be sure there
> > aren't an races.
> >
> > One easy solution is to use devm_add_action to put the clk_disable_unprepare
> > into the cleanup list.
>
> Okay.
>
> >
> > > + if (ret < 0) {
> > > + dev_err(&pdev->dev, "failed to register interrupt\n");
> > > + goto clk_disable;
> > > + }
> > > +
> > > + platform_set_drvdata(pdev, indio_dev);
> > > +
> > > + return iio_device_register(indio_dev);
> >
> > What if device register returns an error? You leave the clock enabled.
> >
> > That way everything is using the managed cleanup and the ordering will be
> > correct.
> >
> > The alternative is to not use devm forms for everything after the
> > clk_prepare_enable.
>
> Yes. This is better.
>
> >
> > > +
> > > +clk_disable:
> > > + clk_disable_unprepare(ams->clk);
> > > +
> > > + return ret;
> > > +}
> > > +
> > > +static int ams_remove(struct platform_device *pdev) {
> > > + struct iio_dev *indio_dev = platform_get_drvdata(pdev);
> > > + struct ams *ams = iio_priv(indio_dev);
> > > +
> > > + cancel_delayed_work(&ams->ams_unmask_work);
> > > +
> > > + /* Unregister the device */
> > > + iio_device_unregister(indio_dev);
> > > + clk_disable_unprepare(ams->clk);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int __maybe_unused ams_suspend(struct device *dev) {
> > > + struct iio_dev *indio_dev = dev_get_drvdata(dev);
> > > + struct ams *ams = iio_priv(indio_dev);
> > > +
> > > + clk_disable_unprepare(ams->clk);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static int __maybe_unused ams_resume(struct device *dev) {
> > > + struct iio_dev *indio_dev = dev_get_drvdata(dev);
> > > + struct ams *ams = iio_priv(indio_dev);
> > You could streamline this without loss of clarity as.
> >
> > struct ams *ams = iio_priv(dev_get_drvdata(dev));
> >
> > (very minor point!)
>
> Okay.
>
> > > +
> > > + clk_prepare_enable(ams->clk);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +static SIMPLE_DEV_PM_OPS(ams_pm_ops, ams_suspend, ams_resume);
> > > +
> > > +static struct platform_driver ams_driver = {
> > > + .probe = ams_probe,
> > > + .remove = ams_remove,
> > > + .driver = {
> > > + .name = "ams",
> > > + .pm = &ams_pm_ops,
> >
> > Spacing here seems a little odd. Just have a single space after pm
>
> Okay.
>
> >
> > > + .of_match_table = ams_of_match_table,
> > > + },
> > > +};
> > > +module_platform_driver(ams_driver);
> > > +
> > > +MODULE_LICENSE("GPL");
> > > +MODULE_AUTHOR("Xilinx, Inc.");
> > > diff --git a/drivers/iio/adc/xilinx-ams.h
> > > b/drivers/iio/adc/xilinx-ams.h new file mode 100644 index
> > > 0000000..b9fa262
> > > --- /dev/null
> > > +++ b/drivers/iio/adc/xilinx-ams.h
> >
> > I can't immediately see why a lot of this wants to be in a header.
> >
> > Please move it inline with the C file.
>
> Okay. I will update that in v2.
>
> >
> >
> > > @@ -0,0 +1,281 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * Xilinx AMS driver
> > > + *
> > > + * Copyright (C) 2017-2018 Xilinx, Inc.
> > > + *
> > > + * Manish Narani <mnarani@xilinx.com>
> > > + * Rajnikant Bhojani <rajnikant.bhojani@xilinx.com> */
> > > +
> > > +#ifndef __XILINX_AMS_H__
> > > +#define __XILINX_AMS_H__
> > > +
> > > +#define AMS_MISC_CTRL 0x000
> > > +#define AMS_ISR_0 0x010
> > > +#define AMS_ISR_1 0x014
> > > +#define AMS_IMR_0 0x018
> > > +#define AMS_IMR_1 0x01c
> > > +#define AMS_IER_0 0x020
> > > +#define AMS_IER_1 0x024
> > > +#define AMS_IDR_0 0x028
> > > +#define AMS_IDR_1 0x02c
> > > +#define AMS_PS_CSTS 0x040
> > > +#define AMS_PL_CSTS 0x044
> > > +#define AMS_MON_CSTS 0x050
> > > +
> > > +#define AMS_VCC_PSPLL0 0x060
> > > +#define AMS_VCC_PSPLL3 0x06C
> > > +#define AMS_VCCINT 0x078
> > > +#define AMS_VCCBRAM 0x07C
> > > +#define AMS_VCCAUX 0x080
> > > +#define AMS_PSDDRPLL 0x084
> > > +#define AMS_PSINTFPDDR 0x09C
> > > +
> > > +#define AMS_VCC_PSPLL0_CH 48
> > > +#define AMS_VCC_PSPLL3_CH 51
> > > +#define AMS_VCCINT_CH 54
> > > +#define AMS_VCCBRAM_CH 55
> > > +#define AMS_VCCAUX_CH 56
> > > +#define AMS_PSDDRPLL_CH 57
> > > +#define AMS_PSINTFPDDR_CH 63
> > > +
> > > +#define AMS_REG_CONFIG0 0x100
> > > +#define AMS_REG_CONFIG1 0x104
> > > +#define AMS_REG_CONFIG2 0x108
> > > +#define AMS_REG_CONFIG3 0x10C
> > > +#define AMS_REG_CONFIG4 0x110
> > > +#define AMS_REG_SEQ_CH0 0x120
> > > +#define AMS_REG_SEQ_CH1 0x124
> > > +#define AMS_REG_SEQ_CH2 0x118
> > > +
> > > +#define AMS_TEMP 0x000
> > > +#define AMS_SUPPLY1 0x004
> > > +#define AMS_SUPPLY2 0x008
> > > +#define AMS_VP_VN 0x00c
> > > +#define AMS_VREFP 0x010
> > > +#define AMS_VREFN 0x014
> > > +#define AMS_SUPPLY3 0x018
> > > +#define AMS_SUPPLY4 0x034
> > > +#define AMS_SUPPLY5 0x038
> > > +#define AMS_SUPPLY6 0x03c
> > > +#define AMS_SUPPLY7 0x200
> > > +#define AMS_SUPPLY8 0x204
> > > +#define AMS_SUPPLY9 0x208
> > > +#define AMS_SUPPLY10 0x20c
> > > +#define AMS_VCCAMS 0x210
> > > +#define AMS_TEMP_REMOTE 0x214
> > > +
> > > +#define AMS_REG_VAUX(x) (0x40 + (4*(x)))
> > > +#define AMS_REG_VUSER(x) (0x200 + (4*(x)))
> > > +
> > > +#define AMS_PS_RESET_VALUE 0xFFFFU
> > > +#define AMS_PL_RESET_VALUE 0xFFFFU
> > > +
> > > +#define AMS_CONF0_CHANNEL_NUM_MASK (0x3f << 0)
> > > +
> > > +#define AMS_CONF1_SEQ_MASK (0xf << 12)
> > > +#define AMS_CONF1_SEQ_DEFAULT (0 << 12)
> > > +#define AMS_CONF1_SEQ_SINGLE_PASS (1 << 12)
> > > +#define AMS_CONF1_SEQ_CONTINUOUS (2 << 12)
> > > +#define AMS_CONF1_SEQ_SINGLE_CHANNEL (3 << 12)
> > > +
> > > +#define AMS_REG_SEQ0_MASK 0xFFFF
> > > +#define AMS_REG_SEQ2_MASK 0x3F
> > > +#define AMS_REG_SEQ1_MASK 0xFFFF
> > > +#define AMS_REG_SEQ2_MASK_SHIFT 16
> > > +#define AMS_REG_SEQ1_MASK_SHIFT 22
> > > +
> > > +#define AMS_REGCFG1_ALARM_MASK 0xF0F
> > > +#define AMS_REGCFG3_ALARM_MASK 0x3F
> > > +
> > > +#define AMS_ALARM_TEMP 0x140
> > > +#define AMS_ALARM_SUPPLY1 0x144
> > > +#define AMS_ALARM_SUPPLY2 0x148
> > > +#define AMS_ALARM_OT 0x14c
> > > +
> > > +#define AMS_ALARM_SUPPLY3 0x160
> > > +#define AMS_ALARM_SUPPLY4 0x164
> > > +#define AMS_ALARM_SUPPLY5 0x168
> > > +#define AMS_ALARM_SUPPLY6 0x16c
> > > +#define AMS_ALARM_SUPPLY7 0x180
> > > +#define AMS_ALARM_SUPPLY8 0x184
> > > +#define AMS_ALARM_SUPPLY9 0x188
> > > +#define AMS_ALARM_SUPPLY10 0x18c
> > > +#define AMS_ALARM_VCCAMS 0x190
> > > +#define AMS_ALARM_TEMP_REMOTE 0x194
> > > +#define AMS_ALARM_THRESHOLD_OFF_10 0x10 #define
> > > +AMS_ALARM_THRESHOLD_OFF_20 0x20
> > > +
> > > +#define AMS_ALARM_THR_DIRECT_MASK 0x01
> > > +#define AMS_ALARM_THR_MIN 0x0000
> > > +#define AMS_ALARM_THR_MAX 0xffff
> > > +
> > > +#define AMS_NO_OF_ALARMS 32
> > > +#define AMS_PL_ALARM_START 16
> > > +#define AMS_ISR0_ALARM_MASK 0xFFFFFFFFU
> > > +#define AMS_ISR1_ALARM_MASK 0xE000001FU
> > > +#define AMS_ISR1_INTR_MASK_SHIFT 32
> > > +#define AMS_ISR0_ALARM_2_TO_0_MASK 0x07
> > > +#define AMS_ISR0_ALARM_6_TO_3_MASK 0x78
> > > +#define AMS_ISR0_ALARM_12_TO_7_MASK 0x3F
> > > +#define AMS_CONF1_ALARM_2_TO_0_SHIFT 1
> > > +#define AMS_CONF1_ALARM_6_TO_3_SHIFT 5
> > > +#define AMS_CONF3_ALARM_12_TO_7_SHIFT 8
> > > +
> > > +#define AMS_PS_CSTS_PS_READY 0x08010000U
> > > +#define AMS_PL_CSTS_ACCESS_MASK 0x00000001U
> > > +
> > > +#define AMS_PL_MAX_FIXED_CHANNEL 10
> > > +#define AMS_PL_MAX_EXT_CHANNEL 20
> > > +
> > > +#define AMS_INIT_TIMEOUT 10000
> > > +
> > > +/*
> > > + * Following scale and offset value is derived from
> > > + * UG580 (v1.7) December 20, 2016
> > > + */
> > > +#define AMS_SUPPLY_SCALE_1VOLT 1000
> > > +#define AMS_SUPPLY_SCALE_3VOLT 3000
> > > +#define AMS_SUPPLY_SCALE_6VOLT 6000
> > > +#define AMS_SUPPLY_SCALE_DIV_BIT 16
> > > +
> > > +#define AMS_TEMP_SCALE 509314
> > > +#define AMS_TEMP_SCALE_DIV_BIT 16
> > > +#define AMS_TEMP_OFFSET -((280230L << 16) / 509314)
> > > +
> > > +enum ams_alarm_bit {
> > > + AMS_ALARM_BIT_TEMP,
> > > + AMS_ALARM_BIT_SUPPLY1,
> > > + AMS_ALARM_BIT_SUPPLY2,
> > > + AMS_ALARM_BIT_SUPPLY3,
> > > + AMS_ALARM_BIT_SUPPLY4,
> > > + AMS_ALARM_BIT_SUPPLY5,
> > > + AMS_ALARM_BIT_SUPPLY6,
> > > + AMS_ALARM_BIT_RESERVED,
> > > + AMS_ALARM_BIT_SUPPLY7,
> > > + AMS_ALARM_BIT_SUPPLY8,
> > > + AMS_ALARM_BIT_SUPPLY9,
> > > + AMS_ALARM_BIT_SUPPLY10,
> > > + AMS_ALARM_BIT_VCCAMS,
> > > + AMS_ALARM_BIT_TEMP_REMOTE
> > > +};
> > > +
> > > +enum ams_seq {
> > > + AMS_SEQ_VCC_PSPLL,
> > > + AMS_SEQ_VCC_PSBATT,
> > > + AMS_SEQ_VCCINT,
> > > + AMS_SEQ_VCCBRAM,
> > > + AMS_SEQ_VCCAUX,
> > > + AMS_SEQ_PSDDRPLL,
> > > + AMS_SEQ_INTDDR
> > > +};
> > > +
> > > +enum ams_ps_pl_seq {
> > > + AMS_SEQ_CALIB,
> > > + AMS_SEQ_RSVD_1,
> > > + AMS_SEQ_RSVD_2,
> > > + AMS_SEQ_TEST,
> > > + AMS_SEQ_RSVD_4,
> > > + AMS_SEQ_SUPPLY4,
> > > + AMS_SEQ_SUPPLY5,
> > > + AMS_SEQ_SUPPLY6,
> > > + AMS_SEQ_TEMP,
> > > + AMS_SEQ_SUPPLY2,
> > > + AMS_SEQ_SUPPLY1,
> > > + AMS_SEQ_VP_VN,
> > > + AMS_SEQ_VREFP,
> > > + AMS_SEQ_VREFN,
> > > + AMS_SEQ_SUPPLY3,
> > > + AMS_SEQ_CURRENT_MON,
> > > + AMS_SEQ_SUPPLY7,
> > > + AMS_SEQ_SUPPLY8,
> > > + AMS_SEQ_SUPPLY9,
> > > + AMS_SEQ_SUPPLY10,
> > > + AMS_SEQ_VCCAMS,
> > > + AMS_SEQ_TEMP_REMOTE,
> > > + AMS_SEQ_MAX
> > > +};
> > > +
> > > +#define AMS_SEQ(x) (AMS_SEQ_MAX + (x))
> > > +#define AMS_VAUX_SEQ(x) (AMS_SEQ_MAX + (x))
> > > +
> > > +#define PS_SEQ_MAX AMS_SEQ_MAX
> > > +#define PS_SEQ(x) (x)
> > > +#define PL_SEQ(x) (PS_SEQ_MAX + x)
> > > +
> > > +#define AMS_CHAN_TEMP(_scan_index, _addr, _ext) { \
> > > + .type = IIO_TEMP, \
> > > + .indexed = 1, \
> > > + .address = (_addr), \
> > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
> > > + BIT(IIO_CHAN_INFO_SCALE) | \
> > > + BIT(IIO_CHAN_INFO_OFFSET), \
> > > + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> > > + .event_spec = ams_temp_events, \
> > > + .num_event_specs = ARRAY_SIZE(ams_temp_events), \
> > > + .scan_index = (_scan_index), \
> > > + .scan_type = { \
> > > + .sign = 'u', \
> > > + .realbits = 12, \
> > > + .storagebits = 16, \
> > > + .shift = 4, \
> > > + .endianness = IIO_CPU, \
> > > + }, \
> > > + .extend_name = _ext, \
> > > +}
> > > +
> > > +#define AMS_CHAN_VOLTAGE(_scan_index, _addr, _ext, _alarm) { \
> > > + .type = IIO_VOLTAGE, \
> > > + .indexed = 1, \
> > > + .address = (_addr), \
> > > + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
> > > + BIT(IIO_CHAN_INFO_SCALE), \
> > > + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> > > + .event_spec = (_alarm) ? ams_voltage_events : NULL, \
> > > + .num_event_specs = (_alarm) ? ARRAY_SIZE(ams_voltage_events) : 0, \
> > > + .scan_index = (_scan_index), \
> > > + .scan_type = { \
> > > + .realbits = 10, \
> > > + .storagebits = 16, \
> > > + .shift = 6, \
> > > + .endianness = IIO_CPU, \
> > > + }, \
> > > + .extend_name = _ext, \
> > > +}
> > > +
> > > +#define AMS_PS_CHAN_TEMP(_scan_index, _addr, _ext) \
> > > + AMS_CHAN_TEMP(PS_SEQ(_scan_index), _addr, _ext) #define
> > > +AMS_PS_CHAN_VOLTAGE(_scan_index, _addr, _ext) \
> > > + AMS_CHAN_VOLTAGE(PS_SEQ(_scan_index), _addr, _ext, true)
> > > +
> > > +#define AMS_PL_CHAN_TEMP(_scan_index, _addr, _ext) \
> > > + AMS_CHAN_TEMP(PL_SEQ(_scan_index), _addr, _ext) #define
> > > +AMS_PL_CHAN_VOLTAGE(_scan_index, _addr, _ext, _alarm) \
> > > + AMS_CHAN_VOLTAGE(PL_SEQ(_scan_index), _addr, _ext, _alarm)
> > #define
> > > +AMS_PL_AUX_CHAN_VOLTAGE(_auxno, _ext) \
> > > + AMS_CHAN_VOLTAGE(PL_SEQ(AMS_VAUX_SEQ(_auxno)), \
> > > + AMS_REG_VAUX(_auxno), _ext, false) #define
> > > +AMS_CTRL_CHAN_VOLTAGE(_scan_index, _addr, _ext) \
> > > +
> > AMS_CHAN_VOLTAGE(PL_SEQ(AMS_VAUX_SEQ(AMS_SEQ(_scan_inde
> > x))), \
> > > + _addr, _ext, false)
> > > +
> > > +struct ams {
> > > + void __iomem *base;
> > > + void __iomem *ps_base;
> > > + void __iomem *pl_base;
> > > + struct clk *clk;
> > > + struct device *dev;
> > > +
> > > + struct mutex mutex;
> > Pleases give clear comments on what these locks are protecting.
> > It's certainly curious to see a mutex and a spinlock with such generic names
> > right next to each other.
>
> Okay. I will update with the comments in v2.
>
> >
> > > + spinlock_t lock;
> > > +
> > > + unsigned int alarm_mask;
> > > + unsigned int masked_alarm;
> > > + u64 intr_mask;
> > > + int irq;
> > As noted above, this is only used next to where it is first set.
> > No advantage in having it here.
>
> I will remove this in v2.
>
> >
> > > +
> > > + struct delayed_work ams_unmask_work; };
> > > +
> > > +#endif /* __XILINX_AMS_H__ */
>
>
> Thanks,
> Manish Narani
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2018-09-08 18:36 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2018-08-30 10:22 [PATCH 0/3] Add Xilinx AMS Driver Manish Narani
2018-08-30 10:22 ` [PATCH 1/3] dt-bindings: iio: adc: Add Xilinx AMS binding documentation Manish Narani
2018-09-02 18:14 ` Jonathan Cameron
2018-09-06 13:27 ` Manish Narani
2018-09-08 13:42 ` Jonathan Cameron
2018-09-04 1:17 ` Rob Herring
2018-09-06 13:42 ` Manish Narani
2018-08-30 10:22 ` [PATCH 2/3] iio: adc: Add Xilinx AMS driver Manish Narani
2018-08-30 15:36 ` Randy Dunlap
2018-09-02 19:56 ` Jonathan Cameron
2018-09-06 15:19 ` Manish Narani
2018-09-08 13:50 ` Jonathan Cameron
2018-08-30 10:22 ` [PATCH 3/3] arm64: zynqmp: DT: Add Xilinx AMS node Manish Narani
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).