summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/thermal/Kconfig6
-rw-r--r--drivers/thermal/Makefile1
-rw-r--r--drivers/thermal/pwm_fan.c637
-rw-r--r--include/linux/platform_data/pwm_fan.h37
4 files changed, 681 insertions, 0 deletions
diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig
index 18852b785123..d83c94e28aa8 100644
--- a/drivers/thermal/Kconfig
+++ b/drivers/thermal/Kconfig
@@ -87,3 +87,9 @@ config THERMAL_DEFAULT_GOV_USER_SPACE
Select this if you want to let the user space manage the
platform thermals.
endchoice
+
+config PWM_FAN
+ bool "PWM Fan driver"
+ depends on THERMAL
+ help
+ Enables the fan driver that is controlled by pwm
diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile
index 8f2199aa4bc8..0469787f11ca 100644
--- a/drivers/thermal/Makefile
+++ b/drivers/thermal/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
obj-$(CONFIG_CPU_THERMAL) += cpu_cooling.o
obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o
obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o
+obj-$(CONFIG_PWM_FAN) += pwm_fan.o
obj-$(CONFIG_FAIR_SHARE) += fair_share.o
obj-$(CONFIG_STEP_WISE) += step_wise.o
obj-$(CONFIG_USER_SPACE) += user_space.o
diff --git a/drivers/thermal/pwm_fan.c b/drivers/thermal/pwm_fan.c
new file mode 100644
index 000000000000..4ea4ef76d8bc
--- /dev/null
+++ b/drivers/thermal/pwm_fan.c
@@ -0,0 +1,637 @@
+/*
+ * pwm_fan.c fan driver that is controlled by pwm
+ *
+ * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
+ *
+ * Author: Anshul Jain <anshulj@nvidia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/therm_est.h>
+#include <linux/slab.h>
+#include <linux/platform_data/pwm_fan.h>
+#include <linux/thermal.h>
+#include <linux/mutex.h>
+#include <linux/debugfs.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include <linux/seq_file.h>
+#include <linux/pwm.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+
+struct fan_dev_data {
+ int next_state;
+ int active_steps;
+ int *fan_rpm;
+ int *fan_pwm;
+ int *fan_rru;
+ int *fan_rrd;
+ struct workqueue_struct *workqueue;
+ int fan_temp_control_flag;
+ struct pwm_device *pwm_dev;
+ int fan_cap_pwm;
+ int fan_cur_pwm;
+ int next_target_pwm;
+ struct thermal_cooling_device *cdev;
+ struct delayed_work fan_ramp_work;
+ int step_time;
+ int precision_multiplier;
+ struct mutex fan_state_lock;
+ int pwm_period;
+ struct device *dev;
+};
+
+#ifdef CONFIG_DEBUG_FS
+static struct dentry *fan_debugfs_root;
+
+static int fan_target_pwm_show(void *data, u64 *val)
+{
+ struct fan_dev_data *fan_data = (struct fan_dev_data *)data;
+
+ if (!fan_data)
+ return -EINVAL;
+ mutex_lock(&fan_data->fan_state_lock);
+ *val = ((struct fan_dev_data *)data)->next_target_pwm /
+ fan_data->precision_multiplier;
+ mutex_unlock(&fan_data->fan_state_lock);
+ return 0;
+}
+
+static int fan_target_pwm_set(void *data, u64 val)
+{
+ struct fan_dev_data *fan_data = (struct fan_dev_data *)data;
+
+ if (!fan_data)
+ return -EINVAL;
+ if (val < 0)
+ val = 0;
+ else if (val > fan_data->pwm_period)
+ val = fan_data->pwm_period;
+
+ mutex_lock(&fan_data->fan_state_lock);
+ fan_data->next_target_pwm =
+ min((int)(val * fan_data->precision_multiplier),
+ fan_data->fan_cap_pwm);
+
+ if (fan_data->next_target_pwm != fan_data->fan_cur_pwm)
+ queue_delayed_work(fan_data->workqueue,
+ &fan_data->fan_ramp_work,
+ msecs_to_jiffies(fan_data->step_time));
+ mutex_unlock(&fan_data->fan_state_lock);
+ return 0;
+}
+
+static int fan_temp_control_show(void *data, u64 *val)
+{
+ struct fan_dev_data *fan_data = (struct fan_dev_data *)data;
+
+ if (!fan_data)
+ return -EINVAL;
+ mutex_lock(&fan_data->fan_state_lock);
+ *val = fan_data->fan_temp_control_flag;
+ mutex_unlock(&fan_data->fan_state_lock);
+ return 0;
+}
+
+static int fan_temp_control_set(void *data, u64 val)
+{
+ struct fan_dev_data *fan_data = (struct fan_dev_data *)data;
+
+ if (!fan_data)
+ return -EINVAL;
+
+ mutex_lock(&fan_data->fan_state_lock);
+ fan_data->fan_temp_control_flag = val > 0 ? 1 : 0;
+ mutex_unlock(&fan_data->fan_state_lock);
+ return 0;
+}
+
+static int fan_cap_pwm_set(void *data, u64 val)
+{
+ struct fan_dev_data *fan_data = (struct fan_dev_data *)data;
+
+ if (!fan_data)
+ return -EINVAL;
+ if (val < 0)
+ val = 0;
+ else if (val > fan_data->pwm_period)
+ val = fan_data->pwm_period;
+ mutex_lock(&fan_data->fan_state_lock);
+ fan_data->fan_cap_pwm = val * fan_data->precision_multiplier;
+ fan_data->next_target_pwm = min(fan_data->fan_cap_pwm,
+ fan_data->next_target_pwm);
+ mutex_unlock(&fan_data->fan_state_lock);
+ return 0;
+}
+
+static int fan_cap_pwm_show(void *data, u64 *val)
+{
+ struct fan_dev_data *fan_data = (struct fan_dev_data *)data;
+
+ if (!fan_data)
+ return -EINVAL;
+ mutex_lock(&fan_data->fan_state_lock);
+ *val = fan_data->fan_cap_pwm / fan_data->precision_multiplier;
+ mutex_unlock(&fan_data->fan_state_lock);
+ return 0;
+}
+
+static int fan_step_time_set(void *data, u64 val)
+{
+ struct fan_dev_data *fan_data = (struct fan_dev_data *)data;
+
+ if (!fan_data)
+ return -EINVAL;
+ mutex_lock(&fan_data->fan_state_lock);
+ fan_data->step_time = val;
+ mutex_unlock(&fan_data->fan_state_lock);
+ return 0;
+}
+
+static int fan_cur_pwm_show(void *data, u64 *val)
+{
+ struct fan_dev_data *fan_data = (struct fan_dev_data *)data;
+
+ if (!fan_data)
+ return -EINVAL;
+ mutex_lock(&fan_data->fan_state_lock);
+ *val = (fan_data->fan_cur_pwm / fan_data->precision_multiplier);
+ mutex_unlock(&fan_data->fan_state_lock);
+ return 0;
+}
+
+static int fan_step_time_show(void *data, u64 *val)
+{
+ struct fan_dev_data *fan_data = (struct fan_dev_data *)data;
+
+ if (!fan_data)
+ return -EINVAL;
+ mutex_lock(&fan_data->fan_state_lock);
+ *val = fan_data->step_time;
+ mutex_unlock(&fan_data->fan_state_lock);
+ return 0;
+}
+
+static int fan_debugfs_show(struct seq_file *s, void *data)
+{
+ int i;
+ struct fan_dev_data *fan_data = s->private;
+
+ if (!fan_data)
+ return -EINVAL;
+ seq_printf(s, "(Index, RPM, PWM, RRU*1024, RRD*1024)\n");
+ for (i = 0; i < fan_data->active_steps; i++) {
+ seq_printf(s, "(%d, %d, %d, %d, %d)\n", i, fan_data->fan_rpm[i],
+ fan_data->fan_pwm[i]/fan_data->precision_multiplier,
+ fan_data->fan_rru[i],
+ fan_data->fan_rrd[i]);
+ }
+ return 0;
+}
+
+static int fan_debugfs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, fan_debugfs_show, inode->i_private);
+}
+
+static const struct file_operations fan_rpm_table_fops = {
+ .open = fan_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+DEFINE_SIMPLE_ATTRIBUTE(fan_cap_pwm_fops,
+ fan_cap_pwm_show,
+ fan_cap_pwm_set, "%llu\n");
+DEFINE_SIMPLE_ATTRIBUTE(fan_temp_control_fops,
+ fan_temp_control_show,
+ fan_temp_control_set, "%llu\n");
+DEFINE_SIMPLE_ATTRIBUTE(fan_target_pwm_fops,
+ fan_target_pwm_show,
+ fan_target_pwm_set, "%llu\n");
+DEFINE_SIMPLE_ATTRIBUTE(fan_cur_pwm_fops,
+ fan_cur_pwm_show,
+ NULL, "%llu\n");
+DEFINE_SIMPLE_ATTRIBUTE(fan_step_time_fops,
+ fan_step_time_show,
+ fan_step_time_set, "%llu\n");
+
+static int pwm_fan_debug_init(struct fan_dev_data *fan_data)
+{
+ fan_debugfs_root = debugfs_create_dir("tegra_fan", 0);
+
+ if (!fan_debugfs_root)
+ return -ENOMEM;
+
+ if (!debugfs_create_file("target_pwm", 0644, fan_debugfs_root,
+ (void *)fan_data,
+ &fan_target_pwm_fops))
+ goto err_out;
+
+ if (!debugfs_create_file("temp_control", 0644, fan_debugfs_root,
+ (void *)fan_data,
+ &fan_temp_control_fops))
+ goto err_out;
+
+ if (!debugfs_create_file("pwm_cap", 0644, fan_debugfs_root,
+ (void *)fan_data,
+ &fan_cap_pwm_fops))
+ goto err_out;
+
+ if (!debugfs_create_file("pwm_rpm_table", 0444, fan_debugfs_root,
+ (void *)fan_data,
+ &fan_rpm_table_fops))
+ goto err_out;
+
+ if (!debugfs_create_file("step_time", 0644, fan_debugfs_root,
+ (void *)fan_data,
+ &fan_step_time_fops))
+ goto err_out;
+
+ if (!debugfs_create_file("cur_pwm", 0444, fan_debugfs_root,
+ (void *)fan_data,
+ &fan_cur_pwm_fops))
+ goto err_out;
+ return 0;
+
+err_out:
+ debugfs_remove_recursive(fan_debugfs_root);
+ return -ENOMEM;
+}
+#else
+static inline int pwm_fan_debug_init(struct fan_dev_data *fan_data)
+{
+ return 0;
+}
+#endif /* DEBUG_FS*/
+
+static int pwm_fan_get_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long *cur_state)
+{
+ struct fan_dev_data *fan_data = cdev->devdata;
+
+ if (!fan_data)
+ return -EINVAL;
+
+ mutex_lock(&fan_data->fan_state_lock);
+ *cur_state = fan_data->next_state;
+ mutex_unlock(&fan_data->fan_state_lock);
+ return 0;
+}
+
+static int pwm_fan_set_cur_state(struct thermal_cooling_device *cdev,
+ unsigned long cur_state)
+{
+ struct fan_dev_data *fan_data = cdev->devdata;
+
+ if (!fan_data)
+ return -EINVAL;
+#ifdef CONFIG_DEBUG_FS
+ if (!fan_data->fan_temp_control_flag)
+ return 0;
+#endif
+ mutex_lock(&fan_data->fan_state_lock);
+
+ fan_data->next_state = cur_state;
+
+ if (fan_data->next_state <= 0)
+ fan_data->next_target_pwm = 0;
+ else
+ fan_data->next_target_pwm = fan_data->fan_pwm[cur_state - 1];
+
+ fan_data->next_target_pwm =
+ min(fan_data->fan_cap_pwm, fan_data->next_target_pwm);
+ if (fan_data->next_target_pwm != fan_data->fan_cur_pwm)
+ queue_delayed_work(fan_data->workqueue,
+ &(fan_data->fan_ramp_work),
+ msecs_to_jiffies(fan_data->step_time));
+
+ mutex_unlock(&fan_data->fan_state_lock);
+ return 0;
+}
+
+static int pwm_fan_get_max_state(struct thermal_cooling_device *cdev,
+ unsigned long *max_state)
+{
+ struct fan_dev_data *fan_data = cdev->devdata;
+
+ *max_state = fan_data->active_steps;
+ return 0;
+}
+
+static struct thermal_cooling_device_ops pwm_fan_cooling_ops = {
+ .get_max_state = pwm_fan_get_max_state,
+ .get_cur_state = pwm_fan_get_cur_state,
+ .set_cur_state = pwm_fan_set_cur_state,
+};
+
+static int fan_get_rru(int pwm, struct fan_dev_data *fan_data)
+{
+ int i;
+
+ for (i = 0; i < fan_data->active_steps - 1 ; i++) {
+ if ((pwm >= fan_data->fan_pwm[i]) &&
+ (pwm < fan_data->fan_pwm[i + 1])) {
+ return fan_data->fan_rru[i];
+ }
+ }
+ return fan_data->fan_rru[fan_data->active_steps - 1];
+}
+
+static int fan_get_rrd(int pwm, struct fan_dev_data *fan_data)
+{
+ int i;
+
+ for (i = 0; i < fan_data->active_steps - 1 ; i++) {
+ if ((pwm >= fan_data->fan_pwm[i]) &&
+ (pwm < fan_data->fan_pwm[i + 1])) {
+ return fan_data->fan_rrd[i];
+ }
+ }
+ return fan_data->fan_rrd[fan_data->active_steps - 1];
+}
+
+static void set_pwm_duty_cycle(int pwm, struct fan_dev_data *fan_data)
+{
+ if (fan_data != NULL && fan_data->pwm_dev != NULL) {
+ pwm_config(fan_data->pwm_dev, fan_data->pwm_period - pwm,
+ fan_data->pwm_period);
+ pwm_enable(fan_data->pwm_dev);
+ } else {
+ dev_err(fan_data->dev,
+ "FAN:PWM device or fan data is null\n");
+ }
+}
+
+static int get_next_higher_pwm(int pwm, struct fan_dev_data *fan_data)
+{
+ int i;
+
+ for (i = 0; i < fan_data->active_steps; i++)
+ if (pwm < fan_data->fan_pwm[i])
+ return fan_data->fan_pwm[i];
+
+ return fan_data->fan_pwm[fan_data->active_steps - 1];
+}
+
+static int get_next_lower_pwm(int pwm, struct fan_dev_data *fan_data)
+{
+ int i;
+
+ for (i = fan_data->active_steps - 1; i >= 0; i--)
+ if (pwm > fan_data->fan_pwm[i])
+ return fan_data->fan_pwm[i];
+
+ return fan_data->fan_pwm[fan_data->active_steps - 1];
+}
+
+static void fan_ramping_work_func(struct work_struct *work)
+{
+ int rru, rrd;
+ int cur_pwm, next_pwm;
+ struct delayed_work *dwork = container_of(work, struct delayed_work,
+ work);
+ struct fan_dev_data *fan_data = container_of(dwork, struct
+ fan_dev_data, fan_ramp_work);
+
+ if (!fan_data) {
+ dev_err(fan_data->dev, "Fan data is null\n");
+ return;
+ }
+ mutex_lock(&fan_data->fan_state_lock);
+ cur_pwm = fan_data->fan_cur_pwm;
+ rru = fan_get_rru(cur_pwm, fan_data);
+ rrd = fan_get_rrd(cur_pwm, fan_data);
+ next_pwm = cur_pwm;
+
+ if (fan_data->next_target_pwm > fan_data->fan_cur_pwm) {
+ fan_data->fan_cur_pwm = fan_data->fan_cur_pwm + rru;
+ next_pwm = min(
+ get_next_higher_pwm(cur_pwm, fan_data),
+ fan_data->fan_cur_pwm);
+ next_pwm = min(fan_data->next_target_pwm, next_pwm);
+ next_pwm = min(fan_data->fan_cap_pwm, next_pwm);
+ } else if (fan_data->next_target_pwm < fan_data->fan_cur_pwm) {
+ fan_data->fan_cur_pwm = fan_data->fan_cur_pwm - rrd;
+ next_pwm = max(get_next_lower_pwm(cur_pwm, fan_data),
+ fan_data->fan_cur_pwm);
+ next_pwm = max(next_pwm, fan_data->next_target_pwm);
+ next_pwm = max(0, next_pwm);
+ }
+ set_pwm_duty_cycle(next_pwm/fan_data->precision_multiplier, fan_data);
+ fan_data->fan_cur_pwm = next_pwm;
+ if (fan_data->next_target_pwm != next_pwm)
+ queue_delayed_work(fan_data->workqueue,
+ &(fan_data->fan_ramp_work),
+ msecs_to_jiffies(fan_data->step_time));
+ mutex_unlock(&fan_data->fan_state_lock);
+}
+
+
+static ssize_t show_fan_pwm_cap_sysfs(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct fan_dev_data *fan_data = dev_get_drvdata(dev);
+ int ret;
+
+ if (!fan_data)
+ return -EINVAL;
+ mutex_lock(&fan_data->fan_state_lock);
+ ret = sprintf(buf, "%d\n",
+ (fan_data->fan_cap_pwm / fan_data->precision_multiplier));
+ mutex_unlock(&fan_data->fan_state_lock);
+ return ret;
+}
+
+static ssize_t set_fan_pwm_cap_sysfs(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct fan_dev_data *fan_data = dev_get_drvdata(dev);
+ long val;
+ int ret;
+
+ ret = kstrtol(buf, 10, &val);
+
+ if (ret < 0)
+ return -EINVAL;
+
+ if (!fan_data)
+ return -EINVAL;
+
+ if (val < 0)
+ val = 0;
+ else if (val > fan_data->pwm_period)
+ val = fan_data->pwm_period;
+ mutex_lock(&fan_data->fan_state_lock);
+ fan_data->fan_cap_pwm = val * fan_data->precision_multiplier;
+ fan_data->next_target_pwm = min(fan_data->fan_cap_pwm,
+ fan_data->next_target_pwm);
+ mutex_unlock(&fan_data->fan_state_lock);
+ return count;
+}
+
+static DEVICE_ATTR(pwm_cap, S_IWUSR | S_IRUGO, show_fan_pwm_cap_sysfs,
+ set_fan_pwm_cap_sysfs);
+static struct attribute *pwm_fan_attributes[] = {
+ &dev_attr_pwm_cap.attr,
+ NULL
+};
+
+static const struct attribute_group pwm_fan_group = {
+ .attrs = pwm_fan_attributes,
+};
+
+static int add_sysfs_entry(struct device *dev)
+{
+ return sysfs_create_group(&dev->kobj, &pwm_fan_group);
+}
+
+static void remove_sysfs_entry(struct device *dev)
+{
+ sysfs_remove_group(&dev->kobj, &pwm_fan_group);
+}
+
+static int __devinit pwm_fan_probe(struct platform_device *pdev)
+{
+ int i;
+ struct pwm_fan_platform_data *data;
+ struct fan_dev_data *fan_data;
+ int *rpm_data;
+ int err = 0;
+
+ data = dev_get_platdata(&pdev->dev);
+ if (!data) {
+ dev_err(&pdev->dev, "platform data is null\n");
+ return -EINVAL;
+ }
+
+ fan_data = devm_kzalloc(&pdev->dev,
+ sizeof(struct fan_dev_data), GFP_KERNEL);
+ if (!fan_data)
+ return -ENOMEM;
+
+ rpm_data = devm_kzalloc(&pdev->dev,
+ 4 * sizeof(int) * data->active_steps, GFP_KERNEL);
+ if (!rpm_data)
+ return -ENOMEM;
+
+ fan_data->fan_rpm = rpm_data;
+ fan_data->fan_pwm = rpm_data + data->active_steps;
+ fan_data->fan_rru = fan_data->fan_pwm + data->active_steps;
+ fan_data->fan_rrd = fan_data->fan_rru + data->active_steps;
+
+ mutex_init(&fan_data->fan_state_lock);
+
+ fan_data->workqueue = alloc_workqueue(dev_name(&pdev->dev),
+ WQ_HIGHPRI | WQ_UNBOUND | WQ_RESCUER, 1);
+ if (!fan_data->workqueue)
+ return -ENOMEM;
+
+ INIT_DELAYED_WORK(&(fan_data->fan_ramp_work), fan_ramping_work_func);
+
+ fan_data->precision_multiplier = data->precision_multiplier;
+ fan_data->fan_cap_pwm = data->pwm_cap * data->precision_multiplier;
+ fan_data->step_time = data->step_time;
+ fan_data->active_steps = data->active_steps;
+ fan_data->pwm_period = data->pwm_period;
+ fan_data->dev = &pdev->dev;
+
+ for (i = 0; i < fan_data->active_steps; i++) {
+ fan_data->fan_rpm[i] = data->active_rpm[i];
+ fan_data->fan_pwm[i] = data->active_pwm[i];
+ fan_data->fan_rru[i] = data->active_rru[i];
+ fan_data->fan_rrd[i] = data->active_rrd[i];
+ dev_info(&pdev->dev, "rpm=%d, pwm=%d, rru=%d, rrd=%d\n",
+ fan_data->fan_rpm[i],
+ fan_data->fan_pwm[i],
+ fan_data->fan_rru[i],
+ fan_data->fan_rrd[i]);
+ }
+
+ fan_data->cdev =
+ thermal_cooling_device_register((char *)dev_name(&pdev->dev),
+ fan_data, &pwm_fan_cooling_ops);
+
+ if (IS_ERR_OR_NULL(fan_data->cdev)) {
+ dev_err(&pdev->dev, "Failed to register cooling device\n");
+ return -EINVAL;
+ }
+
+ fan_data->pwm_dev = pwm_request(data->pwm_id, dev_name(&pdev->dev));
+ if (IS_ERR_OR_NULL(fan_data->pwm_dev)) {
+ dev_err(&pdev->dev, "unable to request PWM for fan\n");
+ err = -ENODEV;
+ goto pwm_req_fail;
+ } else {
+ dev_info(&pdev->dev, "got pwm for fan\n");
+ }
+
+ platform_set_drvdata(pdev, fan_data);
+
+ /*turn temp control on*/
+ fan_data->fan_temp_control_flag = 1;
+ set_pwm_duty_cycle(0, fan_data);
+
+ if (add_sysfs_entry(&pdev->dev) < 0) {
+ dev_err(&pdev->dev, "FAN:Can't create syfs node");
+ err = -ENOMEM;
+ goto sysfs_fail;
+ }
+
+ if (pwm_fan_debug_init(fan_data) < 0) {
+ dev_err(&pdev->dev, "FAN:Can't create debug fs nodes");
+ /*Just continue without debug fs*/
+ }
+ return err;
+
+sysfs_fail:
+ pwm_free(fan_data->pwm_dev);
+pwm_req_fail:
+ thermal_cooling_device_unregister(fan_data->cdev);
+ return err;
+}
+
+static int __devexit pwm_fan_remove(struct platform_device *pdev)
+{
+ struct fan_dev_data *fan_data = platform_get_drvdata(pdev);
+
+ if (!fan_data)
+ return -EINVAL;
+ pwm_config(fan_data->pwm_dev, 0, fan_data->pwm_period);
+ pwm_disable(fan_data->pwm_dev);
+ pwm_free(fan_data->pwm_dev);
+ thermal_cooling_device_unregister(fan_data->cdev);
+ remove_sysfs_entry(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver pwm_fan_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "pwm-fan",
+ },
+ .probe = pwm_fan_probe,
+ .remove = __devexit_p(pwm_fan_remove),
+};
+
+module_platform_driver(pwm_fan_driver);
+
+MODULE_DESCRIPTION("pwm fan driver");
+MODULE_AUTHOR("Anshul Jain <anshulj@nvidia.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/platform_data/pwm_fan.h b/include/linux/platform_data/pwm_fan.h
new file mode 100644
index 000000000000..8bc5b4e94f16
--- /dev/null
+++ b/include/linux/platform_data/pwm_fan.h
@@ -0,0 +1,37 @@
+/*
+ * include/linux/platform_data/pwm_fan.h
+ *
+ * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _PWM_FAN_H_
+#define _PWM_FAN_H_
+
+#define MAX_ACTIVE_STATES 10
+
+struct pwm_fan_platform_data {
+ int active_steps;
+ int active_rpm[MAX_ACTIVE_STATES];
+ int active_pwm[MAX_ACTIVE_STATES];
+ int active_rru[MAX_ACTIVE_STATES];
+ int active_rrd[MAX_ACTIVE_STATES];
+ int pwm_cap;
+ int pwm_period;
+ int active_temps[MAX_ACTIVE_STATES];
+ int pwm_id;
+ int step_time;
+ int precision_multiplier;
+};
+#endif