Merge "driver: thermal: sdpm: Add a snapshot of sdpm clock notifier driver"

This commit is contained in:
qctecmdr 2022-11-11 13:35:07 -08:00 committed by Gerrit - the friendly Code Review server
commit 6e4382ba68
4 changed files with 706 additions and 0 deletions

View File

@ -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.

View File

@ -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

View 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");

View 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");