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