root/drivers/gpu/drm/mcde/mcde_clk_div.c
// SPDX-License-Identifier: GPL-2.0
#include <linux/clk-provider.h>
#include <linux/io.h>
#include <linux/regulator/consumer.h>

#include "mcde_drm.h"
#include "mcde_display_regs.h"

/* The MCDE internal clock dividers for FIFO A and B */
struct mcde_clk_div {
        struct clk_hw hw;
        struct mcde *mcde;
        u32 cr;
        u32 cr_div;
};

static int mcde_clk_div_enable(struct clk_hw *hw)
{
        struct mcde_clk_div *cdiv = container_of(hw, struct mcde_clk_div, hw);
        struct mcde *mcde = cdiv->mcde;
        u32 val;

        spin_lock(&mcde->fifo_crx1_lock);
        val = readl(mcde->regs + cdiv->cr);
        /*
         * Select the PLL72 (LCD) clock as parent
         * FIXME: implement other parents.
         */
        val &= ~MCDE_CRX1_CLKSEL_MASK;
        val |= MCDE_CRX1_CLKSEL_CLKPLL72 << MCDE_CRX1_CLKSEL_SHIFT;
        /* Internal clock */
        val |= MCDE_CRA1_CLKTYPE_TVXCLKSEL1;

        /* Clear then set the divider */
        val &= ~(MCDE_CRX1_BCD | MCDE_CRX1_PCD_MASK);
        val |= cdiv->cr_div;

        writel(val, mcde->regs + cdiv->cr);
        spin_unlock(&mcde->fifo_crx1_lock);

        return 0;
}

static int mcde_clk_div_choose_div(struct clk_hw *hw, unsigned long rate,
                                   unsigned long *prate, bool set_parent)
{
        int best_div = 1, div;
        struct clk_hw *parent = clk_hw_get_parent(hw);
        unsigned long best_prate = 0;
        unsigned long best_diff = ~0ul;
        int max_div = (1 << MCDE_CRX1_PCD_BITS) - 1;

        for (div = 1; div < max_div; div++) {
                unsigned long this_prate, div_rate, diff;

                if (set_parent)
                        this_prate = clk_hw_round_rate(parent, rate * div);
                else
                        this_prate = *prate;
                div_rate = DIV_ROUND_UP_ULL(this_prate, div);
                diff = abs(rate - div_rate);

                if (diff < best_diff) {
                        best_div = div;
                        best_diff = diff;
                        best_prate = this_prate;
                }
        }

        *prate = best_prate;
        return best_div;
}

static int mcde_clk_div_determine_rate(struct clk_hw *hw,
                                       struct clk_rate_request *req)
{
        int div = mcde_clk_div_choose_div(hw, req->rate,
                                          &req->best_parent_rate, true);

        req->rate = DIV_ROUND_UP_ULL(req->best_parent_rate, div);

        return 0;
}

static unsigned long mcde_clk_div_recalc_rate(struct clk_hw *hw,
                                               unsigned long prate)
{
        struct mcde_clk_div *cdiv = container_of(hw, struct mcde_clk_div, hw);
        struct mcde *mcde = cdiv->mcde;
        u32 cr;
        int div;

        /*
         * If the MCDE is not powered we can't access registers.
         * It will come up with 0 in the divider register bits, which
         * means "divide by 2".
         */
        if (!regulator_is_enabled(mcde->epod))
                return DIV_ROUND_UP_ULL(prate, 2);

        cr = readl(mcde->regs + cdiv->cr);
        if (cr & MCDE_CRX1_BCD)
                return prate;

        /* 0 in the PCD means "divide by 2", 1 means "divide by 3" etc */
        div = cr & MCDE_CRX1_PCD_MASK;
        div += 2;

        return DIV_ROUND_UP_ULL(prate, div);
}

static int mcde_clk_div_set_rate(struct clk_hw *hw, unsigned long rate,
                                  unsigned long prate)
{
        struct mcde_clk_div *cdiv = container_of(hw, struct mcde_clk_div, hw);
        int div = mcde_clk_div_choose_div(hw, rate, &prate, false);
        u32 cr = 0;

        /*
         * We cache the CR bits to set the divide in the state so that
         * we can call this before we can even write to the hardware.
         */
        if (div == 1) {
                /* Bypass clock divider */
                cr |= MCDE_CRX1_BCD;
        } else {
                div -= 2;
                cr |= div & MCDE_CRX1_PCD_MASK;
        }
        cdiv->cr_div = cr;

        return 0;
}

static const struct clk_ops mcde_clk_div_ops = {
        .enable = mcde_clk_div_enable,
        .recalc_rate = mcde_clk_div_recalc_rate,
        .determine_rate = mcde_clk_div_determine_rate,
        .set_rate = mcde_clk_div_set_rate,
};

int mcde_init_clock_divider(struct mcde *mcde)
{
        struct device *dev = mcde->dev;
        struct mcde_clk_div *fifoa;
        struct mcde_clk_div *fifob;
        const char *parent_name;
        struct clk_init_data fifoa_init = {
                .name = "fifoa",
                .ops = &mcde_clk_div_ops,
                .parent_names = &parent_name,
                .num_parents = 1,
                .flags = CLK_SET_RATE_PARENT,
        };
        struct clk_init_data fifob_init = {
                .name = "fifob",
                .ops = &mcde_clk_div_ops,
                .parent_names = &parent_name,
                .num_parents = 1,
                .flags = CLK_SET_RATE_PARENT,
        };
        int ret;

        spin_lock_init(&mcde->fifo_crx1_lock);
        parent_name = __clk_get_name(mcde->lcd_clk);

        /* Allocate 2 clocks */
        fifoa = devm_kzalloc(dev, sizeof(*fifoa), GFP_KERNEL);
        if (!fifoa)
                return -ENOMEM;
        fifob = devm_kzalloc(dev, sizeof(*fifob), GFP_KERNEL);
        if (!fifob)
                return -ENOMEM;

        fifoa->mcde = mcde;
        fifoa->cr = MCDE_CRA1;
        fifoa->hw.init = &fifoa_init;
        ret = devm_clk_hw_register(dev, &fifoa->hw);
        if (ret) {
                dev_err(dev, "error registering FIFO A clock divider\n");
                return ret;
        }
        mcde->fifoa_clk = fifoa->hw.clk;

        fifob->mcde = mcde;
        fifob->cr = MCDE_CRB1;
        fifob->hw.init = &fifob_init;
        ret = devm_clk_hw_register(dev, &fifob->hw);
        if (ret) {
                dev_err(dev, "error registering FIFO B clock divider\n");
                return ret;
        }
        mcde->fifob_clk = fifob->hw.clk;

        return 0;
}