431 lines
10 KiB
C
Executable File
431 lines
10 KiB
C
Executable File
/*
|
|
* Copyright (c) 2015 Samsung Electronics Co., Ltd.
|
|
* http://www.samsung.com
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/debug-snapshot.h>
|
|
#include <linux/soc/samsung/exynos-soc.h>
|
|
#include <linux/sched/clock.h>
|
|
|
|
#include "acpm.h"
|
|
#include "acpm_ipc.h"
|
|
#include "../cal-if/fvmap.h"
|
|
#include "fw_header/framework.h"
|
|
|
|
static int ipc_done;
|
|
static unsigned long long ipc_time_start;
|
|
static unsigned long long ipc_time_end;
|
|
|
|
static struct acpm_info *exynos_acpm;
|
|
|
|
static int acpm_send_data(struct device_node *node, unsigned int check_id,
|
|
struct ipc_config *config);
|
|
|
|
static int handle_dynamic_plugin(struct device_node *node, unsigned int id, unsigned int attach)
|
|
{
|
|
struct ipc_config config;
|
|
unsigned int cmd[4] = {0, };
|
|
int ret = 0;
|
|
|
|
config.cmd = cmd;
|
|
if (attach)
|
|
config.cmd[0] = (1 << ACPM_IPC_PROTOCOL_DP_A);
|
|
else
|
|
config.cmd[0] = (1 << ACPM_IPC_PROTOCOL_DP_D);
|
|
|
|
config.cmd[0] |= id << ACPM_IPC_PROTOCOL_ID;
|
|
|
|
config.response = true;
|
|
config.indirection = false;
|
|
|
|
ret = acpm_send_data(node, id, &config);
|
|
|
|
config.cmd = NULL;
|
|
|
|
if (!ret) {
|
|
if (attach)
|
|
pr_info("[ACPM] plugin(id = %d) attach done!\n", id);
|
|
else
|
|
pr_info("[ACPM] plugin(id = %d) detach done!\n", id);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void firmware_load(void *base, const char *firmware, int size)
|
|
{
|
|
memcpy(base, firmware, size);
|
|
}
|
|
|
|
static int firmware_update(struct device *dev, void *fw_base, const char *fw_name)
|
|
{
|
|
const struct firmware *fw_entry = NULL;
|
|
int err;
|
|
|
|
dev_info(dev, "Loading %s firmware ... ", fw_name);
|
|
err = request_firmware(&fw_entry, fw_name, dev);
|
|
if (err < 0) {
|
|
dev_err(dev, "firmware request FAIL \n");
|
|
return err;
|
|
}
|
|
|
|
firmware_load(fw_base, fw_entry->data, fw_entry->size);
|
|
|
|
dev_info(dev, "OK \n");
|
|
|
|
release_firmware(fw_entry);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int plugins_init(void)
|
|
{
|
|
struct plugin *plugins;
|
|
int i, len, ret = 0;
|
|
unsigned int plugin_id;
|
|
char name[50];
|
|
const char *fw_name = NULL;
|
|
void __iomem *fw_base_addr = 0, *dvfs_base_addr = 0;
|
|
struct device_node *node, *child;
|
|
const __be32 *prop;
|
|
unsigned int offset = 0;
|
|
|
|
plugins = (struct plugin *)(acpm_srambase + acpm_initdata->plugins);
|
|
|
|
for (i = 0; i < acpm_initdata->num_plugins; i++) {
|
|
if ((plugins[i].is_attached == 0 && plugins[i].stay_attached == 1) ||
|
|
(plugins[i].is_attached == 1 && plugins[i].stay_attached == 0)) {
|
|
|
|
if (plugins[i].is_attached == 0 && plugins[i].stay_attached == 1) {
|
|
fw_name = (const char *)(acpm_srambase + plugins[i].fw_name);
|
|
if (!plugins[i].fw_name) {
|
|
dev_err(exynos_acpm->dev, "fw_name missing on plugins %d.\n", plugins[i].id);
|
|
return -ENOENT;
|
|
}
|
|
|
|
fw_base_addr = acpm_srambase + (plugins[i].base_addr & ~0x1);
|
|
if (!plugins[i].base_addr) {
|
|
dev_err(exynos_acpm->dev, "base_addr missing on plugins %d.\n", plugins[i].id);
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* Override fw_name from acpm firmware if it is described on DT. */
|
|
node = of_get_child_by_name(exynos_acpm->dev->of_node, "dynamic-plugins");
|
|
for_each_child_of_node(node, child) {
|
|
prop = of_get_property(child, "plugin-id", &len);
|
|
if (!prop)
|
|
continue;
|
|
plugin_id = be32_to_cpup(prop);
|
|
|
|
if (plugin_id != i)
|
|
continue;
|
|
|
|
if (of_property_read_string(child, "fw-name", &fw_name)) {
|
|
dev_err(exynos_acpm->dev, "fw name of plugin %d is invalid in DT.\n", i);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
strncpy(name, fw_name, 50);
|
|
name[49]= 0;
|
|
|
|
firmware_update(exynos_acpm->dev, fw_base_addr, name);
|
|
}
|
|
|
|
ret = handle_dynamic_plugin(exynos_acpm->dev->of_node, plugins[i].id, plugins[i].stay_attached);
|
|
if (ret < 0)
|
|
dev_err(exynos_acpm->dev, "plugin attach/detach:%u fail! plugin_id:%d, ret:%d",
|
|
plugins[i].stay_attached, plugins[i].id, ret);
|
|
|
|
} else if (plugins[i].is_attached == 1 && plugins[i].stay_attached == 1) {
|
|
fw_name = (const char *)(acpm_srambase + plugins[i].fw_name);
|
|
|
|
if (plugins[i].fw_name && fw_name &&
|
|
(strstr(fw_name, "DVFS") || strstr(fw_name, "dvfs"))) {
|
|
|
|
dvfs_base_addr = acpm_srambase + (plugins[i].base_addr & ~0x1);
|
|
prop = of_get_property(exynos_acpm->dev->of_node, "fvmap_offset", &len);
|
|
if (prop)
|
|
offset = be32_to_cpup(prop);
|
|
}
|
|
}
|
|
}
|
|
|
|
fvmap_init(dvfs_base_addr + offset);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int debug_log_level_get(void *data, unsigned long long *val)
|
|
{
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int debug_log_level_set(void *data, unsigned long long val)
|
|
{
|
|
acpm_fw_log_level((unsigned int)val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int debug_ipc_loopback_test_get(void *data, unsigned long long *val)
|
|
{
|
|
struct ipc_config config;
|
|
int ret = 0;
|
|
unsigned int cmd[4] = {0, };
|
|
|
|
config.cmd = cmd;
|
|
config.cmd[0] = (1 << ACPM_IPC_PROTOCOL_TEST);
|
|
config.cmd[0] |= 0x3 << ACPM_IPC_PROTOCOL_ID;
|
|
|
|
config.response = true;
|
|
config.indirection = false;
|
|
|
|
ret = acpm_send_data(exynos_acpm->dev->of_node, 3, &config);
|
|
|
|
if (!ret)
|
|
*val = ipc_time_end - ipc_time_start;
|
|
else
|
|
*val = 0;
|
|
|
|
config.cmd = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int debug_ipc_loopback_test_set(void *data, unsigned long long val)
|
|
{
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int debug_mifdn_count_get(void *data, unsigned long long *val)
|
|
{
|
|
*val = acpm_get_mifdn_count();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int debug_mifdn_count_set(void *data, unsigned long long val)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_SIMPLE_ATTRIBUTE(debug_log_level_fops,
|
|
debug_log_level_get, debug_log_level_set, "%llu\n");
|
|
DEFINE_SIMPLE_ATTRIBUTE(debug_ipc_loopback_test_fops,
|
|
debug_ipc_loopback_test_get, debug_ipc_loopback_test_set, "%llu\n");
|
|
DEFINE_SIMPLE_ATTRIBUTE(debug_mifdn_count_fops,
|
|
debug_mifdn_count_get, debug_mifdn_count_set, "%llu\n");
|
|
|
|
static void acpm_debugfs_init(struct acpm_info *acpm)
|
|
{
|
|
struct dentry *den;
|
|
|
|
den = debugfs_create_dir("acpm_framework", NULL);
|
|
debugfs_create_file("ipc_loopback_test", 0644, den, acpm, &debug_ipc_loopback_test_fops);
|
|
debugfs_create_file("log_level", 0644, den, NULL, &debug_log_level_fops);
|
|
debugfs_create_file("mifdn_count", 0644, den, NULL, &debug_mifdn_count_fops);
|
|
}
|
|
|
|
void *memcpy_align_4(void *dest, const void *src, unsigned int n)
|
|
{
|
|
unsigned int *dp = dest;
|
|
const unsigned int *sp = src;
|
|
int i;
|
|
|
|
if ((n % 4))
|
|
BUG();
|
|
|
|
n = n >> 2;
|
|
|
|
for (i = 0; i < n; i++)
|
|
*dp++ = *sp++;
|
|
|
|
return dest;
|
|
}
|
|
|
|
void acpm_enter_wfi(void)
|
|
{
|
|
struct ipc_config config;
|
|
int ret = 0;
|
|
unsigned int cmd[4] = {0, };
|
|
|
|
if (exynos_acpm->enter_wfi)
|
|
return;
|
|
|
|
config.cmd = cmd;
|
|
config.response = true;
|
|
config.indirection = false;
|
|
config.cmd[0] = 1 << ACPM_IPC_PROTOCOL_STOP;
|
|
|
|
ret = acpm_send_data(exynos_acpm->dev->of_node, ACPM_IPC_PROTOCOL_STOP, &config);
|
|
|
|
config.cmd = NULL;
|
|
|
|
if (ret) {
|
|
pr_err("[ACPM] acpm enter wfi fail!!\n");
|
|
} else {
|
|
pr_err("[ACPM] wfi done\n");
|
|
exynos_acpm->enter_wfi++;
|
|
}
|
|
}
|
|
|
|
void exynos_acpm_timer_clear(void)
|
|
{
|
|
writel(exynos_acpm->timer_cnt, exynos_acpm->timer_base + EXYNOS_TIMER_APM_TCVR);
|
|
}
|
|
|
|
void exynos_acpm_reboot(void)
|
|
{
|
|
u32 soc_id, revision;
|
|
|
|
acpm_ipc_set_waiting_mode(BUSY_WAIT);
|
|
|
|
soc_id = exynos_soc_info.product_id;
|
|
revision = exynos_soc_info.revision;
|
|
|
|
if (!(soc_id == EXYNOS9810_SOC_ID && revision < EXYNOS_MAIN_REV_1))
|
|
acpm_enter_wfi();
|
|
}
|
|
|
|
static int acpm_send_data(struct device_node *node, unsigned int check_id,
|
|
struct ipc_config *config)
|
|
{
|
|
unsigned int channel_num, size;
|
|
int ret = 0;
|
|
int timeout_flag;
|
|
unsigned int id = 0;
|
|
|
|
if (!acpm_ipc_request_channel(node, NULL,
|
|
&channel_num, &size)) {
|
|
ipc_done = -1;
|
|
|
|
ipc_time_start = sched_clock();
|
|
ret = acpm_ipc_send_data(channel_num, config);
|
|
|
|
if (config->cmd[0] & ACPM_IPC_PROTOCOL_DP_CMD) {
|
|
id = config->cmd[0] & ACPM_IPC_PROTOCOL_IDX;
|
|
id = id >> ACPM_IPC_PROTOCOL_ID;
|
|
ipc_done = id;
|
|
} else if (config->cmd[0] & (1 << ACPM_IPC_PROTOCOL_STOP)) {
|
|
ipc_done = ACPM_IPC_PROTOCOL_STOP;
|
|
} else if (config->cmd[0] & (1 << ACPM_IPC_PROTOCOL_TEST)) {
|
|
id = config->cmd[0] & ACPM_IPC_PROTOCOL_IDX;
|
|
id = id >> ACPM_IPC_PROTOCOL_ID;
|
|
ipc_done = id;
|
|
} else {
|
|
id = config->cmd[0] & ACPM_IPC_PROTOCOL_IDX;
|
|
id = id >> ACPM_IPC_PROTOCOL_ID;
|
|
ipc_done = id;
|
|
}
|
|
|
|
/* Response interrupt waiting */
|
|
UNTIL_EQUAL(ipc_done, check_id, timeout_flag);
|
|
|
|
if (timeout_flag)
|
|
ret = -ETIMEDOUT;
|
|
|
|
acpm_ipc_release_channel(node, channel_num);
|
|
} else {
|
|
pr_err("%s ipc request_channel fail, id:%u, size:%u\n",
|
|
__func__, channel_num, size);
|
|
ret = -EBUSY;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int acpm_probe(struct platform_device *pdev)
|
|
{
|
|
struct acpm_info *acpm;
|
|
struct device_node *node = pdev->dev.of_node;
|
|
struct resource *res;
|
|
int ret = 0;
|
|
|
|
dev_info(&pdev->dev, "acpm probe\n");
|
|
|
|
if (!node) {
|
|
dev_err(&pdev->dev, "driver doesnt support"
|
|
"non-dt devices\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
acpm = devm_kzalloc(&pdev->dev,
|
|
sizeof(struct acpm_info), GFP_KERNEL);
|
|
if (IS_ERR(acpm))
|
|
return PTR_ERR(acpm);
|
|
if (!acpm)
|
|
return -ENOMEM;
|
|
|
|
acpm->dev = &pdev->dev;
|
|
|
|
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "timer_apm");
|
|
acpm->timer_base = devm_ioremap_resource(&pdev->dev, res);
|
|
if (IS_ERR(acpm->timer_base))
|
|
dev_info(&pdev->dev, "could not find timer base addr \n");
|
|
|
|
if (of_property_read_u32(node, "peritimer-cnt", &acpm->timer_cnt))
|
|
pr_warn("No matching property: peritiemr_cnt\n");
|
|
|
|
exynos_acpm = acpm;
|
|
|
|
acpm_debugfs_init(acpm);
|
|
|
|
exynos_acpm_timer_clear();
|
|
return ret;
|
|
}
|
|
|
|
static int acpm_remove(struct platform_device *pdev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id acpm_match[] = {
|
|
{ .compatible = "samsung,exynos-acpm" },
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver samsung_acpm_driver = {
|
|
.probe = acpm_probe,
|
|
.remove = acpm_remove,
|
|
.driver = {
|
|
.name = "exynos-acpm",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = acpm_match,
|
|
},
|
|
};
|
|
|
|
static int __init exynos_acpm_init(void)
|
|
{
|
|
return platform_driver_register(&samsung_acpm_driver);
|
|
}
|
|
arch_initcall_sync(exynos_acpm_init);
|
|
|
|
static int __init exynos_acpm_binary_update(void)
|
|
{
|
|
int ret;
|
|
|
|
acpm_ipc_set_waiting_mode(BUSY_WAIT);
|
|
|
|
ret = plugins_init();
|
|
|
|
return ret;
|
|
}
|
|
fs_initcall_sync(exynos_acpm_binary_update);
|