From f98abeb2ec6657acb08750024e4ac22c1bae2fbf Mon Sep 17 00:00:00 2001 From: yi jaeuk Date: Fri, 4 May 2018 14:24:42 +0900 Subject: [PATCH] [9610] drivers: soc: introduce ACPM driver Change-Id: I74dca6a17cf3d2b1189371d9c9d8c5a52e9bd70b Signed-off-by: yi jaeuk --- drivers/soc/samsung/acpm/Kconfig | 59 ++ drivers/soc/samsung/acpm/Makefile | 7 + drivers/soc/samsung/acpm/acpm.c | 425 ++++++++ drivers/soc/samsung/acpm/acpm.h | 29 + drivers/soc/samsung/acpm/acpm_ipc.c | 905 ++++++++++++++++++ drivers/soc/samsung/acpm/acpm_ipc.h | 140 +++ drivers/soc/samsung/acpm/acpm_mfd.c | 253 +++++ drivers/soc/samsung/acpm/acpm_s2d.c | 192 ++++ drivers/soc/samsung/acpm/fw_header/common.h | 163 ++++ .../soc/samsung/acpm/fw_header/framework.h | 280 ++++++ include/soc/samsung/acpm_ipc_ctrl.h | 74 ++ include/soc/samsung/acpm_mfd.h | 84 ++ 12 files changed, 2611 insertions(+) create mode 100644 drivers/soc/samsung/acpm/Kconfig create mode 100644 drivers/soc/samsung/acpm/Makefile create mode 100644 drivers/soc/samsung/acpm/acpm.c create mode 100644 drivers/soc/samsung/acpm/acpm.h create mode 100644 drivers/soc/samsung/acpm/acpm_ipc.c create mode 100644 drivers/soc/samsung/acpm/acpm_ipc.h create mode 100644 drivers/soc/samsung/acpm/acpm_mfd.c create mode 100644 drivers/soc/samsung/acpm/acpm_s2d.c create mode 100644 drivers/soc/samsung/acpm/fw_header/common.h create mode 100644 drivers/soc/samsung/acpm/fw_header/framework.h create mode 100644 include/soc/samsung/acpm_ipc_ctrl.h create mode 100644 include/soc/samsung/acpm_mfd.h diff --git a/drivers/soc/samsung/acpm/Kconfig b/drivers/soc/samsung/acpm/Kconfig new file mode 100644 index 000000000000..4752c97930cf --- /dev/null +++ b/drivers/soc/samsung/acpm/Kconfig @@ -0,0 +1,59 @@ +# +# ACPM driver configuration +# + +menuconfig EXYNOS_ACPM + bool "ACPM driver support" + default n + help + Enable ACPM support + +if EXYNOS_ACPM + +config EXYNOS7570_ACPM + bool "EXYNOS7570_ACPM support" + default y + depends on SOC_EXYNOS7570 + help + Enable ACPM support + +config EXYNOS7270_ACPM + bool "EXYNOS7270_ACPM support" + default y + depends on SOC_EXYNOS7270 + help + Enable ACPM support + +config EXYNOS8895_ACPM + bool "EXYNOS8895_ACPM support" + default y + depends on SOC_EXYNOS8895 + help + Enable ACPM support + +config EXYNOS9810_ACPM + bool "EXYNOS9810_ACPM support" + default y + depends on SOC_EXYNOS9810 + help + Enable ACPM support + +config EXYNOS9610_ACPM + bool "EXYNOS9610_ACPM support" + default y + depends on SOC_EXYNOS9610 + help + Enable ACPM support + +config EXYNOS_ACPM_MFD + bool "EXYNOS_ACPM_MFD support" + default y + help + Enable ACPM_MFD support + +config EXYNOS_ACPM_S2D + bool "EXYNOS_ACPM_S2D support" + help + Enable ACPM_S2D support + +endif diff --git a/drivers/soc/samsung/acpm/Makefile b/drivers/soc/samsung/acpm/Makefile new file mode 100644 index 000000000000..2740ba8e3cf8 --- /dev/null +++ b/drivers/soc/samsung/acpm/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for ACPM. +# + +obj-$(CONFIG_EXYNOS_ACPM) += acpm.o acpm_ipc.o +obj-$(CONFIG_EXYNOS_ACPM_MFD) += acpm_mfd.o +obj-$(CONFIG_EXYNOS_ACPM_S2D) += acpm_s2d.o diff --git a/drivers/soc/samsung/acpm/acpm.c b/drivers/soc/samsung/acpm/acpm.c new file mode 100644 index 000000000000..c24607883982 --- /dev/null +++ b/drivers/soc/samsung/acpm/acpm.c @@ -0,0 +1,425 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 || !fw_entry) { + 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, }; + + 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"); +} + +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); diff --git a/drivers/soc/samsung/acpm/acpm.h b/drivers/soc/samsung/acpm/acpm.h new file mode 100644 index 000000000000..e69617e48fde --- /dev/null +++ b/drivers/soc/samsung/acpm/acpm.h @@ -0,0 +1,29 @@ +#ifndef __ACPM_H__ +#define __ACPM_H__ + +struct acpm_info { + unsigned int plugin_num; + struct device *dev; + void __iomem *timer_base; + unsigned int timer_cnt; +}; + +extern void *memcpy_align_4(void *dest, const void *src, unsigned int n); + +#define EXYNOS_PMU_CORTEX_APM_CONFIGURATION (0x0100) +#define EXYNOS_PMU_CORTEX_APM_STATUS (0x0104) +#define EXYNOS_PMU_CORTEX_APM_OPTION (0x0108) +#define EXYNOS_PMU_CORTEX_APM_DURATION0 (0x0110) +#define EXYNOS_PMU_CORTEX_APM_DURATION1 (0x0114) +#define EXYNOS_PMU_CORTEX_APM_DURATION2 (0x0118) +#define EXYNOS_PMU_CORTEX_APM_DURATION3 (0x011C) + +#define EXYNOS_TIMER_APM_TCVR (0x0008) + +#define APM_LOCAL_PWR_CFG_RESET (~(0x1 << 0)) + +extern struct acpm_framework *acpm_initdata; +extern void __iomem *acpm_srambase; +extern void exynos_acpm_timer_clear(void); + +#endif diff --git a/drivers/soc/samsung/acpm/acpm_ipc.c b/drivers/soc/samsung/acpm/acpm_ipc.c new file mode 100644 index 000000000000..19026c93b5fd --- /dev/null +++ b/drivers/soc/samsung/acpm/acpm_ipc.c @@ -0,0 +1,905 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "acpm.h" +#include "acpm_ipc.h" +#include "fw_header/framework.h" + +static struct acpm_ipc_info *acpm_ipc; +static struct workqueue_struct *debug_logging_wq; +static struct workqueue_struct *update_log_wq; +static struct acpm_debug_info *acpm_debug; +static bool is_acpm_stop_log = false; +static bool acpm_stop_log_req = false; +struct acpm_framework *acpm_initdata; +void __iomem *acpm_srambase; +struct regulator_ss_info regulator_ss[REGULATOR_SS_MAX]; +char reg_map[0x100] = {0}; +bool is_set_regmap = false; +u32 acpm_reg_id = REGULATOR_INFO_ID; + +void acpm_ipc_set_waiting_mode(bool mode) +{ + acpm_ipc->w_mode = mode; +} + +void acpm_fw_log_level(unsigned int on) +{ + acpm_debug->debug_log_level = on; +} + +u32 acpm_get_mifdn_count(void) +{ + return acpm_initdata->mifdn_count; +} + +void acpm_get_inform(void) +{ + int i; + u32 user; + u32 time, start, end; + u32 hour, min, sec; + + for (i = 0; i < 6; i++) { + user = acpm_initdata->inform0[i]; + time = acpm_initdata->inform1[i]; + start = acpm_initdata->inform2[i]; + end = acpm_initdata->inform3[i]; + hour = ((time & (0xff << 24)) >> 24) & 0x3f; + min = ((time & (0xff << 16)) >> 16) & 0x7f; + sec = ((time & (0xff << 8)) >> 8) & 0x7f; + if (user) + pr_info("\t%s: mifuser: 0x%x, time: %d:%d:%d, latency: %d[usec]\n", + "EXYNOS-PM", user, hour, min, sec, (start - end) * 41 / 1000); + } + + for (i = 0; i < 6; i++) { + acpm_initdata->inform_head = 0; + acpm_initdata->inform0[i] = 0; + acpm_initdata->inform1[i] = 0; + acpm_initdata->inform2[i] = 0; + acpm_initdata->inform3[i] = 0; + } +} + +void acpm_ramdump(void) +{ +#ifdef CONFIG_EXYNOS_SNAPSHOT_ACPM + if (acpm_debug->dump_size) + memcpy(acpm_debug->dump_dram_base, acpm_debug->dump_base, acpm_debug->dump_size); +#endif +} + +void timestamp_write(void) +{ + unsigned int tmp_index; + if (spin_trylock(&acpm_debug->lock)) { + tmp_index = __raw_readl(acpm_debug->time_index); + + tmp_index++; + + if (tmp_index == acpm_debug->num_timestamps) + tmp_index = 0; + + acpm_debug->timestamps[tmp_index] = sched_clock(); + + __raw_writel(tmp_index, acpm_debug->time_index); + exynos_acpm_timer_clear(); + + spin_unlock(&acpm_debug->lock); + } +} + +struct regulator_ss_info *get_regulator_ss(int n) +{ + if (n < REGULATOR_SS_MAX) + return ®ulator_ss[n]; + else + return NULL; +} + +void set_reg_map(void) +{ + u32 idx; + int i; + + for (i = 0; i < REGULATOR_SS_MAX; i++) { + idx = regulator_ss[i].vsel_reg & 0xFF; + if (idx == 0) + continue; + + is_set_regmap = true; + + if (reg_map[idx] != 0) + pr_err("duplicated set_reg_map [%d] reg_map %x\n", i, reg_map[idx]); + + reg_map[idx] = i + 1; + } +} + +unsigned int get_reg_id(unsigned int addr) +{ + int id; + + if (addr >> 8 != 0x1) + return NO_SS_RANGE; + + id = reg_map[addr & 0xff]; + if (id != 0) + return id - 1; + + return NO_SS_RANGE; +} + +unsigned int get_reg_voltage(struct regulator_ss_info reg_info, unsigned int selector) +{ + if (reg_info.name[0] == 'L') + selector = selector & 0x3F; + + return reg_info.min_uV + (reg_info.uV_step * (selector - reg_info.linear_min_sel)); +} + +void acpm_log_print(void) +{ + unsigned int front; + unsigned int rear; + unsigned int id; + unsigned int index; + unsigned int count; + unsigned char str[9] = {0,}; + unsigned int val; + unsigned int log_header; + unsigned long long time; + unsigned int log_level; + unsigned int reg_id; + + if (is_acpm_stop_log) + return ; + /* ACPM Log data dequeue & print */ + front = __raw_readl(acpm_debug->log_buff_front); + rear = __raw_readl(acpm_debug->log_buff_rear); + + while (rear != front) { + log_header = __raw_readl(acpm_debug->log_buff_base + acpm_debug->log_buff_size * rear); + + /* log header information + * id: [31:28] + * log level : [27] + * index: [26:22] + * apm systick count: [15:0] + */ + id = (log_header & (0xF << LOG_ID_SHIFT)) >> LOG_ID_SHIFT; + log_level = (log_header & (0x1 << LOG_LEVEL)) >> LOG_LEVEL; + index = (log_header & (0x1f << LOG_TIME_INDEX)) >> LOG_TIME_INDEX; + count = log_header & 0xffff; + + /* string length: log_buff_size - header(4) - integer_data(4) */ + memcpy_align_4(str, acpm_debug->log_buff_base + (acpm_debug->log_buff_size * rear) + 4, + acpm_debug->log_buff_size - 8); + + val = __raw_readl(acpm_debug->log_buff_base + acpm_debug->log_buff_size * rear + + acpm_debug->log_buff_size - 4); + + time = acpm_debug->timestamps[index]; + + /* peritimer period: (1 * 256) / 24.576MHz*/ + time += count * APM_PERITIMER_NS_PERIOD; + + /* addr : [19:8], val : [7:0]*/ + if (id == acpm_reg_id) { + if (is_set_regmap == false) + set_reg_map(); + + if (is_set_regmap == false) + reg_id = NO_SET_REGMAP; + else + reg_id = get_reg_id(val >> 12); + + if (reg_id == NO_SS_RANGE) + exynos_ss_regulator(time, "outSc", val >> 12, (val >> 4) & 0xFF, (val >> 4) & 0xFF, val & 0xF); + else if (reg_id == NO_SET_REGMAP) + exynos_ss_regulator(time, "noMap", val >> 12, (val >> 4) & 0xFF, (val >> 4) & 0xFF, val & 0xF); + else + exynos_ss_regulator(time, regulator_ss[reg_id].name, val >> 12, + get_reg_voltage(regulator_ss[reg_id], (val >> 4) & 0xFF), + (val >> 4) & 0xFF, + val & 0xF); + } + exynos_ss_acpm(time, str, val); + + if (acpm_debug->debug_log_level == 1 || !log_level) + pr_info("[ACPM_FW] : %llu id:%u, %s, %x\n", time, id, str, val); + + if (acpm_debug->log_buff_len == (rear + 1)) + rear = 0; + else + rear++; + + __raw_writel(rear, acpm_debug->log_buff_rear); + front = __raw_readl(acpm_debug->log_buff_front); + } + + if (acpm_stop_log_req) { + is_acpm_stop_log = true; + acpm_ramdump(); + } +} + +void acpm_stop_log(void) +{ + acpm_stop_log_req = true; +} + +static void acpm_update_log(struct work_struct *work) +{ + acpm_log_print(); +} + +static void acpm_debug_logging(struct work_struct *work) +{ + acpm_log_print(); + timestamp_write(); + + queue_delayed_work(debug_logging_wq, &acpm_debug->periodic_work, + msecs_to_jiffies(acpm_debug->period)); +} + +int acpm_ipc_set_ch_mode(struct device_node *np, bool polling) +{ + int reg; + int i, len, req_ch_id; + const __be32 *prop; + + if (!np) + return -ENODEV; + + prop = of_get_property(np, "acpm-ipc-channel", &len); + if (!prop) + return -ENOENT; + req_ch_id = be32_to_cpup(prop); + + for(i = 0; i < acpm_ipc->num_channels; i++) { + if (acpm_ipc->channel[i].id == req_ch_id) { + + reg = __raw_readl(acpm_ipc->intr + INTMR1); + reg &= ~(1 << acpm_ipc->channel[i].id); + reg |= polling << acpm_ipc->channel[i].id; + __raw_writel(reg, acpm_ipc->intr + INTMR1); + + acpm_ipc->channel[i].polling = polling; + + return 0; + } + } + + return -ENODEV; +} + +unsigned int acpm_ipc_request_channel(struct device_node *np, ipc_callback handler, + unsigned int *id, unsigned int *size) +{ + struct callback_info *cb; + int i, len, req_ch_id; + const __be32 *prop; + + if (!np) + return -ENODEV; + + prop = of_get_property(np, "acpm-ipc-channel", &len); + if (!prop) + return -ENOENT; + req_ch_id = be32_to_cpup(prop); + + for(i = 0; i < acpm_ipc->num_channels; i++) { + if (acpm_ipc->channel[i].id == req_ch_id) { + *id = acpm_ipc->channel[i].id; + *size = acpm_ipc->channel[i].tx_ch.size; + + if (handler) { + cb = devm_kzalloc(acpm_ipc->dev, sizeof(struct callback_info), + GFP_KERNEL); + if (cb == NULL) + return -ENOMEM; + cb->ipc_callback = handler; + cb->client = np; + + spin_lock(&acpm_ipc->channel[i].ch_lock); + list_add(&cb->list, &acpm_ipc->channel[i].list); + spin_unlock(&acpm_ipc->channel[i].ch_lock); + } + + return 0; + } + } + + return -ENODEV; +} + +unsigned int acpm_ipc_release_channel(struct device_node *np, unsigned int channel_id) +{ + struct acpm_ipc_ch *channel = &acpm_ipc->channel[channel_id]; + struct list_head *cb_list = &channel->list; + struct callback_info *cb; + + list_for_each_entry(cb, cb_list, list) { + if (cb->client == np) { + spin_lock(&channel->ch_lock); + list_del(&cb->list); + spin_unlock(&channel->ch_lock); + devm_kfree(acpm_ipc->dev, cb); + break; + } + } + + return 0; +} + +static bool check_response(struct acpm_ipc_ch *channel, struct ipc_config *cfg) +{ + unsigned int front; + unsigned int rear; + struct list_head *cb_list = &channel->list; + struct callback_info *cb; + unsigned int data; + bool ret = true; + unsigned int i; + + spin_lock(&channel->rx_lock); + /* IPC command dequeue */ + front = __raw_readl(channel->rx_ch.front); + rear = __raw_readl(channel->rx_ch.rear); + + i = rear; + + while (i != front) { + data = __raw_readl(channel->rx_ch.base + channel->rx_ch.size * i); + data = (data >> ACPM_IPC_PROTOCOL_SEQ_NUM) & 0x3f; + + if (data == ((cfg->cmd[0] >> ACPM_IPC_PROTOCOL_SEQ_NUM) & 0x3f)) { + memcpy_align_4(cfg->cmd, channel->rx_ch.base + channel->rx_ch.size * i, + channel->rx_ch.size); + memcpy_align_4(channel->cmd, channel->rx_ch.base + channel->rx_ch.size * i, + channel->rx_ch.size); + + /* i: target command, rear: another command + * 1. i index command dequeue + * 2. rear index command copy to i index position + * 3. incresed rear index + */ + if (i != rear) + memcpy_align_4(channel->rx_ch.base + channel->rx_ch.size * i, + channel->rx_ch.base + channel->rx_ch.size * rear, + channel->rx_ch.size); + + list_for_each_entry(cb, cb_list, list) + if (cb && cb->ipc_callback) + cb->ipc_callback(channel->cmd, channel->rx_ch.size); + + rear++; + rear = rear % channel->rx_ch.len; + + __raw_writel(rear, channel->rx_ch.rear); + front = __raw_readl(channel->rx_ch.front); + + if (rear == front) { + __raw_writel((1 << channel->id), acpm_ipc->intr + INTCR1); + if (rear != __raw_readl(channel->rx_ch.front)) { + __raw_writel((1 << channel->id), acpm_ipc->intr + INTGR1); + } + } + ret = false; + break; + } + i++; + i = i % channel->rx_ch.len; + } + + spin_unlock(&channel->rx_lock); + + return ret; +} + +static void dequeue_policy(struct acpm_ipc_ch *channel) +{ + unsigned int front; + unsigned int rear; + struct list_head *cb_list = &channel->list; + struct callback_info *cb; + + spin_lock(&channel->rx_lock); + + if (channel->type == TYPE_BUFFER) { + memcpy_align_4(channel->cmd, channel->rx_ch.base, channel->rx_ch.size); + spin_unlock(&channel->rx_lock); + list_for_each_entry(cb, cb_list, list) + if (cb && cb->ipc_callback) + cb->ipc_callback(channel->cmd, channel->rx_ch.size); + + return; + } + + /* IPC command dequeue */ + front = __raw_readl(channel->rx_ch.front); + rear = __raw_readl(channel->rx_ch.rear); + + while (rear != front) { + memcpy_align_4(channel->cmd, channel->rx_ch.base + channel->rx_ch.size * rear, + channel->rx_ch.size); + + list_for_each_entry(cb, cb_list, list) + if (cb && cb->ipc_callback) + cb->ipc_callback(channel->cmd, channel->rx_ch.size); + + if (channel->rx_ch.len == (rear + 1)) + rear = 0; + else + rear++; + + if (!channel->polling) + complete(&channel->wait); + + __raw_writel(rear, channel->rx_ch.rear); + front = __raw_readl(channel->rx_ch.front); + } + + acpm_log_print(); + spin_unlock(&channel->rx_lock); +} + +static irqreturn_t acpm_ipc_irq_handler(int irq, void *data) +{ + struct acpm_ipc_info *ipc = data; + unsigned int status; + int i; + + /* ACPM IPC INTERRUPT STATUS REGISTER */ + status = __raw_readl(acpm_ipc->intr + INTSR1); + + for (i = 0; i < acpm_ipc->num_channels; i++) { + if (!ipc->channel[i].polling && (status & (0x1 << ipc->channel[i].id))) { + /* ACPM IPC INTERRUPT PENDING CLEAR */ + __raw_writel(1 << ipc->channel[i].id, ipc->intr + INTCR1); + } + } + + ipc->intr_status = status; + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t acpm_ipc_irq_handler_thread(int irq, void *data) +{ + struct acpm_ipc_info *ipc = data; + int i; + + for (i = 0; i < acpm_ipc->num_channels; i++) + if (!ipc->channel[i].polling && (ipc->intr_status & (1 << i))) + dequeue_policy(&ipc->channel[i]); + + return IRQ_HANDLED; +} + +static void apm_interrupt_gen(unsigned int id) +{ + /* APM NVIC INTERRUPT GENERATE */ + writel((1 << id) << 16, acpm_ipc->intr + INTGR0); +} + +static int enqueue_indirection_cmd(struct acpm_ipc_ch *channel, + struct ipc_config *cfg) +{ + unsigned int front; + unsigned int rear; + unsigned int buf; + bool timeout_flag = 0; + + if (cfg->indirection) { + front = __raw_readl(channel->tx_ch.front); + rear = __raw_readl(channel->tx_ch.rear); + + /* another indirection command check */ + while (rear != front) { + buf = __raw_readl(channel->tx_ch.base + channel->tx_ch.size * rear); + + if (buf & (1 << ACPM_IPC_PROTOCOL_INDIRECTION)) { + + UNTIL_EQUAL(true, rear != __raw_readl(channel->tx_ch.rear), + timeout_flag); + + if (timeout_flag) { + acpm_log_print(); + return -ETIMEDOUT; + } else { + rear = __raw_readl(channel->tx_ch.rear); + } + + } else { + if (channel->tx_ch.len == (rear + 1)) + rear = 0; + else + rear++; + } + } + + if (cfg->indirection_base) + memcpy_align_4(channel->tx_ch.direction, cfg->indirection_base, + cfg->indirection_size); + else + return -EINVAL; + } + + return 0; +} + +int acpm_ipc_send_data_sync(unsigned int channel_id, struct ipc_config *cfg) +{ + int ret; + struct acpm_ipc_ch *channel; + + ret = acpm_ipc_send_data(channel_id, cfg); + + if (!ret) { + channel = &acpm_ipc->channel[channel_id]; + + if (!channel->polling && cfg->response) { + ret = wait_for_completion_interruptible_timeout(&channel->wait, + msecs_to_jiffies(50)); + if (!ret) { + pr_err("[%s] ipc_timeout!!!\n", __func__); + ret = -ETIMEDOUT; + } else { + ret = 0; + } + } + } + + return ret; +} + +int acpm_ipc_send_data(unsigned int channel_id, struct ipc_config *cfg) +{ + unsigned int front; + unsigned int rear; + unsigned int tmp_index; + struct acpm_ipc_ch *channel; + bool timeout_flag = 0; + int ret; + u64 timeout, now; + u32 retry_cnt = 0; + + if (channel_id >= acpm_ipc->num_channels && !cfg) + return -EIO; + + channel = &acpm_ipc->channel[channel_id]; + + spin_lock(&channel->tx_lock); + + front = __raw_readl(channel->tx_ch.front); + rear = __raw_readl(channel->tx_ch.rear); + + tmp_index = front + 1; + + if (tmp_index >= channel->tx_ch.len) + tmp_index = 0; + + /* buffer full check */ + UNTIL_EQUAL(true, tmp_index != __raw_readl(channel->tx_ch.rear), timeout_flag); + if (timeout_flag) { + acpm_log_print(); + acpm_debug->debug_log_level = 1; + spin_unlock(&channel->tx_lock); + pr_err("[%s] tx buffer full! timeout!!!\n", __func__); + return -ETIMEDOUT; + } + + if (!cfg->cmd) { + spin_unlock(&channel->tx_lock); + return -EIO; + } + + if (++channel->seq_num == 64) + channel->seq_num = 1; + + cfg->cmd[0] |= (channel->seq_num & 0x3f) << ACPM_IPC_PROTOCOL_SEQ_NUM; + + memcpy_align_4(channel->tx_ch.base + channel->tx_ch.size * front, cfg->cmd, + channel->tx_ch.size); + + cfg->cmd[1] = 0; + cfg->cmd[2] = 0; + cfg->cmd[3] = 0; + + ret = enqueue_indirection_cmd(channel, cfg); + if (ret) { + pr_err("[ACPM] indirection command fail %d\n", ret); + spin_unlock(&channel->tx_lock); + return ret; + } + + writel(tmp_index, channel->tx_ch.front); + + apm_interrupt_gen(channel->id); + spin_unlock(&channel->tx_lock); + + if (channel->polling && cfg->response) { +retry: + timeout = sched_clock() + IPC_TIMEOUT; + timeout_flag = false; + + while (!(__raw_readl(acpm_ipc->intr + INTSR1) & (1 << channel->id)) || + check_response(channel, cfg)) { + now = sched_clock(); + if (timeout < now) { + if (retry_cnt++ < 5) { + pr_err("acpm_ipc timeout retry %d" + "now = %llu," + "timeout = %llu\n", + retry_cnt, now, timeout); + goto retry; + } + timeout_flag = true; + break; + } else { + if (acpm_ipc->w_mode) + usleep_range(50, 100); + else + cpu_relax(); + } + } + + if (timeout_flag) { + if (!check_response(channel, cfg)) + return 0; + pr_err("%s Timeout error! now = %llu, timeout = %llu\n", + __func__, now, timeout); + pr_err("[ACPM] status:0x%x, 0x%x\n", + __raw_readl(acpm_ipc->intr + INTSR1), + 1 << channel->id); + pr_err("[ACPM] queue, rear:%u, front:%u\n", + __raw_readl(channel->rx_ch.rear), + __raw_readl(channel->rx_ch.front)); + + acpm_debug->debug_log_level = 1; + acpm_log_print(); + acpm_debug->debug_log_level = 0; + acpm_ramdump(); + + BUG_ON(timeout_flag); + return -ETIMEDOUT; + } + + queue_work(update_log_wq, &acpm_debug->update_log_work); + } + + return 0; +} + +static void log_buffer_init(struct device *dev, struct device_node *node) +{ + const __be32 *prop; + unsigned int num_timestamps = 0; + unsigned int len = 0; + unsigned int dump_base = 0; + unsigned int dump_size = 0; + + prop = of_get_property(node, "num-timestamps", &len); + if (prop) + num_timestamps = be32_to_cpup(prop); + + acpm_debug = devm_kzalloc(dev, sizeof(struct acpm_debug_info), GFP_KERNEL); + if (IS_ERR(acpm_debug)) + return ; + + acpm_debug->time_index = acpm_ipc->sram_base + acpm_ipc->initdata->ktime_index; + acpm_debug->num_timestamps = num_timestamps; + acpm_debug->timestamps = devm_kzalloc(dev, + sizeof(unsigned long long) * num_timestamps, GFP_KERNEL); + acpm_debug->log_buff_rear = acpm_ipc->sram_base + acpm_ipc->initdata->log_buf_rear; + acpm_debug->log_buff_front = acpm_ipc->sram_base + acpm_ipc->initdata->log_buf_front; + acpm_debug->log_buff_base = acpm_ipc->sram_base + acpm_ipc->initdata->log_data; + acpm_debug->log_buff_len = acpm_ipc->initdata->log_entry_len; + acpm_debug->log_buff_size = acpm_ipc->initdata->log_entry_size; + + prop = of_get_property(node, "debug-log-level", &len); + if (prop) + acpm_debug->debug_log_level = be32_to_cpup(prop); + + prop = of_get_property(node, "dump-base", &len); + if (prop) + dump_base = be32_to_cpup(prop); + + prop = of_get_property(node, "dump-size", &len); + if (prop) + dump_size = be32_to_cpup(prop); + + if (dump_base && dump_size) { + acpm_debug->dump_base = ioremap(dump_base, dump_size); + acpm_debug->dump_size = dump_size; + } + + prop = of_get_property(node, "logging-period", &len); + if (prop) + acpm_debug->period = be32_to_cpup(prop); + +#ifdef CONFIG_EXYNOS_SNAPSHOT_ACPM + acpm_debug->dump_dram_base = kzalloc(acpm_debug->dump_size, GFP_KERNEL); + exynos_ss_printk("[ACPM] acpm framework SRAM dump to dram base: 0x%x\n", + virt_to_phys(acpm_debug->dump_dram_base)); +#endif + pr_info("[ACPM] acpm framework SRAM dump to dram base: 0x%llx\n", + virt_to_phys(acpm_debug->dump_dram_base)); + + spin_lock_init(&acpm_debug->lock); + + if (acpm_initdata->regulator_id) + acpm_reg_id = acpm_initdata->regulator_id; +} + +static int channel_init(void) +{ + int i; + unsigned int mask = 0; + struct ipc_channel *ipc_ch; + + acpm_ipc->num_channels = acpm_ipc->initdata->ipc_ap_max; + + acpm_ipc->channel = devm_kzalloc(acpm_ipc->dev, + sizeof(struct acpm_ipc_ch) * acpm_ipc->num_channels, GFP_KERNEL); + + for (i = 0; i < acpm_ipc->num_channels; i++) { + ipc_ch = (struct ipc_channel *)(acpm_ipc->sram_base + acpm_ipc->initdata->ipc_channels); + acpm_ipc->channel[i].polling = ipc_ch[i].ap_poll; + acpm_ipc->channel[i].id = ipc_ch[i].id; + acpm_ipc->channel[i].type = ipc_ch[i].type; + mask |= acpm_ipc->channel[i].polling << acpm_ipc->channel[i].id; + + /* Channel's RX buffer info */ + acpm_ipc->channel[i].rx_ch.size = ipc_ch[i].ch.q_elem_size; + acpm_ipc->channel[i].rx_ch.len = ipc_ch[i].ch.q_len; + acpm_ipc->channel[i].rx_ch.rear = acpm_ipc->sram_base + ipc_ch[i].ch.tx_rear; + acpm_ipc->channel[i].rx_ch.front = acpm_ipc->sram_base + ipc_ch[i].ch.tx_front; + acpm_ipc->channel[i].rx_ch.base = acpm_ipc->sram_base + ipc_ch[i].ch.tx_base; + /* Channel's TX buffer info */ + acpm_ipc->channel[i].tx_ch.size = ipc_ch[i].ch.q_elem_size; + acpm_ipc->channel[i].tx_ch.len = ipc_ch[i].ch.q_len; + acpm_ipc->channel[i].tx_ch.rear = acpm_ipc->sram_base + ipc_ch[i].ch.rx_rear; + acpm_ipc->channel[i].tx_ch.front = acpm_ipc->sram_base + ipc_ch[i].ch.rx_front; + acpm_ipc->channel[i].tx_ch.base = acpm_ipc->sram_base + ipc_ch[i].ch.rx_base; + acpm_ipc->channel[i].tx_ch.d_buff_size = ipc_ch[i].ch.rx_indr_buf_size; + acpm_ipc->channel[i].tx_ch.direction = acpm_ipc->sram_base + ipc_ch[i].ch.rx_indr_buf; + + acpm_ipc->channel[i].cmd = devm_kzalloc(acpm_ipc->dev, + acpm_ipc->channel[i].tx_ch.size, GFP_KERNEL); + + init_completion(&acpm_ipc->channel[i].wait); + spin_lock_init(&acpm_ipc->channel[i].rx_lock); + spin_lock_init(&acpm_ipc->channel[i].tx_lock); + spin_lock_init(&acpm_ipc->channel[i].ch_lock); + INIT_LIST_HEAD(&acpm_ipc->channel[i].list); + } + + __raw_writel(mask, acpm_ipc->intr + INTMR1); + + return 0; +} + +static int acpm_ipc_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct resource *res; + int ret = 0, len; + const __be32 *prop; + + if (!node) { + dev_err(&pdev->dev, "driver doesnt support" + "non-dt devices\n"); + return -ENODEV; + } + + dev_info(&pdev->dev, "acpm_ipc probe\n"); + + acpm_ipc = devm_kzalloc(&pdev->dev, + sizeof(struct acpm_ipc_info), GFP_KERNEL); + + if (IS_ERR(acpm_ipc)) + return PTR_ERR(acpm_ipc); + + acpm_ipc->irq = irq_of_parse_and_map(node, 0); + + ret = devm_request_threaded_irq(&pdev->dev, acpm_ipc->irq, acpm_ipc_irq_handler, + acpm_ipc_irq_handler_thread, + IRQF_ONESHOT, + dev_name(&pdev->dev), acpm_ipc); + + if (ret) { + dev_err(&pdev->dev, "failed to register acpm_ipc interrupt:%d\n", ret); + return ret; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + acpm_ipc->intr = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(acpm_ipc->intr)) + return PTR_ERR(acpm_ipc->intr); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + acpm_ipc->sram_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(acpm_ipc->sram_base)) + return PTR_ERR(acpm_ipc->sram_base); + + prop = of_get_property(node, "initdata-base", &len); + if (prop) { + acpm_ipc->initdata_base = be32_to_cpup(prop); + } else { + dev_err(&pdev->dev, "Parsing initdata_base failed.\n"); + return -EINVAL; + } + acpm_ipc->initdata = (struct acpm_framework *)(acpm_ipc->sram_base + acpm_ipc->initdata_base); + acpm_initdata = acpm_ipc->initdata; + acpm_srambase = acpm_ipc->sram_base; + + acpm_ipc->dev = &pdev->dev; + + log_buffer_init(&pdev->dev, node); + + channel_init(); + + update_log_wq = create_freezable_workqueue("acpm_update_log"); + INIT_WORK(&acpm_debug->update_log_work, acpm_update_log); + + if (acpm_debug->period) { + debug_logging_wq = create_freezable_workqueue("acpm_debug_logging"); + INIT_DELAYED_WORK(&acpm_debug->periodic_work, acpm_debug_logging); + + queue_delayed_work(debug_logging_wq, &acpm_debug->periodic_work, + msecs_to_jiffies(10000)); + } + + return ret; +} + +static int acpm_ipc_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id acpm_ipc_match[] = { + { .compatible = "samsung,exynos-acpm-ipc" }, + {}, +}; + +static struct platform_driver samsung_acpm_ipc_driver = { + .probe = acpm_ipc_probe, + .remove = acpm_ipc_remove, + .driver = { + .name = "exynos-acpm-ipc", + .owner = THIS_MODULE, + .of_match_table = acpm_ipc_match, + }, +}; + +static int __init exynos_acpm_ipc_init(void) +{ + return platform_driver_register(&samsung_acpm_ipc_driver); +} +arch_initcall(exynos_acpm_ipc_init); diff --git a/drivers/soc/samsung/acpm/acpm_ipc.h b/drivers/soc/samsung/acpm/acpm_ipc.h new file mode 100644 index 000000000000..e35ea1a4a4af --- /dev/null +++ b/drivers/soc/samsung/acpm/acpm_ipc.h @@ -0,0 +1,140 @@ +#ifndef __ACPM_IPC_H_ +#define __ACPM_IPC_H_ + +#include + +struct buff_info { + void __iomem *rear; + void __iomem *front; + void __iomem *base; + void __iomem *direction; + + unsigned int size; + unsigned int len; + unsigned int d_buff_size; +}; + +struct callback_info { + void (*ipc_callback) (unsigned int *cmd, unsigned int size); + struct device_node *client; + struct list_head list; +}; + +struct acpm_ipc_ch { + struct buff_info rx_ch; + struct buff_info tx_ch; + struct list_head list; + + unsigned int id; + unsigned int type; + unsigned int seq_num; + unsigned int *cmd; + spinlock_t rx_lock; + spinlock_t tx_lock; + spinlock_t ch_lock; + struct mutex wait_lock; + + struct completion wait; + bool polling; +}; + +struct acpm_ipc_info { + unsigned int num_channels; + struct device *dev; + struct acpm_ipc_ch *channel; + unsigned int irq; + void __iomem *intr; + void __iomem *sram_base; + bool w_mode; + struct acpm_framework *initdata; + unsigned int initdata_base; + unsigned int intr_status; +}; + +struct acpm_debug_info { + unsigned int period; + void __iomem *time_index; + unsigned int num_timestamps; + unsigned long long *timestamps; + + void __iomem *log_buff_rear; + void __iomem *log_buff_front; + void __iomem *log_buff_base; + unsigned int log_buff_len; + unsigned int log_buff_size; + void __iomem *dump_base; + unsigned int dump_size; + void __iomem *dump_dram_base; + unsigned int debug_log_level; + struct delayed_work periodic_work; + struct work_struct update_log_work; + + spinlock_t lock; +}; + +struct regulator_ss_info { + char name[7]; + unsigned int min_uV; + unsigned int uV_step; + unsigned int linear_min_sel; + unsigned int vsel_reg; +}; + +#define LOG_ID_SHIFT (28) +#define LOG_LEVEL (27) +#define LOG_TIME_INDEX (22) + +#define BUSY_WAIT (0) +#define SLEEP_WAIT (1) +#define INTGR0 0x0008 +#define INTCR0 0x000C +#define INTMR0 0x0010 +#define INTSR0 0x0014 +#define INTMSR0 0x0018 +#define INTGR1 0x001C +#define INTCR1 0x0020 +#define INTMR1 0x0024 +#define INTSR1 0x0028 +#define INTMSR1 0x002C +#define SR0 0x0080 +#define SR1 0x0084 +#define SR2 0x0088 +#define SR3 0x008C + +#define IPC_TIMEOUT (15000000) +#define APM_PERITIMER_NS_PERIOD (10416) + +#define UNTIL_EQUAL(arg0, arg1, flag) \ +do { \ + u64 timeout = sched_clock() + IPC_TIMEOUT; \ + bool t_flag = true; \ + do { \ + if ((arg0) == (arg1)) { \ + t_flag = false; \ + break; \ + } else { \ + cpu_relax(); \ + } \ + } while (timeout >= sched_clock()); \ + if (t_flag) { \ + pr_err("%s %d Timeout error!\n", \ + __func__, __LINE__); \ + } \ + (flag) = t_flag; \ +} while(0) + +#define REGULATOR_INFO_ID 7 +#define REGULATOR_SS_MAX 128 +#define NO_SS_RANGE (REGULATOR_SS_MAX + 100) +#define NO_SET_REGMAP (REGULATOR_SS_MAX + 99) + +extern void acpm_log_print(void); +extern void timestamp_write(void); +extern void acpm_ramdump(void); +extern void acpm_fw_log_level(unsigned int on); +extern void acpm_ipc_set_waiting_mode(bool mode); +extern u32 acpm_get_mifdn_count(void); + +extern struct regulator_ss_info *get_regulator_ss(int n); + +#endif diff --git a/drivers/soc/samsung/acpm/acpm_mfd.c b/drivers/soc/samsung/acpm/acpm_mfd.c new file mode 100644 index 000000000000..813a4e21c776 --- /dev/null +++ b/drivers/soc/samsung/acpm/acpm_mfd.c @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2016 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 +#include +#include +#include +#include +#include + +extern struct device_node *acpm_mfd_node; +static DEFINE_MUTEX(acpm_mfd_lock); + +int exynos_acpm_read_reg(u16 type, u8 reg, u8 *dest) +{ + unsigned int channel_num, size; + struct ipc_config config; + unsigned int command[4] = {0,}; + int ret = 0; + + mutex_lock(&acpm_mfd_lock); + if (!acpm_ipc_request_channel(acpm_mfd_node, NULL, &channel_num, &size)) { + config.cmd = command; + config.cmd[0] = set_protocol(type, TYPE) | set_protocol(reg, REG); + config.cmd[1] = set_protocol(FUNC_READ, FUNC); + config.response = true; + config.indirection = false; + + ACPM_MFD_PRINT("%s - addr: 0x%03x\n", + __func__, set_protocol(type, TYPE) | set_protocol(reg, REG)); + + ret = acpm_ipc_send_data(channel_num, &config); + if (ret) { + pr_err(ACPM_MFD_PREFIX "%s - acpm_ipc_send_data fail.\n", __func__); + goto err; + } + + *dest = read_protocol(config.cmd[1], DEST); + ret = read_protocol(config.cmd[1], RETURN); + if (ret) { + pr_err(ACPM_MFD_PREFIX "%s - APM's speedy_rx fail.\n", __func__); + goto err; + } + + ACPM_MFD_PRINT("%s - data = 0x%02x ret = 0x%02x\n", + __func__, *dest, ret); + + acpm_ipc_release_channel(acpm_mfd_node, channel_num); + } else { + pr_err(ACPM_MFD_PREFIX "%s ipc request_channel fail, id:%u, size:%u\n", + __func__, channel_num, size); + ret = -EBUSY; + } +err: + mutex_unlock(&acpm_mfd_lock); + return ret; +} + +int exynos_acpm_bulk_read(u16 type, u8 reg, int count, u8 *buf) +{ + unsigned int channel_num, size; + struct ipc_config config; + unsigned int command[4] = {0,}; + int i, ret = 0; + + mutex_lock(&acpm_mfd_lock); + if (!acpm_ipc_request_channel(acpm_mfd_node, NULL, &channel_num, &size)) { + config.cmd = command; + config.cmd[0] = set_protocol(type, TYPE) | set_protocol(reg, REG); + config.cmd[1] = set_protocol(FUNC_BULK_READ, FUNC) | set_protocol(count, CNT); + config.response = true; + config.indirection = false; + + ACPM_MFD_PRINT("%s - addr: 0x%03x\n", + __func__, set_protocol(type, TYPE) | set_protocol(reg, REG)); + + ret = acpm_ipc_send_data(channel_num, &config); + if (ret) { + pr_err(ACPM_MFD_PREFIX "%s - acpm_ipc_send_data fail.\n", __func__); + goto err; + } + + ret = read_protocol(config.cmd[1], RETURN); + if (ret) { + pr_err(ACPM_MFD_PREFIX "%s - APM's speedy_rx fail.\n", __func__); + goto err; + } + + for (i = 0; i < count; i++) { + if (i < 4) + buf[i] = read_bulk_protocol(config.cmd[2], BULK_VAL, i); + else + buf[i] = read_bulk_protocol(config.cmd[3], BULK_VAL , i - 4); + } + + acpm_ipc_release_channel(acpm_mfd_node, channel_num); + } else { + pr_err(ACPM_MFD_PREFIX "%s ipc request_channel fail, id:%u, size:%u\n", + __func__, channel_num, size); + ret = -EBUSY; + } + +err: + mutex_unlock(&acpm_mfd_lock); + return ret; +} + +int exynos_acpm_write_reg(u16 type, u8 reg, u8 value) +{ + unsigned int channel_num, size; + struct ipc_config config; + unsigned int command[4] = {0,}; + int ret = 0; + + mutex_lock(&acpm_mfd_lock); + if (!acpm_ipc_request_channel(acpm_mfd_node, NULL, &channel_num, &size)) { + config.cmd = command; + config.cmd[0] = set_protocol(type, TYPE) | set_protocol(reg, REG); + config.cmd[1] = set_protocol(FUNC_WRITE, FUNC) | set_protocol(value, WRITE_VAL); + config.response = true; + config.indirection = false; + + ACPM_MFD_PRINT("%s - addr: 0x%03x val: 0x%02x\n", + __func__, set_protocol(type, TYPE) | set_protocol(reg, REG), value); + + ret = acpm_ipc_send_data(channel_num, &config); + if (ret) { + pr_err(ACPM_MFD_PREFIX "%s - acpm_ipc_send_data fail.\n", __func__); + goto err; + } + + ret = read_protocol(config.cmd[1], RETURN); + if (ret) { + pr_err(ACPM_MFD_PREFIX "%s - APM's speedy_tx fail.\n", __func__); + goto err; + } + + ACPM_MFD_PRINT("%s - read(ret) val: 0x%02x\n", __func__, ret); + + acpm_ipc_release_channel(acpm_mfd_node, channel_num); + } else { + pr_err(ACPM_MFD_PREFIX "%s ipc request_channel fail, id:%u, size:%u\n", + __func__, channel_num, size); + ret = -EBUSY; + } + +err: + mutex_unlock(&acpm_mfd_lock); + return ret; +} + +int exynos_acpm_bulk_write(u16 type, u8 reg, int count, u8 *buf) +{ + unsigned int channel_num, size; + struct ipc_config config; + unsigned int command[4] = {0,}; + int ret = 0; + int i; + + mutex_lock(&acpm_mfd_lock); + if (!acpm_ipc_request_channel(acpm_mfd_node, NULL, &channel_num, &size)) { + config.cmd = command; + config.cmd[0] = set_protocol(type, TYPE) | set_protocol(reg, REG); + config.cmd[1] = set_protocol(FUNC_BULK_WRITE, FUNC) | set_protocol(count, CNT); + config.response = true; + config.indirection = false; + + ACPM_MFD_PRINT("%s - addr: 0x%03x\n cnt: 0x%02x\n", + __func__, set_protocol(type, TYPE) | set_protocol(reg, REG), count); + + for (i = 0; i < count; i++) { + if (i < 4) + config.cmd[2] |= set_bulk_protocol(buf[i], BULK_VAL, i); + else + config.cmd[3] |= set_bulk_protocol(buf[i], BULK_VAL, i - 4); + } + + ret = acpm_ipc_send_data(channel_num, &config); + if (ret) { + pr_err(ACPM_MFD_PREFIX "%s - acpm_ipc_send_data fail.\n", __func__); + goto err; + } + + ret = read_protocol(config.cmd[1], RETURN); + if (ret) { + pr_err(ACPM_MFD_PREFIX "%s - APM's speedy_tx fail.\n", __func__); + goto err; + } + + acpm_ipc_release_channel(acpm_mfd_node, channel_num); + } else { + pr_err(ACPM_MFD_PREFIX "%s ipc request_channel fail, id:%u, size:%u\n", + __func__, channel_num, size); + ret = -EBUSY; + } + +err: + mutex_unlock(&acpm_mfd_lock); + return ret; +} + +int exynos_acpm_update_reg(u16 type, u8 reg, u8 value, u8 mask) +{ + unsigned int channel_num, size; + struct ipc_config config; + unsigned int command[4] = {0,}; + int ret = 0; + + mutex_lock(&acpm_mfd_lock); + if (!acpm_ipc_request_channel(acpm_mfd_node, NULL, &channel_num, &size)) { + config.cmd = command; + config.cmd[0] = set_protocol(type, TYPE) | set_protocol(reg, REG); + config.cmd[1] = set_protocol(FUNC_UPDATE, FUNC) + | set_protocol(value, UPDATE_VAL) + | set_protocol(mask, UPDATE_MASK); + config.response = true; + config.indirection = false; + + ACPM_MFD_PRINT("%s - addr: 0x%03x val: 0x%02x mask: 0x%02x\n", + __func__, set_protocol(type, TYPE) | set_protocol(reg, REG), value, mask); + + ret = acpm_ipc_send_data(channel_num, &config); + if (ret) { + pr_err(ACPM_MFD_PREFIX "%s - acpm_ipc_send_data fail.\n", __func__); + goto err; + } + + ret = read_protocol(config.cmd[1], RETURN); + if (ret) { + if (ret == 1) + pr_err(ACPM_MFD_PREFIX "%s - APM's speedy_rx fail.\n", __func__); + else if (ret == 2) + pr_err(ACPM_MFD_PREFIX "%s - APM's speedy_tx fail.\n", __func__); + goto err; + } + + acpm_ipc_release_channel(acpm_mfd_node, channel_num); + } else { + pr_err(ACPM_MFD_PREFIX "%s ipc request_channel fail, id:%u, size:%u\n", + __func__, channel_num, size); + ret = -EBUSY; + } + +err: + mutex_unlock(&acpm_mfd_lock); + return ret; +} diff --git a/drivers/soc/samsung/acpm/acpm_s2d.c b/drivers/soc/samsung/acpm/acpm_s2d.c new file mode 100644 index 000000000000..8ba497f4714a --- /dev/null +++ b/drivers/soc/samsung/acpm/acpm_s2d.c @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2016 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define EXYNOS_S2D_DBG_PREFIX "EXYNOS-S2D-DBG: " +#define BUF_SIZE 32 + +struct scan2dram_info { + struct device_node *np; + unsigned int ch; + unsigned int size; + + struct dentry *den; + struct file_operations fops; +}; + +static int s2d_en = 0; +static struct scan2dram_info *acpm_s2d; +static struct dentry *s2d_dbg_root; + +static int exynos_acpm_s2d_update_en(void) +{ + struct ipc_config config; + unsigned int cmd[4] = {0,}; + unsigned long long before, after, latency; + int ret; + + config.cmd = cmd; + config.response = true; + config.indirection = false; + config.cmd[1] = exynos_ss_get_item_paddr("log_s2d"); + config.cmd[2] = s2d_en; + + before = sched_clock(); + ret = acpm_ipc_send_data(acpm_s2d->ch, &config); + after = sched_clock(); + latency = after - before; + if (ret) + pr_err("%s: latency = %llu ret = %d", + __func__, latency, ret); + + return ret; +} + +static ssize_t exynos_s2d_en_dbg_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + ssize_t ret; + char buf[BUF_SIZE] = {0,}; + + ret = snprintf(buf, BUF_SIZE, "%s : %d\n", "Scan2dram enable", s2d_en); + if (ret < 0) + return ret; + + return simple_read_from_buffer(user_buf, count, ppos, buf, ret); +} + +static ssize_t exynos_s2d_en_dbg_write(struct file *file, const char __user *user_buf, + size_t count, loff_t *ppos) +{ + ssize_t ret; + char buf[BUF_SIZE]; + + ret = simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, user_buf, count); + if (ret < 0) + return ret; + + if (buf[0] == '0') { + s2d_en = 0; + exynos_acpm_s2d_update_en(); + } else if (buf[0] == '1') { + s2d_en = 1; + exynos_acpm_s2d_update_en(); + } + + return ret; +} + +static int exynos_acpm_s2d_probe(struct platform_device *pdev) +{ + int ret; + + acpm_s2d = kzalloc(sizeof(struct scan2dram_info), GFP_KERNEL); + if (!acpm_s2d) { + pr_err("%s %s: can not allocate mem for acpm_s2d\n", + EXYNOS_S2D_DBG_PREFIX, __func__); + ret = -ENOMEM; + goto err_s2d_info; + } + + acpm_s2d->np = of_find_node_by_name(NULL, "acpm_s2d"); + if (!acpm_s2d->np) { + pr_err("%s %s: can not get node for acpm_s2d\n", + EXYNOS_S2D_DBG_PREFIX, __func__); + ret = -ENODEV; + goto err_dbgfs_probe; + } + + ret = acpm_ipc_request_channel(acpm_s2d->np, NULL, + &acpm_s2d->ch, &acpm_s2d->size); + if (ret < 0) { + pr_err("%s %s: acpm_s2d ipc request fail ret = %d\n", + EXYNOS_S2D_DBG_PREFIX, __func__, ret); + ret = -ENODEV; + } + + exynos_acpm_s2d_update_en(); + + s2d_dbg_root = debugfs_create_dir("scan2dram", NULL); + if (!s2d_dbg_root) { + pr_err("%s %s: could not create debugfs root dir\n", + EXYNOS_S2D_DBG_PREFIX, __func__); + ret = -ENOMEM; + goto err_dbgfs_probe; + } + + acpm_s2d->fops.open = simple_open; + acpm_s2d->fops.read = exynos_s2d_en_dbg_read; + acpm_s2d->fops.write = exynos_s2d_en_dbg_write; + acpm_s2d->fops.llseek = default_llseek; + acpm_s2d->den = debugfs_create_file("enable", 0644, s2d_dbg_root, + NULL, &acpm_s2d->fops); + + platform_set_drvdata(pdev, acpm_s2d); + + return 0; + +err_dbgfs_probe: + kfree(acpm_s2d); +err_s2d_info: + return ret; +} + +static int exynos_acpm_s2d_remove(struct platform_device *pdev) +{ + struct scan2dram_info *acpm_s2d = platform_get_drvdata(pdev); + + acpm_ipc_release_channel(acpm_s2d->np, acpm_s2d->ch); + debugfs_remove_recursive(s2d_dbg_root); + kfree(acpm_s2d); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct of_device_id exynos_acpm_s2d_match[] = { + { + .compatible = "samsung,exynos-acpm-s2d", + }, + {}, +}; + +static struct platform_driver exynos_acpm_s2d_drv = { + .probe = exynos_acpm_s2d_probe, + .remove = exynos_acpm_s2d_remove, + .driver = { + .name = "exynos_acpm_s2d", + .owner = THIS_MODULE, + .of_match_table = exynos_acpm_s2d_match, + }, +}; + +static int __init exynos_acpm_s2d_init(void) +{ + return platform_driver_register(&exynos_acpm_s2d_drv); +} +late_initcall(exynos_acpm_s2d_init); + +static int __init s2d_enable(char *str) +{ + s2d_en = 1; + return 1; +} +__setup("s2d_enable", s2d_enable); diff --git a/drivers/soc/samsung/acpm/fw_header/common.h b/drivers/soc/samsung/acpm/fw_header/common.h new file mode 100644 index 000000000000..0e1fbde87be7 --- /dev/null +++ b/drivers/soc/samsung/acpm/fw_header/common.h @@ -0,0 +1,163 @@ +/* + * common.h - common header for framework and plugins + * + * author : Yongjin Lee (yongjin0.lee@samsung.com) + * Jeonghoon Jang (jnghn.jang@samsung.com) + */ + +#ifndef __COMMON_H__ +#define __COMMON_H__ + +#ifndef CONFIG_EXYNOS_ACPM +typedef unsigned int u32; +typedef unsigned short u16; +typedef unsigned char u8; +typedef signed int s32; +typedef signed short s16; +typedef signed char s8; +typedef float f32; +typedef unsigned char bool; +#endif + +/** + * struct ipc_cmd_info - RX command buffer info for framework and plugins + * + * @rx_cmd: pointer to the RX buffer entry to be dequeued. + * @rx_cmd_indr: pointer to the RX indirection buffer entry to be dequeued. + */ +struct ipc_cmd_info { + u32 rx_cmd; + u32 rx_cmd_indr; +}; + +/** + * struct dbg_log_info - log buffer entry format + */ +struct dbg_log_info { + char str[8]; + u32 val; + u32 log_level; +}; + +/** + * struct build_info + */ +#define APSHARE_BUILDINFO_OFFSET (25 * 4) +#define BUILDINFO_ELEMENT_SIZE (24) +struct build_info { + char build_version[BUILDINFO_ELEMENT_SIZE]; + char build_time[BUILDINFO_ELEMENT_SIZE]; +}; + +/** + * struct acpm_ops - framework callbacks to be provided to plugins + * + * @set_timer_event: adds timer request which expires at every 'msec'. + * @del_timer_event: removes the existing timer request. + * @insert_dbg_log: inserts a log entry at predefined log buffer region. + * @get_tx_dest: does full check of tx queue and returns ptr to enqueue. + * @enqueue_tx: updated tx_front ptr of tx queue and generates mbox irq. + * @get_total_size: returns size_of(framework + all plugins) + */ +struct acpm_ops { + s32 (*set_timer_event) (u32 plugin_id, u32 msec); + void (*del_timer_event) (u32 plugin_id); + void (*insert_dbg_log) (u32 plugin_id, struct dbg_log_info *log); + u32 (*get_tx_dest) (u32 ch_num, u32 *index); + void (*enqueue_tx) (u32 ch_num, u32 index); + u32 (*get_total_size) (void); + s32 (*secure_func) (void *plugin, u32 arg0, u32 arg1, + u32 arg2, u32 arg3); + s32 (*external_plugin_func) (void *plugin, u32 pid, + u32 *arg0, u32 *arg1, u32 *arg2); + s32 (*speedy_init)(void); + s32 (*speedy_read)(u32 addr); + s32 (*speedy_write)(u32 addr, u32 data); + void (*udelay)(u32 udelay); + void (*intr_enable)(u32 pid, u32 intr); + void (*intr_disable)(u32 pid, u32 intr); +}; + +/** + * struct plugin_ops - plugin callbacks to be provided to framework + * + * @ipc_handler: handler to be executed when ipc for this plugin is arrived. + * @irq_handler: handler to be executed when hw irq for this plugin is arrived. + * @timer_event_handler:handler to be executed when requested timer is expired. + */ +struct plugin_ops { + s32 (*ipc_handler) (struct ipc_cmd_info *cmd, u32 ch_num); + s32 (*irq_handler) (u32 intr); + s32 (*timer_event_handler) (void); + s32 (*extern_func) (u32 *arg0, u32 *arg1, u32 *arg2); + struct build_info info; +}; + +/** + * struct timer_desc - A descriptor for timer request + * + * @period: requested period that framework executes timer_event_handler. + * @multiplier: + */ +struct timer_desc { + u32 period; + u32 multiplier; +}; + +/** + * struct plugin - The basic plugin structure + * + * @id: Predefined id for this plugin. + * @base_addr: Predefined base addr for this plugin. (entrypoint) + * @acpm_ops: Framework callbacks. + * @plugin_ops: Plugin callbacks. + * @timer: Timer descriptor for this plugin. + * @is_attached: For dynamic plugin support. + * @size: The size of this plugin. + */ +struct plugin { + u32 id; +#ifndef CONFIG_EXYNOS_ACPM + void *base_addr; + struct acpm_ops *acpm_ops; + struct plugin_ops *plugin_ops; +#else + u32 base_addr; + u32 acpm_ops; + u32 plugin_ops; +#endif + u32 secure_func_mask; + u32 extern_func_mask; + struct timer_desc timer; + u8 is_attached; + u32 size; + u8 stay_attached; +#ifndef CONFIG_EXYNOS_ACPM + const char *fw_name; +#else + u32 fw_name; +#endif +}; + +typedef void (*pfn_plugin_init) (struct plugin *p); + +enum ret_type { + RET_OK, + RET_FAIL, +}; + +#ifndef CONFIG_EXYNOS_ACPM +#define NULL 0 +#define FALSE ((bool) 0) +#define TRUE ((bool) 1) + +#define __raw_writel(data, base) (*(volatile int*)(base) = (data)) +#define __raw_readl(base) (*(volatile int*)(base)) + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) + +#define EBUSY 100 +#define EINVAL 101 +#endif + +#endif diff --git a/drivers/soc/samsung/acpm/fw_header/framework.h b/drivers/soc/samsung/acpm/fw_header/framework.h new file mode 100644 index 000000000000..3dd599b24659 --- /dev/null +++ b/drivers/soc/samsung/acpm/fw_header/framework.h @@ -0,0 +1,280 @@ +/* + * framework.h - Header for the ACPM framework + * + * author : Yongjin Lee (yongjin0.lee@samsung.com) + * Jeonghoon Jang (jnghn.jang@samsung.com) + */ + +#ifndef __FRAMEWORK_H_ +#define __FRAMEWORK_H_ + +#ifdef CONFIG_EXYNOS_ACPM +#include "common.h" +#endif + +/** + * struct acpm_framework - General information for ACPM framework. + * + * @plugins: Pointer to soc-specific plugins array. + * @pid_framework: Plugin ID for ACPM framework. + * @pid_max: # of plugins. + * @ipc_max: # of IPC channels. + * @irq_max: # of interrupt sources. + * @ktime_index: ktime information from the kernel. + * @log_buf_rear: Rear pointer of the log buffer. + * @log_buf_front: Front pointer of the log buffer. + * @log_data: Base address of the log buffer. + * @log_entry_size: Entry size of the log buffer. + * @log_entry_len: Length of the log buffer. + * @ipc_base: Base address of the IPC buffer. + * @ipc_buf_tx_size: Size of the IPC TX buffer. + * @total_size: Size of the framework and all plugins attached. + * @fw_size: Size of the ACPM framework. + * @intr_to_skip: Disabled interrupts. + * @intr_flag_offset: Field offset for Mailbox interrupt pending register. + */ +struct acpm_framework { +#ifndef CONFIG_EXYNOS_ACPM + struct plugin *plugins; +#else + u32 plugins; +#endif + u32 num_plugins; +#ifndef CONFIG_EXYNOS_ACPM + struct ipc_channel *ipc_channels; +#else + u32 ipc_channels; +#endif + u32 num_ipc_channels; + u32 pid_framework; + u32 pid_max; + u32 ipc_ap_max; + u32 ipc_cp_max; + u32 ipc_gnss_max; + u32 ipc_shub_max; + u32 ipc_wlbt_max; + u32 ipc_max; + u32 irq_max; + u32 ktime_index; + u32 log_buf_rear; + u32 log_buf_front; + u32 log_data; + u32 log_entry_size; + u32 log_entry_len; + u32 ipc_base; + u32 ipc_buf_tx_size; + u32 total_size; + u32 fw_size; + u32 intr_to_skip; + u32 intr_flag_offset; + struct build_info info; + u32 regulator_id; + u32 mifdn_count; + u32 inform_head; + u32 inform0[6]; + u32 inform1[6]; + u32 inform2[6]; + u32 inform3[6]; +}; + +/** + * struct channel_info - IPC information of a channel + * + * @rx_rear: Rear ptr for RX cmd queue. (for dequeue) + * @rx_front: Front ptr for RX cmd queue. (just for empty check) + * @rx_base: Predefined base addr for RX cmd queue. + * @rx_indr_buf: Predefined base addr for RX indirect buffer. + * @tx_rear: Rear ptr for TX queue. + * @tx_front: Front ptr for TX queue. + * @tx_base: Predefined base addr for TX queue. + * @q_len: Length of both TX/RX queue. + * @q_elem_size: Element size of both TX/RX queue. + * @credit: For preventing starvation of certain plugin. + */ +struct channel_info { + u32 rx_rear; + u32 rx_front; + u32 rx_base; + u32 rx_indr_buf; + u32 rx_indr_buf_base; + u32 rx_indr_buf_size; + u32 tx_rear; + u32 tx_front; + u32 tx_base; + u32 q_len; + u32 q_elem_size; + u32 credit; + u32 mbox_addr; +}; + +/** + * struct ipc_channel - descriptor for ipc channel. + * + * @id: The ipc channel's id. + * @field: The ipc channel's field in mailbox status register. + * @owner: This interrupt's Belonged plugin. + * @type: The ipc channel's memory type; QUEUE or Register. + * @ch: IPC information for this plugin's channel. + * @ap_poll: The flag indicating whether AP polls on this channel or not. (interrupt) + */ +struct ipc_channel { + u32 id; + u32 field; + s32 owner; + u32 type; + struct channel_info ch; + u32 ap_poll; +}; + +/** + * struct acpm_interrupt - descriptor for individual interrupt source. + * + * @field: The interrupt's field in NVIC pending register. + * @handler: Handler for this interrupt. + * @owner: This interrupt's Belonged plugin. + * @is_disabled: Flag to skip this interrupt or not. + */ +struct acpm_interrupt { + u32 field; + s32 (*handler)(u32 intr); + s32 owner; + bool is_disabled; + bool log_skip; +}; + +/** + * struct apm_peri - The abstraction for APM peripherals. + * + * @mpu_init: Initializes Core's MPU. (Regions, ...) + * @enable_systick: Enables System Tick. + * @disable_systick: Disables System Tick. + * @get_systick_cvr: Get the current value register of System Tick. + * @get_systick_icvr: Get (MAX_VAL - CUR_VAL) of System Tick. + * @get_systick_icvra: Get (MAX_VAL - CUR_VAL) & MAX_VAL of System Tick. + * @get_systick_csr: Get the current status register of System Tick. + * @enable_wdt: Enables Watchdog Timer. + * @disable_wdt: Disables Watchdog Timer. + * @enable_intr: Enables specific interrupts on NVIC. + * @get_enabled_intr: Get enabled interrupts on NVIC. + * @clr_pend_intr: Clears pended interrupts on NVIC. + * @get_pend_intr: Get Pended interrupts on NVIC. + * @get_mbox_pend_intr: Get Pended interrupts on Mailbox. + * @clr_mbox_pend_intr: Clears pended interrupts on Mailbox. + * @gen_mbox_intr_ap: Generates Mailbox interrupt to AP. + */ +struct apm_peri { + void (*power_mode_init) (void); + void (*peri_timer_init) (void); + void (*set_peri_timer_event) (u32 msec); + void (*del_peri_timer_event) (void); + u32 (*get_peri_timer_cvr) (void); + u32 (*get_peri_timer_icvr) (void); + u32 (*get_peri_timer_icvra) (void); + void (*mpu_init) (void); + void (*enable_systick) (void); + void (*disable_systick) (void); + u32 (*get_systick_cvr) (void); + u32 (*get_systick_icvr) (void); + u32 (*get_systick_icvra) (void); + u32 (*get_systick_csr) (void); + void (*enable_wdt) (void); + void (*disable_wdt) (void); + void (*enable_intr) (u32 idx, u32 intr); + void (*disable_intr) (u32 idx, u32 intr); + u32 (*get_enabled_intr) (u32 idx); + void (*clr_pend_intr) (u32 idx, u32 intr); + u32 (*get_pend_intr) (u32 idx); + u32 (*get_mbox_pend_intr) (u32 mbox_addr); + void (*clr_mbox_pend_intr) (u32 mbox_addr, u32 intr); + void (*gen_mbox_intr) (struct ipc_channel *ipc_ch); + u32 (*set_idle_ip) (u32 is_idle, u32 intr_filed); + s32 (*udelay_systick) (u32 udelay_us); + void (*set_wdtrst_req) (void); + u32 (*get_mif_up_req) (void); +}; +extern struct apm_peri apm_peri; + +/** + * struct plat_ops - Getters for platform specific data to ACPM framework. + * + * @plugin_base_init: initializes base address of plugins. + * @get_acpm_platform_data: inits the remaining data. + * @get_target_plugin: returns next plugin to be executed. + */ +struct plat_ops { + void (*plugin_base_init) (struct acpm_framework *acpm); + void (*get_acpm_platform_data) (struct acpm_framework *acpm); + u32 (*get_target_plugin) (u32 intr, void *acpm_data, u32 int_status, u32 *ch_num); + void (*ipc_channel_init) (struct acpm_framework *acpm); + void *(*get_plugins_extern_fn) (void); + u32 (*get_mbox_addr) (u32 intr); + u32 (*filter_buff_int_status) (u32 intr, u32 int_status); + u32 (*filter_queue_int_status) (u32 intr, u32 int_status); + void (*wait_for_intr) (void); + u32 (*system_reset) (void); +}; +extern struct plat_ops plat_ops; + +s32 shard_queue_ipc_dispatcher(u32 intr); +s32 shard_buffer_ipc_dispatcher(u32 intr); +s32 dummy_irq_handler(u32 intr); +void acpm_insert_dbg_log(u32 plugin_id, struct dbg_log_info *log); +char *acpm_strcpy(char *dest, const char *src); +extern void acpm_print(u32 id, const char *s, u32 int_data, u32 level); +extern void *get_intr_vector(u32 *irq_max); + +enum shard_buffer_type { + TYPE_QUEUE = 1, + TYPE_BUFFER, +}; + +#define LOG_ID_SHIFT (28) +#define LOG_LEVEL (27) +#define LOG_TIME_INDEX (22) +#define AVAILABLE_TIME (26000) + +#define ACPM_DEBUG_PRINT + +#ifdef ACPM_DEBUG_PRINT +#define ACPM_PRINT_ERR(id, s, int_data) acpm_print(id, s, int_data, 0xF) +#define ACPM_PRINT(id, s, int_data) acpm_print(id, s, int_data, 0x1) +#else +#define ACPM_PRINT_ERR(id, s, int_data) do {} while(0) +#define ACPM_PRINT(a, b, c) do {} while(0) +#endif + +#define true (1) +#define false (0) + +/* IPC Protocol bit field definitions */ +#define IPC_PRTC_OWN (31) +#define IPC_PRTC_RSP (30) +#define IPC_PRTC_INDR (29) +#define IPC_PRTC_ID (26) +#define IPC_PRTC_IDX (0x7 << IPC_PRTC_ID) +#define IPC_PRTC_DP_ATTACH (25) +#define IPC_PRTC_DP_DETACH (24) +#define IPC_PRTC_DP_CMD ((1 << IPC_PRTC_DP_ATTACH) | (1 << IPC_PRTC_DP_DETACH)) +#define IPC_PRTC_TEST (23) +#define IPC_PRTC_STOP (22) + +#define ERR_NOPLUGIN (10) +#define ERR_REJECTED (11) +#define ERR_NOTYET (12) + +/* speedy definition */ +struct speedyops { + int (*init)(void); + int (*tx)(unsigned int reg, unsigned int val); + int (*rx)(unsigned int reg); +}; + +extern struct speedyops speedyops; + +#define speedy_init() speedyops.init() +#define speedy_tx(reg, val) speedyops.tx(reg, val) +#define speedy_rx(reg) speedyops.rx(reg) + +#define MAGIC 0x0BAD0BAD + +#endif diff --git a/include/soc/samsung/acpm_ipc_ctrl.h b/include/soc/samsung/acpm_ipc_ctrl.h new file mode 100644 index 000000000000..ab45419427e9 --- /dev/null +++ b/include/soc/samsung/acpm_ipc_ctrl.h @@ -0,0 +1,74 @@ +#ifndef __ACPM_IPC_CTRL_H__ +#define __ACPM_IPC_CTRL_H__ + +typedef void (*ipc_callback)(unsigned int *cmd, unsigned int size); + +struct ipc_config { + unsigned int *cmd; + unsigned int *indirection_base; + unsigned int indirection_size; + bool response; + bool indirection; +}; + +#define ACPM_IPC_PROTOCOL_OWN (31) +#define ACPM_IPC_PROTOCOL_RSP (30) +#define ACPM_IPC_PROTOCOL_INDIRECTION (29) +#define ACPM_IPC_PROTOCOL_ID (26) +#define ACPM_IPC_PROTOCOL_IDX (0x7 << ACPM_IPC_PROTOCOL_ID) +#define ACPM_IPC_PROTOCOL_DP_A (25) +#define ACPM_IPC_PROTOCOL_DP_D (24) +#define ACPM_IPC_PROTOCOL_DP_CMD (0x3 << ACPM_IPC_PROTOCOL_DP_D) +#define ACPM_IPC_PROTOCOL_TEST (23) +#define ACPM_IPC_PROTOCOL_STOP (22) +#define ACPM_IPC_PROTOCOL_SEQ_NUM (16) + +#ifdef CONFIG_EXYNOS_ACPM +unsigned int acpm_ipc_request_channel(struct device_node *np, ipc_callback handler, + unsigned int *id, unsigned int *size); +unsigned int acpm_ipc_release_channel(struct device_node *np, unsigned int channel_id); +int acpm_ipc_send_data(unsigned int channel_id, struct ipc_config *cfg); +int acpm_ipc_send_data_sync(unsigned int channel_id, struct ipc_config *cfg); +int acpm_ipc_set_ch_mode(struct device_node *np, bool polling); +void exynos_acpm_reboot(void); +void acpm_stop_log(void); +#else + +static inline unsigned int acpm_ipc_request_channel(struct device_node *np, ipc_callback handler, + unsigned int *id, unsigned int *size) +{ + return 0; +} + +static inline unsigned int acpm_ipc_release_channel(struct device_node *np, unsigned int channel_id) +{ + return 0; +} + +static inline int acpm_ipc_send_data(unsigned int channel_id, struct ipc_config *cfg) +{ + return 0; +} + +static inline int acpm_ipc_send_data_sync(unsigned int channel_id, struct ipc_config *cfg) +{ + return 0; +} + +static inline int acpm_ipc_set_ch_mode(struct device_node *np, bool polling) +{ + return 0; +} + +static inline void exynos_acpm_reboot(void) +{ + return; +} + +static inline void acpm_stop_log(void) +{ + return; +} +#endif + +#endif diff --git a/include/soc/samsung/acpm_mfd.h b/include/soc/samsung/acpm_mfd.h new file mode 100644 index 000000000000..22baee829d03 --- /dev/null +++ b/include/soc/samsung/acpm_mfd.h @@ -0,0 +1,84 @@ +#ifndef __ACPM_MFD_H__ +#define __ACPM_MFD_H__ + +/* Define shift */ +#define SHIFT_BYTE (8) +#define SHIFT_REG (0) +#define SHIFT_TYPE (8) +#define SHIFT_FUNC (0) +#define SHIFT_DEST (8) +#define SHIFT_CNT (8) +#define SHIFT_WRITE_VAL (8) +#define SHIFT_UPDATE_VAL (8) +#define SHIFT_UPDATE_MASK (16) +#define SHIFT_RETURN (24) +#define SHIFT_BULK_VAL SHIFT_BYTE + +/* Define mask */ +#define MASK_BYTE (0xFF) +#define MASK_REG MASK_BYTE +#define MASK_TYPE (0xF) +#define MASK_FUNC MASK_BYTE +#define MASK_DEST MASK_BYTE +#define MASK_CNT MASK_BYTE +#define MASK_WRITE_VAL MASK_BYTE +#define MASK_UPDATE_VAL MASK_BYTE +#define MASK_UPDATE_MASK MASK_BYTE +#define MASK_RETURN MASK_BYTE +#define MASK_BULK_VAL MASK_BYTE + +/* Command */ +#define set_protocol(data, protocol) ((data & MASK_##protocol) << SHIFT_##protocol) +#define set_bulk_protocol(data, protocol, n) ((data & MASK_##protocol) << (SHIFT_##protocol * (n))) +#define read_protocol(data, protocol) ((data >> SHIFT_##protocol) & MASK_##protocol) +#define read_bulk_protocol(data, protocol, n) ((data >> (SHIFT_##protocol * (n))) & MASK_##protocol) + +#define ACPM_MFD_PREFIX "ACPM-MFD: " + +#ifndef pr_fmt +#define pr_fmt(fmt) fmt +#endif + +#ifdef ACPM_MFD_DEBUG +#define ACPM_MFD_PRINT(fmt, ...) printk(ACPM_MFD_PREFIX pr_fmt(fmt), ##__VA_ARGS__) +#else +#define ACPM_MFD_PRINT(fmt, ...) +#endif + +enum mfd_func { + FUNC_READ, + FUNC_WRITE, + FUNC_UPDATE, + FUNC_BULK_READ, + FUNC_BULK_WRITE, +}; + +#ifdef CONFIG_EXYNOS_ACPM +extern int exynos_acpm_read_reg(u16 type, u8 reg, u8 *dest); +extern int exynos_acpm_bulk_read(u16 type, u8 reg, int count, u8 *buf); +extern int exynos_acpm_write_reg(u16 type, u8 reg, u8 value); +extern int exynos_acpm_bulk_write(u16 type, u8 reg, int count, u8 *buf); +extern int exynos_acpm_update_reg(u16 type, u8 reg, u8 value, u8 mask); +#else +static inline int exynos_acpm_read_reg(u16 type, u8 reg, u8 *dest) +{ + return 0; +} +static inline int exynos_acpm_bulk_read(u16 type, u8 reg, int count, u8 *buf) +{ + return 0; +} +static inline int exynos_acpm_write_reg(u16 type, u8 reg, u8 value) +{ + return 0; +} +static inline int exynos_acpm_bulk_write(u16 type, u8 reg, int count, u8 *buf) +{ + return 0; +} +static inline int exynos_acpm_update_reg(u16 type, u8 reg, u8 value, u8 mask) +{ + return 0; +} +#endif +#endif /* __ACPM_MFD_H__ */ -- 2.20.1