Skip to content

Conversation

@ruiqurm
Copy link

@ruiqurm ruiqurm commented Mar 25, 2025

This patchset introduces out-of-band (OOB) support for Rockchip PWM controllers, enabling real-time PWM control through the EVL framework. The changes include:

  • Implementation of the pwm_cdev interface structure to expose PWM channels to userspace through /dev/pwm.

  • Generic support for OOB PWM control, providing an abstraction for real-time, low-latency updates to PWM state.

  • Rockchip-specific OOB PWM driver that integrates with the EVL kernel to provide deterministic PWM updates via the OOB path.

Tested on Rock5b+/5c(rk3588) platform.

Example test:

#include<evl/thread.h>
#include<sys/ioctl.h>
#include<evl/clock.h>
#include<stdbool.h>
#include<linux/pwm.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define MIN_THROTTLE 125000 // 125us
#define MAX_THROTTLE 250000 // 250us
#define ONE_SECOND_IN_NS 1000000000
#define FREQUENCY 500
const long PERIOD = ONE_SECOND_IN_NS / FREQUENCY;
struct timespec period_in_timespec = {
        .tv_sec = 0,
    .tv_nsec = PERIOD
};

void timespec_add_inplace(struct timespec *a, const struct timespec *b) {
    a->tv_sec += b->tv_sec;
    a->tv_nsec += b->tv_nsec;

    if (a->tv_nsec >= 1000000000L) {
        a->tv_sec += 1;
        a->tv_nsec -= 1000000000L;
    }
}

void init_esc(int fd){
        struct pwm_state_request req;
        struct timespec now;
        int ret;
        req.enabled = true;
        req.oneshot_count = 1;
        req.duty_cycle = MIN_THROTTLE;
        req.polarity = PWM_UAPI_POLARITY_NORMAL;
        req.period = MIN_THROTTLE;
        ret = evl_read_clock(EVL_CLOCK_MONOTONIC,&now);
        if (ret < 0){
                perror("%d read clock failed\n",ret);
                exit(-1);
        }
        for (int i =0;i<FREQUENCY*2;i++){
                ret = oob_ioctl(fd,PWM_SET_STATE_IOCTL,&req);
                if (ret < 0){
                perror("Failed to change pwm state");
                        exit(-1);
                }
                timespec_add_inplace(&now,&period_in_timespec);
                evl_sleep_until(EVL_CLOCK_MONOTONIC,&now);
        }
}
void test(int fd){
        struct pwm_state_request req;
        struct timespec now;
        int ret;
        int step = 50;
        req.enabled = true;
        req.oneshot_count = 1;
        req.duty_cycle = 125000;
        req.polarity = PWM_UAPI_POLARITY_NORMAL;
        req.period = 125000;
        ret = evl_read_clock(EVL_CLOCK_MONOTONIC,&now);
        if (ret < 0){
                perror("%d read clock failed\n",ret);
                exit(-1);
        }
        for (int i =0;i<FREQUENCY*2;i++){
                ret = oob_ioctl(fd,PWM_SET_STATE_IOCTL,&req);
                if (ret < 0){
                perror("Failed to change pwm state.period=%d",req.period);
                        exit(-1);
                }
                req.duty_cycle += step;
                req.period += step;
                timespec_add_inplace(&now,&period_in_timespec);
                evl_sleep_until(EVL_CLOCK_MONOTONIC,&now);
        }
}
int set_throttle(int fd,double throttle_in_precent){
        struct pwm_state_request req;
        int ret;
        long throttle_period;
        if (throttle_in_precent < 0 || throttle_in_precent > 1){
                return -1;
        }
        throttle_period =  throttle_in_precent * (MAX_THROTTLE - MIN_THROTTLE) + MIN_THROTTLE;
        req.enabled = true;
        req.oneshot_count = 1;
        req.duty_cycle = throttle_period;
        req.polarity = PWM_UAPI_POLARITY_NORMAL;
        req.period = throttle_period;
        ret = ioctl(fd,PWM_SET_STATE_IOCTL,&req);
        return ret;
}

void disable_pwm(int fd){
        int ret;
        struct pwm_state_request req;
        req.enabled = false;
        req.polarity= PWM_UAPI_POLARITY_NORMAL;
        ret = oob_ioctl(fd,PWM_SET_STATE_IOCTL,&req);
        if (ret < 0){
        perror("Failed to disable pwm");
                exit(-1);
        }
        close(fd);
}

int main(){
        int fd;
        struct pwm_state_request req;

        fd = open("/dev/pwm1",O_WRONLY | 010000000000);
        if (fd < 0) {
            perror("Failed to open /dev/pwm0\n");
            return 1;
        }

        evl_attach_self("test_pwm");

        init_esc(fd);
        test(fd);
        disable_pwm(fd);
        return 0;
}

ruiqurm added 4 commits March 25, 2025 14:43
To enable oob pwm operations, we need a cdev to do some oob_ioctl()s. The commit only provide a basic interface.
* add `oob_apply`, `oob_prepare` and `oob_finish` operations to support out-of-bound pwm handling.
* Support `oneshot-mode`  for rockchip.
* Support v1_config only.
* Support regular, one-shot and capture mode. But not test the `capture` mode so far.
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;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There could be some concurrency problems, where user use two threads to call the oob_apply and apply at same time.

}


static irqreturn_t rockchip_pwm_oob_irq_v1(int irq, void *data)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the difference between rockchip_pwm_oob_irq_v1 and the oridinary is that the oob function call the oob apply. Maybe we can have some tricks to bridge the gap.

}

static struct class * pwm_cdev_class;
static const struct file_operations pwm_fops = {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could add an operation for compat pointer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants