307 lines
8.6 KiB
C

/*
* tegra124-dfll-action.c - connect T124 DFLL clock driver to thermal framework
*
* Copyright (C) 2012-2013 NVIDIA Corporation. All rights reserved.
*
* Aleksandr Frid <afrid@nvidia.com>
* Paul Walmsley <pwalmsley@nvidia.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.
*
* 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 <http://www.gnu.org/licenses/>.
*
* To do:
* - Does not handle more than one DFLL per system.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/thermal.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/clk-provider.h>
#include <linux/clk/tegra124-dfll.h>
/**
* struct tegra124_dfll_cdev_data - DFLL cooling device info
* @cdev_floor: pointer to floor cooling device, after thermal registration
* @cdev_cap: pointer to cap cooling device, after thermal registration
* @dfll_pdev: pointer to the DFLL instance associated with this @cdev
*/
struct tegra124_dfll_cdev_data {
struct thermal_cooling_device *cdev_floor;
struct thermal_cooling_device *cdev_cap;
struct platform_device *dfll_pdev;
};
static struct tegra124_dfll_cdev_data tegra124_dfll_cdev_data;
/*
* Thermal cooling_device interface
*/
static int
tegra124_dfll_cdev_floor_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *max_state)
{
struct platform_device *pdev = (struct platform_device *)cdev->devdata;
*max_state = tegra124_dfll_count_therm_states(pdev,
TEGRA_DFLL_THERM_FLOOR);
return 0;
}
static int
tegra124_dfll_cdev_floor_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *cur_state)
{
struct platform_device *pdev = (struct platform_device *)cdev->devdata;
*cur_state = tegra124_dfll_get_thermal_index(pdev,
TEGRA_DFLL_THERM_FLOOR);
return 0;
}
static int
tegra124_dfll_cdev_floor_set_state(struct thermal_cooling_device *cdev,
unsigned long cur_state)
{
struct platform_device *pdev = (struct platform_device *)cdev->devdata;
return tegra124_dfll_update_thermal_index(pdev,
TEGRA_DFLL_THERM_FLOOR,
cur_state);
}
static struct thermal_cooling_device_ops tegra124_dfll_floor_cooling_ops = {
.get_max_state = tegra124_dfll_cdev_floor_get_max_state,
.get_cur_state = tegra124_dfll_cdev_floor_get_cur_state,
.set_cur_state = tegra124_dfll_cdev_floor_set_state,
};
static int tegra124_dfll_register_therm_floor(struct platform_device *pdev,
struct platform_device *dfll_pdev)
{
struct thermal_cooling_device *tcd;
struct device *dev = &pdev->dev;
const char *cdev_type;
int ret;
/* just report error - initialized at WC temperature, anyway */
cdev_type = of_get_property(dev->of_node, "cdev-floor-type", NULL);
if (IS_ERR_OR_NULL(cdev_type)) {
dev_err(dev,
"Tegra124 DFLL thermal reaction: missing floor type\n");
ret = -EINVAL;
goto error;
}
tcd = thermal_cooling_device_register((char *)cdev_type,
(void *)dfll_pdev,
&tegra124_dfll_floor_cooling_ops);
if (IS_ERR(tcd)) {
dev_err(dev,
"Tegra124 DFLL thermal reaction: failed to register\n");
ret = PTR_ERR(tcd);
goto error;
}
tegra124_dfll_cdev_data.cdev_floor = tcd;
tegra124_dfll_cdev_data.dfll_pdev = dfll_pdev;
ret = tegra124_dfll_attach_thermal(dfll_pdev,
TEGRA_DFLL_THERM_FLOOR,
tcd);
if (ret) {
dev_err(dev,
"Tegra124 DFLL thermal reaction: failed to attach\n");
thermal_cooling_device_unregister(tcd);
goto error;
}
dev_info(dev, "Tegra124 DFLL 'floor cooling device' registered\n");
error:
return ret;
}
static int
tegra124_dfll_cdev_cap_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *max_state)
{
struct platform_device *pdev = (struct platform_device *)cdev->devdata;
*max_state = tegra124_dfll_count_therm_states(pdev,
TEGRA_DFLL_THERM_CAP);
return 0;
}
static int
tegra124_dfll_cdev_cap_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *cur_state)
{
struct platform_device *pdev = (struct platform_device *)cdev->devdata;
*cur_state = tegra124_dfll_get_thermal_index(pdev,
TEGRA_DFLL_THERM_CAP);
return 0;
}
static int
tegra124_dfll_cdev_cap_set_state(struct thermal_cooling_device *cdev,
unsigned long cur_state)
{
struct platform_device *pdev = (struct platform_device *)cdev->devdata;
return tegra124_dfll_update_thermal_index(pdev,
TEGRA_DFLL_THERM_CAP,
cur_state);
}
static struct thermal_cooling_device_ops tegra124_dfll_cap_cooling_ops = {
.get_max_state = tegra124_dfll_cdev_cap_get_max_state,
.get_cur_state = tegra124_dfll_cdev_cap_get_cur_state,
.set_cur_state = tegra124_dfll_cdev_cap_set_state,
};
static int tegra124_dfll_register_therm_cap(struct platform_device *pdev,
struct platform_device *dfll_pdev)
{
struct thermal_cooling_device *tcd;
struct device *dev = &pdev->dev;
const char *cdev_type;
int ret;
/* just report error - initialized at WC temperature, anyway */
cdev_type = of_get_property(dev->of_node, "cdev-cap-type", NULL);
if (IS_ERR_OR_NULL(cdev_type)) {
dev_err(dev,
"Tegra124 DFLL thermal reaction: missing cap type\n");
ret = -EINVAL;
goto error;
}
tcd = thermal_cooling_device_register((char *)cdev_type,
(void *)dfll_pdev,
&tegra124_dfll_cap_cooling_ops);
if (IS_ERR(tcd)) {
dev_err(dev,
"Tegra124 DFLL thermal reaction: failed to register\n");
ret = PTR_ERR(tcd);
goto error;
}
tegra124_dfll_cdev_data.cdev_cap = tcd;
tegra124_dfll_cdev_data.dfll_pdev = dfll_pdev;
ret = tegra124_dfll_attach_thermal(dfll_pdev,
TEGRA_DFLL_THERM_CAP,
tcd);
if (ret) {
dev_err(dev,
"Tegra124 DFLL thermal reaction: failed to attach\n");
thermal_cooling_device_unregister(tcd);
goto error;
}
dev_info(dev, "Tegra124 DFLL 'cap cooling device' registered\n");
error:
return ret;
}
static int tegra124_dfll_cdev_probe(struct platform_device *pdev)
{
struct device_node *dn;
const __be32 *prop;
struct platform_device *dfll_pdev;
struct device *dev = &pdev->dev;
struct clk *cpu_dfll_clk;
int ret;
prop = of_get_property(dev->of_node, "act-dev", NULL);
if (!prop) {
dev_err(dev,
"Tegra124 DFLL thermal reaction: missing dfll node\n");
return -ENOENT;
}
dn = of_find_node_by_phandle(be32_to_cpup(prop));
if (!dn) {
dev_err(dev,
"Tegra124 DFLL thermal reaction: missing dfll node\n");
return -ENOENT;
}
cpu_dfll_clk = devm_clk_get(dev, "dfllCPU_out");
if (IS_ERR(cpu_dfll_clk)) {
dev_info(dev,
"Tegra124 DFLL thermal reaction: DFLL not ready\n");
ret = -EPROBE_DEFER;
goto error;
}
dfll_pdev = of_find_device_by_node(dn);
if (!dfll_pdev) {
dev_err(dev,
"Tegra124 DFLL thermal reaction: missing dfll dev\n");
ret = -ENOENT;
goto error;
}
ret = tegra124_dfll_register_therm_floor(pdev, dfll_pdev);
if (ret)
goto error;
ret = tegra124_dfll_register_therm_cap(pdev, dfll_pdev);
if (ret) {
tegra124_dfll_detach_thermal(tegra124_dfll_cdev_data.dfll_pdev,
TEGRA_DFLL_THERM_FLOOR,
tegra124_dfll_cdev_data.cdev_floor);
thermal_cooling_device_unregister(
tegra124_dfll_cdev_data.cdev_floor);
}
error:
of_node_put(dn);
return ret;
}
static int tegra124_dfll_cdev_remove(struct platform_device *pdev)
{
tegra124_dfll_detach_thermal(tegra124_dfll_cdev_data.dfll_pdev,
TEGRA_DFLL_THERM_FLOOR,
tegra124_dfll_cdev_data.cdev_floor);
thermal_cooling_device_unregister(tegra124_dfll_cdev_data.cdev_floor);
tegra124_dfll_detach_thermal(tegra124_dfll_cdev_data.dfll_pdev,
TEGRA_DFLL_THERM_CAP,
tegra124_dfll_cdev_data.cdev_cap);
thermal_cooling_device_unregister(tegra124_dfll_cdev_data.cdev_cap);
return 0;
}
static const struct of_device_id tegra124_dfll_cdev_match[] = {
{ .compatible = "nvidia,tegra124-dfll-cdev-action", },
{},
};
static struct platform_driver tegra124_dfll_cdev_driver = {
.driver = {
.name = "tegra124_dfll_action",
.owner = THIS_MODULE,
.of_match_table = tegra124_dfll_cdev_match,
},
.probe = tegra124_dfll_cdev_probe,
.remove = tegra124_dfll_cdev_remove,
};
module_platform_driver(tegra124_dfll_cdev_driver);
MODULE_DESCRIPTION("Tegra124 DFLL thermal reaction driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Aleksandr Frid <afrid@nvidia.com>");
MODULE_AUTHOR("Paul Walmsley <pwalmsley@nvidia.com>");