// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2020 MediaTek Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "thermal_ipi.h" #define THERMAL_IPI_TIMEOUT_MS (2000) #define for_each_ipi_target(i) \ for (i = 0; i < NUM_THERMAL_IPI_TARGET; i++) #define is_target_invalid(t) (t != IPI_TARGET_ALL && t >= NUM_THERMAL_IPI_TARGET) static DEFINE_MUTEX(ipi_send_lock); static struct thermal_ipi_target_data ipi_data[NUM_THERMAL_IPI_TARGET] = { [IPI_TARGET_SSPM] = { .name = "sspm", #if IS_ENABLED(CONFIG_MTK_TINYSYS_SSPM_SUPPORT) .config = { .dev = &sspm_ipidev, .id = IPIS_C_THERMAL, .use_platform_ipi = 0, .opt = IPI_SEND_POLLING, }, #endif }, [IPI_TARGET_MCUPM] = { .name = "mcupm", #if IS_ENABLED(CONFIG_MTK_TINYSYS_MCUPM_SUPPORT) .config = { /* symbol is not exported */ /* .dev = &mcupm_ipidev, */ .id = CH_S_PLATFORM, .use_platform_ipi = 1, .opt = IPI_SEND_WAIT, }, #endif }, }; static int thermal_ipi_get_idx(char *name) { int i; if (!strncasecmp(name, "all", 3)) return IPI_TARGET_ALL; for_each_ipi_target(i) { if (!strncasecmp(name, ipi_data[i].name, strlen(name))) return i; } return -1; } static int thermal_ipi_register(enum thermal_ipi_target target) { struct thermal_ipi_config *cfg; int ret = -1; if (target >= NUM_THERMAL_IPI_TARGET) return -EINVAL; cfg = &ipi_data[target].config; if (!cfg->dev) { pr_err("[thermal_ipi] target %s is not support\n", ipi_data[target].name); return -EPERM; } /* no need to register if we share IPI pin with platform driver */ if (cfg->use_platform_ipi) goto register_done; ret = mtk_ipi_register(cfg->dev, cfg->id, NULL, NULL, (void *)&cfg->ack_data); if (ret != 0) { pr_err("[thermal_ipi] Fail to register %s IPI, ret:%d\n", ipi_data[target].name, ret); return ret; } register_done: pr_info("[thermal_ipi] register %s ipi done\n", ipi_data[target].name); ipi_data[target].is_registered = 1; return 0; } static int thermal_ipi_send(unsigned int cmd, enum thermal_ipi_target target, struct thermal_ipi_data *thermal_data) { struct thermal_ipi_config *cfg; unsigned int ack_data; int ret = -1; if (target >= NUM_THERMAL_IPI_TARGET) return IPI_NOT_SUPPORT; mutex_lock(&ipi_send_lock); if (!ipi_data[target].is_registered) { ack_data = IPI_NOT_SUPPORT; goto end; } cfg = &ipi_data[target].config; thermal_data->cmd = cmd; ret = mtk_ipi_send_compl(cfg->dev, cfg->id, cfg->opt, thermal_data, THERMAL_SLOT_NUM, THERMAL_IPI_TIMEOUT_MS); if (ret != 0) { pr_err("[thermal_ipi] send cmd(%d) to %s error ret:%d\n", cmd, ipi_data[target].name, ret); ack_data = IPI_FAIL; goto end; } ack_data = cfg->ack_data; pr_info("[thermal_ipi] %s cmd(%d) send done, ack: %u\n", ipi_data[target].name, cmd, ack_data); end: mutex_unlock(&ipi_send_lock); return ack_data; } static void thermal_ipi_send_all(unsigned int cmd, struct thermal_ipi_data *thermal_data) { unsigned int i; for_each_ipi_target(i) thermal_ipi_send(cmd, (enum thermal_ipi_target)i, thermal_data); } static int thermal_ipi_set_throttle_enable(enum thermal_ipi_target target, int enable) { struct thermal_ipi_data thermal_data; unsigned int ipi_cmd; int ret = 0; if (is_target_invalid(target)) return -EINVAL; ipi_cmd = THERMAL_THROTTLE_DISABLE; thermal_data.arg[0] = (enable) ? 0 : 1; thermal_data.arg[1] = 0; thermal_data.arg[2] = 0; if (target == IPI_TARGET_ALL) thermal_ipi_send_all(ipi_cmd, &thermal_data); else ret = thermal_ipi_send(ipi_cmd, target, &thermal_data); return ret; } static ssize_t setting_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int idx; char cmd[20], name[THERMAL_TARGET_NAME_LEN]; if (sscanf(buf, "%9s %19s", name, cmd) == 2) { idx = thermal_ipi_get_idx(name); if (idx < 0) { pr_err("[thermal_ipi] target %s not found\n", name); return -EINVAL; } pr_info("[thermal_ipi] idx(%d) cmd(%s)\n", idx, cmd); if (!strncasecmp(cmd, "disable", 7)) { thermal_ipi_set_throttle_enable((enum thermal_ipi_target)idx, 0); } else if (!strncasecmp(cmd, "enable", 6)) { thermal_ipi_set_throttle_enable((enum thermal_ipi_target)idx, 1); } else { pr_err("[thermal_ipi] invalid cmd(%s)\n", cmd); return -EINVAL; } } else { pr_err("[thermal_ipi] invalid input\n"); return -EINVAL; } return count; } static struct kobj_attribute thermal_ipi_attr = __ATTR_WO(setting); static struct attribute *thermal_ipi_attrs[] = { &thermal_ipi_attr.attr, NULL }; static struct attribute_group thermal_ipi_attr_group = { .name = "thermal_ipi", .attrs = thermal_ipi_attrs, }; static const struct of_device_id thermal_ipi_of_match[] = { { .compatible = "mediatek,thermal-ipi", }, {}, }; MODULE_DEVICE_TABLE(of, thermal_ipi_of_match); static int thermal_ipi_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; unsigned int target_bitmask; int ret, i; if (!np) { dev_err(dev, "thermal_ipi DT node not found\n"); return -ENODEV; } ret = of_property_read_u32(np, "target-bitmask", &target_bitmask); if (ret < 0) { dev_err(dev, "failed to ipi target bitmask from device tree\n"); return -EINVAL; } #if IS_ENABLED(CONFIG_MTK_TINYSYS_MCUPM_SUPPORT) ipi_data[IPI_TARGET_MCUPM].config.dev = (struct mtk_ipi_device *)get_mcupm_ipidev(); #endif for_each_ipi_target(i) { if (target_bitmask & (1 << i)) thermal_ipi_register((enum thermal_ipi_target)i); } ret = sysfs_create_group(kernel_kobj, &thermal_ipi_attr_group); if (ret) dev_err(dev, "failed to create thermal_ipi sysfs, ret=%d!\n", ret); return ret; } static int thermal_ipi_remove(struct platform_device *pdev) { return sysfs_create_group(kernel_kobj, &thermal_ipi_attr_group); } static struct platform_driver thermal_ipi_driver = { .probe = thermal_ipi_probe, .remove = thermal_ipi_remove, .driver = { .name = "mtk-thermal-ipi", .of_match_table = thermal_ipi_of_match, }, }; module_platform_driver(thermal_ipi_driver); MODULE_AUTHOR("Shun-Yao Yang "); MODULE_DESCRIPTION("Mediatek thermal ipi driver"); MODULE_LICENSE("GPL v2");