天天看點

uboot 啟動 PWM -- 直接配置寄存器一、導論二、寄存器配置三、完整代碼

一、導論

工作中要求在 uboot 啟動期間将 PWM 跑起來,以達到裝置啟動亮燈的一個效果。搞了一下 xxxxx 系列的晶片的 uboot 啟動,在這裡記錄一下學習的過程。

首先拿到官方的 SDK 包後,熟悉了一下 uboot 啟動流程。接着認真仔細的閱讀了官方的 Datasheet 文檔,盡管我英文很差,但仍然借助工具了解了官方文檔所寫内容。最後,借鑒官方 SDK 中的代碼,選擇在 uboot 啟動期間初始化硬體資源的時候同時初始化 PWM ,然後在 uboot 倒計時加載 Linux 核心結束之前 關閉 PWM。

跟讀 SDK 包中 uboot 的代碼,硬體初始化在 init_sequence_r[] 數組中的 misc_init_r() 函數中,接着跟讀代碼到 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)

0x1C TOP Control Register 7
2…0

PWM0

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…18

PWM6

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

0xD0 DGPIO PinMux Register 0 (D_GPIO)
10…0

D_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)

0x04 In PWM Mode : PWM0 Period Register
7…0

PWM0_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…8

PWM0_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…16

PWM0_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…24 Reserved
28

PWM0_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…29 Reserved
以此類推 PWM1-11
0x104 PWM Disable Register
11…0

PWMx_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)

0x100 PWM Enable Register
10…0

PWMx_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;
}
           
uboot 啟動 PWM -- 直接配置寄存器一、導論二、寄存器配置三、完整代碼

同步記錄到微信公衆号,歡迎關注!