linux/drivers/mfd/atc260x-core.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Core support for ATC260x PMICs
   4 *
   5 * Copyright (C) 2019 Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
   6 * Copyright (C) 2020 Cristian Ciocaltea <cristian.ciocaltea@gmail.com>
   7 */
   8
   9#include <linux/interrupt.h>
  10#include <linux/mfd/atc260x/core.h>
  11#include <linux/mfd/core.h>
  12#include <linux/module.h>
  13#include <linux/of.h>
  14#include <linux/of_device.h>
  15#include <linux/regmap.h>
  16
  17#define ATC260X_CHIP_REV_MAX    31
  18
  19struct atc260x_init_regs {
  20        unsigned int cmu_devrst;
  21        unsigned int cmu_devrst_ints;
  22        unsigned int ints_msk;
  23        unsigned int pad_en;
  24        unsigned int pad_en_extirq;
  25};
  26
  27static void regmap_lock_mutex(void *__mutex)
  28{
  29        struct mutex *mutex = __mutex;
  30
  31        /*
  32         * Using regmap within an atomic context (e.g. accessing a PMIC when
  33         * powering system down) is normally allowed only if the regmap type
  34         * is MMIO and the regcache type is either REGCACHE_NONE or
  35         * REGCACHE_FLAT. For slow buses like I2C and SPI, the regmap is
  36         * internally protected by a mutex which is acquired non-atomically.
  37         *
  38         * Let's improve this by using a customized locking scheme inspired
  39         * from I2C atomic transfer. See i2c_in_atomic_xfer_mode() for a
  40         * starting point.
  41         */
  42        if (system_state > SYSTEM_RUNNING && irqs_disabled())
  43                mutex_trylock(mutex);
  44        else
  45                mutex_lock(mutex);
  46}
  47
  48static void regmap_unlock_mutex(void *__mutex)
  49{
  50        struct mutex *mutex = __mutex;
  51
  52        mutex_unlock(mutex);
  53}
  54
  55static const struct regmap_config atc2603c_regmap_config = {
  56        .reg_bits = 8,
  57        .val_bits = 16,
  58        .max_register = ATC2603C_SADDR,
  59        .cache_type = REGCACHE_NONE,
  60};
  61
  62static const struct regmap_config atc2609a_regmap_config = {
  63        .reg_bits = 8,
  64        .val_bits = 16,
  65        .max_register = ATC2609A_SADDR,
  66        .cache_type = REGCACHE_NONE,
  67};
  68
  69static const struct regmap_irq atc2603c_regmap_irqs[] = {
  70        REGMAP_IRQ_REG(ATC2603C_IRQ_AUDIO,      0, ATC2603C_INTS_MSK_AUDIO),
  71        REGMAP_IRQ_REG(ATC2603C_IRQ_OV,         0, ATC2603C_INTS_MSK_OV),
  72        REGMAP_IRQ_REG(ATC2603C_IRQ_OC,         0, ATC2603C_INTS_MSK_OC),
  73        REGMAP_IRQ_REG(ATC2603C_IRQ_OT,         0, ATC2603C_INTS_MSK_OT),
  74        REGMAP_IRQ_REG(ATC2603C_IRQ_UV,         0, ATC2603C_INTS_MSK_UV),
  75        REGMAP_IRQ_REG(ATC2603C_IRQ_ALARM,      0, ATC2603C_INTS_MSK_ALARM),
  76        REGMAP_IRQ_REG(ATC2603C_IRQ_ONOFF,      0, ATC2603C_INTS_MSK_ONOFF),
  77        REGMAP_IRQ_REG(ATC2603C_IRQ_SGPIO,      0, ATC2603C_INTS_MSK_SGPIO),
  78        REGMAP_IRQ_REG(ATC2603C_IRQ_IR,         0, ATC2603C_INTS_MSK_IR),
  79        REGMAP_IRQ_REG(ATC2603C_IRQ_REMCON,     0, ATC2603C_INTS_MSK_REMCON),
  80        REGMAP_IRQ_REG(ATC2603C_IRQ_POWER_IN,   0, ATC2603C_INTS_MSK_POWERIN),
  81};
  82
  83static const struct regmap_irq atc2609a_regmap_irqs[] = {
  84        REGMAP_IRQ_REG(ATC2609A_IRQ_AUDIO,      0, ATC2609A_INTS_MSK_AUDIO),
  85        REGMAP_IRQ_REG(ATC2609A_IRQ_OV,         0, ATC2609A_INTS_MSK_OV),
  86        REGMAP_IRQ_REG(ATC2609A_IRQ_OC,         0, ATC2609A_INTS_MSK_OC),
  87        REGMAP_IRQ_REG(ATC2609A_IRQ_OT,         0, ATC2609A_INTS_MSK_OT),
  88        REGMAP_IRQ_REG(ATC2609A_IRQ_UV,         0, ATC2609A_INTS_MSK_UV),
  89        REGMAP_IRQ_REG(ATC2609A_IRQ_ALARM,      0, ATC2609A_INTS_MSK_ALARM),
  90        REGMAP_IRQ_REG(ATC2609A_IRQ_ONOFF,      0, ATC2609A_INTS_MSK_ONOFF),
  91        REGMAP_IRQ_REG(ATC2609A_IRQ_WKUP,       0, ATC2609A_INTS_MSK_WKUP),
  92        REGMAP_IRQ_REG(ATC2609A_IRQ_IR,         0, ATC2609A_INTS_MSK_IR),
  93        REGMAP_IRQ_REG(ATC2609A_IRQ_REMCON,     0, ATC2609A_INTS_MSK_REMCON),
  94        REGMAP_IRQ_REG(ATC2609A_IRQ_POWER_IN,   0, ATC2609A_INTS_MSK_POWERIN),
  95};
  96
  97static const struct regmap_irq_chip atc2603c_regmap_irq_chip = {
  98        .name = "atc2603c",
  99        .irqs = atc2603c_regmap_irqs,
 100        .num_irqs = ARRAY_SIZE(atc2603c_regmap_irqs),
 101        .num_regs = 1,
 102        .status_base = ATC2603C_INTS_PD,
 103        .mask_base = ATC2603C_INTS_MSK,
 104        .mask_invert = true,
 105};
 106
 107static const struct regmap_irq_chip atc2609a_regmap_irq_chip = {
 108        .name = "atc2609a",
 109        .irqs = atc2609a_regmap_irqs,
 110        .num_irqs = ARRAY_SIZE(atc2609a_regmap_irqs),
 111        .num_regs = 1,
 112        .status_base = ATC2609A_INTS_PD,
 113        .mask_base = ATC2609A_INTS_MSK,
 114        .mask_invert = true,
 115};
 116
 117static const struct resource atc2603c_onkey_resources[] = {
 118        DEFINE_RES_IRQ(ATC2603C_IRQ_ONOFF),
 119};
 120
 121static const struct resource atc2609a_onkey_resources[] = {
 122        DEFINE_RES_IRQ(ATC2609A_IRQ_ONOFF),
 123};
 124
 125static const struct mfd_cell atc2603c_mfd_cells[] = {
 126        { .name = "atc260x-regulator" },
 127        { .name = "atc260x-pwrc" },
 128        {
 129                .name = "atc260x-onkey",
 130                .num_resources = ARRAY_SIZE(atc2603c_onkey_resources),
 131                .resources = atc2603c_onkey_resources,
 132        },
 133};
 134
 135static const struct mfd_cell atc2609a_mfd_cells[] = {
 136        { .name = "atc260x-regulator" },
 137        { .name = "atc260x-pwrc" },
 138        {
 139                .name = "atc260x-onkey",
 140                .num_resources = ARRAY_SIZE(atc2609a_onkey_resources),
 141                .resources = atc2609a_onkey_resources,
 142        },
 143};
 144
 145static const struct atc260x_init_regs atc2603c_init_regs = {
 146        .cmu_devrst = ATC2603C_CMU_DEVRST,
 147        .cmu_devrst_ints = ATC2603C_CMU_DEVRST_INTS,
 148        .ints_msk = ATC2603C_INTS_MSK,
 149        .pad_en = ATC2603C_PAD_EN,
 150        .pad_en_extirq = ATC2603C_PAD_EN_EXTIRQ,
 151};
 152
 153static const struct atc260x_init_regs atc2609a_init_regs = {
 154        .cmu_devrst = ATC2609A_CMU_DEVRST,
 155        .cmu_devrst_ints = ATC2609A_CMU_DEVRST_INTS,
 156        .ints_msk = ATC2609A_INTS_MSK,
 157        .pad_en = ATC2609A_PAD_EN,
 158        .pad_en_extirq = ATC2609A_PAD_EN_EXTIRQ,
 159};
 160
 161static void atc260x_cmu_reset(struct atc260x *atc260x)
 162{
 163        const struct atc260x_init_regs *regs = atc260x->init_regs;
 164
 165        /* Assert reset */
 166        regmap_update_bits(atc260x->regmap, regs->cmu_devrst,
 167                           regs->cmu_devrst_ints, ~regs->cmu_devrst_ints);
 168
 169        /* De-assert reset */
 170        regmap_update_bits(atc260x->regmap, regs->cmu_devrst,
 171                           regs->cmu_devrst_ints, regs->cmu_devrst_ints);
 172}
 173
 174static void atc260x_dev_init(struct atc260x *atc260x)
 175{
 176        const struct atc260x_init_regs *regs = atc260x->init_regs;
 177
 178        /* Initialize interrupt block */
 179        atc260x_cmu_reset(atc260x);
 180
 181        /* Disable all interrupt sources */
 182        regmap_write(atc260x->regmap, regs->ints_msk, 0);
 183
 184        /* Enable EXTIRQ pad */
 185        regmap_update_bits(atc260x->regmap, regs->pad_en,
 186                           regs->pad_en_extirq, regs->pad_en_extirq);
 187}
 188
 189/**
 190 * atc260x_match_device(): Setup ATC260x variant related fields
 191 *
 192 * @atc260x: ATC260x device to setup (.dev field must be set)
 193 * @regmap_cfg: regmap config associated with this ATC260x device
 194 *
 195 * This lets the ATC260x core configure the MFD cells and register maps
 196 * for later use.
 197 */
 198int atc260x_match_device(struct atc260x *atc260x, struct regmap_config *regmap_cfg)
 199{
 200        struct device *dev = atc260x->dev;
 201        const void *of_data;
 202
 203        of_data = of_device_get_match_data(dev);
 204        if (!of_data)
 205                return -ENODEV;
 206
 207        atc260x->ic_type = (unsigned long)of_data;
 208
 209        switch (atc260x->ic_type) {
 210        case ATC2603C:
 211                *regmap_cfg = atc2603c_regmap_config;
 212                atc260x->regmap_irq_chip = &atc2603c_regmap_irq_chip;
 213                atc260x->cells = atc2603c_mfd_cells;
 214                atc260x->nr_cells = ARRAY_SIZE(atc2603c_mfd_cells);
 215                atc260x->type_name = "atc2603c";
 216                atc260x->rev_reg = ATC2603C_CHIP_VER;
 217                atc260x->init_regs = &atc2603c_init_regs;
 218                break;
 219        case ATC2609A:
 220                *regmap_cfg = atc2609a_regmap_config;
 221                atc260x->regmap_irq_chip = &atc2609a_regmap_irq_chip;
 222                atc260x->cells = atc2609a_mfd_cells;
 223                atc260x->nr_cells = ARRAY_SIZE(atc2609a_mfd_cells);
 224                atc260x->type_name = "atc2609a";
 225                atc260x->rev_reg = ATC2609A_CHIP_VER;
 226                atc260x->init_regs = &atc2609a_init_regs;
 227                break;
 228        default:
 229                dev_err(dev, "Unsupported ATC260x device type: %u\n",
 230                        atc260x->ic_type);
 231                return -EINVAL;
 232        }
 233
 234        atc260x->regmap_mutex = devm_kzalloc(dev, sizeof(*atc260x->regmap_mutex),
 235                                             GFP_KERNEL);
 236        if (!atc260x->regmap_mutex)
 237                return -ENOMEM;
 238
 239        mutex_init(atc260x->regmap_mutex);
 240
 241        regmap_cfg->lock = regmap_lock_mutex,
 242        regmap_cfg->unlock = regmap_unlock_mutex,
 243        regmap_cfg->lock_arg = atc260x->regmap_mutex;
 244
 245        return 0;
 246}
 247EXPORT_SYMBOL_GPL(atc260x_match_device);
 248
 249/**
 250 * atc260x_device_probe(): Probe a configured ATC260x device
 251 *
 252 * @atc260x: ATC260x device to probe (must be configured)
 253 *
 254 * This function lets the ATC260x core register the ATC260x MFD devices
 255 * and IRQCHIP. The ATC260x device passed in must be fully configured
 256 * with atc260x_match_device, its IRQ set, and regmap created.
 257 */
 258int atc260x_device_probe(struct atc260x *atc260x)
 259{
 260        struct device *dev = atc260x->dev;
 261        unsigned int chip_rev;
 262        int ret;
 263
 264        if (!atc260x->irq) {
 265                dev_err(dev, "No interrupt support\n");
 266                return -EINVAL;
 267        }
 268
 269        /* Initialize the hardware */
 270        atc260x_dev_init(atc260x);
 271
 272        ret = regmap_read(atc260x->regmap, atc260x->rev_reg, &chip_rev);
 273        if (ret) {
 274                dev_err(dev, "Failed to get chip revision\n");
 275                return ret;
 276        }
 277
 278        if (chip_rev > ATC260X_CHIP_REV_MAX) {
 279                dev_err(dev, "Unknown chip revision: %u\n", chip_rev);
 280                return -EINVAL;
 281        }
 282
 283        atc260x->ic_ver = __ffs(chip_rev + 1U);
 284
 285        dev_info(dev, "Detected chip type %s rev.%c\n",
 286                 atc260x->type_name, 'A' + atc260x->ic_ver);
 287
 288        ret = devm_regmap_add_irq_chip(dev, atc260x->regmap, atc260x->irq, IRQF_ONESHOT,
 289                                       -1, atc260x->regmap_irq_chip, &atc260x->irq_data);
 290        if (ret) {
 291                dev_err(dev, "Failed to add IRQ chip: %d\n", ret);
 292                return ret;
 293        }
 294
 295        ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
 296                                   atc260x->cells, atc260x->nr_cells, NULL, 0,
 297                                   regmap_irq_get_domain(atc260x->irq_data));
 298        if (ret) {
 299                dev_err(dev, "Failed to add child devices: %d\n", ret);
 300                regmap_del_irq_chip(atc260x->irq, atc260x->irq_data);
 301        }
 302
 303        return ret;
 304}
 305EXPORT_SYMBOL_GPL(atc260x_device_probe);
 306
 307MODULE_DESCRIPTION("ATC260x PMICs Core support");
 308MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>");
 309MODULE_AUTHOR("Cristian Ciocaltea <cristian.ciocaltea@gmail.com>");
 310MODULE_LICENSE("GPL");
 311