| /* |
| * Clock tree for CSR SiRFprimaII |
| * |
| * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. |
| * |
| * Licensed under GPLv2 or later. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/bitops.h> |
| #include <linux/err.h> |
| #include <linux/errno.h> |
| #include <linux/io.h> |
| #include <linux/clkdev.h> |
| #include <linux/clk.h> |
| #include <linux/spinlock.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <asm/mach/map.h> |
| #include <mach/map.h> |
| |
| #define SIRFSOC_CLKC_CLK_EN0 0x0000 |
| #define SIRFSOC_CLKC_CLK_EN1 0x0004 |
| #define SIRFSOC_CLKC_REF_CFG 0x0014 |
| #define SIRFSOC_CLKC_CPU_CFG 0x0018 |
| #define SIRFSOC_CLKC_MEM_CFG 0x001c |
| #define SIRFSOC_CLKC_SYS_CFG 0x0020 |
| #define SIRFSOC_CLKC_IO_CFG 0x0024 |
| #define SIRFSOC_CLKC_DSP_CFG 0x0028 |
| #define SIRFSOC_CLKC_GFX_CFG 0x002c |
| #define SIRFSOC_CLKC_MM_CFG 0x0030 |
| #define SIRFSOC_LKC_LCD_CFG 0x0034 |
| #define SIRFSOC_CLKC_MMC_CFG 0x0038 |
| #define SIRFSOC_CLKC_PLL1_CFG0 0x0040 |
| #define SIRFSOC_CLKC_PLL2_CFG0 0x0044 |
| #define SIRFSOC_CLKC_PLL3_CFG0 0x0048 |
| #define SIRFSOC_CLKC_PLL1_CFG1 0x004c |
| #define SIRFSOC_CLKC_PLL2_CFG1 0x0050 |
| #define SIRFSOC_CLKC_PLL3_CFG1 0x0054 |
| #define SIRFSOC_CLKC_PLL1_CFG2 0x0058 |
| #define SIRFSOC_CLKC_PLL2_CFG2 0x005c |
| #define SIRFSOC_CLKC_PLL3_CFG2 0x0060 |
| |
| #define SIRFSOC_CLOCK_VA_BASE SIRFSOC_VA(0x005000) |
| |
| #define KHZ 1000 |
| #define MHZ (KHZ * KHZ) |
| |
| struct clk_ops { |
| unsigned long (*get_rate)(struct clk *clk); |
| long (*round_rate)(struct clk *clk, unsigned long rate); |
| int (*set_rate)(struct clk *clk, unsigned long rate); |
| int (*enable)(struct clk *clk); |
| int (*disable)(struct clk *clk); |
| struct clk *(*get_parent)(struct clk *clk); |
| int (*set_parent)(struct clk *clk, struct clk *parent); |
| }; |
| |
| struct clk { |
| struct clk *parent; /* parent clk */ |
| unsigned long rate; /* clock rate in Hz */ |
| signed char usage; /* clock enable count */ |
| signed char enable_bit; /* enable bit: 0 ~ 63 */ |
| unsigned short regofs; /* register offset */ |
| struct clk_ops *ops; /* clock operation */ |
| }; |
| |
| static DEFINE_SPINLOCK(clocks_lock); |
| |
| static inline unsigned long clkc_readl(unsigned reg) |
| { |
| return readl(SIRFSOC_CLOCK_VA_BASE + reg); |
| } |
| |
| static inline void clkc_writel(u32 val, unsigned reg) |
| { |
| writel(val, SIRFSOC_CLOCK_VA_BASE + reg); |
| } |
| |
| /* |
| * osc_rtc - real time oscillator - 32.768KHz |
| * osc_sys - high speed oscillator - 26MHz |
| */ |
| |
| static struct clk clk_rtc = { |
| .rate = 32768, |
| }; |
| |
| static struct clk clk_osc = { |
| .rate = 26 * MHZ, |
| }; |
| |
| /* |
| * std pll |
| */ |
| static unsigned long std_pll_get_rate(struct clk *clk) |
| { |
| unsigned long fin = clk_get_rate(clk->parent); |
| u32 regcfg2 = clk->regofs + SIRFSOC_CLKC_PLL1_CFG2 - |
| SIRFSOC_CLKC_PLL1_CFG0; |
| |
| if (clkc_readl(regcfg2) & BIT(2)) { |
| /* pll bypass mode */ |
| clk->rate = fin; |
| } else { |
| /* fout = fin * nf / nr / od */ |
| u32 cfg0 = clkc_readl(clk->regofs); |
| u32 nf = (cfg0 & (BIT(13) - 1)) + 1; |
| u32 nr = ((cfg0 >> 13) & (BIT(6) - 1)) + 1; |
| u32 od = ((cfg0 >> 19) & (BIT(4) - 1)) + 1; |
| WARN_ON(fin % MHZ); |
| clk->rate = fin / MHZ * nf / nr / od * MHZ; |
| } |
| |
| return clk->rate; |
| } |
| |
| static int std_pll_set_rate(struct clk *clk, unsigned long rate) |
| { |
| unsigned long fin, nf, nr, od, reg; |
| |
| /* |
| * fout = fin * nf / (nr * od); |
| * set od = 1, nr = fin/MHz, so fout = nf * MHz |
| */ |
| |
| nf = rate / MHZ; |
| if (unlikely((rate % MHZ) || nf > BIT(13) || nf < 1)) |
| return -EINVAL; |
| |
| fin = clk_get_rate(clk->parent); |
| BUG_ON(fin < MHZ); |
| |
| nr = fin / MHZ; |
| BUG_ON((fin % MHZ) || nr > BIT(6)); |
| |
| od = 1; |
| |
| reg = (nf - 1) | ((nr - 1) << 13) | ((od - 1) << 19); |
| clkc_writel(reg, clk->regofs); |
| |
| reg = clk->regofs + SIRFSOC_CLKC_PLL1_CFG1 - SIRFSOC_CLKC_PLL1_CFG0; |
| clkc_writel((nf >> 1) - 1, reg); |
| |
| reg = clk->regofs + SIRFSOC_CLKC_PLL1_CFG2 - SIRFSOC_CLKC_PLL1_CFG0; |
| while (!(clkc_readl(reg) & BIT(6))) |
| cpu_relax(); |
| |
| clk->rate = 0; /* set to zero will force recalculation */ |
| return 0; |
| } |
| |
| static struct clk_ops std_pll_ops = { |
| .get_rate = std_pll_get_rate, |
| .set_rate = std_pll_set_rate, |
| }; |
| |
| static struct clk clk_pll1 = { |
| .parent = &clk_osc, |
| .regofs = SIRFSOC_CLKC_PLL1_CFG0, |
| .ops = &std_pll_ops, |
| }; |
| |
| static struct clk clk_pll2 = { |
| .parent = &clk_osc, |
| .regofs = SIRFSOC_CLKC_PLL2_CFG0, |
| .ops = &std_pll_ops, |
| }; |
| |
| static struct clk clk_pll3 = { |
| .parent = &clk_osc, |
| .regofs = SIRFSOC_CLKC_PLL3_CFG0, |
| .ops = &std_pll_ops, |
| }; |
| |
| /* |
| * clock domains - cpu, mem, sys/io |
| */ |
| |
| static struct clk clk_mem; |
| |
| static struct clk *dmn_get_parent(struct clk *clk) |
| { |
| struct clk *clks[] = { |
| &clk_osc, &clk_rtc, &clk_pll1, &clk_pll2, &clk_pll3 |
| }; |
| u32 cfg = clkc_readl(clk->regofs); |
| WARN_ON((cfg & (BIT(3) - 1)) > 4); |
| return clks[cfg & (BIT(3) - 1)]; |
| } |
| |
| static int dmn_set_parent(struct clk *clk, struct clk *parent) |
| { |
| const struct clk *clks[] = { |
| &clk_osc, &clk_rtc, &clk_pll1, &clk_pll2, &clk_pll3 |
| }; |
| u32 cfg = clkc_readl(clk->regofs); |
| int i; |
| for (i = 0; i < ARRAY_SIZE(clks); i++) { |
| if (clks[i] == parent) { |
| cfg &= ~(BIT(3) - 1); |
| clkc_writel(cfg | i, clk->regofs); |
| /* BIT(3) - switching status: 1 - busy, 0 - done */ |
| while (clkc_readl(clk->regofs) & BIT(3)) |
| cpu_relax(); |
| return 0; |
| } |
| } |
| return -EINVAL; |
| } |
| |
| static unsigned long dmn_get_rate(struct clk *clk) |
| { |
| unsigned long fin = clk_get_rate(clk->parent); |
| u32 cfg = clkc_readl(clk->regofs); |
| if (cfg & BIT(24)) { |
| /* fcd bypass mode */ |
| clk->rate = fin; |
| } else { |
| /* |
| * wait count: bit[19:16], hold count: bit[23:20] |
| */ |
| u32 wait = (cfg >> 16) & (BIT(4) - 1); |
| u32 hold = (cfg >> 20) & (BIT(4) - 1); |
| |
| clk->rate = fin / (wait + hold + 2); |
| } |
| |
| return clk->rate; |
| } |
| |
| static int dmn_set_rate(struct clk *clk, unsigned long rate) |
| { |
| unsigned long fin; |
| unsigned ratio, wait, hold, reg; |
| unsigned bits = (clk == &clk_mem) ? 3 : 4; |
| |
| fin = clk_get_rate(clk->parent); |
| ratio = fin / rate; |
| |
| if (unlikely(ratio < 2 || ratio > BIT(bits + 1))) |
| return -EINVAL; |
| |
| WARN_ON(fin % rate); |
| |
| wait = (ratio >> 1) - 1; |
| hold = ratio - wait - 2; |
| |
| reg = clkc_readl(clk->regofs); |
| reg &= ~(((BIT(bits) - 1) << 16) | ((BIT(bits) - 1) << 20)); |
| reg |= (wait << 16) | (hold << 20) | BIT(25); |
| clkc_writel(reg, clk->regofs); |
| |
| /* waiting FCD been effective */ |
| while (clkc_readl(clk->regofs) & BIT(25)) |
| cpu_relax(); |
| |
| clk->rate = 0; /* set to zero will force recalculation */ |
| |
| return 0; |
| } |
| |
| /* |
| * cpu clock has no FCD register in Prima2, can only change pll |
| */ |
| static int cpu_set_rate(struct clk *clk, unsigned long rate) |
| { |
| int ret1, ret2; |
| struct clk *cur_parent, *tmp_parent; |
| |
| cur_parent = dmn_get_parent(clk); |
| BUG_ON(cur_parent == NULL || cur_parent->usage > 1); |
| |
| /* switch to tmp pll before setting parent clock's rate */ |
| tmp_parent = cur_parent == &clk_pll1 ? &clk_pll2 : &clk_pll1; |
| ret1 = dmn_set_parent(clk, tmp_parent); |
| BUG_ON(ret1); |
| |
| ret2 = clk_set_rate(cur_parent, rate); |
| |
| ret1 = dmn_set_parent(clk, cur_parent); |
| |
| clk->rate = 0; /* set to zero will force recalculation */ |
| |
| return ret2 ? ret2 : ret1; |
| } |
| |
| static struct clk_ops cpu_ops = { |
| .get_parent = dmn_get_parent, |
| .set_parent = dmn_set_parent, |
| .set_rate = cpu_set_rate, |
| }; |
| |
| static struct clk clk_cpu = { |
| .parent = &clk_pll1, |
| .regofs = SIRFSOC_CLKC_CPU_CFG, |
| .ops = &cpu_ops, |
| }; |
| |
| |
| static struct clk_ops msi_ops = { |
| .set_rate = dmn_set_rate, |
| .get_rate = dmn_get_rate, |
| .set_parent = dmn_set_parent, |
| .get_parent = dmn_get_parent, |
| }; |
| |
| static struct clk clk_mem = { |
| .parent = &clk_pll2, |
| .regofs = SIRFSOC_CLKC_MEM_CFG, |
| .ops = &msi_ops, |
| }; |
| |
| static struct clk clk_sys = { |
| .parent = &clk_pll3, |
| .regofs = SIRFSOC_CLKC_SYS_CFG, |
| .ops = &msi_ops, |
| }; |
| |
| static struct clk clk_io = { |
| .parent = &clk_pll3, |
| .regofs = SIRFSOC_CLKC_IO_CFG, |
| .ops = &msi_ops, |
| }; |
| |
| /* |
| * on-chip clock sets |
| */ |
| static struct clk_lookup onchip_clks[] = { |
| { |
| .dev_id = "rtc", |
| .clk = &clk_rtc, |
| }, { |
| .dev_id = "osc", |
| .clk = &clk_osc, |
| }, { |
| .dev_id = "pll1", |
| .clk = &clk_pll1, |
| }, { |
| .dev_id = "pll2", |
| .clk = &clk_pll2, |
| }, { |
| .dev_id = "pll3", |
| .clk = &clk_pll3, |
| }, { |
| .dev_id = "cpu", |
| .clk = &clk_cpu, |
| }, { |
| .dev_id = "mem", |
| .clk = &clk_mem, |
| }, { |
| .dev_id = "sys", |
| .clk = &clk_sys, |
| }, { |
| .dev_id = "io", |
| .clk = &clk_io, |
| }, |
| }; |
| |
| int clk_enable(struct clk *clk) |
| { |
| unsigned long flags; |
| |
| if (unlikely(IS_ERR_OR_NULL(clk))) |
| return -EINVAL; |
| |
| if (clk->parent) |
| clk_enable(clk->parent); |
| |
| spin_lock_irqsave(&clocks_lock, flags); |
| if (!clk->usage++ && clk->ops && clk->ops->enable) |
| clk->ops->enable(clk); |
| spin_unlock_irqrestore(&clocks_lock, flags); |
| return 0; |
| } |
| EXPORT_SYMBOL(clk_enable); |
| |
| void clk_disable(struct clk *clk) |
| { |
| unsigned long flags; |
| |
| if (unlikely(IS_ERR_OR_NULL(clk))) |
| return; |
| |
| WARN_ON(!clk->usage); |
| |
| spin_lock_irqsave(&clocks_lock, flags); |
| if (--clk->usage == 0 && clk->ops && clk->ops->disable) |
| clk->ops->disable(clk); |
| spin_unlock_irqrestore(&clocks_lock, flags); |
| |
| if (clk->parent) |
| clk_disable(clk->parent); |
| } |
| EXPORT_SYMBOL(clk_disable); |
| |
| unsigned long clk_get_rate(struct clk *clk) |
| { |
| if (unlikely(IS_ERR_OR_NULL(clk))) |
| return 0; |
| |
| if (clk->rate) |
| return clk->rate; |
| |
| if (clk->ops && clk->ops->get_rate) |
| return clk->ops->get_rate(clk); |
| |
| return clk_get_rate(clk->parent); |
| } |
| EXPORT_SYMBOL(clk_get_rate); |
| |
| long clk_round_rate(struct clk *clk, unsigned long rate) |
| { |
| if (unlikely(IS_ERR_OR_NULL(clk))) |
| return 0; |
| |
| if (clk->ops && clk->ops->round_rate) |
| return clk->ops->round_rate(clk, rate); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(clk_round_rate); |
| |
| int clk_set_rate(struct clk *clk, unsigned long rate) |
| { |
| if (unlikely(IS_ERR_OR_NULL(clk))) |
| return -EINVAL; |
| |
| if (!clk->ops || !clk->ops->set_rate) |
| return -EINVAL; |
| |
| return clk->ops->set_rate(clk, rate); |
| } |
| EXPORT_SYMBOL(clk_set_rate); |
| |
| int clk_set_parent(struct clk *clk, struct clk *parent) |
| { |
| int ret; |
| unsigned long flags; |
| |
| if (unlikely(IS_ERR_OR_NULL(clk))) |
| return -EINVAL; |
| |
| if (!clk->ops || !clk->ops->set_parent) |
| return -EINVAL; |
| |
| spin_lock_irqsave(&clocks_lock, flags); |
| ret = clk->ops->set_parent(clk, parent); |
| if (!ret) { |
| parent->usage += clk->usage; |
| clk->parent->usage -= clk->usage; |
| BUG_ON(clk->parent->usage < 0); |
| clk->parent = parent; |
| } |
| spin_unlock_irqrestore(&clocks_lock, flags); |
| return ret; |
| } |
| EXPORT_SYMBOL(clk_set_parent); |
| |
| struct clk *clk_get_parent(struct clk *clk) |
| { |
| unsigned long flags; |
| |
| if (unlikely(IS_ERR_OR_NULL(clk))) |
| return NULL; |
| |
| if (!clk->ops || !clk->ops->get_parent) |
| return clk->parent; |
| |
| spin_lock_irqsave(&clocks_lock, flags); |
| clk->parent = clk->ops->get_parent(clk); |
| spin_unlock_irqrestore(&clocks_lock, flags); |
| return clk->parent; |
| } |
| EXPORT_SYMBOL(clk_get_parent); |
| |
| static void __init sirfsoc_clk_init(void) |
| { |
| clkdev_add_table(onchip_clks, ARRAY_SIZE(onchip_clks)); |
| } |
| |
| static struct of_device_id clkc_ids[] = { |
| { .compatible = "sirf,prima2-clkc" }, |
| {}, |
| }; |
| |
| void __init sirfsoc_of_clk_init(void) |
| { |
| struct device_node *np; |
| struct resource res; |
| struct map_desc sirfsoc_clkc_iodesc = { |
| .virtual = SIRFSOC_CLOCK_VA_BASE, |
| .type = MT_DEVICE, |
| }; |
| |
| np = of_find_matching_node(NULL, clkc_ids); |
| if (!np) |
| panic("unable to find compatible clkc node in dtb\n"); |
| |
| if (of_address_to_resource(np, 0, &res)) |
| panic("unable to find clkc range in dtb"); |
| of_node_put(np); |
| |
| sirfsoc_clkc_iodesc.pfn = __phys_to_pfn(res.start); |
| sirfsoc_clkc_iodesc.length = 1 + res.end - res.start; |
| |
| iotable_init(&sirfsoc_clkc_iodesc, 1); |
| |
| sirfsoc_clk_init(); |
| } |