从零开始之驱动发开、linux驱动(四十一、Linux中旧的时钟管理体系)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_16777851/article/details/84493290

老的时钟管理和新的时钟管理并无本质的差别。

这里先列出新的新的common clock framework和老的区别,后面再分析老的实现。

1.老的时钟框架没有区分各种时钟类型,新的框架把时钟分成了五个不同的时钟类型,固定频率的,分频的,开关类型的,多选一类型。

2.老的时钟框架需要厂商自己提供ops函数,新的时钟框架因为已经抽象出了5种时钟,所以这五种时钟的ops内核框架都已经抽象出来实现了。

3.老的时钟框架所有的时钟都是用同一个struct clksrc_clk(见下面)维护,所有的信息都是采用静态分配方式的clk,而新的框架分成五类时钟后,都是在注册时动态分配的clk。

4.老的时钟框架只维护了一个clock链表,而新的时钟框架因为抽象了时钟类型,采用树形结构有维护了一个链表。

5.老的时钟框架clk结构厂商自己定义,新的框架有内核定义。

下面这就是老的时钟框架对一个时钟资源的描述


/**
 * struct clksrc_clk - class of clock for newer style samsung devices.
 * @clk: the standard clock representation
 * @sources: the sources for this clock
 * @reg_src: the register definition for selecting the clock's source
 * @reg_div: the register definition for the clock's output divisor
 *
 * This clock implements the features required by the newer SoCs where
 * the standard clock block provides an input mux and a post-mux divisor
 * to provide the periperhal's clock.
 *
 * The array of @sources provides the mapping of mux position to the
 * clock, and @reg_src shows the code where to modify to change the mux
 * position. The @reg_div defines how to change the divider settings on
 * the output.
 */
struct clksrc_clk {
	struct clk		clk;
	struct clksrc_sources	*sources;

	struct clksrc_reg	reg_src;
	struct clksrc_reg	reg_div;
};



/**
 * struct clk_ops - standard clock operations
 * @set_rate: set the clock rate, see clk_set_rate().
 * @get_rate: get the clock rate, see clk_get_rate().
 * @round_rate: round a given clock rate, see clk_round_rate().
 * @set_parent: set the clock's parent, see clk_set_parent().
 *
 * Group the common clock implementations together so that we
 * don't have to keep setting the same fields again. We leave
 * enable in struct clk.
 *
 * Adding an extra layer of indirection into the process should
 * not be a problem as it is unlikely these operations are going
 * to need to be called quickly.
 */
struct clk_ops {
	int		    (*set_rate)(struct clk *c, unsigned long rate);
	unsigned long	    (*get_rate)(struct clk *c);
	unsigned long	    (*round_rate)(struct clk *c, unsigned long rate);
	int		    (*set_parent)(struct clk *c, struct clk *parent);
};

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
};

下面就对旧的时钟对时钟的注册使用。

s5pv210使用下面这个函数描述的。

/* 这个参数就是输入时钟,s5pv210的一般都是24M */

void __init s5pv210_init_clocks(int xtal)
{
	printk(KERN_DEBUG "%s: initializing clocks\n", __func__);

	s3c24xx_register_baseclocks(xtal);
	s5p_register_clocks(xtal);
	s5pv210_register_clocks();
	s5pv210_setup_clocks();
}

基本时钟初始化,早期的


/* initialise all the clocks */

int __init s3c24xx_register_baseclocks(unsigned long xtal)
{
	printk(KERN_INFO "S3C24XX Clocks, Copyright 2004 Simtec Electronics\n");

	clk_xtal.rate = xtal;            /* 初始化时钟 */

	/* register our clocks */

	if (s3c24xx_register_clock(&clk_xtal) < 0)
		printk(KERN_ERR "failed to register master xtal\n");

	if (s3c24xx_register_clock(&clk_mpll) < 0)
		printk(KERN_ERR "failed to register mpll clock\n");

	if (s3c24xx_register_clock(&clk_upll) < 0)
		printk(KERN_ERR "failed to register upll clock\n");

	if (s3c24xx_register_clock(&clk_f) < 0)
		printk(KERN_ERR "failed to register cpu fclk\n");

	if (s3c24xx_register_clock(&clk_h) < 0)
		printk(KERN_ERR "failed to register cpu hclk\n");

	if (s3c24xx_register_clock(&clk_p) < 0)
		printk(KERN_ERR "failed to register cpu pclk\n");

	return 0;
}




/* initialise the clock system */

/**
 * s3c24xx_register_clock() - register a clock
 * @clk: The clock to register
 *
 * Add the specified clock to the list of clocks known by the system.
 */
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;                    /* lookup和clk互相挂钩 */
	clkdev_add(&clk->lookup);                 /* 注册到clock链表上 */

	return 0;
}


void clkdev_add(struct clk_lookup *cl)
{
	mutex_lock(&clocks_mutex);
	list_add_tail(&cl->node, &clocks);        /* 最终注册到clock链表 */
	mutex_unlock(&clocks_mutex);
}


/* 固定时钟源 */
//时钟在上面入口初始化的
struct clk clk_xtal = {
	.name		= "xtal",
	.rate		= 0,
	.parent		= NULL,
	.ctrlbit	= 0,
};

struct clk clk_ext = {
	.name		= "ext",
};

struct clk clk_epll = {
	.name		= "epll",
};

/* 倍频 */
struct clk clk_mpll = {
	.name		= "mpll",
	.ops		= &clk_ops_def_setrate,
};

struct clk clk_upll = {
	.name		= "upll",
	.parent		= NULL,
	.ctrlbit	= 0,
};

struct clk clk_f = {
	.name		= "fclk",
	.rate		= 0,
	.parent		= &clk_mpll,
	.ctrlbit	= 0,
};

struct clk clk_h = {
	.name		= "hclk",
	.rate		= 0,
	.parent		= NULL,
	.ctrlbit	= 0,
	.ops		= &clk_ops_def_setrate,
};

struct clk clk_p = {
	.name		= "pclk",
	.rate		= 0,
	.parent		= NULL,
	.ctrlbit	= 0,
	.ops		= &clk_ops_def_setrate,
};


早期的函数都是使用的默认ops
/* base clocks */

int clk_default_setrate(struct clk *clk, unsigned long rate)
{
	clk->rate = rate;
	return 0;
}

struct clk_ops clk_ops_def_setrate = {
	.set_rate	= clk_default_setrate,
};

/* 注册s5p通用的时钟 */
static struct clk *s5p_clks[] __initdata = {
	&clk_ext_xtal_mux,
	&clk_48m,
	&s5p_clk_27m,
	&clk_fout_apll,
	&clk_fout_mpll,
	&clk_fout_epll,
	&clk_fout_dpll,
	&clk_fout_vpll,
	&clk_vpll,
	&clk_xusbxti,
};

void __init s5p_register_clocks(unsigned long xtal_freq)
{
	int ret;

	clk_ext_xtal_mux.rate = xtal_freq;        /* 初始化外部时钟24M */

    /* 注册s5p都有的 */
	ret = s3c24xx_register_clocks(s5p_clks, ARRAY_SIZE(s5p_clks));
	if (ret > 0)
		printk(KERN_ERR "Failed to register s5p clocks\n");
}


/* fin_apll, fin_mpll and fin_epll are all the same clock, which we call
 * clk_ext_xtal_mux.
*/
struct clk clk_ext_xtal_mux = {
	.name		= "ext_xtal",
	.id		= -1,
};

struct clk clk_xusbxti = {
	.name		= "xusbxti",
	.id		= -1,
	.rate		= 24000000,
};

struct clk s5p_clk_27m = {
	.name		= "clk_27m",
	.id		= -1,
	.rate		= 27000000,
};

/* 48MHz USB Phy clock output */
struct clk clk_48m = {
	.name		= "clk_48m",
	.id		= -1,
	.rate		= 48000000,
};

/* APLL clock output
 * No need .ctrlbit, this is always on
*/
struct clk clk_fout_apll = {
	.name		= "fout_apll",
	.id		= -1,
};

/* BPLL clock output */

struct clk clk_fout_bpll = {
	.name		= "fout_bpll",
	.id		= -1,
};

struct clk clk_fout_bpll_div2 = {
	.name		= "fout_bpll_div2",
	.id		= -1,
};

/* CPLL clock output */

struct clk clk_fout_cpll = {
	.name		= "fout_cpll",
	.id		= -1,
};

/* MPLL clock output
 * No need .ctrlbit, this is always on
*/
struct clk clk_fout_mpll = {
	.name		= "fout_mpll",
	.id		= -1,
};

struct clk clk_fout_mpll_div2 = {
	.name		= "fout_mpll_div2",
	.id		= -1,
};

/* EPLL clock output */
struct clk clk_fout_epll = {
	.name		= "fout_epll",
	.id		= -1,
	.ctrlbit	= (1 << 31),
};

/* DPLL clock output */
struct clk clk_fout_dpll = {
	.name		= "fout_dpll",
	.id		= -1,
	.ctrlbit	= (1 << 31),
};

/* VPLL clock output */
struct clk clk_fout_vpll = {
	.name		= "fout_vpll",
	.id		= -1,
	.ctrlbit	= (1 << 31),
};

void __init s5pv210_register_clocks(void)
{
	int ptr;

    /* 注册固定时钟 */
	s3c24xx_register_clocks(clks, ARRAY_SIZE(clks));

    /* 注册系统时钟源 */
	for (ptr = 0; ptr < ARRAY_SIZE(sysclks); ptr++)
		s3c_register_clksrc(sysclks[ptr], 1);

    /* 注册显示相关时钟源 */
	for (ptr = 0; ptr < ARRAY_SIZE(sclk_tv); ptr++)
		s3c_register_clksrc(sclk_tv[ptr], 1);

    /* 注册设备时钟源(uart mmc spi) */
	for (ptr = 0; ptr < ARRAY_SIZE(clksrc_cdev); ptr++)
		s3c_register_clksrc(clksrc_cdev[ptr], 1);

    /* 注册sclk下面的设备源 */
	s3c_register_clksrc(clksrcs, ARRAY_SIZE(clksrcs));
    /* 注册部分设备时钟 */
	s3c_register_clocks(init_clocks, ARRAY_SIZE(init_clocks));

    /* 注册部设备分时钟的开关 */
	s3c_register_clocks(init_clocks_off, ARRAY_SIZE(init_clocks_off));
    /* 默认关掉这些设备的时钟 */
	s3c_disable_clocks(init_clocks_off, ARRAY_SIZE(init_clocks_off));
    /* 加入到设备管理表中 */
	clkdev_add_table(s5pv210_clk_lookup, ARRAY_SIZE(s5pv210_clk_lookup));

    /* 注册部分设备源 */
	s3c24xx_register_clocks(clk_cdev, ARRAY_SIZE(clk_cdev));
    /* 关掉这些设备 */
	for (ptr = 0; ptr < ARRAY_SIZE(clk_cdev); ptr++)
		s3c_disable_clocks(clk_cdev[ptr], 1);

}

可以看到老的时钟子系统相当的简单,三星这边总共只用到了四个函数,下面一一分析。

1.s3c24xx_register_clocks,使用这个函数注册的都是单独的ops操作函数,或没有ops操作函数的一些集合


/**
 * s3c24xx_register_clocks() - register an array of clock pointers
 * @clks: Pointer to an array of struct clk pointers
 * @nr_clks: The number of clocks in the @clks array.
 *
 * Call s3c24xx_register_clock() for all the clock pointers contained
 * in the @clks list. Returns the number of failures.
 */
int s3c24xx_register_clocks(struct clk **clks, int nr_clks)
{
	int fails = 0;

    /* 注册多个clk */
	for (; nr_clks > 0; nr_clks--, clks++) {
		if (s3c24xx_register_clock(*clks) < 0) {
			struct clk *clk = *clks;
			printk(KERN_ERR "%s: failed to register %p: %s\n",
			       __func__, clk, clk->name);
			fails++;
		}
	}

	return fails;
}

第一类使用上面操作接口的就是一些固定时钟的,不能更改,也不能开关。

static struct clk *clks[] __initdata = {
	&clk_sclk_hdmi27m,
	&clk_sclk_hdmiphy,
	&clk_sclk_usbphy0,
	&clk_sclk_usbphy1,
	&clk_pcmcdclk0,
	&clk_pcmcdclk1,
	&clk_pcmcdclk2,
};



static struct clk clk_sclk_hdmi27m = {
	.name		= "sclk_hdmi27m",
	.rate		= 27000000,
};

static struct clk clk_sclk_hdmiphy = {
	.name		= "sclk_hdmiphy",
};

static struct clk clk_sclk_usbphy0 = {
	.name		= "sclk_usbphy0",
};

static struct clk clk_sclk_usbphy1 = {
	.name		= "sclk_usbphy1",
};

static struct clk clk_pcmcdclk0 = {
	.name		= "pcmcdclk",
};

static struct clk clk_pcmcdclk1 = {
	.name		= "pcmcdclk",
};

static struct clk clk_pcmcdclk2 = {
	.name		= "pcmcdclk",
};

第二类使用这种时钟的是,既可以开关,父时钟也已经记录


static struct clk init_clocks[] = {
	{
		.name		= "hclk_imem",
		.parent		= &clk_hclk_msys.clk,
		.ctrlbit	= (1 << 5),                  /* 时钟开关由第五位决定 */
		.enable		= s5pv210_clk_ip0_ctrl,      /* 提高了开关时钟函数 */
		.ops		= &clk_hclk_imem_ops,        /* 内存控制的提高了ops函数 */
	}, {
		.name		= "uart",
		.devname	= "s5pv210-uart.0",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip3_ctrl,      /* 提高了开关时钟函数 */
		.ctrlbit	= (1 << 17),                 /* 时钟开关由第17bit决定 */
	}, {
		.name		= "uart",
		.devname	= "s5pv210-uart.1",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1 << 18),
	}, {
		.name		= "uart",
		.devname	= "s5pv210-uart.2",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1 << 19),
	}, {
		.name		= "uart",
		.devname	= "s5pv210-uart.3",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1 << 20),
	}, {
		.name		= "sromc",
		.parent		= &clk_hclk_psys.clk,
		.enable		= s5pv210_clk_ip1_ctrl,
		.ctrlbit	= (1 << 26),
	},
};

上面的是类似的,所以我就以imem和uart的分析一下


/* 父子时钟关系固定,不能改变,不向新的多选一开关方便 */
static struct clksrc_clk clk_armclk = {
	.clk	= {
		.name		= "armclk",
	},
	.sources	= &clkset_armclk,
	.reg_src	= { .reg = S5P_CLK_SRC0, .shift = 16, .size = 1 },
	.reg_div	= { .reg = S5P_CLK_DIV0, .shift = 0, .size = 3 },
};

static struct clksrc_clk clk_hclk_msys = {
	.clk	= {
		.name		= "hclk_msys",            
		.parent		= &clk_armclk.clk,        /* 父时钟是armclk */
	},
    /* 分屏寄存器固定映射好的虚拟地址 */
	.reg_div	= { .reg = S5P_CLK_DIV0, .shift = 8, .size = 3 },
};

static struct clksrc_clk clk_pclk_msys = {
	.clk	= {
		.name		= "pclk_msys",
		.parent		= &clk_hclk_msys.clk,    /* 父时钟是msys的hclk */
	},
	.reg_div        = { .reg = S5P_CLK_DIV0, .shift = 12, .size = 3 },
};

上面用到的寄存器是内核启动期间静态映射好的,可以直接使用的虚拟地址



static struct clk_ops clk_hclk_imem_ops = {
	.get_rate	= s5pv210_clk_imem_get_rate,
};
static unsigned long s5pv210_clk_imem_get_rate(struct clk *clk)
{
    /* imem提供了时钟获取的ops接口,为parent的一般 */
	return clk_get_rate(clk->parent) / 2;
}




/* imem的时钟开关函数 */
static int s5pv210_clk_ip0_ctrl(struct clk *clk, int enable)
{
    /* 这里的寄存器是已经映射好的虚拟地址 */
	return s5p_gatectrl(S5P_CLKGATE_IP0, clk, enable);
}

/* 串口的时钟开关函数 */
static int s5pv210_clk_ip3_ctrl(struct clk *clk, int enable)
{
	return s5p_gatectrl(S5P_CLKGATE_IP3, clk, enable);
}

int s5p_gatectrl(void __iomem *reg, struct clk *clk, int enable)
{
	unsigned int ctrlbit = clk->ctrlbit;      /* 控制位 */
	u32 con;

    /* 读改写,来根据enable传的真假值来确定ctrlbit控制的那个时钟的使能 */
	con = __raw_readl(reg);
	con = enable ? (con | ctrlbit) : (con & ~ctrlbit);
	__raw_writel(con, reg);
	return 0;
}


/* 其它固定类型设备的时钟的开关如下,和上完全一样,就不分析了 */


static struct clk init_clocks_off[] = {
	{
		.name		= "rot",
		.parent		= &clk_hclk_dsys.clk,
		.enable		= s5pv210_clk_ip0_ctrl,
		.ctrlbit	= (1<<29),
	}, {
		.name		= "fimc",
		.devname	= "s5pv210-fimc.0",
		.parent		= &clk_hclk_dsys.clk,
		.enable		= s5pv210_clk_ip0_ctrl,
		.ctrlbit	= (1 << 24),
	}, {
		.name		= "fimc",
		.devname	= "s5pv210-fimc.1",
		.parent		= &clk_hclk_dsys.clk,
		.enable		= s5pv210_clk_ip0_ctrl,
		.ctrlbit	= (1 << 25),
	}, {
		.name		= "fimc",
		.devname	= "s5pv210-fimc.2",
		.parent		= &clk_hclk_dsys.clk,
		.enable		= s5pv210_clk_ip0_ctrl,
		.ctrlbit	= (1 << 26),
	}, {
		.name		= "jpeg",
		.parent		= &clk_hclk_dsys.clk,
		.enable		= s5pv210_clk_ip0_ctrl,
		.ctrlbit	= (1 << 28),
	}, {
		.name		= "mfc",
		.devname	= "s5p-mfc",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip0_ctrl,
		.ctrlbit	= (1 << 16),
	}, {
		.name		= "dac",
		.devname	= "s5p-sdo",
		.parent		= &clk_hclk_dsys.clk,
		.enable		= s5pv210_clk_ip1_ctrl,
		.ctrlbit	= (1 << 10),
	}, {
		.name		= "mixer",
		.devname	= "s5p-mixer",
		.parent		= &clk_hclk_dsys.clk,
		.enable		= s5pv210_clk_ip1_ctrl,
		.ctrlbit	= (1 << 9),
	}, {
		.name		= "vp",
		.devname	= "s5p-mixer",
		.parent		= &clk_hclk_dsys.clk,
		.enable		= s5pv210_clk_ip1_ctrl,
		.ctrlbit	= (1 << 8),
	}, {
		.name		= "hdmi",
		.devname	= "s5pv210-hdmi",
		.parent		= &clk_hclk_dsys.clk,
		.enable		= s5pv210_clk_ip1_ctrl,
		.ctrlbit	= (1 << 11),
	}, {
		.name		= "hdmiphy",
		.devname	= "s5pv210-hdmi",
		.enable		= s5pv210_clk_hdmiphy_ctrl,
		.ctrlbit	= (1 << 0),
	}, {
		.name		= "dacphy",
		.devname	= "s5p-sdo",
		.enable		= exynos4_clk_dac_ctrl,
		.ctrlbit	= (1 << 0),
	}, {
		.name		= "otg",
		.parent		= &clk_hclk_psys.clk,
		.enable		= s5pv210_clk_ip1_ctrl,
		.ctrlbit	= (1<<16),
	}, {
		.name		= "usb-host",
		.parent		= &clk_hclk_psys.clk,
		.enable		= s5pv210_clk_ip1_ctrl,
		.ctrlbit	= (1<<17),
	}, {
		.name		= "lcd",
		.parent		= &clk_hclk_dsys.clk,
		.enable		= s5pv210_clk_ip1_ctrl,
		.ctrlbit	= (1<<0),
	}, {
		.name		= "cfcon",
		.parent		= &clk_hclk_psys.clk,
		.enable		= s5pv210_clk_ip1_ctrl,
		.ctrlbit	= (1<<25),
	}, {
		.name		= "systimer",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1<<16),
	}, {
		.name		= "watchdog",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1<<22),
	}, {
		.name		= "rtc",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1<<15),
	}, {
		.name		= "i2c",
		.devname	= "s3c2440-i2c.0",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1<<7),
	}, {
		.name		= "i2c",
		.devname	= "s3c2440-i2c.1",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1 << 10),
	}, {
		.name		= "i2c",
		.devname	= "s3c2440-i2c.2",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1<<9),
	}, {
		.name		= "i2c",
		.devname	= "s3c2440-hdmiphy-i2c",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1 << 11),
	}, {
		.name		= "spi",
		.devname	= "s5pv210-spi.0",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1<<12),
	}, {
		.name		= "spi",
		.devname	= "s5pv210-spi.1",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1<<13),
	}, {
		.name		= "spi",
		.devname	= "s5pv210-spi.2",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1<<14),
	}, {
		.name		= "timers",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1<<23),
	}, {
		.name		= "adc",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1<<24),
	}, {
		.name		= "keypad",
		.parent		= &clk_pclk_psys.clk,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1<<21),
	}, {
		.name		= "iis",
		.devname	= "samsung-i2s.0",
		.parent		= &clk_p,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1<<4),
	}, {
		.name		= "iis",
		.devname	= "samsung-i2s.1",
		.parent		= &clk_p,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1 << 5),
	}, {
		.name		= "iis",
		.devname	= "samsung-i2s.2",
		.parent		= &clk_p,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1 << 6),
	}, {
		.name		= "spdif",
		.parent		= &clk_p,
		.enable		= s5pv210_clk_ip3_ctrl,
		.ctrlbit	= (1 << 0),
	},
};




static struct clk *clk_cdev[] = {
	&clk_hsmmc0,
	&clk_hsmmc1,
	&clk_hsmmc2,
	&clk_hsmmc3,
	&clk_pdma0,
	&clk_pdma1,
};

/* 提供开关时钟函数,提供开关位,提供父时钟 */
static struct clk clk_hsmmc0 = {
	.name		= "hsmmc",
	.devname	= "s3c-sdhci.0",
	.parent		= &clk_hclk_psys.clk,
	.enable		= s5pv210_clk_ip2_ctrl,
	.ctrlbit	= (1<<16),
};

static struct clk clk_hsmmc1 = {
	.name		= "hsmmc",
	.devname	= "s3c-sdhci.1",
	.parent		= &clk_hclk_psys.clk,
	.enable		= s5pv210_clk_ip2_ctrl,
	.ctrlbit	= (1<<17),
};

static struct clk clk_hsmmc2 = {
	.name		= "hsmmc",
	.devname	= "s3c-sdhci.2",
	.parent		= &clk_hclk_psys.clk,
	.enable		= s5pv210_clk_ip2_ctrl,
	.ctrlbit	= (1<<18),
};

static struct clk clk_hsmmc3 = {
	.name		= "hsmmc",
	.devname	= "s3c-sdhci.3",
	.parent		= &clk_hclk_psys.clk,
	.enable		= s5pv210_clk_ip2_ctrl,
	.ctrlbit	= (1<<19),
};

static struct clk clk_pdma0 = {
	.name		= "pdma0",
	.parent		= &clk_hclk_psys.clk,
	.enable		= s5pv210_clk_ip0_ctrl,
	.ctrlbit	= (1 << 3),
};

static struct clk clk_pdma1 = {
	.name		= "pdma1",
	.parent		= &clk_hclk_psys.clk,
	.enable		= s5pv210_clk_ip0_ctrl,
	.ctrlbit	= (1 << 4),
};



当然上面的开关类时钟中,大部分设备的时钟内核启动时就关掉了,调用的是clk自己提供的开关函数,
/**
 * s3c_disable_clocks() - disable an array of clocks
 * @clkp: Pointer to the first clock in the array.
 * @nr_clks: Number of clocks to register.
 *
 * for internal use only at initialisation time. disable the clocks in the
 * @clkp array.
 */

void __init s3c_disable_clocks(struct clk *clkp, int nr_clks)
{
	for (; nr_clks > 0; nr_clks--, clkp++)
		(clkp->enable)(clkp, 0);    /* 第二个参数为0,则关时钟 */
}

开始和资源有关系的时钟之前,先看一下,怎么描述这些资源时钟,多选一,分配等

/**
 * struct clksrc_sources - list of sources for a given clock
 * @sources: array of pointers to clocks
 * @nr_sources: The size of @sources
 */
struct clksrc_sources {
	unsigned int	nr_sources;
	struct clk	**sources;
};

/**
 * struct clksrc_reg - register definition for clock control bits
 * @reg: pointer to the register in virtual memory.
 * @shift: the shift in bits to where the bitfield is.
 * @size: the size in bits of the bitfield.
 *
 * This specifies the size and position of the bits we are interested
 * in within the register specified by @reg.
 */
struct clksrc_reg {
	void __iomem		*reg;        /* 资源寄存器 */
	unsigned short		shift;       /* 资源在这个寄存器的第几位开始控制 */
	unsigned short		size;        /* 资源由几位管理 */
};


/**
 * struct clksrc_clk - class of clock for newer style samsung devices.
 * @clk: the standard clock representation
 * @sources: the sources for this clock
 * @reg_src: the register definition for selecting the clock's source
 * @reg_div: the register definition for the clock's output divisor
 *
 * This clock implements the features required by the newer SoCs where
 * the standard clock block provides an input mux and a post-mux divisor
 * to provide the periperhal's clock.
 *
 * The array of @sources provides the mapping of mux position to the
 * clock, and @reg_src shows the code where to modify to change the mux
 * position. The @reg_div defines how to change the divider settings on
 * the output.
 */
struct clksrc_clk {
	struct clk		clk;
	struct clksrc_sources	*sources;    /* 多选一使用可选的所有可能 */

	struct clksrc_reg	reg_src;        /* 多选一 */
	struct clksrc_reg	reg_div;        /* 分频 */
};
/* 这类资源控制,比如分频,倍频,多路切换的都需要配置,所以都要有ops函数的 */
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 */

        /* 如果clk没设置ops接口,则由下面几个选择 */
		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);

        /* 把该clk绑定到clock链表上 */
		ret = s3c24xx_register_clock(&clksrc->clk);

		if (ret < 0) {
			printk(KERN_ERR "%s: failed to register %s (%d)\n",
			       __func__, clksrc->clk.name, ret);
		}
	}
}


/* 下面就是上面的三个操作集合,其实总共就4个函数,下面我们一一分析 */
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,
};

static struct clk_ops clksrc_ops_nodiv = {
	.set_parent	= s3c_setparent_clksrc,
};

static struct clk_ops clksrc_ops_nosrc = {
	.get_rate	= s3c_getrate_clksrc,
	.set_rate	= s3c_setrate_clksrc,
	.round_rate	= s3c_roundrate_clksrc,
};

多选一时钟,跟换其父时钟操作


static int s3c_setparent_clksrc(struct clk *clk, struct clk *parent)
{
	struct clksrc_clk *sclk = to_clksrc(clk);
	struct clksrc_sources *srcs = sclk->sources;
	u32 clksrc = __raw_readl(sclk->reg_src.reg);
	u32 mask = bit_mask(sclk->reg_src.shift, sclk->reg_src.size);
	int src_nr = -1;
	int ptr;

    /* 在clk的时钟源中找到使用的parent */
	for (ptr = 0; ptr < srcs->nr_sources; ptr++)
		if (srcs->sources[ptr] == parent) {
			src_nr = ptr;
			break;
		}

    /* 跟换clk的parent,并设置(寄存器)跟换时钟源 */
	if (src_nr >= 0) {
		clk->parent = parent;

		clksrc &= ~mask;
		clksrc |= src_nr << sclk->reg_src.shift;

		__raw_writel(clksrc, sclk->reg_src.reg);
		return 0;
	}

	return -EINVAL;
}

获取时钟频率

static unsigned long s3c_getrate_clksrc(struct clk *clk)
{
	struct clksrc_clk *sclk = to_clksrc(clk);
	unsigned long rate = clk_get_rate(clk->parent);    /* 获取父时钟频率 */
	u32 clkdiv = __raw_readl(sclk->reg_div.reg);       /* 获得父时钟对该时钟的分频值 */
	u32 mask = bit_mask(sclk->reg_div.shift, sclk->reg_div.size);

    /* 分频系数 */
	clkdiv &= mask;
	clkdiv >>= sclk->reg_div.shift;
	clkdiv++;

    /* 得到该时钟的频率 */
	rate /= clkdiv;
	return rate;
}

设置时钟频率

/* 如果有计算频率的函数,则计算设置后的精确值 */
long clk_round_rate(struct clk *clk, unsigned long rate)
{
    /* 这种计算的好处是,父时钟频率是25M,如果子时钟频率要20M
     * 父时钟没法分频得到20M时钟,对多分频得到12.5M,这个12.5就是父时钟计算的
     * 最接近并且小于20M的频率
     */
	if (!IS_ERR_OR_NULL(clk) && clk->ops && clk->ops->round_rate)
		return (clk->ops->round_rate)(clk, rate);

	return 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;
}

计算得到最满足要求的频率


static unsigned long s3c_roundrate_clksrc(struct clk *clk,
					      unsigned long rate)
{
	struct clksrc_clk *sclk = to_clksrc(clk);
	unsigned long parent_rate = clk_get_rate(clk->parent);
	int max_div = 1 << sclk->reg_div.size;
	int div;

    /* 子时钟频率不能大于父时钟频率 */
	if (rate >= parent_rate)
		rate = parent_rate;
	else {
		div = parent_rate / rate;    /* 得到小于设定的分频时钟最接近的时钟 */
		if (parent_rate % rate)
			div++;

		if (div == 0)
			div = 1;
		if (div > max_div)    /* 分频值不能超过分频上限 */
			div = max_div;

		rate = parent_rate / div;    /* 最终计算的最合适的值 */
	}

	return rate;
}

根据寄存器的默认值,确定当前的父时钟是那个,并绑定到clk上


/* Clock initialisation code */

void __init_or_cpufreq s3c_set_clksrc(struct clksrc_clk *clk, bool announce)
{
	struct clksrc_sources *srcs = clk->sources;
	u32 mask = bit_mask(clk->reg_src.shift, clk->reg_src.size);
	u32 clksrc;

    /* 错误检查 */
	if (!clk->reg_src.reg) {
		if (!clk->clk.parent)
			printk(KERN_ERR "%s: no parent clock specified\n",
				clk->clk.name);
		return;
	}

    /* 得到时钟寄存器数据 */
	clksrc = __raw_readl(clk->reg_src.reg);
	clksrc &= mask;
    /* 获取时钟源设定值 */
	clksrc >>= clk->reg_src.shift;

    /* 检查父时钟的个数 */
	if (clksrc > srcs->nr_sources || !srcs->sources[clksrc]) {
		printk(KERN_ERR "%s: bad source %d\n",
		       clk->clk.name, clksrc);
		return;
	}

    /* 绑定父时钟 */
	clk->clk.parent = srcs->sources[clksrc];

	if (announce)
		printk(KERN_INFO "%s: source is %s (%d), rate is %ld\n",
		       clk->clk.name, clk->clk.parent->name, clksrc,
		       clk_get_rate(&clk->clk));
}

上面就是多选一和分频类时钟的选择的一些通用的注册接口。

下面就看有哪些时钟用到了上面的。


/* Clock initialisation code */
static struct clksrc_clk *sysclks[] = {
	&clk_mout_apll,
	&clk_mout_epll,
	&clk_mout_mpll,
	&clk_armclk,
	&clk_hclk_msys,
	&clk_sclk_a2m,
	&clk_hclk_dsys,
	&clk_hclk_psys,
	&clk_pclk_msys,
	&clk_pclk_dsys,
	&clk_pclk_psys,
	&clk_vpllsrc,
	&clk_sclk_vpll,
	&clk_mout_dmc0,
	&clk_sclk_dmc0,
	&clk_sclk_audio0,
	&clk_sclk_audio1,
	&clk_sclk_audio2,
	&clk_sclk_spdif,
};



static struct clksrc_clk clk_mout_apll = {
	.clk	= {
		.name		= "mout_apll",
	},
	.sources	= &clk_src_apll,
	.reg_src	= { .reg = S5P_CLK_SRC0, .shift = 0, .size = 1 },
};

static struct clksrc_clk clk_mout_epll = {
	.clk	= {
		.name		= "mout_epll",
	},
	.sources	= &clk_src_epll,
	.reg_src	= { .reg = S5P_CLK_SRC0, .shift = 8, .size = 1 },
};

static struct clksrc_clk clk_mout_mpll = {
	.clk = {
		.name		= "mout_mpll",
	},
	.sources	= &clk_src_mpll,
	.reg_src	= { .reg = S5P_CLK_SRC0, .shift = 4, .size = 1 },
};

static struct clk *clkset_armclk_list[] = {
	[0] = &clk_mout_apll.clk,
	[1] = &clk_mout_mpll.clk,
};

static struct clksrc_sources clkset_armclk = {
	.sources	= clkset_armclk_list,
	.nr_sources	= ARRAY_SIZE(clkset_armclk_list),
};

static struct clksrc_clk clk_armclk = {
	.clk	= {
		.name		= "armclk",
	},
	.sources	= &clkset_armclk,
	.reg_src	= { .reg = S5P_CLK_SRC0, .shift = 16, .size = 1 },
	.reg_div	= { .reg = S5P_CLK_DIV0, .shift = 0, .size = 3 },
};

static struct clksrc_clk clk_hclk_msys = {
	.clk	= {
		.name		= "hclk_msys",
		.parent		= &clk_armclk.clk,
	},
	.reg_div	= { .reg = S5P_CLK_DIV0, .shift = 8, .size = 3 },
};

static struct clksrc_clk clk_pclk_msys = {
	.clk	= {
		.name		= "pclk_msys",
		.parent		= &clk_hclk_msys.clk,
	},
	.reg_div        = { .reg = S5P_CLK_DIV0, .shift = 12, .size = 3 },
};

static struct clksrc_clk clk_sclk_a2m = {
	.clk	= {
		.name		= "sclk_a2m",
		.parent		= &clk_mout_apll.clk,
	},
	.reg_div	= { .reg = S5P_CLK_DIV0, .shift = 4, .size = 3 },
};

static struct clk *clkset_hclk_sys_list[] = {
	[0] = &clk_mout_mpll.clk,
	[1] = &clk_sclk_a2m.clk,
};

static struct clksrc_sources clkset_hclk_sys = {
	.sources	= clkset_hclk_sys_list,
	.nr_sources	= ARRAY_SIZE(clkset_hclk_sys_list),
};

static struct clksrc_clk clk_hclk_dsys = {
	.clk	= {
		.name	= "hclk_dsys",
	},
	.sources	= &clkset_hclk_sys,
	.reg_src        = { .reg = S5P_CLK_SRC0, .shift = 20, .size = 1 },
	.reg_div        = { .reg = S5P_CLK_DIV0, .shift = 16, .size = 4 },
};

static struct clksrc_clk clk_pclk_dsys = {
	.clk	= {
		.name	= "pclk_dsys",
		.parent	= &clk_hclk_dsys.clk,
	},
	.reg_div = { .reg = S5P_CLK_DIV0, .shift = 20, .size = 3 },
};

static struct clksrc_clk clk_hclk_psys = {
	.clk	= {
		.name	= "hclk_psys",
	},
	.sources	= &clkset_hclk_sys,
	.reg_src        = { .reg = S5P_CLK_SRC0, .shift = 24, .size = 1 },
	.reg_div        = { .reg = S5P_CLK_DIV0, .shift = 24, .size = 4 },
};

static struct clksrc_clk clk_pclk_psys = {
	.clk	= {
		.name	= "pclk_psys",
		.parent	= &clk_hclk_psys.clk,
	},
	.reg_div        = { .reg = S5P_CLK_DIV0, .shift = 28, .size = 3 },
};

可以看到这些都是分配或者多选一的。

最后就是倍频的pll类型的时钟了,最后面设置。

这个只针对单独的处理器,单独设置


void __init_or_cpufreq s5pv210_setup_clocks(void)
{
	struct clk *xtal_clk;
	unsigned long vpllsrc;
	unsigned long armclk;
	unsigned long hclk_msys;
	unsigned long hclk_dsys;
	unsigned long hclk_psys;
	unsigned long pclk_msys;
	unsigned long pclk_dsys;
	unsigned long pclk_psys;
	unsigned long apll;
	unsigned long mpll;
	unsigned long epll;
	unsigned long vpll;
	unsigned int ptr;
	u32 clkdiv0, clkdiv1;

	/* Set functions for clk_fout_epll */
    /* pll相关的ops绑定已经注册到mux的source上 */
	clk_fout_epll.enable = s5p_epll_enable;
	clk_fout_epll.ops = &s5pv210_epll_ops;

	printk(KERN_DEBUG "%s: registering clocks\n", __func__);

    /* 获取bootloader启动后寄存器设置的分频  */
	clkdiv0 = __raw_readl(S5P_CLK_DIV0);
	clkdiv1 = __raw_readl(S5P_CLK_DIV1);

	printk(KERN_DEBUG "%s: clkdiv0 = %08x, clkdiv1 = %08x\n",
				__func__, clkdiv0, clkdiv1);

    /* 打印基本输入时钟 */
	xtal_clk = clk_get(NULL, "xtal");
	BUG_ON(IS_ERR(xtal_clk));

    /* 得到基本时钟输入频率 */
	xtal = clk_get_rate(xtal_clk);
	clk_put(xtal_clk);

	printk(KERN_DEBUG "%s: xtal is %ld\n", __func__, xtal);

    /* 计算倍频后额值 */
	apll = s5p_get_pll45xx(xtal, __raw_readl(S5P_APLL_CON), pll_4508);
	mpll = s5p_get_pll45xx(xtal, __raw_readl(S5P_MPLL_CON), pll_4502);
	epll = s5p_get_pll46xx(xtal, __raw_readl(S5P_EPLL_CON),
				__raw_readl(S5P_EPLL_CON1), pll_4600);
	vpllsrc = clk_get_rate(&clk_vpllsrc.clk);
	vpll = s5p_get_pll45xx(vpllsrc, __raw_readl(S5P_VPLL_CON), pll_4502);

    /* 绑定操作函数和倍频后的输出速率 */
	clk_fout_apll.ops = &clk_fout_apll_ops;
	clk_fout_mpll.rate = mpll;
	clk_fout_epll.rate = epll;
	clk_fout_vpll.ops = &s5pv210_vpll_ops;
	clk_fout_vpll.rate = vpll;

	printk(KERN_INFO "S5PV210: PLL settings, A=%ld, M=%ld, E=%ld V=%ld",
			apll, mpll, epll, vpll);

    /* pll后的时钟速率已经得到,其后面的各个sys中的时钟也就可以直接计算得到了 */
	armclk = clk_get_rate(&clk_armclk.clk);
	hclk_msys = clk_get_rate(&clk_hclk_msys.clk);
	hclk_dsys = clk_get_rate(&clk_hclk_dsys.clk);
	hclk_psys = clk_get_rate(&clk_hclk_psys.clk);
	pclk_msys = clk_get_rate(&clk_pclk_msys.clk);
	pclk_dsys = clk_get_rate(&clk_pclk_dsys.clk);
	pclk_psys = clk_get_rate(&clk_pclk_psys.clk);

    /* 打印出相关时钟 */
	printk(KERN_INFO "S5PV210: ARMCLK=%ld, HCLKM=%ld, HCLKD=%ld\n"
			 "HCLKP=%ld, PCLKM=%ld, PCLKD=%ld, PCLKP=%ld\n",
			armclk, hclk_msys, hclk_dsys, hclk_psys,
			pclk_msys, pclk_dsys, pclk_psys);

    /* 跟新线面注册的clk的时钟 */
	clk_f.rate = armclk;
	clk_h.rate = hclk_psys;
	clk_p.rate = pclk_psys;

    /* 跟新并打印clksrcs数组中的各个模块时钟 */
	for (ptr = 0; ptr < ARRAY_SIZE(clksrcs); ptr++)
		s3c_set_clksrc(&clksrcs[ptr], true);
}

下面就是内核启动过程中打印的相关信息。上面都已经见到了。

分析的第一个函数中打印的。

最后一个函数中打印的各PLL后输出的值和各sys域的值。

最后一个函数因为参数announce传的是true而打印出的各时钟模块的值。


/* Clock initialisation code */

void __init_or_cpufreq s3c_set_clksrc(struct clksrc_clk *clk, bool announce)
{
	struct clksrc_sources *srcs = clk->sources;
	u32 mask = bit_mask(clk->reg_src.shift, clk->reg_src.size);
	u32 clksrc;

	if (!clk->reg_src.reg) {
		if (!clk->clk.parent)
			printk(KERN_ERR "%s: no parent clock specified\n",
				clk->clk.name);
		return;
	}

	clksrc = __raw_readl(clk->reg_src.reg);
	clksrc &= mask;
	clksrc >>= clk->reg_src.shift;

	if (clksrc > srcs->nr_sources || !srcs->sources[clksrc]) {
		printk(KERN_ERR "%s: bad source %d\n",
		       clk->clk.name, clksrc);
		return;
	}

	clk->clk.parent = srcs->sources[clksrc];

	if (announce)
		printk(KERN_INFO "%s: source is %s (%d), rate is %ld\n",
		       clk->clk.name, clk->clk.parent->name, clksrc,
		       clk_get_rate(&clk->clk));
}



static struct clksrc_clk clksrcs[] = {
	{
		.clk	= {
			.name		= "sclk_dmc",
		},
		.sources = &clkset_group1,
		.reg_src = { .reg = S5P_CLK_SRC6, .shift = 24, .size = 2 },
		.reg_div = { .reg = S5P_CLK_DIV6, .shift = 28, .size = 4 },
	}, {
		.clk	= {
			.name		= "sclk_onenand",
		},
		.sources = &clkset_sclk_onenand,
		.reg_src = { .reg = S5P_CLK_SRC0, .shift = 28, .size = 1 },
		.reg_div = { .reg = S5P_CLK_DIV6, .shift = 12, .size = 3 },
	}, {
		.clk	= {
			.name		= "sclk_fimc",
			.devname	= "s5pv210-fimc.0",
			.enable		= s5pv210_clk_mask1_ctrl,
			.ctrlbit	= (1 << 2),
		},
		.sources = &clkset_group2,
		.reg_src = { .reg = S5P_CLK_SRC3, .shift = 12, .size = 4 },
		.reg_div = { .reg = S5P_CLK_DIV3, .shift = 12, .size = 4 },
	}, {
		.clk	= {
			.name		= "sclk_fimc",
			.devname	= "s5pv210-fimc.1",
			.enable		= s5pv210_clk_mask1_ctrl,
			.ctrlbit	= (1 << 3),
		},
		.sources = &clkset_group2,
		.reg_src = { .reg = S5P_CLK_SRC3, .shift = 16, .size = 4 },
		.reg_div = { .reg = S5P_CLK_DIV3, .shift = 16, .size = 4 },
	}, {
		.clk	= {
			.name		= "sclk_fimc",
			.devname	= "s5pv210-fimc.2",
			.enable		= s5pv210_clk_mask1_ctrl,
			.ctrlbit	= (1 << 4),
		},
		.sources = &clkset_group2,
		.reg_src = { .reg = S5P_CLK_SRC3, .shift = 20, .size = 4 },
		.reg_div = { .reg = S5P_CLK_DIV3, .shift = 20, .size = 4 },
	}, {
		.clk		= {
			.name		= "sclk_cam0",
			.enable		= s5pv210_clk_mask0_ctrl,
			.ctrlbit	= (1 << 3),
		},
		.sources = &clkset_group2,
		.reg_src = { .reg = S5P_CLK_SRC1, .shift = 12, .size = 4 },
		.reg_div = { .reg = S5P_CLK_DIV1, .shift = 12, .size = 4 },
	}, {
		.clk		= {
			.name		= "sclk_cam1",
			.enable		= s5pv210_clk_mask0_ctrl,
			.ctrlbit	= (1 << 4),
		},
		.sources = &clkset_group2,
		.reg_src = { .reg = S5P_CLK_SRC1, .shift = 16, .size = 4 },
		.reg_div = { .reg = S5P_CLK_DIV1, .shift = 16, .size = 4 },
	}, {
		.clk		= {
			.name		= "sclk_fimd",
			.enable		= s5pv210_clk_mask0_ctrl,
			.ctrlbit	= (1 << 5),
		},
		.sources = &clkset_group2,
		.reg_src = { .reg = S5P_CLK_SRC1, .shift = 20, .size = 4 },
		.reg_div = { .reg = S5P_CLK_DIV1, .shift = 20, .size = 4 },
	}, {
		.clk		= {
			.name		= "sclk_mfc",
			.devname	= "s5p-mfc",
			.enable		= s5pv210_clk_ip0_ctrl,
			.ctrlbit	= (1 << 16),
		},
		.sources = &clkset_group1,
		.reg_src = { .reg = S5P_CLK_SRC2, .shift = 4, .size = 2 },
		.reg_div = { .reg = S5P_CLK_DIV2, .shift = 4, .size = 4 },
	}, {
		.clk		= {
			.name		= "sclk_g2d",
			.enable		= s5pv210_clk_ip0_ctrl,
			.ctrlbit	= (1 << 12),
		},
		.sources = &clkset_group1,
		.reg_src = { .reg = S5P_CLK_SRC2, .shift = 8, .size = 2 },
		.reg_div = { .reg = S5P_CLK_DIV2, .shift = 8, .size = 4 },
	}, {
		.clk		= {
			.name		= "sclk_g3d",
			.enable		= s5pv210_clk_ip0_ctrl,
			.ctrlbit	= (1 << 8),
		},
		.sources = &clkset_group1,
		.reg_src = { .reg = S5P_CLK_SRC2, .shift = 0, .size = 2 },
		.reg_div = { .reg = S5P_CLK_DIV2, .shift = 0, .size = 4 },
	}, {
		.clk		= {
			.name		= "sclk_csis",
			.enable		= s5pv210_clk_mask0_ctrl,
			.ctrlbit	= (1 << 6),
		},
		.sources = &clkset_group2,
		.reg_src = { .reg = S5P_CLK_SRC1, .shift = 24, .size = 4 },
		.reg_div = { .reg = S5P_CLK_DIV1, .shift = 28, .size = 4 },
	}, {
		.clk		= {
			.name		= "sclk_pwi",
			.enable		= s5pv210_clk_mask0_ctrl,
			.ctrlbit	= (1 << 29),
		},
		.sources = &clkset_group2,
		.reg_src = { .reg = S5P_CLK_SRC6, .shift = 20, .size = 4 },
		.reg_div = { .reg = S5P_CLK_DIV6, .shift = 24, .size = 4 },
	}, {
		.clk		= {
			.name		= "sclk_pwm",
			.enable		= s5pv210_clk_mask0_ctrl,
			.ctrlbit	= (1 << 19),
		},
		.sources = &clkset_group2,
		.reg_src = { .reg = S5P_CLK_SRC5, .shift = 12, .size = 4 },
		.reg_div = { .reg = S5P_CLK_DIV5, .shift = 12, .size = 4 },
	},
};

最后列出三星自己提供的api函数(注意老的时钟绝大多数由厂家来实现)

下面这些函数三星的实现和common clock framework里面的实现基本是一样的,只不过就是管理没common clock framework正式。


int clk_enable(struct clk *clk)
{
	unsigned long flags;

	if (IS_ERR(clk) || clk == NULL)
		return -EINVAL;

    /* 使能某个时钟,必须父时钟先使能 */
	clk_enable(clk->parent);

	spin_lock_irqsave(&clocks_lock, flags);

    /* 只要使能过,则引用计数++,在引用计数为0时,代表时钟关,此时需要调用真正的开时钟函数 */
	if ((clk->usage++) == 0)
		(clk->enable)(clk, 1);

	spin_unlock_irqrestore(&clocks_lock, flags);
	return 0;
}

void clk_disable(struct clk *clk)
{
	unsigned long flags;

	if (IS_ERR(clk) || clk == NULL)
		return;

	spin_lock_irqsave(&clocks_lock, flags);

    /* 关时钟前先减1,它的引用计数,可能其他设备也在用这个时钟 */
	if ((--clk->usage) == 0)
		(clk->enable)(clk, 0);

	spin_unlock_irqrestore(&clocks_lock, flags);
	clk_disable(clk->parent);    /* 父时钟的引用计数也要减1 */
}


unsigned long clk_get_rate(struct clk *clk)
{
	if (IS_ERR_OR_NULL(clk))
		return 0;

	if (clk->rate != 0)
		return clk->rate;        /* 该时钟有值,直接返回 */

    /* 时钟值为0,则调用相关函数计算时钟 */
	if (clk->ops != NULL && clk->ops->get_rate != NULL)
		return (clk->ops->get_rate)(clk);

    /* 有父时钟,该时钟速率就是父时钟速率 */
	if (clk->parent != NULL)
		return clk_get_rate(clk->parent);

    /* 如果以上都不是则返回0 */
	return clk->rate;
}


/* 计算最接近的时钟范围 */
long clk_round_rate(struct clk *clk, unsigned long rate)
{
	if (!IS_ERR_OR_NULL(clk) && clk->ops && clk->ops->round_rate)
		return (clk->ops->round_rate)(clk, rate);

	return rate;
}


/* 设置时钟速率 */
int clk_set_rate(struct clk *clk, unsigned long rate)
{
	unsigned long flags;
	int ret;

	if (IS_ERR_OR_NULL(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_irqsave(&clocks_lock, flags);
	ret = (clk->ops->set_rate)(clk, rate);
	spin_unlock_irqrestore(&clocks_lock, flags);

	return ret;
}

/* 获取父时钟 */
struct clk *clk_get_parent(struct clk *clk)
{
	return clk->parent;
}

/* 跟换父时钟 */
int clk_set_parent(struct clk *clk, struct clk *parent)
{
	unsigned long flags;
	int ret = 0;

	if (IS_ERR_OR_NULL(clk) || IS_ERR_OR_NULL(parent))
		return -EINVAL;

	spin_lock_irqsave(&clocks_lock, flags);

	if (clk->ops && clk->ops->set_parent)
		ret = (clk->ops->set_parent)(clk, parent);

	spin_unlock_irqrestore(&clocks_lock, flags);

	return ret;
}

当然老的时钟框架中对驱动工程师而言,使用和新的是一样的

因为操作步骤都是一样的,因为都是。

步骤都是获得时钟,使能时钟。

获得时钟所用的clk_get函数也都是完全一样的。因为都是从clock链表中查找返回一个clk指针。

唯一的差别就是使能的函数这里。


/* clk_prepare_enable helps cases using clk_enable in non-atomic context. */
static inline int clk_prepare_enable(struct clk *clk)
{
	int ret;

	ret = clk_prepare(clk);
	if (ret)
		return ret;
	ret = clk_enable(clk);
	if (ret)
		clk_unprepare(clk);

	return ret;
}

/* clk_disable_unprepare helps cases using clk_disable in non-atomic context. */
static inline void clk_disable_unprepare(struct clk *clk)
{
	clk_disable(clk);
	clk_unprepare(clk);
}

老的框架是没有clk_prepare/clk_unprepare函数的,所以就用了一个可能睡眠的函数,表明这个函数的功能。

#ifdef CONFIG_HAVE_CLK_PREPARE   /* 这个宏是 common clock framework才有的 */
int clk_prepare(struct clk *clk);
#else
static inline int clk_prepare(struct clk *clk)
{
	might_sleep();
	return 0;
}
#endif

这个宏主要用来做调试工作,在你不确定不期望睡眠的地方是否真的不会睡眠时,就把这个宏加进去,进行调试。

当然前提是打开相关调试宏。

void __might_sleep(const char *file, int line, int preempt_offset)
{
	static unsigned long prev_jiffy;	/* ratelimiting */

	rcu_sleep_check(); /* WARN_ON_ONCE() by default, no rate limit reqd. */
	if ((preempt_count_equals(preempt_offset) && !irqs_disabled() &&
	     !is_idle_task(current)) ||
	    system_state != SYSTEM_RUNNING || oops_in_progress)
		return;
	if (time_before(jiffies, prev_jiffy + HZ) && prev_jiffy)
		return;
	prev_jiffy = jiffies;

	printk(KERN_ERR
		"BUG: sleeping function called from invalid context at %s:%d\n",
			file, line);
	printk(KERN_ERR
		"in_atomic(): %d, irqs_disabled(): %d, pid: %d, name: %s\n",
			in_atomic(), irqs_disabled(),
			current->pid, current->comm);

	debug_show_held_locks(current);
	if (irqs_disabled())
		print_irqtrace_events(current);
#ifdef CONFIG_DEBUG_PREEMPT
	if (!preempt_count_equals(preempt_offset)) {
		pr_err("Preemption disabled at:");
		print_ip_sym(current->preempt_disable_ip);
		pr_cont("\n");
	}
#endif
	dump_stack();        /* 打印出栈的回溯信息 */
}

猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/84493290