diff mbox series

[v2,1/2] clk: renesas: rzv2h-cpg: Add support for dynamic switching divider clocks

Message ID 20240822111631.544886-2-prabhakar.mahadev-lad.rj@bp.renesas.com (mailing list archive)
State Changes Requested, archived
Headers show
Series clk: renesas: rzv2h-cpg: Add divider clock support | expand

Commit Message

Lad, Prabhakar Aug. 22, 2024, 11:16 a.m. UTC
From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>

Add support for dynamic switching divider clocks.

Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
---
v1->v2
- Dropped DDIV_DIVCTL_WIDTH
- width is now extracted from conf
- Updated DDIV_GET_* macros
- Now doing rmw as some of the DIVCTLx require it
---
 drivers/clk/renesas/rzv2h-cpg.c | 165 ++++++++++++++++++++++++++++++++
 drivers/clk/renesas/rzv2h-cpg.h |   7 ++
 2 files changed, 172 insertions(+)

Comments

Geert Uytterhoeven Aug. 26, 2024, 1:34 p.m. UTC | #1
Hi Prabhakar,

On Thu, Aug 22, 2024 at 1:16 PM Prabhakar <prabhakar.csengg@gmail.com> wrote:
> From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
>
> Add support for dynamic switching divider clocks.
>
> Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
> ---
> v1->v2
> - Dropped DDIV_DIVCTL_WIDTH
> - width is now extracted from conf
> - Updated DDIV_GET_* macros
> - Now doing rmw as some of the DIVCTLx require it

Thanks for the update!

> --- a/drivers/clk/renesas/rzv2h-cpg.c
> +++ b/drivers/clk/renesas/rzv2h-cpg.c
> @@ -45,14 +45,23 @@
>  #define PDIV(val)              FIELD_GET(GENMASK(5, 0), (val))
>  #define SDIV(val)              FIELD_GET(GENMASK(2, 0), (val))
>
> +#define DDIV_DIVCTL_WEN(shift)         (1 << ((shift) + 16))

BIT((shift) + 16)

> +#define DDIV_GET_WIDTH(val)            FIELD_GET(GENMASK(3, 0), (val))
> +#define DDIV_GET_SHIFT(val)            FIELD_GET(GENMASK(7, 4), (val))
> +#define DDIV_GET_REG_OFFSET(val)       FIELD_GET(GENMASK(18, 8), (val))
> +#define DDIV_GET_MON(val)              FIELD_GET(GENMASK(23, 19), (val))

These are not register fields, so you might as well just use C bitfields
accesses instead:

    struct ddiv {
            unsigned int width:4;
            unsigned int shift:4;
            unsigned int offset:11;
            unsigned int monbit:5;
    };

    if ((shift + core->ddiv.width > 16)
            return ERR_PTR(-EINVAL);

(you can put cpg_core_clk.conf and cpg_core_clk.ddiv in a union to save space)

The rest LGTM.

Gr{oetje,eeting}s,

                        Geert
Lad, Prabhakar Aug. 27, 2024, 1:03 p.m. UTC | #2
Hi Geert,

Thank you for the review.

On Mon, Aug 26, 2024 at 2:34 PM Geert Uytterhoeven <geert@linux-m68k.org> wrote:
>
> Hi Prabhakar,
>
> On Thu, Aug 22, 2024 at 1:16 PM Prabhakar <prabhakar.csengg@gmail.com> wrote:
> > From: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
> >
> > Add support for dynamic switching divider clocks.
> >
> > Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
> > ---
> > v1->v2
> > - Dropped DDIV_DIVCTL_WIDTH
> > - width is now extracted from conf
> > - Updated DDIV_GET_* macros
> > - Now doing rmw as some of the DIVCTLx require it
>
> Thanks for the update!
>
> > --- a/drivers/clk/renesas/rzv2h-cpg.c
> > +++ b/drivers/clk/renesas/rzv2h-cpg.c
> > @@ -45,14 +45,23 @@
> >  #define PDIV(val)              FIELD_GET(GENMASK(5, 0), (val))
> >  #define SDIV(val)              FIELD_GET(GENMASK(2, 0), (val))
> >
> > +#define DDIV_DIVCTL_WEN(shift)         (1 << ((shift) + 16))
>
> BIT((shift) + 16)
>
Agreed.

> > +#define DDIV_GET_WIDTH(val)            FIELD_GET(GENMASK(3, 0), (val))
> > +#define DDIV_GET_SHIFT(val)            FIELD_GET(GENMASK(7, 4), (val))
> > +#define DDIV_GET_REG_OFFSET(val)       FIELD_GET(GENMASK(18, 8), (val))
> > +#define DDIV_GET_MON(val)              FIELD_GET(GENMASK(23, 19), (val))
>
> These are not register fields, so you might as well just use C bitfields
> accesses instead:
>
>     struct ddiv {
>             unsigned int width:4;
>             unsigned int shift:4;
>             unsigned int offset:11;
>             unsigned int monbit:5;
>     };
>
>     if ((shift + core->ddiv.width > 16)
>             return ERR_PTR(-EINVAL);
>
> (you can put cpg_core_clk.conf and cpg_core_clk.ddiv in a union to save space)
>
OK, I'll update it to below,

/**
 * struct ddiv - Structure for dynamic switching divider
 *
 * @offset: register offset
 * @shift: position of the divider bit
 * @width: width of the divider
 * @monbit: monitor bit in CPG_CLKSTATUS0 register
 */
struct ddiv {
    unsigned int offset:11;
    unsigned int shift:4;
    unsigned int width:4;
    unsigned int monbit:5;
};

#define DDIV_PACK(_offset, _shift, _width, _monbit) \
    ((struct ddiv){ \
        .offset = _offset, \
        .shift = _shift, \
        .width = _width, \
        .monbit = _monbit \
    })

#define CPG_CDDIV0        (0x400)

#define CDDIV0_DIVCTL2    DDIV_PACK(CPG_CDDIV0, 8, 3, 2)

/**
 * Definitions of CPG Core Clocks
 *
 * These include:
 *   - Clock outputs exported to DT
 *   - External input clocks
 *   - Internal CPG clocks
 */
struct cpg_core_clk {
    const char *name;
    unsigned int id;
    unsigned int parent;
    unsigned int div;
    unsigned int mult;
    unsigned int type;
    union {
        unsigned int conf;
        struct ddiv ddiv;
    } cfg;
    const struct clk_div_table *dtable;
    u32 flag;
};

Cheers,
Prabhakar
diff mbox series

Patch

diff --git a/drivers/clk/renesas/rzv2h-cpg.c b/drivers/clk/renesas/rzv2h-cpg.c
index 34221046dc46..54070dd1c019 100644
--- a/drivers/clk/renesas/rzv2h-cpg.c
+++ b/drivers/clk/renesas/rzv2h-cpg.c
@@ -45,14 +45,23 @@ 
 #define PDIV(val)		FIELD_GET(GENMASK(5, 0), (val))
 #define SDIV(val)		FIELD_GET(GENMASK(2, 0), (val))
 
+#define DDIV_DIVCTL_WEN(shift)		(1 << ((shift) + 16))
+#define DDIV_GET_WIDTH(val)		FIELD_GET(GENMASK(3, 0), (val))
+#define DDIV_GET_SHIFT(val)		FIELD_GET(GENMASK(7, 4), (val))
+#define DDIV_GET_REG_OFFSET(val)	FIELD_GET(GENMASK(18, 8), (val))
+#define DDIV_GET_MON(val)		FIELD_GET(GENMASK(23, 19), (val))
+
 #define GET_MOD_CLK_ID(base, index, bit)		\
 			((base) + ((((index) * (16))) + (bit)))
 
+#define CPG_CLKSTATUS0		(0x700)
+
 /**
  * struct rzv2h_cpg_priv - Clock Pulse Generator Private Data
  *
  * @dev: CPG device
  * @base: CPG register block base address
+ * @rmw_lock: protects register accesses
  * @clks: Array containing all Core and Module Clocks
  * @num_core_clks: Number of Core Clocks in clks[]
  * @num_mod_clks: Number of Module Clocks in clks[]
@@ -64,6 +73,7 @@ 
 struct rzv2h_cpg_priv {
 	struct device *dev;
 	void __iomem *base;
+	spinlock_t rmw_lock;
 
 	struct clk **clks;
 	unsigned int num_core_clks;
@@ -108,6 +118,21 @@  struct mod_clock {
 
 #define to_mod_clock(_hw) container_of(_hw, struct mod_clock, hw)
 
+/**
+ * struct ddiv_clk - DDIV clock
+ *
+ * @priv: CPG private data
+ * @div: divider clk
+ * @mon: monitor bit in CPG_CLKSTATUS0 register
+ */
+struct ddiv_clk {
+	struct rzv2h_cpg_priv *priv;
+	struct clk_divider div;
+	u8 mon;
+};
+
+#define to_ddiv_clock(_div) container_of(_div, struct ddiv_clk, div)
+
 static unsigned long rzv2h_cpg_pll_clk_recalc_rate(struct clk_hw *hw,
 						   unsigned long parent_rate)
 {
@@ -173,6 +198,141 @@  rzv2h_cpg_pll_clk_register(const struct cpg_core_clk *core,
 	return pll_clk->hw.clk;
 }
 
+static unsigned long rzv2h_ddiv_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct clk_divider *divider = to_clk_divider(hw);
+	unsigned int val;
+
+	val = readl(divider->reg) >> divider->shift;
+	val &= clk_div_mask(divider->width);
+
+	return divider_recalc_rate(hw, parent_rate, val, divider->table,
+				   divider->flags, divider->width);
+}
+
+static long rzv2h_ddiv_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *prate)
+{
+	struct clk_divider *divider = to_clk_divider(hw);
+
+	return divider_round_rate(hw, rate, prate, divider->table,
+				  divider->width, divider->flags);
+}
+
+static int rzv2h_ddiv_determine_rate(struct clk_hw *hw,
+				     struct clk_rate_request *req)
+{
+	struct clk_divider *divider = to_clk_divider(hw);
+
+	return divider_determine_rate(hw, req, divider->table, divider->width,
+				      divider->flags);
+}
+
+static inline int rzv2h_cpg_wait_ddiv_clk_update_done(void __iomem *base, u8 mon)
+{
+	u32 bitmask = BIT(mon);
+	u32 val;
+
+	return readl_poll_timeout_atomic(base + CPG_CLKSTATUS0, val, !(val & bitmask), 10, 200);
+}
+
+static int rzv2h_ddiv_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct clk_divider *divider = to_clk_divider(hw);
+	struct ddiv_clk *ddiv = to_ddiv_clock(divider);
+	struct rzv2h_cpg_priv *priv = ddiv->priv;
+	unsigned long flags = 0;
+	int value;
+	u32 val;
+	int ret;
+
+	value = divider_get_val(rate, parent_rate, divider->table,
+				divider->width, divider->flags);
+	if (value < 0)
+		return value;
+
+	spin_lock_irqsave(divider->lock, flags);
+
+	ret = rzv2h_cpg_wait_ddiv_clk_update_done(priv->base, ddiv->mon);
+	if (ret)
+		goto ddiv_timeout;
+
+	val = readl(divider->reg) | DDIV_DIVCTL_WEN(divider->shift);
+	val &= ~(clk_div_mask(divider->width) << divider->shift);
+	val |= (u32)value << divider->shift;
+	writel(val, divider->reg);
+
+	ret = rzv2h_cpg_wait_ddiv_clk_update_done(priv->base, ddiv->mon);
+	if (ret)
+		goto ddiv_timeout;
+
+	spin_unlock_irqrestore(divider->lock, flags);
+
+	return 0;
+
+ddiv_timeout:
+	spin_unlock_irqrestore(divider->lock, flags);
+	return ret;
+}
+
+static const struct clk_ops rzv2h_ddiv_clk_divider_ops = {
+	.recalc_rate = rzv2h_ddiv_recalc_rate,
+	.round_rate = rzv2h_ddiv_round_rate,
+	.determine_rate = rzv2h_ddiv_determine_rate,
+	.set_rate = rzv2h_ddiv_set_rate,
+};
+
+static struct clk * __init
+rzv2h_cpg_ddiv_clk_register(const struct cpg_core_clk *core,
+			    struct rzv2h_cpg_priv *priv)
+{
+	u8 shift = DDIV_GET_SHIFT(core->conf);
+	struct clk_init_data init = {};
+	struct device *dev = priv->dev;
+	const struct clk *parent;
+	const char *parent_name;
+	struct clk_divider *div;
+	struct ddiv_clk *ddiv;
+	int ret;
+
+	parent = priv->clks[core->parent];
+	if (IS_ERR(parent))
+		return ERR_CAST(parent);
+
+	parent_name = __clk_get_name(parent);
+
+	if ((shift + DDIV_GET_WIDTH(core->conf)) > 16)
+		return ERR_PTR(-EINVAL);
+
+	ddiv = devm_kzalloc(priv->dev, sizeof(*ddiv), GFP_KERNEL);
+	if (!ddiv)
+		return ERR_PTR(-ENOMEM);
+
+	init.name = core->name;
+	init.ops = &rzv2h_ddiv_clk_divider_ops;
+	init.parent_names = &parent_name;
+	init.num_parents = 1;
+
+	ddiv->priv = priv;
+	ddiv->mon = DDIV_GET_MON(core->conf);
+	div = &ddiv->div;
+	div->reg = priv->base + DDIV_GET_REG_OFFSET(core->conf);
+	div->shift = shift;
+	div->width = DDIV_GET_WIDTH(core->conf);
+	div->flags = core->flag;
+	div->lock = &priv->rmw_lock;
+	div->hw.init = &init;
+	div->table = core->dtable;
+
+	ret = devm_clk_hw_register(dev, &div->hw);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return div->hw.clk;
+}
+
 static struct clk
 *rzv2h_cpg_clk_src_twocell_get(struct of_phandle_args *clkspec,
 			       void *data)
@@ -254,6 +414,9 @@  rzv2h_cpg_register_core_clk(const struct cpg_core_clk *core,
 	case CLK_TYPE_PLL:
 		clk = rzv2h_cpg_pll_clk_register(core, priv, &rzv2h_cpg_pll_ops);
 		break;
+	case CLK_TYPE_DDIV:
+		clk = rzv2h_cpg_ddiv_clk_register(core, priv);
+		break;
 	default:
 		goto fail;
 	}
@@ -612,6 +775,8 @@  static int __init rzv2h_cpg_probe(struct platform_device *pdev)
 	if (!priv)
 		return -ENOMEM;
 
+	spin_lock_init(&priv->rmw_lock);
+
 	priv->dev = dev;
 
 	priv->base = devm_platform_ioremap_resource(pdev, 0);
diff --git a/drivers/clk/renesas/rzv2h-cpg.h b/drivers/clk/renesas/rzv2h-cpg.h
index 6df59e041701..936af15b648a 100644
--- a/drivers/clk/renesas/rzv2h-cpg.h
+++ b/drivers/clk/renesas/rzv2h-cpg.h
@@ -24,6 +24,8 @@  struct cpg_core_clk {
 	unsigned int mult;
 	unsigned int type;
 	unsigned int conf;
+	const struct clk_div_table *dtable;
+	u32 flag;
 };
 
 enum clk_types {
@@ -31,6 +33,7 @@  enum clk_types {
 	CLK_TYPE_IN,		/* External Clock Input */
 	CLK_TYPE_FF,		/* Fixed Factor Clock */
 	CLK_TYPE_PLL,
+	CLK_TYPE_DDIV,		/* Dynamic Switching Divider */
 };
 
 /* BIT(31) indicates if CLK1/2 are accessible or not */
@@ -49,6 +52,10 @@  enum clk_types {
 	DEF_TYPE(_name, _id, CLK_TYPE_IN)
 #define DEF_FIXED(_name, _id, _parent, _mult, _div) \
 	DEF_BASE(_name, _id, CLK_TYPE_FF, _parent, .div = _div, .mult = _mult)
+#define DEF_DDIV(_name, _id, _parent, _conf, _dtable) \
+	DEF_TYPE(_name, _id, CLK_TYPE_DDIV, .conf = _conf, \
+		 .parent = _parent, .dtable = _dtable, \
+		 .flag = CLK_DIVIDER_HIWORD_MASK)
 
 /**
  * struct rzv2h_mod_clk - Module Clocks definitions