2019-11-30 18:49:52 weiqifa0 阅读数 65
  • 定时器、看门狗和RTC-1.9.ARM裸机第九部分

    本期课程主要讲述SoC中的时间相关的外设,包括定时器、看门狗定时器和实时时钟RTC。首先讲述了定时器的基本概念,然后以PWM定时器为例详细讲解了定时器的使用及编程细节;看门狗定时器部分详细讲了看门狗的意义和常规工作形式;后2节课讲了RTC的概念、框图和编程方法

    7410 人正在学习 去看看 朱有鹏

嵌入式系统一般要求低功耗,出于这个原因,一般只把需要使用到的外设时钟源打开,其他不需要使用到的模块,则默认关闭它们。

LCD 模块,上电时候默认情况是关闭的,所以,要想使用 LCD 模块,配置它寄存器必须先开启它时钟。

如何知道,哪个模块时钟源是打开的?哪些模块时钟源是关闭的?不同的芯片时钟设置一定不相同的,所以实现代码是编写在和具体芯片相关的文件中:

Clock-exynos4.c (arch\arm\mach-exynos)

内核使用 struct clk 结构描述一个外设模块的时钟信息:

struct clk {
struct list_head list;
struct module *owner;
struct clk *parent;
const char *name;
const char *devname;//设备名,用来查找。
int id;
int usage;
unsigned long rate;
unsigned long ctrlbit;

struct clk_ops *ops;
int (*enable)(struct clk *, int enable);//指向模块时钟使能/禁止时钟的函数
struct clk_lookup lookup;
#if defined(CONFIG_PM_DEBUG) && defined(CONFIG_DEBUG_FS)
struct dentry *dent; /* For visible tree hierarchy */
#endif
};

一个已经移植好,可以运行的内核,它的外设时钟都已经在系统初期已经完成注册,实现文件就在

Clock-exynos4.c  arch\arm\Mach-exynos

关于 LCD 控制器(fimd0)模块的时钟定义:

把 exynos4_clk_fimd0 结构放入数组中:

void __init exynos4_register_clocks(void)
{
int ptr;

s3c24xx_register_clocks(exynos4_clks, ARRAY_SIZE(exynos4_clks));

for (ptr = 0; ptr < ARRAY_SIZE(exynos4_sysclks); ptr++)
s3c_register_clksrc(exynos4_sysclks[ptr], 1);

for (ptr = 0; ptr < ARRAY_SIZE(exynos4_sclk_tv); ptr++)
s3c_register_clksrc(exynos4_sclk_tv[ptr], 1);
for (ptr = 0; ptr < ARRAY_SIZE(exynos4_clksrc_cdev); ptr++)
s3c_register_clksrc(exynos4_clksrc_cdev[ptr], 1);
//注册时钟源,其中 sclk_fimd0 就是在这里注册的 ,在 exynos4_clksrcs 数组中定义
s3c_register_clksrc(exynos4_clksrcs, ARRAY_SIZE(exynos4_clksrcs));
//默认打开时钟的模块
s3c_register_clocks(exynos4_init_clocks_on, ARRAY_SIZE(exynos4_init_clocks_on));

s3c_register_clocks(exynos4_init_audss_clocks, ARRAY_SIZE(exynos4_init_audss_clocks));
s3c_disable_clocks(exynos4_init_audss_clocks, ARRAY_SIZE(exynos4_init_audss_clocks));

s3c24xx_register_clocks(exynos4_gate_clocks, ARRAY_SIZE(exynos4_gate_clocks));
//fyyy:注册设备时钟,其中 LCD 时钟就在这里注册,可以通过 clk_get 获得
s3c24xx_register_clocks(exynos4_clk_cdev, ARRAY_SIZE(exynos4_clk_cdev));
//fyyy:注册后禁止它,为了降低功耗
for (ptr = 0; ptr < ARRAY_SIZE(exynos4_clk_cdev); ptr++)
s3c_disable_clocks(exynos4_clk_cdev[ptr], 1);//这里有禁止 lcd 相关的时钟 fimd0

s3c_register_clocks(exynos4_init_clocks_off, ARRAY_SIZE(exynos4_init_clocks_off));
//默认关闭时钟的模块
s3c_disable_clocks(exynos4_init_clocks_off, ARRAY_SIZE(exynos4_init_clocks_off));
//可以查找的时钟 ,可以通过 clk_get 获得
Clkdev_add_table(exynos4_clk_lookup, ARRAY_SIZE(exynos4_clk_lookup));

register_syscore_ops(&exynos4_clock_syscore_ops);
s3c24xx_register_clock(&dummy_apb_pclk);

s3c_pwmclk_init();
}

分析:

s3c24xx_register_clocks(exynos4_clk_cdev, ARRAY_SIZE(exynos4_clk_cdev));

是注册了 fimd0 模块的时钟信息

//fyyy:注册后禁止它,为了降低功耗
for (ptr = 0; ptr < ARRAY_SIZE(exynos4_clk_cdev); ptr++) {
s3c_disable_clocks(exynos4_clk_cdev[ptr], 1);//这里有禁止 lcd 相关的时钟 fimd0
}

要使用这个模块,必须先开这个模块的时钟。

clkdev_add_table(exynos4_clk_lookup, ARRAY_SIZE(exynos4_clk_lookup));

这一行是把可以通过设备名查找到的 clk 结构加到可查询的链表上。内核 struct clk_lookup 结构来表示一个可以被查找到的时钟结构。

Clkdev.h linux-3.5\include\Linux
//它是用来查找 struct clk 结构的。
//有了它,就可以通过设备名或时钟源的名字来找到相应的 struct clk 结构。
struct clk_lookup {
struct list_head node;
const char *dev_id; //设备名,提供对外搜索的名字,匹配使用的
const char *con_id; //总线名,也可以用来搜索,匹配使用
struct clk *clk; //指向模块时钟信息结构
};

实际的匹配过程是会比较 dev_id 和 con_id 两个成员的,如果匹配上,则返回 clk 结构。

内核提供一个辅助填充宏:CLKDEV_INIT

定义如下:

#define CLKDEV_INIT(d, n, c) \
{ \
.dev_id = d, \
.con_id = n, \
.clk = c, \
}
//可以被查找操作的模块时钟
//它是用来查找 struct clk 结构的。
//有了它,就可以通过设备名或时钟源的名字来找到相应的 struct clk 结构。
static struct clk_lookup exynos4_clk_lookup[] = {
……
//通过设备名或时钟源名查找到 exynos4_clk_fimd0 结构
CLKDEV_INIT("exynos4-fb.0", "lcd", &exynos4_clk_fimd0),
……
};
struct device dev;
struct clk * clk_bus;
dev. init_name = "exynos4-fb.0";
clk_bus = clk_get(&dev, "lcd" );

如何找到模块的时钟结构?内核提供了操作时钟相关的 API 函数,这些 API 接口函数是通用的,声明在 Clk.h linux-3.5\include\Linux 。时钟获得结构获取函数:

struct clk *clk_get(struct device *dev, const char *id);

功能:通过 dev. init_name 和参数 id 进行在 struct clk_lookup 注册到内核的时钟结构链表查找。参数 dev. init_name 和 clk_lookup 结构中的 dev_id 成员比较 参数 id 和 clk_lookup 结构中的 con_id 比较 如果两个成员都相同就返回 clk_lookup 结构中的中 clk 指针。

返回值:IS_ERR(clk_get 返回值) 

非 0: 获得失败,这时候应该返回 –ENODEV 错误码 IS_ERR(clk_get 返回值) 

0: 获得时钟成功

示例:

s3c_ac97.ac97_clk = clk_get(&pdev->dev, "ac97");
if (IS_ERR(s3c_ac97.ac97_clk)) {
dev_err(&pdev->dev, "ac97 failed to get ac97_clock\n");
ret = -ENODEV;
goto err2;
}
clk_enable(s3c_ac97.ac97_clk); //获得成功后可以使能模块时钟了

时钟使能函数:

int clk_enable(struct clk *clk);

功能: 在获得 clk 结构后,就可以调用 clk_enable 函数来使能模块的时钟

返回: 0:成员;负数:失败 时钟禁止函数:

void clk_disable(struct clk *clk);

功能:当不需要使用一个模块时候,要降低功耗,可以关闭它。获得模块的运行时钟频率:

unsigned long clk_get_rate(struct clk *clk);

功能: 根据结构获得模块的运行频率 

返回:模块的运行频率,单位是 HZ 减少时钟引用计数,如果你使用

void clk_put(struct clk *clk);

当使用了 clk_get, clk_enable 后,如果不想使用模块了,则需要 clk_put 引用计数。设置模块的运行时钟:

int clk_set_rate(struct clk *clk, unsigned long rate);

参数: rate 要设置的目标运行频率

返回: 0:成员;负数:失败

—————END—————

扫码或长按关注

回复「 加群 」进入技术群聊

2014-05-29 01:37:50 pseraph 阅读数 3445
  • 定时器、看门狗和RTC-1.9.ARM裸机第九部分

    本期课程主要讲述SoC中的时间相关的外设,包括定时器、看门狗定时器和实时时钟RTC。首先讲述了定时器的基本概念,然后以PWM定时器为例详细讲解了定时器的使用及编程细节;看门狗定时器部分详细讲了看门狗的意义和常规工作形式;后2节课讲了RTC的概念、框图和编程方法

    7410 人正在学习 去看看 朱有鹏

linux CCF 时钟框架

 

简介

         这里讲的时钟是给soc各组件提供时钟的树状框架,并不是内核使用的时间,和其他模块一样,clk也有框架,用以适配不同的平台。适配层之上是客户代码和接口,也就是各模块(如需要时钟信号的外设,usb等)的驱动。适配层之下是具体的soc平台的时钟操作细节。

         内核中另外一个具有类似树状框架特点的是regulator框架。对比regulator框架,clk框架不确定性更大,内核中仅仅提供了少数的适配规范,struct clk都是各平台自己的clk驱动实现。         3.4.5内核里基本上还是这种状态,但是新的3.10内核很多socclk驱动已经改为common clock frameworkCCF)。各平台采用CCF的的clock驱动都统一在drivers/clk目录。

common clock frameworkMike Turquette2012.5引入kernel 3.4

 

下图引用自Emb edded Linux Conference 2013Gregory CLEMENT的一篇介绍elc2013_clement.pdf

 

 

        内核版本: linux-linaro-stable-android-3.10.37-2014.04

 

CCF相关的内核配置宏

 

CONFIG_COMMON_CLK

 

CCF core

 

 

CCF core主要代码在drivers/clk/clk.c里。主要维护时钟树以及操作,互斥锁,通知链。

 

主要结构体定义

只有定义了CONFIG_COMMON_CLK才会有CCF框架。

 

include/linux/clk- private.h:

 

 

#ifdef CONFIG_COMMON_CLK

 

struct clk {

         const char                *name; //名字用来在全局链表里查找clk用的

         const struct clk_ops        *ops;  //抽象的标准ops操作

         struct clk_hw           *hw;   //clk_hw后面有专门介绍

         struct clk          *parent;  //父时钟

         const char                **parent_names;  //父时钟的个数

         struct clk          **parents;        

         u8                      num_parents;            //父时钟的名字字符串数组

         unsigned long          rate;                    //频率

         unsigned long          new_rate;

         unsigned long          flags;

         unsigned int             enable_count;    //

         unsigned int             prepare_count;

         struct hlist_head     children;

         struct hlist_node     child_node;

         unsigned int             notifier_count;

#ifdef CONFIG_COMMON_CLK_DEBUG

         struct dentry            *dentry;

#endif

};

#endif

 

 

 


struct clk_ops {

         int             (*prepare)(struct clk_hw *hw);  //开时钟前调用,可能会造成休眠,所以把休眠部分放到这里,可以原子操作的放到enable里

         void          (*unprepare)(struct clk_hw *hw); //prepare的反操作

         int             (*is_prepared)(struct clk_hw *hw);  //是否prepared

         void          (*unprepare_unused)(struct clk_hw *hw);  //只在clk_disable_unused里特殊需求调用,可能会休眠

         int             (*enable)(struct clk_hw *hw);      //原子操作,打开时钟,这个函数必须在产生实际可用的时钟信号后才能返回

         void          (*disable)(struct clk_hw *hw);    //原子操作,关闭时钟

         int             (*is_enabled)(struct clk_hw *hw);  

         void          (*disable_unused)(struct clk_hw *hw);   //只在clk_disable_unused里特殊需求调用,不能休眠

         unsigned long (*recalc_rate)(struct clk_hw *hw,

                                            unsigned long parent_rate);                    //查询硬件,重新计算频率

         long          (*round_rate)(struct clk_hw *hw, unsigned long,

                                            unsigned long *);                                       //计算最接近要求的频率

         int             (*set_parent)(struct clk_hw *hw, u8 index);       //MUX会使用

         u8             (*get_parent)(struct clk_hw *hw);                                   //MUX会使用

         int             (*set_rate)(struct clk_hw *hw, unsigned long,        //设置频率

                                       unsigned long);

         void          (*init)(struct clk_hw *hw);                                    //初始化

};

 


struct clk_init_data {

         const char                *name;

         const struct clk_ops        *ops;   //操作函数集,和其他框架的ops作用一样,提供实际的操作函数。

         const char                **parent_names;   //父时钟的名字字符串数组

         u8                      num_parents;            //父时钟的个数

         unsigned long          flags;

};


//用来连接clk结构体和实际硬件的关系

struct clk_hw {

         struct clk *clk;

         const struct clk_init_data *init;

};

 

 

时钟的基本种类

  CCF将soc抽象出5个基本种类,可以快捷的定义。

固定速率

不能设置的时钟

门时钟

和上级时钟同频,只能打开和关闭操作

MUX

多选一

固定倍频

上级时钟的频率有固定倍频或者分频,不能关闭

分频

上级时钟的频率分频,可以选择不同的分频比

 

 

5种时钟类型都有不同的注册函数和结构体,如MUX时钟

 

结构体毫无例外是封装包含struct clk_hw,然后加上该种类的特性的成员。

struct clk_mux {

         struct clk_hw  hw;

         void __iomem         *reg;

         u32           *table;

         u32           mask;

         u8             shift;

         u8             flags;

         spinlock_t        *lock;

};

struct clk *clk_register_mux(struct device *dev, const char *name,

                  const char **parent_names, u8 num_parents, unsigned long flags,

                  void __iomem *reg, u8 shift, u8 width,

                  u8 clk_mux_flags, spinlock_t *lock);

 

 

 

一般SOC都有大量的时钟,用数组变量定义批量时钟是最方便的,但是内核不推荐这样做。新开发的驱动用clk_init_dataclk_register()定义。

 

 

时钟标准驱动层

         CCF提供的API,实际是调用了clk_ops的实际操作函数,这些函数是按照5种基本的时钟分类来的。

值得注意的是,一般的驱动框架,比如网卡,usbregulator,都是内核的core层提供管理逻辑,由芯片驱动提供实际的操作。但是clk的实际操作是由CCF API完成,而不是芯片驱动完成的。之所以能够做到这一点,是因为芯片的时钟操作方法比较类似。soc平台注册时钟的时候,只需要提供操作的信息,就可以由CCF的统一操作函数对这些信息进行操作。

 

 

 

MUXclk_set_parent分析为例

 

clk_set_parent->__clk_set_parent->clk->(ops->set_parent)

 

ops->set_parent的定义如下,在注册时钟的时候就设置好了。

const struct clk_ops clk_mux_ops = {

         .get_parent = clk_mux_get_parent,

         .set_parent = clk_mux_set_parent,

};

 

 

 

static int clk_mux_set_parent(struct clk_hw *hw, u8 index)

{

         struct clk_mux *mux = to_clk_mux(hw);

         u32 val;

         unsigned long flags = 0;

 

         if (mux->table)

                  index = mux->table[index];

 

         else {

                  if (mux->flags & CLK_MUX_INDEX_BIT)

                          index = (1 << ffs(index));

 

                  if (mux->flags & CLK_MUX_INDEX_ONE)

                          index++;

         }

 

         if (mux->lock)

                  spin_lock_irqsave(mux->lock, flags);

 

         val = readl(mux->reg);

         val &= ~(mux->mask << mux->shift);

         val |= index << mux->shift;

         writel(val, mux->reg);

 

         if (mux->lock)

                  spin_unlock_irqrestore(mux->lock, flags);

 

         return 0;

}

 

 

可见,平台代码并没有提供实际的ops,只是提供tablebitreg等信息就可以了。CCFops可以直接调用writel操作硬件。

 

 

驱动样例分析

 

 

准备5类时钟信息

每个soc有很多时钟,按照CCF5个种类分开定义.

 

struct samsung_mux_clock {

         unsigned int             id;

         const char                *dev_name;

         const char                *name;

         const char                **parent_names;

         u8                      num_parents;

         unsigned long          flags;

         unsigned long          offset;

         u8                      shift;

         u8                      width;

         u8                      mux_flags;

         const char                *alias;

};

 

 

struct samsung_mux_clock exynos5250_mux_clks[] __initdata = {

         MUX_A(none, "mout_apll", mout_apll_p, SRC_CPU, 0, 1, "mout_apll"),

         MUX(none, "mout_mpll_fout", mout_mpll_fout_p, PLL_DIV2_SEL, 4, 1),

         MUX_A(none, "sclk_mpll", mout_mpll_p, SRC_CORE1, 8, 1, "mout_mpll"),

……

}

 

 

参考MUX(none, "mout_mpll_fout", mout_mpll_fout_p, PLL_DIV2_SEL, 4, 1),

 

#define __MUX(_id, dname, cname, pnames, o, s, w, f, mf, a)   \

         {                                                            \

                  .id             = _id,                                  \

                  .dev_name      = dname,                  \

                  .name               = cname,                  \

                  .parent_names        = pnames,                         \

                  .num_parents = ARRAY_SIZE(pnames),          \

                  .flags                = f,                             \

                  .offset              = o,                            \

                  .shift                 = s,                             \

                  .width               = w,                            \

                  .mux_flags      = mf,                                   \

                  .alias                 = a,                            \

         }

 

#define MUX(_id, cname, pnames, o, s, w)                   \

         __MUX(_id, NULL, cname, pnames, o, s, w, 0, 0, NULL)

 

实际上就是利用宏简化赋值代码。mout_mpll_fout展开如下

struct samsung_mux_clock –

         {                                                            \

                  .id             = none,                              \

                  .dev_name      = NULL,                     \

                  .name               = "mout_mpll_fout",                        \

                  .parent_names        = mout_mpll_fout_p,                       \

                  .num_parents = ARRAY_SIZE(mout_mpll_fout_p),                \

                  .flags                = 0,                            \

                  .offset              = PLL_DIV2_SEL,                               \

                  .shift                 = 4,                            \

                  .width               = 1,                            \

                  .mux_flags      = NULL,                             \

                  .alias                 = NULL,                             \

         }

 

 

 

结合时钟标准驱动层int clk_set_parent(struct clk *clk, struct clk *parent)来看。对mout_mpll_fout设置mux的方法分为以下几个步骤:

1. 将本clk和父clk为参数输入clk_set_parent

2. for循环在本clkparents成员数组查找指针和入参clk *parent相等的。返回数组的index

3. 找到偏移为PLL_DIV2_SEL的寄存器,将index左移4bit设置为1就可以。

 

从上面可以看出,定义clk的时候,父时钟的顺序必须和寄存器设置的顺序匹配才可以。不支持这种规律的芯片,是不能用CCF的。

 

 

注册5类时钟

void __init exynos5250_clk_init(struct device_node *np)

{

 

         samsung_clk_register_fixed_rate(exynos5250_fixed_rate_clks,

                          ARRAY_SIZE(exynos5250_fixed_rate_clks));

         samsung_clk_register_fixed_factor(exynos5250_fixed_factor_clks,

                          ARRAY_SIZE(exynos5250_fixed_factor_clks));

         samsung_clk_register_mux(exynos5250_mux_clks,

                          ARRAY_SIZE(exynos5250_mux_clks));

 

 

}

 

 

 

准备非5类时钟信息

         出了标准的5类时钟类型,不标准的时钟类型需要单独准备clk_init_data init;

 

 

注册非5类时钟

         apll = samsung_clk_register_pll35xx("fout_apll", "fin_pll",

                          reg_base + 0x100);

 

struct samsung_clk_pll35xx {

         struct clk_hw           hw;

         const void __iomem       *con_reg;

};

 

struct clk * __init samsung_clk_register_pll35xx(const char *name,

                          const char *pname, const void __iomem *con_reg)

{

         struct samsung_clk_pll35xx *pll;

         struct clk *clk;

         struct clk_init_data init;

 

         //如果是标准类型,调用标准类型的注册函数里会分配时钟结构体的内存

         pll = kzalloc(sizeof(*pll), GFP_KERNEL);

         if (!pll) {

                  pr_err("%s: could not allocate pll clk %s\n", __func__, name);

                  return NULL;

         }

 

         //配置clk_init_data

         init.name = name;

         init.ops = &samsung_pll35xx_clk_ops;

         init.flags = CLK_GET_RATE_NOCACHE;

         init.parent_names = &pname;

         init.num_parents = 1;

 

         pll->hw.init = &init;

         pll->con_reg = con_reg;

 

         //通用注册函数,标准类型的注册函数最终也是调用这个

         clk = clk_register(NULL, &pll->hw);

         if (IS_ERR(clk)) {

                  pr_err("%s: failed to register pll clock %s\n", __func__,

                                   name);

                  kfree(pll);

         }

 

         //注册到clocks全局链表。clk_register_clkdev会申请struct clk_lookup,不用caller关心。

         if (clk_register_clkdev(clk, name, NULL))

                  pr_err("%s: failed to register lookup for %s", __func__, name);

 

         return clk;

}

 

 

//由于是私有函数,可以随便写了。

static const struct clk_ops samsung_pll35xx_clk_ops = {

         .recalc_rate = samsung_pll35xx_recalc_rate,

};

 

clk api的使用方法

regulator框架类似,首先调用clk_get()得到struct clk *,然后将struct clk *作为入参调用CCF提供的API,如int clk_prepare(struct clk *clk)


2020-03-18 17:29:33 weixin_44553006 阅读数 24
  • 定时器、看门狗和RTC-1.9.ARM裸机第九部分

    本期课程主要讲述SoC中的时间相关的外设,包括定时器、看门狗定时器和实时时钟RTC。首先讲述了定时器的基本概念,然后以PWM定时器为例详细讲解了定时器的使用及编程细节;看门狗定时器部分详细讲了看门狗的意义和常规工作形式;后2节课讲了RTC的概念、框图和编程方法

    7410 人正在学习 去看看 朱有鹏

系统时钟来源

注:默认主频396M
在这里插入图片描述
在这里插入图片描述
从上图可以看出 I.MX6U-ALPHA 开发板的系统时钟来源于两部分:32.768KHz 和 24MHz 的晶振,其中 32.768KHz 晶振是 I.MX6U 的 RTC 时钟源,24MHz 晶振是 I.MX6U 内核 和其它外设的时钟源,也是我们重点要分析的

7 路 PLL 时钟源介绍

I.MX6U 的外设有很多,不同的外设时钟源不同, NXP 将这些外设的时钟源进行了分组,一共有 7 组,这 7 组时钟源都是从 24MHz 晶振 PLL 而来的,因此也叫做 7 组 PLL

①、ARM_PLL(PLL1),此路 PLL 是供 ARM 内核使用的, ARM 内核时钟就是由此 PLL 生成的,此 PLL 通过编程的方式最高可倍频到 1.3GHz。

②、 528_PLL(PLL2),此路 PLL 也叫做 System_PLL,此路 PLL 是固定的 22 倍频,不可编程修改。因此,此路 PLL 时钟=24MHz * 22 = 528MHz,这也是为什么此 PLL 叫做 528_PLL 的原因。此 PLL 分出了 4 路 PFD,分别为:PLL2_PFD0~PLL2_PFD3,这 4 路 PFD 和 528_PLL 共同作为其它很多外设的根时钟源。通常 528_PLL 和这 4 路 PFD 是 I.MX6U 内部系统总线的时钟源,比如内处理逻辑单元、 DDR 接口、 NAND/NOR 接口等等。

③、 USB1_PLL(PLL3),此路 PLL 主要用于 USBPHY,此 PLL 也有四路 PFD,为:PLL3_PFD0~PLL3_PFD3, USB1_PLL 是固定的 20 倍频,因此 USB1_PLL=24MHz *20=480MHz。USB1_PLL虽然主要用于USB1PHY,但是其和四路PFD同样也可以作为其他外设的根时钟源。

④、 USB2_PLL(PLL7,没有写错!就是 PLL7,虽然序号标为 4,但是实际是 PLL7),看名字就知道此路PLL是给USB2PHY使用的。同样的,此路PLL固定为20倍频,因此也是480MHz。

⑤、 ENET_PLL(PLL6),此路 PLL 固定为 20+5/6 倍频,因此 ENET_PLL=24MHz * (20+5/6)= 500MHz。此路 PLL 用于生成网络所需的时钟,可以在此 PLL 的基础上生成 25/50/100/125MHz
的网络时钟。

⑥、 VIDEO_PLL(PLL5),此路 PLL 用于显示相关的外设,比如 LCD,此路 PLL 的倍频可以调整, PLL 的输出范围在 650MHz~1300MHz。此路 PLL 在最终输出的时候还可以进行分频,可选 1/2/4/8/16 分频。

⑦、 AUDIO_PLL(PLL4),此路 PLL 用于音频相关的外设,此路 PLL 的倍频可以调整, PLL的输出范围同样也是 650MHz~1300MHz,此路 PLL 在最终输出的时候也可以进行分频,可选1/2/4 分频。

在这里插入图片描述
在这里插入图片描述
时钟配置范围
在这里插入图片描述
注意
默认频率为系统电源接通复位后的复位值。它不同于ROM代码初始化的值,这取决于引导CFG引脚/熔断器设置。

I.MX6U系统主频的配置

在这里插入图片描述
①、要设置ARM内核主频为528MHz,设置CACRR寄存器的ARM_PODF位为2分频,然后设置PLL1=1056MHz即可。CACRR的bit3 ~ 0为ARM_PODF位,可设置0 ~ 7,分别对应1 ~ 8分频。应该设置CACRR寄存器的ARM_PODF=1。

②、设置PLL1=1056MHz。PLL1=pll1_sw_clk。pll1_sw_clk有两路可以选择,分别为pll1_main_clk,和step_clk,通过CCSR寄存器的pll1_sw_clk_sel位(bit2)来选择。为0的时候选择pll1_main_clk,为1的时候选额step_clk。

③、在修改PLL1的时候,也就是设置系统时钟的时候需要给6ULL一个临时的时钟,也就是step_clk。在修改PLL1的时候需要将pll1_sw_clk切换到step_clk上。

③、设置step_clk。Step_clk也有两路来源,由CCSR的step_sel位(bit8)来设置,为0的时候设置step_clk为osc=24MHz。为1的时候选择secondary_clk(一般不用)

④、时钟切换成功以后就可以修改PLL1的值。

⑤、通过CCM_ANALOG_PLL_ARM寄存器的DIV_SELECT位(bit6~0)来设置PLL1的频率,公式位:Output = frefDIV_SEL/2 1056=24DIV_SEL/2=>DIEV_SEL=88。设置CCM_ANALOG_PLL_ARM寄存器的DIV_SELECT位=88即可。PLL1=1056MHz,还要设置CCM_ANALOG_PLL_ARM寄存器的ENABLE位(bit13)为1,也就是使能输出。

⑥、在切换回PLL1之前,设置置CACRR寄存器的ARM_PODF=1!!切记。

2020-03-05 10:54:24 weixin_43482414 阅读数 32
  • 定时器、看门狗和RTC-1.9.ARM裸机第九部分

    本期课程主要讲述SoC中的时间相关的外设,包括定时器、看门狗定时器和实时时钟RTC。首先讲述了定时器的基本概念,然后以PWM定时器为例详细讲解了定时器的使用及编程细节;看门狗定时器部分详细讲了看门狗的意义和常规工作形式;后2节课讲了RTC的概念、框图和编程方法

    7410 人正在学习 去看看 朱有鹏

时钟树分析

树根–时钟源

IMX6ULL的时钟源有两个:24M外部晶振和32.768K晶振,其中32.768K晶振用于内部RTC模块使用。24M外部晶振用于内核和各种外设使用,是时钟树的根源。
在这里插入图片描述

主干-- 7路PLL时钟源+8路PFD

显然一个24M频率的时钟不足以供内部外设使用,因此在24M的基础上,将其倍频分离出7路PLL时钟源如下:
在这里插入图片描述

  • ARM PLL :供ARM内核使用,最高可倍频到1.3Ghz。
  • 528 PLL:又叫System_PLL,此路固定为24M的22倍频,也就是528M,不可修改。在528 PLL基础上又分离出4路PFD。常 528_PLL 和这 4 路 PFD 是 I.MX6U 内部系统总线的时钟源,比如内处理逻辑单元、 DDR 接口、 NAND/NOR 接口等等。
  • USB1_PLL:用于USB1PHY。此路固定为24M的20倍频,也就是480M。此路也分离出4路PFD。
  • USB2_PLL:用于USB2PHY。此路固定为24M的20倍频,也就是480M。
  • ENET PLL:用于生成网络所需时钟,此路固定为24M的20+5/6倍频,也就是500M。
  • VIDEO PLL:用于显示相关外设,此路倍频可调整,范围在650~1300Mhz。最终输出时还可以分频,1/2/4/8/16分频。
  • AUDIO PLL:用于音频相关外设,此路倍频可调整,范围在650~1300Mhz。最终输出时还可以分频,1/2/4分频。

主分支-- CLOCK ROOT GENERATOR

在生成7路PLL和8路PFD后,系统产生了足够的时钟源,但是如何供给外设使用呢,这就需要CLOCK ROOT GENERATOR牵线搭桥。其作用就是根据实际需要,选择合适的时钟源,并对其进行分频,倍频,切换等工作,从而让外设拥有合适的时钟源。
在这里插入图片描述
我们以ESAI时钟为例:
在这里插入图片描述
1.时钟源选择器,由寄存器CCM->CSCMR2的ESAI_CLK_SEL位选择时钟源:PLL4,PLL5,PLL3_PFD2和pll3_sw_clk。
2. 前级分频器,由寄存器 CCM_CS1CDR 的 ESAI_CLK_PRED来确定的,可设置 1~8 分频,假如现在 PLL4=650MHz,我们选择 PLL4 作为 ESAI 时钟,前级分频选择 2 分频,那么此时的时钟就是 650/2=325MHz。
3. 后级分频器,分频值由寄存器
CCM_CS1CDR 的 ESAI_CLK_PODF 来决定,可设置 1~8 分频。假如我们设置为 8 分频的话,经过此分频器以后的时钟就是 325/8=40.625MHz。因此最终进入到 ESAI 外设的时钟就是40.625MHz。

果实 – 外设

略。

以上便是IMX6ULL时钟树分析,总的来说便是24M晶振输入,生成7路PLL和8路PFD,在经过各种分频倍频供给外设使用。

IMX6ULL时钟设置

内核时钟设置

原理分析

在这里插入图片描述

  1. 我们需要设置内核时钟为528M,因此PLL1频率为528*2=1056M。然后通过CACRR寄存器的ARM_PODF设置分频系数为2。PLL1 的频率可以通过寄存器 CCM_ANALOG_PLL_ARMn 来设置。
  2. 在修改 PLL1 时钟频率的时候我们需要先将内核时钟源改为其他的时钟源,可选时钟源如下,由CCSR控制。
    在这里插入图片描述

设置步骤

  • 设置寄存器 CCSR 的 STEP_SEL 位,设置 step_clk 的时钟源为 24M 的晶振。
  • 设置寄存器 CCSR 的 PLL1_SW_CLK_SEL 位,设置 pll1_sw_clk 的时钟源为step_clk=24MHz,通过这一步我们就将 I.MX6U 的主频先设置为 24MHz,直接来自于外部的24M 晶振。
  • 设置寄存器 CCM_ANALOG_PLL_ARMn,将 pll1_main_clk(PLL1)设置为 1056MHz。
  • 设置寄存器 CCSR 的 PLL1_SW_CLK_SEL 位,重新将 pll1_sw_clk 的时钟源切换回pll1_main_clk,切换回来以后的 pll1_sw_clk 就等于 1056MHz。
  • 最后设置寄存器 CCM_CACRR 的 ARM_PODF 为 2 分频, I.MX6U 的内核主频就为1056/2=528MHz。

PFD时钟设置

  • 8路PFD推荐值
    在这里插入图片描述

原理分析

  • 先设置 PLL2 的 4 路 PFD 频率,用到寄存器是 CCM_ANALOG_PFD_528n。

AHB,IPG和PERRCLK根时钟设置

。。。。。。。。。。。。。。。。。。。。

程序编写

程序主要修改bsp_clk.c

#include "bsp_clk.h"

/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名	: 	 bsp_clk.c
作者	   : 左忠凯
版本	   : V1.0
描述	   : 系统时钟驱动。
其他	   : 无
论坛 	   : www.openedv.com
日志	   : 初版V1.0 2019/1/4 左忠凯创建
***************************************************************/

/*
 * @description	: 使能I.MX6U所有外设时钟
 * @param 		: 无
 * @return 		: 无
 */
void clk_enable(void)
{
	CCM->CCGR0 = 0XFFFFFFFF;
	CCM->CCGR1 = 0XFFFFFFFF;
	CCM->CCGR2 = 0XFFFFFFFF;
	CCM->CCGR3 = 0XFFFFFFFF;
	CCM->CCGR4 = 0XFFFFFFFF;
	CCM->CCGR5 = 0XFFFFFFFF;
	CCM->CCGR6 = 0XFFFFFFFF;
}

/*
 * @description	: 初始化系统时钟为528Mhz,设置8路PFD时钟为推荐值。
 * @param 		: 无
 * @return 		: 无
 */
void imx6u_clkinit(void)
{
	unsigned int reg = 0;
	/*
		1.设置内核时钟为528M
		1.1.判断当前时钟源,若为PLL_main_clk,则切换时钟源到step_clk,修改完后再切回来
	*/
	if( ( ( ( CCM->CCSR )>>2 ) & 0x1 ) == 0)/*pll_main_clk*/
	{
		CCM->CCSR &= ~(1<<8);/* 设置step_clk时钟源为24M OSC */
		CCM->CCSR |= (1<<2);/*  切换到step_clk */
	}

	/*
		1.2 设置PLL_min_clk为1056M,配置 CCM_ANLOG->PLL_ARM 寄存器
		bit13: 1 使能时钟输出
		bit[6:0]: 88, 由公式: Fout = Fin * div_select / 2.0,
		1056=24*div_select/2.0, 得出: div_select=88。
	*/
	CCM_ANALOG->PLL_ARM = (1<<13)|((88<0)&0x7f);
	CCM->CCSR &= ~(1<<2);/* 将时钟源切回PLL_main_clk */
	CCM->CACRR = 1;/*  时钟分频1056/2 = 528 */


	/*
		2.设置PLL2的PFD
	*/
	reg = CCM_ANALOG->PFD_528;
	reg &= ~(0x3F3F3F3F);/*清除原有值*/
	reg |= 32<<24; /* PLL2_PFD3=528*18/32=297Mhz */
	reg |= 24<<16; /* PLL2_PFD2=528*18/24=396Mhz */
	reg |= 16<<8; /* PLL2_PFD1=528*18/16=594Mhz */
	reg |= 27<<0; /* PLL2_PFD0=528*18/27=352Mhz */
	CCM_ANALOG->PFD_528=reg; /* 设置 PLL2_PFD0~3 */

	/*
		3.设置PLL3的PFD
	*/
	reg = 0;
	reg = CCM_ANALOG->PFD_480;
	reg &= ~(0x3F3F3F3F);/*清除原有值*/
	reg |= 19<<24; /* PLL3_PFD3=480*18/19=454.74Mhz */
	reg |= 17<<16; /* PLL3_PFD2=480*18/17=508.24Mhz */
	reg |= 16<<8; /* PLL3_PFD1=480*18/16=540Mhz */
	reg |= 12<<0; /* PLL3_PFD0=480*18/12=720Mhz */
	CCM_ANALOG->PFD_480=reg; /* 设置 PLL3_PFD0~3 */

	/*
		4.设置AHB时钟,最小6M 最大132M
	*/
	CCM->CBCMR &= ~(3<<18);/*清楚原有值*/
	CCM->CBCMR |= (1<<18);/*pre_periph_clk=PLL2_PFD2=396MHz*/
	CCM->CBCDR &= ~(1 << 25); /* periph_clk=pre_periph_clk=396MHz */
	while(CCM->CDHIPR & (1 << 5));/* 等待握手完成 */
#if 0
	CCM->CBCDR &= ~(7 << 10);/* CBCDR 的 AHB_PODF 清零 */
	CCM->CBCDR |= 2 << 10; /* AHB_PODF 3 分频, AHB_CLK_ROOT=132MH
	while(CCM->CDHIPR & (1 << 1));/* 等待握手完成 */
#endif
	/* 5、设置 IPG_CLK_ROOT 最小 3Mhz,最大 66Mhz */
	CCM->CBCDR &= ~(3 << 8); /* CBCDR 的 IPG_PODF 清零 */
	CCM->CBCDR |= 1 << 8; /* IPG_PODF 2 分频, IPG_CLK_ROOT=66MH
	/* 6、设置 PERCLK_CLK_ROOT 时钟 */
	CCM->CSCMR1 &= ~(1 << 6); /* PERCLK_CLK_ROOT 时钟源为 IPG */
	CCM->CSCMR1 &= ~(7 << 0); /* PERCLK_PODF 位清零,即 1 分频 */

}

烧录测试

2018-12-30 23:07:42 qq_40788950 阅读数 133
  • 定时器、看门狗和RTC-1.9.ARM裸机第九部分

    本期课程主要讲述SoC中的时间相关的外设,包括定时器、看门狗定时器和实时时钟RTC。首先讲述了定时器的基本概念,然后以PWM定时器为例详细讲解了定时器的使用及编程细节;看门狗定时器部分详细讲了看门狗的意义和常规工作形式;后2节课讲了RTC的概念、框图和编程方法

    7410 人正在学习 去看看 朱有鹏

之前分析过da850evm_init中的其它函数,现在来看一下da850_evm_init_cpufreq()这个函数:

ret = da850_evm_init_cpufreq();
if (ret)
pr_warning("da850_evm_init: cpufreq registration failed: %d\n",ret);

这个函数的内容如下:

#ifdef CONFIG_CPU_FREQ
static __init int da850_evm_init_cpufreq(void)
{
	/*
	 * 始终将 da850-sdi的最大速度设置为 CONFIG_DA850_SDI_MAX_SPEED ,
	 * 并且取消掉原来用来设置“da850_max_speed"的”system_rev"逻辑
	 */
	da850_max_speed = CONFIG_DA850_SDI_MAX_SPEED;//
	return da850_register_cpufreq("pll0_sysclk3");
}

static struct clk pll0_sysclk3 = {
	.name		= "pll0_sysclk3",
	.parent		= &pll0_clk,
	.flags		= CLK_PLL,
	.div_reg	= PLLDIV3,
	.set_rate	= da850_set_pll0sysclk3_rate,
	.maxrate	= 152000000,
};

先来分析
/* Use PLL1_SYSCLK3 for the PLL0 bypass clock */
da850_set_pll0_bypass_src(true);
->


static void da850_set_pll0_bypass_src(bool pll1_sysclk3)
{
	struct clk *clk = &pll0_clk;
	struct pll_data *pll;
	unsigned int v;

	pll = clk->pll_data;
	v = __raw_readl(pll->base + PLLCTL);
	if (pll1_sysclk3)
		v |= PLLC0_PLL1_SYSCLK3_EXTCLKSRC;
	else
		v &= ~PLLC0_PLL1_SYSCLK3_EXTCLKSRC;
	__raw_writel(v, pll->base + PLLCTL);
}

v是从pll0_clk的PLLCTL寄存器中读出的值,然后再和 PLLC0_PLL1_SYSCLK3_EXTCLKSRC BIT(9)=1<<9相或,表示
使用PLL1_SYSCLK3 作为PLL0的旁路时钟。然后再将这个值写回寄存器中。
再来分析:

for (i = 0; i < ARRAY_SIZE(da850_freq_table); i++) {
		if (da850_freq_table[i].frequency <= da850_max_speed) {
			cpufreq_info.freq_table = &da850_freq_table[i];
			break;
		}

将小于456000KHz的频率都赋值给cpufreq_info.freq_table。
cpufreq_info的结构内容如下:


static struct davinci_cpufreq_config cpufreq_info = {
	.freq_table = da850_freq_table,
#ifdef CONFIG_REGULATOR
	.init = da850_regulator_init,
	.set_voltage = da850_set_voltage,
#endif
	.emif_rate = CONFIG_DA850_FIX_PLL0_SYSCLK3RATE,
};

da850_freq_table的内容如下:


static struct cpufreq_frequency_table da850_freq_table[] = {
	OPP(456),
	OPP(432),
	OPP(408),
	OPP(372),
	OPP(300),
	OPP(200),
	OPP(96),
	{
		.index		= 0,
		.frequency	= CPUFREQ_TABLE_END,
	},
};

又因为:

#define OPP(freq) 		\
	{				\
		.index = (unsigned int) &da850_opp_##freq,	\
		.frequency = freq * 1000, \
	}

以OPP(456)为例:

OPP(456)
{
.index =(unsigned int) &da850_opp_456,\
.frequency=freq*1000,\
}

展开da850_opp_456得到:

 static const struct da850_opp da850_opp_456 = {
	.freq		= 456000,
	.prediv		= 1,
	.mult		= 19,
	.postdiv	= 1,
	.cvdd_min	= 1300000,
	.cvdd_max	= 1350000,
};
没有更多推荐了,返回首页