1*56e1d40dSTony Lindgren /* 2*56e1d40dSTony Lindgren * Motorola CPCAP PMIC core driver 3*56e1d40dSTony Lindgren * 4*56e1d40dSTony Lindgren * Copyright (C) 2016 Tony Lindgren <tony@atomide.com> 5*56e1d40dSTony Lindgren * 6*56e1d40dSTony Lindgren * This program is free software; you can redistribute it and/or modify 7*56e1d40dSTony Lindgren * it under the terms of the GNU General Public License version 2 as 8*56e1d40dSTony Lindgren * published by the Free Software Foundation. 9*56e1d40dSTony Lindgren */ 10*56e1d40dSTony Lindgren 11*56e1d40dSTony Lindgren #include <linux/device.h> 12*56e1d40dSTony Lindgren #include <linux/err.h> 13*56e1d40dSTony Lindgren #include <linux/interrupt.h> 14*56e1d40dSTony Lindgren #include <linux/irq.h> 15*56e1d40dSTony Lindgren #include <linux/kernel.h> 16*56e1d40dSTony Lindgren #include <linux/module.h> 17*56e1d40dSTony Lindgren #include <linux/of_device.h> 18*56e1d40dSTony Lindgren #include <linux/regmap.h> 19*56e1d40dSTony Lindgren #include <linux/sysfs.h> 20*56e1d40dSTony Lindgren 21*56e1d40dSTony Lindgren #include <linux/mfd/motorola-cpcap.h> 22*56e1d40dSTony Lindgren #include <linux/spi/spi.h> 23*56e1d40dSTony Lindgren 24*56e1d40dSTony Lindgren #define CPCAP_NR_IRQ_REG_BANKS 6 25*56e1d40dSTony Lindgren #define CPCAP_NR_IRQ_CHIPS 3 26*56e1d40dSTony Lindgren 27*56e1d40dSTony Lindgren struct cpcap_ddata { 28*56e1d40dSTony Lindgren struct spi_device *spi; 29*56e1d40dSTony Lindgren struct regmap_irq *irqs; 30*56e1d40dSTony Lindgren struct regmap_irq_chip_data *irqdata[CPCAP_NR_IRQ_CHIPS]; 31*56e1d40dSTony Lindgren const struct regmap_config *regmap_conf; 32*56e1d40dSTony Lindgren struct regmap *regmap; 33*56e1d40dSTony Lindgren }; 34*56e1d40dSTony Lindgren 35*56e1d40dSTony Lindgren static int cpcap_check_revision(struct cpcap_ddata *cpcap) 36*56e1d40dSTony Lindgren { 37*56e1d40dSTony Lindgren u16 vendor, rev; 38*56e1d40dSTony Lindgren int ret; 39*56e1d40dSTony Lindgren 40*56e1d40dSTony Lindgren ret = cpcap_get_vendor(&cpcap->spi->dev, cpcap->regmap, &vendor); 41*56e1d40dSTony Lindgren if (ret) 42*56e1d40dSTony Lindgren return ret; 43*56e1d40dSTony Lindgren 44*56e1d40dSTony Lindgren ret = cpcap_get_revision(&cpcap->spi->dev, cpcap->regmap, &rev); 45*56e1d40dSTony Lindgren if (ret) 46*56e1d40dSTony Lindgren return ret; 47*56e1d40dSTony Lindgren 48*56e1d40dSTony Lindgren dev_info(&cpcap->spi->dev, "CPCAP vendor: %s rev: %i.%i (%x)\n", 49*56e1d40dSTony Lindgren vendor == CPCAP_VENDOR_ST ? "ST" : "TI", 50*56e1d40dSTony Lindgren CPCAP_REVISION_MAJOR(rev), CPCAP_REVISION_MINOR(rev), 51*56e1d40dSTony Lindgren rev); 52*56e1d40dSTony Lindgren 53*56e1d40dSTony Lindgren if (rev < CPCAP_REVISION_2_1) { 54*56e1d40dSTony Lindgren dev_info(&cpcap->spi->dev, 55*56e1d40dSTony Lindgren "Please add old CPCAP revision support as needed\n"); 56*56e1d40dSTony Lindgren return -ENODEV; 57*56e1d40dSTony Lindgren } 58*56e1d40dSTony Lindgren 59*56e1d40dSTony Lindgren return 0; 60*56e1d40dSTony Lindgren } 61*56e1d40dSTony Lindgren 62*56e1d40dSTony Lindgren /* 63*56e1d40dSTony Lindgren * First two irq chips are the two private macro interrupt chips, the third 64*56e1d40dSTony Lindgren * irq chip is for register banks 1 - 4 and is available for drivers to use. 65*56e1d40dSTony Lindgren */ 66*56e1d40dSTony Lindgren static struct regmap_irq_chip cpcap_irq_chip[CPCAP_NR_IRQ_CHIPS] = { 67*56e1d40dSTony Lindgren { 68*56e1d40dSTony Lindgren .name = "cpcap-m2", 69*56e1d40dSTony Lindgren .num_regs = 1, 70*56e1d40dSTony Lindgren .status_base = CPCAP_REG_MI1, 71*56e1d40dSTony Lindgren .ack_base = CPCAP_REG_MI1, 72*56e1d40dSTony Lindgren .mask_base = CPCAP_REG_MIM1, 73*56e1d40dSTony Lindgren .use_ack = true, 74*56e1d40dSTony Lindgren }, 75*56e1d40dSTony Lindgren { 76*56e1d40dSTony Lindgren .name = "cpcap-m2", 77*56e1d40dSTony Lindgren .num_regs = 1, 78*56e1d40dSTony Lindgren .status_base = CPCAP_REG_MI2, 79*56e1d40dSTony Lindgren .ack_base = CPCAP_REG_MI2, 80*56e1d40dSTony Lindgren .mask_base = CPCAP_REG_MIM2, 81*56e1d40dSTony Lindgren .use_ack = true, 82*56e1d40dSTony Lindgren }, 83*56e1d40dSTony Lindgren { 84*56e1d40dSTony Lindgren .name = "cpcap1-4", 85*56e1d40dSTony Lindgren .num_regs = 4, 86*56e1d40dSTony Lindgren .status_base = CPCAP_REG_INT1, 87*56e1d40dSTony Lindgren .ack_base = CPCAP_REG_INT1, 88*56e1d40dSTony Lindgren .mask_base = CPCAP_REG_INTM1, 89*56e1d40dSTony Lindgren .type_base = CPCAP_REG_INTS1, 90*56e1d40dSTony Lindgren .use_ack = true, 91*56e1d40dSTony Lindgren }, 92*56e1d40dSTony Lindgren }; 93*56e1d40dSTony Lindgren 94*56e1d40dSTony Lindgren static void cpcap_init_one_regmap_irq(struct cpcap_ddata *cpcap, 95*56e1d40dSTony Lindgren struct regmap_irq *rirq, 96*56e1d40dSTony Lindgren int irq_base, int irq) 97*56e1d40dSTony Lindgren { 98*56e1d40dSTony Lindgren unsigned int reg_offset; 99*56e1d40dSTony Lindgren unsigned int bit, mask; 100*56e1d40dSTony Lindgren 101*56e1d40dSTony Lindgren reg_offset = irq - irq_base; 102*56e1d40dSTony Lindgren reg_offset /= cpcap->regmap_conf->val_bits; 103*56e1d40dSTony Lindgren reg_offset *= cpcap->regmap_conf->reg_stride; 104*56e1d40dSTony Lindgren 105*56e1d40dSTony Lindgren bit = irq % cpcap->regmap_conf->val_bits; 106*56e1d40dSTony Lindgren mask = (1 << bit); 107*56e1d40dSTony Lindgren 108*56e1d40dSTony Lindgren rirq->reg_offset = reg_offset; 109*56e1d40dSTony Lindgren rirq->mask = mask; 110*56e1d40dSTony Lindgren } 111*56e1d40dSTony Lindgren 112*56e1d40dSTony Lindgren static int cpcap_init_irq_chip(struct cpcap_ddata *cpcap, int irq_chip, 113*56e1d40dSTony Lindgren int irq_start, int nr_irqs) 114*56e1d40dSTony Lindgren { 115*56e1d40dSTony Lindgren struct regmap_irq_chip *chip = &cpcap_irq_chip[irq_chip]; 116*56e1d40dSTony Lindgren int i, ret; 117*56e1d40dSTony Lindgren 118*56e1d40dSTony Lindgren for (i = irq_start; i < irq_start + nr_irqs; i++) { 119*56e1d40dSTony Lindgren struct regmap_irq *rirq = &cpcap->irqs[i]; 120*56e1d40dSTony Lindgren 121*56e1d40dSTony Lindgren cpcap_init_one_regmap_irq(cpcap, rirq, irq_start, i); 122*56e1d40dSTony Lindgren } 123*56e1d40dSTony Lindgren chip->irqs = &cpcap->irqs[irq_start]; 124*56e1d40dSTony Lindgren chip->num_irqs = nr_irqs; 125*56e1d40dSTony Lindgren chip->irq_drv_data = cpcap; 126*56e1d40dSTony Lindgren 127*56e1d40dSTony Lindgren ret = devm_regmap_add_irq_chip(&cpcap->spi->dev, cpcap->regmap, 128*56e1d40dSTony Lindgren cpcap->spi->irq, 129*56e1d40dSTony Lindgren IRQF_TRIGGER_RISING | 130*56e1d40dSTony Lindgren IRQF_SHARED, -1, 131*56e1d40dSTony Lindgren chip, &cpcap->irqdata[irq_chip]); 132*56e1d40dSTony Lindgren if (ret) { 133*56e1d40dSTony Lindgren dev_err(&cpcap->spi->dev, "could not add irq chip %i: %i\n", 134*56e1d40dSTony Lindgren irq_chip, ret); 135*56e1d40dSTony Lindgren return ret; 136*56e1d40dSTony Lindgren } 137*56e1d40dSTony Lindgren 138*56e1d40dSTony Lindgren return 0; 139*56e1d40dSTony Lindgren } 140*56e1d40dSTony Lindgren 141*56e1d40dSTony Lindgren static int cpcap_init_irq(struct cpcap_ddata *cpcap) 142*56e1d40dSTony Lindgren { 143*56e1d40dSTony Lindgren int ret; 144*56e1d40dSTony Lindgren 145*56e1d40dSTony Lindgren cpcap->irqs = devm_kzalloc(&cpcap->spi->dev, 146*56e1d40dSTony Lindgren sizeof(*cpcap->irqs) * 147*56e1d40dSTony Lindgren CPCAP_NR_IRQ_REG_BANKS * 148*56e1d40dSTony Lindgren cpcap->regmap_conf->val_bits, 149*56e1d40dSTony Lindgren GFP_KERNEL); 150*56e1d40dSTony Lindgren if (!cpcap->irqs) 151*56e1d40dSTony Lindgren return -ENOMEM; 152*56e1d40dSTony Lindgren 153*56e1d40dSTony Lindgren ret = cpcap_init_irq_chip(cpcap, 0, 0, 16); 154*56e1d40dSTony Lindgren if (ret) 155*56e1d40dSTony Lindgren return ret; 156*56e1d40dSTony Lindgren 157*56e1d40dSTony Lindgren ret = cpcap_init_irq_chip(cpcap, 1, 16, 16); 158*56e1d40dSTony Lindgren if (ret) 159*56e1d40dSTony Lindgren return ret; 160*56e1d40dSTony Lindgren 161*56e1d40dSTony Lindgren ret = cpcap_init_irq_chip(cpcap, 2, 32, 64); 162*56e1d40dSTony Lindgren if (ret) 163*56e1d40dSTony Lindgren return ret; 164*56e1d40dSTony Lindgren 165*56e1d40dSTony Lindgren enable_irq_wake(cpcap->spi->irq); 166*56e1d40dSTony Lindgren 167*56e1d40dSTony Lindgren return 0; 168*56e1d40dSTony Lindgren } 169*56e1d40dSTony Lindgren 170*56e1d40dSTony Lindgren static const struct of_device_id cpcap_of_match[] = { 171*56e1d40dSTony Lindgren { .compatible = "motorola,cpcap", }, 172*56e1d40dSTony Lindgren { .compatible = "st,6556002", }, 173*56e1d40dSTony Lindgren {}, 174*56e1d40dSTony Lindgren }; 175*56e1d40dSTony Lindgren MODULE_DEVICE_TABLE(of, cpcap_of_match); 176*56e1d40dSTony Lindgren 177*56e1d40dSTony Lindgren static const struct regmap_config cpcap_regmap_config = { 178*56e1d40dSTony Lindgren .reg_bits = 16, 179*56e1d40dSTony Lindgren .reg_stride = 4, 180*56e1d40dSTony Lindgren .pad_bits = 0, 181*56e1d40dSTony Lindgren .val_bits = 16, 182*56e1d40dSTony Lindgren .write_flag_mask = 0x8000, 183*56e1d40dSTony Lindgren .max_register = CPCAP_REG_ST_TEST2, 184*56e1d40dSTony Lindgren .cache_type = REGCACHE_NONE, 185*56e1d40dSTony Lindgren .reg_format_endian = REGMAP_ENDIAN_LITTLE, 186*56e1d40dSTony Lindgren .val_format_endian = REGMAP_ENDIAN_LITTLE, 187*56e1d40dSTony Lindgren }; 188*56e1d40dSTony Lindgren 189*56e1d40dSTony Lindgren static int cpcap_probe(struct spi_device *spi) 190*56e1d40dSTony Lindgren { 191*56e1d40dSTony Lindgren const struct of_device_id *match; 192*56e1d40dSTony Lindgren struct cpcap_ddata *cpcap; 193*56e1d40dSTony Lindgren int ret; 194*56e1d40dSTony Lindgren 195*56e1d40dSTony Lindgren match = of_match_device(of_match_ptr(cpcap_of_match), &spi->dev); 196*56e1d40dSTony Lindgren if (!match) 197*56e1d40dSTony Lindgren return -ENODEV; 198*56e1d40dSTony Lindgren 199*56e1d40dSTony Lindgren cpcap = devm_kzalloc(&spi->dev, sizeof(*cpcap), GFP_KERNEL); 200*56e1d40dSTony Lindgren if (!cpcap) 201*56e1d40dSTony Lindgren return -ENOMEM; 202*56e1d40dSTony Lindgren 203*56e1d40dSTony Lindgren cpcap->spi = spi; 204*56e1d40dSTony Lindgren spi_set_drvdata(spi, cpcap); 205*56e1d40dSTony Lindgren 206*56e1d40dSTony Lindgren spi->bits_per_word = 16; 207*56e1d40dSTony Lindgren spi->mode = SPI_MODE_0 | SPI_CS_HIGH; 208*56e1d40dSTony Lindgren 209*56e1d40dSTony Lindgren ret = spi_setup(spi); 210*56e1d40dSTony Lindgren if (ret) 211*56e1d40dSTony Lindgren return ret; 212*56e1d40dSTony Lindgren 213*56e1d40dSTony Lindgren cpcap->regmap_conf = &cpcap_regmap_config; 214*56e1d40dSTony Lindgren cpcap->regmap = devm_regmap_init_spi(spi, &cpcap_regmap_config); 215*56e1d40dSTony Lindgren if (IS_ERR(cpcap->regmap)) { 216*56e1d40dSTony Lindgren ret = PTR_ERR(cpcap->regmap); 217*56e1d40dSTony Lindgren dev_err(&cpcap->spi->dev, "Failed to initialize regmap: %d\n", 218*56e1d40dSTony Lindgren ret); 219*56e1d40dSTony Lindgren 220*56e1d40dSTony Lindgren return ret; 221*56e1d40dSTony Lindgren } 222*56e1d40dSTony Lindgren 223*56e1d40dSTony Lindgren ret = cpcap_check_revision(cpcap); 224*56e1d40dSTony Lindgren if (ret) { 225*56e1d40dSTony Lindgren dev_err(&cpcap->spi->dev, "Failed to detect CPCAP: %i\n", ret); 226*56e1d40dSTony Lindgren return ret; 227*56e1d40dSTony Lindgren } 228*56e1d40dSTony Lindgren 229*56e1d40dSTony Lindgren ret = cpcap_init_irq(cpcap); 230*56e1d40dSTony Lindgren if (ret) 231*56e1d40dSTony Lindgren return ret; 232*56e1d40dSTony Lindgren 233*56e1d40dSTony Lindgren return of_platform_populate(spi->dev.of_node, NULL, NULL, 234*56e1d40dSTony Lindgren &cpcap->spi->dev); 235*56e1d40dSTony Lindgren } 236*56e1d40dSTony Lindgren 237*56e1d40dSTony Lindgren static int cpcap_remove(struct spi_device *pdev) 238*56e1d40dSTony Lindgren { 239*56e1d40dSTony Lindgren struct cpcap_ddata *cpcap = spi_get_drvdata(pdev); 240*56e1d40dSTony Lindgren 241*56e1d40dSTony Lindgren of_platform_depopulate(&cpcap->spi->dev); 242*56e1d40dSTony Lindgren 243*56e1d40dSTony Lindgren return 0; 244*56e1d40dSTony Lindgren } 245*56e1d40dSTony Lindgren 246*56e1d40dSTony Lindgren static struct spi_driver cpcap_driver = { 247*56e1d40dSTony Lindgren .driver = { 248*56e1d40dSTony Lindgren .name = "cpcap-core", 249*56e1d40dSTony Lindgren .of_match_table = cpcap_of_match, 250*56e1d40dSTony Lindgren }, 251*56e1d40dSTony Lindgren .probe = cpcap_probe, 252*56e1d40dSTony Lindgren .remove = cpcap_remove, 253*56e1d40dSTony Lindgren }; 254*56e1d40dSTony Lindgren module_spi_driver(cpcap_driver); 255*56e1d40dSTony Lindgren 256*56e1d40dSTony Lindgren MODULE_ALIAS("platform:cpcap"); 257*56e1d40dSTony Lindgren MODULE_DESCRIPTION("CPCAP driver"); 258*56e1d40dSTony Lindgren MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>"); 259*56e1d40dSTony Lindgren MODULE_LICENSE("GPL v2"); 260