2018-07-05 15:17:13

by Pascal Paillet

[permalink] [raw]
Subject: [PATCH 0/8] Introduce STPMU1 PMIC Driver

From: pascal paillet <[email protected]>

The goal of this patch-set is to propose a driver for the STPMU1 PMIC from
ST Microelectronics.
The STPMU1 regulators supply power to an application processor as well as
to external system peripherals such as DDR, Flash memories and system
devices. It also features onkey button input and an hardware watchdog.
The STPMU1 is controlled via I2C.

Main driver is drivers/mfd/stpmu1 that handle I2C regmap configuration and
irqchip. stpmu1_regulator, stpmu1_onkey and stpmu1_wdt need stpmu1 mfd
as parent.

stpmu1 mfd regulator drivers maybe mandatory at boot time.

*** BLURB HERE ***

pascal paillet (8):
dt-bindings: mfd: document stpmu1 pmic
mfd: stpmu1: add stpmu1 pmic driver
dt-bindings: regulator: document stpmu1 pmic regulators
regulator: stpmu1: add stpmu1 regulator driver
dt-bindings: input: document stpmu1 pmic onkey
input: stpmu1: add stpmu1 onkey driver
dt-bindings: watchdog: document stpmu1 pmic watchdog
watchdog: stpmu1: add stpmu1 watchdog driver

.../devicetree/bindings/input/st,stpmu1-onkey.txt | 31 +
.../devicetree/bindings/mfd/st,stpmu1.txt | 138 ++++
.../bindings/regulator/st,stpmu1-regulator.txt | 72 ++
.../devicetree/bindings/watchdog/st,stpmu1-wdt.txt | 11 +
drivers/input/misc/Kconfig | 11 +
drivers/input/misc/Makefile | 2 +
drivers/input/misc/stpmu1_onkey.c | 321 +++++++
drivers/mfd/Kconfig | 14 +
drivers/mfd/Makefile | 1 +
drivers/mfd/stpmu1.c | 490 +++++++++++
drivers/regulator/Kconfig | 12 +
drivers/regulator/Makefile | 2 +
drivers/regulator/stpmu1_regulator.c | 919 +++++++++++++++++++++
drivers/watchdog/Kconfig | 12 +
drivers/watchdog/Makefile | 1 +
drivers/watchdog/stpmu1_wdt.c | 177 ++++
include/dt-bindings/mfd/st,stpmu1.h | 46 ++
include/linux/mfd/stpmu1.h | 220 +++++
18 files changed, 2480 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/st,stpmu1-onkey.txt
create mode 100644 Documentation/devicetree/bindings/mfd/st,stpmu1.txt
create mode 100644 Documentation/devicetree/bindings/regulator/st,stpmu1-regulator.txt
create mode 100644 Documentation/devicetree/bindings/watchdog/st,stpmu1-wdt.txt
create mode 100644 drivers/input/misc/stpmu1_onkey.c
create mode 100644 drivers/mfd/stpmu1.c
create mode 100644 drivers/regulator/stpmu1_regulator.c
create mode 100644 drivers/watchdog/stpmu1_wdt.c
create mode 100644 include/dt-bindings/mfd/st,stpmu1.h
create mode 100644 include/linux/mfd/stpmu1.h

--
1.9.1


2018-07-05 15:16:39

by Pascal Paillet

[permalink] [raw]
Subject: [PATCH 8/8] watchdog: stpmu1: add stpmu1 watchdog driver

From: pascal paillet <[email protected]>

The stpmu1 PMIC embeds a watchdog which is disabled by default. As soon
as the watchdog is started, it must be refreshed periodically otherwise
the PMIC goes off.

Signed-off-by: pascal paillet <[email protected]>
---
drivers/watchdog/Kconfig | 12 +++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/stpmu1_wdt.c | 177 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 190 insertions(+)
create mode 100644 drivers/watchdog/stpmu1_wdt.c

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 9af07fd..2155f4d 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -796,6 +796,18 @@ config STM32_WATCHDOG
To compile this driver as a module, choose M here: the
module will be called stm32_iwdg.

+config STPMU1_WATCHDOG
+ tristate "STPMU1 PMIC watchdog support"
+ depends on MFD_STPMU1
+ select WATCHDOG_CORE
+ help
+ Say Y here to include watchdog support embedded into STPMU1 PMIC.
+ If the watchdog timer expires, stpmu1 shut-down all its power
+ supplies.
+
+ To compile this driver as a module, choose M here: the
+ module will be called spmu1_wdt.
+
config UNIPHIER_WATCHDOG
tristate "UniPhier watchdog support"
depends on ARCH_UNIPHIER || COMPILE_TEST
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 1d3c6b0..c9eba94 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -216,3 +216,4 @@ obj-$(CONFIG_ZIIRAVE_WATCHDOG) += ziirave_wdt.o
obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o
obj-$(CONFIG_RAVE_SP_WATCHDOG) += rave-sp-wdt.o
+obj-$(CONFIG_STPMU1_WATCHDOG) += stpmu1_wdt.o
diff --git a/drivers/watchdog/stpmu1_wdt.c b/drivers/watchdog/stpmu1_wdt.c
new file mode 100644
index 0000000..57e0afa
--- /dev/null
+++ b/drivers/watchdog/stpmu1_wdt.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Philippe Peurichard <[email protected]>,
+ * Pascal Paillet <[email protected]> for STMicroelectronics.
+ */
+
+#include <linux/kernel.h>
+#include <linux/mfd/stpmu1.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+#include <linux/watchdog.h>
+
+/* WATCHDOG CONTROL REGISTER bit */
+#define WDT_START BIT(0)
+#define WDT_PING BIT(1)
+#define WDT_START_MASK BIT(0)
+#define WDT_PING_MASK BIT(1)
+
+#define PMIC_WDT_MIN_TIMEOUT 1
+#define PMIC_WDT_MAX_TIMEOUT 256
+
+struct stpmu1_wdt {
+ struct stpmu1_dev *pmic;
+ struct watchdog_device wdtdev;
+ struct notifier_block restart_handler;
+};
+
+static int pmic_wdt_start(struct watchdog_device *wdd)
+{
+ struct stpmu1_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ return regmap_update_bits(wdt->pmic->regmap,
+ WCHDG_CR, WDT_START_MASK, WDT_START);
+}
+
+static int pmic_wdt_stop(struct watchdog_device *wdd)
+{
+ struct stpmu1_wdt *wdt = watchdog_get_drvdata(wdd);
+
+ return regmap_update_bits(wdt->pmic->regmap,
+ WCHDG_CR, WDT_START_MASK, ~WDT_START);
+}
+
+static int pmic_wdt_ping(struct watchdog_device *wdd)
+{
+ struct stpmu1_wdt *wdt = watchdog_get_drvdata(wdd);
+ int ret;
+
+ return regmap_update_bits(wdt->pmic->regmap,
+ WCHDG_CR, WDT_PING_MASK, WDT_PING);
+ return ret;
+}
+
+static int pmic_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct stpmu1_wdt *wdt = watchdog_get_drvdata(wdd);
+ int ret;
+
+ ret = regmap_write(wdt->pmic->regmap, WCHDG_TIMER_CR, timeout);
+ if (ret)
+ dev_err(wdt->pmic->dev,
+ "Failed to set watchdog timeout (err = %d)\n", ret);
+ else
+ wdd->timeout = PMIC_WDT_MAX_TIMEOUT;
+
+ return ret;
+}
+
+static int pmic_wdt_restart_handler(struct notifier_block *this,
+ unsigned long mode, void *cmd)
+{
+ struct stpmu1_wdt *wdt = container_of(this,
+ struct stpmu1_wdt,
+ restart_handler);
+
+ dev_info(wdt->pmic->dev,
+ "PMIC Watchdog Elapsed (timeout %d), shutdown of PMIC initiated\n",
+ wdt->wdtdev.timeout);
+
+ return NOTIFY_DONE;
+}
+
+static const struct watchdog_info pmic_watchdog_info = {
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+ .identity = "STPMU1 PMIC Watchdog",
+};
+
+static const struct watchdog_ops pmic_watchdog_ops = {
+ .owner = THIS_MODULE,
+ .start = pmic_wdt_start,
+ .stop = pmic_wdt_stop,
+ .ping = pmic_wdt_ping,
+ .set_timeout = pmic_wdt_set_timeout,
+};
+
+static int pmic_wdt_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct stpmu1_dev *pmic;
+ struct stpmu1_wdt *wdt;
+
+ if (!pdev->dev.parent)
+ return -EINVAL;
+
+ pmic = dev_get_drvdata(pdev->dev.parent);
+ if (!pmic)
+ return -EINVAL;
+
+ wdt = devm_kzalloc(&pdev->dev, sizeof(struct stpmu1_wdt), GFP_KERNEL);
+ if (!wdt)
+ return -ENOMEM;
+
+ wdt->pmic = pmic;
+
+ wdt->wdtdev.info = &pmic_watchdog_info;
+ wdt->wdtdev.ops = &pmic_watchdog_ops;
+ wdt->wdtdev.min_timeout = PMIC_WDT_MIN_TIMEOUT;
+ wdt->wdtdev.max_timeout = PMIC_WDT_MAX_TIMEOUT;
+ wdt->wdtdev.timeout = PMIC_WDT_MAX_TIMEOUT;
+
+ wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
+
+ watchdog_set_drvdata(&wdt->wdtdev, wdt);
+ dev_set_drvdata(&pdev->dev, wdt);
+
+ ret = watchdog_register_device(&wdt->wdtdev);
+ if (ret)
+ return ret;
+
+ wdt->restart_handler.notifier_call = pmic_wdt_restart_handler;
+ wdt->restart_handler.priority = 128;
+ ret = register_restart_handler(&wdt->restart_handler);
+ if (ret) {
+ dev_err(wdt->pmic->dev, "failed to register restart handler\n");
+ return ret;
+ }
+
+ dev_dbg(wdt->pmic->dev, "PMIC Watchdog driver probed\n");
+ return 0;
+}
+
+static int pmic_wdt_remove(struct platform_device *pdev)
+{
+ struct stpmu1_wdt *wdt = dev_get_drvdata(&pdev->dev);
+
+ unregister_restart_handler(&wdt->restart_handler);
+ watchdog_unregister_device(&wdt->wdtdev);
+
+ return 0;
+}
+
+static const struct of_device_id of_pmic_wdt_match[] = {
+ { .compatible = "st,stpmu1-wdt" },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, of_pmic_wdt_match);
+
+static struct platform_driver stpmu1_wdt_driver = {
+ .probe = pmic_wdt_probe,
+ .remove = pmic_wdt_remove,
+ .driver = {
+ .name = "stpmu1-wdt",
+ .of_match_table = of_pmic_wdt_match,
+ },
+};
+module_platform_driver(stpmu1_wdt_driver);
+
+MODULE_AUTHOR("[email protected]>");
+MODULE_DESCRIPTION("Watchdog driver for STPMU1 device");
+MODULE_LICENSE("GPL");
--
1.9.1

2018-07-05 15:17:00

by Pascal Paillet

[permalink] [raw]
Subject: [PATCH 3/8] dt-bindings: regulator: document stpmu1 pmic regulators

From: pascal paillet <[email protected]>

The STPMU1 regulators supply power to the application processor as well as
to the external system peripherals such as DDR, Flash memories and system
devices.

Signed-off-by: pascal paillet <[email protected]>
---
.../bindings/regulator/st,stpmu1-regulator.txt | 72 ++++++++++++++++++++++
1 file changed, 72 insertions(+)
create mode 100644 Documentation/devicetree/bindings/regulator/st,stpmu1-regulator.txt

diff --git a/Documentation/devicetree/bindings/regulator/st,stpmu1-regulator.txt b/Documentation/devicetree/bindings/regulator/st,stpmu1-regulator.txt
new file mode 100644
index 0000000..888e759
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/st,stpmu1-regulator.txt
@@ -0,0 +1,72 @@
+STMicroelectronics STPMU1 Voltage regulators
+
+Regulator Nodes are optional depending on needs.
+
+Available Regulators in STPMU1 device are:
+ - buck1 for Buck BUCK1
+ - buck2 for Buck BUCK2
+ - buck3 for Buck BUCK3
+ - buck4 for Buck BUCK4
+ - ldo1 for LDO LDO1
+ - ldo2 for LDO LDO2
+ - ldo3 for LDO LDO3
+ - ldo4 for LDO LDO4
+ - ldo5 for LDO LDO5
+ - ldo6 for LDO LDO6
+ - vref_ddr for LDO Vref DDR
+ - boost for Buck BOOST
+ - pwr_sw1 for VBUS_OTG switch
+ - pwr_sw2 for SW_OUT switch
+
+Switches are fixed voltage regulators with only enable/disable capability.
+
+Optional properties:
+- st,mask_reset: stay on during Reset for particular regulator
+- regulator-pull-down: enable high pull down
+ if not specified light pull down is used
+- regulator-over-current-protection:
+ if set, all regulators are switched off in case of over-current detection
+ on this regulator,
+ if not set, the driver only send an over-current event.
+- interrupt-parent: phandle to the parent interrupt controller
+- interrupts: index of current limit detection interrupt
+- <regulator>-supply: phandle to the parent supply/regulator node
+ each regulator supply can be described except vref_ddr.
+
+Example:
+regulators {
+ compatible = "st,stpmu1-regulators";
+
+ ldo6-supply = <&v3v3>;
+
+ vdd_core: regulator@0 {
+ regulator-compatible = "buck1";
+ regulator-name = "vdd_core";
+ interrupts = <IT_CURLIM_BUCK1 0>;
+ interrupt-parent = <&pmic>;
+ st,mask_reset;
+ regulator-pull-down;
+ st,pulldown_config = <2>;
+ regulator-min-microvolt = <700000>;
+ regulator-max-microvolt = <1200000>;
+ };
+
+ v3v3: buck4 {
+ regulator-compatible = "buck4";
+ regulator-name = "v3v3";
+ interrupts = <IT_CURLIM_BUCK4 0>;
+ interrupt-parent = <&mypmic>;
+
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ };
+
+ v1v8: ldo6 {
+ regulator-compatible = "ldo6";
+ regulator-name = "v1v8";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-over-current-protection;
+ };
+
+};
--
1.9.1

2018-07-05 15:17:31

by Pascal Paillet

[permalink] [raw]
Subject: [PATCH 2/8] mfd: stpmu1: add stpmu1 pmic driver

From: pascal paillet <[email protected]>

stpmu1 is a pmic from STMicroelectronics. The stpmu1 integrates 10
regulators and 3 switches with various capabilities.

Signed-off-by: pascal paillet <[email protected]>
---
drivers/mfd/Kconfig | 14 ++
drivers/mfd/Makefile | 1 +
drivers/mfd/stpmu1.c | 490 ++++++++++++++++++++++++++++++++++++
include/dt-bindings/mfd/st,stpmu1.h | 46 ++++
include/linux/mfd/stpmu1.h | 220 ++++++++++++++++
5 files changed, 771 insertions(+)
create mode 100644 drivers/mfd/stpmu1.c
create mode 100644 include/dt-bindings/mfd/st,stpmu1.h
create mode 100644 include/linux/mfd/stpmu1.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index b860eb5..e15140b 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1812,6 +1812,20 @@ config MFD_STM32_TIMERS
for PWM and IIO Timer. This driver allow to share the
registers between the others drivers.

+config MFD_STPMU1
+ tristate "Support for STPMU1 PMIC"
+ depends on (I2C=y && OF)
+ select REGMAP_I2C
+ select REGMAP_IRQ
+ select MFD_CORE
+ help
+ Support for ST Microelectronics STPMU1 PMIC. Stpmu1 mfd driver is
+ the core driver for stpmu1 component that mainly handles interrupts.
+
+ To compile this driver as a module, choose M here: the
+ module will be called stpmu1.
+
+
menu "Multimedia Capabilities Port drivers"
depends on ARCH_SA1100

diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index e9fd20d..f1c4be1 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -220,6 +220,7 @@ obj-$(CONFIG_INTEL_SOC_PMIC_CHTDC_TI) += intel_soc_pmic_chtdc_ti.o
obj-$(CONFIG_MFD_MT6397) += mt6397-core.o

obj-$(CONFIG_MFD_ALTERA_A10SR) += altera-a10sr.o
+obj-$(CONFIG_MFD_STPMU1) += stpmu1.o
obj-$(CONFIG_MFD_SUN4I_GPADC) += sun4i-gpadc.o

obj-$(CONFIG_MFD_STM32_LPTIMER) += stm32-lptimer.o
diff --git a/drivers/mfd/stpmu1.c b/drivers/mfd/stpmu1.c
new file mode 100644
index 0000000..a284a3e
--- /dev/null
+++ b/drivers/mfd/stpmu1.c
@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Philippe Peurichard <[email protected]>,
+ * Pascal Paillet <[email protected]> for STMicroelectronics.
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/stpmu1.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/pm_runtime.h>
+#include <linux/pm_wakeirq.h>
+#include <linux/regmap.h>
+#include <dt-bindings/mfd/st,stpmu1.h>
+
+static bool stpmu1_reg_readable(struct device *dev, unsigned int reg);
+static bool stpmu1_reg_writeable(struct device *dev, unsigned int reg);
+static bool stpmu1_reg_volatile(struct device *dev, unsigned int reg);
+
+const struct regmap_config stpmu1_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .cache_type = REGCACHE_RBTREE,
+ .max_register = PMIC_MAX_REGISTER_ADDRESS,
+ .readable_reg = stpmu1_reg_readable,
+ .writeable_reg = stpmu1_reg_writeable,
+ .volatile_reg = stpmu1_reg_volatile,
+};
+
+#define FILL_IRQS(_index) \
+ [(_index)] = { \
+ .reg_offset = ((_index) >> 3), \
+ .mask = (1 << (_index % 8)), \
+ }
+
+static const struct regmap_irq stpmu1_irqs[] = {
+ FILL_IRQS(IT_PONKEY_F),
+ FILL_IRQS(IT_PONKEY_R),
+ FILL_IRQS(IT_WAKEUP_F),
+ FILL_IRQS(IT_WAKEUP_R),
+ FILL_IRQS(IT_VBUS_OTG_F),
+ FILL_IRQS(IT_VBUS_OTG_R),
+ FILL_IRQS(IT_SWOUT_F),
+ FILL_IRQS(IT_SWOUT_R),
+
+ FILL_IRQS(IT_CURLIM_BUCK1),
+ FILL_IRQS(IT_CURLIM_BUCK2),
+ FILL_IRQS(IT_CURLIM_BUCK3),
+ FILL_IRQS(IT_CURLIM_BUCK4),
+ FILL_IRQS(IT_OCP_OTG),
+ FILL_IRQS(IT_OCP_SWOUT),
+ FILL_IRQS(IT_OCP_BOOST),
+ FILL_IRQS(IT_OVP_BOOST),
+
+ FILL_IRQS(IT_CURLIM_LDO1),
+ FILL_IRQS(IT_CURLIM_LDO2),
+ FILL_IRQS(IT_CURLIM_LDO3),
+ FILL_IRQS(IT_CURLIM_LDO4),
+ FILL_IRQS(IT_CURLIM_LDO5),
+ FILL_IRQS(IT_CURLIM_LDO6),
+ FILL_IRQS(IT_SHORT_SWOTG),
+ FILL_IRQS(IT_SHORT_SWOUT),
+
+ FILL_IRQS(IT_TWARN_F),
+ FILL_IRQS(IT_TWARN_R),
+ FILL_IRQS(IT_VINLOW_F),
+ FILL_IRQS(IT_VINLOW_R),
+ FILL_IRQS(IT_SWIN_F),
+ FILL_IRQS(IT_SWIN_R),
+};
+
+static const struct regmap_irq_chip stpmu1_regmap_irq_chip = {
+ .name = "pmic_irq",
+ .status_base = INT_PENDING_R1,
+ .mask_base = INT_CLEAR_MASK_R1,
+ .unmask_base = INT_SET_MASK_R1,
+ .ack_base = INT_CLEAR_R1,
+ .num_regs = STPMU1_PMIC_NUM_IRQ_REGS,
+ .irqs = stpmu1_irqs,
+ .num_irqs = ARRAY_SIZE(stpmu1_irqs),
+};
+
+static bool stpmu1_reg_readable(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TURN_ON_SR:
+ case TURN_OFF_SR:
+ case ICC_LDO_TURN_OFF_SR:
+ case ICC_BUCK_TURN_OFF_SR:
+ case RREQ_STATE_SR:
+ case VERSION_SR:
+ case SWOFF_PWRCTRL_CR:
+ case PADS_PULL_CR:
+ case BUCKS_PD_CR:
+ case LDO14_PD_CR:
+ case LDO56_VREF_PD_CR:
+ case VBUS_DET_VIN_CR:
+ case PKEY_TURNOFF_CR:
+ case BUCKS_MASK_RANK_CR:
+ case BUCKS_MASK_RESET_CR:
+ case LDOS_MASK_RANK_CR:
+ case LDOS_MASK_RESET_CR:
+ case WCHDG_CR:
+ case WCHDG_TIMER_CR:
+ case BUCKS_ICCTO_CR:
+ case LDOS_ICCTO_CR:
+ case BUCK1_ACTIVE_CR:
+ case BUCK2_ACTIVE_CR:
+ case BUCK3_ACTIVE_CR:
+ case BUCK4_ACTIVE_CR:
+ case VREF_DDR_ACTIVE_CR:
+ case LDO1_ACTIVE_CR:
+ case LDO2_ACTIVE_CR:
+ case LDO3_ACTIVE_CR:
+ case LDO4_ACTIVE_CR:
+ case LDO5_ACTIVE_CR:
+ case LDO6_ACTIVE_CR:
+ case BUCK1_STDBY_CR:
+ case BUCK2_STDBY_CR:
+ case BUCK3_STDBY_CR:
+ case BUCK4_STDBY_CR:
+ case VREF_DDR_STDBY_CR:
+ case LDO1_STDBY_CR:
+ case LDO2_STDBY_CR:
+ case LDO3_STDBY_CR:
+ case LDO4_STDBY_CR:
+ case LDO5_STDBY_CR:
+ case LDO6_STDBY_CR:
+ case BST_SW_CR:
+ case INT_PENDING_R1:
+ case INT_PENDING_R2:
+ case INT_PENDING_R3:
+ case INT_PENDING_R4:
+ case INT_DBG_LATCH_R1:
+ case INT_DBG_LATCH_R2:
+ case INT_DBG_LATCH_R3:
+ case INT_DBG_LATCH_R4:
+ case INT_CLEAR_R1:
+ case INT_CLEAR_R2:
+ case INT_CLEAR_R3:
+ case INT_CLEAR_R4:
+ case INT_MASK_R1:
+ case INT_MASK_R2:
+ case INT_MASK_R3:
+ case INT_MASK_R4:
+ case INT_SET_MASK_R1:
+ case INT_SET_MASK_R2:
+ case INT_SET_MASK_R3:
+ case INT_SET_MASK_R4:
+ case INT_CLEAR_MASK_R1:
+ case INT_CLEAR_MASK_R2:
+ case INT_CLEAR_MASK_R3:
+ case INT_CLEAR_MASK_R4:
+ case INT_SRC_R1:
+ case INT_SRC_R2:
+ case INT_SRC_R3:
+ case INT_SRC_R4:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool stpmu1_reg_writeable(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SWOFF_PWRCTRL_CR:
+ case PADS_PULL_CR:
+ case BUCKS_PD_CR:
+ case LDO14_PD_CR:
+ case LDO56_VREF_PD_CR:
+ case VBUS_DET_VIN_CR:
+ case PKEY_TURNOFF_CR:
+ case BUCKS_MASK_RANK_CR:
+ case BUCKS_MASK_RESET_CR:
+ case LDOS_MASK_RANK_CR:
+ case LDOS_MASK_RESET_CR:
+ case WCHDG_CR:
+ case WCHDG_TIMER_CR:
+ case BUCKS_ICCTO_CR:
+ case LDOS_ICCTO_CR:
+ case BUCK1_ACTIVE_CR:
+ case BUCK2_ACTIVE_CR:
+ case BUCK3_ACTIVE_CR:
+ case BUCK4_ACTIVE_CR:
+ case VREF_DDR_ACTIVE_CR:
+ case LDO1_ACTIVE_CR:
+ case LDO2_ACTIVE_CR:
+ case LDO3_ACTIVE_CR:
+ case LDO4_ACTIVE_CR:
+ case LDO5_ACTIVE_CR:
+ case LDO6_ACTIVE_CR:
+ case BUCK1_STDBY_CR:
+ case BUCK2_STDBY_CR:
+ case BUCK3_STDBY_CR:
+ case BUCK4_STDBY_CR:
+ case VREF_DDR_STDBY_CR:
+ case LDO1_STDBY_CR:
+ case LDO2_STDBY_CR:
+ case LDO3_STDBY_CR:
+ case LDO4_STDBY_CR:
+ case LDO5_STDBY_CR:
+ case LDO6_STDBY_CR:
+ case BST_SW_CR:
+ case INT_DBG_LATCH_R1:
+ case INT_DBG_LATCH_R2:
+ case INT_DBG_LATCH_R3:
+ case INT_DBG_LATCH_R4:
+ case INT_CLEAR_R1:
+ case INT_CLEAR_R2:
+ case INT_CLEAR_R3:
+ case INT_CLEAR_R4:
+ case INT_SET_MASK_R1:
+ case INT_SET_MASK_R2:
+ case INT_SET_MASK_R3:
+ case INT_SET_MASK_R4:
+ case INT_CLEAR_MASK_R1:
+ case INT_CLEAR_MASK_R2:
+ case INT_CLEAR_MASK_R3:
+ case INT_CLEAR_MASK_R4:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool stpmu1_reg_volatile(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TURN_ON_SR:
+ case TURN_OFF_SR:
+ case ICC_LDO_TURN_OFF_SR:
+ case ICC_BUCK_TURN_OFF_SR:
+ case RREQ_STATE_SR:
+ case INT_PENDING_R1:
+ case INT_PENDING_R2:
+ case INT_PENDING_R3:
+ case INT_PENDING_R4:
+ case INT_SRC_R1:
+ case INT_SRC_R2:
+ case INT_SRC_R3:
+ case INT_SRC_R4:
+ case WCHDG_CR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int stpmu1_configure_from_dt(struct stpmu1_dev *pmic_dev)
+{
+ struct device_node *np = pmic_dev->np;
+ u32 reg = 0;
+ int ret = 0;
+ int irq;
+
+ irq = of_irq_get(np, 0);
+ if (irq <= 0) {
+ dev_err(pmic_dev->dev,
+ "Failed to get irq config: %d\n", irq);
+ return irq ? irq : -ENODEV;
+ }
+ pmic_dev->irq = irq;
+
+ irq = of_irq_get(np, 1);
+ if (irq <= 0) {
+ dev_err(pmic_dev->dev,
+ "Failed to get irq_wake config: %d\n", irq);
+ return irq ? irq : -ENODEV;
+ }
+ pmic_dev->irq_wake = irq;
+
+ device_init_wakeup(pmic_dev->dev, true);
+ ret = dev_pm_set_dedicated_wake_irq(pmic_dev->dev, pmic_dev->irq_wake);
+ if (ret)
+ dev_warn(pmic_dev->dev, "failed to set up wakeup irq");
+
+ if (!of_property_read_u32(np, "st,main_control_register", &reg)) {
+ ret = regmap_update_bits(pmic_dev->regmap,
+ SWOFF_PWRCTRL_CR,
+ PWRCTRL_POLARITY_HIGH |
+ PWRCTRL_PIN_VALID |
+ RESTART_REQUEST_ENABLED,
+ reg);
+ if (ret) {
+ dev_err(pmic_dev->dev,
+ "Failed to update main control register: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ if (!of_property_read_u32(np, "st,pads_pull_register", &reg)) {
+ ret = regmap_update_bits(pmic_dev->regmap,
+ PADS_PULL_CR,
+ WAKEUP_DETECTOR_DISABLED |
+ PWRCTRL_PD_ACTIVE |
+ PWRCTRL_PU_ACTIVE |
+ WAKEUP_PD_ACTIVE,
+ reg);
+ if (ret) {
+ dev_err(pmic_dev->dev,
+ "Failed to update pads control register: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ if (!of_property_read_u32(np, "st,vin_control_register", &reg)) {
+ ret = regmap_update_bits(pmic_dev->regmap,
+ VBUS_DET_VIN_CR,
+ VINLOW_CTRL_REG_MASK,
+ reg);
+ if (ret) {
+ dev_err(pmic_dev->dev,
+ "Failed to update vin control register: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ if (!of_property_read_u32(np, "st,usb_control_register", &reg)) {
+ ret = regmap_update_bits(pmic_dev->regmap, BST_SW_CR,
+ BOOST_OVP_DISABLED |
+ VBUS_OTG_DETECTION_DISABLED |
+ SW_OUT_DISCHARGE |
+ VBUS_OTG_DISCHARGE |
+ OCP_LIMIT_HIGH,
+ reg);
+ if (ret) {
+ dev_err(pmic_dev->dev,
+ "Failed to update usb control register: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+int stpmu1_device_init(struct stpmu1_dev *pmic_dev)
+{
+ int ret;
+ unsigned int val;
+
+ pmic_dev->regmap =
+ devm_regmap_init_i2c(pmic_dev->i2c, &stpmu1_regmap_config);
+
+ if (IS_ERR(pmic_dev->regmap)) {
+ ret = PTR_ERR(pmic_dev->regmap);
+ dev_err(pmic_dev->dev, "Failed to allocate register map: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = stpmu1_configure_from_dt(pmic_dev);
+ if (ret < 0) {
+ dev_err(pmic_dev->dev,
+ "Unable to configure PMIC from Device Tree: %d\n", ret);
+ return ret;
+ }
+
+ /* Read Version ID */
+ ret = regmap_read(pmic_dev->regmap, VERSION_SR, &val);
+ if (ret < 0) {
+ dev_err(pmic_dev->dev, "Unable to read pmic version\n");
+ return ret;
+ }
+ dev_dbg(pmic_dev->dev, "PMIC Chip Version: 0x%x\n", val);
+
+ /* Initialize PMIC IRQ Chip & IRQ domains associated */
+ ret = devm_regmap_add_irq_chip(pmic_dev->dev, pmic_dev->regmap,
+ pmic_dev->irq,
+ IRQF_ONESHOT | IRQF_SHARED,
+ 0, &stpmu1_regmap_irq_chip,
+ &pmic_dev->irq_data);
+ if (ret < 0) {
+ dev_err(pmic_dev->dev, "IRQ Chip registration failed: %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id stpmu1_dt_match[] = {
+ {.compatible = "st,stpmu1"},
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, stpmu1_dt_match);
+
+static int stpmu1_remove(struct i2c_client *i2c)
+{
+ struct stpmu1_dev *pmic_dev = i2c_get_clientdata(i2c);
+
+ of_platform_depopulate(pmic_dev->dev);
+
+ return 0;
+}
+
+static int stpmu1_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct stpmu1_dev *pmic;
+ struct device *dev = &i2c->dev;
+ int ret = 0;
+
+ pmic = devm_kzalloc(dev, sizeof(struct stpmu1_dev), GFP_KERNEL);
+ if (!pmic)
+ return -ENOMEM;
+
+ pmic->np = dev->of_node;
+
+ dev_set_drvdata(dev, pmic);
+ pmic->dev = dev;
+ pmic->i2c = i2c;
+
+ ret = stpmu1_device_init(pmic);
+ if (ret < 0)
+ goto err;
+
+ ret = of_platform_populate(pmic->np, NULL, NULL, pmic->dev);
+
+ dev_dbg(dev, "stpmu1 driver probed\n");
+err:
+ return ret;
+}
+
+static const struct i2c_device_id stpmu1_id[] = {
+ {"stpmu1", 0},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, stpmu1_id);
+
+#ifdef CONFIG_PM_SLEEP
+static int stpmu1_suspend(struct device *dev)
+{
+ struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
+ struct stpmu1_dev *pmic_dev = i2c_get_clientdata(i2c);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(pmic_dev->irq_wake);
+
+ disable_irq(pmic_dev->irq);
+ return 0;
+}
+
+static int stpmu1_resume(struct device *dev)
+{
+ struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
+ struct stpmu1_dev *pmic_dev = i2c_get_clientdata(i2c);
+
+ regcache_sync(pmic_dev->regmap);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(pmic_dev->irq_wake);
+
+ enable_irq(pmic_dev->irq);
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(stpmu1_pm, stpmu1_suspend, stpmu1_resume);
+
+static struct i2c_driver stpmu1_driver = {
+ .driver = {
+ .name = "stpmu1",
+ .owner = THIS_MODULE,
+ .pm = &stpmu1_pm,
+ .of_match_table = of_match_ptr(stpmu1_dt_match),
+ },
+ .probe = stpmu1_probe,
+ .remove = stpmu1_remove,
+ .id_table = stpmu1_id,
+};
+
+module_i2c_driver(stpmu1_driver);
+
+MODULE_DESCRIPTION("STPMU1 PMIC I2C Client");
+MODULE_AUTHOR("<[email protected]>");
+MODULE_LICENSE("GPL");
diff --git a/include/dt-bindings/mfd/st,stpmu1.h b/include/dt-bindings/mfd/st,stpmu1.h
new file mode 100644
index 0000000..2d3bdf1
--- /dev/null
+++ b/include/dt-bindings/mfd/st,stpmu1.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Philippe Peurichard <[email protected]>,
+ * Pascal Paillet <[email protected]> for STMicroelectronics.
+ */
+
+#ifndef __DT_BINDINGS_STPMU1_H__
+#define __DT_BINDINGS_STPMU1_H__
+
+/* IRQ definitions */
+#define IT_PONKEY_F 0
+#define IT_PONKEY_R 1
+#define IT_WAKEUP_F 2
+#define IT_WAKEUP_R 3
+#define IT_VBUS_OTG_F 4
+#define IT_VBUS_OTG_R 5
+#define IT_SWOUT_F 6
+#define IT_SWOUT_R 7
+
+#define IT_CURLIM_BUCK1 8
+#define IT_CURLIM_BUCK2 9
+#define IT_CURLIM_BUCK3 10
+#define IT_CURLIM_BUCK4 11
+#define IT_OCP_OTG 12
+#define IT_OCP_SWOUT 13
+#define IT_OCP_BOOST 14
+#define IT_OVP_BOOST 15
+
+#define IT_CURLIM_LDO1 16
+#define IT_CURLIM_LDO2 17
+#define IT_CURLIM_LDO3 18
+#define IT_CURLIM_LDO4 19
+#define IT_CURLIM_LDO5 20
+#define IT_CURLIM_LDO6 21
+#define IT_SHORT_SWOTG 22
+#define IT_SHORT_SWOUT 23
+
+#define IT_TWARN_F 24
+#define IT_TWARN_R 25
+#define IT_VINLOW_F 26
+#define IT_VINLOW_R 27
+#define IT_SWIN_F 30
+#define IT_SWIN_R 31
+
+#endif /* __DT_BINDINGS_STPMU1_H__ */
diff --git a/include/linux/mfd/stpmu1.h b/include/linux/mfd/stpmu1.h
new file mode 100644
index 0000000..05fd029
--- /dev/null
+++ b/include/linux/mfd/stpmu1.h
@@ -0,0 +1,220 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Philippe Peurichard <[email protected]>,
+ * Pascal Paillet <[email protected]> for STMicroelectronics.
+ */
+
+#ifndef __LINUX_MFD_STPMU1_H
+#define __LINUX_MFD_STPMU1_H
+
+#define TURN_ON_SR 0x1
+#define TURN_OFF_SR 0x2
+#define ICC_LDO_TURN_OFF_SR 0x3
+#define ICC_BUCK_TURN_OFF_SR 0x4
+#define RREQ_STATE_SR 0x5
+#define VERSION_SR 0x6
+
+#define SWOFF_PWRCTRL_CR 0x10
+#define PADS_PULL_CR 0x11
+#define BUCKS_PD_CR 0x12
+#define LDO14_PD_CR 0x13
+#define LDO56_VREF_PD_CR 0x14
+#define VBUS_DET_VIN_CR 0x15
+#define PKEY_TURNOFF_CR 0x16
+#define BUCKS_MASK_RANK_CR 0x17
+#define BUCKS_MASK_RESET_CR 0x18
+#define LDOS_MASK_RANK_CR 0x19
+#define LDOS_MASK_RESET_CR 0x1A
+#define WCHDG_CR 0x1B
+#define WCHDG_TIMER_CR 0x1C
+#define BUCKS_ICCTO_CR 0x1D
+#define LDOS_ICCTO_CR 0x1E
+
+#define BUCK1_ACTIVE_CR 0x20
+#define BUCK2_ACTIVE_CR 0x21
+#define BUCK3_ACTIVE_CR 0x22
+#define BUCK4_ACTIVE_CR 0x23
+#define VREF_DDR_ACTIVE_CR 0x24
+#define LDO1_ACTIVE_CR 0x25
+#define LDO2_ACTIVE_CR 0x26
+#define LDO3_ACTIVE_CR 0x27
+#define LDO4_ACTIVE_CR 0x28
+#define LDO5_ACTIVE_CR 0x29
+#define LDO6_ACTIVE_CR 0x2A
+
+#define BUCK1_STDBY_CR 0x30
+#define BUCK2_STDBY_CR 0x31
+#define BUCK3_STDBY_CR 0x32
+#define BUCK4_STDBY_CR 0x33
+#define VREF_DDR_STDBY_CR 0x34
+#define LDO1_STDBY_CR 0x35
+#define LDO2_STDBY_CR 0x36
+#define LDO3_STDBY_CR 0x37
+#define LDO4_STDBY_CR 0x38
+#define LDO5_STDBY_CR 0x39
+#define LDO6_STDBY_CR 0x3A
+
+#define BST_SW_CR 0x40
+
+#define INT_PENDING_R1 0x50
+#define INT_PENDING_R2 0x51
+#define INT_PENDING_R3 0x52
+#define INT_PENDING_R4 0x53
+
+#define INT_DBG_LATCH_R1 0x60
+#define INT_DBG_LATCH_R2 0x61
+#define INT_DBG_LATCH_R3 0x62
+#define INT_DBG_LATCH_R4 0x63
+
+#define INT_CLEAR_R1 0x70
+#define INT_CLEAR_R2 0x71
+#define INT_CLEAR_R3 0x72
+#define INT_CLEAR_R4 0x73
+
+#define INT_MASK_R1 0x80
+#define INT_MASK_R2 0x81
+#define INT_MASK_R3 0x82
+#define INT_MASK_R4 0x83
+
+#define INT_SET_MASK_R1 0x90
+#define INT_SET_MASK_R2 0x91
+#define INT_SET_MASK_R3 0x92
+#define INT_SET_MASK_R4 0x93
+
+#define INT_CLEAR_MASK_R1 0xA0
+#define INT_CLEAR_MASK_R2 0xA1
+#define INT_CLEAR_MASK_R3 0xA2
+#define INT_CLEAR_MASK_R4 0xA3
+
+#define INT_SRC_R1 0xB0
+#define INT_SRC_R2 0xB1
+#define INT_SRC_R3 0xB2
+#define INT_SRC_R4 0xB3
+
+#define PMIC_MAX_REGISTER_ADDRESS INT_SRC_R4
+
+#define STPMU1_PMIC_NUM_IRQ_REGS 4
+
+#define TURN_OFF_SR_ICC_EVENT 0x08
+
+#define LDO_VOLTAGE_MASK GENMASK(6, 2)
+#define BUCK_VOLTAGE_MASK GENMASK(7, 2)
+#define LDO_BUCK_VOLTAGE_SHIFT 2
+
+#define LDO_ENABLE_MASK BIT(0)
+#define BUCK_ENABLE_MASK BIT(0)
+
+#define BUCK_HPLP_ENABLE_MASK BIT(1)
+#define BUCK_HPLP_SHIFT 1
+
+#define STDBY_ENABLE_MASK BIT(0)
+
+#define BUCKS_PD_CR_REG_MASK GENMASK(7, 0)
+#define BUCK_MASK_RANK_REGISTER_MASK GENMASK(3, 0)
+#define BUCK_MASK_RESET_REGISTER_MASK GENMASK(3, 0)
+#define LDO1234_PULL_DOWN_REGISTER_MASK GENMASK(7, 0)
+#define LDO56_VREF_PD_CR_REG_MASK GENMASK(5, 0)
+#define LDO_MASK_RANK_REGISTER_MASK GENMASK(5, 0)
+#define LDO_MASK_RESET_REGISTER_MASK GENMASK(5, 0)
+
+#define BUCK1_PULL_DOWN_REG BUCKS_PD_CR
+#define BUCK1_PULL_DOWN_MASK BIT(0)
+#define BUCK2_PULL_DOWN_REG BUCKS_PD_CR
+#define BUCK2_PULL_DOWN_MASK BIT(2)
+#define BUCK3_PULL_DOWN_REG BUCKS_PD_CR
+#define BUCK3_PULL_DOWN_MASK BIT(4)
+#define BUCK4_PULL_DOWN_REG BUCKS_PD_CR
+#define BUCK4_PULL_DOWN_MASK BIT(6)
+
+#define LDO1_PULL_DOWN_REG LDO14_PD_CR
+#define LDO1_PULL_DOWN_MASK BIT(0)
+#define LDO2_PULL_DOWN_REG LDO14_PD_CR
+#define LDO2_PULL_DOWN_MASK BIT(2)
+#define LDO3_PULL_DOWN_REG LDO14_PD_CR
+#define LDO3_PULL_DOWN_MASK BIT(4)
+#define LDO4_PULL_DOWN_REG LDO14_PD_CR
+#define LDO4_PULL_DOWN_MASK BIT(6)
+#define LDO5_PULL_DOWN_REG LDO56_VREF_PD_CR
+#define LDO5_PULL_DOWN_MASK BIT(0)
+#define LDO6_PULL_DOWN_REG LDO56_VREF_PD_CR
+#define LDO6_PULL_DOWN_MASK BIT(2)
+#define VREF_DDR_PULL_DOWN_REG LDO56_VREF_PD_CR
+#define VREF_DDR_PULL_DOWN_MASK BIT(4)
+
+#define BUCKS_ICCTO_CR_REG_MASK GENMASK(6, 0)
+#define LDOS_ICCTO_CR_REG_MASK GENMASK(5, 0)
+
+#define LDO_BYPASS_MASK BIT(7)
+
+/* Main PMIC Control Register
+ * SWOFF_PWRCTRL_CR
+ * Address : 0x10
+ */
+#define ICC_EVENT_ENABLED BIT(4)
+#define PWRCTRL_POLARITY_HIGH BIT(3)
+#define PWRCTRL_PIN_VALID BIT(2)
+#define RESTART_REQUEST_ENABLED BIT(1)
+#define SOFTWARE_SWITCH_OFF_ENABLED BIT(0)
+
+/* Main PMIC PADS Control Register
+ * PADS_PULL_CR
+ * Address : 0x11
+ */
+#define WAKEUP_DETECTOR_DISABLED BIT(4)
+#define PWRCTRL_PD_ACTIVE BIT(3)
+#define PWRCTRL_PU_ACTIVE BIT(2)
+#define WAKEUP_PD_ACTIVE BIT(1)
+#define PONKEY_PU_ACTIVE BIT(0)
+
+/* Main PMIC VINLOW Control Register
+ * VBUS_DET_VIN_CRC DMSC
+ * Address : 0x15
+ */
+#define SWIN_DETECTOR_ENABLED BIT(7)
+#define SWOUT_DETECTOR_ENABLED BIT(6)
+#define VINLOW_ENABLED BIT(0)
+#define VINLOW_CTRL_REG_MASK GENMASK(7, 0)
+
+/* USB Control Register
+ * Address : 0x40
+ */
+#define BOOST_OVP_DISABLED BIT(7)
+#define VBUS_OTG_DETECTION_DISABLED BIT(6)
+#define SW_OUT_DISCHARGE BIT(5)
+#define VBUS_OTG_DISCHARGE BIT(4)
+#define OCP_LIMIT_HIGH BIT(3)
+#define SWIN_SWOUT_ENABLED BIT(2)
+#define USBSW_OTG_SWITCH_ENABLED BIT(1)
+#define BOOST_ENABLED BIT(0)
+
+/* PKEY_TURNOFF_CR
+ * Address : 0x16
+ */
+#define PONKEY_PWR_OFF BIT(7)
+#define PONKEY_CC_FLAG_CLEAR BIT(6)
+#define PONKEY_TURNOFF_TIMER_MASK GENMASK(3, 0)
+#define PONKEY_TURNOFF_MASK GENMASK(7, 0)
+
+/*
+ * struct stpmu1_dev - stpmu1 master device for sub-drivers
+ * @dev: master device of the chip (can be used to access platform data)
+ * @i2c: i2c client private data for regulator
+ * @np: device DT node pointer
+ * @irq_base: base IRQ numbers
+ * @irq: generic IRQ number
+ * @irq_wake: wakeup IRQ number
+ * @regmap_irq_chip_data: irq chip data
+ */
+struct stpmu1_dev {
+ struct device *dev;
+ struct i2c_client *i2c;
+ struct regmap *regmap;
+ struct device_node *np;
+ unsigned int irq_base;
+ int irq;
+ int irq_wake;
+ struct regmap_irq_chip_data *irq_data;
+};
+
+#endif /* __LINUX_MFD_STPMU1_H */
--
1.9.1

2018-07-05 15:17:42

by Pascal Paillet

[permalink] [raw]
Subject: [PATCH 4/8] regulator: stpmu1: add stpmu1 regulator driver

From: pascal paillet <[email protected]>

The stpmu1 PMIC embeds several regulators and witches with
different capabilities.

Signed-off-by: pascal paillet <[email protected]>
---
drivers/regulator/Kconfig | 12 +
drivers/regulator/Makefile | 2 +
drivers/regulator/stpmu1_regulator.c | 919 +++++++++++++++++++++++++++++++++++
3 files changed, 933 insertions(+)
create mode 100644 drivers/regulator/stpmu1_regulator.c

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 5dbccf5..0b508f5 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -804,6 +804,18 @@ config REGULATOR_TI_ABB
on TI SoCs may be unstable without enabling this as it provides
device specific optimized bias to allow/optimize functionality.

+config REGULATOR_STPMU1
+ tristate "ST Microelectronics STPMU1 PMIC Regulators"
+ depends on MFD_STPMU1
+ help
+ This driver supports ST Microelectronics STPMU1 PMIC voltage
+ regulators and switches. The STPMU1 regulators supply power to
+ an application processor as well as to external system
+ peripherals such as DDR, Flash memories and system devices.
+
+ To compile this driver as a module, choose M here: the
+ module will be called stpmu1_regulator.
+
config REGULATOR_STW481X_VMMC
bool "ST Microelectronics STW481X VMMC regulator"
depends on MFD_STW481X || COMPILE_TEST
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index bd818ce..3d572ae 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_OF) += of_regulator.o
obj-$(CONFIG_REGULATOR_FIXED_VOLTAGE) += fixed.o
obj-$(CONFIG_REGULATOR_VIRTUAL_CONSUMER) += virtual.o
obj-$(CONFIG_REGULATOR_USERSPACE_CONSUMER) += userspace-consumer.o
+obj-$(CONFIG_PROTECTION_CONSUMER) += protection-consumer.o

obj-$(CONFIG_REGULATOR_88PG86X) += 88pg86x.o
obj-$(CONFIG_REGULATOR_88PM800) += 88pm800.o
@@ -100,6 +101,7 @@ obj-$(CONFIG_REGULATOR_S5M8767) += s5m8767.o
obj-$(CONFIG_REGULATOR_SC2731) += sc2731-regulator.o
obj-$(CONFIG_REGULATOR_SKY81452) += sky81452-regulator.o
obj-$(CONFIG_REGULATOR_STM32_VREFBUF) += stm32-vrefbuf.o
+obj-$(CONFIG_REGULATOR_STPMU1) += stpmu1_regulator.o
obj-$(CONFIG_REGULATOR_STW481X_VMMC) += stw481x-vmmc.o
obj-$(CONFIG_REGULATOR_SY8106A) += sy8106a-regulator.o
obj-$(CONFIG_REGULATOR_TI_ABB) += ti-abb-regulator.o
diff --git a/drivers/regulator/stpmu1_regulator.c b/drivers/regulator/stpmu1_regulator.c
new file mode 100644
index 0000000..aed778b
--- /dev/null
+++ b/drivers/regulator/stpmu1_regulator.c
@@ -0,0 +1,919 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Philippe Peurichard <[email protected]>,
+ * Pascal Paillet <[email protected]> for STMicroelectronics.
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/stpmu1.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/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+
+/**
+ * stpmu1 regulator description
+ * @desc: regulator framework description
+ * @valid_modes_mask: modes supported by the regulator
+ * @valid_ops_mask: ops supported by the regulator
+ * @pull_down_reg: pull down register address
+ * @mask_reset_reg: mask reset register address
+ * @mask_reset_mask: mask rank and mask reset register mask
+ * @icc_reg: icc register address
+ * @icc_mask: icc register mask
+ */
+struct stpmu1_regulator_cfg {
+ struct regulator_desc desc;
+
+ unsigned int valid_modes_mask;
+ unsigned int valid_ops_mask;
+ u8 mask_reset_reg;
+ u8 mask_reset_mask;
+ u8 icc_reg;
+ u8 icc_mask;
+};
+
+/**
+ * stpmu1 regulator data: this structure is used as driver data
+ * @regul_id: regulator id
+ * @reg_node: DT node of regulator (unused on non-DT platforms)
+ * @cfg: stpmu specific regulator description
+ * @voltage_ref_reg: Special case for Vref DDR & LDO3 for which voltage
+ * depends on Buck2
+ * @mask_reset: mask_reset bit value
+ * @irq_curlim: current limit interrupt number
+ * @regmap: point to parent regmap structure
+ */
+struct stpmu1_regulator {
+ unsigned int regul_id;
+ struct device_node *reg_node;
+ struct stpmu1_regulator_cfg *cfg;
+ struct regulator_dev *voltage_ref_reg;
+ u8 mask_reset;
+ int irq_curlim;
+ struct regmap *regmap;
+};
+
+/**
+ * struct stpmu1_device_data - contains all regulators data
+ * @regulator_table : contains all the regulators
+ * @num_regulators: number of regulators used
+ */
+struct stpmu1_device_data {
+ struct stpmu1_regulator *regulator_table;
+ int num_regulators;
+};
+
+static int stpmu1_set_mode(struct regulator_dev *rdev, unsigned int mode);
+static unsigned int stpmu1_get_mode(struct regulator_dev *rdev);
+static int stpmu1_set_icc(struct regulator_dev *rdev);
+static int stpmu1_set_bypass(struct regulator_dev *rdev, bool enable);
+static int stpmu1_regulator_parse_dt(void *driver_data);
+static unsigned int stpmu1_regulator_get_max_volt(struct regulator_desc *desc);
+static unsigned int stpmu1_map_mode(unsigned int mode);
+static int stpmu1_ldo3_list_voltage(struct regulator_dev *rdev,
+ unsigned int sel);
+static int stpmu1_ldo3_get_voltage(struct regulator_dev *rdev);
+static int stpmu1_fixed_regul_get_voltage(struct regulator_dev *rdev);
+
+enum {
+ STPMU1_BUCK1 = 0,
+ STPMU1_BUCK2 = 1,
+ STPMU1_BUCK3 = 2,
+ STPMU1_BUCK4 = 3,
+ STPMU1_LDO1 = 4,
+ STPMU1_LDO2 = 5,
+ STPMU1_LDO3 = 6,
+ STPMU1_LDO4 = 7,
+ STPMU1_LDO5 = 8,
+ STPMU1_LDO6 = 9,
+ STPMU1_VREF_DDR = 10,
+ STPMU1_BOOST = 11,
+ STPMU1_VBUS_OTG = 12,
+ STPMU1_SW_OUT = 13,
+};
+
+/*
+ * PMIC Ramp_delay: Time to settle down after voltage change (unit: uV/us)
+ * or enable
+ * is 3.6mV/uS +/-60% -> 2.25mV/uS worst case
+ */
+#define PMIC_RAMP_SLOPE_UV_PER_US 2250
+/* Enable time is 5000V/2250mV/uS */
+#define PMIC_ENABLE_TIME_US 2200
+
+struct regulator_linear_range buck1_ranges[] = {
+ REGULATOR_LINEAR_RANGE(600000, 0, 30, 25000),
+ REGULATOR_LINEAR_RANGE(1350000, 31, 63, 0),
+};
+
+struct regulator_linear_range buck2_ranges[] = {
+ REGULATOR_LINEAR_RANGE(1000000, 0, 17, 0),
+ REGULATOR_LINEAR_RANGE(1050000, 18, 19, 0),
+ REGULATOR_LINEAR_RANGE(1100000, 20, 21, 0),
+ REGULATOR_LINEAR_RANGE(1150000, 22, 23, 0),
+ REGULATOR_LINEAR_RANGE(1200000, 24, 25, 0),
+ REGULATOR_LINEAR_RANGE(1250000, 26, 27, 0),
+ REGULATOR_LINEAR_RANGE(1300000, 28, 29, 0),
+ REGULATOR_LINEAR_RANGE(1350000, 30, 31, 0),
+ REGULATOR_LINEAR_RANGE(1400000, 32, 33, 0),
+ REGULATOR_LINEAR_RANGE(1450000, 34, 35, 0),
+ REGULATOR_LINEAR_RANGE(1500000, 36, 63, 0),
+};
+
+struct regulator_linear_range buck3_ranges[] = {
+ REGULATOR_LINEAR_RANGE(1000000, 0, 19, 0),
+ REGULATOR_LINEAR_RANGE(1100000, 20, 23, 0),
+ REGULATOR_LINEAR_RANGE(1200000, 24, 27, 0),
+ REGULATOR_LINEAR_RANGE(1300000, 28, 31, 0),
+ REGULATOR_LINEAR_RANGE(1400000, 32, 35, 0),
+ REGULATOR_LINEAR_RANGE(1500000, 36, 55, 100000),
+ REGULATOR_LINEAR_RANGE(3400000, 56, 63, 0),
+
+};
+
+struct regulator_linear_range buck4_ranges[] = {
+ REGULATOR_LINEAR_RANGE(600000, 0, 27, 25000),
+ REGULATOR_LINEAR_RANGE(1300000, 28, 29, 0),
+ REGULATOR_LINEAR_RANGE(1350000, 30, 31, 0),
+ REGULATOR_LINEAR_RANGE(1400000, 32, 33, 0),
+ REGULATOR_LINEAR_RANGE(1450000, 34, 35, 0),
+ REGULATOR_LINEAR_RANGE(1500000, 36, 60, 100000),
+ REGULATOR_LINEAR_RANGE(3900000, 61, 63, 0),
+
+};
+
+struct regulator_linear_range ldo1_ranges[] = {
+ REGULATOR_LINEAR_RANGE(1700000, 0, 7, 0),
+ REGULATOR_LINEAR_RANGE(1700000, 8, 24, 100000),
+ REGULATOR_LINEAR_RANGE(3300000, 25, 31, 0),
+
+};
+
+struct regulator_linear_range ldo2_ranges[] = {
+ REGULATOR_LINEAR_RANGE(1700000, 0, 7, 0),
+ REGULATOR_LINEAR_RANGE(1700000, 8, 24, 100000),
+ REGULATOR_LINEAR_RANGE(3300000, 25, 30, 0),
+
+};
+
+struct regulator_linear_range ldo3_ranges[] = {
+ /* Special case to allow range to cover lowest value of Buck2/2 */
+ REGULATOR_LINEAR_RANGE(500000, 0, 0, 0),
+ REGULATOR_LINEAR_RANGE(1700000, 1, 7, 0),
+ REGULATOR_LINEAR_RANGE(1700000, 8, 24, 100000),
+ /* index 31 is special case when LDO3 is in mode DDR */
+ REGULATOR_LINEAR_RANGE(3300000, 25, 31, 0),
+};
+
+struct regulator_linear_range ldo5_ranges[] = {
+ REGULATOR_LINEAR_RANGE(1700000, 0, 7, 0),
+ REGULATOR_LINEAR_RANGE(1700000, 8, 30, 100000),
+ REGULATOR_LINEAR_RANGE(3900000, 31, 31, 0),
+};
+
+struct regulator_linear_range ldo6_ranges[] = {
+ REGULATOR_LINEAR_RANGE(900000, 0, 24, 100000),
+ REGULATOR_LINEAR_RANGE(3300000, 25, 31, 0),
+};
+
+static struct regulator_ops stpmu1_ldo_ops = {
+ .list_voltage = regulator_list_voltage_linear_range,
+ .map_voltage = regulator_map_voltage_linear_range,
+ .is_enabled = regulator_is_enabled_regmap,
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+ .set_pull_down = regulator_set_pull_down_regmap,
+ .set_over_current_protection = stpmu1_set_icc,
+};
+
+static struct regulator_ops stpmu1_ldo3_ops = {
+ .list_voltage = stpmu1_ldo3_list_voltage,
+ .map_voltage = regulator_map_voltage_iterate,
+ .is_enabled = regulator_is_enabled_regmap,
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .get_voltage = stpmu1_ldo3_get_voltage,
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+ .set_pull_down = regulator_set_pull_down_regmap,
+ .get_bypass = regulator_get_bypass_regmap,
+ .set_bypass = stpmu1_set_bypass,
+ .set_over_current_protection = stpmu1_set_icc,
+};
+
+static struct regulator_ops stpmu1_ldo4_fixed_regul_ops = {
+ .is_enabled = regulator_is_enabled_regmap,
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .set_pull_down = regulator_set_pull_down_regmap,
+ .set_over_current_protection = stpmu1_set_icc,
+};
+
+static struct regulator_ops stpmu1_buck_ops = {
+ .list_voltage = regulator_list_voltage_linear_range,
+ .map_voltage = regulator_map_voltage_linear_range,
+ .is_enabled = regulator_is_enabled_regmap,
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+ .set_pull_down = regulator_set_pull_down_regmap,
+ .set_mode = stpmu1_set_mode,
+ .get_mode = stpmu1_get_mode,
+ .set_over_current_protection = stpmu1_set_icc,
+};
+
+static struct regulator_ops stpmu1_fixed_regul_ops = {
+ .is_enabled = regulator_is_enabled_regmap,
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .get_voltage = stpmu1_fixed_regul_get_voltage,
+ .set_pull_down = regulator_set_pull_down_regmap,
+};
+
+static struct regulator_ops stpmu1_switch_regul_ops = {
+ .is_enabled = regulator_is_enabled_regmap,
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .get_voltage = stpmu1_fixed_regul_get_voltage,
+ .set_over_current_protection = stpmu1_set_icc,
+};
+
+#define REG_LDO(ids, base) { \
+ .name = #ids, \
+ .id = STPMU1_##ids, \
+ .n_voltages = 32, \
+ .ops = &stpmu1_ldo_ops, \
+ .linear_ranges = base ## _ranges, \
+ .n_linear_ranges = ARRAY_SIZE(base ## _ranges), \
+ .type = REGULATOR_VOLTAGE, \
+ .owner = THIS_MODULE, \
+ .vsel_reg = ids##_ACTIVE_CR, \
+ .vsel_mask = LDO_VOLTAGE_MASK, \
+ .enable_reg = ids##_ACTIVE_CR, \
+ .enable_mask = LDO_ENABLE_MASK, \
+ .enable_val = 1, \
+ .disable_val = 0, \
+ .pull_down_reg = ids##_PULL_DOWN_REG, \
+ .pull_down_mask = ids##_PULL_DOWN_MASK, \
+ .supply_name = #base, \
+}
+
+#define REG_LDO3(ids) { \
+ .name = #ids, \
+ .id = STPMU1_##ids, \
+ .n_voltages = 32, \
+ .ops = &stpmu1_ldo3_ops, \
+ .linear_ranges = ldo3_ranges, \
+ .n_linear_ranges = ARRAY_SIZE(ldo3_ranges), \
+ .type = REGULATOR_VOLTAGE, \
+ .owner = THIS_MODULE, \
+ .vsel_reg = LDO3_ACTIVE_CR, \
+ .vsel_mask = LDO_VOLTAGE_MASK, \
+ .enable_reg = LDO3_ACTIVE_CR, \
+ .enable_mask = LDO_ENABLE_MASK, \
+ .enable_val = 1, \
+ .disable_val = 0, \
+ .bypass_reg = LDO3_ACTIVE_CR, \
+ .bypass_mask = LDO_BYPASS_MASK, \
+ .bypass_val_on = LDO_BYPASS_MASK, \
+ .bypass_val_off = 0, \
+ .pull_down_reg = ids##_PULL_DOWN_REG, \
+ .pull_down_mask = ids##_PULL_DOWN_MASK, \
+ .supply_name = "ldo3", \
+}
+
+#define REG_LDO4(ids) { \
+ .name = #ids, \
+ .id = STPMU1_##ids, \
+ .n_voltages = 1, \
+ .ops = &stpmu1_ldo4_fixed_regul_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .owner = THIS_MODULE, \
+ .min_uV = 3300000, \
+ .fixed_uV = 3300000, \
+ .enable_reg = LDO4_ACTIVE_CR, \
+ .enable_mask = LDO_ENABLE_MASK, \
+ .enable_val = 1, \
+ .disable_val = 0, \
+ .pull_down_reg = ids##_PULL_DOWN_REG, \
+ .pull_down_mask = ids##_PULL_DOWN_MASK, \
+ .supply_name = "ldo4", \
+}
+
+#define REG_BUCK(ids, base) { \
+ .name = #ids, \
+ .id = STPMU1_##ids, \
+ .ops = &stpmu1_buck_ops, \
+ .n_voltages = 64, \
+ .linear_ranges = base ## _ranges, \
+ .n_linear_ranges = ARRAY_SIZE(base ## _ranges), \
+ .type = REGULATOR_VOLTAGE, \
+ .owner = THIS_MODULE, \
+ .vsel_reg = ids##_ACTIVE_CR, \
+ .vsel_mask = BUCK_VOLTAGE_MASK, \
+ .enable_reg = ids##_ACTIVE_CR, \
+ .enable_mask = BUCK_ENABLE_MASK, \
+ .enable_val = 1, \
+ .disable_val = 0, \
+ .of_map_mode = stpmu1_map_mode, \
+ .pull_down_reg = ids##_PULL_DOWN_REG, \
+ .pull_down_mask = ids##_PULL_DOWN_MASK, \
+ .supply_name = #base, \
+}
+
+#define REG_VREF_DDR(ids, reg) { \
+ .name = #ids, \
+ .id = STPMU1_##ids, \
+ .n_voltages = 1, \
+ .ops = &stpmu1_fixed_regul_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .owner = THIS_MODULE, \
+ .min_uV = 0, \
+ .fixed_uV = 5000000, \
+ .enable_reg = (reg), \
+ .enable_mask = BUCK_ENABLE_MASK, \
+ .enable_val = 1, \
+ .disable_val = 0, \
+ .of_map_mode = stpmu1_map_mode, \
+ .pull_down_reg = ids##_PULL_DOWN_REG, \
+ .pull_down_mask = ids##_PULL_DOWN_MASK, \
+ .supply_name = NULL, \
+}
+
+#define REG_SWITCH(ids, base, reg, mask, val) { \
+ .name = #ids, \
+ .id = STPMU1_##ids, \
+ .n_voltages = 1, \
+ .ops = &stpmu1_switch_regul_ops, \
+ .type = REGULATOR_VOLTAGE, \
+ .owner = THIS_MODULE, \
+ .min_uV = 0, \
+ .fixed_uV = 5000000, \
+ .enable_reg = (reg), \
+ .enable_mask = (mask), \
+ .enable_val = (val), \
+ .disable_val = 0, \
+ .of_map_mode = stpmu1_map_mode, \
+ .supply_name = #base, \
+}
+
+struct stpmu1_regulator_cfg stpmu1_regulator_cfgs[] = {
+ [STPMU1_BUCK1] = {
+ .desc = REG_BUCK(BUCK1, buck1),
+ .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE |
+ REGULATOR_CHANGE_MODE,
+ .valid_modes_mask = REGULATOR_MODE_NORMAL |
+ REGULATOR_MODE_STANDBY,
+ .icc_reg = BUCKS_ICCTO_CR,
+ .icc_mask = BIT(0),
+ .mask_reset_reg = BUCKS_MASK_RESET_CR,
+ .mask_reset_mask = BIT(0),
+ },
+ [STPMU1_BUCK2] = {
+ .desc = REG_BUCK(BUCK2, buck2),
+ .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE |
+ REGULATOR_CHANGE_MODE,
+ .valid_modes_mask = REGULATOR_MODE_NORMAL |
+ REGULATOR_MODE_STANDBY,
+ .icc_reg = BUCKS_ICCTO_CR,
+ .icc_mask = BIT(1),
+ .mask_reset_reg = BUCKS_MASK_RESET_CR,
+ .mask_reset_mask = BIT(1),
+ },
+ [STPMU1_BUCK3] = {
+ .desc = REG_BUCK(BUCK3, buck3),
+ .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE |
+ REGULATOR_CHANGE_MODE,
+ .valid_modes_mask = REGULATOR_MODE_NORMAL |
+ REGULATOR_MODE_STANDBY,
+ .icc_reg = BUCKS_ICCTO_CR,
+ .icc_mask = BIT(2),
+ .mask_reset_reg = BUCKS_MASK_RESET_CR,
+ .mask_reset_mask = BIT(2),
+ },
+ [STPMU1_BUCK4] = {
+ .desc = REG_BUCK(BUCK4, buck4),
+ .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE |
+ REGULATOR_CHANGE_MODE,
+ .valid_modes_mask = REGULATOR_MODE_NORMAL |
+ REGULATOR_MODE_STANDBY,
+ .icc_reg = BUCKS_ICCTO_CR,
+ .icc_mask = BIT(3),
+ .mask_reset_reg = BUCKS_MASK_RESET_CR,
+ .mask_reset_mask = BIT(3),
+ },
+ [STPMU1_LDO1] = {
+ .desc = REG_LDO(LDO1, ldo1),
+ .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE,
+ .icc_reg = LDOS_ICCTO_CR,
+ .icc_mask = BIT(0),
+ .mask_reset_reg = LDOS_MASK_RESET_CR,
+ .mask_reset_mask = BIT(0),
+ },
+ [STPMU1_LDO2] = {
+ .desc = REG_LDO(LDO2, ldo2),
+ .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE,
+ .icc_reg = LDOS_ICCTO_CR,
+ .icc_mask = BIT(1),
+ .mask_reset_reg = LDOS_MASK_RESET_CR,
+ .mask_reset_mask = BIT(1),
+ },
+ [STPMU1_LDO3] = {
+ .desc = REG_LDO3(LDO3),
+ .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE |
+ REGULATOR_CHANGE_BYPASS,
+ .icc_reg = LDOS_ICCTO_CR,
+ .icc_mask = BIT(2),
+ .mask_reset_reg = LDOS_MASK_RESET_CR,
+ .mask_reset_mask = BIT(2),
+ },
+ [STPMU1_LDO4] = {
+ .desc = REG_LDO4(LDO4),
+ .icc_reg = LDOS_ICCTO_CR,
+ .icc_mask = BIT(3),
+ .mask_reset_reg = LDOS_MASK_RESET_CR,
+ .mask_reset_mask = BIT(3),
+ },
+ [STPMU1_LDO5] = {
+ .desc = REG_LDO(LDO5, ldo5),
+ .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE,
+ .icc_reg = LDOS_ICCTO_CR,
+ .icc_mask = BIT(4),
+ .mask_reset_reg = LDOS_MASK_RESET_CR,
+ .mask_reset_mask = BIT(4),
+ },
+ [STPMU1_LDO6] = {
+ .desc = REG_LDO(LDO6, ldo6),
+ .valid_ops_mask = REGULATOR_CHANGE_VOLTAGE,
+ .icc_reg = LDOS_ICCTO_CR,
+ .icc_mask = BIT(5),
+ .mask_reset_reg = LDOS_MASK_RESET_CR,
+ .mask_reset_mask = BIT(5),
+ },
+ [STPMU1_VREF_DDR] = {
+ .desc = REG_VREF_DDR(VREF_DDR, VREF_DDR_ACTIVE_CR),
+ .mask_reset_reg = LDOS_MASK_RESET_CR,
+ .mask_reset_mask = BIT(6),
+ },
+ [STPMU1_BOOST] = {
+ .desc = REG_SWITCH(BOOST, boost, BST_SW_CR,
+ BOOST_ENABLED,
+ BOOST_ENABLED),
+ .icc_reg = BUCKS_ICCTO_CR,
+ .icc_mask = BIT(6),
+ },
+ [STPMU1_VBUS_OTG] = {
+ .desc = REG_SWITCH(VBUS_OTG, pwr_sw1, BST_SW_CR,
+ USBSW_OTG_SWITCH_ENABLED,
+ USBSW_OTG_SWITCH_ENABLED),
+ .icc_reg = BUCKS_ICCTO_CR,
+ .icc_mask = BIT(4),
+ },
+ [STPMU1_SW_OUT] = {
+ .desc = REG_SWITCH(SW_OUT, pwr_sw2, BST_SW_CR,
+ SWIN_SWOUT_ENABLED,
+ SWIN_SWOUT_ENABLED),
+ .icc_reg = BUCKS_ICCTO_CR,
+ .icc_mask = BIT(5),
+ },
+};
+
+#define GET_MINIMUM_VOLTAGE(_regulator_desc_ptr) \
+ ((_regulator_desc_ptr)->linear_ranges[0].min_uV)
+
+static unsigned int stpmu1_map_mode(unsigned int mode)
+{
+ return mode == REGULATOR_MODE_STANDBY ?
+ REGULATOR_MODE_STANDBY : REGULATOR_MODE_NORMAL;
+}
+
+static unsigned int stpmu1_regulator_get_max_volt(struct regulator_desc *desc)
+{
+ const struct regulator_linear_range *last_range =
+ &desc->linear_ranges[desc->n_linear_ranges - 1];
+
+ unsigned int max_voltage =
+ last_range->min_uV +
+ (last_range->max_sel - last_range->min_sel + 1)
+ * last_range->uV_step;
+
+ return max_voltage;
+}
+
+static int stpmu1_get_voltage_regmap(struct regulator_dev *rdev)
+{
+ int selector = 0;
+
+ if (!rdev)
+ return -EINVAL;
+ selector = regulator_get_voltage_sel_regmap(rdev);
+ return regulator_list_voltage_linear_range(rdev, selector);
+}
+
+static int stpmu1_ldo3_list_voltage(struct regulator_dev *rdev,
+ unsigned int sel)
+{
+ struct stpmu1_regulator *regul = rdev_get_drvdata(rdev);
+
+ if (sel == 0)
+ return regulator_list_voltage_linear_range(rdev, 1);
+
+ if (sel < 31)
+ return regulator_list_voltage_linear_range(rdev, sel);
+
+ if (sel == 31)
+ return stpmu1_get_voltage_regmap(regul->voltage_ref_reg) / 2;
+
+ return -EINVAL;
+}
+
+static int stpmu1_ldo3_get_voltage(struct regulator_dev *rdev)
+{
+ int sel = regulator_get_voltage_sel_regmap(rdev);
+
+ if (sel < 0)
+ return -EINVAL;
+
+ return stpmu1_ldo3_list_voltage(rdev, (unsigned int)sel);
+}
+
+static int stpmu1_fixed_regul_get_voltage(struct regulator_dev *rdev)
+{
+ struct stpmu1_regulator *regul = rdev_get_drvdata(rdev);
+ int id = rdev_get_id(rdev);
+
+ /* VREF_DDR voltage is equal to Buck2/2 */
+ if (id == STPMU1_VREF_DDR)
+ return stpmu1_get_voltage_regmap(regul->voltage_ref_reg) / 2;
+
+ /* For all other case , rely on fixed value defined by Hw settings */
+ return regul->cfg->desc.fixed_uV;
+}
+
+static int stpmu1_set_mode(struct regulator_dev *rdev, unsigned int mode)
+{
+ struct stpmu1_regulator *regul = rdev_get_drvdata(rdev);
+ unsigned int hplp;
+
+ /* The low power mode will be set for NORMAL/RUN registers */
+ switch (mode) {
+ case REGULATOR_MODE_NORMAL:
+ hplp = 0;
+ break;
+ case REGULATOR_MODE_STANDBY:
+ hplp = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return regmap_update_bits(regul->regmap, regul->cfg->desc.enable_reg,
+ BUCK_HPLP_ENABLE_MASK,
+ hplp << BUCK_HPLP_SHIFT);
+}
+
+static unsigned int stpmu1_get_mode(struct regulator_dev *rdev)
+{
+ struct stpmu1_regulator *regul = rdev_get_drvdata(rdev);
+ int ret;
+ unsigned int val;
+
+ ret = regmap_read(regul->regmap, regul->cfg->desc.enable_reg, &val);
+ if (ret < 0)
+ return ret;
+
+ val &= BUCK_HPLP_ENABLE_MASK;
+ val >>= BUCK_HPLP_SHIFT;
+
+ return val ? REGULATOR_MODE_STANDBY : REGULATOR_MODE_NORMAL;
+}
+
+static int stpmu1_set_icc(struct regulator_dev *rdev)
+{
+ struct stpmu1_regulator *regul = rdev_get_drvdata(rdev);
+
+ /* enable switch off in case of over current */
+ return regmap_update_bits(regul->regmap, regul->cfg->icc_reg,
+ regul->cfg->icc_mask, regul->cfg->icc_mask);
+}
+
+static int stpmu1_set_bypass(struct regulator_dev *rdev, bool enable)
+{
+ struct stpmu1_regulator *regul = rdev_get_drvdata(rdev);
+ int ret;
+ unsigned int val;
+
+ if (enable)
+ val = regul->cfg->desc.bypass_val_on;
+ else
+ val = regul->cfg->desc.bypass_val_off;
+
+ ret = regmap_update_bits(regul->regmap, regul->cfg->desc.bypass_reg,
+ regul->cfg->desc.bypass_mask, val);
+
+ return ret;
+}
+
+static irqreturn_t stpmu1_curlim_irq_handler(int irq, void *data)
+{
+ struct regulator_dev *rdev = (struct regulator_dev *)data;
+
+ mutex_lock(&rdev->mutex);
+
+ /* Send an overcurrent notification */
+ regulator_notifier_call_chain(rdev,
+ REGULATOR_EVENT_OVER_CURRENT,
+ NULL);
+
+ mutex_unlock(&rdev->mutex);
+
+ return IRQ_HANDLED;
+}
+
+static int stpmu1_regulator_init(struct platform_device *pdev,
+ struct regulator_dev *rdev)
+{
+ struct stpmu1_regulator *regul = rdev_get_drvdata(rdev);
+ int ret = 0;
+
+ /* set mask reset */
+ if (regul->mask_reset && regul->cfg->mask_reset_reg != 0) {
+ ret = regmap_update_bits(regul->regmap,
+ regul->cfg->mask_reset_reg,
+ regul->cfg->mask_reset_mask,
+ regul->cfg->mask_reset_mask);
+ if (ret) {
+ dev_err(&pdev->dev, "set mask reset failed\n");
+ return ret;
+ }
+ }
+
+ /* setup an irq handler for over-current detection */
+ if (regul->irq_curlim >= 0) {
+ ret = devm_request_threaded_irq(&pdev->dev,
+ regul->irq_curlim, NULL,
+ stpmu1_curlim_irq_handler,
+ IRQF_ONESHOT | IRQF_SHARED,
+ pdev->name, rdev);
+ if (ret) {
+ dev_err(&pdev->dev, "Request IRQ failed\n");
+ return ret;
+ }
+ }
+ return 0;
+}
+
+#define MATCH(_name, _id) \
+ [STPMU1_##_id] = { \
+ .name = #_name, \
+ .desc = &stpmu1_regulator_cfgs[STPMU1_##_id].desc, \
+ }
+
+static struct of_regulator_match stpmu1_regulators_matches[] = {
+ MATCH(buck1, BUCK1),
+ MATCH(buck2, BUCK2),
+ MATCH(buck3, BUCK3),
+ MATCH(buck4, BUCK4),
+ MATCH(ldo1, LDO1),
+ MATCH(ldo2, LDO2),
+ MATCH(ldo3, LDO3),
+ MATCH(ldo4, LDO4),
+ MATCH(ldo5, LDO5),
+ MATCH(ldo6, LDO6),
+ MATCH(vref_ddr, VREF_DDR),
+ MATCH(boost, BOOST),
+ MATCH(pwr_sw1, VBUS_OTG),
+ MATCH(pwr_sw2, SW_OUT),
+};
+
+static inline struct device_node *match_of_node(struct platform_device *pdev,
+ int index)
+{
+ if (!stpmu1_regulators_matches[index].of_node)
+ dev_info(&pdev->dev,
+ "DT node not found for regulator %i\n\r", index);
+
+ return stpmu1_regulators_matches[index].of_node;
+}
+
+static int stpmu1_regulator_parse_dt(void *driver_data)
+{
+ struct stpmu1_regulator *regul =
+ (struct stpmu1_regulator *)driver_data;
+
+ if (!regul)
+ return -EINVAL;
+
+ if (of_get_property(regul->reg_node, "st,mask_reset", NULL))
+ regul->mask_reset = 1;
+
+ regul->irq_curlim = of_irq_get(regul->reg_node, 0);
+
+ return 0;
+}
+
+static void update_regulator_constraints(int index,
+ struct regulator_init_data *init_data)
+{
+ struct stpmu1_regulator_cfg *cfg = &stpmu1_regulator_cfgs[index];
+ struct regulator_desc *desc = &cfg->desc;
+
+ init_data->constraints.valid_ops_mask |=
+ cfg->valid_ops_mask;
+ init_data->constraints.valid_modes_mask |=
+ cfg->valid_modes_mask;
+
+ /*
+ * if all constraints are not specified in DT , ensure Hw
+ * constraints are met
+ */
+ if (desc->n_voltages > 1) {
+ if (!init_data->constraints.min_uV)
+ init_data->constraints.min_uV =
+ GET_MINIMUM_VOLTAGE(desc);
+ if (!init_data->constraints.max_uV)
+ init_data->constraints.max_uV =
+ stpmu1_regulator_get_max_volt(desc);
+ }
+
+ if (!init_data->constraints.ramp_delay)
+ init_data->constraints.ramp_delay = PMIC_RAMP_SLOPE_UV_PER_US;
+
+ if (!init_data->constraints.enable_time)
+ init_data->constraints.enable_time = PMIC_ENABLE_TIME_US;
+}
+
+static struct
+regulator_dev *stpmu1_regulator_register(struct platform_device *pdev, int id,
+ struct regulator_init_data *init_data,
+ struct stpmu1_regulator *regul,
+ struct regulator_dev *buck2)
+{
+ struct stpmu1_dev *pmic_dev = dev_get_drvdata(pdev->dev.parent);
+ struct regulator_dev *rdev;
+ struct regulator_config config = {};
+
+ config.dev = &pdev->dev;
+ config.init_data = init_data;
+ config.of_node = match_of_node(pdev, id);
+ config.regmap = pmic_dev->regmap;
+ config.driver_data = regul;
+
+ regul->regul_id = id;
+ regul->reg_node = config.of_node;
+ regul->cfg = &stpmu1_regulator_cfgs[id];
+ regul->regmap = pmic_dev->regmap;
+
+ /* LDO3 and VREF_DDR can use buck2 as reference voltage */
+ if (regul->regul_id == STPMU1_LDO3 ||
+ regul->regul_id == STPMU1_VREF_DDR) {
+ if (!buck2) {
+ dev_err(&pdev->dev,
+ "Error in PMIC regulator settings: Buck2 is not defined prior to LDO3 or VREF_DDR regulators\n"
+ );
+ return ERR_PTR(-EINVAL);
+ }
+ regul->voltage_ref_reg = buck2;
+ }
+
+ rdev = devm_regulator_register(&pdev->dev, &regul->cfg->desc, &config);
+ if (IS_ERR(rdev)) {
+ dev_err(&pdev->dev, "failed to register %s regulator\n",
+ regul->cfg->desc.name);
+ }
+
+ return rdev;
+}
+
+static int stpmu1_regulator_probe(struct platform_device *pdev)
+{
+ struct stpmu1_dev *pmic_dev = dev_get_drvdata(pdev->dev.parent);
+ struct stpmu1_device_data *ddata;
+ struct regulator_dev *rdev;
+ struct stpmu1_regulator *regul;
+ struct regulator_init_data *init_data;
+ struct device_node *np;
+ int i, ret;
+ struct regulator_dev *buck2_rdev = NULL;
+
+ ddata = devm_kzalloc(&pdev->dev, sizeof(struct stpmu1_device_data),
+ GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, ddata);
+
+ /* disable icc for all reguls */
+ ret = regmap_update_bits(pmic_dev->regmap, BUCKS_ICCTO_CR,
+ BUCKS_ICCTO_CR_REG_MASK, 0);
+ if (ret == 0)
+ ret = regmap_update_bits(pmic_dev->regmap, LDOS_ICCTO_CR,
+ LDOS_ICCTO_CR_REG_MASK, 0);
+ /* reset pulldown value */
+ if (ret == 0)
+ ret = regmap_update_bits(pmic_dev->regmap, BUCKS_PD_CR,
+ BUCKS_PD_CR_REG_MASK, 0);
+ if (ret == 0)
+ ret = regmap_update_bits(pmic_dev->regmap, LDO14_PD_CR,
+ LDO1234_PULL_DOWN_REGISTER_MASK, 0);
+ if (ret == 0)
+ ret = regmap_update_bits(pmic_dev->regmap, LDO56_VREF_PD_CR,
+ LDO56_VREF_PD_CR_REG_MASK, 0);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to update stpmu1 register %d\n", ret);
+ return ret;
+ }
+
+ np = pdev->dev.of_node;
+ if (!np) {
+ dev_err(&pdev->dev, "regulators node not found\n");
+ return -EINVAL;
+ }
+
+ ret = of_regulator_match(&pdev->dev, np,
+ stpmu1_regulators_matches,
+ ARRAY_SIZE(stpmu1_regulators_matches));
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "Error in PMIC regulator device tree node");
+ return ret;
+ }
+ ddata->num_regulators = ret;
+
+ dev_dbg(&pdev->dev, "%d regulator(s) found in DT\n",
+ ddata->num_regulators);
+
+ regul = devm_kzalloc(&pdev->dev, ddata->num_regulators *
+ sizeof(struct stpmu1_regulator),
+ GFP_KERNEL);
+ if (!regul)
+ return -ENOMEM;
+
+ ddata->regulator_table = regul;
+
+ /* Register all defined (from DT) regulators to Regulator Framework */
+ for (i = 0; i < ARRAY_SIZE(stpmu1_regulator_cfgs); i++) {
+ /* Parse DT & find regulators to register */
+ init_data = stpmu1_regulators_matches[i].init_data;
+ if (init_data) {
+ init_data->regulator_init = &stpmu1_regulator_parse_dt;
+
+ update_regulator_constraints(i, init_data);
+
+ rdev = stpmu1_regulator_register(pdev, i, init_data,
+ regul, buck2_rdev);
+ if (IS_ERR(rdev))
+ return PTR_ERR(rdev);
+
+ ret = stpmu1_regulator_init(pdev, rdev);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to initialize regulator %d\n",
+ ret);
+ return ret;
+ }
+
+ if (regul->regul_id == STPMU1_BUCK2)
+ buck2_rdev = rdev;
+
+ regul++;
+ }
+ }
+
+ dev_dbg(&pdev->dev, "stpmu1_regulator driver probed\n");
+
+ return 0;
+}
+
+static const struct of_device_id of_pmic_regulator_match[] = {
+ { .compatible = "st,stpmu1-regulators" },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, of_pmic_regulator_match);
+
+static struct platform_driver stpmu1_regulator_driver = {
+ .driver = {
+ .name = "stpmu1-regulator",
+ .of_match_table = of_match_ptr(of_pmic_regulator_match),
+ },
+ .probe = stpmu1_regulator_probe,
+};
+
+module_platform_driver(stpmu1_regulator_driver);
+
+MODULE_DESCRIPTION("STPMU1 PMIC voltage regulator driver");
+MODULE_AUTHOR("<[email protected]>");
+MODULE_LICENSE("GPL");
--
1.9.1

2018-07-05 15:18:12

by Pascal Paillet

[permalink] [raw]
Subject: [PATCH 6/8] input: stpmu1: add stpmu1 onkey driver

From: pascal paillet <[email protected]>

The stpmu1 pmic is able to manage an onkey button. This driver exposes
the stpmu1 onkey as an input device. It can also be configured to
shut-down the power supplies on a long key-press with an adjustable
duration.

Signed-off-by: pascal paillet <[email protected]>
---
drivers/input/misc/Kconfig | 11 ++
drivers/input/misc/Makefile | 2 +
drivers/input/misc/stpmu1_onkey.c | 321 ++++++++++++++++++++++++++++++++++++++
3 files changed, 334 insertions(+)
create mode 100644 drivers/input/misc/stpmu1_onkey.c

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index c25606e..d020971 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -841,4 +841,15 @@ config INPUT_RAVE_SP_PWRBUTTON
To compile this driver as a module, choose M here: the
module will be called rave-sp-pwrbutton.

+config INPUT_STPMU1_ONKEY
+ tristate "STPMU1 PMIC Onkey support"
+ depends on MFD_STPMU1
+ help
+ Say Y to enable support of onkey embedded into STPMU1 PMIC. onkey
+ can be used to wakeup from low power modes and force a shut-down on
+ long press.
+
+ To compile this driver as a module, choose M here: the
+ module will be called stpmu1_onkey.
+
endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 72cde28..cc60dc1 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -70,6 +70,7 @@ obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o
obj-$(CONFIG_INPUT_SIRFSOC_ONKEY) += sirfsoc-onkey.o
obj-$(CONFIG_INPUT_SOC_BUTTON_ARRAY) += soc_button_array.o
obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o
+obj-$(CONFIG_INPUT_STPMU1_ONKEY) += stpmu1_onkey.o
obj-$(CONFIG_INPUT_TPS65218_PWRBUTTON) += tps65218-pwrbutton.o
obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o
obj-$(CONFIG_INPUT_TWL4030_VIBRA) += twl4030-vibra.o
@@ -80,3 +81,4 @@ obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o
obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o
obj-$(CONFIG_INPUT_YEALINK) += yealink.o
obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR) += ideapad_slidebar.o
+
diff --git a/drivers/input/misc/stpmu1_onkey.c b/drivers/input/misc/stpmu1_onkey.c
new file mode 100644
index 0000000..084a31f
--- /dev/null
+++ b/drivers/input/misc/stpmu1_onkey.c
@@ -0,0 +1,321 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
+ * Author: Philippe Peurichard <[email protected]>,
+ * Pascal Paillet <[email protected]> for STMicroelectronics.
+ */
+
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/stpmu1.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/**
+ * struct stpmu1_onkey - OnKey data
+ * @pmic: pointer to STPMU1 PMIC device
+ * @input_dev: pointer to input device
+ * @irq_falling: irq that we are hooked on to
+ * @irq_rising: irq that we are hooked on to
+ */
+struct stpmu1_onkey {
+ struct stpmu1_dev *pmic;
+ struct input_dev *input_dev;
+ int irq_falling;
+ int irq_rising;
+};
+
+/**
+ * struct pmic_onkey_config - configuration of pmic PONKEYn
+ * @turnoff_enabled: value to enable turnoff condition
+ * @cc_flag_clear: value to clear CC flag in case of PowerOff
+ * trigger by longkey press
+ * @onkey_pullup_val: value of PONKEY PullUp (active or inactive)
+ * @long_press_time_val: value for long press h/w shutdown event
+ */
+struct pmic_onkey_config {
+ bool turnoff_enabled;
+ bool cc_flag_clear;
+ u8 onkey_pullup_val;
+ u8 long_press_time_val;
+};
+
+/**
+ * onkey_falling_irq() - button press isr
+ * @irq: irq
+ * @pmic_onkey: onkey device
+ *
+ * Return: IRQ_HANDLED
+ */
+static irqreturn_t onkey_falling_irq(int irq, void *ponkey)
+{
+ struct stpmu1_onkey *onkey = ponkey;
+ struct input_dev *input_dev = onkey->input_dev;
+
+ input_report_key(input_dev, KEY_POWER, 1);
+ pm_wakeup_event(input_dev->dev.parent, 0);
+ input_sync(input_dev);
+
+ dev_dbg(&input_dev->dev, "Pwr Onkey Falling Interrupt received\n");
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * onkey_rising_irq() - button released isr
+ * @irq: irq
+ * @pmic_onkey: onkey device
+ *
+ * Return: IRQ_HANDLED
+ */
+static irqreturn_t onkey_rising_irq(int irq, void *ponkey)
+{
+ struct stpmu1_onkey *onkey = ponkey;
+ struct input_dev *input_dev = onkey->input_dev;
+
+ input_report_key(input_dev, KEY_POWER, 0);
+ pm_wakeup_event(input_dev->dev.parent, 0);
+ input_sync(input_dev);
+
+ dev_dbg(&input_dev->dev, "Pwr Onkey Rising Interrupt received\n");
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * stpmu1_onkey_dt_params() - device tree parameter parser
+ * @pdev: platform device for the PONKEY
+ * @onkey: pointer to onkey driver data
+ * @config: configuration params to be filled up
+ */
+static int stpmu1_onkey_dt_params(struct platform_device *pdev,
+ struct stpmu1_onkey *onkey,
+ struct pmic_onkey_config *config)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np;
+ u32 val;
+
+ np = dev->of_node;
+ if (!np)
+ return -EINVAL;
+
+ onkey->irq_falling = platform_get_irq_byname(pdev, "onkey-falling");
+ if (onkey->irq_falling < 0) {
+ dev_err(dev, "failed: request IRQ onkey-falling %d",
+ onkey->irq_falling);
+ return onkey->irq_falling;
+ }
+
+ onkey->irq_rising = platform_get_irq_byname(pdev, "onkey-rising");
+ if (onkey->irq_rising < 0) {
+ dev_err(dev, "failed: request IRQ onkey-rising %d",
+ onkey->irq_rising);
+ return onkey->irq_rising;
+ }
+
+ if (!of_property_read_u32(np, "st,onkey-long-press-seconds", &val)) {
+ if (val >= 1 && val <= 16) {
+ config->long_press_time_val = (16 - val);
+ } else {
+ dev_warn(dev,
+ "Invalid range of long key pressed timer %d (<16)\n\r",
+ val);
+ }
+ }
+ if (of_get_property(np, "st,onkey-pwroff-enabled", NULL))
+ config->turnoff_enabled = true;
+
+ if (of_get_property(np, "st,onkey-clear-cc-flag", NULL))
+ config->cc_flag_clear = true;
+
+ if (of_get_property(np, "st,onkey-pu-inactive", NULL))
+ config->onkey_pullup_val = PONKEY_PU_ACTIVE;
+
+ dev_dbg(dev, "onkey-switch-off duration=%d seconds\n",
+ config->long_press_time_val);
+
+ return 0;
+}
+
+/**
+ * stpmu1_onkey_probe() - probe
+ * @pdev: platform device for the PONKEY
+ *
+ * Return: 0 for successful probe else appropriate error
+ */
+static int stpmu1_onkey_probe(struct platform_device *pdev)
+{
+ struct stpmu1_dev *pmic = dev_get_drvdata(pdev->dev.parent);
+ struct device *dev = &pdev->dev;
+ struct input_dev *input_dev;
+ struct stpmu1_onkey *onkey;
+ struct pmic_onkey_config config;
+ unsigned int val = 0;
+ int ret;
+
+ onkey = devm_kzalloc(dev, sizeof(*onkey), GFP_KERNEL);
+ if (!onkey)
+ return -ENOMEM;
+
+ memset(&config, 0, sizeof(struct pmic_onkey_config));
+ ret = stpmu1_onkey_dt_params(pdev, onkey, &config);
+ if (ret < 0)
+ goto err;
+
+ input_dev = devm_input_allocate_device(dev);
+ if (!input_dev) {
+ dev_err(dev, "Can't allocate Pwr Onkey Input Device\n");
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ input_dev->name = "pmic_onkey";
+ input_dev->phys = "pmic_onkey/input0";
+ input_dev->dev.parent = dev;
+
+ input_set_capability(input_dev, EV_KEY, KEY_POWER);
+
+ /* Setup Power Onkey Hardware parameters (long key press)*/
+ val = (config.long_press_time_val & PONKEY_TURNOFF_TIMER_MASK);
+ if (config.turnoff_enabled)
+ val |= PONKEY_PWR_OFF;
+ if (config.cc_flag_clear)
+ val |= PONKEY_CC_FLAG_CLEAR;
+ ret = regmap_update_bits(pmic->regmap, PKEY_TURNOFF_CR,
+ PONKEY_TURNOFF_MASK, val);
+ if (ret) {
+ dev_err(dev, "LONG_PRESS_KEY_UPDATE failed: %d\n", ret);
+ goto err;
+ }
+
+ ret = regmap_update_bits(pmic->regmap, PADS_PULL_CR,
+ PONKEY_PU_ACTIVE,
+ config.onkey_pullup_val);
+
+ if (ret) {
+ dev_err(dev, "ONKEY Pads configuration failed: %d\n", ret);
+ goto err;
+ }
+
+ onkey->pmic = pmic;
+ onkey->input_dev = input_dev;
+
+ ret = devm_request_threaded_irq(dev, onkey->irq_falling, NULL,
+ onkey_falling_irq,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ dev_name(dev), onkey);
+ if (ret) {
+ dev_err(dev, "Can't get IRQ for Onkey Falling edge: %d\n", ret);
+ goto err;
+ }
+
+ ret = devm_request_threaded_irq(dev, onkey->irq_rising, NULL,
+ onkey_rising_irq,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ dev_name(dev), onkey);
+ if (ret) {
+ dev_err(dev, "Can't get IRQ for Onkey Rising edge: %d\n", ret);
+ goto err;
+ }
+
+ ret = input_register_device(input_dev);
+ if (ret) {
+ dev_err(dev, "Can't register power button: %d\n", ret);
+ goto err;
+ }
+
+ platform_set_drvdata(pdev, onkey);
+ device_init_wakeup(dev, true);
+
+ dev_dbg(dev, "PMIC Pwr Onkey driver probed\n");
+
+err:
+ return ret;
+}
+
+/**
+ * stpmu1_onkey_remove() - Cleanup on removal
+ * @pdev: platform device for the button
+ *
+ * Return: 0
+ */
+static int stpmu1_onkey_remove(struct platform_device *pdev)
+{
+ struct stpmu1_onkey *onkey = platform_get_drvdata(pdev);
+
+ input_unregister_device(onkey->input_dev);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+
+/**
+ * pmic_onkey_suspend() - suspend handler
+ * @dev: power button device
+ *
+ * Cancel all pending work items for the power button, setup irq for wakeup
+ *
+ * Return: 0
+ */
+static int __maybe_unused stpmu1_onkey_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct stpmu1_onkey *onkey = platform_get_drvdata(pdev);
+
+ if (device_may_wakeup(dev)) {
+ enable_irq_wake(onkey->irq_falling);
+ enable_irq_wake(onkey->irq_rising);
+ }
+ return 0;
+}
+
+/**
+ * pmic_onkey_resume() - resume handler
+ * @dev: power button device
+ *
+ * Just disable the wakeup capability of irq here.
+ *
+ * Return: 0
+ */
+static int __maybe_unused stpmu1_onkey_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct stpmu1_onkey *onkey = platform_get_drvdata(pdev);
+
+ if (device_may_wakeup(dev)) {
+ disable_irq_wake(onkey->irq_falling);
+ disable_irq_wake(onkey->irq_rising);
+ }
+ return 0;
+}
+
+#endif
+
+static SIMPLE_DEV_PM_OPS(stpmu1_onkey_pm,
+ stpmu1_onkey_suspend,
+ stpmu1_onkey_resume);
+
+static const struct of_device_id of_stpmu1_onkey_match[] = {
+ { .compatible = "st,stpmu1-onkey" },
+ { },
+};
+
+MODULE_DEVICE_TABLE(of, of_stpmu1_onkey_match);
+
+static struct platform_driver stpmu1_onkey_driver = {
+ .probe = stpmu1_onkey_probe,
+ .remove = stpmu1_onkey_remove,
+ .driver = {
+ .name = "stpmu1_onkey",
+ .of_match_table = of_match_ptr(of_stpmu1_onkey_match),
+ .pm = &stpmu1_onkey_pm,
+ },
+};
+module_platform_driver(stpmu1_onkey_driver);
+
+MODULE_DESCRIPTION("Onkey driver for STPMU1");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("[email protected]>");
--
1.9.1

2018-07-05 15:19:13

by Pascal Paillet

[permalink] [raw]
Subject: [PATCH 7/8] dt-bindings: watchdog: document stpmu1 pmic watchdog

From: pascal paillet <[email protected]>

The stpmu1 PMIC embeds a watchdog which is disabled by default.
In case of watchdog, the PMIC goes off.

Signed-off-by: pascal paillet <[email protected]>
---
Documentation/devicetree/bindings/watchdog/st,stpmu1-wdt.txt | 11 +++++++++++
1 file changed, 11 insertions(+)
create mode 100644 Documentation/devicetree/bindings/watchdog/st,stpmu1-wdt.txt

diff --git a/Documentation/devicetree/bindings/watchdog/st,stpmu1-wdt.txt b/Documentation/devicetree/bindings/watchdog/st,stpmu1-wdt.txt
new file mode 100644
index 0000000..b558b3c
--- /dev/null
+++ b/Documentation/devicetree/bindings/watchdog/st,stpmu1-wdt.txt
@@ -0,0 +1,11 @@
+STMicroelectronics STPMU1 Watchdog
+
+Required properties:
+
+- compatible : should be "st,stpmu1-wdt"
+
+Example:
+
+watchdog {
+ compatible = "st,stpmu1-wdt";
+};
--
1.9.1

2018-07-05 15:19:21

by Pascal Paillet

[permalink] [raw]
Subject: [PATCH 1/8] dt-bindings: mfd: document stpmu1 pmic

From: pascal paillet <[email protected]>

stpmu1 is a pmic from STMicroelectronics. The stpmu1 integrates 10
regulators and 3 switches with various capabilities.

Signed-off-by: pascal paillet <[email protected]>
---
.../devicetree/bindings/mfd/st,stpmu1.txt | 138 +++++++++++++++++++++
1 file changed, 138 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mfd/st,stpmu1.txt

diff --git a/Documentation/devicetree/bindings/mfd/st,stpmu1.txt b/Documentation/devicetree/bindings/mfd/st,stpmu1.txt
new file mode 100644
index 0000000..53bdab4
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/st,stpmu1.txt
@@ -0,0 +1,138 @@
+* STMicroelectronics STPMU1 Power Management IC
+
+Required parent device properties:
+- compatible: "st,stpmu1"
+- reg: the I2C slave address for the stpmu1 chip
+- interrupts-extended: interrupt lines to use: second irq is for wakeup.
+- #interrupt-cells: should be 2.
+- interrupt-controller: describes the STPMU1 as an interrupt
+ controller (has its own domain). interrupt number are the following:
+ /* Interrupt Register 1 (0x50 for latch) */
+ IT_SWOUT_R=0
+ IT_SWOUT_F=1
+ IT_VBUS_OTG_R=2
+ IT_VBUS_OTG_F=3
+ IT_WAKEUP_R=4
+ IT_WAKEUP_F=5
+ IT_PONKEY_R=6
+ IT_PONKEY_F=7
+ /* Interrupt Register 2 (0x51 for latch) */
+ IT_OVP_BOOST=8
+ IT_OCP_BOOST=9
+ IT_OCP_SWOUT=10
+ IT_OCP_OTG=11
+ IT_CURLIM_BUCK4=12
+ IT_CURLIM_BUCK3=13
+ IT_CURLIM_BUCK2=14
+ IT_CURLIM_BUCK1=15
+ /* Interrupt Register 3 (0x52 for latch) */
+ IT_SHORT_SWOUT=16
+ IT_SHORT_SWOTG=17
+ IT_CURLIM_LDO6=18
+ IT_CURLIM_LDO5=19
+ IT_CURLIM_LDO4=20
+ IT_CURLIM_LDO3=21
+ IT_CURLIM_LDO2=22
+ IT_CURLIM_LDO1=23
+ /* Interrupt Register 3 (0x52 for latch) */
+ IT_SWIN_R=24
+ IT_SWIN_F=25
+ IT_RESERVED_1=26
+ IT_RESERVED_2=27
+ IT_VINLOW_R=28
+ IT_VINLOW_F=29
+ IT_TWARN_R=30
+ IT_TWARN_F=31
+
+Optional parent device properties:
+- st,main_control_register:
+ -bit 1: Power cycling will be performed on turn OFF condition
+ -bit 2: PWRCTRL is functional
+ -bit 3: PWRCTRL active high
+- st,pads_pull_register:
+ -bit 1: WAKEUP pull down is not active
+ -bit 2: PWRCTRL pull up is active
+ -bit 3: PWRCTRL pull down is active
+ -bit 4: WAKEUP detector is disabled
+- st,vin_control_register:
+ -bit 0: VINLOW monitoring is enabled
+ -bit [1...3]: VINLOW rising threshold
+ 000 VINOK_f + 50mV
+ 001 VINOK_f + 100mV
+ 010 VINOK_f + 150mV
+ 011 VINOK_f + 200mV
+ 100 VINOK_f + 250mV
+ 101 VINOK_f + 300mV
+ 110 VINOK_f + 350mV
+ 111 VINOK_f + 400mV
+ -bit [4...5]: VINLOW hyst
+ 00 100mV
+ 01 200mV
+ 10 300mV
+ 11 400mV
+ -bit 6: SW_OUT detector is disabled
+ -bit 7: SW_IN detector is enabled.
+- st,usb_control_register:
+ -bit 3: SW_OUT current limit
+ 0: 600mA
+ 1: 1.1A
+ -bit 4: VBUS_OTG discharge is enabled
+ -bit 5: SW_OUT discharge is enabled
+ -bit 6: VBUS_OTG detection is enabled
+ -bit 7: BOOST_OVP is disabled
+
+
+stpmu1 consists is a varied group of sub-devices:
+
+Device Description
+------ ------------
+stpmu1-onkey : On key
+stpmu1-regulators : Regulators
+stpmu1-wdt : Watchdog
+
+each sub-device bindings is be described in associated driver
+documentation section.
+
+Example:
+
+pmic: stpmu1@33 {
+ compatible = "st,stpmu1";
+ reg = <0x33>;
+ interrupts = <0 2>;
+ interrupts-extended = <&intc GIC_SPI 149 IRQ_TYPE_NONE>,
+ <&exti 55 1>;
+ st,version_status = <0x10>;
+ st,main_control_register=<0x0c>;
+ interrupt-controller;
+ #interrupt-cells = <2>;
+ onkey {
+ compatible = "st,stpmu1-onkey";
+ interrupt-parent = <&pmic>;
+ interrupts = <7 0>,<6 1>;
+ st,onkey-pwroff-enabled;
+ st,onkey-press-seconds = <10>;
+ };
+
+ watchdog {
+ compatible = "st,stpmu1-wdt";
+ };
+
+ regulators {
+ compatible = "st,stpmu1-regulators";
+
+ vdd_core: regulator@0 {
+ regulator-compatible = "buck1";
+ regulator-name = "vdd_core";
+ regulator-boot-on;
+ regulator-min-microvolt = <700000>;
+ regulator-max-microvolt = <1200000>;
+ };
+ vdd: regulator@1 {
+ regulator-compatible = "buck3";
+ regulator-name = "vdd";
+ regulator-min-microvolt = <3300000>;
+ regulator-max-microvolt = <3300000>;
+ regulator-boot-on;
+ regulator-pull-down;
+ };
+ };
--
1.9.1

2018-07-05 15:19:49

by Pascal Paillet

[permalink] [raw]
Subject: [PATCH 5/8] dt-bindings: input: document stpmu1 pmic onkey

From: pascal paillet <[email protected]>

The stpmu1 pmic is able to manage an onkey button. It can be configured
to shut-down the power supplies on a long key-press with an adjustable
duration.

Signed-off-by: pascal paillet <[email protected]>
---
.../devicetree/bindings/input/st,stpmu1-onkey.txt | 31 ++++++++++++++++++++++
1 file changed, 31 insertions(+)
create mode 100644 Documentation/devicetree/bindings/input/st,stpmu1-onkey.txt

diff --git a/Documentation/devicetree/bindings/input/st,stpmu1-onkey.txt b/Documentation/devicetree/bindings/input/st,stpmu1-onkey.txt
new file mode 100644
index 0000000..cc42b7f
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/st,stpmu1-onkey.txt
@@ -0,0 +1,31 @@
+STMicroelectronics STPMU1 Onkey
+
+Required properties:
+
+- compatible = "st,stpmu1-onkey";
+- interrupt-parent: phandle to the parent interrupt controller
+- interrupts: interrupt line to use
+- interrupt-names = "onkey-falling", "onkey-rising"
+ onkey-falling: happens when onkey is pressed; IT_PONKEY_F of pmic
+ onkey-rising: happens when onkey is released; IT_PONKEY_R of pmic
+
+Optional properties:
+
+- st,onkey-pwroff-enabled: power off on long key-press
+- st,onkey-long-press-seconds: long key-press duration from 1 to 16s
+ (default 16s)
+- st,onkey-clear-cc-flag: onkey is able power on after an
+ over-current shutdown event.
+- st,onkey-pu-inactive: onkey pull up is not active
+
+Example:
+
+onkey {
+ compatible = "st,stpmu1-onkey";
+ interrupt-parent = <&pmic>;
+ interrupts = <7 0>,<6 1>;
+ interrupt-names = "onkey-falling", "onkey-rising";
+ status = "okay";
+ st,onkey-pwroff-enabled;
+ st,onkey-long-press-seconds = <10>;
+};
--
1.9.1

2018-07-05 16:49:34

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 4/8] regulator: stpmu1: add stpmu1 regulator driver

On Thu, Jul 05, 2018 at 03:14:23PM +0000, Pascal PAILLET-LME wrote:

> obj-$(CONFIG_REGULATOR_FIXED_VOLTAGE) += fixed.o
> obj-$(CONFIG_REGULATOR_VIRTUAL_CONSUMER) += virtual.o
> obj-$(CONFIG_REGULATOR_USERSPACE_CONSUMER) += userspace-consumer.o
> +obj-$(CONFIG_PROTECTION_CONSUMER) += protection-consumer.o
>
> obj-$(CONFIG_REGULATOR_88PG86X) += 88pg86x.o
> obj-$(CONFIG_REGULATOR_88PM800) += 88pm800.o

Looks like this got included by mistake...

> --- /dev/null
> +++ b/drivers/regulator/stpmu1_regulator.c
> @@ -0,0 +1,919 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) STMicroelectronics 2018 - All Rights Reserved

All rights reserved and GPL :)

Please also make the entire comment a C++ one so the block looks more
intentional.

> +static unsigned int stpmu1_map_mode(unsigned int mode)
> +{
> + return mode == REGULATOR_MODE_STANDBY ?
> + REGULATOR_MODE_STANDBY : REGULATOR_MODE_NORMAL;
> +}

This looks confused - what's going on here? Normally a map_mode()
operation would be translating values in DT but this translates
everything that isn't standby into normal which suggests there's an
error checking problem.

> +static int stpmu1_ldo3_list_voltage(struct regulator_dev *rdev,
> + unsigned int sel)
> +{
> + struct stpmu1_regulator *regul = rdev_get_drvdata(rdev);
> +
> + if (sel == 0)
> + return regulator_list_voltage_linear_range(rdev, 1);
> +
> + if (sel < 31)
> + return regulator_list_voltage_linear_range(rdev, sel);
> +
> + if (sel == 31)
> + return stpmu1_get_voltage_regmap(regul->voltage_ref_reg) / 2;
> +
> + return -EINVAL;
> +}

The only thing that's going on here that's odd and that couldn't be
represented with a helper is selector 31 which looks to be some sort of
divided bypass mode - can you explain what this represents in hardware
terms please?

> +static int stpmu1_ldo3_get_voltage(struct regulator_dev *rdev)
> +{
> + int sel = regulator_get_voltage_sel_regmap(rdev);
> +
> + if (sel < 0)
> + return -EINVAL;
> +
> + return stpmu1_ldo3_list_voltage(rdev, (unsigned int)sel);
> +}

This is just the standard core behaviour, no need for this.

> +static int stpmu1_fixed_regul_get_voltage(struct regulator_dev *rdev)
> +{
> + struct stpmu1_regulator *regul = rdev_get_drvdata(rdev);
> + int id = rdev_get_id(rdev);
> +
> + /* VREF_DDR voltage is equal to Buck2/2 */
> + if (id == STPMU1_VREF_DDR)
> + return stpmu1_get_voltage_regmap(regul->voltage_ref_reg) / 2;
> +
> + /* For all other case , rely on fixed value defined by Hw settings */
> + return regul->cfg->desc.fixed_uV;
> +}

It'd be better to just use a separate set of operations for the DDR
regulator rather than have a conditional here, less code and makes it
clearer that this one is a special case.

> +static void update_regulator_constraints(int index,
> + struct regulator_init_data *init_data)
> +{
> + struct stpmu1_regulator_cfg *cfg = &stpmu1_regulator_cfgs[index];
> + struct regulator_desc *desc = &cfg->desc;
> +
> + init_data->constraints.valid_ops_mask |=
> + cfg->valid_ops_mask;
> + init_data->constraints.valid_modes_mask |=
> + cfg->valid_modes_mask;

Drivers should never be modifying their constraints, this is a no no.

> + /*
> + * if all constraints are not specified in DT , ensure Hw
> + * constraints are met
> + */
> + if (desc->n_voltages > 1) {

Drivers shouldn't be doing this either. The API will not allow any
modifications of hardware state without constraints so unless the
bootloader is leaving things in a broken state we should be fine.

> + if (!init_data->constraints.ramp_delay)
> + init_data->constraints.ramp_delay = PMIC_RAMP_SLOPE_UV_PER_US;
> +
> + if (!init_data->constraints.enable_time)
> + init_data->constraints.enable_time = PMIC_ENABLE_TIME_US;

If these are hard constraints we know from the chip design they should
be being set in the descriptor. The constraints are there to override
if either they're board dependent or the board needs something longer
than the chip default.

> + /* LDO3 and VREF_DDR can use buck2 as reference voltage */
> + if (regul->regul_id == STPMU1_LDO3 ||
> + regul->regul_id == STPMU1_VREF_DDR) {
> + if (!buck2) {
> + dev_err(&pdev->dev,
> + "Error in PMIC regulator settings: Buck2 is not defined prior to LDO3 or VREF_DDR regulators\n"
> + );
> + return ERR_PTR(-EINVAL);
> + }
> + regul->voltage_ref_reg = buck2;
> + }

Can or do? Usually this would be hooked up in the DT (with the
regulator specifying a supply name in the desc).

> + np = pdev->dev.of_node;
> + if (!np) {
> + dev_err(&pdev->dev, "regulators node not found\n");
> + return -EINVAL;
> + }
> +

May as well let the driver probe?

> + /* Register all defined (from DT) regulators to Regulator Framework */
> + for (i = 0; i < ARRAY_SIZE(stpmu1_regulator_cfgs); i++) {
> + /* Parse DT & find regulators to register */
> + init_data = stpmu1_regulators_matches[i].init_data;

You should register everything that's physically there unconditionally,
there's no harm in having a regulator registered that's not used and it
might be useful for debugging purposes.

> +static const struct of_device_id of_pmic_regulator_match[] = {
> + { .compatible = "st,stpmu1-regulators" },
> + { },
> +};

This is part of a MFD for a single chip, why do we need a specific
compatible string here rather than just enumerating from the base
registration of the device?


Attachments:
(No filename) (5.41 kB)
signature.asc (499.00 B)
Download all attachments

2018-07-05 18:49:57

by Guenter Roeck

[permalink] [raw]
Subject: Re: [PATCH 8/8] watchdog: stpmu1: add stpmu1 watchdog driver

On 07/05/2018 08:14 AM, Pascal PAILLET-LME wrote:
> From: pascal paillet <[email protected]>
>
> The stpmu1 PMIC embeds a watchdog which is disabled by default. As soon
> as the watchdog is started, it must be refreshed periodically otherwise
> the PMIC goes off.
>
> Signed-off-by: pascal paillet <[email protected]>
> ---
> drivers/watchdog/Kconfig | 12 +++
> drivers/watchdog/Makefile | 1 +
> drivers/watchdog/stpmu1_wdt.c | 177 ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 190 insertions(+)
> create mode 100644 drivers/watchdog/stpmu1_wdt.c
>
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index 9af07fd..2155f4d 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -796,6 +796,18 @@ config STM32_WATCHDOG
> To compile this driver as a module, choose M here: the
> module will be called stm32_iwdg.
>
> +config STPMU1_WATCHDOG
> + tristate "STPMU1 PMIC watchdog support"
> + depends on MFD_STPMU1
> + select WATCHDOG_CORE
> + help
> + Say Y here to include watchdog support embedded into STPMU1 PMIC.
> + If the watchdog timer expires, stpmu1 shut-down all its power
> + supplies.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called spmu1_wdt.
> +
> config UNIPHIER_WATCHDOG
> tristate "UniPhier watchdog support"
> depends on ARCH_UNIPHIER || COMPILE_TEST
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index 1d3c6b0..c9eba94 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -216,3 +216,4 @@ obj-$(CONFIG_ZIIRAVE_WATCHDOG) += ziirave_wdt.o
> obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
> obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o
> obj-$(CONFIG_RAVE_SP_WATCHDOG) += rave-sp-wdt.o
> +obj-$(CONFIG_STPMU1_WATCHDOG) += stpmu1_wdt.o
> diff --git a/drivers/watchdog/stpmu1_wdt.c b/drivers/watchdog/stpmu1_wdt.c
> new file mode 100644
> index 0000000..57e0afa
> --- /dev/null
> +++ b/drivers/watchdog/stpmu1_wdt.c
> @@ -0,0 +1,177 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
> + * Author: Philippe Peurichard <[email protected]>,
> + * Pascal Paillet <[email protected]> for STMicroelectronics.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/mfd/stpmu1.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/of.h>
> +#include <linux/regmap.h>
> +#include <linux/reboot.h>
> +#include <linux/slab.h>
> +#include <linux/watchdog.h>
> +
> +/* WATCHDOG CONTROL REGISTER bit */
> +#define WDT_START BIT(0)
> +#define WDT_PING BIT(1)
> +#define WDT_START_MASK BIT(0)
> +#define WDT_PING_MASK BIT(1)
> +
> +#define PMIC_WDT_MIN_TIMEOUT 1
> +#define PMIC_WDT_MAX_TIMEOUT 256
> +
> +struct stpmu1_wdt {
> + struct stpmu1_dev *pmic;
> + struct watchdog_device wdtdev;
> + struct notifier_block restart_handler;
> +};
> +
> +static int pmic_wdt_start(struct watchdog_device *wdd)
> +{
> + struct stpmu1_wdt *wdt = watchdog_get_drvdata(wdd);
> +
> + return regmap_update_bits(wdt->pmic->regmap,
> + WCHDG_CR, WDT_START_MASK, WDT_START);
> +}
> +
> +static int pmic_wdt_stop(struct watchdog_device *wdd)
> +{
> + struct stpmu1_wdt *wdt = watchdog_get_drvdata(wdd);
> +
> + return regmap_update_bits(wdt->pmic->regmap,
> + WCHDG_CR, WDT_START_MASK, ~WDT_START);
> +}
> +
> +static int pmic_wdt_ping(struct watchdog_device *wdd)
> +{
> + struct stpmu1_wdt *wdt = watchdog_get_drvdata(wdd);
> + int ret;
> +
> + return regmap_update_bits(wdt->pmic->regmap,
> + WCHDG_CR, WDT_PING_MASK, WDT_PING);
> + return ret;
> +}
> +
> +static int pmic_wdt_set_timeout(struct watchdog_device *wdd,
> + unsigned int timeout)
> +{
> + struct stpmu1_wdt *wdt = watchdog_get_drvdata(wdd);
> + int ret;
> +
> + ret = regmap_write(wdt->pmic->regmap, WCHDG_TIMER_CR, timeout);
> + if (ret)
> + dev_err(wdt->pmic->dev,
> + "Failed to set watchdog timeout (err = %d)\n", ret);
> + else
> + wdd->timeout = PMIC_WDT_MAX_TIMEOUT;

First the requested timeout is set, then the caller is notified
that the timeout was set to the maximum possible value ? That doesn't
make sense. If that is really intentional, I would expect a detailed
explanation, and I would expect that the value written into the chip
register matches the value reported back to the user.

> +
> + return ret;
> +}
> +
> +static int pmic_wdt_restart_handler(struct notifier_block *this,
> + unsigned long mode, void *cmd)
> +{
> + struct stpmu1_wdt *wdt = container_of(this,
> + struct stpmu1_wdt,
> + restart_handler);
> +
> + dev_info(wdt->pmic->dev,
> + "PMIC Watchdog Elapsed (timeout %d), shutdown of PMIC initiated\n",
> + wdt->wdtdev.timeout);
> +

Register a restart handler just to issue a message ? That is quite pointless.
A restart handler is supposed to restart the system. Besides, the message
is highly misleading; there is no reason to believe that it will be called
after the watchdog expired.

This function should restart the system. If it doesn't, drop it.

> + return NOTIFY_DONE;
> +}
> +
> +static const struct watchdog_info pmic_watchdog_info = {
> + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
> + .identity = "STPMU1 PMIC Watchdog",
> +};
> +
> +static const struct watchdog_ops pmic_watchdog_ops = {
> + .owner = THIS_MODULE,
> + .start = pmic_wdt_start,
> + .stop = pmic_wdt_stop,
> + .ping = pmic_wdt_ping,
> + .set_timeout = pmic_wdt_set_timeout,
> +};
> +
> +static int pmic_wdt_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct stpmu1_dev *pmic;
> + struct stpmu1_wdt *wdt;
> +
> + if (!pdev->dev.parent)
> + return -EINVAL;
> +
> + pmic = dev_get_drvdata(pdev->dev.parent);
> + if (!pmic)
> + return -EINVAL;
> +
> + wdt = devm_kzalloc(&pdev->dev, sizeof(struct stpmu1_wdt), GFP_KERNEL);
> + if (!wdt)
> + return -ENOMEM;
> +
> + wdt->pmic = pmic;
> +
> + wdt->wdtdev.info = &pmic_watchdog_info;
> + wdt->wdtdev.ops = &pmic_watchdog_ops;
> + wdt->wdtdev.min_timeout = PMIC_WDT_MIN_TIMEOUT;
> + wdt->wdtdev.max_timeout = PMIC_WDT_MAX_TIMEOUT;
> + wdt->wdtdev.timeout = PMIC_WDT_MAX_TIMEOUT;

256 seconds default timeout ? Unusual, just making sure that this is what you want.

> +
> + wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
> +
> + watchdog_set_drvdata(&wdt->wdtdev, wdt);
> + dev_set_drvdata(&pdev->dev, wdt);
> +
> + ret = watchdog_register_device(&wdt->wdtdev);
> + if (ret)
> + return ret;
> +
> + wdt->restart_handler.notifier_call = pmic_wdt_restart_handler;
> + wdt->restart_handler.priority = 128;
> + ret = register_restart_handler(&wdt->restart_handler);

Why is the restart handler provided by the watchdog core not sufficient ?


> + if (ret) {
> + dev_err(wdt->pmic->dev, "failed to register restart handler\n");
> + return ret;
> + }
> +
> + dev_dbg(wdt->pmic->dev, "PMIC Watchdog driver probed\n");

The only reasons not to use the devm_ function to register the watchdog
are the restart handler, which 1) doesn't do anything and 2) should use
the watchdog core, and this debug message. I would suggest to use the
devm_ function to register the watchdog instead.

> + return 0;
> +}
> +
> +static int pmic_wdt_remove(struct platform_device *pdev)
> +{
> + struct stpmu1_wdt *wdt = dev_get_drvdata(&pdev->dev);
> +
> + unregister_restart_handler(&wdt->restart_handler);
> + watchdog_unregister_device(&wdt->wdtdev);
> +
> + return 0;
> +}
> +
> +static const struct of_device_id of_pmic_wdt_match[] = {
> + { .compatible = "st,stpmu1-wdt" },
> + { },
> +};
> +
> +MODULE_DEVICE_TABLE(of, of_pmic_wdt_match);
> +
> +static struct platform_driver stpmu1_wdt_driver = {
> + .probe = pmic_wdt_probe,
> + .remove = pmic_wdt_remove,
> + .driver = {
> + .name = "stpmu1-wdt",
> + .of_match_table = of_pmic_wdt_match,
> + },
> +};
> +module_platform_driver(stpmu1_wdt_driver);
> +
> +MODULE_AUTHOR("[email protected]>");
> +MODULE_DESCRIPTION("Watchdog driver for STPMU1 device");
> +MODULE_LICENSE("GPL");
>


2018-07-09 22:39:58

by Enric Balletbo Serra

[permalink] [raw]
Subject: Re: [PATCH 2/8] mfd: stpmu1: add stpmu1 pmic driver

Hi Pascal,

Thanks for the patch some comments below.

Missatge de Pascal PAILLET-LME <[email protected]> del dia dj., 5 de
jul. 2018 a les 17:17:
>
> From: pascal paillet <[email protected]>
>
> stpmu1 is a pmic from STMicroelectronics. The stpmu1 integrates 10
> regulators and 3 switches with various capabilities.
>
> Signed-off-by: pascal paillet <[email protected]>
> ---
> drivers/mfd/Kconfig | 14 ++
> drivers/mfd/Makefile | 1 +
> drivers/mfd/stpmu1.c | 490 ++++++++++++++++++++++++++++++++++++
> include/dt-bindings/mfd/st,stpmu1.h | 46 ++++
> include/linux/mfd/stpmu1.h | 220 ++++++++++++++++
> 5 files changed, 771 insertions(+)
> create mode 100644 drivers/mfd/stpmu1.c
> create mode 100644 include/dt-bindings/mfd/st,stpmu1.h
> create mode 100644 include/linux/mfd/stpmu1.h
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index b860eb5..e15140b 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -1812,6 +1812,20 @@ config MFD_STM32_TIMERS
> for PWM and IIO Timer. This driver allow to share the
> registers between the others drivers.
>
> +config MFD_STPMU1
> + tristate "Support for STPMU1 PMIC"
> + depends on (I2C=y && OF)
> + select REGMAP_I2C
> + select REGMAP_IRQ
> + select MFD_CORE
> + help
> + Support for ST Microelectronics STPMU1 PMIC. Stpmu1 mfd driver is
> + the core driver for stpmu1 component that mainly handles interrupts.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called stpmu1.
> +
> +
Extra line not needed.

> menu "Multimedia Capabilities Port drivers"
> depends on ARCH_SA1100
>
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index e9fd20d..f1c4be1 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -220,6 +220,7 @@ obj-$(CONFIG_INTEL_SOC_PMIC_CHTDC_TI) += intel_soc_pmic_chtdc_ti.o
> obj-$(CONFIG_MFD_MT6397) += mt6397-core.o
>
> obj-$(CONFIG_MFD_ALTERA_A10SR) += altera-a10sr.o
> +obj-$(CONFIG_MFD_STPMU1) += stpmu1.o
> obj-$(CONFIG_MFD_SUN4I_GPADC) += sun4i-gpadc.o
>
> obj-$(CONFIG_MFD_STM32_LPTIMER) += stm32-lptimer.o
> diff --git a/drivers/mfd/stpmu1.c b/drivers/mfd/stpmu1.c
> new file mode 100644
> index 0000000..a284a3e
> --- /dev/null
> +++ b/drivers/mfd/stpmu1.c
> @@ -0,0 +1,490 @@
> +// SPDX-License-Identifier: GPL-2.0

There is a license mismatch between SPDX and MODULE_LICENSE. Or SPDX
identifier should be GPL-2.0-or-later or MODULE_LICENSE should be
("GPL v2")

See https://elixir.bootlin.com/linux/latest/source/include/linux/module.h#L175

> +/*
> + * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
> + * Author: Philippe Peurichard <[email protected]>,
> + * Pascal Paillet <[email protected]> for STMicroelectronics.
> + */
> +
I think that Lee, like Linus, prefers the C++ style here

> +#include <linux/err.h>
That this include is not needed.

> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/mfd/core.h>
> +#include <linux/mfd/stpmu1.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_irq.h>
> +#include <linux/of_platform.h>
> +#include <linux/platform_device.h>
ditto
> +#include <linux/pm_runtime.h>
ditto

> +#include <linux/pm_wakeirq.h>
> +#include <linux/regmap.h>
> +#include <dt-bindings/mfd/st,stpmu1.h>
> +

[snip]

> +
> +static int stpmu1_configure_from_dt(struct stpmu1_dev *pmic_dev)
> +{
> + struct device_node *np = pmic_dev->np;
> + u32 reg = 0;
You don't need to initialize reg to 0, anyway will be overwriten.

> + int ret = 0;
You don't need to initialize ret to 0, anyway will be overwritten.

> + int irq;
> +
> + irq = of_irq_get(np, 0);
> + if (irq <= 0) {
> + dev_err(pmic_dev->dev,
> + "Failed to get irq config: %d\n", irq);
This can be in one line.

> + return irq ? irq : -ENODEV;
nit: return irq ?: -ENODEV;

> + }
> + pmic_dev->irq = irq;
> +
> + irq = of_irq_get(np, 1);
> + if (irq <= 0) {
> + dev_err(pmic_dev->dev,
> + "Failed to get irq_wake config: %d\n", irq);
> + return irq ? irq : -ENODEV;
nit: return irq ?: -ENODEV;

> + }
> + pmic_dev->irq_wake = irq;
> +
> + device_init_wakeup(pmic_dev->dev, true);
> + ret = dev_pm_set_dedicated_wake_irq(pmic_dev->dev, pmic_dev->irq_wake);
> + if (ret)
> + dev_warn(pmic_dev->dev, "failed to set up wakeup irq");
> +
> + if (!of_property_read_u32(np, "st,main_control_register", &reg)) {
> + ret = regmap_update_bits(pmic_dev->regmap,
> + SWOFF_PWRCTRL_CR,
> + PWRCTRL_POLARITY_HIGH |
> + PWRCTRL_PIN_VALID |
> + RESTART_REQUEST_ENABLED,
> + reg);
> + if (ret) {
> + dev_err(pmic_dev->dev,
> + "Failed to update main control register: %d\n",
> + ret);
> + return ret;
> + }
> + }
> +
> + if (!of_property_read_u32(np, "st,pads_pull_register", &reg)) {
> + ret = regmap_update_bits(pmic_dev->regmap,
> + PADS_PULL_CR,
> + WAKEUP_DETECTOR_DISABLED |
> + PWRCTRL_PD_ACTIVE |
> + PWRCTRL_PU_ACTIVE |
> + WAKEUP_PD_ACTIVE,
> + reg);
> + if (ret) {
> + dev_err(pmic_dev->dev,
> + "Failed to update pads control register: %d\n",
> + ret);
> + return ret;
> + }
> + }
> +
> + if (!of_property_read_u32(np, "st,vin_control_register", &reg)) {
> + ret = regmap_update_bits(pmic_dev->regmap,
> + VBUS_DET_VIN_CR,
> + VINLOW_CTRL_REG_MASK,
> + reg);
> + if (ret) {
> + dev_err(pmic_dev->dev,
> + "Failed to update vin control register: %d\n",
> + ret);
> + return ret;
> + }
> + }
> +
> + if (!of_property_read_u32(np, "st,usb_control_register", &reg)) {
> + ret = regmap_update_bits(pmic_dev->regmap, BST_SW_CR,
> + BOOST_OVP_DISABLED |
> + VBUS_OTG_DETECTION_DISABLED |
> + SW_OUT_DISCHARGE |
> + VBUS_OTG_DISCHARGE |
> + OCP_LIMIT_HIGH,
> + reg);
> + if (ret) {
> + dev_err(pmic_dev->dev,
> + "Failed to update usb control register: %d\n",
> + ret);
> + return ret;
> + }
> + }
> +
> + return 0;
> +}
> +
> +int stpmu1_device_init(struct stpmu1_dev *pmic_dev)
> +{
> + int ret;
> + unsigned int val;
> +
> + pmic_dev->regmap =
> + devm_regmap_init_i2c(pmic_dev->i2c, &stpmu1_regmap_config);
> +
> + if (IS_ERR(pmic_dev->regmap)) {
> + ret = PTR_ERR(pmic_dev->regmap);
You can remove this ...

> + dev_err(pmic_dev->dev, "Failed to allocate register map: %d\n",
> + ret);
> + return ret;
and just return PTR_ERR(pmic_dev->regmap);

> + }
> +
> + ret = stpmu1_configure_from_dt(pmic_dev);
> + if (ret < 0) {
Is ret >0 return valid? If not, perhaps "if (ret)" would be better.

> + dev_err(pmic_dev->dev,
> + "Unable to configure PMIC from Device Tree: %d\n", ret);
> + return ret;
> + }
> +
> + /* Read Version ID */
> + ret = regmap_read(pmic_dev->regmap, VERSION_SR, &val);
> + if (ret < 0) {
Is ret >0 return valid? If not, perhaps "if (ret)" would be better.

> + dev_err(pmic_dev->dev, "Unable to read pmic version\n");
> + return ret;
> + }
> + dev_dbg(pmic_dev->dev, "PMIC Chip Version: 0x%x\n", val);
nit: Maybe that should be dev_info instead of dev_dbg?

> +
> + /* Initialize PMIC IRQ Chip & IRQ domains associated */
> + ret = devm_regmap_add_irq_chip(pmic_dev->dev, pmic_dev->regmap,
> + pmic_dev->irq,
> + IRQF_ONESHOT | IRQF_SHARED,
> + 0, &stpmu1_regmap_irq_chip,
> + &pmic_dev->irq_data);
> + if (ret < 0) {
Is ret >0 return valid? If not, perhaps "if (ret)" would be better.

> + dev_err(pmic_dev->dev, "IRQ Chip registration failed: %d\n",
> + ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static const struct of_device_id stpmu1_dt_match[] = {
> + {.compatible = "st,stpmu1"},
> + {},
I'd rewrite this as
+ { .compatible = "st,stpmu1" },
+ { }
Space after/before brackets and no comma at the end. The sentinel
indicates the last item on structure/arrays so no need to add a comma
at the end.

> +};
> +
Remove this line

> +MODULE_DEVICE_TABLE(of, stpmu1_dt_match);
> +
> +static int stpmu1_remove(struct i2c_client *i2c)
> +{
> + struct stpmu1_dev *pmic_dev = i2c_get_clientdata(i2c);
> +
> + of_platform_depopulate(pmic_dev->dev);
> +
> + return 0;
> +}
You can remove this function, see below ...

> +
> +static int stpmu1_probe(struct i2c_client *i2c,
> + const struct i2c_device_id *id)
> +{
> + struct stpmu1_dev *pmic;
> + struct device *dev = &i2c->dev;
> + int ret = 0;
No need to initialize to 0 if ...

> +
> + pmic = devm_kzalloc(dev, sizeof(struct stpmu1_dev), GFP_KERNEL);
> + if (!pmic)
> + return -ENOMEM;
> +
> + pmic->np = dev->of_node;
> +
> + dev_set_drvdata(dev, pmic);
> + pmic->dev = dev;
> + pmic->i2c = i2c;
> +
> + ret = stpmu1_device_init(pmic);
> + if (ret < 0)
Is ret >0 return valid? If not, perhaps "if (ret)" would be better.
> + goto err;
return ret;

> +
> + ret = of_platform_populate(pmic->np, NULL, NULL, pmic->dev);
> +
ret = devm_of_platform_populate(pmic->dev);

or even better

return devm_of_platform_populate(pmic->dev);

And remove the stpmu1_remove function.

> + dev_dbg(dev, "stpmu1 driver probed\n");
That message looks redundant to me. I'd remove it.

> +err:
And you can remove this label.

> + return ret;
And this

> +}
> +
> +static const struct i2c_device_id stpmu1_id[] = {
> + {"stpmu1", 0},
> + {}
> +};
> +
> +MODULE_DEVICE_TABLE(i2c, stpmu1_id);
The above code shouldn't be needed anymore for DT-only devices. See
da10c06a044b ("i2c: Make I2C ID tables non-mandatory for DT'ed
devices")

> +
> +#ifdef CONFIG_PM_SLEEP
> +static int stpmu1_suspend(struct device *dev)
> +{
> + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
> + struct stpmu1_dev *pmic_dev = i2c_get_clientdata(i2c);
> +
> + if (device_may_wakeup(dev))
> + enable_irq_wake(pmic_dev->irq_wake);
> +
> + disable_irq(pmic_dev->irq);
> + return 0;
> +}
> +
> +static int stpmu1_resume(struct device *dev)
> +{
> + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
> + struct stpmu1_dev *pmic_dev = i2c_get_clientdata(i2c);
> +
> + regcache_sync(pmic_dev->regmap);
Maybe you would like to check for an error here.

> +
> + if (device_may_wakeup(dev))
> + disable_irq_wake(pmic_dev->irq_wake);
> +
> + enable_irq(pmic_dev->irq);
> + return 0;
> +}
> +#endif
> +
> +static SIMPLE_DEV_PM_OPS(stpmu1_pm, stpmu1_suspend, stpmu1_resume);
> +
> +static struct i2c_driver stpmu1_driver = {
> + .driver = {
> + .name = "stpmu1",
> + .owner = THIS_MODULE,
This is not needed, the core does it for you.

> + .pm = &stpmu1_pm,
> + .of_match_table = of_match_ptr(stpmu1_dt_match),
It is a DT-only device so no need the of_match_ptr.

> + },
> + .probe = stpmu1_probe,
> + .remove = stpmu1_remove,
Now you can remove this

> + .id_table = stpmu1_id,
And you can remove this also.

> +};
> +
> +module_i2c_driver(stpmu1_driver);
> +
> +MODULE_DESCRIPTION("STPMU1 PMIC I2C Client");
nit: PMIC I2C Client sounds weird to me, "STPMU1 PMIC driver" ? Note
that I am not english native so I could be wrong.

> +MODULE_AUTHOR("<[email protected]>");
Use "Name <email>" or just "Name"

> +MODULE_LICENSE("GPL");
As I told you there is a license mismatch with SPDX.

[snip]

Best regards,
Enric

2018-07-16 22:16:05

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH 1/8] dt-bindings: mfd: document stpmu1 pmic

On Thu, Jul 05, 2018 at 03:14:22PM +0000, Pascal PAILLET-LME wrote:
> From: pascal paillet <[email protected]>
>
> stpmu1 is a pmic from STMicroelectronics. The stpmu1 integrates 10
> regulators and 3 switches with various capabilities.
>
> Signed-off-by: pascal paillet <[email protected]>
> ---
> .../devicetree/bindings/mfd/st,stpmu1.txt | 138 +++++++++++++++++++++
> 1 file changed, 138 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mfd/st,stpmu1.txt
>
> diff --git a/Documentation/devicetree/bindings/mfd/st,stpmu1.txt b/Documentation/devicetree/bindings/mfd/st,stpmu1.txt
> new file mode 100644
> index 0000000..53bdab4
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/st,stpmu1.txt
> @@ -0,0 +1,138 @@
> +* STMicroelectronics STPMU1 Power Management IC
> +
> +Required parent device properties:
> +- compatible: "st,stpmu1"
> +- reg: the I2C slave address for the stpmu1 chip
> +- interrupts-extended: interrupt lines to use: second irq is for wakeup.
> +- #interrupt-cells: should be 2.
> +- interrupt-controller: describes the STPMU1 as an interrupt
> + controller (has its own domain). interrupt number are the following:
> + /* Interrupt Register 1 (0x50 for latch) */
> + IT_SWOUT_R=0
> + IT_SWOUT_F=1
> + IT_VBUS_OTG_R=2
> + IT_VBUS_OTG_F=3
> + IT_WAKEUP_R=4
> + IT_WAKEUP_F=5
> + IT_PONKEY_R=6
> + IT_PONKEY_F=7
> + /* Interrupt Register 2 (0x51 for latch) */
> + IT_OVP_BOOST=8
> + IT_OCP_BOOST=9
> + IT_OCP_SWOUT=10
> + IT_OCP_OTG=11
> + IT_CURLIM_BUCK4=12
> + IT_CURLIM_BUCK3=13
> + IT_CURLIM_BUCK2=14
> + IT_CURLIM_BUCK1=15
> + /* Interrupt Register 3 (0x52 for latch) */
> + IT_SHORT_SWOUT=16
> + IT_SHORT_SWOTG=17
> + IT_CURLIM_LDO6=18
> + IT_CURLIM_LDO5=19
> + IT_CURLIM_LDO4=20
> + IT_CURLIM_LDO3=21
> + IT_CURLIM_LDO2=22
> + IT_CURLIM_LDO1=23
> + /* Interrupt Register 3 (0x52 for latch) */
> + IT_SWIN_R=24
> + IT_SWIN_F=25
> + IT_RESERVED_1=26
> + IT_RESERVED_2=27
> + IT_VINLOW_R=28
> + IT_VINLOW_F=29
> + IT_TWARN_R=30
> + IT_TWARN_F=31
> +
> +Optional parent device properties:
> +- st,main_control_register:

s/_/-/

And elsewhere...

> + -bit 1: Power cycling will be performed on turn OFF condition
> + -bit 2: PWRCTRL is functional
> + -bit 3: PWRCTRL active high
> +- st,pads_pull_register:
> + -bit 1: WAKEUP pull down is not active
> + -bit 2: PWRCTRL pull up is active
> + -bit 3: PWRCTRL pull down is active
> + -bit 4: WAKEUP detector is disabled
> +- st,vin_control_register:
> + -bit 0: VINLOW monitoring is enabled
> + -bit [1...3]: VINLOW rising threshold
> + 000 VINOK_f + 50mV
> + 001 VINOK_f + 100mV
> + 010 VINOK_f + 150mV
> + 011 VINOK_f + 200mV
> + 100 VINOK_f + 250mV
> + 101 VINOK_f + 300mV
> + 110 VINOK_f + 350mV
> + 111 VINOK_f + 400mV
> + -bit [4...5]: VINLOW hyst
> + 00 100mV
> + 01 200mV
> + 10 300mV
> + 11 400mV
> + -bit 6: SW_OUT detector is disabled
> + -bit 7: SW_IN detector is enabled.
> +- st,usb_control_register:
> + -bit 3: SW_OUT current limit
> + 0: 600mA
> + 1: 1.1A
> + -bit 4: VBUS_OTG discharge is enabled
> + -bit 5: SW_OUT discharge is enabled
> + -bit 6: VBUS_OTG detection is enabled
> + -bit 7: BOOST_OVP is disabled

Just dumping register values into DT is not the greatest design.

> +
> +
> +stpmu1 consists is a varied group of sub-devices:
> +
> +Device Description
> +------ ------------
> +stpmu1-onkey : On key
> +stpmu1-regulators : Regulators
> +stpmu1-wdt : Watchdog

These should match the node name below.

> +
> +each sub-device bindings is be described in associated driver
> +documentation section.
> +
> +Example:
> +
> +pmic: stpmu1@33 {
> + compatible = "st,stpmu1";
> + reg = <0x33>;
> + interrupts = <0 2>;
> + interrupts-extended = <&intc GIC_SPI 149 IRQ_TYPE_NONE>,
> + <&exti 55 1>;
> + st,version_status = <0x10>;
> + st,main_control_register=<0x0c>;
> + interrupt-controller;
> + #interrupt-cells = <2>;
> + onkey {
> + compatible = "st,stpmu1-onkey";
> + interrupt-parent = <&pmic>;
> + interrupts = <7 0>,<6 1>;

> + st,onkey-pwroff-enabled;
> + st,onkey-press-seconds = <10>;

IIRC, we have some standard properties for these.

> + };
> +
> + watchdog {
> + compatible = "st,stpmu1-wdt";
> + };
> +
> + regulators {
> + compatible = "st,stpmu1-regulators";
> +
> + vdd_core: regulator@0 {

unit-address without reg is not valid. regulator-buck1, etc. instead.

> + regulator-compatible = "buck1";
> + regulator-name = "vdd_core";
> + regulator-boot-on;
> + regulator-min-microvolt = <700000>;
> + regulator-max-microvolt = <1200000>;
> + };
> + vdd: regulator@1 {
> + regulator-compatible = "buck3";
> + regulator-name = "vdd";
> + regulator-min-microvolt = <3300000>;
> + regulator-max-microvolt = <3300000>;
> + regulator-boot-on;
> + regulator-pull-down;
> + };
> + };
> --
> 1.9.1

2018-07-16 22:16:48

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH 2/8] mfd: stpmu1: add stpmu1 pmic driver

On Thu, Jul 05, 2018 at 03:14:22PM +0000, Pascal PAILLET-LME wrote:
> From: pascal paillet <[email protected]>
>
> stpmu1 is a pmic from STMicroelectronics. The stpmu1 integrates 10
> regulators and 3 switches with various capabilities.
>
> Signed-off-by: pascal paillet <[email protected]>
> ---
> drivers/mfd/Kconfig | 14 ++
> drivers/mfd/Makefile | 1 +
> drivers/mfd/stpmu1.c | 490 ++++++++++++++++++++++++++++++++++++
> include/dt-bindings/mfd/st,stpmu1.h | 46 ++++

This belongs with patch 1.

> include/linux/mfd/stpmu1.h | 220 ++++++++++++++++
> 5 files changed, 771 insertions(+)
> create mode 100644 drivers/mfd/stpmu1.c
> create mode 100644 include/dt-bindings/mfd/st,stpmu1.h
> create mode 100644 include/linux/mfd/stpmu1.h

2018-07-16 22:22:21

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH 3/8] dt-bindings: regulator: document stpmu1 pmic regulators

On Thu, Jul 05, 2018 at 03:14:23PM +0000, Pascal PAILLET-LME wrote:
> From: pascal paillet <[email protected]>
>
> The STPMU1 regulators supply power to the application processor as well as
> to the external system peripherals such as DDR, Flash memories and system
> devices.
>
> Signed-off-by: pascal paillet <[email protected]>
> ---
> .../bindings/regulator/st,stpmu1-regulator.txt | 72 ++++++++++++++++++++++
> 1 file changed, 72 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/regulator/st,stpmu1-regulator.txt
>
> diff --git a/Documentation/devicetree/bindings/regulator/st,stpmu1-regulator.txt b/Documentation/devicetree/bindings/regulator/st,stpmu1-regulator.txt
> new file mode 100644
> index 0000000..888e759
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/regulator/st,stpmu1-regulator.txt
> @@ -0,0 +1,72 @@
> +STMicroelectronics STPMU1 Voltage regulators
> +
> +Regulator Nodes are optional depending on needs.
> +
> +Available Regulators in STPMU1 device are:
> + - buck1 for Buck BUCK1
> + - buck2 for Buck BUCK2
> + - buck3 for Buck BUCK3
> + - buck4 for Buck BUCK4
> + - ldo1 for LDO LDO1
> + - ldo2 for LDO LDO2
> + - ldo3 for LDO LDO3
> + - ldo4 for LDO LDO4
> + - ldo5 for LDO LDO5
> + - ldo6 for LDO LDO6
> + - vref_ddr for LDO Vref DDR
> + - boost for Buck BOOST
> + - pwr_sw1 for VBUS_OTG switch
> + - pwr_sw2 for SW_OUT switch
> +
> +Switches are fixed voltage regulators with only enable/disable capability.
> +
> +Optional properties:
> +- st,mask_reset: stay on during Reset for particular regulator

s/_/-/

What's the type? 'mask' in the name makes it sound like a bitmask.

> +- regulator-pull-down: enable high pull down
> + if not specified light pull down is used
> +- regulator-over-current-protection:
> + if set, all regulators are switched off in case of over-current detection
> + on this regulator,
> + if not set, the driver only send an over-current event.
> +- interrupt-parent: phandle to the parent interrupt controller
> +- interrupts: index of current limit detection interrupt
> +- <regulator>-supply: phandle to the parent supply/regulator node
> + each regulator supply can be described except vref_ddr.
> +
> +Example:
> +regulators {
> + compatible = "st,stpmu1-regulators";
> +
> + ldo6-supply = <&v3v3>;
> +
> + vdd_core: regulator@0 {

Drop the unit address.

> + regulator-compatible = "buck1";
> + regulator-name = "vdd_core";

I think you have the names backwards here. Plus, regulator-compatible is
not generally used.

> + interrupts = <IT_CURLIM_BUCK1 0>;
> + interrupt-parent = <&pmic>;
> + st,mask_reset;
> + regulator-pull-down;
> + st,pulldown_config = <2>;
> + regulator-min-microvolt = <700000>;
> + regulator-max-microvolt = <1200000>;
> + };
> +
> + v3v3: buck4 {
> + regulator-compatible = "buck4";
> + regulator-name = "v3v3";
> + interrupts = <IT_CURLIM_BUCK4 0>;
> + interrupt-parent = <&mypmic>;
> +
> + regulator-min-microvolt = <3300000>;
> + regulator-max-microvolt = <3300000>;
> + };
> +
> + v1v8: ldo6 {
> + regulator-compatible = "ldo6";
> + regulator-name = "v1v8";
> + regulator-min-microvolt = <1800000>;
> + regulator-max-microvolt = <1800000>;
> + regulator-over-current-protection;
> + };
> +
> +};
> --
> 1.9.1

2018-08-06 23:58:19

by Dmitry Torokhov

[permalink] [raw]
Subject: Re: [PATCH 6/8] input: stpmu1: add stpmu1 onkey driver

Hi Pascal,

On Thu, Jul 05, 2018 at 03:14:24PM +0000, Pascal PAILLET-LME wrote:
> From: pascal paillet <[email protected]>
>
> The stpmu1 pmic is able to manage an onkey button. This driver exposes
> the stpmu1 onkey as an input device. It can also be configured to
> shut-down the power supplies on a long key-press with an adjustable
> duration.
>
> Signed-off-by: pascal paillet <[email protected]>
> ---
> drivers/input/misc/Kconfig | 11 ++
> drivers/input/misc/Makefile | 2 +
> drivers/input/misc/stpmu1_onkey.c | 321 ++++++++++++++++++++++++++++++++++++++
> 3 files changed, 334 insertions(+)
> create mode 100644 drivers/input/misc/stpmu1_onkey.c
>
> diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
> index c25606e..d020971 100644
> --- a/drivers/input/misc/Kconfig
> +++ b/drivers/input/misc/Kconfig
> @@ -841,4 +841,15 @@ config INPUT_RAVE_SP_PWRBUTTON
> To compile this driver as a module, choose M here: the
> module will be called rave-sp-pwrbutton.
>
> +config INPUT_STPMU1_ONKEY
> + tristate "STPMU1 PMIC Onkey support"
> + depends on MFD_STPMU1
> + help
> + Say Y to enable support of onkey embedded into STPMU1 PMIC. onkey
> + can be used to wakeup from low power modes and force a shut-down on
> + long press.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called stpmu1_onkey.
> +
> endif
> diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
> index 72cde28..cc60dc1 100644
> --- a/drivers/input/misc/Makefile
> +++ b/drivers/input/misc/Makefile
> @@ -70,6 +70,7 @@ obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o
> obj-$(CONFIG_INPUT_SIRFSOC_ONKEY) += sirfsoc-onkey.o
> obj-$(CONFIG_INPUT_SOC_BUTTON_ARRAY) += soc_button_array.o
> obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o
> +obj-$(CONFIG_INPUT_STPMU1_ONKEY) += stpmu1_onkey.o
> obj-$(CONFIG_INPUT_TPS65218_PWRBUTTON) += tps65218-pwrbutton.o
> obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o
> obj-$(CONFIG_INPUT_TWL4030_VIBRA) += twl4030-vibra.o
> @@ -80,3 +81,4 @@ obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o
> obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o
> obj-$(CONFIG_INPUT_YEALINK) += yealink.o
> obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR) += ideapad_slidebar.o
> +
> diff --git a/drivers/input/misc/stpmu1_onkey.c b/drivers/input/misc/stpmu1_onkey.c
> new file mode 100644
> index 0000000..084a31f
> --- /dev/null
> +++ b/drivers/input/misc/stpmu1_onkey.c
> @@ -0,0 +1,321 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
> + * Author: Philippe Peurichard <[email protected]>,
> + * Pascal Paillet <[email protected]> for STMicroelectronics.
> + */
> +
> +#include <linux/input.h>
> +#include <linux/interrupt.h>
> +#include <linux/mfd/stpmu1.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +
> +/**
> + * struct stpmu1_onkey - OnKey data
> + * @pmic: pointer to STPMU1 PMIC device
> + * @input_dev: pointer to input device
> + * @irq_falling: irq that we are hooked on to
> + * @irq_rising: irq that we are hooked on to
> + */
> +struct stpmu1_onkey {
> + struct stpmu1_dev *pmic;
> + struct input_dev *input_dev;
> + int irq_falling;
> + int irq_rising;
> +};
> +
> +/**
> + * struct pmic_onkey_config - configuration of pmic PONKEYn
> + * @turnoff_enabled: value to enable turnoff condition
> + * @cc_flag_clear: value to clear CC flag in case of PowerOff
> + * trigger by longkey press
> + * @onkey_pullup_val: value of PONKEY PullUp (active or inactive)
> + * @long_press_time_val: value for long press h/w shutdown event
> + */
> +struct pmic_onkey_config {
> + bool turnoff_enabled;
> + bool cc_flag_clear;
> + u8 onkey_pullup_val;
> + u8 long_press_time_val;
> +};
> +
> +/**
> + * onkey_falling_irq() - button press isr
> + * @irq: irq
> + * @pmic_onkey: onkey device
> + *
> + * Return: IRQ_HANDLED
> + */

This is internal driver functions, I do not see the need for kernel-doc
style comments (or any comments for this one to be honest).

> +static irqreturn_t onkey_falling_irq(int irq, void *ponkey)
> +{
> + struct stpmu1_onkey *onkey = ponkey;
> + struct input_dev *input_dev = onkey->input_dev;
> +
> + input_report_key(input_dev, KEY_POWER, 1);
> + pm_wakeup_event(input_dev->dev.parent, 0);
> + input_sync(input_dev);
> +
> + dev_dbg(&input_dev->dev, "Pwr Onkey Falling Interrupt received\n");
> +
> + return IRQ_HANDLED;
> +}
> +
> +/**
> + * onkey_rising_irq() - button released isr
> + * @irq: irq
> + * @pmic_onkey: onkey device
> + *
> + * Return: IRQ_HANDLED
> + */
> +static irqreturn_t onkey_rising_irq(int irq, void *ponkey)
> +{
> + struct stpmu1_onkey *onkey = ponkey;
> + struct input_dev *input_dev = onkey->input_dev;
> +
> + input_report_key(input_dev, KEY_POWER, 0);
> + pm_wakeup_event(input_dev->dev.parent, 0);
> + input_sync(input_dev);
> +
> + dev_dbg(&input_dev->dev, "Pwr Onkey Rising Interrupt received\n");
> +
> + return IRQ_HANDLED;
> +}
> +
> +/**
> + * stpmu1_onkey_dt_params() - device tree parameter parser
> + * @pdev: platform device for the PONKEY
> + * @onkey: pointer to onkey driver data
> + * @config: configuration params to be filled up
> + */
> +static int stpmu1_onkey_dt_params(struct platform_device *pdev,
> + struct stpmu1_onkey *onkey,
> + struct pmic_onkey_config *config)
> +{
> + struct device *dev = &pdev->dev;
> + struct device_node *np;
> + u32 val;
> +
> + np = dev->of_node;
> + if (!np)
> + return -EINVAL;

Is this possible?

> +
> + onkey->irq_falling = platform_get_irq_byname(pdev, "onkey-falling");
> + if (onkey->irq_falling < 0) {
> + dev_err(dev, "failed: request IRQ onkey-falling %d",

Some of your messages use \n, some don't. I'd rather they all did.

> + onkey->irq_falling);
> + return onkey->irq_falling;
> + }
> +
> + onkey->irq_rising = platform_get_irq_byname(pdev, "onkey-rising");
> + if (onkey->irq_rising < 0) {
> + dev_err(dev, "failed: request IRQ onkey-rising %d",
> + onkey->irq_rising);
> + return onkey->irq_rising;
> + }
> +
> + if (!of_property_read_u32(np, "st,onkey-long-press-seconds", &val)) {
> + if (val >= 1 && val <= 16) {
> + config->long_press_time_val = (16 - val);
> + } else {
> + dev_warn(dev,
> + "Invalid range of long key pressed timer %d (<16)\n\r",

Why \n\r?

> + val);
> + }
> + }
> + if (of_get_property(np, "st,onkey-pwroff-enabled", NULL))
> + config->turnoff_enabled = true;
> +
> + if (of_get_property(np, "st,onkey-clear-cc-flag", NULL))
> + config->cc_flag_clear = true;
> +
> + if (of_get_property(np, "st,onkey-pu-inactive", NULL))
> + config->onkey_pullup_val = PONKEY_PU_ACTIVE;

Even though the driver is only used in OF systems, I wonder if we should
not be using generic device property API.

> +
> + dev_dbg(dev, "onkey-switch-off duration=%d seconds\n",
> + config->long_press_time_val);
> +
> + return 0;
> +}
> +
> +/**
> + * stpmu1_onkey_probe() - probe
> + * @pdev: platform device for the PONKEY
> + *
> + * Return: 0 for successful probe else appropriate error
> + */
> +static int stpmu1_onkey_probe(struct platform_device *pdev)
> +{
> + struct stpmu1_dev *pmic = dev_get_drvdata(pdev->dev.parent);
> + struct device *dev = &pdev->dev;
> + struct input_dev *input_dev;
> + struct stpmu1_onkey *onkey;
> + struct pmic_onkey_config config;
> + unsigned int val = 0;
> + int ret;

Call this variable "error" please.

> +
> + onkey = devm_kzalloc(dev, sizeof(*onkey), GFP_KERNEL);
> + if (!onkey)
> + return -ENOMEM;
> +
> + memset(&config, 0, sizeof(struct pmic_onkey_config));
> + ret = stpmu1_onkey_dt_params(pdev, onkey, &config);
> + if (ret < 0)

stpmu1_onkey_dt_params() does not return positives (good) so:

if (error)
return error;

> + goto err;
> +
> + input_dev = devm_input_allocate_device(dev);
> + if (!input_dev) {
> + dev_err(dev, "Can't allocate Pwr Onkey Input Device\n");
> + ret = -ENOMEM;
> + goto err;
> + }
> +
> + input_dev->name = "pmic_onkey";
> + input_dev->phys = "pmic_onkey/input0";
> + input_dev->dev.parent = dev;

This is already set by devm_input_allocate_device().

> +
> + input_set_capability(input_dev, EV_KEY, KEY_POWER);
> +
> + /* Setup Power Onkey Hardware parameters (long key press)*/
> + val = (config.long_press_time_val & PONKEY_TURNOFF_TIMER_MASK);
> + if (config.turnoff_enabled)
> + val |= PONKEY_PWR_OFF;
> + if (config.cc_flag_clear)
> + val |= PONKEY_CC_FLAG_CLEAR;
> + ret = regmap_update_bits(pmic->regmap, PKEY_TURNOFF_CR,
> + PONKEY_TURNOFF_MASK, val);
> + if (ret) {
> + dev_err(dev, "LONG_PRESS_KEY_UPDATE failed: %d\n", ret);
> + goto err;

You are using all managed resources, so "return error" directly, no need
to just to error unwinding path.

> + }
> +
> + ret = regmap_update_bits(pmic->regmap, PADS_PULL_CR,
> + PONKEY_PU_ACTIVE,
> + config.onkey_pullup_val);
> +
> + if (ret) {
> + dev_err(dev, "ONKEY Pads configuration failed: %d\n", ret);
> + goto err;
> + }
> +
> + onkey->pmic = pmic;
> + onkey->input_dev = input_dev;
> +
> + ret = devm_request_threaded_irq(dev, onkey->irq_falling, NULL,

Why does this need to be threaded? The isr does not seem to be calling
any APIs that may wait.

> + onkey_falling_irq,
> + IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
> + dev_name(dev), onkey);
> + if (ret) {
> + dev_err(dev, "Can't get IRQ for Onkey Falling edge: %d\n", ret);
> + goto err;
> + }
> +
> + ret = devm_request_threaded_irq(dev, onkey->irq_rising, NULL,
> + onkey_rising_irq,
> + IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
> + dev_name(dev), onkey);
> + if (ret) {
> + dev_err(dev, "Can't get IRQ for Onkey Rising edge: %d\n", ret);
> + goto err;
> + }
> +
> + ret = input_register_device(input_dev);
> + if (ret) {
> + dev_err(dev, "Can't register power button: %d\n", ret);
> + goto err;
> + }
> +
> + platform_set_drvdata(pdev, onkey);
> + device_init_wakeup(dev, true);
> +
> + dev_dbg(dev, "PMIC Pwr Onkey driver probed\n");

I'd rather dropped this. Input core will print when registering device
already.

> +
> +err:
> + return ret;
> +}
> +
> +/**
> + * stpmu1_onkey_remove() - Cleanup on removal
> + * @pdev: platform device for the button
> + *
> + * Return: 0
> + */
> +static int stpmu1_onkey_remove(struct platform_device *pdev)
> +{
> + struct stpmu1_onkey *onkey = platform_get_drvdata(pdev);
> +
> + input_unregister_device(onkey->input_dev);

You are using managed input device, so this call is not needed. You
should be able to remove entire stpmu1_onkey_remove().

> + return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP

You annotated suspend/resume methods with __maybe_unused, so this guard
is not needed.

> +
> +/**
> + * pmic_onkey_suspend() - suspend handler
> + * @dev: power button device
> + *
> + * Cancel all pending work items for the power button, setup irq for wakeup
> + *
> + * Return: 0
> + */
> +static int __maybe_unused stpmu1_onkey_suspend(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct stpmu1_onkey *onkey = platform_get_drvdata(pdev);
> +
> + if (device_may_wakeup(dev)) {
> + enable_irq_wake(onkey->irq_falling);
> + enable_irq_wake(onkey->irq_rising);
> + }
> + return 0;
> +}
> +
> +/**
> + * pmic_onkey_resume() - resume handler
> + * @dev: power button device
> + *
> + * Just disable the wakeup capability of irq here.
> + *
> + * Return: 0
> + */
> +static int __maybe_unused stpmu1_onkey_resume(struct device *dev)
> +{
> + struct platform_device *pdev = to_platform_device(dev);
> + struct stpmu1_onkey *onkey = platform_get_drvdata(pdev);
> +
> + if (device_may_wakeup(dev)) {
> + disable_irq_wake(onkey->irq_falling);
> + disable_irq_wake(onkey->irq_rising);
> + }
> + return 0;
> +}
> +
> +#endif
> +
> +static SIMPLE_DEV_PM_OPS(stpmu1_onkey_pm,
> + stpmu1_onkey_suspend,
> + stpmu1_onkey_resume);
> +
> +static const struct of_device_id of_stpmu1_onkey_match[] = {
> + { .compatible = "st,stpmu1-onkey" },
> + { },
> +};
> +
> +MODULE_DEVICE_TABLE(of, of_stpmu1_onkey_match);
> +
> +static struct platform_driver stpmu1_onkey_driver = {
> + .probe = stpmu1_onkey_probe,
> + .remove = stpmu1_onkey_remove,
> + .driver = {
> + .name = "stpmu1_onkey",
> + .of_match_table = of_match_ptr(of_stpmu1_onkey_match),
> + .pm = &stpmu1_onkey_pm,
> + },
> +};
> +module_platform_driver(stpmu1_onkey_driver);
> +
> +MODULE_DESCRIPTION("Onkey driver for STPMU1");
> +MODULE_LICENSE("GPL");

To match your SPDX notice this should be MODULE_LICENSE("GPL v2").

> +MODULE_AUTHOR("[email protected]>");
> --
> 1.9.1

Thanks.

--
Dmitry

2018-08-21 12:39:50

by Pascal Paillet

[permalink] [raw]
Subject: Re: [PATCH 2/8] mfd: stpmu1: add stpmu1 pmic driver

Hi,

Thanks a lot for the review ! I just have a question below regarding
populate method.


Le 07/10/2018 12:38 AM, Enric Balletbo Serra a écrit :
> Hi Pascal,
>
> Thanks for the patch some comments below.
>
> Missatge de Pascal PAILLET-LME <[email protected]> del dia dj., 5 de
> jul. 2018 a les 17:17:
>> From: pascal paillet <[email protected]>
>>
>> stpmu1 is a pmic from STMicroelectronics. The stpmu1 integrates 10
>> regulators and 3 switches with various capabilities.
>>
>> Signed-off-by: pascal paillet <[email protected]>
>> ---
>> drivers/mfd/Kconfig | 14 ++
>> drivers/mfd/Makefile | 1 +
>> drivers/mfd/stpmu1.c | 490 ++++++++++++++++++++++++++++++++++++
>> include/dt-bindings/mfd/st,stpmu1.h | 46 ++++
>> include/linux/mfd/stpmu1.h | 220 ++++++++++++++++
>> 5 files changed, 771 insertions(+)
>> create mode 100644 drivers/mfd/stpmu1.c
>> create mode 100644 include/dt-bindings/mfd/st,stpmu1.h
>> create mode 100644 include/linux/mfd/stpmu1.h
>>
>> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
>> index b860eb5..e15140b 100644
>> --- a/drivers/mfd/Kconfig
>> +++ b/drivers/mfd/Kconfig
>> @@ -1812,6 +1812,20 @@ config MFD_STM32_TIMERS
>> for PWM and IIO Timer. This driver allow to share the
>> registers between the others drivers.
>>
>> +config MFD_STPMU1
>> + tristate "Support for STPMU1 PMIC"
>> + depends on (I2C=y && OF)
>> + select REGMAP_I2C
>> + select REGMAP_IRQ
>> + select MFD_CORE
>> + help
>> + Support for ST Microelectronics STPMU1 PMIC. Stpmu1 mfd driver is
>> + the core driver for stpmu1 component that mainly handles interrupts.
>> +
>> + To compile this driver as a module, choose M here: the
>> + module will be called stpmu1.
>> +
>> +
> Extra line not needed.
>
>> menu "Multimedia Capabilities Port drivers"
>> depends on ARCH_SA1100
>>
>> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
>> index e9fd20d..f1c4be1 100644
>> --- a/drivers/mfd/Makefile
>> +++ b/drivers/mfd/Makefile
>> @@ -220,6 +220,7 @@ obj-$(CONFIG_INTEL_SOC_PMIC_CHTDC_TI) += intel_soc_pmic_chtdc_ti.o
>> obj-$(CONFIG_MFD_MT6397) += mt6397-core.o
>>
>> obj-$(CONFIG_MFD_ALTERA_A10SR) += altera-a10sr.o
>> +obj-$(CONFIG_MFD_STPMU1) += stpmu1.o
>> obj-$(CONFIG_MFD_SUN4I_GPADC) += sun4i-gpadc.o
>>
>> obj-$(CONFIG_MFD_STM32_LPTIMER) += stm32-lptimer.o
>> diff --git a/drivers/mfd/stpmu1.c b/drivers/mfd/stpmu1.c
>> new file mode 100644
>> index 0000000..a284a3e
>> --- /dev/null
>> +++ b/drivers/mfd/stpmu1.c
>> @@ -0,0 +1,490 @@
>> +// SPDX-License-Identifier: GPL-2.0
> There is a license mismatch between SPDX and MODULE_LICENSE. Or SPDX
> identifier should be GPL-2.0-or-later or MODULE_LICENSE should be
> ("GPL v2")
>
> See https://elixir.bootlin.com/linux/latest/source/include/linux/module.h#L175
>
>> +/*
>> + * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
>> + * Author: Philippe Peurichard <[email protected]>,
>> + * Pascal Paillet <[email protected]> for STMicroelectronics.
>> + */
>> +
> I think that Lee, like Linus, prefers the C++ style here
>
>> +#include <linux/err.h>
> That this include is not needed.
>
>> +#include <linux/i2c.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/mfd/core.h>
>> +#include <linux/mfd/stpmu1.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_irq.h>
>> +#include <linux/of_platform.h>
>> +#include <linux/platform_device.h>
> ditto
>> +#include <linux/pm_runtime.h>
> ditto
>
>> +#include <linux/pm_wakeirq.h>
>> +#include <linux/regmap.h>
>> +#include <dt-bindings/mfd/st,stpmu1.h>
>> +
> [snip]
>
>> +
>> +static int stpmu1_configure_from_dt(struct stpmu1_dev *pmic_dev)
>> +{
>> + struct device_node *np = pmic_dev->np;
>> + u32 reg = 0;
> You don't need to initialize reg to 0, anyway will be overwriten.
>
>> + int ret = 0;
> You don't need to initialize ret to 0, anyway will be overwritten.
>
>> + int irq;
>> +
>> + irq = of_irq_get(np, 0);
>> + if (irq <= 0) {
>> + dev_err(pmic_dev->dev,
>> + "Failed to get irq config: %d\n", irq);
> This can be in one line.
>
>> + return irq ? irq : -ENODEV;
> nit: return irq ?: -ENODEV;
>
>> + }
>> + pmic_dev->irq = irq;
>> +
>> + irq = of_irq_get(np, 1);
>> + if (irq <= 0) {
>> + dev_err(pmic_dev->dev,
>> + "Failed to get irq_wake config: %d\n", irq);
>> + return irq ? irq : -ENODEV;
> nit: return irq ?: -ENODEV;
>
>> + }
>> + pmic_dev->irq_wake = irq;
>> +
>> + device_init_wakeup(pmic_dev->dev, true);
>> + ret = dev_pm_set_dedicated_wake_irq(pmic_dev->dev, pmic_dev->irq_wake);
>> + if (ret)
>> + dev_warn(pmic_dev->dev, "failed to set up wakeup irq");
>> +
>> + if (!of_property_read_u32(np, "st,main_control_register", &reg)) {
>> + ret = regmap_update_bits(pmic_dev->regmap,
>> + SWOFF_PWRCTRL_CR,
>> + PWRCTRL_POLARITY_HIGH |
>> + PWRCTRL_PIN_VALID |
>> + RESTART_REQUEST_ENABLED,
>> + reg);
>> + if (ret) {
>> + dev_err(pmic_dev->dev,
>> + "Failed to update main control register: %d\n",
>> + ret);
>> + return ret;
>> + }
>> + }
>> +
>> + if (!of_property_read_u32(np, "st,pads_pull_register", &reg)) {
>> + ret = regmap_update_bits(pmic_dev->regmap,
>> + PADS_PULL_CR,
>> + WAKEUP_DETECTOR_DISABLED |
>> + PWRCTRL_PD_ACTIVE |
>> + PWRCTRL_PU_ACTIVE |
>> + WAKEUP_PD_ACTIVE,
>> + reg);
>> + if (ret) {
>> + dev_err(pmic_dev->dev,
>> + "Failed to update pads control register: %d\n",
>> + ret);
>> + return ret;
>> + }
>> + }
>> +
>> + if (!of_property_read_u32(np, "st,vin_control_register", &reg)) {
>> + ret = regmap_update_bits(pmic_dev->regmap,
>> + VBUS_DET_VIN_CR,
>> + VINLOW_CTRL_REG_MASK,
>> + reg);
>> + if (ret) {
>> + dev_err(pmic_dev->dev,
>> + "Failed to update vin control register: %d\n",
>> + ret);
>> + return ret;
>> + }
>> + }
>> +
>> + if (!of_property_read_u32(np, "st,usb_control_register", &reg)) {
>> + ret = regmap_update_bits(pmic_dev->regmap, BST_SW_CR,
>> + BOOST_OVP_DISABLED |
>> + VBUS_OTG_DETECTION_DISABLED |
>> + SW_OUT_DISCHARGE |
>> + VBUS_OTG_DISCHARGE |
>> + OCP_LIMIT_HIGH,
>> + reg);
>> + if (ret) {
>> + dev_err(pmic_dev->dev,
>> + "Failed to update usb control register: %d\n",
>> + ret);
>> + return ret;
>> + }
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +int stpmu1_device_init(struct stpmu1_dev *pmic_dev)
>> +{
>> + int ret;
>> + unsigned int val;
>> +
>> + pmic_dev->regmap =
>> + devm_regmap_init_i2c(pmic_dev->i2c, &stpmu1_regmap_config);
>> +
>> + if (IS_ERR(pmic_dev->regmap)) {
>> + ret = PTR_ERR(pmic_dev->regmap);
> You can remove this ...
>
>> + dev_err(pmic_dev->dev, "Failed to allocate register map: %d\n",
>> + ret);
>> + return ret;
> and just return PTR_ERR(pmic_dev->regmap);
>
>> + }
>> +
>> + ret = stpmu1_configure_from_dt(pmic_dev);
>> + if (ret < 0) {
> Is ret >0 return valid? If not, perhaps "if (ret)" would be better.
>
>> + dev_err(pmic_dev->dev,
>> + "Unable to configure PMIC from Device Tree: %d\n", ret);
>> + return ret;
>> + }
>> +
>> + /* Read Version ID */
>> + ret = regmap_read(pmic_dev->regmap, VERSION_SR, &val);
>> + if (ret < 0) {
> Is ret >0 return valid? If not, perhaps "if (ret)" would be better.
>
>> + dev_err(pmic_dev->dev, "Unable to read pmic version\n");
>> + return ret;
>> + }
>> + dev_dbg(pmic_dev->dev, "PMIC Chip Version: 0x%x\n", val);
> nit: Maybe that should be dev_info instead of dev_dbg?
>
>> +
>> + /* Initialize PMIC IRQ Chip & IRQ domains associated */
>> + ret = devm_regmap_add_irq_chip(pmic_dev->dev, pmic_dev->regmap,
>> + pmic_dev->irq,
>> + IRQF_ONESHOT | IRQF_SHARED,
>> + 0, &stpmu1_regmap_irq_chip,
>> + &pmic_dev->irq_data);
>> + if (ret < 0) {
> Is ret >0 return valid? If not, perhaps "if (ret)" would be better.
>
>> + dev_err(pmic_dev->dev, "IRQ Chip registration failed: %d\n",
>> + ret);
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static const struct of_device_id stpmu1_dt_match[] = {
>> + {.compatible = "st,stpmu1"},
>> + {},
> I'd rewrite this as
> + { .compatible = "st,stpmu1" },
> + { }
> Space after/before brackets and no comma at the end. The sentinel
> indicates the last item on structure/arrays so no need to add a comma
> at the end.
>
>> +};
>> +
> Remove this line
>
>> +MODULE_DEVICE_TABLE(of, stpmu1_dt_match);
>> +
>> +static int stpmu1_remove(struct i2c_client *i2c)
>> +{
>> + struct stpmu1_dev *pmic_dev = i2c_get_clientdata(i2c);
>> +
>> + of_platform_depopulate(pmic_dev->dev);
>> +
>> + return 0;
>> +}
> You can remove this function, see below ...
>
>> +
>> +static int stpmu1_probe(struct i2c_client *i2c,
>> + const struct i2c_device_id *id)
>> +{
>> + struct stpmu1_dev *pmic;
>> + struct device *dev = &i2c->dev;
>> + int ret = 0;
> No need to initialize to 0 if ...
>
>> +
>> + pmic = devm_kzalloc(dev, sizeof(struct stpmu1_dev), GFP_KERNEL);
>> + if (!pmic)
>> + return -ENOMEM;
>> +
>> + pmic->np = dev->of_node;
>> +
>> + dev_set_drvdata(dev, pmic);
>> + pmic->dev = dev;
>> + pmic->i2c = i2c;
>> +
>> + ret = stpmu1_device_init(pmic);
>> + if (ret < 0)
> Is ret >0 return valid? If not, perhaps "if (ret)" would be better.
>> + goto err;
> return ret;
>
>> +
>> + ret = of_platform_populate(pmic->np, NULL, NULL, pmic->dev);
>> +
> ret = devm_of_platform_populate(pmic->dev);
>
> or even better
>
> return devm_of_platform_populate(pmic->dev);
>
> And remove the stpmu1_remove function.
From the regulator driver review, Mark Brown suggest to use
mfd_add_devices() to remove the compatible strings in the child drivers.
This means adding struct mfd_cell with a list of devices to probe.
Is it ok if I switch to mfd_add_devices() ?


>> + dev_dbg(dev, "stpmu1 driver probed\n");
> That message looks redundant to me. I'd remove it.
>
>> +err:
> And you can remove this label.
>
>> + return ret;
> And this
>
>> +}
>> +
>> +static const struct i2c_device_id stpmu1_id[] = {
>> + {"stpmu1", 0},
>> + {}
>> +};
>> +
>> +MODULE_DEVICE_TABLE(i2c, stpmu1_id);
> The above code shouldn't be needed anymore for DT-only devices. See
> da10c06a044b ("i2c: Make I2C ID tables non-mandatory for DT'ed
> devices")
>
>> +
>> +#ifdef CONFIG_PM_SLEEP
>> +static int stpmu1_suspend(struct device *dev)
>> +{
>> + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
>> + struct stpmu1_dev *pmic_dev = i2c_get_clientdata(i2c);
>> +
>> + if (device_may_wakeup(dev))
>> + enable_irq_wake(pmic_dev->irq_wake);
>> +
>> + disable_irq(pmic_dev->irq);
>> + return 0;
>> +}
>> +
>> +static int stpmu1_resume(struct device *dev)
>> +{
>> + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev);
>> + struct stpmu1_dev *pmic_dev = i2c_get_clientdata(i2c);
>> +
>> + regcache_sync(pmic_dev->regmap);
> Maybe you would like to check for an error here.
>
>> +
>> + if (device_may_wakeup(dev))
>> + disable_irq_wake(pmic_dev->irq_wake);
>> +
>> + enable_irq(pmic_dev->irq);
>> + return 0;
>> +}
>> +#endif
>> +
>> +static SIMPLE_DEV_PM_OPS(stpmu1_pm, stpmu1_suspend, stpmu1_resume);
>> +
>> +static struct i2c_driver stpmu1_driver = {
>> + .driver = {
>> + .name = "stpmu1",
>> + .owner = THIS_MODULE,
> This is not needed, the core does it for you.
>
>> + .pm = &stpmu1_pm,
>> + .of_match_table = of_match_ptr(stpmu1_dt_match),
> It is a DT-only device so no need the of_match_ptr.
>
>> + },
>> + .probe = stpmu1_probe,
>> + .remove = stpmu1_remove,
> Now you can remove this
>
>> + .id_table = stpmu1_id,
> And you can remove this also.
>
>> +};
>> +
>> +module_i2c_driver(stpmu1_driver);
>> +
>> +MODULE_DESCRIPTION("STPMU1 PMIC I2C Client");
> nit: PMIC I2C Client sounds weird to me, "STPMU1 PMIC driver" ? Note
> that I am not english native so I could be wrong.
>
>> +MODULE_AUTHOR("<[email protected]>");
> Use "Name <email>" or just "Name"
>
>> +MODULE_LICENSE("GPL");
> As I told you there is a license mismatch with SPDX.
>
> [snip]
>
> Best regards,
> Enric
>

Best Regards,
pascal