OpenWrt – Rev 1

Subversion Repositories:
Rev:
/*
 * Moschip MCS814x clock routines
 *
 * Copyright (C) 2012, Florian Fainelli <florian@openwrt.org>
 *
 * Licensed under GPLv2
 */
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/export.h>
#include <linux/spinlock.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/clkdev.h>
#include <linux/clk.h>

#include <mach/mcs814x.h>

#include "common.h"

#define KHZ     1000
#define MHZ     (KHZ * KHZ)

struct clk_ops {
        unsigned long (*get_rate)(struct clk *clk);
        int (*set_rate)(struct clk *clk, unsigned long rate);
        struct clk *(*get_parent)(struct clk *clk);
        int (*enable)(struct clk *clk, int enable);
};

struct clk {
        struct clk *parent;             /* parent clk */
        unsigned long rate;             /* clock rate in Hz */
        unsigned long divider;          /* clock divider */
        u32 usecount;                   /* reference count */
        struct clk_ops *ops;            /* clock operation */
        u32 enable_reg;                 /* clock enable register */
        u32 enable_mask;                /* clock enable mask */
};

static unsigned long clk_divide_parent(struct clk *clk)
{
        if (clk->parent && clk->divider)
                return clk_get_rate(clk->parent) / clk->divider;
        else
                return 0;
}

static int clk_local_onoff_enable(struct clk *clk, int enable)
{
        u32 tmp;

        /* no enable_reg means the clock is always enabled */
        if (!clk->enable_reg)
                return 0;

        tmp = readl_relaxed(mcs814x_sysdbg_base + clk->enable_reg);
        if (!enable)
                tmp &= ~clk->enable_mask;
        else
                tmp |= clk->enable_mask;

        writel_relaxed(tmp, mcs814x_sysdbg_base + clk->enable_reg);

        return 0;
}

static struct clk_ops default_clk_ops = {
        .get_rate       = clk_divide_parent,
        .enable         = clk_local_onoff_enable,
};

static DEFINE_SPINLOCK(clocks_lock);

static const unsigned long cpu_freq_table[] = {
        175000,
        300000,
        125000,
        137500,
        212500,
        250000,
        162500,
        187500,
        162500,
        150000,
        225000,
        237500,
        200000,
        262500,
        275000,
        287500
};

static struct clk clk_cpu;

/* System clock is fixed at 50Mhz */
static struct clk clk_sys = {
        .rate   = 50 * MHZ,
};

static struct clk clk_sdram;

static struct clk clk_timer0 = {
        .parent = &clk_sdram,
        .divider = 2,
        .ops    = &default_clk_ops,
};

static struct clk clk_timer1_2 = {
        .parent = &clk_sys,
};

/* Watchdog clock is system clock / 128 */
static struct clk clk_wdt = {
        .parent = &clk_sys,
        .divider = 128,
        .ops    = &default_clk_ops,
};

static struct clk clk_emac = {
        .ops            = &default_clk_ops,
        .enable_reg     = SYSDBG_SYSCTL,
        .enable_mask    = SYSCTL_EMAC,
};

static struct clk clk_ephy = {
        .ops            = &default_clk_ops,
        .enable_reg     = SYSDBG_PLL_CTL,
        .enable_mask    = ~SYSCTL_EPHY, /* active low */
};

static struct clk clk_cipher = {
        .ops            = &default_clk_ops,
        .enable_reg     = SYSDBG_SYSCTL,
        .enable_mask    = SYSCTL_CIPHER,
};

#define CLK(_dev, _con, _clk)   \
{ .dev_id = (_dev), .con_id = (_con), .clk = (_clk) },

static struct clk_lookup mcs814x_chip_clks[] = {
        CLK("cpu", NULL, &clk_cpu)
        CLK("sys", NULL, &clk_sys)
        CLK("sdram", NULL, &clk_sdram)
        /* 32-bits timer0 */
        CLK("timer0", NULL, &clk_timer0)
        /* 16-bits timer1 */
        CLK("timer1", NULL, &clk_timer1_2)
        /* 64-bits timer2, same as timer 1 */
        CLK("timer2", NULL, &clk_timer1_2)
        CLK(NULL, "wdt", &clk_wdt)
        CLK(NULL, "emac", &clk_emac)
        CLK(NULL, "ephy", &clk_ephy)
        CLK(NULL, "cipher", &clk_cipher)
};

static void local_clk_disable(struct clk *clk)
{
        WARN_ON(!clk->usecount);

        if (clk->usecount > 0) {
                clk->usecount--;

                if ((clk->usecount == 0) && (clk->ops->enable))
                        clk->ops->enable(clk, 0);

                if (clk->parent)
                        local_clk_disable(clk->parent);
        }
}

static int local_clk_enable(struct clk *clk)
{
        int ret = 0;

        if (clk->parent)
                ret = local_clk_enable(clk->parent);

        if (ret)
                return ret;

        if ((clk->usecount == 0) && (clk->ops->enable))
                ret = clk->ops->enable(clk, 1);

        if (!ret)
                clk->usecount++;
        else if (clk->parent && clk->parent->ops->enable)
                local_clk_disable(clk->parent);

        return ret;
}

int clk_enable(struct clk *clk)
{
        int ret;
        unsigned long flags;

        spin_lock_irqsave(&clocks_lock, flags);
        ret = local_clk_enable(clk);
        spin_unlock_irqrestore(&clocks_lock, flags);

        return ret;
}
EXPORT_SYMBOL(clk_enable);

void clk_disable(struct clk *clk)
{
        unsigned long flags;

        spin_lock_irqsave(&clocks_lock, flags);
        local_clk_disable(clk);
        spin_unlock_irqrestore(&clocks_lock, flags);
}
EXPORT_SYMBOL(clk_disable);

unsigned long clk_get_rate(struct clk *clk)
{
        if (unlikely(IS_ERR_OR_NULL(clk)))
                return 0;

        if (clk->rate)
                return clk->rate;

        if (clk->ops && clk->ops->get_rate)
                return clk->ops->get_rate(clk);

        return clk_get_rate(clk->parent);
}
EXPORT_SYMBOL(clk_get_rate);

struct clk *clk_get_parent(struct clk *clk)
{
        unsigned long flags;

        if (unlikely(IS_ERR_OR_NULL(clk)))
                return NULL;

        if (!clk->ops || !clk->ops->get_parent)
                return clk->parent;

        spin_lock_irqsave(&clocks_lock, flags);
        clk->parent = clk->ops->get_parent(clk);
        spin_unlock_irqrestore(&clocks_lock, flags);

        return clk->parent;
}
EXPORT_SYMBOL(clk_get_parent);

void __init mcs814x_clk_init(void)
{
        u32 bs1;
        u8 cpu_freq;

        clkdev_add_table(mcs814x_chip_clks, ARRAY_SIZE(mcs814x_chip_clks));

        /* read the bootstrap registers to know the exact clocking scheme */
        bs1 = readl_relaxed(mcs814x_sysdbg_base + SYSDBG_BS1);
        cpu_freq = (bs1 >> CPU_FREQ_SHIFT) & CPU_FREQ_MASK;

        pr_info("CPU frequency: %lu (kHz)\n", cpu_freq_table[cpu_freq]);
        clk_cpu.rate = cpu_freq * KHZ;

        /* read SDRAM frequency */
        if (bs1 & SDRAM_FREQ_BIT)
                clk_sdram.rate = 100 * MHZ;
        else
                clk_sdram.rate = 133 * MHZ;

        pr_info("SDRAM frequency: %lu (MHz)\n", clk_sdram.rate / MHZ);
}