linux/drivers/clk/at91/clk-system.c
<<
>>
Prefs
   1/*
   2 *  Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
   3 *
   4 * This program is free software; you can redistribute it and/or modify
   5 * it under the terms of the GNU General Public License as published by
   6 * the Free Software Foundation; either version 2 of the License, or
   7 * (at your option) any later version.
   8 *
   9 */
  10
  11#include <linux/clk-provider.h>
  12#include <linux/clkdev.h>
  13#include <linux/clk/at91_pmc.h>
  14#include <linux/of.h>
  15#include <linux/mfd/syscon.h>
  16#include <linux/regmap.h>
  17
  18#include "pmc.h"
  19
  20#define SYSTEM_MAX_ID           31
  21
  22#define SYSTEM_MAX_NAME_SZ      32
  23
  24#define to_clk_system(hw) container_of(hw, struct clk_system, hw)
  25struct clk_system {
  26        struct clk_hw hw;
  27        struct regmap *regmap;
  28        u8 id;
  29};
  30
  31static inline int is_pck(int id)
  32{
  33        return (id >= 8) && (id <= 15);
  34}
  35
  36static inline bool clk_system_ready(struct regmap *regmap, int id)
  37{
  38        unsigned int status;
  39
  40        regmap_read(regmap, AT91_PMC_SR, &status);
  41
  42        return status & (1 << id) ? 1 : 0;
  43}
  44
  45static int clk_system_prepare(struct clk_hw *hw)
  46{
  47        struct clk_system *sys = to_clk_system(hw);
  48
  49        regmap_write(sys->regmap, AT91_PMC_SCER, 1 << sys->id);
  50
  51        if (!is_pck(sys->id))
  52                return 0;
  53
  54        while (!clk_system_ready(sys->regmap, sys->id))
  55                cpu_relax();
  56
  57        return 0;
  58}
  59
  60static void clk_system_unprepare(struct clk_hw *hw)
  61{
  62        struct clk_system *sys = to_clk_system(hw);
  63
  64        regmap_write(sys->regmap, AT91_PMC_SCDR, 1 << sys->id);
  65}
  66
  67static int clk_system_is_prepared(struct clk_hw *hw)
  68{
  69        struct clk_system *sys = to_clk_system(hw);
  70        unsigned int status;
  71
  72        regmap_read(sys->regmap, AT91_PMC_SCSR, &status);
  73
  74        if (!(status & (1 << sys->id)))
  75                return 0;
  76
  77        if (!is_pck(sys->id))
  78                return 1;
  79
  80        regmap_read(sys->regmap, AT91_PMC_SR, &status);
  81
  82        return status & (1 << sys->id) ? 1 : 0;
  83}
  84
  85static const struct clk_ops system_ops = {
  86        .prepare = clk_system_prepare,
  87        .unprepare = clk_system_unprepare,
  88        .is_prepared = clk_system_is_prepared,
  89};
  90
  91static struct clk_hw * __init
  92at91_clk_register_system(struct regmap *regmap, const char *name,
  93                         const char *parent_name, u8 id)
  94{
  95        struct clk_system *sys;
  96        struct clk_hw *hw;
  97        struct clk_init_data init;
  98        int ret;
  99
 100        if (!parent_name || id > SYSTEM_MAX_ID)
 101                return ERR_PTR(-EINVAL);
 102
 103        sys = kzalloc(sizeof(*sys), GFP_KERNEL);
 104        if (!sys)
 105                return ERR_PTR(-ENOMEM);
 106
 107        init.name = name;
 108        init.ops = &system_ops;
 109        init.parent_names = &parent_name;
 110        init.num_parents = 1;
 111        init.flags = CLK_SET_RATE_PARENT;
 112
 113        sys->id = id;
 114        sys->hw.init = &init;
 115        sys->regmap = regmap;
 116
 117        hw = &sys->hw;
 118        ret = clk_hw_register(NULL, &sys->hw);
 119        if (ret) {
 120                kfree(sys);
 121                hw = ERR_PTR(ret);
 122        }
 123
 124        return hw;
 125}
 126
 127static void __init of_at91rm9200_clk_sys_setup(struct device_node *np)
 128{
 129        int num;
 130        u32 id;
 131        struct clk_hw *hw;
 132        const char *name;
 133        struct device_node *sysclknp;
 134        const char *parent_name;
 135        struct regmap *regmap;
 136
 137        num = of_get_child_count(np);
 138        if (num > (SYSTEM_MAX_ID + 1))
 139                return;
 140
 141        regmap = syscon_node_to_regmap(of_get_parent(np));
 142        if (IS_ERR(regmap))
 143                return;
 144
 145        for_each_child_of_node(np, sysclknp) {
 146                if (of_property_read_u32(sysclknp, "reg", &id))
 147                        continue;
 148
 149                if (of_property_read_string(np, "clock-output-names", &name))
 150                        name = sysclknp->name;
 151
 152                parent_name = of_clk_get_parent_name(sysclknp, 0);
 153
 154                hw = at91_clk_register_system(regmap, name, parent_name, id);
 155                if (IS_ERR(hw))
 156                        continue;
 157
 158                of_clk_add_hw_provider(sysclknp, of_clk_hw_simple_get, hw);
 159        }
 160}
 161CLK_OF_DECLARE(at91rm9200_clk_sys, "atmel,at91rm9200-clk-system",
 162               of_at91rm9200_clk_sys_setup);
 163