diff options
Diffstat (limited to 'drivers/input/misc/qpnp-power-on.c')
-rw-r--r-- | drivers/input/misc/qpnp-power-on.c | 2413 |
1 files changed, 2413 insertions, 0 deletions
diff --git a/drivers/input/misc/qpnp-power-on.c b/drivers/input/misc/qpnp-power-on.c new file mode 100644 index 000000000000..339f94c072f4 --- /dev/null +++ b/drivers/input/misc/qpnp-power-on.c @@ -0,0 +1,2413 @@ +/* Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/debugfs.h> +#include <linux/kernel.h> +#include <linux/regmap.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/spmi.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/input.h> +#include <linux/log2.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/input/qpnp-power-on.h> +#include <linux/power_supply.h> + +#define PMIC_VER_8941 0x01 +#define PMIC_VERSION_REG 0x0105 +#define PMIC_VERSION_REV4_REG 0x0103 + +#define PMIC8941_V1_REV4 0x01 +#define PMIC8941_V2_REV4 0x02 +#define PON_PRIMARY 0x01 +#define PON_SECONDARY 0x02 +#define PON_1REG 0x03 +#define PON_GEN2_PRIMARY 0x04 +#define PON_GEN2_SECONDARY 0x05 + +#define PON_OFFSET(subtype, offset_gen1, offset_gen2) \ + (((subtype == PON_PRIMARY) || \ + (subtype == PON_SECONDARY) || \ + (subtype == PON_1REG)) ? offset_gen1 : offset_gen2) + +/* Common PNP defines */ +#define QPNP_PON_REVISION2(pon) ((pon)->base + 0x01) +#define QPNP_PON_PERPH_SUBTYPE(pon) ((pon)->base + 0x05) + +/* PON common register addresses */ +#define QPNP_PON_RT_STS(pon) ((pon)->base + 0x10) +#define QPNP_PON_PULL_CTL(pon) ((pon)->base + 0x70) +#define QPNP_PON_DBC_CTL(pon) ((pon)->base + 0x71) + +/* PON/RESET sources register addresses */ +#define QPNP_PON_REASON1(pon) \ + ((pon)->base + PON_OFFSET((pon)->subtype, 0x8, 0xC0)) +#define QPNP_PON_WARM_RESET_REASON1(pon) \ + ((pon)->base + PON_OFFSET((pon)->subtype, 0xA, 0xC2)) +#define QPNP_POFF_REASON1(pon) \ + ((pon)->base + PON_OFFSET((pon)->subtype, 0xC, 0xC5)) +#define QPNP_PON_WARM_RESET_REASON2(pon) ((pon)->base + 0xB) +#define QPNP_PON_OFF_REASON(pon) ((pon)->base + 0xC7) +#define QPNP_FAULT_REASON1(pon) ((pon)->base + 0xC8) +#define QPNP_S3_RESET_REASON(pon) ((pon)->base + 0xCA) +#define QPNP_PON_KPDPWR_S1_TIMER(pon) ((pon)->base + 0x40) +#define QPNP_PON_KPDPWR_S2_TIMER(pon) ((pon)->base + 0x41) +#define QPNP_PON_KPDPWR_S2_CNTL(pon) ((pon)->base + 0x42) +#define QPNP_PON_KPDPWR_S2_CNTL2(pon) ((pon)->base + 0x43) +#define QPNP_PON_RESIN_S1_TIMER(pon) ((pon)->base + 0x44) +#define QPNP_PON_RESIN_S2_TIMER(pon) ((pon)->base + 0x45) +#define QPNP_PON_RESIN_S2_CNTL(pon) ((pon)->base + 0x46) +#define QPNP_PON_RESIN_S2_CNTL2(pon) ((pon)->base + 0x47) +#define QPNP_PON_KPDPWR_RESIN_S1_TIMER(pon) ((pon)->base + 0x48) +#define QPNP_PON_KPDPWR_RESIN_S2_TIMER(pon) ((pon)->base + 0x49) +#define QPNP_PON_KPDPWR_RESIN_S2_CNTL(pon) ((pon)->base + 0x4A) +#define QPNP_PON_KPDPWR_RESIN_S2_CNTL2(pon) ((pon)->base + 0x4B) +#define QPNP_PON_PS_HOLD_RST_CTL(pon) ((pon)->base + 0x5A) +#define QPNP_PON_PS_HOLD_RST_CTL2(pon) ((pon)->base + 0x5B) +#define QPNP_PON_WD_RST_S2_CTL(pon) ((pon)->base + 0x56) +#define QPNP_PON_WD_RST_S2_CTL2(pon) ((pon)->base + 0x57) +#define QPNP_PON_S3_SRC(pon) ((pon)->base + 0x74) +#define QPNP_PON_S3_DBC_CTL(pon) ((pon)->base + 0x75) +#define QPNP_PON_SMPL_CTL(pon) ((pon)->base + 0x7F) +#define QPNP_PON_TRIGGER_EN(pon) ((pon)->base + 0x80) +#define QPNP_PON_XVDD_RB_SPARE(pon) ((pon)->base + 0x8E) +#define QPNP_PON_SOFT_RB_SPARE(pon) ((pon)->base + 0x8F) +#define QPNP_PON_SEC_ACCESS(pon) ((pon)->base + 0xD0) + +#define QPNP_PON_SEC_UNLOCK 0xA5 + +#define QPNP_PON_WARM_RESET_TFT BIT(4) + +#define QPNP_PON_RESIN_PULL_UP BIT(0) +#define QPNP_PON_KPDPWR_PULL_UP BIT(1) +#define QPNP_PON_CBLPWR_PULL_UP BIT(2) +#define QPNP_PON_FAULT_PULL_UP BIT(4) +#define QPNP_PON_S2_CNTL_EN BIT(7) +#define QPNP_PON_S2_RESET_ENABLE BIT(7) +#define QPNP_PON_DELAY_BIT_SHIFT 6 +#define QPNP_PON_GEN2_DELAY_BIT_SHIFT 14 + +#define QPNP_PON_S1_TIMER_MASK (0xF) +#define QPNP_PON_S2_TIMER_MASK (0x7) +#define QPNP_PON_S2_CNTL_TYPE_MASK (0xF) + +#define QPNP_PON_DBC_DELAY_MASK(pon) \ + PON_OFFSET((pon)->subtype, 0x7, 0xF) + +#define QPNP_PON_KPDPWR_N_SET BIT(0) +#define QPNP_PON_RESIN_N_SET BIT(1) +#define QPNP_PON_CBLPWR_N_SET BIT(2) +#define QPNP_PON_RESIN_BARK_N_SET BIT(4) +#define QPNP_PON_KPDPWR_RESIN_BARK_N_SET BIT(5) + +#define QPNP_PON_WD_EN BIT(7) +#define QPNP_PON_RESET_EN BIT(7) +#define QPNP_PON_POWER_OFF_MASK 0xF +#define QPNP_GEN2_POFF_SEQ BIT(7) +#define QPNP_GEN2_FAULT_SEQ BIT(6) +#define QPNP_GEN2_S3_RESET_SEQ BIT(5) + +#define QPNP_PON_S3_SRC_KPDPWR 0 +#define QPNP_PON_S3_SRC_RESIN 1 +#define QPNP_PON_S3_SRC_KPDPWR_AND_RESIN 2 +#define QPNP_PON_S3_SRC_KPDPWR_OR_RESIN 3 +#define QPNP_PON_S3_SRC_MASK 0x3 +#define QPNP_PON_HARD_RESET_MASK GENMASK(7, 5) + +#define QPNP_PON_UVLO_DLOAD_EN BIT(7) +#define QPNP_PON_SMPL_EN BIT(7) + +/* Ranges */ +#define QPNP_PON_S1_TIMER_MAX 10256 +#define QPNP_PON_S2_TIMER_MAX 2000 +#define QPNP_PON_S3_TIMER_SECS_MAX 128 +#define QPNP_PON_S3_DBC_DELAY_MASK 0x07 +#define QPNP_PON_RESET_TYPE_MAX 0xF +#define PON_S1_COUNT_MAX 0xF +#define QPNP_PON_MIN_DBC_US (USEC_PER_SEC / 64) +#define QPNP_PON_MAX_DBC_US (USEC_PER_SEC * 2) +#define QPNP_PON_GEN2_MIN_DBC_US 62 +#define QPNP_PON_GEN2_MAX_DBC_US (USEC_PER_SEC / 4) + +#define QPNP_KEY_STATUS_DELAY msecs_to_jiffies(250) + +#define QPNP_PON_BUFFER_SIZE 9 + +#define QPNP_POFF_REASON_UVLO 13 + +enum qpnp_pon_version { + QPNP_PON_GEN1_V1, + QPNP_PON_GEN1_V2, + QPNP_PON_GEN2, +}; + +enum pon_type { + PON_KPDPWR, + PON_RESIN, + PON_CBLPWR, + PON_KPDPWR_RESIN, +}; + +struct qpnp_pon_config { + u32 pon_type; + u32 support_reset; + u32 key_code; + u32 s1_timer; + u32 s2_timer; + u32 s2_type; + u32 pull_up; + u32 state_irq; + u32 bark_irq; + u16 s2_cntl_addr; + u16 s2_cntl2_addr; + bool old_state; + bool use_bark; + bool config_reset; +}; + +struct pon_regulator { + struct qpnp_pon *pon; + struct regulator_dev *rdev; + struct regulator_desc rdesc; + u32 addr; + u32 bit; + bool enabled; +}; + +struct qpnp_pon { + struct platform_device *pdev; + struct regmap *regmap; + struct input_dev *pon_input; + struct qpnp_pon_config *pon_cfg; + struct pon_regulator *pon_reg_cfg; + struct list_head list; + struct delayed_work bark_work; + struct dentry *debugfs; + int pon_trigger_reason; + int pon_power_off_reason; + int num_pon_reg; + int num_pon_config; + u32 dbc_time_us; + u32 uvlo; + int warm_reset_poff_type; + int hard_reset_poff_type; + int shutdown_poff_type; + u16 base; + u8 subtype; + u8 pon_ver; + u8 warm_reset_reason1; + u8 warm_reset_reason2; + bool is_spon; + bool store_hard_reset_reason; + bool kpdpwr_dbc_enable; + ktime_t kpdpwr_last_release_time; +}; + +static int pon_ship_mode_en; +module_param_named( + ship_mode_en, pon_ship_mode_en, int, 0600 +); + +static struct qpnp_pon *sys_reset_dev; +static DEFINE_SPINLOCK(spon_list_slock); +static LIST_HEAD(spon_dev_list); + +static u32 s1_delay[PON_S1_COUNT_MAX + 1] = { + 0, 32, 56, 80, 138, 184, 272, 408, 608, 904, 1352, 2048, + 3072, 4480, 6720, 10256 +}; + +static const char * const qpnp_pon_reason[] = { + [0] = "Triggered from Hard Reset", + [1] = "Triggered from SMPL (sudden momentary power loss)", + [2] = "Triggered from RTC (RTC alarm expiry)", + [3] = "Triggered from DC (DC charger insertion)", + [4] = "Triggered from USB (USB charger insertion)", + [5] = "Triggered from PON1 (secondary PMIC)", + [6] = "Triggered from CBL (external power supply)", + [7] = "Triggered from KPD (power key press)", +}; + +#define POFF_REASON_FAULT_OFFSET 16 +#define POFF_REASON_S3_RESET_OFFSET 32 +static const char * const qpnp_poff_reason[] = { + /* QPNP_PON_GEN1 POFF reasons */ + [0] = "Triggered from SOFT (Software)", + [1] = "Triggered from PS_HOLD (PS_HOLD/MSM controlled shutdown)", + [2] = "Triggered from PMIC_WD (PMIC watchdog)", + [3] = "Triggered from GP1 (Keypad_Reset1)", + [4] = "Triggered from GP2 (Keypad_Reset2)", + [5] = "Triggered from KPDPWR_AND_RESIN (Simultaneous power key and reset line)", + [6] = "Triggered from RESIN_N (Reset line/Volume Down Key)", + [7] = "Triggered from KPDPWR_N (Long Power Key hold)", + [8] = "N/A", + [9] = "N/A", + [10] = "N/A", + [11] = "Triggered from CHARGER (Charger ENUM_TIMER, BOOT_DONE)", + [12] = "Triggered from TFT (Thermal Fault Tolerance)", + [13] = "Triggered from UVLO (Under Voltage Lock Out)", + [14] = "Triggered from OTST3 (Overtemp)", + [15] = "Triggered from STAGE3 (Stage 3 reset)", + + /* QPNP_PON_GEN2 FAULT reasons */ + [16] = "Triggered from GP_FAULT0", + [17] = "Triggered from GP_FAULT1", + [18] = "Triggered from GP_FAULT2", + [19] = "Triggered from GP_FAULT3", + [20] = "Triggered from MBG_FAULT", + [21] = "Triggered from OVLO (Over Voltage Lock Out)", + [22] = "Triggered from UVLO (Under Voltage Lock Out)", + [23] = "Triggered from AVDD_RB", + [24] = "N/A", + [25] = "N/A", + [26] = "N/A", + [27] = "Triggered from FAULT_FAULT_N", + [28] = "Triggered from FAULT_PBS_WATCHDOG_TO", + [29] = "Triggered from FAULT_PBS_NACK", + [30] = "Triggered from FAULT_RESTART_PON", + [31] = "Triggered from OTST3 (Overtemp)", + + /* QPNP_PON_GEN2 S3_RESET reasons */ + [32] = "N/A", + [33] = "N/A", + [34] = "N/A", + [35] = "N/A", + [36] = "Triggered from S3_RESET_FAULT_N", + [37] = "Triggered from S3_RESET_PBS_WATCHDOG_TO", + [38] = "Triggered from S3_RESET_PBS_NACK", + [39] = "Triggered from S3_RESET_KPDPWR_ANDOR_RESIN (power key and/or reset line)", +}; + +static int +qpnp_pon_masked_write(struct qpnp_pon *pon, u16 addr, u8 mask, u8 val) +{ + int rc; + + rc = regmap_update_bits(pon->regmap, addr, mask, val); + if (rc) + dev_err(&pon->pdev->dev, + "Unable to regmap_update_bits to addr=%hx, rc(%d)\n", + addr, rc); + return rc; +} + +static bool is_pon_gen1(struct qpnp_pon *pon) +{ + return pon->subtype == PON_PRIMARY || + pon->subtype == PON_SECONDARY; +} + +static bool is_pon_gen2(struct qpnp_pon *pon) +{ + return pon->subtype == PON_GEN2_PRIMARY || + pon->subtype == PON_GEN2_SECONDARY; +} + +/** + * qpnp_pon_set_restart_reason - Store device restart reason in PMIC register. + * + * Returns = 0 if PMIC feature is not available or store restart reason + * successfully. + * Returns > 0 for errors + * + * This function is used to store device restart reason in PMIC register. + * It checks here to see if the restart reason register has been specified. + * If it hasn't, this function should immediately return 0 + */ +int qpnp_pon_set_restart_reason(enum pon_restart_reason reason) +{ + int rc = 0; + struct qpnp_pon *pon = sys_reset_dev; + + if (!pon) + return 0; + + if (!pon->store_hard_reset_reason) + return 0; + + if (is_pon_gen2(pon)) + rc = qpnp_pon_masked_write(pon, QPNP_PON_SOFT_RB_SPARE(pon), + GENMASK(7, 1), (reason << 1)); + else + rc = qpnp_pon_masked_write(pon, QPNP_PON_SOFT_RB_SPARE(pon), + GENMASK(7, 2), (reason << 2)); + + if (rc) + dev_err(&pon->pdev->dev, + "Unable to write to addr=%x, rc(%d)\n", + QPNP_PON_SOFT_RB_SPARE(pon), rc); + return rc; +} +EXPORT_SYMBOL(qpnp_pon_set_restart_reason); + +/* + * qpnp_pon_check_hard_reset_stored - Checks if the PMIC need to + * store hard reset reason. + * + * Returns true if reset reason can be stored, false if it cannot be stored + * + */ +bool qpnp_pon_check_hard_reset_stored(void) +{ + struct qpnp_pon *pon = sys_reset_dev; + + if (!pon) + return false; + + return pon->store_hard_reset_reason; +} +EXPORT_SYMBOL(qpnp_pon_check_hard_reset_stored); + +static int qpnp_pon_set_dbc(struct qpnp_pon *pon, u32 delay) +{ + int rc = 0; + u32 val; + + if (delay == pon->dbc_time_us) + goto out; + + if (pon->pon_input) + mutex_lock(&pon->pon_input->mutex); + + if (is_pon_gen2(pon)) { + if (delay < QPNP_PON_GEN2_MIN_DBC_US) + delay = QPNP_PON_GEN2_MIN_DBC_US; + else if (delay > QPNP_PON_GEN2_MAX_DBC_US) + delay = QPNP_PON_GEN2_MAX_DBC_US; + val = (delay << QPNP_PON_GEN2_DELAY_BIT_SHIFT) / USEC_PER_SEC; + } else { + if (delay < QPNP_PON_MIN_DBC_US) + delay = QPNP_PON_MIN_DBC_US; + else if (delay > QPNP_PON_MAX_DBC_US) + delay = QPNP_PON_MAX_DBC_US; + val = (delay << QPNP_PON_DELAY_BIT_SHIFT) / USEC_PER_SEC; + } + + val = ilog2(val); + rc = qpnp_pon_masked_write(pon, QPNP_PON_DBC_CTL(pon), + QPNP_PON_DBC_DELAY_MASK(pon), val); + if (rc) { + dev_err(&pon->pdev->dev, "Unable to set PON debounce\n"); + goto unlock; + } + + pon->dbc_time_us = delay; + +unlock: + if (pon->pon_input) + mutex_unlock(&pon->pon_input->mutex); +out: + return rc; +} + +static int qpnp_pon_get_dbc(struct qpnp_pon *pon, u32 *delay) +{ + int rc; + unsigned int val; + + rc = regmap_read(pon->regmap, QPNP_PON_DBC_CTL(pon), &val); + if (rc) { + pr_err("Unable to read pon_dbc_ctl rc=%d\n", rc); + return rc; + } + val &= QPNP_PON_DBC_DELAY_MASK(pon); + + if (is_pon_gen2(pon)) + *delay = USEC_PER_SEC / + (1 << (QPNP_PON_GEN2_DELAY_BIT_SHIFT - val)); + else + *delay = USEC_PER_SEC / + (1 << (QPNP_PON_DELAY_BIT_SHIFT - val)); + + return rc; +} + +static ssize_t qpnp_pon_dbc_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct qpnp_pon *pon = dev_get_drvdata(dev); + + return snprintf(buf, QPNP_PON_BUFFER_SIZE, "%d\n", pon->dbc_time_us); +} + +static ssize_t qpnp_pon_dbc_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct qpnp_pon *pon = dev_get_drvdata(dev); + u32 value; + int rc; + + if (size > QPNP_PON_BUFFER_SIZE) + return -EINVAL; + + rc = kstrtou32(buf, 10, &value); + if (rc) + return rc; + + rc = qpnp_pon_set_dbc(pon, value); + if (rc < 0) + return rc; + + return size; +} + +static DEVICE_ATTR(debounce_us, 0664, qpnp_pon_dbc_show, qpnp_pon_dbc_store); + +static int qpnp_pon_reset_config(struct qpnp_pon *pon, + enum pon_power_off_type type) +{ + int rc; + u16 rst_en_reg; + + if (pon->pon_ver == QPNP_PON_GEN1_V1) + rst_en_reg = QPNP_PON_PS_HOLD_RST_CTL(pon); + else + rst_en_reg = QPNP_PON_PS_HOLD_RST_CTL2(pon); + + /* + * Based on the poweroff type set for a PON device through device tree + * change the type being configured into PS_HOLD_RST_CTL. + */ + switch (type) { + case PON_POWER_OFF_WARM_RESET: + if (pon->warm_reset_poff_type != -EINVAL) + type = pon->warm_reset_poff_type; + break; + case PON_POWER_OFF_HARD_RESET: + if (pon->hard_reset_poff_type != -EINVAL) + type = pon->hard_reset_poff_type; + break; + case PON_POWER_OFF_SHUTDOWN: + if (pon->shutdown_poff_type != -EINVAL) + type = pon->shutdown_poff_type; + break; + default: + break; + } + + rc = qpnp_pon_masked_write(pon, rst_en_reg, QPNP_PON_RESET_EN, 0); + if (rc) + dev_err(&pon->pdev->dev, + "Unable to write to addr=%hx, rc(%d)\n", + rst_en_reg, rc); + + /* + * We need 10 sleep clock cycles here. But since the clock is + * internally generated, we need to add 50% tolerance to be + * conservative. + */ + udelay(500); + + rc = qpnp_pon_masked_write(pon, QPNP_PON_PS_HOLD_RST_CTL(pon), + QPNP_PON_POWER_OFF_MASK, type); + if (rc) + dev_err(&pon->pdev->dev, + "Unable to write to addr=%x, rc(%d)\n", + QPNP_PON_PS_HOLD_RST_CTL(pon), rc); + + rc = qpnp_pon_masked_write(pon, rst_en_reg, QPNP_PON_RESET_EN, + QPNP_PON_RESET_EN); + if (rc) + dev_err(&pon->pdev->dev, + "Unable to write to addr=%hx, rc(%d)\n", + rst_en_reg, rc); + + dev_dbg(&pon->pdev->dev, "power off type = 0x%02X\n", type); + return rc; +} + +/** + * qpnp_pon_system_pwr_off - Configure system-reset PMIC for shutdown or reset + * @type: Determines the type of power off to perform - shutdown, reset, etc + * + * This function will support configuring for multiple PMICs. In some cases, the + * PON of secondary PMICs also needs to be configured. So this supports that + * requirement. Once the system-reset and secondary PMIC is configured properly, + * the MSM can drop PS_HOLD to activate the specified configuration. Note that + * this function may be called from atomic context as in the case of the panic + * notifier path and thus it should not rely on function calls that may sleep. + */ +int qpnp_pon_system_pwr_off(enum pon_power_off_type type) +{ + int rc = 0; + struct qpnp_pon *pon = sys_reset_dev; + struct qpnp_pon *tmp; + struct power_supply *batt_psy; + union power_supply_propval val; + unsigned long flags; + + if (!pon) + return -ENODEV; + + rc = qpnp_pon_reset_config(pon, type); + if (rc) { + dev_err(&pon->pdev->dev, + "Error configuring main PON rc: %d\n", + rc); + return rc; + } + + /* + * Check if a secondary PON device needs to be configured. If it + * is available, configure that also as per the requested power off + * type + */ + spin_lock_irqsave(&spon_list_slock, flags); + if (list_empty(&spon_dev_list)) + goto out; + + list_for_each_entry_safe(pon, tmp, &spon_dev_list, list) { + dev_emerg(&pon->pdev->dev, + "PMIC@SID%d: configuring PON for reset\n", + to_spmi_device(pon->pdev->dev.parent)->usid); + rc = qpnp_pon_reset_config(pon, type); + if (rc) { + dev_err(&pon->pdev->dev, + "Error configuring secondary PON rc: %d\n", + rc); + goto out; + } + } + /* Set ship mode here if it has been requested */ + if (!!pon_ship_mode_en) { + batt_psy = power_supply_get_by_name("battery"); + if (batt_psy) { + pr_debug("Set ship mode!\n"); + val.intval = 1; + rc = power_supply_set_property(batt_psy, + POWER_SUPPLY_PROP_SET_SHIP_MODE, &val); + if (rc) + dev_err(&pon->pdev->dev, + "Set ship-mode failed\n"); + } + } +out: + spin_unlock_irqrestore(&spon_list_slock, flags); + return rc; +} +EXPORT_SYMBOL(qpnp_pon_system_pwr_off); + +/** + * qpnp_pon_is_warm_reset - Checks if the PMIC went through a warm reset. + * + * Returns > 0 for warm resets, 0 for not warm reset, < 0 for errors + * + * Note that this function will only return the warm vs not-warm reset status + * of the PMIC that is configured as the system-reset device. + */ +int qpnp_pon_is_warm_reset(void) +{ + struct qpnp_pon *pon = sys_reset_dev; + + if (!pon) + return -EPROBE_DEFER; + + if (is_pon_gen1(pon) || pon->subtype == PON_1REG) + return pon->warm_reset_reason1 + || (pon->warm_reset_reason2 & QPNP_PON_WARM_RESET_TFT); + else + return pon->warm_reset_reason1; +} +EXPORT_SYMBOL(qpnp_pon_is_warm_reset); + +/** + * qpnp_pon_wd_config - Disable the wd in a warm reset. + * @enable: to enable or disable the PON watch dog + * + * Returns = 0 for operate successfully, < 0 for errors + */ +int qpnp_pon_wd_config(bool enable) +{ + struct qpnp_pon *pon = sys_reset_dev; + int rc = 0; + + if (!pon) + return -EPROBE_DEFER; + + rc = qpnp_pon_masked_write(pon, QPNP_PON_WD_RST_S2_CTL2(pon), + QPNP_PON_WD_EN, enable ? QPNP_PON_WD_EN : 0); + if (rc) + dev_err(&pon->pdev->dev, + "Unable to write to addr=%x, rc(%d)\n", + QPNP_PON_WD_RST_S2_CTL2(pon), rc); + + return rc; +} +EXPORT_SYMBOL(qpnp_pon_wd_config); + +static int qpnp_pon_get_trigger_config(enum pon_trigger_source pon_src, + bool *enabled) +{ + struct qpnp_pon *pon = sys_reset_dev; + int rc; + u16 addr; + int val; + u8 mask; + + if (!pon) + return -ENODEV; + + if (pon_src < PON_SMPL || pon_src > PON_KPDPWR_N) { + dev_err(&pon->pdev->dev, "Invalid PON source\n"); + return -EINVAL; + } + + addr = QPNP_PON_TRIGGER_EN(pon); + mask = BIT(pon_src); + if (is_pon_gen2(pon) && pon_src == PON_SMPL) { + addr = QPNP_PON_SMPL_CTL(pon); + mask = QPNP_PON_SMPL_EN; + } + + + rc = regmap_read(pon->regmap, addr, &val); + if (rc) + dev_err(&pon->pdev->dev, + "Unable to read from addr=%hx, rc(%d)\n", + addr, rc); + else + *enabled = !!(val & mask); + + return rc; +} + +/** + * qpnp_pon_trigger_config - Configures (enable/disable) the PON trigger source + * @pon_src: PON source to be configured + * @enable: to enable or disable the PON trigger + * + * This function configures the power-on trigger capability of a + * PON source. If a specific PON trigger is disabled it cannot act + * as a power-on source to the PMIC. + */ + +int qpnp_pon_trigger_config(enum pon_trigger_source pon_src, bool enable) +{ + struct qpnp_pon *pon = sys_reset_dev; + int rc; + + if (!pon) + return -EPROBE_DEFER; + + if (pon_src < PON_SMPL || pon_src > PON_KPDPWR_N) { + dev_err(&pon->pdev->dev, "Invalid PON source\n"); + return -EINVAL; + } + + if (is_pon_gen2(pon) && pon_src == PON_SMPL) { + rc = qpnp_pon_masked_write(pon, QPNP_PON_SMPL_CTL(pon), + QPNP_PON_SMPL_EN, enable ? QPNP_PON_SMPL_EN : 0); + if (rc) + dev_err(&pon->pdev->dev, + "Unable to write to addr=%x, rc(%d)\n", + QPNP_PON_SMPL_CTL(pon), rc); + } else { + rc = qpnp_pon_masked_write(pon, QPNP_PON_TRIGGER_EN(pon), + BIT(pon_src), enable ? BIT(pon_src) : 0); + if (rc) + dev_err(&pon->pdev->dev, + "Unable to write to addr=%x, rc(%d)\n", + QPNP_PON_TRIGGER_EN(pon), rc); + } + + return rc; +} +EXPORT_SYMBOL(qpnp_pon_trigger_config); + +/* + * This function stores the PMIC warm reset reason register values. It also + * clears these registers if the qcom,clear-warm-reset device tree property + * is specified. + */ +static int qpnp_pon_store_and_clear_warm_reset(struct qpnp_pon *pon) +{ + int rc; + u8 reg = 0; + uint val; + + rc = regmap_read(pon->regmap, QPNP_PON_WARM_RESET_REASON1(pon), + &val); + if (rc) { + dev_err(&pon->pdev->dev, "Unable to read addr=%x, rc(%d)\n", + QPNP_PON_WARM_RESET_REASON1(pon), rc); + return rc; + } + pon->warm_reset_reason1 = (u8)val; + + if (is_pon_gen1(pon) || pon->subtype == PON_1REG) { + rc = regmap_read(pon->regmap, QPNP_PON_WARM_RESET_REASON2(pon), + &val); + if (rc) { + dev_err(&pon->pdev->dev, + "Unable to read addr=%x, rc(%d)\n", + QPNP_PON_WARM_RESET_REASON2(pon), rc); + return rc; + } + pon->warm_reset_reason2 = (u8)val; + } + + if (of_property_read_bool(pon->pdev->dev.of_node, + "qcom,clear-warm-reset")) { + rc = regmap_write(pon->regmap, + QPNP_PON_WARM_RESET_REASON1(pon), reg); + if (rc) + dev_err(&pon->pdev->dev, + "Unable to write to addr=%hx, rc(%d)\n", + QPNP_PON_WARM_RESET_REASON1(pon), rc); + } + + return 0; +} + +static struct qpnp_pon_config * +qpnp_get_cfg(struct qpnp_pon *pon, u32 pon_type) +{ + int i; + + for (i = 0; i < pon->num_pon_config; i++) { + if (pon_type == pon->pon_cfg[i].pon_type) + return &pon->pon_cfg[i]; + } + + return NULL; +} + +static int +qpnp_pon_input_dispatch(struct qpnp_pon *pon, u32 pon_type) +{ + int rc; + struct qpnp_pon_config *cfg = NULL; + u8 pon_rt_bit = 0; + u32 key_status; + uint pon_rt_sts; + u64 elapsed_us; + + cfg = qpnp_get_cfg(pon, pon_type); + if (!cfg) + return -EINVAL; + + /* Check if key reporting is supported */ + if (!cfg->key_code) + return 0; + + if (pon->kpdpwr_dbc_enable && cfg->pon_type == PON_KPDPWR) { + elapsed_us = ktime_us_delta(ktime_get(), + pon->kpdpwr_last_release_time); + if (elapsed_us < pon->dbc_time_us) { + pr_debug("Ignoring kpdpwr event - within debounce time\n"); + return 0; + } + } + + /* check the RT status to get the current status of the line */ + rc = regmap_read(pon->regmap, QPNP_PON_RT_STS(pon), &pon_rt_sts); + if (rc) { + dev_err(&pon->pdev->dev, "Unable to read PON RT status\n"); + return rc; + } + + switch (cfg->pon_type) { + case PON_KPDPWR: + pon_rt_bit = QPNP_PON_KPDPWR_N_SET; + break; + case PON_RESIN: + pon_rt_bit = QPNP_PON_RESIN_N_SET; + break; + case PON_CBLPWR: + pon_rt_bit = QPNP_PON_CBLPWR_N_SET; + break; + case PON_KPDPWR_RESIN: + pon_rt_bit = QPNP_PON_KPDPWR_RESIN_BARK_N_SET; + break; + default: + return -EINVAL; + } + + pr_debug("PMIC input: code=%d, sts=0x%hhx\n", + cfg->key_code, pon_rt_sts); + key_status = pon_rt_sts & pon_rt_bit; + + if (pon->kpdpwr_dbc_enable && cfg->pon_type == PON_KPDPWR) { + if (!key_status) + pon->kpdpwr_last_release_time = ktime_get(); + } + + /* + * simulate press event in case release event occurred + * without a press event + */ + if (!cfg->old_state && !key_status) { + input_report_key(pon->pon_input, cfg->key_code, 1); + input_sync(pon->pon_input); + } + + input_report_key(pon->pon_input, cfg->key_code, key_status); + input_sync(pon->pon_input); + + cfg->old_state = !!key_status; + + return 0; +} + +static irqreturn_t qpnp_kpdpwr_irq(int irq, void *_pon) +{ + int rc; + struct qpnp_pon *pon = _pon; + + rc = qpnp_pon_input_dispatch(pon, PON_KPDPWR); + if (rc) + dev_err(&pon->pdev->dev, "Unable to send input event\n"); + + return IRQ_HANDLED; +} + +static irqreturn_t qpnp_kpdpwr_bark_irq(int irq, void *_pon) +{ + return IRQ_HANDLED; +} + +static irqreturn_t qpnp_resin_irq(int irq, void *_pon) +{ + int rc; + struct qpnp_pon *pon = _pon; + + rc = qpnp_pon_input_dispatch(pon, PON_RESIN); + if (rc) + dev_err(&pon->pdev->dev, "Unable to send input event\n"); + return IRQ_HANDLED; +} + +static irqreturn_t qpnp_kpdpwr_resin_bark_irq(int irq, void *_pon) +{ + return IRQ_HANDLED; +} + +static irqreturn_t qpnp_cblpwr_irq(int irq, void *_pon) +{ + int rc; + struct qpnp_pon *pon = _pon; + + rc = qpnp_pon_input_dispatch(pon, PON_CBLPWR); + if (rc) + dev_err(&pon->pdev->dev, "Unable to send input event\n"); + + return IRQ_HANDLED; +} + +static void print_pon_reg(struct qpnp_pon *pon, u16 offset) +{ + int rc; + u16 addr; + uint reg; + + addr = pon->base + offset; + rc = regmap_read(pon->regmap, addr, ®); + if (rc) + dev_emerg(&pon->pdev->dev, + "Unable to read reg at 0x%04hx\n", addr); + else + dev_emerg(&pon->pdev->dev, "reg@0x%04hx: %02hhx\n", addr, reg); +} + +#define PON_PBL_STATUS 0x7 +#define PON_PON_REASON1(subtype) PON_OFFSET(subtype, 0x8, 0xC0) +#define PON_PON_REASON2 0x9 +#define PON_WARM_RESET_REASON1(subtype) PON_OFFSET(subtype, 0xA, 0xC2) +#define PON_WARM_RESET_REASON2 0xB +#define PON_POFF_REASON1(subtype) PON_OFFSET(subtype, 0xC, 0xC5) +#define PON_POFF_REASON2 0xD +#define PON_SOFT_RESET_REASON1(subtype) PON_OFFSET(subtype, 0xE, 0xCB) +#define PON_SOFT_RESET_REASON2 0xF +#define PON_FAULT_REASON1 0xC8 +#define PON_FAULT_REASON2 0xC9 +#define PON_PMIC_WD_RESET_S1_TIMER 0x54 +#define PON_PMIC_WD_RESET_S2_TIMER 0x55 +static irqreturn_t qpnp_pmic_wd_bark_irq(int irq, void *_pon) +{ + struct qpnp_pon *pon = _pon; + + print_pon_reg(pon, PON_PBL_STATUS); + print_pon_reg(pon, PON_PON_REASON1(pon->subtype)); + print_pon_reg(pon, PON_WARM_RESET_REASON1(pon->subtype)); + print_pon_reg(pon, PON_SOFT_RESET_REASON1(pon->subtype)); + print_pon_reg(pon, PON_POFF_REASON1(pon->subtype)); + if (is_pon_gen1(pon) || pon->subtype == PON_1REG) { + print_pon_reg(pon, PON_PON_REASON2); + print_pon_reg(pon, PON_WARM_RESET_REASON2); + print_pon_reg(pon, PON_POFF_REASON2); + print_pon_reg(pon, PON_SOFT_RESET_REASON2); + } else { + print_pon_reg(pon, PON_FAULT_REASON1); + print_pon_reg(pon, PON_FAULT_REASON2); + } + print_pon_reg(pon, PON_PMIC_WD_RESET_S1_TIMER); + print_pon_reg(pon, PON_PMIC_WD_RESET_S2_TIMER); + panic("PMIC Watch dog triggered"); + + return IRQ_HANDLED; +} + +static void bark_work_func(struct work_struct *work) +{ + int rc; + uint pon_rt_sts = 0; + struct qpnp_pon_config *cfg; + struct qpnp_pon *pon = + container_of(work, struct qpnp_pon, bark_work.work); + + cfg = qpnp_get_cfg(pon, PON_RESIN); + if (!cfg) { + dev_err(&pon->pdev->dev, "Invalid config pointer\n"); + goto err_return; + } + + /* enable reset */ + rc = qpnp_pon_masked_write(pon, cfg->s2_cntl2_addr, + QPNP_PON_S2_CNTL_EN, QPNP_PON_S2_CNTL_EN); + if (rc) { + dev_err(&pon->pdev->dev, "Unable to configure S2 enable\n"); + goto err_return; + } + /* bark RT status update delay */ + msleep(100); + /* read the bark RT status */ + rc = regmap_read(pon->regmap, QPNP_PON_RT_STS(pon), &pon_rt_sts); + if (rc) { + dev_err(&pon->pdev->dev, "Unable to read PON RT status\n"); + goto err_return; + } + + if (!(pon_rt_sts & QPNP_PON_RESIN_BARK_N_SET)) { + /* report the key event and enable the bark IRQ */ + input_report_key(pon->pon_input, cfg->key_code, 0); + input_sync(pon->pon_input); + enable_irq(cfg->bark_irq); + } else { + /* disable reset */ + rc = qpnp_pon_masked_write(pon, cfg->s2_cntl2_addr, + QPNP_PON_S2_CNTL_EN, 0); + if (rc) { + dev_err(&pon->pdev->dev, + "Unable to configure S2 enable\n"); + goto err_return; + } + /* re-arm the work */ + schedule_delayed_work(&pon->bark_work, QPNP_KEY_STATUS_DELAY); + } + +err_return: + return; +} + +static irqreturn_t qpnp_resin_bark_irq(int irq, void *_pon) +{ + int rc; + struct qpnp_pon *pon = _pon; + struct qpnp_pon_config *cfg; + + /* disable the bark interrupt */ + disable_irq_nosync(irq); + + cfg = qpnp_get_cfg(pon, PON_RESIN); + if (!cfg) { + dev_err(&pon->pdev->dev, "Invalid config pointer\n"); + goto err_exit; + } + + /* disable reset */ + rc = qpnp_pon_masked_write(pon, cfg->s2_cntl2_addr, + QPNP_PON_S2_CNTL_EN, 0); + if (rc) { + dev_err(&pon->pdev->dev, "Unable to configure S2 enable\n"); + goto err_exit; + } + + /* report the key event */ + input_report_key(pon->pon_input, cfg->key_code, 1); + input_sync(pon->pon_input); + /* schedule work to check the bark status for key-release */ + schedule_delayed_work(&pon->bark_work, QPNP_KEY_STATUS_DELAY); +err_exit: + return IRQ_HANDLED; +} + +static int +qpnp_config_pull(struct qpnp_pon *pon, struct qpnp_pon_config *cfg) +{ + int rc; + u8 pull_bit; + + switch (cfg->pon_type) { + case PON_KPDPWR: + pull_bit = QPNP_PON_KPDPWR_PULL_UP; + break; + case PON_RESIN: + pull_bit = QPNP_PON_RESIN_PULL_UP; + break; + case PON_CBLPWR: + pull_bit = QPNP_PON_CBLPWR_PULL_UP; + break; + case PON_KPDPWR_RESIN: + pull_bit = QPNP_PON_KPDPWR_PULL_UP | QPNP_PON_RESIN_PULL_UP; + break; + default: + return -EINVAL; + } + + rc = qpnp_pon_masked_write(pon, QPNP_PON_PULL_CTL(pon), + pull_bit, cfg->pull_up ? pull_bit : 0); + if (rc) + dev_err(&pon->pdev->dev, "Unable to config pull-up\n"); + + return rc; +} + +static int +qpnp_config_reset(struct qpnp_pon *pon, struct qpnp_pon_config *cfg) +{ + int rc; + u8 i; + u16 s1_timer_addr, s2_timer_addr; + + switch (cfg->pon_type) { + case PON_KPDPWR: + s1_timer_addr = QPNP_PON_KPDPWR_S1_TIMER(pon); + s2_timer_addr = QPNP_PON_KPDPWR_S2_TIMER(pon); + break; + case PON_RESIN: + s1_timer_addr = QPNP_PON_RESIN_S1_TIMER(pon); + s2_timer_addr = QPNP_PON_RESIN_S2_TIMER(pon); + break; + case PON_KPDPWR_RESIN: + s1_timer_addr = QPNP_PON_KPDPWR_RESIN_S1_TIMER(pon); + s2_timer_addr = QPNP_PON_KPDPWR_RESIN_S2_TIMER(pon); + break; + default: + return -EINVAL; + } + /* disable S2 reset */ + rc = qpnp_pon_masked_write(pon, cfg->s2_cntl2_addr, + QPNP_PON_S2_CNTL_EN, 0); + if (rc) { + dev_err(&pon->pdev->dev, "Unable to configure S2 enable\n"); + return rc; + } + + usleep_range(100, 120); + + /* configure s1 timer, s2 timer and reset type */ + for (i = 0; i < PON_S1_COUNT_MAX + 1; i++) { + if (cfg->s1_timer <= s1_delay[i]) + break; + } + rc = qpnp_pon_masked_write(pon, s1_timer_addr, + QPNP_PON_S1_TIMER_MASK, i); + if (rc) { + dev_err(&pon->pdev->dev, "Unable to configure S1 timer\n"); + return rc; + } + + i = 0; + if (cfg->s2_timer) { + i = cfg->s2_timer / 10; + i = ilog2(i + 1); + } + + rc = qpnp_pon_masked_write(pon, s2_timer_addr, + QPNP_PON_S2_TIMER_MASK, i); + if (rc) { + dev_err(&pon->pdev->dev, "Unable to configure S2 timer\n"); + return rc; + } + + rc = qpnp_pon_masked_write(pon, cfg->s2_cntl_addr, + QPNP_PON_S2_CNTL_TYPE_MASK, (u8)cfg->s2_type); + if (rc) { + dev_err(&pon->pdev->dev, + "Unable to configure S2 reset type\n"); + return rc; + } + + /* enable S2 reset */ + rc = qpnp_pon_masked_write(pon, cfg->s2_cntl2_addr, + QPNP_PON_S2_CNTL_EN, QPNP_PON_S2_CNTL_EN); + if (rc) { + dev_err(&pon->pdev->dev, "Unable to configure S2 enable\n"); + return rc; + } + + return 0; +} + +static int +qpnp_pon_request_irqs(struct qpnp_pon *pon, struct qpnp_pon_config *cfg) +{ + int rc = 0; + + switch (cfg->pon_type) { + case PON_KPDPWR: + rc = devm_request_irq(&pon->pdev->dev, cfg->state_irq, + qpnp_kpdpwr_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "qpnp_kpdpwr_status", pon); + if (rc < 0) { + dev_err(&pon->pdev->dev, "Can't request %d IRQ\n", + cfg->state_irq); + return rc; + } + if (cfg->use_bark) { + rc = devm_request_irq(&pon->pdev->dev, cfg->bark_irq, + qpnp_kpdpwr_bark_irq, + IRQF_TRIGGER_RISING, + "qpnp_kpdpwr_bark", pon); + if (rc < 0) { + dev_err(&pon->pdev->dev, + "Can't request %d IRQ\n", + cfg->bark_irq); + return rc; + } + } + break; + case PON_RESIN: + rc = devm_request_irq(&pon->pdev->dev, cfg->state_irq, + qpnp_resin_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "qpnp_resin_status", pon); + if (rc < 0) { + dev_err(&pon->pdev->dev, "Can't request %d IRQ\n", + cfg->state_irq); + return rc; + } + if (cfg->use_bark) { + rc = devm_request_irq(&pon->pdev->dev, cfg->bark_irq, + qpnp_resin_bark_irq, + IRQF_TRIGGER_RISING, + "qpnp_resin_bark", pon); + if (rc < 0) { + dev_err(&pon->pdev->dev, + "Can't request %d IRQ\n", + cfg->bark_irq); + return rc; + } + } + break; + case PON_CBLPWR: + rc = devm_request_irq(&pon->pdev->dev, cfg->state_irq, + qpnp_cblpwr_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "qpnp_cblpwr_status", pon); + if (rc < 0) { + dev_err(&pon->pdev->dev, "Can't request %d IRQ\n", + cfg->state_irq); + return rc; + } + break; + case PON_KPDPWR_RESIN: + if (cfg->use_bark) { + rc = devm_request_irq(&pon->pdev->dev, cfg->bark_irq, + qpnp_kpdpwr_resin_bark_irq, + IRQF_TRIGGER_RISING, + "qpnp_kpdpwr_resin_bark", pon); + if (rc < 0) { + dev_err(&pon->pdev->dev, + "Can't request %d IRQ\n", + cfg->bark_irq); + return rc; + } + } + break; + default: + return -EINVAL; + } + + /* mark the interrupts wakeable if they support linux-key */ + if (cfg->key_code) { + enable_irq_wake(cfg->state_irq); + /* special handling for RESIN due to a hardware bug */ + if (cfg->pon_type == PON_RESIN && cfg->support_reset) + enable_irq_wake(cfg->bark_irq); + } + + return rc; +} + +static int +qpnp_pon_config_input(struct qpnp_pon *pon, struct qpnp_pon_config *cfg) +{ + if (!pon->pon_input) { + pon->pon_input = input_allocate_device(); + if (!pon->pon_input) { + dev_err(&pon->pdev->dev, + "Can't allocate pon input device\n"); + return -ENOMEM; + } + pon->pon_input->name = "qpnp_pon"; + pon->pon_input->phys = "qpnp_pon/input0"; + } + + input_set_capability(pon->pon_input, EV_KEY, cfg->key_code); + + return 0; +} + +static int qpnp_pon_config_init(struct qpnp_pon *pon) +{ + int rc = 0, i = 0, pmic_wd_bark_irq; + struct device_node *pp = NULL; + struct qpnp_pon_config *cfg; + uint pmic_type; + uint revid_rev4; + + if (!pon->num_pon_config) { + dev_dbg(&pon->pdev->dev, "num_pon_config: %d\n", + pon->num_pon_config); + return 0; + } + + /* iterate through the list of pon configs */ + for_each_available_child_of_node(pon->pdev->dev.of_node, pp) { + if (!of_find_property(pp, "qcom,pon-type", NULL)) + continue; + + cfg = &pon->pon_cfg[i++]; + + rc = of_property_read_u32(pp, "qcom,pon-type", &cfg->pon_type); + if (rc) { + dev_err(&pon->pdev->dev, "PON type not specified\n"); + return rc; + } + + switch (cfg->pon_type) { + case PON_KPDPWR: + cfg->state_irq = platform_get_irq_byname(pon->pdev, + "kpdpwr"); + if (cfg->state_irq < 0) { + dev_err(&pon->pdev->dev, + "Unable to get kpdpwr irq\n"); + return cfg->state_irq; + } + + rc = of_property_read_u32(pp, "qcom,support-reset", + &cfg->support_reset); + + if (rc) { + if (rc == -EINVAL) { + dev_dbg(&pon->pdev->dev, + "'qcom,support-reset' DT property doesn't exist\n"); + } else { + dev_err(&pon->pdev->dev, + "Unable to read 'qcom,support-reset'\n"); + return rc; + } + } else { + cfg->config_reset = true; + } + + cfg->use_bark = of_property_read_bool(pp, + "qcom,use-bark"); + if (cfg->use_bark) { + cfg->bark_irq + = platform_get_irq_byname(pon->pdev, + "kpdpwr-bark"); + if (cfg->bark_irq < 0) { + dev_err(&pon->pdev->dev, + "Unable to get kpdpwr-bark irq\n"); + return cfg->bark_irq; + } + } + + /* + * If the value read from REVISION2 register is 0x00, + * then there is a single register to control s2 reset. + * Otherwise there are separate registers for s2 reset + * type and s2 reset enable. + */ + if (pon->pon_ver == QPNP_PON_GEN1_V1) { + cfg->s2_cntl_addr = cfg->s2_cntl2_addr = + QPNP_PON_KPDPWR_S2_CNTL(pon); + } else { + cfg->s2_cntl_addr = + QPNP_PON_KPDPWR_S2_CNTL(pon); + cfg->s2_cntl2_addr = + QPNP_PON_KPDPWR_S2_CNTL2(pon); + } + + break; + case PON_RESIN: + cfg->state_irq = platform_get_irq_byname(pon->pdev, + "resin"); + if (cfg->state_irq < 0) { + dev_err(&pon->pdev->dev, + "Unable to get resin irq\n"); + return cfg->bark_irq; + } + + rc = of_property_read_u32(pp, "qcom,support-reset", + &cfg->support_reset); + + if (rc) { + if (rc == -EINVAL) { + dev_dbg(&pon->pdev->dev, + "'qcom,support-reset' DT property doesn't exist\n"); + } else { + dev_err(&pon->pdev->dev, + "Unable to read 'qcom,support-reset'\n"); + return rc; + } + } else { + cfg->config_reset = true; + } + + cfg->use_bark = of_property_read_bool(pp, + "qcom,use-bark"); + + rc = regmap_read(pon->regmap, PMIC_VERSION_REG, + &pmic_type); + + if (rc) { + dev_err(&pon->pdev->dev, + "Unable to read PMIC type\n"); + return rc; + } + + if (pmic_type == PMIC_VER_8941) { + + rc = regmap_read(pon->regmap, + PMIC_VERSION_REV4_REG, + &revid_rev4); + + if (rc) { + dev_err(&pon->pdev->dev, + "Unable to read PMIC revision ID\n"); + return rc; + } + + /* + * PM8941 V3 does not have hardware bug. Hence + * bark is not required from PMIC versions 3.0. + */ + if (!(revid_rev4 == PMIC8941_V1_REV4 || + revid_rev4 == PMIC8941_V2_REV4)) { + cfg->support_reset = false; + cfg->use_bark = false; + } + } + + if (cfg->use_bark) { + cfg->bark_irq + = platform_get_irq_byname(pon->pdev, + "resin-bark"); + if (cfg->bark_irq < 0) { + dev_err(&pon->pdev->dev, + "Unable to get resin-bark irq\n"); + return cfg->bark_irq; + } + } + + if (pon->pon_ver == QPNP_PON_GEN1_V1) { + cfg->s2_cntl_addr = cfg->s2_cntl2_addr = + QPNP_PON_RESIN_S2_CNTL(pon); + } else { + cfg->s2_cntl_addr = + QPNP_PON_RESIN_S2_CNTL(pon); + cfg->s2_cntl2_addr = + QPNP_PON_RESIN_S2_CNTL2(pon); + } + + break; + case PON_CBLPWR: + cfg->state_irq = platform_get_irq_byname(pon->pdev, + "cblpwr"); + if (cfg->state_irq < 0) { + dev_err(&pon->pdev->dev, + "Unable to get cblpwr irq\n"); + return rc; + } + break; + case PON_KPDPWR_RESIN: + rc = of_property_read_u32(pp, "qcom,support-reset", + &cfg->support_reset); + + if (rc) { + if (rc == -EINVAL) { + dev_dbg(&pon->pdev->dev, + "'qcom,support-reset' DT property doesn't exist\n"); + } else { + dev_err(&pon->pdev->dev, + "Unable to read 'qcom,support-reset'\n"); + return rc; + } + } else { + cfg->config_reset = true; + } + + cfg->use_bark = of_property_read_bool(pp, + "qcom,use-bark"); + if (cfg->use_bark) { + cfg->bark_irq + = platform_get_irq_byname(pon->pdev, + "kpdpwr-resin-bark"); + if (cfg->bark_irq < 0) { + dev_err(&pon->pdev->dev, + "Unable to get kpdpwr-resin-bark irq\n"); + return cfg->bark_irq; + } + } + + if (pon->pon_ver == QPNP_PON_GEN1_V1) { + cfg->s2_cntl_addr = cfg->s2_cntl2_addr = + QPNP_PON_KPDPWR_RESIN_S2_CNTL(pon); + } else { + cfg->s2_cntl_addr = + QPNP_PON_KPDPWR_RESIN_S2_CNTL(pon); + cfg->s2_cntl2_addr = + QPNP_PON_KPDPWR_RESIN_S2_CNTL2(pon); + } + + break; + default: + dev_err(&pon->pdev->dev, "PON RESET %d not supported", + cfg->pon_type); + return -EINVAL; + } + + if (cfg->support_reset) { + /* + * Get the reset parameters (bark debounce time and + * reset debounce time) for the reset line. + */ + rc = of_property_read_u32(pp, "qcom,s1-timer", + &cfg->s1_timer); + if (rc) { + dev_err(&pon->pdev->dev, + "Unable to read s1-timer\n"); + return rc; + } + if (cfg->s1_timer > QPNP_PON_S1_TIMER_MAX) { + dev_err(&pon->pdev->dev, + "Incorrect S1 debounce time\n"); + return -EINVAL; + } + rc = of_property_read_u32(pp, "qcom,s2-timer", + &cfg->s2_timer); + if (rc) { + dev_err(&pon->pdev->dev, + "Unable to read s2-timer\n"); + return rc; + } + if (cfg->s2_timer > QPNP_PON_S2_TIMER_MAX) { + dev_err(&pon->pdev->dev, + "Incorrect S2 debounce time\n"); + return -EINVAL; + } + rc = of_property_read_u32(pp, "qcom,s2-type", + &cfg->s2_type); + if (rc) { + dev_err(&pon->pdev->dev, + "Unable to read s2-type\n"); + return rc; + } + if (cfg->s2_type > QPNP_PON_RESET_TYPE_MAX) { + dev_err(&pon->pdev->dev, + "Incorrect reset type specified\n"); + return -EINVAL; + } + } + /* + * Get the standard-key parameters. This might not be + * specified if there is no key mapping on the reset line. + */ + rc = of_property_read_u32(pp, "linux,code", &cfg->key_code); + if (rc && rc != -EINVAL) { + dev_err(&pon->pdev->dev, "Unable to read key-code\n"); + return rc; + } + /* Register key configuration */ + if (cfg->key_code) { + rc = qpnp_pon_config_input(pon, cfg); + if (rc < 0) + return rc; + } + /* get the pull-up configuration */ + rc = of_property_read_u32(pp, "qcom,pull-up", &cfg->pull_up); + if (rc && rc != -EINVAL) { + dev_err(&pon->pdev->dev, "Unable to read pull-up\n"); + return rc; + } + } + + pmic_wd_bark_irq = platform_get_irq_byname(pon->pdev, "pmic-wd-bark"); + /* request the pmic-wd-bark irq only if it is defined */ + if (pmic_wd_bark_irq >= 0) { + rc = devm_request_irq(&pon->pdev->dev, pmic_wd_bark_irq, + qpnp_pmic_wd_bark_irq, + IRQF_TRIGGER_RISING, + "qpnp_pmic_wd_bark", pon); + if (rc < 0) { + dev_err(&pon->pdev->dev, + "Can't request %d IRQ\n", + pmic_wd_bark_irq); + goto free_input_dev; + } + } + + /* register the input device */ + if (pon->pon_input) { + rc = input_register_device(pon->pon_input); + if (rc) { + dev_err(&pon->pdev->dev, + "Can't register pon key: %d\n", rc); + goto free_input_dev; + } + } + + for (i = 0; i < pon->num_pon_config; i++) { + cfg = &pon->pon_cfg[i]; + /* Configure the pull-up */ + rc = qpnp_config_pull(pon, cfg); + if (rc) { + dev_err(&pon->pdev->dev, "Unable to config pull-up\n"); + goto unreg_input_dev; + } + + if (cfg->config_reset) { + /* Configure the reset-configuration */ + if (cfg->support_reset) { + rc = qpnp_config_reset(pon, cfg); + if (rc) { + dev_err(&pon->pdev->dev, + "Unable to config pon reset\n"); + goto unreg_input_dev; + } + } else { + if (cfg->pon_type != PON_CBLPWR) { + /* disable S2 reset */ + rc = qpnp_pon_masked_write(pon, + cfg->s2_cntl2_addr, + QPNP_PON_S2_CNTL_EN, 0); + if (rc) { + dev_err(&pon->pdev->dev, + "Unable to disable S2 reset\n"); + goto unreg_input_dev; + } + } + } + } + + rc = qpnp_pon_request_irqs(pon, cfg); + if (rc) { + dev_err(&pon->pdev->dev, "Unable to request-irq's\n"); + goto unreg_input_dev; + } + } + + device_init_wakeup(&pon->pdev->dev, 1); + + return rc; + +unreg_input_dev: + if (pon->pon_input) + input_unregister_device(pon->pon_input); +free_input_dev: + if (pon->pon_input) + input_free_device(pon->pon_input); + return rc; +} + +static int pon_spare_regulator_enable(struct regulator_dev *rdev) +{ + int rc = 0; + u8 value; + struct pon_regulator *pon_reg = rdev_get_drvdata(rdev); + + pr_debug("reg %s enable addr: %x bit: %d\n", rdev->desc->name, + pon_reg->addr, pon_reg->bit); + + value = BIT(pon_reg->bit) & 0xFF; + rc = qpnp_pon_masked_write(pon_reg->pon, pon_reg->pon->base + + pon_reg->addr, value, value); + if (rc) + dev_err(&pon_reg->pon->pdev->dev, "Unable to write to %x\n", + pon_reg->pon->base + pon_reg->addr); + else + pon_reg->enabled = true; + return rc; +} + +static int pon_spare_regulator_disable(struct regulator_dev *rdev) +{ + int rc = 0; + u8 mask; + struct pon_regulator *pon_reg = rdev_get_drvdata(rdev); + + pr_debug("reg %s disable addr: %x bit: %d\n", rdev->desc->name, + pon_reg->addr, pon_reg->bit); + + mask = BIT(pon_reg->bit) & 0xFF; + rc = qpnp_pon_masked_write(pon_reg->pon, pon_reg->pon->base + + pon_reg->addr, mask, 0); + if (rc) + dev_err(&pon_reg->pon->pdev->dev, "Unable to write to %x\n", + pon_reg->pon->base + pon_reg->addr); + else + pon_reg->enabled = false; + return rc; +} + +static int pon_spare_regulator_is_enable(struct regulator_dev *rdev) +{ + struct pon_regulator *pon_reg = rdev_get_drvdata(rdev); + + return pon_reg->enabled; +} + +struct regulator_ops pon_spare_reg_ops = { + .enable = pon_spare_regulator_enable, + .disable = pon_spare_regulator_disable, + .is_enabled = pon_spare_regulator_is_enable, +}; + +static int pon_regulator_init(struct qpnp_pon *pon) +{ + int rc = 0, i = 0; + struct regulator_init_data *init_data; + struct regulator_config reg_cfg = {}; + struct device_node *node = NULL; + struct device *dev = &pon->pdev->dev; + struct pon_regulator *pon_reg; + + if (!pon->num_pon_reg) + return 0; + + pon->pon_reg_cfg = devm_kcalloc(dev, pon->num_pon_reg, + sizeof(*(pon->pon_reg_cfg)), + GFP_KERNEL); + + if (!pon->pon_reg_cfg) + return -ENOMEM; + + for_each_available_child_of_node(dev->of_node, node) { + if (!of_find_property(node, "regulator-name", NULL)) + continue; + + pon_reg = &pon->pon_reg_cfg[i++]; + pon_reg->pon = pon; + + rc = of_property_read_u32(node, "qcom,pon-spare-reg-addr", + &pon_reg->addr); + if (rc) { + dev_err(dev, "Unable to read address for regulator, rc=%d\n", + rc); + return rc; + } + + rc = of_property_read_u32(node, "qcom,pon-spare-reg-bit", + &pon_reg->bit); + if (rc) { + dev_err(dev, "Unable to read bit for regulator, rc=%d\n", + rc); + return rc; + } + + init_data = of_get_regulator_init_data(dev, node, + &pon_reg->rdesc); + if (!init_data) { + dev_err(dev, "regulator init data is missing\n"); + return -EINVAL; + } + init_data->constraints.valid_ops_mask + |= REGULATOR_CHANGE_STATUS; + + if (!init_data->constraints.name) { + dev_err(dev, "regulator-name is missing\n"); + return -EINVAL; + } + + pon_reg->rdesc.owner = THIS_MODULE; + pon_reg->rdesc.type = REGULATOR_VOLTAGE; + pon_reg->rdesc.ops = &pon_spare_reg_ops; + pon_reg->rdesc.name = init_data->constraints.name; + + reg_cfg.dev = dev; + reg_cfg.init_data = init_data; + reg_cfg.driver_data = pon_reg; + reg_cfg.of_node = node; + + pon_reg->rdev = regulator_register(&pon_reg->rdesc, ®_cfg); + if (IS_ERR(pon_reg->rdev)) { + rc = PTR_ERR(pon_reg->rdev); + pon_reg->rdev = NULL; + if (rc != -EPROBE_DEFER) + dev_err(dev, "regulator_register failed, rc=%d\n", + rc); + return rc; + } + } + return rc; +} + +static bool smpl_en; + +static int qpnp_pon_smpl_en_get(char *buf, const struct kernel_param *kp) +{ + bool enabled = 0; + int rc; + + rc = qpnp_pon_get_trigger_config(PON_SMPL, &enabled); + if (rc < 0) + return rc; + + return snprintf(buf, QPNP_PON_BUFFER_SIZE, "%d", enabled); +} + +static int qpnp_pon_smpl_en_set(const char *val, + const struct kernel_param *kp) +{ + int rc; + + rc = param_set_bool(val, kp); + if (rc < 0) { + pr_err("Unable to set smpl_en rc=%d\n", rc); + return rc; + } + + rc = qpnp_pon_trigger_config(PON_SMPL, *(bool *)kp->arg); + return rc; +} + +static struct kernel_param_ops smpl_en_ops = { + .set = qpnp_pon_smpl_en_set, + .get = qpnp_pon_smpl_en_get, +}; + +module_param_cb(smpl_en, &smpl_en_ops, &smpl_en, 0644); + +static bool dload_on_uvlo; + +static int qpnp_pon_debugfs_uvlo_dload_get(char *buf, + const struct kernel_param *kp) +{ + struct qpnp_pon *pon = sys_reset_dev; + int rc = 0; + uint reg; + + if (!pon) + return -ENODEV; + + rc = regmap_read(pon->regmap, QPNP_PON_XVDD_RB_SPARE(pon), ®); + if (rc) { + dev_err(&pon->pdev->dev, + "Unable to read addr=%x, rc(%d)\n", + QPNP_PON_XVDD_RB_SPARE(pon), rc); + return rc; + } + + return snprintf(buf, PAGE_SIZE, "%d", + !!(QPNP_PON_UVLO_DLOAD_EN & reg)); +} + +static int qpnp_pon_debugfs_uvlo_dload_set(const char *val, + const struct kernel_param *kp) +{ + struct qpnp_pon *pon = sys_reset_dev; + int rc = 0; + uint reg; + + if (!pon) + return -ENODEV; + + rc = param_set_bool(val, kp); + if (rc) { + pr_err("Unable to set bms_reset: %d\n", rc); + return rc; + } + + rc = regmap_read(pon->regmap, QPNP_PON_XVDD_RB_SPARE(pon), ®); + if (rc) { + dev_err(&pon->pdev->dev, + "Unable to read addr=%x, rc(%d)\n", + QPNP_PON_XVDD_RB_SPARE(pon), rc); + return rc; + } + + reg &= ~QPNP_PON_UVLO_DLOAD_EN; + if (*(bool *)kp->arg) + reg |= QPNP_PON_UVLO_DLOAD_EN; + + rc = regmap_write(pon->regmap, QPNP_PON_XVDD_RB_SPARE(pon), reg); + if (rc) { + dev_err(&pon->pdev->dev, + "Unable to write to addr=%hx, rc(%d)\n", + QPNP_PON_XVDD_RB_SPARE(pon), rc); + return rc; + } + + return 0; +} + +static struct kernel_param_ops dload_on_uvlo_ops = { + .set = qpnp_pon_debugfs_uvlo_dload_set, + .get = qpnp_pon_debugfs_uvlo_dload_get, +}; + +module_param_cb(dload_on_uvlo, &dload_on_uvlo_ops, &dload_on_uvlo, 0644); + +#if defined(CONFIG_DEBUG_FS) + +static int qpnp_pon_debugfs_uvlo_get(void *data, u64 *val) +{ + struct qpnp_pon *pon = (struct qpnp_pon *) data; + + *val = pon->uvlo; + + return 0; +} + +static int qpnp_pon_debugfs_uvlo_set(void *data, u64 val) +{ + struct qpnp_pon *pon = (struct qpnp_pon *) data; + + if (pon->pon_trigger_reason == PON_SMPL || + pon->pon_power_off_reason == QPNP_POFF_REASON_UVLO) + panic("An UVLO was occurred.\n"); + pon->uvlo = val; + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(qpnp_pon_debugfs_uvlo_fops, qpnp_pon_debugfs_uvlo_get, + qpnp_pon_debugfs_uvlo_set, "0x%02llx\n"); + +static void qpnp_pon_debugfs_init(struct platform_device *pdev) +{ + struct qpnp_pon *pon = dev_get_drvdata(&pdev->dev); + struct dentry *ent; + + pon->debugfs = debugfs_create_dir(dev_name(&pdev->dev), NULL); + if (!pon->debugfs) { + dev_err(&pon->pdev->dev, + "Unable to create debugfs directory\n"); + } else { + ent = debugfs_create_file("uvlo_panic", 0644, + pon->debugfs, pon, &qpnp_pon_debugfs_uvlo_fops); + if (!ent) + dev_err(&pon->pdev->dev, + "Unable to create uvlo_panic debugfs file.\n"); + } +} + +static void qpnp_pon_debugfs_remove(struct platform_device *pdev) +{ + struct qpnp_pon *pon = dev_get_drvdata(&pdev->dev); + + debugfs_remove_recursive(pon->debugfs); +} + +#else + +static void qpnp_pon_debugfs_init(struct platform_device *pdev) +{} + +static void qpnp_pon_debugfs_remove(struct platform_device *pdev) +{} +#endif + +static int read_gen2_pon_off_reason(struct qpnp_pon *pon, u16 *reason, + int *reason_index_offset) +{ + int rc; + int buf[2], reg; + + rc = regmap_read(pon->regmap, + QPNP_PON_OFF_REASON(pon), + ®); + if (rc) { + dev_err(&pon->pdev->dev, "Unable to read PON_OFF_REASON reg rc:%d\n", + rc); + return rc; + } + + if (reg & QPNP_GEN2_POFF_SEQ) { + rc = regmap_read(pon->regmap, + QPNP_POFF_REASON1(pon), + buf); + if (rc) { + dev_err(&pon->pdev->dev, "Unable to read POFF_REASON1 reg rc:%d\n", + rc); + return rc; + } + *reason = (u8)buf[0]; + *reason_index_offset = 0; + } else if (reg & QPNP_GEN2_FAULT_SEQ) { + rc = regmap_bulk_read(pon->regmap, + QPNP_FAULT_REASON1(pon), + buf, 2); + if (rc) { + dev_err(&pon->pdev->dev, "Unable to read FAULT_REASON regs rc:%d\n", + rc); + return rc; + } + *reason = (u8)buf[0] | (u16)(buf[1] << 8); + *reason_index_offset = POFF_REASON_FAULT_OFFSET; + } else if (reg & QPNP_GEN2_S3_RESET_SEQ) { + rc = regmap_read(pon->regmap, + QPNP_S3_RESET_REASON(pon), + buf); + if (rc) { + dev_err(&pon->pdev->dev, "Unable to read S3_RESET_REASON reg rc:%d\n", + rc); + return rc; + } + *reason = (u8)buf[0]; + *reason_index_offset = POFF_REASON_S3_RESET_OFFSET; + } + + return 0; +} + +static int qpnp_pon_probe(struct platform_device *pdev) +{ + struct qpnp_pon *pon; + unsigned int base; + struct device_node *node = NULL; + u32 delay = 0, s3_debounce = 0; + int rc, sys_reset, index; + int reason_index_offset = 0; + u8 buf[2]; + uint pon_sts = 0; + u16 poff_sts = 0; + const char *s3_src; + u8 s3_src_reg; + unsigned long flags; + uint temp = 0; + + pon = devm_kzalloc(&pdev->dev, sizeof(struct qpnp_pon), GFP_KERNEL); + if (!pon) + return -ENOMEM; + + pon->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!pon->regmap) { + dev_err(&pdev->dev, "Couldn't get parent's regmap\n"); + return -EINVAL; + } + + sys_reset = of_property_read_bool(pdev->dev.of_node, + "qcom,system-reset"); + if (sys_reset && sys_reset_dev) { + dev_err(&pdev->dev, + "qcom,system-reset property can only be specified for one device on the system\n"); + return -EINVAL; + } else if (sys_reset) { + sys_reset_dev = pon; + } + + pon->pdev = pdev; + + rc = of_property_read_u32(pdev->dev.of_node, "reg", &base); + if (rc < 0) { + dev_err(&pdev->dev, + "Couldn't find reg in node = %s rc = %d\n", + pdev->dev.of_node->full_name, rc); + return rc; + } + pon->base = base; + + /* get the total number of pon configurations */ + for_each_available_child_of_node(pdev->dev.of_node, node) { + if (of_find_property(node, "regulator-name", NULL)) { + pon->num_pon_reg++; + } else if (of_find_property(node, "qcom,pon-type", NULL)) { + pon->num_pon_config++; + } else { + pr_err("Unknown sub-node\n"); + return -EINVAL; + } + } + + pr_debug("PON@SID %d: num_pon_config: %d num_pon_reg: %d\n", + to_spmi_device(pon->pdev->dev.parent)->usid, + pon->num_pon_config, pon->num_pon_reg); + + rc = pon_regulator_init(pon); + if (rc) { + dev_err(&pdev->dev, "Error in pon_regulator_init rc: %d\n", + rc); + return rc; + } + + if (!pon->num_pon_config) + /* No PON config., do not register the driver */ + dev_info(&pdev->dev, "No PON config. specified\n"); + else + pon->pon_cfg = devm_kzalloc(&pdev->dev, + sizeof(struct qpnp_pon_config) * + pon->num_pon_config, GFP_KERNEL); + + /* Read PON_PERPH_SUBTYPE register to get PON type */ + rc = regmap_read(pon->regmap, + QPNP_PON_PERPH_SUBTYPE(pon), + &temp); + if (rc) { + dev_err(&pon->pdev->dev, + "Unable to read PON_PERPH_SUBTYPE register rc: %d\n", + rc); + return rc; + } + pon->subtype = temp; + + /* Check if it is rev B */ + rc = regmap_read(pon->regmap, + QPNP_PON_REVISION2(pon), &temp); + if (rc) { + dev_err(&pon->pdev->dev, + "Unable to read addr=%x, rc(%d)\n", + QPNP_PON_REVISION2(pon), rc); + return rc; + } + + pon->pon_ver = temp; + if (is_pon_gen1(pon)) { + if (pon->pon_ver == 0) + pon->pon_ver = QPNP_PON_GEN1_V1; + else + pon->pon_ver = QPNP_PON_GEN1_V2; + } else if (is_pon_gen2(pon)) { + pon->pon_ver = QPNP_PON_GEN2; + } else if (pon->subtype == PON_1REG) { + pon->pon_ver = QPNP_PON_GEN1_V2; + } else { + dev_err(&pon->pdev->dev, + "Invalid PON_PERPH_SUBTYPE value %x\n", + pon->subtype); + return -EINVAL; + } + + pr_debug("%s: pon_subtype=%x, pon_version=%x\n", __func__, + pon->subtype, pon->pon_ver); + + rc = qpnp_pon_store_and_clear_warm_reset(pon); + if (rc) { + dev_err(&pon->pdev->dev, + "Unable to store/clear WARM_RESET_REASONx registers rc: %d\n", + rc); + return rc; + } + + /* PON reason */ + rc = regmap_read(pon->regmap, QPNP_PON_REASON1(pon), &pon_sts); + if (rc) { + dev_err(&pon->pdev->dev, + "Unable to read PON_RESASON1 reg rc: %d\n", + rc); + return rc; + } + + if (sys_reset) + boot_reason = ffs(pon_sts); + + index = ffs(pon_sts) - 1; + cold_boot = !qpnp_pon_is_warm_reset(); + if (index >= ARRAY_SIZE(qpnp_pon_reason) || index < 0) { + dev_info(&pon->pdev->dev, + "PMIC@SID%d Power-on reason: Unknown and '%s' boot\n", + to_spmi_device(pon->pdev->dev.parent)->usid, + cold_boot ? "cold" : "warm"); + } else { + pon->pon_trigger_reason = index; + dev_info(&pon->pdev->dev, + "PMIC@SID%d Power-on reason: %s and '%s' boot\n", + to_spmi_device(pon->pdev->dev.parent)->usid, + qpnp_pon_reason[index], + cold_boot ? "cold" : "warm"); + } + + /* POFF reason */ + if (!is_pon_gen1(pon) && pon->subtype != PON_1REG) { + rc = read_gen2_pon_off_reason(pon, &poff_sts, + &reason_index_offset); + if (rc) + return rc; + } else { + rc = regmap_bulk_read(pon->regmap, QPNP_POFF_REASON1(pon), + buf, 2); + if (rc) { + dev_err(&pon->pdev->dev, "Unable to read POFF_REASON regs rc:%d\n", + rc); + return rc; + } + poff_sts = buf[0] | (buf[1] << 8); + } + index = ffs(poff_sts) - 1 + reason_index_offset; + if (index >= ARRAY_SIZE(qpnp_poff_reason) || index < 0) { + dev_info(&pon->pdev->dev, + "PMIC@SID%d: Unknown power-off reason\n", + to_spmi_device(pon->pdev->dev.parent)->usid); + } else { + pon->pon_power_off_reason = index; + dev_info(&pon->pdev->dev, + "PMIC@SID%d: Power-off reason: %s\n", + to_spmi_device(pon->pdev->dev.parent)->usid, + qpnp_poff_reason[index]); + } + + if (pon->pon_trigger_reason == PON_SMPL || + pon->pon_power_off_reason == QPNP_POFF_REASON_UVLO) { + if (of_property_read_bool(pdev->dev.of_node, + "qcom,uvlo-panic")) + panic("An UVLO was occurred."); + } + + /* program s3 debounce */ + rc = of_property_read_u32(pon->pdev->dev.of_node, + "qcom,s3-debounce", &s3_debounce); + if (rc) { + if (rc != -EINVAL) { + dev_err(&pon->pdev->dev, + "Unable to read s3 timer rc:%d\n", + rc); + return rc; + } + } else { + if (s3_debounce > QPNP_PON_S3_TIMER_SECS_MAX) { + dev_info(&pon->pdev->dev, + "Exceeded S3 max value, set it to max\n"); + s3_debounce = QPNP_PON_S3_TIMER_SECS_MAX; + } + + /* 0 is a special value to indicate instant s3 reset */ + if (s3_debounce != 0) + s3_debounce = ilog2(s3_debounce); + + /* s3 debounce is SEC_ACCESS register */ + rc = qpnp_pon_masked_write(pon, QPNP_PON_SEC_ACCESS(pon), + 0xFF, QPNP_PON_SEC_UNLOCK); + if (rc) { + dev_err(&pdev->dev, "Unable to do SEC_ACCESS rc:%d\n", + rc); + return rc; + } + + rc = qpnp_pon_masked_write(pon, QPNP_PON_S3_DBC_CTL(pon), + QPNP_PON_S3_DBC_DELAY_MASK, s3_debounce); + if (rc) { + dev_err(&pdev->dev, + "Unable to set S3 debounce rc:%d\n", + rc); + return rc; + } + } + + /* program s3 source */ + s3_src = "kpdpwr-and-resin"; + rc = of_property_read_string(pon->pdev->dev.of_node, + "qcom,s3-src", &s3_src); + if (rc && rc != -EINVAL) { + dev_err(&pon->pdev->dev, "Unable to read s3 timer rc: %d\n", + rc); + return rc; + } + + if (!strcmp(s3_src, "kpdpwr")) + s3_src_reg = QPNP_PON_S3_SRC_KPDPWR; + else if (!strcmp(s3_src, "resin")) + s3_src_reg = QPNP_PON_S3_SRC_RESIN; + else if (!strcmp(s3_src, "kpdpwr-or-resin")) + s3_src_reg = QPNP_PON_S3_SRC_KPDPWR_OR_RESIN; + else /* default combination */ + s3_src_reg = QPNP_PON_S3_SRC_KPDPWR_AND_RESIN; + + /* + * S3 source is a write once register. If the register has + * been configured by bootloader then this operation will + * not be effective. + */ + rc = qpnp_pon_masked_write(pon, QPNP_PON_S3_SRC(pon), + QPNP_PON_S3_SRC_MASK, s3_src_reg); + if (rc) { + dev_err(&pdev->dev, "Unable to program s3 source rc: %d\n", + rc); + return rc; + } + + dev_set_drvdata(&pdev->dev, pon); + + INIT_DELAYED_WORK(&pon->bark_work, bark_work_func); + + /* register the PON configurations */ + rc = qpnp_pon_config_init(pon); + if (rc) { + dev_err(&pdev->dev, + "Unable to initialize PON configurations rc: %d\n", rc); + return rc; + } + + rc = of_property_read_u32(pon->pdev->dev.of_node, + "qcom,pon-dbc-delay", &delay); + if (rc) { + if (rc != -EINVAL) { + dev_err(&pdev->dev, + "Unable to read debounce delay rc: %d\n", rc); + return rc; + } + } else { + rc = qpnp_pon_set_dbc(pon, delay); + if (rc) { + dev_err(&pdev->dev, + "Unable to set PON debounce delay rc=%d\n", rc); + return rc; + } + } + rc = qpnp_pon_get_dbc(pon, &pon->dbc_time_us); + if (rc) { + dev_err(&pdev->dev, + "Unable to get PON debounce delay rc=%d\n", rc); + return rc; + } + + pon->kpdpwr_dbc_enable = of_property_read_bool(pon->pdev->dev.of_node, + "qcom,kpdpwr-sw-debounce"); + + rc = of_property_read_u32(pon->pdev->dev.of_node, + "qcom,warm-reset-poweroff-type", + &pon->warm_reset_poff_type); + if (rc) { + if (rc != -EINVAL) { + dev_err(&pdev->dev, "Unable to read warm reset poweroff type rc: %d\n", + rc); + return rc; + } + pon->warm_reset_poff_type = -EINVAL; + } else if (pon->warm_reset_poff_type <= PON_POWER_OFF_RESERVED || + pon->warm_reset_poff_type >= PON_POWER_OFF_MAX_TYPE) { + dev_err(&pdev->dev, "Invalid warm-reset-poweroff-type\n"); + pon->warm_reset_poff_type = -EINVAL; + } + + rc = of_property_read_u32(pon->pdev->dev.of_node, + "qcom,hard-reset-poweroff-type", + &pon->hard_reset_poff_type); + if (rc) { + if (rc != -EINVAL) { + dev_err(&pdev->dev, "Unable to read hard reset poweroff type rc: %d\n", + rc); + return rc; + } + pon->hard_reset_poff_type = -EINVAL; + } else if (pon->hard_reset_poff_type <= PON_POWER_OFF_RESERVED || + pon->hard_reset_poff_type >= PON_POWER_OFF_MAX_TYPE) { + dev_err(&pdev->dev, "Invalid hard-reset-poweroff-type\n"); + pon->hard_reset_poff_type = -EINVAL; + } + + rc = of_property_read_u32(pon->pdev->dev.of_node, + "qcom,shutdown-poweroff-type", + &pon->shutdown_poff_type); + if (rc) { + if (rc != -EINVAL) { + dev_err(&pdev->dev, "Unable to read shutdown poweroff type rc: %d\n", + rc); + return rc; + } + pon->shutdown_poff_type = -EINVAL; + } else if (pon->shutdown_poff_type <= PON_POWER_OFF_RESERVED || + pon->shutdown_poff_type >= PON_POWER_OFF_MAX_TYPE) { + dev_err(&pdev->dev, "Invalid shutdown-poweroff-type\n"); + pon->shutdown_poff_type = -EINVAL; + } + + rc = device_create_file(&pdev->dev, &dev_attr_debounce_us); + if (rc) { + dev_err(&pdev->dev, "sys file creation failed rc: %d\n", rc); + return rc; + } + + if (of_property_read_bool(pdev->dev.of_node, + "qcom,secondary-pon-reset")) { + if (sys_reset) { + dev_err(&pdev->dev, + "qcom,system-reset property shouldn't be used along with qcom,secondary-pon-reset property\n"); + return -EINVAL; + } + spin_lock_irqsave(&spon_list_slock, flags); + list_add(&pon->list, &spon_dev_list); + spin_unlock_irqrestore(&spon_list_slock, flags); + pon->is_spon = true; + } + + /* config whether store the hard reset reason */ + pon->store_hard_reset_reason = of_property_read_bool(pdev->dev.of_node, + "qcom,store-hard-reset-reason"); + + qpnp_pon_debugfs_init(pdev); + return 0; +} + +static int qpnp_pon_remove(struct platform_device *pdev) +{ + struct qpnp_pon *pon = dev_get_drvdata(&pdev->dev); + unsigned long flags; + + device_remove_file(&pdev->dev, &dev_attr_debounce_us); + + cancel_delayed_work_sync(&pon->bark_work); + + if (pon->pon_input) + input_unregister_device(pon->pon_input); + qpnp_pon_debugfs_remove(pdev); + if (pon->is_spon) { + spin_lock_irqsave(&spon_list_slock, flags); + list_del(&pon->list); + spin_unlock_irqrestore(&spon_list_slock, flags); + } + return 0; +} + +static const struct of_device_id spmi_match_table[] = { + { .compatible = "qcom,qpnp-power-on", }, + {} +}; + +static struct platform_driver qpnp_pon_driver = { + .driver = { + .name = "qcom,qpnp-power-on", + .of_match_table = spmi_match_table, + }, + .probe = qpnp_pon_probe, + .remove = qpnp_pon_remove, +}; + +static int __init qpnp_pon_init(void) +{ + return platform_driver_register(&qpnp_pon_driver); +} +subsys_initcall(qpnp_pon_init); + +static void __exit qpnp_pon_exit(void) +{ + return platform_driver_unregister(&qpnp_pon_driver); +} +module_exit(qpnp_pon_exit); + +MODULE_DESCRIPTION("QPNP PMIC POWER-ON driver"); +MODULE_LICENSE("GPL v2"); |