/* * Copyright (c) 2013 Samsung Electronics Co., Ltd. * Author: Hyunki Koo * * 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 pwm timer Clock Controller. */ #include #include #include #include #include "clk.h" /* Each of the timers 0 through 5 go through the following * clock tree, with the inputs depending on the timers. * * pclk ---- [ prescaler 0 ] -+---> timer 0 * +---> timer 1 * * pclk ---- [ prescaler 1 ] -+---> timer 2 * +---> timer 3 * \---> timer 4 * * Which are fed into the timers as so: * * prescaled 0 ---- [ div 2,4,8,16 ] ---\ * [mux] -> timer 0 (tin) * tclk 0 ------------------------------/ * * prescaled 0 ---- [ div 2,4,8,16 ] ---\ * [mux] -> timer 1 (tin) * tclk 0 ------------------------------/ * * * prescaled 1 ---- [ div 2,4,8,16 ] ---\ * [mux] -> timer 2 (tin) * tclk 1 ------------------------------/ * * prescaled 1 ---- [ div 2,4,8,16 ] ---\ * [mux] -> timer 3 (tin) * tclk 1 ------------------------------/ * * prescaled 1 ---- [ div 2,4,8, 16 ] --\ * [mux] -> timer 4 (tin) * tclk 1 ------------------------------/ * * Since the mux and the divider are tied together in the * same register space, it is impossible to set the parent * and the rate at the same time. To avoid this, we add an * intermediate 'prescaled-and-divided' clock to select * as the parent for the timer input clock called tdiv. * * prescaled clk --> pwm-tdiv ---\ * [ mux ] --> timer X * tclk -------------------------/ * * tclk is deprecated in exynos * */ static DEFINE_SPINLOCK(lock); static struct clk **clk_table; static struct clk_onecell_data clk_data; #define REG_TCFG0 0x00 #define REG_TCFG1 0x04 #define REG_TCON 0x08 #define REG_TINT_CSTAT 0x44 #define MASK_TCFG0_PRESCALE0 0x00FF #define MASK_TCFG0_PRESCALE1 0xFF00 enum exynos_pwm_clks { pwm_clock = 0, pwm_scaler0, pwm_scaler1, pwm_tclk0, pwm_tclk1, pwm_tdiv0 = 5, pwm_tdiv1, pwm_tdiv2, pwm_tdiv3, pwm_tdiv4, pwm_tin0 = 10, pwm_tin1, pwm_tin2, pwm_tin3, pwm_tin4, exynos_pwm_max_clks, }; static const char *pwm_tin0_p[] = { "pwm-tdiv0", "pwm-tclk" }; static const char *pwm_tin1_p[] = { "pwm-tdiv1", "pwm-tclk" }; static const char *pwm_tin2_p[] = { "pwm-tdiv2", "pwm-tclk" }; static const char *pwm_tin3_p[] = { "pwm-tdiv3", "pwm-tclk" }; static const char *pwm_tin4_p[] = { "pwm-tdiv4", "pwm-tclk" }; static const struct clk_div_table pwm_div_table[5] = { /* { val, div } */ { 0, 1 }, { 1, 2 }, { 2, 4 }, { 3, 8 }, { 4, 16 }, }; /* register exynos_pwm clocks */ void __init exynos_pwm_clk_init(struct device_node *np) { static void __iomem *reg_base; unsigned int reg_tcfg0; reg_base = of_iomap(np, 0); if (!reg_base) { pr_err("%s: failed to map pwm registers\n", __func__); return; } clk_table = kzalloc(sizeof(struct clk *) * exynos_pwm_max_clks, GFP_KERNEL); if (!clk_table) { pr_err("%s: could not allocate clk lookup table\n", __func__); return; } clk_data.clks = clk_table; clk_data.clk_num = exynos_pwm_max_clks; of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data); reg_tcfg0 = __raw_readl(reg_base + REG_TCFG0); reg_tcfg0 &= ~(MASK_TCFG0_PRESCALE0 | MASK_TCFG0_PRESCALE1); __raw_writel(reg_tcfg0, reg_base + REG_TCFG0); __raw_writel(0, reg_base + REG_TCFG1); clk_table[pwm_scaler0] = clk_register_divider(NULL, "pwm-scaler0", "pwm-clock", 0, reg_base + REG_TCFG0, 0, 8, CLK_DIVIDER_ALLOW_ZERO, &lock); clk_table[pwm_scaler1] = clk_register_divider(NULL, "pwm-scaler1", "pwm-clock", 0, reg_base + REG_TCFG0, 8, 8, CLK_DIVIDER_ALLOW_ZERO, &lock); clk_table[pwm_tdiv0] = clk_register_divider_table(NULL, "pwm-tdiv0", "pwm-scaler0", 0, reg_base + REG_TCFG1, 0, 4, CLK_DIVIDER_ALLOW_ZERO, pwm_div_table, &lock); clk_table[pwm_tdiv1] = clk_register_divider_table(NULL, "pwm-tdiv1", "pwm-scaler0", 0, reg_base + REG_TCFG1, 4, 4, CLK_DIVIDER_ALLOW_ZERO, pwm_div_table, &lock); clk_table[pwm_tdiv2] = clk_register_divider_table(NULL, "pwm-tdiv2", "pwm-scaler1", 0, reg_base + REG_TCFG1, 8, 4, CLK_DIVIDER_ALLOW_ZERO, pwm_div_table, &lock); clk_table[pwm_tdiv3] = clk_register_divider_table(NULL, "pwm-tdiv3", "pwm-scaler1", 0, reg_base + REG_TCFG1, 12, 4, CLK_DIVIDER_ALLOW_ZERO, pwm_div_table, &lock); clk_table[pwm_tdiv4] = clk_register_divider_table(NULL, "pwm-tdiv4", "pwm-scaler1", 0, reg_base + REG_TCFG1, 16, 4, CLK_DIVIDER_ALLOW_ZERO, pwm_div_table, &lock); clk_table[pwm_tin0] = clk_register_mux(NULL, "pwm-tin0", pwm_tin0_p, ARRAY_SIZE(pwm_tin0_p), 0, reg_base + REG_TCFG1, 3, 0, 0, &lock); clk_table[pwm_tin1] = clk_register_mux(NULL, "pwm-tin1", pwm_tin1_p, ARRAY_SIZE(pwm_tin1_p), 0, reg_base + REG_TCFG1, 7, 0, 0, &lock); clk_table[pwm_tin2] = clk_register_mux(NULL, "pwm-tin2", pwm_tin2_p, ARRAY_SIZE(pwm_tin2_p), 0, reg_base + REG_TCFG1, 11, 0, 0, &lock); clk_table[pwm_tin3] = clk_register_mux(NULL, "pwm-tin3", pwm_tin3_p, ARRAY_SIZE(pwm_tin3_p), 0, reg_base + REG_TCFG1, 15, 0, 0, &lock); clk_table[pwm_tin4] = clk_register_mux(NULL, "pwm-tin4", pwm_tin4_p, ARRAY_SIZE(pwm_tin4_p), 0, reg_base + REG_TCFG1, 19, 0, 0, &lock); pr_info("Exynos: pwm: clock setup completed\n"); } CLK_OF_DECLARE(exynos_pwm_clk, "samsung,exynos-pwm-clock", exynos_pwm_clk_init);