/*
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
* Author: Mateusz Krawczuk <
[email protected]>
*
* Based on clock drivers for S3C64xx and Exynos4 SoCs.
*
* 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 all S5PC110/S5PV210 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 "clk.h"
#include "clk-pll.h"
#include <dt-bindings/clock/s5pv210.h>
/* S5PC110/S5PV210 clock controller register offsets */
#define APLL_LOCK 0x0000
#define MPLL_LOCK 0x0008
#define EPLL_LOCK 0x0010
#define VPLL_LOCK 0x0020
#define APLL_CON0 0x0100
#define APLL_CON1 0x0104
#define MPLL_CON 0x0108
#define EPLL_CON0 0x0110
#define EPLL_CON1 0x0114
#define VPLL_CON 0x0120
#define CLK_SRC0 0x0200
#define CLK_SRC1 0x0204
#define CLK_SRC2 0x0208
#define CLK_SRC3 0x020c
#define CLK_SRC4 0x0210
#define CLK_SRC5 0x0214
#define CLK_SRC6 0x0218
#define CLK_SRC_MASK0 0x0280
#define CLK_SRC_MASK1 0x0284
#define CLK_DIV0 0x0300
#define CLK_DIV1 0x0304
#define CLK_DIV2 0x0308
#define CLK_DIV3 0x030c
#define CLK_DIV4 0x0310
#define CLK_DIV5 0x0314
#define CLK_DIV6 0x0318
#define CLK_DIV7 0x031c
#define CLK_GATE_MAIN0 0x0400
#define CLK_GATE_MAIN1 0x0404
#define CLK_GATE_MAIN2 0x0408
#define CLK_GATE_PERI0 0x0420
#define CLK_GATE_PERI1 0x0424
#define CLK_GATE_SCLK0 0x0440
#define CLK_GATE_SCLK1 0x0444
#define CLK_GATE_IP0 0x0460
#define CLK_GATE_IP1 0x0464
#define CLK_GATE_IP2 0x0468
#define CLK_GATE_IP3 0x046c
#define CLK_GATE_IP4 0x0470
#define CLK_GATE_BLOCK 0x0480
#define CLK_GATE_IP5 0x0484
#define CLK_OUT 0x0500
#define MISC 0xe000
#define OM_STAT 0xe100
/* IDs of PLLs available on S5PV210/S5P6442 SoCs */
enum {
apll,
mpll,
epll,
vpll,
};
/* IDs of external clocks (used for legacy boards) */
enum {
xxti,
xusbxti,
};
static void __iomem *reg_base;
#ifdef CONFIG_PM_SLEEP
static struct samsung_clk_reg_dump *s5pv210_clk_dump;
/* List of registers that need to be preserved across suspend/resume. */
static unsigned long s5pv210_clk_regs[] __initdata = {
CLK_SRC0,
CLK_SRC1,
CLK_SRC2,
CLK_SRC3,
CLK_SRC4,
CLK_SRC5,
CLK_SRC6,
CLK_SRC_MASK0,
CLK_SRC_MASK1,
CLK_DIV0,
CLK_DIV1,
CLK_DIV2,
CLK_DIV3,
CLK_DIV4,
CLK_DIV5,
CLK_DIV6,
CLK_DIV7,
CLK_GATE_MAIN0,
CLK_GATE_MAIN1,
CLK_GATE_MAIN2,
CLK_GATE_PERI0,
CLK_GATE_PERI1,
CLK_GATE_SCLK0,
CLK_GATE_SCLK1,
CLK_GATE_IP0,
CLK_GATE_IP1,
CLK_GATE_IP2,
CLK_GATE_IP3,
CLK_GATE_IP4,
CLK_GATE_IP5,
CLK_GATE_BLOCK,
APLL_LOCK,
MPLL_LOCK,
EPLL_LOCK,
VPLL_LOCK,
APLL_CON0,
APLL_CON1,
MPLL_CON,
EPLL_CON0,
EPLL_CON1,
VPLL_CON,
CLK_OUT,
};
static int s5pv210_clk_suspend(void)
{
samsung_clk_save(reg_base, s5pv210_clk_dump,
ARRAY_SIZE(s5pv210_clk_regs));
return 0;
}
static void s5pv210_clk_resume(void)
{
samsung_clk_restore(reg_base, s5pv210_clk_dump,
ARRAY_SIZE(s5pv210_clk_regs));
}
static struct syscore_ops s5pv210_clk_syscore_ops = {
.suspend = s5pv210_clk_suspend,
.resume = s5pv210_clk_resume,
};
static void s5pv210_clk_sleep_init(void)
{
s5pv210_clk_dump =
samsung_clk_alloc_reg_dump(s5pv210_clk_regs,
ARRAY_SIZE(s5pv210_clk_regs));
if (!s5pv210_clk_dump) {
pr_warn("%s: Failed to allocate sleep save data\n", __func__);
return;
}
register_syscore_ops(&s5pv210_clk_syscore_ops);
}
#else
static inline void s5pv210_clk_sleep_init(void) { }
#endif
/* Mux parent lists. */
static const char *fin_pll_p[] __initconst = {
"xxti",
"xusbxti"
};
static const char *mout_apll_p[] __initconst = {
"fin_pll",
"fout_apll"
};
static const char *mout_mpll_p[] __initconst = {
"fin_pll",
"fout_mpll"
};
static const char *mout_epll_p[] __initconst = {
"fin_pll",
"fout_epll"
};
static const char *mout_vpllsrc_p[] __initconst = {
"fin_pll",
"sclk_hdmi27m"
};
static const char *mout_vpll_p[] __initconst = {
"mout_vpllsrc",
"fout_vpll"
};
static const char *mout_group1_p[] __initconst = {
"dout_a2m",
"mout_mpll",
"mout_epll",
"mout_vpll"
};
static const char *mout_group2_p[] __initconst = {
"xxti",
"xusbxti",
"sclk_hdmi27m",
"sclk_usbphy0",
"sclk_usbphy1",
"sclk_hdmiphy",
"mout_mpll",
"mout_epll",
"mout_vpll",
};
static const char *mout_audio0_p[] __initconst = {
"xxti",
"pcmcdclk0",
"sclk_hdmi27m",
"sclk_usbphy0",
"sclk_usbphy1",
"sclk_hdmiphy",
"mout_mpll",
"mout_epll",
"mout_vpll",
};
static const char *mout_audio1_p[] __initconst = {
"i2scdclk1",
"pcmcdclk1",
"sclk_hdmi27m",
"sclk_usbphy0",
"sclk_usbphy1",
"sclk_hdmiphy",
"mout_mpll",
"mout_epll",
"mout_vpll",
};
static const char *mout_audio2_p[] __initconst = {
"i2scdclk2",
"pcmcdclk2",
"sclk_hdmi27m",
"sclk_usbphy0",
"sclk_usbphy1",
"sclk_hdmiphy",
"mout_mpll",
"mout_epll",
"mout_vpll",
};
static const char *mout_spdif_p[] __initconst = {
"dout_audio0",
"dout_audio1",
"dout_audio3",
};
static const char *mout_group3_p[] __initconst = {
"mout_apll",
"mout_mpll"
};
static const char *mout_group4_p[] __initconst = {
"mout_mpll",
"dout_a2m"
};
static const char *mout_flash_p[] __initconst = {
"dout_hclkd",
"dout_hclkp"
};
static const char *mout_dac_p[] __initconst = {
"mout_vpll",
"sclk_hdmiphy"
};
static const char *mout_hdmi_p[] __initconst = {
"sclk_hdmiphy",
"dout_tblk"
};
static const char *mout_mixer_p[] __initconst = {
"mout_dac",
"mout_hdmi"
};
static const char *mout_vpll_6442_p[] __initconst = {
"fin_pll",
"fout_vpll"
};
static const char *mout_mixer_6442_p[] __initconst = {
"mout_vpll",
"dout_mixer"
};
static const char *mout_d0sync_6442_p[] __initconst = {
"mout_dsys",
"div_apll"
};
static const char *mout_d1sync_6442_p[] __initconst = {
"mout_psys",
"div_apll"
};
static const char *mout_group2_6442_p[] __initconst = {
"fin_pll",
"none",
"none",
"sclk_usbphy0",
"none",
"none",
"mout_mpll",
"mout_epll",
"mout_vpll",
};
static const char *mout_audio0_6442_p[] __initconst = {
"fin_pll",
"pcmcdclk0",
"none",
"sclk_usbphy0",
"none",
"none",
"mout_mpll",
"mout_epll",
"mout_vpll",
};
static const char *mout_audio1_6442_p[] __initconst = {
"i2scdclk1",
"pcmcdclk1",
"none",
"sclk_usbphy0",
"none",
"none",
"mout_mpll",
"mout_epll",
"mout_vpll",
"fin_pll",
};
static const char *mout_clksel_p[] __initconst = {
"fout_apll_clkout",
"fout_mpll_clkout",
"fout_epll",
"fout_vpll",
"sclk_usbphy0",
"sclk_usbphy1",
"sclk_hdmiphy",
"rtc",
"rtc_tick",
"dout_hclkm",
"dout_pclkm",
"dout_hclkd",
"dout_pclkd",
"dout_hclkp",
"dout_pclkp",
"dout_apll_clkout",
"dout_hpm",
"xxti",
"xusbxti",
"div_dclk"
};
static const char *mout_clksel_6442_p[] __initconst = {
"fout_apll_clkout",
"fout_mpll_clkout",
"fout_epll",
"fout_vpll",
"sclk_usbphy0",
"none",
"none",
"rtc",
"rtc_tick",
"none",
"none",
"dout_hclkd",
"dout_pclkd",
"dout_hclkp",
"dout_pclkp",
"dout_apll_clkout",
"none",
"fin_pll",
"none",
"div_dclk"
};
static const char *mout_clkout_p[] __initconst = {
"dout_clkout",
"none",
"xxti",
"xusbxti"
};
/* Common fixed factor clocks. */
static struct samsung_fixed_factor_clock ffactor_clks[] __initdata = {
FFACTOR(FOUT_APLL_CLKOUT, "fout_apll_clkout", "fout_apll", 1, 4, 0),
FFACTOR(FOUT_MPLL_CLKOUT, "fout_mpll_clkout", "fout_mpll", 1, 2, 0),
FFACTOR(DOUT_APLL_CLKOUT, "dout_apll_clkout", "dout_apll", 1, 4, 0),
};
/* PLL input mux (fin_pll), which needs to be registered before PLLs. */
static struct samsung_mux_clock early_mux_clks[] __initdata = {
MUX_F(FIN_PLL, "fin_pll", fin_pll_p, OM_STAT, 0, 1,
CLK_MUX_READ_ONLY, 0),
};
/* Common clock muxes. */
static struct samsung_mux_clock mux_clks[] __initdata = {
MUX(MOUT_FLASH, "mout_flash", mout_flash_p, CLK_SRC0, 28, 1),
MUX(MOUT_PSYS, "mout_psys", mout_group4_p, CLK_SRC0, 24, 1),