linux/drivers/clk/versatile/clk-sp810.c
<<
>>
Prefs
   1/*
   2 * This program is free software; you can redistribute it and/or modify
   3 * it under the terms of the GNU General Public License version 2 as
   4 * published by the Free Software Foundation.
   5 *
   6 * This program is distributed in the hope that it will be useful,
   7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
   8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   9 * GNU General Public License for more details.
  10 *
  11 * Copyright (C) 2013 ARM Limited
  12 */
  13
  14#include <linux/amba/sp810.h>
  15#include <linux/clkdev.h>
  16#include <linux/clk-provider.h>
  17#include <linux/err.h>
  18#include <linux/of.h>
  19#include <linux/of_address.h>
  20
  21#define to_clk_sp810_timerclken(_hw) \
  22                container_of(_hw, struct clk_sp810_timerclken, hw)
  23
  24struct clk_sp810;
  25
  26struct clk_sp810_timerclken {
  27        struct clk_hw hw;
  28        struct clk *clk;
  29        struct clk_sp810 *sp810;
  30        int channel;
  31};
  32
  33struct clk_sp810 {
  34        struct device_node *node;
  35        int refclk_index, timclk_index;
  36        void __iomem *base;
  37        spinlock_t lock;
  38        struct clk_sp810_timerclken timerclken[4];
  39        struct clk *refclk;
  40        struct clk *timclk;
  41};
  42
  43static u8 clk_sp810_timerclken_get_parent(struct clk_hw *hw)
  44{
  45        struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw);
  46        u32 val = readl(timerclken->sp810->base + SCCTRL);
  47
  48        return !!(val & (1 << SCCTRL_TIMERENnSEL_SHIFT(timerclken->channel)));
  49}
  50
  51static int clk_sp810_timerclken_set_parent(struct clk_hw *hw, u8 index)
  52{
  53        struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw);
  54        struct clk_sp810 *sp810 = timerclken->sp810;
  55        u32 val, shift = SCCTRL_TIMERENnSEL_SHIFT(timerclken->channel);
  56        unsigned long flags = 0;
  57
  58        if (WARN_ON(index > 1))
  59                return -EINVAL;
  60
  61        spin_lock_irqsave(&sp810->lock, flags);
  62
  63        val = readl(sp810->base + SCCTRL);
  64        val &= ~(1 << shift);
  65        val |= index << shift;
  66        writel(val, sp810->base + SCCTRL);
  67
  68        spin_unlock_irqrestore(&sp810->lock, flags);
  69
  70        return 0;
  71}
  72
  73/*
  74 * FIXME - setting the parent every time .prepare is invoked is inefficient.
  75 * This is better handled by a dedicated clock tree configuration mechanism at
  76 * init-time.  Revisit this later when such a mechanism exists
  77 */
  78static int clk_sp810_timerclken_prepare(struct clk_hw *hw)
  79{
  80        struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw);
  81        struct clk_sp810 *sp810 = timerclken->sp810;
  82        struct clk *old_parent = __clk_get_parent(hw->clk);
  83        struct clk *new_parent;
  84
  85        if (!sp810->refclk)
  86                sp810->refclk = of_clk_get(sp810->node, sp810->refclk_index);
  87
  88        if (!sp810->timclk)
  89                sp810->timclk = of_clk_get(sp810->node, sp810->timclk_index);
  90
  91        if (WARN_ON(IS_ERR(sp810->refclk) || IS_ERR(sp810->timclk)))
  92                return -ENOENT;
  93
  94        /* Select fastest parent */
  95        if (clk_get_rate(sp810->refclk) > clk_get_rate(sp810->timclk))
  96                new_parent = sp810->refclk;
  97        else
  98                new_parent = sp810->timclk;
  99
 100        /* Switch the parent if necessary */
 101        if (old_parent != new_parent) {
 102                clk_prepare(new_parent);
 103                clk_set_parent(hw->clk, new_parent);
 104                clk_unprepare(old_parent);
 105        }
 106
 107        return 0;
 108}
 109
 110static void clk_sp810_timerclken_unprepare(struct clk_hw *hw)
 111{
 112        struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw);
 113        struct clk_sp810 *sp810 = timerclken->sp810;
 114
 115        clk_put(sp810->timclk);
 116        clk_put(sp810->refclk);
 117}
 118
 119static const struct clk_ops clk_sp810_timerclken_ops = {
 120        .prepare = clk_sp810_timerclken_prepare,
 121        .unprepare = clk_sp810_timerclken_unprepare,
 122        .get_parent = clk_sp810_timerclken_get_parent,
 123        .set_parent = clk_sp810_timerclken_set_parent,
 124};
 125
 126static struct clk *clk_sp810_timerclken_of_get(struct of_phandle_args *clkspec,
 127                void *data)
 128{
 129        struct clk_sp810 *sp810 = data;
 130
 131        if (WARN_ON(clkspec->args_count != 1 || clkspec->args[0] >
 132                        ARRAY_SIZE(sp810->timerclken)))
 133                return NULL;
 134
 135        return sp810->timerclken[clkspec->args[0]].clk;
 136}
 137
 138void __init clk_sp810_of_setup(struct device_node *node)
 139{
 140        struct clk_sp810 *sp810 = kzalloc(sizeof(*sp810), GFP_KERNEL);
 141        const char *parent_names[2];
 142        char name[12];
 143        struct clk_init_data init;
 144        int i;
 145
 146        if (!sp810) {
 147                pr_err("Failed to allocate memory for SP810!\n");
 148                return;
 149        }
 150
 151        sp810->refclk_index = of_property_match_string(node, "clock-names",
 152                        "refclk");
 153        parent_names[0] = of_clk_get_parent_name(node, sp810->refclk_index);
 154
 155        sp810->timclk_index = of_property_match_string(node, "clock-names",
 156                        "timclk");
 157        parent_names[1] = of_clk_get_parent_name(node, sp810->timclk_index);
 158
 159        if (parent_names[0] <= 0 || parent_names[1] <= 0) {
 160                pr_warn("Failed to obtain parent clocks for SP810!\n");
 161                return;
 162        }
 163
 164        sp810->node = node;
 165        sp810->base = of_iomap(node, 0);
 166        spin_lock_init(&sp810->lock);
 167
 168        init.name = name;
 169        init.ops = &clk_sp810_timerclken_ops;
 170        init.flags = CLK_IS_BASIC;
 171        init.parent_names = parent_names;
 172        init.num_parents = ARRAY_SIZE(parent_names);
 173
 174        for (i = 0; i < ARRAY_SIZE(sp810->timerclken); i++) {
 175                snprintf(name, ARRAY_SIZE(name), "timerclken%d", i);
 176
 177                sp810->timerclken[i].sp810 = sp810;
 178                sp810->timerclken[i].channel = i;
 179                sp810->timerclken[i].hw.init = &init;
 180
 181                sp810->timerclken[i].clk = clk_register(NULL,
 182                                &sp810->timerclken[i].hw);
 183                WARN_ON(IS_ERR(sp810->timerclken[i].clk));
 184        }
 185
 186        of_clk_add_provider(node, clk_sp810_timerclken_of_get, sp810);
 187}
 188CLK_OF_DECLARE(sp810, "arm,sp810", clk_sp810_of_setup);
 189