blob: 5f8d1e215fdb6e6ae818ec6498569714c10aeaa6 [file] [log] [blame]
/*
* Copyright (C) 2013-2015 Freescale Semiconductor, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/thermal.h>
#include <linux/err.h>
#include <linux/slab.h>
struct devfreq_cooling_device {
int id;
struct thermal_cooling_device *cool_dev;
unsigned int devfreq_state;
unsigned int max_state;
};
static DEFINE_IDR(devfreq_idr);
static DEFINE_MUTEX(devfreq_cooling_lock);
static BLOCKING_NOTIFIER_HEAD(devfreq_cooling_chain_head);
int register_devfreq_cooling_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_register(
&devfreq_cooling_chain_head, nb);
}
EXPORT_SYMBOL_GPL(register_devfreq_cooling_notifier);
int unregister_devfreq_cooling_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(
&devfreq_cooling_chain_head, nb);
}
EXPORT_SYMBOL_GPL(unregister_devfreq_cooling_notifier);
static int devfreq_cooling_notifier_call_chain(unsigned long val)
{
return (blocking_notifier_call_chain(
&devfreq_cooling_chain_head, val, NULL)
== NOTIFY_BAD) ? -EINVAL : 0;
}
static int devfreq_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long state)
{
struct devfreq_cooling_device *devfreq_device = cdev->devdata;
int ret;
ret = devfreq_cooling_notifier_call_chain(state);
if (ret)
return -EINVAL;
devfreq_device->devfreq_state = state;
return 0;
}
static int devfreq_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct devfreq_cooling_device *devfreq_device = cdev->devdata;
*state = devfreq_device->max_state;
return 0;
}
static int devfreq_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
struct devfreq_cooling_device *devfreq_device = cdev->devdata;
*state = devfreq_device->devfreq_state;
return 0;
}
static struct thermal_cooling_device_ops const devfreq_cooling_ops = {
.get_max_state = devfreq_get_max_state,
.get_cur_state = devfreq_get_cur_state,
.set_cur_state = devfreq_set_cur_state,
};
static int get_idr(struct idr *idr, int *id)
{
int ret;
mutex_lock(&devfreq_cooling_lock);
ret = idr_alloc(idr, NULL, 0, 0, GFP_KERNEL);
mutex_unlock(&devfreq_cooling_lock);
if (unlikely(ret < 0))
return ret;
*id = ret;
return 0;
}
static void release_idr(struct idr *idr, int id)
{
mutex_lock(&devfreq_cooling_lock);
idr_remove(idr, id);
mutex_unlock(&devfreq_cooling_lock);
}
struct thermal_cooling_device *devfreq_cooling_register(struct device_node *np,
unsigned long states)
{
struct thermal_cooling_device *cool_dev;
struct devfreq_cooling_device *devfreq_dev = NULL;
char dev_name[THERMAL_NAME_LENGTH];
int ret = 0;
devfreq_dev = kzalloc(sizeof(struct devfreq_cooling_device),
GFP_KERNEL);
if (!devfreq_dev)
return ERR_PTR(-ENOMEM);
ret = get_idr(&devfreq_idr, &devfreq_dev->id);
if (ret) {
kfree(devfreq_dev);
return ERR_PTR(-EINVAL);
}
snprintf(dev_name, sizeof(dev_name), "thermal-devfreq-%d",
devfreq_dev->id);
devfreq_dev->max_state = states;
cool_dev = thermal_of_cooling_device_register(np, dev_name,
devfreq_dev,
&devfreq_cooling_ops);
if (!cool_dev) {
release_idr(&devfreq_idr, devfreq_dev->id);
kfree(devfreq_dev);
return ERR_PTR(-EINVAL);
}
devfreq_dev->cool_dev = cool_dev;
devfreq_dev->devfreq_state = 0;
return cool_dev;
}
EXPORT_SYMBOL_GPL(devfreq_cooling_register);
void devfreq_cooling_unregister(struct thermal_cooling_device *cdev)
{
struct devfreq_cooling_device *devfreq_dev = cdev->devdata;
thermal_cooling_device_unregister(devfreq_dev->cool_dev);
release_idr(&devfreq_idr, devfreq_dev->id);
kfree(devfreq_dev);
}
EXPORT_SYMBOL_GPL(devfreq_cooling_unregister);