File Index
Files and directories
Sign in
FILE

mcde_clk_div.c

File preview with syntax highlighting.

linux-6.19.14/drivers/gpu/drm/mcde/mcde_clk_div.c C
  1// SPDX-License-Identifier: GPL-2.0
  2#include <linux/clk-provider.h>
  3#include <linux/io.h>
  4#include <linux/regulator/consumer.h>
  5
  6#include "mcde_drm.h"
  7#include "mcde_display_regs.h"
  8
  9/* The MCDE internal clock dividers for FIFO A and B */
 10struct mcde_clk_div {
 11	struct clk_hw hw;
 12	struct mcde *mcde;
 13	u32 cr;
 14	u32 cr_div;
 15};
 16
 17static int mcde_clk_div_enable(struct clk_hw *hw)
 18{
 19	struct mcde_clk_div *cdiv = container_of(hw, struct mcde_clk_div, hw);
 20	struct mcde *mcde = cdiv->mcde;
 21	u32 val;
 22
 23	spin_lock(&mcde->fifo_crx1_lock);
 24	val = readl(mcde->regs + cdiv->cr);
 25	/*
 26	 * Select the PLL72 (LCD) clock as parent
 27	 * FIXME: implement other parents.
 28	 */
 29	val &= ~MCDE_CRX1_CLKSEL_MASK;
 30	val |= MCDE_CRX1_CLKSEL_CLKPLL72 << MCDE_CRX1_CLKSEL_SHIFT;
 31	/* Internal clock */
 32	val |= MCDE_CRA1_CLKTYPE_TVXCLKSEL1;
 33
 34	/* Clear then set the divider */
 35	val &= ~(MCDE_CRX1_BCD | MCDE_CRX1_PCD_MASK);
 36	val |= cdiv->cr_div;
 37
 38	writel(val, mcde->regs + cdiv->cr);
 39	spin_unlock(&mcde->fifo_crx1_lock);
 40
 41	return 0;
 42}
 43
 44static int mcde_clk_div_choose_div(struct clk_hw *hw, unsigned long rate,
 45				   unsigned long *prate, bool set_parent)
 46{
 47	int best_div = 1, div;
 48	struct clk_hw *parent = clk_hw_get_parent(hw);
 49	unsigned long best_prate = 0;
 50	unsigned long best_diff = ~0ul;
 51	int max_div = (1 << MCDE_CRX1_PCD_BITS) - 1;
 52
 53	for (div = 1; div < max_div; div++) {
 54		unsigned long this_prate, div_rate, diff;
 55
 56		if (set_parent)
 57			this_prate = clk_hw_round_rate(parent, rate * div);
 58		else
 59			this_prate = *prate;
 60		div_rate = DIV_ROUND_UP_ULL(this_prate, div);
 61		diff = abs(rate - div_rate);
 62
 63		if (diff < best_diff) {
 64			best_div = div;
 65			best_diff = diff;
 66			best_prate = this_prate;
 67		}
 68	}
 69
 70	*prate = best_prate;
 71	return best_div;
 72}
 73
 74static int mcde_clk_div_determine_rate(struct clk_hw *hw,
 75				       struct clk_rate_request *req)
 76{
 77	int div = mcde_clk_div_choose_div(hw, req->rate,
 78					  &req->best_parent_rate, true);
 79
 80	req->rate = DIV_ROUND_UP_ULL(req->best_parent_rate, div);
 81
 82	return 0;
 83}
 84
 85static unsigned long mcde_clk_div_recalc_rate(struct clk_hw *hw,
 86					       unsigned long prate)
 87{
 88	struct mcde_clk_div *cdiv = container_of(hw, struct mcde_clk_div, hw);
 89	struct mcde *mcde = cdiv->mcde;
 90	u32 cr;
 91	int div;
 92
 93	/*
 94	 * If the MCDE is not powered we can't access registers.
 95	 * It will come up with 0 in the divider register bits, which
 96	 * means "divide by 2".
 97	 */
 98	if (!regulator_is_enabled(mcde->epod))
 99		return DIV_ROUND_UP_ULL(prate, 2);
100
101	cr = readl(mcde->regs + cdiv->cr);
102	if (cr & MCDE_CRX1_BCD)
103		return prate;
104
105	/* 0 in the PCD means "divide by 2", 1 means "divide by 3" etc */
106	div = cr & MCDE_CRX1_PCD_MASK;
107	div += 2;
108
109	return DIV_ROUND_UP_ULL(prate, div);
110}
111
112static int mcde_clk_div_set_rate(struct clk_hw *hw, unsigned long rate,
113				  unsigned long prate)
114{
115	struct mcde_clk_div *cdiv = container_of(hw, struct mcde_clk_div, hw);
116	int div = mcde_clk_div_choose_div(hw, rate, &prate, false);
117	u32 cr = 0;
118
119	/*
120	 * We cache the CR bits to set the divide in the state so that
121	 * we can call this before we can even write to the hardware.
122	 */
123	if (div == 1) {
124		/* Bypass clock divider */
125		cr |= MCDE_CRX1_BCD;
126	} else {
127		div -= 2;
128		cr |= div & MCDE_CRX1_PCD_MASK;
129	}
130	cdiv->cr_div = cr;
131
132	return 0;
133}
134
135static const struct clk_ops mcde_clk_div_ops = {
136	.enable = mcde_clk_div_enable,
137	.recalc_rate = mcde_clk_div_recalc_rate,
138	.determine_rate = mcde_clk_div_determine_rate,
139	.set_rate = mcde_clk_div_set_rate,
140};
141
142int mcde_init_clock_divider(struct mcde *mcde)
143{
144	struct device *dev = mcde->dev;
145	struct mcde_clk_div *fifoa;
146	struct mcde_clk_div *fifob;
147	const char *parent_name;
148	struct clk_init_data fifoa_init = {
149		.name = "fifoa",
150		.ops = &mcde_clk_div_ops,
151		.parent_names = &parent_name,
152		.num_parents = 1,
153		.flags = CLK_SET_RATE_PARENT,
154	};
155	struct clk_init_data fifob_init = {
156		.name = "fifob",
157		.ops = &mcde_clk_div_ops,
158		.parent_names = &parent_name,
159		.num_parents = 1,
160		.flags = CLK_SET_RATE_PARENT,
161	};
162	int ret;
163
164	spin_lock_init(&mcde->fifo_crx1_lock);
165	parent_name = __clk_get_name(mcde->lcd_clk);
166
167	/* Allocate 2 clocks */
168	fifoa = devm_kzalloc(dev, sizeof(*fifoa), GFP_KERNEL);
169	if (!fifoa)
170		return -ENOMEM;
171	fifob = devm_kzalloc(dev, sizeof(*fifob), GFP_KERNEL);
172	if (!fifob)
173		return -ENOMEM;
174
175	fifoa->mcde = mcde;
176	fifoa->cr = MCDE_CRA1;
177	fifoa->hw.init = &fifoa_init;
178	ret = devm_clk_hw_register(dev, &fifoa->hw);
179	if (ret) {
180		dev_err(dev, "error registering FIFO A clock divider\n");
181		return ret;
182	}
183	mcde->fifoa_clk = fifoa->hw.clk;
184
185	fifob->mcde = mcde;
186	fifob->cr = MCDE_CRB1;
187	fifob->hw.init = &fifob_init;
188	ret = devm_clk_hw_register(dev, &fifob->hw);
189	if (ret) {
190		dev_err(dev, "error registering FIFO B clock divider\n");
191		return ret;
192	}
193	mcde->fifob_clk = fifob->hw.clk;
194
195	return 0;
196}
~/ Read-only file preview.