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

source code of linux/drivers/mfd/atc260x-core.c