Commit | Line | Data |
---|---|---|
f7cb7fe3 CC |
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"); |