| 
									
										
										
										
											2012-04-11 18:04:23 +05:30
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright (C) 2012 ST Microelectronics | 
					
						
							| 
									
										
										
										
											2012-06-20 12:53:02 -07:00
										 |  |  |  * Viresh Kumar <viresh.linux@gmail.com> | 
					
						
							| 
									
										
										
										
											2012-04-11 18:04:23 +05:30
										 |  |  |  * | 
					
						
							|  |  |  |  * This file is licensed under the terms of the GNU General Public | 
					
						
							|  |  |  |  * License version 2. This program is licensed "as is" without any | 
					
						
							|  |  |  |  * warranty of any kind, whether express or implied. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * General Purpose Timer Synthesizer clock implementation | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define pr_fmt(fmt) "clk-gpt-synth: " fmt
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/clk-provider.h>
 | 
					
						
							|  |  |  | #include <linux/slab.h>
 | 
					
						
							|  |  |  | #include <linux/io.h>
 | 
					
						
							|  |  |  | #include <linux/err.h>
 | 
					
						
							|  |  |  | #include "clk.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define GPT_MSCALE_MASK		0xFFF
 | 
					
						
							|  |  |  | #define GPT_NSCALE_SHIFT	12
 | 
					
						
							|  |  |  | #define GPT_NSCALE_MASK		0xF
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * DOC: General Purpose Timer Synthesizer clock | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Calculates gpt synth clk rate for different values of mscale and nscale | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Fout= Fin/((2 ^ (N+1)) * (M+1)) | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define to_clk_gpt(_hw) container_of(_hw, struct clk_gpt, hw)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static unsigned long gpt_calc_rate(struct clk_hw *hw, unsigned long prate, | 
					
						
							|  |  |  | 		int index) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct clk_gpt *gpt = to_clk_gpt(hw); | 
					
						
							|  |  |  | 	struct gpt_rate_tbl *rtbl = gpt->rtbl; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	prate /= ((1 << (rtbl[index].nscale + 1)) * (rtbl[index].mscale + 1)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return prate; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static long clk_gpt_round_rate(struct clk_hw *hw, unsigned long drate, | 
					
						
							|  |  |  | 		unsigned long *prate) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct clk_gpt *gpt = to_clk_gpt(hw); | 
					
						
							|  |  |  | 	int unused; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return clk_round_rate_index(hw, drate, *prate, gpt_calc_rate, | 
					
						
							|  |  |  | 			gpt->rtbl_cnt, &unused); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static unsigned long clk_gpt_recalc_rate(struct clk_hw *hw, | 
					
						
							|  |  |  | 		unsigned long parent_rate) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct clk_gpt *gpt = to_clk_gpt(hw); | 
					
						
							|  |  |  | 	unsigned long flags = 0; | 
					
						
							|  |  |  | 	unsigned int div = 1, val; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (gpt->lock) | 
					
						
							|  |  |  | 		spin_lock_irqsave(gpt->lock, flags); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	val = readl_relaxed(gpt->reg); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (gpt->lock) | 
					
						
							|  |  |  | 		spin_unlock_irqrestore(gpt->lock, flags); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	div += val & GPT_MSCALE_MASK; | 
					
						
							|  |  |  | 	div *= 1 << (((val >> GPT_NSCALE_SHIFT) & GPT_NSCALE_MASK) + 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!div) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return parent_rate / div; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Configures new clock rate of gpt */ | 
					
						
							|  |  |  | static int clk_gpt_set_rate(struct clk_hw *hw, unsigned long drate, | 
					
						
							|  |  |  | 				unsigned long prate) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct clk_gpt *gpt = to_clk_gpt(hw); | 
					
						
							|  |  |  | 	struct gpt_rate_tbl *rtbl = gpt->rtbl; | 
					
						
							|  |  |  | 	unsigned long flags = 0, val; | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	clk_round_rate_index(hw, drate, prate, gpt_calc_rate, gpt->rtbl_cnt, | 
					
						
							|  |  |  | 			&i); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (gpt->lock) | 
					
						
							|  |  |  | 		spin_lock_irqsave(gpt->lock, flags); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	val = readl(gpt->reg) & ~GPT_MSCALE_MASK; | 
					
						
							|  |  |  | 	val &= ~(GPT_NSCALE_MASK << GPT_NSCALE_SHIFT); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	val |= rtbl[i].mscale & GPT_MSCALE_MASK; | 
					
						
							|  |  |  | 	val |= (rtbl[i].nscale & GPT_NSCALE_MASK) << GPT_NSCALE_SHIFT; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	writel_relaxed(val, gpt->reg); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (gpt->lock) | 
					
						
							|  |  |  | 		spin_unlock_irqrestore(gpt->lock, flags); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct clk_ops clk_gpt_ops = { | 
					
						
							|  |  |  | 	.recalc_rate = clk_gpt_recalc_rate, | 
					
						
							|  |  |  | 	.round_rate = clk_gpt_round_rate, | 
					
						
							|  |  |  | 	.set_rate = clk_gpt_set_rate, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct clk *clk_register_gpt(const char *name, const char *parent_name, unsigned | 
					
						
							|  |  |  | 		long flags, void __iomem *reg, struct gpt_rate_tbl *rtbl, u8 | 
					
						
							|  |  |  | 		rtbl_cnt, spinlock_t *lock) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct clk_init_data init; | 
					
						
							|  |  |  | 	struct clk_gpt *gpt; | 
					
						
							|  |  |  | 	struct clk *clk; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!name || !parent_name || !reg || !rtbl || !rtbl_cnt) { | 
					
						
							|  |  |  | 		pr_err("Invalid arguments passed"); | 
					
						
							|  |  |  | 		return ERR_PTR(-EINVAL); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	gpt = kzalloc(sizeof(*gpt), GFP_KERNEL); | 
					
						
							|  |  |  | 	if (!gpt) { | 
					
						
							|  |  |  | 		pr_err("could not allocate gpt clk\n"); | 
					
						
							|  |  |  | 		return ERR_PTR(-ENOMEM); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* struct clk_gpt assignments */ | 
					
						
							|  |  |  | 	gpt->reg = reg; | 
					
						
							|  |  |  | 	gpt->rtbl = rtbl; | 
					
						
							|  |  |  | 	gpt->rtbl_cnt = rtbl_cnt; | 
					
						
							|  |  |  | 	gpt->lock = lock; | 
					
						
							|  |  |  | 	gpt->hw.init = &init; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	init.name = name; | 
					
						
							|  |  |  | 	init.ops = &clk_gpt_ops; | 
					
						
							|  |  |  | 	init.flags = flags; | 
					
						
							|  |  |  | 	init.parent_names = &parent_name; | 
					
						
							|  |  |  | 	init.num_parents = 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	clk = clk_register(NULL, &gpt->hw); | 
					
						
							|  |  |  | 	if (!IS_ERR_OR_NULL(clk)) | 
					
						
							|  |  |  | 		return clk; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pr_err("clk register failed\n"); | 
					
						
							|  |  |  | 	kfree(gpt); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return NULL; | 
					
						
							|  |  |  | } |