/* * Copyright (c) 2004-2008 Simtec Electronics * Ben Dooks <ben@simtec.co.uk> * http://armlinux.simtec.co.uk/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * S3C24XX - definitions for DCLK and CLKOUT registers */ #include <linux/kernel.h> #include <linux/errno.h> #include <linux/clk.h> #include <linux/io.h> #include <mach/regs-clock.h> #include <mach/regs-gpio.h> #include <plat/clock.h> #include <plat/cpu.h> /* clocks that could be registered by external code */ static int s3c24xx_dclk_enable(struct clk *clk, int enable) { unsigned long dclkcon = __raw_readl(S3C24XX_DCLKCON); if (enable) dclkcon |= clk->ctrlbit; else dclkcon &= ~clk->ctrlbit; __raw_writel(dclkcon, S3C24XX_DCLKCON); return 0; } static int s3c24xx_dclk_setparent(struct clk *clk, struct clk *parent) { unsigned long dclkcon; unsigned int uclk; if (parent == &clk_upll) uclk = 1; else if (parent == &clk_p) uclk = 0; else return -EINVAL; clk->parent = parent; dclkcon = __raw_readl(S3C24XX_DCLKCON); if (clk->ctrlbit == S3C2410_DCLKCON_DCLK0EN) { if (uclk) dclkcon |= S3C2410_DCLKCON_DCLK0_UCLK; else dclkcon &= ~S3C2410_DCLKCON_DCLK0_UCLK; } else { if (uclk) dclkcon |= S3C2410_DCLKCON_DCLK1_UCLK; else dclkcon &= ~S3C2410_DCLKCON_DCLK1_UCLK; } __raw_writel(dclkcon, S3C24XX_DCLKCON); return 0; } static unsigned long s3c24xx_calc_div(struct clk *clk, unsigned long rate) { unsigned long div; if ((rate == 0) || !clk->parent) return 0; div = clk_get_rate(clk->parent) / rate; if (div < 2) div = 2; else if (div > 16) div = 16; return div; } static unsigned long s3c24xx_round_dclk_rate(struct clk *clk, unsigned long rate) { unsigned long div = s3c24xx_calc_div(clk, rate); if (div == 0) return 0; return clk_get_rate(clk->parent) / div; } static int s3c24xx_set_dclk_rate(struct clk *clk, unsigned long rate) { unsigned long mask, data, div = s3c24xx_calc_div(clk, rate); if (div == 0) return -EINVAL; if (clk == &s3c24xx_dclk0) { mask = S3C2410_DCLKCON_DCLK0_DIV_MASK | S3C2410_DCLKCON_DCLK0_CMP_MASK; data = S3C2410_DCLKCON_DCLK0_DIV(div) | S3C2410_DCLKCON_DCLK0_CMP((div + 1) / 2); } else if (clk == &s3c24xx_dclk1) { mask = S3C2410_DCLKCON_DCLK1_DIV_MASK | S3C2410_DCLKCON_DCLK1_CMP_MASK; data = S3C2410_DCLKCON_DCLK1_DIV(div) | S3C2410_DCLKCON_DCLK1_CMP((div + 1) / 2); } else return -EINVAL; clk->rate = clk_get_rate(clk->parent) / div; __raw_writel(((__raw_readl(S3C24XX_DCLKCON) & ~mask) | data), S3C24XX_DCLKCON); return clk->rate; } static int s3c24xx_clkout_setparent(struct clk *clk, struct clk *parent) { unsigned long mask; unsigned long source; /* calculate the MISCCR setting for the clock */ if (parent == &clk_mpll) source = S3C2410_MISCCR_CLK0_MPLL; else if (parent == &clk_upll) source = S3C2410_MISCCR_CLK0_UPLL; else if (parent == &clk_f) source = S3C2410_MISCCR_CLK0_FCLK; else if (parent == &clk_h) source = S3C2410_MISCCR_CLK0_HCLK; else if (parent == &clk_p) source = S3C2410_MISCCR_CLK0_PCLK; else if (clk == &s3c24xx_clkout0 && parent == &s3c24xx_dclk0) source = S3C2410_MISCCR_CLK0_DCLK0; else if (clk == &s3c24xx_clkout1 && parent == &s3c24xx_dclk1) source = S3C2410_MISCCR_CLK0_DCLK0; else return -EINVAL; clk->parent = parent; if (clk == &s3c24xx_clkout0) mask = S3C2410_MISCCR_CLK0_MASK; else { source <<= 4; mask = S3C2410_MISCCR_CLK1_MASK; } s3c2410_modify_misccr(mask, source); return 0; } /* external clock definitions */ static struct clk_ops dclk_ops = { .set_parent = s3c24xx_dclk_setparent, .set_rate = s3c24xx_set_dclk_rate, .round_rate = s3c24xx_round_dclk_rate, }; struct clk s3c24xx_dclk0 = { .name = "dclk0", .ctrlbit = S3C2410_DCLKCON_DCLK0EN, .enable = s3c24xx_dclk_enable, .ops = &dclk_ops, }; struct clk s3c24xx_dclk1 = { .name = "dclk1", .ctrlbit = S3C2410_DCLKCON_DCLK1EN, .enable = s3c24xx_dclk_enable, .ops = &dclk_ops, }; static struct clk_ops clkout_ops = { .set_parent = s3c24xx_clkout_setparent, }; struct clk s3c24xx_clkout0 = { .name = "clkout0", .ops = &clkout_ops, }; struct clk s3c24xx_clkout1 = { .name = "clkout1", .ops = &clkout_ops, };