/* * Clock tree for CSR SiRFprimaII * * Copyright (c) 2011 Cambridge Silicon Radio Limited, a CSR plc group company. * * Licensed under GPLv2 or later. */ #include #include #include #include #include #include #include #include #include #include #include #include #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(); }