root/drivers/misc/tps6594-pfsm.c
// SPDX-License-Identifier: GPL-2.0
/*
 * PFSM (Pre-configurable Finite State Machine) driver for the following
 * PMICs:
 * - LP8764
 * - TPS65224
 * - TPS652G1
 * - TPS6594
 * - TPS6593
 *
 * Copyright (C) 2023 BayLibre Incorporated - https://www.baylibre.com/
 */

#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/ioctl.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>

#include <linux/mfd/tps6594.h>

#include <linux/tps6594_pfsm.h>

#define TPS6594_STARTUP_DEST_MCU_ONLY_VAL 2
#define TPS6594_STARTUP_DEST_ACTIVE_VAL   3
#define TPS6594_STARTUP_DEST_SHIFT        5
#define TPS6594_STARTUP_DEST_MCU_ONLY     (TPS6594_STARTUP_DEST_MCU_ONLY_VAL \
                                           << TPS6594_STARTUP_DEST_SHIFT)
#define TPS6594_STARTUP_DEST_ACTIVE       (TPS6594_STARTUP_DEST_ACTIVE_VAL \
                                           << TPS6594_STARTUP_DEST_SHIFT)

/*
 * To update the PMIC firmware, the user must be able to access
 * page 0 (user registers) and page 1 (NVM control and configuration).
 */
#define TPS6594_PMIC_MAX_POS 0x200

#define TPS6594_FILE_TO_PFSM(f) container_of((f)->private_data, struct tps6594_pfsm, miscdev)

/**
 * struct tps6594_pfsm - device private data structure
 *
 * @miscdev: misc device infos
 * @regmap:  regmap for accessing the device registers
 * @chip_id: chip identifier of the device
 */
struct tps6594_pfsm {
        struct miscdevice miscdev;
        struct regmap *regmap;
        unsigned long chip_id;
};

static ssize_t tps6594_pfsm_read(struct file *f, char __user *buf,
                                 size_t count, loff_t *ppos)
{
        struct tps6594_pfsm *pfsm = TPS6594_FILE_TO_PFSM(f);
        loff_t pos = *ppos;
        unsigned int val;
        int ret;
        int i;

        if (pos < 0)
                return -EINVAL;
        if (pos >= TPS6594_PMIC_MAX_POS)
                return 0;
        if (count > TPS6594_PMIC_MAX_POS - pos)
                count = TPS6594_PMIC_MAX_POS - pos;

        for (i = 0 ; i < count ; i++) {
                ret = regmap_read(pfsm->regmap, pos + i, &val);
                if (ret)
                        return ret;

                if (put_user(val, buf + i))
                        return -EFAULT;
        }

        *ppos = pos + count;

        return count;
}

static ssize_t tps6594_pfsm_write(struct file *f, const char __user *buf,
                                  size_t count, loff_t *ppos)
{
        struct tps6594_pfsm *pfsm = TPS6594_FILE_TO_PFSM(f);
        loff_t pos = *ppos;
        char val;
        int ret;
        int i;

        if (pos < 0)
                return -EINVAL;
        if (pos >= TPS6594_PMIC_MAX_POS || !count)
                return 0;
        if (count > TPS6594_PMIC_MAX_POS - pos)
                count = TPS6594_PMIC_MAX_POS - pos;

        for (i = 0 ; i < count ; i++) {
                if (get_user(val, buf + i))
                        return -EFAULT;

                ret = regmap_write(pfsm->regmap, pos + i, val);
                if (ret)
                        return ret;
        }

        *ppos = pos + count;

        return count;
}

static int tps6594_pfsm_configure_ret_trig(struct regmap *regmap, u8 gpio_ret, u8 ddr_ret)
{
        int ret;

        if (gpio_ret)
                ret = regmap_set_bits(regmap, TPS6594_REG_FSM_I2C_TRIGGERS,
                                      TPS6594_BIT_TRIGGER_I2C(5) | TPS6594_BIT_TRIGGER_I2C(6));
        else
                ret = regmap_clear_bits(regmap, TPS6594_REG_FSM_I2C_TRIGGERS,
                                        TPS6594_BIT_TRIGGER_I2C(5) | TPS6594_BIT_TRIGGER_I2C(6));
        if (ret)
                return ret;

        if (ddr_ret)
                ret = regmap_set_bits(regmap, TPS6594_REG_FSM_I2C_TRIGGERS,
                                      TPS6594_BIT_TRIGGER_I2C(7));
        else
                ret = regmap_clear_bits(regmap, TPS6594_REG_FSM_I2C_TRIGGERS,
                                        TPS6594_BIT_TRIGGER_I2C(7));

        return ret;
}

static long tps6594_pfsm_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
        struct tps6594_pfsm *pfsm = TPS6594_FILE_TO_PFSM(f);
        struct pmic_state_opt state_opt;
        void __user *argp = (void __user *)arg;
        unsigned int regmap_reg, mask;
        int ret = -ENOIOCTLCMD;

        switch (cmd) {
        case PMIC_GOTO_STANDBY:
                /* Disable LP mode on TPS6594 Family PMIC */
                if (pfsm->chip_id != TPS65224 && pfsm->chip_id != TPS652G1) {
                        ret = regmap_clear_bits(pfsm->regmap, TPS6594_REG_RTC_CTRL_2,
                                                TPS6594_BIT_LP_STANDBY_SEL);

                        if (ret)
                                return ret;
                }

                /* Force trigger */
                ret = regmap_write_bits(pfsm->regmap, TPS6594_REG_FSM_I2C_TRIGGERS,
                                        TPS6594_BIT_TRIGGER_I2C(0), TPS6594_BIT_TRIGGER_I2C(0));
                break;
        case PMIC_GOTO_LP_STANDBY:
                /* TPS65224/TPS652G1 does not support LP STANDBY */
                if (pfsm->chip_id == TPS65224 || pfsm->chip_id == TPS652G1)
                        return ret;

                /* Enable LP mode */
                ret = regmap_set_bits(pfsm->regmap, TPS6594_REG_RTC_CTRL_2,
                                      TPS6594_BIT_LP_STANDBY_SEL);
                if (ret)
                        return ret;

                /* Force trigger */
                ret = regmap_write_bits(pfsm->regmap, TPS6594_REG_FSM_I2C_TRIGGERS,
                                        TPS6594_BIT_TRIGGER_I2C(0), TPS6594_BIT_TRIGGER_I2C(0));
                break;
        case PMIC_UPDATE_PGM:
                /* Force trigger */
                ret = regmap_write_bits(pfsm->regmap, TPS6594_REG_FSM_I2C_TRIGGERS,
                                        TPS6594_BIT_TRIGGER_I2C(3), TPS6594_BIT_TRIGGER_I2C(3));
                break;
        case PMIC_SET_ACTIVE_STATE:
                /* Modify NSLEEP1-2 bits */
                ret = regmap_set_bits(pfsm->regmap, TPS6594_REG_FSM_NSLEEP_TRIGGERS,
                                      TPS6594_BIT_NSLEEP1B | TPS6594_BIT_NSLEEP2B);
                break;
        case PMIC_SET_MCU_ONLY_STATE:
                /* TPS65224/TPS652G1 does not support MCU_ONLY_STATE */
                if (pfsm->chip_id == TPS65224 || pfsm->chip_id == TPS652G1)
                        return ret;

                if (copy_from_user(&state_opt, argp, sizeof(state_opt)))
                        return -EFAULT;

                /* Configure retention triggers */
                ret = tps6594_pfsm_configure_ret_trig(pfsm->regmap, state_opt.gpio_retention,
                                                      state_opt.ddr_retention);
                if (ret)
                        return ret;

                /* Modify NSLEEP1-2 bits */
                ret = regmap_clear_bits(pfsm->regmap, TPS6594_REG_FSM_NSLEEP_TRIGGERS,
                                        TPS6594_BIT_NSLEEP1B);
                if (ret)
                        return ret;

                ret = regmap_set_bits(pfsm->regmap, TPS6594_REG_FSM_NSLEEP_TRIGGERS,
                                      TPS6594_BIT_NSLEEP2B);
                break;
        case PMIC_SET_RETENTION_STATE:
                if (copy_from_user(&state_opt, argp, sizeof(state_opt)))
                        return -EFAULT;

                /* Configure wake-up destination */
                if (pfsm->chip_id == TPS65224 || pfsm->chip_id == TPS652G1) {
                        regmap_reg = TPS65224_REG_STARTUP_CTRL;
                        mask = TPS65224_MASK_STARTUP_DEST;
                } else {
                        regmap_reg = TPS6594_REG_RTC_CTRL_2;
                        mask = TPS6594_MASK_STARTUP_DEST;
                }

                if (state_opt.mcu_only_startup_dest)
                        ret = regmap_write_bits(pfsm->regmap, regmap_reg,
                                                mask, TPS6594_STARTUP_DEST_MCU_ONLY);
                else
                        ret = regmap_write_bits(pfsm->regmap, regmap_reg,
                                                mask, TPS6594_STARTUP_DEST_ACTIVE);
                if (ret)
                        return ret;

                /* Configure retention triggers */
                ret = tps6594_pfsm_configure_ret_trig(pfsm->regmap, state_opt.gpio_retention,
                                                      state_opt.ddr_retention);
                if (ret)
                        return ret;

                /* Modify NSLEEP1-2 bits */
                if (pfsm->chip_id == TPS65224 || pfsm->chip_id == TPS652G1)
                        ret = regmap_clear_bits(pfsm->regmap,
                                                TPS6594_REG_FSM_NSLEEP_TRIGGERS,
                                                TPS6594_BIT_NSLEEP1B);
                else
                        ret = regmap_clear_bits(pfsm->regmap,
                                                TPS6594_REG_FSM_NSLEEP_TRIGGERS,
                                                TPS6594_BIT_NSLEEP2B);
                break;
        }

        return ret;
}

static const struct file_operations tps6594_pfsm_fops = {
        .owner          = THIS_MODULE,
        .llseek         = generic_file_llseek,
        .read           = tps6594_pfsm_read,
        .write          = tps6594_pfsm_write,
        .unlocked_ioctl = tps6594_pfsm_ioctl,
        .compat_ioctl   = compat_ptr_ioctl,
};

static irqreturn_t tps6594_pfsm_isr(int irq, void *dev_id)
{
        struct platform_device *pdev = dev_id;
        int i;

        for (i = 0 ; i < pdev->num_resources ; i++) {
                if (irq == platform_get_irq_byname(pdev, pdev->resource[i].name)) {
                        dev_err(pdev->dev.parent, "%s event detected\n", pdev->resource[i].name);
                        return IRQ_HANDLED;
                }
        }

        return IRQ_NONE;
}

static int tps6594_pfsm_probe(struct platform_device *pdev)
{
        struct tps6594_pfsm *pfsm;
        struct tps6594 *tps = dev_get_drvdata(pdev->dev.parent);
        struct device *dev = &pdev->dev;
        int irq;
        int ret;
        int i;

        pfsm = devm_kzalloc(dev, sizeof(struct tps6594_pfsm), GFP_KERNEL);
        if (!pfsm)
                return -ENOMEM;

        pfsm->regmap = tps->regmap;

        pfsm->miscdev.minor = MISC_DYNAMIC_MINOR;
        pfsm->miscdev.name = devm_kasprintf(dev, GFP_KERNEL, "pfsm-%ld-0x%02x",
                                            tps->chip_id, tps->reg);
        if (!pfsm->miscdev.name)
                return -ENOMEM;

        pfsm->miscdev.fops = &tps6594_pfsm_fops;
        pfsm->miscdev.parent = dev->parent;
        pfsm->chip_id = tps->chip_id;

        for (i = 0 ; i < pdev->num_resources ; i++) {
                irq = platform_get_irq_byname(pdev, pdev->resource[i].name);
                if (irq < 0)
                        return irq;

                ret = devm_request_threaded_irq(dev, irq, NULL,
                                                tps6594_pfsm_isr, IRQF_ONESHOT,
                                                pdev->resource[i].name, pdev);
                if (ret)
                        return dev_err_probe(dev, ret, "Failed to request irq\n");
        }

        platform_set_drvdata(pdev, pfsm);

        return misc_register(&pfsm->miscdev);
}

static void tps6594_pfsm_remove(struct platform_device *pdev)
{
        struct tps6594_pfsm *pfsm = platform_get_drvdata(pdev);

        misc_deregister(&pfsm->miscdev);
}

static struct platform_driver tps6594_pfsm_driver = {
        .driver = {
                .name = "tps6594-pfsm",
        },
        .probe = tps6594_pfsm_probe,
        .remove = tps6594_pfsm_remove,
};

module_platform_driver(tps6594_pfsm_driver);

MODULE_ALIAS("platform:tps6594-pfsm");
MODULE_AUTHOR("Julien Panis <jpanis@baylibre.com>");
MODULE_DESCRIPTION("TPS6594 Pre-configurable Finite State Machine Driver");
MODULE_LICENSE("GPL");