root/drivers/clk/at91/clk-peripheral.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. clk_peripheral_enable
  2. clk_peripheral_disable
  3. clk_peripheral_is_enabled
  4. at91_clk_register_peripheral
  5. clk_sam9x5_peripheral_autodiv
  6. clk_sam9x5_peripheral_enable
  7. clk_sam9x5_peripheral_disable
  8. clk_sam9x5_peripheral_is_enabled
  9. clk_sam9x5_peripheral_recalc_rate
  10. clk_sam9x5_peripheral_round_rate
  11. clk_sam9x5_peripheral_set_rate
  12. at91_clk_register_sam9x5_peripheral

   1 // SPDX-License-Identifier: GPL-2.0-or-later
   2 /*
   3  *  Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
   4  */
   5 
   6 #include <linux/bitops.h>
   7 #include <linux/clk-provider.h>
   8 #include <linux/clkdev.h>
   9 #include <linux/clk/at91_pmc.h>
  10 #include <linux/of.h>
  11 #include <linux/mfd/syscon.h>
  12 #include <linux/regmap.h>
  13 
  14 #include "pmc.h"
  15 
  16 DEFINE_SPINLOCK(pmc_pcr_lock);
  17 
  18 #define PERIPHERAL_ID_MIN       2
  19 #define PERIPHERAL_ID_MAX       31
  20 #define PERIPHERAL_MASK(id)     (1 << ((id) & PERIPHERAL_ID_MAX))
  21 
  22 #define PERIPHERAL_MAX_SHIFT    3
  23 
  24 struct clk_peripheral {
  25         struct clk_hw hw;
  26         struct regmap *regmap;
  27         u32 id;
  28 };
  29 
  30 #define to_clk_peripheral(hw) container_of(hw, struct clk_peripheral, hw)
  31 
  32 struct clk_sam9x5_peripheral {
  33         struct clk_hw hw;
  34         struct regmap *regmap;
  35         struct clk_range range;
  36         spinlock_t *lock;
  37         u32 id;
  38         u32 div;
  39         const struct clk_pcr_layout *layout;
  40         bool auto_div;
  41 };
  42 
  43 #define to_clk_sam9x5_peripheral(hw) \
  44         container_of(hw, struct clk_sam9x5_peripheral, hw)
  45 
  46 static int clk_peripheral_enable(struct clk_hw *hw)
  47 {
  48         struct clk_peripheral *periph = to_clk_peripheral(hw);
  49         int offset = AT91_PMC_PCER;
  50         u32 id = periph->id;
  51 
  52         if (id < PERIPHERAL_ID_MIN)
  53                 return 0;
  54         if (id > PERIPHERAL_ID_MAX)
  55                 offset = AT91_PMC_PCER1;
  56         regmap_write(periph->regmap, offset, PERIPHERAL_MASK(id));
  57 
  58         return 0;
  59 }
  60 
  61 static void clk_peripheral_disable(struct clk_hw *hw)
  62 {
  63         struct clk_peripheral *periph = to_clk_peripheral(hw);
  64         int offset = AT91_PMC_PCDR;
  65         u32 id = periph->id;
  66 
  67         if (id < PERIPHERAL_ID_MIN)
  68                 return;
  69         if (id > PERIPHERAL_ID_MAX)
  70                 offset = AT91_PMC_PCDR1;
  71         regmap_write(periph->regmap, offset, PERIPHERAL_MASK(id));
  72 }
  73 
  74 static int clk_peripheral_is_enabled(struct clk_hw *hw)
  75 {
  76         struct clk_peripheral *periph = to_clk_peripheral(hw);
  77         int offset = AT91_PMC_PCSR;
  78         unsigned int status;
  79         u32 id = periph->id;
  80 
  81         if (id < PERIPHERAL_ID_MIN)
  82                 return 1;
  83         if (id > PERIPHERAL_ID_MAX)
  84                 offset = AT91_PMC_PCSR1;
  85         regmap_read(periph->regmap, offset, &status);
  86 
  87         return status & PERIPHERAL_MASK(id) ? 1 : 0;
  88 }
  89 
  90 static const struct clk_ops peripheral_ops = {
  91         .enable = clk_peripheral_enable,
  92         .disable = clk_peripheral_disable,
  93         .is_enabled = clk_peripheral_is_enabled,
  94 };
  95 
  96 struct clk_hw * __init
  97 at91_clk_register_peripheral(struct regmap *regmap, const char *name,
  98                              const char *parent_name, u32 id)
  99 {
 100         struct clk_peripheral *periph;
 101         struct clk_init_data init;
 102         struct clk_hw *hw;
 103         int ret;
 104 
 105         if (!name || !parent_name || id > PERIPHERAL_ID_MAX)
 106                 return ERR_PTR(-EINVAL);
 107 
 108         periph = kzalloc(sizeof(*periph), GFP_KERNEL);
 109         if (!periph)
 110                 return ERR_PTR(-ENOMEM);
 111 
 112         init.name = name;
 113         init.ops = &peripheral_ops;
 114         init.parent_names = (parent_name ? &parent_name : NULL);
 115         init.num_parents = (parent_name ? 1 : 0);
 116         init.flags = 0;
 117 
 118         periph->id = id;
 119         periph->hw.init = &init;
 120         periph->regmap = regmap;
 121 
 122         hw = &periph->hw;
 123         ret = clk_hw_register(NULL, &periph->hw);
 124         if (ret) {
 125                 kfree(periph);
 126                 hw = ERR_PTR(ret);
 127         }
 128 
 129         return hw;
 130 }
 131 
 132 static void clk_sam9x5_peripheral_autodiv(struct clk_sam9x5_peripheral *periph)
 133 {
 134         struct clk_hw *parent;
 135         unsigned long parent_rate;
 136         int shift = 0;
 137 
 138         if (!periph->auto_div)
 139                 return;
 140 
 141         if (periph->range.max) {
 142                 parent = clk_hw_get_parent_by_index(&periph->hw, 0);
 143                 parent_rate = clk_hw_get_rate(parent);
 144                 if (!parent_rate)
 145                         return;
 146 
 147                 for (; shift < PERIPHERAL_MAX_SHIFT; shift++) {
 148                         if (parent_rate >> shift <= periph->range.max)
 149                                 break;
 150                 }
 151         }
 152 
 153         periph->auto_div = false;
 154         periph->div = shift;
 155 }
 156 
 157 static int clk_sam9x5_peripheral_enable(struct clk_hw *hw)
 158 {
 159         struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
 160         unsigned long flags;
 161 
 162         if (periph->id < PERIPHERAL_ID_MIN)
 163                 return 0;
 164 
 165         spin_lock_irqsave(periph->lock, flags);
 166         regmap_write(periph->regmap, periph->layout->offset,
 167                      (periph->id & periph->layout->pid_mask));
 168         regmap_update_bits(periph->regmap, periph->layout->offset,
 169                            periph->layout->div_mask | periph->layout->cmd |
 170                            AT91_PMC_PCR_EN,
 171                            field_prep(periph->layout->div_mask, periph->div) |
 172                            periph->layout->cmd |
 173                            AT91_PMC_PCR_EN);
 174         spin_unlock_irqrestore(periph->lock, flags);
 175 
 176         return 0;
 177 }
 178 
 179 static void clk_sam9x5_peripheral_disable(struct clk_hw *hw)
 180 {
 181         struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
 182         unsigned long flags;
 183 
 184         if (periph->id < PERIPHERAL_ID_MIN)
 185                 return;
 186 
 187         spin_lock_irqsave(periph->lock, flags);
 188         regmap_write(periph->regmap, periph->layout->offset,
 189                      (periph->id & periph->layout->pid_mask));
 190         regmap_update_bits(periph->regmap, periph->layout->offset,
 191                            AT91_PMC_PCR_EN | periph->layout->cmd,
 192                            periph->layout->cmd);
 193         spin_unlock_irqrestore(periph->lock, flags);
 194 }
 195 
 196 static int clk_sam9x5_peripheral_is_enabled(struct clk_hw *hw)
 197 {
 198         struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
 199         unsigned long flags;
 200         unsigned int status;
 201 
 202         if (periph->id < PERIPHERAL_ID_MIN)
 203                 return 1;
 204 
 205         spin_lock_irqsave(periph->lock, flags);
 206         regmap_write(periph->regmap, periph->layout->offset,
 207                      (periph->id & periph->layout->pid_mask));
 208         regmap_read(periph->regmap, periph->layout->offset, &status);
 209         spin_unlock_irqrestore(periph->lock, flags);
 210 
 211         return status & AT91_PMC_PCR_EN ? 1 : 0;
 212 }
 213 
 214 static unsigned long
 215 clk_sam9x5_peripheral_recalc_rate(struct clk_hw *hw,
 216                                   unsigned long parent_rate)
 217 {
 218         struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
 219         unsigned long flags;
 220         unsigned int status;
 221 
 222         if (periph->id < PERIPHERAL_ID_MIN)
 223                 return parent_rate;
 224 
 225         spin_lock_irqsave(periph->lock, flags);
 226         regmap_write(periph->regmap, periph->layout->offset,
 227                      (periph->id & periph->layout->pid_mask));
 228         regmap_read(periph->regmap, periph->layout->offset, &status);
 229         spin_unlock_irqrestore(periph->lock, flags);
 230 
 231         if (status & AT91_PMC_PCR_EN) {
 232                 periph->div = field_get(periph->layout->div_mask, status);
 233                 periph->auto_div = false;
 234         } else {
 235                 clk_sam9x5_peripheral_autodiv(periph);
 236         }
 237 
 238         return parent_rate >> periph->div;
 239 }
 240 
 241 static long clk_sam9x5_peripheral_round_rate(struct clk_hw *hw,
 242                                              unsigned long rate,
 243                                              unsigned long *parent_rate)
 244 {
 245         int shift = 0;
 246         unsigned long best_rate;
 247         unsigned long best_diff;
 248         unsigned long cur_rate = *parent_rate;
 249         unsigned long cur_diff;
 250         struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
 251 
 252         if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max)
 253                 return *parent_rate;
 254 
 255         if (periph->range.max) {
 256                 for (; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
 257                         cur_rate = *parent_rate >> shift;
 258                         if (cur_rate <= periph->range.max)
 259                                 break;
 260                 }
 261         }
 262 
 263         if (rate >= cur_rate)
 264                 return cur_rate;
 265 
 266         best_diff = cur_rate - rate;
 267         best_rate = cur_rate;
 268         for (; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
 269                 cur_rate = *parent_rate >> shift;
 270                 if (cur_rate < rate)
 271                         cur_diff = rate - cur_rate;
 272                 else
 273                         cur_diff = cur_rate - rate;
 274 
 275                 if (cur_diff < best_diff) {
 276                         best_diff = cur_diff;
 277                         best_rate = cur_rate;
 278                 }
 279 
 280                 if (!best_diff || cur_rate < rate)
 281                         break;
 282         }
 283 
 284         return best_rate;
 285 }
 286 
 287 static int clk_sam9x5_peripheral_set_rate(struct clk_hw *hw,
 288                                           unsigned long rate,
 289                                           unsigned long parent_rate)
 290 {
 291         int shift;
 292         struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
 293         if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max) {
 294                 if (parent_rate == rate)
 295                         return 0;
 296                 else
 297                         return -EINVAL;
 298         }
 299 
 300         if (periph->range.max && rate > periph->range.max)
 301                 return -EINVAL;
 302 
 303         for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
 304                 if (parent_rate >> shift == rate) {
 305                         periph->auto_div = false;
 306                         periph->div = shift;
 307                         return 0;
 308                 }
 309         }
 310 
 311         return -EINVAL;
 312 }
 313 
 314 static const struct clk_ops sam9x5_peripheral_ops = {
 315         .enable = clk_sam9x5_peripheral_enable,
 316         .disable = clk_sam9x5_peripheral_disable,
 317         .is_enabled = clk_sam9x5_peripheral_is_enabled,
 318         .recalc_rate = clk_sam9x5_peripheral_recalc_rate,
 319         .round_rate = clk_sam9x5_peripheral_round_rate,
 320         .set_rate = clk_sam9x5_peripheral_set_rate,
 321 };
 322 
 323 struct clk_hw * __init
 324 at91_clk_register_sam9x5_peripheral(struct regmap *regmap, spinlock_t *lock,
 325                                     const struct clk_pcr_layout *layout,
 326                                     const char *name, const char *parent_name,
 327                                     u32 id, const struct clk_range *range)
 328 {
 329         struct clk_sam9x5_peripheral *periph;
 330         struct clk_init_data init;
 331         struct clk_hw *hw;
 332         int ret;
 333 
 334         if (!name || !parent_name)
 335                 return ERR_PTR(-EINVAL);
 336 
 337         periph = kzalloc(sizeof(*periph), GFP_KERNEL);
 338         if (!periph)
 339                 return ERR_PTR(-ENOMEM);
 340 
 341         init.name = name;
 342         init.ops = &sam9x5_peripheral_ops;
 343         init.parent_names = (parent_name ? &parent_name : NULL);
 344         init.num_parents = (parent_name ? 1 : 0);
 345         init.flags = 0;
 346 
 347         periph->id = id;
 348         periph->hw.init = &init;
 349         periph->div = 0;
 350         periph->regmap = regmap;
 351         periph->lock = lock;
 352         if (layout->div_mask)
 353                 periph->auto_div = true;
 354         periph->layout = layout;
 355         periph->range = *range;
 356 
 357         hw = &periph->hw;
 358         ret = clk_hw_register(NULL, &periph->hw);
 359         if (ret) {
 360                 kfree(periph);
 361                 hw = ERR_PTR(ret);
 362         } else {
 363                 clk_sam9x5_peripheral_autodiv(periph);
 364                 pmc_register_id(id);
 365         }
 366 
 367         return hw;
 368 }

/* [<][>][^][v][top][bottom][index][help] */