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