1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * MFD core driver for Ricoh RN5T618 PMIC |
4 | * |
5 | * Copyright (C) 2014 Beniamino Galvani <b.galvani@gmail.com> |
6 | * Copyright (C) 2016 Toradex AG |
7 | */ |
8 | |
9 | #include <linux/delay.h> |
10 | #include <linux/i2c.h> |
11 | #include <linux/interrupt.h> |
12 | #include <linux/irq.h> |
13 | #include <linux/mfd/core.h> |
14 | #include <linux/mfd/rn5t618.h> |
15 | #include <linux/module.h> |
16 | #include <linux/of.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/reboot.h> |
19 | #include <linux/regmap.h> |
20 | |
21 | static const struct mfd_cell rn5t618_cells[] = { |
22 | { .name = "rn5t618-regulator" }, |
23 | { .name = "rn5t618-wdt" }, |
24 | }; |
25 | |
26 | static const struct mfd_cell rc5t619_cells[] = { |
27 | { .name = "rn5t618-adc" }, |
28 | { .name = "rn5t618-power" }, |
29 | { .name = "rn5t618-regulator" }, |
30 | { .name = "rc5t619-rtc" }, |
31 | { .name = "rn5t618-wdt" }, |
32 | }; |
33 | |
34 | static bool rn5t618_volatile_reg(struct device *dev, unsigned int reg) |
35 | { |
36 | switch (reg) { |
37 | case RN5T618_WATCHDOGCNT: |
38 | case RN5T618_DCIRQ: |
39 | case RN5T618_ILIMDATAH ... RN5T618_AIN0DATAL: |
40 | case RN5T618_ADCCNT3: |
41 | case RN5T618_IR_ADC1 ... RN5T618_IR_ADC3: |
42 | case RN5T618_IR_GPR: |
43 | case RN5T618_IR_GPF: |
44 | case RN5T618_MON_IOIN: |
45 | case RN5T618_INTMON: |
46 | case RN5T618_RTC_CTRL1 ... RN5T618_RTC_CTRL2: |
47 | case RN5T618_RTC_SECONDS ... RN5T618_RTC_YEAR: |
48 | case RN5T618_CHGCTL1: |
49 | case RN5T618_REGISET1 ... RN5T618_REGISET2: |
50 | case RN5T618_CHGSTATE: |
51 | case RN5T618_CHGCTRL_IRR ... RN5T618_CHGERR_MONI: |
52 | case RN5T618_GCHGDET: |
53 | case RN5T618_CONTROL ... RN5T618_CC_AVEREG0: |
54 | return true; |
55 | default: |
56 | return false; |
57 | } |
58 | } |
59 | |
60 | static const struct regmap_config rn5t618_regmap_config = { |
61 | .reg_bits = 8, |
62 | .val_bits = 8, |
63 | .volatile_reg = rn5t618_volatile_reg, |
64 | .max_register = RN5T618_MAX_REG, |
65 | .cache_type = REGCACHE_RBTREE, |
66 | }; |
67 | |
68 | static const struct regmap_irq rc5t619_irqs[] = { |
69 | REGMAP_IRQ_REG(RN5T618_IRQ_SYS, 0, BIT(0)), |
70 | REGMAP_IRQ_REG(RN5T618_IRQ_DCDC, 0, BIT(1)), |
71 | REGMAP_IRQ_REG(RN5T618_IRQ_RTC, 0, BIT(2)), |
72 | REGMAP_IRQ_REG(RN5T618_IRQ_ADC, 0, BIT(3)), |
73 | REGMAP_IRQ_REG(RN5T618_IRQ_GPIO, 0, BIT(4)), |
74 | REGMAP_IRQ_REG(RN5T618_IRQ_CHG, 0, BIT(6)), |
75 | }; |
76 | |
77 | static const struct regmap_irq_chip rc5t619_irq_chip = { |
78 | .name = "rc5t619" , |
79 | .irqs = rc5t619_irqs, |
80 | .num_irqs = ARRAY_SIZE(rc5t619_irqs), |
81 | .num_regs = 1, |
82 | .status_base = RN5T618_INTMON, |
83 | .unmask_base = RN5T618_INTEN, |
84 | }; |
85 | |
86 | static struct i2c_client *rn5t618_pm_power_off; |
87 | static struct notifier_block rn5t618_restart_handler; |
88 | |
89 | static int rn5t618_irq_init(struct rn5t618 *rn5t618) |
90 | { |
91 | const struct regmap_irq_chip *irq_chip = NULL; |
92 | int ret; |
93 | |
94 | if (!rn5t618->irq) |
95 | return 0; |
96 | |
97 | switch (rn5t618->variant) { |
98 | case RC5T619: |
99 | irq_chip = &rc5t619_irq_chip; |
100 | break; |
101 | default: |
102 | dev_err(rn5t618->dev, "Currently no IRQ support for variant %d\n" , |
103 | (int)rn5t618->variant); |
104 | return -ENOENT; |
105 | } |
106 | |
107 | ret = devm_regmap_add_irq_chip(dev: rn5t618->dev, map: rn5t618->regmap, |
108 | irq: rn5t618->irq, |
109 | IRQF_TRIGGER_LOW | IRQF_ONESHOT, |
110 | irq_base: 0, chip: irq_chip, data: &rn5t618->irq_data); |
111 | if (ret) |
112 | dev_err(rn5t618->dev, "Failed to register IRQ chip\n" ); |
113 | |
114 | return ret; |
115 | } |
116 | |
117 | static void rn5t618_trigger_poweroff_sequence(bool repower) |
118 | { |
119 | int ret; |
120 | |
121 | /* disable automatic repower-on */ |
122 | ret = i2c_smbus_read_byte_data(client: rn5t618_pm_power_off, RN5T618_REPCNT); |
123 | if (ret < 0) |
124 | goto err; |
125 | |
126 | ret &= ~RN5T618_REPCNT_REPWRON; |
127 | if (repower) |
128 | ret |= RN5T618_REPCNT_REPWRON; |
129 | |
130 | ret = i2c_smbus_write_byte_data(client: rn5t618_pm_power_off, |
131 | RN5T618_REPCNT, value: (u8)ret); |
132 | if (ret < 0) |
133 | goto err; |
134 | |
135 | /* start power-off sequence */ |
136 | ret = i2c_smbus_read_byte_data(client: rn5t618_pm_power_off, RN5T618_SLPCNT); |
137 | if (ret < 0) |
138 | goto err; |
139 | |
140 | ret |= RN5T618_SLPCNT_SWPWROFF; |
141 | |
142 | ret = i2c_smbus_write_byte_data(client: rn5t618_pm_power_off, |
143 | RN5T618_SLPCNT, value: (u8)ret); |
144 | if (ret < 0) |
145 | goto err; |
146 | |
147 | return; |
148 | |
149 | err: |
150 | dev_alert(&rn5t618_pm_power_off->dev, "Failed to shutdown (err = %d)\n" , ret); |
151 | } |
152 | |
153 | static void rn5t618_power_off(void) |
154 | { |
155 | rn5t618_trigger_poweroff_sequence(repower: false); |
156 | } |
157 | |
158 | static int rn5t618_restart(struct notifier_block *this, |
159 | unsigned long mode, void *cmd) |
160 | { |
161 | rn5t618_trigger_poweroff_sequence(repower: true); |
162 | |
163 | /* |
164 | * Re-power factor detection on PMIC side is not instant. 1ms |
165 | * proved to be enough time until reset takes effect. |
166 | */ |
167 | mdelay(1); |
168 | |
169 | return NOTIFY_DONE; |
170 | } |
171 | |
172 | static const struct of_device_id rn5t618_of_match[] = { |
173 | { .compatible = "ricoh,rn5t567" , .data = (void *)RN5T567 }, |
174 | { .compatible = "ricoh,rn5t618" , .data = (void *)RN5T618 }, |
175 | { .compatible = "ricoh,rc5t619" , .data = (void *)RC5T619 }, |
176 | { } |
177 | }; |
178 | MODULE_DEVICE_TABLE(of, rn5t618_of_match); |
179 | |
180 | static int rn5t618_i2c_probe(struct i2c_client *i2c) |
181 | { |
182 | struct rn5t618 *priv; |
183 | int ret; |
184 | |
185 | priv = devm_kzalloc(dev: &i2c->dev, size: sizeof(*priv), GFP_KERNEL); |
186 | if (!priv) |
187 | return -ENOMEM; |
188 | |
189 | i2c_set_clientdata(client: i2c, data: priv); |
190 | priv->variant = (long)i2c_get_match_data(client: i2c); |
191 | priv->irq = i2c->irq; |
192 | priv->dev = &i2c->dev; |
193 | |
194 | priv->regmap = devm_regmap_init_i2c(i2c, &rn5t618_regmap_config); |
195 | if (IS_ERR(ptr: priv->regmap)) { |
196 | ret = PTR_ERR(ptr: priv->regmap); |
197 | dev_err(&i2c->dev, "regmap init failed: %d\n" , ret); |
198 | return ret; |
199 | } |
200 | |
201 | if (priv->variant == RC5T619) |
202 | ret = devm_mfd_add_devices(dev: &i2c->dev, PLATFORM_DEVID_NONE, |
203 | cells: rc5t619_cells, |
204 | ARRAY_SIZE(rc5t619_cells), |
205 | NULL, irq_base: 0, NULL); |
206 | else |
207 | ret = devm_mfd_add_devices(dev: &i2c->dev, PLATFORM_DEVID_NONE, |
208 | cells: rn5t618_cells, |
209 | ARRAY_SIZE(rn5t618_cells), |
210 | NULL, irq_base: 0, NULL); |
211 | if (ret) { |
212 | dev_err(&i2c->dev, "failed to add sub-devices: %d\n" , ret); |
213 | return ret; |
214 | } |
215 | |
216 | rn5t618_pm_power_off = i2c; |
217 | if (of_device_is_system_power_controller(np: i2c->dev.of_node)) { |
218 | if (!pm_power_off) |
219 | pm_power_off = rn5t618_power_off; |
220 | else |
221 | dev_warn(&i2c->dev, "Poweroff callback already assigned\n" ); |
222 | } |
223 | |
224 | rn5t618_restart_handler.notifier_call = rn5t618_restart; |
225 | rn5t618_restart_handler.priority = 192; |
226 | |
227 | ret = register_restart_handler(&rn5t618_restart_handler); |
228 | if (ret) { |
229 | dev_err(&i2c->dev, "cannot register restart handler, %d\n" , ret); |
230 | return ret; |
231 | } |
232 | |
233 | return rn5t618_irq_init(rn5t618: priv); |
234 | } |
235 | |
236 | static void rn5t618_i2c_remove(struct i2c_client *i2c) |
237 | { |
238 | if (i2c == rn5t618_pm_power_off) { |
239 | rn5t618_pm_power_off = NULL; |
240 | pm_power_off = NULL; |
241 | } |
242 | |
243 | unregister_restart_handler(&rn5t618_restart_handler); |
244 | } |
245 | |
246 | static int __maybe_unused rn5t618_i2c_suspend(struct device *dev) |
247 | { |
248 | struct rn5t618 *priv = dev_get_drvdata(dev); |
249 | |
250 | if (priv->irq) |
251 | disable_irq(irq: priv->irq); |
252 | |
253 | return 0; |
254 | } |
255 | |
256 | static int __maybe_unused rn5t618_i2c_resume(struct device *dev) |
257 | { |
258 | struct rn5t618 *priv = dev_get_drvdata(dev); |
259 | |
260 | if (priv->irq) |
261 | enable_irq(irq: priv->irq); |
262 | |
263 | return 0; |
264 | } |
265 | |
266 | static SIMPLE_DEV_PM_OPS(rn5t618_i2c_dev_pm_ops, |
267 | rn5t618_i2c_suspend, |
268 | rn5t618_i2c_resume); |
269 | |
270 | static struct i2c_driver rn5t618_i2c_driver = { |
271 | .driver = { |
272 | .name = "rn5t618" , |
273 | .of_match_table = rn5t618_of_match, |
274 | .pm = &rn5t618_i2c_dev_pm_ops, |
275 | }, |
276 | .probe = rn5t618_i2c_probe, |
277 | .remove = rn5t618_i2c_remove, |
278 | }; |
279 | |
280 | module_i2c_driver(rn5t618_i2c_driver); |
281 | |
282 | MODULE_AUTHOR("Beniamino Galvani <b.galvani@gmail.com>" ); |
283 | MODULE_DESCRIPTION("Ricoh RN5T567/618 MFD driver" ); |
284 | MODULE_LICENSE("GPL v2" ); |
285 | |