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 | |
18 | struct 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 | |
26 | static 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 | |
47 | static void regmap_unlock_mutex(void *__mutex) |
48 | { |
49 | struct mutex *mutex = __mutex; |
50 | |
51 | mutex_unlock(lock: mutex); |
52 | } |
53 | |
54 | static 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 | |
61 | static 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 | |
68 | static 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 | |
82 | static 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 | |
96 | static 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 | |
105 | static 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 | |
114 | static const struct resource atc2603c_onkey_resources[] = { |
115 | DEFINE_RES_IRQ(ATC2603C_IRQ_ONOFF), |
116 | }; |
117 | |
118 | static const struct resource atc2609a_onkey_resources[] = { |
119 | DEFINE_RES_IRQ(ATC2609A_IRQ_ONOFF), |
120 | }; |
121 | |
122 | static 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 | |
132 | static 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 | |
142 | static 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 | |
150 | static 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 | |
158 | static 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 | |
171 | static 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 | */ |
195 | int 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 | } |
244 | EXPORT_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 | */ |
255 | int 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 | } |
302 | EXPORT_SYMBOL_GPL(atc260x_device_probe); |
303 | |
304 | MODULE_DESCRIPTION("ATC260x PMICs Core support" ); |
305 | MODULE_AUTHOR("Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>" ); |
306 | MODULE_AUTHOR("Cristian Ciocaltea <cristian.ciocaltea@gmail.com>" ); |
307 | MODULE_LICENSE("GPL" ); |
308 | |