mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-15 08:14:15 +08:00
4f88bd23ba
This adds an interface for configuring Qualcomm's "secure SMMU" and adds support for booting the modem Hexagon on MSM8996. Two new debugfs entries are added in the remoteproc core to introspect the list of memory carveouts and the loaded resource table. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJaD26iAAoJEAsfOT8Nma3FUp0QAL/WZhL8QsvVFbAlv/Aa6NKm z8blXscD0GKdQWDWGaGPtpbnpOWOYhJs9WxstiB2q9ooSa9brIdjG5ukr4DTgtRs 12If5tfkVFIjlBhAdYYK+biNSQtZE+37jb7aJ+j4N3Po3O72pxekcLMLhAdvY6yk zHnhDZ8N7KBxAa0eOg7PJz7AGRF6BHhPxkqgJm0FzBj4HLQ/lwrv6iYJRrZgw6CT OhIHgPwCSHmt2fTO2QDLOWJtbVMSFhunHbuYlzPwvhMAGCT87VmXCIkW8iVhcD24 uJMrFbQoBoWVMB68JgGUKd7SSs8pV7DMrGfJll1kBEQfMLynn8Omre7WDAfwjUEn ACasSDWdWEPABJoIRVkGhS+2BnfCehdvDC7QxFFPAf8oc49O3t6DyXcCQdcVkzzE f1r/G2JKsoNQxjklFJewU5r0EjWgvV/78Dmpi7tuUtjgMSyEqkYORmInbbFIqZrU 9bHCqrmlxuRFSklxoG2u7F2SVYgMVlqM4rmj8QqR85SeIfyZtcCx5i5yAj8dZsI1 JVjucOCROHFqs/3SYFKlo6x4XHIchJD4p64QOXPCVhw7R1NXTPRTFBCQ75PKdiD1 d7kcGhdaz2cMeN1WRhIQ00lh/uiDNd4Ez7sDuU8MnWiEcIs1/ZP1LCHZTNttoljH rWZp4XlrhT+Vx91zV5u5 =lnNj -----END PGP SIGNATURE----- Merge tag 'rproc-v4.15' of git://github.com/andersson/remoteproc Pull remoteproc updates from Bjorn Andersson: "This adds an interface for configuring Qualcomm's "secure SMMU" and adds support for booting the modem Hexagon on MSM8996. Two new debugfs entries are added in the remoteproc core to introspect the list of memory carveouts and the loaded resource table" * tag 'rproc-v4.15' of git://github.com/andersson/remoteproc: remoteproc: qcom: Fix error handling paths in order to avoid memory leaks remoteproc: qcom: Drop pr_err in q6v5_xfer_mem_ownership() remoteproc: debug: add carveouts list dump feature remoteproc: debug: add resource table dump feature remoteproc: qcom: Add support for mss remoteproc on msm8996 remoteproc: qcom: Make secure world call for mem ownership switch remoteproc: qcom: refactor mss fw image loading sequence firmware: scm: Add new SCM call API for switching memory ownership
652 lines
16 KiB
C
652 lines
16 KiB
C
/*
|
|
* Qualcomm SCM driver
|
|
*
|
|
* Copyright (c) 2010,2015, The Linux Foundation. All rights reserved.
|
|
* Copyright (C) 2015 Linaro Ltd.
|
|
*
|
|
* 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/platform_device.h>
|
|
#include <linux/init.h>
|
|
#include <linux/cpumask.h>
|
|
#include <linux/export.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/module.h>
|
|
#include <linux/types.h>
|
|
#include <linux/qcom_scm.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/reset-controller.h>
|
|
|
|
#include "qcom_scm.h"
|
|
|
|
static bool download_mode = IS_ENABLED(CONFIG_QCOM_SCM_DOWNLOAD_MODE_DEFAULT);
|
|
module_param(download_mode, bool, 0);
|
|
|
|
#define SCM_HAS_CORE_CLK BIT(0)
|
|
#define SCM_HAS_IFACE_CLK BIT(1)
|
|
#define SCM_HAS_BUS_CLK BIT(2)
|
|
|
|
struct qcom_scm {
|
|
struct device *dev;
|
|
struct clk *core_clk;
|
|
struct clk *iface_clk;
|
|
struct clk *bus_clk;
|
|
struct reset_controller_dev reset;
|
|
|
|
u64 dload_mode_addr;
|
|
};
|
|
|
|
struct qcom_scm_current_perm_info {
|
|
__le32 vmid;
|
|
__le32 perm;
|
|
__le64 ctx;
|
|
__le32 ctx_size;
|
|
__le32 unused;
|
|
};
|
|
|
|
struct qcom_scm_mem_map_info {
|
|
__le64 mem_addr;
|
|
__le64 mem_size;
|
|
};
|
|
|
|
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
|
|
* @cpus: The cpumask of cpus that will use the entry point
|
|
*
|
|
* Set the cold boot address of the cpus. Any cpu outside the supported
|
|
* range would be removed from the cpu present mask.
|
|
*/
|
|
int qcom_scm_set_cold_boot_addr(void *entry, const cpumask_t *cpus)
|
|
{
|
|
return __qcom_scm_set_cold_boot_addr(entry, cpus);
|
|
}
|
|
EXPORT_SYMBOL(qcom_scm_set_cold_boot_addr);
|
|
|
|
/**
|
|
* qcom_scm_set_warm_boot_addr() - Set the warm boot address for cpus
|
|
* @entry: Entry point function for the cpus
|
|
* @cpus: The cpumask of cpus that will use the entry point
|
|
*
|
|
* Set the Linux entry point for the SCM to transfer control to when coming
|
|
* out of a power down. CPU power down may be executed on cpuidle or hotplug.
|
|
*/
|
|
int qcom_scm_set_warm_boot_addr(void *entry, const cpumask_t *cpus)
|
|
{
|
|
return __qcom_scm_set_warm_boot_addr(__scm->dev, entry, cpus);
|
|
}
|
|
EXPORT_SYMBOL(qcom_scm_set_warm_boot_addr);
|
|
|
|
/**
|
|
* qcom_scm_cpu_power_down() - Power down the cpu
|
|
* @flags - Flags to flush cache
|
|
*
|
|
* This is an end point to power down cpu. If there was a pending interrupt,
|
|
* the control would return from this function, otherwise, the cpu jumps to the
|
|
* warm boot entry point set for this cpu upon reset.
|
|
*/
|
|
void qcom_scm_cpu_power_down(u32 flags)
|
|
{
|
|
__qcom_scm_cpu_power_down(flags);
|
|
}
|
|
EXPORT_SYMBOL(qcom_scm_cpu_power_down);
|
|
|
|
/**
|
|
* qcom_scm_hdcp_available() - Check if secure environment supports HDCP.
|
|
*
|
|
* Return true if HDCP is supported, false if not.
|
|
*/
|
|
bool qcom_scm_hdcp_available(void)
|
|
{
|
|
int ret = qcom_scm_clk_enable();
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = __qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_HDCP,
|
|
QCOM_SCM_CMD_HDCP);
|
|
|
|
qcom_scm_clk_disable();
|
|
|
|
return ret > 0 ? true : false;
|
|
}
|
|
EXPORT_SYMBOL(qcom_scm_hdcp_available);
|
|
|
|
/**
|
|
* qcom_scm_hdcp_req() - Send HDCP request.
|
|
* @req: HDCP request array
|
|
* @req_cnt: HDCP request array count
|
|
* @resp: response buffer passed to SCM
|
|
*
|
|
* Write HDCP register(s) through SCM.
|
|
*/
|
|
int qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt, u32 *resp)
|
|
{
|
|
int ret = qcom_scm_clk_enable();
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = __qcom_scm_hdcp_req(__scm->dev, 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(__scm->dev, QCOM_SCM_SVC_PIL,
|
|
QCOM_SCM_PAS_IS_SUPPORTED_CMD);
|
|
if (ret <= 0)
|
|
return false;
|
|
|
|
return __qcom_scm_pas_supported(__scm->dev, 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(__scm->dev, 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(__scm->dev, 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(__scm->dev, 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(__scm->dev, peripheral);
|
|
qcom_scm_clk_disable();
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(qcom_scm_pas_shutdown);
|
|
|
|
static int qcom_scm_pas_reset_assert(struct reset_controller_dev *rcdev,
|
|
unsigned long idx)
|
|
{
|
|
if (idx != 0)
|
|
return -EINVAL;
|
|
|
|
return __qcom_scm_pas_mss_reset(__scm->dev, 1);
|
|
}
|
|
|
|
static int qcom_scm_pas_reset_deassert(struct reset_controller_dev *rcdev,
|
|
unsigned long idx)
|
|
{
|
|
if (idx != 0)
|
|
return -EINVAL;
|
|
|
|
return __qcom_scm_pas_mss_reset(__scm->dev, 0);
|
|
}
|
|
|
|
static const struct reset_control_ops qcom_scm_pas_reset_ops = {
|
|
.assert = qcom_scm_pas_reset_assert,
|
|
.deassert = qcom_scm_pas_reset_deassert,
|
|
};
|
|
|
|
int qcom_scm_restore_sec_cfg(u32 device_id, u32 spare)
|
|
{
|
|
return __qcom_scm_restore_sec_cfg(__scm->dev, device_id, spare);
|
|
}
|
|
EXPORT_SYMBOL(qcom_scm_restore_sec_cfg);
|
|
|
|
int qcom_scm_iommu_secure_ptbl_size(u32 spare, size_t *size)
|
|
{
|
|
return __qcom_scm_iommu_secure_ptbl_size(__scm->dev, spare, size);
|
|
}
|
|
EXPORT_SYMBOL(qcom_scm_iommu_secure_ptbl_size);
|
|
|
|
int qcom_scm_iommu_secure_ptbl_init(u64 addr, u32 size, u32 spare)
|
|
{
|
|
return __qcom_scm_iommu_secure_ptbl_init(__scm->dev, addr, size, spare);
|
|
}
|
|
EXPORT_SYMBOL(qcom_scm_iommu_secure_ptbl_init);
|
|
|
|
int qcom_scm_io_readl(phys_addr_t addr, unsigned int *val)
|
|
{
|
|
return __qcom_scm_io_readl(__scm->dev, addr, val);
|
|
}
|
|
EXPORT_SYMBOL(qcom_scm_io_readl);
|
|
|
|
int qcom_scm_io_writel(phys_addr_t addr, unsigned int val)
|
|
{
|
|
return __qcom_scm_io_writel(__scm->dev, addr, val);
|
|
}
|
|
EXPORT_SYMBOL(qcom_scm_io_writel);
|
|
|
|
static void qcom_scm_set_download_mode(bool enable)
|
|
{
|
|
bool avail;
|
|
int ret = 0;
|
|
|
|
avail = __qcom_scm_is_call_available(__scm->dev,
|
|
QCOM_SCM_SVC_BOOT,
|
|
QCOM_SCM_SET_DLOAD_MODE);
|
|
if (avail) {
|
|
ret = __qcom_scm_set_dload_mode(__scm->dev, enable);
|
|
} else if (__scm->dload_mode_addr) {
|
|
ret = __qcom_scm_io_writel(__scm->dev, __scm->dload_mode_addr,
|
|
enable ? QCOM_SCM_SET_DLOAD_MODE : 0);
|
|
} else {
|
|
dev_err(__scm->dev,
|
|
"No available mechanism for setting download mode\n");
|
|
}
|
|
|
|
if (ret)
|
|
dev_err(__scm->dev, "failed to set download mode: %d\n", ret);
|
|
}
|
|
|
|
static int qcom_scm_find_dload_address(struct device *dev, u64 *addr)
|
|
{
|
|
struct device_node *tcsr;
|
|
struct device_node *np = dev->of_node;
|
|
struct resource res;
|
|
u32 offset;
|
|
int ret;
|
|
|
|
tcsr = of_parse_phandle(np, "qcom,dload-mode", 0);
|
|
if (!tcsr)
|
|
return 0;
|
|
|
|
ret = of_address_to_resource(tcsr, 0, &res);
|
|
of_node_put(tcsr);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = of_property_read_u32_index(np, "qcom,dload-mode", 1, &offset);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
*addr = res.start + offset;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* qcom_scm_is_available() - Checks if SCM is available
|
|
*/
|
|
bool qcom_scm_is_available(void)
|
|
{
|
|
return !!__scm;
|
|
}
|
|
EXPORT_SYMBOL(qcom_scm_is_available);
|
|
|
|
int qcom_scm_set_remote_state(u32 state, u32 id)
|
|
{
|
|
return __qcom_scm_set_remote_state(__scm->dev, state, id);
|
|
}
|
|
EXPORT_SYMBOL(qcom_scm_set_remote_state);
|
|
|
|
/**
|
|
* qcom_scm_assign_mem() - Make a secure call to reassign memory ownership
|
|
* @mem_addr: mem region whose ownership need to be reassigned
|
|
* @mem_sz: size of the region.
|
|
* @srcvm: vmid for current set of owners, each set bit in
|
|
* flag indicate a unique owner
|
|
* @newvm: array having new owners and corrsponding permission
|
|
* flags
|
|
* @dest_cnt: number of owners in next set.
|
|
*
|
|
* Return negative errno on failure, 0 on success, with @srcvm updated.
|
|
*/
|
|
int qcom_scm_assign_mem(phys_addr_t mem_addr, size_t mem_sz,
|
|
unsigned int *srcvm,
|
|
struct qcom_scm_vmperm *newvm, int dest_cnt)
|
|
{
|
|
struct qcom_scm_current_perm_info *destvm;
|
|
struct qcom_scm_mem_map_info *mem_to_map;
|
|
phys_addr_t mem_to_map_phys;
|
|
phys_addr_t dest_phys;
|
|
phys_addr_t ptr_phys;
|
|
size_t mem_to_map_sz;
|
|
size_t dest_sz;
|
|
size_t src_sz;
|
|
size_t ptr_sz;
|
|
int next_vm;
|
|
__le32 *src;
|
|
void *ptr;
|
|
int ret;
|
|
int len;
|
|
int i;
|
|
|
|
src_sz = hweight_long(*srcvm) * sizeof(*src);
|
|
mem_to_map_sz = sizeof(*mem_to_map);
|
|
dest_sz = dest_cnt * sizeof(*destvm);
|
|
ptr_sz = ALIGN(src_sz, SZ_64) + ALIGN(mem_to_map_sz, SZ_64) +
|
|
ALIGN(dest_sz, SZ_64);
|
|
|
|
ptr = dma_alloc_coherent(__scm->dev, ptr_sz, &ptr_phys, GFP_KERNEL);
|
|
if (!ptr)
|
|
return -ENOMEM;
|
|
|
|
/* Fill source vmid detail */
|
|
src = ptr;
|
|
len = hweight_long(*srcvm);
|
|
for (i = 0; i < len; i++) {
|
|
src[i] = cpu_to_le32(ffs(*srcvm) - 1);
|
|
*srcvm ^= 1 << (ffs(*srcvm) - 1);
|
|
}
|
|
|
|
/* Fill details of mem buff to map */
|
|
mem_to_map = ptr + ALIGN(src_sz, SZ_64);
|
|
mem_to_map_phys = ptr_phys + ALIGN(src_sz, SZ_64);
|
|
mem_to_map[0].mem_addr = cpu_to_le64(mem_addr);
|
|
mem_to_map[0].mem_size = cpu_to_le64(mem_sz);
|
|
|
|
next_vm = 0;
|
|
/* Fill details of next vmid detail */
|
|
destvm = ptr + ALIGN(mem_to_map_sz, SZ_64) + ALIGN(src_sz, SZ_64);
|
|
dest_phys = ptr_phys + ALIGN(mem_to_map_sz, SZ_64) + ALIGN(src_sz, SZ_64);
|
|
for (i = 0; i < dest_cnt; i++) {
|
|
destvm[i].vmid = cpu_to_le32(newvm[i].vmid);
|
|
destvm[i].perm = cpu_to_le32(newvm[i].perm);
|
|
destvm[i].ctx = 0;
|
|
destvm[i].ctx_size = 0;
|
|
next_vm |= BIT(newvm[i].vmid);
|
|
}
|
|
|
|
ret = __qcom_scm_assign_mem(__scm->dev, mem_to_map_phys, mem_to_map_sz,
|
|
ptr_phys, src_sz, dest_phys, dest_sz);
|
|
dma_free_coherent(__scm->dev, ALIGN(ptr_sz, SZ_64), ptr, ptr_phys);
|
|
if (ret) {
|
|
dev_err(__scm->dev,
|
|
"Assign memory protection call failed %d.\n", ret);
|
|
return -EINVAL;
|
|
}
|
|
|
|
*srcvm = next_vm;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(qcom_scm_assign_mem);
|
|
|
|
static int qcom_scm_probe(struct platform_device *pdev)
|
|
{
|
|
struct qcom_scm *scm;
|
|
unsigned long clks;
|
|
int ret;
|
|
|
|
scm = devm_kzalloc(&pdev->dev, sizeof(*scm), GFP_KERNEL);
|
|
if (!scm)
|
|
return -ENOMEM;
|
|
|
|
ret = qcom_scm_find_dload_address(&pdev->dev, &scm->dload_mode_addr);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
clks = (unsigned long)of_device_get_match_data(&pdev->dev);
|
|
if (clks & SCM_HAS_CORE_CLK) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
if (clks & SCM_HAS_IFACE_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);
|
|
}
|
|
}
|
|
|
|
if (clks & SCM_HAS_BUS_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);
|
|
}
|
|
}
|
|
|
|
scm->reset.ops = &qcom_scm_pas_reset_ops;
|
|
scm->reset.nr_resets = 1;
|
|
scm->reset.of_node = pdev->dev.of_node;
|
|
ret = devm_reset_controller_register(&pdev->dev, &scm->reset);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* vote for max clk rate for highest performance */
|
|
ret = clk_set_rate(scm->core_clk, INT_MAX);
|
|
if (ret)
|
|
return ret;
|
|
|
|
__scm = scm;
|
|
__scm->dev = &pdev->dev;
|
|
|
|
__qcom_scm_init();
|
|
|
|
/*
|
|
* If requested enable "download mode", from this point on warmboot
|
|
* will cause the the boot stages to enter download mode, unless
|
|
* disabled below by a clean shutdown/reboot.
|
|
*/
|
|
if (download_mode)
|
|
qcom_scm_set_download_mode(true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void qcom_scm_shutdown(struct platform_device *pdev)
|
|
{
|
|
/* Clean shutdown, disable download mode to allow normal restart */
|
|
if (download_mode)
|
|
qcom_scm_set_download_mode(false);
|
|
}
|
|
|
|
static const struct of_device_id qcom_scm_dt_match[] = {
|
|
{ .compatible = "qcom,scm-apq8064",
|
|
/* FIXME: This should have .data = (void *) SCM_HAS_CORE_CLK */
|
|
},
|
|
{ .compatible = "qcom,scm-msm8660",
|
|
.data = (void *) SCM_HAS_CORE_CLK,
|
|
},
|
|
{ .compatible = "qcom,scm-msm8960",
|
|
.data = (void *) SCM_HAS_CORE_CLK,
|
|
},
|
|
{ .compatible = "qcom,scm-msm8996",
|
|
.data = NULL, /* no clocks */
|
|
},
|
|
{ .compatible = "qcom,scm",
|
|
.data = (void *)(SCM_HAS_CORE_CLK
|
|
| SCM_HAS_IFACE_CLK
|
|
| SCM_HAS_BUS_CLK),
|
|
},
|
|
{}
|
|
};
|
|
|
|
static struct platform_driver qcom_scm_driver = {
|
|
.driver = {
|
|
.name = "qcom_scm",
|
|
.of_match_table = qcom_scm_dt_match,
|
|
},
|
|
.probe = qcom_scm_probe,
|
|
.shutdown = qcom_scm_shutdown,
|
|
};
|
|
|
|
static int __init qcom_scm_init(void)
|
|
{
|
|
struct device_node *np, *fw_np;
|
|
int ret;
|
|
|
|
fw_np = of_find_node_by_name(NULL, "firmware");
|
|
|
|
if (!fw_np)
|
|
return -ENODEV;
|
|
|
|
np = of_find_matching_node(fw_np, qcom_scm_dt_match);
|
|
|
|
if (!np) {
|
|
of_node_put(fw_np);
|
|
return -ENODEV;
|
|
}
|
|
|
|
of_node_put(np);
|
|
|
|
ret = of_platform_populate(fw_np, qcom_scm_dt_match, NULL, NULL);
|
|
|
|
of_node_put(fw_np);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
return platform_driver_register(&qcom_scm_driver);
|
|
}
|
|
subsys_initcall(qcom_scm_init);
|