From: Nicolas Saenz Julienne <hidden> Date: 2021-01-18 12:38:21
The aim of this series is to add support to the fan found on RPi's PoE
HAT. Some commentary on the design can be found below. But the important
part to the people CC'd here not involved with PWM is that, in order to
achieve this properly, we also have to fix the firmware interface the
driver uses to communicate with the PWM bus (and many other low level
functions). Specifically, we have to make sure the firmware interface
isn't unbound while consumers are still up. So, patch #1 & #2 introduce
reference counting in the firmware interface driver and patches #3 to #8
update all firmware users. Patches #9 to #11 introduce the new PWM
driver.
I sent everything as a single series as the final version of the PWM
drivers depends on the firmware fixes, but I'll be happy to split this
into two separate series if you think it's better.
--- Original cover letter below ---
This series aims at adding support to RPi's official PoE HAT fan[1].
The HW setup is the following:
| Raspberry Pi | PoE HAT |
arm core -> Mailbox -> RPi co-processor -> I2C -> Atmel MCU -> PWM -> FAN
The arm cores have only access to the mailbox interface, as i2c0, even if
physically accessible, is to be used solely by the co-processor
(VideoCore 4/6).
This series implements a PWM bus, and has pwm-fan sitting on top of it as per
this discussion: https://lkml.org/lkml/2018/9/2/486. Although this design has a
series of shortcomings:
- It depends on a DT binding: it's not flexible if a new hat shows up with new
functionality, we're not 100% sure we'll be able to expand it without
breaking backwards compatibility. But without it we can't make use of DT
thermal-zones, which IMO is overkill.
- We're using pwm-fan, writing a hwmon driver would, again, give us more
flexibility, but it's not really needed at the moment.
I personally think that it's not worth the effort, it's unlikely we'll get
things right in advance. And ultimately, if the RPi people come up with
something new, we can always write a new driver/bindings from scratch (as in
not reusing previous code).
That said, I'm more than happy to change things if there is a consensus that
another design will do the trick.
[1] https://www.raspberrypi.org/blog/introducing-power-over-ethernet-poe-hat/
---
Changes since v6:
- Address PWM driver comments
Changes since v5:
- Small cleanups
- Add extra code comments
Changes since v4:
- Cleanup devm calls
- Rename compatible string so it's unique to the PoE HAT
Changes since v3:
- Split first patch, #1 introduces refcount, then #2 the devm function
- Fix touchscreen function
- Use kref
Changes since v2:
- Introduce devm_rpi_firmware_get()
- Small cleanups in PWM driver
Changes since v1:
- Address PWM driver changes
- Fix binding, now with 2 cells
Nicolas Saenz Julienne (11):
firmware: raspberrypi: Keep count of all consumers
firmware: raspberrypi: Introduce devm_rpi_firmware_get()
clk: bcm: rpi: Release firmware handle on unbind
gpio: raspberrypi-exp: Release firmware handle on unbind
reset: raspberrypi: Release firmware handle on unbind
soc: bcm: raspberrypi-power: Release firmware handle on unbind
staging: vchiq: Release firmware handle on unbind
input: raspberrypi-ts: Release firmware handle when not needed
dt-bindings: pwm: Add binding for RPi firmware PWM bus
DO NOT MERGE: ARM: dts: Add RPi's official PoE hat support
pwm: Add Raspberry Pi Firmware based PWM bus
.../arm/bcm/raspberrypi,bcm2835-firmware.yaml | 20 ++
arch/arm/boot/dts/bcm2711-rpi-4-b.dts | 54 +++++
drivers/clk/bcm/clk-raspberrypi.c | 2 +-
drivers/firmware/raspberrypi.c | 69 +++++-
drivers/gpio/gpio-raspberrypi-exp.c | 2 +-
drivers/input/touchscreen/raspberrypi-ts.c | 2 +-
drivers/pwm/Kconfig | 9 +
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-raspberrypi-poe.c | 220 ++++++++++++++++++
drivers/reset/reset-raspberrypi.c | 2 +-
drivers/soc/bcm/raspberrypi-power.c | 2 +-
.../interface/vchiq_arm/vchiq_arm.c | 2 +-
.../pwm/raspberrypi,firmware-poe-pwm.h | 13 ++
include/soc/bcm2835/raspberrypi-firmware.h | 10 +
14 files changed, 399 insertions(+), 9 deletions(-)
create mode 100644 drivers/pwm/pwm-raspberrypi-poe.c
create mode 100644 include/dt-bindings/pwm/raspberrypi,firmware-poe-pwm.h
--
2.29.2
From: Nicolas Saenz Julienne <hidden> Date: 2021-01-18 12:38:53
Use devm_rpi_firmware_get() so as to make sure we release RPi's firmware
interface when unbinding the device.
Signed-off-by: Nicolas Saenz Julienne <redacted>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Acked-by: Bartosz Golaszewski <redacted>
---
drivers/gpio/gpio-raspberrypi-exp.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
From: Nicolas Saenz Julienne <hidden> Date: 2021-01-18 12:40:28
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 <redacted>
---
Changes since v6:
- Use %pe
- Round divisions properly
- Use dev_err_probe()
- Pass check_patch
Changes since v3:
- Rename compatible string to be more explicit WRT to bus's limitations
Changes since v2:
- Use devm_rpi_firmware_get()
- Rename driver
- Small cleanups
Changes since v1:
- Use default pwm bindings and get rid of xlate() function
- Correct spelling errors
- Correct apply() function
- Round values
- Fix divisions in arm32 mode
- Small cleanups
drivers/pwm/Kconfig | 9 ++
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-raspberrypi-poe.c | 220 ++++++++++++++++++++++++++++++
3 files changed, 230 insertions(+)
create mode 100644 drivers/pwm/pwm-raspberrypi-poe.c
@@ -423,6 +423,15 @@ config PWM_PXATocompilethisdriverasamodule,chooseMhere:themodulewillbecalledpwm-pxa.+configPWM_RASPBERRYPI_POE+tristate"Raspberry Pi Firwmware PoE Hat PWM support"+# Make sure not 'y' when RASPBERRYPI_FIRMWARE is 'm'. This can only+# happen when COMPILE_TEST=y, hence the added !RASPBERRYPI_FIRMWARE.+depends onRASPBERRYPI_FIRMWARE||(COMPILE_TEST&&!RASPBERRYPI_FIRMWARE)+help+EnableRaspberryPifirmwarecontrollerPWMbususedtocontrolthe+officialRPIPoEhat+configPWM_RCARtristate"Renesas R-Car PWM support"depends onARCH_RENESAS||COMPILE_TEST
@@ -0,0 +1,220 @@+// SPDX-License-Identifier: GPL-2.0+/*+*Copyright2020NicolasSaenzJulienne<nsaenzjulienne@suse.de>+*FormoreinformationonRaspberryPi'sPoEhatsee:+*https://www.raspberrypi.org/products/poe-hat/+*+*Limitations:+*-Nodisablebit,soadisabledPWMissimulatedbyduty_cycle0+*-Onlynormalpolarity+*-Fixed12.5kHzperiod+*+*ThecurrentperiodiscompletedwhenHWisreconfigured.+*/++#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-poe-pwm.h>++#define RPI_PWM_MAX_DUTY 255+#define RPI_PWM_PERIOD_NS 80000 /* 12.5 kHz */++#define RPI_PWM_CUR_DUTY_REG 0x0+#define RPI_PWM_DEF_DUTY_REG 0x1++structraspberrypi_pwm{+structrpi_firmware*firmware;+structpwm_chipchip;+unsignedintduty_cycle;+};++structraspberrypi_pwm_prop{+__le32reg;+__le32val;+__le32ret;+}__packed;++staticinline+structraspberrypi_pwm*raspberrypi_pwm_from_chip(structpwm_chip*chip)+{+returncontainer_of(chip,structraspberrypi_pwm,chip);+}++staticintraspberrypi_pwm_set_property(structrpi_firmware*firmware,+u32reg,u32val)+{+structraspberrypi_pwm_propmsg={+.reg=cpu_to_le32(reg),+.val=cpu_to_le32(val),+};+intret;++ret=rpi_firmware_property(firmware,RPI_FIRMWARE_SET_POE_HAT_VAL,+&msg,sizeof(msg));+if(ret)+returnret;+if(msg.ret)+return-EIO;++return0;+}++staticintraspberrypi_pwm_get_property(structrpi_firmware*firmware,+u32reg,u32*val)+{+structraspberrypi_pwm_propmsg={+.reg=reg+};+intret;++ret=rpi_firmware_property(firmware,RPI_FIRMWARE_GET_POE_HAT_VAL,+&msg,sizeof(msg));+if(ret)+returnret;+if(msg.ret)+return-EIO;++*val=le32_to_cpu(msg.val);++return0;+}++staticvoidraspberrypi_pwm_get_state(structpwm_chip*chip,+structpwm_device*pwm,+structpwm_state*state)+{+structraspberrypi_pwm*rpipwm=raspberrypi_pwm_from_chip(chip);++state->period=RPI_PWM_PERIOD_NS;+state->duty_cycle=DIV_ROUND_UP(rpipwm->duty_cycle*RPI_PWM_PERIOD_NS,+RPI_PWM_MAX_DUTY);+state->enabled=!!(rpipwm->duty_cycle);+state->polarity=PWM_POLARITY_NORMAL;+}++staticintraspberrypi_pwm_apply(structpwm_chip*chip,structpwm_device*pwm,+conststructpwm_state*state)+{+structraspberrypi_pwm*rpipwm=raspberrypi_pwm_from_chip(chip);+unsignedintduty_cycle;+intret;++if(state->period<RPI_PWM_PERIOD_NS||+state->polarity!=PWM_POLARITY_NORMAL)+return-EINVAL;++if(!state->enabled)+duty_cycle=0;+elseif(state->duty_cycle<RPI_PWM_PERIOD_NS)+duty_cycle=DIV_ROUND_DOWN_ULL(state->duty_cycle*RPI_PWM_MAX_DUTY,+RPI_PWM_PERIOD_NS);+else+duty_cycle=RPI_PWM_MAX_DUTY;++if(duty_cycle==rpipwm->duty_cycle)+return0;++ret=raspberrypi_pwm_set_property(rpipwm->firmware,RPI_PWM_CUR_DUTY_REG,+duty_cycle);+if(ret){+dev_err(chip->dev,"Failed to set duty cycle: %pe\n",+ERR_PTR(ret));+returnret;+}++/*+*Thissetsthedefaultdutycycleafterresettingtheboard,we+*updatediteverytimetomimicRaspberryPi'sdownstream'sdriver+*behaviour.+*/+ret=raspberrypi_pwm_set_property(rpipwm->firmware,RPI_PWM_DEF_DUTY_REG,+duty_cycle);+if(ret){+dev_err(chip->dev,"Failed to set default duty cycle: %pe\n",+ERR_PTR(ret));+returnret;+}++rpipwm->duty_cycle=duty_cycle;++return0;+}++staticconststructpwm_opsraspberrypi_pwm_ops={+.get_state=raspberrypi_pwm_get_state,+.apply=raspberrypi_pwm_apply,+.owner=THIS_MODULE,+};++staticintraspberrypi_pwm_probe(structplatform_device*pdev)+{+structdevice_node*firmware_node;+structdevice*dev=&pdev->dev;+structrpi_firmware*firmware;+structraspberrypi_pwm*rpipwm;+intret;++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)+returndev_err_probe(dev,-EPROBE_DEFER,+"Failed to get firmware handle\n");++rpipwm=devm_kzalloc(&pdev->dev,sizeof(*rpipwm),GFP_KERNEL);+if(!rpipwm)+return-ENOMEM;++rpipwm->firmware=firmware;+rpipwm->chip.dev=dev;+rpipwm->chip.ops=&raspberrypi_pwm_ops;+rpipwm->chip.base=-1;+rpipwm->chip.npwm=RASPBERRYPI_FIRMWARE_PWM_NUM;++platform_set_drvdata(pdev,rpipwm);++ret=raspberrypi_pwm_get_property(rpipwm->firmware,RPI_PWM_CUR_DUTY_REG,+&rpipwm->duty_cycle);+if(ret){+dev_err(dev,"Failed to get duty cycle: %pe\n",ERR_PTR(ret));+returnret;+}++returnpwmchip_add(&rpipwm->chip);+}++staticintraspberrypi_pwm_remove(structplatform_device*pdev)+{+structraspberrypi_pwm*rpipwm=platform_get_drvdata(pdev);++returnpwmchip_remove(&rpipwm->chip);+}++staticconststructof_device_idraspberrypi_pwm_of_match[]={+{.compatible="raspberrypi,firmware-poe-pwm",},+{}+};+MODULE_DEVICE_TABLE(of,raspberrypi_pwm_of_match);++staticstructplatform_driverraspberrypi_pwm_driver={+.driver={+.name="raspberrypi-poe-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@suse.de>");+MODULE_DESCRIPTION("Raspberry Pi Firmware Based PWM Bus Driver");+MODULE_LICENSE("GPL v2");
From: Nicolas Saenz Julienne <hidden> Date: 2021-01-18 13:12:39
The PWM bus controlling the fan in RPi's official PoE hat can only be
controlled by the board's co-processor.
Signed-off-by: Nicolas Saenz Julienne <redacted>
Reviewed-by: Rob Herring <robh@kernel.org>
---
Changes since v4:
- Rename compatible string to be more explicit with the bus' limitations
Changes since v3:
- Fix example
Changes since v1:
- Update bindings to use 2 #pwm-cells
.../arm/bcm/raspberrypi,bcm2835-firmware.yaml | 20 +++++++++++++++++++
.../pwm/raspberrypi,firmware-poe-pwm.h | 13 ++++++++++++
2 files changed, 33 insertions(+)
create mode 100644 include/dt-bindings/pwm/raspberrypi,firmware-poe-pwm.h
@@ -64,6 +64,21 @@ properties:-compatible-"#reset-cells"+pwm:+type:object++properties:+compatible:+const:raspberrypi,firmware-poe-pwm++"#pwm-cells":+# See pwm.yaml in this directory for a description of the cells format.+const:2++required:+-compatible+-"#pwm-cells"+additionalProperties:falserequired:
From: Nicolas Saenz Julienne <hidden> Date: 2021-01-18 13:13:19
This is an example on how to enable the fan on top of RPi's official PoE
hat.
Signed-off-by: Nicolas Saenz Julienne <redacted>
---
arch/arm/boot/dts/bcm2711-rpi-4-b.dts | 54 +++++++++++++++++++++++++++
1 file changed, 54 insertions(+)
From: Nicolas Saenz Julienne <hidden> Date: 2021-01-18 13:13:39
There is no use for the firmware interface after getting the touch
buffer address, so release it.
Signed-off-by: Nicolas Saenz Julienne <redacted>
Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
---
Changes since v5:
- Correct commit message
Changes since v3:
- Release firmware handle in probe function
Changes since v2:
- Use devm_rpi_firmware_get(), instead of remove function
drivers/input/touchscreen/raspberrypi-ts.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
@@ -160,7 +160,7 @@ static int rpi_ts_probe(struct platform_device *pdev)touchbuf=(u32)ts->fw_regs_phys;error=rpi_firmware_property(fw,RPI_FIRMWARE_FRAMEBUFFER_SET_TOUCHBUF,&touchbuf,sizeof(touchbuf));-+rpi_firmware_put(fw);if(error||touchbuf!=0){dev_warn(dev,"Failed to set touchbuf, %d\n",error);returnerror;
From: Nicolas Saenz Julienne <hidden> Date: 2021-01-18 13:14:09
Use devm_rpi_firmware_get() so as to make sure we release RPi's firmware
interface when unbinding the device.
Signed-off-by: Nicolas Saenz Julienne <redacted>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
---
drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
From: Nicolas Saenz Julienne <hidden> Date: 2021-01-18 13:14:19
Use devm_rpi_firmware_get() so as to make sure we release RPi's firmware
interface when unbinding the device.
Signed-off-by: Nicolas Saenz Julienne <redacted>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
---
drivers/reset/reset-raspberrypi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
From: Nicolas Saenz Julienne <hidden> Date: 2021-01-18 13:14:30
Use devm_rpi_firmware_get() so as to make sure we release RPi's firmware
interface when unbinding the device.
Signed-off-by: Nicolas Saenz Julienne <redacted>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
---
drivers/soc/bcm/raspberrypi-power.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
From: Nicolas Saenz Julienne <hidden> Date: 2021-01-18 13:26:23
When unbinding the firmware device we need to make sure it has no
consumers left. Otherwise we'd leave them with a firmware handle
pointing at freed memory.
Keep a reference count of all consumers and introduce rpi_firmware_put()
which will permit automatically decrease the reference count upon
unbinding consumer drivers.
Suggested-by: Uwe Kleine-König <redacted>
Signed-off-by: Nicolas Saenz Julienne <redacted>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Reviewed-by: Stephen Boyd <sboyd@kernel.org>
Reviewed-by: Bartosz Golaszewski <redacted>
---
Changes since v5:
- Add comment to avoid people blindly switching the memory allocation
to the devm variant.
- Fix function documentation as per Florian's comment.
Changes since v3:
- Use kref instead of waiting on refcount
drivers/firmware/raspberrypi.c | 40 ++++++++++++++++++++--
include/soc/bcm2835/raspberrypi-firmware.h | 2 ++
2 files changed, 39 insertions(+), 3 deletions(-)
From: Nicolas Saenz Julienne <hidden> Date: 2021-01-18 13:26:24
Use devm_rpi_firmware_get() so as to make sure we release RPi's firmware
interface when unbinding the device.
Signed-off-by: Nicolas Saenz Julienne <redacted>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
Acked-by: Stephen Boyd <sboyd@kernel.org>
drivers/clk/bcm/clk-raspberrypi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
On Mon, Jan 18, 2021 at 01:32:40PM +0100, Nicolas Saenz Julienne wrote:
Use devm_rpi_firmware_get() so as to make sure we release RPi's firmware
interface when unbinding the device.
Signed-off-by: Nicolas Saenz Julienne <redacted>
Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
---
drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
From: Nicolas Saenz Julienne <hidden> Date: 2021-02-08 21:38:28
On Mon, 2021-01-18 at 13:32 +0100, 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 <redacted>
---
From: Nicolas Saenz Julienne <hidden> Date: 2021-03-09 09:59:44
On Mon, 2021-01-18 at 13:32 +0100, 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 <redacted>
---
ping :)
quoted hunk
Changes since v6:
- Use %pe
- Round divisions properly
- Use dev_err_probe()
- Pass check_patch
Changes since v3:
- Rename compatible string to be more explicit WRT to bus's limitations
Changes since v2:
- Use devm_rpi_firmware_get()
- Rename driver
- Small cleanups
Changes since v1:
- Use default pwm bindings and get rid of xlate() function
- Correct spelling errors
- Correct apply() function
- Round values
- Fix divisions in arm32 mode
- Small cleanups
drivers/pwm/Kconfig | 9 ++
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-raspberrypi-poe.c | 220 ++++++++++++++++++++++++++++++
3 files changed, 230 insertions(+)
create mode 100644 drivers/pwm/pwm-raspberrypi-poe.c
To compile this driver as a module, choose M here: the module
will be called pwm-pxa.
+config PWM_RASPBERRYPI_POE
+ tristate "Raspberry Pi Firwmware PoE Hat PWM support"
+ # Make sure not 'y' when RASPBERRYPI_FIRMWARE is 'm'. This can only
+ # happen when COMPILE_TEST=y, hence the added !RASPBERRYPI_FIRMWARE.
+ depends on RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE)
+ 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
@@ -0,0 +1,220 @@+// SPDX-License-Identifier: GPL-2.0+/*+*Copyright2020NicolasSaenzJulienne<nsaenzjulienne@suse.de>+*FormoreinformationonRaspberryPi'sPoEhatsee:+*https://www.raspberrypi.org/products/poe-hat/+*+*Limitations:+*-Nodisablebit,soadisabledPWMissimulatedbyduty_cycle0+*-Onlynormalpolarity+*-Fixed12.5kHzperiod+*+*ThecurrentperiodiscompletedwhenHWisreconfigured.+*/++#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-poe-pwm.h>++#define RPI_PWM_MAX_DUTY 255+#define RPI_PWM_PERIOD_NS 80000 /* 12.5 kHz */++#define RPI_PWM_CUR_DUTY_REG 0x0+#define RPI_PWM_DEF_DUTY_REG 0x1++structraspberrypi_pwm{+structrpi_firmware*firmware;+structpwm_chipchip;+unsignedintduty_cycle;+};++structraspberrypi_pwm_prop{+__le32reg;+__le32val;+__le32ret;+}__packed;++staticinline+structraspberrypi_pwm*raspberrypi_pwm_from_chip(structpwm_chip*chip)+{+returncontainer_of(chip,structraspberrypi_pwm,chip);+}++staticintraspberrypi_pwm_set_property(structrpi_firmware*firmware,+u32reg,u32val)+{+structraspberrypi_pwm_propmsg={+.reg=cpu_to_le32(reg),+.val=cpu_to_le32(val),+};+intret;++ret=rpi_firmware_property(firmware,RPI_FIRMWARE_SET_POE_HAT_VAL,+&msg,sizeof(msg));+if(ret)+returnret;+if(msg.ret)+return-EIO;++return0;+}++staticintraspberrypi_pwm_get_property(structrpi_firmware*firmware,+u32reg,u32*val)+{+structraspberrypi_pwm_propmsg={+.reg=reg+};+intret;++ret=rpi_firmware_property(firmware,RPI_FIRMWARE_GET_POE_HAT_VAL,+&msg,sizeof(msg));+if(ret)+returnret;+if(msg.ret)+return-EIO;++*val=le32_to_cpu(msg.val);++return0;+}++staticvoidraspberrypi_pwm_get_state(structpwm_chip*chip,+structpwm_device*pwm,+structpwm_state*state)+{+structraspberrypi_pwm*rpipwm=raspberrypi_pwm_from_chip(chip);++state->period=RPI_PWM_PERIOD_NS;+state->duty_cycle=DIV_ROUND_UP(rpipwm->duty_cycle*RPI_PWM_PERIOD_NS,+RPI_PWM_MAX_DUTY);+state->enabled=!!(rpipwm->duty_cycle);+state->polarity=PWM_POLARITY_NORMAL;+}++staticintraspberrypi_pwm_apply(structpwm_chip*chip,structpwm_device*pwm,+conststructpwm_state*state)+{+structraspberrypi_pwm*rpipwm=raspberrypi_pwm_from_chip(chip);+unsignedintduty_cycle;+intret;++if(state->period<RPI_PWM_PERIOD_NS||+state->polarity!=PWM_POLARITY_NORMAL)+return-EINVAL;++if(!state->enabled)+duty_cycle=0;+elseif(state->duty_cycle<RPI_PWM_PERIOD_NS)+duty_cycle=DIV_ROUND_DOWN_ULL(state->duty_cycle*RPI_PWM_MAX_DUTY,+RPI_PWM_PERIOD_NS);+else+duty_cycle=RPI_PWM_MAX_DUTY;++if(duty_cycle==rpipwm->duty_cycle)+return0;++ret=raspberrypi_pwm_set_property(rpipwm->firmware,RPI_PWM_CUR_DUTY_REG,+duty_cycle);+if(ret){+dev_err(chip->dev,"Failed to set duty cycle: %pe\n",+ERR_PTR(ret));+returnret;+}++/*+*Thissetsthedefaultdutycycleafterresettingtheboard,we+*updatediteverytimetomimicRaspberryPi'sdownstream'sdriver+*behaviour.+*/+ret=raspberrypi_pwm_set_property(rpipwm->firmware,RPI_PWM_DEF_DUTY_REG,+duty_cycle);+if(ret){+dev_err(chip->dev,"Failed to set default duty cycle: %pe\n",+ERR_PTR(ret));+returnret;+}++rpipwm->duty_cycle=duty_cycle;++return0;+}++staticconststructpwm_opsraspberrypi_pwm_ops={+.get_state=raspberrypi_pwm_get_state,+.apply=raspberrypi_pwm_apply,+.owner=THIS_MODULE,+};++staticintraspberrypi_pwm_probe(structplatform_device*pdev)+{+structdevice_node*firmware_node;+structdevice*dev=&pdev->dev;+structrpi_firmware*firmware;+structraspberrypi_pwm*rpipwm;+intret;++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)+returndev_err_probe(dev,-EPROBE_DEFER,+"Failed to get firmware handle\n");++rpipwm=devm_kzalloc(&pdev->dev,sizeof(*rpipwm),GFP_KERNEL);+if(!rpipwm)+return-ENOMEM;++rpipwm->firmware=firmware;+rpipwm->chip.dev=dev;+rpipwm->chip.ops=&raspberrypi_pwm_ops;+rpipwm->chip.base=-1;+rpipwm->chip.npwm=RASPBERRYPI_FIRMWARE_PWM_NUM;++platform_set_drvdata(pdev,rpipwm);++ret=raspberrypi_pwm_get_property(rpipwm->firmware,RPI_PWM_CUR_DUTY_REG,+&rpipwm->duty_cycle);+if(ret){+dev_err(dev,"Failed to get duty cycle: %pe\n",ERR_PTR(ret));+returnret;+}++returnpwmchip_add(&rpipwm->chip);+}++staticintraspberrypi_pwm_remove(structplatform_device*pdev)+{+structraspberrypi_pwm*rpipwm=platform_get_drvdata(pdev);++returnpwmchip_remove(&rpipwm->chip);+}++staticconststructof_device_idraspberrypi_pwm_of_match[]={+{.compatible="raspberrypi,firmware-poe-pwm",},+{}+};+MODULE_DEVICE_TABLE(of,raspberrypi_pwm_of_match);++staticstructplatform_driverraspberrypi_pwm_driver={+.driver={+.name="raspberrypi-poe-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@suse.de>");+MODULE_DESCRIPTION("Raspberry Pi Firmware Based PWM Bus Driver");+MODULE_LICENSE("GPL v2");
+ */
+
[...]
+static int raspberrypi_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct raspberrypi_pwm *rpipwm = raspberrypi_pwm_from_chip(chip);
+ unsigned int duty_cycle;
+ int ret;
+
+ 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 = DIV_ROUND_DOWN_ULL(state->duty_cycle * RPI_PWM_MAX_DUTY,
+ RPI_PWM_PERIOD_NS);
+ else
+ duty_cycle = RPI_PWM_MAX_DUTY;
+
+ if (duty_cycle == rpipwm->duty_cycle)
+ return 0;
+
+ ret = raspberrypi_pwm_set_property(rpipwm->firmware, RPI_PWM_CUR_DUTY_REG,
+ duty_cycle);
+ if (ret) {
+ dev_err(chip->dev, "Failed to set duty cycle: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+
+ /*
+ * This sets the default duty cycle after resetting the board, we
+ * updated it every time to mimic Raspberry Pi's downstream's driver
+ * behaviour.
+ */
+ ret = raspberrypi_pwm_set_property(rpipwm->firmware, RPI_PWM_DEF_DUTY_REG,
+ duty_cycle);
+ if (ret) {
+ dev_err(chip->dev, "Failed to set default duty cycle: %pe\n",
+ ERR_PTR(ret));
+ return ret;
This only has an effect for the next reboot, right? If so I wonder if it
is a good idea in general. (Think: The current PWM setting enables a
motor that makes a self-driving car move at 100 km/h. Consider the rpi
crashes, do I want to car to pick up driving 100 km/h at power up even
before Linux is up again?) And if we agree it's a good idea: Should
raspberrypi_pwm_apply return 0 if setting the duty cycle succeeded and
only setting the default didn't?
Other than that the patch looks fine.
Best regards
Uwe
--
Pengutronix e.K. | Uwe Kleine-König |
Industrial Linux Solutions | https://www.pengutronix.de/ |
From: Nicolas Saenz Julienne <hidden> Date: 2021-03-11 13:02:06
Hi Uwe,
thanks for taking the time to look into this. :)
On Wed, 2021-03-10 at 12:50 +0100, Uwe Kleine-König wrote:
Hello Nicolas,
On Mon, Jan 18, 2021 at 01:32:44PM +0100, Nicolas Saenz Julienne wrote:
[...]
quoted
+ /*
+ * This sets the default duty cycle after resetting the board, we
+ * updated it every time to mimic Raspberry Pi's downstream's driver
+ * behaviour.
+ */
+ ret = raspberrypi_pwm_set_property(rpipwm->firmware, RPI_PWM_DEF_DUTY_REG,
+ duty_cycle);
+ if (ret) {
+ dev_err(chip->dev, "Failed to set default duty cycle: %pe\n",
+ ERR_PTR(ret));
+ return ret;
This only has an effect for the next reboot, right?
It effects all reboots until it's further changed.
If so I wonder if it is a good idea in general. (Think: The current PWM
setting enables a motor that makes a self-driving car move at 100 km/h.
Consider the rpi crashes, do I want to car to pick up driving 100 km/h at
power up even before Linux is up again?)
I get your point. But this isn't used as a general purpose PWM. For now the
interface is solely there to drive a PWM fan that's arguably harmless. This
doesn't mean that the RPi foundation will not reuse the firmware interface for
other means in the future. In such case we can always use a new DT compatible
and bypass this feature (the current DT string is
'raspberrypi,firmware-poe-pwm', which is specific to this use-case).
My aim here is to be on par feature wise with RPi's downstream implementation.
So as for them to be able to use it as is and avoid duplication. Now, if this
is blocking the driver from being merged, I'd rather remove it. It'll be a
patch for the downstream kernel to maintain, but better than nothing.
And if we agree it's a good idea: Should raspberrypi_pwm_apply return 0 if
setting the duty cycle succeeded and only setting the default didn't?
Good point. I don't think so. We'd be also missing on the following by
returning early:
rpipwm->duty_cycle = duty_cycle;
I propose to change it to a 'best effort' approach, if it fails, log it and
continue successfully.
Regards,
Nicolas
Hello Nicolas,
On Thu, Mar 11, 2021 at 02:01:00PM +0100, Nicolas Saenz Julienne wrote:
On Wed, 2021-03-10 at 12:50 +0100, Uwe Kleine-König wrote:
quoted
On Mon, Jan 18, 2021 at 01:32:44PM +0100, Nicolas Saenz Julienne wrote:
[...]
quoted
quoted
+ /*
+ * This sets the default duty cycle after resetting the board, we
+ * updated it every time to mimic Raspberry Pi's downstream's driver
+ * behaviour.
+ */
+ ret = raspberrypi_pwm_set_property(rpipwm->firmware, RPI_PWM_DEF_DUTY_REG,
+ duty_cycle);
+ if (ret) {
+ dev_err(chip->dev, "Failed to set default duty cycle: %pe\n",
+ ERR_PTR(ret));
+ return ret;
This only has an effect for the next reboot, right?
It effects all reboots until it's further changed.
quoted
If so I wonder if it is a good idea in general. (Think: The current PWM
setting enables a motor that makes a self-driving car move at 100 km/h.
Consider the rpi crashes, do I want to car to pick up driving 100 km/h at
power up even before Linux is up again?)
I get your point. But this isn't used as a general purpose PWM. For now the
interface is solely there to drive a PWM fan that's arguably harmless. This
doesn't mean that the RPi foundation will not reuse the firmware interface for
other means in the future. In such case we can always use a new DT compatible
and bypass this feature (the current DT string is
'raspberrypi,firmware-poe-pwm', which is specific to this use-case).
My aim here is to be on par feature wise with RPi's downstream implementation.
Just because the downstream kernel does it should not be the (single)
reason to do that. My gut feeling is: For a motor restoring the PWM
config on reboot is bad and for a fan it doesn't really hurt if it
doesn't restart automatically. So I'd prefer to to drop this feature.
Best regards
Uwe
--
Pengutronix e.K. | Uwe Kleine-König |
Industrial Linux Solutions | https://www.pengutronix.de/ |
From: Nicolas Saenz Julienne <hidden> Date: 2021-03-11 13:42:11
On Thu, 2021-03-11 at 14:18 +0100, Uwe Kleine-König wrote:
Hello Nicolas,
On Thu, Mar 11, 2021 at 02:01:00PM +0100, Nicolas Saenz Julienne wrote:
quoted
On Wed, 2021-03-10 at 12:50 +0100, Uwe Kleine-König wrote:
quoted
On Mon, Jan 18, 2021 at 01:32:44PM +0100, Nicolas Saenz Julienne wrote:
[...]
quoted
quoted
+ /*
+ * This sets the default duty cycle after resetting the board, we
+ * updated it every time to mimic Raspberry Pi's downstream's driver
+ * behaviour.
+ */
+ ret = raspberrypi_pwm_set_property(rpipwm->firmware, RPI_PWM_DEF_DUTY_REG,
+ duty_cycle);
+ if (ret) {
+ dev_err(chip->dev, "Failed to set default duty cycle: %pe\n",
+ ERR_PTR(ret));
+ return ret;
This only has an effect for the next reboot, right?
It effects all reboots until it's further changed.
quoted
If so I wonder if it is a good idea in general. (Think: The current PWM
setting enables a motor that makes a self-driving car move at 100 km/h.
Consider the rpi crashes, do I want to car to pick up driving 100 km/h at
power up even before Linux is up again?)
I get your point. But this isn't used as a general purpose PWM. For now the
interface is solely there to drive a PWM fan that's arguably harmless. This
doesn't mean that the RPi foundation will not reuse the firmware interface for
other means in the future. In such case we can always use a new DT compatible
and bypass this feature (the current DT string is
'raspberrypi,firmware-poe-pwm', which is specific to this use-case).
My aim here is to be on par feature wise with RPi's downstream implementation.
Just because the downstream kernel does it should not be the (single)
reason to do that. My gut feeling is: For a motor restoring the PWM
config on reboot is bad and for a fan it doesn't really hurt if it
doesn't restart automatically. So I'd prefer to to drop this feature.
Fair enough, I'll remove it then.
Regards,
Nicolas