Files
BPI-CM9-Linux/aml-comp/kernel/aml-6.12/common_drivers/drivers/thermal/ddr_control.c
T
2026-08-24 10:03:24 +08:00

158 lines
3.7 KiB
C

// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
//#define DEBUG
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/printk.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/bitfield.h>
#include <linux/of_device.h>
#include <dt-bindings/thermal/ddr-params.h>
#include "ddr_control.h"
static bool ddr_control_enable = 1;
module_param(ddr_control_enable, bool, 0644);
void ddr_control_disable(void)
{
ddr_control_enable = 0;
}
static u32 ddr_get_temperature(const struct ddr_control *control, int idx)
{
struct ddr_cstep *ddr_cstep = control->ddr_cstep;
if (control->cstep_cnt < 1)
return 0;
if (idx >= control->cstep_cnt)
idx = control->cstep_cnt - 1;
return ddr_cstep[idx].temp;
}
static int ddr_get_dmc_step(struct control_device *cdev)
{
const struct ddr_control *ddr_control = cdev->ddr_control;
const struct ddr_dmc *dmc_ctrl = ddr_control->condition;
int idx, step;
int vote_idx = 0;
u32 cur_temp, pre_temp = 0;
for (idx = 0; idx < ddr_control->con_cnt; idx++) {
step = (readl(dmc_ctrl[idx].rd_addr) & dmc_ctrl[idx].mask);
step = step >> dmc_ctrl[idx].offset;
cur_temp = ddr_get_temperature(ddr_control, step);
dev_dbg(cdev->dev, "step %d cur temp %d\n", step, cur_temp);
if (cur_temp >= pre_temp) {
pre_temp = cur_temp;
vote_idx = step;
}
}
return vote_idx;
}
static int ddr_get_thermal_step(struct control_device *cdev)
{
const struct ddr_control *ddr_control = cdev->ddr_control;
const struct ddr_thermal *thermal = ddr_control->condition;
struct ddr_cstep *ddr_cstep = ddr_control->ddr_cstep;
int temp = 0, ret, idx;
int last_idx = cdev->last_idx;
if (ddr_control->cstep_cnt == 0)
return -EINVAL;
ret = thermal_zone_get_temp(thermal->tzd, &temp);
if (ret) {
dev_err(cdev->dev, "fail to read temperature %d\n", ret);
return ret;
}
for (idx = 0; idx < ddr_control->cstep_cnt; idx++) {
if (temp < ddr_cstep[idx].temp)
break;
}
if (idx == ddr_control->cstep_cnt)
idx = ddr_control->cstep_cnt - 1;
if (last_idx > idx && last_idx < ddr_control->cstep_cnt) {
if ((ddr_cstep[last_idx].temp - thermal->hyst_temp) < temp)
return last_idx;
}
return idx;
}
static int ddr_update_caddr(struct ddr_cstep *ddr_cstep, int idx)
{
struct ddr_caddr *ddr_caddr;
u32 val;
int a_idx;
for (a_idx = 0; a_idx < ddr_cstep->caddr_cnt; a_idx++) {
ddr_caddr = &ddr_cstep->caddr_tab[a_idx];
val = readl(ddr_caddr->wr_addr);
val &= ~ddr_caddr->wr_mask;
val |= ((ddr_caddr->table[idx] << ddr_caddr->wr_offset)
& ddr_caddr->wr_mask);
writel(val, ddr_caddr->wr_addr);
}
return 0;
}
int ddr_control_device(struct control_device *cdev)
{
const struct ddr_control *ddr_control = cdev->ddr_control;
struct ddr_cstep *ddr_cstep = ddr_control->ddr_cstep;
int idx;
if (!ddr_control_enable)
return 0;
switch (ddr_control->cd_type) {
case DDR_DMC:
idx = ddr_get_dmc_step(cdev);
if (idx < 0)
return idx;
break;
case DDR_THERMAL:
idx = ddr_get_thermal_step(cdev);
if (idx < 0)
return idx;
break;
default:
dev_err(cdev->dev, "control state error\n");
return -EINVAL;
};
if (idx == ddr_control->cstep_cnt) {
dev_err(cdev->dev, "Not match any thermal threshold\n");
return -EINVAL;
}
if (idx == cdev->last_idx)
return 0;
cdev->last_idx = idx;
ddr_update_caddr(ddr_cstep, idx);
return 0;
}
void control_poll_work(struct work_struct *work)
{
struct control_device *cdev = container_of(work,
struct control_device, poll_dwork.work);
ddr_control_device(cdev);
mod_delayed_work(system_freezable_power_efficient_wq,
&cdev->poll_dwork,
msecs_to_jiffies(cdev->poll_time_ms));
}