| // 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; |
| } |