mirror of
https://github.com/torvalds/linux.git
synced 2026-07-31 11:37:06 +02:00
Merge "driver: thermal: sdpm: Add a snapshot of sdpm clock notifier driver"
This commit is contained in:
commit
6e4382ba68
|
|
@ -140,3 +140,22 @@ config QTI_USERSPACE_CDEV
|
|||
socket, so that userspace cooling device can perform a mitigation
|
||||
action.
|
||||
|
||||
config QTI_THERMAL_LIMITS_DCVS
|
||||
tristate "QTI LMH DCVS Driver"
|
||||
depends on THERMAL && CPU_THERMAL
|
||||
help
|
||||
This enables the driver for Limits Management Hardware - DCVS block
|
||||
for the application processors. The h/w block that is available for
|
||||
each cluster can be used to perform quick thermal mitigations by
|
||||
tracking temperatures of the CPUs and taking thermal action in the
|
||||
hardware without s/w intervention.
|
||||
|
||||
config QTI_SDPM_CLOCK_MONITOR
|
||||
tristate "QTI SDPM Clock Monitor"
|
||||
depends on COMMON_CLK && CPU_FREQ
|
||||
help
|
||||
This enables the QTI SDPM Clock Monitor. This driver can register
|
||||
for different clock rate change notifications and write the clock
|
||||
rate into the SDPM CSR register. This driver will receive the clock
|
||||
list and the CSR details from devicetree.
|
||||
|
||||
|
|
|
|||
|
|
@ -21,3 +21,5 @@ 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
|
||||
obj-$(CONFIG_QTI_USERSPACE_CDEV) += qti_userspace_cdev.o
|
||||
obj-$(CONFIG_QTI_THERMAL_LIMITS_DCVS) += msm_lmh_dcvs.o
|
||||
obj-${CONFIG_QTI_SDPM_CLOCK_MONITOR} += sdpm_clk.o
|
||||
|
|
|
|||
421
drivers/thermal/qcom/msm_lmh_dcvs.c
Normal file
421
drivers/thermal/qcom/msm_lmh_dcvs.c
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
|
||||
* Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/thermal.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/pm_opp.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <asm/smp_plat.h>
|
||||
|
||||
#define LIMITS_DCVSH 0x10
|
||||
#define LIMITS_NODE_DCVS 0x44435653
|
||||
|
||||
#define LIMITS_SUB_FN_THERMAL 0x54484D4C
|
||||
#define LIMITS_HI_THRESHOLD 0x48494748
|
||||
#define LIMITS_LOW_THRESHOLD 0x4C4F5700
|
||||
#define LIMITS_ARM_THRESHOLD 0x41524D00
|
||||
|
||||
#define LIMITS_CLUSTER_0 0x6370302D
|
||||
#define LIMITS_CLUSTER_1 0x6370312D
|
||||
|
||||
#define LIMITS_FREQ_CAP 0x46434150
|
||||
|
||||
#define LIMITS_TEMP_DEFAULT 75000
|
||||
#define LIMITS_TEMP_HIGH_THRESH_MAX 120000
|
||||
#define LIMITS_LOW_THRESHOLD_OFFSET 500
|
||||
#define LIMITS_POLLING_DELAY_MS 10
|
||||
#define LIMITS_CLUSTER_REQ_OFFSET 0x704
|
||||
#define LIMITS_CLUSTER_INT_CLR_OFFSET 0x8
|
||||
#define dcvsh_get_frequency(_val, _max) do { \
|
||||
_max = (_val) & 0x3FF; \
|
||||
_max *= 19200; \
|
||||
} while (0)
|
||||
#define FREQ_KHZ_TO_HZ(_val) ((_val) * 1000)
|
||||
#define FREQ_HZ_TO_KHZ(_val) ((_val) / 1000)
|
||||
|
||||
enum lmh_hw_trips {
|
||||
LIMITS_TRIP_ARM,
|
||||
LIMITS_TRIP_HI,
|
||||
LIMITS_TRIP_MAX,
|
||||
};
|
||||
|
||||
struct __limits_cdev_data {
|
||||
struct thermal_cooling_device *cdev;
|
||||
u32 max_freq;
|
||||
};
|
||||
|
||||
struct limits_dcvs_hw {
|
||||
char sensor_name[THERMAL_NAME_LENGTH];
|
||||
uint32_t affinity;
|
||||
int irq_num;
|
||||
void *osm_hw_reg;
|
||||
void *int_clr_reg;
|
||||
cpumask_t core_map;
|
||||
struct delayed_work freq_poll_work;
|
||||
unsigned long max_freq[NR_CPUS];
|
||||
unsigned long hw_freq_limit;
|
||||
struct device_attribute lmh_freq_attr;
|
||||
struct list_head list;
|
||||
bool is_irq_enabled;
|
||||
struct mutex access_lock;
|
||||
struct __limits_cdev_data *cdev_data;
|
||||
uint32_t cdev_registered;
|
||||
struct regulator *isens_reg[2];
|
||||
};
|
||||
|
||||
LIST_HEAD(lmh_dcvs_hw_list);
|
||||
DEFINE_MUTEX(lmh_dcvs_list_access);
|
||||
|
||||
static void limits_dcvs_get_freq_limits(struct limits_dcvs_hw *hw)
|
||||
{
|
||||
unsigned long freq_ceil = UINT_MAX, freq_floor = 0;
|
||||
struct device *cpu_dev = NULL;
|
||||
uint32_t cpu, idx = 0;
|
||||
|
||||
for_each_cpu(cpu, &hw->core_map) {
|
||||
freq_ceil = UINT_MAX;
|
||||
freq_floor = 0;
|
||||
cpu_dev = get_cpu_device(cpu);
|
||||
if (!cpu_dev) {
|
||||
pr_err("Error in get CPU%d device\n", cpu);
|
||||
idx++;
|
||||
continue;
|
||||
}
|
||||
|
||||
dev_pm_opp_find_freq_floor(cpu_dev, &freq_ceil);
|
||||
dev_pm_opp_find_freq_ceil(cpu_dev, &freq_floor);
|
||||
|
||||
hw->max_freq[idx] = freq_ceil / 1000;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned long limits_mitigation_notify(struct limits_dcvs_hw *hw)
|
||||
{
|
||||
uint32_t val = 0, max_cpu_ct = 0, max_cpu_limit = 0, idx = 0, cpu = 0;
|
||||
struct device *cpu_dev = NULL;
|
||||
unsigned long freq_val, max_limit = 0;
|
||||
struct dev_pm_opp *opp_entry;
|
||||
|
||||
val = readl_relaxed(hw->osm_hw_reg);
|
||||
dcvsh_get_frequency(val, max_limit);
|
||||
for_each_cpu(cpu, &hw->core_map) {
|
||||
cpu_dev = get_cpu_device(cpu);
|
||||
if (!cpu_dev) {
|
||||
pr_err("Error in get CPU%d device\n",
|
||||
cpumask_first(&hw->core_map));
|
||||
goto notify_exit;
|
||||
}
|
||||
|
||||
pr_debug("CPU:%d max value read:%lu\n",
|
||||
cpumask_first(&hw->core_map),
|
||||
max_limit);
|
||||
freq_val = FREQ_KHZ_TO_HZ(max_limit);
|
||||
opp_entry = dev_pm_opp_find_freq_floor(cpu_dev, &freq_val);
|
||||
/*
|
||||
* Hardware mitigation frequency can be lower than the lowest
|
||||
* possible CPU frequency. In that case freq floor call will
|
||||
* fail with -ERANGE and we need to match to the lowest
|
||||
* frequency using freq_ceil.
|
||||
*/
|
||||
if (IS_ERR(opp_entry) && PTR_ERR(opp_entry) == -ERANGE) {
|
||||
opp_entry = dev_pm_opp_find_freq_ceil(cpu_dev,
|
||||
&freq_val);
|
||||
if (IS_ERR(opp_entry))
|
||||
dev_err(cpu_dev,
|
||||
"frequency:%lu. opp error:%ld\n",
|
||||
freq_val, PTR_ERR(opp_entry));
|
||||
}
|
||||
if (FREQ_HZ_TO_KHZ(freq_val) == hw->max_freq[idx]) {
|
||||
max_cpu_ct++;
|
||||
if (max_cpu_limit < hw->max_freq[idx])
|
||||
max_cpu_limit = hw->max_freq[idx];
|
||||
idx++;
|
||||
continue;
|
||||
}
|
||||
max_limit = FREQ_HZ_TO_KHZ(freq_val);
|
||||
break;
|
||||
}
|
||||
|
||||
if (max_cpu_ct == cpumask_weight(&hw->core_map))
|
||||
max_limit = max_cpu_limit;
|
||||
|
||||
pr_debug("CPU:%d max limit:%lu\n", cpumask_first(&hw->core_map),
|
||||
max_limit);
|
||||
|
||||
notify_exit:
|
||||
hw->hw_freq_limit = max_limit;
|
||||
return max_limit;
|
||||
}
|
||||
|
||||
static void limits_dcvs_poll(struct work_struct *work)
|
||||
{
|
||||
unsigned long max_limit = 0;
|
||||
struct limits_dcvs_hw *hw = container_of(work,
|
||||
struct limits_dcvs_hw,
|
||||
freq_poll_work.work);
|
||||
int cpu_ct = 0, cpu = 0, idx = 0;
|
||||
|
||||
mutex_lock(&hw->access_lock);
|
||||
if (hw->max_freq[0] == U32_MAX)
|
||||
limits_dcvs_get_freq_limits(hw);
|
||||
max_limit = limits_mitigation_notify(hw);
|
||||
for_each_cpu(cpu, &hw->core_map) {
|
||||
if (max_limit >= hw->max_freq[idx])
|
||||
cpu_ct++;
|
||||
idx++;
|
||||
}
|
||||
if (cpu_ct >= cpumask_weight(&hw->core_map)) {
|
||||
writel_relaxed(0xFF, hw->int_clr_reg);
|
||||
hw->is_irq_enabled = true;
|
||||
enable_irq(hw->irq_num);
|
||||
} else {
|
||||
mod_delayed_work(system_highpri_wq, &hw->freq_poll_work,
|
||||
msecs_to_jiffies(LIMITS_POLLING_DELAY_MS));
|
||||
}
|
||||
mutex_unlock(&hw->access_lock);
|
||||
}
|
||||
|
||||
static void lmh_dcvs_notify(struct limits_dcvs_hw *hw)
|
||||
{
|
||||
if (hw->is_irq_enabled) {
|
||||
hw->is_irq_enabled = false;
|
||||
disable_irq_nosync(hw->irq_num);
|
||||
limits_mitigation_notify(hw);
|
||||
mod_delayed_work(system_highpri_wq, &hw->freq_poll_work,
|
||||
msecs_to_jiffies(LIMITS_POLLING_DELAY_MS));
|
||||
}
|
||||
}
|
||||
|
||||
static irqreturn_t lmh_dcvs_handle_isr(int irq, void *data)
|
||||
{
|
||||
struct limits_dcvs_hw *hw = data;
|
||||
|
||||
mutex_lock(&hw->access_lock);
|
||||
lmh_dcvs_notify(hw);
|
||||
mutex_unlock(&hw->access_lock);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void limits_isens_qref_init(struct platform_device *pdev,
|
||||
struct limits_dcvs_hw *hw,
|
||||
int idx, char *reg_name,
|
||||
char *reg_setting)
|
||||
{
|
||||
int ret = 0;
|
||||
uint32_t settings[3];
|
||||
|
||||
ret = of_property_read_u32_array(pdev->dev.of_node,
|
||||
reg_setting, settings, 3);
|
||||
if (ret) {
|
||||
if (ret == -EINVAL)
|
||||
return;
|
||||
|
||||
pr_err("Regulator:isens_vref settings read error:%d\n",
|
||||
ret);
|
||||
return;
|
||||
}
|
||||
hw->isens_reg[idx] = devm_regulator_get(&pdev->dev, reg_name);
|
||||
if (IS_ERR_OR_NULL(hw->isens_reg[idx])) {
|
||||
pr_err("Regulator:isens_vref init error:%ld\n",
|
||||
PTR_ERR(hw->isens_reg[idx]));
|
||||
return;
|
||||
}
|
||||
ret = regulator_set_voltage(hw->isens_reg[idx], settings[0],
|
||||
settings[1]);
|
||||
if (ret) {
|
||||
pr_err("Regulator:isens_vref set voltage error:%d\n", ret);
|
||||
return;
|
||||
}
|
||||
ret = regulator_set_load(hw->isens_reg[idx], settings[2]);
|
||||
if (ret) {
|
||||
pr_err("Regulator:isens_vref set load error:%d\n", ret);
|
||||
return;
|
||||
}
|
||||
if (regulator_enable(hw->isens_reg[idx])) {
|
||||
pr_err("Failed to enable regulator:isens_vref\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void limits_isens_vref_ldo_init(struct platform_device *pdev,
|
||||
struct limits_dcvs_hw *hw)
|
||||
{
|
||||
limits_isens_qref_init(pdev, hw, 0, "isens_vref_1p8",
|
||||
"isens-vref-1p8-settings");
|
||||
limits_isens_qref_init(pdev, hw, 1, "isens_vref_0p8",
|
||||
"isens-vref-0p8-settings");
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
lmh_freq_limit_show(struct device *dev, struct device_attribute *devattr,
|
||||
char *buf)
|
||||
{
|
||||
struct limits_dcvs_hw *hw = container_of(devattr,
|
||||
struct limits_dcvs_hw,
|
||||
lmh_freq_attr);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%lu\n", hw->hw_freq_limit);
|
||||
}
|
||||
|
||||
static int limits_dcvs_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
int affinity = -1;
|
||||
struct limits_dcvs_hw *hw;
|
||||
struct device_node *dn = pdev->dev.of_node;
|
||||
struct device_node *cpu_node, *lmh_node;
|
||||
uint32_t request_reg, clear_reg;
|
||||
int cpu, idx = 0;
|
||||
cpumask_t mask = { CPU_BITS_NONE };
|
||||
const __be32 *addr;
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
cpu_node = of_cpu_device_node_get(cpu);
|
||||
if (!cpu_node)
|
||||
continue;
|
||||
lmh_node = of_parse_phandle(cpu_node, "qcom,lmh-dcvs", 0);
|
||||
if (lmh_node == dn) {
|
||||
/*set the cpumask*/
|
||||
cpumask_set_cpu(cpu, &(mask));
|
||||
}
|
||||
of_node_put(cpu_node);
|
||||
of_node_put(lmh_node);
|
||||
}
|
||||
|
||||
hw = devm_kzalloc(&pdev->dev, sizeof(*hw), GFP_KERNEL);
|
||||
if (!hw)
|
||||
return -ENOMEM;
|
||||
/*
|
||||
* We just init regulator if none of the CPUs have
|
||||
* reference to our LMH node
|
||||
*/
|
||||
if (cpumask_empty(&mask)) {
|
||||
limits_isens_vref_ldo_init(pdev, hw);
|
||||
mutex_lock(&lmh_dcvs_list_access);
|
||||
INIT_LIST_HEAD(&hw->list);
|
||||
list_add_tail(&hw->list, &lmh_dcvs_hw_list);
|
||||
mutex_unlock(&lmh_dcvs_list_access);
|
||||
return 0;
|
||||
}
|
||||
|
||||
hw->cdev_data = devm_kcalloc(&pdev->dev, cpumask_weight(&mask),
|
||||
sizeof(*hw->cdev_data),
|
||||
GFP_KERNEL);
|
||||
if (!hw->cdev_data)
|
||||
return -ENOMEM;
|
||||
|
||||
cpumask_copy(&hw->core_map, &mask);
|
||||
hw->cdev_registered = 0;
|
||||
for_each_cpu(cpu, &hw->core_map) {
|
||||
hw->cdev_data[idx].cdev = NULL;
|
||||
hw->cdev_data[idx].max_freq = U32_MAX;
|
||||
hw->max_freq[idx] = U32_MAX;
|
||||
idx++;
|
||||
}
|
||||
ret = of_property_read_u32(dn, "qcom,affinity", &affinity);
|
||||
if (ret)
|
||||
return -ENODEV;
|
||||
switch (affinity) {
|
||||
case 0:
|
||||
hw->affinity = LIMITS_CLUSTER_0;
|
||||
break;
|
||||
case 1:
|
||||
hw->affinity = LIMITS_CLUSTER_1;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
addr = of_get_address(dn, 0, NULL, NULL);
|
||||
if (!addr) {
|
||||
pr_err("Property llm-base-addr not found\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
clear_reg = be32_to_cpu(addr[0]) + LIMITS_CLUSTER_INT_CLR_OFFSET;
|
||||
addr = of_get_address(dn, 1, NULL, NULL);
|
||||
if (!addr) {
|
||||
pr_err("Property osm-base-addr not found\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
request_reg = be32_to_cpu(addr[0]) + LIMITS_CLUSTER_REQ_OFFSET;
|
||||
|
||||
hw->hw_freq_limit = U32_MAX;
|
||||
snprintf(hw->sensor_name, sizeof(hw->sensor_name), "limits_sensor-%02d",
|
||||
affinity);
|
||||
|
||||
mutex_init(&hw->access_lock);
|
||||
INIT_DEFERRABLE_WORK(&hw->freq_poll_work, limits_dcvs_poll);
|
||||
hw->osm_hw_reg = devm_ioremap(&pdev->dev, request_reg, 0x4);
|
||||
if (!hw->osm_hw_reg) {
|
||||
pr_err("register remap failed\n");
|
||||
goto probe_exit;
|
||||
}
|
||||
hw->int_clr_reg = devm_ioremap(&pdev->dev, clear_reg, 0x4);
|
||||
if (!hw->int_clr_reg) {
|
||||
pr_err("interrupt clear reg remap failed\n");
|
||||
goto probe_exit;
|
||||
}
|
||||
|
||||
hw->irq_num = of_irq_get(pdev->dev.of_node, 0);
|
||||
if (hw->irq_num < 0) {
|
||||
pr_err("Error getting IRQ number. err:%d\n", hw->irq_num);
|
||||
goto probe_exit;
|
||||
}
|
||||
hw->is_irq_enabled = true;
|
||||
ret = devm_request_threaded_irq(&pdev->dev, hw->irq_num, NULL,
|
||||
lmh_dcvs_handle_isr, IRQF_TRIGGER_HIGH | IRQF_ONESHOT
|
||||
| IRQF_NO_SUSPEND | IRQF_SHARED, hw->sensor_name, hw);
|
||||
if (ret) {
|
||||
pr_err("Error registering for irq. err:%d\n", ret);
|
||||
ret = 0;
|
||||
goto probe_exit;
|
||||
}
|
||||
limits_isens_vref_ldo_init(pdev, hw);
|
||||
sysfs_attr_init(&hw->lmh_freq_attr.attr);
|
||||
hw->lmh_freq_attr.attr.name = "lmh_freq_limit";
|
||||
hw->lmh_freq_attr.show = lmh_freq_limit_show;
|
||||
hw->lmh_freq_attr.attr.mode = 0444;
|
||||
device_create_file(&pdev->dev, &hw->lmh_freq_attr);
|
||||
|
||||
probe_exit:
|
||||
mutex_lock(&lmh_dcvs_list_access);
|
||||
INIT_LIST_HEAD(&hw->list);
|
||||
list_add_tail(&hw->list, &lmh_dcvs_hw_list);
|
||||
mutex_unlock(&lmh_dcvs_list_access);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct of_device_id limits_dcvs_match[] = {
|
||||
{ .compatible = "qcom,msm-hw-limits", },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver limits_dcvs_driver = {
|
||||
.probe = limits_dcvs_probe,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.of_match_table = limits_dcvs_match,
|
||||
},
|
||||
};
|
||||
builtin_platform_driver(limits_dcvs_driver);
|
||||
MODULE_LICENSE("GPL");
|
||||
264
drivers/thermal/qcom/sdpm_clk.c
Normal file
264
drivers/thermal/qcom/sdpm_clk.c
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
|
||||
* Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
|
||||
#define SDPM_DRIVER "sdpm-clk-notify"
|
||||
#define CSR_MAX_VAL 7
|
||||
#define CSR_OFFSET 0xF00
|
||||
#define FREQ_HZ_TO_MHZ(f) ((f) / 1000000)
|
||||
|
||||
struct sdpm_clk_instance;
|
||||
struct sdpm_clk_data {
|
||||
struct notifier_block clk_rate_nb;
|
||||
struct clk *clk;
|
||||
const char *clock_name;
|
||||
struct notifier_block reg_nb;
|
||||
struct regulator *reg;
|
||||
uint8_t reg_enable;
|
||||
uint32_t csr_id;
|
||||
unsigned long last_freq;
|
||||
struct mutex sdpm_mutex;
|
||||
struct sdpm_clk_instance *sdpm_inst;
|
||||
};
|
||||
struct sdpm_clk_instance {
|
||||
struct device *dev;
|
||||
void __iomem *regmap;
|
||||
uint32_t clk_ct;
|
||||
struct sdpm_clk_data *clk_data;
|
||||
};
|
||||
|
||||
static void sdpm_csr_write(struct sdpm_clk_data *sdpm_data,
|
||||
unsigned long clk_rate)
|
||||
{
|
||||
struct sdpm_clk_instance *sdpm_inst = sdpm_data->sdpm_inst;
|
||||
uint32_t val = sdpm_data->reg_enable ? clk_rate : 0;
|
||||
|
||||
sdpm_data->last_freq = clk_rate;
|
||||
|
||||
dev_dbg(sdpm_inst->dev, "clock:%s offset:0x%x frequency:%u\n",
|
||||
sdpm_data->clock_name,
|
||||
CSR_OFFSET + sdpm_data->csr_id * 4, val);
|
||||
writel_relaxed(val,
|
||||
sdpm_inst->regmap + CSR_OFFSET + sdpm_data->csr_id * 4);
|
||||
}
|
||||
|
||||
static int sdpm_reg_notifier(struct notifier_block *nb, unsigned long event,
|
||||
void *data)
|
||||
{
|
||||
struct sdpm_clk_data *sdpm_data = container_of(nb,
|
||||
struct sdpm_clk_data, reg_nb);
|
||||
|
||||
dev_dbg(sdpm_data->sdpm_inst->dev, "reg:%s event:%lu\n",
|
||||
sdpm_data->clock_name, event);
|
||||
switch (event) {
|
||||
case REGULATOR_EVENT_ENABLE:
|
||||
mutex_lock(&sdpm_data->sdpm_mutex);
|
||||
sdpm_data->reg_enable = 1;
|
||||
sdpm_csr_write(sdpm_data, sdpm_data->last_freq);
|
||||
mutex_unlock(&sdpm_data->sdpm_mutex);
|
||||
return NOTIFY_OK;
|
||||
case REGULATOR_EVENT_DISABLE:
|
||||
mutex_lock(&sdpm_data->sdpm_mutex);
|
||||
sdpm_data->reg_enable = 0;
|
||||
sdpm_csr_write(sdpm_data, sdpm_data->last_freq);
|
||||
mutex_unlock(&sdpm_data->sdpm_mutex);
|
||||
return NOTIFY_OK;
|
||||
default:
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static int sdpm_clock_notifier(struct notifier_block *nb,
|
||||
unsigned long event, void *data)
|
||||
{
|
||||
struct clk_notifier_data *ndata = data;
|
||||
struct sdpm_clk_data *sdpm_data = container_of(nb,
|
||||
struct sdpm_clk_data, clk_rate_nb);
|
||||
|
||||
dev_dbg(sdpm_data->sdpm_inst->dev, "clock:%s event:%lu\n",
|
||||
sdpm_data->clock_name, event);
|
||||
switch (event) {
|
||||
case PRE_RATE_CHANGE:
|
||||
mutex_lock(&sdpm_data->sdpm_mutex);
|
||||
if (ndata->new_rate > ndata->old_rate)
|
||||
sdpm_csr_write(sdpm_data,
|
||||
FREQ_HZ_TO_MHZ(ndata->new_rate));
|
||||
mutex_unlock(&sdpm_data->sdpm_mutex);
|
||||
return NOTIFY_DONE;
|
||||
case POST_RATE_CHANGE:
|
||||
mutex_lock(&sdpm_data->sdpm_mutex);
|
||||
if (ndata->new_rate < ndata->old_rate)
|
||||
sdpm_csr_write(sdpm_data,
|
||||
FREQ_HZ_TO_MHZ(ndata->new_rate));
|
||||
mutex_unlock(&sdpm_data->sdpm_mutex);
|
||||
return NOTIFY_DONE;
|
||||
case ABORT_RATE_CHANGE:
|
||||
mutex_lock(&sdpm_data->sdpm_mutex);
|
||||
if (ndata->new_rate > ndata->old_rate)
|
||||
sdpm_csr_write(sdpm_data,
|
||||
FREQ_HZ_TO_MHZ(ndata->old_rate));
|
||||
mutex_unlock(&sdpm_data->sdpm_mutex);
|
||||
return NOTIFY_DONE;
|
||||
default:
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
static int sdpm_clk_device_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
int ret = 0, idx = 0, clk_ct = 0, csr = 0, csr_ct = 0;
|
||||
struct sdpm_clk_instance *sdpm_clk;
|
||||
struct device_node *dev_node = dev->of_node;
|
||||
struct resource *res;
|
||||
|
||||
sdpm_clk = devm_kzalloc(dev, sizeof(*sdpm_clk), GFP_KERNEL);
|
||||
if (!sdpm_clk)
|
||||
return -ENOMEM;
|
||||
sdpm_clk->dev = dev;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(dev, "Couldn't get MEM resource\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
dev_dbg(dev, "sdpm@0x%x size:%d\n", res->start,
|
||||
resource_size(res));
|
||||
dev_set_drvdata(dev, sdpm_clk);
|
||||
|
||||
sdpm_clk->regmap = devm_ioremap_resource(dev, res);
|
||||
if (!sdpm_clk->regmap) {
|
||||
dev_err(dev, "Couldn't get regmap\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = of_property_count_strings(dev_node, "clock-names");
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Couldn't get clock names. %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
clk_ct = ret;
|
||||
ret = of_property_count_u32_elems(dev_node, "csr-id");
|
||||
if (ret <= 0) {
|
||||
dev_err(dev, "Couldn't get csr ID array. %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
csr_ct = ret;
|
||||
if (clk_ct != csr_ct) {
|
||||
dev_err(dev, "Invalid csr:%d and clk:%d count.\n", csr_ct,
|
||||
clk_ct);
|
||||
return -EINVAL;
|
||||
}
|
||||
sdpm_clk->clk_ct = clk_ct;
|
||||
sdpm_clk->clk_data = devm_kcalloc(dev, clk_ct,
|
||||
sizeof(*sdpm_clk->clk_data), GFP_KERNEL);
|
||||
if (!sdpm_clk->clk_data)
|
||||
return -ENOMEM;
|
||||
|
||||
for (idx = 0; idx < sdpm_clk->clk_ct; idx++) {
|
||||
ret = of_property_read_string_index(dev_node, "clock-names",
|
||||
idx, &sdpm_clk->clk_data[idx].clock_name);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Couldn't get clk name index:%d. %d\n",
|
||||
idx, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
sdpm_clk->clk_data[idx].clk = devm_clk_get(dev,
|
||||
sdpm_clk->clk_data[idx].clock_name);
|
||||
if (IS_ERR(sdpm_clk->clk_data[idx].clk))
|
||||
return PTR_ERR(sdpm_clk->clk_data[idx].clk);
|
||||
|
||||
ret = of_property_read_u32_index(dev_node, "csr-id", idx, &csr);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Couldn't get CSR for index:%d. %d\n",
|
||||
idx, ret);
|
||||
return ret;
|
||||
}
|
||||
if (ret > CSR_MAX_VAL) {
|
||||
dev_err(dev, "Invalid CSR %d\n", csr);
|
||||
return -EINVAL;
|
||||
}
|
||||
dev_dbg(dev, "SDPM clock:%s csr:%d initialized\n",
|
||||
sdpm_clk->clk_data[idx].clock_name, csr);
|
||||
sdpm_clk->clk_data[idx].csr_id = csr;
|
||||
sdpm_clk->clk_data[idx].sdpm_inst = sdpm_clk;
|
||||
sdpm_clk->clk_data[idx].clk_rate_nb.notifier_call =
|
||||
sdpm_clock_notifier;
|
||||
sdpm_clk->clk_data[idx].last_freq = FREQ_HZ_TO_MHZ(
|
||||
clk_get_rate(sdpm_clk->clk_data[idx].clk));
|
||||
sdpm_clk->clk_data[idx].reg_enable = 1;
|
||||
sdpm_clk->clk_data[idx].reg = NULL;
|
||||
sdpm_csr_write(&sdpm_clk->clk_data[idx],
|
||||
sdpm_clk->clk_data[idx].last_freq);
|
||||
mutex_init(&sdpm_clk->clk_data[idx].sdpm_mutex);
|
||||
clk_notifier_register(sdpm_clk->clk_data[idx].clk,
|
||||
&sdpm_clk->clk_data[idx].clk_rate_nb);
|
||||
sdpm_clk->clk_data[idx].reg = devm_regulator_get(dev,
|
||||
sdpm_clk->clk_data[idx].clock_name);
|
||||
if (IS_ERR(sdpm_clk->clk_data[idx].reg)) {
|
||||
dev_err(dev, "regulator:%s get err:%d\n",
|
||||
sdpm_clk->clk_data[idx].clock_name,
|
||||
PTR_ERR(sdpm_clk->clk_data[idx].reg));
|
||||
if (PTR_ERR(sdpm_clk->clk_data[idx].reg)
|
||||
== -EPROBE_DEFER)
|
||||
return PTR_ERR(sdpm_clk->clk_data[idx].reg);
|
||||
} else {
|
||||
sdpm_clk->clk_data[idx].reg_nb.notifier_call =
|
||||
sdpm_reg_notifier;
|
||||
regulator_register_notifier(
|
||||
sdpm_clk->clk_data[idx].reg,
|
||||
&sdpm_clk->clk_data[idx].reg_nb);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdpm_clk_device_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct sdpm_clk_instance *sdpm_clk =
|
||||
(struct sdpm_clk_instance *)dev_get_drvdata(&pdev->dev);
|
||||
int idx = 0;
|
||||
|
||||
for (idx = 0; idx < sdpm_clk->clk_ct; idx++) {
|
||||
clk_notifier_unregister(sdpm_clk->clk_data[idx].clk,
|
||||
&sdpm_clk->clk_data[idx].clk_rate_nb);
|
||||
if (!sdpm_clk->clk_data[idx].reg)
|
||||
continue;
|
||||
regulator_unregister_notifier(sdpm_clk->clk_data[idx].reg,
|
||||
&sdpm_clk->clk_data[idx].reg_nb);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id sdpm_clk_device_match[] = {
|
||||
{.compatible = "qcom,sdpm"},
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver sdpm_clk_device_driver = {
|
||||
.probe = sdpm_clk_device_probe,
|
||||
.remove = sdpm_clk_device_remove,
|
||||
.driver = {
|
||||
.name = SDPM_DRIVER,
|
||||
.of_match_table = sdpm_clk_device_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(sdpm_clk_device_driver);
|
||||
MODULE_LICENSE("GPL");
|
||||
Loading…
Reference in New Issue
Block a user