Re: [PATCH v3 25/37] clk: ingenic: add driver for Ingenic SoC CGU clocks

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, Apr 22, 2015 at 12:25:32AM +0100, James Hogan wrote:
> On Tue, Apr 21, 2015 at 03:46:52PM +0100, Paul Burton wrote:
> > +static int ingenic_clk_set_parent(struct clk_hw *hw, u8 idx)
> > +{
> > +	struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
> > +	struct ingenic_cgu *cgu = ingenic_clk->cgu;
> > +	const struct ingenic_cgu_clk_info *clk_info;
> > +	unsigned long flags;
> > +	u8 curr_idx, hw_idx, num_poss;
> > +	u32 reg, mask;
> > +
> > +	clk_info = &cgu->clock_info[ingenic_clk->idx];
> > +
> > +	if (clk_info->type & CGU_CLK_MUX) {
> > +		/*
> > +		 * Convert the parent index to the hardware index by adding
> > +		 * 1 for any -1 in the parents array preceding the given
> > +		 * index. That is, we want the index of idx'th entry in
> > +		 * clk_info->parents which does not equal -1.
> > +		 */
> > +		hw_idx = curr_idx = 0;
> > +		num_poss = 1 << clk_info->mux.bits;
> > +		for (; (hw_idx < num_poss) && (curr_idx != idx); hw_idx++) {
> 
> if idx==0, this'll never iterate since curr_idx starts at 0, even if the
> first parent is -1. It's basically an off-by-one error I think.
> 
> > +			if (clk_info->parents[hw_idx] == -1)
> > +				continue;
> > +			curr_idx++;
> 
> maybe move the curr_idx == idx check here.

Sorry. Before the curr_idx++ would of course be better.

> Its getting late. I'll continue looking at this patch some other time.

Here goes...

> > +
> > +static int ingenic_clk_enable(struct clk_hw *hw)
> > +{
> > +	struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
> > +	struct ingenic_cgu *cgu = ingenic_clk->cgu;
> > +	const struct ingenic_cgu_clk_info *clk_info;
> > +	unsigned long flags;
> > +
> > +	clk_info = &cgu->clock_info[ingenic_clk->idx];
> > +
> > +	if (clk_info->type & CGU_CLK_GATE) {
> > +		/* ungate the clock */
> > +		spin_lock_irqsave(&cgu->power_lock, flags);
> > +		ingenic_cgu_gate_set(cgu, &clk_info->gate, false);
> > +		spin_unlock_irqrestore(&cgu->power_lock, flags);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static void ingenic_clk_disable(struct clk_hw *hw)
> > +{
> > +	struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
> > +	struct ingenic_cgu *cgu = ingenic_clk->cgu;
> > +	const struct ingenic_cgu_clk_info *clk_info;
> > +	unsigned long flags;
> > +
> > +	clk_info = &cgu->clock_info[ingenic_clk->idx];
> > +
> > +	if (clk_info->type & CGU_CLK_GATE) {
> > +		/* gate the clock */
> > +		spin_lock_irqsave(&cgu->power_lock, flags);
> > +		ingenic_cgu_gate_set(cgu, &clk_info->gate, true);
> > +		spin_unlock_irqrestore(&cgu->power_lock, flags);
> > +	}
> > +}
> > +
> > +static int ingenic_clk_is_enabled(struct clk_hw *hw)
> > +{
> > +	struct ingenic_clk *ingenic_clk = to_ingenic_clk(hw);
> > +	struct ingenic_cgu *cgu = ingenic_clk->cgu;
> > +	const struct ingenic_cgu_clk_info *clk_info;
> > +	unsigned long flags;
> > +	int enabled = 1;
> > +
> > +	clk_info = &cgu->clock_info[ingenic_clk->idx];
> > +
> > +	if (clk_info->type & CGU_CLK_GATE) {
> > +		spin_lock_irqsave(&cgu->power_lock, flags);
> > +		enabled = !ingenic_cgu_gate_get(cgu, &clk_info->gate);
> > +		spin_unlock_irqrestore(&cgu->power_lock, flags);
> > +	}
> > +
> > +	return enabled;
> > +}
> > +
> > +static const struct clk_ops ingenic_clk_ops = {
> > +	.get_parent = ingenic_clk_get_parent,
> > +	.set_parent = ingenic_clk_set_parent,
> > +
> > +	.recalc_rate = ingenic_clk_recalc_rate,
> > +	.round_rate = ingenic_clk_round_rate,
> > +	.set_rate = ingenic_clk_set_rate,
> > +
> > +	.enable = ingenic_clk_enable,
> > +	.disable = ingenic_clk_disable,
> > +	.is_enabled = ingenic_clk_is_enabled,
> > +};
> > +
> > +/*
> > + * Setup functions.
> > + */
> > +
> > +static int register_clock(struct ingenic_cgu *cgu, unsigned idx)
> > +{
> > +	const struct ingenic_cgu_clk_info *clk_info = &cgu->clock_info[idx];
> > +	struct clk_init_data clk_init;
> > +	struct ingenic_clk *ingenic_clk = NULL;
> > +	struct clk *clk, *parent;
> > +	const char *parent_names[4];
> > +	unsigned caps, i, num_possible;
> > +	int err = -EINVAL;
> > +
> > +	BUILD_BUG_ON(ARRAY_SIZE(clk_info->parents) > ARRAY_SIZE(parent_names));
> > +
> > +	if (clk_info->type == CGU_CLK_EXT) {
> > +		clk = of_clk_get_by_name(cgu->np, clk_info->name);
> > +		if (IS_ERR(clk)) {
> > +			pr_err("%s: no external clock '%s' provided\n",
> > +			       __func__, clk_info->name);
> > +			err = -ENODEV;
> > +			goto out;
> > +		}
> > +		err = clk_register_clkdev(clk, clk_info->name, NULL);
> > +		if (err) {
> > +			clk_put(clk);
> > +			goto out;
> > +		}
> > +		cgu->clocks.clks[idx] = clk;
> > +		return 0;
> > +	}
> > +
> > +	if (!clk_info->type) {
> > +		pr_err("%s: no clock type specified for '%s'\n", __func__,
> > +		       clk_info->name);
> > +		goto out;
> > +	}
> > +
> > +	ingenic_clk = kzalloc(sizeof(*ingenic_clk), GFP_KERNEL);
> > +	if (!ingenic_clk) {
> > +		err = -ENOMEM;
> > +		goto out;
> > +	}
> > +
> > +	ingenic_clk->hw.init = &clk_init;
> > +	ingenic_clk->cgu = cgu;
> > +	ingenic_clk->idx = idx;
> > +
> > +	clk_init.name = clk_info->name;
> > +	clk_init.flags = 0;
> > +	clk_init.parent_names = parent_names;
> > +
> > +	caps = clk_info->type;
> > +
> > +	if (caps & (CGU_CLK_MUX | CGU_CLK_CUSTOM)) {
> > +		clk_init.num_parents = 0;
> > +
> > +		if (caps & CGU_CLK_MUX)
> > +			num_possible = 1 << clk_info->mux.bits;
> > +		else
> > +			num_possible = ARRAY_SIZE(clk_info->parents);
> > +
> > +		for (i = 0; i < num_possible; i++) {
> > +			if (clk_info->parents[i] == -1)
> > +				continue;
> > +
> > +			parent = cgu->clocks.clks[clk_info->parents[i]];
> > +			parent_names[clk_init.num_parents] =
> > +				__clk_get_name(parent);
> > +			clk_init.num_parents++;
> > +		}
> > +
> > +		BUG_ON(!clk_init.num_parents);
> > +		BUG_ON(clk_init.num_parents > ARRAY_SIZE(parent_names));
> > +	} else {
> > +		BUG_ON(clk_info->parents[0] == -1);
> > +		clk_init.num_parents = 1;
> > +		parent = cgu->clocks.clks[clk_info->parents[0]];
> > +		parent_names[0] = __clk_get_name(parent);
> > +	}
> > +
> > +	if (caps & CGU_CLK_CUSTOM) {
> > +		clk_init.ops = clk_info->custom.clk_ops;
> > +
> > +		caps &= ~CGU_CLK_CUSTOM;
> > +
> > +		if (caps) {
> > +			pr_err("%s: custom clock may not be combined with type 0x%x\n",
> > +			       __func__, caps);
> > +			goto out;
> > +		}
> > +	} else if (caps & CGU_CLK_PLL) {
> > +		clk_init.ops = &ingenic_pll_ops;
> > +
> > +		caps &= ~CGU_CLK_PLL;
> > +
> > +		if (caps) {
> > +			pr_err("%s: PLL may not be combined with type 0x%x\n",
> > +			       __func__, caps);
> > +			goto out;
> > +		}
> > +	} else {
> > +		clk_init.ops = &ingenic_clk_ops;
> > +	}
> > +
> > +	/* nothing to do for gates or fixed dividers */
> > +	caps &= ~(CGU_CLK_GATE | CGU_CLK_FIXDIV);
> > +
> > +	if (caps & CGU_CLK_MUX) {
> > +		if (!(caps & CGU_CLK_MUX_GLITCHFREE))
> > +			clk_init.flags |= CLK_SET_PARENT_GATE;
> > +
> > +		caps &= ~(CGU_CLK_MUX | CGU_CLK_MUX_GLITCHFREE);
> > +	}
> > +
> > +	if (caps & CGU_CLK_DIV) {
> > +		caps &= ~CGU_CLK_DIV;
> > +	} else {
> > +		/* pass rate changes to the parent clock */
> > +		clk_init.flags |= CLK_SET_RATE_PARENT;
> > +	}
> > +
> > +	if (caps) {
> > +		pr_err("%s: unknown clock type 0x%x\n", __func__, caps);
> > +		goto out;
> > +	}
> > +
> > +	clk = clk_register(NULL, &ingenic_clk->hw);
> > +	if (IS_ERR(clk)) {
> > +		pr_err("%s: failed to register clock '%s'\n", __func__,
> > +		       clk_info->name);
> > +		err = PTR_ERR(clk);
> > +		goto out;
> > +	}
> > +
> > +	err = clk_register_clkdev(clk, clk_info->name, NULL);
> > +	if (err)
> > +		goto out;

Should this unregister the clock in case of failure?

> > +
> > +	cgu->clocks.clks[idx] = clk;
> > +out:
> > +	if (err)
> > +		kfree(ingenic_clk);
> > +	return err;
> > +}
> > +
> > +struct ingenic_cgu *
> > +ingenic_cgu_new(const struct ingenic_cgu_clk_info *clock_info,
> > +		unsigned num_clocks, struct device_node *np)
> > +{
> > +	struct ingenic_cgu *cgu;
> > +
> > +	cgu = kzalloc(sizeof(*cgu), GFP_KERNEL);
> > +	if (!cgu)
> > +		goto err_out;
> > +
> > +	cgu->base = of_iomap(np, 0);
> > +	if (!cgu->base) {
> > +		pr_err("%s: failed to map CGU registers\n", __func__);
> > +		goto err_out_free;
> > +	}
> > +
> > +	cgu->np = np;
> > +	cgu->clock_info = clock_info;
> > +	cgu->clocks.clk_num = num_clocks;
> > +
> > +	spin_lock_init(&cgu->divmux_lock);
> > +	spin_lock_init(&cgu->power_lock);
> > +	spin_lock_init(&cgu->pll_lock);
> > +
> > +	return cgu;
> > +
> > +err_out_free:
> > +	kfree(cgu);
> > +err_out:
> > +	return NULL;
> > +}
> > +
> > +int ingenic_cgu_register_clocks(struct ingenic_cgu *cgu)
> > +{
> > +	unsigned i;
> > +	int err;
> > +
> > +	cgu->clocks.clks = kcalloc(cgu->clocks.clk_num, sizeof(struct clk *),
> > +				   GFP_KERNEL);
> > +	if (!cgu->clocks.clks) {
> > +		err = -ENOMEM;
> > +		goto err_out;
> > +	}
> > +
> > +	for (i = 0; i < cgu->clocks.clk_num; i++) {
> > +		err = register_clock(cgu, i);
> > +		if (err)
> > +			goto err_out_unregister;
> > +	}
> > +
> > +	err = of_clk_add_provider(cgu->np, of_clk_src_onecell_get,
> > +				  &cgu->clocks);
> > +	if (err)
> > +		goto err_out_unregister;
> > +
> > +	return 0;
> > +
> > +err_out_unregister:
> > +	if (cgu) {

This check looks redundant, since the rest of the function already assumes
non-NULL.

> > +		for (i = 0; i < cgu->clocks.clk_num; i++) {
> > +			if (!cgu->clocks.clks[i])
> > +				continue;
> > +			if (cgu->clock_info[i].type & CGU_CLK_EXT)
> > +				clk_put(cgu->clocks.clks[i]);
> > +			else
> > +				clk_unregister(cgu->clocks.clks[i]);
> > +		}
> > +		kfree(cgu->clocks.clks);
> > +	}
> > +err_out:
> > +	return err;
> > +}
> > diff --git a/drivers/clk/ingenic/cgu.h b/drivers/clk/ingenic/cgu.h
> > new file mode 100644
> > index 0000000..47e0552
> > --- /dev/null
> > +++ b/drivers/clk/ingenic/cgu.h
> > @@ -0,0 +1,223 @@
> > +/*
> > + * Ingenic SoC CGU driver
> > + *
> > + * Copyright (c) 2013-2015 Imagination Technologies
> > + * Author: Paul Burton <paul.burton@xxxxxxxxxx>
> > + *
> > + * This program is free software; you can redistribute it and/or
> > + * modify it under the terms of the GNU General Public License as
> > + * published by the Free Software Foundation; either version 2 of
> > + * the License, or (at your option) any later version.
> > + *
> > + * This program is distributed in the hope that it will be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > + * GNU General Public License for more details.
> > + */
> > +
> > +#ifndef __DRIVERS_CLK_INGENIC_CGU_H__
> > +#define __DRIVERS_CLK_INGENIC_CGU_H__
> > +
> > +#include <linux/of.h>
> > +#include <linux/spinlock.h>
> > +
> > +/**
> > + * struct ingenic_cgu_pll_info - information about a PLL
> > + * @reg: the offset of the PLL's control register within the CGU
> > + * @m_shift: the number of bits to shift the multiplier value by (ie. the
> > + *           index of the lowest bit of the multiplier value in the PLL's
> > + *           control register)
> > + * @m_bits: the size of the multiplier field in bits
> > + * @m_offset: the multiplier value which encodes to 0 in the PLL's control
> > + *            register
> > + * @n_shift: the number of bits to shift the divider value by (ie. the
> > + *           index of the lowest bit of the divider value in the PLL's
> > + *           control register)
> > + * @n_bits: the size of the divider field in bits
> > + * @n_offset: the divider value which encodes to 0 in the PLL's control
> > + *            register
> > + * @od_shift: the number of bits to shift the post-VCO divider value by (ie.
> > + *            the index of the lowest bit of the post-VCO divider value in
> > + *            the PLL's control register)
> > + * @od_bits: the size of the post-VCO divider field in bits
> > + * @od_max: the maximum post-VCO divider value
> > + * @od_encoding: a pointer to an array mapping post-VCO divider values to
> > + *               their encoded values in the PLL control register, or -1 for
> > + *               unsupported values
> > + * @bypass_bit: the index of the bypass bit in the PLL control register
> > + * @enable_bit: the index of the enable bit in the PLL control register
> > + * @stable_bit: the index of the stable bit in the PLL control register
> > + */
> > +struct ingenic_cgu_pll_info {
> > +	unsigned reg;
> > +	unsigned m_shift, m_bits, m_offset;
> > +	unsigned n_shift, n_bits, n_offset;
> > +	unsigned od_shift, od_bits, od_max;
> > +	const s8 *od_encoding;
> > +	unsigned bypass_bit;
> > +	unsigned enable_bit;
> > +	unsigned stable_bit;

I suppose a bunch of these could be reduced to u8's, but no big deal...

> > +};
> > +
> > +/**
> > + * struct ingenic_cgu_mux_info - information about a clock mux
> > + * @reg: offset of the mux control register within the CGU
> > + * @shift: number of bits to shift the mux value by (ie. the index of
> > + *         the lowest bit of the mux value within its control register)
> > + * @bits: the size of the mux value in bits
> > + */
> > +struct ingenic_cgu_mux_info {
> > +	unsigned reg;
> > +	unsigned shift:5;
> > +	unsigned bits:5;

... especially as this seems to go to the other extreme

> > +};
> > +
> > +/**
> > + * struct ingenic_cgu_div_info - information about a divider
> > + * @reg: offset of the divider control register within the CGU
> > + * @shift: number of bits to shift the divide value by (ie. the index of
> > + *         the lowest bit of the divide value within its control register)
> > + * @bits: the size of the divide value in bits
> > + * @ce_bit: the index of the change enable bit within reg, or -1 is there
> > + *          isn't one
> > + * @busy_bit: the index of the busy bit within reg, or -1 is there isn't one
> > + * @stop_bit: the index of the stop bit within reg, or -1 is there isn't one

s/is/if/ for all 3 *_bit fields?

> > + */
> > +struct ingenic_cgu_div_info {
> > +	unsigned reg;
> > +	unsigned shift:5;
> > +	unsigned bits:5;
> > +	int ce_bit:6;
> > +	int busy_bit:6;
> > +	int stop_bit:6;
> > +};
> > +
> > +/**
> > + * struct ingenic_cgu_fixdiv_info - information about a fixed divider
> > + * @div: the divider applied to the parent clock
> > + */
> > +struct ingenic_cgu_fixdiv_info {
> > +	unsigned div;
> > +};
> > +
> > +/**
> > + * struct ingenic_cgu_gate_info - information about a clock gate
> > + * @reg: offset of the gate control register within the CGU
> > + * @bit: offset of the bit in the register that controls the gate
> > + */
> > +struct ingenic_cgu_gate_info {
> > +	unsigned reg;
> > +	unsigned bit:5;
> > +};
> > +
> > +/**
> > + * struct ingenic_cgu_custom_info - information about a custom (SoC) clock

no description of clk_ops.

> > + */
> > +struct ingenic_cgu_custom_info {
> > +	struct clk_ops *clk_ops;
> > +};
> > +
> > +/**
> > + * struct ingenic_cgu_clk_info - information about a clock
> > + * @name: name of the clock
> > + * @type: a bitmask formed from CGU_CLK_* values
> > + * @parents: an array of the indices of potential parents of this clock
> > + *           within the clock_info array of the CGU, or -1 in entries
> > + *           which correspond to no valid parent
> > + * @pll: information valid if type includes CGU_CLK_PLL
> > + * @gate: information valid if type includes CGU_CLK_GATE
> > + * @mux: information valid if type includes CGU_CLK_MUX
> > + * @div: information valid if type includes CGU_CLK_DIV

fixdiv and custom are undescribed.

> > + */
> > +struct ingenic_cgu_clk_info {
> > +	const char *name;
> > +
> > +	enum {
> > +		CGU_CLK_NONE		= 0,
> > +		CGU_CLK_EXT		= (1 << 0),
> > +		CGU_CLK_PLL		= (1 << 1),
> > +		CGU_CLK_GATE		= (1 << 2),
> > +		CGU_CLK_MUX		= (1 << 3),
> > +		CGU_CLK_MUX_GLITCHFREE	= (1 << 4),
> > +		CGU_CLK_DIV		= (1 << 5),
> > +		CGU_CLK_FIXDIV		= (1 << 6),
> > +		CGU_CLK_CUSTOM		= (1 << 7),

Appropriate to use BIT() from <linux/bitops.h> here?

> > +	} type;
> > +
> > +	int parents[4];
> > +
> > +	union {
> > +		struct ingenic_cgu_pll_info pll;
> > +
> > +		struct {
> > +			struct ingenic_cgu_gate_info gate;
> > +			struct ingenic_cgu_mux_info mux;
> > +			struct ingenic_cgu_div_info div;
> > +			struct ingenic_cgu_fixdiv_info fixdiv;
> > +		};
> > +
> > +		struct ingenic_cgu_custom_info custom;
> > +	};
> > +};
> > +
> > +/**
> > + * struct ingenic_cgu - data about the CGU
> > + * @np: the device tree node that caused the CGU to be probed
> > + * @base: the ioremap'ed base address of the CGU registers
> > + * @clock_info: an array containing information about implemented clocks
> > + * @clocks: used to provide clocks to DT, allows lookup of struct clk*
> > + * @gate_lock: lock to be held whilst (un)gating a clock

stale

> > + * @divmux_lock: lock to be held whilst re-muxing of rate-changing a clock

power_lock and pll_lock undescribed

> > + */
> > +struct ingenic_cgu {
> > +	struct device_node *np;
> > +	void __iomem *base;
> > +
> > +	const struct ingenic_cgu_clk_info *clock_info;
> > +	struct clk_onecell_data clocks;
> > +
> > +	spinlock_t divmux_lock;		/* must be held when changing a divide
> > +					   or re-muxing a clock */
> > +	spinlock_t power_lock;		/* must be held when changing a power
> > +					   manager register */
> > +	spinlock_t pll_lock;		/* must be held when changing a PLL
> > +					   control register */

those comments should go in the kernel doc description.

(Although see my previous email about locking in common clock framework).

> > +};
> > +
> > +/**
> > + * struct ingenic_clk - private data for a clock
> > + * @hw: see Documentation/clk.txt
> > + * @cgu: a pointer to the CGU data
> > + * @idx: the index of this clock in cgu->clock_info
> > + */
> > +struct ingenic_clk {
> > +	struct clk_hw hw;
> > +	struct ingenic_cgu *cgu;
> > +	unsigned idx;
> > +};
> > +
> > +#define to_ingenic_clk(_hw) container_of(_hw, struct ingenic_clk, hw)
> > +
> > +/**
> > + * ingenic_cgu_new - create a new CGU instance

function, so needs ()

> > + * @clock_info: an array of clock information structures describing the clocks
> > + *              which are implemented by the CGU
> > + * @num_clocks: the number of entries in clock_info
> > + * @np: the device tree node which causes this CGU to be probed
> > + *
> > + * Returns an opaque pointer to the CGU instance if initialisation & clock
> > + * registration is successful, otherwise NULL.

As above, do this sort of thing:
 * Return:	an opaque pointer to the CGU instance if initialisation & clock
 *		registration is successful, otherwise NULL.

> > + */
> > +struct ingenic_cgu *
> > +ingenic_cgu_new(const struct ingenic_cgu_clk_info *clock_info,
> > +		unsigned num_clocks, struct device_node *np);
> > +
> > +/**
> > + * ingenic_cgu_register_clocks - Registers the clocks
> > + * @cgu: pointer to cgu data
> > + *
> > + * Returns 1 on success and -EINVAL if unsuccesful.

same

> > + */
> > +int ingenic_cgu_register_clocks(struct ingenic_cgu *cgu);
> > +
> > +#endif /* __DRIVERS_CLK_INGENIC_CGU_H__ */

Nice work guys!

Cheers
James

Attachment: signature.asc
Description: Digital signature


[Index of Archives]     [Linux MIPS Home]     [LKML Archive]     [Linux ARM Kernel]     [Linux ARM]     [Linux]     [Git]     [Yosemite News]     [Linux SCSI]     [Linux Hams]

  Powered by Linux