|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | #include <linux/clk-provider.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 long mcde_clk_div_round_rate(struct clk_hw *hw, unsigned long rate, | 
|  | unsigned long *prate) | 
|  | { | 
|  | int div = mcde_clk_div_choose_div(hw, rate, prate, true); | 
|  |  | 
|  | return DIV_ROUND_UP_ULL(*prate, div); | 
|  | } | 
|  |  | 
|  | 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, | 
|  | .round_rate = mcde_clk_div_round_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; | 
|  | } |