diff options
Diffstat (limited to 'drivers/pwm')
| -rw-r--r-- | drivers/pwm/Kconfig | 20 | ||||
| -rw-r--r-- | drivers/pwm/Makefile | 2 | ||||
| -rw-r--r-- | drivers/pwm/pwm-pio-rp1.c | 250 | ||||
| -rw-r--r-- | drivers/pwm/pwm-raspberrypi-poe.c | 88 | ||||
| -rw-r--r-- | drivers/pwm/pwm-rp1.c | 186 |
5 files changed, 513 insertions, 33 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index c2fd3f4b62d9..ff356d639266 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -544,6 +544,17 @@ config PWM_PCA9685 To compile this driver as a module, choose M here: the module will be called pwm-pca9685. +config PWM_PIO_RP1 + tristate "RP1 PIO PWM support" + depends on FIRMWARE_RP1 || COMPILE_TEST + help + This is a PWM framework driver for Raspberry Pi 5, using the PIO + hardware of RP1 to provide PWM functionality. Supports up to 4 + instances on GPIOs in bank 0. + + To compile this driver as a module, choose M here: the module + will be called pwm-pio-rp1. + config PWM_PXA tristate "PXA PWM support" depends on ARCH_PXA || ARCH_MMP || ARCH_SPACEMIT || COMPILE_TEST @@ -563,6 +574,15 @@ config PWM_RASPBERRYPI_POE Enable Raspberry Pi firmware controller PWM bus used to control the official RPI PoE hat +config PWM_RP1 + tristate "RP1 PWM support" + depends on ARCH_BCM2835 || COMPILE_TEST + help + PWM framework driver for Raspberry Pi RP1 controller + + To compile this driver as a module, choose M here: the module + will be called pwm-rp1. + config PWM_RENESAS_RCAR tristate "Renesas R-Car PWM support" depends on ARCH_RENESAS || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index dfa8b4966ee1..602b802cb82f 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -48,8 +48,10 @@ obj-$(CONFIG_PWM_MXS) += pwm-mxs.o obj-$(CONFIG_PWM_NTXEC) += pwm-ntxec.o obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o +obj-$(CONFIG_PWM_PIO_RP1) += pwm-pio-rp1.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm-raspberrypi-poe.o +obj-$(CONFIG_PWM_RP1) += pwm-rp1.o obj-$(CONFIG_PWM_RENESAS_RCAR) += pwm-rcar.o obj-$(CONFIG_PWM_RENESAS_RZG2L_GPT) += pwm-rzg2l-gpt.o obj-$(CONFIG_PWM_RENESAS_RZ_MTU3) += pwm-rz-mtu3.o diff --git a/drivers/pwm/pwm-pio-rp1.c b/drivers/pwm/pwm-pio-rp1.c new file mode 100644 index 000000000000..6f781a1fb6fd --- /dev/null +++ b/drivers/pwm/pwm-pio-rp1.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Raspberry Pi PIO PWM. + * + * Copyright (C) 2024 Raspberry Pi Ltd. + * + * Author: Phil Elwell ([email protected]) + * + * Based on the pwm-rp1 driver by: + * Naushir Patuck <[email protected]> + * and on the pwm-gpio driver by: + * Vincent Whitchurch <[email protected]> + */ + +#include <linux/err.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/pio_rp1.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +struct pwm_pio_rp1 { + struct pwm_chip chip; + struct device *dev; + struct gpio_desc *gpiod; + struct mutex mutex; + PIO pio; + uint sm; + uint offset; + uint gpio; + uint32_t period; /* In SM cycles */ + uint32_t duty_cycle; /* In SM cycles */ + enum pwm_polarity polarity; + bool enabled; +}; + +/* Generated from pwm.pio by pioasm */ +#define pwm_wrap_target 0 +#define pwm_wrap 6 +#define pwm_loop_ticks 3 + +static const uint16_t pwm_program_instructions[] = { + // .wrap_target + 0x9080, // 0: pull noblock side 0 + 0xa027, // 1: mov x, osr + 0xa046, // 2: mov y, isr + 0x00a5, // 3: jmp x != y, 5 + 0x1806, // 4: jmp 6 side 1 + 0xa042, // 5: nop + 0x0083, // 6: jmp y--, 3 + // .wrap +}; + +static const struct pio_program pwm_program = { + .instructions = pwm_program_instructions, + .length = 7, + .origin = -1, +}; + +static unsigned int pwm_pio_resolution __read_mostly; + +static inline pio_sm_config pwm_program_get_default_config(uint offset) +{ + pio_sm_config c = pio_get_default_sm_config(); + + sm_config_set_wrap(&c, offset + pwm_wrap_target, offset + pwm_wrap); + sm_config_set_sideset(&c, 2, true, false); + return c; +} + +static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) +{ + pio_gpio_init(pio, pin); + + pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); + pio_sm_config c = pwm_program_get_default_config(offset); + + sm_config_set_sideset_pins(&c, pin); + pio_sm_init(pio, sm, offset, &c); +} + +/* Write `period` to the input shift register - must be disabled */ +static void pio_pwm_set_period(PIO pio, uint sm, uint32_t period) +{ + pio_sm_put_blocking(pio, sm, period); + pio_sm_exec(pio, sm, pio_encode_pull(false, false)); + pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32)); +} + +/* Write `level` to TX FIFO. State machine will copy this into X. */ +static void pio_pwm_set_level(PIO pio, uint sm, uint32_t level) +{ + pio_sm_put_blocking(pio, sm, level); +} + +static int pwm_pio_rp1_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct pwm_pio_rp1 *ppwm = container_of(chip, struct pwm_pio_rp1, chip); + uint32_t new_duty_cycle; + uint32_t new_period; + + if (state->duty_cycle && state->duty_cycle < pwm_pio_resolution) + return -EINVAL; + + if (state->duty_cycle != state->period && + (state->period - state->duty_cycle < pwm_pio_resolution)) + return -EINVAL; + + new_period = state->period / pwm_pio_resolution; + new_duty_cycle = state->duty_cycle / pwm_pio_resolution; + + mutex_lock(&ppwm->mutex); + + if ((ppwm->enabled && !state->enabled) || new_period != ppwm->period) { + pio_sm_set_enabled(ppwm->pio, ppwm->sm, false); + ppwm->enabled = false; + } + + if (new_period != ppwm->period) { + pio_pwm_set_period(ppwm->pio, ppwm->sm, new_period); + ppwm->period = new_period; + } + + if (state->enabled && new_duty_cycle != ppwm->duty_cycle) { + pio_pwm_set_level(ppwm->pio, ppwm->sm, new_duty_cycle); + ppwm->duty_cycle = new_duty_cycle; + } + + if (state->polarity != ppwm->polarity) { + pio_gpio_set_outover(ppwm->pio, ppwm->gpio, + (state->polarity == PWM_POLARITY_INVERSED) ? + GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL); + ppwm->polarity = state->polarity; + } + + if (!ppwm->enabled && state->enabled) { + pio_sm_set_enabled(ppwm->pio, ppwm->sm, true); + ppwm->enabled = true; + } + + mutex_unlock(&ppwm->mutex); + + return 0; +} + +static const struct pwm_ops pwm_pio_rp1_ops = { + .apply = pwm_pio_rp1_apply, +}; + +static int pwm_pio_rp1_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct of_phandle_args of_args = { 0 }; + struct device *dev = &pdev->dev; + struct pwm_pio_rp1 *ppwm; + struct pwm_chip *chip; + bool is_rp1; + + chip = devm_pwmchip_alloc(dev, 1, sizeof(*ppwm)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + ppwm = pwmchip_get_drvdata(chip); + + mutex_init(&ppwm->mutex); + + ppwm->gpiod = devm_gpiod_get(dev, NULL, GPIOD_ASIS); + /* Need to check that this is an RP1 GPIO in the first bank, and retrieve the offset */ + /* Unfortunately I think this has to be done by parsing the gpios property */ + if (IS_ERR(ppwm->gpiod)) + return dev_err_probe(dev, PTR_ERR(ppwm->gpiod), + "could not get a gpio\n"); + + /* This really shouldn't fail, given that we have a gpiod */ + if (of_parse_phandle_with_args(np, "gpios", "#gpio-cells", 0, &of_args)) + return dev_err_probe(dev, -EINVAL, + "can't find gpio declaration\n"); + + is_rp1 = of_device_is_compatible(of_args.np, "raspberrypi,rp1-gpio"); + of_node_put(of_args.np); + if (!is_rp1 || of_args.args_count != 2) + return dev_err_probe(dev, -EINVAL, + "not an RP1 gpio\n"); + + ppwm->gpio = of_args.args[0]; + + ppwm->pio = pio_open(); + if (IS_ERR(ppwm->pio)) + return dev_err_probe(dev, PTR_ERR(ppwm->pio), + "%pfw: could not open PIO\n", + dev_fwnode(dev)); + + ppwm->sm = pio_claim_unused_sm(ppwm->pio, false); + if ((int)ppwm->sm < 0) { + pio_close(ppwm->pio); + return dev_err_probe(dev, -EBUSY, + "%pfw: no free PIO SM\n", + dev_fwnode(dev)); + } + + ppwm->offset = pio_add_program(ppwm->pio, &pwm_program); + if (ppwm->offset == PIO_ORIGIN_ANY) { + pio_close(ppwm->pio); + return dev_err_probe(dev, -EBUSY, + "%pfw: not enough PIO program space\n", + dev_fwnode(dev)); + } + + pwm_program_init(ppwm->pio, ppwm->sm, ppwm->offset, ppwm->gpio); + + pwm_pio_resolution = (1000u * 1000 * 1000 * pwm_loop_ticks) / clock_get_hz(clk_sys); + + chip->ops = &pwm_pio_rp1_ops; + chip->atomic = true; + chip->npwm = 1; + + platform_set_drvdata(pdev, ppwm); + + return devm_pwmchip_add(dev, chip); +} + +static void pwm_pio_rp1_remove(struct platform_device *pdev) +{ + struct pwm_pio_rp1 *ppwm = platform_get_drvdata(pdev); + + pio_close(ppwm->pio); +} + +static const struct of_device_id pwm_pio_rp1_dt_ids[] = { + { .compatible = "raspberrypi,pwm-pio-rp1" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, pwm_pio_rp1_dt_ids); + +static struct platform_driver pwm_pio_rp1_driver = { + .driver = { + .name = "pwm-pio-rp1", + .of_match_table = pwm_pio_rp1_dt_ids, + }, + .probe = pwm_pio_rp1_probe, + .remove = pwm_pio_rp1_remove, +}; +module_platform_driver(pwm_pio_rp1_driver); + +MODULE_DESCRIPTION("PWM PIO RP1 driver"); +MODULE_AUTHOR("Phil Elwell"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-raspberrypi-poe.c b/drivers/pwm/pwm-raspberrypi-poe.c index 8921e7ea2cea..19ff0726f78e 100644 --- a/drivers/pwm/pwm-raspberrypi-poe.c +++ b/drivers/pwm/pwm-raspberrypi-poe.c @@ -16,6 +16,7 @@ #include <linux/of.h> #include <linux/platform_device.h> #include <linux/pwm.h> +#include <linux/regmap.h> #include <soc/bcm2835/raspberrypi-firmware.h> #include <dt-bindings/pwm/raspberrypi,firmware-poe-pwm.h> @@ -27,6 +28,10 @@ struct raspberrypi_pwm { struct rpi_firmware *firmware; + + struct regmap *regmap; + u32 offset; + unsigned int duty_cycle; }; @@ -42,7 +47,7 @@ struct raspberrypi_pwm *raspberrypi_pwm_from_chip(struct pwm_chip *chip) return pwmchip_get_drvdata(chip); } -static int raspberrypi_pwm_set_property(struct rpi_firmware *firmware, +static int raspberrypi_pwm_set_property(struct raspberrypi_pwm *pwm, u32 reg, u32 val) { struct raspberrypi_pwm_prop msg = { @@ -51,17 +56,19 @@ static int raspberrypi_pwm_set_property(struct rpi_firmware *firmware, }; int ret; - ret = rpi_firmware_property(firmware, RPI_FIRMWARE_SET_POE_HAT_VAL, - &msg, sizeof(msg)); - if (ret) - return ret; - if (msg.ret) - return -EIO; + if (pwm->firmware) { + ret = rpi_firmware_property(pwm->firmware, RPI_FIRMWARE_SET_POE_HAT_VAL, + &msg, sizeof(msg)); + if (!ret && msg.ret) + ret = -EIO; + } else { + ret = regmap_write(pwm->regmap, pwm->offset + reg, val); + } - return 0; + return ret; } -static int raspberrypi_pwm_get_property(struct rpi_firmware *firmware, +static int raspberrypi_pwm_get_property(struct raspberrypi_pwm *pwm, u32 reg, u32 *val) { struct raspberrypi_pwm_prop msg = { @@ -69,16 +76,17 @@ static int raspberrypi_pwm_get_property(struct rpi_firmware *firmware, }; int ret; - ret = rpi_firmware_property(firmware, RPI_FIRMWARE_GET_POE_HAT_VAL, - &msg, sizeof(msg)); - if (ret) - return ret; - if (msg.ret) - return -EIO; - - *val = le32_to_cpu(msg.val); + if (pwm->firmware) { + ret = rpi_firmware_property(pwm->firmware, RPI_FIRMWARE_GET_POE_HAT_VAL, + &msg, sizeof(msg)); + if (!ret && msg.ret) + ret = -EIO; + *val = le32_to_cpu(msg.val); + } else { + ret = regmap_read(pwm->regmap, pwm->offset + reg, val); + } - return 0; + return ret; } static int raspberrypi_pwm_get_state(struct pwm_chip *chip, @@ -118,7 +126,7 @@ static int raspberrypi_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, if (duty_cycle == rpipwm->duty_cycle) return 0; - ret = raspberrypi_pwm_set_property(rpipwm->firmware, RPI_PWM_CUR_DUTY_REG, + ret = raspberrypi_pwm_set_property(rpipwm, RPI_PWM_CUR_DUTY_REG, duty_cycle); if (ret) { dev_err(pwmchip_parent(chip), "Failed to set duty cycle: %pe\n", @@ -145,28 +153,41 @@ static int raspberrypi_pwm_probe(struct platform_device *pdev) struct raspberrypi_pwm *rpipwm; int ret; - firmware_node = of_get_parent(dev->of_node); - if (!firmware_node) { - dev_err(dev, "Missing firmware node\n"); - return -ENOENT; - } - - firmware = devm_rpi_firmware_get(&pdev->dev, firmware_node); - of_node_put(firmware_node); - if (!firmware) - return dev_err_probe(dev, -EPROBE_DEFER, - "Failed to get firmware handle\n"); - chip = devm_pwmchip_alloc(&pdev->dev, RASPBERRYPI_FIRMWARE_PWM_NUM, sizeof(*rpipwm)); if (IS_ERR(chip)) return PTR_ERR(chip); rpipwm = raspberrypi_pwm_from_chip(chip); - rpipwm->firmware = firmware; + if (!rpipwm) + return -ENOMEM; + + if (pdev->dev.parent) + rpipwm->regmap = dev_get_regmap(pdev->dev.parent, NULL); + + if (rpipwm->regmap) { + ret = device_property_read_u32(&pdev->dev, "reg", &rpipwm->offset); + if (ret) + return -EINVAL; + } else { + firmware_node = of_get_parent(dev->of_node); + if (!firmware_node) { + dev_err(dev, "Missing firmware node\n"); + return -ENOENT; + } + + firmware = devm_rpi_firmware_get(&pdev->dev, firmware_node); + of_node_put(firmware_node); + if (!firmware) + return dev_err_probe(dev, -EPROBE_DEFER, + "Failed to get firmware handle\n"); + + rpipwm->firmware = firmware; + } + chip->ops = &raspberrypi_pwm_ops; - ret = raspberrypi_pwm_get_property(rpipwm->firmware, RPI_PWM_CUR_DUTY_REG, + ret = raspberrypi_pwm_get_property(rpipwm, RPI_PWM_CUR_DUTY_REG, &rpipwm->duty_cycle); if (ret) { dev_err(dev, "Failed to get duty cycle: %pe\n", ERR_PTR(ret)); @@ -178,6 +199,7 @@ static int raspberrypi_pwm_probe(struct platform_device *pdev) static const struct of_device_id raspberrypi_pwm_of_match[] = { { .compatible = "raspberrypi,firmware-poe-pwm", }, + { .compatible = "raspberrypi,poe-pwm", }, { } }; MODULE_DEVICE_TABLE(of, raspberrypi_pwm_of_match); diff --git a/drivers/pwm/pwm-rp1.c b/drivers/pwm/pwm-rp1.c new file mode 100644 index 000000000000..fcf20ded80cf --- /dev/null +++ b/drivers/pwm/pwm-rp1.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * pwm-rp1.c + * + * Raspberry Pi RP1 PWM. + * + * Copyright © 2023 Raspberry Pi Ltd. + * + * Author: Naushir Patuck ([email protected]) + * + * Based on the pwm-bcm2835 driver by: + * Bart Tanghe <[email protected]> + */ + +#include <linux/bitops.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +#define PWM_GLOBAL_CTRL 0x000 +#define PWM_CHANNEL_CTRL(x) (0x014 + ((x) * 16)) +#define PWM_RANGE(x) (0x018 + ((x) * 16)) +#define PWM_DUTY(x) (0x020 + ((x) * 16)) + +/* 8:FIFO_POP_MASK + 0:Trailing edge M/S modulation */ +#define PWM_CHANNEL_DEFAULT (BIT(8) + BIT(0)) +#define PWM_CHANNEL_ENABLE(x) BIT(x) +#define PWM_POLARITY BIT(3) +#define SET_UPDATE BIT(31) +#define PWM_MODE_MASK GENMASK(1, 0) + +struct rp1_pwm { + void __iomem *base; + struct clk *clk; +}; + +static inline struct rp1_pwm *to_rp1_pwm(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static void rp1_pwm_apply_config(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rp1_pwm *pc = to_rp1_pwm(chip); + u32 value; + + value = readl(pc->base + PWM_GLOBAL_CTRL); + value |= SET_UPDATE; + writel(value, pc->base + PWM_GLOBAL_CTRL); +} + +static int rp1_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rp1_pwm *pc = to_rp1_pwm(chip); + + writel(PWM_CHANNEL_DEFAULT, pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm)); + return 0; +} + +static void rp1_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct rp1_pwm *pc = to_rp1_pwm(chip); + u32 value; + + value = readl(pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm)); + value &= ~PWM_MODE_MASK; + writel(value, pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm)); + rp1_pwm_apply_config(chip, pwm); +} + +static int rp1_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct rp1_pwm *pc = to_rp1_pwm(chip); + unsigned long clk_rate = clk_get_rate(pc->clk); + unsigned long clk_period; + u32 value; + + if (!clk_rate) { + dev_err(&chip->dev, "failed to get clock rate\n"); + return -EINVAL; + } + + /* set period */ + clk_period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, clk_rate); + + writel(DIV_ROUND_CLOSEST(state->duty_cycle, clk_period), + pc->base + PWM_DUTY(pwm->hwpwm)); + + /* set duty cycle */ + writel(DIV_ROUND_CLOSEST(state->period, clk_period), + pc->base + PWM_RANGE(pwm->hwpwm)); + + /* set polarity */ + value = readl(pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm)); + if (state->polarity == PWM_POLARITY_NORMAL) + value &= ~PWM_POLARITY; + else + value |= PWM_POLARITY; + writel(value, pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm)); + + /* enable/disable */ + value = readl(pc->base + PWM_GLOBAL_CTRL); + if (state->enabled) + value |= PWM_CHANNEL_ENABLE(pwm->hwpwm); + else + value &= ~PWM_CHANNEL_ENABLE(pwm->hwpwm); + writel(value, pc->base + PWM_GLOBAL_CTRL); + + rp1_pwm_apply_config(chip, pwm); + + return 0; +} + +static const struct pwm_ops rp1_pwm_ops = { + .request = rp1_pwm_request, + .free = rp1_pwm_free, + .apply = rp1_pwm_apply, +}; + +static int rp1_pwm_probe(struct platform_device *pdev) +{ + struct pwm_chip *chip; + struct rp1_pwm *pc; + int ret; + + chip = devm_pwmchip_alloc(&pdev->dev, 4, sizeof(*pc)); + + if (IS_ERR(chip)) + return PTR_ERR(chip); + + pc = to_rp1_pwm(chip); + + pc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pc->base)) + return PTR_ERR(pc->base); + + pc->clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(pc->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk), + "clock not found\n"); + + chip->ops = &rp1_pwm_ops; + chip->of_xlate = of_pwm_xlate_with_flags; + + ret = devm_pwmchip_add(&pdev->dev, chip); + if (ret < 0) + goto add_fail; + + return 0; + +add_fail: + clk_disable_unprepare(pc->clk); + return ret; +} + +static void rp1_pwm_remove(struct platform_device *pdev) +{ + struct rp1_pwm *pc = platform_get_drvdata(pdev); + + clk_disable_unprepare(pc->clk); +} + +static const struct of_device_id rp1_pwm_of_match[] = { + { .compatible = "raspberrypi,rp1-pwm" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rp1_pwm_of_match); + +static struct platform_driver rp1_pwm_driver = { + .driver = { + .name = "rpi-pwm", + .of_match_table = rp1_pwm_of_match, + }, + .probe = rp1_pwm_probe, + .remove = rp1_pwm_remove, +}; +module_platform_driver(rp1_pwm_driver); + +MODULE_AUTHOR("Naushir Patuck <[email protected]"); +MODULE_DESCRIPTION("RP1 PWM driver"); +MODULE_LICENSE("GPL"); |
