root/drivers/mfd/atc260x-core.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Core support for ATC260x PMICs
 *
 * Copyright (C) 2019 Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
 * Copyright (C) 2020 Cristian Ciocaltea <cristian.ciocaltea@gmail.com>
 */

#include <linux/interrupt.h>
#include <linux/mfd/atc260x/core.h>
#include <linux/mfd/core.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regmap.h>

#define ATC260X_CHIP_REV_MAX    31

struct atc260x_init_regs {
        unsigned int cmu_devrst;
        unsigned int cmu_devrst_ints;
        unsigned int ints_msk;
        unsigned int pad_en;
        unsigned int pad_en_extirq;
};

static void regmap_lock_mutex(void *__mutex)
{
        struct mutex *mutex = __mutex;

        /*
         * Using regmap within an atomic context (e.g. accessing a PMIC when
         * powering system down) is normally allowed only if the regmap type
         * is MMIO and the regcache type is either REGCACHE_NONE or
         * REGCACHE_FLAT. For slow buses like I2C and SPI, the regmap is
         * internally protected by a mutex which is acquired non-atomically.
         *
         * Let's improve this by using a customized locking scheme inspired
         * from I2C atomic transfer. See i2c_in_atomic_xfer_mode() for a
         * starting point.
         */
        if (system_state > SYSTEM_RUNNING && irqs_disabled())
                mutex_trylock(mutex);
        else
                mutex_lock(mutex);
}

static void regmap_unlock_mutex(void *__mutex)
{
        struct mutex *mutex = __mutex;

        mutex_unlock(mutex);
}

static const struct regmap_config atc2603c_regmap_config = {
        .reg_bits = 8,
        .val_bits = 16,
        .max_register = ATC2603C_SADDR,
        .cache_type = REGCACHE_NONE,
};

static const struct regmap_config atc2609a_regmap_config = {
        .reg_bits = 8,
        .val_bits = 16,
        .max_register = ATC2609A_SADDR,
        .cache_type = REGCACHE_NONE,
};

static const struct regmap_irq atc2603c_regmap_irqs[] = {
        REGMAP_IRQ_REG(ATC2603C_IRQ_AUDIO,      0, ATC2603C_INTS_MSK_AUDIO),
        REGMAP_IRQ_REG(ATC2603C_IRQ_OV,         0, ATC2603C_INTS_MSK_OV),
        REGMAP_IRQ_REG(ATC2603C_IRQ_OC,         0, ATC2603C_INTS_MSK_OC),
        REGMAP_IRQ_REG(ATC2603C_IRQ_OT,         0, ATC2603C_INTS_MSK_OT),
        REGMAP_IRQ_REG(ATC2603C_IRQ_UV,         0, ATC2603C_INTS_MSK_UV),
        REGMAP_IRQ_REG(ATC2603C_IRQ_ALARM,      0, ATC2603C_INTS_MSK_ALARM),
        REGMAP_IRQ_REG(ATC2603C_IRQ_ONOFF,      0, ATC2603C_INTS_MSK_ONOFF),
        REGMAP_IRQ_REG(ATC2603C_IRQ_SGPIO,      0, ATC2603C_INTS_MSK_SGPIO),
        REGMAP_IRQ_REG(ATC2603C_IRQ_IR,         0, ATC2603C_INTS_MSK_IR),
        REGMAP_IRQ_REG(ATC2603C_IRQ_REMCON,     0, ATC2603C_INTS_MSK_REMCON),
        REGMAP_IRQ_REG(ATC2603C_IRQ_POWER_IN,   0, ATC2603C_INTS_MSK_POWERIN),
};

static const struct regmap_irq atc2609a_regmap_irqs[] = {
        REGMAP_IRQ_REG(ATC2609A_IRQ_AUDIO,      0, ATC2609A_INTS_MSK_AUDIO),
        REGMAP_IRQ_REG(ATC2609A_IRQ_OV,         0, ATC2609A_INTS_MSK_OV),
        REGMAP_IRQ_REG(ATC2609A_IRQ_OC,         0, ATC2609A_INTS_MSK_OC),
        REGMAP_IRQ_REG(ATC2609A_IRQ_OT,         0, ATC2609A_INTS_MSK_OT),
        REGMAP_IRQ_REG(ATC2609A_IRQ_UV,         0, ATC2609A_INTS_MSK_UV),
        REGMAP_IRQ_REG(ATC2609A_IRQ_ALARM,      0, ATC2609A_INTS_MSK_ALARM),
        REGMAP_IRQ_REG(ATC2609A_IRQ_ONOFF,      0, ATC2609A_INTS_MSK_ONOFF),
        REGMAP_IRQ_REG(ATC2609A_IRQ_WKUP,       0, ATC2609A_INTS_MSK_WKUP),
        REGMAP_IRQ_REG(ATC2609A_IRQ_IR,         0, ATC2609A_INTS_MSK_IR),
        REGMAP_IRQ_REG(ATC2609A_IRQ_REMCON,     0, ATC2609A_INTS_MSK_REMCON),
        REGMAP_IRQ_REG(ATC2609A_IRQ_POWER_IN,   0, ATC2609A_INTS_MSK_POWERIN),
};

static const struct regmap_irq_chip atc2603c_regmap_irq_chip = {
        .name = "atc2603c",
        .irqs = atc2603c_regmap_irqs,
        .num_irqs = ARRAY_SIZE(atc2603c_regmap_irqs),
        .num_regs = 1,
        .status_base = ATC2603C_INTS_PD,
        .unmask_base = ATC2603C_INTS_MSK,
};

static const struct regmap_irq_chip atc2609a_regmap_irq_chip = {
        .name = "atc2609a",
        .irqs = atc2609a_regmap_irqs,
        .num_irqs = ARRAY_SIZE(atc2609a_regmap_irqs),
        .num_regs = 1,
        .status_base = ATC2609A_INTS_PD,
        .unmask_base = ATC2609A_INTS_MSK,
};

static const struct resource atc2603c_onkey_resources[] = {
        DEFINE_RES_IRQ(ATC2603C_IRQ_ONOFF),
};

static const struct resource atc2609a_onkey_resources[] = {
        DEFINE_RES_IRQ(ATC2609A_IRQ_ONOFF),
};

static const struct mfd_cell atc2603c_mfd_cells[] = {
        { .name = "atc260x-regulator" },
        { .name = "atc260x-pwrc" },
        {
                .name = "atc260x-onkey",
                .num_resources = ARRAY_SIZE(atc2603c_onkey_resources),
                .resources = atc2603c_onkey_resources,
        },
};

static const struct mfd_cell atc2609a_mfd_cells[] = {
        { .name = "atc260x-regulator" },
        { .name = "atc260x-pwrc" },
        {
                .name = "atc260x-onkey",
                .num_resources = ARRAY_SIZE(atc2609a_onkey_resources),
                .resources = atc2609a_onkey_resources,
        },
};

static const struct atc260x_init_regs atc2603c_init_regs = {
        .cmu_devrst = ATC2603C_CMU_DEVRST,
        .cmu_devrst_ints = ATC2603C_CMU_DEVRST_INTS,
        .ints_msk = ATC2603C_INTS_MSK,
        .pad_en = ATC2603C_PAD_EN,
        .pad_en_extirq = ATC2603C_PAD_EN_EXTIRQ,
};

static const struct atc260x_init_regs atc2609a_init_regs = {
        .cmu_devrst = ATC2609A_CMU_DEVRST,
        .cmu_devrst_ints = ATC2609A_CMU_DEVRST_INTS,
        .ints_msk = ATC2609A_INTS_MSK,
        .pad_en = ATC2609A_PAD_EN,
        .pad_en_extirq = ATC2609A_PAD_EN_EXTIRQ,
};

static void atc260x_cmu_reset(struct atc260x *atc260x)
{
        const struct atc260x_init_regs *regs = atc260x->init_regs;

        /* Assert reset */
        regmap_update_bits(atc260x->regmap, regs->cmu_devrst,
                           regs->cmu_devrst_ints, ~regs->cmu_devrst_ints);

        /* De-assert reset */
        regmap_update_bits(atc260x->regmap, regs->cmu_devrst,
                           regs->cmu_devrst_ints, regs->cmu_devrst_ints);
}

static void atc260x_dev_init(struct atc260x *atc260x)
{
        const struct atc260x_init_regs *regs = atc260x->init_regs;

        /* Initialize interrupt block */
        atc260x_cmu_reset(atc260x);

        /* Disable all interrupt sources */
        regmap_write(atc260x->regmap, regs->ints_msk, 0);

        /* Enable EXTIRQ pad */
        regmap_update_bits(atc260x->regmap, regs->pad_en,
                           regs->pad_en_extirq, regs->pad_en_extirq);
}

/**
 * atc260x_match_device(): Setup ATC260x variant related fields
 *
 * @atc260x: ATC260x device to setup (.dev field must be set)
 * @regmap_cfg: regmap config associated with this ATC260x device
 *
 * This lets the ATC260x core configure the MFD cells and register maps
 * for later use.
 */
int atc260x_match_device(struct atc260x *atc260x, struct regmap_config *regmap_cfg)
{
        struct device *dev = atc260x->dev;
        const void *of_data;

        of_data = of_device_get_match_data(dev);
        if (!of_data)
                return -ENODEV;

        atc260x->ic_type = (unsigned long)of_data;

        switch (atc260x->ic_type) {
        case ATC2603C:
                *regmap_cfg = atc2603c_regmap_config;
                atc260x->regmap_irq_chip = &atc2603c_regmap_irq_chip;
                atc260x->cells = atc2603c_mfd_cells;
                atc260x->nr_cells = ARRAY_SIZE(atc2603c_mfd_cells);
                atc260x->type_name = "atc2603c";
                atc260x->rev_reg = ATC2603C_CHIP_VER;
                atc260x->init_regs = &atc2603c_init_regs;
                break;
        case ATC2609A:
                *regmap_cfg = atc2609a_regmap_config;
                atc260x->regmap_irq_chip = &atc2609a_regmap_irq_chip;
                atc260x->cells = atc2609a_mfd_cells;
                atc260x->nr_cells = ARRAY_SIZE(atc2609a_mfd_cells);
                atc260x->type_name = "atc2609a";
                atc260x->rev_reg = ATC2609A_CHIP_VER;
                atc260x->init_regs = &atc2609a_init_regs;
                break;
        default:
                dev_err(dev, "Unsupported ATC260x device type: %u\n",
                        atc260x->ic_type);
                return -EINVAL;
        }

        atc260x->regmap_mutex = devm_kzalloc(dev, sizeof(*atc260x->regmap_mutex),
                                             GFP_KERNEL);
        if (!atc260x->regmap_mutex)
                return -ENOMEM;

        mutex_init(atc260x->regmap_mutex);

        regmap_cfg->lock = regmap_lock_mutex;
        regmap_cfg->unlock = regmap_unlock_mutex;
        regmap_cfg->lock_arg = atc260x->regmap_mutex;

        return 0;
}
EXPORT_SYMBOL_GPL(atc260x_match_device);

/**
 * atc260x_device_probe(): Probe a configured ATC260x device
 *
 * @atc260x: ATC260x device to probe (must be configured)
 *
 * This function lets the ATC260x core register the ATC260x MFD devices
 * and IRQCHIP. The ATC260x device passed in must be fully configured
 * with atc260x_match_device, its IRQ set, and regmap created.
 */
int atc260x_device_probe(struct atc260x *atc260x)
{
        struct device *dev = atc260x->dev;
        unsigned int chip_rev;
        int ret;

        if (!atc260x->irq) {
                dev_err(dev, "No interrupt support\n");
                return -EINVAL;
        }

        /* Initialize the hardware */
        atc260x_dev_init(atc260x);

        ret = regmap_read(atc260x->regmap, atc260x->rev_reg, &chip_rev);
        if (ret) {
                dev_err(dev, "Failed to get chip revision\n");
                return ret;
        }

        if (chip_rev > ATC260X_CHIP_REV_MAX) {
                dev_err(dev, "Unknown chip revision: %u\n", chip_rev);
                return -EINVAL;
        }

        atc260x->ic_ver = __ffs(chip_rev + 1U);

        dev_info(dev, "Detected chip type %s rev.%c\n",
                 atc260x->type_name, 'A' + atc260x->ic_ver);

        ret = devm_regmap_add_irq_chip(dev, atc260x->regmap, atc260x->irq, IRQF_ONESHOT,
                                       -1, atc260x->regmap_irq_chip, &atc260x->irq_data);
        if (ret) {
                dev_err(dev, "Failed to add IRQ chip: %d\n", ret);
                return ret;
        }

        ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
                                   atc260x->cells, atc260x->nr_cells, NULL, 0,
                                   regmap_irq_get_domain(atc260x->irq_data));
        if (ret) {
                dev_err(dev, "Failed to add child devices: %d\n", ret);
                regmap_del_irq_chip(atc260x->irq, atc260x->irq_data);
        }

        return ret;
}
EXPORT_SYMBOL_GPL(atc260x_device_probe);

MODULE_DESCRIPTION("ATC260x PMICs Core support");
MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>");
MODULE_AUTHOR("Cristian Ciocaltea <cristian.ciocaltea@gmail.com>");
MODULE_LICENSE("GPL");