Blame view

drivers/hwmon/pwm-fan.c 7.84 KB
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  /*
   * pwm-fan.c - Hwmon driver for fans connected to PWM lines.
   *
   * Copyright (c) 2014 Samsung Electronics Co., Ltd.
   *
   * Author: Kamil Debski <k.debski@samsung.com>
   *
   * This program is free software; you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation; either version 2 of the License, or
   * (at your option) any later version.
   *
   * This program is distributed in the hope that 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.
   */
  
  #include <linux/hwmon.h>
  #include <linux/hwmon-sysfs.h>
  #include <linux/module.h>
  #include <linux/mutex.h>
  #include <linux/of.h>
  #include <linux/platform_device.h>
  #include <linux/pwm.h>
  #include <linux/sysfs.h>
b6bddec01   Lukasz Majewski   hwmon: (pwm-fan) ...
27
  #include <linux/thermal.h>
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
28
29
30
31
32
33
  
  #define MAX_PWM 255
  
  struct pwm_fan_ctx {
  	struct mutex lock;
  	struct pwm_device *pwm;
2e5219c77   Lukasz Majewski   hwmon: (pwm-fan) ...
34
35
36
37
  	unsigned int pwm_value;
  	unsigned int pwm_fan_state;
  	unsigned int pwm_fan_max_state;
  	unsigned int *pwm_fan_cooling_levels;
b6bddec01   Lukasz Majewski   hwmon: (pwm-fan) ...
38
  	struct thermal_cooling_device *cdev;
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
39
  };
cb85ca332   Lukasz Majewski   hwmon: (pwm-fan) ...
40
  static int  __set_pwm(struct pwm_fan_ctx *ctx, unsigned long pwm)
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
41
  {
2289711c9   Boris Brezillon   hwmon: pwm-fan: U...
42
  	struct pwm_args pargs;
cb85ca332   Lukasz Majewski   hwmon: (pwm-fan) ...
43
44
  	unsigned long duty;
  	int ret = 0;
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
45

2289711c9   Boris Brezillon   hwmon: pwm-fan: U...
46
  	pwm_get_args(ctx->pwm, &pargs);
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
47
  	mutex_lock(&ctx->lock);
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
48
  	if (ctx->pwm_value == pwm)
cb85ca332   Lukasz Majewski   hwmon: (pwm-fan) ...
49
  		goto exit_set_pwm_err;
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
50

2289711c9   Boris Brezillon   hwmon: pwm-fan: U...
51
52
  	duty = DIV_ROUND_UP(pwm * (pargs.period - 1), MAX_PWM);
  	ret = pwm_config(ctx->pwm, duty, pargs.period);
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
53
54
  	if (ret)
  		goto exit_set_pwm_err;
f354169e0   Anand Moon   hwmon: (pwm-fan) ...
55
56
  	if (pwm == 0)
  		pwm_disable(ctx->pwm);
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
57
58
59
60
61
  	if (ctx->pwm_value == 0) {
  		ret = pwm_enable(ctx->pwm);
  		if (ret)
  			goto exit_set_pwm_err;
  	}
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
62
  	ctx->pwm_value = pwm;
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
63
64
65
66
  exit_set_pwm_err:
  	mutex_unlock(&ctx->lock);
  	return ret;
  }
b6bddec01   Lukasz Majewski   hwmon: (pwm-fan) ...
67
68
69
70
71
72
73
74
75
76
  static void pwm_fan_update_state(struct pwm_fan_ctx *ctx, unsigned long pwm)
  {
  	int i;
  
  	for (i = 0; i < ctx->pwm_fan_max_state; ++i)
  		if (pwm < ctx->pwm_fan_cooling_levels[i + 1])
  			break;
  
  	ctx->pwm_fan_state = i;
  }
cb85ca332   Lukasz Majewski   hwmon: (pwm-fan) ...
77
78
79
80
81
82
83
84
85
86
87
88
89
  static ssize_t set_pwm(struct device *dev, struct device_attribute *attr,
  		       const char *buf, size_t count)
  {
  	struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
  	unsigned long pwm;
  	int ret;
  
  	if (kstrtoul(buf, 10, &pwm) || pwm > MAX_PWM)
  		return -EINVAL;
  
  	ret = __set_pwm(ctx, pwm);
  	if (ret)
  		return ret;
b6bddec01   Lukasz Majewski   hwmon: (pwm-fan) ...
90
  	pwm_fan_update_state(ctx, pwm);
cb85ca332   Lukasz Majewski   hwmon: (pwm-fan) ...
91
92
  	return count;
  }
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
  static ssize_t show_pwm(struct device *dev,
  			struct device_attribute *attr, char *buf)
  {
  	struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
  
  	return sprintf(buf, "%u
  ", ctx->pwm_value);
  }
  
  
  static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, show_pwm, set_pwm, 0);
  
  static struct attribute *pwm_fan_attrs[] = {
  	&sensor_dev_attr_pwm1.dev_attr.attr,
  	NULL,
  };
  
  ATTRIBUTE_GROUPS(pwm_fan);
b6bddec01   Lukasz Majewski   hwmon: (pwm-fan) ...
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
  /* thermal cooling device callbacks */
  static int pwm_fan_get_max_state(struct thermal_cooling_device *cdev,
  				 unsigned long *state)
  {
  	struct pwm_fan_ctx *ctx = cdev->devdata;
  
  	if (!ctx)
  		return -EINVAL;
  
  	*state = ctx->pwm_fan_max_state;
  
  	return 0;
  }
  
  static int pwm_fan_get_cur_state(struct thermal_cooling_device *cdev,
  				 unsigned long *state)
  {
  	struct pwm_fan_ctx *ctx = cdev->devdata;
  
  	if (!ctx)
  		return -EINVAL;
  
  	*state = ctx->pwm_fan_state;
  
  	return 0;
  }
  
  static int
  pwm_fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
  {
  	struct pwm_fan_ctx *ctx = cdev->devdata;
  	int ret;
  
  	if (!ctx || (state > ctx->pwm_fan_max_state))
  		return -EINVAL;
  
  	if (state == ctx->pwm_fan_state)
  		return 0;
  
  	ret = __set_pwm(ctx, ctx->pwm_fan_cooling_levels[state]);
  	if (ret) {
  		dev_err(&cdev->device, "Cannot set pwm!
  ");
  		return ret;
  	}
  
  	ctx->pwm_fan_state = state;
  
  	return ret;
  }
  
  static const 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,
  };
de52b049d   Guenter Roeck   hwmon: (pwm-fan) ...
167
168
  static int pwm_fan_of_get_cooling_data(struct device *dev,
  				       struct pwm_fan_ctx *ctx)
2e5219c77   Lukasz Majewski   hwmon: (pwm-fan) ...
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
  {
  	struct device_node *np = dev->of_node;
  	int num, i, ret;
  
  	if (!of_find_property(np, "cooling-levels", NULL))
  		return 0;
  
  	ret = of_property_count_u32_elems(np, "cooling-levels");
  	if (ret <= 0) {
  		dev_err(dev, "Wrong data!
  ");
  		return ret ? : -EINVAL;
  	}
  
  	num = ret;
  	ctx->pwm_fan_cooling_levels = devm_kzalloc(dev, num * sizeof(u32),
  						   GFP_KERNEL);
  	if (!ctx->pwm_fan_cooling_levels)
  		return -ENOMEM;
  
  	ret = of_property_read_u32_array(np, "cooling-levels",
  					 ctx->pwm_fan_cooling_levels, num);
  	if (ret) {
  		dev_err(dev, "Property 'cooling-levels' cannot be read!
  ");
  		return ret;
  	}
  
  	for (i = 0; i < num; i++) {
  		if (ctx->pwm_fan_cooling_levels[i] > MAX_PWM) {
  			dev_err(dev, "PWM fan state[%d]:%d > %d
  ", i,
  				ctx->pwm_fan_cooling_levels[i], MAX_PWM);
  			return -EINVAL;
  		}
  	}
  
  	ctx->pwm_fan_max_state = num - 1;
  
  	return 0;
  }
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
210
211
  static int pwm_fan_probe(struct platform_device *pdev)
  {
b6bddec01   Lukasz Majewski   hwmon: (pwm-fan) ...
212
  	struct thermal_cooling_device *cdev;
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
213
  	struct pwm_fan_ctx *ctx;
2289711c9   Boris Brezillon   hwmon: pwm-fan: U...
214
  	struct pwm_args pargs;
b6bddec01   Lukasz Majewski   hwmon: (pwm-fan) ...
215
  	struct device *hwmon;
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
  	int duty_cycle;
  	int ret;
  
  	ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
  	if (!ctx)
  		return -ENOMEM;
  
  	mutex_init(&ctx->lock);
  
  	ctx->pwm = devm_of_pwm_get(&pdev->dev, pdev->dev.of_node, NULL);
  	if (IS_ERR(ctx->pwm)) {
  		dev_err(&pdev->dev, "Could not get PWM
  ");
  		return PTR_ERR(ctx->pwm);
  	}
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
231
  	platform_set_drvdata(pdev, ctx);
2289711c9   Boris Brezillon   hwmon: pwm-fan: U...
232
233
234
235
236
  	/*
  	 * FIXME: pwm_apply_args() should be removed when switching to the
  	 * atomic PWM API.
  	 */
  	pwm_apply_args(ctx->pwm);
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
237
  	/* Set duty cycle to maximum allowed */
2289711c9   Boris Brezillon   hwmon: pwm-fan: U...
238
239
240
  	pwm_get_args(ctx->pwm, &pargs);
  
  	duty_cycle = pargs.period - 1;
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
241
  	ctx->pwm_value = MAX_PWM;
2289711c9   Boris Brezillon   hwmon: pwm-fan: U...
242
  	ret = pwm_config(ctx->pwm, duty_cycle, pargs.period);
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
  	if (ret) {
  		dev_err(&pdev->dev, "Failed to configure PWM
  ");
  		return ret;
  	}
  
  	/* Enbale PWM output */
  	ret = pwm_enable(ctx->pwm);
  	if (ret) {
  		dev_err(&pdev->dev, "Failed to enable PWM
  ");
  		return ret;
  	}
  
  	hwmon = devm_hwmon_device_register_with_groups(&pdev->dev, "pwmfan",
  						       ctx, pwm_fan_groups);
  	if (IS_ERR(hwmon)) {
  		dev_err(&pdev->dev, "Failed to register hwmon device
  ");
  		pwm_disable(ctx->pwm);
  		return PTR_ERR(hwmon);
  	}
2e5219c77   Lukasz Majewski   hwmon: (pwm-fan) ...
265
266
267
268
  
  	ret = pwm_fan_of_get_cooling_data(&pdev->dev, ctx);
  	if (ret)
  		return ret;
b6bddec01   Lukasz Majewski   hwmon: (pwm-fan) ...
269
270
271
272
273
274
275
276
277
278
279
280
281
282
  	ctx->pwm_fan_state = ctx->pwm_fan_max_state;
  	if (IS_ENABLED(CONFIG_THERMAL)) {
  		cdev = thermal_of_cooling_device_register(pdev->dev.of_node,
  							  "pwm-fan", ctx,
  							  &pwm_fan_cooling_ops);
  		if (IS_ERR(cdev)) {
  			dev_err(&pdev->dev,
  				"Failed to register pwm-fan as cooling device");
  			pwm_disable(ctx->pwm);
  			return PTR_ERR(cdev);
  		}
  		ctx->cdev = cdev;
  		thermal_cdev_update(cdev);
  	}
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
283
284
285
286
287
288
  	return 0;
  }
  
  static int pwm_fan_remove(struct platform_device *pdev)
  {
  	struct pwm_fan_ctx *ctx = platform_get_drvdata(pdev);
b6bddec01   Lukasz Majewski   hwmon: (pwm-fan) ...
289
  	thermal_cooling_device_unregister(ctx->cdev);
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
  	if (ctx->pwm_value)
  		pwm_disable(ctx->pwm);
  	return 0;
  }
  
  #ifdef CONFIG_PM_SLEEP
  static int pwm_fan_suspend(struct device *dev)
  {
  	struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
  
  	if (ctx->pwm_value)
  		pwm_disable(ctx->pwm);
  	return 0;
  }
  
  static int pwm_fan_resume(struct device *dev)
  {
  	struct pwm_fan_ctx *ctx = dev_get_drvdata(dev);
2289711c9   Boris Brezillon   hwmon: pwm-fan: U...
308
  	struct pwm_args pargs;
48b9d5b4f   Kamil Debski   hwmon: (pwm-fan) ...
309
310
  	unsigned long duty;
  	int ret;
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
311

48b9d5b4f   Kamil Debski   hwmon: (pwm-fan) ...
312
313
  	if (ctx->pwm_value == 0)
  		return 0;
2289711c9   Boris Brezillon   hwmon: pwm-fan: U...
314
315
316
  	pwm_get_args(ctx->pwm, &pargs);
  	duty = DIV_ROUND_UP(ctx->pwm_value * (pargs.period - 1), MAX_PWM);
  	ret = pwm_config(ctx->pwm, duty, pargs.period);
48b9d5b4f   Kamil Debski   hwmon: (pwm-fan) ...
317
318
319
  	if (ret)
  		return ret;
  	return pwm_enable(ctx->pwm);
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
320
321
322
323
  }
  #endif
  
  static SIMPLE_DEV_PM_OPS(pwm_fan_pm, pwm_fan_suspend, pwm_fan_resume);
d720acace   Fabian Frederick   hwmon: (pwm-fan, ...
324
  static const struct of_device_id of_pwm_fan_match[] = {
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
325
326
327
  	{ .compatible = "pwm-fan", },
  	{},
  };
f491e70cc   Luis de Bethencourt   hwmon: (pwm-fan) ...
328
  MODULE_DEVICE_TABLE(of, of_pwm_fan_match);
d82d57767   Kamil Debski   hwmon: Add pwm-fa...
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
  
  static struct platform_driver pwm_fan_driver = {
  	.probe		= pwm_fan_probe,
  	.remove		= pwm_fan_remove,
  	.driver	= {
  		.name		= "pwm-fan",
  		.pm		= &pwm_fan_pm,
  		.of_match_table	= of_pwm_fan_match,
  	},
  };
  
  module_platform_driver(pwm_fan_driver);
  
  MODULE_AUTHOR("Kamil Debski <k.debski@samsung.com>");
  MODULE_ALIAS("platform:pwm-fan");
  MODULE_DESCRIPTION("PWM FAN driver");
  MODULE_LICENSE("GPL");