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 GPIO PWM driver

Add a software PWM which toggles a GPIO from a high-resolution timer.

This will naturally not be as accurate or as efficient as a hardware
PWM, but it is useful in some cases. I have for example used it for
evaluating LED brightness handling (via leds-pwm) on a board where the
LED was just hooked up to a GPIO, and for a simple verification of the
timer frequency on another platform.

Since high-resolution timers are used, sleeping GPIO chips are not
supported and are rejected in the probe function.

Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
Co-developed-by: Stefan Wahren <wahrenst@gmx.net>
Signed-off-by: Stefan Wahren <wahrenst@gmx.net>
Co-developed-by: Linus Walleij <linus.walleij@linaro.org>
Reviewed-by: Andy Shevchenko <andy@kernel.org>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Reviewed-by: Dhruva Gole <d-gole@ti.com>
Link: https://lore.kernel.org/r/20240604-pwm-gpio-v7-2-6b67cf60db92@linaro.org
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>

authored by

Vincent Whitchurch and committed by
Uwe Kleine-König
7f61257c 1577ddaa

+259 -1
+6 -1
Documentation/driver-api/gpio/drivers-on-gpio.rst
··· 27 27 to the lines for a more permanent solution of this type. 28 28 29 29 - gpio-beeper: drivers/input/misc/gpio-beeper.c is used to provide a beep from 30 - an external speaker connected to a GPIO line. 30 + an external speaker connected to a GPIO line. (If the beep is controlled by 31 + off/on, for an actual PWM waveform, see pwm-gpio below.) 32 + 33 + - pwm-gpio: drivers/pwm/pwm-gpio.c is used to toggle a GPIO with a high 34 + resolution timer producing a PWM waveform on the GPIO line, as well as 35 + Linux high resolution timers can do. 31 36 32 37 - extcon-gpio: drivers/extcon/extcon-gpio.c is used when you need to read an 33 38 external connector status, such as a headset line for an audio driver or an
+11
drivers/pwm/Kconfig
··· 236 236 To compile this driver as a module, choose M here: the module 237 237 will be called pwm-fsl-ftm. 238 238 239 + config PWM_GPIO 240 + tristate "GPIO PWM support" 241 + depends on GPIOLIB 242 + depends on HIGH_RES_TIMERS 243 + help 244 + Generic PWM framework driver for software PWM toggling a GPIO pin 245 + from kernel high-resolution timers. 246 + 247 + To compile this driver as a module, choose M here: the module 248 + will be called pwm-gpio. 249 + 239 250 config PWM_HIBVT 240 251 tristate "HiSilicon BVT PWM support" 241 252 depends on ARCH_HISI || COMPILE_TEST
+1
drivers/pwm/Makefile
··· 19 19 obj-$(CONFIG_PWM_DWC) += pwm-dwc.o 20 20 obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o 21 21 obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o 22 + obj-$(CONFIG_PWM_GPIO) += pwm-gpio.o 22 23 obj-$(CONFIG_PWM_HIBVT) += pwm-hibvt.o 23 24 obj-$(CONFIG_PWM_IMG) += pwm-img.o 24 25 obj-$(CONFIG_PWM_IMX1) += pwm-imx1.o
+241
drivers/pwm/pwm-gpio.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Generic software PWM for modulating GPIOs 4 + * 5 + * Copyright (C) 2020 Axis Communications AB 6 + * Copyright (C) 2020 Nicola Di Lieto 7 + * Copyright (C) 2024 Stefan Wahren 8 + * Copyright (C) 2024 Linus Walleij 9 + */ 10 + 11 + #include <linux/cleanup.h> 12 + #include <linux/container_of.h> 13 + #include <linux/device.h> 14 + #include <linux/err.h> 15 + #include <linux/gpio/consumer.h> 16 + #include <linux/hrtimer.h> 17 + #include <linux/math.h> 18 + #include <linux/module.h> 19 + #include <linux/mod_devicetable.h> 20 + #include <linux/platform_device.h> 21 + #include <linux/property.h> 22 + #include <linux/pwm.h> 23 + #include <linux/spinlock.h> 24 + #include <linux/time.h> 25 + #include <linux/types.h> 26 + 27 + struct pwm_gpio { 28 + struct hrtimer gpio_timer; 29 + struct gpio_desc *gpio; 30 + struct pwm_state state; 31 + struct pwm_state next_state; 32 + 33 + /* Protect internal state between pwm_ops and hrtimer */ 34 + spinlock_t lock; 35 + 36 + bool changing; 37 + bool running; 38 + bool level; 39 + }; 40 + 41 + static void pwm_gpio_round(struct pwm_state *dest, const struct pwm_state *src) 42 + { 43 + u64 dividend; 44 + u32 remainder; 45 + 46 + *dest = *src; 47 + 48 + /* Round down to hrtimer resolution */ 49 + dividend = dest->period; 50 + remainder = do_div(dividend, hrtimer_resolution); 51 + dest->period -= remainder; 52 + 53 + dividend = dest->duty_cycle; 54 + remainder = do_div(dividend, hrtimer_resolution); 55 + dest->duty_cycle -= remainder; 56 + } 57 + 58 + static u64 pwm_gpio_toggle(struct pwm_gpio *gpwm, bool level) 59 + { 60 + const struct pwm_state *state = &gpwm->state; 61 + bool invert = state->polarity == PWM_POLARITY_INVERSED; 62 + 63 + gpwm->level = level; 64 + gpiod_set_value(gpwm->gpio, gpwm->level ^ invert); 65 + 66 + if (!state->duty_cycle || state->duty_cycle == state->period) { 67 + gpwm->running = false; 68 + return 0; 69 + } 70 + 71 + gpwm->running = true; 72 + return level ? state->duty_cycle : state->period - state->duty_cycle; 73 + } 74 + 75 + static enum hrtimer_restart pwm_gpio_timer(struct hrtimer *gpio_timer) 76 + { 77 + struct pwm_gpio *gpwm = container_of(gpio_timer, struct pwm_gpio, 78 + gpio_timer); 79 + u64 next_toggle; 80 + bool new_level; 81 + 82 + guard(spinlock_irqsave)(&gpwm->lock); 83 + 84 + /* Apply new state at end of current period */ 85 + if (!gpwm->level && gpwm->changing) { 86 + gpwm->changing = false; 87 + gpwm->state = gpwm->next_state; 88 + new_level = !!gpwm->state.duty_cycle; 89 + } else { 90 + new_level = !gpwm->level; 91 + } 92 + 93 + next_toggle = pwm_gpio_toggle(gpwm, new_level); 94 + if (next_toggle) 95 + hrtimer_forward(gpio_timer, hrtimer_get_expires(gpio_timer), 96 + ns_to_ktime(next_toggle)); 97 + 98 + return next_toggle ? HRTIMER_RESTART : HRTIMER_NORESTART; 99 + } 100 + 101 + static int pwm_gpio_apply(struct pwm_chip *chip, struct pwm_device *pwm, 102 + const struct pwm_state *state) 103 + { 104 + struct pwm_gpio *gpwm = pwmchip_get_drvdata(chip); 105 + bool invert = state->polarity == PWM_POLARITY_INVERSED; 106 + 107 + if (state->duty_cycle && state->duty_cycle < hrtimer_resolution) 108 + return -EINVAL; 109 + 110 + if (state->duty_cycle != state->period && 111 + (state->period - state->duty_cycle < hrtimer_resolution)) 112 + return -EINVAL; 113 + 114 + if (!state->enabled) { 115 + hrtimer_cancel(&gpwm->gpio_timer); 116 + } else if (!gpwm->running) { 117 + int ret; 118 + 119 + /* 120 + * This just enables the output, but pwm_gpio_toggle() 121 + * really starts the duty cycle. 122 + */ 123 + ret = gpiod_direction_output(gpwm->gpio, invert); 124 + if (ret) 125 + return ret; 126 + } 127 + 128 + guard(spinlock_irqsave)(&gpwm->lock); 129 + 130 + if (!state->enabled) { 131 + pwm_gpio_round(&gpwm->state, state); 132 + gpwm->running = false; 133 + gpwm->changing = false; 134 + 135 + gpiod_set_value(gpwm->gpio, invert); 136 + } else if (gpwm->running) { 137 + pwm_gpio_round(&gpwm->next_state, state); 138 + gpwm->changing = true; 139 + } else { 140 + unsigned long next_toggle; 141 + 142 + pwm_gpio_round(&gpwm->state, state); 143 + gpwm->changing = false; 144 + 145 + next_toggle = pwm_gpio_toggle(gpwm, !!state->duty_cycle); 146 + if (next_toggle) 147 + hrtimer_start(&gpwm->gpio_timer, next_toggle, 148 + HRTIMER_MODE_REL); 149 + } 150 + 151 + return 0; 152 + } 153 + 154 + static int pwm_gpio_get_state(struct pwm_chip *chip, struct pwm_device *pwm, 155 + struct pwm_state *state) 156 + { 157 + struct pwm_gpio *gpwm = pwmchip_get_drvdata(chip); 158 + 159 + guard(spinlock_irqsave)(&gpwm->lock); 160 + 161 + if (gpwm->changing) 162 + *state = gpwm->next_state; 163 + else 164 + *state = gpwm->state; 165 + 166 + return 0; 167 + } 168 + 169 + static const struct pwm_ops pwm_gpio_ops = { 170 + .apply = pwm_gpio_apply, 171 + .get_state = pwm_gpio_get_state, 172 + }; 173 + 174 + static void pwm_gpio_disable_hrtimer(void *data) 175 + { 176 + struct pwm_gpio *gpwm = data; 177 + 178 + hrtimer_cancel(&gpwm->gpio_timer); 179 + } 180 + 181 + static int pwm_gpio_probe(struct platform_device *pdev) 182 + { 183 + struct device *dev = &pdev->dev; 184 + struct pwm_chip *chip; 185 + struct pwm_gpio *gpwm; 186 + int ret; 187 + 188 + chip = devm_pwmchip_alloc(dev, 1, sizeof(*gpwm)); 189 + if (IS_ERR(chip)) 190 + return PTR_ERR(chip); 191 + 192 + gpwm = pwmchip_get_drvdata(chip); 193 + 194 + spin_lock_init(&gpwm->lock); 195 + 196 + gpwm->gpio = devm_gpiod_get(dev, NULL, GPIOD_ASIS); 197 + if (IS_ERR(gpwm->gpio)) 198 + return dev_err_probe(dev, PTR_ERR(gpwm->gpio), 199 + "%pfw: could not get gpio\n", 200 + dev_fwnode(dev)); 201 + 202 + if (gpiod_cansleep(gpwm->gpio)) 203 + return dev_err_probe(dev, -EINVAL, 204 + "%pfw: sleeping GPIO not supported\n", 205 + dev_fwnode(dev)); 206 + 207 + chip->ops = &pwm_gpio_ops; 208 + chip->atomic = true; 209 + 210 + hrtimer_init(&gpwm->gpio_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); 211 + ret = devm_add_action_or_reset(dev, pwm_gpio_disable_hrtimer, gpwm); 212 + if (ret) 213 + return ret; 214 + 215 + gpwm->gpio_timer.function = pwm_gpio_timer; 216 + 217 + ret = pwmchip_add(chip); 218 + if (ret < 0) 219 + return dev_err_probe(dev, ret, "could not add pwmchip\n"); 220 + 221 + return 0; 222 + } 223 + 224 + static const struct of_device_id pwm_gpio_dt_ids[] = { 225 + { .compatible = "pwm-gpio" }, 226 + { /* sentinel */ } 227 + }; 228 + MODULE_DEVICE_TABLE(of, pwm_gpio_dt_ids); 229 + 230 + static struct platform_driver pwm_gpio_driver = { 231 + .driver = { 232 + .name = "pwm-gpio", 233 + .of_match_table = pwm_gpio_dt_ids, 234 + }, 235 + .probe = pwm_gpio_probe, 236 + }; 237 + module_platform_driver(pwm_gpio_driver); 238 + 239 + MODULE_DESCRIPTION("PWM GPIO driver"); 240 + MODULE_AUTHOR("Vincent Whitchurch"); 241 + MODULE_LICENSE("GPL");