353 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Motorola CPCAP PMIC core driver4 *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/mod_devicetable.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 623#define CPCAP_NR_IRQ_CHIPS 324#define CPCAP_REGISTER_SIZE 425#define CPCAP_REGISTER_BITS 1626 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 third90 * 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 224static int cpcap_suspend(struct device *dev)225{226 struct spi_device *spi = to_spi_device(dev);227 228 disable_irq(spi->irq);229 230 return 0;231}232 233static int cpcap_resume(struct device *dev)234{235 struct spi_device *spi = to_spi_device(dev);236 237 enable_irq(spi->irq);238 239 return 0;240}241 242static DEFINE_SIMPLE_DEV_PM_OPS(cpcap_pm, cpcap_suspend, cpcap_resume);243 244static const struct mfd_cell cpcap_mfd_devices[] = {245 {246 .name = "cpcap_adc",247 .of_compatible = "motorola,mapphone-cpcap-adc",248 }, {249 .name = "cpcap_battery",250 .of_compatible = "motorola,cpcap-battery",251 }, {252 .name = "cpcap-charger",253 .of_compatible = "motorola,mapphone-cpcap-charger",254 }, {255 .name = "cpcap-regulator",256 .of_compatible = "motorola,mapphone-cpcap-regulator",257 }, {258 .name = "cpcap-rtc",259 .of_compatible = "motorola,cpcap-rtc",260 }, {261 .name = "cpcap-pwrbutton",262 .of_compatible = "motorola,cpcap-pwrbutton",263 }, {264 .name = "cpcap-usb-phy",265 .of_compatible = "motorola,mapphone-cpcap-usb-phy",266 }, {267 .name = "cpcap-led",268 .id = 0,269 .of_compatible = "motorola,cpcap-led-red",270 }, {271 .name = "cpcap-led",272 .id = 1,273 .of_compatible = "motorola,cpcap-led-green",274 }, {275 .name = "cpcap-led",276 .id = 2,277 .of_compatible = "motorola,cpcap-led-blue",278 }, {279 .name = "cpcap-led",280 .id = 3,281 .of_compatible = "motorola,cpcap-led-adl",282 }, {283 .name = "cpcap-led",284 .id = 4,285 .of_compatible = "motorola,cpcap-led-cp",286 }, {287 .name = "cpcap-codec",288 }289};290 291static int cpcap_probe(struct spi_device *spi)292{293 struct cpcap_ddata *cpcap;294 int ret;295 296 cpcap = devm_kzalloc(&spi->dev, sizeof(*cpcap), GFP_KERNEL);297 if (!cpcap)298 return -ENOMEM;299 300 cpcap->spi = spi;301 spi_set_drvdata(spi, cpcap);302 303 spi->bits_per_word = 16;304 spi->mode = SPI_MODE_0 | SPI_CS_HIGH;305 306 ret = spi_setup(spi);307 if (ret)308 return ret;309 310 cpcap->regmap_conf = &cpcap_regmap_config;311 cpcap->regmap = devm_regmap_init_spi(spi, &cpcap_regmap_config);312 if (IS_ERR(cpcap->regmap)) {313 ret = PTR_ERR(cpcap->regmap);314 dev_err(&cpcap->spi->dev, "Failed to initialize regmap: %d\n",315 ret);316 317 return ret;318 }319 320 ret = cpcap_check_revision(cpcap);321 if (ret) {322 dev_err(&cpcap->spi->dev, "Failed to detect CPCAP: %i\n", ret);323 return ret;324 }325 326 ret = cpcap_init_irq(cpcap);327 if (ret)328 return ret;329 330 /* Parent SPI controller uses DMA, CPCAP and child devices do not */331 spi->dev.coherent_dma_mask = 0;332 spi->dev.dma_mask = &spi->dev.coherent_dma_mask;333 334 return devm_mfd_add_devices(&spi->dev, 0, cpcap_mfd_devices,335 ARRAY_SIZE(cpcap_mfd_devices), NULL, 0, NULL);336}337 338static struct spi_driver cpcap_driver = {339 .driver = {340 .name = "cpcap-core",341 .of_match_table = cpcap_of_match,342 .pm = pm_sleep_ptr(&cpcap_pm),343 },344 .probe = cpcap_probe,345 .id_table = cpcap_spi_ids,346};347module_spi_driver(cpcap_driver);348 349MODULE_ALIAS("platform:cpcap");350MODULE_DESCRIPTION("CPCAP driver");351MODULE_AUTHOR("Tony Lindgren <tony@atomide.com>");352MODULE_LICENSE("GPL v2");353