Linux设备驱动开发 - S3C2440时钟分析

news2024/9/23 1:35:02

By: fulinux
E-mail: fulinux@sina.com
Blog: https://blog.csdn.net/fulinus
喜欢的盆友欢迎点赞和订阅!
你的喜欢就是我写作的动力!

在这里插入图片描述

目录

  • 系统框架图
  • FLCK,HCLK,PCLK
    • 时钟源的选择
  • 时钟体系流程
  • 流程分析
  • 驱动中的clk

系统框架图

2440是一个SOC(system on chip)系统,不仅有很多CPU,还有很多外设,在2440芯片手册有系统框架图如下:
在这里插入图片描述
2440框架图中,不仅有CPU,而且有很多外设,其中外设分为两部分,一部分是AHB总线,一部分是APB总线。

FLCK,HCLK,PCLK

因此引入了三个时钟,FLCK,HCLK,PCLK
CPU工作与FCLK,最大工作频率400MHZ
AHB总线工作于HCLK,最大工作频率136MHZ
APB总线工作与PCLK,最大工作频率68MHZ
晶振OSC = 12M,如何得到FCLK、HCLK、PCLK呢?
在这里插入图片描述

时钟源的选择

从图中看出时钟源有两个分别是外部晶振和引脚,通过OM[3:2]来选择,模式选择如下图
在这里插入图片描述
在2440的原理图上,可以看到OM3,OM2都接到了GND上,因此会选择使用外部晶振作为输入 :
在这里插入图片描述

时钟体系流程

12MHZ晶振通过MPLL锁相环得到FLCK,CPU直接使用FCLK,FCLK通过PDIV分频得到PCLK提供给APB总线上的设备使用,FCLK通过HDIV分频得到HCLK提供给AHB总线上使用,12MHZ晶振也会经过UPLL锁相环提供给USB使用。
在这里插入图片描述
注:一旦设置PLL,就会锁定LOCK TIME 直到PLL输出稳定,然后CPU开始工作于新的频率FCLK

流程分析

一上电,复位引脚会维持一段时间(等待电源稳定),通过复位芯片来维持,如下:

  1. 根据OM[3:2]的值FCLK等于晶振的12M
  2. PLL锁存OM[3:2]的值,同时CPU开始运行(复位引脚被拉高)
  3. 设置PLL
  4. 在CPU停止运行一段时间设置PLL
  5. 设置完成后FLCK等于PLL输出的新的频率
  6. 寄存器的设置(目的FCLK=400MZH, HCLK=100MZH, PCLK=50MHZ)
  7. 设置LOCKTIME寄存器,决定MPLL和UPLL的LOCKTIME
    设置为默认值即可
  8. 设置MPLLCON/UPLLCON寄存器,决定FCLKi,如下图
    在这里插入图片描述
    其中公式如下图:
    在这里插入图片描述
    参考设置值如下,我们选择MDIV=92, PDIV = 1, SDIV=1即可设置
    m = (92 + 8) = 100
    p = (1 + 2) =3
    s = 1
    Mpll = (2 * 100 * 12000000) / (3 * 2^1) = 400MHz
    FCLK = Mpll = 400MHZ:

在这里插入图片描述
MPLLCON = (92 << 12) | (1 << 4) | (1 << 0);
设置CLKCON寄存器(关掉用不到的设备)
设置CLKDIVN寄存器,决定HCLK,PCLK,如下图:
在这里插入图片描述
首先要设置bit[2:1]为10,这时HCLK=FCLK/4=100MHZ,但是需要CAMDIVN[9]=0(默认值就是0)
设置bit[0]为1,这时PCLK=HCLK/2=50MHZ
bit[3]取默认值
如果HDIVN不等于0,CPU必须设置为异步模式,CPU会工作于HCLK,如下图
在这里插入图片描述
对于此处的汇编命令 orr r0,r0,#R1_nF:OR:R1_iA ,查阅相关资料大概了解了一下:【它扯到了协处理器P15的C1寄存器】可以参考以下地址了解协处理器P15(https://www.cnblogs.com/lifexy/p/7203786.html)
原来iA位和nF位是控制CPU总线模式的:

orr r0,r0,#R1_nF:OR:R1_iA

这命令的意思肯定是让CPU的总线模式从“fast bus mode”变为“asynchronous bus mode”
怎么理解#R1_nF:OR:R1_iA这东西,刚开始以为这是arm指令的某个命令,网上找到有人把它理解成条件运算符(exp1?exp2:exp3;),其实它就是对r0寄存器的30,31位置“1”的一条伪代码。所以我们在bootloader里会看到这样的代码:

mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000;R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0

所以 #R1_nF:OR:R1_iA 就是 #0xc0000000的意思

提高系统时钟代码如下(FCLK=400MHZ,HCLK=100MHZ,PCLK=50MHZ):

.text
.global _start
_start:
	/* 1.关闭看门狗 */
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]
	/* 2.设置时钟 */
	/* 2.1 设置LOCKTIME(0x4C000000)=0xFFFFFFFF */
	ldr r0, =0x4C000000
	ldr r1, =0xFFFFFFFF
	str r1, [r0]
	/* 2.2 设置CLKDIVN(0x4C000014) = 0x5 FCLK : HCLK : PCLK = 400m : 100m : 50m*/
	ldr r0, =0x4C000014
	ldr r1, =0x5
	str r1, [r0]
	/* 2.3 设置CPU处于异步模式 */
	mrc p15,0,r0,c1,c0,0
	orr r0,r0,#0xc0000000 /* #R1_nF:OR:R1_iA */
	mcr p15,0,r0,c1,c0,0
	/* 2.4 设置MPLLCON(0x4C000004)=(92<<12)   | (1 << 4) | (1 << 0)
	 *	   m = MDIV + 8 = 100
	 *	   p = PDIV + 2 = 3
	 *	   s = SDIV = 1
	 * 	   Mpll = (2 * m * Fin) / (p * 2 ^ s)= (2 * 100 * 12) / (3 * 2 ^ 1) = 400MHZ
	 */
	ldr r0, =0x4C000004
	ldr r1, =(92<<12) | (1 << 4) | (1 << 0)
	str r1, [r0]
      /* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
       * 然后CPU工作于新的频率FCLK
       */

	/* 3.设置栈
	 *   自动分辨NOR启动或者NAND启动
	 *   向0地址写入0,在读出来,如果写入则是NAND,否则是NOR
	 */
	ldr r0, =0
	ldr r1, [r0] /* 读出原来的值备份 */
	str r0, [r0] /* 向0地址写入0 */
	ldr r2, [r0] /* 再次读出来 */
	cmp r1, r2
	ldr sp, =0x40000000 + 4096 /* nor启动 */
	moveq sp, #4096			   /* nand启动 */
	streq r1, [r0]			   /* 恢复原来的值 */
	bl main
halt:
	b halt

驱动中的clk

drivers/clk/samsung/clk-s3c2410.c

static void __init mini2440_init_time(void)
{
    s3c2440_init_clocks(12000000);
    +----s3c2410_common_clk_init(NULL, 12000000, 1, S3C24XX_VA_CLKPWR);

void __init s3c2410_common_clk_init(struct device_node *np, unsigned long xti_f, //0
                    int current_soc, //S3C2440
                    void __iomem *base) //0
{
	struct samsung_clk_provider *samsung_clk_init(np, reg_base, NR_CLKS); // NR_CLKS=41
		struct samsung_clk_provider * ctx = kzalloc(sizeof(struct samsung_clk_provider), GFP_KERNEL);
		struct clk **clk_table = kcalloc(nr_clks, sizeof(struct clk *), GFP_KERNEL);
		ctx->reg_base = base;
		ctx->clk_data.clks = clk_table;
		ctx->clk_data.clk_num = nr_clks;
		spin_lock_init(&ctx->lock);
	
	 /*
      * 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;
     //看下pll_s3c244x_12mhz_tbl
     /* S3C244x specific clocks */
	static struct samsung_pll_rate_table pll_s3c244x_12mhz_tbl[] __initdata = { 
	    /* sorted in descending order */
	    PLL_35XX_RATE(400000000, 0x5c, 1, 1),  //MDIV = 0x5c = 92, PDIV = 1, SDIV=1
	    //...
	    PLL_35XX_RATE(75000000, 0x75, 3, 3), 
	    { /* sentinel */ },
	};
	/* Register PLLs. */
    samsung_clk_register_pll(ctx, s3c244x_common_plls, ARRAY_SIZE(s3c244x_common_plls), reg_base);
}

下面看下samsung_clk_register_pll函数:
drivers/clk/samsung/clk-pll.c

void __init samsung_clk_register_pll(struct samsung_clk_provider *ctx,
            const struct samsung_pll_clock *pll_list,
            unsigned int nr_pll, void __iomem *base)
{
    int cnt; 

    for (cnt = 0; cnt < nr_pll; cnt++)
        _samsung_clk_register_pll(ctx, &pll_list[cnt], base);
}

static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx,
                const struct samsung_pll_clock *pll_clk,
                void __iomem *base)
{
	struct samsung_clk_pll *pll = kzalloc(sizeof(*pll), GFP_KERNEL);
    struct clk *clk;
    struct clk_init_data init;
    
    init.name = pll_clk->name;
    init.flags = pll_clk->flags;
    init.parent_names = &pll_clk->parent_name;
    init.num_parents = 1;

	if (pll_clk->rate_table) {
	        /* find count of rates in rate_table */
	        for (len = 0; pll_clk->rate_table[len].rate != 0; )
	            len++;
	        pll->rate_count = len;
	        pll->rate_table = kmemdup(pll_clk->rate_table,
	                    pll->rate_count *
	                    sizeof(struct samsung_pll_rate_table),
	                    GFP_KERNEL);
	    }
	}
	
	init.ops = &samsung_s3c2440_mpll_clk_ops; //下面

	pll->hw.init = &init;
    pll->type = pll_clk->type;
    pll->lock_reg = base + pll_clk->lock_offset;
    pll->con_reg = base + pll_clk->con_offset;
static struct samsung_pll_clock s3c244x_common_plls[] __initdata = {
    [mpll] = PLL(pll_s3c2440_mpll, MPLL, "mpll", "xti",
                        LOCKTIME, MPLLCON, NULL),
    [upll] = PLL(pll_s3c2410_upll, UPLL, "upll", "xti",
                        LOCKTIME, UPLLCON, NULL),
};

#define __PLL(_typ, _id, _dname, _name, _pname, _flags, _lock, _con,    \
        _rtable, _alias)                    \
    {                               \
        .id     = _id,                  \
        .type       = _typ,                 \
        .dev_name   = _dname,               \
        .name       = _name,                \
        .parent_name    = _pname,               \
        .flags      = CLK_GET_RATE_NOCACHE,         \
        .con_offset = _con,                 \
        .lock_offset    = _lock,                \
        .rate_table = _rtable,              \
        .alias      = _alias,               \
    }

#define PLL(_typ, _id, _name, _pname, _lock, _con, _rtable) \
    __PLL(_typ, _id, NULL, _name, _pname, CLK_GET_RATE_NOCACHE, \
        _lock, _con, _rtable, _name)
init.ops = &samsung_s3c2440_mpll_clk_ops;
static const struct clk_ops samsung_s3c2440_mpll_clk_ops = {
    .recalc_rate = samsung_s3c2440_mpll_recalc_rate,
    .enable = samsung_s3c2410_mpll_enable,
    .disable = samsung_s3c2410_mpll_disable,
    .round_rate = samsung_pll_round_rate,
    .set_rate = samsung_s3c2410_pll_set_rate,
};

static unsigned long samsung_s3c2440_mpll_recalc_rate(struct clk_hw *hw,
                    unsigned long parent_rate)
{
    struct samsung_clk_pll *pll = to_clk_pll(hw);
    u32 pll_con, mdiv, pdiv, sdiv;
    u64 fvco = parent_rate;

    pll_con = readl_relaxed(pll->con_reg);
    mdiv = (pll_con >> PLLS3C2410_MDIV_SHIFT) & PLLS3C2410_MDIV_MASK;
    pdiv = (pll_con >> PLLS3C2410_PDIV_SHIFT) & PLLS3C2410_PDIV_MASK;
    sdiv = (pll_con >> PLLS3C2410_SDIV_SHIFT) & PLLS3C2410_SDIV_MASK;

    fvco *= (2 * (mdiv + 8));
    do_div(fvco, (pdiv + 2) << sdiv);

    return (unsigned int)fvco;
}
static int samsung_s3c2410_mpll_enable(struct clk_hw *hw)
{
    return samsung_s3c2410_pll_enable(hw, 5, true); //CLKSLOW[5]:MPLL_OFF
}

static int samsung_s3c2410_pll_enable(struct clk_hw *hw, int bit, bool enable)
{
    struct samsung_clk_pll *pll = to_clk_pll(hw);
    u32 pll_en = readl_relaxed(pll->lock_reg + PLLS3C2410_ENABLE_REG_OFFSET);
    u32 pll_en_orig = pll_en;

    if (enable)
        pll_en &= ~BIT(bit);
    else
        pll_en |= BIT(bit);

    writel_relaxed(pll_en, pll->lock_reg + PLLS3C2410_ENABLE_REG_OFFSET); //0x10

    /* if we started the UPLL, then allow to settle */
    if (enable && (pll_en_orig & BIT(bit)))
        udelay(300);

    return 0;
}
static int samsung_s3c2410_upll_enable(struct clk_hw *hw)
{
    return samsung_s3c2410_pll_enable(hw, 7, true); //CLKSLOW[7]:UCLK_ON
}

在这里插入图片描述

static int samsung_s3c2410_pll_set_rate(struct clk_hw *hw, unsigned long drate,
                    unsigned long prate)
{
    struct samsung_clk_pll *pll = to_clk_pll(hw);
    const struct samsung_pll_rate_table *rate;
    u32 tmp;

    /* Get required rate settings from table */
    rate = samsung_get_pll_settings(pll, drate); //下面根据频率值得到rate结构,包含mdiv、pdiv和sdiv数据。
    if (!rate) {
        pr_err("%s: Invalid rate : %lu for pll clk %s\n", __func__,
            drate, clk_hw_get_name(hw));
        return -EINVAL;
    }

    tmp = readl_relaxed(pll->con_reg);

    /* Change PLL PMS values */
    tmp &= ~((PLLS3C2410_MDIV_MASK << PLLS3C2410_MDIV_SHIFT) |
            (PLLS3C2410_PDIV_MASK << PLLS3C2410_PDIV_SHIFT) |
            (PLLS3C2410_SDIV_MASK << PLLS3C2410_SDIV_SHIFT));
    tmp |= (rate->mdiv << PLLS3C2410_MDIV_SHIFT) |
            (rate->pdiv << PLLS3C2410_PDIV_SHIFT) |
            (rate->sdiv << PLLS3C2410_SDIV_SHIFT);
    writel_relaxed(tmp, pll->con_reg);

    /* Time to settle according to the manual */
    udelay(300);

    return 0;
}

继续看下:

static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx,
                const struct samsung_pll_clock *pll_clk,
                void __iomem *base)
{
	clk = clk_register(NULL, &pll->hw);
	samsung_clk_add_lookup(ctx, clk, pll_clk->id);
	ret = clk_register_clkdev(clk, pll_clk->alias, pll_clk->dev_name);
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),
};

在这里插入图片描述
在这里插入图片描述

samsung_clk_register_gate(ctx, s3c2410_common_gates, ARRAY_SIZE(s3c2410_common_gates));
	for (idx = 0; idx < nr_clk; idx++, list++) {
		clk = clk_register_gate(NULL, list->name, list->parent_name,
                list->flags, ctx->reg_base + list->offset,
                list->bit_idx, list->gate_flags, &ctx->lock);
        /* register a clock lookup only if a clock alias is specified */
        if (list->alias) {
        	ret = clk_register_clkdev(clk, list->alias,
                            list->dev_name);
        }

看下clk_register_gate(drivers/clk/clk-gate.c)

struct clk *clk_register_gate(struct device *dev, const char *name,
        const char *parent_name, unsigned long flags,
        void __iomem *reg, u8 bit_idx, 
        u8 clk_gate_flags, spinlock_t *lock)
{               
    struct clk_hw *hw;
        
    hw = clk_hw_register_gate(dev, name, parent_name, flags, reg,
                  bit_idx, clk_gate_flags, lock);
    if (IS_ERR(hw))
        return ERR_CAST(hw);
    return hw->clk;
}       
EXPORT_SYMBOL_GPL(clk_register_gate);

/**
 * clk_hw_register_gate - register a gate clock with the clock framework
 * @dev: device that is registering this clock
 * @name: name of this clock
 * @parent_name: name of this clock's parent
 * @flags: framework-specific flags for this clock
 * @reg: register address to control gating of this clock
 * @bit_idx: which bit in the register controls gating of this clock
 * @clk_gate_flags: gate-specific flags for this clock
 * @lock: shared register lock for this clock
 */ 
struct clk_hw *clk_hw_register_gate(struct device *dev, const char *name,
        const char *parent_name, unsigned long flags,
        void __iomem *reg, u8 bit_idx,
        u8 clk_gate_flags, spinlock_t *lock)
{   
    struct clk_gate *gate;
    struct clk_hw *hw; 
    struct clk_init_data init = {};
    int ret;
    
    if (clk_gate_flags & CLK_GATE_HIWORD_MASK) {
        if (bit_idx > 15) {
            pr_err("gate bit exceeds LOWORD field\n");
            return ERR_PTR(-EINVAL);
        }       
    }
    
    /* allocate the gate */
    gate = kzalloc(sizeof(*gate), GFP_KERNEL);
    if (!gate)
        return ERR_PTR(-ENOMEM);

    init.name = name;
    init.ops = &clk_gate_ops; //下面看下这个
    init.flags = flags | CLK_IS_BASIC;
    init.parent_names = (parent_name ? &parent_name: NULL);
    init.num_parents = (parent_name ? 1 : 0);
        
    /* struct clk_gate assignments */
    gate->reg = reg;
    gate->bit_idx = bit_idx;
    gate->flags = clk_gate_flags;
    gate->lock = lock;
    gate->hw.init = &init;
                  
    hw = &gate->hw;
    ret = clk_hw_register(dev, hw);
    if (ret) {
        kfree(gate);
        hw = ERR_PTR(ret);
    }       

    return hw;
}   
EXPORT_SYMBOL_GPL(clk_hw_register_gate);
const struct clk_ops clk_gate_ops = {
    .enable = clk_gate_enable,
    .disable = clk_gate_disable,
    .is_enabled = clk_gate_is_enabled,
};
EXPORT_SYMBOL_GPL(clk_gate_ops);
static int clk_gate_enable(struct clk_hw *hw)
{   
    clk_gate_endisable(hw, 1);

    return 0;
}
 *
 * For disabling clock, enable = 0
 *  set2dis = 1 -> set bit  -> set = 1
 *  set2dis = 0 -> clear bit    -> set = 0
 *
 * So, result is always: enable xor set2dis.
 */
static void clk_gate_endisable(struct clk_hw *hw, int enable)
{
    struct clk_gate *gate = to_clk_gate(hw);
    int set = gate->flags & CLK_GATE_SET_TO_DISABLE ? 1 : 0;
    unsigned long uninitialized_var(flags);
    u32 reg;

    set ^= enable;

    if (gate->lock)
        spin_lock_irqsave(gate->lock, flags);
    else
        __acquire(gate->lock);

    if (gate->flags & CLK_GATE_HIWORD_MASK) {
        reg = BIT(gate->bit_idx + 16);
        if (set)
            reg |= BIT(gate->bit_idx);
    } else {
        reg = clk_readl(gate->reg);

        if (set)
            reg |= BIT(gate->bit_idx);
        else
            reg &= ~BIT(gate->bit_idx);
    }

    clk_writel(reg, gate->reg);

    if (gate->lock)
        spin_unlock_irqrestore(gate->lock, flags);
    else
        __release(gate->lock);
}

看下clk_register_clkdev(drivers/clk/clkdev.c)

/**         
 * clk_register_clkdev - register one clock lookup for a struct clk
 * @clk: struct clk to associate with all clk_lookups
 * @con_id: connection ID string on device
 * @dev_id: string describing device name
 *      
 * con_id or dev_id may be NULL as a wildcard, just as in the rest of
 * clkdev.  
 *                          
 * To make things easier for mass registration, we detect error clks
 * from a previous clk_register() call, and return the error code for
 * those.  This is to permit this function to be called immediately
 * after clk_register().
 */
int clk_register_clkdev(struct clk *clk, const char *con_id,
    const char *dev_id)
{
    struct clk_lookup *cl;

    if (IS_ERR(clk))
        return PTR_ERR(clk);

    /*
     * Since dev_id can be NULL, and NULL is handled specially, we must
     * pass it as either a NULL format string, or with "%s".
     */     
    if (dev_id)
        cl = __clk_register_clkdev(__clk_get_hw(clk), con_id, "%s", dev_id);
    else
        cl = __clk_register_clkdev(__clk_get_hw(clk), con_id, NULL);
    
    return cl ? 0 : -ENOMEM;
}           
EXPORT_SYMBOL(clk_register_clkdev);

static struct clk_lookup *__clk_register_clkdev(struct clk_hw *hw,
                        const char *con_id,
                        const char *dev_id, ...)
{               
    struct clk_lookup *cl;
    va_list ap;

    va_start(ap, dev_id);
    cl = vclkdev_create(hw, con_id, dev_id, ap);
    va_end(ap);
    
    return cl;
}
static struct clk_lookup *
vclkdev_create(struct clk_hw *hw, const char *con_id, const char *dev_fmt,
    va_list ap)
{
    struct clk_lookup *cl;

    cl = vclkdev_alloc(hw, con_id, dev_fmt, ap);
    if (cl)
        __clkdev_add(cl);

    return cl;
}
static void __clkdev_add(struct clk_lookup *cl)
{
    mutex_lock(&clocks_mutex);
    list_add_tail(&cl->node, &clocks);
    mutex_unlock(&clocks_mutex);
}
static LIST_HEAD(clocks); //clocks链表

所有的struct clk_lookup都会被添加到clocks链表中。

使用的时候:

info->clk = devm_clk_get(&pdev->dev, "nand");
static void devm_clk_release(struct device *dev, void *res)
{
    clk_put(*(struct clk **)res);
}
struct clk *devm_clk_get(struct device *dev, const char *id)
{
    struct clk **ptr, *clk;

    ptr = devres_alloc(devm_clk_release, sizeof(*ptr), GFP_KERNEL);
    if (!ptr)
        return ERR_PTR(-ENOMEM);

    clk = clk_get(dev, id);
    if (!IS_ERR(clk)) {
        *ptr = clk;
        devres_add(dev, ptr);
    } else {
        devres_free(ptr);
    }   

    return clk;
}
//drivers/clk/clkdev.c
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);
}
EXPORT_SYMBOL(clk_get);
/*
 * Find the correct struct clk for the device and connection ID.
 * We do slightly fuzzy matching here:
 *  An entry with a NULL ID is assumed to be a wildcard.
 *  If an entry has a device ID, it must match
 *  If an entry has a connection ID, it must match
 * Then we take the most specific entry - with the following
 * order of precedence: dev+con > dev only > con only.
 */
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) { //这里clocks
        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;
            else
                break;
        }
    }
    return cl;
}

这里的devm_clk_get和devm_clk_release实现机制很有意思。

在这里插入图片描述

XTIpll是clk最初的源,相当于OSC:

FRATE(XTI, "xti", NULL, 0, 0)

XTIpll经过OM[3:2]后分两路,一路MPLL器件,另一路是UPLL,因此由如下定义:

[mpll] = PLL(pll_s3c2440_mpll, MPLL, "mpll", "xti", LOCKTIME, MPLLCON, NULL),
[upll] = PLL(pll_s3c2410_upll, UPLL, "upll", "xti", LOCKTIME, UPLLCON, NULL),

因为我们的MPLL是我们关注的点,所以下面继续沿着MPLL分析,MPLL经过分频器后可以得到FCLK。
FCLK有两个时钟源,分别是MPLL和DIV_SLOW,可以通过时钟选择器选择时钟:

//PNAME(fclk_p) = { "mpll", "div_slow" };
MUX(FCLK, "fclk", fclk_p, CLKSLOW, 4, 1),

FCLK通过分频器后分别得到HCLK、PCLK:

//PNAME(hclk_p) = { "fclk", "div_hclk_2", "div_hclk_4", "div_hclk_3" }; //0, 1, 2, 3
MUX(HCLK, "hclk", hclk_p, CLKDIVN, 1, 2) //id, dev_name, cname, parent_name offset, shift, width

其中div_hclk_4是通过CLKDIVN分频后得到的,也就是HCLK=FCLK / 4:
在这里插入图片描述
问题:那么在哪里设置了CLKDIVN[2:1] = 2呢?

samsung_clk_register_mux(ctx, s3c244x_common_muxes, ARRAY_SIZE(s3c244x_common_muxes));
	struct clk *clk = clk_register_mux(NULL, list->name, list->parent_names,
            list->num_parents, list->flags,
            ctx->reg_base + list->offset,
            list->shift, list->width, list->mux_flags, &ctx->lock);
  		return clk_register_mux_table(dev, name, parent_names, num_parents,
                      flags, reg, shift, mask, clk_mux_flags, NULL, lock);
                      hw = clk_hw_register_mux_table(dev, name, parent_names, num_parents,
                       flags, reg, shift, mask, clk_mux_flags, table, lock);
                       //init.ops = &clk_mux_ops;
                       const struct clk_ops clk_mux_ops = {
						    .get_parent = clk_mux_get_parent,
						    .set_parent = clk_mux_set_parent,
						    .determine_rate = __clk_mux_determine_rate,
					   };
					   //static int clk_mux_set_parent(struct clk_hw *hw, u8 index) 这里的index表示的是hclk_p中的索引,即如果是2的话就是div_hclk_4,而且2就是10b
					   val |= index << mux->shift;
    				   clk_writel(val, mux->reg);

看下div_hclk_4的时钟设置,时钟源是fclk,还有一点看上图HCLK=FCLK / 4的条件是CAMDIVN[9]位要为0:

static struct clk_div_table div_hclk_4_d[] = {
    { .val = 0, .div = 4 }, 
    { .val = 1, .div = 8 },
    { /* sentinel */ },
};
DIV_T(0, "div_hclk_4", "fclk", CAMDIVN, 9, 1, div_hclk_4_d) // id, name, parent_name, offset, shift, width, table

这里是怎么实现当HCLK=FCLK / 4的时候设置CAMDIVN[9] = 0呢?看下面逻辑:

samsung_clk_register_div(ctx, s3c244x_common_dividers, ARRAY_SIZE(s3c244x_common_dividers));
	clk = clk_register_divider_table(NULL, list->name,
                list->parent_name, list->flags, //flags = 0
                ctx->reg_base + list->offset, //offset = CAMDIVN
                list->shift, list->width, list->div_flags, //div_flags = 0
                list->table, &ctx->lock);
                struct clk_hw *hw =  _register_divider(dev, name, parent_name, flags, reg, shift, width, clk_divider_flags, table, lock);
                	init.ops = &clk_divider_ops;
                		const struct clk_ops clk_divider_ops = {
						    .recalc_rate = clk_divider_recalc_rate,
						    .round_rate = clk_divider_round_rate,
						    .set_rate = clk_divider_set_rate,
						};
						//clk_divider_set_rate
						value = divider_get_val(rate, parent_rate, divider->table, divider->width, divider->flags);
							div = DIV_ROUND_UP_ULL((u64)parent_rate, rate); //parent_rate = 400M  rate = 100M, div = 4
							_get_table_val(table, div);
								if (clkt->div == div)  //4 = 4
									return clkt->val;  //val = 0

所以上面的{ .val = 0, .div = 4 }就是这个意义。

我们继续看下nand的时钟设置:

struct samsung_gate_clock s3c2410_common_gates[] __initdata = {
	//...
    GATE(HCLK_NAND, "nand", "hclk", CLKCON, 4, 0, 0), //id, name, parent_name, offset, bit_idx, flags, gate_flags
samsung_clk_register_gate(ctx, s3c2410_common_gates, ARRAY_SIZE(s3c2410_common_gates));
	clk = clk_register_gate(NULL, list->name, list->parent_name,
                list->flags, ctx->reg_base + list->offset,
                list->bit_idx, list->gate_flags, &ctx->lock);
                struct clk_hw *hw = clk_hw_register_gate(dev, name, parent_name, flags, reg, bit_idx, clk_gate_flags, lock);
                //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,
				};
				//static int clk_gate_enable(struct clk_hw *hw)
				clk_gate_endisable(hw, 1);
					reg = clk_readl(gate->reg);
			        if (set)
			            reg |= BIT(gate->bit_idx);
			        else
			            reg &= ~BIT(gate->bit_idx);
			        clk_writel(reg, gate->reg);

arch/arm/mach-s3c24xx/mach-mini2440.c

static void __init mini2440_init_time(void)
{
    s3c2440_init_clocks(12000000);
    +----s3c2410_common_clk_init(NULL, 12000000, 1, S3C24XX_VA_CLKPWR);
    +--------s3c2410_common_clk_register_fixed_ext(ctx, 12000000);
    +------------clk = clk_register_fixed_rate(NULL, list->name,list->parent_name, list->flags, list->fixed_rate); //fixed_rate=12000000
    samsung_timer_init();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/440272.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【网络编程】网络编程 和 Socket 套接字认识

✨个人主页&#xff1a;bit me&#x1f447; ✨当前专栏&#xff1a;Java EE初阶&#x1f447; 目 录 &#x1f3a7;一. 网络编程基础&#x1f3ba;1. 为什么需要网络编程&#xff1f;&#x1f3b7;2. 什么是网络编程&#x1f3b8;3. 网络编程中的基础概念 &#x1f3bf;二. So…

继续学习Easyx

画圆角矩形函数&#xff1a;roundrect&#xff0c;前四个参数是和矩形一样的&#xff0c;表示先画一个矩形&#xff0c;然后就是要画它的圆角了&#xff0c;要怎么画它的圆角&#xff1f;这里使用四个一样的椭圆来将它的四个角画出来&#xff0c;而在这个里面的椭圆不再需要四个…

MySQL8.0.33主从复制配置记录

目录 1. 下载2. 解压3.重命名4.创建存储数据文件5. 设置用户组并赋权6. 初始化MySQL7.配置文件8. 启动MySQL9. 设置软连接&#xff0c;并重启MySQL10. 登录并更改密码11. 开放远程连接12. 连接工具测试连接MySQL13. 开机自启配置14.从服务器配置15. 主库配置16. 从库配置17. 测…

功率MOS管烧毁,有这些原因

功率MOS管烧毁的原因以及相应的预防措施。在本文中&#xff0c;我将会介绍功率MOS管的基本结构、工作原理&#xff0c;以及可能导致功率MOS管烧毁的原因&#xff0c;并提供相应的解决方案。 1. 功率MOS管的基本结构和工作原理 功率MOS管是一种常用的功率电子器件&#xff0c;…

SpringBoot集成MyBatis-plus

SpringBoot集成MyBatis-plus 一、Mybatis-Plus简介1.简介2.特性3.官网及文档地址 二、入门案例1.开发环境2.创建数据库及表3.创建Springboot项目导入依赖4.配置application.yml5.启动类6.实体类7.添加mapper8.添加UserController9.日志配置 三、CURD1.BaseMapper2.通用Service …

事件主循环

一、事件主循环 1、事件处理主流程 libevent的事件循环主要是通过event_base_loop函数来完成&#xff0c;其主要的操作如下&#xff1a; 1、根据timer-heap中的事件最小超时时间&#xff0c;计算系统I/O demultiplexer的最大等待时间。例如&#xff1a;当底层使用的是Linux提供…

Android系统启动流程--system_server进程的启动流程

紧接上一篇zygote进程的启动流程&#xff0c;上一篇的结尾提到zygote进程中会fock出一个system_server进程&#xff0c;用于启动和管理Android系统中大部分的系统服务&#xff0c;本篇就来分析system_server进程是如何创建并运行的以及它都做了哪些重要的工作。 //文件路径&am…

Show, Attend, and Tell | a PyTorch Tutorial to Image Captioning代码调试(跑通)

Show, Attend, and Tell | a PyTorch Tutorial to Image Captioning代码调试&#xff08;跑通&#xff09; 文章目录 Show, Attend, and Tell | a PyTorch Tutorial to Image Captioning代码调试&#xff08;跑通&#xff09;前言1. 创建、安装所用的包1.1 创建环境&#xff0c…

【深度学习】OCR文本识别

OCR文字识别定义 OCR&#xff08;optical character recognition&#xff09;文字识别是指电子设备&#xff08;例如扫描仪或数码相机&#xff09;检查纸上打印的字符&#xff0c;然后用字符识别方法将形状翻译成计算机文字的过程&#xff1b;即&#xff0c;对文本资料进行扫描…

【数据结构】二叉树经典oj题

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a;初阶数据结构 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是对…

B. Make Them Equal(Codeforces Round 673 (Div. 1))

传送门 题意&#xff1a; 思路&#xff1a; 首先判断是否能够操作达到目的&#xff1a;即所有的数都相等。 不能达到有两种情况&#xff1a; 1&#xff1a;所有数之和对n取余不等于0 2: 每个ai都是小于i的&#xff0c;例如n5, a[]{0,1,2,3,4}。因为每个数都是小于 i 的&am…

idea中的 debug 中小功能按钮都代表的意思

1.step over 步过----->一行一行的往下走,如果这一行中有方法那么不会进入该方法,直接一行一行往下走,除非你在该方法中打入断点 2.step into 步入—>可以进入方法内部,但是只能进入自己写的方法内部,而不会进入方法的类库中 3.Force step into 强制步入---->可以步…

编译livox ros driver2(ROS2、livox、rviz、ubuntu22.04)

1. 编译Livox-SDK2 官方地址&#xff1a;https://github.com/Livox-SDK/Livox-SDK2 执行一下命令&#xff1a; git clone https://github.com/Livox-SDK/Livox-SDK2.git cd ./Livox-SDK2/ mkdir build cd build cmake .. && make sudo make install 如上就安装完成了…

嵌入式【CPLD】5M570ZT100C5N、5M1270ZF256C5N、5M2210ZF256C5N采用独特的非易失性架构,低成本应用设计。

英特尔MAX V CPLD 采用独特的非易失性架构&#xff0c;提供低功耗片上功能&#xff0c;适用于以边缘为中心的应用。MAX V CPLD系列能够在单位空间中提供大量 I/O 和逻辑。这些设备还使用了低成本绿色封装技术&#xff0c;封装大小只有 20 毫米。 MAX V系列的关键应用包括&…

PCL点云库(1) - 简介与数据类型

目录 1.1 简介 1.2 PCL安装 1.2.1 安装方法 1.2.2 测试程序 1.3 PCL数据类型 1.4 PCL中自定义point类型 1.4.1 增加自定义point的步骤 1.4.2 完整代码 1.1 简介 来源&#xff1a;PCL&#xff08;点云库&#xff09;_百度百科 PCL&#xff08;Point Cloud Library&…

个推打造消息推送专项运营提升方案,数据驱动APP触达效果升级

“数智化运营”能力已成为企业的核心竞争力之一。借助数据和算法&#xff0c;构建完善的数智化运营体系&#xff0c;企业可增强用户洞察和科学决策能力&#xff0c;提高日常运营效率和投入产出比。近半年&#xff0c;个推精准把握行业客户的切实需求&#xff0c;将“数智化运营…

分析型数据库:MPP 数据库的概念、技术架构与未来发展方向

随着企业数据量的增多&#xff0c;为了配合企业的业务分析、商业智能等应用场景&#xff0c;从而驱动数据化的商业决策&#xff0c;分析型数据库诞生了。由于数据分析一般涉及的数据量大&#xff0c;计算复杂&#xff0c;分析型数据库一般都是采用大规模并行计算或者分布式计算…

css的属性选择器

文章目录 属性选择器的原理简单的语法介绍子串值&#xff08;Substring value&#xff09;属性选择器 CSS 属性选择器的最基本用法复杂一点的用法层叠选择多条件复合选择伪正则写法配合 **:not()** 伪类重写行内样式 组合用法&#xff1a;搭配伪元素提升用户体验角标功能属性选…

基于51单片机的智能晾衣架的设计与实现(源码+论文)_kaic

【摘要】随着社会和市场的变化&#xff0c;我国经济的快速发展和房地产行业的快速扩张&#xff0c;使得装修家居行业飞速发展&#xff0c;在行业高速发展的同时&#xff0c;消费者家居智能化要求也在日益发展。随着科学技术的进步和电子技术的发展&#xff0c;单片机作为智能控…

Stable Diffusion一键安装器,只有2.3M

最近AI画图真的是太火了&#xff0c;但是Midjourney收费之后大家就不知道去哪里能用AI画图了&#xff0c; Stable Diffusion很多人听过&#xff0c;但是安装特别麻烦。所以为大家介绍一款软件&#xff0c;一键安装SD。 Stable Diffusion一键安装器_SD一键启动器-Stable Diffus…