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/slab.h>
  16#include <linux/clk.h>
  17#include <linux/clk-provider.h>
  18#include <linux/err.h>
  19#include <linux/of.h>
  20#include <linux/of_address.h>
  21
  22#define to_clk_sp810_timerclken(_hw) \
  23                container_of(_hw, struct clk_sp810_timerclken, hw)
  24
  25struct clk_sp810;
  26
  27struct clk_sp810_timerclken {
  28        struct clk_hw hw;
  29        struct clk *clk;
  30        struct clk_sp810 *sp810;
  31        int channel;
  32};
  33
  34struct clk_sp810 {
  35        struct device_node *node;
  36        void __iomem *base;
  37        spinlock_t lock;
  38        struct clk_sp810_timerclken timerclken[4];
  39};
  40
  41static u8 clk_sp810_timerclken_get_parent(struct clk_hw *hw)
  42{
  43        struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw);
  44        u32 val = readl(timerclken->sp810->base + SCCTRL);
  45
  46        return !!(val & (1 << SCCTRL_TIMERENnSEL_SHIFT(timerclken->channel)));
  47}
  48
  49static int clk_sp810_timerclken_set_parent(struct clk_hw *hw, u8 index)
  50{
  51        struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw);
  52        struct clk_sp810 *sp810 = timerclken->sp810;
  53        u32 val, shift = SCCTRL_TIMERENnSEL_SHIFT(timerclken->channel);
  54        unsigned long flags = 0;
  55
  56        if (WARN_ON(index > 1))
  57                return -EINVAL;
  58
  59        spin_lock_irqsave(&sp810->lock, flags);
  60
  61        val = readl(sp810->base + SCCTRL);
  62        val &= ~(1 << shift);
  63        val |= index << shift;
  64        writel(val, sp810->base + SCCTRL);
  65
  66        spin_unlock_irqrestore(&sp810->lock, flags);
  67
  68        return 0;
  69}
  70
  71static const struct clk_ops clk_sp810_timerclken_ops = {
  72        .get_parent = clk_sp810_timerclken_get_parent,
  73        .set_parent = clk_sp810_timerclken_set_parent,
  74};
  75
  76static struct clk *clk_sp810_timerclken_of_get(struct of_phandle_args *clkspec,
  77                void *data)
  78{
  79        struct clk_sp810 *sp810 = data;
  80
  81        if (WARN_ON(clkspec->args_count != 1 ||
  82                    clkspec->args[0] >= ARRAY_SIZE(sp810->timerclken)))
  83                return NULL;
  84
  85        return sp810->timerclken[clkspec->args[0]].clk;
  86}
  87
  88static void __init clk_sp810_of_setup(struct device_node *node)
  89{
  90        struct clk_sp810 *sp810 = kzalloc(sizeof(*sp810), GFP_KERNEL);
  91        const char *parent_names[2];
  92        int num = ARRAY_SIZE(parent_names);
  93        char name[12];
  94        struct clk_init_data init;
  95        static int instance;
  96        int i;
  97        bool deprecated;
  98
  99        if (!sp810)
 100                return;
 101
 102        if (of_clk_parent_fill(node, parent_names, num) != num) {
 103                pr_warn("Failed to obtain parent clocks for SP810!\n");
 104                kfree(sp810);
 105                return;
 106        }
 107
 108        sp810->node = node;
 109        sp810->base = of_iomap(node, 0);
 110        spin_lock_init(&sp810->lock);
 111
 112        init.name = name;
 113        init.ops = &clk_sp810_timerclken_ops;
 114        init.flags = CLK_IS_BASIC;
 115        init.parent_names = parent_names;
 116        init.num_parents = num;
 117
 118        deprecated = !of_find_property(node, "assigned-clock-parents", NULL);
 119
 120        for (i = 0; i < ARRAY_SIZE(sp810->timerclken); i++) {
 121                snprintf(name, sizeof(name), "sp810_%d_%d", instance, i);
 122
 123                sp810->timerclken[i].sp810 = sp810;
 124                sp810->timerclken[i].channel = i;
 125                sp810->timerclken[i].hw.init = &init;
 126
 127                /*
 128                 * If DT isn't setting the parent, force it to be
 129                 * the 1 MHz clock without going through the framework.
 130                 * We do this before clk_register() so that it can determine
 131                 * the parent and setup the tree properly.
 132                 */
 133                if (deprecated)
 134                        init.ops->set_parent(&sp810->timerclken[i].hw, 1);
 135
 136                sp810->timerclken[i].clk = clk_register(NULL,
 137                                &sp810->timerclken[i].hw);
 138                WARN_ON(IS_ERR(sp810->timerclken[i].clk));
 139        }
 140
 141        of_clk_add_provider(node, clk_sp810_timerclken_of_get, sp810);
 142        instance++;
 143}
 144CLK_OF_DECLARE(sp810, "arm,sp810", clk_sp810_of_setup);
 145