123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- /*
- * Motorola CPCAP PMIC core driver
- *
- * Copyright (C) 2016 Tony Lindgren <tony@atomide.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- */
- #include <linux/device.h>
- #include <linux/err.h>
- #include <linux/interrupt.h>
- #include <linux/irq.h>
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/of_device.h>
- #include <linux/regmap.h>
- #include <linux/sysfs.h>
- #include <linux/mfd/motorola-cpcap.h>
- #include <linux/spi/spi.h>
- #define CPCAP_NR_IRQ_REG_BANKS 6
- #define CPCAP_NR_IRQ_CHIPS 3
- #define CPCAP_REGISTER_SIZE 4
- #define CPCAP_REGISTER_BITS 16
- struct cpcap_ddata {
- struct spi_device *spi;
- struct regmap_irq *irqs;
- struct regmap_irq_chip_data *irqdata[CPCAP_NR_IRQ_CHIPS];
- const struct regmap_config *regmap_conf;
- struct regmap *regmap;
- };
- static int cpcap_sense_irq(struct regmap *regmap, int irq)
- {
- int regnum = irq / CPCAP_REGISTER_BITS;
- int mask = BIT(irq % CPCAP_REGISTER_BITS);
- int reg = CPCAP_REG_INTS1 + (regnum * CPCAP_REGISTER_SIZE);
- int err, val;
- if (reg < CPCAP_REG_INTS1 || reg > CPCAP_REG_INTS4)
- return -EINVAL;
- err = regmap_read(regmap, reg, &val);
- if (err)
- return err;
- return !!(val & mask);
- }
- int cpcap_sense_virq(struct regmap *regmap, int virq)
- {
- struct regmap_irq_chip_data *d = irq_get_chip_data(virq);
- int irq_base = regmap_irq_chip_get_base(d);
- return cpcap_sense_irq(regmap, virq - irq_base);
- }
- EXPORT_SYMBOL_GPL(cpcap_sense_virq);
- static int cpcap_check_revision(struct cpcap_ddata *cpcap)
- {
- u16 vendor, rev;
- int ret;
- ret = cpcap_get_vendor(&cpcap->spi->dev, cpcap->regmap, &vendor);
- if (ret)
- return ret;
- ret = cpcap_get_revision(&cpcap->spi->dev, cpcap->regmap, &rev);
- if (ret)
- return ret;
- dev_info(&cpcap->spi->dev, "CPCAP vendor: %s rev: %i.%i (%x)\n",
- vendor == CPCAP_VENDOR_ST ? "ST" : "TI",
- CPCAP_REVISION_MAJOR(rev), CPCAP_REVISION_MINOR(rev),
- rev);
- if (rev < CPCAP_REVISION_2_1) {
- dev_info(&cpcap->spi->dev,
- "Please add old CPCAP revision support as needed\n");
- return -ENODEV;
- }
- return 0;
- }
- /*
- * First two irq chips are the two private macro interrupt chips, the third
- * irq chip is for register banks 1 - 4 and is available for drivers to use.
- */
- static struct regmap_irq_chip cpcap_irq_chip[CPCAP_NR_IRQ_CHIPS] = {
- {
- .name = "cpcap-m2",
- .num_regs = 1,
- .status_base = CPCAP_REG_MI1,
- .ack_base = CPCAP_REG_MI1,
- .mask_base = CPCAP_REG_MIM1,
- .use_ack = true,
- .ack_invert = true,
- },
- {
- .name = "cpcap-m2",
- .num_regs = 1,
- .status_base = CPCAP_REG_MI2,
- .ack_base = CPCAP_REG_MI2,
- .mask_base = CPCAP_REG_MIM2,
- .use_ack = true,
- .ack_invert = true,
- },
- {
- .name = "cpcap1-4",
- .num_regs = 4,
- .status_base = CPCAP_REG_INT1,
- .ack_base = CPCAP_REG_INT1,
- .mask_base = CPCAP_REG_INTM1,
- .use_ack = true,
- .ack_invert = true,
- },
- };
- static void cpcap_init_one_regmap_irq(struct cpcap_ddata *cpcap,
- struct regmap_irq *rirq,
- int irq_base, int irq)
- {
- unsigned int reg_offset;
- unsigned int bit, mask;
- reg_offset = irq - irq_base;
- reg_offset /= cpcap->regmap_conf->val_bits;
- reg_offset *= cpcap->regmap_conf->reg_stride;
- bit = irq % cpcap->regmap_conf->val_bits;
- mask = (1 << bit);
- rirq->reg_offset = reg_offset;
- rirq->mask = mask;
- }
- static int cpcap_init_irq_chip(struct cpcap_ddata *cpcap, int irq_chip,
- int irq_start, int nr_irqs)
- {
- struct regmap_irq_chip *chip = &cpcap_irq_chip[irq_chip];
- int i, ret;
- for (i = irq_start; i < irq_start + nr_irqs; i++) {
- struct regmap_irq *rirq = &cpcap->irqs[i];
- cpcap_init_one_regmap_irq(cpcap, rirq, irq_start, i);
- }
- chip->irqs = &cpcap->irqs[irq_start];
- chip->num_irqs = nr_irqs;
- chip->irq_drv_data = cpcap;
- ret = devm_regmap_add_irq_chip(&cpcap->spi->dev, cpcap->regmap,
- cpcap->spi->irq,
- irq_get_trigger_type(cpcap->spi->irq) |
- IRQF_SHARED, -1,
- chip, &cpcap->irqdata[irq_chip]);
- if (ret) {
- dev_err(&cpcap->spi->dev, "could not add irq chip %i: %i\n",
- irq_chip, ret);
- return ret;
- }
- return 0;
- }
- static int cpcap_init_irq(struct cpcap_ddata *cpcap)
- {
- int ret;
- cpcap->irqs = devm_kzalloc(&cpcap->spi->dev,
- array3_size(sizeof(*cpcap->irqs),
- CPCAP_NR_IRQ_REG_BANKS,
- cpcap->regmap_conf->val_bits),
- GFP_KERNEL);
- if (!cpcap->irqs)
- return -ENOMEM;
- ret = cpcap_init_irq_chip(cpcap, 0, 0, 16);
- if (ret)
- return ret;
- ret = cpcap_init_irq_chip(cpcap, 1, 16, 16);
- if (ret)
- return ret;
- ret = cpcap_init_irq_chip(cpcap, 2, 32, 64);
- if (ret)
- return ret;
- enable_irq_wake(cpcap->spi->irq);
- return 0;
- }
- static const struct of_device_id cpcap_of_match[] = {
- { .compatible = "motorola,cpcap", },
- { .compatible = "st,6556002", },
- {},
- };
- MODULE_DEVICE_TABLE(of, cpcap_of_match);
- static const struct regmap_config cpcap_regmap_config = {
- .reg_bits = 16,
- .reg_stride = 4,
- .pad_bits = 0,
- .val_bits = 16,
- .write_flag_mask = 0x8000,
- .max_register = CPCAP_REG_ST_TEST2,
- .cache_type = REGCACHE_NONE,
- .reg_format_endian = REGMAP_ENDIAN_LITTLE,
- .val_format_endian = REGMAP_ENDIAN_LITTLE,
- };
- static int cpcap_probe(struct spi_device *spi)
- {
- const struct of_device_id *match;
- struct cpcap_ddata *cpcap;
- int ret;
- match = of_match_device(of_match_ptr(cpcap_of_match), &spi->dev);
- if (!match)
- return -ENODEV;
- cpcap = devm_kzalloc(&spi->dev, sizeof(*cpcap), GFP_KERNEL);
- if (!cpcap)
- return -ENOMEM;
- cpcap->spi = spi;
- spi_set_drvdata(spi, cpcap);
- spi->bits_per_word = 16;
- spi->mode = SPI_MODE_0 | SPI_CS_HIGH;
- ret = spi_setup(spi);
- if (ret)
- return ret;
- cpcap->regmap_conf = &cpcap_regmap_config;
- cpcap->regmap = devm_regmap_init_spi(spi, &cpcap_regmap_config);
- if (IS_ERR(cpcap->regmap)) {
- ret = PTR_ERR(cpcap->regmap);
- dev_err(&cpcap->spi->dev, "Failed to initialize regmap: %d\n",
- ret);
- return ret;
- }
- ret = cpcap_check_revision(cpcap);
- if (ret) {
- dev_err(&cpcap->spi->dev, "Failed to detect CPCAP: %i\n", ret);
- return ret;
- }
- ret = cpcap_init_irq(cpcap);
- if (ret)
- return ret;
- return devm_of_platform_populate(&cpcap->spi->dev);
- }
- static struct spi_driver cpcap_driver = {
- .driver = {
- .name = "cpcap-core",
- .of_match_table = cpcap_of_match,
- },
- .probe = cpcap_probe,
- };
- module_spi_driver(cpcap_driver);
- MODULE_ALIAS("platform:cpcap");
- MODULE_DESCRIPTION("CPCAP driver");
- MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");
- MODULE_LICENSE("GPL v2");
|