linux/drivers/mfd/motorola-cpcap.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Motorola CPCAP PMIC core driver
   4 *
   5 * Copyright (C) 2016 Tony Lindgren <tony@atomide.com>
   6 */
   7
   8#include <linux/device.h>
   9#include <linux/err.h>
  10#include <linux/interrupt.h>
  11#include <linux/irq.h>
  12#include <linux/kernel.h>
  13#include <linux/module.h>
  14#include <linux/of_device.h>
  15#include <linux/regmap.h>
  16#include <linux/sysfs.h>
  17
  18#include <linux/mfd/core.h>
  19#include <linux/mfd/motorola-cpcap.h>
  20#include <linux/spi/spi.h>
  21
  22#define CPCAP_NR_IRQ_REG_BANKS  6
  23#define CPCAP_NR_IRQ_CHIPS      3
  24#define CPCAP_REGISTER_SIZE     4
  25#define CPCAP_REGISTER_BITS     16
  26
  27struct cpcap_ddata {
  28        struct spi_device *spi;
  29        struct regmap_irq *irqs;
  30        struct regmap_irq_chip_data *irqdata[CPCAP_NR_IRQ_CHIPS];
  31        const struct regmap_config *regmap_conf;
  32        struct regmap *regmap;
  33};
  34
  35static int cpcap_sense_irq(struct regmap *regmap, int irq)
  36{
  37        int regnum = irq / CPCAP_REGISTER_BITS;
  38        int mask = BIT(irq % CPCAP_REGISTER_BITS);
  39        int reg = CPCAP_REG_INTS1 + (regnum * CPCAP_REGISTER_SIZE);
  40        int err, val;
  41
  42        if (reg < CPCAP_REG_INTS1 || reg > CPCAP_REG_INTS4)
  43                return -EINVAL;
  44
  45        err = regmap_read(regmap, reg, &val);
  46        if (err)
  47                return err;
  48
  49        return !!(val & mask);
  50}
  51
  52int cpcap_sense_virq(struct regmap *regmap, int virq)
  53{
  54        struct regmap_irq_chip_data *d = irq_get_chip_data(virq);
  55        int irq_base = regmap_irq_chip_get_base(d);
  56
  57        return cpcap_sense_irq(regmap, virq - irq_base);
  58}
  59EXPORT_SYMBOL_GPL(cpcap_sense_virq);
  60
  61static int cpcap_check_revision(struct cpcap_ddata *cpcap)
  62{
  63        u16 vendor, rev;
  64        int ret;
  65
  66        ret = cpcap_get_vendor(&cpcap->spi->dev, cpcap->regmap, &vendor);
  67        if (ret)
  68                return ret;
  69
  70        ret = cpcap_get_revision(&cpcap->spi->dev, cpcap->regmap, &rev);
  71        if (ret)
  72                return ret;
  73
  74        dev_info(&cpcap->spi->dev, "CPCAP vendor: %s rev: %i.%i (%x)\n",
  75                 vendor == CPCAP_VENDOR_ST ? "ST" : "TI",
  76                 CPCAP_REVISION_MAJOR(rev), CPCAP_REVISION_MINOR(rev),
  77                 rev);
  78
  79        if (rev < CPCAP_REVISION_2_1) {
  80                dev_info(&cpcap->spi->dev,
  81                         "Please add old CPCAP revision support as needed\n");
  82                return -ENODEV;
  83        }
  84
  85        return 0;
  86}
  87
  88/*
  89 * First two irq chips are the two private macro interrupt chips, the third
  90 * irq chip is for register banks 1 - 4 and is available for drivers to use.
  91 */
  92static struct regmap_irq_chip cpcap_irq_chip[CPCAP_NR_IRQ_CHIPS] = {
  93        {
  94                .name = "cpcap-m2",
  95                .num_regs = 1,
  96                .status_base = CPCAP_REG_MI1,
  97                .ack_base = CPCAP_REG_MI1,
  98                .mask_base = CPCAP_REG_MIM1,
  99                .use_ack = true,
 100                .ack_invert = true,
 101        },
 102        {
 103                .name = "cpcap-m2",
 104                .num_regs = 1,
 105                .status_base = CPCAP_REG_MI2,
 106                .ack_base = CPCAP_REG_MI2,
 107                .mask_base = CPCAP_REG_MIM2,
 108                .use_ack = true,
 109                .ack_invert = true,
 110        },
 111        {
 112                .name = "cpcap1-4",
 113                .num_regs = 4,
 114                .status_base = CPCAP_REG_INT1,
 115                .ack_base = CPCAP_REG_INT1,
 116                .mask_base = CPCAP_REG_INTM1,
 117                .use_ack = true,
 118                .ack_invert = true,
 119        },
 120};
 121
 122static void cpcap_init_one_regmap_irq(struct cpcap_ddata *cpcap,
 123                                      struct regmap_irq *rirq,
 124                                      int irq_base, int irq)
 125{
 126        unsigned int reg_offset;
 127        unsigned int bit, mask;
 128
 129        reg_offset = irq - irq_base;
 130        reg_offset /= cpcap->regmap_conf->val_bits;
 131        reg_offset *= cpcap->regmap_conf->reg_stride;
 132
 133        bit = irq % cpcap->regmap_conf->val_bits;
 134        mask = (1 << bit);
 135
 136        rirq->reg_offset = reg_offset;
 137        rirq->mask = mask;
 138}
 139
 140static int cpcap_init_irq_chip(struct cpcap_ddata *cpcap, int irq_chip,
 141                               int irq_start, int nr_irqs)
 142{
 143        struct regmap_irq_chip *chip = &cpcap_irq_chip[irq_chip];
 144        int i, ret;
 145
 146        for (i = irq_start; i < irq_start + nr_irqs; i++) {
 147                struct regmap_irq *rirq = &cpcap->irqs[i];
 148
 149                cpcap_init_one_regmap_irq(cpcap, rirq, irq_start, i);
 150        }
 151        chip->irqs = &cpcap->irqs[irq_start];
 152        chip->num_irqs = nr_irqs;
 153        chip->irq_drv_data = cpcap;
 154
 155        ret = devm_regmap_add_irq_chip(&cpcap->spi->dev, cpcap->regmap,
 156                                       cpcap->spi->irq,
 157                                       irq_get_trigger_type(cpcap->spi->irq) |
 158                                       IRQF_SHARED, -1,
 159                                       chip, &cpcap->irqdata[irq_chip]);
 160        if (ret) {
 161                dev_err(&cpcap->spi->dev, "could not add irq chip %i: %i\n",
 162                        irq_chip, ret);
 163                return ret;
 164        }
 165
 166        return 0;
 167}
 168
 169static int cpcap_init_irq(struct cpcap_ddata *cpcap)
 170{
 171        int ret;
 172
 173        cpcap->irqs = devm_kzalloc(&cpcap->spi->dev,
 174                                   array3_size(sizeof(*cpcap->irqs),
 175                                               CPCAP_NR_IRQ_REG_BANKS,
 176                                               cpcap->regmap_conf->val_bits),
 177                                   GFP_KERNEL);
 178        if (!cpcap->irqs)
 179                return -ENOMEM;
 180
 181        ret = cpcap_init_irq_chip(cpcap, 0, 0, 16);
 182        if (ret)
 183                return ret;
 184
 185        ret = cpcap_init_irq_chip(cpcap, 1, 16, 16);
 186        if (ret)
 187                return ret;
 188
 189        ret = cpcap_init_irq_chip(cpcap, 2, 32, 64);
 190        if (ret)
 191                return ret;
 192
 193        enable_irq_wake(cpcap->spi->irq);
 194
 195        return 0;
 196}
 197
 198static const struct of_device_id cpcap_of_match[] = {
 199        { .compatible = "motorola,cpcap", },
 200        { .compatible = "st,6556002", },
 201        {},
 202};
 203MODULE_DEVICE_TABLE(of, cpcap_of_match);
 204
 205static const struct regmap_config cpcap_regmap_config = {
 206        .reg_bits = 16,
 207        .reg_stride = 4,
 208        .pad_bits = 0,
 209        .val_bits = 16,
 210        .write_flag_mask = 0x8000,
 211        .max_register = CPCAP_REG_ST_TEST2,
 212        .cache_type = REGCACHE_NONE,
 213        .reg_format_endian = REGMAP_ENDIAN_LITTLE,
 214        .val_format_endian = REGMAP_ENDIAN_LITTLE,
 215};
 216
 217#ifdef CONFIG_PM_SLEEP
 218static int cpcap_suspend(struct device *dev)
 219{
 220        struct spi_device *spi = to_spi_device(dev);
 221
 222        disable_irq(spi->irq);
 223
 224        return 0;
 225}
 226
 227static int cpcap_resume(struct device *dev)
 228{
 229        struct spi_device *spi = to_spi_device(dev);
 230
 231        enable_irq(spi->irq);
 232
 233        return 0;
 234}
 235#endif
 236
 237static SIMPLE_DEV_PM_OPS(cpcap_pm, cpcap_suspend, cpcap_resume);
 238
 239static const struct mfd_cell cpcap_mfd_devices[] = {
 240        {
 241                .name          = "cpcap_adc",
 242                .of_compatible = "motorola,mapphone-cpcap-adc",
 243        }, {
 244                .name          = "cpcap_battery",
 245                .of_compatible = "motorola,cpcap-battery",
 246        }, {
 247                .name          = "cpcap-charger",
 248                .of_compatible = "motorola,mapphone-cpcap-charger",
 249        }, {
 250                .name          = "cpcap-regulator",
 251                .of_compatible = "motorola,mapphone-cpcap-regulator",
 252        }, {
 253                .name          = "cpcap-rtc",
 254                .of_compatible = "motorola,cpcap-rtc",
 255        }, {
 256                .name          = "cpcap-pwrbutton",
 257                .of_compatible = "motorola,cpcap-pwrbutton",
 258        }, {
 259                .name          = "cpcap-usb-phy",
 260                .of_compatible = "motorola,mapphone-cpcap-usb-phy",
 261        }, {
 262                .name          = "cpcap-led",
 263                .id            = 0,
 264                .of_compatible = "motorola,cpcap-led-red",
 265        }, {
 266                .name          = "cpcap-led",
 267                .id            = 1,
 268                .of_compatible = "motorola,cpcap-led-green",
 269        }, {
 270                .name          = "cpcap-led",
 271                .id            = 2,
 272                .of_compatible = "motorola,cpcap-led-blue",
 273        }, {
 274                .name          = "cpcap-led",
 275                .id            = 3,
 276                .of_compatible = "motorola,cpcap-led-adl",
 277        }, {
 278                .name          = "cpcap-led",
 279                .id            = 4,
 280                .of_compatible = "motorola,cpcap-led-cp",
 281        }, {
 282                .name          = "cpcap-codec",
 283        }
 284};
 285
 286static int cpcap_probe(struct spi_device *spi)
 287{
 288        const struct of_device_id *match;
 289        struct cpcap_ddata *cpcap;
 290        int ret;
 291
 292        match = of_match_device(of_match_ptr(cpcap_of_match), &spi->dev);
 293        if (!match)
 294                return -ENODEV;
 295
 296        cpcap = devm_kzalloc(&spi->dev, sizeof(*cpcap), GFP_KERNEL);
 297        if (!cpcap)
 298                return -ENOMEM;
 299
 300        cpcap->spi = spi;
 301        spi_set_drvdata(spi, cpcap);
 302
 303        spi->bits_per_word = 16;
 304        spi->mode = SPI_MODE_0 | SPI_CS_HIGH;
 305
 306        ret = spi_setup(spi);
 307        if (ret)
 308                return ret;
 309
 310        cpcap->regmap_conf = &cpcap_regmap_config;
 311        cpcap->regmap = devm_regmap_init_spi(spi, &cpcap_regmap_config);
 312        if (IS_ERR(cpcap->regmap)) {
 313                ret = PTR_ERR(cpcap->regmap);
 314                dev_err(&cpcap->spi->dev, "Failed to initialize regmap: %d\n",
 315                        ret);
 316
 317                return ret;
 318        }
 319
 320        ret = cpcap_check_revision(cpcap);
 321        if (ret) {
 322                dev_err(&cpcap->spi->dev, "Failed to detect CPCAP: %i\n", ret);
 323                return ret;
 324        }
 325
 326        ret = cpcap_init_irq(cpcap);
 327        if (ret)
 328                return ret;
 329
 330        return devm_mfd_add_devices(&spi->dev, 0, cpcap_mfd_devices,
 331                                    ARRAY_SIZE(cpcap_mfd_devices), NULL, 0, NULL);
 332}
 333
 334static struct spi_driver cpcap_driver = {
 335        .driver = {
 336                .name = "cpcap-core",
 337                .of_match_table = cpcap_of_match,
 338                .pm = &cpcap_pm,
 339        },
 340        .probe = cpcap_probe,
 341};
 342module_spi_driver(cpcap_driver);
 343
 344MODULE_ALIAS("platform:cpcap");
 345MODULE_DESCRIPTION("CPCAP driver");
 346MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
 347MODULE_LICENSE("GPL v2");
 348