1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 256e1d40dSTony Lindgren /* 356e1d40dSTony Lindgren * Motorola CPCAP PMIC core driver 456e1d40dSTony Lindgren * 556e1d40dSTony Lindgren * Copyright (C) 2016 Tony Lindgren <tony@atomide.com> 656e1d40dSTony Lindgren */ 756e1d40dSTony Lindgren 856e1d40dSTony Lindgren #include <linux/device.h> 956e1d40dSTony Lindgren #include <linux/err.h> 1056e1d40dSTony Lindgren #include <linux/interrupt.h> 1156e1d40dSTony Lindgren #include <linux/irq.h> 1256e1d40dSTony Lindgren #include <linux/kernel.h> 1356e1d40dSTony Lindgren #include <linux/module.h> 1456e1d40dSTony Lindgren #include <linux/of_device.h> 1556e1d40dSTony Lindgren #include <linux/regmap.h> 1656e1d40dSTony Lindgren #include <linux/sysfs.h> 1756e1d40dSTony Lindgren 1886f955d2SSebastian Reichel #include <linux/mfd/core.h> 1956e1d40dSTony Lindgren #include <linux/mfd/motorola-cpcap.h> 2056e1d40dSTony Lindgren #include <linux/spi/spi.h> 2156e1d40dSTony Lindgren 2256e1d40dSTony Lindgren #define CPCAP_NR_IRQ_REG_BANKS 6 2356e1d40dSTony Lindgren #define CPCAP_NR_IRQ_CHIPS 3 24ab781ec0SSebastian Reichel #define CPCAP_REGISTER_SIZE 4 25ab781ec0SSebastian Reichel #define CPCAP_REGISTER_BITS 16 2656e1d40dSTony Lindgren 2756e1d40dSTony Lindgren struct cpcap_ddata { 2856e1d40dSTony Lindgren struct spi_device *spi; 2956e1d40dSTony Lindgren struct regmap_irq *irqs; 3056e1d40dSTony Lindgren struct regmap_irq_chip_data *irqdata[CPCAP_NR_IRQ_CHIPS]; 3156e1d40dSTony Lindgren const struct regmap_config *regmap_conf; 3256e1d40dSTony Lindgren struct regmap *regmap; 3356e1d40dSTony Lindgren }; 3456e1d40dSTony Lindgren 35ab781ec0SSebastian Reichel static int cpcap_sense_irq(struct regmap *regmap, int irq) 36ab781ec0SSebastian Reichel { 37ab781ec0SSebastian Reichel int regnum = irq / CPCAP_REGISTER_BITS; 38ab781ec0SSebastian Reichel int mask = BIT(irq % CPCAP_REGISTER_BITS); 39ab781ec0SSebastian Reichel int reg = CPCAP_REG_INTS1 + (regnum * CPCAP_REGISTER_SIZE); 40ab781ec0SSebastian Reichel int err, val; 41ab781ec0SSebastian Reichel 42ab781ec0SSebastian Reichel if (reg < CPCAP_REG_INTS1 || reg > CPCAP_REG_INTS4) 43ab781ec0SSebastian Reichel return -EINVAL; 44ab781ec0SSebastian Reichel 45ab781ec0SSebastian Reichel err = regmap_read(regmap, reg, &val); 46ab781ec0SSebastian Reichel if (err) 47ab781ec0SSebastian Reichel return err; 48ab781ec0SSebastian Reichel 49ab781ec0SSebastian Reichel return !!(val & mask); 50ab781ec0SSebastian Reichel } 51ab781ec0SSebastian Reichel 52ab781ec0SSebastian Reichel int cpcap_sense_virq(struct regmap *regmap, int virq) 53ab781ec0SSebastian Reichel { 54ab781ec0SSebastian Reichel struct regmap_irq_chip_data *d = irq_get_chip_data(virq); 55ab781ec0SSebastian Reichel int irq_base = regmap_irq_chip_get_base(d); 56ab781ec0SSebastian Reichel 57ab781ec0SSebastian Reichel return cpcap_sense_irq(regmap, virq - irq_base); 58ab781ec0SSebastian Reichel } 59ab781ec0SSebastian Reichel EXPORT_SYMBOL_GPL(cpcap_sense_virq); 60ab781ec0SSebastian Reichel 6156e1d40dSTony Lindgren static int cpcap_check_revision(struct cpcap_ddata *cpcap) 6256e1d40dSTony Lindgren { 6356e1d40dSTony Lindgren u16 vendor, rev; 6456e1d40dSTony Lindgren int ret; 6556e1d40dSTony Lindgren 6656e1d40dSTony Lindgren ret = cpcap_get_vendor(&cpcap->spi->dev, cpcap->regmap, &vendor); 6756e1d40dSTony Lindgren if (ret) 6856e1d40dSTony Lindgren return ret; 6956e1d40dSTony Lindgren 7056e1d40dSTony Lindgren ret = cpcap_get_revision(&cpcap->spi->dev, cpcap->regmap, &rev); 7156e1d40dSTony Lindgren if (ret) 7256e1d40dSTony Lindgren return ret; 7356e1d40dSTony Lindgren 7456e1d40dSTony Lindgren dev_info(&cpcap->spi->dev, "CPCAP vendor: %s rev: %i.%i (%x)\n", 7556e1d40dSTony Lindgren vendor == CPCAP_VENDOR_ST ? "ST" : "TI", 7656e1d40dSTony Lindgren CPCAP_REVISION_MAJOR(rev), CPCAP_REVISION_MINOR(rev), 7756e1d40dSTony Lindgren rev); 7856e1d40dSTony Lindgren 7956e1d40dSTony Lindgren if (rev < CPCAP_REVISION_2_1) { 8056e1d40dSTony Lindgren dev_info(&cpcap->spi->dev, 8156e1d40dSTony Lindgren "Please add old CPCAP revision support as needed\n"); 8256e1d40dSTony Lindgren return -ENODEV; 8356e1d40dSTony Lindgren } 8456e1d40dSTony Lindgren 8556e1d40dSTony Lindgren return 0; 8656e1d40dSTony Lindgren } 8756e1d40dSTony Lindgren 8856e1d40dSTony Lindgren /* 8956e1d40dSTony Lindgren * First two irq chips are the two private macro interrupt chips, the third 9056e1d40dSTony Lindgren * irq chip is for register banks 1 - 4 and is available for drivers to use. 9156e1d40dSTony Lindgren */ 9256e1d40dSTony Lindgren static struct regmap_irq_chip cpcap_irq_chip[CPCAP_NR_IRQ_CHIPS] = { 9356e1d40dSTony Lindgren { 9456e1d40dSTony Lindgren .name = "cpcap-m2", 9556e1d40dSTony Lindgren .num_regs = 1, 9656e1d40dSTony Lindgren .status_base = CPCAP_REG_MI1, 9756e1d40dSTony Lindgren .ack_base = CPCAP_REG_MI1, 9856e1d40dSTony Lindgren .mask_base = CPCAP_REG_MIM1, 9956e1d40dSTony Lindgren .use_ack = true, 10014639a22STony Lindgren .clear_ack = true, 10156e1d40dSTony Lindgren }, 10256e1d40dSTony Lindgren { 10356e1d40dSTony Lindgren .name = "cpcap-m2", 10456e1d40dSTony Lindgren .num_regs = 1, 10556e1d40dSTony Lindgren .status_base = CPCAP_REG_MI2, 10656e1d40dSTony Lindgren .ack_base = CPCAP_REG_MI2, 10756e1d40dSTony Lindgren .mask_base = CPCAP_REG_MIM2, 10856e1d40dSTony Lindgren .use_ack = true, 10914639a22STony Lindgren .clear_ack = true, 11056e1d40dSTony Lindgren }, 11156e1d40dSTony Lindgren { 11256e1d40dSTony Lindgren .name = "cpcap1-4", 11356e1d40dSTony Lindgren .num_regs = 4, 11456e1d40dSTony Lindgren .status_base = CPCAP_REG_INT1, 11556e1d40dSTony Lindgren .ack_base = CPCAP_REG_INT1, 11656e1d40dSTony Lindgren .mask_base = CPCAP_REG_INTM1, 11756e1d40dSTony Lindgren .use_ack = true, 11814639a22STony Lindgren .clear_ack = true, 11956e1d40dSTony Lindgren }, 12056e1d40dSTony Lindgren }; 12156e1d40dSTony Lindgren 12256e1d40dSTony Lindgren static void cpcap_init_one_regmap_irq(struct cpcap_ddata *cpcap, 12356e1d40dSTony Lindgren struct regmap_irq *rirq, 12456e1d40dSTony Lindgren int irq_base, int irq) 12556e1d40dSTony Lindgren { 12656e1d40dSTony Lindgren unsigned int reg_offset; 12756e1d40dSTony Lindgren unsigned int bit, mask; 12856e1d40dSTony Lindgren 12956e1d40dSTony Lindgren reg_offset = irq - irq_base; 13056e1d40dSTony Lindgren reg_offset /= cpcap->regmap_conf->val_bits; 13156e1d40dSTony Lindgren reg_offset *= cpcap->regmap_conf->reg_stride; 13256e1d40dSTony Lindgren 13356e1d40dSTony Lindgren bit = irq % cpcap->regmap_conf->val_bits; 13456e1d40dSTony Lindgren mask = (1 << bit); 13556e1d40dSTony Lindgren 13656e1d40dSTony Lindgren rirq->reg_offset = reg_offset; 13756e1d40dSTony Lindgren rirq->mask = mask; 13856e1d40dSTony Lindgren } 13956e1d40dSTony Lindgren 14056e1d40dSTony Lindgren static int cpcap_init_irq_chip(struct cpcap_ddata *cpcap, int irq_chip, 14156e1d40dSTony Lindgren int irq_start, int nr_irqs) 14256e1d40dSTony Lindgren { 14356e1d40dSTony Lindgren struct regmap_irq_chip *chip = &cpcap_irq_chip[irq_chip]; 14456e1d40dSTony Lindgren int i, ret; 14556e1d40dSTony Lindgren 14656e1d40dSTony Lindgren for (i = irq_start; i < irq_start + nr_irqs; i++) { 14756e1d40dSTony Lindgren struct regmap_irq *rirq = &cpcap->irqs[i]; 14856e1d40dSTony Lindgren 14956e1d40dSTony Lindgren cpcap_init_one_regmap_irq(cpcap, rirq, irq_start, i); 15056e1d40dSTony Lindgren } 15156e1d40dSTony Lindgren chip->irqs = &cpcap->irqs[irq_start]; 15256e1d40dSTony Lindgren chip->num_irqs = nr_irqs; 15356e1d40dSTony Lindgren chip->irq_drv_data = cpcap; 15456e1d40dSTony Lindgren 15556e1d40dSTony Lindgren ret = devm_regmap_add_irq_chip(&cpcap->spi->dev, cpcap->regmap, 15656e1d40dSTony Lindgren cpcap->spi->irq, 157ac894732STony Lindgren irq_get_trigger_type(cpcap->spi->irq) | 15856e1d40dSTony Lindgren IRQF_SHARED, -1, 15956e1d40dSTony Lindgren chip, &cpcap->irqdata[irq_chip]); 16056e1d40dSTony Lindgren if (ret) { 16156e1d40dSTony Lindgren dev_err(&cpcap->spi->dev, "could not add irq chip %i: %i\n", 16256e1d40dSTony Lindgren irq_chip, ret); 16356e1d40dSTony Lindgren return ret; 16456e1d40dSTony Lindgren } 16556e1d40dSTony Lindgren 16656e1d40dSTony Lindgren return 0; 16756e1d40dSTony Lindgren } 16856e1d40dSTony Lindgren 16956e1d40dSTony Lindgren static int cpcap_init_irq(struct cpcap_ddata *cpcap) 17056e1d40dSTony Lindgren { 17156e1d40dSTony Lindgren int ret; 17256e1d40dSTony Lindgren 17356e1d40dSTony Lindgren cpcap->irqs = devm_kzalloc(&cpcap->spi->dev, 174a86854d0SKees Cook array3_size(sizeof(*cpcap->irqs), 175a86854d0SKees Cook CPCAP_NR_IRQ_REG_BANKS, 176a86854d0SKees Cook cpcap->regmap_conf->val_bits), 17756e1d40dSTony Lindgren GFP_KERNEL); 17856e1d40dSTony Lindgren if (!cpcap->irqs) 17956e1d40dSTony Lindgren return -ENOMEM; 18056e1d40dSTony Lindgren 18156e1d40dSTony Lindgren ret = cpcap_init_irq_chip(cpcap, 0, 0, 16); 18256e1d40dSTony Lindgren if (ret) 18356e1d40dSTony Lindgren return ret; 18456e1d40dSTony Lindgren 18556e1d40dSTony Lindgren ret = cpcap_init_irq_chip(cpcap, 1, 16, 16); 18656e1d40dSTony Lindgren if (ret) 18756e1d40dSTony Lindgren return ret; 18856e1d40dSTony Lindgren 18956e1d40dSTony Lindgren ret = cpcap_init_irq_chip(cpcap, 2, 32, 64); 19056e1d40dSTony Lindgren if (ret) 19156e1d40dSTony Lindgren return ret; 19256e1d40dSTony Lindgren 19356e1d40dSTony Lindgren enable_irq_wake(cpcap->spi->irq); 19456e1d40dSTony Lindgren 19556e1d40dSTony Lindgren return 0; 19656e1d40dSTony Lindgren } 19756e1d40dSTony Lindgren 19856e1d40dSTony Lindgren static const struct of_device_id cpcap_of_match[] = { 19956e1d40dSTony Lindgren { .compatible = "motorola,cpcap", }, 20056e1d40dSTony Lindgren { .compatible = "st,6556002", }, 20156e1d40dSTony Lindgren {}, 20256e1d40dSTony Lindgren }; 20356e1d40dSTony Lindgren MODULE_DEVICE_TABLE(of, cpcap_of_match); 20456e1d40dSTony Lindgren 205*d5fa8592SMark Brown static const struct spi_device_id cpcap_spi_ids[] = { 206*d5fa8592SMark Brown { .name = "cpcap", }, 207*d5fa8592SMark Brown { .name = "6556002", }, 208*d5fa8592SMark Brown {}, 209*d5fa8592SMark Brown }; 210*d5fa8592SMark Brown MODULE_DEVICE_TABLE(spi, cpcap_spi_ids); 211*d5fa8592SMark Brown 21256e1d40dSTony Lindgren static const struct regmap_config cpcap_regmap_config = { 21356e1d40dSTony Lindgren .reg_bits = 16, 21456e1d40dSTony Lindgren .reg_stride = 4, 21556e1d40dSTony Lindgren .pad_bits = 0, 21656e1d40dSTony Lindgren .val_bits = 16, 21756e1d40dSTony Lindgren .write_flag_mask = 0x8000, 21856e1d40dSTony Lindgren .max_register = CPCAP_REG_ST_TEST2, 21956e1d40dSTony Lindgren .cache_type = REGCACHE_NONE, 22056e1d40dSTony Lindgren .reg_format_endian = REGMAP_ENDIAN_LITTLE, 22156e1d40dSTony Lindgren .val_format_endian = REGMAP_ENDIAN_LITTLE, 22256e1d40dSTony Lindgren }; 22356e1d40dSTony Lindgren 224819e42e0STony Lindgren #ifdef CONFIG_PM_SLEEP 225819e42e0STony Lindgren static int cpcap_suspend(struct device *dev) 226819e42e0STony Lindgren { 227819e42e0STony Lindgren struct spi_device *spi = to_spi_device(dev); 228819e42e0STony Lindgren 229819e42e0STony Lindgren disable_irq(spi->irq); 230819e42e0STony Lindgren 231819e42e0STony Lindgren return 0; 232819e42e0STony Lindgren } 233819e42e0STony Lindgren 234819e42e0STony Lindgren static int cpcap_resume(struct device *dev) 235819e42e0STony Lindgren { 236819e42e0STony Lindgren struct spi_device *spi = to_spi_device(dev); 237819e42e0STony Lindgren 238819e42e0STony Lindgren enable_irq(spi->irq); 239819e42e0STony Lindgren 240819e42e0STony Lindgren return 0; 241819e42e0STony Lindgren } 242819e42e0STony Lindgren #endif 243819e42e0STony Lindgren 244819e42e0STony Lindgren static SIMPLE_DEV_PM_OPS(cpcap_pm, cpcap_suspend, cpcap_resume); 245819e42e0STony Lindgren 24686f955d2SSebastian Reichel static const struct mfd_cell cpcap_mfd_devices[] = { 24786f955d2SSebastian Reichel { 24886f955d2SSebastian Reichel .name = "cpcap_adc", 24986f955d2SSebastian Reichel .of_compatible = "motorola,mapphone-cpcap-adc", 25086f955d2SSebastian Reichel }, { 25186f955d2SSebastian Reichel .name = "cpcap_battery", 25286f955d2SSebastian Reichel .of_compatible = "motorola,cpcap-battery", 25386f955d2SSebastian Reichel }, { 25486f955d2SSebastian Reichel .name = "cpcap-charger", 25586f955d2SSebastian Reichel .of_compatible = "motorola,mapphone-cpcap-charger", 25686f955d2SSebastian Reichel }, { 25786f955d2SSebastian Reichel .name = "cpcap-regulator", 25886f955d2SSebastian Reichel .of_compatible = "motorola,mapphone-cpcap-regulator", 25986f955d2SSebastian Reichel }, { 26086f955d2SSebastian Reichel .name = "cpcap-rtc", 26186f955d2SSebastian Reichel .of_compatible = "motorola,cpcap-rtc", 26286f955d2SSebastian Reichel }, { 26386f955d2SSebastian Reichel .name = "cpcap-pwrbutton", 26486f955d2SSebastian Reichel .of_compatible = "motorola,cpcap-pwrbutton", 26586f955d2SSebastian Reichel }, { 26686f955d2SSebastian Reichel .name = "cpcap-usb-phy", 26786f955d2SSebastian Reichel .of_compatible = "motorola,mapphone-cpcap-usb-phy", 26886f955d2SSebastian Reichel }, { 26986f955d2SSebastian Reichel .name = "cpcap-led", 27086f955d2SSebastian Reichel .id = 0, 27186f955d2SSebastian Reichel .of_compatible = "motorola,cpcap-led-red", 27286f955d2SSebastian Reichel }, { 27386f955d2SSebastian Reichel .name = "cpcap-led", 27486f955d2SSebastian Reichel .id = 1, 27586f955d2SSebastian Reichel .of_compatible = "motorola,cpcap-led-green", 27686f955d2SSebastian Reichel }, { 27786f955d2SSebastian Reichel .name = "cpcap-led", 27886f955d2SSebastian Reichel .id = 2, 27986f955d2SSebastian Reichel .of_compatible = "motorola,cpcap-led-blue", 28086f955d2SSebastian Reichel }, { 28186f955d2SSebastian Reichel .name = "cpcap-led", 28286f955d2SSebastian Reichel .id = 3, 28386f955d2SSebastian Reichel .of_compatible = "motorola,cpcap-led-adl", 28486f955d2SSebastian Reichel }, { 28586f955d2SSebastian Reichel .name = "cpcap-led", 28686f955d2SSebastian Reichel .id = 4, 28786f955d2SSebastian Reichel .of_compatible = "motorola,cpcap-led-cp", 28886f955d2SSebastian Reichel }, { 28986f955d2SSebastian Reichel .name = "cpcap-codec", 29086f955d2SSebastian Reichel } 29186f955d2SSebastian Reichel }; 29286f955d2SSebastian Reichel 29356e1d40dSTony Lindgren static int cpcap_probe(struct spi_device *spi) 29456e1d40dSTony Lindgren { 29556e1d40dSTony Lindgren const struct of_device_id *match; 29656e1d40dSTony Lindgren struct cpcap_ddata *cpcap; 29756e1d40dSTony Lindgren int ret; 29856e1d40dSTony Lindgren 29956e1d40dSTony Lindgren match = of_match_device(of_match_ptr(cpcap_of_match), &spi->dev); 30056e1d40dSTony Lindgren if (!match) 30156e1d40dSTony Lindgren return -ENODEV; 30256e1d40dSTony Lindgren 30356e1d40dSTony Lindgren cpcap = devm_kzalloc(&spi->dev, sizeof(*cpcap), GFP_KERNEL); 30456e1d40dSTony Lindgren if (!cpcap) 30556e1d40dSTony Lindgren return -ENOMEM; 30656e1d40dSTony Lindgren 30756e1d40dSTony Lindgren cpcap->spi = spi; 30856e1d40dSTony Lindgren spi_set_drvdata(spi, cpcap); 30956e1d40dSTony Lindgren 31056e1d40dSTony Lindgren spi->bits_per_word = 16; 31156e1d40dSTony Lindgren spi->mode = SPI_MODE_0 | SPI_CS_HIGH; 31256e1d40dSTony Lindgren 31356e1d40dSTony Lindgren ret = spi_setup(spi); 31456e1d40dSTony Lindgren if (ret) 31556e1d40dSTony Lindgren return ret; 31656e1d40dSTony Lindgren 31756e1d40dSTony Lindgren cpcap->regmap_conf = &cpcap_regmap_config; 31856e1d40dSTony Lindgren cpcap->regmap = devm_regmap_init_spi(spi, &cpcap_regmap_config); 31956e1d40dSTony Lindgren if (IS_ERR(cpcap->regmap)) { 32056e1d40dSTony Lindgren ret = PTR_ERR(cpcap->regmap); 32156e1d40dSTony Lindgren dev_err(&cpcap->spi->dev, "Failed to initialize regmap: %d\n", 32256e1d40dSTony Lindgren ret); 32356e1d40dSTony Lindgren 32456e1d40dSTony Lindgren return ret; 32556e1d40dSTony Lindgren } 32656e1d40dSTony Lindgren 32756e1d40dSTony Lindgren ret = cpcap_check_revision(cpcap); 32856e1d40dSTony Lindgren if (ret) { 32956e1d40dSTony Lindgren dev_err(&cpcap->spi->dev, "Failed to detect CPCAP: %i\n", ret); 33056e1d40dSTony Lindgren return ret; 33156e1d40dSTony Lindgren } 33256e1d40dSTony Lindgren 33356e1d40dSTony Lindgren ret = cpcap_init_irq(cpcap); 33456e1d40dSTony Lindgren if (ret) 33556e1d40dSTony Lindgren return ret; 33656e1d40dSTony Lindgren 3370b7cbe81STony Lindgren /* Parent SPI controller uses DMA, CPCAP and child devices do not */ 3380b7cbe81STony Lindgren spi->dev.coherent_dma_mask = 0; 3390b7cbe81STony Lindgren spi->dev.dma_mask = &spi->dev.coherent_dma_mask; 3400b7cbe81STony Lindgren 34186f955d2SSebastian Reichel return devm_mfd_add_devices(&spi->dev, 0, cpcap_mfd_devices, 34286f955d2SSebastian Reichel ARRAY_SIZE(cpcap_mfd_devices), NULL, 0, NULL); 34356e1d40dSTony Lindgren } 34456e1d40dSTony Lindgren 34556e1d40dSTony Lindgren static struct spi_driver cpcap_driver = { 34656e1d40dSTony Lindgren .driver = { 34756e1d40dSTony Lindgren .name = "cpcap-core", 34856e1d40dSTony Lindgren .of_match_table = cpcap_of_match, 349819e42e0STony Lindgren .pm = &cpcap_pm, 35056e1d40dSTony Lindgren }, 35156e1d40dSTony Lindgren .probe = cpcap_probe, 352*d5fa8592SMark Brown .id_table = cpcap_spi_ids, 35356e1d40dSTony Lindgren }; 35456e1d40dSTony Lindgren module_spi_driver(cpcap_driver); 35556e1d40dSTony Lindgren 35656e1d40dSTony Lindgren MODULE_ALIAS("platform:cpcap"); 35756e1d40dSTony Lindgren MODULE_DESCRIPTION("CPCAP driver"); 35856e1d40dSTony Lindgren MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>"); 35956e1d40dSTony Lindgren MODULE_LICENSE("GPL v2"); 360