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

Configure Feed

Select the types of activity you want to include in your feed.

pwm: Add support for pwmchip devices for faster and easier userspace access

With this change each pwmchip defining the new-style waveform callbacks
can be accessed from userspace via a character device. Compared to the
sysfs-API this is faster and allows to pass the whole configuration in a
single ioctl allowing atomic application and thus reducing glitches.

On an STM32MP13 I see:

root@DistroKit:~ time pwmtestperf
real 0m 1.27s
user 0m 0.02s
sys 0m 1.21s
root@DistroKit:~ rm /dev/pwmchip0
root@DistroKit:~ time pwmtestperf
real 0m 3.61s
user 0m 0.27s
sys 0m 3.26s

pwmtestperf does essentially:

for i in 0 .. 50000:
pwm_set_waveform(duty_length_ns=i, period_length_ns=50000, duty_offset_ns=0)

and in the presence of /dev/pwmchip0 is uses the ioctls introduced here,
without that device it uses /sys/class/pwm/pwmchip0.

Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
Link: https://lore.kernel.org/r/ad4a4e49ae3f8ea81e23cac1ac12b338c3bf5c5b.1746010245.git.u.kleine-koenig@baylibre.com
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>

authored by

Uwe Kleine-König and committed by
Uwe Kleine-König
9c06f26b 505b730e

+363 -15
+307 -15
drivers/pwm/core.c
··· 23 23 24 24 #include <dt-bindings/pwm/pwm.h> 25 25 26 + #include <uapi/linux/pwm.h> 27 + 26 28 #define CREATE_TRACE_POINTS 27 29 #include <trace/events/pwm.h> 30 + 31 + #define PWM_MINOR_COUNT 256 28 32 29 33 /* protects access to pwm_chips */ 30 34 static DEFINE_MUTEX(pwm_lock); ··· 2011 2007 } 2012 2008 EXPORT_SYMBOL_GPL(pwm_get); 2013 2009 2014 - /** 2015 - * pwm_put() - release a PWM device 2016 - * @pwm: PWM device 2017 - */ 2018 - void pwm_put(struct pwm_device *pwm) 2010 + static void __pwm_put(struct pwm_device *pwm) 2019 2011 { 2020 - struct pwm_chip *chip; 2021 - 2022 - if (!pwm) 2023 - return; 2024 - 2025 - chip = pwm->chip; 2026 - 2027 - guard(mutex)(&pwm_lock); 2012 + struct pwm_chip *chip = pwm->chip; 2028 2013 2029 2014 /* 2030 2015 * Trigger a warning if a consumer called pwm_put() twice. ··· 2033 2040 put_device(&chip->dev); 2034 2041 2035 2042 module_put(chip->owner); 2043 + } 2044 + 2045 + /** 2046 + * pwm_put() - release a PWM device 2047 + * @pwm: PWM device 2048 + */ 2049 + void pwm_put(struct pwm_device *pwm) 2050 + { 2051 + if (!pwm) 2052 + return; 2053 + 2054 + guard(mutex)(&pwm_lock); 2055 + 2056 + __pwm_put(pwm); 2036 2057 } 2037 2058 EXPORT_SYMBOL_GPL(pwm_put); 2038 2059 ··· 2117 2110 } 2118 2111 EXPORT_SYMBOL_GPL(devm_fwnode_pwm_get); 2119 2112 2113 + struct pwm_cdev_data { 2114 + struct pwm_chip *chip; 2115 + struct pwm_device *pwm[]; 2116 + }; 2117 + 2118 + static int pwm_cdev_open(struct inode *inode, struct file *file) 2119 + { 2120 + struct pwm_chip *chip = container_of(inode->i_cdev, struct pwm_chip, cdev); 2121 + struct pwm_cdev_data *cdata; 2122 + 2123 + guard(mutex)(&pwm_lock); 2124 + 2125 + if (!chip->operational) 2126 + return -ENXIO; 2127 + 2128 + cdata = kzalloc(struct_size(cdata, pwm, chip->npwm), GFP_KERNEL); 2129 + if (!cdata) 2130 + return -ENOMEM; 2131 + 2132 + cdata->chip = chip; 2133 + 2134 + file->private_data = cdata; 2135 + 2136 + return nonseekable_open(inode, file); 2137 + } 2138 + 2139 + static int pwm_cdev_release(struct inode *inode, struct file *file) 2140 + { 2141 + struct pwm_cdev_data *cdata = file->private_data; 2142 + unsigned int i; 2143 + 2144 + for (i = 0; i < cdata->chip->npwm; ++i) { 2145 + struct pwm_device *pwm = cdata->pwm[i]; 2146 + 2147 + if (pwm) { 2148 + const char *label = pwm->label; 2149 + 2150 + pwm_put(cdata->pwm[i]); 2151 + kfree(label); 2152 + } 2153 + } 2154 + kfree(cdata); 2155 + 2156 + return 0; 2157 + } 2158 + 2159 + static int pwm_cdev_request(struct pwm_cdev_data *cdata, unsigned int hwpwm) 2160 + { 2161 + struct pwm_chip *chip = cdata->chip; 2162 + 2163 + if (hwpwm >= chip->npwm) 2164 + return -EINVAL; 2165 + 2166 + if (!cdata->pwm[hwpwm]) { 2167 + struct pwm_device *pwm = &chip->pwms[hwpwm]; 2168 + const char *label; 2169 + int ret; 2170 + 2171 + label = kasprintf(GFP_KERNEL, "pwm-cdev (pid=%d)", current->pid); 2172 + if (!label) 2173 + return -ENOMEM; 2174 + 2175 + ret = pwm_device_request(pwm, label); 2176 + if (ret < 0) { 2177 + kfree(label); 2178 + return ret; 2179 + } 2180 + 2181 + cdata->pwm[hwpwm] = pwm; 2182 + } 2183 + 2184 + return 0; 2185 + } 2186 + 2187 + static int pwm_cdev_free(struct pwm_cdev_data *cdata, unsigned int hwpwm) 2188 + { 2189 + struct pwm_chip *chip = cdata->chip; 2190 + 2191 + if (hwpwm >= chip->npwm) 2192 + return -EINVAL; 2193 + 2194 + if (cdata->pwm[hwpwm]) { 2195 + struct pwm_device *pwm = cdata->pwm[hwpwm]; 2196 + const char *label = pwm->label; 2197 + 2198 + __pwm_put(pwm); 2199 + 2200 + kfree(label); 2201 + 2202 + cdata->pwm[hwpwm] = NULL; 2203 + } 2204 + 2205 + return 0; 2206 + } 2207 + 2208 + static struct pwm_device *pwm_cdev_get_requested_pwm(struct pwm_cdev_data *cdata, 2209 + u32 hwpwm) 2210 + { 2211 + struct pwm_chip *chip = cdata->chip; 2212 + 2213 + if (hwpwm >= chip->npwm) 2214 + return ERR_PTR(-EINVAL); 2215 + 2216 + if (cdata->pwm[hwpwm]) 2217 + return cdata->pwm[hwpwm]; 2218 + 2219 + return ERR_PTR(-EINVAL); 2220 + } 2221 + 2222 + static long pwm_cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 2223 + { 2224 + int ret = 0; 2225 + struct pwm_cdev_data *cdata = file->private_data; 2226 + struct pwm_chip *chip = cdata->chip; 2227 + 2228 + guard(mutex)(&pwm_lock); 2229 + 2230 + if (!chip->operational) 2231 + return -ENODEV; 2232 + 2233 + switch (cmd) { 2234 + case PWM_IOCTL_REQUEST: 2235 + { 2236 + unsigned int hwpwm = arg; 2237 + 2238 + return pwm_cdev_request(cdata, hwpwm); 2239 + } 2240 + 2241 + case PWM_IOCTL_FREE: 2242 + { 2243 + unsigned int hwpwm = arg; 2244 + 2245 + return pwm_cdev_free(cdata, hwpwm); 2246 + } 2247 + 2248 + case PWM_IOCTL_ROUNDWF: 2249 + { 2250 + struct pwmchip_waveform cwf; 2251 + struct pwm_waveform wf; 2252 + struct pwm_device *pwm; 2253 + 2254 + ret = copy_from_user(&cwf, 2255 + (struct pwmchip_waveform __user *)arg, 2256 + sizeof(cwf)); 2257 + if (ret) 2258 + return -EFAULT; 2259 + 2260 + if (cwf.__pad != 0) 2261 + return -EINVAL; 2262 + 2263 + pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm); 2264 + if (IS_ERR(pwm)) 2265 + return PTR_ERR(pwm); 2266 + 2267 + wf = (struct pwm_waveform) { 2268 + .period_length_ns = cwf.period_length_ns, 2269 + .duty_length_ns = cwf.duty_length_ns, 2270 + .duty_offset_ns = cwf.duty_offset_ns, 2271 + }; 2272 + 2273 + ret = pwm_round_waveform_might_sleep(pwm, &wf); 2274 + if (ret < 0) 2275 + return ret; 2276 + 2277 + cwf = (struct pwmchip_waveform) { 2278 + .hwpwm = cwf.hwpwm, 2279 + .period_length_ns = wf.period_length_ns, 2280 + .duty_length_ns = wf.duty_length_ns, 2281 + .duty_offset_ns = wf.duty_offset_ns, 2282 + }; 2283 + 2284 + return copy_to_user((struct pwmchip_waveform __user *)arg, 2285 + &cwf, sizeof(cwf)); 2286 + } 2287 + 2288 + case PWM_IOCTL_GETWF: 2289 + { 2290 + struct pwmchip_waveform cwf; 2291 + struct pwm_waveform wf; 2292 + struct pwm_device *pwm; 2293 + 2294 + ret = copy_from_user(&cwf, 2295 + (struct pwmchip_waveform __user *)arg, 2296 + sizeof(cwf)); 2297 + if (ret) 2298 + return -EFAULT; 2299 + 2300 + if (cwf.__pad != 0) 2301 + return -EINVAL; 2302 + 2303 + pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm); 2304 + if (IS_ERR(pwm)) 2305 + return PTR_ERR(pwm); 2306 + 2307 + ret = pwm_get_waveform_might_sleep(pwm, &wf); 2308 + if (ret) 2309 + return ret; 2310 + 2311 + cwf = (struct pwmchip_waveform) { 2312 + .hwpwm = cwf.hwpwm, 2313 + .period_length_ns = wf.period_length_ns, 2314 + .duty_length_ns = wf.duty_length_ns, 2315 + .duty_offset_ns = wf.duty_offset_ns, 2316 + }; 2317 + 2318 + return copy_to_user((struct pwmchip_waveform __user *)arg, 2319 + &cwf, sizeof(cwf)); 2320 + } 2321 + 2322 + case PWM_IOCTL_SETROUNDEDWF: 2323 + case PWM_IOCTL_SETEXACTWF: 2324 + { 2325 + struct pwmchip_waveform cwf; 2326 + struct pwm_waveform wf; 2327 + struct pwm_device *pwm; 2328 + 2329 + ret = copy_from_user(&cwf, 2330 + (struct pwmchip_waveform __user *)arg, 2331 + sizeof(cwf)); 2332 + if (ret) 2333 + return -EFAULT; 2334 + 2335 + if (cwf.__pad != 0) 2336 + return -EINVAL; 2337 + 2338 + wf = (struct pwm_waveform){ 2339 + .period_length_ns = cwf.period_length_ns, 2340 + .duty_length_ns = cwf.duty_length_ns, 2341 + .duty_offset_ns = cwf.duty_offset_ns, 2342 + }; 2343 + 2344 + if (!pwm_wf_valid(&wf)) 2345 + return -EINVAL; 2346 + 2347 + pwm = pwm_cdev_get_requested_pwm(cdata, cwf.hwpwm); 2348 + if (IS_ERR(pwm)) 2349 + return PTR_ERR(pwm); 2350 + 2351 + ret = pwm_set_waveform_might_sleep(pwm, &wf, 2352 + cmd == PWM_IOCTL_SETEXACTWF); 2353 + 2354 + /* 2355 + * If userspace cares about rounding deviations it has 2356 + * to check the values anyhow, so simplify handling for 2357 + * them and don't signal uprounding. This matches the 2358 + * behaviour of PWM_IOCTL_ROUNDWF which also returns 0 2359 + * in that case. 2360 + */ 2361 + if (ret == 1) 2362 + ret = 0; 2363 + 2364 + return ret; 2365 + } 2366 + 2367 + default: 2368 + return -ENOTTY; 2369 + } 2370 + } 2371 + 2372 + static const struct file_operations pwm_cdev_fileops = { 2373 + .open = pwm_cdev_open, 2374 + .release = pwm_cdev_release, 2375 + .owner = THIS_MODULE, 2376 + .unlocked_ioctl = pwm_cdev_ioctl, 2377 + }; 2378 + 2379 + static dev_t pwm_devt; 2380 + 2120 2381 /** 2121 2382 * __pwmchip_add() - register a new PWM chip 2122 2383 * @chip: the PWM chip to add ··· 2437 2162 scoped_guard(pwmchip, chip) 2438 2163 chip->operational = true; 2439 2164 2440 - ret = device_add(&chip->dev); 2165 + if (chip->ops->write_waveform) { 2166 + if (chip->id < PWM_MINOR_COUNT) 2167 + chip->dev.devt = MKDEV(MAJOR(pwm_devt), chip->id); 2168 + else 2169 + dev_warn(&chip->dev, "chip id too high to create a chardev\n"); 2170 + } 2171 + 2172 + cdev_init(&chip->cdev, &pwm_cdev_fileops); 2173 + chip->cdev.owner = owner; 2174 + 2175 + ret = cdev_device_add(&chip->cdev, &chip->dev); 2441 2176 if (ret) 2442 2177 goto err_device_add; 2443 2178 ··· 2498 2213 idr_remove(&pwm_chips, chip->id); 2499 2214 } 2500 2215 2501 - device_del(&chip->dev); 2216 + cdev_device_del(&chip->cdev, &chip->dev); 2502 2217 } 2503 2218 EXPORT_SYMBOL_GPL(pwmchip_remove); 2504 2219 ··· 2642 2357 { 2643 2358 int ret; 2644 2359 2360 + ret = alloc_chrdev_region(&pwm_devt, 0, PWM_MINOR_COUNT, "pwm"); 2361 + if (ret) { 2362 + pr_err("Failed to initialize chrdev region for PWM usage\n"); 2363 + return ret; 2364 + } 2365 + 2645 2366 ret = class_register(&pwm_class); 2646 2367 if (ret) { 2647 2368 pr_err("Failed to initialize PWM class (%pe)\n", ERR_PTR(ret)); 2369 + unregister_chrdev_region(pwm_devt, 256); 2648 2370 return ret; 2649 2371 } 2650 2372
+3
include/linux/pwm.h
··· 2 2 #ifndef __LINUX_PWM_H 3 3 #define __LINUX_PWM_H 4 4 5 + #include <linux/cdev.h> 5 6 #include <linux/device.h> 6 7 #include <linux/err.h> 7 8 #include <linux/module.h> ··· 312 311 /** 313 312 * struct pwm_chip - abstract a PWM controller 314 313 * @dev: device providing the PWMs 314 + * @cdev: &struct cdev for this device 315 315 * @ops: callbacks for this PWM controller 316 316 * @owner: module providing this chip 317 317 * @id: unique number of this PWM chip ··· 327 325 */ 328 326 struct pwm_chip { 329 327 struct device dev; 328 + struct cdev cdev; 330 329 const struct pwm_ops *ops; 331 330 struct module *owner; 332 331 unsigned int id;
+53
include/uapi/linux/pwm.h
··· 1 + /* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ 2 + 3 + #ifndef _UAPI_PWM_H_ 4 + #define _UAPI_PWM_H_ 5 + 6 + #include <linux/ioctl.h> 7 + #include <linux/types.h> 8 + 9 + /** 10 + * struct pwmchip_waveform - Describe a PWM waveform for a pwm_chip's PWM channel 11 + * @hwpwm: per-chip relative index of the PWM device 12 + * @__pad: padding, must be zero 13 + * @period_length_ns: duration of the repeating period. 14 + * A value of 0 represents a disabled PWM. 15 + * @duty_length_ns: duration of the active part in each period 16 + * @duty_offset_ns: offset of the rising edge from a period's start 17 + */ 18 + struct pwmchip_waveform { 19 + __u32 hwpwm; 20 + __u32 __pad; 21 + __u64 period_length_ns; 22 + __u64 duty_length_ns; 23 + __u64 duty_offset_ns; 24 + }; 25 + 26 + /* Reserves the passed hwpwm for exclusive control. */ 27 + #define PWM_IOCTL_REQUEST _IO(0x75, 1) 28 + 29 + /* counter part to PWM_IOCTL_REQUEST */ 30 + #define PWM_IOCTL_FREE _IO(0x75, 2) 31 + 32 + /* 33 + * Modifies the passed wf according to hardware constraints. All parameters are 34 + * rounded down to the next possible value, unless there is no such value, then 35 + * values are rounded up. Note that zero isn't considered for rounding down 36 + * period_length_ns. 37 + */ 38 + #define PWM_IOCTL_ROUNDWF _IOWR(0x75, 3, struct pwmchip_waveform) 39 + 40 + /* Get the currently implemented waveform */ 41 + #define PWM_IOCTL_GETWF _IOWR(0x75, 4, struct pwmchip_waveform) 42 + 43 + /* Like PWM_IOCTL_ROUNDWF + PWM_IOCTL_SETEXACTWF in one go. */ 44 + #define PWM_IOCTL_SETROUNDEDWF _IOW(0x75, 5, struct pwmchip_waveform) 45 + 46 + /* 47 + * Program the PWM to emit exactly the passed waveform, subject only to rounding 48 + * down each value less than 1 ns. Returns 0 on success, -EDOM if the waveform 49 + * cannot be implemented exactly, or other negative error codes. 50 + */ 51 + #define PWM_IOCTL_SETEXACTWF _IOW(0x75, 6, struct pwmchip_waveform) 52 + 53 + #endif /* _UAPI_PWM_H_ */