19a434ceeSElliot Berman // SPDX-License-Identifier: GPL-2.0-only
29a434ceeSElliot Berman /* Copyright (c) 2015,2019 The Linux Foundation. All rights reserved.
39a434ceeSElliot Berman */
49a434ceeSElliot Berman
59a434ceeSElliot Berman #include <linux/io.h>
69a434ceeSElliot Berman #include <linux/errno.h>
79a434ceeSElliot Berman #include <linux/delay.h>
89a434ceeSElliot Berman #include <linux/mutex.h>
99a434ceeSElliot Berman #include <linux/slab.h>
109a434ceeSElliot Berman #include <linux/types.h>
113bf90ecaSElliot Berman #include <linux/firmware/qcom/qcom_scm.h>
129a434ceeSElliot Berman #include <linux/arm-smccc.h>
139a434ceeSElliot Berman #include <linux/dma-mapping.h>
149a434ceeSElliot Berman
159a434ceeSElliot Berman #include "qcom_scm.h"
169a434ceeSElliot Berman
179a434ceeSElliot Berman /**
189a434ceeSElliot Berman * struct arm_smccc_args
199a434ceeSElliot Berman * @args: The array of values used in registers in smc instruction
209a434ceeSElliot Berman */
219a434ceeSElliot Berman struct arm_smccc_args {
229a434ceeSElliot Berman unsigned long args[8];
239a434ceeSElliot Berman };
249a434ceeSElliot Berman
259a434ceeSElliot Berman static DEFINE_MUTEX(qcom_scm_lock);
269a434ceeSElliot Berman
279a434ceeSElliot Berman #define QCOM_SCM_EBUSY_WAIT_MS 30
289a434ceeSElliot Berman #define QCOM_SCM_EBUSY_MAX_RETRY 20
299a434ceeSElliot Berman
309a434ceeSElliot Berman #define SCM_SMC_N_REG_ARGS 4
319a434ceeSElliot Berman #define SCM_SMC_FIRST_EXT_IDX (SCM_SMC_N_REG_ARGS - 1)
329a434ceeSElliot Berman #define SCM_SMC_N_EXT_ARGS (MAX_QCOM_SCM_ARGS - SCM_SMC_N_REG_ARGS + 1)
339a434ceeSElliot Berman #define SCM_SMC_FIRST_REG_IDX 2
349a434ceeSElliot Berman #define SCM_SMC_LAST_REG_IDX (SCM_SMC_FIRST_REG_IDX + SCM_SMC_N_REG_ARGS - 1)
359a434ceeSElliot Berman
__scm_smc_do_quirk(const struct arm_smccc_args * smc,struct arm_smccc_res * res)369a434ceeSElliot Berman static void __scm_smc_do_quirk(const struct arm_smccc_args *smc,
379a434ceeSElliot Berman struct arm_smccc_res *res)
389a434ceeSElliot Berman {
399a434ceeSElliot Berman unsigned long a0 = smc->args[0];
409a434ceeSElliot Berman struct arm_smccc_quirk quirk = { .id = ARM_SMCCC_QUIRK_QCOM_A6 };
419a434ceeSElliot Berman
429a434ceeSElliot Berman quirk.state.a6 = 0;
439a434ceeSElliot Berman
449a434ceeSElliot Berman do {
459a434ceeSElliot Berman arm_smccc_smc_quirk(a0, smc->args[1], smc->args[2],
469a434ceeSElliot Berman smc->args[3], smc->args[4], smc->args[5],
479a434ceeSElliot Berman quirk.state.a6, smc->args[7], res, &quirk);
489a434ceeSElliot Berman
499a434ceeSElliot Berman if (res->a0 == QCOM_SCM_INTERRUPTED)
509a434ceeSElliot Berman a0 = res->a0;
519a434ceeSElliot Berman
529a434ceeSElliot Berman } while (res->a0 == QCOM_SCM_INTERRUPTED);
539a434ceeSElliot Berman }
549a434ceeSElliot Berman
fill_wq_resume_args(struct arm_smccc_args * resume,u32 smc_call_ctx)556bf32599SGuru Das Srinagesh static void fill_wq_resume_args(struct arm_smccc_args *resume, u32 smc_call_ctx)
566bf32599SGuru Das Srinagesh {
576bf32599SGuru Das Srinagesh memset(resume->args, 0, sizeof(resume->args[0]) * ARRAY_SIZE(resume->args));
586bf32599SGuru Das Srinagesh
596bf32599SGuru Das Srinagesh resume->args[0] = ARM_SMCCC_CALL_VAL(ARM_SMCCC_STD_CALL,
606bf32599SGuru Das Srinagesh ARM_SMCCC_SMC_64, ARM_SMCCC_OWNER_SIP,
616bf32599SGuru Das Srinagesh SCM_SMC_FNID(QCOM_SCM_SVC_WAITQ, QCOM_SCM_WAITQ_RESUME));
626bf32599SGuru Das Srinagesh
636bf32599SGuru Das Srinagesh resume->args[1] = QCOM_SCM_ARGS(1);
646bf32599SGuru Das Srinagesh
656bf32599SGuru Das Srinagesh resume->args[2] = smc_call_ctx;
666bf32599SGuru Das Srinagesh }
676bf32599SGuru Das Srinagesh
scm_get_wq_ctx(u32 * wq_ctx,u32 * flags,u32 * more_pending)686bf32599SGuru Das Srinagesh int scm_get_wq_ctx(u32 *wq_ctx, u32 *flags, u32 *more_pending)
696bf32599SGuru Das Srinagesh {
706bf32599SGuru Das Srinagesh int ret;
716bf32599SGuru Das Srinagesh struct arm_smccc_res get_wq_res;
726bf32599SGuru Das Srinagesh struct arm_smccc_args get_wq_ctx = {0};
736bf32599SGuru Das Srinagesh
74*cdf7efe4SMurali Nalajala get_wq_ctx.args[0] = ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL,
756bf32599SGuru Das Srinagesh ARM_SMCCC_SMC_64, ARM_SMCCC_OWNER_SIP,
766bf32599SGuru Das Srinagesh SCM_SMC_FNID(QCOM_SCM_SVC_WAITQ, QCOM_SCM_WAITQ_GET_WQ_CTX));
776bf32599SGuru Das Srinagesh
786bf32599SGuru Das Srinagesh /* Guaranteed to return only success or error, no WAITQ_* */
796bf32599SGuru Das Srinagesh __scm_smc_do_quirk(&get_wq_ctx, &get_wq_res);
806bf32599SGuru Das Srinagesh ret = get_wq_res.a0;
816bf32599SGuru Das Srinagesh if (ret)
826bf32599SGuru Das Srinagesh return ret;
836bf32599SGuru Das Srinagesh
846bf32599SGuru Das Srinagesh *wq_ctx = get_wq_res.a1;
856bf32599SGuru Das Srinagesh *flags = get_wq_res.a2;
866bf32599SGuru Das Srinagesh *more_pending = get_wq_res.a3;
876bf32599SGuru Das Srinagesh
886bf32599SGuru Das Srinagesh return 0;
896bf32599SGuru Das Srinagesh }
906bf32599SGuru Das Srinagesh
__scm_smc_do_quirk_handle_waitq(struct device * dev,struct arm_smccc_args * waitq,struct arm_smccc_res * res)916bf32599SGuru Das Srinagesh static int __scm_smc_do_quirk_handle_waitq(struct device *dev, struct arm_smccc_args *waitq,
926bf32599SGuru Das Srinagesh struct arm_smccc_res *res)
936bf32599SGuru Das Srinagesh {
946bf32599SGuru Das Srinagesh int ret;
956bf32599SGuru Das Srinagesh u32 wq_ctx, smc_call_ctx;
966bf32599SGuru Das Srinagesh struct arm_smccc_args resume;
976bf32599SGuru Das Srinagesh struct arm_smccc_args *smc = waitq;
986bf32599SGuru Das Srinagesh
996bf32599SGuru Das Srinagesh do {
1006bf32599SGuru Das Srinagesh __scm_smc_do_quirk(smc, res);
1016bf32599SGuru Das Srinagesh
1026bf32599SGuru Das Srinagesh if (res->a0 == QCOM_SCM_WAITQ_SLEEP) {
1036bf32599SGuru Das Srinagesh wq_ctx = res->a1;
1046bf32599SGuru Das Srinagesh smc_call_ctx = res->a2;
1056bf32599SGuru Das Srinagesh
1066bf32599SGuru Das Srinagesh ret = qcom_scm_wait_for_wq_completion(wq_ctx);
1076bf32599SGuru Das Srinagesh if (ret)
1086bf32599SGuru Das Srinagesh return ret;
1096bf32599SGuru Das Srinagesh
1106bf32599SGuru Das Srinagesh fill_wq_resume_args(&resume, smc_call_ctx);
1116bf32599SGuru Das Srinagesh smc = &resume;
1126bf32599SGuru Das Srinagesh }
1136bf32599SGuru Das Srinagesh } while (res->a0 == QCOM_SCM_WAITQ_SLEEP);
1146bf32599SGuru Das Srinagesh
1156bf32599SGuru Das Srinagesh return 0;
1166bf32599SGuru Das Srinagesh }
1176bf32599SGuru Das Srinagesh
__scm_smc_do(struct device * dev,struct arm_smccc_args * smc,struct arm_smccc_res * res,bool atomic)1186bf32599SGuru Das Srinagesh static int __scm_smc_do(struct device *dev, struct arm_smccc_args *smc,
1199a434ceeSElliot Berman struct arm_smccc_res *res, bool atomic)
1209a434ceeSElliot Berman {
1216bf32599SGuru Das Srinagesh int ret, retry_count = 0;
1229a434ceeSElliot Berman
1239a434ceeSElliot Berman if (atomic) {
1249a434ceeSElliot Berman __scm_smc_do_quirk(smc, res);
1256bf32599SGuru Das Srinagesh return 0;
1269a434ceeSElliot Berman }
1279a434ceeSElliot Berman
1289a434ceeSElliot Berman do {
1299a434ceeSElliot Berman mutex_lock(&qcom_scm_lock);
1309a434ceeSElliot Berman
1316bf32599SGuru Das Srinagesh ret = __scm_smc_do_quirk_handle_waitq(dev, smc, res);
1329a434ceeSElliot Berman
1339a434ceeSElliot Berman mutex_unlock(&qcom_scm_lock);
1349a434ceeSElliot Berman
1356bf32599SGuru Das Srinagesh if (ret)
1366bf32599SGuru Das Srinagesh return ret;
1376bf32599SGuru Das Srinagesh
1389a434ceeSElliot Berman if (res->a0 == QCOM_SCM_V2_EBUSY) {
1399a434ceeSElliot Berman if (retry_count++ > QCOM_SCM_EBUSY_MAX_RETRY)
1409a434ceeSElliot Berman break;
1419a434ceeSElliot Berman msleep(QCOM_SCM_EBUSY_WAIT_MS);
1429a434ceeSElliot Berman }
1439a434ceeSElliot Berman } while (res->a0 == QCOM_SCM_V2_EBUSY);
1446bf32599SGuru Das Srinagesh
1456bf32599SGuru Das Srinagesh return 0;
1469a434ceeSElliot Berman }
1479a434ceeSElliot Berman
148f6ea568fSStephen Boyd
__scm_smc_call(struct device * dev,const struct qcom_scm_desc * desc,enum qcom_scm_convention qcom_convention,struct qcom_scm_res * res,bool atomic)149f6ea568fSStephen Boyd int __scm_smc_call(struct device *dev, const struct qcom_scm_desc *desc,
150f6ea568fSStephen Boyd enum qcom_scm_convention qcom_convention,
1519a434ceeSElliot Berman struct qcom_scm_res *res, bool atomic)
1529a434ceeSElliot Berman {
1539a434ceeSElliot Berman int arglen = desc->arginfo & 0xf;
1546bf32599SGuru Das Srinagesh int i, ret;
1559a434ceeSElliot Berman dma_addr_t args_phys = 0;
1569a434ceeSElliot Berman void *args_virt = NULL;
1579a434ceeSElliot Berman size_t alloc_len;
1589a434ceeSElliot Berman gfp_t flag = atomic ? GFP_ATOMIC : GFP_KERNEL;
1599a434ceeSElliot Berman u32 smccc_call_type = atomic ? ARM_SMCCC_FAST_CALL : ARM_SMCCC_STD_CALL;
160f6ea568fSStephen Boyd u32 qcom_smccc_convention = (qcom_convention == SMC_CONVENTION_ARM_32) ?
1619a434ceeSElliot Berman ARM_SMCCC_SMC_32 : ARM_SMCCC_SMC_64;
1629a434ceeSElliot Berman struct arm_smccc_res smc_res;
1639a434ceeSElliot Berman struct arm_smccc_args smc = {0};
1649a434ceeSElliot Berman
1659a434ceeSElliot Berman smc.args[0] = ARM_SMCCC_CALL_VAL(
1669a434ceeSElliot Berman smccc_call_type,
1679a434ceeSElliot Berman qcom_smccc_convention,
1689a434ceeSElliot Berman desc->owner,
1699a434ceeSElliot Berman SCM_SMC_FNID(desc->svc, desc->cmd));
1709a434ceeSElliot Berman smc.args[1] = desc->arginfo;
1719a434ceeSElliot Berman for (i = 0; i < SCM_SMC_N_REG_ARGS; i++)
1729a434ceeSElliot Berman smc.args[i + SCM_SMC_FIRST_REG_IDX] = desc->args[i];
1739a434ceeSElliot Berman
1749a434ceeSElliot Berman if (unlikely(arglen > SCM_SMC_N_REG_ARGS)) {
1759a434ceeSElliot Berman alloc_len = SCM_SMC_N_EXT_ARGS * sizeof(u64);
1769a434ceeSElliot Berman args_virt = kzalloc(PAGE_ALIGN(alloc_len), flag);
1779a434ceeSElliot Berman
1789a434ceeSElliot Berman if (!args_virt)
1799a434ceeSElliot Berman return -ENOMEM;
1809a434ceeSElliot Berman
1819a434ceeSElliot Berman if (qcom_smccc_convention == ARM_SMCCC_SMC_32) {
1829a434ceeSElliot Berman __le32 *args = args_virt;
1839a434ceeSElliot Berman
1849a434ceeSElliot Berman for (i = 0; i < SCM_SMC_N_EXT_ARGS; i++)
1859a434ceeSElliot Berman args[i] = cpu_to_le32(desc->args[i +
1869a434ceeSElliot Berman SCM_SMC_FIRST_EXT_IDX]);
1879a434ceeSElliot Berman } else {
1889a434ceeSElliot Berman __le64 *args = args_virt;
1899a434ceeSElliot Berman
1909a434ceeSElliot Berman for (i = 0; i < SCM_SMC_N_EXT_ARGS; i++)
1919a434ceeSElliot Berman args[i] = cpu_to_le64(desc->args[i +
1929a434ceeSElliot Berman SCM_SMC_FIRST_EXT_IDX]);
1939a434ceeSElliot Berman }
1949a434ceeSElliot Berman
1959a434ceeSElliot Berman args_phys = dma_map_single(dev, args_virt, alloc_len,
1969a434ceeSElliot Berman DMA_TO_DEVICE);
1979a434ceeSElliot Berman
1989a434ceeSElliot Berman if (dma_mapping_error(dev, args_phys)) {
1999a434ceeSElliot Berman kfree(args_virt);
2009a434ceeSElliot Berman return -ENOMEM;
2019a434ceeSElliot Berman }
2029a434ceeSElliot Berman
2039a434ceeSElliot Berman smc.args[SCM_SMC_LAST_REG_IDX] = args_phys;
2049a434ceeSElliot Berman }
2059a434ceeSElliot Berman
2066bf32599SGuru Das Srinagesh /* ret error check follows after args_virt cleanup*/
2076bf32599SGuru Das Srinagesh ret = __scm_smc_do(dev, &smc, &smc_res, atomic);
2089a434ceeSElliot Berman
2099a434ceeSElliot Berman if (args_virt) {
2109a434ceeSElliot Berman dma_unmap_single(dev, args_phys, alloc_len, DMA_TO_DEVICE);
2119a434ceeSElliot Berman kfree(args_virt);
2129a434ceeSElliot Berman }
2139a434ceeSElliot Berman
2146bf32599SGuru Das Srinagesh if (ret)
2156bf32599SGuru Das Srinagesh return ret;
2166bf32599SGuru Das Srinagesh
2179a434ceeSElliot Berman if (res) {
2189a434ceeSElliot Berman res->result[0] = smc_res.a1;
2199a434ceeSElliot Berman res->result[1] = smc_res.a2;
2209a434ceeSElliot Berman res->result[2] = smc_res.a3;
2219a434ceeSElliot Berman }
2229a434ceeSElliot Berman
2239a434ceeSElliot Berman return (long)smc_res.a0 ? qcom_scm_remap_error(smc_res.a0) : 0;
224f6ea568fSStephen Boyd
2259a434ceeSElliot Berman }
226