Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

pwm: Add sysfs interface

Add a simple sysfs interface to the generic PWM framework.

/sys/class/pwm/
`-- pwmchipN/ for each PWM chip
|-- export (w/o) ask the kernel to export a PWM channel
|-- npwm (r/o) number of PWM channels in this PWM chip
|-- pwmX/ for each exported PWM channel
| |-- duty_cycle (r/w) duty cycle (in nanoseconds)
| |-- enable (r/w) enable/disable PWM
| |-- period (r/w) period (in nanoseconds)
| `-- polarity (r/w) polarity of PWM (normal/inversed)
`-- unexport (w/o) return a PWM channel to the kernel

Based on work by Lars Poeschel.

Signed-off-by: H Hartley Sweeten <hsweeten@visionengravers.com>
Cc: Thierry Reding <thierry.reding@gmail.com>
Cc: Lars Poeschel <poeschel@lemonage.de>
Cc: Ryan Mallon <rmallon@gmail.com>
Cc: Rob Landley <rob@landley.net>
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>

authored by

H Hartley Sweeten and committed by
Thierry Reding
76abbdde 3dd0a909

+524 -3
+79
Documentation/ABI/testing/sysfs-class-pwm
··· 1 + What: /sys/class/pwm/ 2 + Date: May 2013 3 + KernelVersion: 3.11 4 + Contact: H Hartley Sweeten <hsweeten@visionengravers.com> 5 + Description: 6 + The pwm/ class sub-directory belongs to the Generic PWM 7 + Framework and provides a sysfs interface for using PWM 8 + channels. 9 + 10 + What: /sys/class/pwm/pwmchipN/ 11 + Date: May 2013 12 + KernelVersion: 3.11 13 + Contact: H Hartley Sweeten <hsweeten@visionengravers.com> 14 + Description: 15 + A /sys/class/pwm/pwmchipN directory is created for each 16 + probed PWM controller/chip where N is the base of the 17 + PWM chip. 18 + 19 + What: /sys/class/pwm/pwmchipN/npwm 20 + Date: May 2013 21 + KernelVersion: 3.11 22 + Contact: H Hartley Sweeten <hsweeten@visionengravers.com> 23 + Description: 24 + The number of PWM channels supported by the PWM chip. 25 + 26 + What: /sys/class/pwm/pwmchipN/export 27 + Date: May 2013 28 + KernelVersion: 3.11 29 + Contact: H Hartley Sweeten <hsweeten@visionengravers.com> 30 + Description: 31 + Exports a PWM channel from the PWM chip for sysfs control. 32 + Value is between 0 and /sys/class/pwm/pwmchipN/npwm - 1. 33 + 34 + What: /sys/class/pwm/pwmchipN/unexport 35 + Date: May 2013 36 + KernelVersion: 3.11 37 + Contact: H Hartley Sweeten <hsweeten@visionengravers.com> 38 + Description: 39 + Unexports a PWM channel. 40 + 41 + What: /sys/class/pwm/pwmchipN/pwmX 42 + Date: May 2013 43 + KernelVersion: 3.11 44 + Contact: H Hartley Sweeten <hsweeten@visionengravers.com> 45 + Description: 46 + A /sys/class/pwm/pwmchipN/pwmX directory is created for 47 + each exported PWM channel where X is the exported PWM 48 + channel number. 49 + 50 + What: /sys/class/pwm/pwmchipN/pwmX/period 51 + Date: May 2013 52 + KernelVersion: 3.11 53 + Contact: H Hartley Sweeten <hsweeten@visionengravers.com> 54 + Description: 55 + Sets the PWM signal period in nanoseconds. 56 + 57 + What: /sys/class/pwm/pwmchipN/pwmX/duty_cycle 58 + Date: May 2013 59 + KernelVersion: 3.11 60 + Contact: H Hartley Sweeten <hsweeten@visionengravers.com> 61 + Description: 62 + Sets the PWM signal duty cycle in nanoseconds. 63 + 64 + What: /sys/class/pwm/pwmchipN/pwmX/polarity 65 + Date: May 2013 66 + KernelVersion: 3.11 67 + Contact: H Hartley Sweeten <hsweeten@visionengravers.com> 68 + Description: 69 + Sets the output polarity of the PWM signal to "normal" or 70 + "inversed". 71 + 72 + What: /sys/class/pwm/pwmchipN/pwmX/enable 73 + Date: May 2013 74 + KernelVersion: 3.11 75 + Contact: H Hartley Sweeten <hsweeten@visionengravers.com> 76 + Description: 77 + Enable/disable the PWM signal. 78 + 0 is disabled 79 + 1 is enabled
+37
Documentation/pwm.txt
··· 45 45 46 46 To start/stop toggling the PWM output use pwm_enable()/pwm_disable(). 47 47 48 + Using PWMs with the sysfs interface 49 + ----------------------------------- 50 + 51 + If CONFIG_SYSFS is enabled in your kernel configuration a simple sysfs 52 + interface is provided to use the PWMs from userspace. It is exposed at 53 + /sys/class/pwm/. Each probed PWM controller/chip will be exported as 54 + pwmchipN, where N is the base of the PWM chip. Inside the directory you 55 + will find: 56 + 57 + npwm - The number of PWM channels this chip supports (read-only). 58 + 59 + export - Exports a PWM channel for use with sysfs (write-only). 60 + 61 + unexport - Unexports a PWM channel from sysfs (write-only). 62 + 63 + The PWM channels are numbered using a per-chip index from 0 to npwm-1. 64 + 65 + When a PWM channel is exported a pwmX directory will be created in the 66 + pwmchipN directory it is associated with, where X is the number of the 67 + channel that was exported. The following properties will then be available: 68 + 69 + period - The total period of the PWM signal (read/write). 70 + Value is in nanoseconds and is the sum of the active and inactive 71 + time of the PWM. 72 + 73 + duty_cycle - The active time of the PWM signal (read/write). 74 + Value is in nanoseconds and must be less than the period. 75 + 76 + polarity - Changes the polarity of the PWM signal (read/write). 77 + Writes to this property only work if the PWM chip supports changing 78 + the polarity. The polarity can only be changed if the PWM is not 79 + enabled. Value is the string "normal" or "inversed". 80 + 81 + enable - Enable/disable the PWM signal (read/write). 82 + 0 - disabled 83 + 1 - enabled 84 + 48 85 Implementing a PWM driver 49 86 ------------------------- 50 87
+4
drivers/pwm/Kconfig
··· 28 28 29 29 if PWM 30 30 31 + config PWM_SYSFS 32 + bool 33 + default y if SYSFS 34 + 31 35 config PWM_AB8500 32 36 tristate "AB8500 PWM support" 33 37 depends on AB8500_CORE && ARCH_U8500
+1
drivers/pwm/Makefile
··· 1 1 obj-$(CONFIG_PWM) += core.o 2 + obj-$(CONFIG_PWM_SYSFS) += sysfs.o 2 3 obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o 3 4 obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o 4 5 obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o
+23 -2
drivers/pwm/core.c
··· 274 274 if (IS_ENABLED(CONFIG_OF)) 275 275 of_pwmchip_add(chip); 276 276 277 + pwmchip_sysfs_export(chip); 278 + 277 279 out: 278 280 mutex_unlock(&pwm_lock); 279 281 return ret; ··· 311 309 of_pwmchip_remove(chip); 312 310 313 311 free_pwms(chip); 312 + 313 + pwmchip_sysfs_unexport(chip); 314 314 315 315 out: 316 316 mutex_unlock(&pwm_lock); ··· 406 402 */ 407 403 int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) 408 404 { 405 + int err; 406 + 409 407 if (!pwm || duty_ns < 0 || period_ns <= 0 || duty_ns > period_ns) 410 408 return -EINVAL; 411 409 412 - return pwm->chip->ops->config(pwm->chip, pwm, duty_ns, period_ns); 410 + err = pwm->chip->ops->config(pwm->chip, pwm, duty_ns, period_ns); 411 + if (err) 412 + return err; 413 + 414 + pwm->duty_cycle = duty_ns; 415 + pwm->period = period_ns; 416 + 417 + return 0; 413 418 } 414 419 EXPORT_SYMBOL_GPL(pwm_config); 415 420 ··· 431 418 */ 432 419 int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity) 433 420 { 421 + int err; 422 + 434 423 if (!pwm || !pwm->chip->ops) 435 424 return -EINVAL; 436 425 ··· 442 427 if (test_bit(PWMF_ENABLED, &pwm->flags)) 443 428 return -EBUSY; 444 429 445 - return pwm->chip->ops->set_polarity(pwm->chip, pwm, polarity); 430 + err = pwm->chip->ops->set_polarity(pwm->chip, pwm, polarity); 431 + if (err) 432 + return err; 433 + 434 + pwm->polarity = polarity; 435 + 436 + return 0; 446 437 } 447 438 EXPORT_SYMBOL_GPL(pwm_set_polarity); 448 439
+352
drivers/pwm/sysfs.c
··· 1 + /* 2 + * A simple sysfs interface for the generic PWM framework 3 + * 4 + * Copyright (C) 2013 H Hartley Sweeten <hsweeten@visionengravers.com> 5 + * 6 + * Based on previous work by Lars Poeschel <poeschel@lemonage.de> 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, or (at your option) 11 + * 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/device.h> 20 + #include <linux/mutex.h> 21 + #include <linux/err.h> 22 + #include <linux/slab.h> 23 + #include <linux/kdev_t.h> 24 + #include <linux/pwm.h> 25 + 26 + struct pwm_export { 27 + struct device child; 28 + struct pwm_device *pwm; 29 + }; 30 + 31 + static struct pwm_export *child_to_pwm_export(struct device *child) 32 + { 33 + return container_of(child, struct pwm_export, child); 34 + } 35 + 36 + static struct pwm_device *child_to_pwm_device(struct device *child) 37 + { 38 + struct pwm_export *export = child_to_pwm_export(child); 39 + 40 + return export->pwm; 41 + } 42 + 43 + static ssize_t pwm_period_show(struct device *child, 44 + struct device_attribute *attr, 45 + char *buf) 46 + { 47 + const struct pwm_device *pwm = child_to_pwm_device(child); 48 + 49 + return sprintf(buf, "%u\n", pwm->period); 50 + } 51 + 52 + static ssize_t pwm_period_store(struct device *child, 53 + struct device_attribute *attr, 54 + const char *buf, size_t size) 55 + { 56 + struct pwm_device *pwm = child_to_pwm_device(child); 57 + unsigned int val; 58 + int ret; 59 + 60 + ret = kstrtouint(buf, 0, &val); 61 + if (ret) 62 + return ret; 63 + 64 + ret = pwm_config(pwm, pwm->duty_cycle, val); 65 + 66 + return ret ? : size; 67 + } 68 + 69 + static ssize_t pwm_duty_cycle_show(struct device *child, 70 + struct device_attribute *attr, 71 + char *buf) 72 + { 73 + const struct pwm_device *pwm = child_to_pwm_device(child); 74 + 75 + return sprintf(buf, "%u\n", pwm->duty_cycle); 76 + } 77 + 78 + static ssize_t pwm_duty_cycle_store(struct device *child, 79 + struct device_attribute *attr, 80 + const char *buf, size_t size) 81 + { 82 + struct pwm_device *pwm = child_to_pwm_device(child); 83 + unsigned int val; 84 + int ret; 85 + 86 + ret = kstrtouint(buf, 0, &val); 87 + if (ret) 88 + return ret; 89 + 90 + ret = pwm_config(pwm, val, pwm->period); 91 + 92 + return ret ? : size; 93 + } 94 + 95 + static ssize_t pwm_enable_show(struct device *child, 96 + struct device_attribute *attr, 97 + char *buf) 98 + { 99 + const struct pwm_device *pwm = child_to_pwm_device(child); 100 + int enabled = test_bit(PWMF_ENABLED, &pwm->flags); 101 + 102 + return sprintf(buf, "%d\n", enabled); 103 + } 104 + 105 + static ssize_t pwm_enable_store(struct device *child, 106 + struct device_attribute *attr, 107 + const char *buf, size_t size) 108 + { 109 + struct pwm_device *pwm = child_to_pwm_device(child); 110 + int val, ret; 111 + 112 + ret = kstrtoint(buf, 0, &val); 113 + if (ret) 114 + return ret; 115 + 116 + switch (val) { 117 + case 0: 118 + pwm_disable(pwm); 119 + break; 120 + case 1: 121 + ret = pwm_enable(pwm); 122 + break; 123 + default: 124 + ret = -EINVAL; 125 + break; 126 + } 127 + 128 + return ret ? : size; 129 + } 130 + 131 + static ssize_t pwm_polarity_show(struct device *child, 132 + struct device_attribute *attr, 133 + char *buf) 134 + { 135 + const struct pwm_device *pwm = child_to_pwm_device(child); 136 + 137 + return sprintf(buf, "%s\n", pwm->polarity ? "inversed" : "normal"); 138 + } 139 + 140 + static ssize_t pwm_polarity_store(struct device *child, 141 + struct device_attribute *attr, 142 + const char *buf, size_t size) 143 + { 144 + struct pwm_device *pwm = child_to_pwm_device(child); 145 + enum pwm_polarity polarity; 146 + int ret; 147 + 148 + if (sysfs_streq(buf, "normal")) 149 + polarity = PWM_POLARITY_NORMAL; 150 + else if (sysfs_streq(buf, "inversed")) 151 + polarity = PWM_POLARITY_INVERSED; 152 + else 153 + return -EINVAL; 154 + 155 + ret = pwm_set_polarity(pwm, polarity); 156 + 157 + return ret ? : size; 158 + } 159 + 160 + static DEVICE_ATTR(period, 0644, pwm_period_show, pwm_period_store); 161 + static DEVICE_ATTR(duty_cycle, 0644, pwm_duty_cycle_show, pwm_duty_cycle_store); 162 + static DEVICE_ATTR(enable, 0644, pwm_enable_show, pwm_enable_store); 163 + static DEVICE_ATTR(polarity, 0644, pwm_polarity_show, pwm_polarity_store); 164 + 165 + static struct attribute *pwm_attrs[] = { 166 + &dev_attr_period.attr, 167 + &dev_attr_duty_cycle.attr, 168 + &dev_attr_enable.attr, 169 + &dev_attr_polarity.attr, 170 + NULL 171 + }; 172 + 173 + static const struct attribute_group pwm_attr_group = { 174 + .attrs = pwm_attrs, 175 + }; 176 + 177 + static const struct attribute_group *pwm_attr_groups[] = { 178 + &pwm_attr_group, 179 + NULL, 180 + }; 181 + 182 + static void pwm_export_release(struct device *child) 183 + { 184 + struct pwm_export *export = child_to_pwm_export(child); 185 + 186 + kfree(export); 187 + } 188 + 189 + static int pwm_export_child(struct device *parent, struct pwm_device *pwm) 190 + { 191 + struct pwm_export *export; 192 + int ret; 193 + 194 + if (test_and_set_bit(PWMF_EXPORTED, &pwm->flags)) 195 + return -EBUSY; 196 + 197 + export = kzalloc(sizeof(*export), GFP_KERNEL); 198 + if (!export) { 199 + clear_bit(PWMF_EXPORTED, &pwm->flags); 200 + return -ENOMEM; 201 + } 202 + 203 + export->pwm = pwm; 204 + 205 + export->child.release = pwm_export_release; 206 + export->child.parent = parent; 207 + export->child.devt = MKDEV(0, 0); 208 + export->child.groups = pwm_attr_groups; 209 + dev_set_name(&export->child, "pwm%u", pwm->hwpwm); 210 + 211 + ret = device_register(&export->child); 212 + if (ret) { 213 + clear_bit(PWMF_EXPORTED, &pwm->flags); 214 + kfree(export); 215 + return ret; 216 + } 217 + 218 + return 0; 219 + } 220 + 221 + static int pwm_unexport_match(struct device *child, void *data) 222 + { 223 + return child_to_pwm_device(child) == data; 224 + } 225 + 226 + static int pwm_unexport_child(struct device *parent, struct pwm_device *pwm) 227 + { 228 + struct device *child; 229 + 230 + if (!test_and_clear_bit(PWMF_EXPORTED, &pwm->flags)) 231 + return -ENODEV; 232 + 233 + child = device_find_child(parent, pwm, pwm_unexport_match); 234 + if (!child) 235 + return -ENODEV; 236 + 237 + /* for device_find_child() */ 238 + put_device(child); 239 + device_unregister(child); 240 + pwm_put(pwm); 241 + 242 + return 0; 243 + } 244 + 245 + static ssize_t pwm_export_store(struct device *parent, 246 + struct device_attribute *attr, 247 + const char *buf, size_t len) 248 + { 249 + struct pwm_chip *chip = dev_get_drvdata(parent); 250 + struct pwm_device *pwm; 251 + unsigned int hwpwm; 252 + int ret; 253 + 254 + ret = kstrtouint(buf, 0, &hwpwm); 255 + if (ret < 0) 256 + return ret; 257 + 258 + if (hwpwm >= chip->npwm) 259 + return -ENODEV; 260 + 261 + pwm = pwm_request_from_chip(chip, hwpwm, "sysfs"); 262 + if (IS_ERR(pwm)) 263 + return PTR_ERR(pwm); 264 + 265 + ret = pwm_export_child(parent, pwm); 266 + if (ret < 0) 267 + pwm_put(pwm); 268 + 269 + return ret ? : len; 270 + } 271 + 272 + static ssize_t pwm_unexport_store(struct device *parent, 273 + struct device_attribute *attr, 274 + const char *buf, size_t len) 275 + { 276 + struct pwm_chip *chip = dev_get_drvdata(parent); 277 + unsigned int hwpwm; 278 + int ret; 279 + 280 + ret = kstrtouint(buf, 0, &hwpwm); 281 + if (ret < 0) 282 + return ret; 283 + 284 + if (hwpwm >= chip->npwm) 285 + return -ENODEV; 286 + 287 + ret = pwm_unexport_child(parent, &chip->pwms[hwpwm]); 288 + 289 + return ret ? : len; 290 + } 291 + 292 + static ssize_t pwm_npwm_show(struct device *parent, 293 + struct device_attribute *attr, 294 + char *buf) 295 + { 296 + const struct pwm_chip *chip = dev_get_drvdata(parent); 297 + 298 + return sprintf(buf, "%u\n", chip->npwm); 299 + } 300 + 301 + static struct device_attribute pwm_chip_attrs[] = { 302 + __ATTR(export, 0200, NULL, pwm_export_store), 303 + __ATTR(unexport, 0200, NULL, pwm_unexport_store), 304 + __ATTR(npwm, 0444, pwm_npwm_show, NULL), 305 + __ATTR_NULL, 306 + }; 307 + 308 + static struct class pwm_class = { 309 + .name = "pwm", 310 + .owner = THIS_MODULE, 311 + .dev_attrs = pwm_chip_attrs, 312 + }; 313 + 314 + static int pwmchip_sysfs_match(struct device *parent, const void *data) 315 + { 316 + return dev_get_drvdata(parent) == data; 317 + } 318 + 319 + void pwmchip_sysfs_export(struct pwm_chip *chip) 320 + { 321 + struct device *parent; 322 + 323 + /* 324 + * If device_create() fails the pwm_chip is still usable by 325 + * the kernel its just not exported. 326 + */ 327 + parent = device_create(&pwm_class, chip->dev, MKDEV(0, 0), chip, 328 + "pwmchip%d", chip->base); 329 + if (IS_ERR(parent)) { 330 + dev_warn(chip->dev, 331 + "device_create failed for pwm_chip sysfs export\n"); 332 + } 333 + } 334 + 335 + void pwmchip_sysfs_unexport(struct pwm_chip *chip) 336 + { 337 + struct device *parent; 338 + 339 + parent = class_find_device(&pwm_class, NULL, chip, 340 + pwmchip_sysfs_match); 341 + if (parent) { 342 + /* for class_find_device() */ 343 + put_device(parent); 344 + device_unregister(parent); 345 + } 346 + } 347 + 348 + static int __init pwm_sysfs_init(void) 349 + { 350 + return class_register(&pwm_class); 351 + } 352 + subsys_initcall(pwm_sysfs_init);
+28 -1
include/linux/pwm.h
··· 76 76 enum { 77 77 PWMF_REQUESTED = 1 << 0, 78 78 PWMF_ENABLED = 1 << 1, 79 + PWMF_EXPORTED = 1 << 2, 79 80 }; 80 81 81 82 struct pwm_device { ··· 87 86 struct pwm_chip *chip; 88 87 void *chip_data; 89 88 90 - unsigned int period; /* in nanoseconds */ 89 + unsigned int period; /* in nanoseconds */ 90 + unsigned int duty_cycle; /* in nanoseconds */ 91 + enum pwm_polarity polarity; 91 92 }; 92 93 93 94 static inline void pwm_set_period(struct pwm_device *pwm, unsigned int period) ··· 101 98 static inline unsigned int pwm_get_period(struct pwm_device *pwm) 102 99 { 103 100 return pwm ? pwm->period : 0; 101 + } 102 + 103 + static inline void pwm_set_duty_cycle(struct pwm_device *pwm, unsigned int duty) 104 + { 105 + if (pwm) 106 + pwm->duty_cycle = duty; 107 + } 108 + 109 + static inline unsigned int pwm_get_duty_cycle(struct pwm_device *pwm) 110 + { 111 + return pwm ? pwm->duty_cycle : 0; 104 112 } 105 113 106 114 /* ··· 291 277 { 292 278 } 293 279 #endif 280 + 281 + #ifdef CONFIG_PWM_SYSFS 282 + void pwmchip_sysfs_export(struct pwm_chip *chip); 283 + void pwmchip_sysfs_unexport(struct pwm_chip *chip); 284 + #else 285 + static inline void pwmchip_sysfs_export(struct pwm_chip *chip) 286 + { 287 + } 288 + 289 + static inline void pwmchip_sysfs_unexport(struct pwm_chip *chip) 290 + { 291 + } 292 + #endif /* CONFIG_PWM_SYSFS */ 294 293 295 294 #endif /* __LINUX_PWM_H */