soc: qcom: add QCOM PBS driver
authorAnjelique Melendez <quic_amelende@quicinc.com>
Thu, 1 Feb 2024 20:44:24 +0000 (12:44 -0800)
committerBjorn Andersson <andersson@kernel.org>
Thu, 1 Feb 2024 22:26:55 +0000 (16:26 -0600)
Add the Qualcomm PBS (Programmable Boot Sequencer) driver. The QCOM PBS
driver supports configuring software PBS trigger events through PBS RAM
on Qualcomm Technologies, Inc (QTI) PMICs.

Signed-off-by: Anjelique Melendez <quic_amelende@quicinc.com>
Link: https://lore.kernel.org/r/20240201204421.16992-6-quic_amelende@quicinc.com
Signed-off-by: Bjorn Andersson <andersson@kernel.org>
drivers/soc/qcom/Kconfig
drivers/soc/qcom/Makefile
drivers/soc/qcom/qcom-pbs.c [new file with mode: 0644]
include/linux/soc/qcom/qcom-pbs.h [new file with mode: 0644]

index c6ca4de425863dd42c6482104ce13f431027c32d..5af33b0e3470ec4e9d5b3242fb9755e94ecbd2df 100644 (file)
@@ -268,4 +268,13 @@ config QCOM_INLINE_CRYPTO_ENGINE
        tristate
        select QCOM_SCM
 
+config QCOM_PBS
+       tristate "PBS trigger support for Qualcomm Technologies, Inc. PMICS"
+       depends on SPMI
+       help
+         This driver supports configuring software programmable boot sequencer (PBS)
+         trigger event through PBS RAM on Qualcomm Technologies, Inc. PMICs.
+         This module provides the APIs to the client drivers that wants to send the
+         PBS trigger event to the PBS RAM.
+
 endmenu
index 05b3d54e8dc93992d3bbce0e1eca0d7fa964cd69..0a419b458fb2c4d0737ee0f1e4cb6b61cb9a50ec 100644 (file)
@@ -34,3 +34,4 @@ obj-$(CONFIG_QCOM_KRYO_L2_ACCESSORS) +=       kryo-l2-accessors.o
 obj-$(CONFIG_QCOM_ICC_BWMON)   += icc-bwmon.o
 qcom_ice-objs                  += ice.o
 obj-$(CONFIG_QCOM_INLINE_CRYPTO_ENGINE)        += qcom_ice.o
+obj-$(CONFIG_QCOM_PBS) +=      qcom-pbs.o
diff --git a/drivers/soc/qcom/qcom-pbs.c b/drivers/soc/qcom/qcom-pbs.c
new file mode 100644 (file)
index 0000000..6af49b5
--- /dev/null
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/regmap.h>
+#include <linux/spmi.h>
+#include <linux/soc/qcom/qcom-pbs.h>
+
+#define PBS_CLIENT_TRIG_CTL            0x42
+#define PBS_CLIENT_SW_TRIG_BIT         BIT(7)
+#define PBS_CLIENT_SCRATCH1            0x50
+#define PBS_CLIENT_SCRATCH2            0x51
+#define PBS_CLIENT_SCRATCH2_ERROR      0xFF
+
+#define RETRIES                                2000
+#define DELAY                          1100
+
+struct pbs_dev {
+       struct device           *dev;
+       struct regmap           *regmap;
+       struct mutex            lock;
+       struct device_link      *link;
+
+       u32                     base;
+};
+
+static int qcom_pbs_wait_for_ack(struct pbs_dev *pbs, u8 bit_pos)
+{
+       unsigned int val;
+       int ret;
+
+       ret = regmap_read_poll_timeout(pbs->regmap,  pbs->base + PBS_CLIENT_SCRATCH2,
+                                      val, val & BIT(bit_pos), DELAY, DELAY * RETRIES);
+
+       if (ret < 0) {
+               dev_err(pbs->dev, "Timeout for PBS ACK/NACK for bit %u\n", bit_pos);
+               return -ETIMEDOUT;
+       }
+
+       if (val == PBS_CLIENT_SCRATCH2_ERROR) {
+               ret = regmap_write(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, 0);
+               dev_err(pbs->dev, "NACK from PBS for bit %u\n", bit_pos);
+               return -EINVAL;
+       }
+
+       dev_dbg(pbs->dev, "PBS sequence for bit %u executed!\n", bit_pos);
+       return 0;
+}
+
+/**
+ * qcom_pbs_trigger_event() - Trigger the PBS RAM sequence
+ * @pbs: Pointer to PBS device
+ * @bitmap: bitmap
+ *
+ * This function is used to trigger the PBS RAM sequence to be
+ * executed by the client driver.
+ *
+ * The PBS trigger sequence involves
+ * 1. setting the PBS sequence bit in PBS_CLIENT_SCRATCH1
+ * 2. Initiating the SW PBS trigger
+ * 3. Checking the equivalent bit in PBS_CLIENT_SCRATCH2 for the
+ *    completion of the sequence.
+ * 4. If PBS_CLIENT_SCRATCH2 == 0xFF, the PBS sequence failed to execute
+ *
+ * Return: 0 on success, < 0 on failure
+ */
+int qcom_pbs_trigger_event(struct pbs_dev *pbs, u8 bitmap)
+{
+       unsigned int val;
+       u16 bit_pos;
+       int ret;
+
+       if (WARN_ON(!bitmap))
+               return -EINVAL;
+
+       if (IS_ERR_OR_NULL(pbs))
+               return -EINVAL;
+
+       mutex_lock(&pbs->lock);
+       ret = regmap_read(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, &val);
+       if (ret < 0)
+               goto out;
+
+       if (val == PBS_CLIENT_SCRATCH2_ERROR) {
+               /* PBS error - clear SCRATCH2 register */
+               ret = regmap_write(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, 0);
+               if (ret < 0)
+                       goto out;
+       }
+
+       for (bit_pos = 0; bit_pos < 8; bit_pos++) {
+               if (!(bitmap & BIT(bit_pos)))
+                       continue;
+
+               /* Clear the PBS sequence bit position */
+               ret = regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2,
+                                        BIT(bit_pos), 0);
+               if (ret < 0)
+                       goto out_clear_scratch1;
+
+               /* Set the PBS sequence bit position */
+               ret = regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH1,
+                                        BIT(bit_pos), BIT(bit_pos));
+               if (ret < 0)
+                       goto out_clear_scratch1;
+
+               /* Initiate the SW trigger */
+               ret = regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_TRIG_CTL,
+                                        PBS_CLIENT_SW_TRIG_BIT, PBS_CLIENT_SW_TRIG_BIT);
+               if (ret < 0)
+                       goto out_clear_scratch1;
+
+               ret = qcom_pbs_wait_for_ack(pbs, bit_pos);
+               if (ret < 0)
+                       goto out_clear_scratch1;
+
+               /* Clear the PBS sequence bit position */
+               regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH1, BIT(bit_pos), 0);
+               regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH2, BIT(bit_pos), 0);
+       }
+
+out_clear_scratch1:
+       /* Clear all the requested bitmap */
+       ret = regmap_update_bits(pbs->regmap, pbs->base + PBS_CLIENT_SCRATCH1, bitmap, 0);
+
+out:
+       mutex_unlock(&pbs->lock);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(qcom_pbs_trigger_event);
+
+/**
+ * get_pbs_client_device() - Get the PBS device used by client
+ * @dev: Client device
+ *
+ * This function is used to get the PBS device that is being
+ * used by the client.
+ *
+ * Return: pbs_dev on success, ERR_PTR on failure
+ */
+struct pbs_dev *get_pbs_client_device(struct device *dev)
+{
+       struct device_node *pbs_dev_node;
+       struct platform_device *pdev;
+       struct pbs_dev *pbs;
+
+       pbs_dev_node = of_parse_phandle(dev->of_node, "qcom,pbs", 0);
+       if (!pbs_dev_node) {
+               dev_err(dev, "Missing qcom,pbs property\n");
+               return ERR_PTR(-ENODEV);
+       }
+
+       pdev = of_find_device_by_node(pbs_dev_node);
+       if (!pdev) {
+               dev_err(dev, "Unable to find PBS dev_node\n");
+               pbs = ERR_PTR(-EPROBE_DEFER);
+               goto out;
+       }
+
+       pbs = platform_get_drvdata(pdev);
+       if (!pbs) {
+               dev_err(dev, "Cannot get pbs instance from %s\n", dev_name(&pdev->dev));
+               platform_device_put(pdev);
+               pbs = ERR_PTR(-EPROBE_DEFER);
+               goto out;
+       }
+
+       pbs->link = device_link_add(dev, &pdev->dev, DL_FLAG_AUTOREMOVE_SUPPLIER);
+       if (!pbs->link) {
+               dev_err(&pdev->dev, "Failed to create device link to consumer %s\n", dev_name(dev));
+               platform_device_put(pdev);
+               pbs = ERR_PTR(-EINVAL);
+               goto out;
+       }
+
+out:
+       of_node_put(pbs_dev_node);
+       return pbs;
+}
+EXPORT_SYMBOL_GPL(get_pbs_client_device);
+
+static int qcom_pbs_probe(struct platform_device *pdev)
+{
+       struct pbs_dev *pbs;
+       u32 val;
+       int ret;
+
+       pbs = devm_kzalloc(&pdev->dev, sizeof(*pbs), GFP_KERNEL);
+       if (!pbs)
+               return -ENOMEM;
+
+       pbs->dev = &pdev->dev;
+       pbs->regmap = dev_get_regmap(pbs->dev->parent, NULL);
+       if (!pbs->regmap) {
+               dev_err(pbs->dev, "Couldn't get parent's regmap\n");
+               return -EINVAL;
+       }
+
+       ret = device_property_read_u32(pbs->dev, "reg", &val);
+       if (ret < 0) {
+               dev_err(pbs->dev, "Couldn't find reg, ret = %d\n", ret);
+               return ret;
+       }
+       pbs->base = val;
+       mutex_init(&pbs->lock);
+
+       platform_set_drvdata(pdev, pbs);
+
+       return 0;
+}
+
+static const struct of_device_id qcom_pbs_match_table[] = {
+       { .compatible = "qcom,pbs" },
+       {}
+};
+MODULE_DEVICE_TABLE(of, qcom_pbs_match_table);
+
+static struct platform_driver qcom_pbs_driver = {
+       .driver = {
+               .name           = "qcom-pbs",
+               .of_match_table = qcom_pbs_match_table,
+       },
+       .probe = qcom_pbs_probe,
+};
+module_platform_driver(qcom_pbs_driver)
+
+MODULE_DESCRIPTION("QCOM PBS DRIVER");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/soc/qcom/qcom-pbs.h b/include/linux/soc/qcom/qcom-pbs.h
new file mode 100644 (file)
index 0000000..8a46209
--- /dev/null
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef _QCOM_PBS_H
+#define _QCOM_PBS_H
+
+#include <linux/errno.h>
+#include <linux/types.h>
+
+struct device_node;
+struct pbs_dev;
+
+#if IS_ENABLED(CONFIG_QCOM_PBS)
+int qcom_pbs_trigger_event(struct pbs_dev *pbs, u8 bitmap);
+struct pbs_dev *get_pbs_client_device(struct device *client_dev);
+#else
+static inline int qcom_pbs_trigger_event(struct pbs_dev *pbs, u8 bitmap)
+{
+       return -ENODEV;
+}
+
+static inline struct pbs_dev *get_pbs_client_device(struct device *client_dev)
+{
+       return ERR_PTR(-ENODEV);
+}
+#endif
+
+#endif