162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * PFSM (Pre-configurable Finite State Machine) driver for TI TPS6594/TPS6593/LP8764 PMICs 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2023 BayLibre Incorporated - https://www.baylibre.com/ 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/errno.h> 962306a36Sopenharmony_ci#include <linux/fs.h> 1062306a36Sopenharmony_ci#include <linux/interrupt.h> 1162306a36Sopenharmony_ci#include <linux/ioctl.h> 1262306a36Sopenharmony_ci#include <linux/miscdevice.h> 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci#include <linux/regmap.h> 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#include <linux/mfd/tps6594.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#include <linux/tps6594_pfsm.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#define TPS6594_STARTUP_DEST_MCU_ONLY_VAL 2 2262306a36Sopenharmony_ci#define TPS6594_STARTUP_DEST_ACTIVE_VAL 3 2362306a36Sopenharmony_ci#define TPS6594_STARTUP_DEST_SHIFT 5 2462306a36Sopenharmony_ci#define TPS6594_STARTUP_DEST_MCU_ONLY (TPS6594_STARTUP_DEST_MCU_ONLY_VAL \ 2562306a36Sopenharmony_ci << TPS6594_STARTUP_DEST_SHIFT) 2662306a36Sopenharmony_ci#define TPS6594_STARTUP_DEST_ACTIVE (TPS6594_STARTUP_DEST_ACTIVE_VAL \ 2762306a36Sopenharmony_ci << TPS6594_STARTUP_DEST_SHIFT) 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci/* 3062306a36Sopenharmony_ci * To update the PMIC firmware, the user must be able to access 3162306a36Sopenharmony_ci * page 0 (user registers) and page 1 (NVM control and configuration). 3262306a36Sopenharmony_ci */ 3362306a36Sopenharmony_ci#define TPS6594_PMIC_MAX_POS 0x200 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#define TPS6594_FILE_TO_PFSM(f) container_of((f)->private_data, struct tps6594_pfsm, miscdev) 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci/** 3862306a36Sopenharmony_ci * struct tps6594_pfsm - device private data structure 3962306a36Sopenharmony_ci * 4062306a36Sopenharmony_ci * @miscdev: misc device infos 4162306a36Sopenharmony_ci * @regmap: regmap for accessing the device registers 4262306a36Sopenharmony_ci */ 4362306a36Sopenharmony_cistruct tps6594_pfsm { 4462306a36Sopenharmony_ci struct miscdevice miscdev; 4562306a36Sopenharmony_ci struct regmap *regmap; 4662306a36Sopenharmony_ci}; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic ssize_t tps6594_pfsm_read(struct file *f, char __user *buf, 4962306a36Sopenharmony_ci size_t count, loff_t *ppos) 5062306a36Sopenharmony_ci{ 5162306a36Sopenharmony_ci struct tps6594_pfsm *pfsm = TPS6594_FILE_TO_PFSM(f); 5262306a36Sopenharmony_ci loff_t pos = *ppos; 5362306a36Sopenharmony_ci unsigned int val; 5462306a36Sopenharmony_ci int ret; 5562306a36Sopenharmony_ci int i; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci if (pos < 0) 5862306a36Sopenharmony_ci return -EINVAL; 5962306a36Sopenharmony_ci if (pos >= TPS6594_PMIC_MAX_POS) 6062306a36Sopenharmony_ci return 0; 6162306a36Sopenharmony_ci if (count > TPS6594_PMIC_MAX_POS - pos) 6262306a36Sopenharmony_ci count = TPS6594_PMIC_MAX_POS - pos; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci for (i = 0 ; i < count ; i++) { 6562306a36Sopenharmony_ci ret = regmap_read(pfsm->regmap, pos + i, &val); 6662306a36Sopenharmony_ci if (ret) 6762306a36Sopenharmony_ci return ret; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci if (put_user(val, buf + i)) 7062306a36Sopenharmony_ci return -EFAULT; 7162306a36Sopenharmony_ci } 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci *ppos = pos + count; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci return count; 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic ssize_t tps6594_pfsm_write(struct file *f, const char __user *buf, 7962306a36Sopenharmony_ci size_t count, loff_t *ppos) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci struct tps6594_pfsm *pfsm = TPS6594_FILE_TO_PFSM(f); 8262306a36Sopenharmony_ci loff_t pos = *ppos; 8362306a36Sopenharmony_ci char val; 8462306a36Sopenharmony_ci int ret; 8562306a36Sopenharmony_ci int i; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci if (pos < 0) 8862306a36Sopenharmony_ci return -EINVAL; 8962306a36Sopenharmony_ci if (pos >= TPS6594_PMIC_MAX_POS || !count) 9062306a36Sopenharmony_ci return 0; 9162306a36Sopenharmony_ci if (count > TPS6594_PMIC_MAX_POS - pos) 9262306a36Sopenharmony_ci count = TPS6594_PMIC_MAX_POS - pos; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci for (i = 0 ; i < count ; i++) { 9562306a36Sopenharmony_ci if (get_user(val, buf + i)) 9662306a36Sopenharmony_ci return -EFAULT; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci ret = regmap_write(pfsm->regmap, pos + i, val); 9962306a36Sopenharmony_ci if (ret) 10062306a36Sopenharmony_ci return ret; 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci *ppos = pos + count; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci return count; 10662306a36Sopenharmony_ci} 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_cistatic int tps6594_pfsm_configure_ret_trig(struct regmap *regmap, u8 gpio_ret, u8 ddr_ret) 10962306a36Sopenharmony_ci{ 11062306a36Sopenharmony_ci int ret; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci if (gpio_ret) 11362306a36Sopenharmony_ci ret = regmap_set_bits(regmap, TPS6594_REG_FSM_I2C_TRIGGERS, 11462306a36Sopenharmony_ci TPS6594_BIT_TRIGGER_I2C(5) | TPS6594_BIT_TRIGGER_I2C(6)); 11562306a36Sopenharmony_ci else 11662306a36Sopenharmony_ci ret = regmap_clear_bits(regmap, TPS6594_REG_FSM_I2C_TRIGGERS, 11762306a36Sopenharmony_ci TPS6594_BIT_TRIGGER_I2C(5) | TPS6594_BIT_TRIGGER_I2C(6)); 11862306a36Sopenharmony_ci if (ret) 11962306a36Sopenharmony_ci return ret; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci if (ddr_ret) 12262306a36Sopenharmony_ci ret = regmap_set_bits(regmap, TPS6594_REG_FSM_I2C_TRIGGERS, 12362306a36Sopenharmony_ci TPS6594_BIT_TRIGGER_I2C(7)); 12462306a36Sopenharmony_ci else 12562306a36Sopenharmony_ci ret = regmap_clear_bits(regmap, TPS6594_REG_FSM_I2C_TRIGGERS, 12662306a36Sopenharmony_ci TPS6594_BIT_TRIGGER_I2C(7)); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci return ret; 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_cistatic long tps6594_pfsm_ioctl(struct file *f, unsigned int cmd, unsigned long arg) 13262306a36Sopenharmony_ci{ 13362306a36Sopenharmony_ci struct tps6594_pfsm *pfsm = TPS6594_FILE_TO_PFSM(f); 13462306a36Sopenharmony_ci struct pmic_state_opt state_opt; 13562306a36Sopenharmony_ci void __user *argp = (void __user *)arg; 13662306a36Sopenharmony_ci int ret = -ENOIOCTLCMD; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci switch (cmd) { 13962306a36Sopenharmony_ci case PMIC_GOTO_STANDBY: 14062306a36Sopenharmony_ci /* Disable LP mode */ 14162306a36Sopenharmony_ci ret = regmap_clear_bits(pfsm->regmap, TPS6594_REG_RTC_CTRL_2, 14262306a36Sopenharmony_ci TPS6594_BIT_LP_STANDBY_SEL); 14362306a36Sopenharmony_ci if (ret) 14462306a36Sopenharmony_ci return ret; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci /* Force trigger */ 14762306a36Sopenharmony_ci ret = regmap_write_bits(pfsm->regmap, TPS6594_REG_FSM_I2C_TRIGGERS, 14862306a36Sopenharmony_ci TPS6594_BIT_TRIGGER_I2C(0), TPS6594_BIT_TRIGGER_I2C(0)); 14962306a36Sopenharmony_ci break; 15062306a36Sopenharmony_ci case PMIC_GOTO_LP_STANDBY: 15162306a36Sopenharmony_ci /* Enable LP mode */ 15262306a36Sopenharmony_ci ret = regmap_set_bits(pfsm->regmap, TPS6594_REG_RTC_CTRL_2, 15362306a36Sopenharmony_ci TPS6594_BIT_LP_STANDBY_SEL); 15462306a36Sopenharmony_ci if (ret) 15562306a36Sopenharmony_ci return ret; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci /* Force trigger */ 15862306a36Sopenharmony_ci ret = regmap_write_bits(pfsm->regmap, TPS6594_REG_FSM_I2C_TRIGGERS, 15962306a36Sopenharmony_ci TPS6594_BIT_TRIGGER_I2C(0), TPS6594_BIT_TRIGGER_I2C(0)); 16062306a36Sopenharmony_ci break; 16162306a36Sopenharmony_ci case PMIC_UPDATE_PGM: 16262306a36Sopenharmony_ci /* Force trigger */ 16362306a36Sopenharmony_ci ret = regmap_write_bits(pfsm->regmap, TPS6594_REG_FSM_I2C_TRIGGERS, 16462306a36Sopenharmony_ci TPS6594_BIT_TRIGGER_I2C(3), TPS6594_BIT_TRIGGER_I2C(3)); 16562306a36Sopenharmony_ci break; 16662306a36Sopenharmony_ci case PMIC_SET_ACTIVE_STATE: 16762306a36Sopenharmony_ci /* Modify NSLEEP1-2 bits */ 16862306a36Sopenharmony_ci ret = regmap_set_bits(pfsm->regmap, TPS6594_REG_FSM_NSLEEP_TRIGGERS, 16962306a36Sopenharmony_ci TPS6594_BIT_NSLEEP1B | TPS6594_BIT_NSLEEP2B); 17062306a36Sopenharmony_ci break; 17162306a36Sopenharmony_ci case PMIC_SET_MCU_ONLY_STATE: 17262306a36Sopenharmony_ci if (copy_from_user(&state_opt, argp, sizeof(state_opt))) 17362306a36Sopenharmony_ci return -EFAULT; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci /* Configure retention triggers */ 17662306a36Sopenharmony_ci ret = tps6594_pfsm_configure_ret_trig(pfsm->regmap, state_opt.gpio_retention, 17762306a36Sopenharmony_ci state_opt.ddr_retention); 17862306a36Sopenharmony_ci if (ret) 17962306a36Sopenharmony_ci return ret; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci /* Modify NSLEEP1-2 bits */ 18262306a36Sopenharmony_ci ret = regmap_clear_bits(pfsm->regmap, TPS6594_REG_FSM_NSLEEP_TRIGGERS, 18362306a36Sopenharmony_ci TPS6594_BIT_NSLEEP1B); 18462306a36Sopenharmony_ci if (ret) 18562306a36Sopenharmony_ci return ret; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci ret = regmap_set_bits(pfsm->regmap, TPS6594_REG_FSM_NSLEEP_TRIGGERS, 18862306a36Sopenharmony_ci TPS6594_BIT_NSLEEP2B); 18962306a36Sopenharmony_ci break; 19062306a36Sopenharmony_ci case PMIC_SET_RETENTION_STATE: 19162306a36Sopenharmony_ci if (copy_from_user(&state_opt, argp, sizeof(state_opt))) 19262306a36Sopenharmony_ci return -EFAULT; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci /* Configure wake-up destination */ 19562306a36Sopenharmony_ci if (state_opt.mcu_only_startup_dest) 19662306a36Sopenharmony_ci ret = regmap_write_bits(pfsm->regmap, TPS6594_REG_RTC_CTRL_2, 19762306a36Sopenharmony_ci TPS6594_MASK_STARTUP_DEST, 19862306a36Sopenharmony_ci TPS6594_STARTUP_DEST_MCU_ONLY); 19962306a36Sopenharmony_ci else 20062306a36Sopenharmony_ci ret = regmap_write_bits(pfsm->regmap, TPS6594_REG_RTC_CTRL_2, 20162306a36Sopenharmony_ci TPS6594_MASK_STARTUP_DEST, 20262306a36Sopenharmony_ci TPS6594_STARTUP_DEST_ACTIVE); 20362306a36Sopenharmony_ci if (ret) 20462306a36Sopenharmony_ci return ret; 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci /* Configure retention triggers */ 20762306a36Sopenharmony_ci ret = tps6594_pfsm_configure_ret_trig(pfsm->regmap, state_opt.gpio_retention, 20862306a36Sopenharmony_ci state_opt.ddr_retention); 20962306a36Sopenharmony_ci if (ret) 21062306a36Sopenharmony_ci return ret; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci /* Modify NSLEEP1-2 bits */ 21362306a36Sopenharmony_ci ret = regmap_clear_bits(pfsm->regmap, TPS6594_REG_FSM_NSLEEP_TRIGGERS, 21462306a36Sopenharmony_ci TPS6594_BIT_NSLEEP2B); 21562306a36Sopenharmony_ci break; 21662306a36Sopenharmony_ci } 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci return ret; 21962306a36Sopenharmony_ci} 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_cistatic const struct file_operations tps6594_pfsm_fops = { 22262306a36Sopenharmony_ci .owner = THIS_MODULE, 22362306a36Sopenharmony_ci .llseek = generic_file_llseek, 22462306a36Sopenharmony_ci .read = tps6594_pfsm_read, 22562306a36Sopenharmony_ci .write = tps6594_pfsm_write, 22662306a36Sopenharmony_ci .unlocked_ioctl = tps6594_pfsm_ioctl, 22762306a36Sopenharmony_ci .compat_ioctl = compat_ptr_ioctl, 22862306a36Sopenharmony_ci}; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_cistatic irqreturn_t tps6594_pfsm_isr(int irq, void *dev_id) 23162306a36Sopenharmony_ci{ 23262306a36Sopenharmony_ci struct platform_device *pdev = dev_id; 23362306a36Sopenharmony_ci int i; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci for (i = 0 ; i < pdev->num_resources ; i++) { 23662306a36Sopenharmony_ci if (irq == platform_get_irq_byname(pdev, pdev->resource[i].name)) { 23762306a36Sopenharmony_ci dev_err(pdev->dev.parent, "%s event detected\n", pdev->resource[i].name); 23862306a36Sopenharmony_ci return IRQ_HANDLED; 23962306a36Sopenharmony_ci } 24062306a36Sopenharmony_ci } 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci return IRQ_NONE; 24362306a36Sopenharmony_ci} 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_cistatic int tps6594_pfsm_probe(struct platform_device *pdev) 24662306a36Sopenharmony_ci{ 24762306a36Sopenharmony_ci struct tps6594_pfsm *pfsm; 24862306a36Sopenharmony_ci struct tps6594 *tps = dev_get_drvdata(pdev->dev.parent); 24962306a36Sopenharmony_ci struct device *dev = &pdev->dev; 25062306a36Sopenharmony_ci int irq; 25162306a36Sopenharmony_ci int ret; 25262306a36Sopenharmony_ci int i; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci pfsm = devm_kzalloc(dev, sizeof(struct tps6594_pfsm), GFP_KERNEL); 25562306a36Sopenharmony_ci if (!pfsm) 25662306a36Sopenharmony_ci return -ENOMEM; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci pfsm->regmap = tps->regmap; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci pfsm->miscdev.minor = MISC_DYNAMIC_MINOR; 26162306a36Sopenharmony_ci pfsm->miscdev.name = devm_kasprintf(dev, GFP_KERNEL, "pfsm-%ld-0x%02x", 26262306a36Sopenharmony_ci tps->chip_id, tps->reg); 26362306a36Sopenharmony_ci pfsm->miscdev.fops = &tps6594_pfsm_fops; 26462306a36Sopenharmony_ci pfsm->miscdev.parent = dev->parent; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci for (i = 0 ; i < pdev->num_resources ; i++) { 26762306a36Sopenharmony_ci irq = platform_get_irq_byname(pdev, pdev->resource[i].name); 26862306a36Sopenharmony_ci if (irq < 0) 26962306a36Sopenharmony_ci return irq; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci ret = devm_request_threaded_irq(dev, irq, NULL, 27262306a36Sopenharmony_ci tps6594_pfsm_isr, IRQF_ONESHOT, 27362306a36Sopenharmony_ci pdev->resource[i].name, pdev); 27462306a36Sopenharmony_ci if (ret) 27562306a36Sopenharmony_ci return dev_err_probe(dev, ret, "Failed to request irq\n"); 27662306a36Sopenharmony_ci } 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci platform_set_drvdata(pdev, pfsm); 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci return misc_register(&pfsm->miscdev); 28162306a36Sopenharmony_ci} 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_cistatic void tps6594_pfsm_remove(struct platform_device *pdev) 28462306a36Sopenharmony_ci{ 28562306a36Sopenharmony_ci struct tps6594_pfsm *pfsm = platform_get_drvdata(pdev); 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci misc_deregister(&pfsm->miscdev); 28862306a36Sopenharmony_ci} 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_cistatic struct platform_driver tps6594_pfsm_driver = { 29162306a36Sopenharmony_ci .driver = { 29262306a36Sopenharmony_ci .name = "tps6594-pfsm", 29362306a36Sopenharmony_ci }, 29462306a36Sopenharmony_ci .probe = tps6594_pfsm_probe, 29562306a36Sopenharmony_ci .remove_new = tps6594_pfsm_remove, 29662306a36Sopenharmony_ci}; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_cimodule_platform_driver(tps6594_pfsm_driver); 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ciMODULE_ALIAS("platform:tps6594-pfsm"); 30162306a36Sopenharmony_ciMODULE_AUTHOR("Julien Panis <jpanis@baylibre.com>"); 30262306a36Sopenharmony_ciMODULE_DESCRIPTION("TPS6594 Pre-configurable Finite State Machine Driver"); 30362306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 304