Cristian Ciocaltea | f7cb7fe | 2021-01-26 11:55:59 +0200 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | /* |
| 3 | * Core support for ATC260x PMICs |
| 4 | * |
| 5 | * Copyright (C) 2019 Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org> |
| 6 | * Copyright (C) 2020 Cristian Ciocaltea <cristian.ciocaltea@gmail.com> |
| 7 | */ |
| 8 | |
| 9 | #include <linux/interrupt.h> |
| 10 | #include <linux/mfd/atc260x/core.h> |
| 11 | #include <linux/mfd/core.h> |
| 12 | #include <linux/module.h> |
| 13 | #include <linux/of.h> |
| 14 | #include <linux/of_device.h> |
| 15 | #include <linux/regmap.h> |
| 16 | |
| 17 | #define ATC260X_CHIP_REV_MAX 31 |
| 18 | |
| 19 | struct atc260x_init_regs { |
| 20 | unsigned int cmu_devrst; |
| 21 | unsigned int cmu_devrst_ints; |
| 22 | unsigned int ints_msk; |
| 23 | unsigned int pad_en; |
| 24 | unsigned int pad_en_extirq; |
| 25 | }; |
| 26 | |
| 27 | static void regmap_lock_mutex(void *__mutex) |
| 28 | { |
| 29 | struct mutex *mutex = __mutex; |
| 30 | |
| 31 | /* |
| 32 | * Using regmap within an atomic context (e.g. accessing a PMIC when |
| 33 | * powering system down) is normally allowed only if the regmap type |
| 34 | * is MMIO and the regcache type is either REGCACHE_NONE or |
| 35 | * REGCACHE_FLAT. For slow buses like I2C and SPI, the regmap is |
| 36 | * internally protected by a mutex which is acquired non-atomically. |
| 37 | * |
| 38 | * Let's improve this by using a customized locking scheme inspired |
| 39 | * from I2C atomic transfer. See i2c_in_atomic_xfer_mode() for a |
| 40 | * starting point. |
| 41 | */ |
| 42 | if (system_state > SYSTEM_RUNNING && irqs_disabled()) |
| 43 | mutex_trylock(mutex); |
| 44 | else |
| 45 | mutex_lock(mutex); |
| 46 | } |
| 47 | |
| 48 | static void regmap_unlock_mutex(void *__mutex) |
| 49 | { |
| 50 | struct mutex *mutex = __mutex; |
| 51 | |
| 52 | mutex_unlock(mutex); |
| 53 | } |
| 54 | |
| 55 | static const struct regmap_config atc2603c_regmap_config = { |
| 56 | .reg_bits = 8, |
| 57 | .val_bits = 16, |
| 58 | .max_register = ATC2603C_SADDR, |
| 59 | .cache_type = REGCACHE_NONE, |
| 60 | }; |
| 61 | |
| 62 | static const struct regmap_config atc2609a_regmap_config = { |
| 63 | .reg_bits = 8, |
| 64 | .val_bits = 16, |
| 65 | .max_register = ATC2609A_SADDR, |
| 66 | .cache_type = REGCACHE_NONE, |
| 67 | }; |
| 68 | |
| 69 | static const struct regmap_irq atc2603c_regmap_irqs[] = { |
| 70 | REGMAP_IRQ_REG(ATC2603C_IRQ_AUDIO, 0, ATC2603C_INTS_MSK_AUDIO), |
| 71 | REGMAP_IRQ_REG(ATC2603C_IRQ_OV, 0, ATC2603C_INTS_MSK_OV), |
| 72 | REGMAP_IRQ_REG(ATC2603C_IRQ_OC, 0, ATC2603C_INTS_MSK_OC), |
| 73 | REGMAP_IRQ_REG(ATC2603C_IRQ_OT, 0, ATC2603C_INTS_MSK_OT), |
| 74 | REGMAP_IRQ_REG(ATC2603C_IRQ_UV, 0, ATC2603C_INTS_MSK_UV), |
| 75 | REGMAP_IRQ_REG(ATC2603C_IRQ_ALARM, 0, ATC2603C_INTS_MSK_ALARM), |
| 76 | REGMAP_IRQ_REG(ATC2603C_IRQ_ONOFF, 0, ATC2603C_INTS_MSK_ONOFF), |
| 77 | REGMAP_IRQ_REG(ATC2603C_IRQ_SGPIO, 0, ATC2603C_INTS_MSK_SGPIO), |
| 78 | REGMAP_IRQ_REG(ATC2603C_IRQ_IR, 0, ATC2603C_INTS_MSK_IR), |
| 79 | REGMAP_IRQ_REG(ATC2603C_IRQ_REMCON, 0, ATC2603C_INTS_MSK_REMCON), |
| 80 | REGMAP_IRQ_REG(ATC2603C_IRQ_POWER_IN, 0, ATC2603C_INTS_MSK_POWERIN), |
| 81 | }; |
| 82 | |
| 83 | static const struct regmap_irq atc2609a_regmap_irqs[] = { |
| 84 | REGMAP_IRQ_REG(ATC2609A_IRQ_AUDIO, 0, ATC2609A_INTS_MSK_AUDIO), |
| 85 | REGMAP_IRQ_REG(ATC2609A_IRQ_OV, 0, ATC2609A_INTS_MSK_OV), |
| 86 | REGMAP_IRQ_REG(ATC2609A_IRQ_OC, 0, ATC2609A_INTS_MSK_OC), |
| 87 | REGMAP_IRQ_REG(ATC2609A_IRQ_OT, 0, ATC2609A_INTS_MSK_OT), |
| 88 | REGMAP_IRQ_REG(ATC2609A_IRQ_UV, 0, ATC2609A_INTS_MSK_UV), |
| 89 | REGMAP_IRQ_REG(ATC2609A_IRQ_ALARM, 0, ATC2609A_INTS_MSK_ALARM), |
| 90 | REGMAP_IRQ_REG(ATC2609A_IRQ_ONOFF, 0, ATC2609A_INTS_MSK_ONOFF), |
| 91 | REGMAP_IRQ_REG(ATC2609A_IRQ_WKUP, 0, ATC2609A_INTS_MSK_WKUP), |
| 92 | REGMAP_IRQ_REG(ATC2609A_IRQ_IR, 0, ATC2609A_INTS_MSK_IR), |
| 93 | REGMAP_IRQ_REG(ATC2609A_IRQ_REMCON, 0, ATC2609A_INTS_MSK_REMCON), |
| 94 | REGMAP_IRQ_REG(ATC2609A_IRQ_POWER_IN, 0, ATC2609A_INTS_MSK_POWERIN), |
| 95 | }; |
| 96 | |
| 97 | static const struct regmap_irq_chip atc2603c_regmap_irq_chip = { |
| 98 | .name = "atc2603c", |
| 99 | .irqs = atc2603c_regmap_irqs, |
| 100 | .num_irqs = ARRAY_SIZE(atc2603c_regmap_irqs), |
| 101 | .num_regs = 1, |
| 102 | .status_base = ATC2603C_INTS_PD, |
| 103 | .mask_base = ATC2603C_INTS_MSK, |
| 104 | .mask_invert = true, |
| 105 | }; |
| 106 | |
| 107 | static const struct regmap_irq_chip atc2609a_regmap_irq_chip = { |
| 108 | .name = "atc2609a", |
| 109 | .irqs = atc2609a_regmap_irqs, |
| 110 | .num_irqs = ARRAY_SIZE(atc2609a_regmap_irqs), |
| 111 | .num_regs = 1, |
| 112 | .status_base = ATC2609A_INTS_PD, |
| 113 | .mask_base = ATC2609A_INTS_MSK, |
| 114 | .mask_invert = true, |
| 115 | }; |
| 116 | |
| 117 | static const struct resource atc2603c_onkey_resources[] = { |
| 118 | DEFINE_RES_IRQ(ATC2603C_IRQ_ONOFF), |
| 119 | }; |
| 120 | |
| 121 | static const struct resource atc2609a_onkey_resources[] = { |
| 122 | DEFINE_RES_IRQ(ATC2609A_IRQ_ONOFF), |
| 123 | }; |
| 124 | |
| 125 | static const struct mfd_cell atc2603c_mfd_cells[] = { |
| 126 | { .name = "atc260x-regulator" }, |
| 127 | { .name = "atc260x-pwrc" }, |
| 128 | { |
| 129 | .name = "atc260x-onkey", |
| 130 | .num_resources = ARRAY_SIZE(atc2603c_onkey_resources), |
| 131 | .resources = atc2603c_onkey_resources, |
| 132 | }, |
| 133 | }; |
| 134 | |
| 135 | static const struct mfd_cell atc2609a_mfd_cells[] = { |
| 136 | { .name = "atc260x-regulator" }, |
| 137 | { .name = "atc260x-pwrc" }, |
| 138 | { |
| 139 | .name = "atc260x-onkey", |
| 140 | .num_resources = ARRAY_SIZE(atc2609a_onkey_resources), |
| 141 | .resources = atc2609a_onkey_resources, |
| 142 | }, |
| 143 | }; |
| 144 | |
| 145 | static const struct atc260x_init_regs atc2603c_init_regs = { |
| 146 | .cmu_devrst = ATC2603C_CMU_DEVRST, |
| 147 | .cmu_devrst_ints = ATC2603C_CMU_DEVRST_INTS, |
| 148 | .ints_msk = ATC2603C_INTS_MSK, |
| 149 | .pad_en = ATC2603C_PAD_EN, |
| 150 | .pad_en_extirq = ATC2603C_PAD_EN_EXTIRQ, |
| 151 | }; |
| 152 | |
| 153 | static const struct atc260x_init_regs atc2609a_init_regs = { |
| 154 | .cmu_devrst = ATC2609A_CMU_DEVRST, |
| 155 | .cmu_devrst_ints = ATC2609A_CMU_DEVRST_INTS, |
| 156 | .ints_msk = ATC2609A_INTS_MSK, |
| 157 | .pad_en = ATC2609A_PAD_EN, |
| 158 | .pad_en_extirq = ATC2609A_PAD_EN_EXTIRQ, |
| 159 | }; |
| 160 | |
| 161 | static void atc260x_cmu_reset(struct atc260x *atc260x) |
| 162 | { |
| 163 | const struct atc260x_init_regs *regs = atc260x->init_regs; |
| 164 | |
| 165 | /* Assert reset */ |
| 166 | regmap_update_bits(atc260x->regmap, regs->cmu_devrst, |
| 167 | regs->cmu_devrst_ints, ~regs->cmu_devrst_ints); |
| 168 | |
| 169 | /* De-assert reset */ |
| 170 | regmap_update_bits(atc260x->regmap, regs->cmu_devrst, |
| 171 | regs->cmu_devrst_ints, regs->cmu_devrst_ints); |
| 172 | } |
| 173 | |
| 174 | static void atc260x_dev_init(struct atc260x *atc260x) |
| 175 | { |
| 176 | const struct atc260x_init_regs *regs = atc260x->init_regs; |
| 177 | |
| 178 | /* Initialize interrupt block */ |
| 179 | atc260x_cmu_reset(atc260x); |
| 180 | |
| 181 | /* Disable all interrupt sources */ |
| 182 | regmap_write(atc260x->regmap, regs->ints_msk, 0); |
| 183 | |
| 184 | /* Enable EXTIRQ pad */ |
| 185 | regmap_update_bits(atc260x->regmap, regs->pad_en, |
| 186 | regs->pad_en_extirq, regs->pad_en_extirq); |
| 187 | } |
| 188 | |
| 189 | /** |
| 190 | * atc260x_match_device(): Setup ATC260x variant related fields |
| 191 | * |
| 192 | * @atc260x: ATC260x device to setup (.dev field must be set) |
| 193 | * @regmap_cfg: regmap config associated with this ATC260x device |
| 194 | * |
| 195 | * This lets the ATC260x core configure the MFD cells and register maps |
| 196 | * for later use. |
| 197 | */ |
| 198 | int atc260x_match_device(struct atc260x *atc260x, struct regmap_config *regmap_cfg) |
| 199 | { |
| 200 | struct device *dev = atc260x->dev; |
| 201 | const void *of_data; |
| 202 | |
| 203 | of_data = of_device_get_match_data(dev); |
| 204 | if (!of_data) |
| 205 | return -ENODEV; |
| 206 | |
| 207 | atc260x->ic_type = (unsigned long)of_data; |
| 208 | |
| 209 | switch (atc260x->ic_type) { |
| 210 | case ATC2603C: |
| 211 | *regmap_cfg = atc2603c_regmap_config; |
| 212 | atc260x->regmap_irq_chip = &atc2603c_regmap_irq_chip; |
| 213 | atc260x->cells = atc2603c_mfd_cells; |
| 214 | atc260x->nr_cells = ARRAY_SIZE(atc2603c_mfd_cells); |
| 215 | atc260x->type_name = "atc2603c"; |
| 216 | atc260x->rev_reg = ATC2603C_CHIP_VER; |
| 217 | atc260x->init_regs = &atc2603c_init_regs; |
| 218 | break; |
| 219 | case ATC2609A: |
| 220 | *regmap_cfg = atc2609a_regmap_config; |
| 221 | atc260x->regmap_irq_chip = &atc2609a_regmap_irq_chip; |
| 222 | atc260x->cells = atc2609a_mfd_cells; |
| 223 | atc260x->nr_cells = ARRAY_SIZE(atc2609a_mfd_cells); |
| 224 | atc260x->type_name = "atc2609a"; |
| 225 | atc260x->rev_reg = ATC2609A_CHIP_VER; |
| 226 | atc260x->init_regs = &atc2609a_init_regs; |
| 227 | break; |
| 228 | default: |
| 229 | dev_err(dev, "Unsupported ATC260x device type: %u\n", |
| 230 | atc260x->ic_type); |
| 231 | return -EINVAL; |
| 232 | } |
| 233 | |
| 234 | atc260x->regmap_mutex = devm_kzalloc(dev, sizeof(*atc260x->regmap_mutex), |
| 235 | GFP_KERNEL); |
| 236 | if (!atc260x->regmap_mutex) |
| 237 | return -ENOMEM; |
| 238 | |
| 239 | mutex_init(atc260x->regmap_mutex); |
| 240 | |
| 241 | regmap_cfg->lock = regmap_lock_mutex, |
| 242 | regmap_cfg->unlock = regmap_unlock_mutex, |
| 243 | regmap_cfg->lock_arg = atc260x->regmap_mutex; |
| 244 | |
| 245 | return 0; |
| 246 | } |
| 247 | EXPORT_SYMBOL_GPL(atc260x_match_device); |
| 248 | |
| 249 | /** |
| 250 | * atc260x_device_probe(): Probe a configured ATC260x device |
| 251 | * |
| 252 | * @atc260x: ATC260x device to probe (must be configured) |
| 253 | * |
| 254 | * This function lets the ATC260x core register the ATC260x MFD devices |
| 255 | * and IRQCHIP. The ATC260x device passed in must be fully configured |
| 256 | * with atc260x_match_device, its IRQ set, and regmap created. |
| 257 | */ |
| 258 | int atc260x_device_probe(struct atc260x *atc260x) |
| 259 | { |
| 260 | struct device *dev = atc260x->dev; |
| 261 | unsigned int chip_rev; |
| 262 | int ret; |
| 263 | |
| 264 | if (!atc260x->irq) { |
| 265 | dev_err(dev, "No interrupt support\n"); |
| 266 | return -EINVAL; |
| 267 | } |
| 268 | |
| 269 | /* Initialize the hardware */ |
| 270 | atc260x_dev_init(atc260x); |
| 271 | |
| 272 | ret = regmap_read(atc260x->regmap, atc260x->rev_reg, &chip_rev); |
| 273 | if (ret) { |
| 274 | dev_err(dev, "Failed to get chip revision\n"); |
| 275 | return ret; |
| 276 | } |
| 277 | |
| 278 | if (chip_rev > ATC260X_CHIP_REV_MAX) { |
| 279 | dev_err(dev, "Unknown chip revision: %u\n", chip_rev); |
| 280 | return -EINVAL; |
| 281 | } |
| 282 | |
| 283 | atc260x->ic_ver = __ffs(chip_rev + 1U); |
| 284 | |
| 285 | dev_info(dev, "Detected chip type %s rev.%c\n", |
| 286 | atc260x->type_name, 'A' + atc260x->ic_ver); |
| 287 | |
| 288 | ret = devm_regmap_add_irq_chip(dev, atc260x->regmap, atc260x->irq, IRQF_ONESHOT, |
| 289 | -1, atc260x->regmap_irq_chip, &atc260x->irq_data); |
| 290 | if (ret) { |
| 291 | dev_err(dev, "Failed to add IRQ chip: %d\n", ret); |
| 292 | return ret; |
| 293 | } |
| 294 | |
| 295 | ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, |
| 296 | atc260x->cells, atc260x->nr_cells, NULL, 0, |
| 297 | regmap_irq_get_domain(atc260x->irq_data)); |
| 298 | if (ret) { |
| 299 | dev_err(dev, "Failed to add child devices: %d\n", ret); |
| 300 | regmap_del_irq_chip(atc260x->irq, atc260x->irq_data); |
| 301 | } |
| 302 | |
| 303 | return ret; |
| 304 | } |
| 305 | EXPORT_SYMBOL_GPL(atc260x_device_probe); |
| 306 | |
| 307 | MODULE_DESCRIPTION("ATC260x PMICs Core support"); |
| 308 | MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>"); |
| 309 | MODULE_AUTHOR("Cristian Ciocaltea <cristian.ciocaltea@gmail.com>"); |
| 310 | MODULE_LICENSE("GPL"); |