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                .clear_ack = 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                .clear_ack = 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                .clear_ack = 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 spi_device_id cpcap_spi_ids[] = {
 206        { .name = "cpcap", },
 207        { .name = "6556002", },
 208        {},
 209};
 210MODULE_DEVICE_TABLE(spi, cpcap_spi_ids);
 211
 212static const struct regmap_config cpcap_regmap_config = {
 213        .reg_bits = 16,
 214        .reg_stride = 4,
 215        .pad_bits = 0,
 216        .val_bits = 16,
 217        .write_flag_mask = 0x8000,
 218        .max_register = CPCAP_REG_ST_TEST2,
 219        .cache_type = REGCACHE_NONE,
 220        .reg_format_endian = REGMAP_ENDIAN_LITTLE,
 221        .val_format_endian = REGMAP_ENDIAN_LITTLE,
 222};
 223
 224#ifdef CONFIG_PM_SLEEP
 225static int cpcap_suspend(struct device *dev)
 226{
 227        struct spi_device *spi = to_spi_device(dev);
 228
 229        disable_irq(spi->irq);
 230
 231        return 0;
 232}
 233
 234static int cpcap_resume(struct device *dev)
 235{
 236        struct spi_device *spi = to_spi_device(dev);
 237
 238        enable_irq(spi->irq);
 239
 240        return 0;
 241}
 242#endif
 243
 244static SIMPLE_DEV_PM_OPS(cpcap_pm, cpcap_suspend, cpcap_resume);
 245
 246static const struct mfd_cell cpcap_mfd_devices[] = {
 247        {
 248                .name          = "cpcap_adc",
 249                .of_compatible = "motorola,mapphone-cpcap-adc",
 250        }, {
 251                .name          = "cpcap_battery",
 252                .of_compatible = "motorola,cpcap-battery",
 253        }, {
 254                .name          = "cpcap-charger",
 255                .of_compatible = "motorola,mapphone-cpcap-charger",
 256        }, {
 257                .name          = "cpcap-regulator",
 258                .of_compatible = "motorola,mapphone-cpcap-regulator",
 259        }, {
 260                .name          = "cpcap-rtc",
 261                .of_compatible = "motorola,cpcap-rtc",
 262        }, {
 263                .name          = "cpcap-pwrbutton",
 264                .of_compatible = "motorola,cpcap-pwrbutton",
 265        }, {
 266                .name          = "cpcap-usb-phy",
 267                .of_compatible = "motorola,mapphone-cpcap-usb-phy",
 268        }, {
 269                .name          = "cpcap-led",
 270                .id            = 0,
 271                .of_compatible = "motorola,cpcap-led-red",
 272        }, {
 273                .name          = "cpcap-led",
 274                .id            = 1,
 275                .of_compatible = "motorola,cpcap-led-green",
 276        }, {
 277                .name          = "cpcap-led",
 278                .id            = 2,
 279                .of_compatible = "motorola,cpcap-led-blue",
 280        }, {
 281                .name          = "cpcap-led",
 282                .id            = 3,
 283                .of_compatible = "motorola,cpcap-led-adl",
 284        }, {
 285                .name          = "cpcap-led",
 286                .id            = 4,
 287                .of_compatible = "motorola,cpcap-led-cp",
 288        }, {
 289                .name          = "cpcap-codec",
 290        }
 291};
 292
 293static int cpcap_probe(struct spi_device *spi)
 294{
 295        const struct of_device_id *match;
 296        struct cpcap_ddata *cpcap;
 297        int ret;
 298
 299        match = of_match_device(of_match_ptr(cpcap_of_match), &spi->dev);
 300        if (!match)
 301                return -ENODEV;
 302
 303        cpcap = devm_kzalloc(&spi->dev, sizeof(*cpcap), GFP_KERNEL);
 304        if (!cpcap)
 305                return -ENOMEM;
 306
 307        cpcap->spi = spi;
 308        spi_set_drvdata(spi, cpcap);
 309
 310        spi->bits_per_word = 16;
 311        spi->mode = SPI_MODE_0 | SPI_CS_HIGH;
 312
 313        ret = spi_setup(spi);
 314        if (ret)
 315                return ret;
 316
 317        cpcap->regmap_conf = &cpcap_regmap_config;
 318        cpcap->regmap = devm_regmap_init_spi(spi, &cpcap_regmap_config);
 319        if (IS_ERR(cpcap->regmap)) {
 320                ret = PTR_ERR(cpcap->regmap);
 321                dev_err(&cpcap->spi->dev, "Failed to initialize regmap: %d\n",
 322                        ret);
 323
 324                return ret;
 325        }
 326
 327        ret = cpcap_check_revision(cpcap);
 328        if (ret) {
 329                dev_err(&cpcap->spi->dev, "Failed to detect CPCAP: %i\n", ret);
 330                return ret;
 331        }
 332
 333        ret = cpcap_init_irq(cpcap);
 334        if (ret)
 335                return ret;
 336
 337        /* Parent SPI controller uses DMA, CPCAP and child devices do not */
 338        spi->dev.coherent_dma_mask = 0;
 339        spi->dev.dma_mask = &spi->dev.coherent_dma_mask;
 340
 341        return devm_mfd_add_devices(&spi->dev, 0, cpcap_mfd_devices,
 342                                    ARRAY_SIZE(cpcap_mfd_devices), NULL, 0, NULL);
 343}
 344
 345static struct spi_driver cpcap_driver = {
 346        .driver = {
 347                .name = "cpcap-core",
 348                .of_match_table = cpcap_of_match,
 349                .pm = &cpcap_pm,
 350        },
 351        .probe = cpcap_probe,
 352        .id_table = cpcap_spi_ids,
 353};
 354module_spi_driver(cpcap_driver);
 355
 356MODULE_ALIAS("platform:cpcap");
 357MODULE_DESCRIPTION("CPCAP driver");
 358MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
 359MODULE_LICENSE("GPL v2");
 360