summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2025-10-01 12:04:12 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2025-10-01 12:04:12 -0700
commitb4918003cf54f99004c136c26f96b6df7ab49fac (patch)
treefdc7c8984d255e9d23117886fb786c188f6e8a72 /drivers
parent2d274536245b58a43753a23d84dfadc9df1df489 (diff)
parentb9d6cfe2ae699bbf230a6c8e0e32212b04bff661 (diff)
Merge tag 'mfd-next-6.18' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd
Pull MFD updates from Lee Jones: "This round of updates contains a fair amount of new device support, a couple of fixes and some refactoring. The most notable additions include new drivers for Loongson's Security Engine, RNG and TPM, new drivers for TI's TPS6594 Power Button and BQ257xx Charger ICs. The rest of the set provides a return value check fix and a refactoring to use a more modern GPIO API for the VEXPRESS sysreg driver, the removal of a deprecated IRQ ACK function from the MC13xxx RTC driver and a new DT binding for the aforementioned TI BQ257xx charger. New Support & Features: - Add a suite of drivers for the Loongson Security Engine, including the core controller, a Random Number Generator (RNG) and Trusted Platform Module (TPM) support. - Introduce support for the TI TPS6594 PMIC's power button, including the input driver, MFD cell registration, and a system power-off handler. - Add comprehensive support for the TI BQ257xx series of charger ICs, including the core MFD driver and a power supply driver for the charger functionality. Improvements & Fixes: - Check the return value of devm_gpiochip_add_data() in the VEXPRESS sysreg driver to prevent potential silent failures. Cleanups & Refactoring: - Add a MAINTAINERS entry for the new Loongson Security Engine drivers. - Convert the VEXPRESS sysreg driver to use the modern generic GPIO chip API. Removals: - Remove the deprecated and unused mc13xxx_irq_ack() function from the MC13xxx RTC, input and touchscreen drivers. Device Tree Bindings Updates: - Add device tree bindings for the TI BQ25703A charger" * tag 'mfd-next-6.18' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd: (69 commits) mfd: simple-mfd-i2c: Add compatible string for LX2160ARDB mfd: simple-mfd-i2c: Keep compatible strings in alphabetical order dt-bindings: mfd: twl: Add missing sub-nodes for TWL4030 & TWL603x dt-bindings: watchdog: Add SMARC-sAM67 support dt-bindings: mfd: tps6594: Allow gpio-line-names mfd: intel-lpss: Add Intel Wildcat Lake LPSS PCI IDs mfd: 88pm886: Add GPADC cell mfd: vexpress-sysreg: Use more common syntax for compound literals mfd: rz-mtu3: Fix MTU5 NFCR register offset mfd: max77705: Setup the core driver as an interrupt controller mfd: cs42l43: Remove IRQ masking in suspend mfd: cs42l43: Move IRQ enable/disable to encompass force suspend mfd: ls2kbmc: Add Loongson-2K BMC reset function support mfd: ls2kbmc: Introduce Loongson-2K BMC core driver mfd: bd71828, bd71815: Prepare for power-supply support dt-bindings: mfd: aspeed: Add AST2700 SCU compatibles dt-bindings: mfd: Convert aspeed,ast2400-p2a-ctrl to DT schema dt-bindings: mfd: fsl,mc13xxx: Add buttons node dt-bindings: mfd: fsl,mc13xxx: Convert txt to DT schema mfd: macsmc: Add "apple,t8103-smc" compatible ...
Diffstat (limited to 'drivers')
-rw-r--r--drivers/char/tpm/Kconfig9
-rw-r--r--drivers/char/tpm/Makefile1
-rw-r--r--drivers/char/tpm/tpm_loongson.c84
-rw-r--r--drivers/crypto/Kconfig1
-rw-r--r--drivers/crypto/Makefile1
-rw-r--r--drivers/crypto/loongson/Kconfig5
-rw-r--r--drivers/crypto/loongson/Makefile1
-rw-r--r--drivers/crypto/loongson/loongson-rng.c209
-rw-r--r--drivers/input/misc/Kconfig10
-rw-r--r--drivers/input/misc/Makefile1
-rw-r--r--drivers/input/misc/mc13783-pwrbutton.c1
-rw-r--r--drivers/input/misc/tps6594-pwrbutton.c126
-rw-r--r--drivers/input/touchscreen/mc13783_ts.c4
-rw-r--r--drivers/mfd/88pm886.c1
-rw-r--r--drivers/mfd/Kconfig52
-rw-r--r--drivers/mfd/Makefile5
-rw-r--r--drivers/mfd/adp5585.c1
-rw-r--r--drivers/mfd/arizona-irq.c5
-rw-r--r--drivers/mfd/bq257xx.c99
-rw-r--r--drivers/mfd/cs42l43.c32
-rw-r--r--drivers/mfd/da9063-i2c.c27
-rw-r--r--drivers/mfd/exynos-lpass.c1
-rw-r--r--drivers/mfd/fsl-imx25-tsadc.c1
-rw-r--r--drivers/mfd/intel-lpss-pci.c13
-rw-r--r--drivers/mfd/intel_soc_pmic_chtdc_ti.c2
-rw-r--r--drivers/mfd/kempld-core.c36
-rw-r--r--drivers/mfd/loongson-se.c253
-rw-r--r--drivers/mfd/ls2k-bmc-core.c528
-rw-r--r--drivers/mfd/macsmc.c5
-rw-r--r--drivers/mfd/madera-core.c4
-rw-r--r--drivers/mfd/max77705.c35
-rw-r--r--drivers/mfd/max8997.c4
-rw-r--r--drivers/mfd/max8998.c4
-rw-r--r--drivers/mfd/mfd-core.c1
-rw-r--r--drivers/mfd/qnap-mcu.c39
-rw-r--r--drivers/mfd/rohm-bd71828.c44
-rw-r--r--drivers/mfd/rz-mtu3.c2
-rw-r--r--drivers/mfd/simple-mfd-i2c.c22
-rw-r--r--drivers/mfd/stm32-lptimer.c1
-rw-r--r--drivers/mfd/stmpe-i2c.c15
-rw-r--r--drivers/mfd/stmpe-spi.c15
-rw-r--r--drivers/mfd/stmpe.c3
-rw-r--r--drivers/mfd/sun4i-gpadc.c1
-rw-r--r--drivers/mfd/tps6594-core.c59
-rw-r--r--drivers/mfd/vexpress-sysreg.c2
-rw-r--r--drivers/power/supply/Kconfig7
-rw-r--r--drivers/power/supply/Makefile1
-rw-r--r--drivers/power/supply/bq257xx_charger.c755
-rw-r--r--drivers/regulator/Kconfig8
-rw-r--r--drivers/regulator/Makefile1
-rw-r--r--drivers/regulator/bq257xx-regulator.c186
-rw-r--r--drivers/rtc/rtc-mc13xxx.c13
52 files changed, 2575 insertions, 161 deletions
diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
index dddd702b2454..ba3924eb13ba 100644
--- a/drivers/char/tpm/Kconfig
+++ b/drivers/char/tpm/Kconfig
@@ -189,6 +189,15 @@ config TCG_IBMVTPM
will be accessible from within Linux. To compile this driver
as a module, choose M here; the module will be called tpm_ibmvtpm.
+config TCG_LOONGSON
+ tristate "Loongson TPM Interface"
+ depends on MFD_LOONGSON_SE
+ help
+ If you want to make Loongson TPM support available, say Yes and
+ it will be accessible from within Linux. To compile this
+ driver as a module, choose M here; the module will be called
+ tpm_loongson.
+
config TCG_XEN
tristate "XEN TPM Interface"
depends on TCG_TPM && XEN
diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile
index 9de1b3ea34a9..5b5cdc0d32e4 100644
--- a/drivers/char/tpm/Makefile
+++ b/drivers/char/tpm/Makefile
@@ -46,3 +46,4 @@ obj-$(CONFIG_TCG_ARM_CRB_FFA) += tpm_crb_ffa.o
obj-$(CONFIG_TCG_VTPM_PROXY) += tpm_vtpm_proxy.o
obj-$(CONFIG_TCG_FTPM_TEE) += tpm_ftpm_tee.o
obj-$(CONFIG_TCG_SVSM) += tpm_svsm.o
+obj-$(CONFIG_TCG_LOONGSON) += tpm_loongson.o
diff --git a/drivers/char/tpm/tpm_loongson.c b/drivers/char/tpm/tpm_loongson.c
new file mode 100644
index 000000000000..9e50250763d1
--- /dev/null
+++ b/drivers/char/tpm/tpm_loongson.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Loongson Technology Corporation Limited. */
+
+#include <linux/device.h>
+#include <linux/mfd/loongson-se.h>
+#include <linux/platform_device.h>
+#include <linux/wait.h>
+
+#include "tpm.h"
+
+struct tpm_loongson_cmd {
+ u32 cmd_id;
+ u32 data_off;
+ u32 data_len;
+ u32 pad[5];
+};
+
+static int tpm_loongson_recv(struct tpm_chip *chip, u8 *buf, size_t count)
+{
+ struct loongson_se_engine *tpm_engine = dev_get_drvdata(&chip->dev);
+ struct tpm_loongson_cmd *cmd_ret = tpm_engine->command_ret;
+
+ if (cmd_ret->data_len > count)
+ return -EIO;
+
+ memcpy(buf, tpm_engine->data_buffer, cmd_ret->data_len);
+
+ return cmd_ret->data_len;
+}
+
+static int tpm_loongson_send(struct tpm_chip *chip, u8 *buf, size_t bufsiz, size_t count)
+{
+ struct loongson_se_engine *tpm_engine = dev_get_drvdata(&chip->dev);
+ struct tpm_loongson_cmd *cmd = tpm_engine->command;
+
+ if (count > tpm_engine->buffer_size)
+ return -E2BIG;
+
+ cmd->data_len = count;
+ memcpy(tpm_engine->data_buffer, buf, count);
+
+ return loongson_se_send_engine_cmd(tpm_engine);
+}
+
+static const struct tpm_class_ops tpm_loongson_ops = {
+ .flags = TPM_OPS_AUTO_STARTUP,
+ .recv = tpm_loongson_recv,
+ .send = tpm_loongson_send,
+};
+
+static int tpm_loongson_probe(struct platform_device *pdev)
+{
+ struct loongson_se_engine *tpm_engine;
+ struct device *dev = &pdev->dev;
+ struct tpm_loongson_cmd *cmd;
+ struct tpm_chip *chip;
+
+ tpm_engine = loongson_se_init_engine(dev->parent, SE_ENGINE_TPM);
+ if (!tpm_engine)
+ return -ENODEV;
+ cmd = tpm_engine->command;
+ cmd->cmd_id = SE_CMD_TPM;
+ cmd->data_off = tpm_engine->buffer_off;
+
+ chip = tpmm_chip_alloc(dev, &tpm_loongson_ops);
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+ chip->flags = TPM_CHIP_FLAG_TPM2 | TPM_CHIP_FLAG_IRQ;
+ dev_set_drvdata(&chip->dev, tpm_engine);
+
+ return tpm_chip_register(chip);
+}
+
+static struct platform_driver tpm_loongson = {
+ .probe = tpm_loongson_probe,
+ .driver = {
+ .name = "tpm_loongson",
+ },
+};
+module_platform_driver(tpm_loongson);
+
+MODULE_ALIAS("platform:tpm_loongson");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Loongson TPM driver");
diff --git a/drivers/crypto/Kconfig b/drivers/crypto/Kconfig
index 04b4c43b6bae..c7a1060ba57a 100644
--- a/drivers/crypto/Kconfig
+++ b/drivers/crypto/Kconfig
@@ -840,6 +840,7 @@ config CRYPTO_DEV_CCREE
If unsure say Y.
source "drivers/crypto/hisilicon/Kconfig"
+source "drivers/crypto/loongson/Kconfig"
source "drivers/crypto/amlogic/Kconfig"
diff --git a/drivers/crypto/Makefile b/drivers/crypto/Makefile
index 22eadcc8f4a2..125b99b24af1 100644
--- a/drivers/crypto/Makefile
+++ b/drivers/crypto/Makefile
@@ -44,6 +44,7 @@ obj-y += inside-secure/
obj-$(CONFIG_CRYPTO_DEV_ARTPEC6) += axis/
obj-y += xilinx/
obj-y += hisilicon/
+obj-y += loongson/
obj-$(CONFIG_CRYPTO_DEV_AMLOGIC_GXL) += amlogic/
obj-y += intel/
obj-y += starfive/
diff --git a/drivers/crypto/loongson/Kconfig b/drivers/crypto/loongson/Kconfig
new file mode 100644
index 000000000000..15475da8fc11
--- /dev/null
+++ b/drivers/crypto/loongson/Kconfig
@@ -0,0 +1,5 @@
+config CRYPTO_DEV_LOONGSON_RNG
+ tristate "Support for Loongson RNG Driver"
+ depends on MFD_LOONGSON_SE
+ help
+ Support for Loongson RNG Driver.
diff --git a/drivers/crypto/loongson/Makefile b/drivers/crypto/loongson/Makefile
new file mode 100644
index 000000000000..1ce5ec32b553
--- /dev/null
+++ b/drivers/crypto/loongson/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_CRYPTO_DEV_LOONGSON_RNG) += loongson-rng.o
diff --git a/drivers/crypto/loongson/loongson-rng.c b/drivers/crypto/loongson/loongson-rng.c
new file mode 100644
index 000000000000..3a4940260f9e
--- /dev/null
+++ b/drivers/crypto/loongson/loongson-rng.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2019 HiSilicon Limited. */
+/* Copyright (c) 2025 Loongson Technology Corporation Limited. */
+
+#include <linux/crypto.h>
+#include <linux/err.h>
+#include <linux/hw_random.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mfd/loongson-se.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/random.h>
+#include <crypto/internal/rng.h>
+
+#define SE_SEED_SIZE 32
+
+struct loongson_rng_list {
+ struct mutex lock;
+ struct list_head list;
+ int registered;
+};
+
+struct loongson_rng {
+ u32 used;
+ struct loongson_se_engine *engine;
+ struct list_head list;
+ struct mutex lock;
+};
+
+struct loongson_rng_ctx {
+ struct loongson_rng *rng;
+};
+
+struct loongson_rng_cmd {
+ u32 cmd_id;
+ union {
+ u32 len;
+ u32 ret;
+ } u;
+ u32 seed_off;
+ u32 out_off;
+ u32 pad[4];
+};
+
+static struct loongson_rng_list rng_devices = {
+ .lock = __MUTEX_INITIALIZER(rng_devices.lock),
+ .list = LIST_HEAD_INIT(rng_devices.list),
+};
+
+static int loongson_rng_generate(struct crypto_rng *tfm, const u8 *src,
+ unsigned int slen, u8 *dstn, unsigned int dlen)
+{
+ struct loongson_rng_ctx *ctx = crypto_rng_ctx(tfm);
+ struct loongson_rng *rng = ctx->rng;
+ struct loongson_rng_cmd *cmd = rng->engine->command;
+ int err, len;
+
+ mutex_lock(&rng->lock);
+ cmd->seed_off = 0;
+ do {
+ len = min(dlen, rng->engine->buffer_size);
+ cmd = rng->engine->command;
+ cmd->u.len = len;
+ err = loongson_se_send_engine_cmd(rng->engine);
+ if (err)
+ break;
+
+ cmd = rng->engine->command_ret;
+ if (cmd->u.ret) {
+ err = -EIO;
+ break;
+ }
+
+ memcpy(dstn, rng->engine->data_buffer, len);
+ dlen -= len;
+ dstn += len;
+ } while (dlen > 0);
+ mutex_unlock(&rng->lock);
+
+ return err;
+}
+
+static int loongson_rng_init(struct crypto_tfm *tfm)
+{
+ struct loongson_rng_ctx *ctx = crypto_tfm_ctx(tfm);
+ struct loongson_rng *rng;
+ u32 min_used = U32_MAX;
+
+ mutex_lock(&rng_devices.lock);
+ list_for_each_entry(rng, &rng_devices.list, list) {
+ if (rng->used < min_used) {
+ ctx->rng = rng;
+ min_used = rng->used;
+ }
+ }
+ ctx->rng->used++;
+ mutex_unlock(&rng_devices.lock);
+
+ return 0;
+}
+
+static void loongson_rng_exit(struct crypto_tfm *tfm)
+{
+ struct loongson_rng_ctx *ctx = crypto_tfm_ctx(tfm);
+
+ mutex_lock(&rng_devices.lock);
+ ctx->rng->used--;
+ mutex_unlock(&rng_devices.lock);
+}
+
+static int loongson_rng_seed(struct crypto_rng *tfm, const u8 *seed,
+ unsigned int slen)
+{
+ struct loongson_rng_ctx *ctx = crypto_rng_ctx(tfm);
+ struct loongson_rng *rng = ctx->rng;
+ struct loongson_rng_cmd *cmd;
+ int err;
+
+ if (slen < SE_SEED_SIZE)
+ return -EINVAL;
+
+ slen = min(slen, rng->engine->buffer_size);
+
+ mutex_lock(&rng->lock);
+ cmd = rng->engine->command;
+ cmd->u.len = slen;
+ cmd->seed_off = rng->engine->buffer_off;
+ memcpy(rng->engine->data_buffer, seed, slen);
+ err = loongson_se_send_engine_cmd(rng->engine);
+ if (err)
+ goto out;
+
+ cmd = rng->engine->command_ret;
+ if (cmd->u.ret)
+ err = -EIO;
+out:
+ mutex_unlock(&rng->lock);
+
+ return err;
+}
+
+static struct rng_alg loongson_rng_alg = {
+ .generate = loongson_rng_generate,
+ .seed = loongson_rng_seed,
+ .seedsize = SE_SEED_SIZE,
+ .base = {
+ .cra_name = "stdrng",
+ .cra_driver_name = "loongson_stdrng",
+ .cra_priority = 300,
+ .cra_ctxsize = sizeof(struct loongson_rng_ctx),
+ .cra_module = THIS_MODULE,
+ .cra_init = loongson_rng_init,
+ .cra_exit = loongson_rng_exit,
+ },
+};
+
+static int loongson_rng_probe(struct platform_device *pdev)
+{
+ struct loongson_rng_cmd *cmd;
+ struct loongson_rng *rng;
+ int ret = 0;
+
+ rng = devm_kzalloc(&pdev->dev, sizeof(*rng), GFP_KERNEL);
+ if (!rng)
+ return -ENOMEM;
+
+ rng->engine = loongson_se_init_engine(pdev->dev.parent, SE_ENGINE_RNG);
+ if (!rng->engine)
+ return -ENODEV;
+ cmd = rng->engine->command;
+ cmd->cmd_id = SE_CMD_RNG;
+ cmd->out_off = rng->engine->buffer_off;
+ mutex_init(&rng->lock);
+
+ mutex_lock(&rng_devices.lock);
+
+ if (!rng_devices.registered) {
+ ret = crypto_register_rng(&loongson_rng_alg);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register crypto(%d)\n", ret);
+ goto out;
+ }
+ rng_devices.registered = 1;
+ }
+
+ list_add_tail(&rng->list, &rng_devices.list);
+out:
+ mutex_unlock(&rng_devices.lock);
+
+ return ret;
+}
+
+static struct platform_driver loongson_rng_driver = {
+ .probe = loongson_rng_probe,
+ .driver = {
+ .name = "loongson-rng",
+ },
+};
+module_platform_driver(loongson_rng_driver);
+
+MODULE_ALIAS("platform:loongson-rng");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Yinggang Gu <guyinggang@loongson.cn>");
+MODULE_AUTHOR("Qunqin Zhao <zhaoqunqin@loongson.cn>");
+MODULE_DESCRIPTION("Loongson Random Number Generator driver");
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index d604aad90a89..0e6b49fb54bc 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -516,6 +516,16 @@ config INPUT_TPS65219_PWRBUTTON
To compile this driver as a module, choose M here. The module will
be called tps65219-pwrbutton.
+config INPUT_TPS6594_PWRBUTTON
+ tristate "TPS6594 Power button driver"
+ depends on MFD_TPS6594
+ help
+ Say Y here if you want to enable power button reporting for
+ TPS6594 Power Management IC devices.
+
+ To compile this driver as a module, choose M here. The module will
+ be called tps6594-pwrbutton.
+
config INPUT_AXP20X_PEK
tristate "X-Powers AXP20X power button driver"
depends on MFD_AXP20X
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index ac45cb9b5c99..ae857c24f48e 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -84,6 +84,7 @@ obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o
obj-$(CONFIG_INPUT_STPMIC1_ONKEY) += stpmic1_onkey.o
obj-$(CONFIG_INPUT_TPS65218_PWRBUTTON) += tps65218-pwrbutton.o
obj-$(CONFIG_INPUT_TPS65219_PWRBUTTON) += tps65219-pwrbutton.o
+obj-$(CONFIG_INPUT_TPS6594_PWRBUTTON) += tps6594-pwrbutton.o
obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o
obj-$(CONFIG_INPUT_TWL4030_VIBRA) += twl4030-vibra.o
obj-$(CONFIG_INPUT_TWL6040_VIBRA) += twl6040-vibra.o
diff --git a/drivers/input/misc/mc13783-pwrbutton.c b/drivers/input/misc/mc13783-pwrbutton.c
index 1c7faa9b7afe..b83d762ae2e9 100644
--- a/drivers/input/misc/mc13783-pwrbutton.c
+++ b/drivers/input/misc/mc13783-pwrbutton.c
@@ -57,7 +57,6 @@ static irqreturn_t button_irq(int irq, void *_priv)
struct mc13783_pwrb *priv = _priv;
int val;
- mc13xxx_irq_ack(priv->mc13783, irq);
mc13xxx_reg_read(priv->mc13783, MC13783_REG_INTERRUPT_SENSE_1, &val);
switch (irq) {
diff --git a/drivers/input/misc/tps6594-pwrbutton.c b/drivers/input/misc/tps6594-pwrbutton.c
new file mode 100644
index 000000000000..cd039b3866dc
--- /dev/null
+++ b/drivers/input/misc/tps6594-pwrbutton.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * power button driver for TI TPS6594 PMICs
+ *
+ * Copyright (C) 2025 Critical Link LLC - https://www.criticallink.com/
+ */
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/tps6594.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+struct tps6594_pwrbutton {
+ struct device *dev;
+ struct input_dev *idev;
+ char phys[32];
+};
+
+static irqreturn_t tps6594_pb_push_irq(int irq, void *_pwr)
+{
+ struct tps6594_pwrbutton *pwr = _pwr;
+
+ input_report_key(pwr->idev, KEY_POWER, 1);
+ pm_wakeup_event(pwr->dev, 0);
+ input_sync(pwr->idev);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t tps6594_pb_release_irq(int irq, void *_pwr)
+{
+ struct tps6594_pwrbutton *pwr = _pwr;
+
+ input_report_key(pwr->idev, KEY_POWER, 0);
+ input_sync(pwr->idev);
+
+ return IRQ_HANDLED;
+}
+
+static int tps6594_pb_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct tps6594_pwrbutton *pwr;
+ struct input_dev *idev;
+ int error;
+ int push_irq;
+ int release_irq;
+
+ pwr = devm_kzalloc(dev, sizeof(*pwr), GFP_KERNEL);
+ if (!pwr)
+ return -ENOMEM;
+
+ idev = devm_input_allocate_device(dev);
+ if (!idev)
+ return -ENOMEM;
+
+ idev->name = pdev->name;
+ snprintf(pwr->phys, sizeof(pwr->phys), "%s/input0",
+ pdev->name);
+ idev->phys = pwr->phys;
+ idev->id.bustype = BUS_I2C;
+
+ input_set_capability(idev, EV_KEY, KEY_POWER);
+
+ pwr->dev = dev;
+ pwr->idev = idev;
+ device_init_wakeup(dev, true);
+
+ push_irq = platform_get_irq(pdev, 0);
+ if (push_irq < 0)
+ return -EINVAL;
+
+ release_irq = platform_get_irq(pdev, 1);
+ if (release_irq < 0)
+ return -EINVAL;
+
+ error = devm_request_threaded_irq(dev, push_irq, NULL,
+ tps6594_pb_push_irq,
+ IRQF_ONESHOT,
+ pdev->resource[0].name, pwr);
+ if (error) {
+ dev_err(dev, "failed to request push IRQ #%d: %d\n", push_irq,
+ error);
+ return error;
+ }
+
+ error = devm_request_threaded_irq(dev, release_irq, NULL,
+ tps6594_pb_release_irq,
+ IRQF_ONESHOT,
+ pdev->resource[1].name, pwr);
+ if (error) {
+ dev_err(dev, "failed to request release IRQ #%d: %d\n",
+ release_irq, error);
+ return error;
+ }
+
+ error = input_register_device(idev);
+ if (error) {
+ dev_err(dev, "Can't register power button: %d\n", error);
+ return error;
+ }
+
+ return 0;
+}
+
+static const struct platform_device_id tps6594_pwrbtn_id_table[] = {
+ { "tps6594-pwrbutton", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, tps6594_pwrbtn_id_table);
+
+static struct platform_driver tps6594_pb_driver = {
+ .probe = tps6594_pb_probe,
+ .driver = {
+ .name = "tps6594_pwrbutton",
+ },
+ .id_table = tps6594_pwrbtn_id_table,
+};
+module_platform_driver(tps6594_pb_driver);
+
+MODULE_DESCRIPTION("TPS6594 Power Button");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/mc13783_ts.c b/drivers/input/touchscreen/mc13783_ts.c
index 33635da85079..47b8da00027f 100644
--- a/drivers/input/touchscreen/mc13783_ts.c
+++ b/drivers/input/touchscreen/mc13783_ts.c
@@ -42,8 +42,6 @@ static irqreturn_t mc13783_ts_handler(int irq, void *data)
{
struct mc13783_ts_priv *priv = data;
- mc13xxx_irq_ack(priv->mc13xxx, irq);
-
/*
* Kick off reading coordinates. Note that if work happens already
* be queued for future execution (it rearms itself) it will not
@@ -137,8 +135,6 @@ static int mc13783_ts_open(struct input_dev *dev)
mc13xxx_lock(priv->mc13xxx);
- mc13xxx_irq_ack(priv->mc13xxx, MC13XXX_IRQ_TS);
-
ret = mc13xxx_irq_request(priv->mc13xxx, MC13XXX_IRQ_TS,
mc13783_ts_handler, MC13783_TS_NAME, priv);
if (ret)
diff --git a/drivers/mfd/88pm886.c b/drivers/mfd/88pm886.c
index 39dd9a818b0f..e411d8dee554 100644
--- a/drivers/mfd/88pm886.c
+++ b/drivers/mfd/88pm886.c
@@ -35,6 +35,7 @@ static const struct resource pm886_onkey_resources[] = {
};
static const struct mfd_cell pm886_devs[] = {
+ MFD_CELL_NAME("88pm886-gpadc"),
MFD_CELL_RES("88pm886-onkey", pm886_onkey_resources),
MFD_CELL_NAME("88pm886-regulator"),
MFD_CELL_NAME("88pm886-rtc"),
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 4d21dbf73a34..67b54e0fd452 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -129,6 +129,7 @@ config MFD_AAT2870_CORE
select MFD_CORE
depends on I2C=y
depends on GPIOLIB || COMPILE_TEST
+ depends on GPIOLIB_LEGACY
help
If you say yes here you get support for the AAT2870.
This driver provides common support for accessing the device,
@@ -1253,6 +1254,19 @@ config MFD_QCOM_RPM
Say M here if you want to include support for the Qualcomm RPM as a
module. This will build a module called "qcom_rpm".
+config MFD_SPACEMIT_P1
+ tristate "SpacemiT P1 PMIC"
+ depends on ARCH_SPACEMIT || COMPILE_TEST
+ depends on I2C
+ select I2C_K1
+ select MFD_SIMPLE_MFD_I2C
+ help
+ This option supports the I2C-based SpacemiT P1 PMIC, which
+ contains regulators, a power switch, GPIOs, an RTC, and more.
+ This option is selected when any of the supported sub-devices
+ is configured. The basic functionality is implemented by the
+ simple MFD I2C driver.
+
config MFD_SPMI_PMIC
tristate "Qualcomm SPMI PMICs"
depends on ARCH_QCOM || COMPILE_TEST
@@ -1426,6 +1440,7 @@ config MFD_SEC_I2C
config MFD_SI476X_CORE
tristate "Silicon Laboratories 4761/64/68 AM/FM radio."
depends on I2C
+ depends on GPIOLIB_LEGACY
select MFD_CORE
select REGMAP_I2C
help
@@ -1656,6 +1671,17 @@ config MFD_TI_LMU
LM36274. It consists of backlight, LED and regulator driver.
It provides consistent device controls for lighting functions.
+config MFD_BQ257XX
+ tristate "TI BQ257XX Buck/Boost Charge Controller"
+ depends on I2C
+ select MFD_CORE
+ select REGMAP_I2C
+ help
+ Support Texas Instruments BQ25703 Buck/Boost converter with
+ charge controller. It consists of regulators that provide
+ system voltage and OTG voltage, and a charger manager for
+ batteries containing one or more cells.
+
config MFD_OMAP_USB_HOST
bool "TI OMAP USBHS core and TLL driver"
depends on USB_EHCI_HCD_OMAP || USB_OHCI_HCD_OMAP3
@@ -1992,7 +2018,7 @@ config MFD_TIMBERDALE
multifunction device which exposes numerous platform devices.
The timberdale FPGA can be found on the Intel Atom development board
- for in-vehicle infontainment, called Russellville.
+ for in-vehicle infotainment, called Russellville.
config MFD_TC3589X
bool "Toshiba TC35892 and variants"
@@ -2443,6 +2469,30 @@ config MFD_INTEL_M10_BMC_PMCI
additional drivers must be enabled in order to use the functionality
of the device.
+config MFD_LOONGSON_SE
+ tristate "Loongson Security Engine chip controller driver"
+ depends on LOONGARCH && ACPI
+ select MFD_CORE
+ help
+ The Loongson Security Engine chip supports RNG, SM2, SM3 and
+ SM4 accelerator engines. Each engine have its own DMA buffer
+ provided by the controller. The kernel cannot directly send
+ commands to the engine and must first send them to the controller,
+ which will forward them to the corresponding engine.
+
+config MFD_LS2K_BMC_CORE
+ bool "Loongson-2K Board Management Controller Support"
+ depends on PCI && ACPI_GENERIC_GSI
+ select MFD_CORE
+ help
+ Say yes here to add support for the Loongson-2K BMC which is a Board
+ Management Controller connected to the PCIe bus. The device supports
+ multiple sub-devices like display and IPMI. This driver provides common
+ support for accessing the devices.
+
+ The display is enabled by default in the driver, while the IPMI interface
+ is enabled independently through the IPMI_LS2K option in the IPMI section.
+
config MFD_QNAP_MCU
tristate "QNAP microcontroller unit core driver"
depends on SERIAL_DEV_BUS
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 3dd7a43c44c0..865e9f12faff 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_MFD_SM501) += sm501.o
obj-$(CONFIG_ARCH_BCM2835) += bcm2835-pm.o
obj-$(CONFIG_MFD_BCM590XX) += bcm590xx.o
obj-$(CONFIG_MFD_BD9571MWV) += bd9571mwv.o
+obj-$(CONFIG_MFD_BQ257XX) += bq257xx.o
obj-$(CONFIG_MFD_CGBC) += cgbc-core.o
obj-$(CONFIG_MFD_CROS_EC_DEV) += cros_ec_dev.o
obj-$(CONFIG_MFD_CS42L43) += cs42l43.o
@@ -289,6 +290,8 @@ obj-$(CONFIG_MFD_INTEL_M10_BMC_CORE) += intel-m10-bmc-core.o
obj-$(CONFIG_MFD_INTEL_M10_BMC_SPI) += intel-m10-bmc-spi.o
obj-$(CONFIG_MFD_INTEL_M10_BMC_PMCI) += intel-m10-bmc-pmci.o
+obj-$(CONFIG_MFD_LS2K_BMC_CORE) += ls2k-bmc-core.o
+
obj-$(CONFIG_MFD_ATC260X) += atc260x-core.o
obj-$(CONFIG_MFD_ATC260X_I2C) += atc260x-i2c.o
@@ -298,3 +301,5 @@ obj-$(CONFIG_MFD_RSMU_I2C) += rsmu_i2c.o rsmu_core.o
obj-$(CONFIG_MFD_RSMU_SPI) += rsmu_spi.o rsmu_core.o
obj-$(CONFIG_MFD_UPBOARD_FPGA) += upboard-fpga.o
+
+obj-$(CONFIG_MFD_LOONGSON_SE) += loongson-se.o
diff --git a/drivers/mfd/adp5585.c b/drivers/mfd/adp5585.c
index 58f7cebe2ea4..46b3ce3d7bae 100644
--- a/drivers/mfd/adp5585.c
+++ b/drivers/mfd/adp5585.c
@@ -432,7 +432,6 @@ static int adp5585_reset_ev_parse(struct adp5585_dev *adp5585)
"Invalid value(%u) for adi,reset-pulse-width-us\n",
prop_val);
}
- return ret;
}
return 0;
diff --git a/drivers/mfd/arizona-irq.c b/drivers/mfd/arizona-irq.c
index 3f8622ee0e59..544016d420fe 100644
--- a/drivers/mfd/arizona-irq.c
+++ b/drivers/mfd/arizona-irq.c
@@ -136,7 +136,7 @@ static irqreturn_t arizona_irq_thread(int irq, void *data)
dev_err(arizona->dev,
"Failed to read main IRQ status: %d\n", ret);
}
-
+#ifdef CONFIG_GPIOLIB_LEGACY
/*
* Poll the IRQ pin status to see if we're really done
* if the interrupt controller can't do it for us.
@@ -150,6 +150,7 @@ static irqreturn_t arizona_irq_thread(int irq, void *data)
!gpio_get_value_cansleep(arizona->pdata.irq_gpio)) {
poll = true;
}
+#endif
} while (poll);
pm_runtime_put_autosuspend(arizona->dev);
@@ -349,6 +350,7 @@ int arizona_irq_init(struct arizona *arizona)
goto err_map_main_irq;
}
+#ifdef CONFIG_GPIOLIB_LEGACY
/* Used to emulate edge trigger and to work around broken pinmux */
if (arizona->pdata.irq_gpio) {
if (gpio_to_irq(arizona->pdata.irq_gpio) != arizona->irq) {
@@ -368,6 +370,7 @@ int arizona_irq_init(struct arizona *arizona)
arizona->pdata.irq_gpio = 0;
}
}
+#endif
ret = request_threaded_irq(arizona->irq, NULL, arizona_irq_thread,
flags, "arizona", arizona);
diff --git a/drivers/mfd/bq257xx.c b/drivers/mfd/bq257xx.c
new file mode 100644
index 000000000000..e9d49dac0a16
--- /dev/null
+++ b/drivers/mfd/bq257xx.c
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BQ257XX Core Driver
+ * Copyright (C) 2025 Chris Morgan <macromorgan@hotmail.com>
+ */
+
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/mfd/bq257xx.h>
+#include <linux/mfd/core.h>
+#include <linux/regmap.h>
+
+static const struct regmap_range bq25703_readonly_reg_ranges[] = {
+ regmap_reg_range(BQ25703_CHARGER_STATUS, BQ25703_MANUFACT_DEV_ID),
+};
+
+static const struct regmap_access_table bq25703_writeable_regs = {
+ .no_ranges = bq25703_readonly_reg_ranges,
+ .n_no_ranges = ARRAY_SIZE(bq25703_readonly_reg_ranges),
+};
+
+static const struct regmap_range bq25703_volatile_reg_ranges[] = {
+ regmap_reg_range(BQ25703_CHARGE_OPTION_0, BQ25703_IIN_HOST),
+ regmap_reg_range(BQ25703_CHARGER_STATUS, BQ25703_ADC_OPTION),
+};
+
+static const struct regmap_access_table bq25703_volatile_regs = {
+ .yes_ranges = bq25703_volatile_reg_ranges,
+ .n_yes_ranges = ARRAY_SIZE(bq25703_volatile_reg_ranges),
+};
+
+static const struct regmap_config bq25703_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .max_register = BQ25703_ADC_OPTION,
+ .cache_type = REGCACHE_MAPLE,
+ .wr_table = &bq25703_writeable_regs,
+ .volatile_table = &bq25703_volatile_regs,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+};
+
+static const struct mfd_cell cells[] = {
+ MFD_CELL_NAME("bq257xx-regulator"),
+ MFD_CELL_NAME("bq257xx-charger"),
+};
+
+static int bq257xx_probe(struct i2c_client *client)
+{
+ struct bq257xx_device *ddata;
+ int ret;
+
+ ddata = devm_kzalloc(&client->dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ ddata->client = client;
+
+ ddata->regmap = devm_regmap_init_i2c(client, &bq25703_regmap_config);
+ if (IS_ERR(ddata->regmap)) {
+ return dev_err_probe(&client->dev, PTR_ERR(ddata->regmap),
+ "Failed to allocate register map\n");
+ }
+
+ i2c_set_clientdata(client, ddata);
+
+ ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_AUTO,
+ cells, ARRAY_SIZE(cells), NULL, 0, NULL);
+ if (ret)
+ return dev_err_probe(&client->dev, ret,
+ "Failed to register child devices\n");
+
+ return ret;
+}
+
+static const struct i2c_device_id bq257xx_i2c_ids[] = {
+ { "bq25703a" },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, bq257xx_i2c_ids);
+
+static const struct of_device_id bq257xx_of_match[] = {
+ { .compatible = "ti,bq25703a" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, bq257xx_of_match);
+
+static struct i2c_driver bq257xx_driver = {
+ .driver = {
+ .name = "bq257xx",
+ .of_match_table = bq257xx_of_match,
+ },
+ .probe = bq257xx_probe,
+ .id_table = bq257xx_i2c_ids,
+};
+module_i2c_driver(bq257xx_driver);
+
+MODULE_DESCRIPTION("bq257xx buck/boost/charger driver");
+MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/cs42l43.c b/drivers/mfd/cs42l43.c
index 07c8f1b8183e..107cfb983fec 100644
--- a/drivers/mfd/cs42l43.c
+++ b/drivers/mfd/cs42l43.c
@@ -1117,24 +1117,6 @@ EXPORT_SYMBOL_NS_GPL(cs42l43_dev_probe, "MFD_CS42L43");
static int cs42l43_suspend(struct device *dev)
{
struct cs42l43 *cs42l43 = dev_get_drvdata(dev);
- static const struct reg_sequence mask_all[] = {
- { CS42L43_DECIM_MASK, 0xFFFFFFFF, },
- { CS42L43_EQ_MIX_MASK, 0xFFFFFFFF, },
- { CS42L43_ASP_MASK, 0xFFFFFFFF, },
- { CS42L43_PLL_MASK, 0xFFFFFFFF, },
- { CS42L43_SOFT_MASK, 0xFFFFFFFF, },
- { CS42L43_SWIRE_MASK, 0xFFFFFFFF, },
- { CS42L43_MSM_MASK, 0xFFFFFFFF, },
- { CS42L43_ACC_DET_MASK, 0xFFFFFFFF, },
- { CS42L43_I2C_TGT_MASK, 0xFFFFFFFF, },
- { CS42L43_SPI_MSTR_MASK, 0xFFFFFFFF, },
- { CS42L43_SW_TO_SPI_BRIDGE_MASK, 0xFFFFFFFF, },
- { CS42L43_OTP_MASK, 0xFFFFFFFF, },
- { CS42L43_CLASS_D_AMP_MASK, 0xFFFFFFFF, },
- { CS42L43_GPIO_INT_MASK, 0xFFFFFFFF, },
- { CS42L43_ASRC_MASK, 0xFFFFFFFF, },
- { CS42L43_HPOUT_MASK, 0xFFFFFFFF, },
- };
int ret;
ret = pm_runtime_resume_and_get(dev);
@@ -1143,13 +1125,7 @@ static int cs42l43_suspend(struct device *dev)
return ret;
}
- /* The IRQs will be re-enabled on resume by the cache sync */
- ret = regmap_multi_reg_write_bypassed(cs42l43->regmap,
- mask_all, ARRAY_SIZE(mask_all));
- if (ret) {
- dev_err(cs42l43->dev, "Failed to mask IRQs: %d\n", ret);
- return ret;
- }
+ disable_irq(cs42l43->irq);
ret = pm_runtime_force_suspend(dev);
if (ret) {
@@ -1164,8 +1140,6 @@ static int cs42l43_suspend(struct device *dev)
if (ret)
return ret;
- disable_irq(cs42l43->irq);
-
return 0;
}
@@ -1196,14 +1170,14 @@ static int cs42l43_resume(struct device *dev)
if (ret)
return ret;
- enable_irq(cs42l43->irq);
-
ret = pm_runtime_force_resume(dev);
if (ret) {
dev_err(cs42l43->dev, "Failed to force resume: %d\n", ret);
return ret;
}
+ enable_irq(cs42l43->irq);
+
return 0;
}
diff --git a/drivers/mfd/da9063-i2c.c b/drivers/mfd/da9063-i2c.c
index c6235cd0dbdc..1ec9ab56442d 100644
--- a/drivers/mfd/da9063-i2c.c
+++ b/drivers/mfd/da9063-i2c.c
@@ -37,9 +37,13 @@ enum da9063_page_sel_buf_fmt {
DA9063_PAGE_SEL_BUF_SIZE,
};
+enum da9063_page_sel_msgs {
+ DA9063_PAGE_SEL_MSG = 0,
+ DA9063_PAGE_SEL_CNT,
+};
+
enum da9063_paged_read_msgs {
- DA9063_PAGED_READ_MSG_PAGE_SEL = 0,
- DA9063_PAGED_READ_MSG_REG_SEL,
+ DA9063_PAGED_READ_MSG_REG_SEL = 0,
DA9063_PAGED_READ_MSG_DATA,
DA9063_PAGED_READ_MSG_CNT,
};
@@ -65,10 +69,21 @@ static int da9063_i2c_blockreg_read(struct i2c_client *client, u16 addr,
(page_num << DA9063_I2C_PAGE_SEL_SHIFT) & DA9063_REG_PAGE_MASK;
/* Write reg address, page selection */
- xfer[DA9063_PAGED_READ_MSG_PAGE_SEL].addr = client->addr;
- xfer[DA9063_PAGED_READ_MSG_PAGE_SEL].flags = 0;
- xfer[DA9063_PAGED_READ_MSG_PAGE_SEL].len = DA9063_PAGE_SEL_BUF_SIZE;
- xfer[DA9063_PAGED_READ_MSG_PAGE_SEL].buf = page_sel_buf;
+ xfer[DA9063_PAGE_SEL_MSG].addr = client->addr;
+ xfer[DA9063_PAGE_SEL_MSG].flags = 0;
+ xfer[DA9063_PAGE_SEL_MSG].len = DA9063_PAGE_SEL_BUF_SIZE;
+ xfer[DA9063_PAGE_SEL_MSG].buf = page_sel_buf;
+
+ ret = i2c_transfer(client->adapter, xfer, DA9063_PAGE_SEL_CNT);
+ if (ret < 0) {
+ dev_err(&client->dev, "Page switch failed: %d\n", ret);
+ return ret;
+ }
+
+ if (ret != DA9063_PAGE_SEL_CNT) {
+ dev_err(&client->dev, "Page switch failed to complete\n");
+ return -EIO;
+ }
/* Select register address */
xfer[DA9063_PAGED_READ_MSG_REG_SEL].addr = client->addr;
diff --git a/drivers/mfd/exynos-lpass.c b/drivers/mfd/exynos-lpass.c
index 44797001a432..9bb2687c2835 100644
--- a/drivers/mfd/exynos-lpass.c
+++ b/drivers/mfd/exynos-lpass.c
@@ -101,7 +101,6 @@ static const struct regmap_config exynos_lpass_reg_conf = {
.reg_stride = 4,
.val_bits = 32,
.max_register = 0xfc,
- .fast_io = true,
};
static void exynos_lpass_disable_lpass(void *data)
diff --git a/drivers/mfd/fsl-imx25-tsadc.c b/drivers/mfd/fsl-imx25-tsadc.c
index 0aab6428e042..467b1a23faeb 100644
--- a/drivers/mfd/fsl-imx25-tsadc.c
+++ b/drivers/mfd/fsl-imx25-tsadc.c
@@ -17,7 +17,6 @@
#include <linux/regmap.h>
static const struct regmap_config mx25_tsadc_regmap_config = {
- .fast_io = true,
.max_register = 8,
.reg_bits = 32,
.val_bits = 32,
diff --git a/drivers/mfd/intel-lpss-pci.c b/drivers/mfd/intel-lpss-pci.c
index 1a5b8b13f8d0..8d92c895d3ae 100644
--- a/drivers/mfd/intel-lpss-pci.c
+++ b/drivers/mfd/intel-lpss-pci.c
@@ -367,6 +367,19 @@ static const struct pci_device_id intel_lpss_pci_ids[] = {
{ PCI_VDEVICE(INTEL, 0x4b79), (kernel_ulong_t)&ehl_i2c_info },
{ PCI_VDEVICE(INTEL, 0x4b7a), (kernel_ulong_t)&ehl_i2c_info },
{ PCI_VDEVICE(INTEL, 0x4b7b), (kernel_ulong_t)&ehl_i2c_info },
+ /* WCL */
+ { PCI_VDEVICE(INTEL, 0x4d25), (kernel_ulong_t)&bxt_uart_info },
+ { PCI_VDEVICE(INTEL, 0x4d26), (kernel_ulong_t)&bxt_uart_info },
+ { PCI_VDEVICE(INTEL, 0x4d27), (kernel_ulong_t)&tgl_spi_info },
+ { PCI_VDEVICE(INTEL, 0x4d30), (kernel_ulong_t)&tgl_spi_info },
+ { PCI_VDEVICE(INTEL, 0x4d46), (kernel_ulong_t)&tgl_spi_info },
+ { PCI_VDEVICE(INTEL, 0x4d50), (kernel_ulong_t)&ehl_i2c_info },
+ { PCI_VDEVICE(INTEL, 0x4d51), (kernel_ulong_t)&ehl_i2c_info },
+ { PCI_VDEVICE(INTEL, 0x4d52), (kernel_ulong_t)&bxt_uart_info },
+ { PCI_VDEVICE(INTEL, 0x4d78), (kernel_ulong_t)&ehl_i2c_info },
+ { PCI_VDEVICE(INTEL, 0x4d79), (kernel_ulong_t)&ehl_i2c_info },
+ { PCI_VDEVICE(INTEL, 0x4d7a), (kernel_ulong_t)&ehl_i2c_info },
+ { PCI_VDEVICE(INTEL, 0x4d7b), (kernel_ulong_t)&ehl_i2c_info },
/* JSL */
{ PCI_VDEVICE(INTEL, 0x4da8), (kernel_ulong_t)&spt_uart_info },
{ PCI_VDEVICE(INTEL, 0x4da9), (kernel_ulong_t)&spt_uart_info },
diff --git a/drivers/mfd/intel_soc_pmic_chtdc_ti.c b/drivers/mfd/intel_soc_pmic_chtdc_ti.c
index 4c1a68c9f575..6daf33e07ea0 100644
--- a/drivers/mfd/intel_soc_pmic_chtdc_ti.c
+++ b/drivers/mfd/intel_soc_pmic_chtdc_ti.c
@@ -82,6 +82,8 @@ static const struct regmap_config chtdc_ti_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0xff,
+ /* The hardware does not support reading multiple registers at once */
+ .use_single_read = true,
};
static const struct regmap_irq chtdc_ti_irqs[] = {
diff --git a/drivers/mfd/kempld-core.c b/drivers/mfd/kempld-core.c
index c5bfb6440a93..c2008d2dc95a 100644
--- a/drivers/mfd/kempld-core.c
+++ b/drivers/mfd/kempld-core.c
@@ -141,10 +141,8 @@ static int kempld_create_platform_device(const struct kempld_platform_data *pdat
};
kempld_pdev = platform_device_register_full(&pdevinfo);
- if (IS_ERR(kempld_pdev))
- return PTR_ERR(kempld_pdev);
- return 0;
+ return PTR_ERR_OR_ZERO(kempld_pdev);
}
/**
@@ -779,22 +777,26 @@ MODULE_DEVICE_TABLE(dmi, kempld_dmi_table);
static int __init kempld_init(void)
{
const struct dmi_system_id *id;
- int ret = -ENODEV;
-
- for (id = dmi_first_match(kempld_dmi_table); id; id = dmi_first_match(id + 1)) {
- /* Check, if user asked for the exact device ID match */
- if (force_device_id[0] && !strstr(id->ident, force_device_id))
- continue;
- ret = kempld_create_platform_device(&kempld_platform_data_generic);
- if (ret)
- continue;
-
- break;
+ /*
+ * This custom DMI iteration allows the driver to be initialized in three ways:
+ * - When a forced_device_id string matches any ident in the kempld_dmi_table,
+ * regardless of whether the DMI device is present in the system dmi table.
+ * - When a matching entry is present in the DMI system tabe.
+ * - Through alternative mechanisms like ACPI.
+ */
+ if (force_device_id[0]) {
+ for (id = kempld_dmi_table; id->matches[0].slot != DMI_NONE; id++)
+ if (strstr(id->ident, force_device_id))
+ if (!kempld_create_platform_device(&kempld_platform_data_generic))
+ break;
+ if (id->matches[0].slot == DMI_NONE)
+ return -ENODEV;
+ } else {
+ for (id = dmi_first_match(kempld_dmi_table); id; id = dmi_first_match(id+1))
+ if (kempld_create_platform_device(&kempld_platform_data_generic))
+ break;
}
- if (ret)
- return ret;
-
return platform_driver_register(&kempld_driver);
}
diff --git a/drivers/mfd/loongson-se.c b/drivers/mfd/loongson-se.c
new file mode 100644
index 000000000000..3902ba377d69
--- /dev/null
+++ b/drivers/mfd/loongson-se.c
@@ -0,0 +1,253 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2025 Loongson Technology Corporation Limited
+ *
+ * Author: Yinggang Gu <guyinggang@loongson.cn>
+ * Author: Qunqin Zhao <zhaoqunqin@loongson.cn>
+ */
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/loongson-se.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+struct loongson_se {
+ void __iomem *base;
+ spinlock_t dev_lock;
+ struct completion cmd_completion;
+
+ void *dmam_base;
+ int dmam_size;
+
+ struct mutex engine_init_lock;
+ struct loongson_se_engine engines[SE_ENGINE_MAX];
+};
+
+struct loongson_se_controller_cmd {
+ u32 command_id;
+ u32 info[7];
+};
+
+static int loongson_se_poll(struct loongson_se *se, u32 int_bit)
+{
+ u32 status;
+ int err;
+
+ spin_lock_irq(&se->dev_lock);
+
+ /* Notify the controller that the engine needs to be started */
+ writel(int_bit, se->base + SE_L2SINT_SET);
+
+ /* Polling until the controller has forwarded the engine command */
+ err = readl_relaxed_poll_timeout_atomic(se->base + SE_L2SINT_STAT, status,
+ !(status & int_bit),
+ 1, LOONGSON_ENGINE_CMD_TIMEOUT_US);
+
+ spin_unlock_irq(&se->dev_lock);
+
+ return err;
+}
+
+static int loongson_se_send_controller_cmd(struct loongson_se *se,
+ struct loongson_se_controller_cmd *cmd)
+{
+ u32 *send_cmd = (u32 *)cmd;
+ int err, i;
+
+ for (i = 0; i < SE_SEND_CMD_REG_LEN; i++)
+ writel(send_cmd[i], se->base + SE_SEND_CMD_REG + i * 4);
+
+ err = loongson_se_poll(se, SE_INT_CONTROLLER);
+ if (err)
+ return err;
+
+ return wait_for_completion_interruptible(&se->cmd_completion);
+}
+
+int loongson_se_send_engine_cmd(struct loongson_se_engine *engine)
+{
+ /*
+ * After engine initialization, the controller already knows
+ * where to obtain engine commands from. Now all we need to
+ * do is notify the controller that the engine needs to be started.
+ */
+ int err = loongson_se_poll(engine->se, BIT(engine->id));
+
+ if (err)
+ return err;
+
+ return wait_for_completion_interruptible(&engine->completion);
+}
+EXPORT_SYMBOL_GPL(loongson_se_send_engine_cmd);
+
+struct loongson_se_engine *loongson_se_init_engine(struct device *dev, int id)
+{
+ struct loongson_se *se = dev_get_drvdata(dev);
+ struct loongson_se_engine *engine = &se->engines[id];
+ struct loongson_se_controller_cmd cmd;
+
+ engine->se = se;
+ engine->id = id;
+ init_completion(&engine->completion);
+
+ /* Divide DMA memory equally among all engines */
+ engine->buffer_size = se->dmam_size / SE_ENGINE_MAX;
+ engine->buffer_off = (se->dmam_size / SE_ENGINE_MAX) * id;
+ engine->data_buffer = se->dmam_base + engine->buffer_off;
+
+ /*
+ * There has no engine0, use its data buffer as command buffer for other
+ * engines. The DMA memory size is obtained from the ACPI table, which
+ * ensures that the data buffer size of engine0 is larger than the
+ * command buffer size of all engines.
+ */
+ engine->command = se->dmam_base + id * (2 * SE_ENGINE_CMD_SIZE);
+ engine->command_ret = engine->command + SE_ENGINE_CMD_SIZE;
+
+ mutex_lock(&se->engine_init_lock);
+
+ /* Tell the controller where to find engine command */
+ cmd.command_id = SE_CMD_SET_ENGINE_CMDBUF;
+ cmd.info[0] = id;
+ cmd.info[1] = engine->command - se->dmam_base;
+ cmd.info[2] = 2 * SE_ENGINE_CMD_SIZE;
+
+ if (loongson_se_send_controller_cmd(se, &cmd))
+ engine = NULL;
+
+ mutex_unlock(&se->engine_init_lock);
+
+ return engine;
+}
+EXPORT_SYMBOL_GPL(loongson_se_init_engine);
+
+static irqreturn_t se_irq_handler(int irq, void *dev_id)
+{
+ struct loongson_se *se = dev_id;
+ u32 int_status;
+ int id;
+
+ spin_lock(&se->dev_lock);
+
+ int_status = readl(se->base + SE_S2LINT_STAT);
+
+ /* For controller */
+ if (int_status & SE_INT_CONTROLLER) {
+ complete(&se->cmd_completion);
+ int_status &= ~SE_INT_CONTROLLER;
+ writel(SE_INT_CONTROLLER, se->base + SE_S2LINT_CL);
+ }
+
+ /* For engines */
+ while (int_status) {
+ id = __ffs(int_status);
+ complete(&se->engines[id].completion);
+ int_status &= ~BIT(id);
+ writel(BIT(id), se->base + SE_S2LINT_CL);
+ }
+
+ spin_unlock(&se->dev_lock);
+
+ return IRQ_HANDLED;
+}
+
+static int loongson_se_init(struct loongson_se *se, dma_addr_t addr, int size)
+{
+ struct loongson_se_controller_cmd cmd;
+ int err;
+
+ cmd.command_id = SE_CMD_START;
+ err = loongson_se_send_controller_cmd(se, &cmd);
+ if (err)
+ return err;
+
+ cmd.command_id = SE_CMD_SET_DMA;
+ cmd.info[0] = lower_32_bits(addr);
+ cmd.info[1] = upper_32_bits(addr);
+ cmd.info[2] = size;
+
+ return loongson_se_send_controller_cmd(se, &cmd);
+}
+
+static const struct mfd_cell engines[] = {
+ { .name = "loongson-rng" },
+ { .name = "tpm_loongson" },
+};
+
+static int loongson_se_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct loongson_se *se;
+ int nr_irq, irq, err, i;
+ dma_addr_t paddr;
+
+ se = devm_kmalloc(dev, sizeof(*se), GFP_KERNEL);
+ if (!se)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, se);
+ init_completion(&se->cmd_completion);
+ spin_lock_init(&se->dev_lock);
+ mutex_init(&se->engine_init_lock);
+
+ dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+ if (device_property_read_u32(dev, "dmam_size", &se->dmam_size))
+ return -ENODEV;
+
+ se->dmam_base = dmam_alloc_coherent(dev, se->dmam_size, &paddr, GFP_KERNEL);
+ if (!se->dmam_base)
+ return -ENOMEM;
+
+ se->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(se->base))
+ return PTR_ERR(se->base);
+
+ writel(SE_INT_ALL, se->base + SE_S2LINT_EN);
+
+ nr_irq = platform_irq_count(pdev);
+ if (nr_irq <= 0)
+ return -ENODEV;
+
+ for (i = 0; i < nr_irq; i++) {
+ irq = platform_get_irq(pdev, i);
+ err = devm_request_irq(dev, irq, se_irq_handler, 0, "loongson-se", se);
+ if (err)
+ dev_err(dev, "failed to request IRQ: %d\n", irq);
+ }
+
+ err = loongson_se_init(se, paddr, se->dmam_size);
+ if (err)
+ return err;
+
+ return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, engines,
+ ARRAY_SIZE(engines), NULL, 0, NULL);
+}
+
+static const struct acpi_device_id loongson_se_acpi_match[] = {
+ { "LOON0011", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, loongson_se_acpi_match);
+
+static struct platform_driver loongson_se_driver = {
+ .probe = loongson_se_probe,
+ .driver = {
+ .name = "loongson-se",
+ .acpi_match_table = loongson_se_acpi_match,
+ },
+};
+module_platform_driver(loongson_se_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Yinggang Gu <guyinggang@loongson.cn>");
+MODULE_AUTHOR("Qunqin Zhao <zhaoqunqin@loongson.cn>");
+MODULE_DESCRIPTION("Loongson Security Engine chip controller driver");
diff --git a/drivers/mfd/ls2k-bmc-core.c b/drivers/mfd/ls2k-bmc-core.c
new file mode 100644
index 000000000000..e162b3c7c9f8
--- /dev/null
+++ b/drivers/mfd/ls2k-bmc-core.c
@@ -0,0 +1,528 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Loongson-2K Board Management Controller (BMC) Core Driver.
+ *
+ * Copyright (C) 2024-2025 Loongson Technology Corporation Limited.
+ *
+ * Authors:
+ * Chong Qiao <qiaochong@loongson.cn>
+ * Binbin Zhou <zhoubinbin@loongson.cn>
+ */
+
+#include <linux/aperture.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/iopoll.h>
+#include <linux/kbd_kern.h>
+#include <linux/kernel.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/pci_ids.h>
+#include <linux/platform_data/simplefb.h>
+#include <linux/platform_device.h>
+#include <linux/stop_machine.h>
+#include <linux/vt_kern.h>
+
+/* LS2K BMC resources */
+#define LS2K_DISPLAY_RES_START (SZ_16M + SZ_2M)
+#define LS2K_IPMI_RES_SIZE 0x1C
+#define LS2K_IPMI0_RES_START (SZ_16M + 0xF00000)
+#define LS2K_IPMI1_RES_START (LS2K_IPMI0_RES_START + LS2K_IPMI_RES_SIZE)
+#define LS2K_IPMI2_RES_START (LS2K_IPMI1_RES_START + LS2K_IPMI_RES_SIZE)
+#define LS2K_IPMI3_RES_START (LS2K_IPMI2_RES_START + LS2K_IPMI_RES_SIZE)
+#define LS2K_IPMI4_RES_START (LS2K_IPMI3_RES_START + LS2K_IPMI_RES_SIZE)
+
+#define LS7A_PCI_CFG_SIZE 0x100
+
+/* LS7A bridge registers */
+#define LS7A_PCIE_PORT_CTL0 0x0
+#define LS7A_PCIE_PORT_STS1 0xC
+#define LS7A_GEN2_CTL 0x80C
+#define LS7A_SYMBOL_TIMER 0x71C
+
+/* Bits of LS7A_PCIE_PORT_CTL0 */
+#define LS2K_BMC_PCIE_LTSSM_ENABLE BIT(3)
+
+/* Bits of LS7A_PCIE_PORT_STS1 */
+#define LS2K_BMC_PCIE_LTSSM_STS GENMASK(5, 0)
+#define LS2K_BMC_PCIE_CONNECTED 0x11
+
+#define LS2K_BMC_PCIE_DELAY_US 1000
+#define LS2K_BMC_PCIE_TIMEOUT_US 1000000
+
+/* Bits of LS7A_GEN2_CTL */
+#define LS7A_GEN2_SPEED_CHANG BIT(17)
+#define LS7A_CONF_PHY_TX BIT(18)
+
+/* Bits of LS7A_SYMBOL_TIMER */
+#define LS7A_MASK_LEN_MATCH BIT(26)
+
+/* Interval between interruptions */
+#define LS2K_BMC_INT_INTERVAL (60 * HZ)
+
+/* Maximum time to wait for U-Boot and DDR to be ready with ms. */
+#define LS2K_BMC_RESET_WAIT_TIME 10000
+
+/* It's an experience value */
+#define LS7A_BAR0_CHECK_MAX_TIMES 2000
+
+#define PCI_REG_STRIDE 0x4
+
+#define LS2K_BMC_RESET_GPIO 14
+#define LOONGSON_GPIO_REG_BASE 0x1FE00500
+#define LOONGSON_GPIO_REG_SIZE 0x18
+#define LOONGSON_GPIO_OEN 0x0
+#define LOONGSON_GPIO_FUNC 0x4
+#define LOONGSON_GPIO_INTPOL 0x10
+#define LOONGSON_GPIO_INTEN 0x14
+
+#define LOONGSON_IO_INT_BASE 16
+#define LS2K_BMC_RESET_GPIO_INT_VEC (LS2K_BMC_RESET_GPIO % 8)
+#define LS2K_BMC_RESET_GPIO_GSI (LOONGSON_IO_INT_BASE + LS2K_BMC_RESET_GPIO_INT_VEC)
+
+enum {
+ LS2K_BMC_DISPLAY,
+ LS2K_BMC_IPMI0,
+ LS2K_BMC_IPMI1,
+ LS2K_BMC_IPMI2,
+ LS2K_BMC_IPMI3,
+ LS2K_BMC_IPMI4,
+};
+
+static struct resource ls2k_display_resources[] = {
+ DEFINE_RES_MEM_NAMED(LS2K_DISPLAY_RES_START, SZ_4M, "simpledrm-res"),
+};
+
+static struct resource ls2k_ipmi0_resources[] = {
+ DEFINE_RES_MEM_NAMED(LS2K_IPMI0_RES_START, LS2K_IPMI_RES_SIZE, "ipmi0-res"),
+};
+
+static struct resource ls2k_ipmi1_resources[] = {
+ DEFINE_RES_MEM_NAMED(LS2K_IPMI1_RES_START, LS2K_IPMI_RES_SIZE, "ipmi1-res"),
+};
+
+static struct resource ls2k_ipmi2_resources[] = {
+ DEFINE_RES_MEM_NAMED(LS2K_IPMI2_RES_START, LS2K_IPMI_RES_SIZE, "ipmi2-res"),
+};
+
+static struct resource ls2k_ipmi3_resources[] = {
+ DEFINE_RES_MEM_NAMED(LS2K_IPMI3_RES_START, LS2K_IPMI_RES_SIZE, "ipmi3-res"),
+};
+
+static struct resource ls2k_ipmi4_resources[] = {
+ DEFINE_RES_MEM_NAMED(LS2K_IPMI4_RES_START, LS2K_IPMI_RES_SIZE, "ipmi4-res"),
+};
+
+static struct mfd_cell ls2k_bmc_cells[] = {
+ [LS2K_BMC_DISPLAY] = {
+ .name = "simple-framebuffer",
+ .num_resources = ARRAY_SIZE(ls2k_display_resources),
+ .resources = ls2k_display_resources
+ },
+ [LS2K_BMC_IPMI0] = {
+ .name = "ls2k-ipmi-si",
+ .num_resources = ARRAY_SIZE(ls2k_ipmi0_resources),
+ .resources = ls2k_ipmi0_resources
+ },
+ [LS2K_BMC_IPMI1] = {
+ .name = "ls2k-ipmi-si",
+ .num_resources = ARRAY_SIZE(ls2k_ipmi1_resources),
+ .resources = ls2k_ipmi1_resources
+ },
+ [LS2K_BMC_IPMI2] = {
+ .name = "ls2k-ipmi-si",
+ .num_resources = ARRAY_SIZE(ls2k_ipmi2_resources),
+ .resources = ls2k_ipmi2_resources
+ },
+ [LS2K_BMC_IPMI3] = {
+ .name = "ls2k-ipmi-si",
+ .num_resources = ARRAY_SIZE(ls2k_ipmi3_resources),
+ .resources = ls2k_ipmi3_resources
+ },
+ [LS2K_BMC_IPMI4] = {
+ .name = "ls2k-ipmi-si",
+ .num_resources = ARRAY_SIZE(ls2k_ipmi4_resources),
+ .resources = ls2k_ipmi4_resources
+ },
+};
+
+/* Index of the BMC PCI configuration space to be restored at BMC reset. */
+struct ls2k_bmc_pci_data {
+ u32 pci_command;
+ u32 base_address0;
+ u32 interrupt_line;
+};
+
+/* Index of the parent PCI configuration space to be restored at BMC reset. */
+struct ls2k_bmc_bridge_pci_data {
+ u32 pci_command;
+ u32 base_address[6];
+ u32 rom_addreess;
+ u32 interrupt_line;
+ u32 msi_hi;
+ u32 msi_lo;
+ u32 devctl;
+ u32 linkcap;
+ u32 linkctl_sts;
+ u32 symbol_timer;
+ u32 gen2_ctrl;
+};
+
+struct ls2k_bmc_ddata {
+ struct device *dev;
+ struct work_struct bmc_reset_work;
+ struct ls2k_bmc_pci_data bmc_pci_data;
+ struct ls2k_bmc_bridge_pci_data bridge_pci_data;
+};
+
+static bool ls2k_bmc_bar0_addr_is_set(struct pci_dev *pdev)
+{
+ u32 addr;
+
+ pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &addr);
+
+ return addr & PCI_BASE_ADDRESS_MEM_MASK ? true : false;
+}
+
+static bool ls2k_bmc_pcie_is_connected(struct pci_dev *parent, struct ls2k_bmc_ddata *ddata)
+{
+ void __iomem *base;
+ int val, ret;
+
+ base = pci_iomap(parent, 0, LS7A_PCI_CFG_SIZE);
+ if (!base)
+ return false;
+
+ val = readl(base + LS7A_PCIE_PORT_CTL0);
+ writel(val | LS2K_BMC_PCIE_LTSSM_ENABLE, base + LS7A_PCIE_PORT_CTL0);
+
+ ret = readl_poll_timeout_atomic(base + LS7A_PCIE_PORT_STS1, val,
+ (val & LS2K_BMC_PCIE_LTSSM_STS) == LS2K_BMC_PCIE_CONNECTED,
+ LS2K_BMC_PCIE_DELAY_US, LS2K_BMC_PCIE_TIMEOUT_US);
+ if (ret) {
+ pci_iounmap(parent, base);
+ dev_err(ddata->dev, "PCI-E training failed status=0x%x\n", val);
+ return false;
+ }
+
+ pci_iounmap(parent, base);
+ return true;
+}
+
+static void ls2k_bmc_restore_bridge_pci_data(struct pci_dev *parent, struct ls2k_bmc_ddata *ddata)
+{
+ int base, i = 0;
+
+ pci_write_config_dword(parent, PCI_COMMAND, ddata->bridge_pci_data.pci_command);
+
+ for (base = PCI_BASE_ADDRESS_0; base <= PCI_BASE_ADDRESS_5; base += PCI_REG_STRIDE, i++)
+ pci_write_config_dword(parent, base, ddata->bridge_pci_data.base_address[i]);
+
+ pci_write_config_dword(parent, PCI_ROM_ADDRESS, ddata->bridge_pci_data.rom_addreess);
+ pci_write_config_dword(parent, PCI_INTERRUPT_LINE, ddata->bridge_pci_data.interrupt_line);
+
+ pci_write_config_dword(parent, parent->msi_cap + PCI_MSI_ADDRESS_LO,
+ ddata->bridge_pci_data.msi_lo);
+ pci_write_config_dword(parent, parent->msi_cap + PCI_MSI_ADDRESS_HI,
+ ddata->bridge_pci_data.msi_hi);
+ pci_write_config_dword(parent, parent->pcie_cap + PCI_EXP_DEVCTL,
+ ddata->bridge_pci_data.devctl);
+ pci_write_config_dword(parent, parent->pcie_cap + PCI_EXP_LNKCAP,
+ ddata->bridge_pci_data.linkcap);
+ pci_write_config_dword(parent, parent->pcie_cap + PCI_EXP_LNKCTL,
+ ddata->bridge_pci_data.linkctl_sts);
+
+ pci_write_config_dword(parent, LS7A_GEN2_CTL, ddata->bridge_pci_data.gen2_ctrl);
+ pci_write_config_dword(parent, LS7A_SYMBOL_TIMER, ddata->bridge_pci_data.symbol_timer);
+}
+
+static int ls2k_bmc_recover_pci_data(void *data)
+{
+ struct ls2k_bmc_ddata *ddata = data;
+ struct pci_dev *pdev = to_pci_dev(ddata->dev);
+ struct pci_dev *parent = pdev->bus->self;
+ u32 i;
+
+ /*
+ * Clear the bus, io and mem resources of the PCI-E bridge to zero, so that
+ * the processor can not access the LS2K PCI-E port, to avoid crashing due to
+ * the lack of return signal from accessing the LS2K PCI-E port.
+ */
+ pci_write_config_dword(parent, PCI_BASE_ADDRESS_2, 0);
+ pci_write_config_dword(parent, PCI_BASE_ADDRESS_3, 0);
+ pci_write_config_dword(parent, PCI_BASE_ADDRESS_4, 0);
+
+ /*
+ * When the LS2K BMC is reset, the LS7A PCI-E port is also reset, and its PCI
+ * BAR0 register is cleared. Due to the time gap between the GPIO interrupt
+ * generation and the LS2K BMC reset, the LS7A PCI BAR0 register is read to
+ * determine whether the reset has begun.
+ */
+ for (i = LS7A_BAR0_CHECK_MAX_TIMES; i > 0 ; i--) {
+ if (!ls2k_bmc_bar0_addr_is_set(parent))
+ break;
+ mdelay(1);
+ };
+
+ if (i == 0)
+ return false;
+
+ ls2k_bmc_restore_bridge_pci_data(parent, ddata);
+
+ /* Check if PCI-E is connected */
+ if (!ls2k_bmc_pcie_is_connected(parent, ddata))
+ return false;
+
+ /* Waiting for U-Boot and DDR ready */
+ mdelay(LS2K_BMC_RESET_WAIT_TIME);
+ if (!ls2k_bmc_bar0_addr_is_set(parent))
+ return false;
+
+ /* Restore LS2K BMC PCI-E config data */
+ pci_write_config_dword(pdev, PCI_COMMAND, ddata->bmc_pci_data.pci_command);
+ pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, ddata->bmc_pci_data.base_address0);
+ pci_write_config_dword(pdev, PCI_INTERRUPT_LINE, ddata->bmc_pci_data.interrupt_line);
+
+ return 0;
+}
+
+static void ls2k_bmc_events_fn(struct work_struct *work)
+{
+ struct ls2k_bmc_ddata *ddata = container_of(work, struct ls2k_bmc_ddata, bmc_reset_work);
+
+ /*
+ * The PCI-E is lost when the BMC resets, at which point access to the PCI-E
+ * from other CPUs is suspended to prevent a crash.
+ */
+ stop_machine(ls2k_bmc_recover_pci_data, ddata, NULL);
+
+ if (IS_ENABLED(CONFIG_VT)) {
+ /* Re-push the display due to previous PCI-E loss. */
+ set_console(vt_move_to_console(MAX_NR_CONSOLES - 1, 1));
+ }
+}
+
+static irqreturn_t ls2k_bmc_interrupt(int irq, void *arg)
+{
+ struct ls2k_bmc_ddata *ddata = arg;
+ static unsigned long last_jiffies;
+
+ if (system_state != SYSTEM_RUNNING)
+ return IRQ_HANDLED;
+
+ /* Skip interrupt in LS2K_BMC_INT_INTERVAL */
+ if (time_after(jiffies, last_jiffies + LS2K_BMC_INT_INTERVAL)) {
+ schedule_work(&ddata->bmc_reset_work);
+ last_jiffies = jiffies;
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Saves the BMC parent device (LS7A) and its own PCI configuration space registers
+ * that need to be restored after BMC reset.
+ */
+static void ls2k_bmc_save_pci_data(struct pci_dev *pdev, struct ls2k_bmc_ddata *ddata)
+{
+ struct pci_dev *parent = pdev->bus->self;
+ int base, i = 0;
+
+ pci_read_config_dword(parent, PCI_COMMAND, &ddata->bridge_pci_data.pci_command);
+
+ for (base = PCI_BASE_ADDRESS_0; base <= PCI_BASE_ADDRESS_5; base += PCI_REG_STRIDE, i++)
+ pci_read_config_dword(parent, base, &ddata->bridge_pci_data.base_address[i]);
+
+ pci_read_config_dword(parent, PCI_ROM_ADDRESS, &ddata->bridge_pci_data.rom_addreess);
+ pci_read_config_dword(parent, PCI_INTERRUPT_LINE, &ddata->bridge_pci_data.interrupt_line);
+
+ pci_read_config_dword(parent, parent->msi_cap + PCI_MSI_ADDRESS_LO,
+ &ddata->bridge_pci_data.msi_lo);
+ pci_read_config_dword(parent, parent->msi_cap + PCI_MSI_ADDRESS_HI,
+ &ddata->bridge_pci_data.msi_hi);
+
+ pci_read_config_dword(parent, parent->pcie_cap + PCI_EXP_DEVCTL,
+ &ddata->bridge_pci_data.devctl);
+ pci_read_config_dword(parent, parent->pcie_cap + PCI_EXP_LNKCAP,
+ &ddata->bridge_pci_data.linkcap);
+ pci_read_config_dword(parent, parent->pcie_cap + PCI_EXP_LNKCTL,
+ &ddata->bridge_pci_data.linkctl_sts);
+
+ pci_read_config_dword(parent, LS7A_GEN2_CTL, &ddata->bridge_pci_data.gen2_ctrl);
+ ddata->bridge_pci_data.gen2_ctrl |= FIELD_PREP(LS7A_GEN2_SPEED_CHANG, 0x1) |
+ FIELD_PREP(LS7A_CONF_PHY_TX, 0x0);
+
+ pci_read_config_dword(parent, LS7A_SYMBOL_TIMER, &ddata->bridge_pci_data.symbol_timer);
+ ddata->bridge_pci_data.symbol_timer |= LS7A_MASK_LEN_MATCH;
+
+ pci_read_config_dword(pdev, PCI_COMMAND, &ddata->bmc_pci_data.pci_command);
+ pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &ddata->bmc_pci_data.base_address0);
+ pci_read_config_dword(pdev, PCI_INTERRUPT_LINE, &ddata->bmc_pci_data.interrupt_line);
+}
+
+static int ls2k_bmc_init(struct ls2k_bmc_ddata *ddata)
+{
+ struct pci_dev *pdev = to_pci_dev(ddata->dev);
+ void __iomem *gpio_base;
+ int gpio_irq, ret, val;
+
+ ls2k_bmc_save_pci_data(pdev, ddata);
+
+ INIT_WORK(&ddata->bmc_reset_work, ls2k_bmc_events_fn);
+
+ ret = devm_request_irq(&pdev->dev, pdev->irq, ls2k_bmc_interrupt,
+ IRQF_SHARED | IRQF_TRIGGER_FALLING, "ls2kbmc pcie", ddata);
+ if (ret) {
+ dev_err(ddata->dev, "Failed to request LS2KBMC PCI-E IRQ %d.\n", pdev->irq);
+ return ret;
+ }
+
+ gpio_base = ioremap(LOONGSON_GPIO_REG_BASE, LOONGSON_GPIO_REG_SIZE);
+ if (!gpio_base)
+ return -ENOMEM;
+
+ /* Disable GPIO output */
+ val = readl(gpio_base + LOONGSON_GPIO_OEN);
+ writel(val | BIT(LS2K_BMC_RESET_GPIO), gpio_base + LOONGSON_GPIO_OEN);
+
+ /* Enable GPIO functionality */
+ val = readl(gpio_base + LOONGSON_GPIO_FUNC);
+ writel(val & ~BIT(LS2K_BMC_RESET_GPIO), gpio_base + LOONGSON_GPIO_FUNC);
+
+ /* Set GPIO interrupts to low-level active */
+ val = readl(gpio_base + LOONGSON_GPIO_INTPOL);
+ writel(val & ~BIT(LS2K_BMC_RESET_GPIO), gpio_base + LOONGSON_GPIO_INTPOL);
+
+ /* Enable GPIO interrupts */
+ val = readl(gpio_base + LOONGSON_GPIO_INTEN);
+ writel(val | BIT(LS2K_BMC_RESET_GPIO), gpio_base + LOONGSON_GPIO_INTEN);
+
+ iounmap(gpio_base);
+
+ /*
+ * Since gpio_chip->to_irq is not implemented in the Loongson-3 GPIO driver,
+ * acpi_register_gsi() is used to obtain the GPIO IRQ. The GPIO interrupt is a
+ * watchdog interrupt that is triggered when the BMC resets.
+ */
+ gpio_irq = acpi_register_gsi(NULL, LS2K_BMC_RESET_GPIO_GSI, ACPI_EDGE_SENSITIVE,
+ ACPI_ACTIVE_LOW);
+ if (gpio_irq < 0)
+ return gpio_irq;
+
+ ret = devm_request_irq(ddata->dev, gpio_irq, ls2k_bmc_interrupt,
+ IRQF_SHARED | IRQF_TRIGGER_FALLING, "ls2kbmc gpio", ddata);
+ if (ret)
+ dev_err(ddata->dev, "Failed to request LS2KBMC GPIO IRQ %d.\n", gpio_irq);
+
+ acpi_unregister_gsi(LS2K_BMC_RESET_GPIO_GSI);
+ return ret;
+}
+
+/*
+ * Currently the Loongson-2K BMC hardware does not have an I2C interface to adapt to the
+ * resolution. We set the resolution by presetting "video=1280x1024-16@2M" to the BMC memory.
+ */
+static int ls2k_bmc_parse_mode(struct pci_dev *pdev, struct simplefb_platform_data *pd)
+{
+ char *mode;
+ int depth, ret;
+
+ /* The last 16M of PCI BAR0 is used to store the resolution string. */
+ mode = devm_ioremap(&pdev->dev, pci_resource_start(pdev, 0) + SZ_16M, SZ_16M);
+ if (!mode)
+ return -ENOMEM;
+
+ /* The resolution field starts with the flag "video=". */
+ if (!strncmp(mode, "video=", 6))
+ mode = mode + 6;
+
+ ret = kstrtoint(strsep(&mode, "x"), 10, &pd->width);
+ if (ret)
+ return ret;
+
+ ret = kstrtoint(strsep(&mode, "-"), 10, &pd->height);
+ if (ret)
+ return ret;
+
+ ret = kstrtoint(strsep(&mode, "@"), 10, &depth);
+ if (ret)
+ return ret;
+
+ pd->stride = pd->width * depth / 8;
+ pd->format = depth == 32 ? "a8r8g8b8" : "r5g6b5";
+
+ return 0;
+}
+
+static int ls2k_bmc_probe(struct pci_dev *dev, const struct pci_device_id *id)
+{
+ struct simplefb_platform_data pd;
+ struct ls2k_bmc_ddata *ddata;
+ resource_size_t base;
+ int ret;
+
+ ret = pci_enable_device(dev);
+ if (ret)
+ return ret;
+
+ ddata = devm_kzalloc(&dev->dev, sizeof(*ddata), GFP_KERNEL);
+ if (IS_ERR(ddata)) {
+ ret = -ENOMEM;
+ goto disable_pci;
+ }
+
+ ddata->dev = &dev->dev;
+
+ ret = ls2k_bmc_init(ddata);
+ if (ret)
+ goto disable_pci;
+
+ ret = ls2k_bmc_parse_mode(dev, &pd);
+ if (ret)
+ goto disable_pci;
+
+ ls2k_bmc_cells[LS2K_BMC_DISPLAY].platform_data = &pd;
+ ls2k_bmc_cells[LS2K_BMC_DISPLAY].pdata_size = sizeof(pd);
+ base = dev->resource[0].start + LS2K_DISPLAY_RES_START;
+
+ /* Remove conflicting efifb device */
+ ret = aperture_remove_conflicting_devices(base, SZ_4M, "simple-framebuffer");
+ if (ret) {
+ dev_err(&dev->dev, "Failed to removed firmware framebuffers: %d\n", ret);
+ goto disable_pci;
+ }
+
+ return devm_mfd_add_devices(&dev->dev, PLATFORM_DEVID_AUTO,
+ ls2k_bmc_cells, ARRAY_SIZE(ls2k_bmc_cells),
+ &dev->resource[0], 0, NULL);
+
+disable_pci:
+ pci_disable_device(dev);
+ return ret;
+}
+
+static void ls2k_bmc_remove(struct pci_dev *dev)
+{
+ pci_disable_device(dev);
+}
+
+static struct pci_device_id ls2k_bmc_devices[] = {
+ { PCI_DEVICE(PCI_VENDOR_ID_LOONGSON, 0x1a05) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, ls2k_bmc_devices);
+
+static struct pci_driver ls2k_bmc_driver = {
+ .name = "ls2k-bmc",
+ .id_table = ls2k_bmc_devices,
+ .probe = ls2k_bmc_probe,
+ .remove = ls2k_bmc_remove,
+};
+module_pci_driver(ls2k_bmc_driver);
+
+MODULE_DESCRIPTION("Loongson-2K Board Management Controller (BMC) Core driver");
+MODULE_AUTHOR("Loongson Technology Corporation Limited");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/macsmc.c b/drivers/mfd/macsmc.c
index 870c8b2028a8..e6cdae221f1d 100644
--- a/drivers/mfd/macsmc.c
+++ b/drivers/mfd/macsmc.c
@@ -429,7 +429,7 @@ static int apple_smc_probe(struct platform_device *pdev)
ret = devm_add_action_or_reset(dev, apple_smc_rtkit_shutdown, smc);
if (ret)
- return dev_err_probe(dev, ret, "Failed to register rtkit shutdown action");
+ return ret;
ret = apple_rtkit_start_ep(smc->rtk, SMC_ENDPOINT);
if (ret)
@@ -465,7 +465,7 @@ static int apple_smc_probe(struct platform_device *pdev)
apple_smc_write_flag(smc, SMC_KEY(NTAP), true);
ret = devm_add_action_or_reset(dev, apple_smc_disable_notifications, smc);
if (ret)
- return dev_err_probe(dev, ret, "Failed to register notification disable action");
+ return ret;
ret = devm_mfd_add_devices(smc->dev, PLATFORM_DEVID_NONE,
apple_smc_devs, ARRAY_SIZE(apple_smc_devs),
@@ -478,6 +478,7 @@ static int apple_smc_probe(struct platform_device *pdev)
}
static const struct of_device_id apple_smc_of_match[] = {
+ { .compatible = "apple,t8103-smc" },
{ .compatible = "apple,smc" },
{},
};
diff --git a/drivers/mfd/madera-core.c b/drivers/mfd/madera-core.c
index bdbd5bfc9714..2f74a8c644a3 100644
--- a/drivers/mfd/madera-core.c
+++ b/drivers/mfd/madera-core.c
@@ -456,7 +456,7 @@ int madera_dev_init(struct madera *madera)
struct device *dev = madera->dev;
unsigned int hwid;
int (*patch_fn)(struct madera *) = NULL;
- const struct mfd_cell *mfd_devs;
+ const struct mfd_cell *mfd_devs = NULL;
int n_devs = 0;
int i, ret;
@@ -670,7 +670,7 @@ int madera_dev_init(struct madera *madera)
goto err_reset;
}
- if (!n_devs) {
+ if (!n_devs || !mfd_devs) {
dev_err(madera->dev, "Device ID 0x%x not a %s\n", hwid,
madera->type_name);
ret = -ENODEV;
diff --git a/drivers/mfd/max77705.c b/drivers/mfd/max77705.c
index 6b263bacb8c2..62dbc63efa8d 100644
--- a/drivers/mfd/max77705.c
+++ b/drivers/mfd/max77705.c
@@ -61,21 +61,21 @@ static const struct regmap_config max77705_regmap_config = {
.max_register = MAX77705_PMIC_REG_USBC_RESET,
};
-static const struct regmap_irq max77705_topsys_irqs[] = {
- { .mask = MAX77705_SYSTEM_IRQ_BSTEN_INT, },
- { .mask = MAX77705_SYSTEM_IRQ_SYSUVLO_INT, },
- { .mask = MAX77705_SYSTEM_IRQ_SYSOVLO_INT, },
- { .mask = MAX77705_SYSTEM_IRQ_TSHDN_INT, },
- { .mask = MAX77705_SYSTEM_IRQ_TM_INT, },
+static const struct regmap_irq max77705_irqs[] = {
+ { .mask = MAX77705_SRC_IRQ_CHG, },
+ { .mask = MAX77705_SRC_IRQ_TOP, },
+ { .mask = MAX77705_SRC_IRQ_FG, },
+ { .mask = MAX77705_SRC_IRQ_USBC, },
};
-static const struct regmap_irq_chip max77705_topsys_irq_chip = {
- .name = "max77705-topsys",
- .status_base = MAX77705_PMIC_REG_SYSTEM_INT,
- .mask_base = MAX77705_PMIC_REG_SYSTEM_INT_MASK,
+static const struct regmap_irq_chip max77705_irq_chip = {
+ .name = "max77705",
+ .status_base = MAX77705_PMIC_REG_INTSRC,
+ .ack_base = MAX77705_PMIC_REG_INTSRC,
+ .mask_base = MAX77705_PMIC_REG_INTSRC_MASK,
.num_regs = 1,
- .irqs = max77705_topsys_irqs,
- .num_irqs = ARRAY_SIZE(max77705_topsys_irqs),
+ .irqs = max77705_irqs,
+ .num_irqs = ARRAY_SIZE(max77705_irqs),
};
static int max77705_i2c_probe(struct i2c_client *i2c)
@@ -110,19 +110,12 @@ static int max77705_i2c_probe(struct i2c_client *i2c)
ret = devm_regmap_add_irq_chip(dev, max77705->regmap,
i2c->irq,
- IRQF_ONESHOT | IRQF_SHARED, 0,
- &max77705_topsys_irq_chip,
+ IRQF_ONESHOT, 0,
+ &max77705_irq_chip,
&irq_data);
if (ret)
return dev_err_probe(dev, ret, "Failed to add IRQ chip\n");
- /* Unmask interrupts from all blocks in interrupt source register */
- ret = regmap_update_bits(max77705->regmap,
- MAX77705_PMIC_REG_INTSRC_MASK,
- MAX77705_SRC_IRQ_ALL, (unsigned int)~MAX77705_SRC_IRQ_ALL);
- if (ret < 0)
- return dev_err_probe(dev, ret, "Could not unmask interrupts in INTSRC\n");
-
domain = regmap_irq_get_domain(irq_data);
ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
diff --git a/drivers/mfd/max8997.c b/drivers/mfd/max8997.c
index ffe96b40368e..7ba8ed1dfde3 100644
--- a/drivers/mfd/max8997.c
+++ b/drivers/mfd/max8997.c
@@ -438,7 +438,7 @@ static int max8997_suspend(struct device *dev)
disable_irq(max8997->irq);
if (device_may_wakeup(dev))
- irq_set_irq_wake(max8997->irq, 1);
+ enable_irq_wake(max8997->irq);
return 0;
}
@@ -448,7 +448,7 @@ static int max8997_resume(struct device *dev)
struct max8997_dev *max8997 = i2c_get_clientdata(i2c);
if (device_may_wakeup(dev))
- irq_set_irq_wake(max8997->irq, 0);
+ disable_irq_wake(max8997->irq);
enable_irq(max8997->irq);
return max8997_irq_resume(max8997);
}
diff --git a/drivers/mfd/max8998.c b/drivers/mfd/max8998.c
index 6ba27171da28..eb13bbaeda55 100644
--- a/drivers/mfd/max8998.c
+++ b/drivers/mfd/max8998.c
@@ -234,7 +234,7 @@ static int max8998_suspend(struct device *dev)
struct max8998_dev *max8998 = i2c_get_clientdata(i2c);
if (device_may_wakeup(dev))
- irq_set_irq_wake(max8998->irq, 1);
+ enable_irq_wake(max8998->irq);
return 0;
}
@@ -244,7 +244,7 @@ static int max8998_resume(struct device *dev)
struct max8998_dev *max8998 = i2c_get_clientdata(i2c);
if (device_may_wakeup(dev))
- irq_set_irq_wake(max8998->irq, 0);
+ disable_irq_wake(max8998->irq);
/*
* In LP3974, if IRQ registers are not "read & clear"
* when it's set during sleep, the interrupt becomes
diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c
index 76bd316a50af..7d14a1e7631e 100644
--- a/drivers/mfd/mfd-core.c
+++ b/drivers/mfd/mfd-core.c
@@ -131,6 +131,7 @@ allocate_of_node:
of_entry->np = np;
list_add_tail(&of_entry->list, &mfd_of_node_list);
+ of_node_get(np);
device_set_node(&pdev->dev, of_fwnode_handle(np));
#endif
return 0;
diff --git a/drivers/mfd/qnap-mcu.c b/drivers/mfd/qnap-mcu.c
index 89a8a1913d42..4ec1f4cf902f 100644
--- a/drivers/mfd/qnap-mcu.c
+++ b/drivers/mfd/qnap-mcu.c
@@ -150,40 +150,40 @@ int qnap_mcu_exec(struct qnap_mcu *mcu,
size_t length = reply_data_size + QNAP_MCU_CHECKSUM_SIZE;
struct qnap_mcu_reply *reply = &mcu->reply;
int ret = 0;
+ u8 crc;
if (length > sizeof(rx)) {
dev_err(&mcu->serdev->dev, "expected data too big for receive buffer");
return -EINVAL;
}
- mutex_lock(&mcu->bus_lock);
+ guard(mutex)(&mcu->bus_lock);
reply->data = rx;
reply->length = length;
reply->received = 0;
reinit_completion(&reply->done);
- qnap_mcu_write(mcu, cmd_data, cmd_data_size);
+ ret = qnap_mcu_write(mcu, cmd_data, cmd_data_size);
+ if (ret < 0)
+ return ret;
serdev_device_wait_until_sent(mcu->serdev, msecs_to_jiffies(QNAP_MCU_TIMEOUT_MS));
if (!wait_for_completion_timeout(&reply->done, msecs_to_jiffies(QNAP_MCU_TIMEOUT_MS))) {
dev_err(&mcu->serdev->dev, "Command timeout\n");
- ret = -ETIMEDOUT;
- } else {
- u8 crc = qnap_mcu_csum(rx, reply_data_size);
-
- if (crc != rx[reply_data_size]) {
- dev_err(&mcu->serdev->dev,
- "Invalid Checksum received\n");
- ret = -EIO;
- } else {
- memcpy(reply_data, rx, reply_data_size);
- }
+ return -ETIMEDOUT;
}
- mutex_unlock(&mcu->bus_lock);
- return ret;
+ crc = qnap_mcu_csum(rx, reply_data_size);
+ if (crc != rx[reply_data_size]) {
+ dev_err(&mcu->serdev->dev, "Invalid Checksum received\n");
+ return -EIO;
+ }
+
+ memcpy(reply_data, rx, reply_data_size);
+
+ return 0;
}
EXPORT_SYMBOL_GPL(qnap_mcu_exec);
@@ -247,6 +247,14 @@ static int qnap_mcu_power_off(struct sys_off_data *data)
return NOTIFY_DONE;
}
+static const struct qnap_mcu_variant qnap_ts233_mcu = {
+ .baud_rate = 115200,
+ .num_drives = 2,
+ .fan_pwm_min = 51, /* Specified in original model.conf */
+ .fan_pwm_max = 255,
+ .usb_led = true,
+};
+
static const struct qnap_mcu_variant qnap_ts433_mcu = {
.baud_rate = 115200,
.num_drives = 4,
@@ -319,6 +327,7 @@ static int qnap_mcu_probe(struct serdev_device *serdev)
}
static const struct of_device_id qnap_mcu_dt_ids[] = {
+ { .compatible = "qnap,ts233-mcu", .data = &qnap_ts233_mcu },
{ .compatible = "qnap,ts433-mcu", .data = &qnap_ts433_mcu },
{ /* sentinel */ }
};
diff --git a/drivers/mfd/rohm-bd71828.c b/drivers/mfd/rohm-bd71828.c
index a14b7aa69c3c..84a64c3b9c9f 100644
--- a/drivers/mfd/rohm-bd71828.c
+++ b/drivers/mfd/rohm-bd71828.c
@@ -45,8 +45,8 @@ static const struct resource bd71828_rtc_irqs[] = {
static const struct resource bd71815_power_irqs[] = {
DEFINE_RES_IRQ_NAMED(BD71815_INT_DCIN_RMV, "bd71815-dcin-rmv"),
- DEFINE_RES_IRQ_NAMED(BD71815_INT_CLPS_OUT, "bd71815-clps-out"),
- DEFINE_RES_IRQ_NAMED(BD71815_INT_CLPS_IN, "bd71815-clps-in"),
+ DEFINE_RES_IRQ_NAMED(BD71815_INT_CLPS_OUT, "bd71815-dcin-clps-out"),
+ DEFINE_RES_IRQ_NAMED(BD71815_INT_CLPS_IN, "bd71815-dcin-clps-in"),
DEFINE_RES_IRQ_NAMED(BD71815_INT_DCIN_OVP_RES, "bd71815-dcin-ovp-res"),
DEFINE_RES_IRQ_NAMED(BD71815_INT_DCIN_OVP_DET, "bd71815-dcin-ovp-det"),
DEFINE_RES_IRQ_NAMED(BD71815_INT_DCIN_MON_RES, "bd71815-dcin-mon-res"),
@@ -56,7 +56,7 @@ static const struct resource bd71815_power_irqs[] = {
DEFINE_RES_IRQ_NAMED(BD71815_INT_VSYS_LOW_RES, "bd71815-vsys-low-res"),
DEFINE_RES_IRQ_NAMED(BD71815_INT_VSYS_LOW_DET, "bd71815-vsys-low-det"),
DEFINE_RES_IRQ_NAMED(BD71815_INT_VSYS_MON_RES, "bd71815-vsys-mon-res"),
- DEFINE_RES_IRQ_NAMED(BD71815_INT_VSYS_MON_RES, "bd71815-vsys-mon-det"),
+ DEFINE_RES_IRQ_NAMED(BD71815_INT_VSYS_MON_DET, "bd71815-vsys-mon-det"),
DEFINE_RES_IRQ_NAMED(BD71815_INT_CHG_WDG_TEMP, "bd71815-chg-wdg-temp"),
DEFINE_RES_IRQ_NAMED(BD71815_INT_CHG_WDG_TIME, "bd71815-chg-wdg"),
DEFINE_RES_IRQ_NAMED(BD71815_INT_CHG_RECHARGE_RES, "bd71815-rechg-res"),
@@ -87,10 +87,10 @@ static const struct resource bd71815_power_irqs[] = {
DEFINE_RES_IRQ_NAMED(BD71815_INT_BAT_OVER_CURR_2_DET, "bd71815-bat-oc2-det"),
DEFINE_RES_IRQ_NAMED(BD71815_INT_BAT_OVER_CURR_3_RES, "bd71815-bat-oc3-res"),
DEFINE_RES_IRQ_NAMED(BD71815_INT_BAT_OVER_CURR_3_DET, "bd71815-bat-oc3-det"),
- DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_LOW_RES, "bd71815-bat-low-res"),
- DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_LOW_DET, "bd71815-bat-low-det"),
- DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_HI_RES, "bd71815-bat-hi-res"),
- DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_HI_DET, "bd71815-bat-hi-det"),
+ DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_LOW_RES, "bd71815-temp-bat-low-res"),
+ DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_LOW_DET, "bd71815-temp-bat-low-det"),
+ DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_HI_RES, "bd71815-temp-bat-hi-res"),
+ DEFINE_RES_IRQ_NAMED(BD71815_INT_TEMP_BAT_HI_DET, "bd71815-temp-bat-hi-det"),
};
static const struct mfd_cell bd71815_mfd_cells[] = {
@@ -109,7 +109,30 @@ static const struct mfd_cell bd71815_mfd_cells[] = {
},
};
-static const struct mfd_cell bd71828_mfd_cells[] = {
+static const struct resource bd71828_power_irqs[] = {
+ DEFINE_RES_IRQ_NAMED(BD71828_INT_CHG_TOPOFF_TO_DONE,
+ "bd71828-chg-done"),
+ DEFINE_RES_IRQ_NAMED(BD71828_INT_DCIN_DET, "bd71828-pwr-dcin-in"),
+ DEFINE_RES_IRQ_NAMED(BD71828_INT_DCIN_RMV, "bd71828-pwr-dcin-out"),
+ DEFINE_RES_IRQ_NAMED(BD71828_INT_BAT_LOW_VOLT_RES,
+ "bd71828-vbat-normal"),
+ DEFINE_RES_IRQ_NAMED(BD71828_INT_BAT_LOW_VOLT_DET, "bd71828-vbat-low"),
+ DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_BAT_HI_DET, "bd71828-btemp-hi"),
+ DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_BAT_HI_RES, "bd71828-btemp-cool"),
+ DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_BAT_LOW_DET, "bd71828-btemp-lo"),
+ DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_BAT_LOW_RES,
+ "bd71828-btemp-warm"),
+ DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_CHIP_OVER_VF_DET,
+ "bd71828-temp-hi"),
+ DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_CHIP_OVER_VF_RES,
+ "bd71828-temp-norm"),
+ DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_CHIP_OVER_125_DET,
+ "bd71828-temp-125-over"),
+ DEFINE_RES_IRQ_NAMED(BD71828_INT_TEMP_CHIP_OVER_125_RES,
+ "bd71828-temp-125-under"),
+};
+
+static struct mfd_cell bd71828_mfd_cells[] = {
{ .name = "bd71828-pmic", },
{ .name = "bd71828-gpio", },
{ .name = "bd71828-led", .of_compatible = "rohm,bd71828-leds" },
@@ -118,8 +141,11 @@ static const struct mfd_cell bd71828_mfd_cells[] = {
* BD70528 clock gate are the register address and mask.
*/
{ .name = "bd71828-clk", },
- { .name = "bd71827-power", },
{
+ .name = "bd71828-power",
+ .resources = bd71828_power_irqs,
+ .num_resources = ARRAY_SIZE(bd71828_power_irqs),
+ }, {
.name = "bd71828-rtc",
.resources = bd71828_rtc_irqs,
.num_resources = ARRAY_SIZE(bd71828_rtc_irqs),
diff --git a/drivers/mfd/rz-mtu3.c b/drivers/mfd/rz-mtu3.c
index f3dac4a29a83..9cdfef610398 100644
--- a/drivers/mfd/rz-mtu3.c
+++ b/drivers/mfd/rz-mtu3.c
@@ -32,7 +32,7 @@ static const unsigned long rz_mtu3_8bit_ch_reg_offs[][13] = {
[RZ_MTU3_CHAN_2] = MTU_8BIT_CH_1_2(0x204, 0x092, 0x205, 0x200, 0x20c, 0x201, 0x202),
[RZ_MTU3_CHAN_3] = MTU_8BIT_CH_3_4_6_7(0x008, 0x093, 0x02c, 0x000, 0x04c, 0x002, 0x004, 0x005, 0x038),
[RZ_MTU3_CHAN_4] = MTU_8BIT_CH_3_4_6_7(0x009, 0x094, 0x02d, 0x001, 0x04d, 0x003, 0x006, 0x007, 0x039),
- [RZ_MTU3_CHAN_5] = MTU_8BIT_CH_5(0xab2, 0x1eb, 0xab4, 0xab6, 0xa84, 0xa85, 0xa86, 0xa94, 0xa95, 0xa96, 0xaa4, 0xaa5, 0xaa6),
+ [RZ_MTU3_CHAN_5] = MTU_8BIT_CH_5(0xab2, 0x895, 0xab4, 0xab6, 0xa84, 0xa85, 0xa86, 0xa94, 0xa95, 0xa96, 0xaa4, 0xaa5, 0xaa6),
[RZ_MTU3_CHAN_6] = MTU_8BIT_CH_3_4_6_7(0x808, 0x893, 0x82c, 0x800, 0x84c, 0x802, 0x804, 0x805, 0x838),
[RZ_MTU3_CHAN_7] = MTU_8BIT_CH_3_4_6_7(0x809, 0x894, 0x82d, 0x801, 0x84d, 0x803, 0x806, 0x807, 0x839),
[RZ_MTU3_CHAN_8] = MTU_8BIT_CH_8(0x404, 0x098, 0x400, 0x406, 0x401, 0x402, 0x403)
diff --git a/drivers/mfd/simple-mfd-i2c.c b/drivers/mfd/simple-mfd-i2c.c
index 22159913bea0..0a607a1e3ca1 100644
--- a/drivers/mfd/simple-mfd-i2c.c
+++ b/drivers/mfd/simple-mfd-i2c.c
@@ -93,12 +93,32 @@ static const struct simple_mfd_data maxim_mon_max77705 = {
.mfd_cell_size = ARRAY_SIZE(max77705_sensor_cells),
};
+static const struct regmap_config spacemit_p1_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+static const struct mfd_cell spacemit_p1_cells[] = {
+ { .name = "spacemit-p1-regulator", },
+ { .name = "spacemit-p1-rtc", },
+};
+
+static const struct simple_mfd_data spacemit_p1 = {
+ .regmap_config = &spacemit_p1_regmap_config,
+ .mfd_cell = spacemit_p1_cells,
+ .mfd_cell_size = ARRAY_SIZE(spacemit_p1_cells),
+};
+
static const struct of_device_id simple_mfd_i2c_of_match[] = {
+ { .compatible = "fsl,ls1028aqds-fpga" },
+ { .compatible = "fsl,lx2160aqds-fpga" },
+ { .compatible = "fsl,lx2160ardb-fpga" },
{ .compatible = "kontron,sl28cpld" },
- { .compatible = "silergy,sy7636a", .data = &silergy_sy7636a},
{ .compatible = "maxim,max5970", .data = &maxim_max5970},
{ .compatible = "maxim,max5978", .data = &maxim_max5970},
{ .compatible = "maxim,max77705-battery", .data = &maxim_mon_max77705},
+ { .compatible = "silergy,sy7636a", .data = &silergy_sy7636a},
+ { .compatible = "spacemit,p1", .data = &spacemit_p1, },
{}
};
MODULE_DEVICE_TABLE(of, simple_mfd_i2c_of_match);
diff --git a/drivers/mfd/stm32-lptimer.c b/drivers/mfd/stm32-lptimer.c
index 09073dbc9c80..123659178cc2 100644
--- a/drivers/mfd/stm32-lptimer.c
+++ b/drivers/mfd/stm32-lptimer.c
@@ -19,7 +19,6 @@ static const struct regmap_config stm32_lptimer_regmap_cfg = {
.val_bits = 32,
.reg_stride = sizeof(u32),
.max_register = STM32_LPTIM_MAX_REGISTER,
- .fast_io = true,
};
static int stm32_lptimer_detect_encoder(struct stm32_lptimer *ddata)
diff --git a/drivers/mfd/stmpe-i2c.c b/drivers/mfd/stmpe-i2c.c
index c24ae16f1d96..943fa363efc3 100644
--- a/drivers/mfd/stmpe-i2c.c
+++ b/drivers/mfd/stmpe-i2c.c
@@ -122,19 +122,8 @@ static struct i2c_driver stmpe_i2c_driver = {
.remove = stmpe_i2c_remove,
.id_table = stmpe_i2c_id,
};
-
-static int __init stmpe_init(void)
-{
- return i2c_add_driver(&stmpe_i2c_driver);
-}
-subsys_initcall(stmpe_init);
-
-static void __exit stmpe_exit(void)
-{
- i2c_del_driver(&stmpe_i2c_driver);
-}
-module_exit(stmpe_exit);
+module_i2c_driver(stmpe_i2c_driver);
MODULE_DESCRIPTION("STMPE MFD I2C Interface Driver");
MODULE_AUTHOR("Rabin Vincent <rabin.vincent@stericsson.com>");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/stmpe-spi.c b/drivers/mfd/stmpe-spi.c
index 9b7bcb03d54c..dea31efface6 100644
--- a/drivers/mfd/stmpe-spi.c
+++ b/drivers/mfd/stmpe-spi.c
@@ -141,19 +141,8 @@ static struct spi_driver stmpe_spi_driver = {
.remove = stmpe_spi_remove,
.id_table = stmpe_spi_id,
};
-
-static int __init stmpe_init(void)
-{
- return spi_register_driver(&stmpe_spi_driver);
-}
-subsys_initcall(stmpe_init);
-
-static void __exit stmpe_exit(void)
-{
- spi_unregister_driver(&stmpe_spi_driver);
-}
-module_exit(stmpe_exit);
+module_spi_driver(stmpe_spi_driver);
MODULE_DESCRIPTION("STMPE MFD SPI Interface Driver");
MODULE_AUTHOR("Viresh Kumar <vireshk@kernel.org>");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/stmpe.c b/drivers/mfd/stmpe.c
index 0cb7af11ea60..3c5c2f157f52 100644
--- a/drivers/mfd/stmpe.c
+++ b/drivers/mfd/stmpe.c
@@ -1486,6 +1486,9 @@ EXPORT_SYMBOL_GPL(stmpe_probe);
void stmpe_remove(struct stmpe *stmpe)
{
+ if (stmpe->domain)
+ irq_domain_remove(stmpe->domain);
+
if (!IS_ERR(stmpe->vio) && regulator_is_enabled(stmpe->vio))
regulator_disable(stmpe->vio);
if (!IS_ERR(stmpe->vcc) && regulator_is_enabled(stmpe->vcc))
diff --git a/drivers/mfd/sun4i-gpadc.c b/drivers/mfd/sun4i-gpadc.c
index 3029d48e982c..bf2f6fdaf8bf 100644
--- a/drivers/mfd/sun4i-gpadc.c
+++ b/drivers/mfd/sun4i-gpadc.c
@@ -72,7 +72,6 @@ static const struct regmap_config sun4i_gpadc_regmap_config = {
.reg_bits = 32,
.val_bits = 32,
.reg_stride = 4,
- .fast_io = true,
};
static const struct of_device_id sun4i_gpadc_of_match[] = {
diff --git a/drivers/mfd/tps6594-core.c b/drivers/mfd/tps6594-core.c
index c16c37e36617..8b26c4127472 100644
--- a/drivers/mfd/tps6594-core.c
+++ b/drivers/mfd/tps6594-core.c
@@ -10,16 +10,20 @@
* Copyright (C) 2023 BayLibre Incorporated - https://www.baylibre.com/
*/
+#include <linux/bitfield.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/reboot.h>
#include <linux/mfd/core.h>
#include <linux/mfd/tps6594.h>
#define TPS6594_CRC_SYNC_TIMEOUT_MS 150
+#define TPS65224_EN_SEL_PB 1
+#define TPS65224_GPIO3_SEL_PB 3
/* Completion to synchronize CRC feature enabling on all PMICs */
static DECLARE_COMPLETION(tps6594_crc_comp);
@@ -128,6 +132,12 @@ static const struct resource tps6594_rtc_resources[] = {
DEFINE_RES_IRQ_NAMED(TPS6594_IRQ_POWER_UP, TPS6594_IRQ_NAME_POWERUP),
};
+static const struct resource tps6594_pwrbutton_resources[] = {
+ DEFINE_RES_IRQ_NAMED(TPS65224_IRQ_PB_FALL, TPS65224_IRQ_NAME_PB_FALL),
+ DEFINE_RES_IRQ_NAMED(TPS65224_IRQ_PB_RISE, TPS65224_IRQ_NAME_PB_RISE),
+ DEFINE_RES_IRQ_NAMED(TPS65224_IRQ_PB_SHORT, TPS65224_IRQ_NAME_PB_SHORT),
+};
+
static const struct mfd_cell tps6594_common_cells[] = {
MFD_CELL_RES("tps6594-regulator", tps6594_regulator_resources),
MFD_CELL_RES("tps6594-pinctrl", tps6594_pinctrl_resources),
@@ -318,8 +328,6 @@ static const struct resource tps65224_pfsm_resources[] = {
DEFINE_RES_IRQ_NAMED(TPS65224_IRQ_REG_UNLOCK, TPS65224_IRQ_NAME_REG_UNLOCK),
DEFINE_RES_IRQ_NAMED(TPS65224_IRQ_TWARN, TPS65224_IRQ_NAME_TWARN),
DEFINE_RES_IRQ_NAMED(TPS65224_IRQ_PB_LONG, TPS65224_IRQ_NAME_PB_LONG),
- DEFINE_RES_IRQ_NAMED(TPS65224_IRQ_PB_FALL, TPS65224_IRQ_NAME_PB_FALL),
- DEFINE_RES_IRQ_NAMED(TPS65224_IRQ_PB_RISE, TPS65224_IRQ_NAME_PB_RISE),
DEFINE_RES_IRQ_NAMED(TPS65224_IRQ_TSD_ORD, TPS65224_IRQ_NAME_TSD_ORD),
DEFINE_RES_IRQ_NAMED(TPS65224_IRQ_BIST_FAIL, TPS65224_IRQ_NAME_BIST_FAIL),
DEFINE_RES_IRQ_NAMED(TPS65224_IRQ_REG_CRC_ERR, TPS65224_IRQ_NAME_REG_CRC_ERR),
@@ -347,6 +355,12 @@ static const struct mfd_cell tps65224_common_cells[] = {
MFD_CELL_RES("tps6594-regulator", tps65224_regulator_resources),
};
+static const struct mfd_cell tps6594_pwrbutton_cell = {
+ .name = "tps6594-pwrbutton",
+ .resources = tps6594_pwrbutton_resources,
+ .num_resources = ARRAY_SIZE(tps6594_pwrbutton_resources),
+};
+
static const struct regmap_irq tps65224_irqs[] = {
/* INT_BUCK register */
REGMAP_IRQ_REG(TPS65224_IRQ_BUCK1_UVOV, 0, TPS65224_BIT_BUCK1_UVOV_INT),
@@ -676,11 +690,25 @@ static int tps6594_enable_crc(struct tps6594 *tps)
return ret;
}
+static int tps6594_power_off_handler(struct sys_off_data *data)
+{
+ struct tps6594 *tps = data->cb_data;
+ int ret;
+
+ ret = regmap_update_bits(tps->regmap, TPS6594_REG_FSM_I2C_TRIGGERS,
+ TPS6594_BIT_TRIGGER_I2C(0), TPS6594_BIT_TRIGGER_I2C(0));
+ if (ret)
+ return notifier_from_errno(ret);
+
+ return NOTIFY_DONE;
+}
+
int tps6594_device_init(struct tps6594 *tps, bool enable_crc)
{
struct device *dev = tps->dev;
int ret;
struct regmap_irq_chip *irq_chip;
+ unsigned int pwr_on, gpio3_cfg;
const struct mfd_cell *cells;
int n_cells;
@@ -727,6 +755,27 @@ int tps6594_device_init(struct tps6594 *tps, bool enable_crc)
if (ret)
return dev_err_probe(dev, ret, "Failed to add common child devices\n");
+ /* If either the PB/EN/VSENSE or GPIO3 is configured as PB, register a driver for it */
+ if (tps->chip_id == TPS65224 || tps->chip_id == TPS652G1) {
+ ret = regmap_read(tps->regmap, TPS6594_REG_NPWRON_CONF, &pwr_on);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read PB/EN/VSENSE config\n");
+
+ ret = regmap_read(tps->regmap, TPS6594_REG_GPIOX_CONF(2), &gpio3_cfg);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read GPIO3 config\n");
+
+ if (FIELD_GET(TPS65224_MASK_EN_PB_VSENSE_CONFIG, pwr_on) == TPS65224_EN_SEL_PB ||
+ FIELD_GET(TPS65224_MASK_GPIO_SEL, gpio3_cfg) == TPS65224_GPIO3_SEL_PB) {
+ ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO,
+ &tps6594_pwrbutton_cell, 1, NULL, 0,
+ regmap_irq_get_domain(tps->irq_data));
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to add power button device.\n");
+ }
+ }
+
/* No RTC for LP8764, TPS65224 and TPS652G1 */
if (tps->chip_id != LP8764 && tps->chip_id != TPS65224 && tps->chip_id != TPS652G1) {
ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO, tps6594_rtc_cells,
@@ -736,6 +785,12 @@ int tps6594_device_init(struct tps6594 *tps, bool enable_crc)
return dev_err_probe(dev, ret, "Failed to add RTC child device\n");
}
+ if (of_device_is_system_power_controller(dev->of_node)) {
+ ret = devm_register_power_off_handler(tps->dev, tps6594_power_off_handler, tps);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register power-off handler\n");
+ }
+
return 0;
}
EXPORT_SYMBOL_GPL(tps6594_device_init);
diff --git a/drivers/mfd/vexpress-sysreg.c b/drivers/mfd/vexpress-sysreg.c
index 9399eb850ca2..f49cee91f71c 100644
--- a/drivers/mfd/vexpress-sysreg.c
+++ b/drivers/mfd/vexpress-sysreg.c
@@ -120,7 +120,7 @@ static int vexpress_sysreg_probe(struct platform_device *pdev)
if (!mmc_gpio_chip)
return -ENOMEM;
- config = (typeof(config)){
+ config = (struct gpio_generic_chip_config) {
.dev = &pdev->dev,
.sz = 4,
.dat = base + SYS_MCI,
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 79ddb006e2da..11893c50c5d2 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -767,6 +767,13 @@ config CHARGER_BQ2515X
rail, ADC for battery and system monitoring, and push-button
controller.
+config CHARGER_BQ257XX
+ tristate "TI BQ257XX battery charger family"
+ depends on MFD_BQ257XX
+ help
+ Say Y to enable support for the TI BQ257XX family of battery
+ charging integrated circuits.
+
config CHARGER_BQ25890
tristate "TI BQ25890 battery charger driver"
depends on I2C
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index f943c9150b32..a7d91244bb82 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -97,6 +97,7 @@ obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o
obj-$(CONFIG_CHARGER_BQ24257) += bq24257_charger.o
obj-$(CONFIG_CHARGER_BQ24735) += bq24735-charger.o
obj-$(CONFIG_CHARGER_BQ2515X) += bq2515x_charger.o
+obj-$(CONFIG_CHARGER_BQ257XX) += bq257xx_charger.o
obj-$(CONFIG_CHARGER_BQ25890) += bq25890_charger.o
obj-$(CONFIG_CHARGER_BQ25980) += bq25980_charger.o
obj-$(CONFIG_CHARGER_BQ256XX) += bq256xx_charger.o
diff --git a/drivers/power/supply/bq257xx_charger.c b/drivers/power/supply/bq257xx_charger.c
new file mode 100644
index 000000000000..02c7d8b61e82
--- /dev/null
+++ b/drivers/power/supply/bq257xx_charger.c
@@ -0,0 +1,755 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BQ257XX Battery Charger Driver
+ * Copyright (C) 2025 Chris Morgan <macromorgan@hotmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/bq257xx.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+/* Forward declaration of driver data. */
+struct bq257xx_chg;
+
+/**
+ * struct bq257xx_chip_info - chip specific routines
+ * @bq257xx_hw_init: init function for hw
+ * @bq257xx_hw_shutdown: shutdown function for hw
+ * @bq257xx_get_state: get and update state of hardware
+ * @bq257xx_set_ichg: set maximum charge current (in uA)
+ * @bq257xx_set_vbatreg: set maximum charge voltage (in uV)
+ * @bq257xx_set_iindpm: set maximum input current (in uA)
+ */
+struct bq257xx_chip_info {
+ int (*bq257xx_hw_init)(struct bq257xx_chg *pdata);
+ void (*bq257xx_hw_shutdown)(struct bq257xx_chg *pdata);
+ int (*bq257xx_get_state)(struct bq257xx_chg *pdata);
+ int (*bq257xx_set_ichg)(struct bq257xx_chg *pdata, int ichg);
+ int (*bq257xx_set_vbatreg)(struct bq257xx_chg *pdata, int vbatreg);
+ int (*bq257xx_set_iindpm)(struct bq257xx_chg *pdata, int iindpm);
+};
+
+/**
+ * struct bq257xx_chg - driver data for charger
+ * @chip: hw specific functions
+ * @bq: parent MFD device
+ * @charger: power supply device
+ * @online: charger input is present
+ * @fast_charge: charger is in fast charge mode
+ * @pre_charge: charger is in pre-charge mode
+ * @ov_fault: charger reports over voltage fault
+ * @batoc_fault: charger reports battery over current fault
+ * @oc_fault: charger reports over current fault
+ * @usb_type: USB type reported from parent power supply
+ * @supplied: Status of parent power supply
+ * @iindpm_max: maximum input current limit (uA)
+ * @vbat_max: maximum charge voltage (uV)
+ * @ichg_max: maximum charge current (uA)
+ * @vsys_min: minimum system voltage (uV)
+ */
+struct bq257xx_chg {
+ const struct bq257xx_chip_info *chip;
+ struct bq257xx_device *bq;
+ struct power_supply *charger;
+ bool online;
+ bool fast_charge;
+ bool pre_charge;
+ bool ov_fault;
+ bool batoc_fault;
+ bool oc_fault;
+ int usb_type;
+ int supplied;
+ u32 iindpm_max;
+ u32 vbat_max;
+ u32 ichg_max;
+ u32 vsys_min;
+};
+
+/**
+ * bq25703_get_state() - Get the current state of the device
+ * @pdata: driver platform data
+ *
+ * Get the current state of the charger. Check if the charger is
+ * powered, what kind of charge state (if any) the device is in,
+ * and if there are any active faults.
+ *
+ * Return: Returns 0 on success, or error on failure to read device.
+ */
+static int bq25703_get_state(struct bq257xx_chg *pdata)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(pdata->bq->regmap, BQ25703_CHARGER_STATUS, &reg);
+ if (ret)
+ return ret;
+
+ pdata->online = reg & BQ25703_STS_AC_STAT;
+ pdata->fast_charge = reg & BQ25703_STS_IN_FCHRG;
+ pdata->pre_charge = reg & BQ25703_STS_IN_PCHRG;
+ pdata->ov_fault = reg & BQ25703_STS_FAULT_ACOV;
+ pdata->batoc_fault = reg & BQ25703_STS_FAULT_BATOC;
+ pdata->oc_fault = reg & BQ25703_STS_FAULT_ACOC;
+
+ return 0;
+}
+
+/**
+ * bq25703_get_min_vsys() - Get the minimum system voltage
+ * @pdata: driver platform data
+ * @intval: value for minimum voltage
+ *
+ * Return: Returns 0 on success or error on failure to read.
+ */
+static int bq25703_get_min_vsys(struct bq257xx_chg *pdata, int *intval)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(pdata->bq->regmap, BQ25703_MIN_VSYS,
+ &reg);
+ if (ret)
+ return ret;
+
+ reg = FIELD_GET(BQ25703_MINVSYS_MASK, reg);
+ *intval = (reg * BQ25703_MINVSYS_STEP_UV) + BQ25703_MINVSYS_MIN_UV;
+
+ return ret;
+}
+
+/**
+ * bq25703_set_min_vsys() - Set the minimum system voltage
+ * @pdata: driver platform data
+ * @vsys: voltage value to set in uV.
+ *
+ * This function takes a requested minimum system voltage value, clamps
+ * it between the minimum supported value by the charger and a user
+ * defined minimum system value, and then writes the value to the
+ * appropriate register.
+ *
+ * Return: Returns 0 on success or error if an error occurs.
+ */
+static int bq25703_set_min_vsys(struct bq257xx_chg *pdata, int vsys)
+{
+ unsigned int reg;
+ int vsys_min = pdata->vsys_min;
+
+ vsys = clamp(vsys, BQ25703_MINVSYS_MIN_UV, vsys_min);
+ reg = ((vsys - BQ25703_MINVSYS_MIN_UV) / BQ25703_MINVSYS_STEP_UV);
+ reg = FIELD_PREP(BQ25703_MINVSYS_MASK, reg);
+
+ return regmap_write(pdata->bq->regmap, BQ25703_MIN_VSYS,
+ reg);
+}
+
+/**
+ * bq25703_get_cur() - Get the reported current from the battery
+ * @pdata: driver platform data
+ * @intval: value of reported battery current
+ *
+ * Read the reported current from the battery. Since value is always
+ * positive set sign to negative if discharging.
+ *
+ * Return: Returns 0 on success or error if unable to read value.
+ */
+static int bq25703_get_cur(struct bq257xx_chg *pdata, int *intval)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(pdata->bq->regmap, BQ25703_ADCIBAT_CHG, &reg);
+ if (ret < 0)
+ return ret;
+
+ if (pdata->online)
+ *intval = FIELD_GET(BQ25703_ADCIBAT_CHG_MASK, reg) *
+ BQ25703_ADCIBAT_CHG_STEP_UA;
+ else
+ *intval = -(FIELD_GET(BQ25703_ADCIBAT_DISCHG_MASK, reg) *
+ BQ25703_ADCIBAT_DIS_STEP_UA);
+
+ return ret;
+}
+
+/**
+ * bq25703_get_ichg_cur() - Get the maximum reported charge current
+ * @pdata: driver platform data
+ * @intval: value of maximum reported charge current
+ *
+ * Get the maximum reported charge current from the battery.
+ *
+ * Return: Returns 0 on success or error if unable to read value.
+ */
+static int bq25703_get_ichg_cur(struct bq257xx_chg *pdata, int *intval)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(pdata->bq->regmap, BQ25703_CHARGE_CURRENT, &reg);
+ if (ret)
+ return ret;
+
+ *intval = FIELD_GET(BQ25703_ICHG_MASK, reg) * BQ25703_ICHG_STEP_UA;
+
+ return ret;
+}
+
+/**
+ * bq25703_set_ichg_cur() - Set the maximum charge current
+ * @pdata: driver platform data
+ * @ichg: current value to set in uA.
+ *
+ * This function takes a requested maximum charge current value, clamps
+ * it between the minimum supported value by the charger and a user
+ * defined maximum charging value, and then writes the value to the
+ * appropriate register.
+ *
+ * Return: Returns 0 on success or error if an error occurs.
+ */
+static int bq25703_set_ichg_cur(struct bq257xx_chg *pdata, int ichg)
+{
+ unsigned int reg;
+ int ichg_max = pdata->ichg_max;
+
+ ichg = clamp(ichg, BQ25703_ICHG_MIN_UA, ichg_max);
+ reg = FIELD_PREP(BQ25703_ICHG_MASK, (ichg / BQ25703_ICHG_STEP_UA));
+
+ return regmap_write(pdata->bq->regmap, BQ25703_CHARGE_CURRENT,
+ reg);
+}
+
+/**
+ * bq25703_get_chrg_volt() - Get the maximum set charge voltage
+ * @pdata: driver platform data
+ * @intval: maximum charge voltage value
+ *
+ * Return: Returns 0 on success or error if unable to read value.
+ */
+static int bq25703_get_chrg_volt(struct bq257xx_chg *pdata, int *intval)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(pdata->bq->regmap, BQ25703_MAX_CHARGE_VOLT,
+ &reg);
+ if (ret)
+ return ret;
+
+ *intval = FIELD_GET(BQ25703_MAX_CHARGE_VOLT_MASK, reg) *
+ BQ25703_VBATREG_STEP_UV;
+
+ return ret;
+}
+
+/**
+ * bq25703_set_chrg_volt() - Set the maximum charge voltage
+ * @pdata: driver platform data
+ * @vbat: voltage value to set in uV.
+ *
+ * This function takes a requested maximum charge voltage value, clamps
+ * it between the minimum supported value by the charger and a user
+ * defined maximum charging value, and then writes the value to the
+ * appropriate register.
+ *
+ * Return: Returns 0 on success or error if an error occurs.
+ */
+static int bq25703_set_chrg_volt(struct bq257xx_chg *pdata, int vbat)
+{
+ unsigned int reg;
+ int vbat_max = pdata->vbat_max;
+
+ vbat = clamp(vbat, BQ25703_VBATREG_MIN_UV, vbat_max);
+
+ reg = FIELD_PREP(BQ25703_MAX_CHARGE_VOLT_MASK,
+ (vbat / BQ25703_VBATREG_STEP_UV));
+
+ return regmap_write(pdata->bq->regmap, BQ25703_MAX_CHARGE_VOLT,
+ reg);
+}
+
+/**
+ * bq25703_get_iindpm() - Get the maximum set input current
+ * @pdata: driver platform data
+ * @intval: maximum input current value
+ *
+ * Read the actual input current limit from the device into intval.
+ * This can differ from the value programmed due to some autonomous
+ * functions that may be enabled (but are not currently). This is why
+ * there is a different register used.
+ *
+ * Return: Returns 0 on success or error if unable to read register
+ * value.
+ */
+static int bq25703_get_iindpm(struct bq257xx_chg *pdata, int *intval)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(pdata->bq->regmap, BQ25703_IIN_DPM, &reg);
+ if (ret)
+ return ret;
+
+ reg = FIELD_GET(BQ25703_IINDPM_MASK, reg);
+ *intval = (reg * BQ25703_IINDPM_STEP_UA) + BQ25703_IINDPM_OFFSET_UA;
+
+ return ret;
+}
+
+/**
+ * bq25703_set_iindpm() - Set the maximum input current
+ * @pdata: driver platform data
+ * @iindpm: current value in uA.
+ *
+ * This function takes a requested maximum input current value, clamps
+ * it between the minimum supported value by the charger and a user
+ * defined maximum input value, and then writes the value to the
+ * appropriate register.
+ *
+ * Return: Returns 0 on success or error if an error occurs.
+ */
+static int bq25703_set_iindpm(struct bq257xx_chg *pdata, int iindpm)
+{
+ unsigned int reg;
+ int iindpm_max = pdata->iindpm_max;
+
+ iindpm = clamp(iindpm, BQ25703_IINDPM_MIN_UA, iindpm_max);
+
+ reg = ((iindpm - BQ25703_IINDPM_OFFSET_UA) / BQ25703_IINDPM_STEP_UA);
+
+ return regmap_write(pdata->bq->regmap, BQ25703_IIN_HOST,
+ FIELD_PREP(BQ25703_IINDPM_MASK, reg));
+}
+
+/**
+ * bq25703_get_vbat() - Get the reported voltage from the battery
+ * @pdata: driver platform data
+ * @intval: value of reported battery voltage
+ *
+ * Read value of battery voltage into intval.
+ *
+ * Return: Returns 0 on success or error if unable to read value.
+ */
+static int bq25703_get_vbat(struct bq257xx_chg *pdata, int *intval)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(pdata->bq->regmap, BQ25703_ADCVSYSVBAT, &reg);
+ if (ret)
+ return ret;
+
+ reg = FIELD_GET(BQ25703_ADCVBAT_MASK, reg);
+ *intval = (reg * BQ25703_ADCVSYSVBAT_STEP) + BQ25703_ADCVSYSVBAT_OFFSET_UV;
+
+ return ret;
+}
+
+/**
+ * bq25703_hw_init() - Set all the required registers to init the charger
+ * @pdata: driver platform data
+ *
+ * Initialize the BQ25703 by first disabling the watchdog timer (which
+ * shuts off the charger in the absence of periodic writes). Then, set
+ * the charge current, charge voltage, minimum system voltage, and
+ * input current limit. Disable low power mode to allow ADCs and
+ * interrupts. Enable the ADC, start the ADC, set the ADC scale to
+ * full, and enable each individual ADC channel.
+ *
+ * Return: Returns 0 on success or error code on error.
+ */
+static int bq25703_hw_init(struct bq257xx_chg *pdata)
+{
+ struct regmap *regmap = pdata->bq->regmap;
+ int ret = 0;
+
+ regmap_update_bits(regmap, BQ25703_CHARGE_OPTION_0,
+ BQ25703_WDTMR_ADJ_MASK,
+ FIELD_PREP(BQ25703_WDTMR_ADJ_MASK,
+ BQ25703_WDTMR_DISABLE));
+
+ ret = pdata->chip->bq257xx_set_ichg(pdata, pdata->ichg_max);
+ if (ret)
+ return ret;
+
+ ret = pdata->chip->bq257xx_set_vbatreg(pdata, pdata->vbat_max);
+ if (ret)
+ return ret;
+
+ ret = bq25703_set_min_vsys(pdata, pdata->vsys_min);
+ if (ret)
+ return ret;
+
+ ret = pdata->chip->bq257xx_set_iindpm(pdata, pdata->iindpm_max);
+ if (ret)
+ return ret;
+
+ /* Disable low power mode by writing 0 to the register. */
+ regmap_update_bits(regmap, BQ25703_CHARGE_OPTION_0,
+ BQ25703_EN_LWPWR, 0);
+
+ /* Enable the ADC. */
+ regmap_update_bits(regmap, BQ25703_ADC_OPTION,
+ BQ25703_ADC_CONV_EN, BQ25703_ADC_CONV_EN);
+
+ /* Start the ADC. */
+ regmap_update_bits(regmap, BQ25703_ADC_OPTION,
+ BQ25703_ADC_START, BQ25703_ADC_START);
+
+ /* Set the scale of the ADC. */
+ regmap_update_bits(regmap, BQ25703_ADC_OPTION,
+ BQ25703_ADC_FULL_SCALE, BQ25703_ADC_FULL_SCALE);
+
+ /* Enable each of the ADC channels available. */
+ regmap_update_bits(regmap, BQ25703_ADC_OPTION,
+ BQ25703_ADC_CH_MASK,
+ (BQ25703_ADC_CMPIN_EN | BQ25703_ADC_VBUS_EN |
+ BQ25703_ADC_PSYS_EN | BQ25703_ADC_IIN_EN |
+ BQ25703_ADC_IDCHG_EN | BQ25703_ADC_ICHG_EN |
+ BQ25703_ADC_VSYS_EN | BQ25703_ADC_VBAT_EN));
+
+ return ret;
+}
+
+/**
+ * bq25703_hw_shutdown() - Set registers for shutdown
+ * @pdata: driver platform data
+ *
+ * Enable low power mode for the device while in shutdown.
+ */
+static void bq25703_hw_shutdown(struct bq257xx_chg *pdata)
+{
+ regmap_update_bits(pdata->bq->regmap, BQ25703_CHARGE_OPTION_0,
+ BQ25703_EN_LWPWR, BQ25703_EN_LWPWR);
+}
+
+static int bq257xx_set_charger_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct bq257xx_chg *pdata = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return pdata->chip->bq257xx_set_iindpm(pdata, val->intval);
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ return pdata->chip->bq257xx_set_vbatreg(pdata, val->intval);
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ return pdata->chip->bq257xx_set_ichg(pdata, val->intval);
+
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static int bq257xx_get_charger_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct bq257xx_chg *pdata = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ ret = pdata->chip->bq257xx_get_state(pdata);
+ if (ret)
+ return ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (!pdata->online)
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (pdata->fast_charge || pdata->pre_charge)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (pdata->ov_fault || pdata->batoc_fault)
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ else if (pdata->oc_fault)
+ val->intval = POWER_SUPPLY_HEALTH_OVERCURRENT;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = "Texas Instruments";
+ break;
+
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = pdata->online;
+ break;
+
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return bq25703_get_iindpm(pdata, &val->intval);
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ return bq25703_get_chrg_volt(pdata, &val->intval);
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ return bq25703_get_cur(pdata, &val->intval);
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ return bq25703_get_vbat(pdata, &val->intval);
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ return bq25703_get_ichg_cur(pdata, &val->intval);
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ return bq25703_get_min_vsys(pdata, &val->intval);
+
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ val->intval = pdata->usb_type;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property bq257xx_power_supply_props[] = {
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_USB_TYPE,
+};
+
+static int bq257xx_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ * bq257xx_external_power_changed() - Handler for external power change
+ * @psy: Power supply data
+ *
+ * When the external power into the charger is changed, check the USB
+ * type so that it can be reported. Additionally, update the max input
+ * current and max charging current to the value reported if it is a
+ * USB PD charger, otherwise use the default value. Note that each time
+ * a charger is removed the max charge current register is erased, so
+ * it must be set again each time the input changes or the device will
+ * not charge.
+ */
+static void bq257xx_external_power_changed(struct power_supply *psy)
+{
+ struct bq257xx_chg *pdata = power_supply_get_drvdata(psy);
+ union power_supply_propval val;
+ int ret;
+ int imax = pdata->iindpm_max;
+
+ pdata->chip->bq257xx_get_state(pdata);
+
+ pdata->supplied = power_supply_am_i_supplied(pdata->charger);
+ if (pdata->supplied < 0)
+ return;
+
+ if (pdata->supplied == 0)
+ goto out;
+
+ ret = power_supply_get_property_from_supplier(psy,
+ POWER_SUPPLY_PROP_USB_TYPE,
+ &val);
+ if (ret)
+ return;
+
+ pdata->usb_type = val.intval;
+
+ if ((pdata->usb_type == POWER_SUPPLY_USB_TYPE_PD) ||
+ (pdata->usb_type == POWER_SUPPLY_USB_TYPE_PD_DRP) ||
+ (pdata->usb_type == POWER_SUPPLY_USB_TYPE_PD_PPS)) {
+ ret = power_supply_get_property_from_supplier(psy,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ &val);
+ if (ret)
+ return;
+
+ if (val.intval)
+ imax = val.intval;
+ }
+
+ if (pdata->supplied) {
+ pdata->chip->bq257xx_set_ichg(pdata, pdata->ichg_max);
+ pdata->chip->bq257xx_set_iindpm(pdata, imax);
+ pdata->chip->bq257xx_set_vbatreg(pdata, pdata->vbat_max);
+ }
+
+out:
+ power_supply_changed(psy);
+}
+
+static irqreturn_t bq257xx_irq_handler_thread(int irq, void *private)
+{
+ struct bq257xx_chg *pdata = private;
+
+ bq257xx_external_power_changed(pdata->charger);
+ return IRQ_HANDLED;
+}
+
+static const struct power_supply_desc bq257xx_power_supply_desc = {
+ .name = "bq257xx-charger",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .usb_types = BIT(POWER_SUPPLY_USB_TYPE_C) |
+ BIT(POWER_SUPPLY_USB_TYPE_PD) |
+ BIT(POWER_SUPPLY_USB_TYPE_PD_DRP) |
+ BIT(POWER_SUPPLY_USB_TYPE_PD_PPS) |
+ BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN),
+ .properties = bq257xx_power_supply_props,
+ .num_properties = ARRAY_SIZE(bq257xx_power_supply_props),
+ .get_property = bq257xx_get_charger_property,
+ .set_property = bq257xx_set_charger_property,
+ .property_is_writeable = bq257xx_property_is_writeable,
+ .external_power_changed = bq257xx_external_power_changed,
+};
+
+static const struct bq257xx_chip_info bq25703_chip_info = {
+ .bq257xx_hw_init = &bq25703_hw_init,
+ .bq257xx_hw_shutdown = &bq25703_hw_shutdown,
+ .bq257xx_get_state = &bq25703_get_state,
+ .bq257xx_set_ichg = &bq25703_set_ichg_cur,
+ .bq257xx_set_vbatreg = &bq25703_set_chrg_volt,
+ .bq257xx_set_iindpm = &bq25703_set_iindpm,
+};
+
+/**
+ * bq257xx_parse_dt() - Parse the device tree for required properties
+ * @pdata: driver platform data
+ * @psy_cfg: power supply config data
+ * @dev: device struct
+ *
+ * Read the device tree to identify the minimum system voltage, the
+ * maximum charge current, the maximum charge voltage, and the maximum
+ * input current.
+ *
+ * Return: Returns 0 on success or error code on error.
+ */
+static int bq257xx_parse_dt(struct bq257xx_chg *pdata,
+ struct power_supply_config *psy_cfg, struct device *dev)
+{
+ struct power_supply_battery_info *bat_info;
+ int ret;
+
+ ret = power_supply_get_battery_info(pdata->charger,
+ &bat_info);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Unable to get battery info\n");
+
+ if ((bat_info->voltage_min_design_uv <= 0) ||
+ (bat_info->constant_charge_voltage_max_uv <= 0) ||
+ (bat_info->constant_charge_current_max_ua <= 0))
+ return dev_err_probe(dev, -EINVAL,
+ "Required bat info missing or invalid\n");
+
+ pdata->vsys_min = bat_info->voltage_min_design_uv;
+ pdata->vbat_max = bat_info->constant_charge_voltage_max_uv;
+ pdata->ichg_max = bat_info->constant_charge_current_max_ua;
+
+ power_supply_put_battery_info(pdata->charger, bat_info);
+
+ ret = device_property_read_u32(dev,
+ "input-current-limit-microamp",
+ &pdata->iindpm_max);
+ if (ret)
+ pdata->iindpm_max = BQ25703_IINDPM_DEFAULT_UA;
+
+ return 0;
+}
+
+static int bq257xx_charger_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct bq257xx_device *bq = dev_get_drvdata(pdev->dev.parent);
+ struct bq257xx_chg *pdata;
+ struct power_supply_config psy_cfg = { };
+ int ret;
+
+ device_set_of_node_from_dev(dev, pdev->dev.parent);
+
+ pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+
+ pdata->bq = bq;
+ pdata->chip = &bq25703_chip_info;
+
+ platform_set_drvdata(pdev, pdata);
+
+ psy_cfg.drv_data = pdata;
+ psy_cfg.fwnode = dev_fwnode(dev);
+
+ pdata->charger = devm_power_supply_register(dev,
+ &bq257xx_power_supply_desc,
+ &psy_cfg);
+ if (IS_ERR(pdata->charger))
+ return dev_err_probe(dev, PTR_ERR(pdata->charger),
+ "Power supply register charger failed\n");
+
+ ret = bq257xx_parse_dt(pdata, &psy_cfg, dev);
+ if (ret)
+ return ret;
+
+ ret = pdata->chip->bq257xx_hw_init(pdata);
+ if (ret)
+ return dev_err_probe(dev, ret, "Cannot initialize the charger\n");
+
+ platform_set_drvdata(pdev, pdata);
+
+ if (bq->client->irq) {
+ ret = devm_request_threaded_irq(dev, bq->client->irq, NULL,
+ bq257xx_irq_handler_thread,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ dev_name(&bq->client->dev), pdata);
+ if (ret < 0)
+ dev_err_probe(dev, ret, "Charger get irq failed\n");
+ }
+
+ return ret;
+}
+
+static void bq257xx_charger_shutdown(struct platform_device *pdev)
+{
+ struct bq257xx_chg *pdata = platform_get_drvdata(pdev);
+
+ pdata->chip->bq257xx_hw_shutdown(pdata);
+}
+
+static struct platform_driver bq257xx_chg_driver = {
+ .driver = {
+ .name = "bq257xx-charger",
+ },
+ .probe = bq257xx_charger_probe,
+ .shutdown = bq257xx_charger_shutdown,
+};
+module_platform_driver(bq257xx_chg_driver);
+
+MODULE_DESCRIPTION("bq257xx charger driver");
+MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index e252bb11ae66..d84f3d054c59 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -297,6 +297,14 @@ config REGULATOR_BD96801
This driver can also be built as a module. If so, the module
will be called bd96801-regulator.
+config REGULATOR_BQ257XX
+ tristate "TI BQ257XX regulator family"
+ depends on MFD_BQ257XX
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ Say Y to enable support for the boost regulator function of
+ the BQ257XX family of charger circuits.
+
config REGULATOR_CPCAP
tristate "Motorola CPCAP regulator"
depends on MFD_CPCAP
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 76b02d12b758..b3101376029d 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -38,6 +38,7 @@ obj-$(CONFIG_REGULATOR_BD71828) += bd71828-regulator.o
obj-$(CONFIG_REGULATOR_BD718XX) += bd718x7-regulator.o
obj-$(CONFIG_REGULATOR_BD9571MWV) += bd9571mwv-regulator.o
obj-$(CONFIG_REGULATOR_BD957XMUF) += bd9576-regulator.o
+obj-$(CONFIG_REGULATOR_BQ257XX) += bq257xx-regulator.o
obj-$(CONFIG_REGULATOR_DA903X) += da903x-regulator.o
obj-$(CONFIG_REGULATOR_BD96801) += bd96801-regulator.o
obj-$(CONFIG_REGULATOR_DA9052) += da9052-regulator.o
diff --git a/drivers/regulator/bq257xx-regulator.c b/drivers/regulator/bq257xx-regulator.c
new file mode 100644
index 000000000000..fc1ccede4468
--- /dev/null
+++ b/drivers/regulator/bq257xx-regulator.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * BQ257XX Battery Charger Driver
+ * Copyright (C) 2025 Chris Morgan <macromorgan@hotmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mfd/bq257xx.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+
+struct bq257xx_reg_data {
+ struct bq257xx_device *bq;
+ struct regulator_dev *bq257xx_reg;
+ struct gpio_desc *otg_en_gpio;
+ struct regulator_desc desc;
+};
+
+static int bq25703_vbus_get_cur_limit(struct regulator_dev *rdev)
+{
+ struct bq257xx_reg_data *pdata = rdev_get_drvdata(rdev);
+ int ret;
+ unsigned int reg;
+
+ ret = regmap_read(pdata->bq->regmap, BQ25703_OTG_CURRENT, &reg);
+ if (ret)
+ return ret;
+ return FIELD_GET(BQ25703_OTG_CUR_MASK, reg) * BQ25703_OTG_CUR_STEP_UA;
+}
+
+/*
+ * Check if the minimum current and maximum current requested are
+ * sane values, then set the register accordingly.
+ */
+static int bq25703_vbus_set_cur_limit(struct regulator_dev *rdev,
+ int min_uA, int max_uA)
+{
+ struct bq257xx_reg_data *pdata = rdev_get_drvdata(rdev);
+ unsigned int reg;
+
+ if ((min_uA > BQ25703_OTG_CUR_MAX_UA) || (max_uA < 0))
+ return -EINVAL;
+
+ reg = (max_uA / BQ25703_OTG_CUR_STEP_UA);
+
+ /* Catch rounding errors since our step is 50000uA. */
+ if ((reg * BQ25703_OTG_CUR_STEP_UA) < min_uA)
+ return -EINVAL;
+
+ return regmap_write(pdata->bq->regmap, BQ25703_OTG_CURRENT,
+ FIELD_PREP(BQ25703_OTG_CUR_MASK, reg));
+}
+
+static int bq25703_vbus_enable(struct regulator_dev *rdev)
+{
+ struct bq257xx_reg_data *pdata = rdev_get_drvdata(rdev);
+
+ if (pdata->otg_en_gpio)
+ gpiod_set_value_cansleep(pdata->otg_en_gpio, 1);
+ return regulator_enable_regmap(rdev);
+}
+
+static int bq25703_vbus_disable(struct regulator_dev *rdev)
+{
+ struct bq257xx_reg_data *pdata = rdev_get_drvdata(rdev);
+
+ if (pdata->otg_en_gpio)
+ gpiod_set_value_cansleep(pdata->otg_en_gpio, 0);
+ return regulator_disable_regmap(rdev);
+}
+
+static const struct regulator_ops bq25703_vbus_ops = {
+ .enable = bq25703_vbus_enable,
+ .disable = bq25703_vbus_disable,
+ .is_enabled = regulator_is_enabled_regmap,
+ .list_voltage = regulator_list_voltage_linear,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+ .get_current_limit = bq25703_vbus_get_cur_limit,
+ .set_current_limit = bq25703_vbus_set_cur_limit,
+};
+
+static const struct regulator_desc bq25703_vbus_desc = {
+ .name = "vbus",
+ .of_match = of_match_ptr("vbus"),
+ .regulators_node = of_match_ptr("regulators"),
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+ .ops = &bq25703_vbus_ops,
+ .min_uV = BQ25703_OTG_VOLT_MIN_UV,
+ .uV_step = BQ25703_OTG_VOLT_STEP_UV,
+ .n_voltages = BQ25703_OTG_VOLT_NUM_VOLT,
+ .enable_mask = BQ25703_EN_OTG_MASK,
+ .enable_reg = BQ25703_CHARGE_OPTION_3,
+ .enable_val = BQ25703_EN_OTG_MASK,
+ .disable_val = 0,
+ .vsel_reg = BQ25703_OTG_VOLT,
+ .vsel_mask = BQ25703_OTG_VOLT_MASK,
+};
+
+/* Get optional GPIO for OTG regulator enable. */
+static void bq257xx_reg_dt_parse_gpio(struct platform_device *pdev)
+{
+ struct device_node *child, *subchild;
+ struct bq257xx_reg_data *pdata = platform_get_drvdata(pdev);
+
+ child = of_get_child_by_name(pdev->dev.of_node,
+ pdata->desc.regulators_node);
+ if (!child)
+ return;
+
+ subchild = of_get_child_by_name(child, pdata->desc.of_match);
+ if (!subchild)
+ return;
+
+ of_node_put(child);
+
+ pdata->otg_en_gpio = devm_fwnode_gpiod_get_index(&pdev->dev,
+ of_fwnode_handle(subchild),
+ "enable", 0,
+ GPIOD_OUT_LOW,
+ pdata->desc.of_match);
+
+ of_node_put(subchild);
+
+ if (IS_ERR(pdata->otg_en_gpio)) {
+ dev_err(&pdev->dev, "Error getting enable gpio: %ld\n",
+ PTR_ERR(pdata->otg_en_gpio));
+ return;
+ }
+}
+
+static int bq257xx_regulator_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct bq257xx_device *bq = dev_get_drvdata(pdev->dev.parent);
+ struct bq257xx_reg_data *pdata;
+ struct device_node *np = dev->of_node;
+ struct regulator_config cfg = {};
+
+ pdev->dev.of_node = pdev->dev.parent->of_node;
+ pdev->dev.of_node_reused = true;
+
+ pdata = devm_kzalloc(&pdev->dev, sizeof(struct bq257xx_reg_data), GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+
+ pdata->bq = bq;
+ pdata->desc = bq25703_vbus_desc;
+
+ platform_set_drvdata(pdev, pdata);
+ bq257xx_reg_dt_parse_gpio(pdev);
+
+ cfg.dev = &pdev->dev;
+ cfg.driver_data = pdata;
+ cfg.of_node = np;
+ cfg.regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!cfg.regmap)
+ return -ENODEV;
+
+ pdata->bq257xx_reg = devm_regulator_register(dev, &pdata->desc, &cfg);
+ if (IS_ERR(pdata->bq257xx_reg)) {
+ return dev_err_probe(&pdev->dev, PTR_ERR(pdata->bq257xx_reg),
+ "error registering bq257xx regulator");
+ }
+
+ return 0;
+}
+
+static struct platform_driver bq257xx_reg_driver = {
+ .driver = {
+ .name = "bq257xx-regulator",
+ },
+ .probe = bq257xx_regulator_probe,
+};
+
+module_platform_driver(bq257xx_reg_driver);
+
+MODULE_DESCRIPTION("bq257xx regulator driver");
+MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/rtc/rtc-mc13xxx.c b/drivers/rtc/rtc-mc13xxx.c
index e7b87130e624..2494d13fd767 100644
--- a/drivers/rtc/rtc-mc13xxx.c
+++ b/drivers/rtc/rtc-mc13xxx.c
@@ -137,10 +137,6 @@ static int mc13xxx_rtc_set_time(struct device *dev, struct rtc_time *tm)
}
if (!priv->valid) {
- ret = mc13xxx_irq_ack(priv->mc13xxx, MC13XXX_IRQ_RTCRST);
- if (unlikely(ret))
- goto out;
-
ret = mc13xxx_irq_unmask(priv->mc13xxx, MC13XXX_IRQ_RTCRST);
}
@@ -208,10 +204,6 @@ static int mc13xxx_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
if (unlikely(ret))
goto out;
- ret = mc13xxx_irq_ack(priv->mc13xxx, MC13XXX_IRQ_TODA);
- if (unlikely(ret))
- goto out;
-
s1970 = rtc_tm_to_time64(&alarm->time);
dev_dbg(dev, "%s: %s %lld\n", __func__, alarm->enabled ? "on" : "off",
@@ -239,12 +231,9 @@ out:
static irqreturn_t mc13xxx_rtc_alarm_handler(int irq, void *dev)
{
struct mc13xxx_rtc *priv = dev;
- struct mc13xxx *mc13xxx = priv->mc13xxx;
rtc_update_irq(priv->rtc, 1, RTC_IRQF | RTC_AF);
- mc13xxx_irq_ack(mc13xxx, irq);
-
return IRQ_HANDLED;
}
@@ -293,8 +282,6 @@ static int __init mc13xxx_rtc_probe(struct platform_device *pdev)
mc13xxx_lock(mc13xxx);
- mc13xxx_irq_ack(mc13xxx, MC13XXX_IRQ_RTCRST);
-
ret = mc13xxx_irq_request(mc13xxx, MC13XXX_IRQ_RTCRST,
mc13xxx_rtc_reset_handler, DRIVER_NAME, priv);
if (ret)