linux/drivers/clk/at91/clk-peripheral.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/of_address.h>
  16#include <linux/io.h>
  17
  18#include "pmc.h"
  19
  20#define PERIPHERAL_MAX          64
  21
  22#define PERIPHERAL_AT91RM9200   0
  23#define PERIPHERAL_AT91SAM9X5   1
  24
  25#define PERIPHERAL_ID_MIN       2
  26#define PERIPHERAL_ID_MAX       31
  27#define PERIPHERAL_MASK(id)     (1 << ((id) & PERIPHERAL_ID_MAX))
  28
  29#define PERIPHERAL_RSHIFT_MASK  0x3
  30#define PERIPHERAL_RSHIFT(val)  (((val) >> 16) & PERIPHERAL_RSHIFT_MASK)
  31
  32#define PERIPHERAL_MAX_SHIFT    3
  33
  34struct clk_peripheral {
  35        struct clk_hw hw;
  36        struct at91_pmc *pmc;
  37        u32 id;
  38};
  39
  40#define to_clk_peripheral(hw) container_of(hw, struct clk_peripheral, hw)
  41
  42struct clk_sam9x5_peripheral {
  43        struct clk_hw hw;
  44        struct at91_pmc *pmc;
  45        struct clk_range range;
  46        u32 id;
  47        u32 div;
  48        bool auto_div;
  49};
  50
  51#define to_clk_sam9x5_peripheral(hw) \
  52        container_of(hw, struct clk_sam9x5_peripheral, hw)
  53
  54static int clk_peripheral_enable(struct clk_hw *hw)
  55{
  56        struct clk_peripheral *periph = to_clk_peripheral(hw);
  57        struct at91_pmc *pmc = periph->pmc;
  58        int offset = AT91_PMC_PCER;
  59        u32 id = periph->id;
  60
  61        if (id < PERIPHERAL_ID_MIN)
  62                return 0;
  63        if (id > PERIPHERAL_ID_MAX)
  64                offset = AT91_PMC_PCER1;
  65        pmc_write(pmc, offset, PERIPHERAL_MASK(id));
  66        return 0;
  67}
  68
  69static void clk_peripheral_disable(struct clk_hw *hw)
  70{
  71        struct clk_peripheral *periph = to_clk_peripheral(hw);
  72        struct at91_pmc *pmc = periph->pmc;
  73        int offset = AT91_PMC_PCDR;
  74        u32 id = periph->id;
  75
  76        if (id < PERIPHERAL_ID_MIN)
  77                return;
  78        if (id > PERIPHERAL_ID_MAX)
  79                offset = AT91_PMC_PCDR1;
  80        pmc_write(pmc, offset, PERIPHERAL_MASK(id));
  81}
  82
  83static int clk_peripheral_is_enabled(struct clk_hw *hw)
  84{
  85        struct clk_peripheral *periph = to_clk_peripheral(hw);
  86        struct at91_pmc *pmc = periph->pmc;
  87        int offset = AT91_PMC_PCSR;
  88        u32 id = periph->id;
  89
  90        if (id < PERIPHERAL_ID_MIN)
  91                return 1;
  92        if (id > PERIPHERAL_ID_MAX)
  93                offset = AT91_PMC_PCSR1;
  94        return !!(pmc_read(pmc, offset) & PERIPHERAL_MASK(id));
  95}
  96
  97static const struct clk_ops peripheral_ops = {
  98        .enable = clk_peripheral_enable,
  99        .disable = clk_peripheral_disable,
 100        .is_enabled = clk_peripheral_is_enabled,
 101};
 102
 103static struct clk * __init
 104at91_clk_register_peripheral(struct at91_pmc *pmc, const char *name,
 105                             const char *parent_name, u32 id)
 106{
 107        struct clk_peripheral *periph;
 108        struct clk *clk = NULL;
 109        struct clk_init_data init;
 110
 111        if (!pmc || !name || !parent_name || id > PERIPHERAL_ID_MAX)
 112                return ERR_PTR(-EINVAL);
 113
 114        periph = kzalloc(sizeof(*periph), GFP_KERNEL);
 115        if (!periph)
 116                return ERR_PTR(-ENOMEM);
 117
 118        init.name = name;
 119        init.ops = &peripheral_ops;
 120        init.parent_names = (parent_name ? &parent_name : NULL);
 121        init.num_parents = (parent_name ? 1 : 0);
 122        init.flags = 0;
 123
 124        periph->id = id;
 125        periph->hw.init = &init;
 126        periph->pmc = pmc;
 127
 128        clk = clk_register(NULL, &periph->hw);
 129        if (IS_ERR(clk))
 130                kfree(periph);
 131
 132        return clk;
 133}
 134
 135static void clk_sam9x5_peripheral_autodiv(struct clk_sam9x5_peripheral *periph)
 136{
 137        struct clk *parent;
 138        unsigned long parent_rate;
 139        int shift = 0;
 140
 141        if (!periph->auto_div)
 142                return;
 143
 144        if (periph->range.max) {
 145                parent = clk_get_parent_by_index(periph->hw.clk, 0);
 146                parent_rate = __clk_get_rate(parent);
 147                if (!parent_rate)
 148                        return;
 149
 150                for (; shift < PERIPHERAL_MAX_SHIFT; shift++) {
 151                        if (parent_rate >> shift <= periph->range.max)
 152                                break;
 153                }
 154        }
 155
 156        periph->auto_div = false;
 157        periph->div = shift;
 158}
 159
 160static int clk_sam9x5_peripheral_enable(struct clk_hw *hw)
 161{
 162        struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
 163        struct at91_pmc *pmc = periph->pmc;
 164
 165        if (periph->id < PERIPHERAL_ID_MIN)
 166                return 0;
 167
 168        pmc_write(pmc, AT91_PMC_PCR, (periph->id & AT91_PMC_PCR_PID) |
 169                                     AT91_PMC_PCR_CMD |
 170                                     AT91_PMC_PCR_DIV(periph->div) |
 171                                     AT91_PMC_PCR_EN);
 172        return 0;
 173}
 174
 175static void clk_sam9x5_peripheral_disable(struct clk_hw *hw)
 176{
 177        struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
 178        struct at91_pmc *pmc = periph->pmc;
 179
 180        if (periph->id < PERIPHERAL_ID_MIN)
 181                return;
 182
 183        pmc_write(pmc, AT91_PMC_PCR, (periph->id & AT91_PMC_PCR_PID) |
 184                                     AT91_PMC_PCR_CMD);
 185}
 186
 187static int clk_sam9x5_peripheral_is_enabled(struct clk_hw *hw)
 188{
 189        struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
 190        struct at91_pmc *pmc = periph->pmc;
 191        int ret;
 192
 193        if (periph->id < PERIPHERAL_ID_MIN)
 194                return 1;
 195
 196        pmc_lock(pmc);
 197        pmc_write(pmc, AT91_PMC_PCR, (periph->id & AT91_PMC_PCR_PID));
 198        ret = !!(pmc_read(pmc, AT91_PMC_PCR) & AT91_PMC_PCR_EN);
 199        pmc_unlock(pmc);
 200
 201        return ret;
 202}
 203
 204static unsigned long
 205clk_sam9x5_peripheral_recalc_rate(struct clk_hw *hw,
 206                                  unsigned long parent_rate)
 207{
 208        struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
 209        struct at91_pmc *pmc = periph->pmc;
 210        u32 tmp;
 211
 212        if (periph->id < PERIPHERAL_ID_MIN)
 213                return parent_rate;
 214
 215        pmc_lock(pmc);
 216        pmc_write(pmc, AT91_PMC_PCR, (periph->id & AT91_PMC_PCR_PID));
 217        tmp = pmc_read(pmc, AT91_PMC_PCR);
 218        pmc_unlock(pmc);
 219
 220        if (tmp & AT91_PMC_PCR_EN) {
 221                periph->div = PERIPHERAL_RSHIFT(tmp);
 222                periph->auto_div = false;
 223        } else {
 224                clk_sam9x5_peripheral_autodiv(periph);
 225        }
 226
 227        return parent_rate >> periph->div;
 228}
 229
 230static long clk_sam9x5_peripheral_round_rate(struct clk_hw *hw,
 231                                             unsigned long rate,
 232                                             unsigned long *parent_rate)
 233{
 234        int shift = 0;
 235        unsigned long best_rate;
 236        unsigned long best_diff;
 237        unsigned long cur_rate = *parent_rate;
 238        unsigned long cur_diff;
 239        struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
 240
 241        if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max)
 242                return *parent_rate;
 243
 244        if (periph->range.max) {
 245                for (; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
 246                        cur_rate = *parent_rate >> shift;
 247                        if (cur_rate <= periph->range.max)
 248                                break;
 249                }
 250        }
 251
 252        if (rate >= cur_rate)
 253                return cur_rate;
 254
 255        best_diff = cur_rate - rate;
 256        best_rate = cur_rate;
 257        for (; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
 258                cur_rate = *parent_rate >> shift;
 259                if (cur_rate < rate)
 260                        cur_diff = rate - cur_rate;
 261                else
 262                        cur_diff = cur_rate - rate;
 263
 264                if (cur_diff < best_diff) {
 265                        best_diff = cur_diff;
 266                        best_rate = cur_rate;
 267                }
 268
 269                if (!best_diff || cur_rate < rate)
 270                        break;
 271        }
 272
 273        return best_rate;
 274}
 275
 276static int clk_sam9x5_peripheral_set_rate(struct clk_hw *hw,
 277                                          unsigned long rate,
 278                                          unsigned long parent_rate)
 279{
 280        int shift;
 281        struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
 282        if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max) {
 283                if (parent_rate == rate)
 284                        return 0;
 285                else
 286                        return -EINVAL;
 287        }
 288
 289        if (periph->range.max && rate > periph->range.max)
 290                return -EINVAL;
 291
 292        for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
 293                if (parent_rate >> shift == rate) {
 294                        periph->auto_div = false;
 295                        periph->div = shift;
 296                        return 0;
 297                }
 298        }
 299
 300        return -EINVAL;
 301}
 302
 303static const struct clk_ops sam9x5_peripheral_ops = {
 304        .enable = clk_sam9x5_peripheral_enable,
 305        .disable = clk_sam9x5_peripheral_disable,
 306        .is_enabled = clk_sam9x5_peripheral_is_enabled,
 307        .recalc_rate = clk_sam9x5_peripheral_recalc_rate,
 308        .round_rate = clk_sam9x5_peripheral_round_rate,
 309        .set_rate = clk_sam9x5_peripheral_set_rate,
 310};
 311
 312static struct clk * __init
 313at91_clk_register_sam9x5_peripheral(struct at91_pmc *pmc, const char *name,
 314                                    const char *parent_name, u32 id,
 315                                    const struct clk_range *range)
 316{
 317        struct clk_sam9x5_peripheral *periph;
 318        struct clk *clk = NULL;
 319        struct clk_init_data init;
 320
 321        if (!pmc || !name || !parent_name)
 322                return ERR_PTR(-EINVAL);
 323
 324        periph = kzalloc(sizeof(*periph), GFP_KERNEL);
 325        if (!periph)
 326                return ERR_PTR(-ENOMEM);
 327
 328        init.name = name;
 329        init.ops = &sam9x5_peripheral_ops;
 330        init.parent_names = (parent_name ? &parent_name : NULL);
 331        init.num_parents = (parent_name ? 1 : 0);
 332        init.flags = 0;
 333
 334        periph->id = id;
 335        periph->hw.init = &init;
 336        periph->div = 0;
 337        periph->pmc = pmc;
 338        periph->auto_div = true;
 339        periph->range = *range;
 340
 341        clk = clk_register(NULL, &periph->hw);
 342        if (IS_ERR(clk))
 343                kfree(periph);
 344        else
 345                clk_sam9x5_peripheral_autodiv(periph);
 346
 347        return clk;
 348}
 349
 350static void __init
 351of_at91_clk_periph_setup(struct device_node *np, struct at91_pmc *pmc, u8 type)
 352{
 353        int num;
 354        u32 id;
 355        struct clk *clk;
 356        const char *parent_name;
 357        const char *name;
 358        struct device_node *periphclknp;
 359
 360        parent_name = of_clk_get_parent_name(np, 0);
 361        if (!parent_name)
 362                return;
 363
 364        num = of_get_child_count(np);
 365        if (!num || num > PERIPHERAL_MAX)
 366                return;
 367
 368        for_each_child_of_node(np, periphclknp) {
 369                if (of_property_read_u32(periphclknp, "reg", &id))
 370                        continue;
 371
 372                if (id >= PERIPHERAL_MAX)
 373                        continue;
 374
 375                if (of_property_read_string(np, "clock-output-names", &name))
 376                        name = periphclknp->name;
 377
 378                if (type == PERIPHERAL_AT91RM9200) {
 379                        clk = at91_clk_register_peripheral(pmc, name,
 380                                                           parent_name, id);
 381                } else {
 382                        struct clk_range range = CLK_RANGE(0, 0);
 383
 384                        of_at91_get_clk_range(periphclknp,
 385                                              "atmel,clk-output-range",
 386                                              &range);
 387
 388                        clk = at91_clk_register_sam9x5_peripheral(pmc, name,
 389                                                                  parent_name,
 390                                                                  id, &range);
 391                }
 392
 393                if (IS_ERR(clk))
 394                        continue;
 395
 396                of_clk_add_provider(periphclknp, of_clk_src_simple_get, clk);
 397        }
 398}
 399
 400void __init of_at91rm9200_clk_periph_setup(struct device_node *np,
 401                                           struct at91_pmc *pmc)
 402{
 403        of_at91_clk_periph_setup(np, pmc, PERIPHERAL_AT91RM9200);
 404}
 405
 406void __init of_at91sam9x5_clk_periph_setup(struct device_node *np,
 407                                           struct at91_pmc *pmc)
 408{
 409        of_at91_clk_periph_setup(np, pmc, PERIPHERAL_AT91SAM9X5);
 410}
 411