blob: 20308dae16cb572e865b64e4fa3af1c467cb2af4 [file] [log] [blame]
/*
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
* Author: Hyunki Koo <hyunki00.koo@samsung.com>
*
* 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 <linux/clkdev.h>
#include <linux/io.h>
#include <linux/clk-provider.h>
#include <linux/of_address.h>
#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);