uboot 启动 PWM -- 直接配置寄存器

一、导论

工作中要求在 uboot 启动期间将 PWM 跑起来,以达到设备启动亮灯的一个效果。搞了一下 xxxxx 系列的芯片的 uboot 启动,在这里记录一下学习的过程。

首先拿到官方的 SDK 包后,熟悉了一下 uboot 启动流程。接着认真仔细的阅读了官方的 Datasheet 文档,尽管我英文很差,但仍然借助工具理解了官方文档所写内容。最后,借鉴官方 SDK 中的代码,选择在 uboot 启动期间初始化硬件资源的时候同时初始化 PWM ,然后在 uboot 倒计时加载 Linux 内核结束之前 关闭 PWM。

跟读 SDK 包中 uboot 的代码,硬件初始化在 init_sequence_r[] 数组中的 misc_init_r() /* miscellaneous platform-dependent init */ 函数中,接着跟读代码到 BSP\u-boot\board\novatek\nvt-na51089\na51089_hw_init.c 文件中的 nvt_ivot_hw_init() 函数。在这个函数里,就是初始化了一些依赖于平台的硬件,例如网络驱动、usb驱动等。选在这里还有一个便利的地方就是,在初始化依赖平台的硬件的时候,一些必要的资源均已经初始化好了,基本上可以直接操作寄存器了。查看了当前文件别的硬件初始化,读写寄存器分别采用INW(register)、OUTW(register, data) 函数。

根据 Datasheet 的介绍,xxxxx 系列芯片有 12个 PWM ,每个 PWM 可以设置 5 个引脚。大致可以将 PWM 的启动流程分为 pwm_init、pwm_config、pwm_enable,三个阶段,下面以本次需要适配的 PWM6_4 为例详细记录。

二、寄存器配置

1、pwm_init 阶段:

在 pwm_init 阶段,需要做三件事:

(1)、开时钟
这里有个坑,xxxxx 系列芯片的 Datasheet 并没有给出 时钟寄存器 相关的介绍(反正我没有看到),然后我是在 xxxxx 系列芯片给出的 SDK 代码中的 nvt_fr_pwm.c 文件里看到了有关时钟的配置:

/* Enable clk -- #define CONIFG_CG_BASE  0xF0020000 */
*(u32*) (CONIFG_CG_BASE + 0x7C) |= (0x1 << pwm_id);
*(u32*) (CONIFG_CG_BASE + 0x88) |= (0x1 << 8);

改写到 pwm_init() 里就是:


/* Enable clk -- #define CONIFG_CG_BASE  0xF0020000 */ 
u32 reg_data = 0;

reg_data = INW(IOADDR_CG_REG_BASE + 0x7C); 
reg_data |= 0x1 << 6;
OUTW(IOADDR_CG_REG_BASE + 0x7C, reg_data);

reg_data = INW(IOADDR_CG_REG_BASE + 0x88);
reg_data |= 0x1 << 8;
OUTW(IOADDR_CG_REG_BASE + 0x88, reg_data);

(2)、使用引脚复用
从 Datasheet 中查看引脚复用相关寄存器的介绍:

Top Controller Registers (Memory map: 0xF001_0000)

0x1CTOP Control Register 7
2…0PWM0
PinMux of PWM0.
0x0: Disable
0x1: Enable PWM0_1 (Assign P_GPIO0 to PWM0)
0x2: Enable PWM0_2 (Assign MC13 to PWM0)
0x3: Enable PWM0_3 (Assign SN_PXCLK to PWM0)
0x4: Enable PWM0_4 (Assign MC4 to PWM0)
0x5: Enable PWM0_5 (Assign DSI_GPIO0 to PWM0)
Others: Reserved
以此类推 PWM1-11
20…18PWM6
PinMux of PWM6.
0x0: Disable
0x1: Enable PWM6_1 (Assign P_GPIO6 to PWM6)
0x2: Enable PWM6_2 (Assign MC17 to PWM6)
0x3: Enable PWM6_3 (Assign S_GPIO7 to PWM6)
0x4: Enable PWM6_4 (Assign JTAG_TDI to PWM6)
0x5: Enable PWM6_5 (Assign DSI_GPIO6 to PWM6)
Others: Reserved
0xD0DGPIO PinMux Register 0 (D_GPIO)
10…0D_GPIO
D_GPIO PinMux Setting
For each bit,
0 = normal functionality enabled
1 = GPIO functionality enabled
Bit-n indicate D_GPIO[n]
别的引脚同理查看相应的寄存器

因此如需引脚复用 PWM6_4 :
    1、启动 PWM --> 寄存器 0xF001_0000 + 0x1C 的 bit[18-20] 配置成 100(b)
    2、设置 GPIO 的复用模式 – > 寄存器 0xF001_0000 + 0xD0 的 bit[5](JTAG_TDI 对应的是 D_GPIO[5]) 配置成 0 (normal functionality enabled)

写成代码就是:

/* Enable pinmux-- #define IOADDR_TOP_REG_BASE 0xF001_0000 */
u32 reg_data;

reg_data = INW(IOADDR_TOP_REG_BASE + 0x1C);    // 读出 0xF001_0000 + 0x1C 的值
reg_data &= ~(0x7 << 18);                      // 将 0xF001_0000 + 0x1C 的 bit[18-20] 清 0
reg_data |= ((0x4 << 18);                      // 将 0xF001_0000 + 0x1C 的 bit[20] 置 1 
OUTW(IOADDR_TOP_REG_BASE + 0x1C, reg_data);    // 将新值写进 0xF001_0000 + 0x1C 寄存器,开启 PWM6_4

reg_data = INW(IOADDR_TOP_REG_BASE + 0xD0);    // 读出 0xF001_0000 + 0xD0 的值
reg_data &= ~(0x1 << 5);                       // 将 D_GPIO[5] 设置成 0 (normal functionality enabled)
OUTW(IOADDR_TOP_REG_BASE + offset, reg_data);  // 将新值写进0xF001_0000 + 0xD0 寄存器,D_GPIO[5] 启用 normal functionality 模式

(3)、设置 pwm_invert no

起先并没有设置 pwm_invert ,但是单板没有能成功跑起来 PWM,后面检查流程的时候发现这个地方要将 pwm_invert 设置成 no 的模式。

根据 Datasheet 的介绍,设置 pwm_invert 的时候要先关闭 PWM。查看相关寄存器的介绍:

PWM Registers (Memory map: 0xF021_0000)

0x04In PWM Mode : PWM0 Period Register
7…0PWM0_R
(In free run mode, it’s period latched)
PWM rising time, the number of base clock cycles. Modify the value after setting PWM0_EN at non-free run mode has no effect.
0: Output is high since started.
1 ~ 255: 1 ~ 255 base clock cycles.
Only used in PWM mode.
Note: In PWM0~PWM7 rising time can expend bit count from 8 to 16. Bit[15…8] please fill at 0x230
PWM0_R_HI
15…8PWM0_F
(In free run mode, it’s period latched)
PWM falling time, the number of base clock cycles. Modify the value after setting PWM at non-free run mode has no effect.
0: Output keeps low during the PWM cycle.
1 ~ 255: 1 ~ 255 clock cycles.
Only used in PWM mode.
Note: In PWM0~PWM7 faling time can expend bit count from 8 to 16. Bit[15…8] please fill at 0x230
PWM0_F_HI
23…16PWM0_PRD
(In free run mode, it’s period latched)
PWM basis period, the number of clock cycles. Modify the value after setting PWM0_EN at non-free run mode has no effect.
2 ~ 255: 2 ~ 255 clock cycles.
Only used in PWM mode. In micro step mode, basis period is a fixed value of 100.(micro step not necessary)
Note: In PWM0~PWM7 rising time can expend bit count from 8 to 16. Bit[15…8] please fill at 0x230
PWM0_PRD_HI
27…24Reserved
28PWM0_INV
Invert or not invert the output source signal. The output source will take effect immediately after this bit is written.
Note: Only write this bit when PWM0 is disabled
(PWM0_EN = 0). (FW take care this limitation)
0: Not invert;
1: PWM0 invert
31…29Reserved
以此类推 PWM1-11
0x104PWM Disable Register
11…0PWMx_DIS
Disable PWM active
Write :
0: No action
1: Disable PWMx active. After write PWMx_DIS bit(HW auto clear this bit) , PWMx must have finished one basis period and then the hardware stop output
PWMx wave.
[Free run mode] :
PWMx_EN bit will be cleared to 0 by HW when output
pwm wave is really stopped after set PWMx_DIS = 1.
[Non-Free run mode]:
PWMx_EN will be cleared to 0 when output pwm wave is really stop after set PWMx_DIS = 1 and output cycle count(which is specified PWMx_CYCLE) is finish
FW need to take care of PWMx_EN=1 then PWMx_DIS can be set as 1. And FW should wait done status to set next enable.
Read:
Not available

由 Datasheet 描述:
    1、关闭 PWM6 --> 寄存器 0xF021_0000 + 0x104 的 bit[6] 配置成 1
    2、关闭 PWM6 的 invert --> 寄存器 0xF021_0000 + 0x30 的 bit[28] 配置成 0

写成代码就是:

/* Disable pwm invert  -- #define IOADDR_PWM_REG_BASE 0xF021_0000 */
u32 reg_data = 0;

OUTW(IOADDR_PWM_REG_BASE + 0x104, 0x1 << 6);  // 关闭 PWM6

reg_data = INW(IOADDR_PWM_REG_BASE + 0x30);     // 读出 0xF021_0000 + 0x30 的值
reg_data &= ~(0x1 << 28);                       // 将 0xF021_0000 + 0x30 的 bit[28] 置 0
OUTW(IOADDR_PWM_REG_BASE + 0x30, reg_data);     // 将新值写进 0xF021_0000 + 0x30 ,关闭 PWM6 的 invert

2、pwm_config阶段:

在 pwm_config 阶段就要配置 PWM 的占空比和频率了。根据 Datasheet(上文 pwm_init 阶段的第 3 步设置 pwm_invert no 已写相关寄存器 PWMx Period Register 介绍) 可知 0xF021_0000 + 0x30 (PWM6) 的 bit[0-7] 表示 PWM 高电平周期,bit[8-15] 表示 PWM 低电平周期,bit[16-23] 表示 PWM 总周期。

Datasheet 中所写的电平周期是指 基准周期的个数,一开始没有搞明白基准周期是多少,或者说是怎么配的,在 Datasheet 中有相关介绍,但没有相关寄存器的介绍(可能跟时钟相关的寄存器都没有介绍吧),最后我仍然是在联咏给出的 SDK 代码中的 nvt_fr_pwm.c 文件里找到了基准周期

#define PCLK_FREQ     250000
u32 pclk_freq = PCLK_FREQ;

在此处,根据硬件工程师的建议,需要将频率设置为 10KHz ,及 25(0x19) 个周期。并且将 PWMx_R 设置为常高电平 (0x0),将 PWMx_F 设置为 占空比数*基准周期 (50 * PCLK_FREQ / hz / 100 占空比50%)。在配置占空比之前要先将 PWM6 配置成 pwm free run 模式,及 寄存器 0xF021_0000 + 0x30 的 bit[0-16] 清零。

/* Set pwm config  -- #define IOADDR_PWM_REG_BASE 0xF021_0000 */
u32 reg_data = 0;

reg_data = INW(IOADDR_PWM_REG_BASE + 0x30);            // 读出 0xF021_0000 + 0x30 的值
reg_data &= ~0x01ffff;                                 // 将 0xF021_0000 + 0x30 的 bit[0-23] 的值 清零,使 PWM6 配置成 pwm free run 模式
OUTW(IOADDR_PWM_REG_BASE + 0x30, reg_data);           // 将 0xF021_0000 + 0x30 的新值写进寄存器

reg_data = INW(IOADDR_PWM_REG_BASE + 0x34);            // 读出 0xF021_0000 + 0x34 的值
reg_data |= (PCLK_FREQ / hz) << 16;                    // 设置总周期的值
reg_data |= (duty_cycle * PCLK_FREQ / hz / 100) << 8;  // 占空比,设置 PWMx_F
reg_data |= 0x0;                     // PWMx_R 设置为常高电平
OUTW(IOADDR_PWM_REG_BASE + 0x34, reg_data);            // 将 0xF021_0000 + 0x34 的新值写进寄存器
OUTW(IOADDR_PWM_REG_BASE + 0x248, 0x0);                // 关闭 占空比值的扩展

3、pwm_enable阶段:

当关于 PWM6 的所有配置都设置好之后,就可以使能 PWM6 了。产看 Datasheet 文档:

PWM Registers (Memory map: 0xF021_0000)

0x100PWM Enable Register
10…0PWMx_EN
Enable PWM
Write cycle:
0: No action
1: Enable PWMx
(FW needs to avoid to enable the same channel twice)
Read Cycle:
Status of PWM clock domain
0: PWMx is inactive.
1: PWMx is active.

写成代码就是:

/* Set pwm config  -- #define IOADDR_PWM_REG_BASE 0xF021_0000 */
OUTW(IOADDR_PWM_REG_BASE + 0x100, 0x1 << 6);

三、完整代码

由于在尝试阶段直接针对 PWM6_4 配置的寄存器,最终要适配到所有 PWM ,所以将代码整理成通用代码。

typedef enum {
    NVT_PINMUX_MODE1,
    NVT_PINMUX_MODE2,
    NVT_PINMUX_MODE3,
    NVT_PINMUX_MODE4,
    NVT_PINMUX_MODE5,
    NVT_PINMUX_MODE_MAX,
} nvt_pinmux_mode;

typedef enum {
    NVT_PWM_0,
    NVT_PWM_1,
    NVT_PWM_2,
    NVT_PWM_3,
    NVT_PWM_4,
    NVT_PWM_5,
    NVT_PWM_6,
    NVT_PWM_7,
    NVT_PWM_8,
    NVT_PWM_9,
    NVT_PWM_10,
    NVT_PWM_11,
    NVT_PWM_NUM_MAX,
} nvt_pwm_num;

void nvt_pwm_enable(int pwm_id)
{
    OUTW(IOADDR_PWM_REG_BASE + 0x100, 0x1 << pwm_id);
}

void nvt_pwm_disable(int pwm_id)
{
    OUTW(IOADDR_PWM_REG_BASE + 0x104, 0x1 << pwm_id);
}

static void nvt_common_clear_register(int offset, int num)
{
    u32 reg_data;

    reg_data = INW(IOADDR_TOP_REG_BASE + offset);
    reg_data &= ~(0x1 << num);
    OUTW(IOADDR_TOP_REG_BASE + offset, reg_data);
}

static void nvt_pwm_no_invert(int pwm_id)
{
    u32 reg_data = 0;

    reg_data = INW(IOADDR_PWM_REG_BASE + 0x04 + pwm_id * 8);
    reg_data &= ~(0x1 << 28);
    OUTW(IOADDR_PWM_REG_BASE + 0x04 + pwm_id * 8, reg_data);
}

static void nvt_enable_pwm_pinmux(int pwm_id, nvt_pinmux_mode mode)
{
    u32 reg_data;

    if (pwm_id < 8) {
        reg_data = INW(IOADDR_TOP_REG_BASE + 0x1C);
        reg_data &= ~(0x7 << pwm_id * 3);
        reg_data |= ((0x1 << (mode - 1)) << pwm_id * 3);
        OUTW(IOADDR_TOP_REG_BASE + 0x1C, reg_data);
    } else {
        reg_data = INW(IOADDR_TOP_REG_BASE + 0x18);
        reg_data &= ~(0x7 << ((pwm_id - 8) * 3 + 16));
        reg_data |= ((0x1 << (mode - 1)) << ((pwm_id - 8) * 3 + 16));
        OUTW(IOADDR_TOP_REG_BASE + 0x18, reg_data);
    }
}

static void nvt_set_pinmux_mode_1(int pwm_id, nvt_pinmux_mode mode)
{
    nvt_enable_pwm_pinmux(pwm_id, mode);
    nvt_common_clear_register(0xA8, pwm_id);
}

static void nvt_set_pinmux_mode_2(int pwm_id, nvt_pinmux_mode mode)
{
    nvt_enable_pwm_pinmux(pwm_id, mode);
        
    if (pwm_id < 4) {
        nvt_common_clear_register(0xA0, pwm_id);    
    } else if (pwm_id < 6) {
        nvt_common_clear_register(0xA0, (11 + pwm_id - 4));
    } else if (pwm_id < 12) {
        nvt_common_clear_register(0xA0, (17 + pwm_id - 6));
    }
}

static void nvt_set_pinmux_mode_3(int pwm_id, nvt_pinmux_mode mode)
{
    nvt_enable_pwm_pinmux(pwm_id, mode);
        
    if (pwm_id < 8) {
        nvt_common_clear_register(0xB0, (1 + pwm_id));
    } else if (pwm_id < 12) {
        nvt_common_clear_register(0xD8, (6 + pwm_id - 8));
    }
}

static void nvt_set_pinmux_mode_4(int pwm_id, nvt_pinmux_mode mode)
{
    nvt_enable_pwm_pinmux(pwm_id, mode);
        
    if (pwm_id < 4) {
        nvt_common_clear_register(0xA0, (4 + pwm_id));
    } else if (pwm_id < 11) {
        nvt_common_clear_register(0xD0, (3 + pwm_id - 4));
    } else if (pwm_id < 12) {
        nvt_common_clear_register(0xD0, 7);
    }
}

static void nvt_set_pinmux_mode_5(int pwm_id, nvt_pinmux_mode mode)
{
    nvt_enable_pwm_pinmux(pwm_id, mode);
        
    if (pwm_id < 11) {
        nvt_common_clear_register(0xEB, pwm_id);
    } else if (pwm_id < 12) {
        nvt_common_clear_register(0xB8, 0);
    }
}

static void nvt_pwm_set_pinmux(int pwm_id, nvt_pinmux_mode mode)
{
    if (mode == NVT_PINMUX_MODE1) {
        nvt_set_pinmux_mode_1(pwm_id, mode);
    } else if (mode == NVT_PINMUX_MODE2) {
        nvt_set_pinmux_mode_2(pwm_id, mode);
    } else if (mode == NVT_PINMUX_MODE3) {
        nvt_set_pinmux_mode_3(pwm_id, mode);
    } else if (mode == NVT_PINMUX_MODE4) {
        nvt_set_pinmux_mode_4(pwm_id, mode);
    } else if (mode == NVT_PINMUX_MODE5) {
        nvt_set_pinmux_mode_5(pwm_id, mode);
    }
}

static void nvt_pwm_enable_clk(int pwm_id)
{
    u32 reg_data = 0;
    
    reg_data = INW(IOADDR_CG_REG_BASE + 0x7C);
    reg_data |= 0x1 << pwm_id;
    OUTW(IOADDR_CG_REG_BASE + 0x7C, reg_data);

    reg_data = INW(IOADDR_CG_REG_BASE + 0x88);
    reg_data |= 0x1 << 8;
    OUTW(IOADDR_CG_REG_BASE + 0x88, reg_data);    
}

static void nvt_pwm_init(int pwm_id, nvt_pinmux_mode mode)
{
    // enable clk
    nvt_pwm_enable_clk(pwm_id);

    // enable pinmux
    nvt_pwm_set_pinmux(pwm_id, mode);
    
    /* Disable pwm before inverting */
    nvt_pwm_disable(pwm_id);
    
    /* Invert pwm */
    nvt_pwm_no_invert(pwm_id);
}

static void nvt_pwm_config(int pwm_id, int hz, int duty_cycle)
{
    u32 reg_data = 0;
    
    reg_data = INW(IOADDR_PWM_REG_BASE + pwm_id * 8);
    reg_data &= ~0x01ffff;
    OUTW(IOADDR_PWM_REG_BASE + pwm_id * 8, reg_data);

    reg_data = INW(IOADDR_PWM_REG_BASE + 0x04 + pwm_id * 8);
    reg_data |= (PCLK_FREQ / hz) << 16;
    reg_data |= (duty_cycle * PCLK_FREQ / hz / 100) << 8;  //占空比
    reg_data |= 0x0;
    OUTW(IOADDR_PWM_REG_BASE + 0x04 + pwm_id * 8, reg_data);
    OUTW(IOADDR_PWM_REG_BASE + 0x230 + pwm_id * 4, 0x0);    
}

static void nvt_pwm_param_check(int pwm_id, nvt_pinmux_mode mode, int hz, int duty_cycle)
{
    if (pwm_id > 11) {
        printf("The pwm_id[%d] is not between [0, 11]!\n", pwm_id);
    }

    if (mode > NVT_PINMUX_MODE5) {
        printf("The pwm_mode[%d] is not between[0, 5]!\n", mode);
    }

    if (duty_cycle < 0 || duty_cycle > 100) {
        printf("The duty_cycle[%d] is not between[0, 100]!\n", duty_cycle);
    }
}

static void nvt_pwm_set(int pwm_id, nvt_pinmux_mode mode, int hz, int duty_cycle)
{
    /* param check */
    nvt_pwm_param_check(pwm_id, mode, hz, duty_cycle);
    
    /* pwm init */
    nvt_pwm_init(pwm_id, mode);
    
    /* pwm config */
    nvt_pwm_config(pwm_id, hz, duty_cycle);

    /* pwm enable */
    nvt_pwm_enable(pwm_id);
}

int nvt_ivot_hw_init(void)
{
    // PWM6_4, 10KHz, 10%占空比
    nvt_pwm_set(NVT_PWM_6, NVT_PINMUX_MODE4, 10000, 10);
    return 0;
}

同步记录到微信公众号
同步记录到微信公众号,欢迎关注!

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值