ARM: SoC driver updates for v4.8

Driver updates for ARM SoCs.
 
 A slew of changes this release cycle. The reset driver tree, that we merge
 through arm-soc for historical reasons, is also sizable this time around.
 
 Among the changes:
 
  - clps711x: Treewide changes to compatible strings, merged here for simplicity.
  - Qualcomm: SCM firmware driver cleanups, move to platform driver
  - ux500: Major cleanups, removal of old mach-specific infrastructure.
  - Atmel external bus memory driver
  - Move of brcmstb platform to the rest of bcm
  - PMC driver updates for tegra, various fixes and improvements
  - Samsung platform driver updates to support 64-bit Exynos platforms
  - Reset controller cleanups moving to devm_reset_controller_register() APIs
  - Reset controller driver for Amlogic Meson
  - Reset controller driver for Hisilicon hi6220
  - ARM SCPI power domain support
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQIcBAABAgAGBQJXnm1XAAoJEIwa5zzehBx35lcP/ApuQarIXeZCQZtjlUBV9McW
 o3o7FhKFHePmEPeoYCvVeK5D8NykTkQv3WpnCknoxPJzxGJF7jbPWQJcVnXfKOXD
 kTcyIK15WL2HHtSE3lYyLfyUPz8AbJyRt0l0cxgcg6jvo+uzlWooNz1y78rLIYzg
 UwRssj7OiHv4dsyYRHZIsjnB8gMWw8rYMk154gP2xy6MnNXXzzOVVnOiVqxSZBm+
 EgIIcROMOqkkHuFlClMYKluIgrmgz1Ypjf+FuAg7dqXZd+TGRrmGXeI7SkGThfLu
 nyvY3N18NViNu7xOUkI9zg7+ifyYM8Si9ylalSICSJdIAxZfiwFqFaLJvVWKU1rY
 rBOBjKckQI0/X9WYusFNFHcijhIFV8/FgGAnVRRMPdvlCss7Zp03C9mR4AEhmKMX
 rLG49x81hU1C+LftC59ml3iB8dhZrrRkbxNHjLFHVGWNrKMrmJKa8JhXGRAoNM+u
 LRauiuJZatqvLfISNvpfcoW2EashVoU3f+uC8ymT3QCyME3wZm0t7T4tllxhMfBl
 sOgJqNkTKDmPLofwm/dASiLML7ZF1WePScrFyOACnj9K4mUD+OaCnowtWoQPu0eI
 aNmT84oosJ2S9F/iUDPtFHXdzQ+1QPPfSiQ9FXMoauciVq/2F+pqq68yYgqoxFOG
 vmkmG2YM4Wyq43u0BONR
 =O8+y
 -----END PGP SIGNATURE-----

Merge tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc

Pull ARM SoC driver updates from Olof Johansson:
 "Driver updates for ARM SoCs.

  A slew of changes this release cycle.  The reset driver tree, that we
  merge through arm-soc for historical reasons, is also sizable this
  time around.

  Among the changes:

   - clps711x: Treewide changes to compatible strings, merged here for simplicity.
   - Qualcomm: SCM firmware driver cleanups, move to platform driver
   - ux500: Major cleanups, removal of old mach-specific infrastructure.
   - Atmel external bus memory driver
   - Move of brcmstb platform to the rest of bcm
   - PMC driver updates for tegra, various fixes and improvements
   - Samsung platform driver updates to support 64-bit Exynos platforms
   - Reset controller cleanups moving to devm_reset_controller_register() APIs
   - Reset controller driver for Amlogic Meson
   - Reset controller driver for Hisilicon hi6220
   - ARM SCPI power domain support"

* tag 'armsoc-drivers' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (100 commits)
  ARM: ux500: consolidate base platform files
  ARM: ux500: move soc_id driver to drivers/soc
  ARM: ux500: call ux500_setup_id later
  ARM: ux500: consolidate soc_device code in id.c
  ARM: ux500: remove cpu_is_u* helpers
  ARM: ux500: use CLK_OF_DECLARE()
  ARM: ux500: move l2x0 init to .init_irq
  mfd: db8500 stop passing around platform data
  ASoC: ab8500-codec: remove platform data based probe
  ARM: ux500: move ab8500_regulator_plat_data into driver
  ARM: ux500: remove unused regulator data
  soc: raspberrypi-power: add CONFIG_OF dependency
  firmware: scpi: add CONFIG_OF dependency
  video: clps711x-fb: Changing the compatibility string to match with the smallest supported chip
  input: clps711x-keypad: Changing the compatibility string to match with the smallest supported chip
  pwm: clps711x: Changing the compatibility string to match with the smallest supported chip
  serial: clps711x: Changing the compatibility string to match with the smallest supported chip
  irqchip: clps711x: Changing the compatibility string to match with the smallest supported chip
  clocksource: clps711x: Changing the compatibility string to match with the smallest supported chip
  clk: clps711x: Changing the compatibility string to match with the smallest supported chip
  ...
This commit is contained in:
Linus Torvalds 2016-08-01 18:36:01 -04:00
commit 43a0a98aa8
140 changed files with 4776 additions and 2641 deletions

View file

@ -10,7 +10,7 @@ config ARM_PSCI_FW
config ARM_SCPI_PROTOCOL
tristate "ARM System Control and Power Interface (SCPI) Message Protocol"
depends on ARM_MHU
depends on MAILBOX
help
System Control and Power Interface (SCPI) Message Protocol is
defined for the purpose of communication between the Application
@ -27,6 +27,15 @@ config ARM_SCPI_PROTOCOL
This protocol library provides interface for all the client drivers
making use of the features offered by the SCP.
config ARM_SCPI_POWER_DOMAIN
tristate "SCPI power domain driver"
depends on ARM_SCPI_PROTOCOL || (COMPILE_TEST && OF)
default y
select PM_GENERIC_DOMAINS if PM
help
This enables support for the SCPI power domains which can be
enabled or disabled via the SCP firmware
config EDD
tristate "BIOS Enhanced Disk Drive calls determine boot disk"
depends on X86
@ -184,6 +193,7 @@ config FW_CFG_SYSFS_CMDLINE
config QCOM_SCM
bool
depends on ARM || ARM64
select RESET_CONTROLLER
config QCOM_SCM_32
def_bool y

View file

@ -3,6 +3,7 @@
#
obj-$(CONFIG_ARM_PSCI_FW) += psci.o
obj-$(CONFIG_ARM_SCPI_PROTOCOL) += arm_scpi.o
obj-$(CONFIG_ARM_SCPI_POWER_DOMAIN) += scpi_pm_domain.o
obj-$(CONFIG_DMI) += dmi_scan.o
obj-$(CONFIG_DMI_SYSFS) += dmi-sysfs.o
obj-$(CONFIG_EDD) += edd.o

View file

@ -210,10 +210,6 @@ struct dvfs_info {
} opps[MAX_DVFS_OPPS];
} __packed;
struct dvfs_get {
u8 index;
} __packed;
struct dvfs_set {
u8 domain;
u8 index;
@ -235,6 +231,11 @@ struct sensor_value {
__le32 hi_val;
} __packed;
struct dev_pstate_set {
u16 dev_id;
u8 pstate;
} __packed;
static struct scpi_drvinfo *scpi_info;
static int scpi_linux_errmap[SCPI_ERR_MAX] = {
@ -431,11 +432,11 @@ static int scpi_clk_set_val(u16 clk_id, unsigned long rate)
static int scpi_dvfs_get_idx(u8 domain)
{
int ret;
struct dvfs_get dvfs;
u8 dvfs_idx;
ret = scpi_send_message(SCPI_CMD_GET_DVFS, &domain, sizeof(domain),
&dvfs, sizeof(dvfs));
return ret ? ret : dvfs.index;
&dvfs_idx, sizeof(dvfs_idx));
return ret ? ret : dvfs_idx;
}
static int scpi_dvfs_set_idx(u8 domain, u8 index)
@ -526,7 +527,7 @@ static int scpi_sensor_get_info(u16 sensor_id, struct scpi_sensor_info *info)
return ret;
}
int scpi_sensor_get_value(u16 sensor, u64 *val)
static int scpi_sensor_get_value(u16 sensor, u64 *val)
{
__le16 id = cpu_to_le16(sensor);
struct sensor_value buf;
@ -541,6 +542,29 @@ int scpi_sensor_get_value(u16 sensor, u64 *val)
return ret;
}
static int scpi_device_get_power_state(u16 dev_id)
{
int ret;
u8 pstate;
__le16 id = cpu_to_le16(dev_id);
ret = scpi_send_message(SCPI_CMD_GET_DEVICE_PWR_STATE, &id,
sizeof(id), &pstate, sizeof(pstate));
return ret ? ret : pstate;
}
static int scpi_device_set_power_state(u16 dev_id, u8 pstate)
{
int stat;
struct dev_pstate_set dev_set = {
.dev_id = cpu_to_le16(dev_id),
.pstate = pstate,
};
return scpi_send_message(SCPI_CMD_SET_DEVICE_PWR_STATE, &dev_set,
sizeof(dev_set), &stat, sizeof(stat));
}
static struct scpi_ops scpi_ops = {
.get_version = scpi_get_version,
.clk_get_range = scpi_clk_get_range,
@ -552,6 +576,8 @@ static struct scpi_ops scpi_ops = {
.sensor_get_capability = scpi_sensor_get_capability,
.sensor_get_info = scpi_sensor_get_info,
.sensor_get_value = scpi_sensor_get_value,
.device_get_power_state = scpi_device_get_power_state,
.device_set_power_state = scpi_device_set_power_state,
};
struct scpi_ops *get_scpi_ops(void)

View file

@ -23,8 +23,7 @@
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/qcom_scm.h>
#include <asm/cacheflush.h>
#include <linux/dma-mapping.h>
#include "qcom_scm.h"
@ -96,44 +95,6 @@ struct qcom_scm_response {
__le32 is_complete;
};
/**
* alloc_qcom_scm_command() - Allocate an SCM command
* @cmd_size: size of the command buffer
* @resp_size: size of the response buffer
*
* Allocate an SCM command, including enough room for the command
* and response headers as well as the command and response buffers.
*
* Returns a valid &qcom_scm_command on success or %NULL if the allocation fails.
*/
static struct qcom_scm_command *alloc_qcom_scm_command(size_t cmd_size, size_t resp_size)
{
struct qcom_scm_command *cmd;
size_t len = sizeof(*cmd) + sizeof(struct qcom_scm_response) + cmd_size +
resp_size;
u32 offset;
cmd = kzalloc(PAGE_ALIGN(len), GFP_KERNEL);
if (cmd) {
cmd->len = cpu_to_le32(len);
offset = offsetof(struct qcom_scm_command, buf);
cmd->buf_offset = cpu_to_le32(offset);
cmd->resp_hdr_offset = cpu_to_le32(offset + cmd_size);
}
return cmd;
}
/**
* free_qcom_scm_command() - Free an SCM command
* @cmd: command to free
*
* Free an SCM command.
*/
static inline void free_qcom_scm_command(struct qcom_scm_command *cmd)
{
kfree(cmd);
}
/**
* qcom_scm_command_to_response() - Get a pointer to a qcom_scm_response
* @cmd: command
@ -168,23 +129,6 @@ static inline void *qcom_scm_get_response_buffer(const struct qcom_scm_response
return (void *)rsp + le32_to_cpu(rsp->buf_offset);
}
static int qcom_scm_remap_error(int err)
{
pr_err("qcom_scm_call failed with error code %d\n", err);
switch (err) {
case QCOM_SCM_ERROR:
return -EIO;
case QCOM_SCM_EINVAL_ADDR:
case QCOM_SCM_EINVAL_ARG:
return -EINVAL;
case QCOM_SCM_EOPNOTSUPP:
return -EOPNOTSUPP;
case QCOM_SCM_ENOMEM:
return -ENOMEM;
}
return -EINVAL;
}
static u32 smc(u32 cmd_addr)
{
int context_id;
@ -209,45 +153,9 @@ static u32 smc(u32 cmd_addr)
return r0;
}
static int __qcom_scm_call(const struct qcom_scm_command *cmd)
{
int ret;
u32 cmd_addr = virt_to_phys(cmd);
/*
* Flush the command buffer so that the secure world sees
* the correct data.
*/
secure_flush_area(cmd, cmd->len);
ret = smc(cmd_addr);
if (ret < 0)
ret = qcom_scm_remap_error(ret);
return ret;
}
static void qcom_scm_inv_range(unsigned long start, unsigned long end)
{
u32 cacheline_size, ctr;
asm volatile("mrc p15, 0, %0, c0, c0, 1" : "=r" (ctr));
cacheline_size = 4 << ((ctr >> 16) & 0xf);
start = round_down(start, cacheline_size);
end = round_up(end, cacheline_size);
outer_inv_range(start, end);
while (start < end) {
asm ("mcr p15, 0, %0, c7, c6, 1" : : "r" (start)
: "memory");
start += cacheline_size;
}
dsb();
isb();
}
/**
* qcom_scm_call() - Send an SCM command
* @dev: struct device
* @svc_id: service identifier
* @cmd_id: command identifier
* @cmd_buf: command buffer
@ -264,42 +172,59 @@ static void qcom_scm_inv_range(unsigned long start, unsigned long end)
* and response buffers is taken care of by qcom_scm_call; however, callers are
* responsible for any other cached buffers passed over to the secure world.
*/
static int qcom_scm_call(u32 svc_id, u32 cmd_id, const void *cmd_buf,
size_t cmd_len, void *resp_buf, size_t resp_len)
static int qcom_scm_call(struct device *dev, u32 svc_id, u32 cmd_id,
const void *cmd_buf, size_t cmd_len, void *resp_buf,
size_t resp_len)
{
int ret;
struct qcom_scm_command *cmd;
struct qcom_scm_response *rsp;
unsigned long start, end;
size_t alloc_len = sizeof(*cmd) + cmd_len + sizeof(*rsp) + resp_len;
dma_addr_t cmd_phys;
cmd = alloc_qcom_scm_command(cmd_len, resp_len);
cmd = kzalloc(PAGE_ALIGN(alloc_len), GFP_KERNEL);
if (!cmd)
return -ENOMEM;
cmd->len = cpu_to_le32(alloc_len);
cmd->buf_offset = cpu_to_le32(sizeof(*cmd));
cmd->resp_hdr_offset = cpu_to_le32(sizeof(*cmd) + cmd_len);
cmd->id = cpu_to_le32((svc_id << 10) | cmd_id);
if (cmd_buf)
memcpy(qcom_scm_get_command_buffer(cmd), cmd_buf, cmd_len);
rsp = qcom_scm_command_to_response(cmd);
cmd_phys = dma_map_single(dev, cmd, alloc_len, DMA_TO_DEVICE);
if (dma_mapping_error(dev, cmd_phys)) {
kfree(cmd);
return -ENOMEM;
}
mutex_lock(&qcom_scm_lock);
ret = __qcom_scm_call(cmd);
ret = smc(cmd_phys);
if (ret < 0)
ret = qcom_scm_remap_error(ret);
mutex_unlock(&qcom_scm_lock);
if (ret)
goto out;
rsp = qcom_scm_command_to_response(cmd);
start = (unsigned long)rsp;
do {
qcom_scm_inv_range(start, start + sizeof(*rsp));
dma_sync_single_for_cpu(dev, cmd_phys + sizeof(*cmd) + cmd_len,
sizeof(*rsp), DMA_FROM_DEVICE);
} while (!rsp->is_complete);
end = (unsigned long)qcom_scm_get_response_buffer(rsp) + resp_len;
qcom_scm_inv_range(start, end);
if (resp_buf)
memcpy(resp_buf, qcom_scm_get_response_buffer(rsp), resp_len);
if (resp_buf) {
dma_sync_single_for_cpu(dev, cmd_phys + sizeof(*cmd) + cmd_len +
le32_to_cpu(rsp->buf_offset),
resp_len, DMA_FROM_DEVICE);
memcpy(resp_buf, qcom_scm_get_response_buffer(rsp),
resp_len);
}
out:
free_qcom_scm_command(cmd);
dma_unmap_single(dev, cmd_phys, alloc_len, DMA_TO_DEVICE);
kfree(cmd);
return ret;
}
@ -342,6 +267,41 @@ static s32 qcom_scm_call_atomic1(u32 svc, u32 cmd, u32 arg1)
return r0;
}
/**
* qcom_scm_call_atomic2() - Send an atomic SCM command with two arguments
* @svc_id: service identifier
* @cmd_id: command identifier
* @arg1: first argument
* @arg2: second argument
*
* This shall only be used with commands that are guaranteed to be
* uninterruptable, atomic and SMP safe.
*/
static s32 qcom_scm_call_atomic2(u32 svc, u32 cmd, u32 arg1, u32 arg2)
{
int context_id;
register u32 r0 asm("r0") = SCM_ATOMIC(svc, cmd, 2);
register u32 r1 asm("r1") = (u32)&context_id;
register u32 r2 asm("r2") = arg1;
register u32 r3 asm("r3") = arg2;
asm volatile(
__asmeq("%0", "r0")
__asmeq("%1", "r0")
__asmeq("%2", "r1")
__asmeq("%3", "r2")
__asmeq("%4", "r3")
#ifdef REQUIRES_SEC
".arch_extension sec\n"
#endif
"smc #0 @ switch to secure world\n"
: "=r" (r0)
: "r" (r0), "r" (r1), "r" (r2), "r" (r3)
);
return r0;
}
u32 qcom_scm_get_version(void)
{
int context_id;
@ -378,22 +338,6 @@ u32 qcom_scm_get_version(void)
}
EXPORT_SYMBOL(qcom_scm_get_version);
/*
* Set the cold/warm boot address for one of the CPU cores.
*/
static int qcom_scm_set_boot_addr(u32 addr, int flags)
{
struct {
__le32 flags;
__le32 addr;
} cmd;
cmd.addr = cpu_to_le32(addr);
cmd.flags = cpu_to_le32(flags);
return qcom_scm_call(QCOM_SCM_SVC_BOOT, QCOM_SCM_BOOT_ADDR,
&cmd, sizeof(cmd), NULL, 0);
}
/**
* qcom_scm_set_cold_boot_addr() - Set the cold boot address for cpus
* @entry: Entry point function for the cpus
@ -423,7 +367,8 @@ int __qcom_scm_set_cold_boot_addr(void *entry, const cpumask_t *cpus)
set_cpu_present(cpu, false);
}
return qcom_scm_set_boot_addr(virt_to_phys(entry), flags);
return qcom_scm_call_atomic2(QCOM_SCM_SVC_BOOT, QCOM_SCM_BOOT_ADDR,
flags, virt_to_phys(entry));
}
/**
@ -434,11 +379,16 @@ int __qcom_scm_set_cold_boot_addr(void *entry, const cpumask_t *cpus)
* 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)
int __qcom_scm_set_warm_boot_addr(struct device *dev, void *entry,
const cpumask_t *cpus)
{
int ret;
int flags = 0;
int cpu;
struct {
__le32 flags;
__le32 addr;
} cmd;
/*
* Reassign only if we are switching from hotplug entry point
@ -454,7 +404,10 @@ int __qcom_scm_set_warm_boot_addr(void *entry, const cpumask_t *cpus)
if (!flags)
return 0;
ret = qcom_scm_set_boot_addr(virt_to_phys(entry), flags);
cmd.addr = cpu_to_le32(virt_to_phys(entry));
cmd.flags = cpu_to_le32(flags);
ret = qcom_scm_call(dev, QCOM_SCM_SVC_BOOT, QCOM_SCM_BOOT_ADDR,
&cmd, sizeof(cmd), NULL, 0);
if (!ret) {
for_each_cpu(cpu, cpus)
qcom_scm_wb[cpu].entry = entry;
@ -477,25 +430,133 @@ void __qcom_scm_cpu_power_down(u32 flags)
flags & QCOM_SCM_FLUSH_FLAG_MASK);
}
int __qcom_scm_is_call_available(u32 svc_id, u32 cmd_id)
int __qcom_scm_is_call_available(struct device *dev, u32 svc_id, u32 cmd_id)
{
int ret;
__le32 svc_cmd = cpu_to_le32((svc_id << 10) | cmd_id);
__le32 ret_val = 0;
ret = qcom_scm_call(QCOM_SCM_SVC_INFO, QCOM_IS_CALL_AVAIL_CMD, &svc_cmd,
sizeof(svc_cmd), &ret_val, sizeof(ret_val));
ret = qcom_scm_call(dev, QCOM_SCM_SVC_INFO, QCOM_IS_CALL_AVAIL_CMD,
&svc_cmd, sizeof(svc_cmd), &ret_val,
sizeof(ret_val));
if (ret)
return ret;
return le32_to_cpu(ret_val);
}
int __qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt, u32 *resp)
int __qcom_scm_hdcp_req(struct device *dev, struct qcom_scm_hdcp_req *req,
u32 req_cnt, u32 *resp)
{
if (req_cnt > QCOM_SCM_HDCP_MAX_REQ_CNT)
return -ERANGE;
return qcom_scm_call(QCOM_SCM_SVC_HDCP, QCOM_SCM_CMD_HDCP,
return qcom_scm_call(dev, QCOM_SCM_SVC_HDCP, QCOM_SCM_CMD_HDCP,
req, req_cnt * sizeof(*req), resp, sizeof(*resp));
}
void __qcom_scm_init(void)
{
}
bool __qcom_scm_pas_supported(struct device *dev, u32 peripheral)
{
__le32 out;
__le32 in;
int ret;
in = cpu_to_le32(peripheral);
ret = qcom_scm_call(dev, QCOM_SCM_SVC_PIL,
QCOM_SCM_PAS_IS_SUPPORTED_CMD,
&in, sizeof(in),
&out, sizeof(out));
return ret ? false : !!out;
}
int __qcom_scm_pas_init_image(struct device *dev, u32 peripheral,
dma_addr_t metadata_phys)
{
__le32 scm_ret;
int ret;
struct {
__le32 proc;
__le32 image_addr;
} request;
request.proc = cpu_to_le32(peripheral);
request.image_addr = cpu_to_le32(metadata_phys);
ret = qcom_scm_call(dev, QCOM_SCM_SVC_PIL,
QCOM_SCM_PAS_INIT_IMAGE_CMD,
&request, sizeof(request),
&scm_ret, sizeof(scm_ret));
return ret ? : le32_to_cpu(scm_ret);
}
int __qcom_scm_pas_mem_setup(struct device *dev, u32 peripheral,
phys_addr_t addr, phys_addr_t size)
{
__le32 scm_ret;
int ret;
struct {
__le32 proc;
__le32 addr;
__le32 len;
} request;
request.proc = cpu_to_le32(peripheral);
request.addr = cpu_to_le32(addr);
request.len = cpu_to_le32(size);
ret = qcom_scm_call(dev, QCOM_SCM_SVC_PIL,
QCOM_SCM_PAS_MEM_SETUP_CMD,
&request, sizeof(request),
&scm_ret, sizeof(scm_ret));
return ret ? : le32_to_cpu(scm_ret);
}
int __qcom_scm_pas_auth_and_reset(struct device *dev, u32 peripheral)
{
__le32 out;
__le32 in;
int ret;
in = cpu_to_le32(peripheral);
ret = qcom_scm_call(dev, QCOM_SCM_SVC_PIL,
QCOM_SCM_PAS_AUTH_AND_RESET_CMD,
&in, sizeof(in),
&out, sizeof(out));
return ret ? : le32_to_cpu(out);
}
int __qcom_scm_pas_shutdown(struct device *dev, u32 peripheral)
{
__le32 out;
__le32 in;
int ret;
in = cpu_to_le32(peripheral);
ret = qcom_scm_call(dev, QCOM_SCM_SVC_PIL,
QCOM_SCM_PAS_SHUTDOWN_CMD,
&in, sizeof(in),
&out, sizeof(out));
return ret ? : le32_to_cpu(out);
}
int __qcom_scm_pas_mss_reset(struct device *dev, bool reset)
{
__le32 out;
__le32 in = cpu_to_le32(reset);
int ret;
ret = qcom_scm_call(dev, QCOM_SCM_SVC_PIL, QCOM_SCM_PAS_MSS_RESET,
&in, sizeof(in),
&out, sizeof(out));
return ret ? : le32_to_cpu(out);
}

View file

@ -12,7 +12,150 @@
#include <linux/io.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/qcom_scm.h>
#include <linux/arm-smccc.h>
#include <linux/dma-mapping.h>
#include "qcom_scm.h"
#define QCOM_SCM_FNID(s, c) ((((s) & 0xFF) << 8) | ((c) & 0xFF))
#define MAX_QCOM_SCM_ARGS 10
#define MAX_QCOM_SCM_RETS 3
enum qcom_scm_arg_types {
QCOM_SCM_VAL,
QCOM_SCM_RO,
QCOM_SCM_RW,
QCOM_SCM_BUFVAL,
};
#define QCOM_SCM_ARGS_IMPL(num, a, b, c, d, e, f, g, h, i, j, ...) (\
(((a) & 0x3) << 4) | \
(((b) & 0x3) << 6) | \
(((c) & 0x3) << 8) | \
(((d) & 0x3) << 10) | \
(((e) & 0x3) << 12) | \
(((f) & 0x3) << 14) | \
(((g) & 0x3) << 16) | \
(((h) & 0x3) << 18) | \
(((i) & 0x3) << 20) | \
(((j) & 0x3) << 22) | \
((num) & 0xf))
#define QCOM_SCM_ARGS(...) QCOM_SCM_ARGS_IMPL(__VA_ARGS__, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
/**
* struct qcom_scm_desc
* @arginfo: Metadata describing the arguments in args[]
* @args: The array of arguments for the secure syscall
* @res: The values returned by the secure syscall
*/
struct qcom_scm_desc {
u32 arginfo;
u64 args[MAX_QCOM_SCM_ARGS];
};
static u64 qcom_smccc_convention = -1;
static DEFINE_MUTEX(qcom_scm_lock);
#define QCOM_SCM_EBUSY_WAIT_MS 30
#define QCOM_SCM_EBUSY_MAX_RETRY 20
#define N_EXT_QCOM_SCM_ARGS 7
#define FIRST_EXT_ARG_IDX 3
#define N_REGISTER_ARGS (MAX_QCOM_SCM_ARGS - N_EXT_QCOM_SCM_ARGS + 1)
/**
* qcom_scm_call() - Invoke a syscall in the secure world
* @dev: device
* @svc_id: service identifier
* @cmd_id: command identifier
* @desc: Descriptor structure containing arguments and return values
*
* Sends a command to the SCM and waits for the command to finish processing.
* This should *only* be called in pre-emptible context.
*/
static int qcom_scm_call(struct device *dev, u32 svc_id, u32 cmd_id,
const struct qcom_scm_desc *desc,
struct arm_smccc_res *res)
{
int arglen = desc->arginfo & 0xf;
int retry_count = 0, i;
u32 fn_id = QCOM_SCM_FNID(svc_id, cmd_id);
u64 cmd, x5 = desc->args[FIRST_EXT_ARG_IDX];
dma_addr_t args_phys = 0;
void *args_virt = NULL;
size_t alloc_len;
if (unlikely(arglen > N_REGISTER_ARGS)) {
alloc_len = N_EXT_QCOM_SCM_ARGS * sizeof(u64);
args_virt = kzalloc(PAGE_ALIGN(alloc_len), GFP_KERNEL);
if (!args_virt)
return -ENOMEM;
if (qcom_smccc_convention == ARM_SMCCC_SMC_32) {
__le32 *args = args_virt;
for (i = 0; i < N_EXT_QCOM_SCM_ARGS; i++)
args[i] = cpu_to_le32(desc->args[i +
FIRST_EXT_ARG_IDX]);
} else {
__le64 *args = args_virt;
for (i = 0; i < N_EXT_QCOM_SCM_ARGS; i++)
args[i] = cpu_to_le64(desc->args[i +
FIRST_EXT_ARG_IDX]);
}
args_phys = dma_map_single(dev, args_virt, alloc_len,
DMA_TO_DEVICE);
if (dma_mapping_error(dev, args_phys)) {
kfree(args_virt);
return -ENOMEM;
}
x5 = args_phys;
}
do {
mutex_lock(&qcom_scm_lock);
cmd = ARM_SMCCC_CALL_VAL(ARM_SMCCC_STD_CALL,
qcom_smccc_convention,
ARM_SMCCC_OWNER_SIP, fn_id);
do {
arm_smccc_smc(cmd, desc->arginfo, desc->args[0],
desc->args[1], desc->args[2], x5, 0, 0,
res);
} while (res->a0 == QCOM_SCM_INTERRUPTED);
mutex_unlock(&qcom_scm_lock);
if (res->a0 == QCOM_SCM_V2_EBUSY) {
if (retry_count++ > QCOM_SCM_EBUSY_MAX_RETRY)
break;
msleep(QCOM_SCM_EBUSY_WAIT_MS);
}
} while (res->a0 == QCOM_SCM_V2_EBUSY);
if (args_virt) {
dma_unmap_single(dev, args_phys, alloc_len, DMA_TO_DEVICE);
kfree(args_virt);
}
if (res->a0 < 0)
return qcom_scm_remap_error(res->a0);
return 0;
}
/**
* qcom_scm_set_cold_boot_addr() - Set the cold boot address for cpus
@ -29,13 +172,15 @@ int __qcom_scm_set_cold_boot_addr(void *entry, const cpumask_t *cpus)
/**
* qcom_scm_set_warm_boot_addr() - Set the warm boot address for cpus
* @dev: Device pointer
* @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)
int __qcom_scm_set_warm_boot_addr(struct device *dev, void *entry,
const cpumask_t *cpus)
{
return -ENOTSUPP;
}
@ -52,12 +197,164 @@ void __qcom_scm_cpu_power_down(u32 flags)
{
}
int __qcom_scm_is_call_available(u32 svc_id, u32 cmd_id)
int __qcom_scm_is_call_available(struct device *dev, u32 svc_id, u32 cmd_id)
{
return -ENOTSUPP;
int ret;
struct qcom_scm_desc desc = {0};
struct arm_smccc_res res;
desc.arginfo = QCOM_SCM_ARGS(1);
desc.args[0] = QCOM_SCM_FNID(svc_id, cmd_id) |
(ARM_SMCCC_OWNER_SIP << ARM_SMCCC_OWNER_SHIFT);
ret = qcom_scm_call(dev, QCOM_SCM_SVC_INFO, QCOM_IS_CALL_AVAIL_CMD,
&desc, &res);
return ret ? : res.a1;
}
int __qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt, u32 *resp)
int __qcom_scm_hdcp_req(struct device *dev, struct qcom_scm_hdcp_req *req,
u32 req_cnt, u32 *resp)
{
return -ENOTSUPP;
int ret;
struct qcom_scm_desc desc = {0};
struct arm_smccc_res res;
if (req_cnt > QCOM_SCM_HDCP_MAX_REQ_CNT)
return -ERANGE;
desc.args[0] = req[0].addr;
desc.args[1] = req[0].val;
desc.args[2] = req[1].addr;
desc.args[3] = req[1].val;
desc.args[4] = req[2].addr;
desc.args[5] = req[2].val;
desc.args[6] = req[3].addr;
desc.args[7] = req[3].val;
desc.args[8] = req[4].addr;
desc.args[9] = req[4].val;
desc.arginfo = QCOM_SCM_ARGS(10);
ret = qcom_scm_call(dev, QCOM_SCM_SVC_HDCP, QCOM_SCM_CMD_HDCP, &desc,
&res);
*resp = res.a1;
return ret;
}
void __qcom_scm_init(void)
{
u64 cmd;
struct arm_smccc_res res;
u32 function = QCOM_SCM_FNID(QCOM_SCM_SVC_INFO, QCOM_IS_CALL_AVAIL_CMD);
/* First try a SMC64 call */
cmd = ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64,
ARM_SMCCC_OWNER_SIP, function);
arm_smccc_smc(cmd, QCOM_SCM_ARGS(1), cmd & (~BIT(ARM_SMCCC_TYPE_SHIFT)),
0, 0, 0, 0, 0, &res);
if (!res.a0 && res.a1)
qcom_smccc_convention = ARM_SMCCC_SMC_64;
else
qcom_smccc_convention = ARM_SMCCC_SMC_32;
}
bool __qcom_scm_pas_supported(struct device *dev, u32 peripheral)
{
int ret;
struct qcom_scm_desc desc = {0};
struct arm_smccc_res res;
desc.args[0] = peripheral;
desc.arginfo = QCOM_SCM_ARGS(1);
ret = qcom_scm_call(dev, QCOM_SCM_SVC_PIL,
QCOM_SCM_PAS_IS_SUPPORTED_CMD,
&desc, &res);
return ret ? false : !!res.a1;
}
int __qcom_scm_pas_init_image(struct device *dev, u32 peripheral,
dma_addr_t metadata_phys)
{
int ret;
struct qcom_scm_desc desc = {0};
struct arm_smccc_res res;
desc.args[0] = peripheral;
desc.args[1] = metadata_phys;
desc.arginfo = QCOM_SCM_ARGS(2, QCOM_SCM_VAL, QCOM_SCM_RW);
ret = qcom_scm_call(dev, QCOM_SCM_SVC_PIL, QCOM_SCM_PAS_INIT_IMAGE_CMD,
&desc, &res);
return ret ? : res.a1;
}
int __qcom_scm_pas_mem_setup(struct device *dev, u32 peripheral,
phys_addr_t addr, phys_addr_t size)
{
int ret;
struct qcom_scm_desc desc = {0};
struct arm_smccc_res res;
desc.args[0] = peripheral;
desc.args[1] = addr;
desc.args[2] = size;
desc.arginfo = QCOM_SCM_ARGS(3);
ret = qcom_scm_call(dev, QCOM_SCM_SVC_PIL, QCOM_SCM_PAS_MEM_SETUP_CMD,
&desc, &res);
return ret ? : res.a1;
}
int __qcom_scm_pas_auth_and_reset(struct device *dev, u32 peripheral)
{
int ret;
struct qcom_scm_desc desc = {0};
struct arm_smccc_res res;
desc.args[0] = peripheral;
desc.arginfo = QCOM_SCM_ARGS(1);
ret = qcom_scm_call(dev, QCOM_SCM_SVC_PIL,
QCOM_SCM_PAS_AUTH_AND_RESET_CMD,
&desc, &res);
return ret ? : res.a1;
}
int __qcom_scm_pas_shutdown(struct device *dev, u32 peripheral)
{
int ret;
struct qcom_scm_desc desc = {0};
struct arm_smccc_res res;
desc.args[0] = peripheral;
desc.arginfo = QCOM_SCM_ARGS(1);
ret = qcom_scm_call(dev, QCOM_SCM_SVC_PIL, QCOM_SCM_PAS_SHUTDOWN_CMD,
&desc, &res);
return ret ? : res.a1;
}
int __qcom_scm_pas_mss_reset(struct device *dev, bool reset)
{
struct qcom_scm_desc desc = {0};
struct arm_smccc_res res;
int ret;
desc.args[0] = reset;
desc.args[1] = 0;
desc.arginfo = QCOM_SCM_ARGS(2);
ret = qcom_scm_call(dev, QCOM_SCM_SVC_PIL, QCOM_SCM_PAS_MSS_RESET, &desc,
&res);
return ret ? : res.a1;
}

View file

@ -10,19 +10,64 @@
* 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/of_platform.h>
#include <linux/clk.h>
#include <linux/reset-controller.h>
#include "qcom_scm.h"
struct qcom_scm {
struct device *dev;
struct clk *core_clk;
struct clk *iface_clk;
struct clk *bus_clk;
struct reset_controller_dev reset;
};
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
@ -47,7 +92,7 @@ EXPORT_SYMBOL(qcom_scm_set_cold_boot_addr);
*/
int qcom_scm_set_warm_boot_addr(void *entry, const cpumask_t *cpus)
{
return __qcom_scm_set_warm_boot_addr(entry, cpus);
return __qcom_scm_set_warm_boot_addr(__scm->dev, entry, cpus);
}
EXPORT_SYMBOL(qcom_scm_set_warm_boot_addr);
@ -72,12 +117,17 @@ EXPORT_SYMBOL(qcom_scm_cpu_power_down);
*/
bool qcom_scm_hdcp_available(void)
{
int ret;
int ret = qcom_scm_clk_enable();
ret = __qcom_scm_is_call_available(QCOM_SCM_SVC_HDCP,
QCOM_SCM_CMD_HDCP);
if (ret)
return ret;
return (ret > 0) ? true : false;
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);
@ -91,6 +141,287 @@ 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(__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,
};
/**
* 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;
int ret;
scm = devm_kzalloc(&pdev->dev, sizeof(*scm), GFP_KERNEL);
if (!scm)
return -ENOMEM;
scm->core_clk = devm_clk_get(&pdev->dev, "core");
if (IS_ERR(scm->core_clk)) {
if (PTR_ERR(scm->core_clk) == -EPROBE_DEFER)
return PTR_ERR(scm->core_clk);
scm->core_clk = NULL;
}
if (of_device_is_compatible(pdev->dev.of_node, "qcom,scm")) {
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);
}
}
scm->reset.ops = &qcom_scm_pas_reset_ops;
scm->reset.nr_resets = 1;
scm->reset.of_node = pdev->dev.of_node;
reset_controller_register(&scm->reset);
/* 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();
return 0;
}
static const struct of_device_id qcom_scm_dt_match[] = {
{ .compatible = "qcom,scm-apq8064",},
{ .compatible = "qcom,scm-msm8660",},
{ .compatible = "qcom,scm-msm8960",},
{ .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,
};
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);
static void __exit qcom_scm_exit(void)
{
platform_driver_unregister(&qcom_scm_driver);
}
module_exit(qcom_scm_exit);
MODULE_DESCRIPTION("Qualcomm SCM driver");
MODULE_LICENSE("GPL v2");

View file

@ -19,7 +19,8 @@
#define QCOM_SCM_FLAG_HLOS 0x01
#define QCOM_SCM_FLAG_COLDBOOT_MC 0x02
#define QCOM_SCM_FLAG_WARMBOOT_MC 0x04
extern int __qcom_scm_set_warm_boot_addr(void *entry, const cpumask_t *cpus);
extern int __qcom_scm_set_warm_boot_addr(struct device *dev, void *entry,
const cpumask_t *cpus);
extern int __qcom_scm_set_cold_boot_addr(void *entry, const cpumask_t *cpus);
#define QCOM_SCM_CMD_TERMINATE_PC 0x2
@ -29,14 +30,34 @@ extern void __qcom_scm_cpu_power_down(u32 flags);
#define QCOM_SCM_SVC_INFO 0x6
#define QCOM_IS_CALL_AVAIL_CMD 0x1
extern int __qcom_scm_is_call_available(u32 svc_id, u32 cmd_id);
extern int __qcom_scm_is_call_available(struct device *dev, u32 svc_id,
u32 cmd_id);
#define QCOM_SCM_SVC_HDCP 0x11
#define QCOM_SCM_CMD_HDCP 0x01
extern int __qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt,
u32 *resp);
extern int __qcom_scm_hdcp_req(struct device *dev,
struct qcom_scm_hdcp_req *req, u32 req_cnt, u32 *resp);
extern void __qcom_scm_init(void);
#define QCOM_SCM_SVC_PIL 0x2
#define QCOM_SCM_PAS_INIT_IMAGE_CMD 0x1
#define QCOM_SCM_PAS_MEM_SETUP_CMD 0x2
#define QCOM_SCM_PAS_AUTH_AND_RESET_CMD 0x5
#define QCOM_SCM_PAS_SHUTDOWN_CMD 0x6
#define QCOM_SCM_PAS_IS_SUPPORTED_CMD 0x7
#define QCOM_SCM_PAS_MSS_RESET 0xa
extern bool __qcom_scm_pas_supported(struct device *dev, u32 peripheral);
extern int __qcom_scm_pas_init_image(struct device *dev, u32 peripheral,
dma_addr_t metadata_phys);
extern int __qcom_scm_pas_mem_setup(struct device *dev, u32 peripheral,
phys_addr_t addr, phys_addr_t size);
extern int __qcom_scm_pas_auth_and_reset(struct device *dev, u32 peripheral);
extern int __qcom_scm_pas_shutdown(struct device *dev, u32 peripheral);
extern int __qcom_scm_pas_mss_reset(struct device *dev, bool reset);
/* common error codes */
#define QCOM_SCM_V2_EBUSY -12
#define QCOM_SCM_ENOMEM -5
#define QCOM_SCM_EOPNOTSUPP -4
#define QCOM_SCM_EINVAL_ADDR -3
@ -44,4 +65,22 @@ extern int __qcom_scm_hdcp_req(struct qcom_scm_hdcp_req *req, u32 req_cnt,
#define QCOM_SCM_ERROR -1
#define QCOM_SCM_INTERRUPTED 1
static inline int qcom_scm_remap_error(int err)
{
switch (err) {
case QCOM_SCM_ERROR:
return -EIO;
case QCOM_SCM_EINVAL_ADDR:
case QCOM_SCM_EINVAL_ARG:
return -EINVAL;
case QCOM_SCM_EOPNOTSUPP:
return -EOPNOTSUPP;
case QCOM_SCM_ENOMEM:
return -ENOMEM;
case QCOM_SCM_V2_EBUSY:
return -EBUSY;
}
return -EINVAL;
}
#endif

View file

@ -0,0 +1,163 @@
/*
* SCPI Generic power domain support.
*
* Copyright (C) 2016 ARM Ltd.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/pm_domain.h>
#include <linux/scpi_protocol.h>
struct scpi_pm_domain {
struct generic_pm_domain genpd;
struct scpi_ops *ops;
u32 domain;
char name[30];
};
/*
* These device power state values are not well-defined in the specification.
* In case, different implementations use different values, we can make these
* specific to compatibles rather than getting these values from device tree.
*/
enum scpi_power_domain_state {
SCPI_PD_STATE_ON = 0,
SCPI_PD_STATE_OFF = 3,
};
#define to_scpi_pd(gpd) container_of(gpd, struct scpi_pm_domain, genpd)
static int scpi_pd_power(struct scpi_pm_domain *pd, bool power_on)
{
int ret;
enum scpi_power_domain_state state;
if (power_on)
state = SCPI_PD_STATE_ON;
else
state = SCPI_PD_STATE_OFF;
ret = pd->ops->device_set_power_state(pd->domain, state);
if (ret)
return ret;
return !(state == pd->ops->device_get_power_state(pd->domain));
}
static int scpi_pd_power_on(struct generic_pm_domain *domain)
{
struct scpi_pm_domain *pd = to_scpi_pd(domain);
return scpi_pd_power(pd, true);
}
static int scpi_pd_power_off(struct generic_pm_domain *domain)
{
struct scpi_pm_domain *pd = to_scpi_pd(domain);
return scpi_pd_power(pd, false);
}
static int scpi_pm_domain_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct scpi_pm_domain *scpi_pd;
struct genpd_onecell_data *scpi_pd_data;
struct generic_pm_domain **domains;
struct scpi_ops *scpi_ops;
int ret, num_domains, i;
scpi_ops = get_scpi_ops();
if (!scpi_ops)
return -EPROBE_DEFER;
if (!np) {
dev_err(dev, "device tree node not found\n");
return -ENODEV;
}
if (!scpi_ops->device_set_power_state ||
!scpi_ops->device_get_power_state) {
dev_err(dev, "power domains not supported in the firmware\n");
return -ENODEV;
}
ret = of_property_read_u32(np, "num-domains", &num_domains);
if (ret) {
dev_err(dev, "number of domains not found\n");
return -EINVAL;
}
scpi_pd = devm_kcalloc(dev, num_domains, sizeof(*scpi_pd), GFP_KERNEL);
if (!scpi_pd)
return -ENOMEM;
scpi_pd_data = devm_kzalloc(dev, sizeof(*scpi_pd_data), GFP_KERNEL);
if (!scpi_pd_data)
return -ENOMEM;
domains = devm_kcalloc(dev, num_domains, sizeof(*domains), GFP_KERNEL);
if (!domains)
return -ENOMEM;
for (i = 0; i < num_domains; i++, scpi_pd++) {
domains[i] = &scpi_pd->genpd;
scpi_pd->domain = i;
scpi_pd->ops = scpi_ops;
sprintf(scpi_pd->name, "%s.%d", np->name, i);
scpi_pd->genpd.name = scpi_pd->name;
scpi_pd->genpd.power_off = scpi_pd_power_off;
scpi_pd->genpd.power_on = scpi_pd_power_on;
/*
* Treat all power domains as off at boot.
*
* The SCP firmware itself may have switched on some domains,
* but for reference counting purpose, keep it this way.
*/
pm_genpd_init(&scpi_pd->genpd, NULL, true);
}
scpi_pd_data->domains = domains;
scpi_pd_data->num_domains = num_domains;
of_genpd_add_provider_onecell(np, scpi_pd_data);
return 0;
}
static const struct of_device_id scpi_power_domain_ids[] = {
{ .compatible = "arm,scpi-power-domains", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, scpi_power_domain_ids);
static struct platform_driver scpi_power_domain_driver = {
.driver = {
.name = "scpi_power_domain",
.of_match_table = scpi_power_domain_ids,
},
.probe = scpi_pm_domain_probe,
};
module_platform_driver(scpi_power_domain_driver);
MODULE_AUTHOR("Sudeep Holla <[email protected]>");
MODULE_DESCRIPTION("ARM SCPI power domain driver");
MODULE_LICENSE("GPL v2");