atc260x-core.c (9327B)
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 19struct 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 27static 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 48static void regmap_unlock_mutex(void *__mutex) 49{ 50 struct mutex *mutex = __mutex; 51 52 mutex_unlock(mutex); 53} 54 55static 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 62static 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 69static 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 83static 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 97static 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 107static 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 117static const struct resource atc2603c_onkey_resources[] = { 118 DEFINE_RES_IRQ(ATC2603C_IRQ_ONOFF), 119}; 120 121static const struct resource atc2609a_onkey_resources[] = { 122 DEFINE_RES_IRQ(ATC2609A_IRQ_ONOFF), 123}; 124 125static 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 135static 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 145static 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 153static 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 161static 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 174static 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 */ 198int 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} 247EXPORT_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 */ 258int 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} 305EXPORT_SYMBOL_GPL(atc260x_device_probe); 306 307MODULE_DESCRIPTION("ATC260x PMICs Core support"); 308MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>"); 309MODULE_AUTHOR("Cristian Ciocaltea <cristian.ciocaltea@gmail.com>"); 310MODULE_LICENSE("GPL");