blob: 7ea6d6fd4ebf1f06b2110e250e56f02ef9f79035 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/module.h>
#include <linux/thermal.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/amlogic/ddr_cooling.h>
#include <linux/amlogic/meson_cooldev.h>
#include <linux/io.h>
#include "../../thermal/thermal_core.h"
static DEFINE_IDR(ddr_idr);
static DEFINE_MUTEX(cooling_ddr_lock);
static DEFINE_MUTEX(cooling_list_lock);
static LIST_HEAD(ddr_dev_list);
static void __iomem *ddr_reg0;
/**
* get_idr - function to get a unique id.
* @idr: struct idr * handle used to create a id.
* @id: int * value generated by this function.
*
* This function will populate @id with an unique
* id, using the idr API.
*
* Return: 0 on success, an error code on failure.
*/
static int get_idr(struct idr *idr)
{
int ret;
mutex_lock(&cooling_ddr_lock);
ret = idr_alloc(idr, NULL, 0, 0, GFP_KERNEL);
mutex_unlock(&cooling_ddr_lock);
return ret;
}
/**
* release_idr - function to free the unique id.
* @idr: struct idr * handle used for creating the id.
* @id: int value representing the unique id.
*/
static void release_idr(struct idr *idr, int id)
{
mutex_lock(&cooling_ddr_lock);
idr_remove(idr, id);
mutex_unlock(&cooling_ddr_lock);
}
static int ddr_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct ddr_cooling_device *ddr_device = cdev->devdata;
*state = ddr_device->ddr_status - 1;
return 0;
}
static int ddr_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
*state = 0;
return 0;
}
static int ddr_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct ddr_cooling_device *ddr_device = cdev->devdata;
if (WARN_ON(state >= ddr_device->ddr_status))
return -EINVAL;
state = 0;
return state;
}
static int ddr_get_requested_power(struct thermal_cooling_device *cdev,
struct thermal_zone_device *tz,
u32 *power)
{
*power = 0;
return 0;
}
static int ddr_state2power(struct thermal_cooling_device *cdev,
struct thermal_zone_device *tz,
unsigned long state, u32 *power)
{
*power = 0;
return 0;
}
static int ddr_power2state(struct thermal_cooling_device *cdev,
struct thermal_zone_device *tz, u32 power,
unsigned long *state)
{
cdev->ops->get_cur_state(cdev, state);
return 0;
}
static int ddr_notify_state(void *thermal_instance,
int trip,
enum thermal_trip_type type)
{
struct thermal_instance *ins = thermal_instance;
struct thermal_zone_device *tz;
struct thermal_cooling_device *cdev;
struct ddr_cooling_device *ddr_device;
int i, hyst = 0, trip_temp;
unsigned int val, val0, reg_val;
if (!ins)
return -EINVAL;
tz = ins->tz;
cdev = ins->cdev;
if (!tz || !cdev)
return -EINVAL;
ddr_device = cdev->devdata;
tz->ops->get_trip_hyst(tz, trip, &hyst);
tz->ops->get_trip_temp(tz, trip, &trip_temp);
reg_val = readl_relaxed(ddr_reg0);
switch (type) {
case THERMAL_TRIP_HOT:
val = ddr_device->ddr_data[0];
for (i = 1; i < ddr_device->ddr_status; i++) {
if (tz->temperature >= (trip_temp + i * hyst))
val = ddr_device->ddr_data[i];
else
break;
}
pr_debug("chip temp: %d, set ddr reg bit val: %x\n", tz->temperature, val);
val0 = reg_val & ddr_device->ddr_bits_keep;
val0 = val0 >> ddr_device->ddr_bits[0];
if (val0 == val)
break;
reg_val &= ddr_device->ddr_bits_keep;
reg_val |= (val << ddr_device->ddr_bits[0]);
pr_debug("last set ddr reg val: %x\n", reg_val);
writel_relaxed(reg_val, ddr_reg0);
default:
break;
}
return 0;
}
static struct thermal_cooling_device_ops const ddr_cooling_ops = {
.get_max_state = ddr_get_max_state,
.get_cur_state = ddr_get_cur_state,
.set_cur_state = ddr_set_cur_state,
.state2power = ddr_state2power,
.power2state = ddr_power2state,
.notify_state = ddr_notify_state,
.get_requested_power = ddr_get_requested_power,
};
struct thermal_cooling_device *
ddr_cooling_register(struct device_node *np, struct cool_dev *cool)
{
struct thermal_cooling_device *cool_dev;
struct ddr_cooling_device *ddr_dev = NULL;
struct thermal_instance *pos = NULL;
char dev_name[THERMAL_NAME_LENGTH];
int i;
ddr_dev = kmalloc(sizeof(*ddr_dev), GFP_KERNEL);
if (!ddr_dev)
return ERR_PTR(-ENOMEM);
ddr_dev->id = get_idr(&ddr_idr);
if (ddr_dev->id < 0) {
kfree(ddr_dev);
return ERR_PTR(-EINVAL);
}
ddr_reg0 = ioremap(cool->ddr_reg, 1);
if (!ddr_reg0) {
pr_err("thermal ddr cdev: ddr reg0 ioremap fail.\n");
release_idr(&ddr_idr, ddr_dev->id);
kfree(ddr_dev);
return ERR_PTR(-EINVAL);
}
snprintf(dev_name, sizeof(dev_name), "thermal-ddr-%d",
ddr_dev->id);
ddr_dev->ddr_reg = cool->ddr_reg;
ddr_dev->ddr_status = cool->ddr_status;
for (i = 0; i < 2; i++)
ddr_dev->ddr_bits[i] = cool->ddr_bits[i];
ddr_dev->ddr_bits_keep = ~(0xffffffff << (ddr_dev->ddr_bits[1] + 1));
ddr_dev->ddr_bits_keep = ~((ddr_dev->ddr_bits_keep >> ddr_dev->ddr_bits[0])
<< ddr_dev->ddr_bits[0]);
for (i = 0; i < cool->ddr_status; i++)
ddr_dev->ddr_data[i] = cool->ddr_data[i];
cool_dev = thermal_of_cooling_device_register(np, dev_name, ddr_dev,
&ddr_cooling_ops);
if (!cool_dev) {
release_idr(&ddr_idr, ddr_dev->id);
kfree(ddr_dev);
return ERR_PTR(-EINVAL);
}
ddr_dev->cool_dev = cool_dev;
list_for_each_entry(pos, &cool_dev->thermal_instances, cdev_node) {
if (!strncmp(pos->cdev->type, dev_name, sizeof(dev_name))) {
cool_dev->ops->notify_state(pos, pos->trip,
THERMAL_TRIP_HOT);
break;
}
}
mutex_lock(&cooling_list_lock);
list_add(&ddr_dev->node, &ddr_dev_list);
mutex_unlock(&cooling_list_lock);
return cool_dev;
}
EXPORT_SYMBOL_GPL(ddr_cooling_register);
/**
* cpucore_cooling_unregister - function to remove cpucore cooling device.
* @cdev: thermal cooling device pointer.
*
* This interface function unregisters the "thermal-cpucore-%x" cooling device.
*/
void ddr_cooling_unregister(struct thermal_cooling_device *cdev)
{
struct ddr_cooling_device *ddr_dev;
if (!cdev)
return;
iounmap(ddr_reg0);
ddr_dev = cdev->devdata;
mutex_lock(&cooling_list_lock);
list_del(&ddr_dev->node);
mutex_unlock(&cooling_list_lock);
thermal_cooling_device_unregister(ddr_dev->cool_dev);
release_idr(&ddr_idr, ddr_dev->id);
kfree(ddr_dev);
}
EXPORT_SYMBOL_GPL(ddr_cooling_unregister);