Tiny4412中配置Camera接口时钟

本文详细解析了Tiny4412开发板中Camera时钟的配置原理,包括时钟驱动的注册流程、时钟源的配置寄存器使用以及Linux上层调用的规范。通过分析时钟驱动文件,了解了如何通过注册函数exynos4_register_clocks完成时钟的注册,并深入探讨了时钟配置的具体步骤。最后,展示了如何在实际应用中配置Camera接口时钟,确保在mclk管脚产生所需的时钟频率。
摘要由CSDN通过智能技术生成

由于Tiny4412是一块新出的开发板,资料比较少,本人又是小白,因此对于Camera的时钟配置没什么概念。没办法,只能从源码看起。


时钟驱动简述

Tiny4412的时钟配置源码在 /arch/arm/plat-samsung/clock.c、/arch/arm/plat-samsung/clock-clksrc.c 和 /arch/arm/mach-exynos/clock-exynos4.c下。

clock.c文件主要实现了Linux时钟接口的一些函数指针,这些指针的声明与统一调用在 include/linux/clk.h 和 drivers/clk/clkdev.c中,实际上系统通过这两个文件中实现的通用方法,最终会调用注册时钟的方法。比如调用clk_set_rate方法最终实际是调用了clk->ops->set_rate方法,这是Linux系统的驱动分层设计思想,在很多驱动中都很常见,这里就不说了。

clock-clksrc.c文件比较小,主要是实现了时钟注册的方法

clock-exynos4.c文件中声明了所有在CPU中能够使用的时钟源和配置寄存器,与芯片datasheet对应(但是没有用ioremap,貌似是给出偏移量来进行IO空间映射的,不知道这个偏移量是怎么算出来的)

三星平台关于时钟的主要结构体定义与顶层函数声明在/arch/arm/plat-samsung/include/plat/clock.h下


主要看的就是这几个源文件了。


首先来分析一下时钟是如何注册的,三星的时钟声明和注册代码在clock-exynos4.c下,主要关注最后一个函数:exynos4_register_clocks,这个函数中完成了注册操作。三星声明的clk结构体有两种,一种是单纯的clk结构体,一种是clksrc结构体。单纯的clk结构体从源码中可以看到,主要用于控制时钟的使能(对应CLK_GATE_xxx等寄存器),只是实现了enable方法,并没有实现set_rate, get_rate等方法,他们只是单纯的开关作用,调用方式为clk_enable(xxxx);

另一类为clksrc_clk结构体,这个结构体中不仅有clk结构体声明了时钟名、enable方法(对应CLK_MASK_XXX寄存器的控制),并且给出了时钟源寄存器(对应CLK_SRC_XXX)和分频寄存器(对应CLK_DIV_XXX),还给出了每个时钟的上层与他相关的时钟。因为要使能一个时钟必须要使能它上层与他相关的所有时钟,因此它采用了一个数组的形式,将所有可能的上层时钟罗列,在注册之前通过读取寄存器配置,可以选择它的上层时钟。这也是为什么不能直接配置时钟寄存器的原因,若只配置了芯片时钟寄存器,然而clk结构体内部的上层时钟的依赖关系并没有发生变化,因此最终得到的时钟配置仍然是错误的!


了解了这些之后,可以看看时钟的注册了,注册无非是将所有clk结构体信息添加至一个链表的尾部,使得系统保存所有clk配置的备份指针,从而在需要的时候可以通过查询链表获得这些clk指针。

注册也分为两种,一种是简单的clk结构体注册,注册函数为

int s3c24xx_register_clock(struct clk *clk)
{
        if (clk->enable == NULL)
                clk->enable = clk_null_enable;

        /* fill up the clk_lookup structure and register it*/
        clk->lookup.dev_id = clk->devname;
        clk->lookup.con_id = clk->name;
        clk->lookup.clk = clk;
        clkdev_add(&clk->lookup);

        return 0;
}
直接将名字添加至clk->lookup,并将这个指针加入链表后面。注意,Tiny4412下的所有clk结构体时钟是没有devname的,只有对应的name,在调用的时候传入devname=NULL


另一种是clksrc_clk的注册,这个注册方式比较麻烦,

void __init s3c_register_clksrc(struct clksrc_clk *clksrc, int size)
{
        int ret;

        for (; size > 0; size--, clksrc++) {
                if (!clksrc->reg_div.reg && !clksrc->reg_src.reg)
                        printk(KERN_ERR "%s: clock %s has no registers set\n",
                               __func__, clksrc->clk.name);

                /* fill in the default functions */

                if (!clksrc->clk.ops) {
                        if (!clksrc->reg_div.reg)
                                clksrc->clk.ops = &clksrc_ops_nodiv;
                        else if (!clksrc->reg_src.reg)
                                clksrc->clk.ops = &clksrc_ops_nosrc;
                        else
                                clksrc->clk.ops = &clksrc_ops;
                }   

                /* setup the clocksource, but do not announce it
                 * as it may be re-set by the setup routines
                 * called after the rest of the clocks have been
                 * registered
                 */
                s3c_set_clksrc(clksrc, false);

                ret = s3c24xx_register_clock(&clksrc->clk);

                if (ret < 0) {
                        printk(KERN_ERR "%s: failed to register %s (%d)\n",
                               __func__, clksrc->clk.name, ret);
                }   
        }   
}
传入的参数是一个clksrc_clk数组,依次进行注册,在每次注册时需要指定时钟的操作函数指针即clk.ops结构体,Tiny4412中所有的clksrc_clk初始都没有指定clk.ops参数,指针都需要在注册的过程中传入,因此所有的clksrc_clk结构体最终的操作函数指针都是相同的clksrc_ops。下面看看clksrc_ops中做了什么:

static struct clk_ops clksrc_ops = {
        .set_parent     = s3c_setparent_clksrc,
        .get_rate       = s3c_getrate_clksrc,
        .set_rate       = s3c_setrate_clksrc,
        .round_rate     = s3c_roundrate_clksrc,
};
给出了时钟的四种操作,对应上层函数中的,以set_rate函数指针为例:

static int s3c_setrate_clksrc(struct clk *clk, unsigned long rate)
{
        struct clksrc_clk *sclk = to_clksrc(clk);
        void __iomem *reg = sclk->reg_div.reg;
        unsigned int div;
        u32 mask = bit_mask(sclk->reg_div.shift, sclk->reg_div.size);
        u32 val;

        rate = clk_round_rate(clk, rate);
        div = clk_get_rate(clk->parent) / rate;
        if (div > (1 << sclk->reg_div.size))
                return -EINVAL;

        val = __raw_readl(reg);
        val &= ~mask;
        val |= (div - 1) << sclk->reg_div.shift;
        __raw_writel(val, reg);

        return 0;
}
代码中计算了时钟的分频系数,并重新将它写入分频寄存器。这是一个通用的方法,也是Linux顶层调用的最终实现。

注意,将clksrc_ops赋值的操作只在clksrc_clk结构体注册的时候才会有,因此只Linux顶层调用的这些函数只能获取并操作clksrc_clk中的clk,这充分说明了普通的clk结构体声明的时钟只是一个开关作用,只能用clk_enable()方法调用。

s3c_set_clksrc()根据时钟源寄存器(CLK_SRC_XXX)中的值指定了每个clk结构体的parent参数。具体过程参见源码。

最后就是普通的注册时钟了。

至此时钟注册完毕。


Linux上层的调用:

clk.h文件中给出了Linux上层调用的规范函数名:

struct clk *clk_get(struct device *dev, const char *id);
int clk_enable(struct clk *clk);
void clk_disable(struct clk *clk);
unsigned long clk_get_rate(struct clk *clk);
void clk_put(struct clk *clk);
long clk_round_rate(struct clk *clk, unsigned long rate);
int clk_set_rate(struct clk *clk, unsigned long rate);
int clk_set_parent(struct clk *clk, struct clk *parent);
struct clk *clk_get_parent(struct clk *clk);
struct clk *clk_get_sys(const char *dev_id, const char *con_id);
int clk_add_alias(const char *alias, const char *alias_dev_name, char *id,
			struct device *dev);

而具体的实现则在不同的平台下有所不同,Tiny4412平台下这些函数的实现在/arch/arm/plat-samsung/clock.c下,同样以clk_set_rate进行说明

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

        if (IS_ERR(clk))
                return -EINVAL;

        /* We do not default just do a clk->rate = rate as
         * the clock may have been made this way by choice.
         */

        WARN_ON(clk->ops == NULL);
        WARN_ON(clk->ops && clk->ops->set_rate == NULL);

        if (clk->ops == NULL || clk->ops->set_rate == NULL)
                return -EINVAL;

        spin_lock(&clocks_lock);
        ret = (clk->ops->set_rate)(clk, rate);
        spin_unlock(&clocks_lock);

        return ret;
}

只是简单地调用了传入clk对象中ops->set_rate方法,也就是上文的s3c_setrate_clksrc函数。

时钟寄存器的操作整个的流程就是这样了。要点为:

1. 注册时钟后ops指针绑定了一系列方法

2. 调用Linux顶层函数时同时实际就是调用这些方法

那么应该如何获得作为参数传入的clk指针呢?

clk_get提供了一个方法来通过时钟名和设备名获得一个clk指针,根据clock-exynos4.c文件中时钟注册时使用的name和devname作为参数传入,就能够返回对应的clk×指针。这个函数实际上是便利整个时钟链表,通过比较name和devname,返回第一个完整匹配的时钟指针,这部分比较简单,可以参看clk_get的源代码。

下面给出Tiny4412配置Camera时钟的源代码:首先是配置GPIO

        GPJ0CON_MAP = ioremap(GPJ0CON,4);
        GPJ1CON_MAP = ioremap(GPJ1CON,4);
        GPJ0PUD_MAP = ioremap(GPJ0PUD,4);
        GPJ1PUD_MAP = ioremap(GPJ1PUD,4);
        /*Set GPJ as Camera interface*/
//      printk("%s():GPJ0CON_MAP = 0x%x\n",__func__,readl(GPJ0CON_MAP));
//      printk("%s():GPJ1CON_MAP = 0x%x\n",__func__,readl(GPJ1CON_MAP));
        writel(0x22222222,GPJ0CON_MAP);
        writel(0x00022222,GPJ1CON_MAP);

        struct device my_device = {
                .init_name = "exynos4-fimc.0",
        };
        clk_set_rate(clk_get(NULL,"sclk_cam0"),24000000);
        cam_clk = clk_get(&my_device,"fimc");
        clk_enable(clk_get(NULL,"sclk_cam0"));
        if(IS_ERR(cam_clk)){
                printk("%s():clk_get failed!\n",__func__);
        }
        else{
                printk("%s():clk_get succeed!\n",__func__);
                if(clk_enable(cam_clk))
                        printk("clk enable failed\n");
                else
                        printk("clk enable succeed\n");
        }
        printk("cam clk rate is %lu\n",clk_get_rate(clk_get(NULL,"sclk_cam0")));
这样就成功配置了Camera接口时钟,并在mclk管脚产生了24MHz时钟

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值