Skip to content

Commit 1f9281a

Browse files
pelwellpopcornmix
authored andcommitted
pwm: Add pwm-pio-rp1 driver
Use the PIO hardware on RP1 to implement a PWM interface. Signed-off-by: Phil Elwell <phil@raspberrypi.com>
1 parent 4fcac0b commit 1f9281a

File tree

3 files changed

+262
-0
lines changed

3 files changed

+262
-0
lines changed

drivers/pwm/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,17 @@ config PWM_PCA9685
491491
To compile this driver as a module, choose M here: the module
492492
will be called pwm-pca9685.
493493

494+
config PWM_PIO_RP1
495+
tristate "RP1 PIO PWM support"
496+
depends on FIRMWARE_RP1 || COMPILE_TEST
497+
help
498+
This is a PWM framework driver for Raspberry Pi 5, using the PIO
499+
hardware of RP1 to provide PWM functionality. Supports up to 4
500+
instances on GPIOs in bank 0.
501+
502+
To compile this driver as a module, choose M here: the module
503+
will be called pwm-pio-rp1.
504+
494505
config PWM_PXA
495506
tristate "PXA PWM support"
496507
depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST

drivers/pwm/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ obj-$(CONFIG_PWM_MXS) += pwm-mxs.o
4444
obj-$(CONFIG_PWM_NTXEC) += pwm-ntxec.o
4545
obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o
4646
obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o
47+
obj-$(CONFIG_PWM_PIO_RP1) += pwm-pio-rp1.o
4748
obj-$(CONFIG_PWM_PXA) += pwm-pxa.o
4849
obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o
4950
obj-$(CONFIG_PWM_RP1) += pwm-rp1.o

drivers/pwm/pwm-pio-rp1.c

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* Raspberry Pi PIO PWM.
4+
*
5+
* Copyright (C) 2024 Raspberry Pi Ltd.
6+
*
7+
* Author: Phil Elwell (phil@raspberrypi.com)
8+
*
9+
* Based on the pwm-rp1 driver by:
10+
* Naushir Patuck <naush@raspberrypi.com>
11+
* and on the pwm-gpio driver by:
12+
* Vincent Whitchurch <vincent.whitchurch@axis.com>
13+
*/
14+
15+
#include <linux/err.h>
16+
#include <linux/gpio/consumer.h>
17+
#include <linux/module.h>
18+
#include <linux/mutex.h>
19+
#include <linux/of.h>
20+
#include <linux/pio_rp1.h>
21+
#include <linux/platform_device.h>
22+
#include <linux/pwm.h>
23+
24+
struct pwm_pio_rp1 {
25+
struct pwm_chip chip;
26+
struct device *dev;
27+
struct gpio_desc *gpiod;
28+
struct mutex mutex;
29+
PIO pio;
30+
uint sm;
31+
uint offset;
32+
uint gpio;
33+
uint32_t period; /* In SM cycles */
34+
uint32_t duty_cycle; /* In SM cycles */
35+
enum pwm_polarity polarity;
36+
bool enabled;
37+
};
38+
39+
/* Generated from pwm.pio by pioasm */
40+
#define pwm_wrap_target 0
41+
#define pwm_wrap 6
42+
#define pwm_loop_ticks 3
43+
44+
static const uint16_t pwm_program_instructions[] = {
45+
// .wrap_target
46+
0x9080, // 0: pull noblock side 0
47+
0xa027, // 1: mov x, osr
48+
0xa046, // 2: mov y, isr
49+
0x00a5, // 3: jmp x != y, 5
50+
0x1806, // 4: jmp 6 side 1
51+
0xa042, // 5: nop
52+
0x0083, // 6: jmp y--, 3
53+
// .wrap
54+
};
55+
56+
static const struct pio_program pwm_program = {
57+
.instructions = pwm_program_instructions,
58+
.length = 7,
59+
.origin = -1,
60+
};
61+
62+
static unsigned int pwm_pio_resolution __read_mostly;
63+
64+
static inline pio_sm_config pwm_program_get_default_config(uint offset)
65+
{
66+
pio_sm_config c = pio_get_default_sm_config();
67+
68+
sm_config_set_wrap(&c, offset + pwm_wrap_target, offset + pwm_wrap);
69+
sm_config_set_sideset(&c, 2, true, false);
70+
return c;
71+
}
72+
73+
static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin)
74+
{
75+
pio_gpio_init(pio, pin);
76+
77+
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
78+
pio_sm_config c = pwm_program_get_default_config(offset);
79+
80+
sm_config_set_sideset_pins(&c, pin);
81+
pio_sm_init(pio, sm, offset, &c);
82+
}
83+
84+
/* Write `period` to the input shift register - must be disabled */
85+
static void pio_pwm_set_period(PIO pio, uint sm, uint32_t period)
86+
{
87+
pio_sm_put_blocking(pio, sm, period);
88+
pio_sm_exec(pio, sm, pio_encode_pull(false, false));
89+
pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
90+
}
91+
92+
/* Write `level` to TX FIFO. State machine will copy this into X. */
93+
static void pio_pwm_set_level(PIO pio, uint sm, uint32_t level)
94+
{
95+
pio_sm_put_blocking(pio, sm, level);
96+
}
97+
98+
static int pwm_pio_rp1_apply(struct pwm_chip *chip, struct pwm_device *pwm,
99+
const struct pwm_state *state)
100+
{
101+
struct pwm_pio_rp1 *ppwm = container_of(chip, struct pwm_pio_rp1, chip);
102+
uint32_t new_duty_cycle;
103+
uint32_t new_period;
104+
105+
if (state->duty_cycle && state->duty_cycle < pwm_pio_resolution)
106+
return -EINVAL;
107+
108+
if (state->duty_cycle != state->period &&
109+
(state->period - state->duty_cycle < pwm_pio_resolution))
110+
return -EINVAL;
111+
112+
new_period = state->period / pwm_pio_resolution;
113+
new_duty_cycle = state->duty_cycle / pwm_pio_resolution;
114+
115+
mutex_lock(&ppwm->mutex);
116+
117+
if ((ppwm->enabled && !state->enabled) || new_period != ppwm->period) {
118+
pio_sm_set_enabled(ppwm->pio, ppwm->sm, false);
119+
ppwm->enabled = false;
120+
}
121+
122+
if (new_period != ppwm->period) {
123+
pio_pwm_set_period(ppwm->pio, ppwm->sm, new_period);
124+
ppwm->period = new_period;
125+
}
126+
127+
if (state->enabled && new_duty_cycle != ppwm->duty_cycle) {
128+
pio_pwm_set_level(ppwm->pio, ppwm->sm, new_duty_cycle);
129+
ppwm->duty_cycle = new_duty_cycle;
130+
}
131+
132+
if (state->polarity != ppwm->polarity) {
133+
pio_gpio_set_outover(ppwm->pio, ppwm->gpio,
134+
(state->polarity == PWM_POLARITY_INVERSED) ?
135+
GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
136+
ppwm->polarity = state->polarity;
137+
}
138+
139+
if (!ppwm->enabled && state->enabled) {
140+
pio_sm_set_enabled(ppwm->pio, ppwm->sm, true);
141+
ppwm->enabled = true;
142+
}
143+
144+
mutex_unlock(&ppwm->mutex);
145+
146+
return 0;
147+
}
148+
149+
static const struct pwm_ops pwm_pio_rp1_ops = {
150+
.apply = pwm_pio_rp1_apply,
151+
};
152+
153+
static int pwm_pio_rp1_probe(struct platform_device *pdev)
154+
{
155+
struct device_node *np = pdev->dev.of_node;
156+
struct of_phandle_args of_args = { 0 };
157+
struct device *dev = &pdev->dev;
158+
struct pwm_pio_rp1 *ppwm;
159+
struct pwm_chip *chip;
160+
bool is_rp1;
161+
162+
chip = devm_pwmchip_alloc(dev, 1, sizeof(*ppwm));
163+
if (IS_ERR(chip))
164+
return PTR_ERR(chip);
165+
166+
ppwm = pwmchip_get_drvdata(chip);
167+
168+
mutex_init(&ppwm->mutex);
169+
170+
ppwm->gpiod = devm_gpiod_get(dev, NULL, GPIOD_ASIS);
171+
/* Need to check that this is an RP1 GPIO in the first bank, and retrieve the offset */
172+
/* Unfortunately I think this has to be done by parsing the gpios property */
173+
if (IS_ERR(ppwm->gpiod))
174+
return dev_err_probe(dev, PTR_ERR(ppwm->gpiod),
175+
"could not get a gpio\n");
176+
177+
/* This really shouldn't fail, given that we have a gpiod */
178+
if (of_parse_phandle_with_args(np, "gpios", "#gpio-cells", 0, &of_args))
179+
return dev_err_probe(dev, -EINVAL,
180+
"can't find gpio declaration\n");
181+
182+
is_rp1 = of_device_is_compatible(of_args.np, "raspberrypi,rp1-gpio");
183+
of_node_put(of_args.np);
184+
if (!is_rp1 || of_args.args_count != 2)
185+
return dev_err_probe(dev, -EINVAL,
186+
"not an RP1 gpio\n");
187+
188+
ppwm->gpio = of_args.args[0];
189+
190+
ppwm->pio = pio_open();
191+
if (IS_ERR(ppwm->pio))
192+
return dev_err_probe(dev, PTR_ERR(ppwm->pio),
193+
"%pfw: could not open PIO\n",
194+
dev_fwnode(dev));
195+
196+
ppwm->sm = pio_claim_unused_sm(ppwm->pio, false);
197+
if ((int)ppwm->sm < 0) {
198+
pio_close(ppwm->pio);
199+
return dev_err_probe(dev, -EBUSY,
200+
"%pfw: no free PIO SM\n",
201+
dev_fwnode(dev));
202+
}
203+
204+
ppwm->offset = pio_add_program(ppwm->pio, &pwm_program);
205+
if (ppwm->offset == PIO_ORIGIN_ANY) {
206+
pio_close(ppwm->pio);
207+
return dev_err_probe(dev, -EBUSY,
208+
"%pfw: not enough PIO program space\n",
209+
dev_fwnode(dev));
210+
}
211+
212+
pwm_program_init(ppwm->pio, ppwm->sm, ppwm->offset, ppwm->gpio);
213+
214+
pwm_pio_resolution = (1000u * 1000 * 1000 * pwm_loop_ticks) / clock_get_hz(clk_sys);
215+
216+
chip->ops = &pwm_pio_rp1_ops;
217+
chip->atomic = true;
218+
chip->npwm = 1;
219+
220+
platform_set_drvdata(pdev, ppwm);
221+
222+
return devm_pwmchip_add(dev, chip);
223+
}
224+
225+
static void pwm_pio_rp1_remove(struct platform_device *pdev)
226+
{
227+
struct pwm_pio_rp1 *ppwm = platform_get_drvdata(pdev);
228+
229+
pio_close(ppwm->pio);
230+
}
231+
232+
static const struct of_device_id pwm_pio_rp1_dt_ids[] = {
233+
{ .compatible = "raspberrypi,pwm-pio-rp1" },
234+
{ /* sentinel */ }
235+
};
236+
MODULE_DEVICE_TABLE(of, pwm_pio_rp1_dt_ids);
237+
238+
static struct platform_driver pwm_pio_rp1_driver = {
239+
.driver = {
240+
.name = "pwm-pio-rp1",
241+
.of_match_table = pwm_pio_rp1_dt_ids,
242+
},
243+
.probe = pwm_pio_rp1_probe,
244+
.remove_new = pwm_pio_rp1_remove,
245+
};
246+
module_platform_driver(pwm_pio_rp1_driver);
247+
248+
MODULE_DESCRIPTION("PWM PIO RP1 driver");
249+
MODULE_AUTHOR("Phil Elwell");
250+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)