Subject: [PATCHv3 1/7] clk: at91: add a driver for the h32mx clock

From: [email-masked] (Nicolas Ferre)
Date: Fri, 19 Sep 2014 14:33:19 +0200

On 15/09/2014 18:15, Alexandre Belloni :
> Newer SoCs have two different AHB interconnect. The AHB 32 bits Matrix
> interconnect (h32mx) has a clock that can be setup at the half of the h64mx
> clock (which is mck). The h32mx clock can not exceed 90 MHz.
> 
> Signed-off-by: Alexandre Belloni <[email-masked]>

Okay on my side:

Acked-by: Nicolas Ferre <[email-masked]>

> ---
> Cc:Mike Turquette <[email-masked]>

Mike,

I guess that you didn't get this v3. Can you "Ack" this one of should we
re-sent to you?

I would like to take this patch with the rest of the SAMA5D4 series: is
it okay for you?

Bye,

>  .../devicetree/bindings/clock/at91-clock.txt       |  15 +++
>  arch/arm/mach-at91/Kconfig                         |   3 +
>  drivers/clk/at91/Makefile                          |   1 +
>  drivers/clk/at91/clk-h32mx.c                       | 123 +++++++++++++++++++++
>  drivers/clk/at91/pmc.c                             |   6 +
>  drivers/clk/at91/pmc.h                             |   5 +
>  include/linux/clk/at91_pmc.h                       |   1 +
>  7 files changed, 154 insertions(+)
>  create mode 100644 drivers/clk/at91/clk-h32mx.c
> 
> diff --git a/Documentation/devicetree/bindings/clock/at91-clock.txt b/Documentation/devicetree/bindings/clock/at91-clock.txt
> index b3d544ca522a..40dc2405de7c 100644
> --- a/Documentation/devicetree/bindings/clock/at91-clock.txt
> +++ b/Documentation/devicetree/bindings/clock/at91-clock.txt
> @@ -74,6 +74,9 @@ Required properties:
>  	"atmel,at91sam9x5-clk-utmi":
>  		at91 utmi clock
>  
> +	"atmel,sama5d4-clk-h32mx":
> +		at91 h32mx clock
> +
>  Required properties for SCKC node:
>  - reg : defines the IO memory reserved for the SCKC.
>  - #size-cells : shall be 0 (reg is used to encode clk id).
> @@ -447,3 +450,15 @@ For example:
>  		#clock-cells = <0>;
>  		clocks = <&main>;
>  	};
> +
> +Required properties for 32 bits bus Matrix clock (h32mx clock):
> +- #clock-cells : from common clock binding; shall be set to 0.
> +- clocks : shall be the master clock source phandle.
> +
> +For example:
> +	h32ck: h32mxck {
> +		#clock-cells = <0>;
> +		compatible = "atmel,sama5d4-clk-h32mx";
> +		clocks = <&mck>;
> +	};
> +
> diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig
> index 6eb3c658761d..fd177956dd56 100644
> --- a/arch/arm/mach-at91/Kconfig
> +++ b/arch/arm/mach-at91/Kconfig
> @@ -39,6 +39,9 @@ config AT91_SAM9_TIME
>  config HAVE_AT91_SMD
>  	bool
>  
> +config HAVE_AT91_H32MX
> +	bool
> +
>  config SOC_AT91SAM9
>  	bool
>  	select AT91_SAM9_TIME
> diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
> index 4998aee59267..89a48a7bd5df 100644
> --- a/drivers/clk/at91/Makefile
> +++ b/drivers/clk/at91/Makefile
> @@ -9,3 +9,4 @@ obj-y += clk-system.o clk-peripheral.o clk-programmable.o
>  obj-$(CONFIG_HAVE_AT91_UTMI)		+= clk-utmi.o
>  obj-$(CONFIG_HAVE_AT91_USB_CLK)		+= clk-usb.o
>  obj-$(CONFIG_HAVE_AT91_SMD)		+= clk-smd.o
> +obj-$(CONFIG_HAVE_AT91_H32MX)		+= clk-h32mx.o
> diff --git a/drivers/clk/at91/clk-h32mx.c b/drivers/clk/at91/clk-h32mx.c
> new file mode 100644
> index 000000000000..152dcb3f7b5f
> --- /dev/null
> +++ b/drivers/clk/at91/clk-h32mx.c
> @@ -0,0 +1,123 @@
> +/*
> + * clk-h32mx.c
> + *
> + *  Copyright (C) 2014 Atmel
> + *
> + * Alexandre Belloni <[email-masked]>
> + *
> + * 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.
> + *
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/clkdev.h>
> +#include <linux/clk/at91_pmc.h>
> +#include <linux/delay.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/of_irq.h>
> +#include <linux/io.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/sched.h>
> +#include <linux/wait.h>
> +
> +#include "pmc.h"
> +
> +#define H32MX_MAX_FREQ	90000000
> +
> +struct clk_sama5d4_h32mx {
> +	struct clk_hw hw;
> +	struct at91_pmc *pmc;
> +};
> +
> +#define to_clk_sama5d4_h32mx(hw) container_of(hw, struct clk_sama5d4_h32mx, hw)
> +
> +static unsigned long clk_sama5d4_h32mx_recalc_rate(struct clk_hw *hw,
> +						 unsigned long parent_rate)
> +{
> +	struct clk_sama5d4_h32mx *h32mxclk = to_clk_sama5d4_h32mx(hw);
> +
> +	if (pmc_read(h32mxclk->pmc, AT91_PMC_MCKR) & AT91_PMC_H32MXDIV)
> +		return parent_rate / 2;
> +
> +	if (parent_rate > H32MX_MAX_FREQ)
> +		pr_warn("H32MX clock is too fast\n");
> +	return parent_rate;
> +}
> +
> +static long clk_sama5d4_h32mx_round_rate(struct clk_hw *hw, unsigned long rate,
> +				       unsigned long *parent_rate)
> +{
> +	unsigned long div;
> +
> +	if (rate > *parent_rate)
> +		return *parent_rate;
> +	div = *parent_rate / 2;
> +	if (rate < div)
> +		return div;
> +
> +	if (rate - div < *parent_rate - rate)
> +		return div;
> +
> +	return *parent_rate;
> +}
> +
> +static int clk_sama5d4_h32mx_set_rate(struct clk_hw *hw, unsigned long rate,
> +				    unsigned long parent_rate)
> +{
> +	struct clk_sama5d4_h32mx *h32mxclk = to_clk_sama5d4_h32mx(hw);
> +	struct at91_pmc *pmc = h32mxclk->pmc;
> +	u32 tmp;
> +
> +	if (parent_rate != rate && (parent_rate / 2) != rate)
> +		return -EINVAL;
> +
> +	pmc_lock(pmc);
> +	tmp = pmc_read(pmc, AT91_PMC_MCKR) & ~AT91_PMC_H32MXDIV;
> +	if ((parent_rate / 2) == rate)
> +		tmp |= AT91_PMC_H32MXDIV;
> +	pmc_write(pmc, AT91_PMC_MCKR, tmp);
> +	pmc_unlock(pmc);
> +
> +	return 0;
> +}
> +
> +static const struct clk_ops h32mx_ops = {
> +	.recalc_rate = clk_sama5d4_h32mx_recalc_rate,
> +	.round_rate = clk_sama5d4_h32mx_round_rate,
> +	.set_rate = clk_sama5d4_h32mx_set_rate,
> +};
> +
> +void __init of_sama5d4_clk_h32mx_setup(struct device_node *np,
> +				     struct at91_pmc *pmc)
> +{
> +	struct clk_sama5d4_h32mx *h32mxclk;
> +	struct clk_init_data init;
> +	const char *parent_name;
> +	struct clk *clk;
> +
> +	h32mxclk = kzalloc(sizeof(*h32mxclk), GFP_KERNEL);
> +	if (!h32mxclk)
> +		return;
> +
> +	parent_name = of_clk_get_parent_name(np, 0);
> +
> +	init.name = np->name;
> +	init.ops = &h32mx_ops;
> +	init.parent_names = parent_name ? &parent_name : NULL;
> +	init.num_parents = parent_name ? 1 : 0;
> +	init.flags = CLK_SET_RATE_GATE;
> +
> +	h32mxclk->hw.init = &init;
> +	h32mxclk->pmc = pmc;
> +
> +	clk = clk_register(NULL, &h32mxclk->hw);
> +	if (!clk)
> +		return;
> +
> +	of_clk_add_provider(np, of_clk_src_simple_get, clk);
> +}
> diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
> index 524196bb35a5..386999b4f8eb 100644
> --- a/drivers/clk/at91/pmc.c
> +++ b/drivers/clk/at91/pmc.c
> @@ -337,6 +337,12 @@ static const struct of_device_id pmc_clk_ids[] __initconst = {
>  		.data = of_at91sam9x5_clk_smd_setup,
>  	},
>  #endif
> +#if defined(CONFIG_HAVE_AT91_H32MX)
> +	{
> +		.compatible = "atmel,sama5d4-clk-h32mx",
> +		.data = of_sama5d4_clk_h32mx_setup,
> +	},
> +#endif
>  	{ /*sentinel*/ }
>  };
>  
> diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
> index 6c7625976113..52d2041fa3f6 100644
> --- a/drivers/clk/at91/pmc.h
> +++ b/drivers/clk/at91/pmc.h
> @@ -120,4 +120,9 @@ extern void __init of_at91sam9x5_clk_smd_setup(struct device_node *np,
>  					       struct at91_pmc *pmc);
>  #endif
>  
> +#if defined(CONFIG_HAVE_AT91_SMD)
> +extern void __init of_sama5d4_clk_h32mx_setup(struct device_node *np,
> +					      struct at91_pmc *pmc);
> +#endif
> +
>  #endif /* __PMC_H_ */
> diff --git a/include/linux/clk/at91_pmc.h b/include/linux/clk/at91_pmc.h
> index de4268d4987a..c8e3b3d1eded 100644
> --- a/include/linux/clk/at91_pmc.h
> +++ b/include/linux/clk/at91_pmc.h
> @@ -125,6 +125,7 @@ extern void __iomem *at91_pmc_base;
>  #define		AT91_PMC_PLLADIV2	(1 << 12)		/* PLLA divisor by 2 [some SAM9 only] */
>  #define			AT91_PMC_PLLADIV2_OFF		(0 << 12)
>  #define			AT91_PMC_PLLADIV2_ON		(1 << 12)
> +#define		AT91_PMC_H32MXDIV	BIT(24)
>  
>  #define	AT91_PMC_USB		0x38			/* USB Clock Register [some SAM9 only] */
>  #define		AT91_PMC_USBS		(0x1 <<  0)		/* USB OHCI Input clock selection */
> 

-- 
Nicolas Ferre