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

Commit a9c0688f authored by Boris Brezillon's avatar Boris Brezillon Committed by Nicolas Ferre
Browse files

clk: at91: add PMC smd clock



This patch adds at91 smd (Soft Modem) clock implementation using common clk
framework.

Not used by any driver right now.

Signed-off-by: default avatarBoris BREZILLON <b.brezillon@overkiz.com>
Acked-by: default avatarMike Turquette <mturquette@linaro.org>
Signed-off-by: default avatarNicolas Ferre <nicolas.ferre@atmel.com>
parent c84a61d8
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -39,6 +39,9 @@ config AT91_SAM9G45_RESET
config AT91_SAM9_TIME
	bool

config HAVE_AT91_SMD
	bool

config SOC_AT91SAM9
	bool
	select AT91_SAM9_TIME
@@ -85,6 +88,7 @@ config SOC_SAMA5D3
	select HAVE_AT91_DBGU1
	select AT91_USE_OLD_CLK
	select HAVE_AT91_UTMI
	select HAVE_AT91_SMD
	select HAVE_AT91_USB_CLK
	help
	  Select this if you are using one of Atmel's SAMA5D3 family SoC.
@@ -157,6 +161,7 @@ config SOC_AT91SAM9X5
	select SOC_AT91SAM9
	select AT91_USE_OLD_CLK
	select HAVE_AT91_UTMI
	select HAVE_AT91_SMD
	select HAVE_AT91_USB_CLK
	help
	  Select this if you are using one of Atmel's AT91SAM9x5 family SoC.
+1 −0
Original line number Diff line number Diff line
@@ -9,3 +9,4 @@ obj-y += clk-system.o clk-peripheral.o
obj-$(CONFIG_AT91_PROGRAMMABLE_CLOCKS)	+= 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
+171 −0
Original line number Diff line number Diff line
/*
 *  Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.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/of.h>
#include <linux/of_address.h>
#include <linux/io.h>

#include "pmc.h"

#define SMD_SOURCE_MAX		2

#define SMD_DIV_SHIFT		8
#define SMD_MAX_DIV		0xf

struct at91sam9x5_clk_smd {
	struct clk_hw hw;
	struct at91_pmc *pmc;
};

#define to_at91sam9x5_clk_smd(hw) \
	container_of(hw, struct at91sam9x5_clk_smd, hw)

static unsigned long at91sam9x5_clk_smd_recalc_rate(struct clk_hw *hw,
						    unsigned long parent_rate)
{
	u32 tmp;
	u8 smddiv;
	struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(hw);
	struct at91_pmc *pmc = smd->pmc;

	tmp = pmc_read(pmc, AT91_PMC_SMD);
	smddiv = (tmp & AT91_PMC_SMD_DIV) >> SMD_DIV_SHIFT;
	return parent_rate / (smddiv + 1);
}

static long at91sam9x5_clk_smd_round_rate(struct clk_hw *hw, unsigned long rate,
					  unsigned long *parent_rate)
{
	unsigned long div;
	unsigned long bestrate;
	unsigned long tmp;

	if (rate >= *parent_rate)
		return *parent_rate;

	div = *parent_rate / rate;
	if (div > SMD_MAX_DIV)
		return *parent_rate / (SMD_MAX_DIV + 1);

	bestrate = *parent_rate / div;
	tmp = *parent_rate / (div + 1);
	if (bestrate - rate > rate - tmp)
		bestrate = tmp;

	return bestrate;
}

static int at91sam9x5_clk_smd_set_parent(struct clk_hw *hw, u8 index)
{
	u32 tmp;
	struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(hw);
	struct at91_pmc *pmc = smd->pmc;

	if (index > 1)
		return -EINVAL;
	tmp = pmc_read(pmc, AT91_PMC_SMD) & ~AT91_PMC_SMDS;
	if (index)
		tmp |= AT91_PMC_SMDS;
	pmc_write(pmc, AT91_PMC_SMD, tmp);
	return 0;
}

static u8 at91sam9x5_clk_smd_get_parent(struct clk_hw *hw)
{
	struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(hw);
	struct at91_pmc *pmc = smd->pmc;

	return pmc_read(pmc, AT91_PMC_SMD) & AT91_PMC_SMDS;
}

static int at91sam9x5_clk_smd_set_rate(struct clk_hw *hw, unsigned long rate,
				       unsigned long parent_rate)
{
	u32 tmp;
	struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(hw);
	struct at91_pmc *pmc = smd->pmc;
	unsigned long div = parent_rate / rate;

	if (parent_rate % rate || div < 1 || div > (SMD_MAX_DIV + 1))
		return -EINVAL;
	tmp = pmc_read(pmc, AT91_PMC_SMD) & ~AT91_PMC_SMD_DIV;
	tmp |= (div - 1) << SMD_DIV_SHIFT;
	pmc_write(pmc, AT91_PMC_SMD, tmp);

	return 0;
}

static const struct clk_ops at91sam9x5_smd_ops = {
	.recalc_rate = at91sam9x5_clk_smd_recalc_rate,
	.round_rate = at91sam9x5_clk_smd_round_rate,
	.get_parent = at91sam9x5_clk_smd_get_parent,
	.set_parent = at91sam9x5_clk_smd_set_parent,
	.set_rate = at91sam9x5_clk_smd_set_rate,
};

static struct clk * __init
at91sam9x5_clk_register_smd(struct at91_pmc *pmc, const char *name,
			    const char **parent_names, u8 num_parents)
{
	struct at91sam9x5_clk_smd *smd;
	struct clk *clk = NULL;
	struct clk_init_data init;

	smd = kzalloc(sizeof(*smd), GFP_KERNEL);
	if (!smd)
		return ERR_PTR(-ENOMEM);

	init.name = name;
	init.ops = &at91sam9x5_smd_ops;
	init.parent_names = parent_names;
	init.num_parents = num_parents;
	init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;

	smd->hw.init = &init;
	smd->pmc = pmc;

	clk = clk_register(NULL, &smd->hw);
	if (IS_ERR(clk))
		kfree(smd);

	return clk;
}

void __init of_at91sam9x5_clk_smd_setup(struct device_node *np,
					struct at91_pmc *pmc)
{
	struct clk *clk;
	int i;
	int num_parents;
	const char *parent_names[SMD_SOURCE_MAX];
	const char *name = np->name;

	num_parents = of_count_phandle_with_args(np, "clocks", "#clock-cells");
	if (num_parents <= 0 || num_parents > SMD_SOURCE_MAX)
		return;

	for (i = 0; i < num_parents; i++) {
		parent_names[i] = of_clk_get_parent_name(np, i);
		if (!parent_names[i])
			return;
	}

	of_property_read_string(np, "clock-output-names", &name);

	clk = at91sam9x5_clk_register_smd(pmc, name, parent_names,
					  num_parents);
	if (IS_ERR(clk))
		return;

	of_clk_add_provider(np, of_clk_src_simple_get, clk);
}
+7 −0
Original line number Diff line number Diff line
@@ -314,6 +314,13 @@ static const struct of_device_id pmc_clk_ids[] __initdata = {
		.compatible = "atmel,at91sam9n12-clk-usb",
		.data = of_at91sam9n12_clk_usb_setup,
	},
#endif
	/* SMD clock */
#if defined(CONFIG_HAVE_AT91_SMD)
	{
		.compatible = "atmel,at91sam9x5-clk-smd",
		.data = of_at91sam9x5_clk_smd_setup,
	},
#endif
	{ /*sentinel*/ }
};
+5 −0
Original line number Diff line number Diff line
@@ -108,4 +108,9 @@ extern void __init of_at91sam9n12_clk_usb_setup(struct device_node *np,
						struct at91_pmc *pmc);
#endif

#if defined(CONFIG_HAVE_AT91_SMD)
extern void __init of_at91sam9x5_clk_smd_setup(struct device_node *np,
					       struct at91_pmc *pmc);
#endif

#endif /* __PMC_H_ */