| // 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_core.h" |
| |
| static int ddrfreqcd_id; |
| static DEFINE_MUTEX(cooling_ddr_lock); |
| static DEFINE_MUTEX(cooling_list_lock); |
| static LIST_HEAD(ddr_dev_list); |
| |
| static void ddr_coolingdevice_id_get(int *id) |
| { |
| mutex_lock(&cooling_ddr_lock); |
| *id = ddrfreqcd_id++; |
| mutex_unlock(&cooling_ddr_lock); |
| } |
| |
| static void ddr_coolingdevice_id_put(void) |
| { |
| mutex_lock(&cooling_ddr_lock); |
| ddrfreqcd_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) |
| { |
| struct ddr_cooling_device *ddr_device = cdev->devdata; |
| u32 val, mask; |
| u32 start_bit, end_bit; |
| int i; |
| |
| start_bit = ddr_device->ddr_bits[0][0]; |
| end_bit = ddr_device->ddr_bits[0][1]; |
| mask = GENMASK(end_bit, start_bit); |
| val = readl_relaxed(ddr_device->vddr_reg[0]); |
| val = (val & mask) >> start_bit; |
| |
| for (i = 0; i < ddr_device->ddr_status; i++) { |
| if (val == ddr_device->ddr_data[0][i]) |
| break; |
| } |
| |
| if (i == ddr_device->ddr_status) { |
| dev_warn(&ddr_device->cool_dev->device, |
| "Not matching any states!\n"); |
| *state = -EINVAL; |
| } else { |
| *state = i; |
| } |
| |
| return 0; |
| } |
| |
| static int ddr_set_cur_state(struct thermal_cooling_device *cdev, |
| unsigned long state) |
| { |
| struct ddr_cooling_device *ddr_device = cdev->devdata; |
| u32 val, mask; |
| u32 start_bit, end_bit; |
| int i; |
| |
| if (!(state < ddr_device->ddr_status)) { |
| dev_warn(&ddr_device->cool_dev->device, |
| "Not matching any states!\n"); |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < ddr_device->ddr_reg_cnt; i++) { |
| start_bit = ddr_device->ddr_bits[i][0]; |
| end_bit = ddr_device->ddr_bits[i][1]; |
| mask = GENMASK(end_bit, start_bit); |
| val = readl_relaxed(ddr_device->vddr_reg[i]); |
| val = (val & (~mask)) | (ddr_device->ddr_data[i][state] << start_bit); |
| |
| writel_relaxed(val, ddr_device->vddr_reg[i]); |
| } |
| |
| return 0; |
| } |
| |
| static unsigned long cdev_calc_next_state_by_temp(struct thermal_instance *instance, |
| int temperature) |
| { |
| struct thermal_instance *ins = instance; |
| struct thermal_zone_device *tz; |
| struct thermal_cooling_device *cdev; |
| struct ddr_cooling_device *ddr_device; |
| int i, hyst = 0, trip_temp, max; |
| |
| if (!ins) |
| return -EINVAL; |
| |
| tz = ins->tz; |
| cdev = ins->cdev; |
| |
| if (!tz || !cdev) |
| return -EINVAL; |
| |
| ddr_device = cdev->devdata; |
| max = ddr_device->ddr_status - 1; |
| |
| tz->ops->get_trip_hyst(tz, instance->trip, &hyst); |
| tz->ops->get_trip_temp(tz, instance->trip, &trip_temp); |
| |
| for (i = 0; i < ddr_device->ddr_status; i++) { |
| if (temperature < (trip_temp + (i + 1) * hyst)) |
| break; |
| } |
| |
| return i > max ? max : i; |
| } |
| |
| static int ddr_get_requested_power(struct thermal_cooling_device *cdev, |
| u32 *power) |
| { |
| struct thermal_instance *instance; |
| struct thermal_zone_device *tz; |
| |
| mutex_lock(&cdev->lock); |
| list_for_each_entry(instance, &cdev->thermal_instances, cdev_node) { |
| tz = instance->tz; |
| if (cdev->ops && cdev->ops->set_cur_state && instance->trip == THERMAL_TRIP_HOT) |
| *power = (u32)cdev_calc_next_state_by_temp(instance, tz->temperature); |
| } |
| mutex_unlock(&cdev->lock); |
| |
| return 0; |
| } |
| |
| static int ddr_state2power(struct thermal_cooling_device *cdev, |
| unsigned long state, u32 *power) |
| { |
| *power = 0; |
| return 0; |
| } |
| |
| static int ddr_power2state(struct thermal_cooling_device *cdev, |
| u32 power, unsigned long *state) |
| { |
| cdev->ops->get_cur_state(cdev, state); |
| 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, |
| .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_coolingdevice_id_get(&ddr_dev->id); |
| ddr_dev->vddr_reg = kmalloc_array(cool->ddr_reg_cnt, sizeof(void *), GFP_KERNEL); |
| ddr_dev->ddr_bits_keep = kmalloc_array(cool->ddr_reg_cnt, sizeof(u32), GFP_KERNEL); |
| if (!ddr_dev->vddr_reg || !ddr_dev->ddr_bits_keep) |
| goto out; |
| |
| ddr_dev->ddr_reg = cool->ddr_reg; |
| ddr_dev->ddr_reg_cnt = cool->ddr_reg_cnt; |
| ddr_dev->ddr_status = cool->ddr_status; |
| ddr_dev->ddr_bits = cool->ddr_bits; |
| ddr_dev->ddr_data = cool->ddr_data; |
| |
| for (i = 0; i < cool->ddr_reg_cnt; i++) { |
| ddr_dev->vddr_reg[i] = ioremap(cool->ddr_reg[i], 4); |
| if (!ddr_dev->vddr_reg[i]) { |
| pr_err("thermal ddr cdev: ddr reg ioremap fail.\n"); |
| goto out; |
| } |
| ddr_dev->ddr_bits_keep[i] = ~(0xffffffff << (ddr_dev->ddr_bits[i][1] + 1)); |
| ddr_dev->ddr_bits_keep[i] = ~((ddr_dev->ddr_bits_keep[i] >> ddr_dev->ddr_bits[i][0]) |
| << ddr_dev->ddr_bits[i][0]); |
| } |
| |
| snprintf(dev_name, sizeof(dev_name), "thermal-ddr-%d", ddr_dev->id); |
| cool_dev = thermal_of_cooling_device_register(np, dev_name, ddr_dev, |
| &ddr_cooling_ops); |
| |
| if (!cool_dev) |
| goto out; |
| |
| 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))) { |
| pr_err("Notice!!! The notify interface has been removed.\n"); |
| break; |
| } |
| } |
| |
| mutex_lock(&cooling_list_lock); |
| list_add(&ddr_dev->node, &ddr_dev_list); |
| mutex_unlock(&cooling_list_lock); |
| |
| return cool_dev; |
| out: |
| ddr_coolingdevice_id_put(); |
| kfree(ddr_dev->vddr_reg); |
| kfree(ddr_dev->ddr_bits_keep); |
| kfree(ddr_dev); |
| return ERR_PTR(-EINVAL); |
| } |
| 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; |
| |
| ddr_dev = cdev->devdata; |
| iounmap(ddr_dev->vddr_reg); |
| |
| mutex_lock(&cooling_list_lock); |
| list_del(&ddr_dev->node); |
| mutex_unlock(&cooling_list_lock); |
| |
| thermal_cooling_device_unregister(ddr_dev->cool_dev); |
| ddr_coolingdevice_id_put(); |
| kfree(ddr_dev); |
| } |
| EXPORT_SYMBOL_GPL(ddr_cooling_unregister); |