/* linux/arch/arm/mach-s3c64xx/clock_s3c6400.c * * Copyright (c) 2004 Simtec Electronics * Ben Dooks * * S3C2412 Clock control support * * Based on, and code from linux/arch/arm/mach-versatile/clock.c ** ** Copyright (C) 2004 ARM Limited. ** Written by Deep Blue Solutions Limited. * * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HCLK_GATING_ON_LIST S3C_CLKCON_HCLK_BUS | S3C_CLKCON_HCLK_DDR1 | S3C_CLKCON_HCLK_DDR0 |\ S3C_CLKCON_HCLK_MEM1 | S3C_CLKCON_HCLK_MEM0 | S3C_CLKCON_HCLK_DMA0 |\ S3C_CLKCON_HCLK_DMA1 | S3C_CLKCON_HCLK_INTC | S3C_CLKCON_HCLK_LCD |\ S3C_CLKCON_HCLK_DHOST | S3C_CLKCON_HCLK_POST0 | S3C_CLKCON_HCLK_MFC #define PCLK_GATING_ON_LIST S3C_CLKCON_PCLK_GPIO | S3C_CLKCON_PCLK_UART0 | S3C_CLKCON_PCLK_UART1 | S3C_CLKCON_PCLK_MFC #define SCLK_GATING_ON_LIST S3C_CLKCON_SCLK_UART | S3C_CLKCON_SCLK_LCD27 | S3C_CLKCON_SCLK_LCD | S3C_CLKCON_SCLK_POST1_27 | \ S3C_CLKCON_SCLK_POST0_27 | S3C_CLKCON_SCLK_POST1 | S3C_CLKCON_SCLK_POST0 | S3C_CLKCON_SCLK_MFC /* Select the source clock of USB Host. 0:48MHz, 1:FoutEpll */ #define S3C_USB_CLKSRC_EPLL 0 /* old functions */ void inline s3c_clk_enable (uint clocks, uint enable, ulong gate_reg) { unsigned long clkcon; unsigned long flags; local_irq_save(flags); clkcon = __raw_readl(gate_reg); clkcon &= ~clocks; if (enable) clkcon |= clocks; __raw_writel(clkcon, gate_reg); local_irq_restore(flags); } /*This function returns the virtual address of gating register*/ ulong clk_get_gate_reg (struct clk *clk) { struct clk *parent_clk = clk_get_parent(clk) ; if (strcmp(parent_clk->name, "hclk") == 0) return (unsigned long) S3C_HCLK_GATE; else if (strcmp(parent_clk->name, "pclk") == 0) return (unsigned long) S3C_PCLK_GATE; else if (strcmp(parent_clk->name, "clk48m") == 0) return (unsigned long) S3C_OTHERS; else return (unsigned long) S3C_SCLK_GATE; } int s3c_clkcon_enable (struct clk *clk, int enable) { unsigned long gate_reg; gate_reg = clk_get_gate_reg(clk); s3c_clk_enable(clk->ctrlbit, enable, gate_reg); return 0; } static unsigned long s3c6400_clk_getrate(struct clk *clk) { /* set clock rate to use in drivers */ if (!clk->rate) { if (clk->parent) { clk->rate = clk->parent->rate; } } return clk->rate; } #define CAMDIV_val 20 static int s3c6400_setrate_camera(struct clk *clk, unsigned long rate) { unsigned int camclk_div, val; unsigned long src_clk = clk_get_rate(clk->parent); if (rate == 4800000) { printk(KERN_INFO "External camera clock is set to 48MHz\n"); } else if (rate > 48000000) { printk(KERN_ERR "Invalid camera clock\n"); } camclk_div = src_clk / rate; printk("Parent clock for Camera = %ld, CAMDIV = %d\n", src_clk, camclk_div); val = readl(S3C_CLK_DIV0); val &= ~(0xf<ctrlbit, enable, gate_reg); /* MUXepll : FOUTepll */ writel(readl(S3C_CLK_SRC)|S3C_CLKSRC_EPLL_CLKSEL, S3C_CLK_SRC); /* UARTsel : MOUTepll */ writel((readl(S3C_CLK_SRC) & ~(0x1 << 13)) | (0 << 13), S3C_CLK_SRC); return 0; } static int s3c6400_setrate_epll_clk_192m(struct clk *clk, unsigned long rate) { writel(S3C_EPLL_EN|S3C_EPLLVAL(32,1,1), S3C_EPLL_CON0); writel(0, S3C_EPLL_CON1); mdelay(5); return 0; } static unsigned long s3c6400_getrate_epll_clk_192m(struct clk *clk) { s3c6400_setrate_epll_clk_192m(clk, 192000000); clk->rate = 192000000; return clk->rate; } static struct clk clk_epll_uart_192m = { .name = "epll_clk_uart_192m", .id = -1, .parent = &clk_epll, .enable = s3c_epll_clk_uart_enable, .ctrlbit = S3C_CLKCON_SCLK_UART, .set_rate = s3c6400_setrate_epll_clk_192m, .get_rate = s3c6400_getrate_epll_clk_192m, }; /* function for UART Clock with MPLL by boyko */ static unsigned long s3c6400_mpll_get_clk(struct clk *clk) { unsigned long mpll_con; unsigned long m = 0; unsigned long p = 0; unsigned long s = 0; unsigned long ret; mpll_con = (readl(S3C_MPLL_CON)); m = (mpll_con >> 16) & 0x3ff; p = (mpll_con >> 8) & 0x3f; s = mpll_con & 0x3; ret = (m * (12000000 / (p * (1 << s)))); return (((readl(S3C_CLK_DIV0) >> 13 ) & 0x1) ? (ret / 2) : ret ); } static int s3c6400_mpll_clk_uart_enable (struct clk *clk, int enable) { unsigned long gate_reg; gate_reg = clk_get_gate_reg(clk); s3c_clk_enable(clk->ctrlbit, enable, gate_reg); writel(readl(S3C_CLK_SRC)|S3C_CLKSRC_EPLL_CLKSEL, S3C_CLK_SRC); writel((readl(S3C_CLK_SRC) & ~(0x1 << 13)) | (1 << 13), S3C_CLK_SRC); return 0; } static int s3c6400_setrate_mpll_clk(struct clk *clk, unsigned long rate) { writel(readl(S3C_CLK_DIV2) & ~(0xf << 16) , S3C_CLK_DIV2); return 0; } static unsigned long s3c6400_getrate_mpll_clk(struct clk *clk) { s3c6400_setrate_mpll_clk(clk,0); return s3c6400_mpll_get_clk(clk); } static struct clk clk_mpll_uart = { .name = "mpll_clk_uart", .id = -1, .parent = &clk_mpll, .enable = s3c6400_mpll_clk_uart_enable, .ctrlbit = S3C_CLKCON_SCLK_UART, .set_rate = s3c6400_setrate_mpll_clk, .get_rate = s3c6400_getrate_mpll_clk, }; /* clocks to add straight away */ static struct clk *clks[] __initdata = { &clk_epll_uart_192m, &clk_mpll_uart, }; void __init s3c6400_init_clocks(int xtal) { unsigned long clkdiv0; unsigned long fclk, fclk_org; unsigned long hclkx2; unsigned long hclk; unsigned long pclk; unsigned long epll_clk; struct clk *clkp; int ret; int ptr; clk_xtal.rate = xtal; /* now we've got our machine bits initialised, work out what * clocks we've got */ /* Changed below lines to support s3c6400 - JaeCheol Lee */ fclk = s3c6400_get_pll(__raw_readl(S3C_APLL_CON), xtal); clkdiv0 = __raw_readl(S3C_CLK_DIV0); fclk /= ((clkdiv0 & S3C_CLKDIVN_APLL_MASK)+1); fclk_org = fclk; if(!(__raw_readl(S3C_OTHERS) & 0x80)) { fclk = s3c6400_get_pll(__raw_readl(S3C_MPLL_CON), xtal); } hclkx2 = fclk / (((clkdiv0 & S3C_CLKDIVN_HCLKX2_MASK)>>9)+1); hclk = hclkx2 / (((clkdiv0 & S3C_CLKDIVN_HCLK_MASK)>>8)+1); //it should get the divisor from register and then sets the pclk properly pclk = hclkx2 / (((clkdiv0 & S3C_CLKDIVN_PCLK_MASK)>>12)+1); /* print brief summary of clocks, etc */ printk("S3C6400: core %ld.%03ld MHz, memory %ld.%03ld MHz, peripheral %ld.%03ld MHz\n", print_mhz(fclk_org), print_mhz(hclk), print_mhz(pclk)); /* Default EPLL frequency : 192Mhz */ writel(S3C_EPLL_EN|S3C_EPLLVAL(32,1,1), S3C_EPLL_CON0); writel(0, S3C_EPLL_CON1); mdelay(50); epll_clk = s3c6400_get_epll(xtal); clk_epll.rate = epll_clk; printk("S3C6400: EPLL %ld.%03ld MHz\n", print_mhz(epll_clk)); /* initialize hclkx2 by jsgood */ clk_hx2.rate = hclkx2; /* initialise the clocks here, to allow other things like the * console to use them, and to add new ones after the initialisation */ s3c24xx_setup_clocks(xtal, fclk_org, hclk, pclk); 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); } } /* register clocks from clock array */ clkp = init_clocks; for (ptr = 0; ptr < ARRAY_SIZE(init_clocks); ptr++, clkp++) { ret = s3c24xx_register_clock(clkp); if (ret < 0) { printk(KERN_ERR "Failed to register clock %s (%d)\n", clkp->name, ret); } /* set clock rate to use in drivers */ if (!clkp->rate) { if (clkp->parent) { clkp->rate = clkp->parent->rate; } } } #ifdef CONFIG_USB_OHCI_HCD if (S3C_USB_CLKSRC_EPLL == 1) { writel((readl(S3C_CLK_SRC)& ~S3C_CLKSRC_UHOST_MASK) |S3C_CLKSRC_EPLL_CLKSEL|S3C_CLKSRC_UHOST_EPLL, S3C_CLK_SRC); /* USB host colock divider ratio is 2 */ writel((readl(S3C_CLK_DIV1)& ~S3C_CLKDIVN_UHOST_MASK) |S3C_CLKDIV1_USBDIV2, S3C_CLK_DIV1); } else { /* 48MHz */ writel(readl(S3C_CLK_SRC)& ~S3C_CLKSRC_UHOST_MASK, S3C_CLK_SRC); writel(readl(S3C_OTHERS)|S3C_OTHERS_USB_SIG_MASK, S3C_OTHERS); /* USB host colock divider ratio is 1 */ writel(readl(S3C_CLK_DIV1)& ~S3C_CLKDIVN_UHOST_MASK, S3C_CLK_DIV1); } writel(readl(S3C_HCLK_GATE)|S3C_CLKCON_HCLK_UHOST|S3C_CLKCON_HCLK_SECUR, S3C_HCLK_GATE); writel(readl(S3C_SCLK_GATE)|S3C_CLKCON_SCLK_UHOST, S3C_SCLK_GATE); #endif /* We must be careful disabling the clocks we are not intending to * be using at boot time, as subsytems 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); } (clkp->enable)(clkp, 0); } } /* clocks that could be registered by external code */ struct clk s3c_dclk0 = { .name = "dclk0", .id = -1, }; struct clk s3c_dclk1 = { .name = "dclk1", .id = -1, }; struct clk s3c_clkout0 = { .name = "clkout0", .id = -1, }; struct clk s3c_clkout1 = { .name = "clkout1", .id = -1, };