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
 217static const struct mfd_cell cpcap_mfd_devices[] = {
 218        {
 219                .name          = "cpcap_adc",
 220                .of_compatible = "motorola,mapphone-cpcap-adc",
 221        }, {
 222                .name          = "cpcap_battery",
 223                .of_compatible = "motorola,cpcap-battery",
 224        }, {
 225                .name          = "cpcap-charger",
 226                .of_compatible = "motorola,mapphone-cpcap-charger",
 227        }, {
 228                .name          = "cpcap-regulator",
 229                .of_compatible = "motorola,mapphone-cpcap-regulator",
 230        }, {
 231                .name          = "cpcap-rtc",
 232                .of_compatible = "motorola,cpcap-rtc",
 233        }, {
 234                .name          = "cpcap-pwrbutton",
 235                .of_compatible = "motorola,cpcap-pwrbutton",
 236        }, {
 237                .name          = "cpcap-usb-phy",
 238                .of_compatible = "motorola,mapphone-cpcap-usb-phy",
 239        }, {
 240                .name          = "cpcap-led",
 241                .id            = 0,
 242                .of_compatible = "motorola,cpcap-led-red",
 243        }, {
 244                .name          = "cpcap-led",
 245                .id            = 1,
 246                .of_compatible = "motorola,cpcap-led-green",
 247        }, {
 248                .name          = "cpcap-led",
 249                .id            = 2,
 250                .of_compatible = "motorola,cpcap-led-blue",
 251        }, {
 252                .name          = "cpcap-led",
 253                .id            = 3,
 254                .of_compatible = "motorola,cpcap-led-adl",
 255        }, {
 256                .name          = "cpcap-led",
 257                .id            = 4,
 258                .of_compatible = "motorola,cpcap-led-cp",
 259        }, {
 260                .name          = "cpcap-codec",
 261        }
 262};
 263
 264static int cpcap_probe(struct spi_device *spi)
 265{
 266        const struct of_device_id *match;
 267        struct cpcap_ddata *cpcap;
 268        int ret;
 269
 270        match = of_match_device(of_match_ptr(cpcap_of_match), &spi->dev);
 271        if (!match)
 272                return -ENODEV;
 273
 274        cpcap = devm_kzalloc(&spi->dev, sizeof(*cpcap), GFP_KERNEL);
 275        if (!cpcap)
 276                return -ENOMEM;
 277
 278        cpcap->spi = spi;
 279        spi_set_drvdata(spi, cpcap);
 280
 281        spi->bits_per_word = 16;
 282        spi->mode = SPI_MODE_0 | SPI_CS_HIGH;
 283
 284        ret = spi_setup(spi);
 285        if (ret)
 286                return ret;
 287
 288        cpcap->regmap_conf = &cpcap_regmap_config;
 289        cpcap->regmap = devm_regmap_init_spi(spi, &cpcap_regmap_config);
 290        if (IS_ERR(cpcap->regmap)) {
 291                ret = PTR_ERR(cpcap->regmap);
 292                dev_err(&cpcap->spi->dev, "Failed to initialize regmap: %d\n",
 293                        ret);
 294
 295                return ret;
 296        }
 297
 298        ret = cpcap_check_revision(cpcap);
 299        if (ret) {
 300                dev_err(&cpcap->spi->dev, "Failed to detect CPCAP: %i\n", ret);
 301                return ret;
 302        }
 303
 304        ret = cpcap_init_irq(cpcap);
 305        if (ret)
 306                return ret;
 307
 308        return devm_mfd_add_devices(&spi->dev, 0, cpcap_mfd_devices,
 309                                    ARRAY_SIZE(cpcap_mfd_devices), NULL, 0, NULL);
 310}
 311
 312static struct spi_driver cpcap_driver = {
 313        .driver = {
 314                .name = "cpcap-core",
 315                .of_match_table = cpcap_of_match,
 316        },
 317        .probe = cpcap_probe,
 318};
 319module_spi_driver(cpcap_driver);
 320
 321MODULE_ALIAS("platform:cpcap");
 322MODULE_DESCRIPTION("CPCAP driver");
 323MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
 324MODULE_LICENSE("GPL v2");
 325