linux/drivers/clk/imx/clk-pfd.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Copyright 2012 Freescale Semiconductor, Inc.
   4 * Copyright 2012 Linaro Ltd.
   5 */
   6
   7#include <linux/clk-provider.h>
   8#include <linux/io.h>
   9#include <linux/slab.h>
  10#include <linux/err.h>
  11#include "clk.h"
  12
  13/**
  14 * struct clk_pfd - IMX PFD clock
  15 * @clk_hw:     clock source
  16 * @reg:        PFD register address
  17 * @idx:        the index of PFD encoded in the register
  18 *
  19 * PFD clock found on i.MX6 series.  Each register for PFD has 4 clk_pfd
  20 * data encoded, and member idx is used to specify the one.  And each
  21 * register has SET, CLR and TOG registers at offset 0x4 0x8 and 0xc.
  22 */
  23struct clk_pfd {
  24        struct clk_hw   hw;
  25        void __iomem    *reg;
  26        u8              idx;
  27};
  28
  29#define to_clk_pfd(_hw) container_of(_hw, struct clk_pfd, hw)
  30
  31#define SET     0x4
  32#define CLR     0x8
  33#define OTG     0xc
  34
  35static int clk_pfd_enable(struct clk_hw *hw)
  36{
  37        struct clk_pfd *pfd = to_clk_pfd(hw);
  38
  39        writel_relaxed(1 << ((pfd->idx + 1) * 8 - 1), pfd->reg + CLR);
  40
  41        return 0;
  42}
  43
  44static void clk_pfd_disable(struct clk_hw *hw)
  45{
  46        struct clk_pfd *pfd = to_clk_pfd(hw);
  47
  48        writel_relaxed(1 << ((pfd->idx + 1) * 8 - 1), pfd->reg + SET);
  49}
  50
  51static unsigned long clk_pfd_recalc_rate(struct clk_hw *hw,
  52                                         unsigned long parent_rate)
  53{
  54        struct clk_pfd *pfd = to_clk_pfd(hw);
  55        u64 tmp = parent_rate;
  56        u8 frac = (readl_relaxed(pfd->reg) >> (pfd->idx * 8)) & 0x3f;
  57
  58        tmp *= 18;
  59        do_div(tmp, frac);
  60
  61        return tmp;
  62}
  63
  64static long clk_pfd_round_rate(struct clk_hw *hw, unsigned long rate,
  65                               unsigned long *prate)
  66{
  67        u64 tmp = *prate;
  68        u8 frac;
  69
  70        tmp = tmp * 18 + rate / 2;
  71        do_div(tmp, rate);
  72        frac = tmp;
  73        if (frac < 12)
  74                frac = 12;
  75        else if (frac > 35)
  76                frac = 35;
  77        tmp = *prate;
  78        tmp *= 18;
  79        do_div(tmp, frac);
  80
  81        return tmp;
  82}
  83
  84static int clk_pfd_set_rate(struct clk_hw *hw, unsigned long rate,
  85                unsigned long parent_rate)
  86{
  87        struct clk_pfd *pfd = to_clk_pfd(hw);
  88        u64 tmp = parent_rate;
  89        u8 frac;
  90
  91        tmp = tmp * 18 + rate / 2;
  92        do_div(tmp, rate);
  93        frac = tmp;
  94        if (frac < 12)
  95                frac = 12;
  96        else if (frac > 35)
  97                frac = 35;
  98
  99        writel_relaxed(0x3f << (pfd->idx * 8), pfd->reg + CLR);
 100        writel_relaxed(frac << (pfd->idx * 8), pfd->reg + SET);
 101
 102        return 0;
 103}
 104
 105static int clk_pfd_is_enabled(struct clk_hw *hw)
 106{
 107        struct clk_pfd *pfd = to_clk_pfd(hw);
 108
 109        if (readl_relaxed(pfd->reg) & (1 << ((pfd->idx + 1) * 8 - 1)))
 110                return 0;
 111
 112        return 1;
 113}
 114
 115static const struct clk_ops clk_pfd_ops = {
 116        .enable         = clk_pfd_enable,
 117        .disable        = clk_pfd_disable,
 118        .recalc_rate    = clk_pfd_recalc_rate,
 119        .round_rate     = clk_pfd_round_rate,
 120        .set_rate       = clk_pfd_set_rate,
 121        .is_enabled     = clk_pfd_is_enabled,
 122};
 123
 124struct clk_hw *imx_clk_hw_pfd(const char *name, const char *parent_name,
 125                        void __iomem *reg, u8 idx)
 126{
 127        struct clk_pfd *pfd;
 128        struct clk_hw *hw;
 129        struct clk_init_data init;
 130        int ret;
 131
 132        pfd = kzalloc(sizeof(*pfd), GFP_KERNEL);
 133        if (!pfd)
 134                return ERR_PTR(-ENOMEM);
 135
 136        pfd->reg = reg;
 137        pfd->idx = idx;
 138
 139        init.name = name;
 140        init.ops = &clk_pfd_ops;
 141        init.flags = 0;
 142        init.parent_names = &parent_name;
 143        init.num_parents = 1;
 144
 145        pfd->hw.init = &init;
 146        hw = &pfd->hw;
 147
 148        ret = clk_hw_register(NULL, hw);
 149        if (ret) {
 150                kfree(pfd);
 151                return ERR_PTR(ret);
 152        }
 153
 154        return hw;
 155}
 156