mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-28 02:32:09 -04:00
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:
commit
43a0a98aa8
140 changed files with 4776 additions and 2641 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
163
drivers/firmware/scpi_pm_domain.c
Normal file
163
drivers/firmware/scpi_pm_domain.c
Normal 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");
|
||||
Loading…
Reference in a new issue