blob: 04fdada7eaa770e6580a14979065a6618fed743f [file] [log] [blame]
/*
* drivers/amlogic/thermal/aml_thermal_hw.c
*
* Copyright (C) 2016 Amlogic, Inc. 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 as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
*/
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/amlogic/cpu_version.h>
#include <linux/amlogic/scpi_protocol.h>
#include <linux/printk.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/cpufreq.h>
#include <linux/cpu_cooling.h>
#include <linux/amlogic/cpucore_cooling.h>
#include <linux/amlogic/gpucore_cooling.h>
#include <linux/amlogic/gpu_cooling.h>
#include <linux/amlogic/aml_thermal_hw.h>
#include <linux/cpu.h>
#define NOT_WRITE_EFUSE 0x0
#define EFUEE_PRIVATE 0x4
#define EFUSE_OPS 0xa
enum cluster_type {
CLUSTER_BIG = 0,
CLUSTER_LITTLE,
NUM_CLUSTERS
};
enum cool_dev_type {
COOL_DEV_TYPE_CPU_FREQ = 0,
COOL_DEV_TYPE_CPU_CORE,
COOL_DEV_TYPE_GPU_FREQ,
COOL_DEV_TYPE_GPU_CORE,
COOL_DEV_TYPE_MAX
};
struct cool_dev {
int min_state;
int coeff;
int cluster_id;
char *device_type;
struct device_node *np;
struct thermal_cooling_device *cooling_dev;
};
struct aml_thermal_sensor {
int chip_trimmed;
int cool_dev_num;
int min_exist;
struct cpumask mask[NUM_CLUSTERS];
struct cool_dev *cool_devs;
struct thermal_zone_device *tzd;
};
static struct aml_thermal_sensor soc_sensor;
static struct gpufreq_cooling_device *gf_cdev_s;
int thermal_firmware_init(void)
{
int ret;
ret = scpi_get_sensor("aml_thermal");
soc_sensor.chip_trimmed = ret < 0 ? 0 : 1;
return ret;
}
EXPORT_SYMBOL(thermal_firmware_init);
int get_cpu_temp(void)
{
unsigned int val = 0;
if (soc_sensor.chip_trimmed) { /* only supported trimmed chips */
if (scpi_get_sensor_value(0, &val) < 0)
return -1000;
return val;
} else {
return -1000;
}
}
EXPORT_SYMBOL(get_cpu_temp);
static int get_cur_temp(void *data, int *temp)
{
int val;
val = get_cpu_temp();
if (val == -1000)
return -EINVAL;
*temp = val * 1000;
return 0;
}
static int get_cool_dev_type(char *type)
{
if (!strcmp(type, "cpufreq"))
return COOL_DEV_TYPE_CPU_FREQ;
if (!strcmp(type, "cpucore"))
return COOL_DEV_TYPE_CPU_CORE;
if (!strcmp(type, "gpufreq"))
return COOL_DEV_TYPE_GPU_FREQ;
if (!strcmp(type, "gpucore"))
return COOL_DEV_TYPE_GPU_CORE;
return COOL_DEV_TYPE_MAX;
}
static struct cool_dev *get_cool_dev_by_node(struct device_node *np)
{
int i;
struct cool_dev *dev;
if (!np)
return NULL;
for (i = 0; i < soc_sensor.cool_dev_num; i++) {
dev = &soc_sensor.cool_devs[i];
if (dev->np == np)
return dev;
}
return NULL;
}
int aml_thermal_min_update(struct thermal_cooling_device *cdev)
{
struct gpufreq_cooling_device *gf_cdev;
struct gpucore_cooling_device *gc_cdev;
struct cool_dev *cool;
long min_state;
int i;
int cpu, c_id;
cool = get_cool_dev_by_node(cdev->np);
if (!cool)
return -ENODEV;
if (cool->cooling_dev == NULL)
cool->cooling_dev = cdev;
if (cool->min_state == 0)
return 0;
switch (get_cool_dev_type(cool->device_type)) {
case COOL_DEV_TYPE_CPU_CORE:
/* TODO: cluster ID */
cool->cooling_dev->ops->get_max_state(cdev, &min_state);
min_state = min_state - cool->min_state;
break;
case COOL_DEV_TYPE_CPU_FREQ:
for_each_possible_cpu(cpu) {
if (mc_capable())
c_id = topology_physical_package_id(cpu);
else
c_id = 0; /* force cluster 0 if no MC */
if (c_id == cool->cluster_id)
break;
}
min_state = cpufreq_cooling_get_level(cpu, cool->min_state);
break;
case COOL_DEV_TYPE_GPU_CORE:
gc_cdev = (struct gpucore_cooling_device *)cdev->devdata;
cdev->ops->get_max_state(cdev, &min_state);
min_state = min_state - cool->min_state;
if (gf_cdev_s != NULL)
gf_cdev_s->max_pp = gc_cdev->max_gpu_core_num;
break;
case COOL_DEV_TYPE_GPU_FREQ:
gf_cdev = (struct gpufreq_cooling_device *)cdev->devdata;
gf_cdev_s = (struct gpufreq_cooling_device *)cdev->devdata;
min_state = gf_cdev->get_gpu_freq_level(cool->min_state);
break;
default:
return -EINVAL;
}
for (i = 0; i < soc_sensor.tzd->trips; i++)
thermal_set_upper(soc_sensor.tzd, cdev, i, min_state);
return 0;
}
EXPORT_SYMBOL(aml_thermal_min_update);
int set_cur_mode(struct thermal_zone_device *tzd, enum thermal_device_mode mode)
{
int i, ret = 0;
struct thermal_cooling_device *cdev;
/*
* each cooling device should return to max state if thermal is disalbed
*/
if (mode != THERMAL_DEVICE_DISABLED)
return 0;
for (i = 0; i < soc_sensor.cool_dev_num; i++) {
cdev = soc_sensor.cool_devs[i].cooling_dev;
if (cdev)
ret |= cdev->ops->set_cur_state(cdev, 0);
}
return ret;
}
static struct thermal_zone_of_device_ops aml_thermal_ops = {
.get_temp = get_cur_temp,
.set_mode = set_cur_mode,
};
static int register_cool_dev(struct cool_dev *cool)
{
int pp;
int id = cool->cluster_id;
struct cpumask *mask;
switch (get_cool_dev_type(cool->device_type)) {
case COOL_DEV_TYPE_CPU_CORE:
cool->cooling_dev = cpucore_cooling_register(cool->np,
cool->cluster_id);
break;
case COOL_DEV_TYPE_CPU_FREQ:
mask = &soc_sensor.mask[id];
cool->cooling_dev = of_cpufreq_power_cooling_register(cool->np,
mask,
cool->coeff,
NULL);
break;
/* GPU is KO, just save these parameters */
case COOL_DEV_TYPE_GPU_FREQ:
if (of_property_read_u32(cool->np, "num_of_pp", &pp))
pr_err("thermal: read num_of_pp failed\n");
save_gpu_cool_para(cool->coeff, cool->np, pp);
return 0;
case COOL_DEV_TYPE_GPU_CORE:
save_gpucore_thermal_para(cool->np);
return 0;
default:
pr_err("thermal: unknown type:%s\n", cool->device_type);
return -EINVAL;
}
if (IS_ERR(cool->cooling_dev)) {
pr_err("thermal: register %s failed\n", cool->device_type);
return -EINVAL;
}
return 0;
}
static int parse_cool_device(struct device_node *np)
{
int i, temp, ret = 0;
struct cool_dev *cool;
struct device_node *node, *child;
const char *str;
child = of_get_next_child(np, NULL);
for (i = 0; i < soc_sensor.cool_dev_num; i++) {
cool = &soc_sensor.cool_devs[i];
if (child == NULL)
break;
if (of_property_read_u32(child, "min_state", &temp))
pr_err("thermal: read min_state failed\n");
else
cool->min_state = temp;
if (of_property_read_u32(child, "dyn_coeff", &temp))
pr_err("thermal: read dyn_coeff failed\n");
else
cool->coeff = temp;
if (of_property_read_u32(child, "cluster_id", &temp))
pr_err("thermal: read cluster_id failed\n");
else
cool->cluster_id = temp;
if (of_property_read_string(child, "device_type", &str))
pr_err("thermal: read device_type failed\n");
else
cool->device_type = (char *)str;
if (of_property_read_string(child, "node_name", &str))
pr_err("thermal: read node_name failed\n");
else {
node = of_find_node_by_name(NULL, str);
if (!node)
pr_err("thermal: can't find node\n");
cool->np = node;
}
if (cool->np)
ret += register_cool_dev(cool);
child = of_get_next_child(np, child);
}
return ret;
}
static int aml_thermal_probe(struct platform_device *pdev)
{
int cpu, i, c_id;
struct device_node *np, *child;
struct cool_dev *cool;
struct cpufreq_policy *policy;
memset(&soc_sensor, 0, sizeof(struct aml_thermal_sensor));
policy = cpufreq_cpu_get(0);
if (!policy || !policy->freq_table) {
dev_info(&pdev->dev,
"Frequency policy not init. Deferring probe...\n");
return -EPROBE_DEFER;
}
if (thermal_firmware_init() < 0) {
dev_err(&pdev->dev, "chip is not trimmed, disable thermal\n");
return -EINVAL;
}
for_each_possible_cpu(cpu) {
if (mc_capable())
c_id = topology_physical_package_id(cpu);
else
c_id = CLUSTER_BIG; /* Always cluster 0 if no mc */
if (c_id > NUM_CLUSTERS) {
pr_err("Cluster id: %d > %d\n", c_id, NUM_CLUSTERS);
return -EINVAL;
}
cpumask_set_cpu(cpu, &soc_sensor.mask[c_id]);
}
np = pdev->dev.of_node;
child = of_get_child_by_name(np, "cooling_devices");
if (child == NULL) {
pr_err("thermal: can't found cooling_devices\n");
return -EINVAL;
}
soc_sensor.cool_dev_num = of_get_child_count(child);
i = sizeof(struct cool_dev) * soc_sensor.cool_dev_num;
soc_sensor.cool_devs = kzalloc(i, GFP_KERNEL);
if (soc_sensor.cool_devs == NULL) {
pr_err("thermal: alloc mem failed\n");
return -ENOMEM;
}
if (parse_cool_device(child))
return -EINVAL;
soc_sensor.tzd = thermal_zone_of_sensor_register(&pdev->dev,
3,
&soc_sensor,
&aml_thermal_ops);
if (IS_ERR(soc_sensor.tzd)) {
dev_warn(&pdev->dev, "Error registering sensor: %p\n",
soc_sensor.tzd);
return PTR_ERR(soc_sensor.tzd);
}
/* update min state for each device */
for (i = 0; i < soc_sensor.cool_dev_num; i++) {
cool = &soc_sensor.cool_devs[i];
if (cool->cooling_dev)
aml_thermal_min_update(cool->cooling_dev);
}
thermal_zone_device_update(soc_sensor.tzd, THERMAL_EVENT_UNSPECIFIED);
return 0;
}
static int aml_thermal_remove(struct platform_device *pdev)
{
kfree(soc_sensor.cool_devs);
return 0;
}
static const struct of_device_id aml_thermal_of_match[] = {
{ .compatible = "amlogic, aml-thermal" },
{},
};
static struct platform_driver aml_thermal_platdrv = {
.driver = {
.name = "aml-thermal",
.owner = THIS_MODULE,
.of_match_table = aml_thermal_of_match,
},
.probe = aml_thermal_probe,
.remove = aml_thermal_remove,
};
static int __init aml_thermal_platdrv_init(void)
{
return platform_driver_register(&(aml_thermal_platdrv));
}
late_initcall(aml_thermal_platdrv_init);