devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Arturs Artamonovs via B4 Relay <devnull+arturs.artamonovs.analog.com@kernel.org>
To: Catalin Marinas <catalin.marinas@arm.com>,
	 Will Deacon <will@kernel.org>,
	Greg Malysa <greg.malysa@timesys.com>,
	 Philipp Zabel <p.zabel@pengutronix.de>,
	Rob Herring <robh@kernel.org>,
	 Krzysztof Kozlowski <krzk+dt@kernel.org>,
	 Conor Dooley <conor+dt@kernel.org>,
	 Utsav Agarwal <Utsav.Agarwal@analog.com>,
	 Michael Turquette <mturquette@baylibre.com>,
	 Stephen Boyd <sboyd@kernel.org>,
	Linus Walleij <linus.walleij@linaro.org>,
	 Bartosz Golaszewski <brgl@bgdev.pl>,
	Thomas Gleixner <tglx@linutronix.de>,
	 Andi Shyti <andi.shyti@kernel.org>,
	 Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	 Jiri Slaby <jirislaby@kernel.org>, Arnd Bergmann <arnd@arndb.de>,
	 Olof Johansson <olof@lixom.net>,
	soc@kernel.org
Cc: linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org,  devicetree@vger.kernel.org,
	linux-clk@vger.kernel.org,  linux-gpio@vger.kernel.org,
	linux-i2c@vger.kernel.org,  linux-serial@vger.kernel.org,
	 Arturs Artamonovs <arturs.artamonovs@analog.com>,
	adsp-linux@analog.com,
	 Arturs Artamonovs <Arturs.Artamonovs@analog.com>,
	 Nathan Barrett-Morrison <nathan.morrison@timesys.com>
Subject: [PATCH 11/21] irqchip: Add irqchip for ADI ADSP-SC5xx platform
Date: Thu, 12 Sep 2024 19:24:56 +0100	[thread overview]
Message-ID: <20240912-test-v1-11-458fa57c8ccf@analog.com> (raw)
In-Reply-To: <20240912-test-v1-0-458fa57c8ccf@analog.com>

From: Arturs Artamonovs <arturs.artamonovs@analog.com>

Support seting extra indepdendent interrupt on pin activity.

Signed-off-by: Arturs Artamonovs <Arturs.Artamonovs@analog.com>
Co-developed-by: Nathan Barrett-Morrison <nathan.morrison@timesys.com>
Signed-off-by: Nathan Barrett-Morrison <nathan.morrison@timesys.com>
Co-developed-by: Greg Malysa <greg.malysa@timesys.com>
Signed-off-by: Greg Malysa <greg.malysa@timesys.com>
---
 drivers/irqchip/Kconfig        |   9 ++
 drivers/irqchip/Makefile       |   2 +
 drivers/irqchip/irq-adi-adsp.c | 310 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 321 insertions(+)

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index d078bdc48c38f13af9a129974f3b637dfee0e40f..1bc8f1bd45b3d2f69d2d0e6c8fa01b17b12ce241 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -91,6 +91,15 @@ config ALPINE_MSI
 	select PCI_MSI
 	select GENERIC_IRQ_CHIP
 
+config ADI_ADSP_IRQ
+	bool "ADI PORT PINT Driver"
+	depends on OF
+	depends on (ARCH_SC59X_64 || ARCH_SC59X)
+	select IRQ_DOMAIN
+	help
+	  Say Y to enable the PORT-based PINT interrupt controller for
+	  Analog Devices ADSP devices.
+
 config AL_FIC
 	bool "Amazon's Annapurna Labs Fabric Interrupt Controller"
 	depends on OF
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index 15635812b2d6605a2dd3bb0e5fb3170ab2cb0f77..258a188676fd97e713f3cebe16e3d563add095f3 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -1,6 +1,8 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_IRQCHIP)			+= irqchip.o
 
+
+obj-$(CONFIG_ADI_ADSP_IRQ)		+= irq-adi-adsp.o
 obj-$(CONFIG_AL_FIC)			+= irq-al-fic.o
 obj-$(CONFIG_ALPINE_MSI)		+= irq-alpine-msi.o
 obj-$(CONFIG_ATH79)			+= irq-ath79-cpu.o
diff --git a/drivers/irqchip/irq-adi-adsp.c b/drivers/irqchip/irq-adi-adsp.c
new file mode 100644
index 0000000000000000000000000000000000000000..75e10575ca80216b8baf5cb8daf1f62efae5f23b
--- /dev/null
+++ b/drivers/irqchip/irq-adi-adsp.c
@@ -0,0 +1,310 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * ADSP PINT PORT driver.
+ *
+ * The default mapping is used for all PINTs, refer to the HRM to identify
+ * PORT mapping to PINTs. For example, PINT0 has PORT B (0-15) and PORT A
+ * (16-31).
+ *
+ * Copyright (C) 2022-2024, Analog Devices, Inc.
+ */
+
+#include <linux/bitops.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdesc.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/soc/adi/adsp-gpio-port.h>
+
+#define ADSP_PINT_IRQS 32
+
+/* Register offsets in a single PINT */
+#define ADSP_PINT_REG_MASK_SET			0x00
+#define ADSP_PINT_REG_MASK_CLEAR		0x04
+#define ADSP_PINT_REG_REQUEST			0x08
+#define ADSP_PINT_REG_ASSIGN			0x0c
+#define ADSP_PINT_REG_EDGE_SET			0x10
+#define ADSP_PINT_REG_EDGE_CLEAR		0x14
+#define ADSP_PINT_REG_INVERT_SET		0x18
+#define ADSP_PINT_REG_INVERT_CLEAR		0x1c
+#define ADSP_PINT_REG_PINSTATE			0x20
+#define ADSP_PINT_REG_LATCH				0x24
+
+struct adsp_pint {
+	struct irq_chip chip;
+	void __iomem *regs;
+	struct irq_domain *domain;
+	unsigned int irq;
+};
+
+static struct adsp_pint *to_adsp_pint(struct irq_chip *chip)
+{
+	return container_of(chip, struct adsp_pint, chip);
+}
+
+/**
+ * Each gpio device should be connected to one of the two valid pints with an
+ * indicator of which half it is connected to:
+ *
+ * pint0 {
+ *   ...
+ * };
+ * gpa {
+ *   adi,pint = <&pint0 1>;
+ * };
+ * gpb {
+ *   adi,pint = <&pint0 0>;
+ * };
+ *
+ * This relies on the default configuration of the hardware, which we do not
+ * expose an interface to change.
+ */
+int adsp_attach_pint_to_gpio(struct adsp_gpio_port *port)
+{
+	struct platform_device *pint_pdev;
+	struct device_node *pint_node;
+	struct adsp_pint *pint;
+	struct of_phandle_args args;
+	int ret;
+
+	ret = of_parse_phandle_with_fixed_args(port->dev->of_node, "adi,pint", 1, 0,
+		&args);
+	if (ret) {
+		dev_err(port->dev, "Missing or invalid adi,pint connection for %pOFn; "
+			"attach a pint instance with one argument for port assignment\n",
+			port->dev->of_node);
+		return ret;
+	}
+
+	pint_node = args.np;
+
+	pint_pdev = of_find_device_by_node(pint_node);
+	if (!pint_pdev) {
+		ret = -EPROBE_DEFER;
+		goto cleanup;
+	}
+
+	pint = dev_get_drvdata(&pint_pdev->dev);
+	if (!pint) {
+		ret = -EPROBE_DEFER;
+		goto cleanup;
+	}
+
+	port->irq_domain = pint->domain;
+
+	if (args.args[0])
+		port->irq_offset = 16;
+	else
+		port->irq_offset = 0;
+
+cleanup:
+	of_node_put(pint_node);
+	return ret;
+}
+
+static void adsp_pint_dispatch_irq(struct irq_desc *desc)
+{
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	struct adsp_pint *pint = to_adsp_pint(chip);
+	unsigned int type = irqd_get_trigger_type(&desc->irq_data);
+	u32 pos = BIT(desc->irq_data.hwirq);
+
+	/* for both edge interrupt, toggle invert bit to catch next edge */
+	if (type == IRQ_TYPE_EDGE_BOTH) {
+		u32 invert = readl(pint->regs + ADSP_PINT_REG_INVERT_SET) & pos;
+
+		if (invert)
+			writel(pos, pint->regs + ADSP_PINT_REG_INVERT_CLEAR);
+		else
+			writel(pos, pint->regs + ADSP_PINT_REG_INVERT_SET);
+	}
+
+	writel(pos, pint->regs + ADSP_PINT_REG_REQUEST);
+
+	/* either edge is set */
+	if (type & IRQ_TYPE_EDGE_BOTH)
+		handle_edge_irq(desc);
+	else
+		handle_level_irq(desc);
+}
+
+static int adsp_pint_irq_map(struct irq_domain *domain, unsigned int irq,
+	irq_hw_number_t hwirq)
+{
+	struct adsp_pint *pint = domain->host_data;
+
+	irq_set_chip_data(irq, pint);
+	irq_set_chip_and_handler(irq, &pint->chip, adsp_pint_dispatch_irq);
+	return 0;
+}
+
+static const struct irq_domain_ops adsp_irq_domain_ops = {
+	.map = adsp_pint_irq_map,
+	.xlate = irq_domain_xlate_onecell,
+};
+
+/**
+ * This handles the GIC interrupt associated with this PINT being activated.
+ * It chains the interrupt associated with a particular pin
+ */
+static void adsp_pint_irq_handler(struct irq_desc *desc)
+{
+	struct adsp_pint *pint = irq_desc_get_handler_data(desc);
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	unsigned long req;
+	int pos;
+
+	chained_irq_enter(chip, desc);
+
+	req = readl(pint->regs + ADSP_PINT_REG_REQUEST);
+
+	for_each_set_bit(pos, &req, 32) {
+		unsigned int virq = irq_find_mapping(pint->domain, pos);
+
+		if (virq)
+			generic_handle_irq(virq);
+	}
+
+	chained_irq_exit(chip, desc);
+}
+
+static void adsp_pint_irq_ack(struct irq_data *d)
+{
+	/* this is required for edge type irqs unconditionally */
+}
+
+static void adsp_pint_irq_mask(struct irq_data *d)
+{
+	struct adsp_pint *pint = irq_data_get_irq_chip_data(d);
+
+	writel(BIT(d->hwirq), pint->regs + ADSP_PINT_REG_MASK_CLEAR);
+}
+
+static void adsp_pint_irq_unmask(struct irq_data *d)
+{
+	struct adsp_pint *pint = irq_data_get_irq_chip_data(d);
+
+	writel(BIT(d->hwirq), pint->regs + ADSP_PINT_REG_MASK_SET);
+}
+
+static int adsp_pint_irq_set_type(struct irq_data *d, unsigned int type)
+{
+	struct adsp_pint *pint = irq_data_get_irq_chip_data(d);
+	unsigned int pos = BIT(d->hwirq);
+
+	switch (type) {
+	case IRQ_TYPE_PROBE:
+		type = IRQ_TYPE_EDGE_BOTH;
+		fallthrough;
+	case IRQ_TYPE_EDGE_BOTH:
+		/* start by looking for rising edge */
+		writel(pos, pint->regs + ADSP_PINT_REG_INVERT_CLEAR);
+		writel(pos, pint->regs + ADSP_PINT_REG_EDGE_SET);
+		return 0;
+
+	case IRQ_TYPE_EDGE_FALLING:
+		writel(pos, pint->regs + ADSP_PINT_REG_INVERT_SET);
+		writel(pos, pint->regs + ADSP_PINT_REG_EDGE_SET);
+		return 0;
+
+	case IRQ_TYPE_EDGE_RISING:
+		writel(pos, pint->regs + ADSP_PINT_REG_INVERT_CLEAR);
+		writel(pos, pint->regs + ADSP_PINT_REG_EDGE_SET);
+		return 0;
+
+	case IRQ_TYPE_LEVEL_HIGH:
+		writel(pos, pint->regs + ADSP_PINT_REG_INVERT_CLEAR);
+		writel(pos, pint->regs + ADSP_PINT_REG_EDGE_CLEAR);
+		return 0;
+
+	case IRQ_TYPE_LEVEL_LOW:
+		writel(pos, pint->regs + ADSP_PINT_REG_INVERT_SET);
+		writel(pos, pint->regs + ADSP_PINT_REG_EDGE_CLEAR);
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+
+}
+
+static int adsp_pint_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	struct adsp_pint *pint;
+
+	pint = devm_kzalloc(dev, sizeof(*pint), GFP_KERNEL);
+	if (!pint)
+		return -ENOMEM;
+
+	pint->regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(pint->regs))
+		return PTR_ERR(pint->regs);
+
+	pint->chip.name = "adsp-pint";
+	pint->chip.irq_ack = adsp_pint_irq_ack;
+	pint->chip.irq_mask = adsp_pint_irq_mask;
+	pint->chip.irq_unmask = adsp_pint_irq_unmask;
+	pint->chip.irq_set_type = adsp_pint_irq_set_type;
+	// @todo potentially only SEC supports wake options, not gic
+
+	// @todo determine if we actually need a raw spinlock
+
+	pint->domain = irq_domain_add_linear(np, ADSP_PINT_IRQS,
+		&adsp_irq_domain_ops, pint);
+	if (!pint->domain) {
+		dev_err(dev, "Could not create irq domain\n");
+		return -EINVAL;
+	}
+
+	pint->irq = platform_get_irq(pdev, 0);
+	if (!pint->irq) {
+		dev_err(dev, "Could not find parent interrupt for port\n");
+		return -EINVAL;
+	}
+
+	irq_set_chained_handler_and_data(pint->irq, adsp_pint_irq_handler, pint);
+	platform_set_drvdata(pdev, pint);
+
+	return 0;
+}
+
+static void adsp_pint_remove(struct platform_device *pdev)
+{
+	struct adsp_pint *pint = platform_get_drvdata(pdev);
+
+	irq_set_chained_handler_and_data(pint->irq, NULL, NULL);
+	irq_domain_remove(pint->domain);
+}
+
+static const struct of_device_id adsp_pint_of_match[] = {
+	{ .compatible = "adi,adsp-pint" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, adsp_pint_of_match);
+
+static struct platform_driver adsp_pint_driver = {
+	.driver = {
+		.name = "adsp-port-pint",
+		.of_match_table = adsp_pint_of_match,
+	},
+	.probe = adsp_pint_probe,
+	.remove = adsp_pint_remove,
+};
+
+static int __init adsp_pint_init(void)
+{
+	return platform_driver_register(&adsp_pint_driver);
+}
+
+arch_initcall(adsp_pint_init);
+
+MODULE_DESCRIPTION("Analog Devices IRQChip driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Greg Malysa <greg.malysa@timesys.com>");
\ No newline at end of file

-- 
2.25.1



  parent reply	other threads:[~2024-09-12 18:20 UTC|newest]

Thread overview: 65+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-09-12 18:24 [PATCH 00/21] Adding support of ADI ARMv8 ADSP-SC598 SoC Arturs Artamonovs via B4 Relay
2024-09-12 18:24 ` [PATCH 01/21] arm64: Add ADI " Arturs Artamonovs via B4 Relay
2024-09-13  8:16   ` Arnd Bergmann
2024-09-13  9:54     ` Artamonovs, Arturs
2024-09-14 17:15   ` Markus Elfring
2024-09-14 17:56     ` Greg Kroah-Hartman
2024-09-16  6:42   ` Krzysztof Kozlowski
2024-09-12 18:24 ` [PATCH 02/21] reset: Add driver for ADI ADSP-SC5xx reset controller Arturs Artamonovs via B4 Relay
2024-09-13  7:22   ` Arnd Bergmann
2024-09-12 18:24 ` [PATCH 03/21] dt-bindigs: arm64: adi,sc598 bindings Arturs Artamonovs via B4 Relay
2024-09-13 22:05   ` Rob Herring
2024-09-16  6:44   ` Krzysztof Kozlowski
2024-09-12 18:24 ` [PATCH 04/21] dt-bindings: arm64: adi,sc598: Add ADSP-SC598 SoC bindings Arturs Artamonovs via B4 Relay
2024-09-16  6:45   ` Krzysztof Kozlowski
2024-09-12 18:24 ` [PATCH 05/21] clock:Add driver for ADI ADSP-SC5xx PLL Arturs Artamonovs via B4 Relay
2024-09-13  7:27   ` Arnd Bergmann
2024-09-16  6:46   ` Krzysztof Kozlowski
2024-09-12 18:24 ` [PATCH 06/21] include: dt-binding: clock: add adi clock header file Arturs Artamonovs via B4 Relay
2024-09-13  7:35   ` Arnd Bergmann
2024-09-16  6:47   ` Krzysztof Kozlowski
2024-09-16  6:48   ` Krzysztof Kozlowski
2024-09-12 18:24 ` [PATCH 07/21] clock: Add driver for ADI ADSP-SC5xx clock Arturs Artamonovs via B4 Relay
2024-09-14 14:18   ` kernel test robot
2024-09-12 18:24 ` [PATCH 08/21] dt-bindings: clock: adi,sc5xx-clocks: add bindings Arturs Artamonovs via B4 Relay
2024-09-13 22:06   ` Rob Herring
2024-09-12 18:24 ` [PATCH 09/21] gpio: add driver for ADI ADSP-SC5xx platform Arturs Artamonovs via B4 Relay
2024-09-13  7:38   ` Arnd Bergmann
2024-09-14 14:29   ` kernel test robot
2024-09-16  6:50   ` Krzysztof Kozlowski
2024-10-01 12:44   ` Linus Walleij
2024-10-01 14:29     ` Artamonovs, Arturs
2024-10-01 21:57     ` Greg Malysa
2024-10-02 13:53       ` Linus Walleij
2024-09-12 18:24 ` [PATCH 10/21] dt-bindings: gpio: adi,adsp-port-gpio: add bindings Arturs Artamonovs via B4 Relay
2024-09-16  6:53   ` Krzysztof Kozlowski
2024-09-12 18:24 ` Arturs Artamonovs via B4 Relay [this message]
2024-09-13 20:40   ` [PATCH 11/21] irqchip: Add irqchip for ADI ADSP-SC5xx platform kernel test robot
2024-09-16  6:56   ` Krzysztof Kozlowski
2024-10-02 10:29   ` Thomas Gleixner
2024-09-12 18:24 ` [PATCH 12/21] dt-bindings: irqchip: adi,adsp-pint: add binding Arturs Artamonovs via B4 Relay
2024-09-16  6:57   ` Krzysztof Kozlowski
2024-09-12 18:24 ` [PATCH 13/21] pinctrl: Add drivers for ADI ADSP-SC5xx platform Arturs Artamonovs via B4 Relay
2024-09-14  2:55   ` kernel test robot
2024-09-12 18:24 ` [PATCH 14/21] dt-bindings: pinctrl: adi,adsp-pinctrl: add bindings Arturs Artamonovs via B4 Relay
2024-09-13 22:09   ` Rob Herring
2024-09-12 18:25 ` [PATCH 15/21] i2c: Add driver for ADI ADSP-SC5xx platforms Arturs Artamonovs via B4 Relay
2024-09-13  7:59   ` Arnd Bergmann
2024-09-16  7:13   ` Krzysztof Kozlowski
2024-09-12 18:25 ` [PATCH 16/21] dt-bindings: i2c: add i2c/twi driver documentation Arturs Artamonovs via B4 Relay
2024-09-13  7:24   ` Arnd Bergmann
2024-09-12 18:25 ` [PATCH 17/21] serial: adi,uart: Add driver for ADI ADSP-SC5xx Arturs Artamonovs via B4 Relay
2024-09-12 18:25 ` [PATCH 18/21] dt-bindings: serial: adi,uart4: add adi,uart4 driver documentation Arturs Artamonovs via B4 Relay
2024-09-12 20:02   ` Rob Herring (Arm)
2024-09-13 14:06   ` Rob Herring
2024-09-12 18:25 ` [PATCH 19/21] arm64: dts: adi: sc598: add device tree Arturs Artamonovs via B4 Relay
2024-09-13  8:05   ` Arnd Bergmann
2024-09-16  7:04   ` Krzysztof Kozlowski
2024-09-12 18:25 ` [PATCH 20/21] arm64: defconfig: sc598 add minimal changes Arturs Artamonovs via B4 Relay
2024-09-13  7:44   ` Arnd Bergmann
2024-09-16  6:58   ` Krzysztof Kozlowski
2024-09-12 18:25 ` [PATCH 21/21] MAINTAINERS: add adi sc5xx maintainers Arturs Artamonovs via B4 Relay
2024-09-12 21:04 ` [PATCH 00/21] Adding support of ADI ARMv8 ADSP-SC598 SoC Rob Herring (Arm)
2024-09-16  6:57   ` Krzysztof Kozlowski
2024-09-13  8:20 ` Arnd Bergmann
2024-09-16  9:05 ` Krzysztof Kozlowski

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20240912-test-v1-11-458fa57c8ccf@analog.com \
    --to=devnull+arturs.artamonovs.analog.com@kernel.org \
    --cc=Utsav.Agarwal@analog.com \
    --cc=adsp-linux@analog.com \
    --cc=andi.shyti@kernel.org \
    --cc=arnd@arndb.de \
    --cc=arturs.artamonovs@analog.com \
    --cc=brgl@bgdev.pl \
    --cc=catalin.marinas@arm.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=greg.malysa@timesys.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=jirislaby@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=linus.walleij@linaro.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-clk@vger.kernel.org \
    --cc=linux-gpio@vger.kernel.org \
    --cc=linux-i2c@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-serial@vger.kernel.org \
    --cc=mturquette@baylibre.com \
    --cc=nathan.morrison@timesys.com \
    --cc=olof@lixom.net \
    --cc=p.zabel@pengutronix.de \
    --cc=robh@kernel.org \
    --cc=sboyd@kernel.org \
    --cc=soc@kernel.org \
    --cc=tglx@linutronix.de \
    --cc=will@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).