device_cooling.c 3.59 KB
/*
 * 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;
};

static DEFINE_IDR(devfreq_idr);
static DEFINE_MUTEX(devfreq_cooling_lock);

#define	MAX_STATE	1

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)
{
	*state = 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(void)
{
	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);

	cool_dev = thermal_cooling_device_register(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);