alistair23-linux/arch/arm/mach-s3c24xx/clock-s3c2412.c
Heiko Stuebner 0fa93b914d ARM: S3C24XX: number the dma clocks
Each dma channel has its own clock. The upcoming dma driver wants to
handle these itself and therefore needs to be able to get the correct
clock for a channel.

Therefore rename the dma clocks to "dma.X" for s3c2412, s3c2416 and
s3c2443. This does not change the behaviour for the old dma driver at
all, as all dma clocks are still always on and not handled by the old
dma driver at all.

Signed-off-by: Heiko Stuebner <heiko@sntech.de>
Acked-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
2013-10-08 06:42:10 +09:00

762 lines
18 KiB
C

/* linux/arch/arm/mach-s3c2412/clock.c
*
* Copyright (c) 2006 Simtec Electronics
* Ben Dooks <ben@simtec.co.uk>
*
* S3C2412,S3C2413 Clock control support
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/device.h>
#include <linux/clk.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/serial_core.h>
#include <linux/io.h>
#include <asm/mach/map.h>
#include <mach/hardware.h>
#include <plat/regs-serial.h>
#include <mach/regs-clock.h>
#include <mach/regs-gpio.h>
#include <plat/clock.h>
#include <plat/cpu.h>
/* We currently have to assume that the system is running
* from the XTPll input, and that all ***REFCLKs are being
* fed from it, as we cannot read the state of OM[4] from
* software.
*
* It would be possible for each board initialisation to
* set the correct muxing at initialisation
*/
static int s3c2412_clkcon_enable(struct clk *clk, int enable)
{
unsigned int clocks = clk->ctrlbit;
unsigned long clkcon;
clkcon = __raw_readl(S3C2410_CLKCON);
if (enable)
clkcon |= clocks;
else
clkcon &= ~clocks;
__raw_writel(clkcon, S3C2410_CLKCON);
return 0;
}
static int s3c2412_upll_enable(struct clk *clk, int enable)
{
unsigned long upllcon = __raw_readl(S3C2410_UPLLCON);
unsigned long orig = upllcon;
if (!enable)
upllcon |= S3C2412_PLLCON_OFF;
else
upllcon &= ~S3C2412_PLLCON_OFF;
__raw_writel(upllcon, S3C2410_UPLLCON);
/* allow ~150uS for the PLL to settle and lock */
if (enable && (orig & S3C2412_PLLCON_OFF))
udelay(150);
return 0;
}
/* clock selections */
static struct clk clk_erefclk = {
.name = "erefclk",
};
static struct clk clk_urefclk = {
.name = "urefclk",
};
static int s3c2412_setparent_usysclk(struct clk *clk, struct clk *parent)
{
unsigned long clksrc = __raw_readl(S3C2412_CLKSRC);
if (parent == &clk_urefclk)
clksrc &= ~S3C2412_CLKSRC_USYSCLK_UPLL;
else if (parent == &clk_upll)
clksrc |= S3C2412_CLKSRC_USYSCLK_UPLL;
else
return -EINVAL;
clk->parent = parent;
__raw_writel(clksrc, S3C2412_CLKSRC);
return 0;
}
static struct clk clk_usysclk = {
.name = "usysclk",
.parent = &clk_xtal,
.ops = &(struct clk_ops) {
.set_parent = s3c2412_setparent_usysclk,
},
};
static struct clk clk_mrefclk = {
.name = "mrefclk",
.parent = &clk_xtal,
};
static struct clk clk_mdivclk = {
.name = "mdivclk",
.parent = &clk_xtal,
};
static int s3c2412_setparent_usbsrc(struct clk *clk, struct clk *parent)
{
unsigned long clksrc = __raw_readl(S3C2412_CLKSRC);
if (parent == &clk_usysclk)
clksrc &= ~S3C2412_CLKSRC_USBCLK_HCLK;
else if (parent == &clk_h)
clksrc |= S3C2412_CLKSRC_USBCLK_HCLK;
else
return -EINVAL;
clk->parent = parent;
__raw_writel(clksrc, S3C2412_CLKSRC);
return 0;
}
static unsigned long s3c2412_roundrate_usbsrc(struct clk *clk,
unsigned long rate)
{
unsigned long parent_rate = clk_get_rate(clk->parent);
int div;
if (rate > parent_rate)
return parent_rate;
div = parent_rate / rate;
if (div > 2)
div = 2;
return parent_rate / div;
}
static unsigned long s3c2412_getrate_usbsrc(struct clk *clk)
{
unsigned long parent_rate = clk_get_rate(clk->parent);
unsigned long div = __raw_readl(S3C2410_CLKDIVN);
return parent_rate / ((div & S3C2412_CLKDIVN_USB48DIV) ? 2 : 1);
}
static int s3c2412_setrate_usbsrc(struct clk *clk, unsigned long rate)
{
unsigned long parent_rate = clk_get_rate(clk->parent);
unsigned long clkdivn = __raw_readl(S3C2410_CLKDIVN);
rate = s3c2412_roundrate_usbsrc(clk, rate);
if ((parent_rate / rate) == 2)
clkdivn |= S3C2412_CLKDIVN_USB48DIV;
else
clkdivn &= ~S3C2412_CLKDIVN_USB48DIV;
__raw_writel(clkdivn, S3C2410_CLKDIVN);
return 0;
}
static struct clk clk_usbsrc = {
.name = "usbsrc",
.ops = &(struct clk_ops) {
.get_rate = s3c2412_getrate_usbsrc,
.set_rate = s3c2412_setrate_usbsrc,
.round_rate = s3c2412_roundrate_usbsrc,
.set_parent = s3c2412_setparent_usbsrc,
},
};
static int s3c2412_setparent_msysclk(struct clk *clk, struct clk *parent)
{
unsigned long clksrc = __raw_readl(S3C2412_CLKSRC);
if (parent == &clk_mdivclk)
clksrc &= ~S3C2412_CLKSRC_MSYSCLK_MPLL;
else if (parent == &clk_mpll)
clksrc |= S3C2412_CLKSRC_MSYSCLK_MPLL;
else
return -EINVAL;
clk->parent = parent;
__raw_writel(clksrc, S3C2412_CLKSRC);
return 0;
}
static struct clk clk_msysclk = {
.name = "msysclk",
.ops = &(struct clk_ops) {
.set_parent = s3c2412_setparent_msysclk,
},
};
static int s3c2412_setparent_armclk(struct clk *clk, struct clk *parent)
{
unsigned long flags;
unsigned long clkdiv;
unsigned long dvs;
/* Note, we current equate fclk andf msysclk for S3C2412 */
if (parent == &clk_msysclk || parent == &clk_f)
dvs = 0;
else if (parent == &clk_h)
dvs = S3C2412_CLKDIVN_DVSEN;
else
return -EINVAL;
clk->parent = parent;
/* update this under irq lockdown, clkdivn is not protected
* by the clock system. */
local_irq_save(flags);
clkdiv = __raw_readl(S3C2410_CLKDIVN);
clkdiv &= ~S3C2412_CLKDIVN_DVSEN;
clkdiv |= dvs;
__raw_writel(clkdiv, S3C2410_CLKDIVN);
local_irq_restore(flags);
return 0;
}
static struct clk clk_armclk = {
.name = "armclk",
.parent = &clk_msysclk,
.ops = &(struct clk_ops) {
.set_parent = s3c2412_setparent_armclk,
},
};
/* these next clocks have an divider immediately after them,
* so we can register them with their divider and leave out the
* intermediate clock stage
*/
static unsigned long s3c2412_roundrate_clksrc(struct clk *clk,
unsigned long rate)
{
unsigned long parent_rate = clk_get_rate(clk->parent);
int div;
if (rate > parent_rate)
return parent_rate;
/* note, we remove the +/- 1 calculations as they cancel out */
div = (rate / parent_rate);
if (div < 1)
div = 1;
else if (div > 16)
div = 16;
return parent_rate / div;
}
static int s3c2412_setparent_uart(struct clk *clk, struct clk *parent)
{
unsigned long clksrc = __raw_readl(S3C2412_CLKSRC);
if (parent == &clk_erefclk)
clksrc &= ~S3C2412_CLKSRC_UARTCLK_MPLL;
else if (parent == &clk_mpll)
clksrc |= S3C2412_CLKSRC_UARTCLK_MPLL;
else
return -EINVAL;
clk->parent = parent;
__raw_writel(clksrc, S3C2412_CLKSRC);
return 0;
}
static unsigned long s3c2412_getrate_uart(struct clk *clk)
{
unsigned long parent_rate = clk_get_rate(clk->parent);
unsigned long div = __raw_readl(S3C2410_CLKDIVN);
div &= S3C2412_CLKDIVN_UARTDIV_MASK;
div >>= S3C2412_CLKDIVN_UARTDIV_SHIFT;
return parent_rate / (div + 1);
}
static int s3c2412_setrate_uart(struct clk *clk, unsigned long rate)
{
unsigned long parent_rate = clk_get_rate(clk->parent);
unsigned long clkdivn = __raw_readl(S3C2410_CLKDIVN);
rate = s3c2412_roundrate_clksrc(clk, rate);
clkdivn &= ~S3C2412_CLKDIVN_UARTDIV_MASK;
clkdivn |= ((parent_rate / rate) - 1) << S3C2412_CLKDIVN_UARTDIV_SHIFT;
__raw_writel(clkdivn, S3C2410_CLKDIVN);
return 0;
}
static struct clk clk_uart = {
.name = "uartclk",
.ops = &(struct clk_ops) {
.get_rate = s3c2412_getrate_uart,
.set_rate = s3c2412_setrate_uart,
.set_parent = s3c2412_setparent_uart,
.round_rate = s3c2412_roundrate_clksrc,
},
};
static int s3c2412_setparent_i2s(struct clk *clk, struct clk *parent)
{
unsigned long clksrc = __raw_readl(S3C2412_CLKSRC);
if (parent == &clk_erefclk)
clksrc &= ~S3C2412_CLKSRC_I2SCLK_MPLL;
else if (parent == &clk_mpll)
clksrc |= S3C2412_CLKSRC_I2SCLK_MPLL;
else
return -EINVAL;
clk->parent = parent;
__raw_writel(clksrc, S3C2412_CLKSRC);
return 0;
}
static unsigned long s3c2412_getrate_i2s(struct clk *clk)
{
unsigned long parent_rate = clk_get_rate(clk->parent);
unsigned long div = __raw_readl(S3C2410_CLKDIVN);
div &= S3C2412_CLKDIVN_I2SDIV_MASK;
div >>= S3C2412_CLKDIVN_I2SDIV_SHIFT;
return parent_rate / (div + 1);
}
static int s3c2412_setrate_i2s(struct clk *clk, unsigned long rate)
{
unsigned long parent_rate = clk_get_rate(clk->parent);
unsigned long clkdivn = __raw_readl(S3C2410_CLKDIVN);
rate = s3c2412_roundrate_clksrc(clk, rate);
clkdivn &= ~S3C2412_CLKDIVN_I2SDIV_MASK;
clkdivn |= ((parent_rate / rate) - 1) << S3C2412_CLKDIVN_I2SDIV_SHIFT;
__raw_writel(clkdivn, S3C2410_CLKDIVN);
return 0;
}
static struct clk clk_i2s = {
.name = "i2sclk",
.ops = &(struct clk_ops) {
.get_rate = s3c2412_getrate_i2s,
.set_rate = s3c2412_setrate_i2s,
.set_parent = s3c2412_setparent_i2s,
.round_rate = s3c2412_roundrate_clksrc,
},
};
static int s3c2412_setparent_cam(struct clk *clk, struct clk *parent)
{
unsigned long clksrc = __raw_readl(S3C2412_CLKSRC);
if (parent == &clk_usysclk)
clksrc &= ~S3C2412_CLKSRC_CAMCLK_HCLK;
else if (parent == &clk_h)
clksrc |= S3C2412_CLKSRC_CAMCLK_HCLK;
else
return -EINVAL;
clk->parent = parent;
__raw_writel(clksrc, S3C2412_CLKSRC);
return 0;
}
static unsigned long s3c2412_getrate_cam(struct clk *clk)
{
unsigned long parent_rate = clk_get_rate(clk->parent);
unsigned long div = __raw_readl(S3C2410_CLKDIVN);
div &= S3C2412_CLKDIVN_CAMDIV_MASK;
div >>= S3C2412_CLKDIVN_CAMDIV_SHIFT;
return parent_rate / (div + 1);
}
static int s3c2412_setrate_cam(struct clk *clk, unsigned long rate)
{
unsigned long parent_rate = clk_get_rate(clk->parent);
unsigned long clkdivn = __raw_readl(S3C2410_CLKDIVN);
rate = s3c2412_roundrate_clksrc(clk, rate);
clkdivn &= ~S3C2412_CLKDIVN_CAMDIV_MASK;
clkdivn |= ((parent_rate / rate) - 1) << S3C2412_CLKDIVN_CAMDIV_SHIFT;
__raw_writel(clkdivn, S3C2410_CLKDIVN);
return 0;
}
static struct clk clk_cam = {
.name = "camif-upll", /* same as 2440 name */
.ops = &(struct clk_ops) {
.get_rate = s3c2412_getrate_cam,
.set_rate = s3c2412_setrate_cam,
.set_parent = s3c2412_setparent_cam,
.round_rate = s3c2412_roundrate_clksrc,
},
};
/* standard clock definitions */
static struct clk init_clocks_disable[] = {
{
.name = "nand",
.parent = &clk_h,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_NAND,
}, {
.name = "sdi",
.parent = &clk_p,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_SDI,
}, {
.name = "adc",
.parent = &clk_p,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_ADC,
}, {
.name = "i2c",
.parent = &clk_p,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_IIC,
}, {
.name = "iis",
.parent = &clk_p,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_IIS,
}, {
.name = "spi",
.parent = &clk_p,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_SPI,
}
};
static struct clk init_clocks[] = {
{
.name = "dma.0",
.parent = &clk_h,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_DMA0,
}, {
.name = "dma.1",
.parent = &clk_h,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_DMA1,
}, {
.name = "dma.2",
.parent = &clk_h,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_DMA2,
}, {
.name = "dma.3",
.parent = &clk_h,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_DMA3,
}, {
.name = "lcd",
.parent = &clk_h,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_LCDC,
}, {
.name = "gpio",
.parent = &clk_p,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_GPIO,
}, {
.name = "usb-host",
.parent = &clk_h,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_USBH,
}, {
.name = "usb-device",
.parent = &clk_h,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_USBD,
}, {
.name = "timers",
.parent = &clk_p,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_PWMT,
}, {
.name = "uart",
.devname = "s3c2412-uart.0",
.parent = &clk_p,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_UART0,
}, {
.name = "uart",
.devname = "s3c2412-uart.1",
.parent = &clk_p,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_UART1,
}, {
.name = "uart",
.devname = "s3c2412-uart.2",
.parent = &clk_p,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_UART2,
}, {
.name = "rtc",
.parent = &clk_p,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_RTC,
}, {
.name = "watchdog",
.parent = &clk_p,
.ctrlbit = 0,
}, {
.name = "usb-bus-gadget",
.parent = &clk_usb_bus,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_USB_DEV48,
}, {
.name = "usb-bus-host",
.parent = &clk_usb_bus,
.enable = s3c2412_clkcon_enable,
.ctrlbit = S3C2412_CLKCON_USB_HOST48,
}
};
/* clocks to add where we need to check their parentage */
struct clk_init {
struct clk *clk;
unsigned int bit;
struct clk *src_0;
struct clk *src_1;
};
static struct clk_init clks_src[] __initdata = {
{
.clk = &clk_usysclk,
.bit = S3C2412_CLKSRC_USBCLK_HCLK,
.src_0 = &clk_urefclk,
.src_1 = &clk_upll,
}, {
.clk = &clk_i2s,
.bit = S3C2412_CLKSRC_I2SCLK_MPLL,
.src_0 = &clk_erefclk,
.src_1 = &clk_mpll,
}, {
.clk = &clk_cam,
.bit = S3C2412_CLKSRC_CAMCLK_HCLK,
.src_0 = &clk_usysclk,
.src_1 = &clk_h,
}, {
.clk = &clk_msysclk,
.bit = S3C2412_CLKSRC_MSYSCLK_MPLL,
.src_0 = &clk_mdivclk,
.src_1 = &clk_mpll,
}, {
.clk = &clk_uart,
.bit = S3C2412_CLKSRC_UARTCLK_MPLL,
.src_0 = &clk_erefclk,
.src_1 = &clk_mpll,
}, {
.clk = &clk_usbsrc,
.bit = S3C2412_CLKSRC_USBCLK_HCLK,
.src_0 = &clk_usysclk,
.src_1 = &clk_h,
/* here we assume OM[4] select xtal */
}, {
.clk = &clk_erefclk,
.bit = S3C2412_CLKSRC_EREFCLK_EXTCLK,
.src_0 = &clk_xtal,
.src_1 = &clk_ext,
}, {
.clk = &clk_urefclk,
.bit = S3C2412_CLKSRC_UREFCLK_EXTCLK,
.src_0 = &clk_xtal,
.src_1 = &clk_ext,
},
};
/* s3c2412_clk_initparents
*
* Initialise the parents for the clocks that we get at start-time
*/
static void __init s3c2412_clk_initparents(void)
{
unsigned long clksrc = __raw_readl(S3C2412_CLKSRC);
struct clk_init *cip = clks_src;
struct clk *src;
int ptr;
int ret;
for (ptr = 0; ptr < ARRAY_SIZE(clks_src); ptr++, cip++) {
ret = s3c24xx_register_clock(cip->clk);
if (ret < 0) {
printk(KERN_ERR "Failed to register clock %s (%d)\n",
cip->clk->name, ret);
}
src = (clksrc & cip->bit) ? cip->src_1 : cip->src_0;
printk(KERN_INFO "%s: parent %s\n", cip->clk->name, src->name);
clk_set_parent(cip->clk, src);
}
}
/* clocks to add straight away */
static struct clk *clks[] __initdata = {
&clk_ext,
&clk_usb_bus,
&clk_mrefclk,
&clk_armclk,
};
static struct clk_lookup s3c2412_clk_lookup[] = {
CLKDEV_INIT(NULL, "clk_uart_baud1", &s3c24xx_uclk),
CLKDEV_INIT(NULL, "clk_uart_baud2", &clk_p),
CLKDEV_INIT(NULL, "clk_uart_baud3", &clk_usysclk),
};
int __init s3c2412_baseclk_add(void)
{
unsigned long clkcon = __raw_readl(S3C2410_CLKCON);
unsigned int dvs;
struct clk *clkp;
int ret;
int ptr;
clk_upll.enable = s3c2412_upll_enable;
clk_usb_bus.parent = &clk_usbsrc;
clk_usb_bus.rate = 0x0;
clk_f.parent = &clk_msysclk;
s3c2412_clk_initparents();
for (ptr = 0; ptr < ARRAY_SIZE(clks); ptr++) {
clkp = clks[ptr];
ret = s3c24xx_register_clock(clkp);
if (ret < 0) {
printk(KERN_ERR "Failed to register clock %s (%d)\n",
clkp->name, ret);
}
}
/* set the dvs state according to what we got at boot time */
dvs = __raw_readl(S3C2410_CLKDIVN) & S3C2412_CLKDIVN_DVSEN;
if (dvs)
clk_armclk.parent = &clk_h;
printk(KERN_INFO "S3C2412: DVS is %s\n", dvs ? "on" : "off");
/* ensure usb bus clock is within correct rate of 48MHz */
if (clk_get_rate(&clk_usb_bus) != (48 * 1000 * 1000)) {
printk(KERN_INFO "Warning: USB bus clock not at 48MHz\n");
/* for the moment, let's use the UPLL, and see if we can
* get 48MHz */
clk_set_parent(&clk_usysclk, &clk_upll);
clk_set_parent(&clk_usbsrc, &clk_usysclk);
clk_set_rate(&clk_usbsrc, 48*1000*1000);
}
printk("S3C2412: upll %s, %ld.%03ld MHz, usb-bus %ld.%03ld MHz\n",
(__raw_readl(S3C2410_UPLLCON) & S3C2412_PLLCON_OFF) ? "off":"on",
print_mhz(clk_get_rate(&clk_upll)),
print_mhz(clk_get_rate(&clk_usb_bus)));
/* register clocks from clock array */
clkp = init_clocks;
for (ptr = 0; ptr < ARRAY_SIZE(init_clocks); ptr++, clkp++) {
/* ensure that we note the clock state */
clkp->usage = clkcon & clkp->ctrlbit ? 1 : 0;
ret = s3c24xx_register_clock(clkp);
if (ret < 0) {
printk(KERN_ERR "Failed to register clock %s (%d)\n",
clkp->name, ret);
}
}
/* We must be careful disabling the clocks we are not intending to
* be using at boot time, as subsystems such as the LCD which do
* their own DMA requests to the bus can cause the system to lockup
* if they where in the middle of requesting bus access.
*
* Disabling the LCD clock if the LCD is active is very dangerous,
* and therefore the bootloader should be careful to not enable
* the LCD clock if it is not needed.
*/
/* install (and disable) the clocks we do not need immediately */
clkp = init_clocks_disable;
for (ptr = 0; ptr < ARRAY_SIZE(init_clocks_disable); ptr++, clkp++) {
ret = s3c24xx_register_clock(clkp);
if (ret < 0) {
printk(KERN_ERR "Failed to register clock %s (%d)\n",
clkp->name, ret);
}
s3c2412_clkcon_enable(clkp, 0);
}
clkdev_add_table(s3c2412_clk_lookup, ARRAY_SIZE(s3c2412_clk_lookup));
return 0;
}