你好!这里是风筝的博客,

欢迎和我一起交流。

我在找资料的时候,发现网上大部分文章都是说:
在s3c244x_init_clocks函数里:

void __init s3c244x_init_clocks(int xtal)
{s3c24xx_register_baseclocks(xtal); //完成祖宗级别时钟的注册  s3c244x_setup_clocks();//填充祖宗级别时钟结构,方便以后调用s3c2410_baseclk_add();//添加一些外设时钟结构到list中,并且关闭它们方便省电
}

可是我在我的kernel4.8.17里面,
已经没有了s3c24xx_register_baseclocks函数,
没有了s3c244x_setup_clocks函数,
没有了s3c2410_baseclk_add函数,,,,,,
而且arch\arm\plat-samsung\目录下也没用了clock.c文件…………

好像说是从Linux3.10内核开始就没有了吧,然后正式的使用CCF(Common Clock Framewrok)框架了。

在以前Clock部分,虽然也提供了相应的API根据名字去获取Clock,设置频率,获取父时钟,设置父时钟的函数,但是这些API都是由每个SoC单独实现,导致了代码的差异很大,于是就引入了一个新的通用的时钟框架来解决这个问题。由TI的工程师Mike Turquette提供了Common Clock Framewrok,让具体SoC实现clk_ops成员函数并通过clk_register、clk_register_clkdev注册时钟源以及源与设备对应关系,具体的clock驱动都统一迁移到drivers/clk目录。因此现在的时钟框架就采用CCF方式,使用CCF前提是内核配置了CONFIG_COMMON_CLK。

所以,以kernel4.8.17为例,我们重新看下clock注册部分,分析它的框架:
在mach-smdk2440.c文件里,调用关系为:

smdk2440_init_time->s3c2440_init_clocks(12000000)->s3c2410_common_clk_init(NULL, xtal, 1, S3C24XX_VA_CLKPWR)

看下s3c2410_common_clk_init函数,重点分析:

void __init s3c2410_common_clk_init(struct device_node *np, unsigned long xti_f,int current_soc,void __iomem *base)
{struct samsung_clk_provider *ctx;reg_base = base;if (np) {reg_base = of_iomap(np, 0);if (!reg_base)panic("%s: failed to map registers\n", __func__);}ctx = samsung_clk_init(np, reg_base, NR_CLKS);/* Register external clocks only in non-dt cases */if (!np)s3c2410_common_clk_register_fixed_ext(ctx, xti_f);if (current_soc == S3C2410) {if (_get_rate("xti") == 12 * MHZ) {s3c2410_plls[mpll].rate_table = pll_s3c2410_12mhz_tbl;s3c2410_plls[upll].rate_table = pll_s3c2410_12mhz_tbl;}/* Register PLLs. */samsung_clk_register_pll(ctx, s3c2410_plls,ARRAY_SIZE(s3c2410_plls), reg_base);//专门用于锁相环的注册} else { /* S3C2440, S3C2442 */if (_get_rate("xti") == 12 * MHZ) {/** plls follow different calculation schemes, with the* upll following the same scheme as the s3c2410 plls*/s3c244x_common_plls[mpll].rate_table =pll_s3c244x_12mhz_tbl;s3c244x_common_plls[upll].rate_table =pll_s3c2410_12mhz_tbl;}/* Register PLLs. */samsung_clk_register_pll(ctx, s3c244x_common_plls,ARRAY_SIZE(s3c244x_common_plls), reg_base);}/* Register common internal clocks. */samsung_clk_register_mux(ctx, s3c2410_common_muxes,ARRAY_SIZE(s3c2410_common_muxes));samsung_clk_register_div(ctx, s3c2410_common_dividers,ARRAY_SIZE(s3c2410_common_dividers));samsung_clk_register_gate(ctx, s3c2410_common_gates,ARRAY_SIZE(s3c2410_common_gates));if (current_soc == S3C2440 || current_soc == S3C2442) {samsung_clk_register_div(ctx, s3c244x_common_dividers,ARRAY_SIZE(s3c244x_common_dividers));samsung_clk_register_gate(ctx, s3c244x_common_gates,ARRAY_SIZE(s3c244x_common_gates));samsung_clk_register_mux(ctx, s3c244x_common_muxes,ARRAY_SIZE(s3c244x_common_muxes));samsung_clk_register_fixed_factor(ctx, s3c244x_common_ffactor,ARRAY_SIZE(s3c244x_common_ffactor));}/* Register SoC-specific clocks. */switch (current_soc) {case S3C2410:samsung_clk_register_div(ctx, s3c2410_dividers,ARRAY_SIZE(s3c2410_dividers));samsung_clk_register_fixed_factor(ctx, s3c2410_ffactor,ARRAY_SIZE(s3c2410_ffactor));samsung_clk_register_alias(ctx, s3c2410_aliases,ARRAY_SIZE(s3c2410_aliases));break;case S3C2440:samsung_clk_register_mux(ctx, s3c2440_muxes,ARRAY_SIZE(s3c2440_muxes));samsung_clk_register_gate(ctx, s3c2440_gates,ARRAY_SIZE(s3c2440_gates));break;case S3C2442:samsung_clk_register_mux(ctx, s3c2442_muxes,ARRAY_SIZE(s3c2442_muxes));samsung_clk_register_fixed_factor(ctx, s3c2442_ffactor,ARRAY_SIZE(s3c2442_ffactor));break;}/** Register common aliases at the end, as some of the aliased clocks* are SoC specific.*/samsung_clk_register_alias(ctx, s3c2410_common_aliases,ARRAY_SIZE(s3c2410_common_aliases));if (current_soc == S3C2440 || current_soc == S3C2442) {samsung_clk_register_alias(ctx, s3c244x_common_aliases,ARRAY_SIZE(s3c244x_common_aliases));}s3c2410_clk_sleep_init();samsung_clk_of_add_provider(np, ctx);
}

这里我们传入的参数np为NULL,xti_f为12000000,current_soc为1。
进入这个函数,分析:
第8行:条件不成立。
第14行:初始化时钟,主要就是给ctx分配空间和填充一些数据,如寄存器基地址。
第18行:注册通用的外部固定时钟,调用关系为:

s3c2410_common_clk_register_fixed_ext->samsung_clk_register_fixed_rate
        ->clk_register_fixed_rate
            ->clk_register_fixed_rate_with_accuracy[注1]
                ->clk_hw_register_fixed_rate_with_accuracy
                    ->clk_hw_register
                        ->clk_register[注2]
        ->clk_register_clkdev
            ->__clk_register_clkdev
                ->vclkdev_create
                    ->__clkdev_add[注3]
    ->samsung_clk_register_alias[注4]
        ->clk_register_clkdev

[注1]:在clk_hw_register_fixed_rate_with_accuracy主要就是设置一些参数,如name、flags、parent_names、num_parents之类的。
[注2]:在clk_register函数里就会初始化core->clks哈希链表,创建一个hw->clk链接到core->clks上,并将hw->clk返回到clk_register_fixed_rate函数上,注意,hw->clk有两个要注意的成员变量:dev_id(device ID)和con_id(connection ID)。
[注3]:在__clkdev_add函数里就会:list_add_tail(&cl->node, &clocks);
将cl->node链接到clocks,注意,cl上同样有dev_id和con_id,同时注意clocks这个链表!
[注4]:在samsung_clk_register_alias函数,注册时钟的别名(比如别名spi,他是挂在PCLK上),同样调用到clk_register_clkdev,最终进入[注3],将list->alias作为con_id,链接到clocks上。

继续:
第20行:我们current_soc为1,S3C2410为0,if不成立。
第43行:专门用于锁相环的注册。
第48~52行:
samsung_clk_register_mux:时钟选择
samsung_clk_register_div:时钟分频
samsung_clk_register_gate:时钟门控,注册的时钟只可以开关,通过.enable/.disable回调

其实这些函数都大同小异,我们以samsung_clk_register_gate来看:
samsung_clk_register_gate(ctx, s3c2410_common_gates , ARRAY_SIZE(s3c2410_common_gates));
看下s3c2410_common_gates数组:

struct samsung_gate_clock s3c2410_common_gates[] __initdata = {GATE(PCLK_SPI, "spi", "pclk", CLKCON, 18, 0, 0),GATE(PCLK_I2S, "i2s", "pclk", CLKCON, 17, 0, 0),GATE(PCLK_I2C, "i2c", "pclk", CLKCON, 16, 0, 0),GATE(PCLK_ADC, "adc", "pclk", CLKCON, 15, 0, 0),GATE(PCLK_RTC, "rtc", "pclk", CLKCON, 14, 0, 0),GATE(PCLK_GPIO, "gpio", "pclk", CLKCON, 13, CLK_IGNORE_UNUSED, 0),GATE(PCLK_UART2, "uart2", "pclk", CLKCON, 12, 0, 0),GATE(PCLK_UART1, "uart1", "pclk", CLKCON, 11, 0, 0),GATE(PCLK_UART0, "uart0", "pclk", CLKCON, 10, 0, 0),GATE(PCLK_SDI, "sdi", "pclk", CLKCON, 9, 0, 0),GATE(PCLK_PWM, "pwm", "pclk", CLKCON, 8, 0, 0),GATE(HCLK_USBD, "usb-device", "hclk", CLKCON, 7, 0, 0),GATE(HCLK_USBH, "usb-host", "hclk", CLKCON, 6, 0, 0),GATE(HCLK_LCD, "lcd", "hclk", CLKCON, 5, 0, 0),GATE(HCLK_NAND, "nand", "hclk", CLKCON, 4, 0, 0),
};//格式:id、name、parent_name、offset、bit_idx、flags、gate_flags

这些就是注册的时钟源了。
samsung_clk_register_gate函数的调用关系如下:

samsung_clk_register_gate->clk_register_gate
        ->clk_hw_register_gate[注5]
            ->clk_hw_register
                ->clk_register[注2]

[注5]:在clk_hw_register_gate函数,同样是设置一些参数,如name、flags、parent_names、num_parents之类的以及填充:init.ops = &clk_gate_ops。

const struct clk_ops clk_gate_ops = {.enable = clk_gate_enable,.disable = clk_gate_disable,.is_enabled = clk_gate_is_enabled,
};

这里就是填充回调函数了,在clk_enable/clk_disable时会调用到。
[注2]:在上面提到过。

继续:
第56~62行:
因为2440和2410是相似的,这是在2410的基础上对2440进行补充。
第94行:注册时钟别名,这就比较重要了:
samsung_clk_register_alias(ctx, s3c2410_common_aliases , ARRAY_SIZE(s3c2410_common_aliases));
看下s3c2410_common_aliases数组:

struct samsung_clock_alias s3c2410_common_aliases[] __initdata = {ALIAS(PCLK_I2C, "s3c2410-i2c.0", "i2c"),ALIAS(PCLK_ADC, NULL, "adc"),ALIAS(PCLK_RTC, NULL, "rtc"),ALIAS(PCLK_PWM, NULL, "timers"),ALIAS(HCLK_LCD, NULL, "lcd"),ALIAS(HCLK_USBD, NULL, "usb-device"),ALIAS(HCLK_USBH, NULL, "usb-host"),ALIAS(UCLK, NULL, "usb-bus-host"),ALIAS(UCLK, NULL, "usb-bus-gadget"),ALIAS(ARMCLK, NULL, "armclk"),ALIAS(UCLK, NULL, "uclk"),ALIAS(HCLK, NULL, "hclk"),ALIAS(MPLL, NULL, "mpll"),ALIAS(FCLK, NULL, "fclk"),ALIAS(PCLK, NULL, "watchdog"),ALIAS(PCLK_SDI, NULL, "sdi"),ALIAS(HCLK_NAND, NULL, "nand"),ALIAS(PCLK_I2S, NULL, "iis"),ALIAS(PCLK_I2C, NULL, "i2c"),
};//格式:id、dev_name、alias

samsung_clk_register_alias函数在之前[注4]说过,就是将con_id(这里即是alias)链接到clocks上。

好了,到这就分析结束了,那我们怎么获取时钟呢?

当然是非常大众化的clk_get函数了:

struct clk *clk_get(struct device *dev, const char *con_id)
{const char *dev_id = dev ? dev_name(dev) : NULL;struct clk *clk;if (dev) {clk = __of_clk_get_by_name(dev->of_node, dev_id, con_id);if (!IS_ERR(clk) || PTR_ERR(clk) == -EPROBE_DEFER)return clk;}return clk_get_sys(dev_id, con_id);
}

通常来说,第一个参数设置为NULL即可。
__of_clk_get_by_name是关于dts设备树的API。
进入clk_get_sys函数看看:

struct clk *clk_get_sys(const char *dev_id, const char *con_id)
{struct clk_lookup *cl;struct clk *clk = NULL;mutex_lock(&clocks_mutex);cl = clk_find(dev_id, con_id);if (!cl)goto out;clk = __clk_create_clk(cl->clk_hw, dev_id, con_id);if (IS_ERR(clk))goto out;if (!__clk_get(clk)) {__clk_free_clk(clk);cl = NULL;goto out;}out:mutex_unlock(&clocks_mutex);return cl ? clk : ERR_PTR(-ENOENT);
}

这里有两个函数:clk_find和__clk_create_clk
看下clk_find函数:

static struct clk_lookup *clk_find(const char *dev_id, const char *con_id)
{struct clk_lookup *p, *cl = NULL;int match, best_found = 0, best_possible = 0;if (dev_id)best_possible += 2;if (con_id)best_possible += 1;list_for_each_entry(p, &clocks, node) {match = 0;if (p->dev_id) {if (!dev_id || strcmp(p->dev_id, dev_id))continue;match += 2;}if (p->con_id) {if (!con_id || strcmp(p->con_id, con_id))continue;match += 1;}if (match > best_found) {cl = p;if (match != best_possible)best_found = match;elsebreak;}}return cl;
}

里面的list_for_each_entry就是遍历clocks这条链表,
可以看下这篇:Linux下链表的使用及探究
然后比较clocks这条链表上的名字和dev_id以及con_id,如果dev_id匹配,那么match+=2,如果con_id匹配,那么match+=1,当match > best_found说明找到了这个clock的结构体,当然,找到了并不意味着最优解,当match == best_possible才是最完美的情况。
这里可以看出:匹配的优先程度是:dev+con > dev only > con only
之后这个clock结构体会给与到__clk_create_clk函数使用:

struct clk *__clk_create_clk(struct clk_hw *hw, const char *dev_id,const char *con_id)
{struct clk *clk;/* This is to allow this function to be chained to others */if (IS_ERR_OR_NULL(hw))return (struct clk *) hw;clk = kzalloc(sizeof(*clk), GFP_KERNEL);if (!clk)return ERR_PTR(-ENOMEM);clk->core = hw->core;clk->dev_id = dev_id;clk->con_id = con_id;clk->max_rate = ULONG_MAX;clk_prepare_lock();hlist_add_head(&clk->clks_node, &hw->core->clks);clk_prepare_unlock();return clk;
}

将之添加到哈希表上进行管理。

最后看下得到clk结构体后是怎么使能时钟的:clk_enable函数:
调用关系为:

clk_enable->clk_core_enable_lock
        ->clk_core_enable   
static int clk_core_enable(struct clk_core *core)
{int ret = 0;lockdep_assert_held(&enable_lock);if (!core)return 0;if (WARN_ON(core->prepare_count == 0))return -ESHUTDOWN;if (core->enable_count == 0) {ret = clk_core_enable(core->parent);if (ret)return ret;trace_clk_enable_rcuidle(core);if (core->ops->enable)ret = core->ops->enable(core->hw);trace_clk_enable_complete_rcuidle(core);if (ret) {clk_core_disable(core->parent);return ret;}}core->enable_count++;return 0;
}

在里面,一开始先判断core的有效性, 然后继续调用自己进行进行递归处理,传入的参数为core->parent,要知道,使能一个设备的clock,一定要保定它父设备的clock是使能的。
之后就掉调用core->ops->enable回调函数进行使能了!

最后的最后,给出一些API函数:
struct clk *__clk_lookup(const char *name) 通过时钟名找到时钟
static inline int clk_enable(struct clk *clk) 使能时钟,不会睡眠
static inline void clk_disable(struct clk *clk) 禁止时钟,不会睡眠
static inline int clk_prepare_enable(struct clk *clk) 使能时钟,可能会睡眠
static inline void clk_disable_unprepare(struct clk *clk) 禁止时钟,可能会睡眠
static inline unsigned long clk_get_rate(struct clk *clk) 获取时钟频率
static inline int clk_set_rate(struct clk *clk, unsigned long rate) 设置时钟频率
static inline long clk_round_rate(struct clk *clk, unsigned long rate) 获取最接近的时钟频率
static inline int clk_set_parent(struct clk *clk, struct clk *parent) 设置时钟的父时钟
static inline struct clk *clk_get_parent(struct clk *clk) 获取时钟的父时钟

嵌入式Linux驱动笔记(十四)------详解clock时钟(CCF)框架及clk_get函数相关推荐

  1. 嵌入式Linux驱动笔记(十八)------浅析V4L2框架之ioctl【转】

    转自:https://blog.csdn.net/Guet_Kite/article/details/78574781 权声明:本文为 风筝 博主原创文章,未经博主允许不得转载!!!!!!谢谢合作 h ...

  2. 嵌入式Linux驱动笔记(十六)------设备驱动模型(kobject、kset、ktype)

    ###你好!这里是风筝的博客, ###欢迎和我一起交流. 前几天去面试,被问到Linux设备驱动模型这个问题,没答好,回来后恶补知识,找了些资料,希望下次能答出个满意答案. Linux早期时候,一个驱 ...

  3. 嵌入式Linux驱动笔记--转自风筝丶

    为了阅读学习方便,将系列博客的网址进行粘贴,感谢原博客的分享. 嵌入式Linux驱动笔记(一)------第一个LED驱动程序 嵌入式Linux驱动笔记(二)------定时器 嵌入式Linux驱动笔 ...

  4. 嵌入式Linux驱动笔记(五)------学习platform设备驱动

    你好!这里是风筝的博客, 欢迎和我一起交流. 设备是设备,驱动是驱动. 如果把两个糅合写一起,当设备发生变化时,势必要改写整个文件,这是非常愚蠢的做法.如果把他们分开来,当设备发生变化时,只要改写设备 ...

  5. 嵌入式Linux驱动笔记(十一)------i2c设备之mpu6050驱动

    ###你好!这里是风筝的博客, ###欢迎和我一起交流. 上一节讲了i2c框架: 嵌入式Linux驱动笔记(十)------通俗易懂式了解i2c框架 这次就来写一写真正的i2c设备驱动: mpu605 ...

  6. 嵌入式Linux驱动笔记(二十四)------framebuffer之使用spi-tft屏幕(上)

    你好!这里是风筝的博客, 欢迎和我一起交流. 最近入手了一块spi接口的tft彩屏,想着在我的h3板子上使用framebuffer驱动起来. 我们知道: Linux抽象出FrameBuffer这个设备 ...

  7. Linux驱动开发必看详解神秘内核(完全转载)

    Linux驱动开发必看详解神秘内核 完全转载-链接:http://blog.chinaunix.net/uid-21356596-id-1827434.html IT168 技术文档]在开始步入Lin ...

  8. 嵌入式Linux驱动笔记(二十五)------Input子系统框架

    你好!这里是风筝的博客, 欢迎和我一起交流. 一.Input子系统概述 二.Input子系统架构 三.Input子系统工作机制 3.1 核心层(input.c) 3.1.1 input_init函数 ...

  9. 嵌入式Linux驱动笔记(二十九)------内存管理之伙伴算法(Buddy)分析

    你好!这里是风筝的博客, 欢迎和我一起交流. 我们知道,在一个通用操作系统里,频繁申请内存释放内存都会出现一个非常著名的内存管理问题:内存碎片. 学过操作系统的都知道,有很多行之有效的方法(比如:记录 ...

最新文章

  1. PetShop之表示层设计 - 《解剖PetShop》系列之六
  2. 13款基于jQuery Mobile的布局插件和示例
  3. sybase备份问题
  4. html元素一行显示不完收缩_这些常用的HTML标签,你还不知道吗?
  5. 阿里面试官整理的JVM面试要点,99%的你都不知道!
  6. [转]localCache与集中式cache
  7. Windows7中被大家忽略的实用七大功能
  8. linux编译trinitycore,TC编译步骤之二代码安装
  9. 金山词霸-身边的实用工具
  10. window下默认的汉字模式——GBK
  11. 【读书笔记】《有效需求分析》
  12. 基于JAVA蔚蓝在线学习平台计算机毕业设计源码+数据库+lw文档+系统+部署
  13. 用Python把20年的GDP、人口以及房价数据进行了可视化
  14. Elasticsearch:Hadoop 大数据集成 (Hadoop => Elasticsearch)
  15. 3. 自定义Java编译时注解处理器
  16. java对接portal协议_Portal协议分析
  17. windows下的host文件在哪里,有什么作用?
  18. CSDN 如何修改用户名(CSDN ID)?
  19. JS实现3D旋转图片
  20. 【机器学习】SMO算法

热门文章

  1. bootstrap-table固定表头固定列
  2. WIN10系统如何开启终端
  3. sanf()、kbhit()、getch()获取键盘信息与peekMessage()获取鼠标信息
  4. JS实战面向对象 - 贪吃蛇
  5. mysql引擎与优化
  6. 基于FPGA的嵌入式图像处理笔记——图像增强的特例(图像反转)
  7. 量产国产服务器cpu芯片,中国第一!百度自研芯片量产:配国产CPU
  8. bowtie里的FM-index简介
  9. qt 关联android,从QT app(com.android.settings)打开android设置
  10. 从工控网络安全攻击中学习的经验