Thread (18 messages) 18 messages, 7 authors, 2021-11-03

RE: [PATCH v7 2/4] iio: adc: Add Xilinx AMS driver

From: Anand Ashok Dumbre <hidden>
Date: 2021-10-26 10:18:32
Also in: linux-iio, lkml

Hey Jonathan,

Thanks for the review. 
On Tue, 19 Oct 2021 16:20:46 +0100
Anand Ashok Dumbre [off-list ref] wrote:
quoted
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.
quoted
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 <redacted>
Signed-off-by: Anand Ashok Dumbre <redacted>
It would be good at some point to move away from of specific firmware
property reading, but on a platform like this I guess you can be fairly sure no
one will be using ACPI or other firmware description options so I'm not going
to insist on it for an initial merge.

Other comment I have are fairly minor but we need to leave some time for
other reviews and in particular DT binding review.
Sure. I will wait for the DT binding review before I send my fixes.
quoted
---
 drivers/iio/adc/Kconfig      |   13 +
 drivers/iio/adc/Makefile     |    1 +
 drivers/iio/adc/xilinx-ams.c | 1341
++++++++++++++++++++++++++++++++++
 3 files changed, 1355 insertions(+)
 create mode 100644 drivers/iio/adc/xilinx-ams.c
...
quoted
diff --git a/drivers/iio/adc/xilinx-ams.c
b/drivers/iio/adc/xilinx-ams.c new file mode 100644 index
000000000000..597cdda8a618
--- /dev/null
+++ b/drivers/iio/adc/xilinx-ams.c
@@ -0,0 +1,1341 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xilinx AMS driver
+ *
+ *  Copyright (C) 2021 Xilinx, Inc.
+ *
+ *  Manish Narani <mnarani@xilinx.com>
+ *  Rajnikant Bhojani <rajnikant.bhojani@xilinx.com>  */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
#include <linux/mod_devicetable.h> for the of_device_id structure
defintion.
Will add it in the next series.
quoted
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/iio/events.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
...
quoted
+/**
+ * struct ams - Driver data for xilinx-ams
+ * @base: physical base address of device
+ * @ps_base: physical base address of PS device
+ * @pl_base: physical base address of PL device
+ * @clk: clocks associated with the device
+ * @dev: pointer to device struct
+ * @lock: to handle multiple user interaction
+ * @intr_lock: to protect interrupt mask values
+ * @masked_alarm: currently masked due to alarm
+ * @alarm_mask: alarm configuration
+ * @interrupt_mask: interrupt configuration
+ * @irq: interrupt number of the sysmon
+ * @ams_unmask_work: re-enables event once the event condition
+disappears
+ *
+ * This structure contains necessary state for Sysmon driver to
+operate  */ struct ams {
+	void __iomem *base;
+	void __iomem *ps_base;
+	void __iomem *pl_base;
+	struct clk *clk;
+	struct device *dev;
+	/* check kernel doc above */
+	struct mutex lock;
+	/* check kernel doc above */
+	spinlock_t intr_lock;
+	unsigned int alarm_mask;
Docs should be same order as the fields.
quoted
+	unsigned int masked_alarm;
+	u64 intr_mask;
That's not the name in the docs.  Run kernel-doc script over this and fix all the
errors / warnings.
Will do it before I send next series.
Will fix the error.
quoted
+	int irq;
+	struct delayed_work ams_unmask_work; };
+
...
quoted
+
+static irqreturn_t ams_irq(int irq, void *data) {
+	struct iio_dev *indio_dev = data;
+	struct ams *ams = iio_priv(indio_dev);
+	u32 isr0;
+
+	spin_lock(&ams->intr_lock);
+
+	isr0 = readl(ams->base + AMS_ISR_0);
+
+	/* only process alarms that are not masked */
+	isr0 &= ~((ams->intr_mask & AMS_ISR0_ALARM_MASK) |
+ams->masked_alarm);
+
+	if (!isr0)
lock held.
Will fix.
quoted
+		return IRQ_NONE;
+
+	/* clear interrupt */
+	writel(isr0, ams->base + AMS_ISR_0);
+
+	/* Mask the alarm interrupts until 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->intr_lock);
+
+	return IRQ_HANDLED;
+}
+
...
quoted
+
+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, vol_ch_cnt = 0, temp_ch_cnt = 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)
+			goto err;
+
+		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)
+				goto err;
for_each_child_of_node() holds a reference if we jump out of the loop
before it terminates.
https://elixir.bootlin.com/linux/latest/source/drivers/of/base.c#L715
is where it ultimately releases that reference when the loop is terminating.
Her you need to do it manually with an of_node_put() call
Correct. Will fix this. 
quoted
+
+			num_channels += ret;
+		}
+	}
+
+	for (i = 0; i < num_channels; i++) {
+		if (ams_channels[i].type == IIO_VOLTAGE)
+			ams_channels[i].channel = vol_ch_cnt++;
+		else
+			ams_channels[i].channel = temp_ch_cnt++;
+
+		if (ams_channels[i].scan_index < (AMS_PS_SEQ_MAX * 3)) {
+			/* set threshold to max and min for each channel */
+			falling_off =
+
	ams_get_alarm_offset(ams_channels[i].scan_index,
quoted
+						     IIO_EV_DIR_FALLING);
+			rising_off =
+
	ams_get_alarm_offset(ams_channels[i].scan_index,
quoted
+						     IIO_EV_DIR_RISING);
+			if (ams_channels[i].scan_index >=
AMS_PS_SEQ_MAX) {
quoted
+				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) {
+		ret = -ENOMEM;
+		goto err;
+	}
We now have the option of devm_krealloc()   If you used that in conjunction
with devm_kzalloc to replace the kzalloc above, you could avoid this need to
copy.  Not important though if you prefer doing this manual version.
For now I will leave this as is. But will update after initial check-in.
quoted
+
+	memcpy(dev_channels, ams_channels,
+	       sizeof(*ams_channels) * num_channels);
+	indio_dev->channels = dev_channels;
+	indio_dev->num_channels = num_channels;
+
+	ret = 0;
+err:
+	kfree(ams_channels);
+
+	return ret;
+}
+
...
quoted
+static int ams_probe(struct platform_device *pdev) {
+	struct iio_dev *indio_dev;
+	struct ams *ams;
+	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->lock);
+	spin_lock_init(&ams->intr_lock);
+
+	indio_dev->name = "xilinx-ams";
+
+	indio_dev->info = &iio_ams_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	ams->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(ams->base))
+		return PTR_ERR(ams->base);
+
+	ams->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(ams->clk))
+		return PTR_ERR(ams->clk);
+	clk_prepare_enable(ams->clk);
+	devm_add_action_or_reset(&pdev->dev,
ams_clk_disable_unprepare,
quoted
+				 ams->clk);
+
+	INIT_DELAYED_WORK(&ams->ams_unmask_work,
ams_unmask_worker);
quoted
+	devm_add_action_or_reset(&pdev->dev,
ams_cancel_delayed_work,
quoted
+				 &ams->ams_unmask_work);
+
+	ret = ams_init_device(ams);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to initialize AMS\n");
+		return ret;
+	}
+
+	ret = ams_parse_dt(indio_dev, pdev);
+	if (ret) {
+		dev_err(&pdev->dev, "failure in parsing DT\n");
+		return ret;
+	}
+
+	ams_enable_channel_sequence(indio_dev);
+
+	ams->irq = platform_get_irq(pdev, 0);
+	if (ams->irq == -EPROBE_DEFER) {
+		ret = -EPROBE_DEFER;
+		return ret;
+	}
+
+	ret = devm_request_irq(&pdev->dev, ams->irq, &ams_irq, 0, "ams-
irq",
quoted
+			       indio_dev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to register interrupt\n");
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, indio_dev);
+
+	ret = devm_iio_device_register(&pdev->dev, indio_dev);
+
return devm_...
Will do.
quoted
+	return ret;
+}
+
Thanks,
Anand
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help