]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - drivers/firmware/qcom_scm.c
Merge remote-tracking branch 'mips/mips-for-linux-next'
[karo-tx-linux.git] / drivers / firmware / qcom_scm.c
index 45c008d688914fcbd63eb47f059bf0ac679761dd..6fc9580a26bd96159b1560a043ba218c420d8a32 100644 (file)
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- * 02110-1301, USA.
  */
-
+#include <linux/platform_device.h>
+#include <linux/module.h>
 #include <linux/cpumask.h>
 #include <linux/export.h>
+#include <linux/dma-mapping.h>
 #include <linux/types.h>
 #include <linux/qcom_scm.h>
+#include <linux/of.h>
+#include <linux/clk.h>
 
 #include "qcom_scm.h"
 
+struct qcom_scm {
+       struct device *dev;
+       struct clk *core_clk;
+       struct clk *iface_clk;
+       struct clk *bus_clk;
+};
+
+static struct qcom_scm *__scm;
+
+static int qcom_scm_clk_enable(void)
+{
+       int ret;
+
+       ret = clk_prepare_enable(__scm->core_clk);
+       if (ret)
+               goto bail;
+       ret = clk_prepare_enable(__scm->iface_clk);
+       if (ret)
+               goto disable_core;
+       ret = clk_prepare_enable(__scm->bus_clk);
+       if (ret)
+               goto disable_iface;
+
+       return 0;
+
+disable_iface:
+       clk_disable_unprepare(__scm->iface_clk);
+disable_core:
+       clk_disable_unprepare(__scm->core_clk);
+bail:
+       return ret;
+}
+
+static void qcom_scm_clk_disable(void)
+{
+       clk_disable_unprepare(__scm->core_clk);
+       clk_disable_unprepare(__scm->iface_clk);
+       clk_disable_unprepare(__scm->bus_clk);
+}
+
 /**
  * qcom_scm_set_cold_boot_addr() - Set the cold boot address for cpus
  * @entry: Entry point function for the cpus
@@ -72,11 +112,17 @@ EXPORT_SYMBOL(qcom_scm_cpu_power_down);
  */
 bool qcom_scm_hdcp_available(void)
 {
-       int ret;
+       int ret = qcom_scm_clk_enable();
+
+       if (ret)
+               goto clk_err;
 
        ret = __qcom_scm_is_call_available(QCOM_SCM_SVC_HDCP,
-               QCOM_SCM_CMD_HDCP);
+                                               QCOM_SCM_CMD_HDCP);
 
+       qcom_scm_clk_disable();
+
+clk_err:
        return (ret > 0) ? true : false;
 }
 EXPORT_SYMBOL(qcom_scm_hdcp_available);
@@ -91,6 +137,215 @@ EXPORT_SYMBOL(qcom_scm_hdcp_available);
  */
 int qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt, u32 *resp)
 {
-       return __qcom_scm_hdcp_req(req, req_cnt, resp);
+       int ret = qcom_scm_clk_enable();
+
+       if (ret)
+               return ret;
+
+       ret = __qcom_scm_hdcp_req(req, req_cnt, resp);
+       qcom_scm_clk_disable();
+       return ret;
 }
 EXPORT_SYMBOL(qcom_scm_hdcp_req);
+
+/**
+ * qcom_scm_pas_supported() - Check if the peripheral authentication service is
+ *                           available for the given peripherial
+ * @peripheral:        peripheral id
+ *
+ * Returns true if PAS is supported for this peripheral, otherwise false.
+ */
+bool qcom_scm_pas_supported(u32 peripheral)
+{
+       int ret;
+
+       ret = __qcom_scm_is_call_available(QCOM_SCM_SVC_PIL,
+                                          QCOM_SCM_PAS_IS_SUPPORTED_CMD);
+       if (ret <= 0)
+               return false;
+
+       return __qcom_scm_pas_supported(peripheral);
+}
+EXPORT_SYMBOL(qcom_scm_pas_supported);
+
+/**
+ * qcom_scm_pas_init_image() - Initialize peripheral authentication service
+ *                            state machine for a given peripheral, using the
+ *                            metadata
+ * @peripheral: peripheral id
+ * @metadata:  pointer to memory containing ELF header, program header table
+ *             and optional blob of data used for authenticating the metadata
+ *             and the rest of the firmware
+ * @size:      size of the metadata
+ *
+ * Returns 0 on success.
+ */
+int qcom_scm_pas_init_image(u32 peripheral, const void *metadata, size_t size)
+{
+       dma_addr_t mdata_phys;
+       void *mdata_buf;
+       int ret;
+
+       /*
+        * During the scm call memory protection will be enabled for the meta
+        * data blob, so make sure it's physically contiguous, 4K aligned and
+        * non-cachable to avoid XPU violations.
+        */
+       mdata_buf = dma_alloc_coherent(__scm->dev, size, &mdata_phys, GFP_KERNEL);
+       if (!mdata_buf) {
+               dev_err(__scm->dev, "Allocation of metadata buffer failed.\n");
+               return -ENOMEM;
+       }
+       memcpy(mdata_buf, metadata, size);
+
+       ret = qcom_scm_clk_enable();
+       if (ret)
+               goto free_metadata;
+
+       ret = __qcom_scm_pas_init_image(peripheral, mdata_phys);
+
+       qcom_scm_clk_disable();
+
+free_metadata:
+       dma_free_coherent(__scm->dev, size, mdata_buf, mdata_phys);
+
+       return ret;
+}
+EXPORT_SYMBOL(qcom_scm_pas_init_image);
+
+/**
+ * qcom_scm_pas_mem_setup() - Prepare the memory related to a given peripheral
+ *                           for firmware loading
+ * @peripheral:        peripheral id
+ * @addr:      start address of memory area to prepare
+ * @size:      size of the memory area to prepare
+ *
+ * Returns 0 on success.
+ */
+int qcom_scm_pas_mem_setup(u32 peripheral, phys_addr_t addr, phys_addr_t size)
+{
+       int ret;
+
+       ret = qcom_scm_clk_enable();
+       if (ret)
+               return ret;
+
+       ret = __qcom_scm_pas_mem_setup(peripheral, addr, size);
+       qcom_scm_clk_disable();
+
+       return ret;
+}
+EXPORT_SYMBOL(qcom_scm_pas_mem_setup);
+
+/**
+ * qcom_scm_pas_auth_and_reset() - Authenticate the given peripheral firmware
+ *                                and reset the remote processor
+ * @peripheral:        peripheral id
+ *
+ * Return 0 on success.
+ */
+int qcom_scm_pas_auth_and_reset(u32 peripheral)
+{
+       int ret;
+
+       ret = qcom_scm_clk_enable();
+       if (ret)
+               return ret;
+
+       ret = __qcom_scm_pas_auth_and_reset(peripheral);
+       qcom_scm_clk_disable();
+
+       return ret;
+}
+EXPORT_SYMBOL(qcom_scm_pas_auth_and_reset);
+
+/**
+ * qcom_scm_pas_shutdown() - Shut down the remote processor
+ * @peripheral: peripheral id
+ *
+ * Returns 0 on success.
+ */
+int qcom_scm_pas_shutdown(u32 peripheral)
+{
+       int ret;
+
+       ret = qcom_scm_clk_enable();
+       if (ret)
+               return ret;
+
+       ret = __qcom_scm_pas_shutdown(peripheral);
+       qcom_scm_clk_disable();
+
+       return ret;
+}
+EXPORT_SYMBOL(qcom_scm_pas_shutdown);
+
+/**
+ * qcom_scm_is_available() - Checks if SCM is available
+ */
+bool qcom_scm_is_available(void)
+{
+       return !!__scm;
+}
+EXPORT_SYMBOL(qcom_scm_is_available);
+
+static int qcom_scm_probe(struct platform_device *pdev)
+{
+       struct qcom_scm *scm;
+       long rate;
+       int ret;
+
+       scm = devm_kzalloc(&pdev->dev, sizeof(*scm), GFP_KERNEL);
+       if (!scm)
+               return -ENOMEM;
+
+       scm->dev = &pdev->dev;
+
+       scm->core_clk = devm_clk_get(&pdev->dev, "core");
+       if (IS_ERR(scm->core_clk)) {
+               if (PTR_ERR(scm->core_clk) != -EPROBE_DEFER)
+                       dev_err(&pdev->dev, "failed to acquire core clk\n");
+               return PTR_ERR(scm->core_clk);
+       }
+
+       scm->iface_clk = devm_clk_get(&pdev->dev, "iface");
+       if (IS_ERR(scm->iface_clk)) {
+               if (PTR_ERR(scm->iface_clk) != -EPROBE_DEFER)
+                       dev_err(&pdev->dev, "failed to acquire iface clk\n");
+               return PTR_ERR(scm->iface_clk);
+       }
+
+       scm->bus_clk = devm_clk_get(&pdev->dev, "bus");
+       if (IS_ERR(scm->bus_clk)) {
+               if (PTR_ERR(scm->bus_clk) != -EPROBE_DEFER)
+                       dev_err(&pdev->dev, "failed to acquire bus clk\n");
+               return PTR_ERR(scm->bus_clk);
+       }
+
+       /* vote for max clk rate for highest performance */
+       rate = clk_round_rate(scm->core_clk, INT_MAX);
+       ret = clk_set_rate(scm->core_clk, rate);
+       if (ret)
+               return ret;
+
+       __scm = scm;
+
+       return 0;
+}
+
+static const struct of_device_id qcom_scm_dt_match[] = {
+       { .compatible = "qcom,scm",},
+       {},
+};
+
+MODULE_DEVICE_TABLE(of, qcom_scm_dt_match);
+
+static struct platform_driver qcom_scm_driver = {
+       .driver = {
+               .name   = "qcom_scm",
+               .of_match_table = qcom_scm_dt_match,
+       },
+       .probe = qcom_scm_probe,
+};
+
+builtin_platform_driver(qcom_scm_driver);