From 06864ab9f5114c153e516c2d324dec811bf4b7f0 Mon Sep 17 00:00:00 2001 From: ruiqurm Date: Tue, 25 Mar 2025 14:43:44 +0800 Subject: [PATCH 1/5] pwm: cdev: add character device for pwm. To enable oob pwm operations, we need a cdev to do some oob_ioctl()s. The commit only provide a basic interface. --- drivers/pwm/Kconfig | 8 ++ drivers/pwm/Makefile | 1 + drivers/pwm/pwmlib-cdev.c | 156 ++++++++++++++++++++++++++++++++++++++ drivers/pwm/pwmlib-cdev.h | 13 ++++ include/linux/pwm.h | 6 ++ include/uapi/linux/pwm.h | 57 ++++++++++++++ 6 files changed, 241 insertions(+) create mode 100644 drivers/pwm/pwmlib-cdev.c create mode 100644 drivers/pwm/pwmlib-cdev.h create mode 100644 include/uapi/linux/pwm.h diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 782bc70138e08..b38e5888f1f8f 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -701,4 +701,12 @@ config PWM_XILINX To compile this driver as a module, choose M here: the module will be called pwm-xilinx. +config PWM_CDEV + bool "PWM Character device (/dev/pwm) support" if EXPERT + default n + help + Say Y here to add the character device /dev/pwm interface + for PWM. The character device allows userspace to control GPIOs + using ioctl() operations. + endif diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index fe2f53833a65e..fbfea9c90032e 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_PWM) += core.o +obj-$(CONFIG_PWM_CDEV) += pwmlib-cdev.o obj-$(CONFIG_PWM_SYSFS) += sysfs.o obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o diff --git a/drivers/pwm/pwmlib-cdev.c b/drivers/pwm/pwmlib-cdev.c new file mode 100644 index 0000000000000..328306f6d9a9c --- /dev/null +++ b/drivers/pwm/pwmlib-cdev.c @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include +#include +#include +#include "pwmlib-cdev.h" + +#define PWM_DEVT_CHIP_BASE_OFFSET 5 +#define PWM_CHIP_BASE(dev) ((unsigned int)((dev) << PWM_DEVT_CHIP_BASE_OFFSET)) +#define PWMCHIP_NAME "pwmchip" +#define PWM_DEV_MAX 256 + +static dev_t pwm_devt; +static bool pwmlib_initialized = true; + +struct pwmlib_context { + struct pwm_device *pwm; +}; + +static long pwmlib_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct pwmlib_context *ctx = filp->private_data; + struct pwm_state state; + struct pwm_state_request ustate; + int ret; + void __user *uarg = (void __user *)arg; + switch (cmd) { + case PWM_GET_STATE_IOCTL: + pwm_get_state(ctx->pwm, &state); + return copy_to_user(uarg, &state, sizeof(ustate)) ? -EFAULT : 0; + + case PWM_SET_STATE_IOCTL: + ret = copy_from_user(&ustate, uarg, sizeof(ustate)); + if (ret) { + return -EFAULT; + } + pwm_get_state(ctx->pwm, &state); + state.period = ustate.period; + state.duty_cycle = ustate.duty_cycle; + state.polarity = ustate.polarity == PWM_UAPI_POLARITY_NORMAL ? + PWM_POLARITY_NORMAL : + PWM_POLARITY_INVERSED; + state.enabled = ustate.enabled; + ret = pwm_apply_state(ctx->pwm, &state); + return ret; + default: + return -EINVAL; + } + + return 0; +} + +static int pwmlib_open(struct inode *inode, struct file *filp) +{ + struct cdev *cdev = inode->i_cdev; + struct pwm_device *pwm; + struct pwmlib_context *ctx; + int ret = 0; + + pwm = container_of(cdev, struct pwm_device, cdev); + if (!pwm->chip) { + ret = -ENODEV; + return ret; + } + pwm = pwm_request_from_chip(pwm->chip, pwm->hwpwm, "cdev"); + if (IS_ERR(pwm)) { + return PTR_ERR(pwm); + } + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + goto alloc_fail; + } + ctx->pwm = pwm; + filp->private_data = ctx; + nonseekable_open(inode, filp); + + return 0; + +alloc_fail: + pwm_put(pwm); + return ret; +} + +static int pwmlib_release(struct inode *inode, struct file *filp) +{ + struct cdev *cdev = inode->i_cdev; + struct pwm_device *pwm = container_of(cdev, struct pwm_device, cdev); + struct pwmlib_context *ctx = filp->private_data; + + kfree(ctx); + pwm_put(pwm); + + return 0; +} + +static struct class * pwm_cdev_class; +static const struct file_operations pwm_fops = { + .unlocked_ioctl = pwmlib_ioctl, + .release = pwmlib_release, + .open = pwmlib_open, + .owner = THIS_MODULE, + .llseek = no_llseek, +}; + +static int __init pwm_cdev_init(void) +{ + int ret; + pwm_cdev_class = class_create(THIS_MODULE, "pwm_cdev"); + if (IS_ERR(pwm_cdev_class)){ + pr_err("failed to create class\n"); + return PTR_ERR(pwm_cdev_class); + } + + ret = alloc_chrdev_region(&pwm_devt, 0, PWM_DEV_MAX, PWMCHIP_NAME); + if (ret < 0) { + pr_err("pwmlib: failed to allocate char dev region\n"); + return ret; + } + pwmlib_initialized = true; + + return 0; +} +core_initcall(pwm_cdev_init); + +int pwmlib_cdev_register(struct pwm_device *pdev) +{ + int ret; + dev_t devt; + cdev_init(&pdev->cdev, &pwm_fops); + pdev->cdev.owner = THIS_MODULE; + devt = MKDEV(MAJOR(pwm_devt), + PWM_CHIP_BASE(pdev->chip->base) | pdev->hwpwm); + device_initialize(&pdev->dev); + pdev->dev.devt = devt; + pdev->dev.class = pwm_cdev_class; + pdev->dev.parent = pdev->chip->dev; + dev_set_name(&pdev->dev, "pwm%d", pdev->chip->base); + + ret = cdev_device_add(&pdev->cdev, &pdev->dev); + if (ret) + return ret; + + dev_dbg(pdev->chip->dev, "added PWM chardev (%d:%d)\n", MAJOR(devt), + MINOR(devt)); + return 0; +} +EXPORT_SYMBOL(pwmlib_cdev_register); + +void pwmlib_cdev_unregister(struct pwm_device *pdev) +{ + cdev_device_del(&pdev->cdev, &pdev->dev); +} +EXPORT_SYMBOL(pwmlib_cdev_unregister); \ No newline at end of file diff --git a/drivers/pwm/pwmlib-cdev.h b/drivers/pwm/pwmlib-cdev.h new file mode 100644 index 0000000000000..f0952807a81c3 --- /dev/null +++ b/drivers/pwm/pwmlib-cdev.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef PWMLIB_CDEV_H +#define PWMLIB_CDEV_H + +#include + +struct pwm_device; + +int pwmlib_cdev_register(struct pwm_device *pdev); +void pwmlib_cdev_unregister(struct pwm_device *pdev); + +#endif /* PWMLIB_CDEV_H */ diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 1a9f06ab9ccb3..75e9b3a8b1531 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -2,6 +2,7 @@ #ifndef __LINUX_PWM_H #define __LINUX_PWM_H +#include "linux/cdev.h" #include #include #include @@ -80,6 +81,8 @@ struct pwm_state { * @args: PWM arguments * @state: last applied state * @last: last implemented state (for PWM_DEBUG) + * @dev: pwm device struct. + * @cdev: pwm character device. */ struct pwm_device { const char *label; @@ -92,6 +95,9 @@ struct pwm_device { struct pwm_args args; struct pwm_state state; struct pwm_state last; + + struct device dev; + struct cdev cdev; }; /** diff --git a/include/uapi/linux/pwm.h b/include/uapi/linux/pwm.h new file mode 100644 index 0000000000000..bbcfe7410f6ad --- /dev/null +++ b/include/uapi/linux/pwm.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * - userspace ABI for the GPIO character devices + * + * Copyright (C) 2025 Qichen Qiu + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ +#ifndef _UAPI_PWM_H_ +#define _UAPI_PWM_H_ + +#include + +/* +* polarity of pwm wawe +*/ +enum uapi_pwm_polarity { + PWM_UAPI_POLARITY_NORMAL, + PWM_UAPI_POLARITY_INVERSED, +}; + +/** + * struct pwm_state_request - Change state of current pwm. + * @period: PWM period (in nanoseconds) + * @duty_cycle: PWM duty cycle (in nanoseconds) + * @polarity: PWM polarity + * @enabled: PWM enabled status + * @oenshot_count: If oneshot_count is non zero and the device support one-shot + * mode, the device will only triger oneshot_count times of + * PWM wave. + * @oneshot_repeat: Reserved. + * @usage_power: If set, the PWM driver is only required to maintain the power + * output but has more freedom regarding signal form. + * If supported, the signal can be optimized, for example to + * improve EMI by phase shifting individual channels. + + */ +struct pwm_state_request { + u64 period; + u64 duty_cycle; + enum uapi_pwm_polarity polarity; + u64 oneshot_count; + u32 oneshot_repeat; + bool enabled; + bool usage_power; +}; + +/* + * v1 ioctl()s + * + */ +#define PWM_GET_STATE_IOCTL _IOR(0xBE, 0x01, struct pwm_state_request) +#define PWM_SET_STATE_IOCTL _IOW(0xBE, 0x02, struct pwm_state_request) + +#endif /* _PWM_GPIO_H */ \ No newline at end of file From 5249575773090d9348f87ac8e8e426b1823a4d3c Mon Sep 17 00:00:00 2001 From: ruiqurm Date: Tue, 25 Mar 2025 14:47:09 +0800 Subject: [PATCH 2/5] pwm: dovtail: add generic support for out-of-bound pwm control. * add `oob_apply`, `oob_prepare` and `oob_finish` operations to support out-of-bound pwm handling. * Support `oneshot-mode` for rockchip. --- drivers/pwm/Kconfig | 6 +++ drivers/pwm/core.c | 77 +++++++++++++++++++++++++++++++++++++++ drivers/pwm/pwmlib-cdev.c | 76 ++++++++++++++++++++++++++++++++++++++ include/linux/pwm.h | 11 ++++++ 4 files changed, 170 insertions(+) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index b38e5888f1f8f..aaebebc0fc0ee 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -709,4 +709,10 @@ config PWM_CDEV for PWM. The character device allows userspace to control GPIOs using ioctl() operations. +config PWM_OOB + bool "Out-of_band PWM calls" + depends on EVL + select PWM_CDEV + help + Enable suuport for out-of-band PWM state request. endif diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index f281e5e32bf66..21eeb73aa2641 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -593,6 +593,83 @@ int pwm_apply_state(struct pwm_device *pwm, const struct pwm_state *state) } EXPORT_SYMBOL_GPL(pwm_apply_state); + +#if IS_ENABLED(CONFIG_PWM_OOB) +/** + * pwm_oob_apply_state() - atomically apply a new state to a PWM device + * @pwm: PWM device + * @state: new state to apply + * + * Unlike pwm_apply_state, the function must not sleep and use limited + * inband service, so that the function can get realtime guarantee. + */ +int pwm_oob_apply_state(struct pwm_device *pwm, const struct pwm_state *state){ + struct pwm_chip *chip; + int err; + if (!pwm || !state || !state->period || + state->duty_cycle > state->period) + return -EINVAL; + + chip = pwm->chip; + + if (state->period == pwm->state.period && + state->duty_cycle == pwm->state.duty_cycle && + state->polarity == pwm->state.polarity && +#ifdef CONFIG_PWM_ROCKCHIP_ONESHOT + state->oneshot_count == pwm->state.oneshot_count && + state->oneshot_repeat == pwm->state.oneshot_repeat && + state->duty_offset == pwm->state.duty_offset && +#endif + state->enabled == pwm->state.enabled && + state->usage_power == pwm->state.usage_power) + return 0; + + err = chip->ops->oob_apply(chip, pwm, state); + if (err) + return err; + + trace_pwm_apply(pwm, state); + + pwm->state = *state; + + return 0; +} + +/** + * pwm_oob_prepare() - setup the device before starting oob service. + * @pwm: PWM device + */ +int pwm_oob_prepare(struct pwm_device* pwm){ + struct pwm_chip* chip; + int err; + chip = pwm->chip; + + if (!chip->ops->oob_prepare){ + return -ENOSYS; + } + + err = chip->ops->oob_prepare(chip, pwm); + if (err) + return err; + return 0; +} + +/** + * pwm_oob_finish() - setup the device after finishing oob service. + * @pwm: PWM device + */ +void pwm_oob_finish(struct pwm_device* pwm){ + struct pwm_chip* chip; + chip = pwm->chip; + + if (!chip->ops->oob_finish){ + return; + } + + chip->ops->oob_finish(chip, pwm); +} +#endif + /** * pwm_capture() - capture and report a PWM signal * @pwm: PWM device diff --git a/drivers/pwm/pwmlib-cdev.c b/drivers/pwm/pwmlib-cdev.c index 328306f6d9a9c..304ddd48680bc 100644 --- a/drivers/pwm/pwmlib-cdev.c +++ b/drivers/pwm/pwmlib-cdev.c @@ -17,6 +17,10 @@ static bool pwmlib_initialized = true; struct pwmlib_context { struct pwm_device *pwm; +#ifdef CONFIG_PWM_OOB + struct evl_file efile; + bool is_oob; +#endif }; static long pwmlib_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) @@ -43,6 +47,9 @@ static long pwmlib_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) PWM_POLARITY_NORMAL : PWM_POLARITY_INVERSED; state.enabled = ustate.enabled; +#ifdef CONFIG_PWM_ROCKCHIP_ONESHOT + state.oneshot_count = ustate.oneshot_count; +#endif ret = pwm_apply_state(ctx->pwm, &state); return ret; default: @@ -52,6 +59,47 @@ static long pwmlib_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return 0; } +static long pwm_oob_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct pwmlib_context *ctx = filp->private_data; + void __user *uarg = (void __user *)arg; + int ret; + struct pwm_state_request ustate; + struct pwm_state state; + + if (!ctx->is_oob) { + return -EPERM; + } + + switch (cmd) { + case PWM_GET_STATE_IOCTL: + pwm_get_state(ctx->pwm, &state); + return copy_to_user(uarg, &state, sizeof(ustate)) ? -EFAULT : 0; + case PWM_SET_STATE_IOCTL: + ret = copy_from_user(&ustate, uarg, sizeof(ustate)); + if (ret) { + return -EFAULT; + } + if (ustate.polarity != PWM_UAPI_POLARITY_NORMAL) { + return -EINVAL; + } + pwm_get_state(ctx->pwm, &state); + state.duty_cycle = ustate.duty_cycle; + state.enabled = ustate.enabled; + state.period = ustate.period; + state.polarity = PWM_POLARITY_NORMAL; +#ifdef CONFIG_PWM_ROCKCHIP_ONESHOT + state.oneshot_count = ustate.oneshot_count; +#endif + return pwm_oob_apply_state(ctx->pwm, &state); + default: + return -EINVAL; + } + + return 0; +} + static int pwmlib_open(struct inode *inode, struct file *filp) { struct cdev *cdev = inode->i_cdev; @@ -73,12 +121,31 @@ static int pwmlib_open(struct inode *inode, struct file *filp) if (!ctx) { goto alloc_fail; } +#ifdef CONFIG_PWM_OOB + if (filp->f_flags & O_OOB) { + ret = evl_open_file(&ctx->efile, filp); + if (ret) { + goto evl_open_failed; + } + ret = pwm_oob_prepare(pwm); + if (ret) { + goto prepare_failed; + } + ctx->is_oob = true; + } +#endif ctx->pwm = pwm; filp->private_data = ctx; nonseekable_open(inode, filp); return 0; +#ifdef CONFIG_PWM_OOB +evl_open_failed: + evl_release_file(&ctx->efile); +prepare_failed: + kfree(ctx); +#endif alloc_fail: pwm_put(pwm); return ret; @@ -90,6 +157,12 @@ static int pwmlib_release(struct inode *inode, struct file *filp) struct pwm_device *pwm = container_of(cdev, struct pwm_device, cdev); struct pwmlib_context *ctx = filp->private_data; +#ifdef CONFIG_PWM_OOB + if (filp->f_mode & O_OOB) { + pwm_oob_finish(pwm); + evl_release_file(&ctx->efile); + } +#endif kfree(ctx); pwm_put(pwm); @@ -103,6 +176,9 @@ static const struct file_operations pwm_fops = { .open = pwmlib_open, .owner = THIS_MODULE, .llseek = no_llseek, +#ifdef CONFIG_PWM_OOB + .oob_ioctl = pwm_oob_ioctl +#endif }; static int __init pwm_cdev_init(void) diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 75e9b3a8b1531..a6a6294ad7517 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -289,6 +289,12 @@ struct pwm_ops { const struct pwm_state *state); int (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state); +#if IS_ENABLED(CONFIG_PWM_OOB) + int (*oob_apply)(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state); + int (*oob_prepare)(struct pwm_chip *chip, struct pwm_device *pwm); + void (*oob_finish)(struct pwm_chip *chip, struct pwm_device *pwm); +#endif struct module *owner; }; @@ -420,6 +426,11 @@ struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id); struct pwm_device *devm_fwnode_pwm_get(struct device *dev, struct fwnode_handle *fwnode, const char *con_id); +#if IS_ENABLED(CONFIG_PWM_OOB) +int pwm_oob_apply_state(struct pwm_device *pwm, const struct pwm_state *state); +int pwm_oob_prepare(struct pwm_device* device); +void pwm_oob_finish(struct pwm_device* device); +#endif #else static inline struct pwm_device *pwm_request(int pwm_id, const char *label) { From 96f30758e47dcdcf192015e7e516f958ef65190b Mon Sep 17 00:00:00 2001 From: ruiqurm Date: Tue, 25 Mar 2025 14:48:51 +0800 Subject: [PATCH 3/5] pwm: rockchip: Add oob capacity for rockchip-pwm. * Support v1_config only. * Support regular, one-shot and capture mode. But not test the `capture` mode so far. --- drivers/pwm/pwm-rockchip.c | 139 ++++++++++++++++++++++++++++++++++++- 1 file changed, 137 insertions(+), 2 deletions(-) diff --git a/drivers/pwm/pwm-rockchip.c b/drivers/pwm/pwm-rockchip.c index 3b8a5945dd56a..4117233cea996 100644 --- a/drivers/pwm/pwm-rockchip.c +++ b/drivers/pwm/pwm-rockchip.c @@ -6,6 +6,7 @@ * Copyright (C) 2014 ROCKCHIP, Inc. */ +#include "pwmlib-cdev.h" #include #include #include @@ -22,6 +23,9 @@ #include #include "pwm-rockchip-irq-callbacks.h" +#define CREATE_TRACE_POINTS +#include + #define PWM_MAX_CHANNEL_NUM 8 /* @@ -480,7 +484,7 @@ static void rockchip_pwm_config_v1(struct pwm_chip *chip, struct pwm_device *pwm delay_ns = DIV_ROUND_UP_ULL(div, pc->clk_rate); } - local_irq_save(flags); + flags = hard_local_irq_save(); ctrl = readl_relaxed(pc->base + PWM_CTRL_V1); if (pc->data->vop_pwm) { @@ -573,7 +577,7 @@ static void rockchip_pwm_config_v1(struct pwm_chip *chip, struct pwm_device *pwm } writel(ctrl, pc->base + PWM_CTRL_V1); - local_irq_restore(flags); + hard_local_irq_restore(flags); } static int rockchip_pwm_enable_v1(struct pwm_chip *chip, struct pwm_device *pwm, bool enable) @@ -1908,10 +1912,123 @@ static inline void rockchip_pwm_debugfs_deinit(struct rockchip_pwm_chip *pc) } #endif +#ifdef CONFIG_PWM_OOB +int rockchip_pwm_oob_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state){ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + u32 enable_conf = pc->data->enable_conf; + u32 val; + bool enable = state->enabled; + val = readl_relaxed(pc->base + PWM_CTRL_V1); + val &= ~pc->data->enable_conf_mask; + + if (PWM_OUTPUT_CENTER & pc->data->enable_conf_mask) + if (pc->center_aligned) + val |= PWM_OUTPUT_CENTER; + + if (pc->oneshot_en) { + enable_conf &= ~PWM_MODE_MASK; + enable_conf |= PWM_ONESHOT; + } else if (pc->capture_en) { + enable_conf &= ~PWM_MODE_MASK; + enable_conf |= PWM_CAPTURE; + } + + if (enable) { + val |= enable_conf; + }else if (pc->capture_en) + val |= PWM_CAPTURE; + + writel_relaxed(val, pc->base + PWM_CTRL_V1); + + if (unlikely((pc->data->vop_pwm))) + pc->vop_pwm_en = enable; + + rockchip_pwm_config(chip, pwm,state); + + trace_pwm_apply(pwm, state); + + pwm->state = *state; + return 0; +} + +int rockchip_pwm_oob_prepare(struct pwm_chip *chip, struct pwm_device *pwm){ + int err; + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + if(!pc->oneshot_en){ + err = clk_enable(pc->pclk); + if (err) + return err; + } + + err = pinctrl_select_state(pc->pinctrl, pc->active_state); + return err; +} +void rockchip_pwm_oob_finish(struct pwm_chip *chip, struct pwm_device *pwm){ + struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip); + if (!pc->oneshot_en){ + clk_disable(pc->pclk); + } +} + + +static irqreturn_t rockchip_pwm_oob_irq_v1(int irq, void *data) +{ + struct rockchip_pwm_chip *pc = data; + struct pwm_state state; + u32 int_ctrl; + unsigned int id = pc->channel_id; + int val; + + if (id > 3) + return IRQ_NONE; + val = readl_relaxed(pc->base + PWM_REG_INTSTS(id)); + + if ((val & PWM_CH_INT(id)) == 0) + return IRQ_NONE; + + writel_relaxed(PWM_CH_INT(id), pc->base + PWM_REG_INTSTS(id)); + + if (pc->oneshot_en) { + /* + * Set pwm state to disabled when the oneshot mode finished. + */ + pwm_get_state(&pc->chip.pwms[0], &state); + state.enabled = false; + rockchip_pwm_oob_apply(pc->chip.pwms[0].chip,&pc->chip.pwms[0],&state); + } else if (pc->capture_en) { + /* + * Capture input waveform: + * _______ _______ + * | | | | + * __| |_______________| |________ + * ^0 ^1 ^2 + * + * At position 0, the interrupt comes, and DUTY_LPR reg shows the + * low polarity cycles which should be ignored. The effective high + * and low polarity cycles will be calculated in position 1 and + * position 2, where the interrupt comes. + */ + if (pc->capture_cnt++ > 3) { + int_ctrl = readl_relaxed(pc->base + PWM_REG_INT_EN(pc->channel_id)); + int_ctrl &= ~PWM_CH_INT(pc->channel_id); + writel_relaxed(int_ctrl, pc->base + PWM_REG_INT_EN(pc->channel_id)); + } + } + + return IRQ_HANDLED; +} +#endif + static const struct pwm_ops rockchip_pwm_ops = { .capture = rockchip_pwm_capture, .apply = rockchip_pwm_apply, .get_state = rockchip_pwm_get_state, + #ifdef CONFIG_PWM_OOB + .oob_apply = rockchip_pwm_oob_apply, + .oob_prepare = rockchip_pwm_oob_prepare, + .oob_finish = rockchip_pwm_oob_finish, + #endif .owner = THIS_MODULE, }; @@ -2057,6 +2174,8 @@ static int rockchip_pwm_get_channel_id(const char *name) return name[len - 2] - '0'; } +static irqreturn_t rockchip_pwm_oob_irq_v1(int irq, void *data); + static int rockchip_pwm_probe(struct platform_device *pdev) { const struct of_device_id *id; @@ -2177,10 +2296,17 @@ static int rockchip_pwm_probe(struct platform_device *pdev) dev_warn(&pdev->dev, "Can't get oneshot mode irq and oneshot interrupt is unsupported\n"); } else { + #ifdef CONFIG_PWM_OOB + ret = devm_request_irq(&pdev->dev, pc->irq, + rockchip_pwm_oob_irq_v1, + IRQF_NO_SUSPEND | IRQF_SHARED | IRQF_OOB, + "oob_rk_pwm_oneshot_irq", pc); + #else ret = devm_request_irq(&pdev->dev, pc->irq, pc->data->funcs.irq_handler, IRQF_NO_SUSPEND | IRQF_SHARED, "rk_pwm_oneshot_irq", pc); + #endif if (ret) { dev_err(&pdev->dev, "Claim oneshot IRQ failed\n"); goto err_pclk; @@ -2228,6 +2354,12 @@ static int rockchip_pwm_probe(struct platform_device *pdev) } } + #ifdef CONFIG_PWM_OOB + ret = pwmlib_cdev_register(&pc->chip.pwms[0]); + if (ret) { + dev_err(&pdev->dev,"Can't create pwm character device\n"); + } + #endif return 0; err_pclk: @@ -2262,6 +2394,9 @@ static int rockchip_pwm_remove(struct platform_device *pdev) } } + #ifdef CONFIG_PWM_OOB + pwmlib_cdev_unregister(&pc->chip.pwms[0]); + #endif pwmchip_remove(&pc->chip); if (pc->oneshot_en) From f1901e85abd8a9cf41b67100b1881e98eb7142f2 Mon Sep 17 00:00:00 2001 From: ruiqurm Date: Tue, 25 Mar 2025 15:00:27 +0800 Subject: [PATCH 4/5] arm64/configs: update rockchip defconfig with OOB pwm support. --- arch/arm64/configs/rockchip_linux_defconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/arm64/configs/rockchip_linux_defconfig b/arch/arm64/configs/rockchip_linux_defconfig index bc27847a943b0..270e2d35583fc 100644 --- a/arch/arm64/configs/rockchip_linux_defconfig +++ b/arch/arm64/configs/rockchip_linux_defconfig @@ -1184,6 +1184,8 @@ CONFIG_IIO_SYSFS_TRIGGER=y CONFIG_PWM=y CONFIG_PWM_GPIO=y CONFIG_PWM_ROCKCHIP=m +CONFIG_PWM_ROCKCHIP_ONESHOT=y +CONFIG_PWM_OOB=y CONFIG_PHY_ROCKCHIP_CSI2_DPHY=y CONFIG_PHY_ROCKCHIP_DP=y CONFIG_PHY_ROCKCHIP_EMMC=y From 0bcd3e8b7d1ab47f619aa18b7fa08d4bb8738040 Mon Sep 17 00:00:00 2001 From: ruiqurm Date: Mon, 14 Apr 2025 19:44:17 +0800 Subject: [PATCH 5/5] pwm: fix type error in uapi pwm.h. --- include/uapi/linux/pwm.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/uapi/linux/pwm.h b/include/uapi/linux/pwm.h index bbcfe7410f6ad..b409df2a967b9 100644 --- a/include/uapi/linux/pwm.h +++ b/include/uapi/linux/pwm.h @@ -38,13 +38,13 @@ enum uapi_pwm_polarity { */ struct pwm_state_request { - u64 period; - u64 duty_cycle; + __u64 period; + __u64 duty_cycle; enum uapi_pwm_polarity polarity; - u64 oneshot_count; - u32 oneshot_repeat; - bool enabled; - bool usage_power; + __u64 oneshot_count; + __u32 oneshot_repeat; + __u8 enabled; + __u8 usage_power; }; /*