| /* |
| * pwm_fan.c fan driver that is controlled by pwm |
| * |
| * Copyright (c) 2012-2013 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> |
| #include <linux/gpio.h> |
| #include <linux/interrupt.h> |
| |
| struct fan_dev_data { |
| int next_state; |
| int active_steps; |
| int *fan_rpm; |
| int *fan_pwm; |
| int *fan_rru; |
| int *fan_rrd; |
| int *fan_state_cap_lookup; |
| 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; |
| int tach_gpio; |
| int tach_irq; |
| int tach_enabled; |
| int fan_state_cap; |
| }; |
| |
| #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 > 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_tach_enabled_show(void *data, u64 *val) |
| { |
| struct fan_dev_data *fan_data = (struct fan_dev_data *)data; |
| |
| if (!fan_data) |
| return -EINVAL; |
| |
| *val = fan_data->tach_enabled; |
| |
| return 0; |
| } |
| |
| static int fan_tach_enabled_set(void *data, u64 val) |
| { |
| struct fan_dev_data *fan_data = (struct fan_dev_data *)data; |
| |
| if (!fan_data) |
| return -EINVAL; |
| if (fan_data->tach_gpio < 0) |
| return -EPERM; |
| |
| if (val == 1 && !fan_data->tach_enabled) { |
| enable_irq(fan_data->tach_irq); |
| fan_data->tach_enabled = val; |
| } else if (val == 0 && fan_data->tach_enabled && |
| fan_data->tach_gpio != -1) { |
| disable_irq(fan_data->tach_irq); |
| fan_data->tach_enabled = val; |
| } |
| |
| 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 > 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_tach_enabled_fops, |
| fan_tach_enabled_show, |
| fan_tach_enabled_set, "%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; |
| |
| if (!debugfs_create_file("tach_enable", 0644, fan_debugfs_root, |
| (void *)fan_data, |
| &fan_tach_enabled_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; |
| |
| 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]; |
| |
| 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 && |
| (fan_data->fan_temp_control_flag)) |
| 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; |
| } |
| |
| /*State cap sysfs fops*/ |
| static ssize_t show_fan_state_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_state_cap); |
| mutex_unlock(&fan_data->fan_state_lock); |
| return ret; |
| } |
| |
| static ssize_t set_fan_state_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->active_steps) |
| val = fan_data->active_steps - 1; |
| |
| mutex_lock(&fan_data->fan_state_lock); |
| fan_data->fan_state_cap = val; |
| fan_data->fan_cap_pwm = |
| fan_data->fan_pwm[fan_data->fan_state_cap_lookup[val]]; |
| fan_data->next_target_pwm = min(fan_data->fan_cap_pwm, |
| fan_data->next_target_pwm); |
| dev_info(dev, "pwm_cap=%d target_pwm=%d\n", |
| 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 DEVICE_ATTR(state_cap, S_IWUSR | S_IRUGO, |
| show_fan_state_cap_sysfs, set_fan_state_cap_sysfs); |
| |
| static struct attribute *pwm_fan_attributes[] = { |
| &dev_attr_pwm_cap.attr, |
| &dev_attr_state_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); |
| } |
| |
| irqreturn_t fan_tach_isr(int irq, void *data) |
| { |
| return IRQ_HANDLED; |
| } |
| |
| static int 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, |
| 5 * 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; |
| fan_data->fan_state_cap_lookup = fan_data->fan_rrd + data->active_steps; |
| |
| mutex_init(&fan_data->fan_state_lock); |
| |
| fan_data->workqueue = alloc_workqueue(dev_name(&pdev->dev), |
| WQ_HIGHPRI | WQ_UNBOUND, 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->step_time = data->step_time; |
| fan_data->active_steps = data->active_steps; |
| fan_data->pwm_period = data->pwm_period; |
| fan_data->dev = &pdev->dev; |
| fan_data->fan_state_cap = data->state_cap; |
| |
| 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]; |
| fan_data->fan_state_cap_lookup[i] = data->state_cap_lookup[i]; |
| dev_info(&pdev->dev, |
| "rpm=%d, pwm=%d, rru=%d, rrd=%d state:%d\n", |
| fan_data->fan_rpm[i], |
| fan_data->fan_pwm[i], |
| fan_data->fan_rru[i], |
| fan_data->fan_rrd[i], |
| fan_data->fan_state_cap_lookup[i]); |
| } |
| fan_data->fan_cap_pwm = data->active_pwm[data->state_cap]; |
| dev_info(&pdev->dev, "cap state:%d, cap pwm:%d\n", |
| fan_data->fan_state_cap, fan_data->fan_cap_pwm); |
| |
| 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"); |
| } |
| |
| |
| fan_data->tach_gpio = data->tach_gpio; |
| fan_data->tach_enabled = 0; |
| |
| if (fan_data->tach_gpio != -1) { |
| /* init fan tach */ |
| fan_data->tach_irq = gpio_to_irq(fan_data->tach_gpio); |
| err = gpio_request(fan_data->tach_gpio, "pwm-fan-tach"); |
| if (err < 0) { |
| dev_err(&pdev->dev, "fan tach gpio request failed\n"); |
| goto tach_gpio_request_fail; |
| } |
| |
| err = gpio_direction_input(fan_data->tach_gpio); |
| if (err < 0) { |
| dev_err(&pdev->dev, "fan tach set gpio direction input failed\n"); |
| goto tach_request_irq_fail; |
| } |
| |
| err = gpio_sysfs_set_active_low(fan_data->tach_gpio, 1); |
| if (err < 0) { |
| dev_err(&pdev->dev, "fan tach set gpio active low failed\n"); |
| goto tach_request_irq_fail; |
| } |
| |
| err = request_irq(fan_data->tach_irq, fan_tach_isr, |
| IRQF_TRIGGER_FALLING , "pwm-fan-tach", NULL); |
| if (err < 0) |
| goto tach_request_irq_fail; |
| disable_irq_nosync(fan_data->tach_irq); |
| } |
| |
| 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); |
| free_irq(fan_data->tach_irq, NULL); |
| tach_request_irq_fail: |
| gpio_free(fan_data->tach_gpio); |
| tach_gpio_request_fail: |
| pwm_req_fail: |
| thermal_cooling_device_unregister(fan_data->cdev); |
| return err; |
| } |
| |
| static int pwm_fan_remove(struct platform_device *pdev) |
| { |
| struct fan_dev_data *fan_data = platform_get_drvdata(pdev); |
| |
| if (!fan_data) |
| return -EINVAL; |
| debugfs_remove_recursive(fan_debugfs_root); |
| free_irq(fan_data->tach_irq, NULL); |
| gpio_free(fan_data->tach_gpio); |
| 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; |
| } |
| |
| #if CONFIG_PM |
| static int pwm_fan_suspend(struct platform_device *pdev, pm_message_t state) |
| { |
| struct fan_dev_data *fan_data = platform_get_drvdata(pdev); |
| |
| mutex_lock(&fan_data->fan_state_lock); |
| cancel_delayed_work(&fan_data->fan_ramp_work); |
| /*Turn the fan off*/ |
| fan_data->fan_cur_pwm = 0; |
| fan_data->next_target_pwm = 0; |
| set_pwm_duty_cycle(0, fan_data); |
| |
| /*Stop thermal control*/ |
| fan_data->fan_temp_control_flag = 0; |
| mutex_unlock(&fan_data->fan_state_lock); |
| return 0; |
| } |
| |
| static int pwm_fan_resume(struct platform_device *pdev) |
| { |
| struct fan_dev_data *fan_data = platform_get_drvdata(pdev); |
| |
| /*Sanity check, want to make sure fan is off when the driver resumes*/ |
| mutex_lock(&fan_data->fan_state_lock); |
| set_pwm_duty_cycle(0, fan_data); |
| |
| /*Start thermal control*/ |
| fan_data->fan_temp_control_flag = 1; |
| |
| mutex_unlock(&fan_data->fan_state_lock); |
| return 0; |
| } |
| #endif |
| |
| static struct platform_driver pwm_fan_driver = { |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = "pwm-fan", |
| }, |
| .probe = pwm_fan_probe, |
| .remove = pwm_fan_remove, |
| #if CONFIG_PM |
| .suspend = pwm_fan_suspend, |
| .resume = pwm_fan_resume, |
| #endif |
| }; |
| |
| module_platform_driver(pwm_fan_driver); |
| |
| MODULE_DESCRIPTION("pwm fan driver"); |
| MODULE_AUTHOR("Anshul Jain <anshulj@nvidia.com>"); |
| MODULE_LICENSE("GPL v2"); |