diff options
author | Olof Johansson <olof@lixom.net> | 2014-05-21 22:28:26 -0700 |
---|---|---|
committer | Olof Johansson <olof@lixom.net> | 2014-05-21 22:28:26 -0700 |
commit | 28d1079c9cc7dd37ce9397d559b5ceef8a66b1db (patch) | |
tree | 6ed3c93d9d93ae4990be8c1bfbb4ab9be757f9f2 /drivers/clk | |
parent | 486ad2ede13314346226ee52e92b8e8773221f63 (diff) | |
parent | 34c453ce16633539a94a2e876faeb731ac1be899 (diff) |
Merge tag 's3c24xx-clk' of http://git.kernel.org/pub/scm/linux/kernel/git/kgene/linux-samsung into next/cleanup
Merge "Samsung S3C24XX updates for 3.16" from Kukjin Kim:
Samsung S3C24XX to use the common clock framework
- S3C2412, S3C2413, S3C2416 and S3C2443 to use CCF
- S3C2410, S3C2440, S3C2442 to use CCF
- Remove legacy samsung clock from mach-s3c24xx/
- Some of them are missed from previous pull-request
- Clock related sutff got ack from Mike and Tomasz
- Created the last commit due to missing changes
during re-sorting because this branch is provided
as a base to samsung clk tree.
* tag 's3c24xx-clk' of http://git.kernel.org/pub/scm/linux/kernel/git/kgene/linux-samsung: (23 commits)
ARM: S3C24XX: fix merge conflict
ARM: S3C24XX: remove SAMSUNG_CLOCK remnants after ccf conversion
ARM: S3C24XX: remove legacy clock code
ARM: S3C24XX: convert s3c2410 to common clock framework
ARM: S3C24XX: convert s3c2440 and s3c2442 to common clock framework
ARM: S3C24XX: add platform code for conversion to the common clock framework
clk: samsung: add clock controller driver for s3c2410, s3c2440 and s3c2442
dt-bindings: add documentation for s3c2410 clock controller
ARM: S3C24XX: enable usage of common dclk if common clock framework is enabled
clk: samsung: add clock driver for external clock outputs
ARM: S3C24XX: cpufreq-utils: don't write raw values to MPLLCON when using ccf
ARM: S3C24XX: convert s3c2412 to common clock framework
clk: samsung: add clock controller driver for s3c2412
dt-bindings: add documentation for s3c2412 clock controller
clk: samsung: add plls used by the early s3c24xx cpus
ARM: S3C24XX: only store clock registers when old clock code is active
ARM: S3C24XX: Convert s3c2416 and s3c2443 to common clock framework
ARM: dts: add clock data for s3c2416
ARM: S3C24XX: prevent conflicts between ccf and non-ccf s3c24xx-socs
clk: samsung: add clock-driver for s3c2416, s3c2443 and s3c2450
...
Signed-off-by: Olof Johansson <olof@lixom.net>
Diffstat (limited to 'drivers/clk')
-rw-r--r-- | drivers/clk/samsung/Makefile | 4 | ||||
-rw-r--r-- | drivers/clk/samsung/clk-pll.c | 266 | ||||
-rw-r--r-- | drivers/clk/samsung/clk-pll.h | 6 | ||||
-rw-r--r-- | drivers/clk/samsung/clk-s3c2410-dclk.c | 440 | ||||
-rw-r--r-- | drivers/clk/samsung/clk-s3c2410.c | 477 | ||||
-rw-r--r-- | drivers/clk/samsung/clk-s3c2412.c | 269 | ||||
-rw-r--r-- | drivers/clk/samsung/clk-s3c2443.c | 462 |
7 files changed, 1922 insertions, 2 deletions
diff --git a/drivers/clk/samsung/Makefile b/drivers/clk/samsung/Makefile index 8eb4799237f0..2cb62f87e068 100644 --- a/drivers/clk/samsung/Makefile +++ b/drivers/clk/samsung/Makefile @@ -8,4 +8,8 @@ obj-$(CONFIG_SOC_EXYNOS5250) += clk-exynos5250.o obj-$(CONFIG_SOC_EXYNOS5420) += clk-exynos5420.o obj-$(CONFIG_SOC_EXYNOS5440) += clk-exynos5440.o obj-$(CONFIG_ARCH_EXYNOS) += clk-exynos-audss.o +obj-$(CONFIG_S3C2410_COMMON_CLK)+= clk-s3c2410.o +obj-$(CONFIG_S3C2410_COMMON_DCLK)+= clk-s3c2410-dclk.o +obj-$(CONFIG_S3C2412_COMMON_CLK)+= clk-s3c2412.o +obj-$(CONFIG_S3C2443_COMMON_CLK)+= clk-s3c2443.o obj-$(CONFIG_ARCH_S3C64XX) += clk-s3c64xx.o diff --git a/drivers/clk/samsung/clk-pll.c b/drivers/clk/samsung/clk-pll.c index 81e6d2f49aa0..7fb0a28e65d5 100644 --- a/drivers/clk/samsung/clk-pll.c +++ b/drivers/clk/samsung/clk-pll.c @@ -11,6 +11,7 @@ #include <linux/errno.h> #include <linux/hrtimer.h> +#include <linux/delay.h> #include "clk.h" #include "clk-pll.h" @@ -59,6 +60,72 @@ static long samsung_pll_round_rate(struct clk_hw *hw, } /* + * PLL2126 Clock Type + */ + +#define PLL2126_MDIV_MASK (0xff) +#define PLL2126_PDIV_MASK (0x3f) +#define PLL2126_SDIV_MASK (0x3) +#define PLL2126_MDIV_SHIFT (16) +#define PLL2126_PDIV_SHIFT (8) +#define PLL2126_SDIV_SHIFT (0) + +static unsigned long samsung_pll2126_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct samsung_clk_pll *pll = to_clk_pll(hw); + u32 pll_con, mdiv, pdiv, sdiv; + u64 fvco = parent_rate; + + pll_con = __raw_readl(pll->con_reg); + mdiv = (pll_con >> PLL2126_MDIV_SHIFT) & PLL2126_MDIV_MASK; + pdiv = (pll_con >> PLL2126_PDIV_SHIFT) & PLL2126_PDIV_MASK; + sdiv = (pll_con >> PLL2126_SDIV_SHIFT) & PLL2126_SDIV_MASK; + + fvco *= (mdiv + 8); + do_div(fvco, (pdiv + 2) << sdiv); + + return (unsigned long)fvco; +} + +static const struct clk_ops samsung_pll2126_clk_ops = { + .recalc_rate = samsung_pll2126_recalc_rate, +}; + +/* + * PLL3000 Clock Type + */ + +#define PLL3000_MDIV_MASK (0xff) +#define PLL3000_PDIV_MASK (0x3) +#define PLL3000_SDIV_MASK (0x3) +#define PLL3000_MDIV_SHIFT (16) +#define PLL3000_PDIV_SHIFT (8) +#define PLL3000_SDIV_SHIFT (0) + +static unsigned long samsung_pll3000_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct samsung_clk_pll *pll = to_clk_pll(hw); + u32 pll_con, mdiv, pdiv, sdiv; + u64 fvco = parent_rate; + + pll_con = __raw_readl(pll->con_reg); + mdiv = (pll_con >> PLL3000_MDIV_SHIFT) & PLL3000_MDIV_MASK; + pdiv = (pll_con >> PLL3000_PDIV_SHIFT) & PLL3000_PDIV_MASK; + sdiv = (pll_con >> PLL3000_SDIV_SHIFT) & PLL3000_SDIV_MASK; + + fvco *= (2 * (mdiv + 8)); + do_div(fvco, pdiv << sdiv); + + return (unsigned long)fvco; +} + +static const struct clk_ops samsung_pll3000_clk_ops = { + .recalc_rate = samsung_pll3000_recalc_rate, +}; + +/* * PLL35xx Clock Type */ /* Maximum lock time can be 270 * PDIV cycles */ @@ -564,7 +631,9 @@ static const struct clk_ops samsung_pll46xx_clk_min_ops = { #define PLL6552_PDIV_MASK 0x3f #define PLL6552_SDIV_MASK 0x7 #define PLL6552_MDIV_SHIFT 16 +#define PLL6552_MDIV_SHIFT_2416 14 #define PLL6552_PDIV_SHIFT 8 +#define PLL6552_PDIV_SHIFT_2416 5 #define PLL6552_SDIV_SHIFT 0 static unsigned long samsung_pll6552_recalc_rate(struct clk_hw *hw, @@ -575,8 +644,13 @@ static unsigned long samsung_pll6552_recalc_rate(struct clk_hw *hw, u64 fvco = parent_rate; pll_con = __raw_readl(pll->con_reg); - mdiv = (pll_con >> PLL6552_MDIV_SHIFT) & PLL6552_MDIV_MASK; - pdiv = (pll_con >> PLL6552_PDIV_SHIFT) & PLL6552_PDIV_MASK; + if (pll->type == pll_6552_s3c2416) { + mdiv = (pll_con >> PLL6552_MDIV_SHIFT_2416) & PLL6552_MDIV_MASK; + pdiv = (pll_con >> PLL6552_PDIV_SHIFT_2416) & PLL6552_PDIV_MASK; + } else { + mdiv = (pll_con >> PLL6552_MDIV_SHIFT) & PLL6552_MDIV_MASK; + pdiv = (pll_con >> PLL6552_PDIV_SHIFT) & PLL6552_PDIV_MASK; + } sdiv = (pll_con >> PLL6552_SDIV_SHIFT) & PLL6552_SDIV_MASK; fvco *= mdiv; @@ -628,6 +702,169 @@ static const struct clk_ops samsung_pll6553_clk_ops = { }; /* + * PLL Clock Type of S3C24XX before S3C2443 + */ + +#define PLLS3C2410_MDIV_MASK (0xff) +#define PLLS3C2410_PDIV_MASK (0x1f) +#define PLLS3C2410_SDIV_MASK (0x3) +#define PLLS3C2410_MDIV_SHIFT (12) +#define PLLS3C2410_PDIV_SHIFT (4) +#define PLLS3C2410_SDIV_SHIFT (0) + +#define PLLS3C2410_ENABLE_REG_OFFSET 0x10 + +static unsigned long samsung_s3c2410_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct samsung_clk_pll *pll = to_clk_pll(hw); + u32 pll_con, mdiv, pdiv, sdiv; + u64 fvco = parent_rate; + + pll_con = __raw_readl(pll->con_reg); + mdiv = (pll_con >> PLLS3C2410_MDIV_SHIFT) & PLLS3C2410_MDIV_MASK; + pdiv = (pll_con >> PLLS3C2410_PDIV_SHIFT) & PLLS3C2410_PDIV_MASK; + sdiv = (pll_con >> PLLS3C2410_SDIV_SHIFT) & PLLS3C2410_SDIV_MASK; + + fvco *= (mdiv + 8); + do_div(fvco, (pdiv + 2) << sdiv); + + return (unsigned int)fvco; +} + +static unsigned long samsung_s3c2440_mpll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct samsung_clk_pll *pll = to_clk_pll(hw); + u32 pll_con, mdiv, pdiv, sdiv; + u64 fvco = parent_rate; + + pll_con = __raw_readl(pll->con_reg); + mdiv = (pll_con >> PLLS3C2410_MDIV_SHIFT) & PLLS3C2410_MDIV_MASK; + pdiv = (pll_con >> PLLS3C2410_PDIV_SHIFT) & PLLS3C2410_PDIV_MASK; + sdiv = (pll_con >> PLLS3C2410_SDIV_SHIFT) & PLLS3C2410_SDIV_MASK; + + fvco *= (2 * (mdiv + 8)); + do_div(fvco, (pdiv + 2) << sdiv); + + return (unsigned int)fvco; +} + +static int samsung_s3c2410_pll_set_rate(struct clk_hw *hw, unsigned long drate, + unsigned long prate) +{ + struct samsung_clk_pll *pll = to_clk_pll(hw); + const struct samsung_pll_rate_table *rate; + u32 tmp; + + /* Get required rate settings from table */ + rate = samsung_get_pll_settings(pll, drate); + if (!rate) { + pr_err("%s: Invalid rate : %lu for pll clk %s\n", __func__, + drate, __clk_get_name(hw->clk)); + return -EINVAL; + } + + tmp = __raw_readl(pll->con_reg); + + /* Change PLL PMS values */ + tmp &= ~((PLLS3C2410_MDIV_MASK << PLLS3C2410_MDIV_SHIFT) | + (PLLS3C2410_PDIV_MASK << PLLS3C2410_PDIV_SHIFT) | + (PLLS3C2410_SDIV_MASK << PLLS3C2410_SDIV_SHIFT)); + tmp |= (rate->mdiv << PLLS3C2410_MDIV_SHIFT) | + (rate->pdiv << PLLS3C2410_PDIV_SHIFT) | + (rate->sdiv << PLLS3C2410_SDIV_SHIFT); + __raw_writel(tmp, pll->con_reg); + + /* Time to settle according to the manual */ + udelay(300); + + return 0; +} + +static int samsung_s3c2410_pll_enable(struct clk_hw *hw, int bit, bool enable) +{ + struct samsung_clk_pll *pll = to_clk_pll(hw); + u32 pll_en = __raw_readl(pll->lock_reg + PLLS3C2410_ENABLE_REG_OFFSET); + u32 pll_en_orig = pll_en; + + if (enable) + pll_en &= ~BIT(bit); + else + pll_en |= BIT(bit); + + __raw_writel(pll_en, pll->lock_reg + PLLS3C2410_ENABLE_REG_OFFSET); + + /* if we started the UPLL, then allow to settle */ + if (enable && (pll_en_orig & BIT(bit))) + udelay(300); + + return 0; +} + +static int samsung_s3c2410_mpll_enable(struct clk_hw *hw) +{ + return samsung_s3c2410_pll_enable(hw, 5, true); +} + +static void samsung_s3c2410_mpll_disable(struct clk_hw *hw) +{ + samsung_s3c2410_pll_enable(hw, 5, false); +} + +static int samsung_s3c2410_upll_enable(struct clk_hw *hw) +{ + return samsung_s3c2410_pll_enable(hw, 7, true); +} + +static void samsung_s3c2410_upll_disable(struct clk_hw *hw) +{ + samsung_s3c2410_pll_enable(hw, 7, false); +} + +static const struct clk_ops samsung_s3c2410_mpll_clk_min_ops = { + .recalc_rate = samsung_s3c2410_pll_recalc_rate, + .enable = samsung_s3c2410_mpll_enable, + .disable = samsung_s3c2410_mpll_disable, +}; + +static const struct clk_ops samsung_s3c2410_upll_clk_min_ops = { + .recalc_rate = samsung_s3c2410_pll_recalc_rate, + .enable = samsung_s3c2410_upll_enable, + .disable = samsung_s3c2410_upll_disable, +}; + +static const struct clk_ops samsung_s3c2440_mpll_clk_min_ops = { + .recalc_rate = samsung_s3c2440_mpll_recalc_rate, + .enable = samsung_s3c2410_mpll_enable, + .disable = samsung_s3c2410_mpll_disable, +}; + +static const struct clk_ops samsung_s3c2410_mpll_clk_ops = { + .recalc_rate = samsung_s3c2410_pll_recalc_rate, + .enable = samsung_s3c2410_mpll_enable, + .disable = samsung_s3c2410_mpll_disable, + .round_rate = samsung_pll_round_rate, + .set_rate = samsung_s3c2410_pll_set_rate, +}; + +static const struct clk_ops samsung_s3c2410_upll_clk_ops = { + .recalc_rate = samsung_s3c2410_pll_recalc_rate, + .enable = samsung_s3c2410_upll_enable, + .disable = samsung_s3c2410_upll_disable, + .round_rate = samsung_pll_round_rate, + .set_rate = samsung_s3c2410_pll_set_rate, +}; + +static const struct clk_ops samsung_s3c2440_mpll_clk_ops = { + .recalc_rate = samsung_s3c2440_mpll_recalc_rate, + .enable = samsung_s3c2410_mpll_enable, + .disable = samsung_s3c2410_mpll_disable, + .round_rate = samsung_pll_round_rate, + .set_rate = samsung_s3c2410_pll_set_rate, +}; + +/* * PLL2550x Clock Type */ @@ -746,6 +983,12 @@ static void __init _samsung_clk_register_pll(struct samsung_pll_clock *pll_clk, } switch (pll_clk->type) { + case pll_2126: + init.ops = &samsung_pll2126_clk_ops; + break; + case pll_3000: + init.ops = &samsung_pll3000_clk_ops; + break; /* clk_ops for 35xx and 2550 are similar */ case pll_35xx: case pll_2550: @@ -773,6 +1016,7 @@ static void __init _samsung_clk_register_pll(struct samsung_pll_clock *pll_clk, init.ops = &samsung_pll36xx_clk_ops; break; case pll_6552: + case pll_6552_s3c2416: init.ops = &samsung_pll6552_clk_ops; break; case pll_6553: @@ -786,6 +1030,24 @@ static void __init _samsung_clk_register_pll(struct samsung_pll_clock *pll_clk, else init.ops = &samsung_pll46xx_clk_ops; break; + case pll_s3c2410_mpll: + if (!pll->rate_table) + init.ops = &samsung_s3c2410_mpll_clk_min_ops; + else + init.ops = &samsung_s3c2410_mpll_clk_ops; + break; + case pll_s3c2410_upll: + if (!pll->rate_table) + init.ops = &samsung_s3c2410_upll_clk_min_ops; + else + init.ops = &samsung_s3c2410_upll_clk_ops; + break; + case pll_s3c2440_mpll: + if (!pll->rate_table) + init.ops = &samsung_s3c2440_mpll_clk_min_ops; + else + init.ops = &samsung_s3c2440_mpll_clk_ops; + break; default: pr_warn("%s: Unknown pll type for pll clk %s\n", __func__, pll_clk->name); diff --git a/drivers/clk/samsung/clk-pll.h b/drivers/clk/samsung/clk-pll.h index 6c39030080fb..6428bcc6df6f 100644 --- a/drivers/clk/samsung/clk-pll.h +++ b/drivers/clk/samsung/clk-pll.h @@ -13,6 +13,8 @@ #define __SAMSUNG_CLK_PLL_H enum samsung_pll_type { + pll_2126, + pll_3000, pll_35xx, pll_36xx, pll_2550, @@ -24,7 +26,11 @@ enum samsung_pll_type { pll_4650, pll_4650c, pll_6552, + pll_6552_s3c2416, pll_6553, + pll_s3c2410_mpll, + pll_s3c2410_upll, + pll_s3c2440_mpll, }; #define PLL_35XX_RATE(_rate, _m, _p, _s) \ diff --git a/drivers/clk/samsung/clk-s3c2410-dclk.c b/drivers/clk/samsung/clk-s3c2410-dclk.c new file mode 100644 index 000000000000..8d8dff005c10 --- /dev/null +++ b/drivers/clk/samsung/clk-s3c2410-dclk.c @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2013 Heiko Stuebner <heiko@sntech.de> + * + * 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. + * + * Common Clock Framework support for s3c24xx external clock output. + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include "clk.h" + +/* legacy access to misccr, until dt conversion is finished */ +#include <mach/hardware.h> +#include <mach/regs-gpio.h> + +#define MUX_DCLK0 0 +#define MUX_DCLK1 1 +#define DIV_DCLK0 2 +#define DIV_DCLK1 3 +#define GATE_DCLK0 4 +#define GATE_DCLK1 5 +#define MUX_CLKOUT0 6 +#define MUX_CLKOUT1 7 +#define DCLK_MAX_CLKS (MUX_CLKOUT1 + 1) + +enum supported_socs { + S3C2410, + S3C2412, + S3C2440, + S3C2443, +}; + +struct s3c24xx_dclk_drv_data { + const char **clkout0_parent_names; + int clkout0_num_parents; + const char **clkout1_parent_names; + int clkout1_num_parents; + const char **mux_parent_names; + int mux_num_parents; +}; + +/* + * Clock for output-parent selection in misccr + */ + +struct s3c24xx_clkout { + struct clk_hw hw; + u32 mask; + u8 shift; +}; + +#define to_s3c24xx_clkout(_hw) container_of(_hw, struct s3c24xx_clkout, hw) + +static u8 s3c24xx_clkout_get_parent(struct clk_hw *hw) +{ + struct s3c24xx_clkout *clkout = to_s3c24xx_clkout(hw); + int num_parents = __clk_get_num_parents(hw->clk); + u32 val; + + val = readl_relaxed(S3C24XX_MISCCR) >> clkout->shift; + val >>= clkout->shift; + val &= clkout->mask; + + if (val >= num_parents) + return -EINVAL; + + return val; +} + +static int s3c24xx_clkout_set_parent(struct clk_hw *hw, u8 index) +{ + struct s3c24xx_clkout *clkout = to_s3c24xx_clkout(hw); + int ret = 0; + + s3c2410_modify_misccr((clkout->mask << clkout->shift), + (index << clkout->shift)); + + return ret; +} + +const struct clk_ops s3c24xx_clkout_ops = { + .get_parent = s3c24xx_clkout_get_parent, + .set_parent = s3c24xx_clkout_set_parent, + .determine_rate = __clk_mux_determine_rate, +}; + +struct clk *s3c24xx_register_clkout(struct device *dev, const char *name, + const char **parent_names, u8 num_parents, + u8 shift, u32 mask) +{ + struct s3c24xx_clkout *clkout; + struct clk *clk; + struct clk_init_data init; + + /* allocate the clkout */ + clkout = kzalloc(sizeof(*clkout), GFP_KERNEL); + if (!clkout) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &s3c24xx_clkout_ops; + init.flags = CLK_IS_BASIC; + init.parent_names = parent_names; + init.num_parents = num_parents; + + clkout->shift = shift; + clkout->mask = mask; + clkout->hw.init = &init; + + clk = clk_register(dev, &clkout->hw); + + return clk; +} + +/* + * dclk and clkout init + */ + +struct s3c24xx_dclk { + struct device *dev; + void __iomem *base; + struct clk_onecell_data clk_data; + struct notifier_block dclk0_div_change_nb; + struct notifier_block dclk1_div_change_nb; + spinlock_t dclk_lock; + unsigned long reg_save; +}; + +#define to_s3c24xx_dclk0(x) \ + container_of(x, struct s3c24xx_dclk, dclk0_div_change_nb) + +#define to_s3c24xx_dclk1(x) \ + container_of(x, struct s3c24xx_dclk, dclk1_div_change_nb) + +PNAME(dclk_s3c2410_p) = { "pclk", "uclk" }; +PNAME(clkout0_s3c2410_p) = { "mpll", "upll", "fclk", "hclk", "pclk", + "gate_dclk0" }; +PNAME(clkout1_s3c2410_p) = { "mpll", "upll", "fclk", "hclk", "pclk", + "gate_dclk1" }; + +PNAME(clkout0_s3c2412_p) = { "mpll", "upll", "rtc_clkout", + "hclk", "pclk", "gate_dclk0" }; +PNAME(clkout1_s3c2412_p) = { "xti", "upll", "fclk", "hclk", "pclk", + "gate_dclk1" }; + +PNAME(clkout0_s3c2440_p) = { "xti", "upll", "fclk", "hclk", "pclk", + "gate_dclk0" }; +PNAME(clkout1_s3c2440_p) = { "mpll", "upll", "rtc_clkout", + "hclk", "pclk", "gate_dclk1" }; + +PNAME(dclk_s3c2443_p) = { "pclk", "epll" }; +PNAME(clkout0_s3c2443_p) = { "xti", "epll", "armclk", "hclk", "pclk", + "gate_dclk0" }; +PNAME(clkout1_s3c2443_p) = { "dummy", "epll", "rtc_clkout", + "hclk", "pclk", "gate_dclk1" }; + +#define DCLKCON_DCLK_DIV_MASK 0xf +#define DCLKCON_DCLK0_DIV_SHIFT 4 +#define DCLKCON_DCLK0_CMP_SHIFT 8 +#define DCLKCON_DCLK1_DIV_SHIFT 20 +#define DCLKCON_DCLK1_CMP_SHIFT 24 + +static void s3c24xx_dclk_update_cmp(struct s3c24xx_dclk *s3c24xx_dclk, + int div_shift, int cmp_shift) +{ + unsigned long flags = 0; + u32 dclk_con, div, cmp; + + spin_lock_irqsave(&s3c24xx_dclk->dclk_lock, flags); + + dclk_con = readl_relaxed(s3c24xx_dclk->base); + + div = ((dclk_con >> div_shift) & DCLKCON_DCLK_DIV_MASK) + 1; + cmp = ((div + 1) / 2) - 1; + + dclk_con &= ~(DCLKCON_DCLK_DIV_MASK << cmp_shift); + dclk_con |= (cmp << cmp_shift); + + writel_relaxed(dclk_con, s3c24xx_dclk->base); + + spin_unlock_irqrestore(&s3c24xx_dclk->dclk_lock, flags); +} + +static int s3c24xx_dclk0_div_notify(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct s3c24xx_dclk *s3c24xx_dclk = to_s3c24xx_dclk0(nb); + + if (event == POST_RATE_CHANGE) { + s3c24xx_dclk_update_cmp(s3c24xx_dclk, + DCLKCON_DCLK0_DIV_SHIFT, DCLKCON_DCLK0_CMP_SHIFT); + } + + return NOTIFY_DONE; +} + +static int s3c24xx_dclk1_div_notify(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct s3c24xx_dclk *s3c24xx_dclk = to_s3c24xx_dclk1(nb); + + if (event == POST_RATE_CHANGE) { + s3c24xx_dclk_update_cmp(s3c24xx_dclk, + DCLKCON_DCLK1_DIV_SHIFT, DCLKCON_DCLK1_CMP_SHIFT); + } + + return NOTIFY_DONE; +} + +#ifdef CONFIG_PM_SLEEP +static int s3c24xx_dclk_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s3c24xx_dclk *s3c24xx_dclk = platform_get_drvdata(pdev); + + s3c24xx_dclk->reg_save = readl_relaxed(s3c24xx_dclk->base); + return 0; +} + +static int s3c24xx_dclk_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s3c24xx_dclk *s3c24xx_dclk = platform_get_drvdata(pdev); + + writel_relaxed(s3c24xx_dclk->reg_save, s3c24xx_dclk->base); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(s3c24xx_dclk_pm_ops, + s3c24xx_dclk_suspend, s3c24xx_dclk_resume); + +static int s3c24xx_dclk_probe(struct platform_device *pdev) +{ + struct s3c24xx_dclk *s3c24xx_dclk; + struct resource *mem; + struct clk **clk_table; + struct s3c24xx_dclk_drv_data *dclk_variant; + int ret, i; + + s3c24xx_dclk = devm_kzalloc(&pdev->dev, sizeof(*s3c24xx_dclk), + GFP_KERNEL); + if (!s3c24xx_dclk) + return -ENOMEM; + + s3c24xx_dclk->dev = &pdev->dev; + platform_set_drvdata(pdev, s3c24xx_dclk); + spin_lock_init(&s3c24xx_dclk->dclk_lock); + + clk_table = devm_kzalloc(&pdev->dev, + sizeof(struct clk *) * DCLK_MAX_CLKS, + GFP_KERNEL); + if (!clk_table) + return -ENOMEM; + + s3c24xx_dclk->clk_data.clks = clk_table; + s3c24xx_dclk->clk_data.clk_num = DCLK_MAX_CLKS; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + s3c24xx_dclk->base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(s3c24xx_dclk->base)) + return PTR_ERR(s3c24xx_dclk->base); + + dclk_variant = (struct s3c24xx_dclk_drv_data *) + platform_get_device_id(pdev)->driver_data; + + + clk_table[MUX_DCLK0] = clk_register_mux(&pdev->dev, "mux_dclk0", + dclk_variant->mux_parent_names, + dclk_variant->mux_num_parents, 0, + s3c24xx_dclk->base, 1, 1, 0, + &s3c24xx_dclk->dclk_lock); + clk_table[MUX_DCLK1] = clk_register_mux(&pdev->dev, "mux_dclk1", + dclk_variant->mux_parent_names, + dclk_variant->mux_num_parents, 0, + s3c24xx_dclk->base, 17, 1, 0, + &s3c24xx_dclk->dclk_lock); + + clk_table[DIV_DCLK0] = clk_register_divider(&pdev->dev, "div_dclk0", + "mux_dclk0", 0, s3c24xx_dclk->base, + 4, 4, 0, &s3c24xx_dclk->dclk_lock); + clk_table[DIV_DCLK1] = clk_register_divider(&pdev->dev, "div_dclk1", + "mux_dclk1", 0, s3c24xx_dclk->base, + 20, 4, 0, &s3c24xx_dclk->dclk_lock); + + clk_table[GATE_DCLK0] = clk_register_gate(&pdev->dev, "gate_dclk0", + "div_dclk0", CLK_SET_RATE_PARENT, + s3c24xx_dclk->base, 0, 0, + &s3c24xx_dclk->dclk_lock); + clk_table[GATE_DCLK1] = clk_register_gate(&pdev->dev, "gate_dclk1", + "div_dclk1", CLK_SET_RATE_PARENT, + s3c24xx_dclk->base, 16, 0, + &s3c24xx_dclk->dclk_lock); + + clk_table[MUX_CLKOUT0] = s3c24xx_register_clkout(&pdev->dev, + "clkout0", dclk_variant->clkout0_parent_names, + dclk_variant->clkout0_num_parents, 4, 7); + clk_table[MUX_CLKOUT1] = s3c24xx_register_clkout(&pdev->dev, + "clkout1", dclk_variant->clkout1_parent_names, + dclk_variant->clkout1_num_parents, 8, 7); + + for (i = 0; i < DCLK_MAX_CLKS; i++) + if (IS_ERR(clk_table[i])) { + dev_err(&pdev->dev, "clock %d failed to register\n", i); + ret = PTR_ERR(clk_table[i]); + goto err_clk_register; + } + + ret = clk_register_clkdev(clk_table[MUX_DCLK0], "dclk0", NULL); + if (!ret) + ret = clk_register_clkdev(clk_table[MUX_DCLK1], "dclk1", NULL); + if (!ret) + ret = clk_register_clkdev(clk_table[MUX_CLKOUT0], + "clkout0", NULL); + if (!ret) + ret = clk_register_clkdev(clk_table[MUX_CLKOUT1], + "clkout1", NULL); + if (ret) { + dev_err(&pdev->dev, "failed to register aliases, %d\n", ret); + goto err_clk_register; + } + + s3c24xx_dclk->dclk0_div_change_nb.notifier_call = + s3c24xx_dclk0_div_notify; + + s3c24xx_dclk->dclk1_div_change_nb.notifier_call = + s3c24xx_dclk1_div_notify; + + ret = clk_notifier_register(clk_table[DIV_DCLK0], + &s3c24xx_dclk->dclk0_div_change_nb); + if (ret) + goto err_clk_register; + + ret = clk_notifier_register(clk_table[DIV_DCLK1], + &s3c24xx_dclk->dclk1_div_change_nb); + if (ret) + goto err_dclk_notify; + + return 0; + +err_dclk_notify: + clk_notifier_unregister(clk_table[DIV_DCLK0], + &s3c24xx_dclk->dclk0_div_change_nb); +err_clk_register: + for (i = 0; i < DCLK_MAX_CLKS; i++) + if (clk_table[i] && !IS_ERR(clk_table[i])) + clk_unregister(clk_table[i]); + + return ret; +} + +static int s3c24xx_dclk_remove(struct platform_device *pdev) +{ + struct s3c24xx_dclk *s3c24xx_dclk = platform_get_drvdata(pdev); + struct clk **clk_table = s3c24xx_dclk->clk_data.clks; + int i; + + clk_notifier_unregister(clk_table[DIV_DCLK1], + &s3c24xx_dclk->dclk1_div_change_nb); + clk_notifier_unregister(clk_table[DIV_DCLK0], + &s3c24xx_dclk->dclk0_div_change_nb); + + for (i = 0; i < DCLK_MAX_CLKS; i++) + clk_unregister(clk_table[i]); + + return 0; +} + +static struct s3c24xx_dclk_drv_data dclk_variants[] = { + [S3C2410] = { + .clkout0_parent_names = clkout0_s3c2410_p, + .clkout0_num_parents = ARRAY_SIZE(clkout0_s3c2410_p), + .clkout1_parent_names = clkout1_s3c2410_p, + .clkout1_num_parents = ARRAY_SIZE(clkout1_s3c2410_p), + .mux_parent_names = dclk_s3c2410_p, + .mux_num_parents = ARRAY_SIZE(dclk_s3c2410_p), + }, + [S3C2412] = { + .clkout0_parent_names = clkout0_s3c2412_p, + .clkout0_num_parents = ARRAY_SIZE(clkout0_s3c2412_p), + .clkout1_parent_names = clkout1_s3c2412_p, + .clkout1_num_parents = ARRAY_SIZE(clkout1_s3c2412_p), + .mux_parent_names = dclk_s3c2410_p, + .mux_num_parents = ARRAY_SIZE(dclk_s3c2410_p), + }, + [S3C2440] = { + .clkout0_parent_names = clkout0_s3c2440_p, + .clkout0_num_parents = ARRAY_SIZE(clkout0_s3c2440_p), + .clkout1_parent_names = clkout1_s3c2440_p, + .clkout1_num_parents = ARRAY_SIZE(clkout1_s3c2440_p), + .mux_parent_names = dclk_s3c2410_p, + .mux_num_parents = ARRAY_SIZE(dclk_s3c2410_p), + }, + [S3C2443] = { + .clkout0_parent_names = clkout0_s3c2443_p, + .clkout0_num_parents = ARRAY_SIZE(clkout0_s3c2443_p), + .clkout1_parent_names = clkout1_s3c2443_p, + .clkout1_num_parents = ARRAY_SIZE(clkout1_s3c2443_p), + .mux_parent_names = dclk_s3c2443_p, + .mux_num_parents = ARRAY_SIZE(dclk_s3c2443_p), + }, +}; + +static struct platform_device_id s3c24xx_dclk_driver_ids[] = { + { + .name = "s3c2410-dclk", + .driver_data = (kernel_ulong_t)&dclk_variants[S3C2410], + }, { + .name = "s3c2412-dclk", + .driver_data = (kernel_ulong_t)&dclk_variants[S3C2412], + }, { + .name = "s3c2440-dclk", + .driver_data = (kernel_ulong_t)&dclk_variants[S3C2440], + }, { + .name = "s3c2443-dclk", + .driver_data = (kernel_ulong_t)&dclk_variants[S3C2443], + }, + { } +}; + +MODULE_DEVICE_TABLE(platform, s3c24xx_dclk_driver_ids); + +static struct platform_driver s3c24xx_dclk_driver = { + .driver = { + .name = "s3c24xx-dclk", + .owner = THIS_MODULE, + .pm = &s3c24xx_dclk_pm_ops, + }, + .probe = s3c24xx_dclk_probe, + .remove = s3c24xx_dclk_remove, + .id_table = s3c24xx_dclk_driver_ids, +}; +module_platform_driver(s3c24xx_dclk_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>"); +MODULE_DESCRIPTION("Driver for the S3C24XX external clock outputs"); diff --git a/drivers/clk/samsung/clk-s3c2410.c b/drivers/clk/samsung/clk-s3c2410.c new file mode 100644 index 000000000000..7b4182186a30 --- /dev/null +++ b/drivers/clk/samsung/clk-s3c2410.c @@ -0,0 +1,477 @@ +/* + * Copyright (c) 2013 Heiko Stuebner <heiko@sntech.de> + * + * 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. + * + * Common Clock Framework support for S3C2410 and following SoCs. + */ + +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/clk-provider.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/syscore_ops.h> + +#include <dt-bindings/clock/s3c2410.h> + +#include "clk.h" +#include "clk-pll.h" + +#define LOCKTIME 0x00 +#define MPLLCON 0x04 +#define UPLLCON 0x08 +#define CLKCON 0x0c +#define CLKSLOW 0x10 +#define CLKDIVN 0x14 +#define CAMDIVN 0x18 + +/* the soc types */ +enum supported_socs { + S3C2410, + S3C2440, + S3C2442, +}; + +/* list of PLLs to be registered */ +enum s3c2410_plls { + mpll, upll, +}; + +static void __iomem *reg_base; + +#ifdef CONFIG_PM_SLEEP +static struct samsung_clk_reg_dump *s3c2410_save; + +/* + * list of controller registers to be saved and restored during a + * suspend/resume cycle. + */ +static unsigned long s3c2410_clk_regs[] __initdata = { + LOCKTIME, + MPLLCON, + UPLLCON, + CLKCON, + CLKSLOW, + CLKDIVN, + CAMDIVN, +}; + +static int s3c2410_clk_suspend(void) +{ + samsung_clk_save(reg_base, s3c2410_save, + ARRAY_SIZE(s3c2410_clk_regs)); + + return 0; +} + +static void s3c2410_clk_resume(void) +{ + samsung_clk_restore(reg_base, s3c2410_save, + ARRAY_SIZE(s3c2410_clk_regs)); +} + +static struct syscore_ops s3c2410_clk_syscore_ops = { + .suspend = s3c2410_clk_suspend, + .resume = s3c2410_clk_resume, +}; + +static void s3c2410_clk_sleep_init(void) +{ + s3c2410_save = samsung_clk_alloc_reg_dump(s3c2410_clk_regs, + ARRAY_SIZE(s3c2410_clk_regs)); + if (!s3c2410_save) { + pr_warn("%s: failed to allocate sleep save data, no sleep support!\n", + __func__); + return; + } + + register_syscore_ops(&s3c2410_clk_syscore_ops); + return; +} +#else +static void s3c2410_clk_sleep_init(void) {} +#endif + +PNAME(fclk_p) = { "mpll", "div_slow" }; + +struct samsung_mux_clock s3c2410_common_muxes[] __initdata = { + MUX(FCLK, "fclk", fclk_p, CLKSLOW, 4, 1), +}; + +static struct clk_div_table divslow_d[] = { + { .val = 0, .div = 1 }, + { .val = 1, .div = 2 }, + { .val = 2, .div = 4 }, + { .val = 3, .div = 6 }, + { .val = 4, .div = 8 }, + { .val = 5, .div = 10 }, + { .val = 6, .div = 12 }, + { .val = 7, .div = 14 }, + { /* sentinel */ }, +}; + +struct samsung_div_clock s3c2410_common_dividers[] __initdata = { + DIV_T(0, "div_slow", "xti", CLKSLOW, 0, 3, divslow_d), + DIV(PCLK, "pclk", "hclk", CLKDIVN, 0, 1), +}; + +struct samsung_gate_clock s3c2410_common_gates[] __initdata = { + GATE(PCLK_SPI, "spi", "pclk", CLKCON, 18, 0, 0), + GATE(PCLK_I2S, "i2s", "pclk", CLKCON, 17, 0, 0), + GATE(PCLK_I2C, "i2c", "pclk", CLKCON, 16, 0, 0), + GATE(PCLK_ADC, "adc", "pclk", CLKCON, 15, 0, 0), + GATE(PCLK_RTC, "rtc", "pclk", CLKCON, 14, 0, 0), + GATE(PCLK_GPIO, "gpio", "pclk", CLKCON, 13, CLK_IGNORE_UNUSED, 0), + GATE(PCLK_UART2, "uart2", "pclk", CLKCON, 12, 0, 0), + GATE(PCLK_UART1, "uart1", "pclk", CLKCON, 11, 0, 0), + GATE(PCLK_UART0, "uart0", "pclk", CLKCON, 10, 0, 0), + GATE(PCLK_SDI, "sdi", "pclk", CLKCON, 9, 0, 0), + GATE(PCLK_PWM, "pwm", "pclk", CLKCON, 8, 0, 0), + GATE(HCLK_USBD, "usb-device", "hclk", CLKCON, 7, 0, 0), + GATE(HCLK_USBH, "usb-host", "hclk", CLKCON, 6, 0, 0), + GATE(HCLK_LCD, "lcd", "hclk", CLKCON, 5, 0, 0), + GATE(HCLK_NAND, "nand", "hclk", CLKCON, 4, 0, 0), +}; + +/* should be added _after_ the soc-specific clocks are created */ +struct samsung_clock_alias s3c2410_common_aliases[] __initdata = { + ALIAS(PCLK_I2C, "s3c2410-i2c.0", "i2c"), + ALIAS(PCLK_ADC, NULL, "adc"), + ALIAS(PCLK_RTC, NULL, "rtc"), + ALIAS(PCLK_PWM, NULL, "timers"), + ALIAS(HCLK_LCD, NULL, "lcd"), + ALIAS(HCLK_USBD, NULL, "usb-device"), + ALIAS(HCLK_USBH, NULL, "usb-host"), + ALIAS(UCLK, NULL, "usb-bus-host"), + ALIAS(UCLK, NULL, "usb-bus-gadget"), + ALIAS(ARMCLK, NULL, "armclk"), + ALIAS(UCLK, NULL, "uclk"), + ALIAS(HCLK, NULL, "hclk"), + ALIAS(MPLL, NULL, "mpll"), + ALIAS(FCLK, NULL, "fclk"), +}; + +/* S3C2410 specific clocks */ + +static struct samsung_pll_rate_table pll_s3c2410_12mhz_tbl[] __initdata = { + /* sorted in descending order */ + /* 2410A extras */ + PLL_35XX_RATE(270000000, 127, 1, 1), + PLL_35XX_RATE(268000000, 126, 1, 1), + PLL_35XX_RATE(266000000, 125, 1, 1), + PLL_35XX_RATE(226000000, 105, 1, 1), + PLL_35XX_RATE(210000000, 132, 2, 1), + /* 2410 common */ + PLL_35XX_RATE(203000000, 161, 3, 1), + PLL_35XX_RATE(192000000, 88, 1, 1), + PLL_35XX_RATE(186000000, 85, 1, 1), + PLL_35XX_RATE(180000000, 82, 1, 1), + PLL_35XX_RATE(170000000, 77, 1, 1), + PLL_35XX_RATE(158000000, 71, 1, 1), + PLL_35XX_RATE(152000000, 68, 1, 1), + PLL_35XX_RATE(147000000, 90, 2, 1), + PLL_35XX_RATE(135000000, 82, 2, 1), + PLL_35XX_RATE(124000000, 116, 1, 2), + PLL_35XX_RATE(118000000, 150, 2, 2), + PLL_35XX_RATE(113000000, 105, 1, 2), + PLL_35XX_RATE(101000000, 127, 2, 2), + PLL_35XX_RATE(90000000, 112, 2, 2), + PLL_35XX_RATE(85000000, 105, 2, 2), + PLL_35XX_RATE(79000000, 71, 1, 2), + PLL_35XX_RATE(68000000, 82, 2, 2), + PLL_35XX_RATE(56000000, 142, 2, 3), + PLL_35XX_RATE(48000000, 120, 2, 3), + PLL_35XX_RATE(51000000, 161, 3, 3), + PLL_35XX_RATE(45000000, 82, 1, 3), + PLL_35XX_RATE(34000000, 82, 2, 3), + { /* sentinel */ }, +}; + +static struct samsung_pll_clock s3c2410_plls[] __initdata = { + [mpll] = PLL(pll_s3c2410_mpll, MPLL, "mpll", "xti", + LOCKTIME, MPLLCON, NULL), + [upll] = PLL(pll_s3c2410_upll, UPLL, "upll", "xti", + LOCKTIME, UPLLCON, NULL), +}; + +struct samsung_div_clock s3c2410_dividers[] __initdata = { + DIV(HCLK, "hclk", "mpll", CLKDIVN, 1, 1), +}; + +struct samsung_fixed_factor_clock s3c2410_ffactor[] __initdata = { + /* + * armclk is directly supplied by the fclk, without + * switching possibility like on the s3c244x below. + */ + FFACTOR(ARMCLK, "armclk", "fclk", 1, 1, 0), + + /* uclk is fed from the unmodified upll */ + FFACTOR(UCLK, "uclk", "upll", 1, 1, 0), +}; + +struct samsung_clock_alias s3c2410_aliases[] __initdata = { + ALIAS(PCLK_UART0, "s3c2410-uart.0", "uart"), + ALIAS(PCLK_UART1, "s3c2410-uart.1", "uart"), + ALIAS(PCLK_UART2, "s3c2410-uart.2", "uart"), + A |