From 55dcc42c73d9d2c733972cd89bbbbb6d75b7d942 Mon Sep 17 00:00:00 2001
From: Romuald JEANNE <romuald.jeanne@st.com>
Date: Tue, 16 Mar 2021 09:12:12 +0100
Subject: [PATCH 11/22] ARM 5.10.10-stm32mp1-r1 MFD

Signed-off-by: Romuald JEANNE <romuald.jeanne@st.com>
---
 drivers/mfd/Kconfig              |  10 +
 drivers/mfd/Makefile             |   1 +
 drivers/mfd/stm32-pwr.c          | 402 +++++++++++++++++++++++++++++++
 drivers/mfd/stmfx.c              |   2 -
 drivers/mfd/stpmic1.c            |   6 +
 drivers/mfd/wm8994-core.c        |   6 +
 include/linux/mfd/stm32-timers.h |   1 +
 7 files changed, 426 insertions(+), 2 deletions(-)
 create mode 100644 drivers/mfd/stm32-pwr.c

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 4789507f325b..2917b4466f74 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2052,6 +2052,16 @@ config MFD_STPMIC1
 	  To compile this driver as a module, choose M here: the
 	  module will be called stpmic1.
 
+config MFD_STM32MP1_PWR
+	bool "STM32MP1 wake-up pins"
+	depends on MACH_STM32MP157
+	default y
+	help
+	  Select this option to enable STM32 PWR Wake-up pins driver.
+
+	  This driver provides interruptions that can be used to wake-up from
+	  suspend.
+
 config MFD_STMFX
 	tristate "Support for STMicroelectronics Multi-Function eXpander (STMFX)"
 	depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 1780019d2474..90ffa501ae2d 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -255,6 +255,7 @@ obj-$(CONFIG_MFD_SUN4I_GPADC)	+= sun4i-gpadc.o
 
 obj-$(CONFIG_MFD_STM32_LPTIMER)	+= stm32-lptimer.o
 obj-$(CONFIG_MFD_STM32_TIMERS) 	+= stm32-timers.o
+obj-$(CONFIG_MFD_STM32MP1_PWR)	+= stm32-pwr.o
 obj-$(CONFIG_MFD_MXS_LRADC)     += mxs-lradc.o
 obj-$(CONFIG_MFD_SC27XX_PMIC)	+= sprd-sc27xx-spi.o
 obj-$(CONFIG_RAVE_SP_CORE)	+= rave-sp.o
diff --git a/drivers/mfd/stm32-pwr.c b/drivers/mfd/stm32-pwr.c
new file mode 100644
index 000000000000..5c130603d554
--- /dev/null
+++ b/drivers/mfd/stm32-pwr.c
@@ -0,0 +1,402 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2017 - All Rights Reserved
+ * Author: Pascal Paillet <p.paillet@st.com> for STMicroelectronics.
+ */
+
+#include <linux/arm-smccc.h>
+#include <linux/gpio.h>
+#include <linux/irqchip.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <asm/exception.h>
+
+#define NB_WAKEUPPINS 6
+
+#define STM32_SVC_PWR 0x82001001
+#define STM32_WRITE 0x1
+#define STM32_SET_BITS 0x2
+#define STM32_CLEAR_BITS 0x3
+
+#define PWR_WKUP_OFFSET 0x20
+// PWR Registers
+#define WKUPCR 0x0
+#define WKUPFR 0x4
+#define MPUWKUPENR 0x8
+
+#define WKUP_FLAGS_MASK GENMASK(5, 0)
+
+// WKUPCR bits definition
+#define WKUP_EDGE_SHIFT 8
+#define WKUP_PULL_SHIFT 16
+#define WKUP_PULL_MASK GENMASK(1, 0)
+
+enum wkup_pull_setting {
+	WKUP_NO_PULL = 0,
+	WKUP_PULL_UP,
+	WKUP_PULL_DOWN,
+	WKUP_PULL_RESERVED
+};
+
+#define SMC(class, op, offset, val) do {				\
+	struct arm_smccc_res res;					\
+	arm_smccc_smc(class, op, PWR_WKUP_OFFSET + (offset), val,	\
+		      0, 0, 0, 0, &res);				\
+} while (0)								\
+
+struct stm32_pwr_data {
+	struct device *dev;
+	void __iomem *base;		/* IO Memory base address */
+	struct irq_domain *domain;	/* Domain for this controller */
+	int irq;			/* Parent interrupt */
+	u32 masked;			/* IRQ is masked */
+	u32 wake;			/* IRQ is wake on */
+	u32 pending;			/* IRQ has been received while wake on*/
+	struct gpio_desc *gpio[NB_WAKEUPPINS];
+};
+
+static void stm32_pwr_irq_ack(struct irq_data *d)
+{
+	struct stm32_pwr_data *priv = d->domain->host_data;
+
+	dev_dbg(priv->dev, "irq:%lu\n", d->hwirq);
+	SMC(STM32_SVC_PWR, STM32_SET_BITS, WKUPCR, BIT(d->hwirq));
+}
+
+static void stm32_pwr_irq_set_enable(struct irq_data *d)
+{
+	struct stm32_pwr_data *priv = d->domain->host_data;
+
+	dev_dbg(priv->dev, "irq:%lu\n", d->hwirq);
+	if (!(priv->masked & BIT(d->hwirq)) || (priv->wake & BIT(d->hwirq)))
+		SMC(STM32_SVC_PWR, STM32_SET_BITS, MPUWKUPENR, BIT(d->hwirq));
+	else
+		SMC(STM32_SVC_PWR, STM32_CLEAR_BITS, MPUWKUPENR, BIT(d->hwirq));
+}
+
+static void stm32_pwr_irq_mask(struct irq_data *d)
+{
+	struct stm32_pwr_data *priv = d->domain->host_data;
+
+	dev_dbg(priv->dev, "irq:%lu\n", d->hwirq);
+	priv->masked |= BIT(d->hwirq);
+	stm32_pwr_irq_set_enable(d);
+}
+
+static void stm32_pwr_irq_unmask(struct irq_data *d)
+{
+	struct stm32_pwr_data *priv = d->domain->host_data;
+
+	dev_dbg(priv->dev, "irq:%lu\n", d->hwirq);
+	priv->masked &= ~BIT(d->hwirq);
+	stm32_pwr_irq_set_enable(d);
+}
+
+static int stm32_pwr_irq_set_wake(struct irq_data *d, unsigned int on)
+{
+	struct stm32_pwr_data *priv = d->domain->host_data;
+	struct irq_data *parent = irq_get_irq_data(priv->irq);
+
+	dev_dbg(priv->dev, "irq:%lu on:%d\n", d->hwirq, on);
+	if (on) {
+		priv->wake |= BIT(d->hwirq);
+	} else {
+		priv->wake &= ~BIT(d->hwirq);
+		priv->pending &= ~BIT(d->hwirq);
+	}
+	stm32_pwr_irq_set_enable(d);
+
+	if (parent->chip && parent->chip->irq_set_wake)
+		return parent->chip->irq_set_wake(parent, on);
+
+	return 0;
+}
+
+static int stm32_pwr_irq_set_type(struct irq_data *d, unsigned int flow_type)
+{
+	struct stm32_pwr_data *priv = d->domain->host_data;
+	int pin_id = d->hwirq;
+	u32 wkupcr;
+	int en;
+
+	dev_dbg(priv->dev, "irq:%lu\n", d->hwirq);
+
+	en = readl_relaxed(priv->base + MPUWKUPENR) & BIT(pin_id);
+	/* reference manual request to disable the wakeup pin while
+	 * changing the edge detection setting
+	 */
+	if (en)
+		stm32_pwr_irq_mask(d);
+
+	wkupcr = readl_relaxed(priv->base + WKUPCR);
+	switch (flow_type & IRQ_TYPE_SENSE_MASK) {
+	case IRQF_TRIGGER_FALLING:
+		wkupcr |= (1 << (WKUP_EDGE_SHIFT + pin_id));
+		break;
+	case IRQF_TRIGGER_RISING:
+		wkupcr &= ~(1 << (WKUP_EDGE_SHIFT + pin_id));
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	SMC(STM32_SVC_PWR, STM32_WRITE, WKUPCR, wkupcr);
+
+	if (en)
+		stm32_pwr_irq_unmask(d);
+
+	return 0;
+}
+
+#ifdef CONFIG_SMP
+static int stm32_pwr_set_affinity_parent(struct irq_data *data,
+					 const struct cpumask *dest, bool force)
+{
+	struct stm32_pwr_data *priv = data->domain->host_data;
+	struct irq_data *parent = irq_get_irq_data(priv->irq);
+
+	irq_data_update_effective_affinity(data, dest);
+
+	if (parent->chip && parent->chip->irq_set_affinity)
+		return parent->chip->irq_set_affinity(parent, dest, force);
+
+	return IRQ_SET_MASK_OK_DONE;
+}
+#endif
+
+static int stm32_pwr_irq_request_resources(struct irq_data *d)
+{
+	struct stm32_pwr_data *priv = d->domain->host_data;
+	struct gpio_desc *gpio;
+	int ret;
+
+	dev_dbg(priv->dev, "irq:%lu\n", d->hwirq);
+	gpio = gpiod_get_index(priv->dev, "wakeup", d->hwirq, GPIOD_IN);
+	if (IS_ERR(gpio)) {
+		ret = PTR_ERR(gpio);
+		dev_err(priv->dev, "Failed to get wakeup gpio: %d", ret);
+		return ret;
+	}
+	priv->gpio[d->hwirq] = gpio;
+	return 0;
+}
+
+static void stm32_pwr_irq_release_resources(struct irq_data *d)
+{
+	struct stm32_pwr_data *priv = d->domain->host_data;
+
+	dev_dbg(priv->dev, "irq:%lu\n", d->hwirq);
+	gpiod_put(priv->gpio[d->hwirq]);
+}
+
+static struct irq_chip stm32_pwr_irq_chip = {
+	.name = "stm32-pwr-irq",
+	.irq_ack = stm32_pwr_irq_ack,
+	.irq_mask = stm32_pwr_irq_mask,
+	.irq_unmask = stm32_pwr_irq_unmask,
+	.irq_set_type = stm32_pwr_irq_set_type,
+	.irq_set_wake = stm32_pwr_irq_set_wake,
+	.irq_request_resources = stm32_pwr_irq_request_resources,
+	.irq_release_resources = stm32_pwr_irq_release_resources,
+#ifdef CONFIG_SMP
+	.irq_set_affinity = stm32_pwr_set_affinity_parent,
+#endif
+};
+
+static int stm32_pwr_irq_set_pull_config(struct irq_domain *d, int pin_id,
+					 enum wkup_pull_setting config)
+{
+	struct stm32_pwr_data *priv = d->host_data;
+	u32 wkupcr;
+
+	dev_dbg(priv->dev, "irq:%d pull config:0x%x\n", pin_id, config);
+
+	if (config >= WKUP_PULL_RESERVED) {
+		pr_err("%s: bad irq pull config\n", __func__);
+		return -EINVAL;
+	}
+
+	wkupcr = readl_relaxed(priv->base + WKUPCR);
+	wkupcr &= ~((WKUP_PULL_MASK) << (WKUP_PULL_SHIFT + pin_id * 2));
+	wkupcr |= (config & WKUP_PULL_MASK) << (WKUP_PULL_SHIFT + pin_id * 2);
+
+	SMC(STM32_SVC_PWR, STM32_WRITE, WKUPCR, wkupcr);
+
+	return 0;
+}
+
+static int stm32_pwr_xlate(struct irq_domain *d, struct device_node *ctrlr,
+			   const u32 *intspec, unsigned int intsize,
+			   irq_hw_number_t *out_hwirq, unsigned int *out_type)
+{
+	if (WARN_ON(intsize < 3)) {
+		pr_err("%s: bad irq config parameters\n", __func__);
+		return -EINVAL;
+	}
+
+	*out_hwirq = intspec[0];
+	*out_type = intspec[1] & (IRQ_TYPE_SENSE_MASK);
+
+	return stm32_pwr_irq_set_pull_config(d, intspec[0], intspec[2]);
+}
+
+static int stm32_pwr_alloc(struct irq_domain *d, unsigned int virq,
+			   unsigned int nr_irqs, void *data)
+{
+	struct irq_fwspec *fwspec = data;
+	irq_hw_number_t hwirq;
+
+	hwirq = fwspec->param[0];
+	irq_domain_set_info(d, virq, hwirq, &stm32_pwr_irq_chip, d->host_data,
+			    handle_edge_irq, NULL, NULL);
+
+	return 0;
+}
+
+static const struct irq_domain_ops stm32_pwr_irq_domain_ops = {
+	.alloc = stm32_pwr_alloc,
+	.xlate = stm32_pwr_xlate,
+	.free = irq_domain_free_irqs_common,
+};
+
+/*
+ * Handler for the cascaded IRQ.
+ */
+static void stm32_pwr_handle_irq(struct irq_desc *desc)
+{
+	struct stm32_pwr_data  *priv = irq_desc_get_handler_data(desc);
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	u32 wkupfr, wkupenr, i;
+
+	chained_irq_enter(chip, desc);
+
+	wkupfr = readl_relaxed(priv->base + WKUPFR);
+	wkupenr = readl_relaxed(priv->base + MPUWKUPENR);
+
+	for (i = 0; i < NB_WAKEUPPINS; i++) {
+		if ((wkupfr & BIT(i)) && (wkupenr & BIT(i))) {
+			struct irq_desc *d;
+
+			d = irq_to_desc(irq_find_mapping(priv->domain, i));
+
+			if (priv->wake & BIT(i)) {
+				dev_dbg(priv->dev,
+					"irq %d while wake enabled\n", i);
+				priv->pending |= BIT(i);
+			}
+
+			dev_dbg(priv->dev, "handle wkup irq:%d\n", i);
+			handle_edge_irq(d);
+		}
+	}
+	chained_irq_exit(chip, desc);
+}
+
+static int __maybe_unused stm32_pwr_suspend(struct device *dev)
+{
+	struct stm32_pwr_data *priv = dev_get_drvdata(dev);
+
+	pr_debug("suspend");
+	if (priv->pending != 0)
+		return -EBUSY;
+
+	return 0;
+}
+
+static const struct dev_pm_ops stm32_pwr_pm = {
+	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(stm32_pwr_suspend, NULL)
+};
+
+static int stm32_pwr_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct stm32_pwr_data *priv;
+	struct device_node *np = dev->of_node;
+	struct resource *res;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+	priv->dev = dev;
+	dev_set_drvdata(dev, priv);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	priv->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(priv->base)) {
+		dev_err(dev, "Unable to map registers\n");
+		return PTR_ERR(priv->base);
+	}
+
+	/* Disable all wake-up pins */
+	SMC(STM32_SVC_PWR, STM32_WRITE, MPUWKUPENR, 0);
+	/* Clear all interrupts flags */
+	SMC(STM32_SVC_PWR, STM32_SET_BITS, WKUPCR, WKUP_FLAGS_MASK);
+
+	priv->domain = irq_domain_add_linear(np, NB_WAKEUPPINS,
+					     &stm32_pwr_irq_domain_ops, priv);
+	if (!priv->domain) {
+		dev_err(dev, "%s: Unable to add irq domain!\n", __func__);
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	ret = irq_of_parse_and_map(np, 0);
+	if (ret < 0) {
+		dev_err(dev, "failed to get PWR IRQ\n");
+		ret = priv->irq;
+		goto out_domain;
+	}
+
+	priv->irq = ret;
+	irq_set_chained_handler_and_data(priv->irq, stm32_pwr_handle_irq, priv);
+
+	of_node_clear_flag(np, OF_POPULATED);
+
+	return 0;
+
+out_domain:
+	irq_domain_remove(priv->domain);
+out:
+	return ret;
+}
+
+static int stm32_pwr_remove(struct platform_device *pdev)
+{
+	struct stm32_pwr_data *priv = dev_get_drvdata(&pdev->dev);
+
+	irq_domain_remove(priv->domain);
+	return 0;
+}
+
+static const struct of_device_id stm32_pwr_ids[] = {
+	{ .compatible = "st,stm32mp1-pwr", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, stm32_pwr_ids);
+
+static struct platform_driver stm32_pwr_driver = {
+	.probe		= stm32_pwr_probe,
+	.remove		= stm32_pwr_remove,
+	.driver		= {
+		.name	= "stm32_pwr",
+		.of_match_table = stm32_pwr_ids,
+		.pm = &stm32_pwr_pm,
+	},
+};
+
+static int __init stm32_pwr_init(void)
+{
+	return platform_driver_register(&stm32_pwr_driver);
+}
+
+static void __exit stm32_pwr_exit(void)
+{
+	return platform_driver_unregister(&stm32_pwr_driver);
+}
+
+arch_initcall(stm32_pwr_init);
+module_exit(stm32_pwr_exit);
diff --git a/drivers/mfd/stmfx.c b/drivers/mfd/stmfx.c
index 988e2ba6dd0f..39b2fc952b7d 100644
--- a/drivers/mfd/stmfx.c
+++ b/drivers/mfd/stmfx.c
@@ -81,13 +81,11 @@ static struct mfd_cell stmfx_cells[] = {
 		.num_resources = ARRAY_SIZE(stmfx_pinctrl_resources),
 	},
 	{
-		.of_compatible = "st,stmfx-0300-idd",
 		.name = "stmfx-idd",
 		.resources = stmfx_idd_resources,
 		.num_resources = ARRAY_SIZE(stmfx_idd_resources),
 	},
 	{
-		.of_compatible = "st,stmfx-0300-ts",
 		.name = "stmfx-ts",
 		.resources = stmfx_ts_resources,
 		.num_resources = ARRAY_SIZE(stmfx_ts_resources),
diff --git a/drivers/mfd/stpmic1.c b/drivers/mfd/stpmic1.c
index eb3da558c3fb..40eef5d18b90 100644
--- a/drivers/mfd/stpmic1.c
+++ b/drivers/mfd/stpmic1.c
@@ -170,6 +170,9 @@ static int stpmic1_suspend(struct device *dev)
 
 	disable_irq(pmic_dev->irq);
 
+	if (device_may_wakeup(dev))
+		enable_irq_wake(pmic_dev->irq);
+
 	return 0;
 }
 
@@ -183,6 +186,9 @@ static int stpmic1_resume(struct device *dev)
 	if (ret)
 		return ret;
 
+	if (device_may_wakeup(dev))
+		disable_irq_wake(pmic_dev->irq);
+
 	enable_irq(pmic_dev->irq);
 
 	return 0;
diff --git a/drivers/mfd/wm8994-core.c b/drivers/mfd/wm8994-core.c
index 3b2b93c5bbcb..507572b091ab 100644
--- a/drivers/mfd/wm8994-core.c
+++ b/drivers/mfd/wm8994-core.c
@@ -185,6 +185,12 @@ static int wm8994_resume(struct device *dev)
 	if (!wm8994->suspended)
 		return 0;
 
+	/*
+	 * LDO1/2 minimum cycle time is 36ms according to codec specification
+	 * Wait before enabling regulator to make sure we fit this requirement
+	 */
+	msleep(40);
+
 	ret = regulator_bulk_enable(wm8994->num_supplies,
 				    wm8994->supplies);
 	if (ret != 0) {
diff --git a/include/linux/mfd/stm32-timers.h b/include/linux/mfd/stm32-timers.h
index f8db83aedb2b..f48f04dc4187 100644
--- a/include/linux/mfd/stm32-timers.h
+++ b/include/linux/mfd/stm32-timers.h
@@ -31,6 +31,7 @@
 #define TIM_BDTR	0x44	/* Break and Dead-Time Reg */
 #define TIM_DCR		0x48	/* DMA control register    */
 #define TIM_DMAR	0x4C	/* DMA register for transfer */
+#define TIM_TISEL	0x68	/* Input Selection         */
 
 #define TIM_CR1_CEN	BIT(0)	/* Counter Enable	   */
 #define TIM_CR1_DIR	BIT(4)  /* Counter Direction	   */
-- 
2.17.1

