diff --git a/drivers/thermal/qcom/Kconfig b/drivers/thermal/qcom/Kconfig index 358d584b2d65..a4118ffe4d96 100644 --- a/drivers/thermal/qcom/Kconfig +++ b/drivers/thermal/qcom/Kconfig @@ -122,3 +122,12 @@ config QTI_CPU_HOTPLUG_COOLING_DEVICE thermal cooling. CPU Hotplug will be done after core isolation, to prevent any process from waking the mitigated CPU. +config QTI_DDR_COOLING_DEVICE + tristate "QTI DDR cooling devices" + depends on THERMAL && INTERCONNECT + help + This enables the QTI DDR cooling devices. These cooling + devices will be used by QTI chipset to place a DDR state request + to meet the performance requirement under thermally constrained + conditions. + diff --git a/drivers/thermal/qcom/Makefile b/drivers/thermal/qcom/Makefile index 76e215ee5989..12772bcadbbf 100644 --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -19,3 +19,4 @@ qti_qmi_sensor_v2-y += thermal_sensor_service_v02.o qmi_sensors_v2.o obj-$(CONFIG_QTI_CPU_VOLTAGE_COOLING_DEVICE) += cpu_voltage_cooling.o obj-$(CONFIG_QTI_CPU_HOTPLUG_COOLING_DEVICE) += cpu_hotplug.o +obj-$(CONFIG_QTI_DDR_COOLING_DEVICE) += ddr_cdev.o diff --git a/drivers/thermal/qcom/ddr_cdev.c b/drivers/thermal/qcom/ddr_cdev.c new file mode 100644 index 000000000000..6a4a79f66465 --- /dev/null +++ b/drivers/thermal/qcom/ddr_cdev.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ +#include +#include +#include +#include +#include +#include + +#define DDR_CDEV_NAME "ddr-cdev" + +struct ddr_cdev { + uint32_t cur_state; + uint32_t max_state; + struct thermal_cooling_device *cdev; + struct icc_path *icc_path; + struct device *dev; + uint32_t *freq_table; + uint32_t freq_table_size; +}; + +/** + * ddr_set_cur_state - callback function to set the ddr state. + * @cdev: thermal cooling device pointer. + * @state: set this variable to the current cooling state. + * + * Callback for the thermal cooling device to change the + * DDR state. + * + * Return: 0 on success, an error code otherwise. + */ +static int ddr_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct ddr_cdev *ddr_cdev = cdev->devdata; + int ret = 0; + + /* Request state should be less than max_level */ + if (state > ddr_cdev->max_state) + return -EINVAL; + + /* Check if the old cooling action is same as new cooling action */ + if (ddr_cdev->cur_state == state) + return 0; + + ret = icc_set_bw(ddr_cdev->icc_path, 0, ddr_cdev->freq_table[state]); + if (ret < 0) { + dev_err(ddr_cdev->dev, "Error placing DDR freq%u. err:%d\n", + ddr_cdev->freq_table[state], ret); + return ret; + } + + ddr_cdev->cur_state = state; + + return ret; +} + +/** + * ddr_get_cur_state - callback function to get the current cooling + * state. + * @cdev: thermal cooling device pointer. + * @state: fill this variable with the current cooling state. + * + * Callback for the thermal cooling device to return the + * current DDR state request. + * + * Return: 0 on success, an error code otherwise. + */ +static int ddr_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct ddr_cdev *ddr_cdev = cdev->devdata; + *state = ddr_cdev->cur_state; + + return 0; +} + +/** + * ddr_get_max_state - callback function to get the max cooling state. + * @cdev: thermal cooling device pointer. + * @state: fill this variable with the max cooling state. + * + * Callback for the thermal cooling device to return the DDR + * max cooling state. + * + * Return: 0 on success, an error code otherwise. + */ +static int ddr_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct ddr_cdev *ddr_cdev = cdev->devdata; + *state = ddr_cdev->max_state; + + return 0; +} + +static struct thermal_cooling_device_ops ddr_cdev_ops = { + .get_max_state = ddr_get_max_state, + .get_cur_state = ddr_get_cur_state, + .set_cur_state = ddr_set_cur_state, +}; + +static int ddr_cdev_probe(struct platform_device *pdev) +{ + int ret = 0, opp_ct = 0, bus_width = 1, idx = 0; + struct ddr_cdev *ddr_cdev = NULL; + struct device_node *np = pdev->dev.of_node; + struct device *dev = &pdev->dev; + uint32_t *freq_table = NULL; + char cdev_name[THERMAL_NAME_LENGTH] = DDR_CDEV_NAME; + + ddr_cdev = devm_kzalloc(dev, sizeof(*ddr_cdev), GFP_KERNEL); + if (!ddr_cdev) + return -ENOMEM; + ddr_cdev->icc_path = of_icc_get(dev, NULL); + if (IS_ERR(ddr_cdev->icc_path)) { + ret = PTR_ERR(ddr_cdev->icc_path); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Unable to register icc path: %d\n", + ret); + return ret; + } + + if (!of_find_property(np, "qcom,freq-table", &opp_ct)) { + dev_err(dev, "No DDR frequency entries\n"); + ret = -ENODEV; + goto err_exit; + } + + if (opp_ct <= 0) { + dev_err(dev, "No DDR frequency\n"); + ret = -ENODEV; + goto err_exit; + } + opp_ct = opp_ct / sizeof(*freq_table); + + /* Add one more entry for 0 or no DDR BW vote */ + opp_ct++; + + freq_table = devm_kcalloc(dev, opp_ct, sizeof(*freq_table), GFP_KERNEL); + if (!freq_table) { + ret = -ENOMEM; + goto err_exit; + } + freq_table[0] = 0; + + ret = of_property_read_u32_array(np, "qcom,freq-table", + &freq_table[1], opp_ct - 1); + if (ret < 0) { + dev_err(dev, "DDR frequency read error:%d\n", ret); + goto err_exit; + } + + ddr_cdev->freq_table = freq_table; + ddr_cdev->freq_table_size = opp_ct; + ddr_cdev->cur_state = 0; + ddr_cdev->max_state = opp_ct - 1; + ddr_cdev->dev = dev; + + ret = of_property_read_u32(np, "qcom,bus-width", &bus_width); + if (ret < 0) { + dev_err(dev, "DDR bus width read error:%d\n", ret); + goto err_exit; + } + + for (idx = 0; idx < opp_ct; idx++) + ddr_cdev->freq_table[idx] *= bus_width; + + ret = icc_set_bw(ddr_cdev->icc_path, 0, freq_table[0]); + if (ret < 0) { + dev_err(dev, "Error placing DDR freq request. err:%d\n", + ret); + goto err_exit; + } + + ddr_cdev->cdev = thermal_of_cooling_device_register(np, cdev_name, + ddr_cdev, &ddr_cdev_ops); + if (IS_ERR(ddr_cdev->cdev)) { + ret = PTR_ERR(ddr_cdev->cdev); + dev_err(dev, "Cdev register failed for %s, ret:%d\n", + cdev_name, ret); + goto err_exit; + } + dev_dbg(dev, "Cooling device [%s] registered.\n", cdev_name); + dev_set_drvdata(dev, ddr_cdev); + + return 0; +err_exit: + icc_put(ddr_cdev->icc_path); + + return ret; +} + +static int ddr_cdev_remove(struct platform_device *pdev) +{ + struct ddr_cdev *ddr_cdev = + (struct ddr_cdev *)dev_get_drvdata(&pdev->dev); + + if (ddr_cdev->cdev) { + thermal_cooling_device_unregister(ddr_cdev->cdev); + ddr_cdev->cdev = NULL; + } + if (ddr_cdev->icc_path) { + icc_set_bw(ddr_cdev->icc_path, 0, ddr_cdev->freq_table[0]); + icc_put(ddr_cdev->icc_path); + ddr_cdev->icc_path = NULL; + } + + return 0; +} + +static const struct of_device_id ddr_cdev_match[] = { + { .compatible = "qcom,ddr-cooling-device", }, + {}, +}; + +static struct platform_driver ddr_cdev_driver = { + .probe = ddr_cdev_probe, + .remove = ddr_cdev_remove, + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = ddr_cdev_match, + }, +}; +module_platform_driver(ddr_cdev_driver); +MODULE_LICENSE("GPL");