Reset types defines behavior of the PMIC during a reset or shutdown
event (e.g. hard reset event). The type of reset event is determined by
configuration register PON_PBS_PS_HOLD_SW_CTL in the PON module.
PON_PBS_PS_HOLD_RESET_CTL2 register contains the field S2_RESET_EN that
enables reset. To configure PON_PBS_PS_HOLD_SW_CTL, the register
PON_PBS_PS_HOLD_RESET_CTL2.S2_RESET_EN needs to be disabled before
re-enabling it again.

Configure PON_PBS_PS_HOLD_SW_CTL and PON_PBS_PS_HOLD_RESET_CTL2
registers before dropping PS_HOLD.

Signed-off-by: Sumit Singh Dhami <[email protected]>
---
 drivers/sysreset/sysreset_qcom-pshold.c | 122 +++++++++++++++++++++++-
 1 file changed, 121 insertions(+), 1 deletion(-)

diff --git a/drivers/sysreset/sysreset_qcom-pshold.c 
b/drivers/sysreset/sysreset_qcom-pshold.c
index 45290478536..c7251551c04 100644
--- a/drivers/sysreset/sysreset_qcom-pshold.c
+++ b/drivers/sysreset/sysreset_qcom-pshold.c
@@ -13,15 +13,92 @@
 #include <sysreset.h>
 #include <asm/io.h>
 #include <linux/delay.h>
+#include <linux/errno.h>
+#include <power/pmic.h>
+
+#define PON_PBS_RESET_TYPE_SHUTDOWN            0x04
+#define PON_PBS_RESET_TYPE_HARD                        0x07
+
+#define PON_PBS_RESET_CTL2_S2_RESET_EN_BMSK            0x80
+
+struct qcom_pshold_pon_data {
+       const char *pon_compatible;
+       u32 pbs_base;
+       u8 sw_ctl_offset;
+       u8 reset_ctl2_offset;
+};
+
+static const struct qcom_pshold_pon_data qcom_pshold_pon_data_table[] = {
+       {
+               .pon_compatible = "qcom,pmk8350-pon",
+               .pbs_base = 0x800,
+               .sw_ctl_offset = 0x52,
+               .reset_ctl2_offset = 0x53,
+       },
+};
 
 struct qcom_pshold_priv {
        phys_addr_t base;
+       bool have_pon;
+       struct udevice *pmic;
+       const struct qcom_pshold_pon_data *pon_data;
 };
 
+static int qcom_pshold_pon_cfg(struct qcom_pshold_priv *priv, u8 reset_type)
+{
+       u32 sw_ctl = priv->pon_data->pbs_base + priv->pon_data->sw_ctl_offset;
+       u32 reset_ctl2 = priv->pon_data->pbs_base +
+                         priv->pon_data->reset_ctl2_offset;
+       int ret;
+
+       ret = pmic_reg_write(priv->pmic, reset_ctl2, 0x0);
+       if (ret)
+               return ret;
+
+       udelay(100);
+
+       ret = pmic_reg_write(priv->pmic, sw_ctl, reset_type);
+       if (ret)
+               return ret;
+
+       udelay(300);
+
+       ret = pmic_reg_write(priv->pmic, reset_ctl2,
+                            PON_PBS_RESET_CTL2_S2_RESET_EN_BMSK);
+       if (ret)
+               return ret;
+
+       udelay(100);
+
+       return 0;
+}
+
 static int qcom_pshold_request(struct udevice *dev, enum sysreset_t type)
 {
        struct qcom_pshold_priv *priv = dev_get_priv(dev);
 
+       if (priv->have_pon) {
+               u8 reset_type;
+               int ret;
+
+               switch (type) {
+               case SYSRESET_COLD:
+               case SYSRESET_POWER:
+                       reset_type = PON_PBS_RESET_TYPE_HARD;
+                       break;
+               case SYSRESET_POWER_OFF:
+                       reset_type = PON_PBS_RESET_TYPE_SHUTDOWN;
+                       break;
+               default:
+                       return -EPROTONOSUPPORT;
+               }
+
+               ret = qcom_pshold_pon_cfg(priv, reset_type);
+               if (ret)
+                       debug("%s: failed to configure PON reset type: %d\n",
+                             dev->name, ret);
+       }
+
        writel(0, priv->base);
        mdelay(10000);
 
@@ -32,12 +109,55 @@ static struct sysreset_ops qcom_pshold_ops = {
        .request = qcom_pshold_request,
 };
 
+static ofnode qcom_pshold_find_pon(struct qcom_pshold_priv *priv)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(qcom_pshold_pon_data_table); i++) {
+               ofnode node;
+
+               node = ofnode_by_compatible(ofnode_null(),
+                                           
qcom_pshold_pon_data_table[i].pon_compatible);
+               if (ofnode_valid(node)) {
+                       priv->pon_data = &qcom_pshold_pon_data_table[i];
+                       return node;
+               }
+       }
+
+       return ofnode_null();
+}
+
 static int qcom_pshold_probe(struct udevice *dev)
 {
        struct qcom_pshold_priv *priv = dev_get_priv(dev);
+       ofnode pon_node, pmic_node;
+       int ret;
+
+       priv->have_pon = false;
 
        priv->base = dev_read_addr(dev);
-       return priv->base == FDT_ADDR_T_NONE ? -EINVAL : 0;
+       if (priv->base == FDT_ADDR_T_NONE)
+               return -EINVAL;
+
+       pon_node = qcom_pshold_find_pon(priv);
+       if (!ofnode_valid(pon_node))
+               return 0;
+
+       pmic_node = ofnode_get_parent(pon_node);
+       if (!ofnode_valid(pmic_node)) {
+               debug("%s: failed to get PON's parent PMIC node\n", dev->name);
+               return 0;
+       }
+
+       ret = uclass_get_device_by_ofnode(UCLASS_PMIC, pmic_node, &priv->pmic);
+       if (ret) {
+               debug("%s: failed to get PMIC device: %d\n", dev->name, ret);
+               return 0;
+       }
+
+       priv->have_pon = true;
+
+       return 0;
 }
 
 static const struct udevice_id qcom_pshold_ids[] = {
-- 
2.55.0

Reply via email to