linux/drivers/clk/at91/clk-smd.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 SMD_SOURCE_MAX          2
  21
  22#define SMD_DIV_SHIFT           8
  23#define SMD_MAX_DIV             0xf
  24
  25struct at91sam9x5_clk_smd {
  26        struct clk_hw hw;
  27        struct regmap *regmap;
  28};
  29
  30#define to_at91sam9x5_clk_smd(hw) \
  31        container_of(hw, struct at91sam9x5_clk_smd, hw)
  32
  33static unsigned long at91sam9x5_clk_smd_recalc_rate(struct clk_hw *hw,
  34                                                    unsigned long parent_rate)
  35{
  36        struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(hw);
  37        unsigned int smdr;
  38        u8 smddiv;
  39
  40        regmap_read(smd->regmap, AT91_PMC_SMD, &smdr);
  41        smddiv = (smdr & AT91_PMC_SMD_DIV) >> SMD_DIV_SHIFT;
  42
  43        return parent_rate / (smddiv + 1);
  44}
  45
  46static long at91sam9x5_clk_smd_round_rate(struct clk_hw *hw, unsigned long rate,
  47                                          unsigned long *parent_rate)
  48{
  49        unsigned long div;
  50        unsigned long bestrate;
  51        unsigned long tmp;
  52
  53        if (rate >= *parent_rate)
  54                return *parent_rate;
  55
  56        div = *parent_rate / rate;
  57        if (div > SMD_MAX_DIV)
  58                return *parent_rate / (SMD_MAX_DIV + 1);
  59
  60        bestrate = *parent_rate / div;
  61        tmp = *parent_rate / (div + 1);
  62        if (bestrate - rate > rate - tmp)
  63                bestrate = tmp;
  64
  65        return bestrate;
  66}
  67
  68static int at91sam9x5_clk_smd_set_parent(struct clk_hw *hw, u8 index)
  69{
  70        struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(hw);
  71
  72        if (index > 1)
  73                return -EINVAL;
  74
  75        regmap_update_bits(smd->regmap, AT91_PMC_SMD, AT91_PMC_SMDS,
  76                           index ? AT91_PMC_SMDS : 0);
  77
  78        return 0;
  79}
  80
  81static u8 at91sam9x5_clk_smd_get_parent(struct clk_hw *hw)
  82{
  83        struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(hw);
  84        unsigned int smdr;
  85
  86        regmap_read(smd->regmap, AT91_PMC_SMD, &smdr);
  87
  88        return smdr & AT91_PMC_SMDS;
  89}
  90
  91static int at91sam9x5_clk_smd_set_rate(struct clk_hw *hw, unsigned long rate,
  92                                       unsigned long parent_rate)
  93{
  94        struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(hw);
  95        unsigned long div = parent_rate / rate;
  96
  97        if (parent_rate % rate || div < 1 || div > (SMD_MAX_DIV + 1))
  98                return -EINVAL;
  99
 100        regmap_update_bits(smd->regmap, AT91_PMC_SMD, AT91_PMC_SMD_DIV,
 101                           (div - 1) << SMD_DIV_SHIFT);
 102
 103        return 0;
 104}
 105
 106static const struct clk_ops at91sam9x5_smd_ops = {
 107        .recalc_rate = at91sam9x5_clk_smd_recalc_rate,
 108        .round_rate = at91sam9x5_clk_smd_round_rate,
 109        .get_parent = at91sam9x5_clk_smd_get_parent,
 110        .set_parent = at91sam9x5_clk_smd_set_parent,
 111        .set_rate = at91sam9x5_clk_smd_set_rate,
 112};
 113
 114static struct clk_hw * __init
 115at91sam9x5_clk_register_smd(struct regmap *regmap, const char *name,
 116                            const char **parent_names, u8 num_parents)
 117{
 118        struct at91sam9x5_clk_smd *smd;
 119        struct clk_hw *hw;
 120        struct clk_init_data init;
 121        int ret;
 122
 123        smd = kzalloc(sizeof(*smd), GFP_KERNEL);
 124        if (!smd)
 125                return ERR_PTR(-ENOMEM);
 126
 127        init.name = name;
 128        init.ops = &at91sam9x5_smd_ops;
 129        init.parent_names = parent_names;
 130        init.num_parents = num_parents;
 131        init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
 132
 133        smd->hw.init = &init;
 134        smd->regmap = regmap;
 135
 136        hw = &smd->hw;
 137        ret = clk_hw_register(NULL, &smd->hw);
 138        if (ret) {
 139                kfree(smd);
 140                hw = ERR_PTR(ret);
 141        }
 142
 143        return hw;
 144}
 145
 146static void __init of_at91sam9x5_clk_smd_setup(struct device_node *np)
 147{
 148        struct clk_hw *hw;
 149        unsigned int num_parents;
 150        const char *parent_names[SMD_SOURCE_MAX];
 151        const char *name = np->name;
 152        struct regmap *regmap;
 153
 154        num_parents = of_clk_get_parent_count(np);
 155        if (num_parents == 0 || num_parents > SMD_SOURCE_MAX)
 156                return;
 157
 158        of_clk_parent_fill(np, parent_names, num_parents);
 159
 160        of_property_read_string(np, "clock-output-names", &name);
 161
 162        regmap = syscon_node_to_regmap(of_get_parent(np));
 163        if (IS_ERR(regmap))
 164                return;
 165
 166        hw = at91sam9x5_clk_register_smd(regmap, name, parent_names,
 167                                          num_parents);
 168        if (IS_ERR(hw))
 169                return;
 170
 171        of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
 172}
 173CLK_OF_DECLARE(at91sam9x5_clk_smd, "atmel,at91sam9x5-clk-smd",
 174               of_at91sam9x5_clk_smd_setup);
 175