Commit | Line | Data |
---|---|---|
d82d5776 KD |
1 | /* |
2 | * pwm-fan.c - Hwmon driver for fans connected to PWM lines. | |
3 | * | |
4 | * Copyright (c) 2014 Samsung Electronics Co., Ltd. | |
5 | * | |
6 | * Author: Kamil Debski <k.debski@samsung.com> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | */ | |
18 | ||
19 | #include <linux/hwmon.h> | |
20 | #include <linux/hwmon-sysfs.h> | |
21 | #include <linux/module.h> | |
22 | #include <linux/mutex.h> | |
23 | #include <linux/of.h> | |
24 | #include <linux/platform_device.h> | |
25 | #include <linux/pwm.h> | |
b57e1d42 | 26 | #include <linux/regulator/consumer.h> |
d82d5776 | 27 | #include <linux/sysfs.h> |
b6bddec0 | 28 | #include <linux/thermal.h> |
d82d5776 KD |
29 | |
30 | #define MAX_PWM 255 | |
31 | ||
32 | struct pwm_fan_ctx { | |
33 | struct mutex lock; | |
34 | struct pwm_device *pwm; | |
b57e1d42 | 35 | struct regulator *reg_en; |
2e5219c7 LM |
36 | unsigned int pwm_value; |
37 | unsigned int pwm_fan_state; | |
38 | unsigned int pwm_fan_max_state; | |
39 | unsigned int *pwm_fan_cooling_levels; | |
b6bddec0 | 40 | struct thermal_cooling_device *cdev; |
d82d5776 KD |
41 | }; |
42 | ||
cb85ca33 | 43 | static int __set_pwm(struct pwm_fan_ctx *ctx, unsigned long pwm) |
d82d5776 | 44 | { |
677252a1 | 45 | unsigned long period; |
cb85ca33 | 46 | int ret = 0; |
677252a1 | 47 | struct pwm_state state = { }; |
2289711c | 48 | |
d82d5776 | 49 | mutex_lock(&ctx->lock); |
d82d5776 | 50 | if (ctx->pwm_value == pwm) |
cb85ca33 | 51 | goto exit_set_pwm_err; |
d82d5776 | 52 | |
677252a1 BZ |
53 | pwm_init_state(ctx->pwm, &state); |
54 | period = ctx->pwm->args.period; | |
55 | state.duty_cycle = DIV_ROUND_UP(pwm * (period - 1), MAX_PWM); | |
56 | state.enabled = pwm ? true : false; | |
f354169e | 57 | |
677252a1 BZ |
58 | ret = pwm_apply_state(ctx->pwm, &state); |
59 | if (!ret) | |
60 | ctx->pwm_value = pwm; | |
d82d5776 KD |
61 | exit_set_pwm_err: |
62 | mutex_unlock(&ctx->lock); | |
63 | return ret; | |
64 | } | |
65 | ||
b6bddec0 LM |
66 | static void pwm_fan_update_state(struct pwm_fan_ctx *ctx, unsigned long pwm) |
67 | { | |
68 | int i; | |
69 | ||
70 | for (i = 0; i < ctx->pwm_fan_max_state; ++i) | |
71 | if (pwm < ctx->pwm_fan_cooling_levels[i + 1]) | |
72 | break; | |
73 | ||
74 | ctx->pwm_fan_state = i; | |
75 | } | |
76 | ||
cb1d8534 GR |
77 | static ssize_t pwm_store(struct device *dev, struct device_attribute *attr, |
78 | const char *buf, size_t count) | |
cb85ca33 LM |
79 | { |
80 | struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); | |
81 | unsigned long pwm; | |
82 | int ret; | |
83 | ||
84 | if (kstrtoul(buf, 10, &pwm) || pwm > MAX_PWM) | |
85 | return -EINVAL; | |
86 | ||
87 | ret = __set_pwm(ctx, pwm); | |
88 | if (ret) | |
89 | return ret; | |
90 | ||
b6bddec0 | 91 | pwm_fan_update_state(ctx, pwm); |
cb85ca33 LM |
92 | return count; |
93 | } | |
94 | ||
cb1d8534 GR |
95 | static ssize_t pwm_show(struct device *dev, struct device_attribute *attr, |
96 | char *buf) | |
d82d5776 KD |
97 | { |
98 | struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); | |
99 | ||
100 | return sprintf(buf, "%u\n", ctx->pwm_value); | |
101 | } | |
102 | ||
103 | ||
cb1d8534 | 104 | static SENSOR_DEVICE_ATTR_RW(pwm1, pwm, 0); |
d82d5776 KD |
105 | |
106 | static struct attribute *pwm_fan_attrs[] = { | |
107 | &sensor_dev_attr_pwm1.dev_attr.attr, | |
108 | NULL, | |
109 | }; | |
110 | ||
111 | ATTRIBUTE_GROUPS(pwm_fan); | |
112 | ||
b6bddec0 LM |
113 | /* thermal cooling device callbacks */ |
114 | static int pwm_fan_get_max_state(struct thermal_cooling_device *cdev, | |
115 | unsigned long *state) | |
116 | { | |
117 | struct pwm_fan_ctx *ctx = cdev->devdata; | |
118 | ||
119 | if (!ctx) | |
120 | return -EINVAL; | |
121 | ||
122 | *state = ctx->pwm_fan_max_state; | |
123 | ||
124 | return 0; | |
125 | } | |
126 | ||
127 | static int pwm_fan_get_cur_state(struct thermal_cooling_device *cdev, | |
128 | unsigned long *state) | |
129 | { | |
130 | struct pwm_fan_ctx *ctx = cdev->devdata; | |
131 | ||
132 | if (!ctx) | |
133 | return -EINVAL; | |
134 | ||
135 | *state = ctx->pwm_fan_state; | |
136 | ||
137 | return 0; | |
138 | } | |
139 | ||
140 | static int | |
141 | pwm_fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) | |
142 | { | |
143 | struct pwm_fan_ctx *ctx = cdev->devdata; | |
144 | int ret; | |
145 | ||
146 | if (!ctx || (state > ctx->pwm_fan_max_state)) | |
147 | return -EINVAL; | |
148 | ||
149 | if (state == ctx->pwm_fan_state) | |
150 | return 0; | |
151 | ||
152 | ret = __set_pwm(ctx, ctx->pwm_fan_cooling_levels[state]); | |
153 | if (ret) { | |
154 | dev_err(&cdev->device, "Cannot set pwm!\n"); | |
155 | return ret; | |
156 | } | |
157 | ||
158 | ctx->pwm_fan_state = state; | |
159 | ||
160 | return ret; | |
161 | } | |
162 | ||
163 | static const struct thermal_cooling_device_ops pwm_fan_cooling_ops = { | |
164 | .get_max_state = pwm_fan_get_max_state, | |
165 | .get_cur_state = pwm_fan_get_cur_state, | |
166 | .set_cur_state = pwm_fan_set_cur_state, | |
167 | }; | |
168 | ||
de52b049 GR |
169 | static int pwm_fan_of_get_cooling_data(struct device *dev, |
170 | struct pwm_fan_ctx *ctx) | |
2e5219c7 LM |
171 | { |
172 | struct device_node *np = dev->of_node; | |
173 | int num, i, ret; | |
174 | ||
175 | if (!of_find_property(np, "cooling-levels", NULL)) | |
176 | return 0; | |
177 | ||
178 | ret = of_property_count_u32_elems(np, "cooling-levels"); | |
179 | if (ret <= 0) { | |
180 | dev_err(dev, "Wrong data!\n"); | |
181 | return ret ? : -EINVAL; | |
182 | } | |
183 | ||
184 | num = ret; | |
a86854d0 | 185 | ctx->pwm_fan_cooling_levels = devm_kcalloc(dev, num, sizeof(u32), |
2e5219c7 LM |
186 | GFP_KERNEL); |
187 | if (!ctx->pwm_fan_cooling_levels) | |
188 | return -ENOMEM; | |
189 | ||
190 | ret = of_property_read_u32_array(np, "cooling-levels", | |
191 | ctx->pwm_fan_cooling_levels, num); | |
192 | if (ret) { | |
193 | dev_err(dev, "Property 'cooling-levels' cannot be read!\n"); | |
194 | return ret; | |
195 | } | |
196 | ||
197 | for (i = 0; i < num; i++) { | |
198 | if (ctx->pwm_fan_cooling_levels[i] > MAX_PWM) { | |
199 | dev_err(dev, "PWM fan state[%d]:%d > %d\n", i, | |
200 | ctx->pwm_fan_cooling_levels[i], MAX_PWM); | |
201 | return -EINVAL; | |
202 | } | |
203 | } | |
204 | ||
205 | ctx->pwm_fan_max_state = num - 1; | |
206 | ||
207 | return 0; | |
208 | } | |
209 | ||
d82d5776 KD |
210 | static int pwm_fan_probe(struct platform_device *pdev) |
211 | { | |
b6bddec0 | 212 | struct thermal_cooling_device *cdev; |
d82d5776 | 213 | struct pwm_fan_ctx *ctx; |
b6bddec0 | 214 | struct device *hwmon; |
d82d5776 | 215 | int ret; |
677252a1 | 216 | struct pwm_state state = { }; |
d82d5776 KD |
217 | |
218 | ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); | |
219 | if (!ctx) | |
220 | return -ENOMEM; | |
221 | ||
222 | mutex_init(&ctx->lock); | |
223 | ||
224 | ctx->pwm = devm_of_pwm_get(&pdev->dev, pdev->dev.of_node, NULL); | |
225 | if (IS_ERR(ctx->pwm)) { | |
9f67f758 TR |
226 | ret = PTR_ERR(ctx->pwm); |
227 | ||
228 | if (ret != -EPROBE_DEFER) | |
229 | dev_err(&pdev->dev, "Could not get PWM: %d\n", ret); | |
230 | ||
231 | return ret; | |
d82d5776 KD |
232 | } |
233 | ||
d82d5776 KD |
234 | platform_set_drvdata(pdev, ctx); |
235 | ||
b57e1d42 SW |
236 | ctx->reg_en = devm_regulator_get_optional(&pdev->dev, "fan"); |
237 | if (IS_ERR(ctx->reg_en)) { | |
238 | if (PTR_ERR(ctx->reg_en) != -ENODEV) | |
239 | return PTR_ERR(ctx->reg_en); | |
240 | ||
241 | ctx->reg_en = NULL; | |
242 | } else { | |
243 | ret = regulator_enable(ctx->reg_en); | |
244 | if (ret) { | |
245 | dev_err(&pdev->dev, | |
246 | "Failed to enable fan supply: %d\n", ret); | |
247 | return ret; | |
248 | } | |
249 | } | |
250 | ||
d82d5776 KD |
251 | ctx->pwm_value = MAX_PWM; |
252 | ||
677252a1 BZ |
253 | /* Set duty cycle to maximum allowed and enable PWM output */ |
254 | pwm_init_state(ctx->pwm, &state); | |
255 | state.duty_cycle = ctx->pwm->args.period - 1; | |
256 | state.enabled = true; | |
d82d5776 | 257 | |
677252a1 | 258 | ret = pwm_apply_state(ctx->pwm, &state); |
d82d5776 | 259 | if (ret) { |
677252a1 | 260 | dev_err(&pdev->dev, "Failed to configure PWM\n"); |
b57e1d42 | 261 | goto err_reg_disable; |
d82d5776 KD |
262 | } |
263 | ||
264 | hwmon = devm_hwmon_device_register_with_groups(&pdev->dev, "pwmfan", | |
265 | ctx, pwm_fan_groups); | |
266 | if (IS_ERR(hwmon)) { | |
267 | dev_err(&pdev->dev, "Failed to register hwmon device\n"); | |
677252a1 BZ |
268 | ret = PTR_ERR(hwmon); |
269 | goto err_pwm_disable; | |
d82d5776 | 270 | } |
2e5219c7 LM |
271 | |
272 | ret = pwm_fan_of_get_cooling_data(&pdev->dev, ctx); | |
273 | if (ret) | |
274 | return ret; | |
275 | ||
b6bddec0 LM |
276 | ctx->pwm_fan_state = ctx->pwm_fan_max_state; |
277 | if (IS_ENABLED(CONFIG_THERMAL)) { | |
278 | cdev = thermal_of_cooling_device_register(pdev->dev.of_node, | |
279 | "pwm-fan", ctx, | |
280 | &pwm_fan_cooling_ops); | |
281 | if (IS_ERR(cdev)) { | |
282 | dev_err(&pdev->dev, | |
283 | "Failed to register pwm-fan as cooling device"); | |
677252a1 BZ |
284 | ret = PTR_ERR(cdev); |
285 | goto err_pwm_disable; | |
b6bddec0 LM |
286 | } |
287 | ctx->cdev = cdev; | |
288 | thermal_cdev_update(cdev); | |
289 | } | |
290 | ||
d82d5776 | 291 | return 0; |
677252a1 BZ |
292 | |
293 | err_pwm_disable: | |
294 | state.enabled = false; | |
295 | pwm_apply_state(ctx->pwm, &state); | |
296 | ||
b57e1d42 SW |
297 | err_reg_disable: |
298 | if (ctx->reg_en) | |
299 | regulator_disable(ctx->reg_en); | |
300 | ||
677252a1 | 301 | return ret; |
d82d5776 KD |
302 | } |
303 | ||
304 | static int pwm_fan_remove(struct platform_device *pdev) | |
305 | { | |
306 | struct pwm_fan_ctx *ctx = platform_get_drvdata(pdev); | |
307 | ||
b6bddec0 | 308 | thermal_cooling_device_unregister(ctx->cdev); |
d82d5776 KD |
309 | if (ctx->pwm_value) |
310 | pwm_disable(ctx->pwm); | |
b57e1d42 SW |
311 | |
312 | if (ctx->reg_en) | |
313 | regulator_disable(ctx->reg_en); | |
314 | ||
d82d5776 KD |
315 | return 0; |
316 | } | |
317 | ||
318 | #ifdef CONFIG_PM_SLEEP | |
319 | static int pwm_fan_suspend(struct device *dev) | |
320 | { | |
321 | struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); | |
95dcd64b TR |
322 | struct pwm_args args; |
323 | int ret; | |
324 | ||
325 | pwm_get_args(ctx->pwm, &args); | |
326 | ||
327 | if (ctx->pwm_value) { | |
328 | ret = pwm_config(ctx->pwm, 0, args.period); | |
329 | if (ret < 0) | |
330 | return ret; | |
d82d5776 | 331 | |
d82d5776 | 332 | pwm_disable(ctx->pwm); |
95dcd64b TR |
333 | } |
334 | ||
b57e1d42 SW |
335 | if (ctx->reg_en) { |
336 | ret = regulator_disable(ctx->reg_en); | |
337 | if (ret) { | |
338 | dev_err(dev, "Failed to disable fan supply: %d\n", ret); | |
339 | return ret; | |
340 | } | |
341 | } | |
342 | ||
d82d5776 KD |
343 | return 0; |
344 | } | |
345 | ||
346 | static int pwm_fan_resume(struct device *dev) | |
347 | { | |
348 | struct pwm_fan_ctx *ctx = dev_get_drvdata(dev); | |
2289711c | 349 | struct pwm_args pargs; |
48b9d5b4 KD |
350 | unsigned long duty; |
351 | int ret; | |
d82d5776 | 352 | |
b57e1d42 SW |
353 | if (ctx->reg_en) { |
354 | ret = regulator_enable(ctx->reg_en); | |
355 | if (ret) { | |
356 | dev_err(dev, "Failed to enable fan supply: %d\n", ret); | |
357 | return ret; | |
358 | } | |
359 | } | |
360 | ||
48b9d5b4 KD |
361 | if (ctx->pwm_value == 0) |
362 | return 0; | |
363 | ||
2289711c BB |
364 | pwm_get_args(ctx->pwm, &pargs); |
365 | duty = DIV_ROUND_UP(ctx->pwm_value * (pargs.period - 1), MAX_PWM); | |
366 | ret = pwm_config(ctx->pwm, duty, pargs.period); | |
48b9d5b4 KD |
367 | if (ret) |
368 | return ret; | |
369 | return pwm_enable(ctx->pwm); | |
d82d5776 KD |
370 | } |
371 | #endif | |
372 | ||
373 | static SIMPLE_DEV_PM_OPS(pwm_fan_pm, pwm_fan_suspend, pwm_fan_resume); | |
374 | ||
d720acac | 375 | static const struct of_device_id of_pwm_fan_match[] = { |
d82d5776 KD |
376 | { .compatible = "pwm-fan", }, |
377 | {}, | |
378 | }; | |
f491e70c | 379 | MODULE_DEVICE_TABLE(of, of_pwm_fan_match); |
d82d5776 KD |
380 | |
381 | static struct platform_driver pwm_fan_driver = { | |
382 | .probe = pwm_fan_probe, | |
383 | .remove = pwm_fan_remove, | |
384 | .driver = { | |
385 | .name = "pwm-fan", | |
386 | .pm = &pwm_fan_pm, | |
387 | .of_match_table = of_pwm_fan_match, | |
388 | }, | |
389 | }; | |
390 | ||
391 | module_platform_driver(pwm_fan_driver); | |
392 | ||
393 | MODULE_AUTHOR("Kamil Debski <k.debski@samsung.com>"); | |
394 | MODULE_ALIAS("platform:pwm-fan"); | |
395 | MODULE_DESCRIPTION("PWM FAN driver"); | |
396 | MODULE_LICENSE("GPL"); |