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(map: pfsm->regmap, reg: pos + i, val: &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(map: pfsm->regmap, reg: 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(map: regmap, TPS6594_REG_FSM_I2C_TRIGGERS, |
114 | TPS6594_BIT_TRIGGER_I2C(5) | TPS6594_BIT_TRIGGER_I2C(6)); |
115 | else |
116 | ret = regmap_clear_bits(map: 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(map: regmap, TPS6594_REG_FSM_I2C_TRIGGERS, |
123 | TPS6594_BIT_TRIGGER_I2C(7)); |
124 | else |
125 | ret = regmap_clear_bits(map: 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(map: 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(map: 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(map: 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(map: 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(map: 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(map: 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(to: &state_opt, from: argp, n: sizeof(state_opt))) |
173 | return -EFAULT; |
174 | |
175 | /* Configure retention triggers */ |
176 | ret = tps6594_pfsm_configure_ret_trig(regmap: pfsm->regmap, gpio_ret: state_opt.gpio_retention, |
177 | ddr_ret: state_opt.ddr_retention); |
178 | if (ret) |
179 | return ret; |
180 | |
181 | /* Modify NSLEEP1-2 bits */ |
182 | ret = regmap_clear_bits(map: pfsm->regmap, TPS6594_REG_FSM_NSLEEP_TRIGGERS, |
183 | TPS6594_BIT_NSLEEP1B); |
184 | if (ret) |
185 | return ret; |
186 | |
187 | ret = regmap_set_bits(map: pfsm->regmap, TPS6594_REG_FSM_NSLEEP_TRIGGERS, |
188 | TPS6594_BIT_NSLEEP2B); |
189 | break; |
190 | case PMIC_SET_RETENTION_STATE: |
191 | if (copy_from_user(to: &state_opt, from: argp, n: 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(map: 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(map: 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(regmap: pfsm->regmap, gpio_ret: state_opt.gpio_retention, |
208 | ddr_ret: state_opt.ddr_retention); |
209 | if (ret) |
210 | return ret; |
211 | |
212 | /* Modify NSLEEP1-2 bits */ |
213 | ret = regmap_clear_bits(map: 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(dev: pdev->dev.parent); |
249 | struct device *dev = &pdev->dev; |
250 | int irq; |
251 | int ret; |
252 | int i; |
253 | |
254 | pfsm = devm_kzalloc(dev, size: 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, fmt: "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) |
269 | return irq; |
270 | |
271 | ret = devm_request_threaded_irq(dev, irq, NULL, |
272 | thread_fn: tps6594_pfsm_isr, IRQF_ONESHOT, |
273 | devname: pdev->resource[i].name, dev_id: pdev); |
274 | if (ret) |
275 | return dev_err_probe(dev, err: ret, fmt: "Failed to request irq\n" ); |
276 | } |
277 | |
278 | platform_set_drvdata(pdev, data: pfsm); |
279 | |
280 | return misc_register(misc: &pfsm->miscdev); |
281 | } |
282 | |
283 | static void tps6594_pfsm_remove(struct platform_device *pdev) |
284 | { |
285 | struct tps6594_pfsm *pfsm = platform_get_drvdata(pdev); |
286 | |
287 | misc_deregister(misc: &pfsm->miscdev); |
288 | } |
289 | |
290 | static struct platform_driver tps6594_pfsm_driver = { |
291 | .driver = { |
292 | .name = "tps6594-pfsm" , |
293 | }, |
294 | .probe = tps6594_pfsm_probe, |
295 | .remove_new = tps6594_pfsm_remove, |
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" ); |
304 | |