Hello, On Fri, Oct 09, 2020 at 05:30:30PM +0200, Nicolas Saenz Julienne wrote: > Adds support to control the PWM bus available in official Raspberry Pi > PoE HAT. Only RPi's co-processor has access to it, so commands have to > be sent through RPi's firmware mailbox interface. > > Signed-off-by: Nicolas Saenz Julienne <nsaenzjulienne@xxxxxxx> > --- > drivers/pwm/Kconfig | 7 ++ > drivers/pwm/Makefile | 1 + > drivers/pwm/pwm-raspberrypi.c | 216 ++++++++++++++++++++++++++++++++++ > 3 files changed, 224 insertions(+) > create mode 100644 drivers/pwm/pwm-raspberrypi.c > > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig > index 63be5362fd3a..a76997ca37d0 100644 > --- a/drivers/pwm/Kconfig > +++ b/drivers/pwm/Kconfig > @@ -379,6 +379,13 @@ config PWM_PXA > To compile this driver as a module, choose M here: the module > will be called pwm-pxa. > > +config PWM_RASPBERRYPI > + tristate "Raspberry Pi Firwmware PWM support" s/Firwmware/Firmware/ > + depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE) This is more complicated than necessary. depends on RASPBERRYPI_FIRMWARE || COMPILE_TEST is logically equivalent. > + help > + Enable Raspberry Pi firmware controller PWM bus used to control the > + official RPI PoE hat > + > config PWM_RCAR > tristate "Renesas R-Car PWM support" > depends on ARCH_RENESAS || COMPILE_TEST > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile > index cbdcd55d69ee..b557b549d9f3 100644 > --- a/drivers/pwm/Makefile > +++ b/drivers/pwm/Makefile > @@ -35,6 +35,7 @@ obj-$(CONFIG_PWM_MXS) += pwm-mxs.o > obj-$(CONFIG_PWM_OMAP_DMTIMER) += pwm-omap-dmtimer.o > obj-$(CONFIG_PWM_PCA9685) += pwm-pca9685.o > obj-$(CONFIG_PWM_PXA) += pwm-pxa.o > +obj-$(CONFIG_PWM_RASPBERRYPI) += pwm-raspberrypi.o > obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o > obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o > obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o > diff --git a/drivers/pwm/pwm-raspberrypi.c b/drivers/pwm/pwm-raspberrypi.c > new file mode 100644 > index 000000000000..1ccff6b1ae34 > --- /dev/null > +++ b/drivers/pwm/pwm-raspberrypi.c > @@ -0,0 +1,216 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright 2020 Nicolas Saenz Julienne <nsaenzjulienne@xxxxxxx> > + */ Please add a paragraph here about the hardware. See pwm-sifive.c for a template. (Please stick to the format there to simplify grepping.) The things to point out there are: - No disable bit, so a disabled PWM is simulated by duty_cycle 0 - Only normal polarity - Fixed period Also add a note about if the currently running period is completed when the hardware is reconfigured. If possible please also add a link to a product page and/or documentation. > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/platform_device.h> > +#include <linux/pwm.h> > + > +#include <soc/bcm2835/raspberrypi-firmware.h> > +#include <dt-bindings/pwm/raspberrypi,firmware-pwm.h> > + > +#define RPI_PWM_MAX_DUTY 255 > +#define RPI_PWM_PERIOD_NS 80000 /* 12.5KHz */ 12.5 kHz > +#define RPI_PWM_CUR_DUTY_REG 0x0 > +#define RPI_PWM_DEF_DUTY_REG 0x1 > + > +struct raspberrypi_pwm { > + struct rpi_firmware *firmware; > + struct pwm_chip chip; > + unsigned int duty_cycle; > +}; > + > +struct raspberrypi_pwm_prop { > + __le32 reg; > + __le32 val; > + __le32 ret; > +} __packed; > + > +static inline struct raspberrypi_pwm *to_raspberrypi_pwm(struct pwm_chip *chip) > +{ > + return container_of(chip, struct raspberrypi_pwm, chip); > +} > + > +static int raspberrypi_pwm_set_property(struct rpi_firmware *firmware, > + u32 reg, u32 val) > +{ > + struct raspberrypi_pwm_prop msg = { > + .reg = cpu_to_le32(reg), > + .val = cpu_to_le32(val), > + }; > + int ret; > + > + ret = rpi_firmware_property(firmware, RPI_FIRMWARE_SET_POE_HAT_VAL, > + &msg, sizeof(msg)); > + if (ret) > + return ret; > + else if (msg.ret) > + return -EIO; > + > + return 0; > +} > + > +static int raspberrypi_pwm_get_property(struct rpi_firmware *firmware, > + u32 reg, u32 *val) > +{ > + struct raspberrypi_pwm_prop msg = { > + .reg = reg > + }; > + int ret; > + > + ret = rpi_firmware_property(firmware, RPI_FIRMWARE_GET_POE_HAT_VAL, > + &msg, sizeof(msg)); > + if (ret) > + return ret; > + else if (msg.ret) > + return -EIO; > + > + *val = le32_to_cpu(msg.val); > + > + return 0; > +} > + > +static void raspberrypi_pwm_get_state(struct pwm_chip *chip, > + struct pwm_device *pwm, > + struct pwm_state *state) > +{ > + struct raspberrypi_pwm *pc = to_raspberrypi_pwm(chip); > + > + state->period = RPI_PWM_PERIOD_NS; > + state->duty_cycle = pc->duty_cycle * RPI_PWM_PERIOD_NS / RPI_PWM_MAX_DUTY; Please round up the result of the division. (The idea is that if you apply the state .get_state() returns this should yield no change.) > + state->enabled = !!(pc->duty_cycle); > + state->polarity = PWM_POLARITY_NORMAL; > +} > + > +static int raspberrypi_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, > + const struct pwm_state *state) > +{ > + struct raspberrypi_pwm *pc = to_raspberrypi_pwm(chip); > + unsigned int duty_cycle; > + int ret; > + You need to check for polarity here. > + if (!state->enabled) > + duty_cycle = 0; > + else > + duty_cycle = state->duty_cycle * RPI_PWM_MAX_DUTY / > + RPI_PWM_PERIOD_NS; > + > + if (duty_cycle == pc->duty_cycle) > + return 0; > + > + pc->duty_cycle = duty_cycle; > + ret = raspberrypi_pwm_set_property(pc->firmware, RPI_PWM_CUR_DUTY_REG, > + pc->duty_cycle); > + if (ret) { > + dev_err(chip->dev, "Failed to set duty cycle: %d\n", ret); > + return ret; > + } What happens if duty_cycle happens to be bigger than RPI_PWM_MAX_DUTY? I think the right thing to do here is: if (state->period < RPI_PWM_PERIOD_NS || state->polarity != PWM_POLARITY_NORMAL) return -EINVAL; if (!state->enabled) duty_cycle = 0 else if (state->duty_cycle < RPI_PWM_PERIOD_NS) duty_cycle = state->duty_cycle * RPI_PWM_MAX_DUTY / RPI_PWM_PERIOD_NS; else duty_cycle = RPI_PWM_MAX_DUTY; ret = raspberrypi_pwm_set_property(pc->firmware, RPI_PWM_CUR_DUTY_REG, pc->duty_cycle); if (ret) ... pc->duty_cycle = duty_cycle; > + > + ret = raspberrypi_pwm_set_property(pc->firmware, RPI_PWM_CUR_DUTY_REG, > + pc->duty_cycle); > + if (ret) { > + dev_err(chip->dev, "Failed to set default duty cycle: %d\n", ret); > + return ret; > + } Huh, why do you have to do this twice, just with different error messages? I assume you want to set RPI_PWM_DEF_DUTY_REG? What is the effect of writing this property? > + > + return 0; > +} > + > +static const struct pwm_ops raspberrypi_pwm_ops = { > + .get_state = raspberrypi_pwm_get_state, > + .apply = raspberrypi_pwm_apply, > + .owner = THIS_MODULE, > +}; > + > +static struct pwm_device *raspberrypi_pwm_xlate(struct pwm_chip *pc, > + const struct of_phandle_args *args) > +{ > + struct pwm_device *pwm; > + > + if (args->args[0] >= pc->npwm) > + return ERR_PTR(-EINVAL); > + > + pwm = pwm_request_from_chip(pc, args->args[0], NULL); > + if (IS_ERR(pwm)) > + return pwm; > + > + /* Firmwre won't let us change the period */ Firmware. > + pwm->args.period = RPI_PWM_PERIOD_NS; > + > + return pwm; > +} I think you don't need this function. Just fix up period in .apply(). > +static int raspberrypi_pwm_probe(struct platform_device *pdev) > +{ > + struct device_node *firmware_node; > + struct device *dev = &pdev->dev; > + struct rpi_firmware *firmware; > + struct raspberrypi_pwm *pc; What does "pc" stand for? I'd have used "rpipwm" or something similar. > + int ret; > + > + firmware_node = of_get_parent(dev->of_node); > + if (!firmware_node) { > + dev_err(dev, "Missing firmware node\n"); > + return -ENOENT; > + } > + > + firmware = rpi_firmware_get(firmware_node); > + of_node_put(firmware_node); > + if (!firmware) > + return -EPROBE_DEFER; I don't see a mechanism that prevents the driver providing the firmware going away while the PWM is still in use. > + pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); > + if (!pc) > + return -ENOMEM; > [...] > + > +static struct platform_driver raspberrypi_pwm_driver = { > + .driver = { > + .name = "raspberrypi-pwm", > + .of_match_table = raspberrypi_pwm_of_match, > + }, > + .probe = raspberrypi_pwm_probe, > + .remove = raspberrypi_pwm_remove, > +}; > +module_platform_driver(raspberrypi_pwm_driver); > + > +MODULE_AUTHOR("Nicolas Saenz Julienne <nsaenzjulienne@xxxxxxx>"); > +MODULE_DESCRIPTION("Raspberry Pi Firwmare Based PWM Bus Driver"); > +MODULE_LICENSE("GPL v2"); > + Please drop the empty line at the end of file. Best regards Uwe -- Pengutronix e.K. | Uwe Kleine-König | Industrial Linux Solutions | https://www.pengutronix.de/ |
Attachment:
signature.asc
Description: PGP signature