156e1d40dSTony Lindgren /* 256e1d40dSTony Lindgren * Motorola CPCAP PMIC core driver 356e1d40dSTony Lindgren * 456e1d40dSTony Lindgren * Copyright (C) 2016 Tony Lindgren <tony@atomide.com> 556e1d40dSTony Lindgren * 656e1d40dSTony Lindgren * This program is free software; you can redistribute it and/or modify 756e1d40dSTony Lindgren * it under the terms of the GNU General Public License version 2 as 856e1d40dSTony Lindgren * published by the Free Software Foundation. 956e1d40dSTony Lindgren */ 1056e1d40dSTony Lindgren 1156e1d40dSTony Lindgren #include <linux/device.h> 1256e1d40dSTony Lindgren #include <linux/err.h> 1356e1d40dSTony Lindgren #include <linux/interrupt.h> 1456e1d40dSTony Lindgren #include <linux/irq.h> 1556e1d40dSTony Lindgren #include <linux/kernel.h> 1656e1d40dSTony Lindgren #include <linux/module.h> 1756e1d40dSTony Lindgren #include <linux/of_device.h> 1856e1d40dSTony Lindgren #include <linux/regmap.h> 1956e1d40dSTony Lindgren #include <linux/sysfs.h> 2056e1d40dSTony Lindgren 2156e1d40dSTony Lindgren #include <linux/mfd/motorola-cpcap.h> 2256e1d40dSTony Lindgren #include <linux/spi/spi.h> 2356e1d40dSTony Lindgren 2456e1d40dSTony Lindgren #define CPCAP_NR_IRQ_REG_BANKS 6 2556e1d40dSTony Lindgren #define CPCAP_NR_IRQ_CHIPS 3 26ab781ec0SSebastian Reichel #define CPCAP_REGISTER_SIZE 4 27ab781ec0SSebastian Reichel #define CPCAP_REGISTER_BITS 16 2856e1d40dSTony Lindgren 2956e1d40dSTony Lindgren struct cpcap_ddata { 3056e1d40dSTony Lindgren struct spi_device *spi; 3156e1d40dSTony Lindgren struct regmap_irq *irqs; 3256e1d40dSTony Lindgren struct regmap_irq_chip_data *irqdata[CPCAP_NR_IRQ_CHIPS]; 3356e1d40dSTony Lindgren const struct regmap_config *regmap_conf; 3456e1d40dSTony Lindgren struct regmap *regmap; 3556e1d40dSTony Lindgren }; 3656e1d40dSTony Lindgren 37ab781ec0SSebastian Reichel static int cpcap_sense_irq(struct regmap *regmap, int irq) 38ab781ec0SSebastian Reichel { 39ab781ec0SSebastian Reichel int regnum = irq / CPCAP_REGISTER_BITS; 40ab781ec0SSebastian Reichel int mask = BIT(irq % CPCAP_REGISTER_BITS); 41ab781ec0SSebastian Reichel int reg = CPCAP_REG_INTS1 + (regnum * CPCAP_REGISTER_SIZE); 42ab781ec0SSebastian Reichel int err, val; 43ab781ec0SSebastian Reichel 44ab781ec0SSebastian Reichel if (reg < CPCAP_REG_INTS1 || reg > CPCAP_REG_INTS4) 45ab781ec0SSebastian Reichel return -EINVAL; 46ab781ec0SSebastian Reichel 47ab781ec0SSebastian Reichel err = regmap_read(regmap, reg, &val); 48ab781ec0SSebastian Reichel if (err) 49ab781ec0SSebastian Reichel return err; 50ab781ec0SSebastian Reichel 51ab781ec0SSebastian Reichel return !!(val & mask); 52ab781ec0SSebastian Reichel } 53ab781ec0SSebastian Reichel 54ab781ec0SSebastian Reichel int cpcap_sense_virq(struct regmap *regmap, int virq) 55ab781ec0SSebastian Reichel { 56ab781ec0SSebastian Reichel struct regmap_irq_chip_data *d = irq_get_chip_data(virq); 57ab781ec0SSebastian Reichel int irq_base = regmap_irq_chip_get_base(d); 58ab781ec0SSebastian Reichel 59ab781ec0SSebastian Reichel return cpcap_sense_irq(regmap, virq - irq_base); 60ab781ec0SSebastian Reichel } 61ab781ec0SSebastian Reichel EXPORT_SYMBOL_GPL(cpcap_sense_virq); 62ab781ec0SSebastian Reichel 6356e1d40dSTony Lindgren static int cpcap_check_revision(struct cpcap_ddata *cpcap) 6456e1d40dSTony Lindgren { 6556e1d40dSTony Lindgren u16 vendor, rev; 6656e1d40dSTony Lindgren int ret; 6756e1d40dSTony Lindgren 6856e1d40dSTony Lindgren ret = cpcap_get_vendor(&cpcap->spi->dev, cpcap->regmap, &vendor); 6956e1d40dSTony Lindgren if (ret) 7056e1d40dSTony Lindgren return ret; 7156e1d40dSTony Lindgren 7256e1d40dSTony Lindgren ret = cpcap_get_revision(&cpcap->spi->dev, cpcap->regmap, &rev); 7356e1d40dSTony Lindgren if (ret) 7456e1d40dSTony Lindgren return ret; 7556e1d40dSTony Lindgren 7656e1d40dSTony Lindgren dev_info(&cpcap->spi->dev, "CPCAP vendor: %s rev: %i.%i (%x)\n", 7756e1d40dSTony Lindgren vendor == CPCAP_VENDOR_ST ? "ST" : "TI", 7856e1d40dSTony Lindgren CPCAP_REVISION_MAJOR(rev), CPCAP_REVISION_MINOR(rev), 7956e1d40dSTony Lindgren rev); 8056e1d40dSTony Lindgren 8156e1d40dSTony Lindgren if (rev < CPCAP_REVISION_2_1) { 8256e1d40dSTony Lindgren dev_info(&cpcap->spi->dev, 8356e1d40dSTony Lindgren "Please add old CPCAP revision support as needed\n"); 8456e1d40dSTony Lindgren return -ENODEV; 8556e1d40dSTony Lindgren } 8656e1d40dSTony Lindgren 8756e1d40dSTony Lindgren return 0; 8856e1d40dSTony Lindgren } 8956e1d40dSTony Lindgren 9056e1d40dSTony Lindgren /* 9156e1d40dSTony Lindgren * First two irq chips are the two private macro interrupt chips, the third 9256e1d40dSTony Lindgren * irq chip is for register banks 1 - 4 and is available for drivers to use. 9356e1d40dSTony Lindgren */ 9456e1d40dSTony Lindgren static struct regmap_irq_chip cpcap_irq_chip[CPCAP_NR_IRQ_CHIPS] = { 9556e1d40dSTony Lindgren { 9656e1d40dSTony Lindgren .name = "cpcap-m2", 9756e1d40dSTony Lindgren .num_regs = 1, 9856e1d40dSTony Lindgren .status_base = CPCAP_REG_MI1, 9956e1d40dSTony Lindgren .ack_base = CPCAP_REG_MI1, 10056e1d40dSTony Lindgren .mask_base = CPCAP_REG_MIM1, 10156e1d40dSTony Lindgren .use_ack = true, 10256e1d40dSTony Lindgren }, 10356e1d40dSTony Lindgren { 10456e1d40dSTony Lindgren .name = "cpcap-m2", 10556e1d40dSTony Lindgren .num_regs = 1, 10656e1d40dSTony Lindgren .status_base = CPCAP_REG_MI2, 10756e1d40dSTony Lindgren .ack_base = CPCAP_REG_MI2, 10856e1d40dSTony Lindgren .mask_base = CPCAP_REG_MIM2, 10956e1d40dSTony Lindgren .use_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 .type_base = CPCAP_REG_INTS1, 11856e1d40dSTony Lindgren .use_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, 157*ac894732STony 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, 17456e1d40dSTony Lindgren sizeof(*cpcap->irqs) * 17556e1d40dSTony Lindgren CPCAP_NR_IRQ_REG_BANKS * 17656e1d40dSTony Lindgren 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 20556e1d40dSTony Lindgren static const struct regmap_config cpcap_regmap_config = { 20656e1d40dSTony Lindgren .reg_bits = 16, 20756e1d40dSTony Lindgren .reg_stride = 4, 20856e1d40dSTony Lindgren .pad_bits = 0, 20956e1d40dSTony Lindgren .val_bits = 16, 21056e1d40dSTony Lindgren .write_flag_mask = 0x8000, 21156e1d40dSTony Lindgren .max_register = CPCAP_REG_ST_TEST2, 21256e1d40dSTony Lindgren .cache_type = REGCACHE_NONE, 21356e1d40dSTony Lindgren .reg_format_endian = REGMAP_ENDIAN_LITTLE, 21456e1d40dSTony Lindgren .val_format_endian = REGMAP_ENDIAN_LITTLE, 21556e1d40dSTony Lindgren }; 21656e1d40dSTony Lindgren 21756e1d40dSTony Lindgren static int cpcap_probe(struct spi_device *spi) 21856e1d40dSTony Lindgren { 21956e1d40dSTony Lindgren const struct of_device_id *match; 22056e1d40dSTony Lindgren struct cpcap_ddata *cpcap; 22156e1d40dSTony Lindgren int ret; 22256e1d40dSTony Lindgren 22356e1d40dSTony Lindgren match = of_match_device(of_match_ptr(cpcap_of_match), &spi->dev); 22456e1d40dSTony Lindgren if (!match) 22556e1d40dSTony Lindgren return -ENODEV; 22656e1d40dSTony Lindgren 22756e1d40dSTony Lindgren cpcap = devm_kzalloc(&spi->dev, sizeof(*cpcap), GFP_KERNEL); 22856e1d40dSTony Lindgren if (!cpcap) 22956e1d40dSTony Lindgren return -ENOMEM; 23056e1d40dSTony Lindgren 23156e1d40dSTony Lindgren cpcap->spi = spi; 23256e1d40dSTony Lindgren spi_set_drvdata(spi, cpcap); 23356e1d40dSTony Lindgren 23456e1d40dSTony Lindgren spi->bits_per_word = 16; 23556e1d40dSTony Lindgren spi->mode = SPI_MODE_0 | SPI_CS_HIGH; 23656e1d40dSTony Lindgren 23756e1d40dSTony Lindgren ret = spi_setup(spi); 23856e1d40dSTony Lindgren if (ret) 23956e1d40dSTony Lindgren return ret; 24056e1d40dSTony Lindgren 24156e1d40dSTony Lindgren cpcap->regmap_conf = &cpcap_regmap_config; 24256e1d40dSTony Lindgren cpcap->regmap = devm_regmap_init_spi(spi, &cpcap_regmap_config); 24356e1d40dSTony Lindgren if (IS_ERR(cpcap->regmap)) { 24456e1d40dSTony Lindgren ret = PTR_ERR(cpcap->regmap); 24556e1d40dSTony Lindgren dev_err(&cpcap->spi->dev, "Failed to initialize regmap: %d\n", 24656e1d40dSTony Lindgren ret); 24756e1d40dSTony Lindgren 24856e1d40dSTony Lindgren return ret; 24956e1d40dSTony Lindgren } 25056e1d40dSTony Lindgren 25156e1d40dSTony Lindgren ret = cpcap_check_revision(cpcap); 25256e1d40dSTony Lindgren if (ret) { 25356e1d40dSTony Lindgren dev_err(&cpcap->spi->dev, "Failed to detect CPCAP: %i\n", ret); 25456e1d40dSTony Lindgren return ret; 25556e1d40dSTony Lindgren } 25656e1d40dSTony Lindgren 25756e1d40dSTony Lindgren ret = cpcap_init_irq(cpcap); 25856e1d40dSTony Lindgren if (ret) 25956e1d40dSTony Lindgren return ret; 26056e1d40dSTony Lindgren 26156e1d40dSTony Lindgren return of_platform_populate(spi->dev.of_node, NULL, NULL, 26256e1d40dSTony Lindgren &cpcap->spi->dev); 26356e1d40dSTony Lindgren } 26456e1d40dSTony Lindgren 26556e1d40dSTony Lindgren static int cpcap_remove(struct spi_device *pdev) 26656e1d40dSTony Lindgren { 26756e1d40dSTony Lindgren struct cpcap_ddata *cpcap = spi_get_drvdata(pdev); 26856e1d40dSTony Lindgren 26956e1d40dSTony Lindgren of_platform_depopulate(&cpcap->spi->dev); 27056e1d40dSTony Lindgren 27156e1d40dSTony Lindgren return 0; 27256e1d40dSTony Lindgren } 27356e1d40dSTony Lindgren 27456e1d40dSTony Lindgren static struct spi_driver cpcap_driver = { 27556e1d40dSTony Lindgren .driver = { 27656e1d40dSTony Lindgren .name = "cpcap-core", 27756e1d40dSTony Lindgren .of_match_table = cpcap_of_match, 27856e1d40dSTony Lindgren }, 27956e1d40dSTony Lindgren .probe = cpcap_probe, 28056e1d40dSTony Lindgren .remove = cpcap_remove, 28156e1d40dSTony Lindgren }; 28256e1d40dSTony Lindgren module_spi_driver(cpcap_driver); 28356e1d40dSTony Lindgren 28456e1d40dSTony Lindgren MODULE_ALIAS("platform:cpcap"); 28556e1d40dSTony Lindgren MODULE_DESCRIPTION("CPCAP driver"); 28656e1d40dSTony Lindgren MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>"); 28756e1d40dSTony Lindgren MODULE_LICENSE("GPL v2"); 288