|
| 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