/* * 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 #include "acpm.h" #include "acpm_ipc.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 void __iomem *fvmap_base_address; static struct acpm_info *exynos_acpm; static int acpm_send_data(struct device_node *node, unsigned int check_id, struct ipc_config *config); u32 acpm_num_eint_clk_users; 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; } void *get_fvmap_base(void) { return fvmap_base_address; } EXPORT_SYMBOL_GPL(get_fvmap_base); 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 = NULL; struct device_node *node, *child; const __be32 *prop; unsigned int offset; 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); if (fw_name && strstr(fw_name, "dvfs")) fvmap_base_address = fw_base_addr + plugins[i].size; } 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"))) { fvmap_base_address = 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_base_address += offset; if (of_property_read_bool(exynos_acpm->dev->of_node, "use-plugin-library")) fvmap_base_address = acpm_srambase + offset; pr_err("acpm_sram_base: 0x%x\n", acpm_srambase); pr_err("plugins[i].base_addr & ~0x1: 0x%x\n", (plugins[i].base_addr & ~0x1)); pr_err("fvmap_base_address: 0x%x\n", fvmap_base_address); } } } } 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; ipc_time_start = sched_clock(); ret = acpm_send_data(exynos_acpm->dev->of_node, 3, &config); ipc_time_end = sched_clock(); 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; } 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"); void acpm_init_eint_clk_req(u32 eint_num) { #if defined(CONFIG_SOC_S5E9925) struct ipc_config config; int ret = 0; unsigned int cmd[4] = {0, }; if (++acpm_num_eint_clk_users > 1) { WARN(1, "There is already 1 eint clk users\n"); return; } config.cmd = cmd; /* Plugin ID = FLEXPMU */ config.cmd[0] = (1 << ACPM_IPC_PROTOCOL_TEST) | (0x4 << ACPM_IPC_PROTOCOL_ID) | (0x6 << IPC_PLUGIN_ID); config.cmd[0] |= (4 << 0); config.cmd[1] = eint_num; config.response = true; config.indirection = false; ret = acpm_send_data(exynos_acpm->dev->of_node, 4, &config); config.cmd = NULL; #endif } EXPORT_SYMBOL_GPL(acpm_init_eint_clk_req); void acpm_init_eint_nfc_clk_req(u32 eint_num) { #if defined(CONFIG_SOC_S5E9925) struct ipc_config config; int ret = 0; unsigned int cmd[4] = {0, }; config.cmd = cmd; /* Plugin ID = FLEXPMU */ config.cmd[0] = (1 << ACPM_IPC_PROTOCOL_TEST) | (0x4 << ACPM_IPC_PROTOCOL_ID) | (0x6 << IPC_PLUGIN_ID); config.cmd[0] |= (5 << 0); config.cmd[1] = eint_num; config.response = true; config.indirection = false; ret = acpm_send_data(exynos_acpm->dev->of_node, 4, &config); config.cmd = NULL; #endif } EXPORT_SYMBOL_GPL(acpm_init_eint_nfc_clk_req); 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); } 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++; } } u32 exynos_get_peri_timer_icvra(void) { return (EXYNOS_PERI_TIMER_MAX - __raw_readl(exynos_acpm->timer_base + EXYNOS_TIMER_APM_TCVR)) & EXYNOS_PERI_TIMER_MAX; } 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; soc_id = exynos_soc_info.product_id; revision = exynos_soc_info.revision; acpm_enter_wfi(); } EXPORT_SYMBOL_GPL(exynos_acpm_reboot); void exynos_acpm_force_apm_wdt_reset(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_WDT); ret = acpm_send_data(exynos_acpm->dev->of_node, ACPM_IPC_PROTOCOL_STOP_WDT, &config); pr_err("ACPM force WDT reset. (ret: %d)\n", ret); } EXPORT_SYMBOL_GPL(exynos_acpm_force_apm_wdt_reset); 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_reboot_register_acpm_ops(exynos_acpm_reboot); exynos_acpm = acpm; acpm_debugfs_init(acpm); exynos_acpm_timer_clear(); ret = plugins_init(); dev_info(&pdev->dev, "acpm probe done.\n"); return ret; } static int acpm_remove(struct platform_device *pdev) { return 0; } static const struct of_device_id acpm_ipc_match[] = { { .compatible = "samsung,exynos-acpm-ipc" }, {}, }; MODULE_DEVICE_TABLE(of, acpm_ipc_match); 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 const struct of_device_id acpm_match[] = { { .compatible = "samsung,exynos-acpm" }, {}, }; MODULE_DEVICE_TABLE(of, acpm_match); 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 exynos_acpm_init(void) { platform_driver_register(&samsung_acpm_ipc_driver); platform_driver_register(&samsung_acpm_driver); return 0; } postcore_initcall_sync(exynos_acpm_init); static void exynos_acpm_exit(void) { platform_driver_unregister(&samsung_acpm_ipc_driver); platform_driver_unregister(&samsung_acpm_driver); } module_exit(exynos_acpm_exit); MODULE_LICENSE("GPL");