Commit | Line | Data |
---|---|---|
a0df3ef0 JP |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * PFSM (Pre-configurable Finite State Machine) driver for TI TPS6594/TPS6593/LP8764 PMICs | |
4 | * | |
5 | * Copyright (C) 2023 BayLibre Incorporated - https://www.baylibre.com/ | |
6 | */ | |
7 | ||
8 | #include <linux/errno.h> | |
9 | #include <linux/fs.h> | |
10 | #include <linux/interrupt.h> | |
11 | #include <linux/ioctl.h> | |
12 | #include <linux/miscdevice.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/platform_device.h> | |
15 | #include <linux/regmap.h> | |
16 | ||
17 | #include <linux/mfd/tps6594.h> | |
18 | ||
19 | #include <linux/tps6594_pfsm.h> | |
20 | ||
21 | #define TPS6594_STARTUP_DEST_MCU_ONLY_VAL 2 | |
22 | #define TPS6594_STARTUP_DEST_ACTIVE_VAL 3 | |
23 | #define TPS6594_STARTUP_DEST_SHIFT 5 | |
24 | #define TPS6594_STARTUP_DEST_MCU_ONLY (TPS6594_STARTUP_DEST_MCU_ONLY_VAL \ | |
25 | << TPS6594_STARTUP_DEST_SHIFT) | |
26 | #define TPS6594_STARTUP_DEST_ACTIVE (TPS6594_STARTUP_DEST_ACTIVE_VAL \ | |
27 | << TPS6594_STARTUP_DEST_SHIFT) | |
28 | ||
29 | /* | |
30 | * To update the PMIC firmware, the user must be able to access | |
31 | * page 0 (user registers) and page 1 (NVM control and configuration). | |
32 | */ | |
33 | #define TPS6594_PMIC_MAX_POS 0x200 | |
34 | ||
35 | #define TPS6594_FILE_TO_PFSM(f) container_of((f)->private_data, struct tps6594_pfsm, miscdev) | |
36 | ||
37 | /** | |
38 | * struct tps6594_pfsm - device private data structure | |
39 | * | |
40 | * @miscdev: misc device infos | |
41 | * @regmap: regmap for accessing the device registers | |
42 | */ | |
43 | struct tps6594_pfsm { | |
44 | struct miscdevice miscdev; | |
45 | struct regmap *regmap; | |
46 | }; | |
47 | ||
48 | static ssize_t tps6594_pfsm_read(struct file *f, char __user *buf, | |
49 | size_t count, loff_t *ppos) | |
50 | { | |
51 | struct tps6594_pfsm *pfsm = TPS6594_FILE_TO_PFSM(f); | |
52 | loff_t pos = *ppos; | |
53 | unsigned int val; | |
54 | int ret; | |
55 | int i; | |
56 | ||
57 | if (pos < 0) | |
58 | return -EINVAL; | |
59 | if (pos >= TPS6594_PMIC_MAX_POS) | |
60 | return 0; | |
61 | if (count > TPS6594_PMIC_MAX_POS - pos) | |
62 | count = TPS6594_PMIC_MAX_POS - pos; | |
63 | ||
64 | for (i = 0 ; i < count ; i++) { | |
65 | ret = regmap_read(pfsm->regmap, pos + i, &val); | |
66 | if (ret) | |
67 | return ret; | |
68 | ||
69 | if (put_user(val, buf + i)) | |
70 | return -EFAULT; | |
71 | } | |
72 | ||
73 | *ppos = pos + count; | |
74 | ||
75 | return count; | |
76 | } | |
77 | ||
78 | static ssize_t tps6594_pfsm_write(struct file *f, const char __user *buf, | |
79 | size_t count, loff_t *ppos) | |
80 | { | |
81 | struct tps6594_pfsm *pfsm = TPS6594_FILE_TO_PFSM(f); | |
82 | loff_t pos = *ppos; | |
83 | char val; | |
84 | int ret; | |
85 | int i; | |
86 | ||
87 | if (pos < 0) | |
88 | return -EINVAL; | |
89 | if (pos >= TPS6594_PMIC_MAX_POS || !count) | |
90 | return 0; | |
91 | if (count > TPS6594_PMIC_MAX_POS - pos) | |
92 | count = TPS6594_PMIC_MAX_POS - pos; | |
93 | ||
94 | for (i = 0 ; i < count ; i++) { | |
95 | if (get_user(val, buf + i)) | |
96 | return -EFAULT; | |
97 | ||
98 | ret = regmap_write(pfsm->regmap, pos + i, val); | |
99 | if (ret) | |
100 | return ret; | |
101 | } | |
102 | ||
103 | *ppos = pos + count; | |
104 | ||
105 | return count; | |
106 | } | |
107 | ||
108 | static int tps6594_pfsm_configure_ret_trig(struct regmap *regmap, u8 gpio_ret, u8 ddr_ret) | |
109 | { | |
110 | int ret; | |
111 | ||
112 | if (gpio_ret) | |
113 | ret = regmap_set_bits(regmap, TPS6594_REG_FSM_I2C_TRIGGERS, | |
114 | TPS6594_BIT_TRIGGER_I2C(5) | TPS6594_BIT_TRIGGER_I2C(6)); | |
115 | else | |
116 | ret = regmap_clear_bits(regmap, TPS6594_REG_FSM_I2C_TRIGGERS, | |
117 | TPS6594_BIT_TRIGGER_I2C(5) | TPS6594_BIT_TRIGGER_I2C(6)); | |
118 | if (ret) | |
119 | return ret; | |
120 | ||
121 | if (ddr_ret) | |
122 | ret = regmap_set_bits(regmap, TPS6594_REG_FSM_I2C_TRIGGERS, | |
123 | TPS6594_BIT_TRIGGER_I2C(7)); | |
124 | else | |
125 | ret = regmap_clear_bits(regmap, TPS6594_REG_FSM_I2C_TRIGGERS, | |
126 | TPS6594_BIT_TRIGGER_I2C(7)); | |
127 | ||
128 | return ret; | |
129 | } | |
130 | ||
131 | static long tps6594_pfsm_ioctl(struct file *f, unsigned int cmd, unsigned long arg) | |
132 | { | |
133 | struct tps6594_pfsm *pfsm = TPS6594_FILE_TO_PFSM(f); | |
134 | struct pmic_state_opt state_opt; | |
135 | void __user *argp = (void __user *)arg; | |
136 | int ret = -ENOIOCTLCMD; | |
137 | ||
138 | switch (cmd) { | |
139 | case PMIC_GOTO_STANDBY: | |
140 | /* Disable LP mode */ | |
141 | ret = regmap_clear_bits(pfsm->regmap, TPS6594_REG_RTC_CTRL_2, | |
142 | TPS6594_BIT_LP_STANDBY_SEL); | |
143 | if (ret) | |
144 | return ret; | |
145 | ||
146 | /* Force trigger */ | |
147 | ret = regmap_write_bits(pfsm->regmap, TPS6594_REG_FSM_I2C_TRIGGERS, | |
148 | TPS6594_BIT_TRIGGER_I2C(0), TPS6594_BIT_TRIGGER_I2C(0)); | |
149 | break; | |
150 | case PMIC_GOTO_LP_STANDBY: | |
151 | /* Enable LP mode */ | |
152 | ret = regmap_set_bits(pfsm->regmap, TPS6594_REG_RTC_CTRL_2, | |
153 | TPS6594_BIT_LP_STANDBY_SEL); | |
154 | if (ret) | |
155 | return ret; | |
156 | ||
157 | /* Force trigger */ | |
158 | ret = regmap_write_bits(pfsm->regmap, TPS6594_REG_FSM_I2C_TRIGGERS, | |
159 | TPS6594_BIT_TRIGGER_I2C(0), TPS6594_BIT_TRIGGER_I2C(0)); | |
160 | break; | |
161 | case PMIC_UPDATE_PGM: | |
162 | /* Force trigger */ | |
163 | ret = regmap_write_bits(pfsm->regmap, TPS6594_REG_FSM_I2C_TRIGGERS, | |
164 | TPS6594_BIT_TRIGGER_I2C(3), TPS6594_BIT_TRIGGER_I2C(3)); | |
165 | break; | |
166 | case PMIC_SET_ACTIVE_STATE: | |
167 | /* Modify NSLEEP1-2 bits */ | |
168 | ret = regmap_set_bits(pfsm->regmap, TPS6594_REG_FSM_NSLEEP_TRIGGERS, | |
169 | TPS6594_BIT_NSLEEP1B | TPS6594_BIT_NSLEEP2B); | |
170 | break; | |
171 | case PMIC_SET_MCU_ONLY_STATE: | |
172 | if (copy_from_user(&state_opt, argp, sizeof(state_opt))) | |
173 | return -EFAULT; | |
174 | ||
175 | /* Configure retention triggers */ | |
176 | ret = tps6594_pfsm_configure_ret_trig(pfsm->regmap, state_opt.gpio_retention, | |
177 | state_opt.ddr_retention); | |
178 | if (ret) | |
179 | return ret; | |
180 | ||
181 | /* Modify NSLEEP1-2 bits */ | |
182 | ret = regmap_clear_bits(pfsm->regmap, TPS6594_REG_FSM_NSLEEP_TRIGGERS, | |
183 | TPS6594_BIT_NSLEEP1B); | |
184 | if (ret) | |
185 | return ret; | |
186 | ||
187 | ret = regmap_set_bits(pfsm->regmap, TPS6594_REG_FSM_NSLEEP_TRIGGERS, | |
188 | TPS6594_BIT_NSLEEP2B); | |
189 | break; | |
190 | case PMIC_SET_RETENTION_STATE: | |
191 | if (copy_from_user(&state_opt, argp, sizeof(state_opt))) | |
192 | return -EFAULT; | |
193 | ||
194 | /* Configure wake-up destination */ | |
195 | if (state_opt.mcu_only_startup_dest) | |
196 | ret = regmap_write_bits(pfsm->regmap, TPS6594_REG_RTC_CTRL_2, | |
197 | TPS6594_MASK_STARTUP_DEST, | |
198 | TPS6594_STARTUP_DEST_MCU_ONLY); | |
199 | else | |
200 | ret = regmap_write_bits(pfsm->regmap, TPS6594_REG_RTC_CTRL_2, | |
201 | TPS6594_MASK_STARTUP_DEST, | |
202 | TPS6594_STARTUP_DEST_ACTIVE); | |
203 | if (ret) | |
204 | return ret; | |
205 | ||
206 | /* Configure retention triggers */ | |
207 | ret = tps6594_pfsm_configure_ret_trig(pfsm->regmap, state_opt.gpio_retention, | |
208 | state_opt.ddr_retention); | |
209 | if (ret) | |
210 | return ret; | |
211 | ||
212 | /* Modify NSLEEP1-2 bits */ | |
213 | ret = regmap_clear_bits(pfsm->regmap, TPS6594_REG_FSM_NSLEEP_TRIGGERS, | |
214 | TPS6594_BIT_NSLEEP2B); | |
215 | break; | |
216 | } | |
217 | ||
218 | return ret; | |
219 | } | |
220 | ||
221 | static const struct file_operations tps6594_pfsm_fops = { | |
222 | .owner = THIS_MODULE, | |
223 | .llseek = generic_file_llseek, | |
224 | .read = tps6594_pfsm_read, | |
225 | .write = tps6594_pfsm_write, | |
226 | .unlocked_ioctl = tps6594_pfsm_ioctl, | |
227 | .compat_ioctl = compat_ptr_ioctl, | |
228 | }; | |
229 | ||
230 | static irqreturn_t tps6594_pfsm_isr(int irq, void *dev_id) | |
231 | { | |
232 | struct platform_device *pdev = dev_id; | |
233 | int i; | |
234 | ||
235 | for (i = 0 ; i < pdev->num_resources ; i++) { | |
236 | if (irq == platform_get_irq_byname(pdev, pdev->resource[i].name)) { | |
237 | dev_err(pdev->dev.parent, "%s event detected\n", pdev->resource[i].name); | |
238 | return IRQ_HANDLED; | |
239 | } | |
240 | } | |
241 | ||
242 | return IRQ_NONE; | |
243 | } | |
244 | ||
245 | static int tps6594_pfsm_probe(struct platform_device *pdev) | |
246 | { | |
247 | struct tps6594_pfsm *pfsm; | |
248 | struct tps6594 *tps = dev_get_drvdata(pdev->dev.parent); | |
249 | struct device *dev = &pdev->dev; | |
250 | int irq; | |
251 | int ret; | |
252 | int i; | |
253 | ||
254 | pfsm = devm_kzalloc(dev, sizeof(struct tps6594_pfsm), GFP_KERNEL); | |
255 | if (!pfsm) | |
256 | return -ENOMEM; | |
257 | ||
258 | pfsm->regmap = tps->regmap; | |
259 | ||
260 | pfsm->miscdev.minor = MISC_DYNAMIC_MINOR; | |
261 | pfsm->miscdev.name = devm_kasprintf(dev, GFP_KERNEL, "pfsm-%ld-0x%02x", | |
262 | tps->chip_id, tps->reg); | |
263 | pfsm->miscdev.fops = &tps6594_pfsm_fops; | |
264 | pfsm->miscdev.parent = dev->parent; | |
265 | ||
266 | for (i = 0 ; i < pdev->num_resources ; i++) { | |
267 | irq = platform_get_irq_byname(pdev, pdev->resource[i].name); | |
268 | if (irq < 0) | |
1314e122 | 269 | return irq; |
a0df3ef0 JP |
270 | |
271 | ret = devm_request_threaded_irq(dev, irq, NULL, | |
272 | tps6594_pfsm_isr, IRQF_ONESHOT, | |
273 | pdev->resource[i].name, pdev); | |
274 | if (ret) | |
275 | return dev_err_probe(dev, ret, "Failed to request irq\n"); | |
276 | } | |
277 | ||
278 | platform_set_drvdata(pdev, pfsm); | |
279 | ||
280 | return misc_register(&pfsm->miscdev); | |
281 | } | |
282 | ||
6dab711d | 283 | static void tps6594_pfsm_remove(struct platform_device *pdev) |
a0df3ef0 JP |
284 | { |
285 | struct tps6594_pfsm *pfsm = platform_get_drvdata(pdev); | |
286 | ||
287 | misc_deregister(&pfsm->miscdev); | |
a0df3ef0 JP |
288 | } |
289 | ||
290 | static struct platform_driver tps6594_pfsm_driver = { | |
291 | .driver = { | |
292 | .name = "tps6594-pfsm", | |
293 | }, | |
294 | .probe = tps6594_pfsm_probe, | |
6dab711d | 295 | .remove_new = tps6594_pfsm_remove, |
a0df3ef0 JP |
296 | }; |
297 | ||
298 | module_platform_driver(tps6594_pfsm_driver); | |
299 | ||
300 | MODULE_ALIAS("platform:tps6594-pfsm"); | |
301 | MODULE_AUTHOR("Julien Panis <jpanis@baylibre.com>"); | |
302 | MODULE_DESCRIPTION("TPS6594 Pre-configurable Finite State Machine Driver"); | |
303 | MODULE_LICENSE("GPL"); |