/* * tegra-core-action.c - connect Tegra CORE DVFS driver to thermal framework * * Copyright (C) 2012-2017 NVIDIA Corporation. All rights reserved. * * 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. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #define CORE_CDEV_TYPE_FLOOR "CORE-floor" #define CORE_CDEV_TYPE_CAP "CORE-cap" /** * struct tegra_core_cdev_data - CORE DVFS cooling device info * @cdev_floor: pointer to floor cooling device, after thermal registration * @cdev_cap: pointer to cap cooling device, after thermal registration * device */ struct tegra_core_cdev_data { struct thermal_cooling_device *cdev_floor; struct thermal_cooling_device *cdev_cap; struct clk **cap_clks; int cap_clks_num; }; static struct tegra_core_cdev_data tegra_core_cdev_data; /* * Thermal cooling device interface */ static int tegra_core_cdev_get_max_state(struct thermal_cooling_device *cdev, unsigned long *max_state) { enum tegra_dvfs_core_thermal_type type; int num; type = (enum tegra_dvfs_core_thermal_type)cdev->devdata; num = tegra_dvfs_core_count_thermal_states(type); if (num <= 0) *max_state = 0; else *max_state = num; return 0; } static int tegra_core_cdev_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *cur_state) { enum tegra_dvfs_core_thermal_type type; type = (enum tegra_dvfs_core_thermal_type)cdev->devdata; *cur_state = tegra_dvfs_core_get_thermal_index(type); return 0; } static int tegra_core_floor_cdev_set_state(struct thermal_cooling_device *cdev, unsigned long cur_state) { enum tegra_dvfs_core_thermal_type type; type = (enum tegra_dvfs_core_thermal_type)cdev->devdata; return tegra_dvfs_core_update_thermal_index(type, cur_state); } static int tegra_core_cap_cdev_set_state(struct thermal_cooling_device *cdev, unsigned long cur_state) { int i, ret; enum tegra_dvfs_core_thermal_type type = TEGRA_DVFS_CORE_THERMAL_CAP; bool t_up = cur_state > tegra_dvfs_core_get_thermal_index(type); if (t_up) { for (i = 0; i < tegra_core_cdev_data.cap_clks_num; i++) tegra_dvfs_core_set_thermal_cap( tegra_core_cdev_data.cap_clks[i], cur_state); } ret = tegra_dvfs_core_update_thermal_index(type, cur_state); if (!t_up) { for (i = 0; i < tegra_core_cdev_data.cap_clks_num; i++) tegra_dvfs_core_set_thermal_cap( tegra_core_cdev_data.cap_clks[i], cur_state); } return ret; } static struct thermal_cooling_device_ops tegra_core_floor_cooling_ops = { .get_max_state = tegra_core_cdev_get_max_state, .get_cur_state = tegra_core_cdev_get_cur_state, .set_cur_state = tegra_core_floor_cdev_set_state, }; static struct thermal_cooling_device_ops tegra_core_cap_cooling_ops = { .get_max_state = tegra_core_cdev_get_max_state, .get_cur_state = tegra_core_cdev_get_cur_state, .set_cur_state = tegra_core_cap_cdev_set_state, }; static int tegra_core_register_therm(struct platform_device *pdev, enum tegra_dvfs_core_thermal_type type) { struct thermal_cooling_device *tcd; struct device *dev = &pdev->dev; int ret = 0; if (type == TEGRA_DVFS_CORE_THERMAL_FLOOR) { tcd = thermal_of_cooling_device_register(dev->of_node, CORE_CDEV_TYPE_FLOOR, (void *)TEGRA_DVFS_CORE_THERMAL_FLOOR, &tegra_core_floor_cooling_ops); if (IS_ERR_OR_NULL(tcd)) { dev_err(dev, "Tegra CORE DVFS thermal reaction: failed to register floor\n"); ret = -EINVAL; goto error; } tegra_core_cdev_data.cdev_floor = tcd; dev_info(dev, "Tegra CORE DVFS 'floor cooling device' registered\n"); } else if (type == TEGRA_DVFS_CORE_THERMAL_CAP) { tcd = thermal_of_cooling_device_register(dev->of_node, CORE_CDEV_TYPE_CAP, (void *)TEGRA_DVFS_CORE_THERMAL_CAP, &tegra_core_cap_cooling_ops); if (IS_ERR_OR_NULL(tcd)) { dev_err(dev, "Tegra CORE DVFS thermal reaction: failed to register cap\n"); ret = -EINVAL; goto error; } tegra_core_cdev_data.cdev_cap = tcd; dev_info(dev, "Tegra CORE DVFS 'cap cooling device' registered\n"); } else { ret = -EINVAL; } error: return ret; } static void tegra_core_cdev_cap_set_max_state(void) { int i; int max_state = tegra_dvfs_core_count_thermal_states( TEGRA_DVFS_CORE_THERMAL_CAP); if (max_state > 0) { for (i = 0; i < tegra_core_cdev_data.cap_clks_num; i++) tegra_dvfs_core_set_thermal_cap( tegra_core_cdev_data.cap_clks[i], max_state); } } static void tegra_core_cdev_put_cap_clks(void) { int i; for (i = 0; i < tegra_core_cdev_data.cap_clks_num; i++) { if (!tegra_core_cdev_data.cap_clks[i]) break; clk_put(tegra_core_cdev_data.cap_clks[i]); } kfree(tegra_core_cdev_data.cap_clks); } static int tegra_core_cdev_of_get_cap_clks(struct device *dev) { const char *name; struct property *prop; int i = 0; struct device_node *np = dev->of_node; int num = of_property_count_strings(np, "clock-names"); if (num <= 0) { dev_err(dev, "No cap clocks specified\n"); return -EINVAL; } tegra_core_cdev_data.cap_clks = kzalloc(num * sizeof(struct clk *), GFP_KERNEL); if (!tegra_core_cdev_data.cap_clks) { dev_err(dev, "Failed to allocate cap clocks array\n"); return -ENOMEM; } tegra_core_cdev_data.cap_clks_num = num; of_property_for_each_string(np, "clock-names", prop, name) { struct clk *c = clk_get(dev, name); if (IS_ERR_OR_NULL(c)) { dev_err(dev, "Failed to get cap clk %s\n", name); tegra_core_cdev_put_cap_clks(); return -ENOENT; } tegra_core_cdev_data.cap_clks[i++] = c; } return 0; } static int tegra_core_cdev_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; const char *cdev_type; int ret; ret = tegra_dvfs_core_count_thermal_states( TEGRA_DVFS_CORE_THERMAL_FLOOR); if (ret < 0) return -EPROBE_DEFER; cdev_type = of_get_property(dev->of_node, "cdev-type", NULL); if (IS_ERR_OR_NULL(cdev_type)) { dev_err(dev, "Tegra CORE DVFS thermal reaction: missing cdev type\n"); ret = -ENOENT; goto error; } if (!strcmp(cdev_type, CORE_CDEV_TYPE_FLOOR)) { ret = tegra_core_register_therm(pdev, TEGRA_DVFS_CORE_THERMAL_FLOOR); } else if (!strcmp(cdev_type, CORE_CDEV_TYPE_CAP)) { ret = tegra_core_cdev_of_get_cap_clks(dev); if (!ret) { ret = tegra_core_register_therm( pdev, TEGRA_DVFS_CORE_THERMAL_CAP); if (ret) { tegra_core_cdev_cap_set_max_state(); tegra_core_cdev_put_cap_clks(); } } } else { ret = -ENOENT; dev_err(dev, "Tegra CORE DVFS thermal reaction: incorrect cdev type\n"); } error: return ret; } static int tegra_core_cdev_remove(struct platform_device *pdev) { if (tegra_core_cdev_data.cdev_floor) thermal_cooling_device_unregister( tegra_core_cdev_data.cdev_floor); if (tegra_core_cdev_data.cdev_cap) thermal_cooling_device_unregister( tegra_core_cdev_data.cdev_cap); if (tegra_core_cdev_data.cap_clks) tegra_core_cdev_put_cap_clks(); return 0; } static const struct of_device_id tegra_core_cdev_match[] = { { .compatible = "nvidia,tegra-core-cdev-action", }, {}, }; static struct platform_driver tegra_core_cdev_driver = { .driver = { .name = "tegra_core_action", .owner = THIS_MODULE, .of_match_table = tegra_core_cdev_match, }, .probe = tegra_core_cdev_probe, .remove = tegra_core_cdev_remove, }; module_platform_driver(tegra_core_cdev_driver); MODULE_DESCRIPTION("Tegra CORE DVFS thermal reaction driver"); MODULE_LICENSE("GPL");