瑞芯微Rockchip开发者社区
直播中

泡芙奶昔

11年用户 716经验值
私信 关注
[问答]

基于RK3399 PWM驱动开发的过程是怎样的?

基于RK3399 PWM驱动开发的过程是怎样的?

回帖(1)

刘畅

2022-3-7 09:37:38
  1. 主板型号:AIO-3399J

2. 芯片型号:RK3399

3. 操作系统版本:Android 7.1

4. Linux版本:v4.4.103

        PWM(Pulse Width Modulation)控制——脉冲宽度调制技术,通过对一系列脉冲的宽度进行调制,来等效地获得所需要波形(含形状和幅值),驱动的设备是雷达扫描电机RPLIDARA3M1内部带有具有可调速功能的电机驱动器,可通过接口中MOTOCTL信号对旋转电机的启动、停止以及旋转速度进行控制。MOTOCTL可视为特定频率和占空比的PWM信号,此时电机的旋转速度将取决于输入MOTOCTL PWM信号的占空比,它的典型值为25000HZ的方波信号,雷达旋转频率10Hz的高脉宽占空比60%。

5、配置 PWM DTS 节点



  • pwm_id:需要申请的pwm通道数。
  • min_period:周期时长最小值。
  • max_period:周期时长最大值。
  • duty_ns:pwm 的占空比激活的时长,单位 ns。




6、 开发PWM 内核驱动:kernel/drivers/pwm/pwm-rockchip.c

实现基本的PWM字符驱动程序,实现以下模块:初始化设备、设备打开、PWM数据的读写和控制、设备释放、设备卸载。

#include
#include
#include
#include
#include
#include
#include
#include
#include

#define PWM_CTRL_TIMER_EN        (1 << 0)
#define PWM_CTRL_OUTPUT_EN        (1 << 3)

#define PWM_ENABLE                (1 << 0)
#define PWM_CONTINUOUS                (1 << 1)
#define PWM_DUTY_POSITIVE        (1 << 3)
#define PWM_DUTY_NEGATIVE        (0 << 3)
#define PWM_INACTIVE_NEGATIVE        (0 << 4)
#define PWM_INACTIVE_POSITIVE        (1 << 4)
#define PWM_POLARITY_MASK        (PWM_DUTY_POSITIVE | PWM_INACTIVE_POSITIVE)
#define PWM_OUTPUT_LEFT                (0 << 5)
#define PWM_LOCK_EN                (1 << 6)
#define PWM_LP_DISABLE                (0 << 8)

struct rockchip_pwm_chip {
        struct pwm_chip chip;
        struct clk *clk;
        struct clk *pclk;
        const struct rockchip_pwm_data *data;
        void __iomem *base;
};

struct rockchip_pwm_regs {
        unsigned long duty;
        unsigned long period;
        unsigned long cntr;
        unsigned long ctrl;
};

struct rockchip_pwm_data {
        struct rockchip_pwm_regs regs;
        unsigned int prescaler;
        bool supports_polarity;
        bool supports_lock;
        u32 enable_conf;
        u32 enable_conf_mask;
};

static inline struct rockchip_pwm_chip *to_rockchip_pwm_chip(struct pwm_chip *c)
{
        return container_of(c, struct rockchip_pwm_chip, chip);
}

static void rockchip_pwm_get_state(struct pwm_chip *chip,
                                   struct pwm_device *pwm,
                                   struct pwm_state *state)
{
        struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
        u32 enable_conf = pc->data->enable_conf;
        unsigned long clk_rate;
        u64 tmp;
        u32 val;
        int ret;

        ret = clk_enable(pc->pclk);
        if (ret)
                return;

        clk_rate = clk_get_rate(pc->clk);

        tmp = readl_relaxed(pc->base + pc->data->regs.period);
        tmp *= pc->data->prescaler * NSEC_PER_SEC;
        state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);

        tmp = readl_relaxed(pc->base + pc->data->regs.duty);
        tmp *= pc->data->prescaler * NSEC_PER_SEC;
        state->duty_cycle =  DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);

        val = readl_relaxed(pc->base + pc->data->regs.ctrl);
        if (pc->data->supports_polarity)
                state->enabled = ((val & enable_conf) != enable_conf) ?
                                 false : true;
        else
                state->enabled = ((val & enable_conf) == enable_conf) ?
                                 true : false;

        if (pc->data->supports_polarity) {
                if (!(val & PWM_DUTY_POSITIVE))
                        state->polarity = PWM_POLARITY_INVERSED;
        }

        clk_disable(pc->pclk);
}

//配置PWM周期和占空比
static void rockchip_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
                                struct pwm_state *state)
{
        struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
        unsigned long period, duty;
        u64 clk_rate, div;
        u32 ctrl;

        clk_rate = clk_get_rate(pc->clk);

        div = clk_rate * state->period;
        period = DIV_ROUND_CLOSEST_ULL(div,
                                       pc->data->prescaler * NSEC_PER_SEC);

        div = clk_rate * state->duty_cycle;
        duty = DIV_ROUND_CLOSEST_ULL(div, pc->data->prescaler * NSEC_PER_SEC);

        ctrl = readl_relaxed(pc->base + pc->data->regs.ctrl);
        if (pc->data->supports_lock) {
                ctrl |= PWM_LOCK_EN;
                writel_relaxed(ctrl, pc->base + pc->data->regs.ctrl);
        }

        writel(period, pc->base + pc->data->regs.period);
        writel(duty, pc->base + pc->data->regs.duty);

        if (pc->data->supports_polarity) {
                ctrl &= ~PWM_POLARITY_MASK;
                if (state->polarity == PWM_POLARITY_INVERSED)
                        ctrl |= PWM_DUTY_NEGATIVE | PWM_INACTIVE_POSITIVE;
                else
                        ctrl |= PWM_DUTY_POSITIVE | PWM_INACTIVE_NEGATIVE;
        }

        if (pc->data->supports_lock)
                ctrl &= ~PWM_LOCK_EN;

        writel(ctrl, pc->base + pc->data->regs.ctrl);

#ifdef CONFIG_FB_ROCKCHIP
        if (!pc->data->regs.ctrl) {
                int ret;

                ret = rk_fb_set_vop_pwm();
                if (ret)
                        dev_err(pc->chip.dev, "rk_fb_set_vop_pwm failed: %dn", ret);
        }
#endif
}

static int rockchip_pwm_enable(struct pwm_chip *chip,
                               struct pwm_device *pwm,
                               bool enable)
{
        struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
        u32 enable_conf = pc->data->enable_conf;
        int ret;
        u32 val;

        if (enable) {
                ret = clk_enable(pc->clk);
                if (ret)
                        return ret;
        }

        val = readl_relaxed(pc->base + pc->data->regs.ctrl);
        val &= ~pc->data->enable_conf_mask;

        if (enable)
                val |= enable_conf;
        else
                val &= ~enable_conf;

        writel_relaxed(val, pc->base + pc->data->regs.ctrl);

        if (!enable)
                clk_disable(pc->clk);

        return 0;
}

static int rockchip_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
                              struct pwm_state *state)
{
        struct rockchip_pwm_chip *pc = to_rockchip_pwm_chip(chip);
        struct pwm_state curstate;
        bool enabled;
        int ret = 0;

        ret = clk_enable(pc->pclk);
        if (ret)
                return ret;

        pwm_get_state(pwm, &curstate);
        enabled = curstate.enabled;

        if (state->polarity != curstate.polarity && enabled &&
            !pc->data->supports_lock) {
                ret = rockchip_pwm_enable(chip, pwm, false);
                if (ret)
                        goto out;
                enabled = false;
        }

        rockchip_pwm_config(chip, pwm, state);
        if (state->enabled != enabled) {
                ret = rockchip_pwm_enable(chip, pwm, state->enabled);
                if (ret)
                        goto out;
        }

        rockchip_pwm_get_state(chip, pwm, state);

out:
        clk_disable(pc->pclk);

        return ret;
}

static const struct pwm_ops rockchip_pwm_ops = {
        .get_state = rockchip_pwm_get_state,
        .apply = rockchip_pwm_apply,
        .owner = THIS_MODULE,
};

static const struct rockchip_pwm_data pwm_data_v1 = {
        .regs = {
                .duty = 0x04,
                .period = 0x08,
                .cntr = 0x00,
                .ctrl = 0x0c,
        },
        .prescaler = 2,
        .supports_polarity = false,
        .supports_lock = false,
        .enable_conf = PWM_CTRL_OUTPUT_EN | PWM_CTRL_TIMER_EN,
        .enable_conf_mask = BIT(1) | BIT(3),
};

static const struct rockchip_pwm_data pwm_data_v2 = {
        .regs = {
                .duty = 0x08,
                .period = 0x04,
                .cntr = 0x00,
                .ctrl = 0x0c,
        },
        .prescaler = 1,
        .supports_polarity = true,
        .supports_lock = false,
        .enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
                       PWM_CONTINUOUS,
        .enable_conf_mask = GENMASK(2, 0) | BIT(5) | BIT(8),
};

static const struct rockchip_pwm_data pwm_data_vop = {
        .regs = {
                .duty = 0x08,
                .period = 0x04,
                .cntr = 0x0c,
                .ctrl = 0x00,
        },
        .prescaler = 1,
        .supports_polarity = true,
        .supports_lock = false,
        .enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
                       PWM_CONTINUOUS,
        .enable_conf_mask = GENMASK(2, 0) | BIT(5) | BIT(8),
};

static const struct rockchip_pwm_data pwm_data_v3 = {
        .regs = {
                .duty = 0x08,
                .period = 0x04,
                .cntr = 0x00,
                .ctrl = 0x0c,
        },
        .prescaler = 1,
        .supports_polarity = true,
        .supports_lock = true,
        .enable_conf = PWM_OUTPUT_LEFT | PWM_LP_DISABLE | PWM_ENABLE |
                       PWM_CONTINUOUS,
        .enable_conf_mask = GENMASK(2, 0) | BIT(5) | BIT(8),
};

static const struct of_device_id rockchip_pwm_dt_ids[] = {
        { .compatible = "rockchip,rk2928-pwm", .data = &pwm_data_v1},
        { .compatible = "rockchip,rk3288-pwm", .data = &pwm_data_v2},
        { .compatible = "rockchip,rk3328-pwm", .data = &pwm_data_v3},
        { .compatible = "rockchip,vop-pwm", .data = &pwm_data_vop},
        { .compatible = "rockchip,rk3399-pwm", .data = &pwm_data_v2},
        { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rockchip_pwm_dt_ids);

static int rockchip_pwm_probe(struct platform_device *pdev)
{
        const struct of_device_id *id;
        struct rockchip_pwm_chip *pc;
        struct resource *r;
        int ret, count;

        id = of_match_device(rockchip_pwm_dt_ids, &pdev->dev);
        if (!id)
                return -EINVAL;

        pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL);
        if (!pc)
                return -ENOMEM;

        r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        pc->base = devm_ioremap(&pdev->dev, r->start,
                                resource_size(r));
        if (IS_ERR(pc->base))
                return PTR_ERR(pc->base);

        pc->clk = devm_clk_get(&pdev->dev, "pwm");
        if (IS_ERR(pc->clk)) {
                pc->clk = devm_clk_get(&pdev->dev, NULL);
                if (IS_ERR(pc->clk)) {
                        ret = PTR_ERR(pc->clk);
                        if (ret != -EPROBE_DEFER)
                                dev_err(&pdev->dev, "Can't get bus clk: %dn",
                                        ret);
                        return ret;
                }
        }

        count = of_count_phandle_with_args(pdev->dev.of_node,
                                           "clocks", "#clock-cells");
        if (count == 2)
                pc->pclk = devm_clk_get(&pdev->dev, "pclk");
        else
                pc->pclk = pc->clk;

        if (IS_ERR(pc->pclk)) {
                ret = PTR_ERR(pc->pclk);
                if (ret != -EPROBE_DEFER)
                        dev_err(&pdev->dev, "Can't get APB clk: %dn", ret);
                return ret;
        }

        ret = clk_prepare_enable(pc->clk);
        if (ret) {
                dev_err(&pdev->dev, "Can't prepare enable bus clk: %dn", ret);
                return ret;
        }

        ret = clk_prepare(pc->pclk);
        if (ret) {
                dev_err(&pdev->dev, "Can't prepare APB clk: %dn", ret);
                goto err_clk;
        }

        platform_set_drvdata(pdev, pc);

        pc->data = id->data;
        pc->chip.dev = &pdev->dev;
        pc->chip.ops = &rockchip_pwm_ops;
        pc->chip.base = -1;
        pc->chip.npwm = 1;//PWM通道数

        if (pc->data->supports_polarity) {
                pc->chip.of_xlate = of_pwm_xlate_rockchip_pwm_chipwith_flags;
                pc->chip.of_pwm_n_cells = 3;
        }
    //填充的struct rockchip_pwm_chip(PWM芯片的描述、芯片提供的PWM器件数量以及支持的PWM操作的芯片)作为参数
        ret = pwmchip_add(&pc->chip);
        if (ret < 0) {
                clk_unprepare(pc->clk);
                dev_err(&pdev->dev, "pwmchip_add() failed: %dn", ret);
                goto err_pclk;
        }

        /* PWM clk enabled */
        if (!pwm_is_enabled(pc->chip.pwms))
                clk_disable(pc->clk);

        return 0;

err_pclk:
        clk_unprepare(pc->pclk);
err_clk:
        clk_disable_unprepare(pc->clk);

        return ret;
}

static int rockchip_pwm_remove(struct platform_device *pdev)
{
        struct rockchip_pwm_chip *pc = platform_get_drvdata(pdev);

        if (pwm_is_enabled(pc->chip.pwms))
                clk_disable(pc->clk);

        clk_unprepare(pc->pclk);
        clk_unprepare(pc->clk);

        return pwmchip_remove(&pc->chip);
}

static struct platform_driver rockchip_pwm_driver = {
        .driver = {
                .name = "rockchip-pwm",
                .of_match_table = rockchip_pwm_dt_ids,
        },
        .probe = rockchip_pwm_probe,
        .remove = rockchip_pwm_remove,
};
module_platform_driver(rockchip_pwm_driver);

7、控制 PWM 设备

7.1、查看当前PWM设备

cat /sys/kernel/debug/pwm



7.2、打开pwm1的占空比60%

rk3399_firefly_edp_box:/ # echo 10000 > /sys/pwm/pwm

7.3、关闭pwm1的占空比

rk3399_firefly_edp_box:/ # echo 25000 > /sys/pwm/pwm
举报

更多回帖

发帖
×
20
完善资料,
赚取积分