Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bcc5fd49 authored by Alexandre Belloni's avatar Alexandre Belloni Committed by Nicolas Ferre
Browse files

clk: at91: add a driver for the h32mx clock



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: default avatarAlexandre Belloni <alexandre.belloni@free-electrons.com>
Acked-by: default avatarBoris Brezillon <boris.brezillon@free-electrons.com>
Signed-off-by: default avatarNicolas Ferre <nicolas.ferre@atmel.com>
parent 5db722ee
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -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,14 @@ 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>;
	};
+3 −0
Original line number Diff line number Diff line
@@ -42,6 +42,9 @@ config AT91_SAM9_TIME
config HAVE_AT91_SMD
	bool

config HAVE_AT91_H32MX
	bool

config SOC_AT91SAM9
	bool
	select AT91_SAM9_TIME
+1 −0
Original line number Diff line number Diff line
@@ -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
+123 −0
Original line number Diff line number Diff line
/*
 * clk-h32mx.c
 *
 *  Copyright (C) 2014 Atmel
 *
 * Alexandre Belloni <alexandre.belloni@free-electrons.com>
 *
 * 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);
}
+6 −0
Original line number Diff line number Diff line
@@ -336,6 +336,12 @@ static const struct of_device_id pmc_clk_ids[] __initconst = {
		.compatible = "atmel,at91sam9x5-clk-smd",
		.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*/ }
};
Loading