From 2cac64c0bcaa2cbd33d33303e4620bbd8989d151 Mon Sep 17 00:00:00 2001 From: coolsnowwolf Date: Tue, 11 Nov 2025 11:02:16 +0800 Subject: [PATCH] rockchip: backport pending PWMv4 support for 6.12 --- target/linux/rockchip/armv8/config-6.12 | 2 + ...> 042-01-v6.13-pwm-Add-more-locking.patch} | 72 +- ...m-New-abstraction-for-PWM-waveforms.patch} | 10 +- ...onsumer-API-functions-for-waveforms.patch} | 4 +- ...m-Add-tracing-for-waveform-callbacks.patch | 236 +++++++ ...-v6.13-pwm-Reorder-symbols-in-core.c.patch | 367 +++++++++++ ...2-06-v6.14-compiler-h-add-const_true.patch | 117 ++++ ...-callbacks-exist-before-calling-them.patch | 99 +++ ...ade-path-to-pwm-cells-3-for-users-of.patch | 61 ++ ...form-succeed-even-if-lowlevel-driver.patch | 71 ++ ...o-stricter-return-value-checking-for.patch | 54 ++ ...-pwm-Better-document-return-value-of.patch | 38 ++ ...ious-formatting-issues-in-kernel-doc.patch | 157 +++++ ...eform_might_sleep-fail-for-exact-but.patch | 64 ++ ...orm_might_sleep-return-0-instead-of-.patch | 58 ++ ...-the-procedure-used-to-pick-a-hardwa.patch | 59 ++ ...ardware-specific-bitfield-operations.patch | 113 ++++ ...62-01-mfd-Add-Rockchip-mfpwm-driver.patch} | 619 +++++------------- ...62-02-pwm-Add-rockchip-PWMv4-driver.patch} | 279 ++++---- ...ter-Add-rockchip-pwm-capture-driver.patch} | 204 +++--- ...-new-binding-for-rockchip-rk3576-pwm.patch | 119 ---- ...der-for-things-shared-across-drivers.patch | 84 --- ...4-add-device-set-of-node-from-parent.patch | 11 - 23 files changed, 1963 insertions(+), 935 deletions(-) rename target/linux/rockchip/patches-6.12/{340-01-pwm-add-more-locking.patch => 042-01-v6.13-pwm-Add-more-locking.patch} (66%) rename target/linux/rockchip/patches-6.12/{340-02-pwm-new-abstraction-for-pwm-waveforms.patch => 042-02-v6.13-pwm-New-abstraction-for-PWM-waveforms.patch} (98%) rename target/linux/rockchip/patches-6.12/{340-03-pwm-provide-new-consumer-API-functions-for-waveforms.patch => 042-03-v6.13-pwm-Provide-new-consumer-API-functions-for-waveforms.patch} (98%) create mode 100644 target/linux/rockchip/patches-6.12/042-04-v6.13-pwm-Add-tracing-for-waveform-callbacks.patch create mode 100644 target/linux/rockchip/patches-6.12/042-05-v6.13-pwm-Reorder-symbols-in-core.c.patch create mode 100644 target/linux/rockchip/patches-6.12/042-06-v6.14-compiler-h-add-const_true.patch create mode 100644 target/linux/rockchip/patches-6.12/042-07-v6.14-pwm-Ensure-callbacks-exist-before-calling-them.patch create mode 100644 target/linux/rockchip/patches-6.12/042-08-v6.15-pwm-Add-upgrade-path-to-pwm-cells-3-for-users-of.patch create mode 100644 target/linux/rockchip/patches-6.12/042-09-v6.15-pwm-Let-pwm_set_waveform-succeed-even-if-lowlevel-driver.patch create mode 100644 target/linux/rockchip/patches-6.12/042-10-v6.16-pwm-Do-stricter-return-value-checking-for.patch create mode 100644 target/linux/rockchip/patches-6.12/042-11-v6.16-pwm-Better-document-return-value-of.patch create mode 100644 target/linux/rockchip/patches-6.12/042-12-v6.16-pwm-Fix-various-formatting-issues-in-kernel-doc.patch create mode 100644 target/linux/rockchip/patches-6.12/042-13-v6.16-pwm-Let-pwm_set_waveform_might_sleep-fail-for-exact-but.patch create mode 100644 target/linux/rockchip/patches-6.12/042-14-v6.16-pwm-Let-pwm_set_waveform_might_sleep-return-0-instead-of-.patch create mode 100644 target/linux/rockchip/patches-6.12/042-15-v6.16-pwm-Formally-describe-the-procedure-used-to-pick-a-hardwa.patch create mode 100644 target/linux/rockchip/patches-6.12/043-v6.18-bitmap-introduce-hardware-specific-bitfield-operations.patch rename target/linux/rockchip/patches-6.12/{341-04-soc-rockchip-add-mfpwm-driver.patch => 162-01-mfd-Add-Rockchip-mfpwm-driver.patch} (61%) rename target/linux/rockchip/patches-6.12/{341-05-pwm-add-rockchip-pwmv4-driver.patch => 162-02-pwm-Add-rockchip-PWMv4-driver.patch} (54%) rename target/linux/rockchip/patches-6.12/{341-06-counter-add-rockchip-pwm-capture-driver.patch => 162-03-counter-Add-rockchip-pwm-capture-driver.patch} (68%) delete mode 100644 target/linux/rockchip/patches-6.12/341-02-dt-bindings-pwm-add-a-new-binding-for-rockchip-rk3576-pwm.patch delete mode 100644 target/linux/rockchip/patches-6.12/341-03-soc-rockchip-add-utils-header-for-things-shared-across-drivers.patch delete mode 100644 target/linux/rockchip/patches-6.12/341-07-pwm-pwmv4-add-device-set-of-node-from-parent.patch diff --git a/target/linux/rockchip/armv8/config-6.12 b/target/linux/rockchip/armv8/config-6.12 index b2055e1a4..3b4b33bb0 100644 --- a/target/linux/rockchip/armv8/config-6.12 +++ b/target/linux/rockchip/armv8/config-6.12 @@ -404,6 +404,7 @@ CONFIG_MFD_CORE=y CONFIG_MFD_RK8XX=y CONFIG_MFD_RK8XX_I2C=y CONFIG_MFD_RK8XX_SPI=y +CONFIG_MFD_ROCKCHIP_MFPWM=y CONFIG_MFD_SYSCON=y CONFIG_MIGRATION=y CONFIG_MMC=y @@ -587,6 +588,7 @@ CONFIG_ROCKCHIP_MBOX=y CONFIG_ROCKCHIP_MFPWM=y CONFIG_ROCKCHIP_PHY=y CONFIG_ROCKCHIP_PM_DOMAINS=y +CONFIG_ROCKCHIP_PWM_CAPTURE=y CONFIG_ROCKCHIP_THERMAL=y CONFIG_ROCKCHIP_TIMER=y CONFIG_RODATA_FULL_DEFAULT_ENABLED=y diff --git a/target/linux/rockchip/patches-6.12/340-01-pwm-add-more-locking.patch b/target/linux/rockchip/patches-6.12/042-01-v6.13-pwm-Add-more-locking.patch similarity index 66% rename from target/linux/rockchip/patches-6.12/340-01-pwm-add-more-locking.patch rename to target/linux/rockchip/patches-6.12/042-01-v6.13-pwm-Add-more-locking.patch index 18a3a1f4a..ecb604c15 100644 --- a/target/linux/rockchip/patches-6.12/340-01-pwm-add-more-locking.patch +++ b/target/linux/rockchip/patches-6.12/042-01-v6.13-pwm-Add-more-locking.patch @@ -1,8 +1,35 @@ -Signed-off-by: Uwe Kleine-König +From 1cc2e1faafb3b5a2be25112559bdb495736b5af7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= +Date: Fri, 20 Sep 2024 10:57:57 +0200 +Subject: [PATCH] pwm: Add more locking +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This ensures that a pwm_chip that has no corresponding driver isn't used +and that a driver doesn't go away while a callback is still running. + +In the presence of device links this isn't necessary yet (so this is no +fix) but for pwm character device support this is needed. + +To not serialize all pwm_apply_state() calls, this introduces a per chip +lock. An additional complication is that for atomic chips a mutex cannot +be used (as pwm_apply_atomic() must not sleep) and a spinlock cannot be +held while calling an operation for a sleeping chip. So depending on the +chip being atomic or not a spinlock or a mutex is used. + +An additional change implemented here is that on driver remove the +.free() callback is called for each requested pwm_device. This is the +right time because later (e.g. when the consumer calls pwm_put()) the +free function is (maybe) not available any more. + +Signed-off-by: Uwe Kleine-K?nig +Link: https://lore.kernel.org/r/026aa891c8270a11723a1ba7e4256f456f7e1e86.1726819463.git.u.kleine-koenig@baylibre.com +Signed-off-by: Uwe Kleine-K?nig --- - drivers/pwm/core.c | 95 +++++++++++++++++++++++++++++++++++++++++---- - include/linux/pwm.h | 13 +++++++ - 2 files changed, 100 insertions(+), 8 deletions(-) + drivers/pwm/core.c | 100 ++++++++++++++++++++++++++++++++++++++++---- + include/linux/pwm.h | 13 ++++++ + 2 files changed, 105 insertions(+), 8 deletions(-) --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -71,8 +98,15 @@ Signed-off-by: Uwe Kleine-König return __pwm_apply(pwm, state); } EXPORT_SYMBOL_GPL(pwm_apply_atomic); -@@ -340,6 +371,11 @@ static int pwm_capture(struct pwm_device +@@ -338,8 +369,18 @@ static int pwm_capture(struct pwm_device + if (!ops->capture) + return -ENOSYS; ++ /* ++ * Holding the pwm_lock is probably not needed. If you use pwm_capture() ++ * and you're interested to speed it up, please convince yourself it's ++ * really not needed, test and then suggest a patch on the mailing list. ++ */ guard(mutex)(&pwm_lock); + guard(pwmchip)(chip); @@ -83,7 +117,7 @@ Signed-off-by: Uwe Kleine-König return ops->capture(chip, pwm, result, timeout); } -@@ -372,6 +408,14 @@ static int pwm_device_request(struct pwm +@@ -372,6 +413,14 @@ static int pwm_device_request(struct pwm if (test_bit(PWMF_REQUESTED, &pwm->flags)) return -EBUSY; @@ -98,7 +132,7 @@ Signed-off-by: Uwe Kleine-König if (!try_module_get(chip->owner)) return -ENODEV; -@@ -400,7 +444,9 @@ err_get_device: +@@ -400,7 +449,9 @@ err_get_device: */ struct pwm_state state = { 0, }; @@ -109,7 +143,7 @@ Signed-off-by: Uwe Kleine-König trace_pwm_get(pwm, &state, err); if (!err) -@@ -1024,6 +1070,7 @@ struct pwm_chip *pwmchip_alloc(struct de +@@ -1024,6 +1075,7 @@ struct pwm_chip *pwmchip_alloc(struct de chip->npwm = npwm; chip->uses_pwmchip_alloc = true; @@ -117,7 +151,7 @@ Signed-off-by: Uwe Kleine-König pwmchip_dev = &chip->dev; device_initialize(pwmchip_dev); -@@ -1129,6 +1176,11 @@ int __pwmchip_add(struct pwm_chip *chip, +@@ -1129,6 +1181,11 @@ int __pwmchip_add(struct pwm_chip *chip, chip->owner = owner; @@ -129,7 +163,7 @@ Signed-off-by: Uwe Kleine-König guard(mutex)(&pwm_lock); ret = idr_alloc(&pwm_chips, chip, 0, 0, GFP_KERNEL); -@@ -1142,6 +1194,9 @@ int __pwmchip_add(struct pwm_chip *chip, +@@ -1142,6 +1199,9 @@ int __pwmchip_add(struct pwm_chip *chip, if (IS_ENABLED(CONFIG_OF)) of_pwmchip_add(chip); @@ -139,7 +173,7 @@ Signed-off-by: Uwe Kleine-König ret = device_add(&chip->dev); if (ret) goto err_device_add; -@@ -1149,6 +1204,9 @@ int __pwmchip_add(struct pwm_chip *chip, +@@ -1149,6 +1209,9 @@ int __pwmchip_add(struct pwm_chip *chip, return 0; err_device_add: @@ -149,7 +183,7 @@ Signed-off-by: Uwe Kleine-König if (IS_ENABLED(CONFIG_OF)) of_pwmchip_remove(chip); -@@ -1168,11 +1226,27 @@ void pwmchip_remove(struct pwm_chip *chi +@@ -1168,11 +1231,27 @@ void pwmchip_remove(struct pwm_chip *chi { pwmchip_sysfs_unexport(chip); @@ -165,7 +199,7 @@ Signed-off-by: Uwe Kleine-König + struct pwm_device *pwm = &chip->pwms[i]; + + if (test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) { -+ dev_alert(&chip->dev, "Freeing requested PWM #%u\n", i); ++ dev_warn(&chip->dev, "Freeing requested PWM #%u\n", i); + if (pwm->chip->ops->free) + pwm->chip->ops->free(pwm->chip, pwm); + } @@ -180,15 +214,15 @@ Signed-off-by: Uwe Kleine-König device_del(&chip->dev); } -@@ -1542,12 +1616,17 @@ void pwm_put(struct pwm_device *pwm) +@@ -1542,12 +1621,17 @@ void pwm_put(struct pwm_device *pwm) guard(mutex)(&pwm_lock); - if (!test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) { + /* -+ * If the chip isn't operational, PWMF_REQUESTED was already cleared. So -+ * don't warn in this case. This can only happen if a consumer called -+ * pwm_put() twice. ++ * Trigger a warning if a consumer called pwm_put() twice. ++ * If the chip isn't operational, PWMF_REQUESTED was already cleared in ++ * pwmchip_remove(). So don't warn in this case. + */ + if (chip->operational && !test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) { pr_warn("PWM device already freed\n"); @@ -221,10 +255,10 @@ Signed-off-by: Uwe Kleine-König + /* + * depending on the chip being atomic or not either the mutex or + * the spinlock is used. It protects .operational and -+ * synchronizes calls to the .ops->apply and .ops->get_state() ++ * synchronizes the callbacks in .ops + */ + struct mutex nonatomic_lock; -+ struct spinlock atomic_lock; ++ spinlock_t atomic_lock; + }; struct pwm_device pwms[] __counted_by(npwm); }; diff --git a/target/linux/rockchip/patches-6.12/340-02-pwm-new-abstraction-for-pwm-waveforms.patch b/target/linux/rockchip/patches-6.12/042-02-v6.13-pwm-New-abstraction-for-PWM-waveforms.patch similarity index 98% rename from target/linux/rockchip/patches-6.12/340-02-pwm-new-abstraction-for-pwm-waveforms.patch rename to target/linux/rockchip/patches-6.12/042-02-v6.13-pwm-New-abstraction-for-PWM-waveforms.patch index af435f5df..b839f7c1b 100644 --- a/target/linux/rockchip/patches-6.12/340-02-pwm-new-abstraction-for-pwm-waveforms.patch +++ b/target/linux/rockchip/patches-6.12/042-02-v6.13-pwm-New-abstraction-for-PWM-waveforms.patch @@ -49,11 +49,11 @@ requested period is too small for the hardware, it's expected that a setting with the minimal period and duty_length_ns = duty_offset_ns = 0 is returned and this fact is signaled by a return value of 1. -Signed-off-by: Uwe Kleine-König +Signed-off-by: Uwe Kleine-K?nig Tested-by: Trevor Gamblin Link: https://lore.kernel.org/r/df0faa33bf9e7c9e2e5eab8d31bbf61e861bd401.1726819463.git.u.kleine-koenig@baylibre.com [ukleinek: Update pwm_check_rounding() to return bool instead of int.] -Signed-off-by: Uwe Kleine-König +Signed-off-by: Uwe Kleine-K?nig --- drivers/pwm/core.c | 234 ++++++++++++++++++++++++++++++++++++++++---- include/linux/pwm.h | 36 +++++++ @@ -302,7 +302,7 @@ Signed-off-by: Uwe Kleine-König /** * pwm_adjust_config() - adjust the current PWM config to the PWM arguments * @pwm: PWM device -@@ -434,7 +618,7 @@ err_get_device: +@@ -439,7 +623,7 @@ err_get_device: } } @@ -311,7 +311,7 @@ Signed-off-by: Uwe Kleine-König /* * Zero-initialize state because most drivers are unaware of * .usage_power. The other members of state are supposed to be -@@ -444,11 +628,7 @@ err_get_device: +@@ -449,11 +633,7 @@ err_get_device: */ struct pwm_state state = { 0, }; @@ -324,7 +324,7 @@ Signed-off-by: Uwe Kleine-König if (!err) pwm->state = state; -@@ -1135,12 +1315,24 @@ static bool pwm_ops_check(const struct p +@@ -1140,12 +1320,24 @@ static bool pwm_ops_check(const struct p { const struct pwm_ops *ops = chip->ops; diff --git a/target/linux/rockchip/patches-6.12/340-03-pwm-provide-new-consumer-API-functions-for-waveforms.patch b/target/linux/rockchip/patches-6.12/042-03-v6.13-pwm-Provide-new-consumer-API-functions-for-waveforms.patch similarity index 98% rename from target/linux/rockchip/patches-6.12/340-03-pwm-provide-new-consumer-API-functions-for-waveforms.patch rename to target/linux/rockchip/patches-6.12/042-03-v6.13-pwm-Provide-new-consumer-API-functions-for-waveforms.patch index d4ccaf68b..37abb4516 100644 --- a/target/linux/rockchip/patches-6.12/340-03-pwm-provide-new-consumer-API-functions-for-waveforms.patch +++ b/target/linux/rockchip/patches-6.12/042-03-v6.13-pwm-Provide-new-consumer-API-functions-for-waveforms.patch @@ -13,10 +13,10 @@ pwm_get_waveform*() is that the latter yields the actually configured hardware state, while the former yields the last state passed to pwm_apply*() and so doesn't account for hardware specific rounding. -Signed-off-by: Uwe Kleine-König +Signed-off-by: Uwe Kleine-K?nig Tested-by: Trevor Gamblin Link: https://lore.kernel.org/r/6c97d27682853f603e18e9196043886dd671845d.1726819463.git.u.kleine-koenig@baylibre.com -Signed-off-by: Uwe Kleine-König +Signed-off-by: Uwe Kleine-K?nig --- drivers/pwm/core.c | 261 ++++++++++++++++++++++++++++++++++++++++++++ include/linux/pwm.h | 6 +- diff --git a/target/linux/rockchip/patches-6.12/042-04-v6.13-pwm-Add-tracing-for-waveform-callbacks.patch b/target/linux/rockchip/patches-6.12/042-04-v6.13-pwm-Add-tracing-for-waveform-callbacks.patch new file mode 100644 index 000000000..1fcf1a01c --- /dev/null +++ b/target/linux/rockchip/patches-6.12/042-04-v6.13-pwm-Add-tracing-for-waveform-callbacks.patch @@ -0,0 +1,236 @@ +From 1afd01db1a76cdd1d96696e3790d66c79621784c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= +Date: Fri, 20 Sep 2024 10:58:00 +0200 +Subject: [PATCH] pwm: Add tracing for waveform callbacks +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This adds trace events for the recently introduced waveform callbacks. +With the introduction of some helper macros consistency among the +different events is ensured. + +Signed-off-by: Uwe Kleine-K?nig +Link: https://lore.kernel.org/r/1d71879b0de3bf01459c7a9d0f040d43eb5ace56.1726819463.git.u.kleine-koenig@baylibre.com +Signed-off-by: Uwe Kleine-K?nig +--- + drivers/pwm/core.c | 24 +++++-- + include/trace/events/pwm.h | 134 ++++++++++++++++++++++++++++++++++--- + 2 files changed, 146 insertions(+), 12 deletions(-) + +--- a/drivers/pwm/core.c ++++ b/drivers/pwm/core.c +@@ -164,30 +164,46 @@ static int __pwm_round_waveform_tohw(str + const struct pwm_waveform *wf, void *wfhw) + { + const struct pwm_ops *ops = chip->ops; ++ int ret; + +- return ops->round_waveform_tohw(chip, pwm, wf, wfhw); ++ ret = ops->round_waveform_tohw(chip, pwm, wf, wfhw); ++ trace_pwm_round_waveform_tohw(pwm, wf, wfhw, ret); ++ ++ return ret; + } + + static int __pwm_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm, + const void *wfhw, struct pwm_waveform *wf) + { + const struct pwm_ops *ops = chip->ops; ++ int ret; ++ ++ ret = ops->round_waveform_fromhw(chip, pwm, wfhw, wf); ++ trace_pwm_round_waveform_fromhw(pwm, wfhw, wf, ret); + +- return ops->round_waveform_fromhw(chip, pwm, wfhw, wf); ++ return ret; + } + + static int __pwm_read_waveform(struct pwm_chip *chip, struct pwm_device *pwm, void *wfhw) + { + const struct pwm_ops *ops = chip->ops; ++ int ret; + +- return ops->read_waveform(chip, pwm, wfhw); ++ ret = ops->read_waveform(chip, pwm, wfhw); ++ trace_pwm_read_waveform(pwm, wfhw, ret); ++ ++ return ret; + } + + static int __pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, const void *wfhw) + { + const struct pwm_ops *ops = chip->ops; ++ int ret; ++ ++ ret = ops->write_waveform(chip, pwm, wfhw); ++ trace_pwm_write_waveform(pwm, wfhw, ret); + +- return ops->write_waveform(chip, pwm, wfhw); ++ return ret; + } + + #define WFHWSIZE 20 +--- a/include/trace/events/pwm.h ++++ b/include/trace/events/pwm.h +@@ -8,15 +8,135 @@ + #include + #include + ++#define TP_PROTO_pwm(args...) \ ++ TP_PROTO(struct pwm_device *pwm, args) ++ ++#define TP_ARGS_pwm(args...) \ ++ TP_ARGS(pwm, args) ++ ++#define TP_STRUCT__entry_pwm(args...) \ ++ TP_STRUCT__entry( \ ++ __field(unsigned int, chipid) \ ++ __field(unsigned int, hwpwm) \ ++ args) ++ ++#define TP_fast_assign_pwm(args...) \ ++ TP_fast_assign( \ ++ __entry->chipid = pwm->chip->id; \ ++ __entry->hwpwm = pwm->hwpwm; \ ++ args) ++ ++#define TP_printk_pwm(fmt, args...) \ ++ TP_printk("pwmchip%u.%u: " fmt, __entry->chipid, __entry->hwpwm, args) ++ ++#define __field_pwmwf(wf) \ ++ __field(u64, wf ## _period_length_ns) \ ++ __field(u64, wf ## _duty_length_ns) \ ++ __field(u64, wf ## _duty_offset_ns) \ ++ ++#define fast_assign_pwmwf(wf) \ ++ __entry->wf ## _period_length_ns = wf->period_length_ns; \ ++ __entry->wf ## _duty_length_ns = wf->duty_length_ns; \ ++ __entry->wf ## _duty_offset_ns = wf->duty_offset_ns ++ ++#define printk_pwmwf_format(wf) \ ++ "%lld/%lld [+%lld]" ++ ++#define printk_pwmwf_formatargs(wf) \ ++ __entry->wf ## _duty_length_ns, __entry->wf ## _period_length_ns, __entry->wf ## _duty_offset_ns ++ ++TRACE_EVENT(pwm_round_waveform_tohw, ++ ++ TP_PROTO_pwm(const struct pwm_waveform *wf, void *wfhw, int err), ++ ++ TP_ARGS_pwm(wf, wfhw, err), ++ ++ TP_STRUCT__entry_pwm( ++ __field_pwmwf(wf) ++ __field(void *, wfhw) ++ __field(int, err) ++ ), ++ ++ TP_fast_assign_pwm( ++ fast_assign_pwmwf(wf); ++ __entry->wfhw = wfhw; ++ __entry->err = err; ++ ), ++ ++ TP_printk_pwm(printk_pwmwf_format(wf) " > %p err=%d", ++ printk_pwmwf_formatargs(wf), __entry->wfhw, __entry->err) ++); ++ ++TRACE_EVENT(pwm_round_waveform_fromhw, ++ ++ TP_PROTO_pwm(const void *wfhw, struct pwm_waveform *wf, int err), ++ ++ TP_ARGS_pwm(wfhw, wf, err), ++ ++ TP_STRUCT__entry_pwm( ++ __field(const void *, wfhw) ++ __field_pwmwf(wf) ++ __field(int, err) ++ ), ++ ++ TP_fast_assign_pwm( ++ __entry->wfhw = wfhw; ++ fast_assign_pwmwf(wf); ++ __entry->err = err; ++ ), ++ ++ TP_printk_pwm("%p > " printk_pwmwf_format(wf) " err=%d", ++ __entry->wfhw, printk_pwmwf_formatargs(wf), __entry->err) ++); ++ ++TRACE_EVENT(pwm_read_waveform, ++ ++ TP_PROTO_pwm(void *wfhw, int err), ++ ++ TP_ARGS_pwm(wfhw, err), ++ ++ TP_STRUCT__entry_pwm( ++ __field(void *, wfhw) ++ __field(int, err) ++ ), ++ ++ TP_fast_assign_pwm( ++ __entry->wfhw = wfhw; ++ __entry->err = err; ++ ), ++ ++ TP_printk_pwm("%p err=%d", ++ __entry->wfhw, __entry->err) ++); ++ ++TRACE_EVENT(pwm_write_waveform, ++ ++ TP_PROTO_pwm(const void *wfhw, int err), ++ ++ TP_ARGS_pwm(wfhw, err), ++ ++ TP_STRUCT__entry_pwm( ++ __field(const void *, wfhw) ++ __field(int, err) ++ ), ++ ++ TP_fast_assign_pwm( ++ __entry->wfhw = wfhw; ++ __entry->err = err; ++ ), ++ ++ TP_printk_pwm("%p err=%d", ++ __entry->wfhw, __entry->err) ++); ++ ++ + DECLARE_EVENT_CLASS(pwm, + + TP_PROTO(struct pwm_device *pwm, const struct pwm_state *state, int err), + + TP_ARGS(pwm, state, err), + +- TP_STRUCT__entry( +- __field(unsigned int, chipid) +- __field(unsigned int, hwpwm) ++ TP_STRUCT__entry_pwm( + __field(u64, period) + __field(u64, duty_cycle) + __field(enum pwm_polarity, polarity) +@@ -24,9 +144,7 @@ DECLARE_EVENT_CLASS(pwm, + __field(int, err) + ), + +- TP_fast_assign( +- __entry->chipid = pwm->chip->id; +- __entry->hwpwm = pwm->hwpwm; ++ TP_fast_assign_pwm( + __entry->period = state->period; + __entry->duty_cycle = state->duty_cycle; + __entry->polarity = state->polarity; +@@ -34,8 +152,8 @@ DECLARE_EVENT_CLASS(pwm, + __entry->err = err; + ), + +- TP_printk("pwmchip%u.%u: period=%llu duty_cycle=%llu polarity=%d enabled=%d err=%d", +- __entry->chipid, __entry->hwpwm, __entry->period, __entry->duty_cycle, ++ TP_printk_pwm("period=%llu duty_cycle=%llu polarity=%d enabled=%d err=%d", ++ __entry->period, __entry->duty_cycle, + __entry->polarity, __entry->enabled, __entry->err) + + ); diff --git a/target/linux/rockchip/patches-6.12/042-05-v6.13-pwm-Reorder-symbols-in-core.c.patch b/target/linux/rockchip/patches-6.12/042-05-v6.13-pwm-Reorder-symbols-in-core.c.patch new file mode 100644 index 000000000..22c006fbc --- /dev/null +++ b/target/linux/rockchip/patches-6.12/042-05-v6.13-pwm-Reorder-symbols-in-core.c.patch @@ -0,0 +1,367 @@ +From 65406de2b0d059d44472ad6f3f88a9b4a9894833 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= +Date: Fri, 20 Sep 2024 10:58:03 +0200 +Subject: [PATCH] pwm: Reorder symbols in core.c +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This moves pwm_get() and friends above the functions handling +registration of pwmchips. The motivation is that character device +support needs pwm_get() and pwm_put() and so ideally is defined below +these and when a pwmchip is registered this registers the character +device. So the natural order is + + pwm_get() and friend + pwm character device symbols + pwm_chip functions + +. The advantage of having these in their natural order is that static +functions don't need to be forward declared. + +Note that the diff that git produces for this change some functions are +moved down instead. This is technically equivalent, but not how this +change was created. + +Signed-off-by: Uwe Kleine-K?nig +Link: https://lore.kernel.org/r/193b3d933294da34e020650bff93b778de46b1c5.1726819463.git.u.kleine-koenig@baylibre.com +Signed-off-by: Uwe Kleine-K?nig +--- + drivers/pwm/core.c | 312 ++++++++++++++++++++++----------------------- + 1 file changed, 156 insertions(+), 156 deletions(-) + +--- a/drivers/pwm/core.c ++++ b/drivers/pwm/core.c +@@ -1619,132 +1619,6 @@ static bool pwm_ops_check(const struct p + return true; + } + +-/** +- * __pwmchip_add() - register a new PWM chip +- * @chip: the PWM chip to add +- * @owner: reference to the module providing the chip. +- * +- * Register a new PWM chip. @owner is supposed to be THIS_MODULE, use the +- * pwmchip_add wrapper to do this right. +- * +- * Returns: 0 on success or a negative error code on failure. +- */ +-int __pwmchip_add(struct pwm_chip *chip, struct module *owner) +-{ +- int ret; +- +- if (!chip || !pwmchip_parent(chip) || !chip->ops || !chip->npwm) +- return -EINVAL; +- +- /* +- * a struct pwm_chip must be allocated using (devm_)pwmchip_alloc, +- * otherwise the embedded struct device might disappear too early +- * resulting in memory corruption. +- * Catch drivers that were not converted appropriately. +- */ +- if (!chip->uses_pwmchip_alloc) +- return -EINVAL; +- +- if (!pwm_ops_check(chip)) +- return -EINVAL; +- +- chip->owner = owner; +- +- if (chip->atomic) +- spin_lock_init(&chip->atomic_lock); +- else +- mutex_init(&chip->nonatomic_lock); +- +- guard(mutex)(&pwm_lock); +- +- ret = idr_alloc(&pwm_chips, chip, 0, 0, GFP_KERNEL); +- if (ret < 0) +- return ret; +- +- chip->id = ret; +- +- dev_set_name(&chip->dev, "pwmchip%u", chip->id); +- +- if (IS_ENABLED(CONFIG_OF)) +- of_pwmchip_add(chip); +- +- scoped_guard(pwmchip, chip) +- chip->operational = true; +- +- ret = device_add(&chip->dev); +- if (ret) +- goto err_device_add; +- +- return 0; +- +-err_device_add: +- scoped_guard(pwmchip, chip) +- chip->operational = false; +- +- if (IS_ENABLED(CONFIG_OF)) +- of_pwmchip_remove(chip); +- +- idr_remove(&pwm_chips, chip->id); +- +- return ret; +-} +-EXPORT_SYMBOL_GPL(__pwmchip_add); +- +-/** +- * pwmchip_remove() - remove a PWM chip +- * @chip: the PWM chip to remove +- * +- * Removes a PWM chip. +- */ +-void pwmchip_remove(struct pwm_chip *chip) +-{ +- pwmchip_sysfs_unexport(chip); +- +- scoped_guard(mutex, &pwm_lock) { +- unsigned int i; +- +- scoped_guard(pwmchip, chip) +- chip->operational = false; +- +- for (i = 0; i < chip->npwm; ++i) { +- struct pwm_device *pwm = &chip->pwms[i]; +- +- if (test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) { +- dev_warn(&chip->dev, "Freeing requested PWM #%u\n", i); +- if (pwm->chip->ops->free) +- pwm->chip->ops->free(pwm->chip, pwm); +- } +- } +- +- if (IS_ENABLED(CONFIG_OF)) +- of_pwmchip_remove(chip); +- +- idr_remove(&pwm_chips, chip->id); +- } +- +- device_del(&chip->dev); +-} +-EXPORT_SYMBOL_GPL(pwmchip_remove); +- +-static void devm_pwmchip_remove(void *data) +-{ +- struct pwm_chip *chip = data; +- +- pwmchip_remove(chip); +-} +- +-int __devm_pwmchip_add(struct device *dev, struct pwm_chip *chip, struct module *owner) +-{ +- int ret; +- +- ret = __pwmchip_add(chip, owner); +- if (ret) +- return ret; +- +- return devm_add_action_or_reset(dev, devm_pwmchip_remove, chip); +-} +-EXPORT_SYMBOL_GPL(__devm_pwmchip_add); +- + static struct device_link *pwm_device_link_add(struct device *dev, + struct pwm_device *pwm) + { +@@ -1923,36 +1797,6 @@ static DEFINE_MUTEX(pwm_lookup_lock); + static LIST_HEAD(pwm_lookup_list); + + /** +- * pwm_add_table() - register PWM device consumers +- * @table: array of consumers to register +- * @num: number of consumers in table +- */ +-void pwm_add_table(struct pwm_lookup *table, size_t num) +-{ +- guard(mutex)(&pwm_lookup_lock); +- +- while (num--) { +- list_add_tail(&table->list, &pwm_lookup_list); +- table++; +- } +-} +- +-/** +- * pwm_remove_table() - unregister PWM device consumers +- * @table: array of consumers to unregister +- * @num: number of consumers in table +- */ +-void pwm_remove_table(struct pwm_lookup *table, size_t num) +-{ +- guard(mutex)(&pwm_lookup_lock); +- +- while (num--) { +- list_del(&table->list); +- table++; +- } +-} +- +-/** + * pwm_get() - look up and request a PWM device + * @dev: device for PWM consumer + * @con_id: consumer name +@@ -2178,6 +2022,162 @@ struct pwm_device *devm_fwnode_pwm_get(s + } + EXPORT_SYMBOL_GPL(devm_fwnode_pwm_get); + ++/** ++ * __pwmchip_add() - register a new PWM chip ++ * @chip: the PWM chip to add ++ * @owner: reference to the module providing the chip. ++ * ++ * Register a new PWM chip. @owner is supposed to be THIS_MODULE, use the ++ * pwmchip_add wrapper to do this right. ++ * ++ * Returns: 0 on success or a negative error code on failure. ++ */ ++int __pwmchip_add(struct pwm_chip *chip, struct module *owner) ++{ ++ int ret; ++ ++ if (!chip || !pwmchip_parent(chip) || !chip->ops || !chip->npwm) ++ return -EINVAL; ++ ++ /* ++ * a struct pwm_chip must be allocated using (devm_)pwmchip_alloc, ++ * otherwise the embedded struct device might disappear too early ++ * resulting in memory corruption. ++ * Catch drivers that were not converted appropriately. ++ */ ++ if (!chip->uses_pwmchip_alloc) ++ return -EINVAL; ++ ++ if (!pwm_ops_check(chip)) ++ return -EINVAL; ++ ++ chip->owner = owner; ++ ++ if (chip->atomic) ++ spin_lock_init(&chip->atomic_lock); ++ else ++ mutex_init(&chip->nonatomic_lock); ++ ++ guard(mutex)(&pwm_lock); ++ ++ ret = idr_alloc(&pwm_chips, chip, 0, 0, GFP_KERNEL); ++ if (ret < 0) ++ return ret; ++ ++ chip->id = ret; ++ ++ dev_set_name(&chip->dev, "pwmchip%u", chip->id); ++ ++ if (IS_ENABLED(CONFIG_OF)) ++ of_pwmchip_add(chip); ++ ++ scoped_guard(pwmchip, chip) ++ chip->operational = true; ++ ++ ret = device_add(&chip->dev); ++ if (ret) ++ goto err_device_add; ++ ++ return 0; ++ ++err_device_add: ++ scoped_guard(pwmchip, chip) ++ chip->operational = false; ++ ++ if (IS_ENABLED(CONFIG_OF)) ++ of_pwmchip_remove(chip); ++ ++ idr_remove(&pwm_chips, chip->id); ++ ++ return ret; ++} ++EXPORT_SYMBOL_GPL(__pwmchip_add); ++ ++/** ++ * pwmchip_remove() - remove a PWM chip ++ * @chip: the PWM chip to remove ++ * ++ * Removes a PWM chip. ++ */ ++void pwmchip_remove(struct pwm_chip *chip) ++{ ++ pwmchip_sysfs_unexport(chip); ++ ++ scoped_guard(mutex, &pwm_lock) { ++ unsigned int i; ++ ++ scoped_guard(pwmchip, chip) ++ chip->operational = false; ++ ++ for (i = 0; i < chip->npwm; ++i) { ++ struct pwm_device *pwm = &chip->pwms[i]; ++ ++ if (test_and_clear_bit(PWMF_REQUESTED, &pwm->flags)) { ++ dev_warn(&chip->dev, "Freeing requested PWM #%u\n", i); ++ if (pwm->chip->ops->free) ++ pwm->chip->ops->free(pwm->chip, pwm); ++ } ++ } ++ ++ if (IS_ENABLED(CONFIG_OF)) ++ of_pwmchip_remove(chip); ++ ++ idr_remove(&pwm_chips, chip->id); ++ } ++ ++ device_del(&chip->dev); ++} ++EXPORT_SYMBOL_GPL(pwmchip_remove); ++ ++static void devm_pwmchip_remove(void *data) ++{ ++ struct pwm_chip *chip = data; ++ ++ pwmchip_remove(chip); ++} ++ ++int __devm_pwmchip_add(struct device *dev, struct pwm_chip *chip, struct module *owner) ++{ ++ int ret; ++ ++ ret = __pwmchip_add(chip, owner); ++ if (ret) ++ return ret; ++ ++ return devm_add_action_or_reset(dev, devm_pwmchip_remove, chip); ++} ++EXPORT_SYMBOL_GPL(__devm_pwmchip_add); ++ ++/** ++ * pwm_add_table() - register PWM device consumers ++ * @table: array of consumers to register ++ * @num: number of consumers in table ++ */ ++void pwm_add_table(struct pwm_lookup *table, size_t num) ++{ ++ guard(mutex)(&pwm_lookup_lock); ++ ++ while (num--) { ++ list_add_tail(&table->list, &pwm_lookup_list); ++ table++; ++ } ++} ++ ++/** ++ * pwm_remove_table() - unregister PWM device consumers ++ * @table: array of consumers to unregister ++ * @num: number of consumers in table ++ */ ++void pwm_remove_table(struct pwm_lookup *table, size_t num) ++{ ++ guard(mutex)(&pwm_lookup_lock); ++ ++ while (num--) { ++ list_del(&table->list); ++ table++; ++ } ++} ++ + static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s) + { + unsigned int i; diff --git a/target/linux/rockchip/patches-6.12/042-06-v6.14-compiler-h-add-const_true.patch b/target/linux/rockchip/patches-6.12/042-06-v6.14-compiler-h-add-const_true.patch new file mode 100644 index 000000000..7f48b46e0 --- /dev/null +++ b/target/linux/rockchip/patches-6.12/042-06-v6.14-compiler-h-add-const_true.patch @@ -0,0 +1,117 @@ +From 4f3d1be4c2f8a22470f3625cbc778ba2e2130def Mon Sep 17 00:00:00 2001 +From: Vincent Mailhol +Date: Thu, 14 Nov 2024 02:18:32 +0900 +Subject: [PATCH] compiler.h: add const_true() + +__builtin_constant_p() is known for not always being able to produce +constant expression [1] which led to the introduction of +__is_constexpr() [2]. Because of its dependency on +__builtin_constant_p(), statically_true() suffers from the same +issues. + +For example: + + void foo(int a) + { + /* fail on GCC */ + BUILD_BUG_ON_ZERO(statically_true(a)); + + /* fail on both clang and GCC */ + static char arr[statically_true(a) ? 1 : 2]; + } + +For the same reasons why __is_constexpr() was created to cover +__builtin_constant_p() edge cases, __is_constexpr() can be used to +resolve statically_true() limitations. + +Note that, somehow, GCC is not always able to fold this: + + __is_constexpr(x) && (x) + +It is OK in BUILD_BUG_ON_ZERO() but not in array declarations nor in +static_assert(): + + void bar(int a) + { + /* success */ + BUILD_BUG_ON_ZERO(__is_constexpr(a) && (a)); + + /* fail on GCC */ + static char arr[__is_constexpr(a) && (a) ? 1 : 2]; + + /* fail on GCC */ + static_assert(__is_constexpr(a) && (a)); + } + +Encapsulating the expression in a __builtin_choose_expr() switch +resolves all these failed tests. + +Define a new const_true() macro which, by making use of the +__builtin_choose_expr() and __is_constexpr(x) combo, always produces a +constant expression. + +It should be noted that statically_true() is the only one able to fold +tautological expressions in which at least one on the operands is not a +constant expression. For example: + + statically_true(true || var) + statically_true(var == var) + statically_true(var * 0 + 1) + statically_true(!(var * 8 % 4)) + +always evaluates to true, whereas all of these would be false under +const_true() if var is not a constant expression [3]. + +For this reason, usage of const_true() should be the exception. +Reflect in the documentation that const_true() is less powerful and +that statically_true() is the overall preferred solution. + +[1] __builtin_constant_p cannot resolve to const when optimizing +Link: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=19449 + +[2] commit 3c8ba0d61d04 ("kernel.h: Retain constant expression output for max()/min()") +Link: https://git.kernel.org/torvalds/c/3c8ba0d61d04 + +[3] https://godbolt.org/z/c61PMxqbK + +CC: Linus Torvalds +CC: Rasmus Villemoes +CC: Luc Van Oostenryck +Reviewed-by: Yury Norov , +Signed-off-by: Vincent Mailhol +Signed-off-by: Yury Norov +--- + include/linux/compiler.h | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +--- a/include/linux/compiler.h ++++ b/include/linux/compiler.h +@@ -303,6 +303,28 @@ static inline void *offset_to_ptr(const + #define statically_true(x) (__builtin_constant_p(x) && (x)) + + /* ++ * Similar to statically_true() but produces a constant expression ++ * ++ * To be used in conjunction with macros, such as BUILD_BUG_ON_ZERO(), ++ * which require their input to be a constant expression and for which ++ * statically_true() would otherwise fail. ++ * ++ * This is a trade-off: const_true() requires all its operands to be ++ * compile time constants. Else, it would always returns false even on ++ * the most trivial cases like: ++ * ++ * true || non_const_var ++ * ++ * On the opposite, statically_true() is able to fold more complex ++ * tautologies and will return true on expressions such as: ++ * ++ * !(non_const_var * 8 % 4) ++ * ++ * For the general case, statically_true() is better. ++ */ ++#define const_true(x) __builtin_choose_expr(__is_constexpr(x), x, false) ++ ++/* + * This is needed in functions which generate the stack canary, see + * arch/x86/kernel/smpboot.c::start_secondary() for an example. + */ diff --git a/target/linux/rockchip/patches-6.12/042-07-v6.14-pwm-Ensure-callbacks-exist-before-calling-them.patch b/target/linux/rockchip/patches-6.12/042-07-v6.14-pwm-Ensure-callbacks-exist-before-calling-them.patch new file mode 100644 index 000000000..815c7796d --- /dev/null +++ b/target/linux/rockchip/patches-6.12/042-07-v6.14-pwm-Ensure-callbacks-exist-before-calling-them.patch @@ -0,0 +1,99 @@ +From da6b353786997c0ffa67127355ad1d54ed3324c2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= +Date: Thu, 23 Jan 2025 18:27:07 +0100 +Subject: [PATCH] pwm: Ensure callbacks exist before calling them +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +If one of the waveform functions is called for a chip that only supports +.apply(), we want that an error code is returned and not a NULL pointer +exception. + +Fixes: 6c5126c6406d ("pwm: Provide new consumer API functions for waveforms") +Cc: stable@vger.kernel.org +Signed-off-by: Uwe Kleine-K?nig +Tested-by: Trevor Gamblin +Link: https://lore.kernel.org/r/20250123172709.391349-2-u.kleine-koenig@baylibre.com +Signed-off-by: Uwe Kleine-K?nig +--- + drivers/pwm/core.c | 13 +++++++++++-- + include/linux/pwm.h | 17 +++++++++++++++++ + 2 files changed, 28 insertions(+), 2 deletions(-) + +--- a/drivers/pwm/core.c ++++ b/drivers/pwm/core.c +@@ -242,6 +242,9 @@ int pwm_round_waveform_might_sleep(struc + + BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + ++ if (!pwmchip_supports_waveform(chip)) ++ return -EOPNOTSUPP; ++ + if (!pwm_wf_valid(wf)) + return -EINVAL; + +@@ -294,6 +297,9 @@ int pwm_get_waveform_might_sleep(struct + + BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + ++ if (!pwmchip_supports_waveform(chip) || !ops->read_waveform) ++ return -EOPNOTSUPP; ++ + guard(pwmchip)(chip); + + if (!chip->operational) +@@ -320,6 +326,9 @@ static int __pwm_set_waveform(struct pwm + + BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + ++ if (!pwmchip_supports_waveform(chip)) ++ return -EOPNOTSUPP; ++ + if (!pwm_wf_valid(wf)) + return -EINVAL; + +@@ -592,7 +601,7 @@ static int __pwm_apply(struct pwm_device + state->usage_power == pwm->state.usage_power) + return 0; + +- if (ops->write_waveform) { ++ if (pwmchip_supports_waveform(chip)) { + struct pwm_waveform wf; + char wfhw[WFHWSIZE]; + +@@ -728,7 +737,7 @@ static int pwm_get_state_hw(struct pwm_d + const struct pwm_ops *ops = chip->ops; + int ret = -EOPNOTSUPP; + +- if (ops->read_waveform) { ++ if (pwmchip_supports_waveform(chip) && ops->read_waveform) { + char wfhw[WFHWSIZE]; + struct pwm_waveform wf; + +--- a/include/linux/pwm.h ++++ b/include/linux/pwm.h +@@ -342,6 +342,23 @@ struct pwm_chip { + struct pwm_device pwms[] __counted_by(npwm); + }; + ++/** ++ * pwmchip_supports_waveform() - checks if the given chip supports waveform callbacks ++ * @chip: The pwm_chip to test ++ * ++ * Returns true iff the pwm chip support the waveform functions like ++ * pwm_set_waveform_might_sleep() and pwm_round_waveform_might_sleep() ++ */ ++static inline bool pwmchip_supports_waveform(struct pwm_chip *chip) ++{ ++ /* ++ * only check for .write_waveform(). If that is available, ++ * .round_waveform_tohw() and .round_waveform_fromhw() asserted to be ++ * available, too, in pwmchip_add(). ++ */ ++ return chip->ops->write_waveform != NULL; ++} ++ + static inline struct device *pwmchip_parent(const struct pwm_chip *chip) + { + return chip->dev.parent; diff --git a/target/linux/rockchip/patches-6.12/042-08-v6.15-pwm-Add-upgrade-path-to-pwm-cells-3-for-users-of.patch b/target/linux/rockchip/patches-6.12/042-08-v6.15-pwm-Add-upgrade-path-to-pwm-cells-3-for-users-of.patch new file mode 100644 index 000000000..aa58693fc --- /dev/null +++ b/target/linux/rockchip/patches-6.12/042-08-v6.15-pwm-Add-upgrade-path-to-pwm-cells-3-for-users-of.patch @@ -0,0 +1,61 @@ +From 895fe4537cc8586f51abb5c66524efaa42c29883 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= +Date: Thu, 6 Feb 2025 13:06:25 +0100 +Subject: [PATCH] pwm: Add upgrade path to #pwm-cells = <3> for users of + of_pwm_single_xlate() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The PWM chip on PXA only has a single output. Back when the device tree +binding was defined it was considered a good idea to not pass the PWM +line index as is done for all other PWM types as it would be always zero +anyhow and so doesn't add any value. + +However for consistency reasons it is nice when all PWMs use the same +binding. For that reason let of_pwm_single_xlate() (i.e. the function +that implements the PXA behaviour) behave in the same way as +of_pwm_xlate_with_flags() for 3 (or more) parameters. With that in +place, the pxa-pwm binding can be updated to #pwm-cells = <3> without +breaking old device trees that stick to #pwm-cells = <1>. + +Reviewed-by: Herve Codina +Tested-by: Duje Mihanovi? +Reviewed-by: Daniel Mack +Signed-off-by: Uwe Kleine-K?nig +Link: https://lore.kernel.org/r/b33a84d3f073880e94fc303cd32ebe095eb5ce46.1738842938.git.u.kleine-koenig@baylibre.com +Signed-off-by: Uwe Kleine-K?nig +--- + drivers/pwm/core.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +--- a/drivers/pwm/core.c ++++ b/drivers/pwm/core.c +@@ -988,11 +988,27 @@ of_pwm_xlate_with_flags(struct pwm_chip + } + EXPORT_SYMBOL_GPL(of_pwm_xlate_with_flags); + ++/* ++ * This callback is used for PXA PWM chips that only have a single PWM line. ++ * For such chips you could argue that passing the line number (i.e. the first ++ * parameter in the common case) is useless as it's always zero. So compared to ++ * the default xlate function of_pwm_xlate_with_flags() the first parameter is ++ * the default period and the second are flags. ++ * ++ * Note that if #pwm-cells = <3>, the semantic is the same as for ++ * of_pwm_xlate_with_flags() to allow converting the affected driver to ++ * #pwm-cells = <3> without breaking the legacy binding. ++ * ++ * Don't use for new drivers. ++ */ + struct pwm_device * + of_pwm_single_xlate(struct pwm_chip *chip, const struct of_phandle_args *args) + { + struct pwm_device *pwm; + ++ if (args->args_count >= 3) ++ return of_pwm_xlate_with_flags(chip, args); ++ + pwm = pwm_request_from_chip(chip, 0, NULL); + if (IS_ERR(pwm)) + return pwm; diff --git a/target/linux/rockchip/patches-6.12/042-09-v6.15-pwm-Let-pwm_set_waveform-succeed-even-if-lowlevel-driver.patch b/target/linux/rockchip/patches-6.12/042-09-v6.15-pwm-Let-pwm_set_waveform-succeed-even-if-lowlevel-driver.patch new file mode 100644 index 000000000..790856873 --- /dev/null +++ b/target/linux/rockchip/patches-6.12/042-09-v6.15-pwm-Let-pwm_set_waveform-succeed-even-if-lowlevel-driver.patch @@ -0,0 +1,71 @@ +From 00e53d0f4baedd72196b65f00698b2a5a537dc2b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= +Date: Sat, 5 Apr 2025 11:27:12 +0200 +Subject: [PATCH] pwm: Let pwm_set_waveform() succeed even if lowlevel driver + rounded up +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Waveform parameters are supposed to be rounded down to the next value +possible for the hardware. However when a requested value is too small, +.round_waveform_tohw() is supposed to pick the next bigger value and +return 1. Let pwm_set_waveform() behave in the same way. + +This creates consistency between pwm_set_waveform_might_sleep() with +exact=false and pwm_round_waveform_might_sleep() + +pwm_set_waveform_might_sleep() with exact=true. + +The PWM_DEBUG rounding check has to be adapted to only trigger if no +uprounding happend. + +Signed-off-by: Uwe Kleine-K?nig +Tested-by: Trevor Gamblin +Link: https://lore.kernel.org/r/353dc6ae31be815e41fd3df89c257127ca0d1a09.1743844730.git.u.kleine-koenig@baylibre.com +Signed-off-by: Uwe Kleine-K?nig +--- + drivers/pwm/core.c | 13 +++++++------ + 1 file changed, 7 insertions(+), 6 deletions(-) + +--- a/drivers/pwm/core.c ++++ b/drivers/pwm/core.c +@@ -322,7 +322,7 @@ static int __pwm_set_waveform(struct pwm + const struct pwm_ops *ops = chip->ops; + char wfhw[WFHWSIZE]; + struct pwm_waveform wf_rounded; +- int err; ++ int err, ret_tohw; + + BUG_ON(WFHWSIZE < ops->sizeof_wfhw); + +@@ -332,16 +332,16 @@ static int __pwm_set_waveform(struct pwm + if (!pwm_wf_valid(wf)) + return -EINVAL; + +- err = __pwm_round_waveform_tohw(chip, pwm, wf, &wfhw); +- if (err) +- return err; ++ ret_tohw = __pwm_round_waveform_tohw(chip, pwm, wf, &wfhw); ++ if (ret_tohw < 0) ++ return ret_tohw; + + if ((IS_ENABLED(CONFIG_PWM_DEBUG) || exact) && wf->period_length_ns) { + err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded); + if (err) + return err; + +- if (IS_ENABLED(CONFIG_PWM_DEBUG) && !pwm_check_rounding(wf, &wf_rounded)) ++ if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_tohw == 0 && !pwm_check_rounding(wf, &wf_rounded)) + dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n", + wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, + wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns); +@@ -382,7 +382,8 @@ static int __pwm_set_waveform(struct pwm + wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns, + wf_set.duty_length_ns, wf_set.period_length_ns, wf_set.duty_offset_ns); + } +- return 0; ++ ++ return ret_tohw; + } + + /** diff --git a/target/linux/rockchip/patches-6.12/042-10-v6.16-pwm-Do-stricter-return-value-checking-for.patch b/target/linux/rockchip/patches-6.12/042-10-v6.16-pwm-Do-stricter-return-value-checking-for.patch new file mode 100644 index 000000000..17db63fee --- /dev/null +++ b/target/linux/rockchip/patches-6.12/042-10-v6.16-pwm-Do-stricter-return-value-checking-for.patch @@ -0,0 +1,54 @@ +From 96d20cfd16e779923153f7347b0bef6b3c7606ce Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= +Date: Sat, 5 Apr 2025 11:27:17 +0200 +Subject: [PATCH] pwm: Do stricter return value checking for + .round_waveform_tohw() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The .round_waveform_tohw() is supposed to return 0 if the request could +be rounded down to match the hardware capabilities and return 1 if +rounding down wasn't possible. + +Expand the PWM_DEBUG check to not only assert proper downrounding if 0 +was returned but also check that it was actually rounded up when the +callback signalled uprounding. + +Signed-off-by: Uwe Kleine-K?nig +Link: https://lore.kernel.org/r/dfb824ae37f99df068c752d48cbd163c044a74fb.1743844730.git.u.kleine-koenig@baylibre.com +Signed-off-by: Uwe Kleine-K?nig +--- + drivers/pwm/core.c | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +--- a/drivers/pwm/core.c ++++ b/drivers/pwm/core.c +@@ -270,10 +270,10 @@ int pwm_round_waveform_might_sleep(struc + wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns, ret_tohw); + + if (IS_ENABLED(CONFIG_PWM_DEBUG) && +- ret_tohw == 0 && !pwm_check_rounding(&wf_req, wf)) +- dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n", ++ (ret_tohw == 0) != pwm_check_rounding(&wf_req, wf)) ++ dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu], ret: %d\n", + wf_req.duty_length_ns, wf_req.period_length_ns, wf_req.duty_offset_ns, +- wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns); ++ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, ret_tohw); + + return ret_tohw; + } +@@ -341,10 +341,10 @@ static int __pwm_set_waveform(struct pwm + if (err) + return err; + +- if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_tohw == 0 && !pwm_check_rounding(wf, &wf_rounded)) +- dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n", ++ if (IS_ENABLED(CONFIG_PWM_DEBUG) && (ret_tohw == 0) != pwm_check_rounding(wf, &wf_rounded)) ++ dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu], ret: %d\n", + wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, +- wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns); ++ wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns, ret_tohw); + + if (exact && pwmwfcmp(wf, &wf_rounded)) { + dev_dbg(&chip->dev, "Requested no rounding, but %llu/%llu [+%llu] -> %llu/%llu [+%llu]\n", diff --git a/target/linux/rockchip/patches-6.12/042-11-v6.16-pwm-Better-document-return-value-of.patch b/target/linux/rockchip/patches-6.12/042-11-v6.16-pwm-Better-document-return-value-of.patch new file mode 100644 index 000000000..175eac69f --- /dev/null +++ b/target/linux/rockchip/patches-6.12/042-11-v6.16-pwm-Better-document-return-value-of.patch @@ -0,0 +1,38 @@ +From e463b05d10da12b13d03f41a407e2ad043af158f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= +Date: Tue, 8 Apr 2025 16:23:54 +0200 +Subject: [PATCH] pwm: Better document return value of + pwm_round_waveform_might_sleep() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Better explain how pwm_round_waveform_might_sleep() (and so the +respective lowlevel driver callback) is supposed to round and the +meaning of the return value. + +Signed-off-by: Uwe Kleine-K?nig +Link: https://lore.kernel.org/r/db84abf1e82e4498fc0e7c318d2673771d0039fe.1744120697.git.ukleinek@kernel.org +[ukleinek: Fix a rst formatting issue reported by Stephen Rothwell] +Signed-off-by: Uwe Kleine-K?nig +--- + drivers/pwm/core.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +--- a/drivers/pwm/core.c ++++ b/drivers/pwm/core.c +@@ -229,8 +229,12 @@ static int __pwm_write_waveform(struct p + * these two calls and the waveform determined by + * pwm_round_waveform_might_sleep() cannot be implemented any more. + * +- * Returns 0 on success, 1 if there is no valid hardware configuration matching +- * the input waveform under the PWM rounding rules or a negative errno. ++ * Usually all values passed in @wf are rounded down to the nearest possible ++ * value (in the order period_length_ns, duty_length_ns and then ++ * duty_offset_ns). Only if this isn't possible, a value might grow. ++ * ++ * Returns 0 on success, 1 if at least one value had to be rounded up or a ++ * negative errno. + */ + int pwm_round_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf) + { diff --git a/target/linux/rockchip/patches-6.12/042-12-v6.16-pwm-Fix-various-formatting-issues-in-kernel-doc.patch b/target/linux/rockchip/patches-6.12/042-12-v6.16-pwm-Fix-various-formatting-issues-in-kernel-doc.patch new file mode 100644 index 000000000..1478fcbe9 --- /dev/null +++ b/target/linux/rockchip/patches-6.12/042-12-v6.16-pwm-Fix-various-formatting-issues-in-kernel-doc.patch @@ -0,0 +1,157 @@ +From 7f8ce4d88b42fcbd3350370ec4d02e00979fc5a9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= +Date: Thu, 17 Apr 2025 20:16:11 +0200 +Subject: [PATCH] pwm: Fix various formatting issues in kernel-doc +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add Return and (where interesting) Context sections, fix some formatting +and drop documenting the internal function __pwm_apply(). + +Signed-off-by: Uwe Kleine-K?nig +Link: https://lore.kernel.org/r/20250417181611.2693599-2-u.kleine-koenig@baylibre.com +Signed-off-by: Uwe Kleine-K?nig +--- + drivers/pwm/core.c | 42 +++++++++++++++++++++++++++--------------- + include/linux/pwm.h | 8 +++++--- + 2 files changed, 32 insertions(+), 18 deletions(-) + +--- a/drivers/pwm/core.c ++++ b/drivers/pwm/core.c +@@ -216,14 +216,14 @@ static int __pwm_write_waveform(struct p + * + * Typically a given waveform cannot be implemented exactly by hardware, e.g. + * because hardware only supports coarse period resolution or no duty_offset. +- * This function returns the actually implemented waveform if you pass wf to +- * pwm_set_waveform_might_sleep now. ++ * This function returns the actually implemented waveform if you pass @wf to ++ * pwm_set_waveform_might_sleep() now. + * + * Note however that the world doesn't stop turning when you call it, so when +- * doing ++ * doing:: + * +- * pwm_round_waveform_might_sleep(mypwm, &wf); +- * pwm_set_waveform_might_sleep(mypwm, &wf, true); ++ * pwm_round_waveform_might_sleep(mypwm, &wf); ++ * pwm_set_waveform_might_sleep(mypwm, &wf, true); + * + * the latter might fail, e.g. because an input clock changed its rate between + * these two calls and the waveform determined by +@@ -233,8 +233,9 @@ static int __pwm_write_waveform(struct p + * value (in the order period_length_ns, duty_length_ns and then + * duty_offset_ns). Only if this isn't possible, a value might grow. + * +- * Returns 0 on success, 1 if at least one value had to be rounded up or a ++ * Returns: 0 on success, 1 if at least one value had to be rounded up or a + * negative errno. ++ * Context: May sleep. + */ + int pwm_round_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf) + { +@@ -291,6 +292,9 @@ EXPORT_SYMBOL_GPL(pwm_round_waveform_mig + * + * Stores the current configuration of the PWM in @wf. Note this is the + * equivalent of pwm_get_state_hw() (and not pwm_get_state()) for pwm_waveform. ++ * ++ * Returns: 0 on success or a negative errno ++ * Context: May sleep. + */ + int pwm_get_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf) + { +@@ -399,13 +403,17 @@ static int __pwm_set_waveform(struct pwm + * + * Typically a requested waveform cannot be implemented exactly, e.g. because + * you requested .period_length_ns = 100 ns, but the hardware can only set +- * periods that are a multiple of 8.5 ns. With that hardware passing exact = ++ * periods that are a multiple of 8.5 ns. With that hardware passing @exact = + * true results in pwm_set_waveform_might_sleep() failing and returning 1. If +- * exact = false you get a period of 93.5 ns (i.e. the biggest period not bigger ++ * @exact = false you get a period of 93.5 ns (i.e. the biggest period not bigger + * than the requested value). +- * Note that even with exact = true, some rounding by less than 1 is ++ * Note that even with @exact = true, some rounding by less than 1 ns is + * possible/needed. In the above example requesting .period_length_ns = 94 and +- * exact = true, you get the hardware configured with period = 93.5 ns. ++ * @exact = true, you get the hardware configured with period = 93.5 ns. ++ * ++ * Returns: 0 on success, 1 if was rounded up (if !@exact) or no perfect match was ++ * possible (if @exact), or a negative errno ++ * Context: May sleep. + */ + int pwm_set_waveform_might_sleep(struct pwm_device *pwm, + const struct pwm_waveform *wf, bool exact) +@@ -565,11 +573,6 @@ static bool pwm_state_valid(const struct + return true; + } + +-/** +- * __pwm_apply() - atomically apply a new state to a PWM device +- * @pwm: PWM device +- * @state: new state to apply +- */ + static int __pwm_apply(struct pwm_device *pwm, const struct pwm_state *state) + { + struct pwm_chip *chip; +@@ -678,6 +681,9 @@ static int __pwm_apply(struct pwm_device + * Cannot be used in atomic context. + * @pwm: PWM device + * @state: new state to apply ++ * ++ * Returns: 0 on success, or a negative errno ++ * Context: May sleep. + */ + int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state) + { +@@ -719,6 +725,9 @@ EXPORT_SYMBOL_GPL(pwm_apply_might_sleep) + * Not all PWM devices support this function, check with pwm_might_sleep(). + * @pwm: PWM device + * @state: new state to apply ++ * ++ * Returns: 0 on success, or a negative errno ++ * Context: Any + */ + int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state) + { +@@ -778,6 +787,9 @@ static int pwm_get_state_hw(struct pwm_d + * This function will adjust the PWM config to the PWM arguments provided + * by the DT or PWM lookup table. This is particularly useful to adapt + * the bootloader config to the Linux one. ++ * ++ * Returns: 0 on success or a negative error code on failure. ++ * Context: May sleep. + */ + int pwm_adjust_config(struct pwm_device *pwm) + { +--- a/include/linux/pwm.h ++++ b/include/linux/pwm.h +@@ -218,6 +218,8 @@ static inline void pwm_init_state(const + * + * pwm_get_state(pwm, &state); + * duty = pwm_get_relative_duty_cycle(&state, 100); ++ * ++ * Returns: rounded relative duty cycle multiplied by @scale + */ + static inline unsigned int + pwm_get_relative_duty_cycle(const struct pwm_state *state, unsigned int scale) +@@ -244,8 +246,8 @@ pwm_get_relative_duty_cycle(const struct + * pwm_set_relative_duty_cycle(&state, 50, 100); + * pwm_apply_might_sleep(pwm, &state); + * +- * This functions returns -EINVAL if @duty_cycle and/or @scale are +- * inconsistent (@scale == 0 or @duty_cycle > @scale). ++ * Returns: 0 on success or ``-EINVAL`` if @duty_cycle and/or @scale are ++ * inconsistent (@scale == 0 or @duty_cycle > @scale) + */ + static inline int + pwm_set_relative_duty_cycle(struct pwm_state *state, unsigned int duty_cycle, +@@ -346,7 +348,7 @@ struct pwm_chip { + * pwmchip_supports_waveform() - checks if the given chip supports waveform callbacks + * @chip: The pwm_chip to test + * +- * Returns true iff the pwm chip support the waveform functions like ++ * Returns: true iff the pwm chip support the waveform functions like + * pwm_set_waveform_might_sleep() and pwm_round_waveform_might_sleep() + */ + static inline bool pwmchip_supports_waveform(struct pwm_chip *chip) diff --git a/target/linux/rockchip/patches-6.12/042-13-v6.16-pwm-Let-pwm_set_waveform_might_sleep-fail-for-exact-but.patch b/target/linux/rockchip/patches-6.12/042-13-v6.16-pwm-Let-pwm_set_waveform_might_sleep-fail-for-exact-but.patch new file mode 100644 index 000000000..e2287b345 --- /dev/null +++ b/target/linux/rockchip/patches-6.12/042-13-v6.16-pwm-Let-pwm_set_waveform_might_sleep-fail-for-exact-but.patch @@ -0,0 +1,64 @@ +From e866834c8baabc33b431902beeeb0c94dfbc1024 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= +Date: Wed, 30 Apr 2025 13:55:58 +0200 +Subject: [PATCH] pwm: Let pwm_set_waveform_might_sleep() fail for exact but + impossible requests +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Up to now pwm_set_waveform_might_sleep() returned 1 for exact requests +that couldn't be served exactly. In contrast to +pwm_round_waveform_might_sleep() and pwm_set_waveform_might_sleep() with +exact = false this is an error condition. So simplify handling for +callers of pwm_set_waveform_might_sleep() by returning -EDOM instead of +1 in this case. + +Signed-off-by: Uwe Kleine-K?nig +Link: https://lore.kernel.org/r/20538a46719584dafd8a1395c886780a97dcdf79.1746010245.git.u.kleine-koenig@baylibre.com +Signed-off-by: Uwe Kleine-K?nig +--- + drivers/pwm/core.c | 21 ++++++++++++++++----- + 1 file changed, 16 insertions(+), 5 deletions(-) + +--- a/drivers/pwm/core.c ++++ b/drivers/pwm/core.c +@@ -404,15 +404,16 @@ static int __pwm_set_waveform(struct pwm + * Typically a requested waveform cannot be implemented exactly, e.g. because + * you requested .period_length_ns = 100 ns, but the hardware can only set + * periods that are a multiple of 8.5 ns. With that hardware passing @exact = +- * true results in pwm_set_waveform_might_sleep() failing and returning 1. If +- * @exact = false you get a period of 93.5 ns (i.e. the biggest period not bigger +- * than the requested value). ++ * true results in pwm_set_waveform_might_sleep() failing and returning -EDOM. ++ * If @exact = false you get a period of 93.5 ns (i.e. the biggest period not ++ * bigger than the requested value). + * Note that even with @exact = true, some rounding by less than 1 ns is + * possible/needed. In the above example requesting .period_length_ns = 94 and + * @exact = true, you get the hardware configured with period = 93.5 ns. + * +- * Returns: 0 on success, 1 if was rounded up (if !@exact) or no perfect match was +- * possible (if @exact), or a negative errno ++ * Returns: 0 on success, 1 if was rounded up (if !@exact), -EDOM if setting ++ * failed due to the exact waveform not being possible (if @exact), or a ++ * different negative errno on failure. + * Context: May sleep. + */ + int pwm_set_waveform_might_sleep(struct pwm_device *pwm, +@@ -440,6 +441,16 @@ int pwm_set_waveform_might_sleep(struct + err = __pwm_set_waveform(pwm, wf, exact); + } + ++ /* ++ * map err == 1 to -EDOM for exact requests. Also make sure that -EDOM is ++ * only returned in exactly that case. Note that __pwm_set_waveform() ++ * should never return -EDOM which justifies the unlikely(). ++ */ ++ if (unlikely(err == -EDOM)) ++ err = -EINVAL; ++ else if (exact && err == 1) ++ err = -EDOM; ++ + return err; + } + EXPORT_SYMBOL_GPL(pwm_set_waveform_might_sleep); diff --git a/target/linux/rockchip/patches-6.12/042-14-v6.16-pwm-Let-pwm_set_waveform_might_sleep-return-0-instead-of-.patch b/target/linux/rockchip/patches-6.12/042-14-v6.16-pwm-Let-pwm_set_waveform_might_sleep-return-0-instead-of-.patch new file mode 100644 index 000000000..436da221d --- /dev/null +++ b/target/linux/rockchip/patches-6.12/042-14-v6.16-pwm-Let-pwm_set_waveform_might_sleep-return-0-instead-of-.patch @@ -0,0 +1,58 @@ +From 164c4ac754abaf9643815d09001cc7d81042d624 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= +Date: Wed, 30 Apr 2025 13:55:59 +0200 +Subject: [PATCH] pwm: Let pwm_set_waveform_might_sleep() return 0 instead of 1 + after rounding up +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +While telling the caller of pwm_set_waveform_might_sleep() if the +request was completed by rounding down only or (some) rounding up gives +additional information, it makes usage this function needlessly hard and +the additional information is not used. A prove for that is that +currently both users of this function just pass the returned value up to +their caller even though a positive value isn't intended there. + +Signed-off-by: Uwe Kleine-K?nig +Link: https://lore.kernel.org/r/528cc3bbd9e35dea8646b1bcc0fbfe6c498bb4ed.1746010245.git.u.kleine-koenig@baylibre.com +Signed-off-by: Uwe Kleine-K?nig +--- + drivers/pwm/core.c | 14 ++++++++------ + 1 file changed, 8 insertions(+), 6 deletions(-) + +--- a/drivers/pwm/core.c ++++ b/drivers/pwm/core.c +@@ -411,9 +411,8 @@ static int __pwm_set_waveform(struct pwm + * possible/needed. In the above example requesting .period_length_ns = 94 and + * @exact = true, you get the hardware configured with period = 93.5 ns. + * +- * Returns: 0 on success, 1 if was rounded up (if !@exact), -EDOM if setting +- * failed due to the exact waveform not being possible (if @exact), or a +- * different negative errno on failure. ++ * Returns: 0 on success, -EDOM if setting failed due to the exact waveform not ++ * being possible (if @exact), or a different negative errno on failure. + * Context: May sleep. + */ + int pwm_set_waveform_might_sleep(struct pwm_device *pwm, +@@ -442,14 +441,17 @@ int pwm_set_waveform_might_sleep(struct + } + + /* +- * map err == 1 to -EDOM for exact requests. Also make sure that -EDOM is +- * only returned in exactly that case. Note that __pwm_set_waveform() +- * should never return -EDOM which justifies the unlikely(). ++ * map err == 1 to -EDOM for exact requests and 0 for !exact ones. Also ++ * make sure that -EDOM is only returned in exactly that case. Note that ++ * __pwm_set_waveform() should never return -EDOM which justifies the ++ * unlikely(). + */ + if (unlikely(err == -EDOM)) + err = -EINVAL; + else if (exact && err == 1) + err = -EDOM; ++ else if (err == 1) ++ err = 0; + + return err; + } diff --git a/target/linux/rockchip/patches-6.12/042-15-v6.16-pwm-Formally-describe-the-procedure-used-to-pick-a-hardwa.patch b/target/linux/rockchip/patches-6.12/042-15-v6.16-pwm-Formally-describe-the-procedure-used-to-pick-a-hardwa.patch new file mode 100644 index 000000000..267250446 --- /dev/null +++ b/target/linux/rockchip/patches-6.12/042-15-v6.16-pwm-Formally-describe-the-procedure-used-to-pick-a-hardwa.patch @@ -0,0 +1,59 @@ +From d041b76ac9fb9e60e7cdb0265ed9d8b6058a88bf Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= +Date: Wed, 30 Apr 2025 13:56:00 +0200 +Subject: [PATCH] pwm: Formally describe the procedure used to pick a hardware + waveform setting +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This serves as specification for both, PWM consumers and the respective +callback for lowlevel drivers. + +Signed-off-by: Uwe Kleine-K?nig +Link: https://lore.kernel.org/r/d2916bfa70274961ded26b07ab6998c36b90e69a.1746010245.git.u.kleine-koenig@baylibre.com +Signed-off-by: Uwe Kleine-K?nig +--- + drivers/pwm/core.c | 24 +++++++++++++++++++++++- + 1 file changed, 23 insertions(+), 1 deletion(-) + +--- a/drivers/pwm/core.c ++++ b/drivers/pwm/core.c +@@ -231,7 +231,9 @@ static int __pwm_write_waveform(struct p + * + * Usually all values passed in @wf are rounded down to the nearest possible + * value (in the order period_length_ns, duty_length_ns and then +- * duty_offset_ns). Only if this isn't possible, a value might grow. ++ * duty_offset_ns). Only if this isn't possible, a value might grow. See the ++ * documentation for pwm_set_waveform_might_sleep() for a more formal ++ * description. + * + * Returns: 0 on success, 1 if at least one value had to be rounded up or a + * negative errno. +@@ -411,6 +413,26 @@ static int __pwm_set_waveform(struct pwm + * possible/needed. In the above example requesting .period_length_ns = 94 and + * @exact = true, you get the hardware configured with period = 93.5 ns. + * ++ * Let C be the set of possible hardware configurations for a given PWM device, ++ * consisting of tuples (p, d, o) where p is the period length, d is the duty ++ * length and o the duty offset. ++ * ++ * The following algorithm is implemented to pick the hardware setting ++ * (p, d, o) ¡Ê C for a given request (p', d', o') with @exact = false:: ++ * ++ * p = max( { ? | ? ?, ? : (?, ?, ?) ¡Ê C ¡Ä ? ¡Ü p' } ¡È { min({ ? | ? ?, ? : (?, ?, ?) ¡Ê C }) }) ++ * d = max( { ? | ? ? : (p, ?, ?) ¡Ê C ¡Ä ? ¡Ü d' } ¡È { min({ ? | ? ? : (p, ?, ?) ¡Ê C }) }) ++ * o = max( { ? | (p, d, ?) ¡Ê C ¡Ä ? ¡Ü o' } ¡È { min({ ? | (p, d, ?) ¡Ê C }) }) ++ * ++ * In words: The chosen period length is the maximal possible period length not ++ * bigger than the requested period length and if that doesn't exist, the ++ * minimal period length. The chosen duty length is the maximal possible duty ++ * length that is compatible with the chosen period length and isn't bigger than ++ * the requested duty length. Again if such a value doesn't exist, the minimal ++ * duty length compatible with the chosen period is picked. After that the duty ++ * offset compatible with the chosen period and duty length is chosen in the ++ * same way. ++ * + * Returns: 0 on success, -EDOM if setting failed due to the exact waveform not + * being possible (if @exact), or a different negative errno on failure. + * Context: May sleep. diff --git a/target/linux/rockchip/patches-6.12/043-v6.18-bitmap-introduce-hardware-specific-bitfield-operations.patch b/target/linux/rockchip/patches-6.12/043-v6.18-bitmap-introduce-hardware-specific-bitfield-operations.patch new file mode 100644 index 000000000..d683f1f29 --- /dev/null +++ b/target/linux/rockchip/patches-6.12/043-v6.18-bitmap-introduce-hardware-specific-bitfield-operations.patch @@ -0,0 +1,113 @@ +From 21368fcbb124d51b5d8bd8fa0a286a23c34a0888 Mon Sep 17 00:00:00 2001 +From: Nicolas Frattaroli +Date: Mon, 25 Aug 2025 10:28:21 +0200 +Subject: [PATCH] bitmap: introduce hardware-specific bitfield operations + +Hardware of various vendors, but very notably Rockchip, often uses +32-bit registers where the upper 16-bit half of the register is a +write-enable mask for the lower half. + +This type of hardware setup allows for more granular concurrent register +write access. + +Over the years, many drivers have hand-rolled their own version of this +macro, usually without any checks, often called something like +HIWORD_UPDATE or FIELD_PREP_HIWORD, commonly with slightly different +semantics between them. + +Clearly there is a demand for such a macro, and thus the demand should +be satisfied in a common header file. As this is a convention that spans +across multiple vendors, and similar conventions may also have +cross-vendor adoption, it's best if it lives in a vendor-agnostic header +file that can be expanded over time. + +Add hw_bitfield.h with two macros: FIELD_PREP_WM16, and +FIELD_PREP_WM16_CONST. The latter is a version that can be used in +initializers, like FIELD_PREP_CONST. + +Suggested-by: Yury Norov (NVIDIA) +Signed-off-by: Nicolas Frattaroli +Acked-by: Jakub Kicinski +Acked-by: Heiko Stuebner +Signed-off-by: Yury Norov (NVIDIA) +--- + MAINTAINERS | 1 + + include/linux/hw_bitfield.h | 62 +++++++++++++++++++++++++++++++++++++ + 2 files changed, 63 insertions(+) + create mode 100644 include/linux/hw_bitfield.h + +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -3904,6 +3904,7 @@ F: include/linux/bits.h + F: include/linux/cpumask.h + F: include/linux/cpumask_types.h + F: include/linux/find.h ++F: include/linux/hw_bitfield.h + F: include/linux/nodemask.h + F: include/linux/nodemask_types.h + F: include/vdso/bits.h +--- /dev/null ++++ b/include/linux/hw_bitfield.h +@@ -0,0 +1,62 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Copyright (C) 2025, Collabora Ltd. ++ */ ++ ++#ifndef _LINUX_HW_BITFIELD_H ++#define _LINUX_HW_BITFIELD_H ++ ++#include ++#include ++#include ++ ++/** ++ * FIELD_PREP_WM16() - prepare a bitfield element with a mask in the upper half ++ * @_mask: shifted mask defining the field's length and position ++ * @_val: value to put in the field ++ * ++ * FIELD_PREP_WM16() masks and shifts up the value, as well as bitwise ORs the ++ * result with the mask shifted up by 16. ++ * ++ * This is useful for a common design of hardware registers where the upper ++ * 16-bit half of a 32-bit register is used as a write-enable mask. In such a ++ * register, a bit in the lower half is only updated if the corresponding bit ++ * in the upper half is high. ++ */ ++#define FIELD_PREP_WM16(_mask, _val) \ ++ ({ \ ++ typeof(_val) __val = _val; \ ++ typeof(_mask) __mask = _mask; \ ++ __BF_FIELD_CHECK(__mask, ((u16)0U), __val, \ ++ "HWORD_UPDATE: "); \ ++ (((typeof(__mask))(__val) << __bf_shf(__mask)) & (__mask)) | \ ++ ((__mask) << 16); \ ++ }) ++ ++/** ++ * FIELD_PREP_WM16_CONST() - prepare a constant bitfield element with a mask in ++ * the upper half ++ * @_mask: shifted mask defining the field's length and position ++ * @_val: value to put in the field ++ * ++ * FIELD_PREP_WM16_CONST() masks and shifts up the value, as well as bitwise ORs ++ * the result with the mask shifted up by 16. ++ * ++ * This is useful for a common design of hardware registers where the upper ++ * 16-bit half of a 32-bit register is used as a write-enable mask. In such a ++ * register, a bit in the lower half is only updated if the corresponding bit ++ * in the upper half is high. ++ * ++ * Unlike FIELD_PREP_WM16(), this is a constant expression and can therefore ++ * be used in initializers. Error checking is less comfortable for this ++ * version. ++ */ ++#define FIELD_PREP_WM16_CONST(_mask, _val) \ ++ ( \ ++ FIELD_PREP_CONST(_mask, _val) | \ ++ (BUILD_BUG_ON_ZERO(const_true((u64)(_mask) > U16_MAX)) + \ ++ ((_mask) << 16)) \ ++ ) ++ ++ ++#endif /* _LINUX_HW_BITFIELD_H */ diff --git a/target/linux/rockchip/patches-6.12/341-04-soc-rockchip-add-mfpwm-driver.patch b/target/linux/rockchip/patches-6.12/162-01-mfd-Add-Rockchip-mfpwm-driver.patch similarity index 61% rename from target/linux/rockchip/patches-6.12/341-04-soc-rockchip-add-mfpwm-driver.patch rename to target/linux/rockchip/patches-6.12/162-01-mfd-Add-Rockchip-mfpwm-driver.patch index 4923633b8..54eef250e 100644 --- a/target/linux/rockchip/patches-6.12/341-04-soc-rockchip-add-mfpwm-driver.patch +++ b/target/linux/rockchip/patches-6.12/162-01-mfd-Add-Rockchip-mfpwm-driver.patch @@ -1,53 +1,83 @@ +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH v3 2/5] mfd: Add Rockchip mfpwm driver +From: Nicolas Frattaroli +Date: Mon, 27 Oct 2025 18:11:57 +0100 +Message-Id: <20251027-rk3576-pwm-v3-2-654a5cb1e3f8@collabora.com> +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 8bit + +With the Rockchip RK3576, the PWM IP used by Rockchip has changed +substantially. Looking at both the downstream pwm-rockchip driver as +well as the mainline pwm-rockchip driver made it clear that with all its +additional features and its differences from previous IP revisions, it +is best supported in a new driver. + +This brings us to the question as to what such a new driver should be. +To me, it soon became clear that it should actually be several new +drivers, most prominently when Uwe Kleine-K?nig let me know that I +should not implement the pwm subsystem's capture callback, but instead +write a counter driver for this functionality. + +Combined with the other as-of-yet unimplemented functionality of this +new IP, it became apparent that it needs to be spread across several +subsystems. + +For this reason, we add a new MFD core driver, called mfpwm (short for +"Multi-function PWM"). This "parent" driver makes sure that only one +device function driver is using the device at a time, and is in charge +of registering the MFD cell devices for the individual device functions +offered by the device. + +An acquire/release pattern is used to guarantee that device function +drivers don't step on each other's toes. + Signed-off-by: Nicolas Frattaroli --- - MAINTAINERS | 2 + - drivers/soc/rockchip/Kconfig | 13 + - drivers/soc/rockchip/Makefile | 1 + - drivers/soc/rockchip/mfpwm.c | 608 ++++++++++++++++++++++++++++++++++++++++++ - include/soc/rockchip/mfpwm.h | 505 +++++++++++++++++++++++++++++++++++ - 5 files changed, 1129 insertions(+) + MAINTAINERS | 2 + + drivers/mfd/Kconfig | 15 ++ + drivers/mfd/Makefile | 1 + + drivers/mfd/rockchip-mfpwm.c | 340 +++++++++++++++++++++++++++ + include/linux/mfd/rockchip-mfpwm.h | 454 +++++++++++++++++++++++++++++++++++++ + 5 files changed, 812 insertions(+) ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -19962,6 +19962,8 @@ L: linux-rockchip@lists.infradead.org - L: linux-pwm@vger.kernel.org - S: Maintained - F: Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml -+F: drivers/soc/rockchip/mfpwm.c -+F: include/soc/rockchip/mfpwm.h +--- a/drivers/mfd/Kconfig ++++ b/drivers/mfd/Kconfig +@@ -1244,6 +1244,21 @@ config MFD_RC5T583 + Additional drivers must be enabled in order to use the + different functionality of the device. - ROCKCHIP RK3568 RANDOM NUMBER GENERATOR SUPPORT - M: Daniel Golle ---- a/drivers/soc/rockchip/Kconfig -+++ b/drivers/soc/rockchip/Kconfig -@@ -30,4 +30,17 @@ config ROCKCHIP_DTPM - on this platform. That will create all the power capping capable - devices. - -+config ROCKCHIP_MFPWM ++config MFD_ROCKCHIP_MFPWM + tristate "Rockchip multi-function PWM controller" + depends on OF + depends on HAS_IOMEM ++ depends on COMMON_CLK ++ select MFD_CORE + help + Some Rockchip SoCs, such as the RK3576, use a PWM controller that has + several different functions, such as generating PWM waveforms but also + counting waveforms. + + This driver manages the overall device, and selects between different -+ functionalities at runtime as needed, with drivers for them -+ implemented in their respective subsystems. ++ functionalities at runtime as needed. Drivers for them are implemented ++ in their respective subsystems. + - endif ---- a/drivers/soc/rockchip/Makefile -+++ b/drivers/soc/rockchip/Makefile -@@ -5,3 +5,4 @@ - obj-$(CONFIG_ROCKCHIP_GRF) += grf.o - obj-$(CONFIG_ROCKCHIP_IODOMAIN) += io-domain.o - obj-$(CONFIG_ROCKCHIP_DTPM) += dtpm.o -+obj-$(CONFIG_ROCKCHIP_MFPWM) += mfpwm.o + config MFD_RK8XX + tristate + select MFD_CORE +--- a/drivers/mfd/Makefile ++++ b/drivers/mfd/Makefile +@@ -227,6 +227,7 @@ obj-$(CONFIG_MFD_PALMAS) += palmas.o + obj-$(CONFIG_MFD_VIPERBOARD) += viperboard.o + obj-$(CONFIG_MFD_NTXEC) += ntxec.o + obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o ++obj-$(CONFIG_MFD_ROCKCHIP_MFPWM) += rockchip-mfpwm.o + obj-$(CONFIG_MFD_RK8XX) += rk8xx-core.o + obj-$(CONFIG_MFD_RK8XX_I2C) += rk8xx-i2c.o + obj-$(CONFIG_MFD_RK8XX_SPI) += rk8xx-spi.o --- /dev/null -+++ b/drivers/soc/rockchip/mfpwm.c -@@ -0,0 +1,608 @@ ++++ b/drivers/mfd/rockchip-mfpwm.c +@@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 Collabora Ltd. @@ -64,13 +94,16 @@ Signed-off-by: Nicolas Frattaroli + * Nicolas Frattaroli + */ + ++#include +#include ++#include ++#include ++#include +#include +#include +#include +#include +#include -+#include + +/** + * struct rockchip_mfpwm - private mfpwm driver instance state struct @@ -78,20 +111,14 @@ Signed-off-by: Nicolas Frattaroli + * @base: pointer to the memory mapped registers of this device + * @pwm_clk: pointer to the PLL clock the PWM signal may be derived from + * @osc_clk: pointer to the fixed crystal the PWM signal may be derived from -+ * @chosen_clk: is one of either @pwm_clk or @osc_clk, depending on choice. -+ * May only be swapped out while holding @state_lock. ++ * @rc_clk: pointer to the RC oscillator the PWM signal may be derived from ++ * @chosen_clk: a clk-mux of pwm_clk, osc_clk and rc_clk + * @pclk: pointer to the APB bus clock needed for mmio register access -+ * @pwm_dev: pointer to the &struct platform_device of the pwm output driver -+ * @counter_dev: pointer to the &struct platform_device of the counter driver + * @active_func: pointer to the currently active device function, or %NULL if no -+ * device function is currently actively using any of the shared -+ * resources. May only be checked/modified with @state_lock held. ++ * device function is currently actively using any of the shared ++ * resources. May only be checked/modified with @state_lock held. + * @acquire_cnt: number of times @active_func has currently mfpwm_acquire()'d + * it. Must only be checked or modified while holding @state_lock. -+ * @pwmclk_enable_cnt: number of times @active_func has enabled the pwmclk sans -+ * disabling it. Must only be checked or modified while -+ * holding @state_lock. Only exists to fix a splat on mfpwm -+ * driver remove. + * @state_lock: this lock is held while either the active device function, the + * enable register, or the chosen clock is being changed. + * @irq: the IRQ number of this device @@ -101,13 +128,11 @@ Signed-off-by: Nicolas Frattaroli + void __iomem *base; + struct clk *pwm_clk; + struct clk *osc_clk; ++ struct clk *rc_clk; + struct clk *chosen_clk; + struct clk *pclk; -+ struct platform_device *pwm_dev; -+ struct platform_device *counter_dev; + struct rockchip_mfpwm_func *active_func; + unsigned int acquire_cnt; -+ unsigned int pwmclk_enable_cnt; + spinlock_t state_lock; + int irq; +}; @@ -119,27 +144,20 @@ Signed-off-by: Nicolas Frattaroli + return platform_get_drvdata(pdev); +} + -+unsigned long mfpwm_clk_get_rate(struct rockchip_mfpwm *mfpwm) -+{ -+ if (!mfpwm || !mfpwm->chosen_clk) -+ return 0; -+ -+ return clk_get_rate(mfpwm->chosen_clk); -+} -+EXPORT_SYMBOL_NS_GPL(mfpwm_clk_get_rate, ROCKCHIP_MFPWM); -+ +static int mfpwm_check_pwmf(const struct rockchip_mfpwm_func *pwmf, + const char *fname) +{ ++ struct device *dev = &pwmf->parent->pdev->dev; ++ + if (IS_ERR_OR_NULL(pwmf)) { -+ WARN(1, "called %s with an erroneous handle, no effect\n", -+ fname); ++ dev_warn(dev, "called %s with an erroneous handle, no effect\n", ++ fname); + return -EINVAL; + } + + if (IS_ERR_OR_NULL(pwmf->parent)) { -+ WARN(1, "called %s with an erroneous mfpwm_func parent, no effect\n", -+ fname); ++ dev_warn(dev, "called %s with an erroneous mfpwm_func parent, no effect\n", ++ fname); + return -EINVAL; + } + @@ -147,56 +165,6 @@ Signed-off-by: Nicolas Frattaroli +} + +__attribute__((nonnull)) -+static bool mfpwm_pwmf_is_active_pwmf(const struct rockchip_mfpwm_func *pwmf) -+{ -+ if (pwmf->parent->active_func) { -+ if (pwmf->parent->active_func->id == pwmf->id) -+ return true; -+ } -+ -+ return false; -+} -+ -+int mfpwm_pwmclk_enable(struct rockchip_mfpwm_func *pwmf) -+{ -+ unsigned long flags; -+ int ret; -+ -+ ret = mfpwm_check_pwmf(pwmf, "mfpwm_pwmclk_enable"); -+ if (ret) -+ return ret; -+ -+ spin_lock_irqsave(&pwmf->parent->state_lock, flags); -+ if (mfpwm_pwmf_is_active_pwmf(pwmf)) { -+ ret = clk_enable(pwmf->parent->chosen_clk); -+ pwmf->parent->pwmclk_enable_cnt++; -+ } else { -+ ret = -EBUSY; -+ } -+ -+ spin_unlock_irqrestore(&pwmf->parent->state_lock, flags); -+ -+ return ret; -+} -+EXPORT_SYMBOL_NS_GPL(mfpwm_pwmclk_enable, ROCKCHIP_MFPWM); -+ -+void mfpwm_pwmclk_disable(struct rockchip_mfpwm_func *pwmf) -+{ -+ unsigned long flags; -+ -+ if (mfpwm_check_pwmf(pwmf, "mfpwm_pwmclk_enable")) -+ return; -+ -+ spin_lock_irqsave(&pwmf->parent->state_lock, flags); -+ if (mfpwm_pwmf_is_active_pwmf(pwmf)) { -+ clk_disable(pwmf->parent->chosen_clk); -+ pwmf->parent->pwmclk_enable_cnt--; -+ } -+ spin_unlock_irqrestore(&pwmf->parent->state_lock, flags); -+} -+EXPORT_SYMBOL_NS_GPL(mfpwm_pwmclk_disable, ROCKCHIP_MFPWM); -+ -+__attribute__((nonnull)) +static int mfpwm_do_acquire(struct rockchip_mfpwm_func *pwmf) +{ + struct rockchip_mfpwm *mfpwm = pwmf->parent; @@ -211,7 +179,8 @@ Signed-off-by: Nicolas Frattaroli + if (!check_add_overflow(mfpwm->acquire_cnt, 1, &cnt)) { + mfpwm->acquire_cnt = cnt; + } else { -+ WARN(1, "prevented acquire counter overflow in %s\n", __func__); ++ dev_warn(&mfpwm->pdev->dev, "prevented acquire counter overflow in %s\n", ++ __func__); + return -EOVERFLOW; + } + @@ -286,63 +255,21 @@ Signed-off-by: Nicolas Frattaroli +} +EXPORT_SYMBOL_NS_GPL(mfpwm_release, ROCKCHIP_MFPWM); + -+void mfpwm_remove_func(struct rockchip_mfpwm_func *pwmf) -+{ -+ struct rockchip_mfpwm *mfpwm; -+ unsigned long flags; -+ -+ if (mfpwm_check_pwmf(pwmf, "mfpwm_remove_func")) -+ return; -+ -+ mfpwm = pwmf->parent; -+ spin_lock_irqsave(&mfpwm->state_lock, flags); -+ -+ if (mfpwm_pwmf_is_active_pwmf(pwmf)) { -+ dev_dbg(&mfpwm->pdev->dev, "removing active function %d\n", -+ pwmf->id); -+ -+ while (mfpwm->acquire_cnt > 0) -+ mfpwm_do_release(pwmf); -+ for (; mfpwm->pwmclk_enable_cnt > 0; mfpwm->pwmclk_enable_cnt--) -+ clk_disable(mfpwm->chosen_clk); -+ -+ mfpwm_reg_write(mfpwm->base, PWMV4_REG_ENABLE, -+ PWMV4_EN(false) | PWMV4_CLK_EN(false)); -+ } -+ -+ if (mfpwm->pwm_dev && mfpwm->pwm_dev->id == pwmf->id) { -+ dev_dbg(&mfpwm->pdev->dev, "clearing pwm_dev pointer\n"); -+ mfpwm->pwm_dev = NULL; -+ } else if (mfpwm->counter_dev && mfpwm->counter_dev->id == pwmf->id) { -+ dev_dbg(&mfpwm->pdev->dev, "clearing counter_dev pointer\n"); -+ mfpwm->counter_dev = NULL; -+ } else { -+ WARN(1, "trying to remove an unknown mfpwm device function"); -+ } -+ -+ spin_unlock_irqrestore(&mfpwm->state_lock, flags); -+} -+EXPORT_SYMBOL_NS_GPL(mfpwm_remove_func, ROCKCHIP_MFPWM); -+ +/** + * mfpwm_register_subdev - register a single mfpwm_func + * @mfpwm: pointer to the parent &struct rockchip_mfpwm -+ * @target: pointer to where the &struct platform_device pointer should be -+ * stored, usually a member of @mfpwm + * @name: sub-device name string + * + * Allocate a single &struct mfpwm_func, fill its members with appropriate data, -+ * and register a new platform device, saving its pointer to @target. The -+ * allocation is devres tracked, so will be automatically freed on mfpwm remove. ++ * and register a new mfd cell. + * + * Returns: 0 on success, negative errno on error + */ +static int mfpwm_register_subdev(struct rockchip_mfpwm *mfpwm, -+ struct platform_device **target, + const char *name) +{ + struct rockchip_mfpwm_func *func; -+ struct platform_device *child; ++ struct mfd_cell cell = {}; + + func = devm_kzalloc(&mfpwm->pdev->dev, sizeof(*func), GFP_KERNEL); + if (IS_ERR(func)) @@ -351,226 +278,39 @@ Signed-off-by: Nicolas Frattaroli + func->parent = mfpwm; + func->id = atomic_inc_return(&subdev_id); + func->base = mfpwm->base; -+ child = platform_device_register_data(&mfpwm->pdev->dev, name, func->id, -+ func, sizeof(*func)); ++ func->core = mfpwm->chosen_clk; ++ cell.name = name; ++ cell.platform_data = func; ++ cell.pdata_size = sizeof(*func); ++ // cell.ignore_resource_conflicts = true; ++ // cell.resources = mfpwm->pdev->resource; ++ // cell.num_resources = mfpwm->pdev->num_resources; + -+ if (IS_ERR(child)) -+ return PTR_ERR(child); -+ -+ *target = child; -+ -+ return 0; ++ return devm_mfd_add_devices(&mfpwm->pdev->dev, func->id, &cell, 1, NULL, ++ 0, NULL); +} + +static int mfpwm_register_subdevs(struct rockchip_mfpwm *mfpwm) +{ + int ret; + -+ ret = mfpwm_register_subdev(mfpwm, &mfpwm->pwm_dev, "pwm-rockchip-v4"); ++ ret = mfpwm_register_subdev(mfpwm, "pwm-rockchip-v4"); + if (ret) + return ret; + -+ ret = mfpwm_register_subdev(mfpwm, &mfpwm->counter_dev, -+ "rockchip-pwm-capture"); ++ ret = mfpwm_register_subdev(mfpwm, "rockchip-pwm-capture"); + if (ret) -+ goto err_unreg_pwm_dev; ++ return ret; + + return 0; -+ -+err_unreg_pwm_dev: -+ platform_device_unregister(mfpwm->pwm_dev); -+ -+ return ret; +} + -+/** -+ * mfpwm_get_clk_src - read the currently selected clock source -+ * @mfpwm: pointer to the driver's private &struct rockchip_mfpwm instance -+ * -+ * Read the device register to extract the currently selected clock source, -+ * and return it. -+ * -+ * Returns: -+ * * the numeric clock source ID on success, 0 <= id <= 2 -+ * * negative errno on error -+ */ -+static int mfpwm_get_clk_src(struct rockchip_mfpwm *mfpwm) -+{ -+ u32 val; -+ -+ clk_enable(mfpwm->pclk); -+ val = mfpwm_reg_read(mfpwm->base, PWMV4_REG_CLK_CTRL); -+ clk_disable(mfpwm->pclk); -+ -+ return (val & PWMV4_CLK_SRC_MASK) >> PWMV4_CLK_SRC_SHIFT; -+} -+ -+static int mfpwm_choose_clk(struct rockchip_mfpwm *mfpwm) -+{ -+ int ret; -+ -+ ret = mfpwm_get_clk_src(mfpwm); -+ if (ret < 0) { -+ dev_err(&mfpwm->pdev->dev, "couldn't get current clock source: %pe\n", -+ ERR_PTR(ret)); -+ return ret; -+ } -+ if (ret == PWMV4_CLK_SRC_CRYSTAL) { -+ if (mfpwm->osc_clk) { -+ mfpwm->chosen_clk = mfpwm->osc_clk; -+ } else { -+ dev_warn(&mfpwm->pdev->dev, "initial state wanted 'osc' as clock source, but it's unavailable. Defaulting to 'pwm'.\n"); -+ mfpwm->chosen_clk = mfpwm->pwm_clk; -+ } -+ } else { -+ mfpwm->chosen_clk = mfpwm->pwm_clk; -+ } -+ -+ return clk_rate_exclusive_get(mfpwm->chosen_clk); -+} -+ -+/** -+ * mfpwm_switch_clk_src - switch between PWM clock sources -+ * @mfpwm: pointer to &struct rockchip_mfpwm driver data -+ * @clk_src: one of either %PWMV4_CLK_SRC_CRYSTAL or %PWMV4_CLK_SRC_PLL -+ * -+ * Switch between clock sources, ``_exclusive_put``ing the old rate, -+ * ``clk_rate_exclusive_get``ing the new one, writing the registers and -+ * swapping out the &struct_rockchip_mfpwm->chosen_clk. -+ * -+ * Returns: -+ * * %0 - Success -+ * * %-EINVAL - A wrong @clk_src was given or it is unavailable -+ * * %-EBUSY - Device is currently in use, try again later -+ */ -+__attribute__((nonnull)) -+static int mfpwm_switch_clk_src(struct rockchip_mfpwm *mfpwm, -+ unsigned int clk_src) -+{ -+ struct clk *prev; -+ int ret = 0; -+ -+ scoped_cond_guard(spinlock_try, return -EBUSY, &mfpwm->state_lock) { -+ /* Don't fiddle with any of this stuff if the PWM is on */ -+ if (mfpwm->active_func) -+ return -EBUSY; -+ -+ prev = mfpwm->chosen_clk; -+ ret = mfpwm_get_clk_src(mfpwm); -+ if (ret < 0) -+ return ret; -+ if (ret == clk_src) -+ return 0; -+ -+ switch (clk_src) { -+ case PWMV4_CLK_SRC_PLL: -+ mfpwm->chosen_clk = mfpwm->pwm_clk; -+ break; -+ case PWMV4_CLK_SRC_CRYSTAL: -+ if (!mfpwm->osc_clk) -+ return -EINVAL; -+ mfpwm->chosen_clk = mfpwm->osc_clk; -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ clk_enable(mfpwm->pclk); -+ -+ mfpwm_reg_write(mfpwm->base, PWMV4_REG_CLK_CTRL, -+ PWMV4_CLK_SRC(clk_src)); -+ clk_rate_exclusive_get(mfpwm->chosen_clk); -+ if (prev) -+ clk_rate_exclusive_put(prev); -+ -+ clk_disable(mfpwm->pclk); -+ } -+ -+ return ret; -+} -+ -+static ssize_t chosen_clock_show(struct device *dev, -+ struct device_attribute *attr, char *buf) -+{ -+ struct rockchip_mfpwm *mfpwm = dev_get_drvdata(dev); -+ unsigned long clk_src = 0; -+ -+ /* -+ * Why the weird indirection here? I have the suspicion that if we -+ * emitted to sysfs with the lock still held, then a nefarious program -+ * could hog the lock by somehow forcing a full buffer condition and -+ * then refusing to read from it. Don't know whether that's feasible -+ * to achieve in reality, but I don't want to find out the hard way -+ * either. -+ */ -+ scoped_guard(spinlock, &mfpwm->state_lock) { -+ if (mfpwm->chosen_clk == mfpwm->pwm_clk) -+ clk_src = PWMV4_CLK_SRC_PLL; -+ else if (mfpwm->osc_clk && mfpwm->chosen_clk == mfpwm->osc_clk) -+ clk_src = PWMV4_CLK_SRC_CRYSTAL; -+ else -+ return -ENODEV; -+ } -+ -+ if (clk_src == PWMV4_CLK_SRC_PLL) -+ return sysfs_emit(buf, "pll\n"); -+ else if (clk_src == PWMV4_CLK_SRC_CRYSTAL) -+ return sysfs_emit(buf, "crystal\n"); -+ -+ return -ENODEV; -+} -+ -+static ssize_t chosen_clock_store(struct device *dev, -+ struct device_attribute *attr, -+ const char *buf, size_t count) -+{ -+ struct rockchip_mfpwm *mfpwm = dev_get_drvdata(dev); -+ int ret; -+ -+ if (sysfs_streq(buf, "pll")) { -+ ret = mfpwm_switch_clk_src(mfpwm, PWMV4_CLK_SRC_PLL); -+ if (ret) -+ return ret; -+ return count; -+ } else if (sysfs_streq(buf, "crystal")) { -+ ret = mfpwm_switch_clk_src(mfpwm, PWMV4_CLK_SRC_CRYSTAL); -+ if (ret) -+ return ret; -+ return count; -+ } else { -+ return -EINVAL; -+ } -+} -+ -+static DEVICE_ATTR_RW(chosen_clock); -+ -+static ssize_t available_clocks_show(struct device *dev, -+ struct device_attribute *attr, char *buf) -+{ -+ struct rockchip_mfpwm *mfpwm = dev_get_drvdata(dev); -+ ssize_t size = 0; -+ -+ size += sysfs_emit_at(buf, size, "pll\n"); -+ if (mfpwm->osc_clk) -+ size += sysfs_emit_at(buf, size, "crystal\n"); -+ -+ return size; -+} -+ -+static DEVICE_ATTR_RO(available_clocks); -+ -+static struct attribute *mfpwm_attrs[] = { -+ &dev_attr_available_clocks.attr, -+ &dev_attr_chosen_clock.attr, -+ NULL, -+}; -+ -+ATTRIBUTE_GROUPS(mfpwm); -+ +static int rockchip_mfpwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rockchip_mfpwm *mfpwm; ++ char *clk_mux_name; ++ const char *mux_p_names[3]; + int ret = 0; + + mfpwm = devm_kzalloc(&pdev->dev, sizeof(*mfpwm), GFP_KERNEL); @@ -600,14 +340,35 @@ Signed-off-by: Nicolas Frattaroli + return dev_err_probe(dev, PTR_ERR(mfpwm->pwm_clk), + "couldn't get and prepare 'pwm' clock\n"); + -+ mfpwm->osc_clk = devm_clk_get_optional_prepared(dev, "osc"); ++ mfpwm->osc_clk = devm_clk_get_prepared(dev, "osc"); + if (IS_ERR(mfpwm->osc_clk)) + return dev_err_probe(dev, PTR_ERR(mfpwm->osc_clk), + "couldn't get and prepare 'osc' clock\n"); + -+ ret = mfpwm_choose_clk(mfpwm); -+ if (ret) ++ mfpwm->rc_clk = devm_clk_get_prepared(dev, "rc"); ++ if (IS_ERR(mfpwm->rc_clk)) ++ return dev_err_probe(dev, PTR_ERR(mfpwm->rc_clk), ++ "couldn't get and prepare 'rc' clock\n"); ++ ++ clk_mux_name = devm_kasprintf(dev, GFP_KERNEL, "%s_chosen", dev_name(dev)); ++ if (!clk_mux_name) ++ return -ENOMEM; ++ ++ mux_p_names[0] = __clk_get_name(mfpwm->pwm_clk); ++ mux_p_names[1] = __clk_get_name(mfpwm->osc_clk); ++ mux_p_names[2] = __clk_get_name(mfpwm->rc_clk); ++ mfpwm->chosen_clk = clk_register_mux(dev, clk_mux_name, mux_p_names, ++ ARRAY_SIZE(mux_p_names), ++ CLK_SET_RATE_PARENT, ++ mfpwm->base + PWMV4_REG_CLK_CTRL, ++ PWMV4_CLK_SRC_SHIFT, PWMV4_CLK_SRC_WIDTH, ++ CLK_MUX_HIWORD_MASK, NULL); ++ ret = clk_prepare(mfpwm->chosen_clk); ++ if (ret) { ++ dev_err(dev, "failed to prepare PWM clock mux: %pe\n", ++ ERR_PTR(ret)); + return ret; ++ } + + platform_set_drvdata(pdev, mfpwm); + @@ -628,8 +389,10 @@ Signed-off-by: Nicolas Frattaroli + + spin_lock_irqsave(&mfpwm->state_lock, flags); + -+ if (mfpwm->chosen_clk) -+ clk_rate_exclusive_put(mfpwm->chosen_clk); ++ if (mfpwm->chosen_clk) { ++ clk_unprepare(mfpwm->chosen_clk); ++ clk_unregister_mux(mfpwm->chosen_clk); ++ } + + spin_unlock_irqrestore(&mfpwm->state_lock, flags); +} @@ -646,7 +409,6 @@ Signed-off-by: Nicolas Frattaroli + .driver = { + .name = KBUILD_MODNAME, + .of_match_table = rockchip_mfpwm_of_match, -+ .dev_groups = mfpwm_groups, + }, + .probe = rockchip_mfpwm_probe, + .remove = rockchip_mfpwm_remove, @@ -657,8 +419,8 @@ Signed-off-by: Nicolas Frattaroli +MODULE_DESCRIPTION("Rockchip MFPWM Driver"); +MODULE_LICENSE("GPL"); --- /dev/null -+++ b/include/soc/rockchip/mfpwm.h -@@ -0,0 +1,505 @@ ++++ b/include/linux/mfd/rockchip-mfpwm.h +@@ -0,0 +1,454 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2025 Collabora Ltd. @@ -673,10 +435,11 @@ Signed-off-by: Nicolas Frattaroli +#ifndef __SOC_ROCKCHIP_MFPWM_H__ +#define __SOC_ROCKCHIP_MFPWM_H__ + ++#include +#include ++#include +#include +#include -+#include + +struct rockchip_mfpwm; + @@ -687,12 +450,14 @@ Signed-off-by: Nicolas Frattaroli + * @base: pointer to start of MMIO registers + * @parent: a pointer to the parent mfpwm struct + * @irq: the shared IRQ gotten from the parent mfpwm device ++ * @core: a pointer to the clk mux that drives this channel's PWM + */ +struct rockchip_mfpwm_func { + int id; + void __iomem *base; + struct rockchip_mfpwm *parent; + int irq; ++ struct clk *core; +}; + +/* @@ -716,7 +481,7 @@ Signed-off-by: Nicolas Frattaroli +/* + * VERSION Register Description + * [31:24] RO | Hardware Major Version -+ * [23:16] RO | Hardware Minor Version docArrive ++ * [23:16] RO | Hardware Minor Version + * [15:15] RO | Reserved + * [14:14] RO | Hardware supports biphasic counters + * [13:13] RO | Hardware supports filters @@ -728,25 +493,6 @@ Signed-off-by: Nicolas Frattaroli + * [07:04] RO | Channel index of this instance + * [03:00] RO | Number of channels the base instance supports + */ -+static inline __pure u32 pwmv4_ver_chn_num(u32 val) -+{ -+ return (val & GENMASK(3, 0)); -+} -+ -+static inline __pure u32 pwmv4_ver_chn_idx(u32 val) -+{ -+ return (val & GENMASK(7, 4)) >> 4; -+} -+ -+static inline __pure u32 pwmv4_ver_major(u32 val) -+{ -+ return (val & GENMASK(31, 24)) >> 24; -+} -+ -+static inline __pure u32 pwmv4_ver_minor(u32 val) -+{ -+ return (val & GENMASK(23, 16)) >> 16; -+} + +#define PWMV4_REG_ENABLE 0x004 +/* @@ -757,40 +503,41 @@ Signed-off-by: Nicolas Frattaroli + * [15:06] RO | Reserved + * [05:05] RW | PWM Channel Counter Read Enable, 1 = enabled + */ -+#define PWMV4_CHN_CNT_RD_EN(v) REG_UPDATE_BIT_WE((v), 5) ++#define PWMV4_CHN_CNT_RD_EN(v) FIELD_PREP_WM16(BIT(5), (v)) +/* + * [04:04] W1T | PWM Globally Joined Control Enable + * 1 = this PWM channel will be enabled by a global pwm enable + * bit instead of the PWM Enable bit. + */ -+#define PWMV4_GLOBAL_CTRL_EN(v) REG_UPDATE_BIT_WE((v), 4) ++#define PWMV4_GLOBAL_CTRL_EN(v) FIELD_PREP_WM16(BIT(4), (v)) +/* + * [03:03] RW | Force Clock Enable + * 0 = disabled, if the PWM channel is inactive then so is the + * clock prescale module + */ -+#define PWMV4_FORCE_CLK_EN(v) REG_UPDATE_BIT_WE((v), 3) ++#define PWMV4_FORCE_CLK_EN(v) FIELD_PREP_WM16(BIT(3), (v)) +/* + * [02:02] W1T | PWM Control Update Enable + * 1 = enabled, commits modifications of _CTRL, _PERIOD, _DUTY and + * _OFFSET registers once 1 is written to it + */ -+#define PWMV4_CTRL_UPDATE_EN(v) REG_UPDATE_BIT_WE((v), 2) -+#define PWMV4_CTRL_UPDATE_EN_MASK BIT(2) ++#define PWMV4_CTRL_UPDATE_EN FIELD_PREP_WM16_CONST(BIT(2), 1) +/* + * [01:01] RW | PWM Enable, 1 = enabled + * If in one-shot mode, clears after end of operation + */ -+#define PWMV4_EN(v) REG_UPDATE_BIT_WE((v), 1) +#define PWMV4_EN_MASK BIT(1) ++#define PWMV4_EN(v) FIELD_PREP_WM16(PWMV4_EN_MASK, \ ++ ((v) ? 1 : 0)) +/* + * [00:00] RW | PWM Clock Enable, 1 = enabled + * If in one-shot mode, clears after end of operation + */ -+#define PWMV4_CLK_EN(v) REG_UPDATE_BIT_WE((v), 0) +#define PWMV4_CLK_EN_MASK BIT(0) ++#define PWMV4_CLK_EN(v) FIELD_PREP_WM16(PWMV4_CLK_EN_MASK, \ ++ ((v) ? 1 : 0)) +#define PWMV4_EN_BOTH_MASK (PWMV4_EN_MASK | PWMV4_CLK_EN_MASK) -+static inline __pure bool pwmv4_is_enabled(unsigned int val) ++static inline __pure bool rockchip_pwm_v4_is_enabled(unsigned int val) +{ + return (val & PWMV4_EN_BOTH_MASK); +} @@ -805,38 +552,34 @@ Signed-off-by: Nicolas Frattaroli + * 0 = current channel scale clock + * 1 = global channel scale clock + */ -+#define PWMV4_CLK_GLOBAL(v) REG_UPDATE_BIT_WE((v), 15) ++#define PWMV4_CLK_GLOBAL(v) FIELD_PREP_WM16(BIT(15), (v)) +/* + * [14:13] RW | Clock Source Selection + * 0 = Clock from PLL, frequency can be configured + * 1 = Clock from crystal oscillator, frequency is fixed + * 2 = Clock from RC oscillator, frequency is fixed + * 3 = Reserved -+ * NOTE: This reg is of questionable usefulness on RK3576, as it -+ * just muxes between 100m_50m_24m and 24m directly. The -+ * only use-case I can come up with is if you must use 100m -+ * or 50m PWM on one channel but absolutely require to use -+ * the lower rate 24m clock on another channel on the same -+ * chip, which doesn't seem like a realistic use case. I -+ * suspect that's why downstream doesn't use it. ++ * NOTE: The purpose for this clock-mux-outside-CRU construct is ++ * to let the SoC go into a sleep state with the PWM ++ * hardware still having a clock signal for IR input, which ++ * can then wake up the SoC. + */ +#define PWMV4_CLK_SRC_PLL 0x0U +#define PWMV4_CLK_SRC_CRYSTAL 0x1U +#define PWMV4_CLK_SRC_RC 0x2U -+#define PWMV4_CLK_SRC(v) REG_UPDATE_WE((v), 13, 14) +#define PWMV4_CLK_SRC_SHIFT 13 -+#define PWMV4_CLK_SRC_MASK GENMASK(14, 13) ++#define PWMV4_CLK_SRC_WIDTH 2 +/* + * [12:04] RW | Scale Factor to apply to pre-scaled clock + * 1 <= v <= 256, v means clock divided by 2*v + */ -+#define PWMV4_CLK_SCALE_F(v) REG_UPDATE_WE((v), 4, 12) ++#define PWMV4_CLK_SCALE_F(v) FIELD_PREP_WM16(GENMASK(12, 4), (v)) +/* + * [03:03] RO | Reserved + * [02:00] RW | Prescale Factor + * v here means the input clock is divided by pow(2, v) + */ -+#define PWMV4_CLK_PRESCALE_F(v) REG_UPDATE_WE((v), 0, 2) ++#define PWMV4_CLK_PRESCALE_F(v) FIELD_PREP_WM16(GENMASK(2, 0), (v)) + +#define PWMV4_REG_CTRL 0x00C +/* @@ -849,13 +592,13 @@ Signed-off-by: Nicolas Frattaroli + * By default, the channel selects its own input, but writing v + * here selects PWM input from channel v instead. + */ -+#define PWMV4_CTRL_IN_SEL(v) REG_UPDATE_WE((v), 6, 8) ++#define PWMV4_CTRL_IN_SEL(v) FIELD_PREP_WM16(GENMASK(8, 6), (v)) +/* [05:05] RW | Aligned Mode, 0 = Valid, 1 = Invalid */ -+#define PWMV4_CTRL_UNALIGNED(v) REG_UPDATE_BIT_WE((v), 5) ++#define PWMV4_CTRL_UNALIGNED(v) FIELD_PREP_WM16(BIT(5), (v)) +/* [04:04] RW | Output Mode, 0 = Left Aligned, 1 = Centre Aligned */ +#define PWMV4_LEFT_ALIGNED 0x0U +#define PWMV4_CENTRE_ALIGNED 0x1U -+#define PWMV4_CTRL_OUT_MODE(v) REG_UPDATE_BIT_WE((v), 4) ++#define PWMV4_CTRL_OUT_MODE(v) FIELD_PREP_WM16(BIT(4), (v)) +/* + * [03:03] RW | Inactive Polarity for when the channel is either disabled or + * has completed outputting the entire waveform in one-shot mode. @@ -863,14 +606,15 @@ Signed-off-by: Nicolas Frattaroli + */ +#define PWMV4_POLARITY_N 0x0U +#define PWMV4_POLARITY_P 0x1U -+#define PWMV4_INACTIVE_POL(v) REG_UPDATE_BIT_WE((v), 3) ++#define PWMV4_INACTIVE_POL(v) FIELD_PREP_WM16(BIT(3), (v)) +/* + * [02:02] RW | Duty Cycle Polarity to use at the start of the waveform. + * 0 = Negative, 1 = Positive + */ -+#define PWMV4_DUTY_POL(v) REG_UPDATE_BIT_WE((v), 2) -+#define PWMV4_DUTY_POL_MASK BIT(2) +#define PWMV4_DUTY_POL_SHIFT 2 ++#define PWMV4_DUTY_POL_MASK BIT(PWMV4_DUTY_POL_SHIFT) ++#define PWMV4_DUTY_POL(v) FIELD_PREP_WM16(PWMV4_DUTY_POL_MASK, \ ++ (v)) +/* + * [01:00] RW | PWM Mode + * 0 = One-shot mode, PWM generates waveform RPT times @@ -881,7 +625,8 @@ Signed-off-by: Nicolas Frattaroli +#define PWMV4_MODE_ONESHOT 0x0U +#define PWMV4_MODE_CONT 0x1U +#define PWMV4_MODE_CAPTURE 0x2U -+#define PWMV4_MODE(v) REG_UPDATE_WE((v), 0, 1) ++#define PWMV4_MODE_MASK GENMASK(1, 0) ++#define PWMV4_MODE(v) FIELD_PREP_WM16(PWMV4_MODE_MASK, (v)) +#define PWMV4_CTRL_COM_FLAGS (PWMV4_INACTIVE_POL(PWMV4_POLARITY_N) | \ + PWMV4_DUTY_POL(PWMV4_POLARITY_P) | \ + PWMV4_CTRL_OUT_MODE(PWMV4_LEFT_ALIGNED) | \ @@ -1038,8 +783,10 @@ Signed-off-by: Nicolas Frattaroli + */ +#define PWMV4_INT_LPC BIT(0) +#define PWMV4_INT_HPC BIT(1) -+#define PWMV4_INT_LPC_W(v) REG_UPDATE_BIT_WE(((v) ? 1 : 0), 0) -+#define PWMV4_INT_HPC_W(v) REG_UPDATE_BIT_WE(((v) ? 1 : 0), 1) ++#define PWMV4_INT_LPC_W(v) FIELD_PREP_WM16(PWMV4_INT_LPC, \ ++ ((v) ? 1 : 0)) ++#define PWMV4_INT_HPC_W(v) FIELD_PREP_WM16(PWMV4_INT_HPC, \ ++ ((v) ? 1 : 0)) + +#define PWMV4_REG_INT_EN 0x074 +/* @@ -1090,14 +837,6 @@ Signed-off-by: Nicolas Frattaroli +} + +/** -+ * mfpwm_clk_get_rate - get the currently used clock's rate, in Hz -+ * @mfpwm: pointer to the &struct rockchip_mfpwm instance -+ * -+ * Returns: %0 on error, clock rate in Hz on success -+ */ -+unsigned long mfpwm_clk_get_rate(struct rockchip_mfpwm *mfpwm); -+ -+/** + * mfpwm_acquire - try becoming the active mfpwm function device + * @pwmf: pointer to the calling driver instance's &struct rockchip_mfpwm_func + * @@ -1123,7 +862,7 @@ Signed-off-by: Nicolas Frattaroli + * * %-EBUSY - a different device function is active + * * %-EOVERFLOW - the acquire counter is at its maximum + */ -+int __must_check mfpwm_acquire(struct rockchip_mfpwm_func *pwmf); ++extern int __must_check mfpwm_acquire(struct rockchip_mfpwm_func *pwmf); + +/** + * mfpwm_release - drop usage of active mfpwm device function by 1 @@ -1133,34 +872,6 @@ Signed-off-by: Nicolas Frattaroli + * function remain, set the mfpwm device to have no active device function, + * allowing other device functions to claim it. + */ -+void mfpwm_release(const struct rockchip_mfpwm_func *pwmf); -+ -+/** -+ * mfpwm_pwmclk_enable - enable the pwm clock the signal and timing is based on -+ * @pwmf: pointer to the calling driver instance's &struct rockchip_mfpwm_func -+ * -+ * Context: must be the active device function to call this -+ * -+ * Returns: 0 on success, negative errno on error. -+ */ -+int mfpwm_pwmclk_enable(struct rockchip_mfpwm_func *pwmf); -+ -+/** -+ * mfpwm_pwmclk_disable - disable the pwm clock the signal and timing is based on -+ * @pwmf: pointer to the calling driver instance's &struct rockchip_mfpwm_func -+ * -+ * Context: must be the active device function to call this -+ */ -+void mfpwm_pwmclk_disable(struct rockchip_mfpwm_func *pwmf); -+ -+/** -+ * mfpwm_remove_func - remove a device function driver from the mfpwm -+ * @pwmf: pointer to the calling driver instance's &struct rockchip_mfpwm_func -+ * -+ * If the device function driver described by @pwmf is the currently active -+ * device function, then it'll have its mfpwm_acquires and its pwmclk_enables -+ * balanced and be removed as the active device function driver. -+ */ -+void mfpwm_remove_func(struct rockchip_mfpwm_func *pwmf); ++extern void mfpwm_release(const struct rockchip_mfpwm_func *pwmf); + +#endif /* __SOC_ROCKCHIP_MFPWM_H__ */ diff --git a/target/linux/rockchip/patches-6.12/341-05-pwm-add-rockchip-pwmv4-driver.patch b/target/linux/rockchip/patches-6.12/162-02-pwm-Add-rockchip-PWMv4-driver.patch similarity index 54% rename from target/linux/rockchip/patches-6.12/341-05-pwm-add-rockchip-pwmv4-driver.patch rename to target/linux/rockchip/patches-6.12/162-02-pwm-Add-rockchip-PWMv4-driver.patch index 422f46a9b..7b66a15e1 100644 --- a/target/linux/rockchip/patches-6.12/341-05-pwm-add-rockchip-pwmv4-driver.patch +++ b/target/linux/rockchip/patches-6.12/162-02-pwm-Add-rockchip-PWMv4-driver.patch @@ -1,32 +1,42 @@ +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH v3 3/5] pwm: Add rockchip PWMv4 driver +From: Nicolas Frattaroli +Date: Mon, 27 Oct 2025 18:11:58 +0100 +Message-Id: <20251027-rk3576-pwm-v3-3-654a5cb1e3f8@collabora.com> +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +The Rockchip RK3576 brings with it a new PWM IP, in downstream code +referred to as "v4". This new IP is different enough from the previous +Rockchip IP that I felt it necessary to add a new driver for it, instead +of shoehorning it in the old one. + +Add this new driver, based on the PWM core's waveform APIs. Its platform +device is registered by the parent mfpwm driver, from which it also +receives a little platform data struct, so that mfpwm can guarantee that +all the platform device drivers spread across different subsystems for +this specific hardware IP do not interfere with each other. + Signed-off-by: Nicolas Frattaroli --- MAINTAINERS | 1 + drivers/pwm/Kconfig | 13 ++ drivers/pwm/Makefile | 1 + - drivers/pwm/pwm-rockchip-v4.c | 336 ++++++++++++++++++++++++++++++++++++++++++ - 4 files changed, 351 insertions(+) + drivers/pwm/pwm-rockchip-v4.c | 353 ++++++++++++++++++++++++++++++++++++++++++ + 4 files changed, 368 insertions(+) ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -19962,6 +19962,7 @@ L: linux-rockchip@lists.infradead.org - L: linux-pwm@vger.kernel.org - S: Maintained - F: Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml -+F: drivers/pwm/pwm-rockchip-v4.c - F: drivers/soc/rockchip/mfpwm.c - F: include/soc/rockchip/mfpwm.h - --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig -@@ -540,6 +540,19 @@ config PWM_ROCKCHIP - Generic PWM framework driver for the PWM controller found on - Rockchip SoCs. +@@ -551,6 +551,19 @@ config PWM_RZ_MTU3 + To compile this driver as a module, choose M here: the module + will be called pwm-rz-mtu3. +config PWM_ROCKCHIP_V4 + tristate "Rockchip PWM v4 support" + depends on ARCH_ROCKCHIP || COMPILE_TEST -+ depends on ROCKCHIP_MFPWM + depends on HAS_IOMEM ++ depends on MFD_ROCKCHIP_MFPWM + help + Generic PWM framework driver for the PWM controller found on + later Rockchip SoCs such as the RK3576. @@ -35,9 +45,9 @@ Signed-off-by: Nicolas Frattaroli + to guarantee fearlessly concurrent operation with other functions of + the same device implemented by drivers in other subsystems. + - config PWM_RZ_MTU3 - tristate "Renesas RZ/G2L MTU3a PWM Timer support" - depends on RZ_MTU3 + config PWM_SAMSUNG + tristate "Samsung PWM support" + depends on PLAT_SAMSUNG || ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -49,6 +49,7 @@ obj-$(CONFIG_PWM_RASPBERRYPI_POE) += pwm @@ -50,7 +60,7 @@ Signed-off-by: Nicolas Frattaroli obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o --- /dev/null +++ b/drivers/pwm/pwm-rockchip-v4.c -@@ -0,0 +1,336 @@ +@@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 Collabora Ltd. @@ -61,13 +71,32 @@ Signed-off-by: Nicolas Frattaroli + * other functions of the device from different drivers interfering with its + * operation while it's active. + * ++ * Technical Reference Manual: Chapter 31 of the RK3506 TRM Part 1, a SoC which ++ * uses the same PWM hardware and has a publicly available TRM. ++ * https://opensource.rock-chips.com/images/3/36/Rockchip_RK3506_TRM_Part_1_V1.2-20250811.pdf ++ * + * Authors: + * Nicolas Frattaroli ++ * ++ * Limitations: ++ * - When the output is disabled, it will end abruptly without letting the ++ * current period complete. ++ * TODO: This can be fixed in the driver in the future by having the enable- ++ * to-disable transition switch to oneshot mode with one repetition, ++ * and then disable the pwmclk and release mfpwm when the oneshot ++ * complete interrupt fires. ++ * - When the output is disabled, the pin will remain driven to whatever state ++ * it last had. ++ * - Adjustments to the duty cycle will only take effect during the next period. ++ * - Adjustments to the period length will only take effect during the next ++ * period. ++ * - offset should be between 0 and (period - duty) + */ + ++#include ++#include +#include +#include -+#include + +struct rockchip_pwm_v4 { + struct rockchip_mfpwm_func *pwmf; @@ -90,29 +119,18 @@ Signed-off-by: Nicolas Frattaroli + * rockchip_pwm_v4_round_single - convert a PWM parameter to hardware + * @rate: clock rate of the PWM clock, as per clk_get_rate + * @in_val: parameter in nanoseconds to convert -+ * @out_val: pointer to location where converted result should be stored. + * -+ * If @out_val is %NULL, no calculation is performed. -+ * -+ * Return: -+ * * %0 - Success -+ * * %-EOVERFLOW - Result too large for target type ++ * Returns the rounded value, saturating at U32_MAX if too large + */ -+static int rockchip_pwm_v4_round_single(unsigned long rate, u64 in_val, -+ u32 *out_val) ++static u32 rockchip_pwm_v4_round_single(unsigned long rate, u64 in_val) +{ + u64 tmp; + -+ if (!out_val) -+ return 0; -+ -+ tmp = mult_frac(rate, in_val, NSEC_PER_SEC); ++ tmp = mul_u64_u64_div_u64(rate, in_val, NSEC_PER_SEC); + if (tmp > U32_MAX) -+ return -EOVERFLOW; ++ tmp = U32_MAX; + -+ *out_val = tmp; -+ -+ return 0; ++ return (u32)tmp; +} + +/** @@ -122,42 +140,27 @@ Signed-off-by: Nicolas Frattaroli + * @period: PWM period in nanoseconds + * @offset: PWM offset in nanoseconds + * @out_duty: pointer to where the rounded duty value should be stored -+ * if NULL, don't calculate or store it + * @out_period: pointer to where the rounded period value should be stored -+ * if NULL, don't calculate or store it + * @out_offset: pointer to where the rounded offset value should be stored -+ * if NULL, don't calculate or store it + * + * Convert nanosecond-based duty/period/offset parameters to the PWM hardware's -+ * native rounded representation in number of cycles at clock rate @rate. If an -+ * out_ parameter is a NULL pointer, the corresponding parameter will not be -+ * calculated or stored. Should an overflow error occur for any of the -+ * parameters, assume the data at all the out_ locations is invalid and may not -+ * even have been touched at all. -+ * -+ * Return: -+ * * %0 - Success -+ * * %-EOVERFLOW - One of the results is too large for the PWM hardware ++ * native rounded representation in number of cycles at clock rate @rate. Should ++ * any of the input parameters be out of range for the hardware, the ++ * corresponding output parameter is the maximum permissible value for said ++ * parameter with considerations to the others. + */ -+static int rockchip_pwm_v4_round_params(unsigned long rate, u64 duty, ++static void rockchip_pwm_v4_round_params(unsigned long rate, u64 duty, + u64 period, u64 offset, u32 *out_duty, + u32 *out_period, u32 *out_offset) +{ -+ int ret; ++ *out_period = rockchip_pwm_v4_round_single(rate, period); + -+ ret = rockchip_pwm_v4_round_single(rate, duty, out_duty); -+ if (ret) -+ return ret; ++ *out_duty = rockchip_pwm_v4_round_single(rate, duty); + -+ ret = rockchip_pwm_v4_round_single(rate, period, out_period); -+ if (ret) -+ return ret; -+ -+ ret = rockchip_pwm_v4_round_single(rate, offset, out_offset); -+ if (ret) -+ return ret; -+ -+ return 0; ++ /* As per TRM, PWM_OFFSET: "The value ranges from 0 to (period-duty)" */ ++ *out_offset = rockchip_pwm_v4_round_single(rate, offset); ++ if (*out_offset > (*out_period - *out_duty)) ++ *out_offset = *out_period - *out_duty; +} + +static int rockchip_pwm_v4_round_wf_tohw(struct pwm_chip *chip, @@ -168,30 +171,24 @@ Signed-off-by: Nicolas Frattaroli + struct rockchip_pwm_v4 *pc = to_rockchip_pwm_v4(chip); + struct rockchip_pwm_v4_wf *wfhw = _wfhw; + unsigned long rate; -+ int ret = 0; + -+ /* We do not want chosen_clk to change out from under us here */ -+ ret = mfpwm_acquire(pc->pwmf); -+ if (ret) -+ return ret; ++ rate = clk_get_rate(pc->pwmf->core); + -+ rate = mfpwm_clk_get_rate(pc->pwmf->parent); -+ -+ ret = rockchip_pwm_v4_round_params(rate, wf->duty_length_ns, -+ wf->period_length_ns, -+ wf->duty_offset_ns, &wfhw->duty, -+ &wfhw->period, &wfhw->offset); ++ rockchip_pwm_v4_round_params(rate, wf->duty_length_ns, ++ wf->period_length_ns, wf->duty_offset_ns, ++ &wfhw->duty, &wfhw->period, &wfhw->offset); + + if (wf->period_length_ns > 0) + wfhw->enable = PWMV4_EN_BOTH_MASK; + else + wfhw->enable = 0; + -+ dev_dbg(&chip->dev, "tohw: duty = %u, period = %u, offset = %u, rate %lu\n", -+ wfhw->duty, wfhw->period, wfhw->offset, rate); ++ dev_dbg(&chip->dev, ++ "tohw: duty %llu -> %u, period %llu -> %u, offset %llu -> %u, rate %lu\n", ++ wf->duty_length_ns, wfhw->duty, wf->period_length_ns, ++ wfhw->period, wf->duty_offset_ns, wfhw->offset, rate); + -+ mfpwm_release(pc->pwmf); -+ return ret; ++ return 0; +} + +static int rockchip_pwm_v4_round_wf_fromhw(struct pwm_chip *chip, @@ -202,37 +199,28 @@ Signed-off-by: Nicolas Frattaroli + struct rockchip_pwm_v4 *pc = to_rockchip_pwm_v4(chip); + const struct rockchip_pwm_v4_wf *wfhw = _wfhw; + unsigned long rate; -+ int ret = 0; + -+ /* We do not want chosen_clk to change out from under us here */ -+ ret = mfpwm_acquire(pc->pwmf); -+ if (ret) -+ return ret; ++ rate = clk_get_rate(pc->pwmf->core); + -+ rate = mfpwm_clk_get_rate(pc->pwmf->parent); ++ if (rockchip_pwm_v4_is_enabled(wfhw->enable)) { ++ if (!rate) ++ return -EINVAL; + -+ /* Let's avoid a cool division-by-zero if the clock's busted. */ -+ if (!rate) { -+ ret = -EINVAL; -+ goto out_mfpwm_release; ++ wf->period_length_ns = (u64)wfhw->period * NSEC_PER_SEC / rate; ++ wf->duty_length_ns = (u64)wfhw->duty * NSEC_PER_SEC / rate; ++ wf->duty_offset_ns = (u64)wfhw->offset * NSEC_PER_SEC / rate; ++ } else { ++ wf->period_length_ns = 0; ++ wf->duty_length_ns = 0; ++ wf->duty_offset_ns = 0; + } + -+ wf->duty_length_ns = mult_frac(wfhw->duty, NSEC_PER_SEC, rate); ++ dev_dbg(&chip->dev, ++ "fromhw: duty %u -> %llu, period %u -> %llu, offset %u -> %llu, rate %lu\n", ++ wfhw->duty, wf->duty_length_ns, wfhw->period, ++ wf->period_length_ns, wfhw->offset, wf->duty_offset_ns, rate); + -+ if (pwmv4_is_enabled(wfhw->enable)) -+ wf->period_length_ns = mult_frac(wfhw->period, NSEC_PER_SEC, -+ rate); -+ else -+ wf->period_length_ns = 0; -+ -+ wf->duty_offset_ns = mult_frac(wfhw->offset, NSEC_PER_SEC, rate); -+ -+ dev_dbg(&chip->dev, "fromhw: duty = %llu, period = %llu, offset = %llu\n", -+ wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns); -+ -+out_mfpwm_release: -+ mfpwm_release(pc->pwmf); -+ return ret; ++ return 0; +} + +static int rockchip_pwm_v4_read_wf(struct pwm_chip *chip, struct pwm_device *pwm, @@ -269,8 +257,8 @@ Signed-off-by: Nicolas Frattaroli + if (ret) + return ret; + -+ was_enabled = pwmv4_is_enabled(mfpwm_reg_read(pc->pwmf->base, -+ PWMV4_REG_ENABLE)); ++ was_enabled = rockchip_pwm_v4_is_enabled(mfpwm_reg_read(pc->pwmf->base, ++ PWMV4_REG_ENABLE)); + + /* + * "But Nicolas", you ask with valid concerns, "why would you enable the @@ -285,7 +273,7 @@ Signed-off-by: Nicolas Frattaroli + * use relaxed writes; it's not worth the footgun. + */ + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, -+ REG_UPDATE_WE(wfhw->enable, 0, 1)); ++ FIELD_PREP_WM16(PWMV4_EN_BOTH_MASK, wfhw->enable)); + + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_PERIOD, wfhw->period); + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_DUTY, wfhw->duty); @@ -295,14 +283,19 @@ Signed-off-by: Nicolas Frattaroli + + /* Commit new configuration to hardware output. */ + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, -+ PWMV4_CTRL_UPDATE_EN(1)); ++ PWMV4_CTRL_UPDATE_EN); + -+ if (pwmv4_is_enabled(wfhw->enable)) { ++ if (rockchip_pwm_v4_is_enabled(wfhw->enable)) { + if (!was_enabled) { -+ dev_dbg(&chip->dev, "enabling PWM output\n"); -+ ret = mfpwm_pwmclk_enable(pc->pwmf); ++ dev_dbg(&chip->dev, "Enabling PWM output\n"); ++ ret = clk_enable(pc->pwmf->core); + if (ret) + goto err_mfpwm_release; ++ ret = clk_rate_exclusive_get(pc->pwmf->core); ++ if (ret) { ++ clk_disable(pc->pwmf->core); ++ goto err_mfpwm_release; ++ } + + /* + * Output should be on now, acquire device to guarantee @@ -313,8 +306,9 @@ Signed-off-by: Nicolas Frattaroli + goto err_mfpwm_release; + } + } else if (was_enabled) { -+ dev_dbg(&chip->dev, "disabling PWM output\n"); -+ mfpwm_pwmclk_disable(pc->pwmf); ++ dev_dbg(&chip->dev, "Disabling PWM output\n"); ++ clk_rate_exclusive_put(pc->pwmf->core); ++ clk_disable(pc->pwmf->core); + /* Output is off now, extra release to balance extra acquire */ + mfpwm_release(pc->pwmf); + } @@ -325,7 +319,6 @@ Signed-off-by: Nicolas Frattaroli + return ret; +} + -+/* We state the PWM chip is atomic, so none of these functions should sleep. */ +static const struct pwm_ops rockchip_pwm_v4_ops = { + .sizeof_wfhw = sizeof(struct rockchip_pwm_v4_wf), + .round_waveform_tohw = rockchip_pwm_v4_round_wf_tohw, @@ -334,39 +327,73 @@ Signed-off-by: Nicolas Frattaroli + .write_waveform = rockchip_pwm_v4_write_wf, +}; + ++static bool rockchip_pwm_v4_on_and_continuous(struct rockchip_pwm_v4 *pc) ++{ ++ bool en; ++ u32 val; ++ ++ en = rockchip_pwm_v4_is_enabled(mfpwm_reg_read(pc->pwmf->base, ++ PWMV4_REG_ENABLE)); ++ val = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_CTRL); ++ ++ return en && ((val & PWMV4_MODE_MASK) == PWMV4_MODE_CONT); ++} ++ +static int rockchip_pwm_v4_probe(struct platform_device *pdev) +{ + struct rockchip_mfpwm_func *pwmf = dev_get_platdata(&pdev->dev); + struct rockchip_pwm_v4 *pc; + struct pwm_chip *chip; ++ struct device *dev = &pdev->dev; + int ret; + -+ chip = devm_pwmchip_alloc(&pdev->dev, 1, sizeof(*pc)); ++ /* ++ * For referencing the PWM in the DT to work, we need the parent MFD ++ * device's OF node. Code shamelessly adapted from `drivers/pci/of.c`'s ++ * pci_set_of_node(), which does this for bus reasons. ++ */ ++ dev->parent->of_node_reused = true; ++ device_set_node(dev, ++ of_fwnode_handle(no_free_ptr(dev->parent->of_node))); ++ ++ chip = devm_pwmchip_alloc(dev, 1, sizeof(*pc)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + pc = to_rockchip_pwm_v4(chip); + pc->pwmf = pwmf; + -+ platform_set_drvdata(pdev, pc); ++ ret = mfpwm_acquire(pwmf); ++ if (ret == -EBUSY) ++ dev_warn(dev, "PWM hardware already in use, can't check initial state\n"); ++ else if (ret < 0) ++ return dev_err_probe(dev, ret, "Couldn't acquire mfpwm in probe\n"); ++ ++ if (!rockchip_pwm_v4_on_and_continuous(pc)) ++ mfpwm_release(pwmf); ++ else { ++ dev_dbg(dev, "PWM was already on at probe time\n"); ++ ret = clk_enable(pwmf->core); ++ if (ret) ++ return dev_err_probe(dev, ret, "Enabling pwm clock failed\n"); ++ ret = clk_rate_exclusive_get(pc->pwmf->core); ++ if (ret) { ++ clk_disable(pwmf->core); ++ return dev_err_probe(dev, ret, "Protecting pwm clock failed\n"); ++ } ++ } ++ ++ platform_set_drvdata(pdev, chip); + + chip->ops = &rockchip_pwm_v4_ops; -+ chip->atomic = true; + -+ ret = pwmchip_add(chip); ++ ret = devm_pwmchip_add(dev, chip); + if (ret) -+ return dev_err_probe(&pdev->dev, ret, "failed to add PWM chip\n"); ++ return dev_err_probe(dev, ret, "Failed to add PWM chip\n"); + + return 0; +} + -+static void rockchip_pwm_v4_remove(struct platform_device *pdev) -+{ -+ struct rockchip_pwm_v4 *pc = platform_get_drvdata(pdev); -+ -+ mfpwm_remove_func(pc->pwmf); -+} -+ +static const struct platform_device_id rockchip_pwm_v4_ids[] = { + { .name = "pwm-rockchip-v4", }, + { /* sentinel */ } @@ -375,7 +402,6 @@ Signed-off-by: Nicolas Frattaroli + +static struct platform_driver rockchip_pwm_v4_driver = { + .probe = rockchip_pwm_v4_probe, -+ .remove = rockchip_pwm_v4_remove, + .driver = { + .name = "pwm-rockchip-v4", + }, @@ -387,3 +413,4 @@ Signed-off-by: Nicolas Frattaroli +MODULE_DESCRIPTION("Rockchip PWMv4 Driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("ROCKCHIP_MFPWM"); ++MODULE_ALIAS("platform:pwm-rockchip-v4"); diff --git a/target/linux/rockchip/patches-6.12/341-06-counter-add-rockchip-pwm-capture-driver.patch b/target/linux/rockchip/patches-6.12/162-03-counter-Add-rockchip-pwm-capture-driver.patch similarity index 68% rename from target/linux/rockchip/patches-6.12/341-06-counter-add-rockchip-pwm-capture-driver.patch rename to target/linux/rockchip/patches-6.12/162-03-counter-Add-rockchip-pwm-capture-driver.patch index 9526e90f6..3779018ce 100644 --- a/target/linux/rockchip/patches-6.12/341-06-counter-add-rockchip-pwm-capture-driver.patch +++ b/target/linux/rockchip/patches-6.12/162-03-counter-Add-rockchip-pwm-capture-driver.patch @@ -1,21 +1,30 @@ +From git@z Thu Jan 1 00:00:00 1970 +Subject: [PATCH v3 4/5] counter: Add rockchip-pwm-capture driver +From: Nicolas Frattaroli +Date: Mon, 27 Oct 2025 18:11:59 +0100 +Message-Id: <20251027-rk3576-pwm-v3-4-654a5cb1e3f8@collabora.com> +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 7bit + +Among many other things, Rockchip's new PWMv4 IP in the RK3576 supports +PWM capture functionality. + +Add a basic driver for this that works to expose HPC/LPC counts and +state change events to userspace through the counter framework. It's +quite basic, but works well enough to demonstrate the device function +exclusion stuff that mfpwm does, in order to eventually support all the +functions of this device in drivers within their appropriate subsystems, +without them interfering with each other. + Signed-off-by: Nicolas Frattaroli --- MAINTAINERS | 1 + drivers/counter/Kconfig | 13 ++ drivers/counter/Makefile | 1 + - drivers/counter/rockchip-pwm-capture.c | 341 +++++++++++++++++++++++++++++++++ - 4 files changed, 356 insertions(+) + drivers/counter/rockchip-pwm-capture.c | 306 +++++++++++++++++++++++++++++++++ + 4 files changed, 321 insertions(+) ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -19962,6 +19962,7 @@ L: linux-rockchip@lists.infradead.org - L: linux-pwm@vger.kernel.org - S: Maintained - F: Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml -+F: drivers/counter/rockchip-pwm-capture.c - F: drivers/pwm/pwm-rockchip-v4.c - F: drivers/soc/rockchip/mfpwm.c - F: include/soc/rockchip/mfpwm.h --- a/drivers/counter/Kconfig +++ b/drivers/counter/Kconfig @@ -90,6 +90,19 @@ config MICROCHIP_TCB_CAPTURE @@ -25,7 +34,7 @@ Signed-off-by: Nicolas Frattaroli +config ROCKCHIP_PWM_CAPTURE + tristate "Rockchip PWM Counter Capture driver" + depends on ARCH_ROCKCHIP || COMPILE_TEST -+ depends on ROCKCHIP_MFPWM ++ depends on MFD_ROCKCHIP_MFPWM + depends on HAS_IOMEM + help + Generic counter framework driver for the multi-function PWM on @@ -47,16 +56,16 @@ Signed-off-by: Nicolas Frattaroli +obj-$(CONFIG_ROCKCHIP_PWM_CAPTURE) += rockchip-pwm-capture.o --- /dev/null +++ b/drivers/counter/rockchip-pwm-capture.c -@@ -0,0 +1,341 @@ +@@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2025 Collabora Ltd. + * + * A counter driver for the Pulse-Width-Modulation (PWM) hardware found on + * Rockchip SoCs such as the RK3576, internally referred to as "PWM v4". It -+ * allows for measuring the period and duty cycle in nanoseconds through the -+ * generic counter framework, while guaranteeing exclusive use over the MFPWM -+ * device while the counter is enabled. ++ * allows for measuring the high cycles and low cycles of a PWM signal through ++ * the generic counter framework, while guaranteeing exclusive use over the ++ * MFPWM device while the counter is enabled. + * + * Authors: + * Nicolas Frattaroli @@ -66,35 +75,35 @@ Signed-off-by: Nicolas Frattaroli +#include +#include +#include -+#include ++#include +#include ++#include +#include +#include -+#include -+#include + +#define RKPWMC_INT_MASK (PWMV4_INT_LPC | PWMV4_INT_HPC) -+/* -+ * amount of jiffies between no PWM signal change and us deciding PWM is off. -+ * PWM signals with a period longer than this won't be detected by us, which is -+ * the trade-off we make to have a faster response time when a signal is turned -+ * off. -+ */ -+#define RKPWMC_CLEAR_DELAY (max(msecs_to_jiffies(100), 1)) + +struct rockchip_pwm_capture { + struct rockchip_mfpwm_func *pwmf; ++ struct counter_device *counter; + bool is_enabled; + spinlock_t enable_lock; -+ atomic_t lpc; -+ atomic_t hpc; -+ atomic_t captures_left; -+ struct delayed_work clear_capture; +}; + ++/* ++ * Channel 0 receives a state changed notification whenever the LPC interrupt ++ * fires. ++ * ++ * Channel 1 receives a state changed notification whenever the HPC interrupt ++ * fires. ++ */ +static struct counter_signal rkpwmc_signals[] = { + { + .id = 0, ++ .name = "Channel 0" ++ }, ++ { ++ .id = 1, + .name = "Channel 1" + }, +}; @@ -110,6 +119,11 @@ Signed-off-by: Nicolas Frattaroli + .num_actions = ARRAY_SIZE(rkpwmc_hpc_lpc_actions), + .signal = &rkpwmc_signals[0] + }, ++ { ++ .actions_list = rkpwmc_hpc_lpc_actions, ++ .num_actions = ARRAY_SIZE(rkpwmc_hpc_lpc_actions), ++ .signal = &rkpwmc_signals[1] ++ }, +}; + +static const enum counter_function rkpwmc_functions[] = { @@ -154,15 +168,18 @@ Signed-off-by: Nicolas Frattaroli + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, + PWMV4_EN(true) | PWMV4_CLK_EN(true)); + -+ ret = mfpwm_pwmclk_enable(pc->pwmf); ++ ret = clk_enable(pc->pwmf->core); + if (ret) + goto err_release; + -+ ret = mfpwm_acquire(pc->pwmf); ++ ret = clk_rate_exclusive_get(pc->pwmf->core); + if (ret) + goto err_disable_pwm_clk; + -+ atomic_set(&pc->captures_left, 4); ++ ret = mfpwm_acquire(pc->pwmf); ++ if (ret) ++ goto err_unprotect_pwm_clk; ++ + pc->is_enabled = true; + } else { + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INT_EN, @@ -170,14 +187,8 @@ Signed-off-by: Nicolas Frattaroli + PWMV4_INT_HPC_W(false)); + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_ENABLE, + PWMV4_EN(false) | PWMV4_CLK_EN(false)); -+ /* -+ * Do not use cancel_delayed_work here unless you want -+ * to cause the interrupt handler, which may still be -+ * running at this point, to stall. Similarly, don't do -+ * flush_delayed_work since it may sleep. -+ */ -+ mod_delayed_work(system_bh_wq, &pc->clear_capture, 0); -+ mfpwm_pwmclk_disable(pc->pwmf); ++ clk_rate_exclusive_put(pc->pwmf->core); ++ clk_disable(pc->pwmf->core); + pc->is_enabled = false; + mfpwm_release(pc->pwmf); + } @@ -187,8 +198,10 @@ Signed-off-by: Nicolas Frattaroli + + return 0; + ++err_unprotect_pwm_clk: ++ clk_rate_exclusive_put(pc->pwmf->core); +err_disable_pwm_clk: -+ mfpwm_pwmclk_disable(pc->pwmf); ++ clk_disable(pc->pwmf->core); +err_release: + mfpwm_release(pc->pwmf); + @@ -200,14 +213,14 @@ Signed-off-by: Nicolas Frattaroli +}; + +enum rkpwmc_count_id { -+ COUNT_PERIOD = 0, -+ COUNT_DUTY = 1, ++ COUNT_LPC = 0, ++ COUNT_HPC = 1, +}; + +static struct counter_count rkpwmc_counts[] = { + { -+ .id = COUNT_PERIOD, -+ .name = "Period in nanoseconds", ++ .id = COUNT_LPC, ++ .name = "PWM core clock cycles during which the signal was low", + .functions_list = rkpwmc_functions, + .num_functions = ARRAY_SIZE(rkpwmc_functions), + .synapses = rkpwmc_pwm_synapses, @@ -216,8 +229,8 @@ Signed-off-by: Nicolas Frattaroli + .num_ext = ARRAY_SIZE(rkpwmc_ext), + }, + { -+ .id = COUNT_DUTY, -+ .name = "Duty cycle in nanoseconds", ++ .id = COUNT_HPC, ++ .name = "PWM core clock cycles during which the signal was high", + .functions_list = rkpwmc_functions, + .num_functions = ARRAY_SIZE(rkpwmc_functions), + .synapses = rkpwmc_pwm_synapses, @@ -231,39 +244,24 @@ Signed-off-by: Nicolas Frattaroli + struct counter_count *count, u64 *value) +{ + struct rockchip_pwm_capture *pc = counter_priv(counter); -+ unsigned long rate; -+ u64 period; -+ u64 lpc; -+ u64 hpc; -+ int ret = 0; + -+ if (count->id != COUNT_PERIOD && count->id != COUNT_DUTY) ++ guard(spinlock)(&pc->enable_lock); ++ ++ if (!pc->is_enabled) { ++ *value = 0; ++ return 0; ++ } ++ ++ switch (count->id) { ++ case COUNT_LPC: ++ *value = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_LPC); ++ return 0; ++ case COUNT_HPC: ++ *value = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_HPC); ++ return 0; ++ default: + return -EINVAL; -+ -+ ret = mfpwm_acquire(pc->pwmf); -+ if (ret) -+ return ret; -+ -+ rate = mfpwm_clk_get_rate(pc->pwmf->parent); -+ if (!rate) { -+ ret = -EINVAL; -+ goto out_release; + } -+ -+ hpc = (u32) atomic_read(&pc->hpc); -+ -+ if (count->id == COUNT_PERIOD) { -+ lpc = (u32) atomic_read(&pc->lpc); -+ period = mult_frac((hpc + lpc), NSEC_PER_SEC, rate); -+ *value = period; -+ } else { -+ *value = mult_frac(hpc, NSEC_PER_SEC, rate); -+ } -+ -+out_release: -+ mfpwm_release(pc->pwmf); -+ -+ return ret; +} + +static const struct counter_ops rkpwmc_ops = { @@ -275,8 +273,6 @@ Signed-off-by: Nicolas Frattaroli + struct rockchip_pwm_capture *pc = data; + u32 intsts; + u32 clr = 0; -+ u32 lpc; -+ u32 hpc; + + intsts = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_INTSTS); + @@ -285,43 +281,24 @@ Signed-off-by: Nicolas Frattaroli + + if (intsts & PWMV4_INT_LPC) { + clr |= PWMV4_INT_LPC; -+ atomic_dec_if_positive(&pc->captures_left); ++ counter_push_event(pc->counter, COUNTER_EVENT_CHANGE_OF_STATE, 0); + } + + if (intsts & PWMV4_INT_HPC) { + clr |= PWMV4_INT_HPC; -+ atomic_dec_if_positive(&pc->captures_left); -+ } -+ -+ /* After 4 interrupts, reset to 4 captures left and read the regs */ -+ if (!atomic_cmpxchg(&pc->captures_left, 0, 4)) { -+ lpc = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_LPC); -+ hpc = mfpwm_reg_read(pc->pwmf->base, PWMV4_REG_HPC); -+ -+ atomic_set(&pc->lpc, lpc); -+ atomic_set(&pc->hpc, hpc); -+ mod_delayed_work(system_bh_wq, &pc->clear_capture, -+ RKPWMC_CLEAR_DELAY); ++ counter_push_event(pc->counter, COUNTER_EVENT_CHANGE_OF_STATE, 1); + } + + if (clr) + mfpwm_reg_write(pc->pwmf->base, PWMV4_REG_INTSTS, clr); + -+ if (intsts ^ clr) ++ /* If other interrupt status bits are set, they're not for this driver */ ++ if (intsts != clr) + return IRQ_NONE; + + return IRQ_HANDLED; +} + -+static void rkpwmc_clear_capture_worker(struct work_struct *work) -+{ -+ struct rockchip_pwm_capture *pc = container_of( -+ work, struct rockchip_pwm_capture, clear_capture.work); -+ -+ atomic_set(&pc->hpc, 0); -+ atomic_set(&pc->lpc, 0); -+} -+ +static int rockchip_pwm_capture_probe(struct platform_device *pdev) +{ + struct rockchip_mfpwm_func *pwmf = dev_get_platdata(&pdev->dev); @@ -329,6 +306,11 @@ Signed-off-by: Nicolas Frattaroli + struct counter_device *counter; + int ret; + ++ /* Set our (still unset) OF node to the parent MFD device's OF node */ ++ pdev->dev.parent->of_node_reused = true; ++ device_set_node(&pdev->dev, ++ of_fwnode_handle(no_free_ptr(pdev->dev.parent->of_node))); ++ + counter = devm_counter_alloc(&pdev->dev, sizeof(*pc)); + if (IS_ERR(counter)) + return PTR_ERR(counter); @@ -344,9 +326,6 @@ Signed-off-by: Nicolas Frattaroli + if (ret) + return dev_err_probe(&pdev->dev, ret, "Failed requesting IRQ\n"); + -+ ret = devm_delayed_work_autocancel(&pdev->dev, &pc->clear_capture, -+ rkpwmc_clear_capture_worker); -+ + counter->name = pdev->name; + counter->signals = rkpwmc_signals; + counter->num_signals = ARRAY_SIZE(rkpwmc_signals); @@ -354,6 +333,8 @@ Signed-off-by: Nicolas Frattaroli + counter->counts = rkpwmc_counts; + counter->num_counts = ARRAY_SIZE(rkpwmc_counts); + ++ pc->counter = counter; ++ + ret = devm_counter_add(&pdev->dev, counter); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "Failed to add counter\n"); @@ -361,13 +342,6 @@ Signed-off-by: Nicolas Frattaroli + return 0; +} + -+static void rockchip_pwm_capture_remove(struct platform_device *pdev) -+{ -+ struct rockchip_mfpwm_func *pwmf = dev_get_platdata(&pdev->dev); -+ -+ mfpwm_remove_func(pwmf); -+} -+ +static const struct platform_device_id rockchip_pwm_capture_id_table[] = { + { .name = "rockchip-pwm-capture", }, + { /* sentinel */ }, @@ -376,7 +350,6 @@ Signed-off-by: Nicolas Frattaroli + +static struct platform_driver rockchip_pwm_capture_driver = { + .probe = rockchip_pwm_capture_probe, -+ .remove = rockchip_pwm_capture_remove, + .id_table = rockchip_pwm_capture_id_table, + .driver = { + .name = "rockchip-pwm-capture", @@ -389,3 +362,4 @@ Signed-off-by: Nicolas Frattaroli +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("ROCKCHIP_MFPWM"); +MODULE_IMPORT_NS("COUNTER"); ++MODULE_ALIAS("platform:rockchip-pwm-capture"); diff --git a/target/linux/rockchip/patches-6.12/341-02-dt-bindings-pwm-add-a-new-binding-for-rockchip-rk3576-pwm.patch b/target/linux/rockchip/patches-6.12/341-02-dt-bindings-pwm-add-a-new-binding-for-rockchip-rk3576-pwm.patch deleted file mode 100644 index 78ce3bafe..000000000 --- a/target/linux/rockchip/patches-6.12/341-02-dt-bindings-pwm-add-a-new-binding-for-rockchip-rk3576-pwm.patch +++ /dev/null @@ -1,119 +0,0 @@ -Signed-off-by: Nicolas Frattaroli ---- - .../bindings/pwm/rockchip,rk3576-pwm.yaml | 94 ++++++++++++++++++++++ - MAINTAINERS | 7 ++ - 2 files changed, 101 insertions(+) - ---- /dev/null -+++ b/Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml -@@ -0,0 +1,94 @@ -+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -+%YAML 1.2 -+--- -+$id: http://devicetree.org/schemas/pwm/rockchip,rk3576-pwm.yaml# -+$schema: http://devicetree.org/meta-schemas/core.yaml# -+ -+title: Rockchip PWMv4 controller -+ -+maintainers: -+ - Nicolas Frattaroli -+ -+description: | -+ The Rockchip PWMv4 controller is a PWM controller found on several Rockchip -+ SoCs, such as the RK3576. -+ -+ It supports both generating and capturing PWM signals. -+ -+allOf: -+ - $ref: pwm.yaml# -+ -+properties: -+ compatible: -+ items: -+ - const: rockchip,rk3576-pwm -+ -+ reg: -+ maxItems: 1 -+ -+ clocks: -+ minItems: 2 -+ items: -+ - description: Used to derive the PWM signal. -+ - description: Used as the APB bus clock. -+ - description: Used as an added alternative to derive the PWM signal. -+ -+ clock-names: -+ minItems: 2 -+ items: -+ - const: pwm -+ - const: pclk -+ - const: osc -+ -+ interrupts: -+ maxItems: 1 -+ -+ "#pwm-cells": -+ const: 3 -+ -+required: -+ - compatible -+ - reg -+ - clocks -+ - clock-names -+ - interrupts -+ -+additionalProperties: false -+ -+examples: -+ - | -+ #include -+ #include -+ #include -+ -+ soc { -+ #address-cells = <2>; -+ #size-cells = <2>; -+ -+ pwm@2add0000 { -+ compatible = "rockchip,rk3576-pwm"; -+ reg = <0x0 0x2add0000 0x0 0x1000>; -+ clocks = <&cru CLK_PWM1>, <&cru PCLK_PWM1>, <&cru CLK_OSC_PWM1>; -+ clock-names = "pwm", "pclk", "osc"; -+ interrupts = ; -+ #pwm-cells = <3>; -+ }; -+ }; -+ - | -+ #include -+ #include -+ #include -+ -+ soc { -+ #address-cells = <2>; -+ #size-cells = <2>; -+ -+ pwm@2ade3000 { -+ compatible = "rockchip,rk3576-pwm"; -+ reg = <0x0 0x2ade3000 0x0 0x1000>; -+ clocks = <&cru CLK_PWM2>, <&cru PCLK_PWM2>; -+ clock-names = "pwm", "pclk"; -+ interrupts = ; -+ #pwm-cells = <3>; -+ }; -+ }; ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -19956,6 +19956,13 @@ F: Documentation/userspace-api/media/v4l - F: drivers/media/platform/rockchip/rkisp1 - F: include/uapi/linux/rkisp1-config.h - -+ROCKCHIP MFPWM -+M: Nicolas Frattaroli -+L: linux-rockchip@lists.infradead.org -+L: linux-pwm@vger.kernel.org -+S: Maintained -+F: Documentation/devicetree/bindings/pwm/rockchip,rk3576-pwm.yaml -+ - ROCKCHIP RK3568 RANDOM NUMBER GENERATOR SUPPORT - M: Daniel Golle - M: Aurelien Jarno diff --git a/target/linux/rockchip/patches-6.12/341-03-soc-rockchip-add-utils-header-for-things-shared-across-drivers.patch b/target/linux/rockchip/patches-6.12/341-03-soc-rockchip-add-utils-header-for-things-shared-across-drivers.patch deleted file mode 100644 index 2e1a2a253..000000000 --- a/target/linux/rockchip/patches-6.12/341-03-soc-rockchip-add-utils-header-for-things-shared-across-drivers.patch +++ /dev/null @@ -1,84 +0,0 @@ -Signed-off-by: Nicolas Frattaroli ---- - include/soc/rockchip/utils.h | 76 ++++++++++++++++++++++++++++++++++++++++++++ - 1 file changed, 76 insertions(+) - ---- /dev/null -+++ b/include/soc/rockchip/utils.h -@@ -0,0 +1,76 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Copyright (c) 2025 Collabora Ltd. -+ * -+ * Utility types, inline functions, and macros that are used across several -+ * Rockchip-specific drivers. -+ * -+ * Authors: -+ * Nicolas Frattaroli -+ */ -+ -+#ifndef __SOC_ROCKCHIP_UTILS_H__ -+#define __SOC_ROCKCHIP_UTILS_H__ -+ -+#include -+#include -+#include -+ -+/* -+ * Incoming macro basilisks, stare directly at them at your own peril. -+ * As a gentle reminder to help with code comprehension: BUILD_BUG_ON_ZERO -+ * is confusingly named; it's a version of BUILD_BUG_ON that evaluates to zero -+ * if it does not trigger, i.e. the assertion within the macro still checks -+ * for a truthy value, not zero. -+ */ -+ -+/** -+ * REG_UPDATE_WE - generate a register write value with a write-enable mask -+ * @_val: unshifted value we wish to update between @_low and @_high -+ * @_low: index of the low bit of the bit range we want to update -+ * @_high: index of the high bit of the bit range we want to update -+ * -+ * This macro statically generates a value consisting of @_val shifted to the -+ * left by @_low, and a write-enable mask in the upper 16 bits of the value -+ * that sets bit ``i << 16`` to ``1`` if bit ``i`` is within the @_low to @_high -+ * range. Only up to bit (@_high - @_low) of @_val is used for safety, i.e. -+ * trying to write a value that doesn't fit in the specified range will simply -+ * truncate it. -+ * -+ * This is useful for some hardware, like some of Rockchip's registers, where -+ * a 32-bit width register is divided into a value low half, and a write enable -+ * high half. Bits in the low half are only update if the corresponding bit in -+ * the high half is ``1``, allowing for lock-free atomic updates of a register. -+ * -+ * This macro replaces the venerable ``HIWORD_UPDATE``, which is copied and -+ * pasted in slightly different forms across many different Rockchip drivers. -+ * Before switching drivers to use it, familiarise yourself with the semantics -+ * of your specific ``HIWORD_UPDATE`` compared to this function-like macro's -+ * semantics. -+ * -+ * Return: the value, shifted into place, with the required write-enable bits -+ */ -+#define REG_UPDATE_WE(_val, _low, _high) ( \ -+ BUILD_BUG_ON_ZERO(const_true((_low) > (_high))) + \ -+ BUILD_BUG_ON_ZERO(const_true((_high) > 15)) + \ -+ BUILD_BUG_ON_ZERO(const_true((_low) < 0)) + \ -+ BUILD_BUG_ON_ZERO(const_true((u64) (_val) > U16_MAX)) + \ -+ ((_val & GENMASK((_high) - (_low), 0)) << (_low) | \ -+ (GENMASK((_high), (_low)) << 16))) -+ -+/** -+ * REG_UPDATE_BIT_WE - update a bit with a write-enable mask -+ * @__val: new value of the bit, either ``0`` 0r ``1`` -+ * @__bit: bit index to modify, 0 <= @__bit < 16. -+ * -+ * This is like REG_UPDATE_WE() but only modifies a single bit, thereby making -+ * invocation easier by avoiding having to pass a repeated value. -+ * -+ * Return: a value with bit @__bit set to @__val and @__bit << 16 set to ``1`` -+ */ -+#define REG_UPDATE_BIT_WE(__val, __bit) ( \ -+ BUILD_BUG_ON_ZERO(const_true((__val) > 1)) + \ -+ BUILD_BUG_ON_ZERO(const_true((__val) < 0)) + \ -+ REG_UPDATE_WE((__val), (__bit), (__bit))) -+ -+#endif /* __SOC_ROCKCHIP_UTILS_H__ */ diff --git a/target/linux/rockchip/patches-6.12/341-07-pwm-pwmv4-add-device-set-of-node-from-parent.patch b/target/linux/rockchip/patches-6.12/341-07-pwm-pwmv4-add-device-set-of-node-from-parent.patch deleted file mode 100644 index 5eec7451c..000000000 --- a/target/linux/rockchip/patches-6.12/341-07-pwm-pwmv4-add-device-set-of-node-from-parent.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/drivers/pwm/pwm-rockchip-v4.c -+++ b/drivers/pwm/pwm-rockchip-v4.c -@@ -292,6 +292,8 @@ static int rockchip_pwm_v4_probe(struct - if (IS_ERR(chip)) - return PTR_ERR(chip); - -+ device_set_of_node_from_dev(&pdev->dev, pdev->dev.parent); -+ - pc = to_rockchip_pwm_v4(chip); - pc->pwmf = pwmf; -