root/drivers/clk/qcom/clk-hfpll.c
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018, The Linux Foundation. All rights reserved.

#include <linux/kernel.h>
#include <linux/export.h>
#include <linux/regmap.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/clk-provider.h>
#include <linux/spinlock.h>

#include "clk-regmap.h"
#include "clk-hfpll.h"

#define PLL_OUTCTRL     BIT(0)
#define PLL_BYPASSNL    BIT(1)
#define PLL_RESET_N     BIT(2)

/* Initialize a HFPLL at a given rate and enable it. */
static void __clk_hfpll_init_once(struct clk_hw *hw)
{
        struct clk_hfpll *h = to_clk_hfpll(hw);
        struct hfpll_data const *hd = h->d;
        struct regmap *regmap = h->clkr.regmap;

        if (likely(h->init_done))
                return;

        /* Configure PLL parameters for integer mode. */
        if (hd->config_val)
                regmap_write(regmap, hd->config_reg, hd->config_val);
        regmap_write(regmap, hd->m_reg, 0);
        regmap_write(regmap, hd->n_reg, 1);

        if (hd->user_reg) {
                u32 regval = hd->user_val;
                unsigned long rate;

                rate = clk_hw_get_rate(hw);

                /* Pick the right VCO. */
                if (hd->user_vco_mask && rate > hd->low_vco_max_rate)
                        regval |= hd->user_vco_mask;
                regmap_write(regmap, hd->user_reg, regval);
        }

        /* Write L_VAL from conf if it exist */
        if (hd->l_val)
                regmap_write(regmap, hd->l_reg, hd->l_val);

        if (hd->droop_reg)
                regmap_write(regmap, hd->droop_reg, hd->droop_val);

        h->init_done = true;
}

static void __clk_hfpll_enable(struct clk_hw *hw)
{
        struct clk_hfpll *h = to_clk_hfpll(hw);
        struct hfpll_data const *hd = h->d;
        struct regmap *regmap = h->clkr.regmap;
        u32 val;

        __clk_hfpll_init_once(hw);

        /* Disable PLL bypass mode. */
        regmap_update_bits(regmap, hd->mode_reg, PLL_BYPASSNL, PLL_BYPASSNL);

        /*
         * H/W requires a 5us delay between disabling the bypass and
         * de-asserting the reset. Delay 10us just to be safe.
         */
        udelay(10);

        /* De-assert active-low PLL reset. */
        regmap_update_bits(regmap, hd->mode_reg, PLL_RESET_N, PLL_RESET_N);

        /* Wait for PLL to lock. */
        if (hd->status_reg)
                /*
                 * Busy wait. Should never timeout, we add a timeout to
                 * prevent any sort of stall.
                 */
                regmap_read_poll_timeout(regmap, hd->status_reg, val,
                                         !(val & BIT(hd->lock_bit)), 0,
                                         100 * USEC_PER_MSEC);
        else
                udelay(60);

        /* Enable PLL output. */
        regmap_update_bits(regmap, hd->mode_reg, PLL_OUTCTRL, PLL_OUTCTRL);
}

/* Enable an already-configured HFPLL. */
static int clk_hfpll_enable(struct clk_hw *hw)
{
        unsigned long flags;
        struct clk_hfpll *h = to_clk_hfpll(hw);
        struct hfpll_data const *hd = h->d;
        struct regmap *regmap = h->clkr.regmap;
        u32 mode;

        spin_lock_irqsave(&h->lock, flags);
        regmap_read(regmap, hd->mode_reg, &mode);
        if (!(mode & (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)))
                __clk_hfpll_enable(hw);
        spin_unlock_irqrestore(&h->lock, flags);

        return 0;
}

static void __clk_hfpll_disable(struct clk_hfpll *h)
{
        struct hfpll_data const *hd = h->d;
        struct regmap *regmap = h->clkr.regmap;

        /*
         * Disable the PLL output, disable test mode, enable the bypass mode,
         * and assert the reset.
         */
        regmap_update_bits(regmap, hd->mode_reg,
                           PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL, 0);
}

static void clk_hfpll_disable(struct clk_hw *hw)
{
        struct clk_hfpll *h = to_clk_hfpll(hw);
        unsigned long flags;

        spin_lock_irqsave(&h->lock, flags);
        __clk_hfpll_disable(h);
        spin_unlock_irqrestore(&h->lock, flags);
}

static int clk_hfpll_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
{
        struct clk_hfpll *h = to_clk_hfpll(hw);
        struct hfpll_data const *hd = h->d;
        unsigned long rrate;

        req->rate = clamp(req->rate, hd->min_rate, hd->max_rate);

        rrate = DIV_ROUND_UP(req->rate, req->best_parent_rate) * req->best_parent_rate;
        if (rrate > hd->max_rate)
                rrate -= req->best_parent_rate;

        req->rate = rrate;
        return 0;
}

/*
 * For optimization reasons, assumes no downstream clocks are actively using
 * it.
 */
static int clk_hfpll_set_rate(struct clk_hw *hw, unsigned long rate,
                              unsigned long parent_rate)
{
        struct clk_hfpll *h = to_clk_hfpll(hw);
        struct hfpll_data const *hd = h->d;
        struct regmap *regmap = h->clkr.regmap;
        unsigned long flags;
        u32 l_val, val;
        bool enabled;

        l_val = rate / parent_rate;

        spin_lock_irqsave(&h->lock, flags);

        enabled = __clk_is_enabled(hw->clk);
        if (enabled)
                __clk_hfpll_disable(h);

        /* Pick the right VCO. */
        if (hd->user_reg && hd->user_vco_mask) {
                regmap_read(regmap, hd->user_reg, &val);
                if (rate <= hd->low_vco_max_rate)
                        val &= ~hd->user_vco_mask;
                else
                        val |= hd->user_vco_mask;
                regmap_write(regmap, hd->user_reg, val);
        }

        regmap_write(regmap, hd->l_reg, l_val);

        if (enabled)
                __clk_hfpll_enable(hw);

        spin_unlock_irqrestore(&h->lock, flags);

        return 0;
}

static unsigned long clk_hfpll_recalc_rate(struct clk_hw *hw,
                                           unsigned long parent_rate)
{
        struct clk_hfpll *h = to_clk_hfpll(hw);
        struct hfpll_data const *hd = h->d;
        struct regmap *regmap = h->clkr.regmap;
        u32 l_val;

        regmap_read(regmap, hd->l_reg, &l_val);

        return l_val * parent_rate;
}

static int clk_hfpll_init(struct clk_hw *hw)
{
        struct clk_hfpll *h = to_clk_hfpll(hw);
        struct hfpll_data const *hd = h->d;
        struct regmap *regmap = h->clkr.regmap;
        u32 mode, status;

        regmap_read(regmap, hd->mode_reg, &mode);
        if (mode != (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL)) {
                __clk_hfpll_init_once(hw);
                return 0;
        }

        if (hd->status_reg) {
                regmap_read(regmap, hd->status_reg, &status);
                if (!(status & BIT(hd->lock_bit))) {
                        WARN(1, "HFPLL %s is ON, but not locked!\n",
                             __clk_get_name(hw->clk));
                        clk_hfpll_disable(hw);
                        __clk_hfpll_init_once(hw);
                }
        }

        return 0;
}

static int hfpll_is_enabled(struct clk_hw *hw)
{
        struct clk_hfpll *h = to_clk_hfpll(hw);
        struct hfpll_data const *hd = h->d;
        struct regmap *regmap = h->clkr.regmap;
        u32 mode;

        regmap_read(regmap, hd->mode_reg, &mode);
        mode &= 0x7;
        return mode == (PLL_BYPASSNL | PLL_RESET_N | PLL_OUTCTRL);
}

const struct clk_ops clk_ops_hfpll = {
        .enable = clk_hfpll_enable,
        .disable = clk_hfpll_disable,
        .is_enabled = hfpll_is_enabled,
        .determine_rate = clk_hfpll_determine_rate,
        .set_rate = clk_hfpll_set_rate,
        .recalc_rate = clk_hfpll_recalc_rate,
        .init = clk_hfpll_init,
};
EXPORT_SYMBOL_GPL(clk_ops_hfpll);