root/drivers/clk/imx/clk-composite-7ulp.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2016 Freescale Semiconductor, Inc.
 * Copyright 2017~2018 NXP
 *
 */

#include <linux/bits.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/slab.h>

#include "../clk-fractional-divider.h"
#include "clk.h"

#define PCG_PR_MASK             BIT(31)
#define PCG_PCS_SHIFT   24
#define PCG_PCS_MASK    0x7
#define PCG_CGC_SHIFT   30
#define PCG_FRAC_SHIFT  3
#define PCG_FRAC_WIDTH  1
#define PCG_PCD_SHIFT   0
#define PCG_PCD_WIDTH   3

#define SW_RST          BIT(28)

static int pcc_gate_enable(struct clk_hw *hw)
{
        struct clk_gate *gate = to_clk_gate(hw);
        unsigned long flags;
        u32 val;
        int ret;

        ret = clk_gate_ops.enable(hw);
        if (ret)
                return ret;

        /* Make sure the IP's clock is ready before release reset */
        udelay(1);

        spin_lock_irqsave(gate->lock, flags);
        /*
         * release the sw reset for peripherals associated with
         * with this pcc clock.
         */
        val = readl(gate->reg);
        val |= SW_RST;
        writel(val, gate->reg);

        spin_unlock_irqrestore(gate->lock, flags);

        /*
         * Read back the register to make sure the previous write has been
         * done in the target HW register. For IP like GPU, after deassert
         * the reset, need to wait for a while to make sure the sync reset
         * is done
         */
        readl(gate->reg);
        udelay(1);

        return 0;
}

static void pcc_gate_disable(struct clk_hw *hw)
{
        clk_gate_ops.disable(hw);
}

static int pcc_gate_is_enabled(struct clk_hw *hw)
{
        return clk_gate_ops.is_enabled(hw);
}

static const struct clk_ops pcc_gate_ops = {
        .enable = pcc_gate_enable,
        .disable = pcc_gate_disable,
        .is_enabled = pcc_gate_is_enabled,
};

static struct clk_hw *imx_ulp_clk_hw_composite(const char *name,
                                     const char * const *parent_names,
                                     int num_parents, bool mux_present,
                                     bool rate_present, bool gate_present,
                                     void __iomem *reg, bool has_swrst)
{
        struct clk_hw *mux_hw = NULL, *fd_hw = NULL, *gate_hw = NULL;
        struct clk_fractional_divider *fd = NULL;
        struct clk_gate *gate = NULL;
        struct clk_mux *mux = NULL;
        struct clk_hw *hw;
        u32 val;

        val = readl(reg);
        if (!(val & PCG_PR_MASK)) {
                pr_info("PCC PR is 0 for clk:%s, bypass\n", name);
                return NULL;
        }

        if (mux_present) {
                mux = kzalloc_obj(*mux);
                if (!mux)
                        return ERR_PTR(-ENOMEM);
                mux_hw = &mux->hw;
                mux->reg = reg;
                mux->shift = PCG_PCS_SHIFT;
                mux->mask = PCG_PCS_MASK;
                if (has_swrst)
                        mux->lock = &imx_ccm_lock;
        }

        if (rate_present) {
                fd = kzalloc_obj(*fd);
                if (!fd) {
                        kfree(mux);
                        return ERR_PTR(-ENOMEM);
                }
                fd_hw = &fd->hw;
                fd->reg = reg;
                fd->mshift = PCG_FRAC_SHIFT;
                fd->mwidth = PCG_FRAC_WIDTH;
                fd->nshift = PCG_PCD_SHIFT;
                fd->nwidth = PCG_PCD_WIDTH;
                fd->flags = CLK_FRAC_DIVIDER_ZERO_BASED;
                if (has_swrst)
                        fd->lock = &imx_ccm_lock;
        }

        if (gate_present) {
                gate = kzalloc_obj(*gate);
                if (!gate) {
                        kfree(mux);
                        kfree(fd);
                        return ERR_PTR(-ENOMEM);
                }
                gate_hw = &gate->hw;
                gate->reg = reg;
                gate->bit_idx = PCG_CGC_SHIFT;
                if (has_swrst)
                        gate->lock = &imx_ccm_lock;
                /*
                 * make sure clock is gated during clock tree initialization,
                 * the HW ONLY allow clock parent/rate changed with clock gated,
                 * during clock tree initialization, clocks could be enabled
                 * by bootloader, so the HW status will mismatch with clock tree
                 * prepare count, then clock core driver will allow parent/rate
                 * change since the prepare count is zero, but HW actually
                 * prevent the parent/rate change due to the clock is enabled.
                 */
                val = readl_relaxed(reg);
                val &= ~(1 << PCG_CGC_SHIFT);
                writel_relaxed(val, reg);
        }

        hw = clk_hw_register_composite(NULL, name, parent_names, num_parents,
                                       mux_hw, &clk_mux_ops, fd_hw,
                                       &clk_fractional_divider_ops, gate_hw,
                                       has_swrst ? &pcc_gate_ops : &clk_gate_ops, CLK_SET_RATE_GATE |
                                       CLK_SET_PARENT_GATE | CLK_SET_RATE_NO_REPARENT);
        if (IS_ERR(hw)) {
                kfree(mux);
                kfree(fd);
                kfree(gate);
        }

        return hw;
}

struct clk_hw *imx7ulp_clk_hw_composite(const char *name, const char * const *parent_names,
                                int num_parents, bool mux_present, bool rate_present,
                                bool gate_present, void __iomem *reg)
{
        return imx_ulp_clk_hw_composite(name, parent_names, num_parents, mux_present, rate_present,
                                        gate_present, reg, false);
}

struct clk_hw *imx8ulp_clk_hw_composite(const char *name, const char * const *parent_names,
                                int num_parents, bool mux_present, bool rate_present,
                                bool gate_present, void __iomem *reg, bool has_swrst)
{
        return imx_ulp_clk_hw_composite(name, parent_names, num_parents, mux_present, rate_present,
                                        gate_present, reg, has_swrst);
}
EXPORT_SYMBOL_GPL(imx8ulp_clk_hw_composite);