| /* Copyright (c) 2012-2018, 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/debugfs.h> |
| #include <linux/delay.h> |
| #include <linux/errno.h> |
| #include <linux/init.h> |
| #include <linux/input.h> |
| #include <linux/interrupt.h> |
| #include <linux/kernel.h> |
| #include <linux/list.h> |
| #include <linux/log2.h> |
| #include <linux/module.h> |
| #include <linux/mutex.h> |
| #include <linux/of_device.h> |
| #include <linux/of.h> |
| #include <linux/platform_device.h> |
| #include <linux/power_supply.h> |
| #include <linux/regmap.h> |
| #include <linux/slab.h> |
| #include <linux/spmi.h> |
| #include <linux/input/qpnp-power-on.h> |
| #include <linux/regulator/driver.h> |
| #include <linux/regulator/machine.h> |
| #include <linux/regulator/of_regulator.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 register addresses */ |
| #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) |
| |
| /* Limits */ |
| #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_POWER_ON_TYPE_KPDPWR, |
| PON_RESIN = PON_POWER_ON_TYPE_RESIN, |
| PON_CBLPWR = PON_POWER_ON_TYPE_CBLPWR, |
| PON_KPDPWR_RESIN = PON_POWER_ON_TYPE_KPDPWR_RESIN, |
| }; |
| |
| struct qpnp_pon_config { |
| u32 pon_type; |
| u32 support_reset; |
| u32 key_code; |
| u32 s1_timer; |
| u32 s2_timer; |
| u32 s2_type; |
| bool pull_up; |
| int state_irq; |
| int 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 device *dev; |
| 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; |
| u16 base; |
| u8 subtype; |
| u8 pon_ver; |
| u8 warm_reset_reason1; |
| u8 warm_reset_reason2; |
| int num_pon_config; |
| int num_pon_reg; |
| int pon_trigger_reason; |
| int pon_power_off_reason; |
| u32 dbc_time_us; |
| u32 uvlo; |
| int warm_reset_poff_type; |
| int hard_reset_poff_type; |
| int shutdown_poff_type; |
| int resin_warm_reset_type; |
| int resin_hard_reset_type; |
| int resin_shutdown_type; |
| bool is_spon; |
| bool store_hard_reset_reason; |
| bool resin_hard_reset_disable; |
| bool resin_shutdown_disable; |
| bool ps_hold_hard_reset_disable; |
| bool ps_hold_shutdown_disable; |
| bool kpdpwr_dbc_enable; |
| bool resin_pon_reset; |
| 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 (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 (Over Temperature)", |
| [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 (Over Temperature)", |
| |
| /* 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", |
| }; |
| |
| 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->dev, "Register write failed, addr=0x%04X, rc=%d\n", |
| addr, rc); |
| return rc; |
| } |
| |
| static int qpnp_pon_read(struct qpnp_pon *pon, u16 addr, unsigned int *val) |
| { |
| int rc; |
| |
| rc = regmap_read(pon->regmap, addr, val); |
| if (rc) |
| dev_err(pon->dev, "Register read failed, addr=0x%04X, 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)); |
| |
| 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) |
| return 0; |
| |
| 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) |
| pon->dbc_time_us = delay; |
| |
| if (pon->pon_input) |
| mutex_unlock(&pon->pon_input->mutex); |
| |
| return rc; |
| } |
| |
| static int qpnp_pon_get_dbc(struct qpnp_pon *pon, u32 *delay) |
| { |
| int rc; |
| unsigned int val; |
| |
| rc = qpnp_pon_read(pon, QPNP_PON_DBC_CTL(pon), &val); |
| if (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 debounce_us_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct qpnp_pon *pon = dev_get_drvdata(dev); |
| |
| return scnprintf(buf, QPNP_PON_BUFFER_SIZE, "%d\n", pon->dbc_time_us); |
| } |
| |
| static ssize_t debounce_us_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_RW(debounce_us); |
| |
| static int qpnp_pon_reset_config(struct qpnp_pon *pon, |
| enum pon_power_off_type type) |
| { |
| int rc; |
| bool disable = false; |
| 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 power-off 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; |
| disable = pon->ps_hold_hard_reset_disable; |
| break; |
| case PON_POWER_OFF_SHUTDOWN: |
| if (pon->shutdown_poff_type != -EINVAL) |
| type = pon->shutdown_poff_type; |
| disable = pon->ps_hold_shutdown_disable; |
| break; |
| default: |
| break; |
| } |
| |
| rc = qpnp_pon_masked_write(pon, rst_en_reg, QPNP_PON_RESET_EN, 0); |
| if (rc) |
| return rc; |
| |
| /* |
| * Check if ps-hold power off configuration needs to be disabled. |
| * If yes, then return without configuring. |
| */ |
| if (disable) |
| return 0; |
| |
| /* |
| * 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) |
| return rc; |
| |
| rc = qpnp_pon_masked_write(pon, rst_en_reg, QPNP_PON_RESET_EN, |
| QPNP_PON_RESET_EN); |
| if (rc) |
| return rc; |
| |
| dev_dbg(pon->dev, "ps_hold power off type = 0x%02X\n", type); |
| |
| return 0; |
| } |
| |
| static int qpnp_resin_pon_reset_config(struct qpnp_pon *pon, |
| enum pon_power_off_type type) |
| { |
| int rc; |
| bool disable = false; |
| u16 rst_en_reg; |
| |
| if (pon->pon_ver == QPNP_PON_GEN1_V1) |
| rst_en_reg = QPNP_PON_RESIN_S2_CNTL(pon); |
| else |
| rst_en_reg = QPNP_PON_RESIN_S2_CNTL2(pon); |
| |
| /* |
| * Based on the power-off type set for a PON device through device tree |
| * change the type being configured into PON_RESIN_S2_CTL. |
| */ |
| switch (type) { |
| case PON_POWER_OFF_WARM_RESET: |
| if (pon->resin_warm_reset_type != -EINVAL) |
| type = pon->resin_warm_reset_type; |
| break; |
| case PON_POWER_OFF_HARD_RESET: |
| if (pon->resin_hard_reset_type != -EINVAL) |
| type = pon->resin_hard_reset_type; |
| disable = pon->resin_hard_reset_disable; |
| break; |
| case PON_POWER_OFF_SHUTDOWN: |
| if (pon->resin_shutdown_type != -EINVAL) |
| type = pon->resin_shutdown_type; |
| disable = pon->resin_shutdown_disable; |
| break; |
| default: |
| break; |
| } |
| |
| rc = qpnp_pon_masked_write(pon, rst_en_reg, QPNP_PON_S2_CNTL_EN, 0); |
| if (rc) |
| return rc; |
| |
| /* |
| * Check if resin power off configuration needs to be disabled. |
| * If yes, then return without configuring. |
| */ |
| if (disable) |
| return 0; |
| |
| /* |
| * 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_RESIN_S2_CNTL(pon), |
| QPNP_PON_S2_CNTL_TYPE_MASK, type); |
| if (rc) |
| return rc; |
| |
| rc = qpnp_pon_masked_write(pon, rst_en_reg, QPNP_PON_S2_CNTL_EN, |
| QPNP_PON_S2_CNTL_EN); |
| if (rc) |
| return rc; |
| |
| dev_dbg(pon->dev, "resin power off type = 0x%02X\n", type); |
| |
| return 0; |
| } |
| |
| /** |
| * 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 supports configuration of 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) |
| { |
| struct qpnp_pon *pon, *tmp; |
| struct power_supply *batt_psy; |
| union power_supply_propval val; |
| unsigned long flags; |
| int rc; |
| |
| if (!sys_reset_dev) |
| return -ENODEV; |
| |
| rc = qpnp_pon_reset_config(sys_reset_dev, type); |
| if (rc) { |
| dev_err(sys_reset_dev->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->dev, "PMIC@SID%d: configuring PON for reset\n", |
| to_spmi_device(pon->dev->parent)->usid); |
| rc = qpnp_pon_reset_config(pon, type); |
| if (rc) { |
| dev_err(pon->dev, "Error configuring secondary PON, rc=%d\n", |
| rc); |
| goto out; |
| } |
| if (pon->resin_pon_reset) { |
| rc = qpnp_resin_pon_reset_config(pon, type); |
| if (rc) { |
| dev_err(pon->dev, "Error configuring secondary PON resin, 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("Setting ship mode\n"); |
| val.intval = 1; |
| rc = power_supply_set_property(batt_psy, |
| POWER_SUPPLY_PROP_SET_SHIP_MODE, &val); |
| if (rc) |
| dev_err(sys_reset_dev->dev, "Failed to set ship mode\n"); |
| } |
| } |
| out: |
| spin_unlock_irqrestore(&spon_list_slock, flags); |
| |
| return rc; |
| } |
| EXPORT_SYMBOL(qpnp_pon_system_pwr_off); |
| |
| static int _qpnp_pon_is_warm_reset(struct qpnp_pon *pon) |
| { |
| if (!pon) |
| return -ENODEV; |
| |
| 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; |
| } |
| |
| /** |
| * 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) |
| { |
| if (!sys_reset_dev) |
| return -EPROBE_DEFER; |
| |
| return _qpnp_pon_is_warm_reset(sys_reset_dev); |
| } |
| EXPORT_SYMBOL(qpnp_pon_is_warm_reset); |
| |
| /** |
| * qpnp_pon_wd_config() - configure the watch dog behavior for warm reset |
| * @enable: to enable or disable the PON watch dog |
| * |
| * Return: 0 for success or < 0 for errors |
| */ |
| int qpnp_pon_wd_config(bool enable) |
| { |
| if (!sys_reset_dev) |
| return -EPROBE_DEFER; |
| |
| return qpnp_pon_masked_write(sys_reset_dev, |
| QPNP_PON_WD_RST_S2_CTL2(sys_reset_dev), |
| QPNP_PON_WD_EN, enable ? QPNP_PON_WD_EN : 0); |
| } |
| 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 val, rc; |
| u16 addr; |
| u8 mask; |
| |
| if (!pon) |
| return -ENODEV; |
| |
| if (pon_src < PON_SMPL || pon_src > PON_KPDPWR_N) { |
| dev_err(pon->dev, "Invalid PON source %d\n", pon_src); |
| 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 = qpnp_pon_read(pon, addr, &val); |
| if (rc) |
| return rc; |
| |
| *enabled = !!(val & mask); |
| |
| return 0; |
| } |
| |
| /** |
| * qpnp_pon_trigger_config() - Configures enable state of 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->dev, "Invalid PON source %d\n", pon_src); |
| 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); |
| else |
| rc = qpnp_pon_masked_write(pon, QPNP_PON_TRIGGER_EN(pon), |
| BIT(pon_src), enable ? BIT(pon_src) : 0); |
| |
| 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; |
| uint val; |
| |
| rc = qpnp_pon_read(pon, QPNP_PON_WARM_RESET_REASON1(pon), &val); |
| if (rc) |
| return rc; |
| pon->warm_reset_reason1 = (u8)val; |
| |
| if (is_pon_gen1(pon) || pon->subtype == PON_1REG) { |
| rc = qpnp_pon_read(pon, QPNP_PON_WARM_RESET_REASON2(pon), &val); |
| if (rc) |
| return rc; |
| pon->warm_reset_reason2 = (u8)val; |
| } |
| |
| if (of_property_read_bool(pon->dev->of_node, "qcom,clear-warm-reset")) { |
| rc = regmap_write(pon->regmap, QPNP_PON_WARM_RESET_REASON1(pon), |
| 0); |
| if (rc) { |
| dev_err(pon->dev, "Register write failed, addr=0x%04X, rc=%d\n", |
| QPNP_PON_WARM_RESET_REASON1(pon), rc); |
| return 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) |
| { |
| struct qpnp_pon_config *cfg = NULL; |
| u8 pon_rt_bit = 0; |
| u32 key_status; |
| uint pon_rt_sts; |
| u64 elapsed_us; |
| int rc; |
| |
| 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 = qpnp_pon_read(pon, QPNP_PON_RT_STS(pon), &pon_rt_sts); |
| if (rc) |
| 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_info("PMIC input: code=%d, sts=0x%hhx, bit=0x%hhx, type=%d, os=%d\n", |
| cfg->key_code, pon_rt_sts, pon_rt_bit, cfg->pon_type, |
| cfg->old_state); |
| |
| 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 a 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->dev, "Unable to send input event, rc=%d\n", rc); |
| |
| 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->dev, "Unable to send input event, rc=%d\n", rc); |
| |
| 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->dev, "Unable to send input event, rc=%d\n", rc); |
| |
| 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->dev, "Register read failed, addr=0x%04X, rc=%d\n", |
| addr, rc); |
| else |
| dev_emerg(pon->dev, "reg@0x%04X: 0x%02X\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) |
| { |
| struct qpnp_pon *pon = |
| container_of(work, struct qpnp_pon, bark_work.work); |
| uint pon_rt_sts = 0; |
| struct qpnp_pon_config *cfg; |
| int rc; |
| |
| cfg = qpnp_get_cfg(pon, PON_RESIN); |
| if (!cfg) { |
| dev_err(pon->dev, "Invalid config pointer\n"); |
| 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) |
| return; |
| |
| /* Wait for bark RT status update delay */ |
| msleep(100); |
| |
| /* Read the bark RT status */ |
| rc = qpnp_pon_read(pon, QPNP_PON_RT_STS(pon), &pon_rt_sts); |
| if (rc) |
| 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) |
| return; |
| |
| /* Re-arm the work */ |
| schedule_delayed_work(&pon->bark_work, QPNP_KEY_STATUS_DELAY); |
| } |
| } |
| |
| static irqreturn_t qpnp_resin_bark_irq(int irq, void *_pon) |
| { |
| struct qpnp_pon *pon = _pon; |
| struct qpnp_pon_config *cfg; |
| int rc; |
| |
| /* Disable the bark interrupt */ |
| disable_irq_nosync(irq); |
| |
| cfg = qpnp_get_cfg(pon, PON_RESIN); |
| if (!cfg) { |
| dev_err(pon->dev, "Invalid config pointer\n"); |
| return IRQ_HANDLED; |
| } |
| |
| /* Disable reset */ |
| rc = qpnp_pon_masked_write(pon, cfg->s2_cntl2_addr, |
| QPNP_PON_S2_CNTL_EN, 0); |
| if (rc) |
| return IRQ_HANDLED; |
| |
| /* 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); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int qpnp_config_pull(struct qpnp_pon *pon, struct qpnp_pon_config *cfg) |
| { |
| 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; |
| } |
| |
| return qpnp_pon_masked_write(pon, QPNP_PON_PULL_CTL(pon), pull_bit, |
| cfg->pull_up ? pull_bit : 0); |
| } |
| |
| static int qpnp_config_reset(struct qpnp_pon *pon, struct qpnp_pon_config *cfg) |
| { |
| u16 s1_timer_addr, s2_timer_addr; |
| int rc; |
| u8 i; |
| |
| 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) |
| 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) |
| 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) |
| return rc; |
| |
| rc = qpnp_pon_masked_write(pon, cfg->s2_cntl_addr, |
| QPNP_PON_S2_CNTL_TYPE_MASK, (u8)cfg->s2_type); |
| if (rc) |
| return rc; |
| |
| /* Enable S2 reset */ |
| return qpnp_pon_masked_write(pon, cfg->s2_cntl2_addr, |
| QPNP_PON_S2_CNTL_EN, QPNP_PON_S2_CNTL_EN); |
| } |
| |
| static int |
| qpnp_pon_request_irqs(struct qpnp_pon *pon, struct qpnp_pon_config *cfg) |
| { |
| int rc; |
| |
| switch (cfg->pon_type) { |
| case PON_KPDPWR: |
| rc = devm_request_irq(pon->dev, cfg->state_irq, qpnp_kpdpwr_irq, |
| IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, |
| "pon_kpdpwr_status", pon); |
| if (rc < 0) { |
| dev_err(pon->dev, "IRQ %d request failed, rc=%d\n", |
| cfg->state_irq, rc); |
| return rc; |
| } |
| |
| if (cfg->use_bark) { |
| rc = devm_request_irq(pon->dev, cfg->bark_irq, |
| qpnp_kpdpwr_bark_irq, |
| IRQF_TRIGGER_RISING, |
| "pon_kpdpwr_bark", pon); |
| if (rc < 0) { |
| dev_err(pon->dev, "IRQ %d request failed, rc=%d\n", |
| cfg->bark_irq, rc); |
| return rc; |
| } |
| } |
| break; |
| case PON_RESIN: |
| rc = devm_request_irq(pon->dev, cfg->state_irq, qpnp_resin_irq, |
| IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, |
| "pon_resin_status", pon); |
| if (rc < 0) { |
| dev_err(pon->dev, "IRQ %d request failed, rc=%d\n", |
| cfg->state_irq, rc); |
| return rc; |
| } |
| |
| if (cfg->use_bark) { |
| rc = devm_request_irq(pon->dev, cfg->bark_irq, |
| qpnp_resin_bark_irq, |
| IRQF_TRIGGER_RISING, |
| "pon_resin_bark", pon); |
| if (rc < 0) { |
| dev_err(pon->dev, "IRQ %d request failed, rc=%d\n", |
| cfg->bark_irq, rc); |
| return rc; |
| } |
| } |
| break; |
| case PON_CBLPWR: |
| rc = devm_request_irq(pon->dev, cfg->state_irq, qpnp_cblpwr_irq, |
| IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, |
| "pon_cblpwr_status", pon); |
| if (rc < 0) { |
| dev_err(pon->dev, "IRQ %d request failed, rc=%d\n", |
| cfg->state_irq, rc); |
| return rc; |
| } |
| break; |
| case PON_KPDPWR_RESIN: |
| if (cfg->use_bark) { |
| rc = devm_request_irq(pon->dev, cfg->bark_irq, |
| qpnp_kpdpwr_resin_bark_irq, |
| IRQF_TRIGGER_RISING, |
| "pon_kpdpwr_resin_bark", pon); |
| if (rc < 0) { |
| dev_err(pon->dev, "IRQ %d request failed, rc=%d\n", |
| cfg->bark_irq, rc); |
| 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 0; |
| } |
| |
| static int |
| qpnp_pon_config_input(struct qpnp_pon *pon, struct qpnp_pon_config *cfg) |
| { |
| if (!pon->pon_input) { |
| pon->pon_input = devm_input_allocate_device(pon->dev); |
| if (!pon->pon_input) |
| 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_kpdpwr_init(struct qpnp_pon *pon, |
| struct platform_device *pdev, |
| struct qpnp_pon_config *cfg, |
| struct device_node *node) |
| { |
| int rc; |
| |
| cfg->state_irq = platform_get_irq_byname(pdev, "kpdpwr"); |
| if (cfg->state_irq < 0) { |
| dev_err(pon->dev, "Unable to get kpdpwr irq, rc=%d\n", |
| cfg->state_irq); |
| return cfg->state_irq; |
| } |
| |
| rc = of_property_read_u32(node, "qcom,support-reset", |
| &cfg->support_reset); |
| if (rc) { |
| if (rc != -EINVAL) { |
| dev_err(pon->dev, "Unable to read qcom,support-reset, rc=%d\n", |
| rc); |
| return rc; |
| } |
| } else { |
| cfg->config_reset = true; |
| } |
| |
| cfg->use_bark = of_property_read_bool(node, "qcom,use-bark"); |
| if (cfg->use_bark) { |
| cfg->bark_irq = platform_get_irq_byname(pdev, "kpdpwr-bark"); |
| if (cfg->bark_irq < 0) { |
| dev_err(pon->dev, "Unable to get kpdpwr-bark irq, rc=%d\n", |
| cfg->bark_irq); |
| return cfg->bark_irq; |
| } |
| } |
| |
| 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); |
| } |
| |
| return 0; |
| } |
| |
| static int qpnp_pon_config_resin_init(struct qpnp_pon *pon, |
| struct platform_device *pdev, |
| struct qpnp_pon_config *cfg, |
| struct device_node *node) |
| { |
| unsigned int pmic_type, revid_rev4; |
| int rc; |
| |
| cfg->state_irq = platform_get_irq_byname(pdev, "resin"); |
| if (cfg->state_irq < 0) { |
| dev_err(pon->dev, "Unable to get resin irq, rc=%d\n", |
| cfg->state_irq); |
| return cfg->state_irq; |
| } |
| |
| rc = of_property_read_u32(node, "qcom,support-reset", |
| &cfg->support_reset); |
| if (rc) { |
| if (rc != -EINVAL) { |
| dev_err(pon->dev, "Unable to read qcom,support-reset, rc=%d\n", |
| rc); |
| return rc; |
| } |
| } else { |
| cfg->config_reset = true; |
| } |
| |
| cfg->use_bark = of_property_read_bool(node, "qcom,use-bark"); |
| |
| rc = qpnp_pon_read(pon, PMIC_VERSION_REG, &pmic_type); |
| if (rc) |
| return rc; |
| |
| if (pmic_type == PMIC_VER_8941) { |
| rc = qpnp_pon_read(pon, PMIC_VERSION_REV4_REG, &revid_rev4); |
| if (rc) |
| 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(pdev, "resin-bark"); |
| if (cfg->bark_irq < 0) { |
| dev_err(pon->dev, "Unable to get resin-bark irq, rc=%d\n", |
| cfg->bark_irq); |
| 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); |
| } |
| |
| return 0; |
| } |
| |
| static int qpnp_pon_config_cblpwr_init(struct qpnp_pon *pon, |
| struct platform_device *pdev, |
| struct qpnp_pon_config *cfg, |
| struct device_node *node) |
| { |
| cfg->state_irq = platform_get_irq_byname(pdev, "cblpwr"); |
| if (cfg->state_irq < 0) { |
| dev_err(pon->dev, "Unable to get cblpwr irq, rc=%d\n", |
| cfg->state_irq); |
| return cfg->state_irq; |
| } |
| |
| return 0; |
| } |
| |
| static int qpnp_pon_config_kpdpwr_resin_init(struct qpnp_pon *pon, |
| struct platform_device *pdev, |
| struct qpnp_pon_config *cfg, |
| struct device_node *node) |
| { |
| int rc; |
| |
| rc = of_property_read_u32(node, "qcom,support-reset", |
| &cfg->support_reset); |
| if (rc) { |
| if (rc != -EINVAL) { |
| dev_err(pon->dev, "Unable to read qcom,support-reset, rc=%d\n", |
| rc); |
| return rc; |
| } |
| } else { |
| cfg->config_reset = true; |
| } |
| |
| cfg->use_bark = of_property_read_bool(node, "qcom,use-bark"); |
| if (cfg->use_bark) { |
| cfg->bark_irq = platform_get_irq_byname(pdev, |
| "kpdpwr-resin-bark"); |
| if (cfg->bark_irq < 0) { |
| dev_err(pon->dev, "Unable to get kpdpwr-resin-bark irq, rc=%d\n", |
| cfg->bark_irq); |
| 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); |
| } |
| |
| return 0; |
| } |
| |
| static int qpnp_pon_config_parse_reset_info(struct qpnp_pon *pon, |
| struct qpnp_pon_config *cfg, |
| struct device_node *node) |
| { |
| int rc; |
| |
| if (!cfg->support_reset) |
| return 0; |
| |
| /* |
| * Get the reset parameters (bark debounce time and |
| * reset debounce time) for the reset line. |
| */ |
| rc = of_property_read_u32(node, "qcom,s1-timer", &cfg->s1_timer); |
| if (rc) { |
| dev_err(pon->dev, "Unable to read s1-timer, rc=%d\n", rc); |
| return rc; |
| } |
| if (cfg->s1_timer > QPNP_PON_S1_TIMER_MAX) { |
| dev_err(pon->dev, "Invalid S1 debounce time %u\n", |
| cfg->s1_timer); |
| return -EINVAL; |
| } |
| |
| rc = of_property_read_u32(node, "qcom,s2-timer", &cfg->s2_timer); |
| if (rc) { |
| dev_err(pon->dev, "Unable to read s2-timer, rc=%d\n", rc); |
| return rc; |
| } |
| if (cfg->s2_timer > QPNP_PON_S2_TIMER_MAX) { |
| dev_err(pon->dev, "Invalid S2 debounce time %u\n", |
| cfg->s2_timer); |
| return -EINVAL; |
| } |
| |
| rc = of_property_read_u32(node, "qcom,s2-type", &cfg->s2_type); |
| if (rc) { |
| dev_err(pon->dev, "Unable to read s2-type, rc=%d\n", rc); |
| return rc; |
| } |
| if (cfg->s2_type > QPNP_PON_RESET_TYPE_MAX) { |
| dev_err(pon->dev, "Invalid reset type specified %u\n", |
| cfg->s2_type); |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int qpnp_pon_config_init(struct qpnp_pon *pon, |
| struct platform_device *pdev) |
| { |
| int rc = 0, i = 0, pmic_wd_bark_irq; |
| struct device_node *cfg_node = NULL; |
| struct qpnp_pon_config *cfg; |
| |
| if (pon->num_pon_config) { |
| pon->pon_cfg = devm_kcalloc(pon->dev, pon->num_pon_config, |
| sizeof(*pon->pon_cfg), GFP_KERNEL); |
| if (!pon->pon_cfg) |
| return -ENOMEM; |
| } |
| |
| /* Iterate through the list of pon configs */ |
| for_each_available_child_of_node(pon->dev->of_node, cfg_node) { |
| if (!of_find_property(cfg_node, "qcom,pon-type", NULL)) |
| continue; |
| |
| cfg = &pon->pon_cfg[i++]; |
| |
| rc = of_property_read_u32(cfg_node, "qcom,pon-type", |
| &cfg->pon_type); |
| if (rc) { |
| dev_err(pon->dev, "PON type not specified\n"); |
| return rc; |
| } |
| |
| switch (cfg->pon_type) { |
| case PON_KPDPWR: |
| rc = qpnp_pon_config_kpdpwr_init(pon, pdev, cfg, |
| cfg_node); |
| if (rc) |
| return rc; |
| break; |
| case PON_RESIN: |
| rc = qpnp_pon_config_resin_init(pon, pdev, cfg, |
| cfg_node); |
| if (rc) |
| return rc; |
| break; |
| case PON_CBLPWR: |
| rc = qpnp_pon_config_cblpwr_init(pon, pdev, cfg, |
| cfg_node); |
| if (rc) |
| return rc; |
| break; |
| case PON_KPDPWR_RESIN: |
| rc = qpnp_pon_config_kpdpwr_resin_init(pon, pdev, cfg, |
| cfg_node); |
| if (rc) |
| return rc; |
| break; |
| default: |
| dev_err(pon->dev, "PON RESET %u not supported\n", |
| cfg->pon_type); |
| return -EINVAL; |
| } |
| |
| rc = qpnp_pon_config_parse_reset_info(pon, cfg, cfg_node); |
| if (rc) |
| return rc; |
| |
| /* |
| * Get the standard key parameters. This might not be |
| * specified if there is no key mapping on the reset line. |
| */ |
| of_property_read_u32(cfg_node, "linux,code", &cfg->key_code); |
| |
| /* Register key configuration */ |
| if (cfg->key_code) { |
| rc = qpnp_pon_config_input(pon, cfg); |
| if (rc < 0) |
| return rc; |
| } |
| |
| /* Get the pull-up configuration */ |
| cfg->pull_up = of_property_read_bool(cfg_node, "qcom,pull-up"); |
| } |
| |
| pmic_wd_bark_irq = platform_get_irq_byname(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->dev, pmic_wd_bark_irq, |
| qpnp_pmic_wd_bark_irq, |
| IRQF_TRIGGER_RISING, |
| "qpnp_pmic_wd_bark", pon); |
| if (rc < 0) { |
| dev_err(pon->dev, "Can't request %d IRQ, rc=%d\n", |
| pmic_wd_bark_irq, rc); |
| return rc; |
| } |
| } |
| |
| /* Register the input device */ |
| if (pon->pon_input) { |
| rc = input_register_device(pon->pon_input); |
| if (rc) { |
| dev_err(pon->dev, "Can't register pon key: %d\n", rc); |
| return rc; |
| } |
| } |
| |
| for (i = 0; i < pon->num_pon_config; i++) { |
| cfg = &pon->pon_cfg[i]; |
| |
| rc = qpnp_config_pull(pon, cfg); |
| if (rc) |
| return rc; |
| |
| if (cfg->config_reset) { |
| if (cfg->support_reset) { |
| rc = qpnp_config_reset(pon, cfg); |
| if (rc) |
| return rc; |
| } 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) |
| return rc; |
| } |
| } |
| |
| rc = qpnp_pon_request_irqs(pon, cfg); |
| if (rc) |
| return rc; |
| } |
| |
| device_init_wakeup(pon->dev, true); |
| |
| return 0; |
| } |
| |
| static int pon_spare_regulator_enable(struct regulator_dev *rdev) |
| { |
| struct pon_regulator *pon_reg = rdev_get_drvdata(rdev); |
| u8 value; |
| int rc; |
| |
| 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) |
| return rc; |
| |
| pon_reg->enabled = true; |
| |
| return 0; |
| } |
| |
| static int pon_spare_regulator_disable(struct regulator_dev *rdev) |
| { |
| struct pon_regulator *pon_reg = rdev_get_drvdata(rdev); |
| u8 mask; |
| int rc; |
| |
| 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) |
| return rc; |
| |
| pon_reg->enabled = false; |
| |
| return 0; |
| } |
| |
| 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) |
| { |
| struct device *dev = pon->dev; |
| struct regulator_init_data *init_data; |
| struct regulator_config reg_cfg = {}; |
| struct device_node *node; |
| struct pon_regulator *pon_reg; |
| int rc, i; |
| |
| 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; |
| |
| i = 0; |
| 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 -ENOMEM; |
| } |
| |
| if (!init_data->constraints.name) { |
| dev_err(dev, "regulator-name is missing\n"); |
| return -EINVAL; |
| } |
| |
| 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 = devm_regulator_register(dev, &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 0; |
| } |
| |
| static bool smpl_en; |
| |
| static int qpnp_pon_smpl_en_get(char *buf, const struct kernel_param *kp) |
| { |
| bool enabled = false; |
| int rc; |
| |
| rc = qpnp_pon_get_trigger_config(PON_SMPL, &enabled); |
| if (rc < 0) |
| return rc; |
| |
| return scnprintf(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; |
| } |
| |
| return qpnp_pon_trigger_config(PON_SMPL, *(bool *)kp->arg); |
| } |
| |
| 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, 0600); |
| |
| static bool dload_on_uvlo; |
| |
| static int |
| qpnp_pon_uvlo_dload_get(char *buf, const struct kernel_param *kp) |
| { |
| struct qpnp_pon *pon = sys_reset_dev; |
| uint reg; |
| int rc; |
| |
| if (!pon) |
| return -ENODEV; |
| |
| rc = qpnp_pon_read(pon, QPNP_PON_XVDD_RB_SPARE(pon), ®); |
| if (rc) |
| return rc; |
| |
| return scnprintf(buf, PAGE_SIZE, "%d", |
| !!(QPNP_PON_UVLO_DLOAD_EN & reg)); |
| } |
| |
| static int |
| qpnp_pon_uvlo_dload_set(const char *val, const struct kernel_param *kp) |
| { |
| struct qpnp_pon *pon = sys_reset_dev; |
| uint reg; |
| int rc; |
| |
| if (!pon) |
| return -ENODEV; |
| |
| rc = param_set_bool(val, kp); |
| if (rc) { |
| pr_err("Unable to set dload_on_uvlo: %d\n", rc); |
| return rc; |
| } |
| |
| reg = *(bool *)kp->arg ? QPNP_PON_UVLO_DLOAD_EN : 0; |
| |
| return qpnp_pon_masked_write(pon, QPNP_PON_XVDD_RB_SPARE(pon), |
| QPNP_PON_UVLO_DLOAD_EN, reg); |
| } |
| |
| static struct kernel_param_ops dload_on_uvlo_ops = { |
| .set = qpnp_pon_uvlo_dload_set, |
| .get = qpnp_pon_uvlo_dload_get, |
| }; |
| |
| module_param_cb(dload_on_uvlo, &dload_on_uvlo_ops, &dload_on_uvlo, 0600); |
| |
| #if defined(CONFIG_DEBUG_FS) |
| |
| static int qpnp_pon_debugfs_uvlo_get(void *data, u64 *val) |
| { |
| struct qpnp_pon *pon = data; |
| |
| *val = pon->uvlo; |
| |
| return 0; |
| } |
| |
| static int qpnp_pon_debugfs_uvlo_set(void *data, u64 val) |
| { |
| struct qpnp_pon *pon = data; |
| |
| if (pon->pon_trigger_reason == PON_SMPL || |
| pon->pon_power_off_reason == QPNP_POFF_REASON_UVLO) |
| panic("UVLO occurred"); |
| pon->uvlo = val; |
| |
| return 0; |
| } |
| DEFINE_DEBUGFS_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 qpnp_pon *pon) |
| { |
| struct dentry *ent; |
| |
| pon->debugfs = debugfs_create_dir(dev_name(pon->dev), NULL); |
| if (!pon->debugfs) { |
| dev_err(pon->dev, "Unable to create debugfs directory\n"); |
| } else { |
| ent = debugfs_create_file_unsafe("uvlo_panic", 0644, |
| pon->debugfs, pon, &qpnp_pon_debugfs_uvlo_fops); |
| if (!ent) |
| dev_err(pon->dev, "Unable to create uvlo_panic debugfs file\n"); |
| } |
| } |
| |
| static void qpnp_pon_debugfs_remove(struct qpnp_pon *pon) |
| { |
| debugfs_remove_recursive(pon->debugfs); |
| } |
| |
| #else |
| |
| static void qpnp_pon_debugfs_init(struct qpnp_pon *pon) |
| {} |
| |
| static void qpnp_pon_debugfs_remove(struct qpnp_pon *pon) |
| {} |
| #endif |
| |
| static int qpnp_pon_read_gen2_pon_off_reason(struct qpnp_pon *pon, u16 *reason, |
| int *reason_index_offset) |
| { |
| u8 buf[2]; |
| unsigned int reg, data; |
| int rc; |
| |
| rc = qpnp_pon_read(pon, QPNP_PON_OFF_REASON(pon), ®); |
| if (rc) |
| return rc; |
| |
| if (reg & QPNP_GEN2_POFF_SEQ) { |
| rc = qpnp_pon_read(pon, QPNP_POFF_REASON1(pon), &data); |
| if (rc) |
| return rc; |
| *reason = data; |
| *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->dev, "Register read failed, addr=0x%04X, rc=%d\n", |
| QPNP_FAULT_REASON1(pon), rc); |
| return rc; |
| } |
| *reason = buf[0] | ((u16)buf[1] << 8); |
| *reason_index_offset = POFF_REASON_FAULT_OFFSET; |
| } else if (reg & QPNP_GEN2_S3_RESET_SEQ) { |
| rc = qpnp_pon_read(pon, QPNP_S3_RESET_REASON(pon), &data); |
| if (rc) |
| return rc; |
| *reason = data; |
| *reason_index_offset = POFF_REASON_S3_RESET_OFFSET; |
| } |
| |
| return 0; |
| } |
| |
| static int qpnp_pon_configure_s3_reset(struct qpnp_pon *pon) |
| { |
| struct device *dev = pon->dev; |
| const char *src_name; |
| u32 debounce; |
| u8 src_val; |
| int rc; |
| |
| /* Program S3 reset debounce time */ |
| rc = of_property_read_u32(dev->of_node, "qcom,s3-debounce", &debounce); |
| if (!rc) { |
| if (debounce > QPNP_PON_S3_TIMER_SECS_MAX) { |
| dev_err(dev, "S3 debounce time %u greater than max supported %u\n", |
| debounce, QPNP_PON_S3_TIMER_SECS_MAX); |
| return -EINVAL; |
| } |
| |
| if (debounce != 0) |
| debounce = ilog2(debounce); |
| |
| /* S3 debounce is a SEC_ACCESS register */ |
| rc = qpnp_pon_masked_write(pon, QPNP_PON_SEC_ACCESS(pon), |
| 0xFF, QPNP_PON_SEC_UNLOCK); |
| if (rc) |
| return rc; |
| |
| rc = qpnp_pon_masked_write(pon, QPNP_PON_S3_DBC_CTL(pon), |
| QPNP_PON_S3_DBC_DELAY_MASK, debounce); |
| if (rc) |
| return rc; |
| } |
| |
| /* Program S3 reset source */ |
| rc = of_property_read_string(dev->of_node, "qcom,s3-src", &src_name); |
| if (!rc) { |
| if (!strcmp(src_name, "kpdpwr")) { |
| src_val = QPNP_PON_S3_SRC_KPDPWR; |
| } else if (!strcmp(src_name, "resin")) { |
| src_val = QPNP_PON_S3_SRC_RESIN; |
| } else if (!strcmp(src_name, "kpdpwr-or-resin")) { |
| src_val = QPNP_PON_S3_SRC_KPDPWR_OR_RESIN; |
| } else if (!strcmp(src_name, "kpdpwr-and-resin")) { |
| src_val = QPNP_PON_S3_SRC_KPDPWR_AND_RESIN; |
| } else { |
| dev_err(dev, "Unknown S3 reset source: %s\n", |
| src_name); |
| return -EINVAL; |
| } |
| |
| /* |
| * S3 source is a write once register. If the register has |
| * been configured by the bootloader then this operation will |
| * not have an effect. |
| */ |
| rc = qpnp_pon_masked_write(pon, QPNP_PON_S3_SRC(pon), |
| QPNP_PON_S3_SRC_MASK, src_val); |
| if (rc) |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| static int qpnp_pon_read_hardware_info(struct qpnp_pon *pon, bool sys_reset) |
| { |
| struct device *dev = pon->dev; |
| unsigned int reg = 0; |
| unsigned int buf[2]; |
| int reason_index_offset = 0; |
| unsigned int pon_sts = 0; |
| u16 poff_sts = 0; |
| int rc, index; |
| |
| /* Read PON_PERPH_SUBTYPE register to get PON type */ |
| rc = qpnp_pon_read(pon, QPNP_PON_PERPH_SUBTYPE(pon), ®); |
| if (rc) |
| return rc; |
| pon->subtype = reg; |
| |
| /* Check if it is rev B */ |
| rc = qpnp_pon_read(pon, QPNP_PON_REVISION2(pon), ®); |
| if (rc) |
| return rc; |
| pon->pon_ver = reg; |
| |
| 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(dev, "Invalid PON_PERPH_SUBTYPE 0x%02X\n", |
| pon->subtype); |
| return -EINVAL; |
| } |
| dev_dbg(dev, "pon_subtype=0x%02X, pon_version=0x%02X\n", pon->subtype, |
| pon->pon_ver); |
| |
| rc = qpnp_pon_store_and_clear_warm_reset(pon); |
| if (rc) |
| return rc; |
| |
| /* PON reason */ |
| rc = qpnp_pon_read(pon, QPNP_PON_REASON1(pon), &pon_sts); |
| if (rc) |
| return rc; |
| |
| if (sys_reset) |
| boot_reason = ffs(pon_sts); |
| |
| index = ffs(pon_sts) - 1; |
| cold_boot = sys_reset_dev ? !_qpnp_pon_is_warm_reset(sys_reset_dev) |
| : !_qpnp_pon_is_warm_reset(pon); |
| if (index >= ARRAY_SIZE(qpnp_pon_reason) || index < 0) { |
| dev_info(dev, "PMIC@SID%d Power-on reason: Unknown and '%s' boot\n", |
| to_spmi_device(dev->parent)->usid, |
| cold_boot ? "cold" : "warm"); |
| } else { |
| pon->pon_trigger_reason = index; |
| dev_info(dev, "PMIC@SID%d Power-on reason: %s and '%s' boot\n", |
| to_spmi_device(dev->parent)->usid, |
| qpnp_pon_reason[index], |
| cold_boot ? "cold" : "warm"); |
| } |
| |
| /* POFF reason */ |
| if (!is_pon_gen1(pon) && pon->subtype != PON_1REG) { |
| rc = qpnp_pon_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(dev, "Register read failed, addr=0x%04X, rc=%d\n", |
| QPNP_POFF_REASON1(pon), 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(dev, "PMIC@SID%d: Unknown power-off reason\n", |
| to_spmi_device(dev->parent)->usid); |
| } else { |
| pon->pon_power_off_reason = index; |
| dev_info(dev, "PMIC@SID%d: Power-off reason: %s\n", |
| to_spmi_device(dev->parent)->usid, |
| qpnp_poff_reason[index]); |
| } |
| |
| if ((pon->pon_trigger_reason == PON_SMPL || |
| pon->pon_power_off_reason == QPNP_POFF_REASON_UVLO) && |
| of_property_read_bool(dev->of_node, "qcom,uvlo-panic")) { |
| panic("UVLO occurred"); |
| } |
| |
| return 0; |
| } |
| |
| static int qpnp_pon_parse_power_off_type(struct qpnp_pon *pon, |
| const char *prop, int *val) |
| { |
| u32 type = 0; |
| int rc; |
| |
| rc = of_property_read_u32(pon->dev->of_node, prop, &type); |
| if (rc) { |
| if (rc != -EINVAL) { |
| dev_err(pon->dev, "Unable to read property %s, rc=%d\n", |
| prop, rc); |
| return rc; |
| } |
| *val = -EINVAL; |
| return 0; |
| } |
| |
| if (type >= PON_POWER_OFF_MAX_TYPE) { |
| dev_err(pon->dev, "Invalid property value %s=%u\n", prop, type); |
| return -EINVAL; |
| } |
| |
| *val = type; |
| |
| return 0; |
| } |
| |
| static int qpnp_pon_parse_dt_power_off_config(struct qpnp_pon *pon) |
| { |
| struct device_node *node = pon->dev->of_node; |
| int rc; |
| |
| rc = qpnp_pon_parse_power_off_type(pon, "qcom,warm-reset-poweroff-type", |
| &pon->warm_reset_poff_type); |
| if (rc) |
| return rc; |
| |
| rc = qpnp_pon_parse_power_off_type(pon, "qcom,hard-reset-poweroff-type", |
| &pon->hard_reset_poff_type); |
| if (rc) |
| return rc; |
| |
| rc = qpnp_pon_parse_power_off_type(pon, "qcom,shutdown-poweroff-type", |
| &pon->shutdown_poff_type); |
| if (rc) |
| return rc; |
| |
| rc = qpnp_pon_parse_power_off_type(pon, "qcom,resin-warm-reset-type", |
| &pon->resin_warm_reset_type); |
| if (rc) |
| return rc; |
| |
| rc = qpnp_pon_parse_power_off_type(pon, "qcom,resin-hard-reset-type", |
| &pon->resin_hard_reset_type); |
| if (rc) |
| return rc; |
| |
| rc = qpnp_pon_parse_power_off_type(pon, "qcom,resin-shutdown-type", |
| &pon->resin_shutdown_type); |
| if (rc) |
| return rc; |
| |
| pon->ps_hold_hard_reset_disable = of_property_read_bool(node, |
| "qcom,ps-hold-hard-reset-disable"); |
| pon->ps_hold_shutdown_disable = of_property_read_bool(node, |
| "qcom,ps-hold-shutdown-disable"); |
| pon->resin_hard_reset_disable = of_property_read_bool(node, |
| "qcom,resin-hard-reset-disable"); |
| pon->resin_shutdown_disable = of_property_read_bool(node, |
| "qcom,resin-shutdown-disable"); |
| pon->resin_pon_reset = of_property_read_bool(node, |
| "qcom,resin-pon-reset"); |
| |
| return 0; |
| } |
| |
| static int qpnp_pon_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct device_node *node; |
| struct qpnp_pon *pon; |
| unsigned long flags; |
| u32 base, delay; |
| bool sys_reset; |
| int rc; |
| |
| pon = devm_kzalloc(dev, sizeof(*pon), GFP_KERNEL); |
| if (!pon) |
| return -ENOMEM; |
| pon->dev = dev; |
| |
| pon->regmap = dev_get_regmap(dev->parent, NULL); |
| if (!pon->regmap) { |
| dev_err(dev, "Parent regmap missing\n"); |
| return -ENODEV; |
| } |
| |
| rc = of_property_read_u32(dev->of_node, "reg", &base); |
| if (rc < 0) { |
| dev_err(dev, "reg property missing, rc=%d\n", rc); |
| return rc; |
| } |
| pon->base = base; |
| |
| sys_reset = of_property_read_bool(dev->of_node, "qcom,system-reset"); |
| if (sys_reset && sys_reset_dev) { |
| dev_err(dev, "qcom,system-reset property must only be specified for one PMIC PON device in the system\n"); |
| return -EINVAL; |
| } |
| |
| /* Get the total number of pon configurations and regulators */ |
| for_each_available_child_of_node(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 { |
| dev_err(dev, "Unknown sub-node found %s\n", node->name); |
| return -EINVAL; |
| } |
| } |
| dev_dbg(dev, "PON@SID%d: num_pon_config=%d, num_pon_reg=%d\n", |
| to_spmi_device(dev->parent)->usid, pon->num_pon_config, |
| pon->num_pon_reg); |
| |
| rc = qpnp_pon_read_hardware_info(pon, sys_reset); |
| if (rc) |
| return rc; |
| |
| rc = pon_regulator_init(pon); |
| if (rc) |
| return rc; |
| |
| rc = qpnp_pon_configure_s3_reset(pon); |
| if (rc) |
| return rc; |
| |
| dev_set_drvdata(dev, pon); |
| |
| INIT_DELAYED_WORK(&pon->bark_work, bark_work_func); |
| |
| rc = qpnp_pon_parse_dt_power_off_config(pon); |
| if (rc) |
| return rc; |
| |
| rc = of_property_read_u32(dev->of_node, "qcom,pon-dbc-delay", &delay); |
| if (!rc) { |
| rc = qpnp_pon_set_dbc(pon, delay); |
| if (rc) |
| return rc; |
| } else if (rc != -EINVAL) { |
| dev_err(dev, "Unable to read debounce delay, rc=%d\n", rc); |
| return rc; |
| } |
| |
| rc = qpnp_pon_get_dbc(pon, &pon->dbc_time_us); |
| if (rc) |
| return rc; |
| |
| pon->kpdpwr_dbc_enable = of_property_read_bool(dev->of_node, |
| "qcom,kpdpwr-sw-debounce"); |
| |
| pon->store_hard_reset_reason = of_property_read_bool(dev->of_node, |
| "qcom,store-hard-reset-reason"); |
| |
| if (of_property_read_bool(dev->of_node, "qcom,secondary-pon-reset")) { |
| if (sys_reset) { |
| dev_err(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; |
| } |
| |
| /* Register the PON configurations */ |
| rc = qpnp_pon_config_init(pon, pdev); |
| if (rc) |
| return rc; |
| |
| rc = device_create_file(dev, &dev_attr_debounce_us); |
| if (rc) { |
| dev_err(dev, "sysfs debounce file creation failed, rc=%d\n", |
| rc); |
| return rc; |
| } |
| |
| if (sys_reset) |
| sys_reset_dev = pon; |
| |
| qpnp_pon_debugfs_init(pon); |
| |
| return 0; |
| } |
| |
| static int qpnp_pon_remove(struct platform_device *pdev) |
| { |
| struct qpnp_pon *pon = platform_get_drvdata(pdev); |
| unsigned long flags; |
| |
| device_remove_file(&pdev->dev, &dev_attr_debounce_us); |
| |
| cancel_delayed_work_sync(&pon->bark_work); |
| |
| qpnp_pon_debugfs_remove(pon); |
| 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 qpnp_pon_match_table[] = { |
| { .compatible = "qcom,qpnp-power-on" }, |
| {} |
| }; |
| |
| static struct platform_driver qpnp_pon_driver = { |
| .driver = { |
| .name = "qcom,qpnp-power-on", |
| .of_match_table = qpnp_pon_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"); |