[PATCH 1/2] regulator: pca9450: enable restart handler for I2C operation

From: Holger Assmann
Date: Fri Apr 19 2024 - 04:31:58 EST


The NXP PCA9450 can perform various kinds of power cycles when triggered
by I2C-command.
We therefore make this functionality accessible by introducing a
respective restart handler. It will be used after a priority has been
defined within the devicetree.

Signed-off-by: Holger Assmann <h.assmann@xxxxxxxxxxxxxx>
---
drivers/regulator/pca9450-regulator.c | 54 +++++++++++++++++++++++++++
include/linux/regulator/pca9450.h | 7 ++++
2 files changed, 61 insertions(+)

diff --git a/drivers/regulator/pca9450-regulator.c b/drivers/regulator/pca9450-regulator.c
index 2ab365d2749f9..e623323599964 100644
--- a/drivers/regulator/pca9450-regulator.c
+++ b/drivers/regulator/pca9450-regulator.c
@@ -16,6 +16,7 @@
#include <linux/regulator/machine.h>
#include <linux/regulator/of_regulator.h>
#include <linux/regulator/pca9450.h>
+#include <linux/reboot.h>

struct pc9450_dvs_config {
unsigned int run_reg; /* dvs0 */
@@ -33,6 +34,7 @@ struct pca9450 {
struct device *dev;
struct regmap *regmap;
struct gpio_desc *sd_vsel_gpio;
+ struct notifier_block restart_handler;
enum pca9450_chip_type type;
unsigned int rcnt;
int irq;
@@ -700,6 +702,34 @@ static irqreturn_t pca9450_irq_handler(int irq, void *data)
return IRQ_HANDLED;
}

+static int pca9450_restart(struct notifier_block *nb,
+ unsigned long mode, void *cmd)
+{
+ struct pca9450 *pmic = container_of(nb, struct pca9450,
+ restart_handler);
+ struct i2c_client *client = container_of(pmic->dev,
+ struct i2c_client, dev);
+ int ret = 0;
+ u32 rtype;
+
+ switch (mode) {
+ case REBOOT_WARM:
+ /* Warm reset (Toggle POR_B for 20 ms) */
+ rtype = 0x35;
+ break;
+ default:
+ /* Cold reset (Power recycle all regulators) */
+ rtype = 0x64;
+ }
+
+ ret = i2c_smbus_write_byte_data(client, PCA9450_REG_SWRST, rtype);
+ if (ret < 0)
+ dev_alert(pmic->dev, "Failed to shutdown (err = %d)\n", ret);
+
+ mdelay(500);
+ return NOTIFY_DONE;
+}
+
static int pca9450_i2c_probe(struct i2c_client *i2c)
{
enum pca9450_chip_type type = (unsigned int)(uintptr_t)
@@ -845,12 +875,35 @@ static int pca9450_i2c_probe(struct i2c_client *i2c)
return PTR_ERR(pca9450->sd_vsel_gpio);
}

+ /* Register I2C restart handler if one is defined by device tree */
+ if (!of_property_read_u32(i2c->dev.of_node, "priority",
+ &pca9450->restart_handler.priority)) {
+ pca9450->restart_handler.notifier_call = pca9450_restart;
+
+ ret = register_restart_handler(&pca9450->restart_handler);
+ if (ret)
+ return dev_err_probe(&i2c->dev, ret,
+ "cannot register restart handler.\n");
+ else
+ dev_info(&i2c->dev,
+ "registered restart handler with priority %d.\n",
+ pca9450->restart_handler.priority);
+ }
+
dev_info(&i2c->dev, "%s probed.\n",
type == PCA9450_TYPE_PCA9450A ? "pca9450a" : "pca9450bc");

return 0;
}

+static void pca9450_i2c_remove(struct i2c_client *i2c)
+{
+ struct pca9450 *pca9450 = dev_get_drvdata(&i2c->dev);
+
+ if (pca9450->restart_handler.notifier_call)
+ unregister_restart_handler(&pca9450->restart_handler);
+}
+
static const struct of_device_id pca9450_of_match[] = {
{
.compatible = "nxp,pca9450a",
@@ -875,6 +928,7 @@ static struct i2c_driver pca9450_i2c_driver = {
.of_match_table = pca9450_of_match,
},
.probe = pca9450_i2c_probe,
+ .remove = pca9450_i2c_remove,
};

module_i2c_driver(pca9450_i2c_driver);
diff --git a/include/linux/regulator/pca9450.h b/include/linux/regulator/pca9450.h
index 505c908dbb817..2ee38bab23402 100644
--- a/include/linux/regulator/pca9450.h
+++ b/include/linux/regulator/pca9450.h
@@ -226,6 +226,13 @@ enum {
#define WDOG_B_CFG_COLD_LDO12 0x80
#define WDOG_B_CFG_COLD 0xC0

+/* PCA9450_REG_SWRST bits */
+#define SW_RST_NONE 0x00
+#define SW_RST_DEFAULTS 0x05
+#define SW_RST_COLD_LDO12_CLK32 0x14
+#define SW_RST_WARM 0x35
+#define SW_RST_COLD 0x64
+
/* PCA9450_REG_CONFIG2 bits */
#define I2C_LT_MASK 0x03
#define I2C_LT_FORCE_DISABLE 0x00
--
2.39.2