mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-20 18:54:09 +08:00
dfc1ebe766
Both platforms had some initial device tree support, but this adds much more to actually make it usable. This is where the really nasty conflicts in the samsung platform start, due to some files getting moved around and combined in the 'restart' branch that has already gone into mainline through Russell's tree. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.11 (GNU/Linux) iQIVAwUATwtUpWCrR//JCVInAQI7bhAA1Q8MXyQ3EwLKMWX2p0vmbb29Nugoq0Y3 u9pBlCqiz0zw/jccPWASCgVgMVYguZLuhvMRCO8Q1D4l3ljcTt7qhtN6lBAESz2N OTTaNU2T84Um2Watm7VAQrnLcJMhxd/wFV06lmE62SgxwIVzyqxo4sr3KB3S5Qyj W3q5wRLuc5pC293HkWSNpLj3nfcKFF2oHOFpEAC5AS/C5S38Eu/T9y4FSUGvoTq4 u7xlZT11uZUTRfvkRQUTOXkh9I0Fk0JuwUpUkqhgvM4jD0Ehs60/702CX4mPAoVd +BFUI23QNSof6O04rUxEzOSt1ZNg4Le+pQZ3vUcOvi539Npq+VgzDU+yo7uzNtYv c22VJihvS9GY2s7ynmmCE6Rgw17B3VOMMy1cBbQEET2V2GwgU9lQLx2eR/bUrOGq ewcTCqgFFWVugsGsn0wM0BiPZAJ+FddXon3w3X09BM0v5a6O6q0aUAQiJnGqDgUE ZLHhYRoL87r2TU6J+3iutK3sDHQrvHkGAZdXX3H5hVWdfLWqnwGgLjT/NpBeUaWc g6nut7pFgVDCD4q4JUCa99XykgKGWRtSHAuHmJQsdZ24PzpXmse3etVZTCYwr7t6 BM3zrozoecQbGTRwZKGb9poOKd7g7xJ7125770GqYgTeX+BnBcA2lIEDAkEKsLBR GaxJggw32Q0= =XY2N -----END PGP SIGNATURE----- Merge tag 'dt' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc Device tree conversions for samsung and tegra Both platforms had some initial device tree support, but this adds much more to actually make it usable. * tag 'dt' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (45 commits) ARM: dts: Add intial dts file for EXYNOS4210 SoC, SMDKV310 and ORIGEN ARM: EXYNOS: Add Exynos4 device tree enabled board file rtc: rtc-s3c: Add device tree support input: samsung-keypad: Add device tree support ARM: S5PV210: Modify platform data for pl330 driver ARM: S5PC100: Modify platform data for pl330 driver ARM: S5P64x0: Modify platform data for pl330 driver ARM: EXYNOS: Add a alias for pdma clocks ARM: EXYNOS: Limit usage of pl330 device instance to non-dt build ARM: SAMSUNG: Add device tree support for pl330 dma engine wrappers DMA: PL330: Add device tree support ARM: EXYNOS: Modify platform data for pl330 driver DMA: PL330: Infer transfer direction from transfer request instead of platform data DMA: PL330: move filter function into driver serial: samsung: Fix build for non-Exynos4210 devices serial: samsung: add device tree support serial: samsung: merge probe() function from all SoC specific extensions serial: samsung: merge all SoC specific port reset functions ARM: SAMSUNG: register uart clocks to clock lookup list serial: samsung: remove all uses of get_clksrc and set_clksrc ... Fix up fairly trivial conflicts in arch/arm/mach-s3c2440/clock.c and drivers/tty/serial/Kconfig both due to just adding code close to changes.
764 lines
18 KiB
C
764 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/s3c2412.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",
|
|
.parent = &clk_h,
|
|
.enable = s3c2412_clkcon_enable,
|
|
.ctrlbit = S3C2412_CLKCON_DMA0,
|
|
}, {
|
|
.name = "dma",
|
|
.parent = &clk_h,
|
|
.enable = s3c2412_clkcon_enable,
|
|
.ctrlbit = S3C2412_CLKCON_DMA1,
|
|
}, {
|
|
.name = "dma",
|
|
.parent = &clk_h,
|
|
.enable = s3c2412_clkcon_enable,
|
|
.ctrlbit = S3C2412_CLKCON_DMA2,
|
|
}, {
|
|
.name = "dma",
|
|
.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));
|
|
s3c_pwmclk_init();
|
|
return 0;
|
|
}
|