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

Commit e874a669 authored by Emilio López's avatar Emilio López Committed by Mike Turquette
Browse files

clk: arm: sunxi: Add a new clock driver for sunxi SOCs



This commit implements the base CPU clocks for sunxi devices. It has
been tested using a slightly modified cpufreq driver from the
linux-sunxi 3.0 tree.

Additionally, document the new bindings introduced by this patch.

Idling:
    / # cat /sys/kernel/debug/clk/clk_summary
       clock                        enable_cnt  prepare_cnt  rate
    ---------------------------------------------------------------------
     osc32k                         0           0            32768
     osc24M_fixed                   0           0            24000000
        osc24M                      0           0            24000000
           apb1_mux                 0           0            24000000
              apb1                  0           0            24000000
           pll1                     0           0            60000000
              cpu                   0           0            60000000
                 axi                0           0            60000000
                    ahb             0           0            60000000
                       apb0         0           0            30000000
     dummy                          0           0            0

After "yes >/dev/null &":
    / # cat /sys/kernel/debug/clk/clk_summary
       clock                        enable_cnt  prepare_cnt  rate
    ---------------------------------------------------------------------
     osc32k                         0           0            32768
     osc24M_fixed                   0           0            24000000
        osc24M                      0           0            24000000
           apb1_mux                 0           0            24000000
              apb1                  0           0            24000000
           pll1                     0           0            1008000000
              cpu                   0           0            1008000000
                 axi                0           0            336000000
                    ahb             0           0            168000000
                       apb0         0           0            84000000
     dummy                          0           0            0

Signed-off-by: default avatarEmilio López <emilio@elopez.com.ar>
Acked-by: default avatarMaxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: default avatarMike Turquette <mturquette@linaro.org>
parent b5489168
Loading
Loading
Loading
Loading
+44 −0
Original line number Diff line number Diff line
Device Tree Clock bindings for arch-sunxi

This binding uses the common clock binding[1].

[1] Documentation/devicetree/bindings/clock/clock-bindings.txt

Required properties:
- compatible : shall be one of the following:
	"allwinner,sunxi-osc-clk" - for a gatable oscillator
	"allwinner,sunxi-pll1-clk" - for the main PLL clock
	"allwinner,sunxi-cpu-clk" - for the CPU multiplexer clock
	"allwinner,sunxi-axi-clk" - for the sunxi AXI clock
	"allwinner,sunxi-ahb-clk" - for the sunxi AHB clock
	"allwinner,sunxi-apb0-clk" - for the sunxi APB0 clock
	"allwinner,sunxi-apb1-clk" - for the sunxi APB1 clock
	"allwinner,sunxi-apb1-mux-clk" - for the sunxi APB1 clock muxing

Required properties for all clocks:
- reg : shall be the control register address for the clock.
- clocks : shall be the input parent clock(s) phandle for the clock
- #clock-cells : from common clock binding; shall be set to 0.

For example:

osc24M: osc24M@01c20050 {
	#clock-cells = <0>;
	compatible = "allwinner,sunxi-osc-clk";
	reg = <0x01c20050 0x4>;
	clocks = <&osc24M_fixed>;
};

pll1: pll1@01c20000 {
	#clock-cells = <0>;
	compatible = "allwinner,sunxi-pll1-clk";
	reg = <0x01c20000 0x4>;
	clocks = <&osc24M>;
};

cpu: cpu@01c20054 {
	#clock-cells = <0>;
	compatible = "allwinner,sunxi-cpu-clk";
	reg = <0x01c20054 0x4>;
	clocks = <&osc32k>, <&osc24M>, <&pll1>;
};
+1 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ ifeq ($(CONFIG_COMMON_CLK), y)
obj-$(CONFIG_ARCH_MMP)		+= mmp/
endif
obj-$(CONFIG_MACH_LOONGSON1)	+= clk-ls1x.o
obj-$(CONFIG_ARCH_SUNXI)	+= sunxi/
obj-$(CONFIG_ARCH_U8500)	+= ux500/
obj-$(CONFIG_ARCH_VT8500)	+= clk-vt8500.o
obj-$(CONFIG_ARCH_ZYNQ)		+= clk-zynq.o
+5 −0
Original line number Diff line number Diff line
#
# Makefile for sunxi specific clk
#

obj-y += clk-sunxi.o clk-factors.o
+180 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 Emilio López <emilio@elopez.com.ar>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Adjustable factor-based clock implementation
 */

#include <linux/clk-provider.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/string.h>

#include <linux/delay.h>

#include "clk-factors.h"

/*
 * DOC: basic adjustable factor-based clock that cannot gate
 *
 * Traits of this clock:
 * prepare - clk_prepare only ensures that parents are prepared
 * enable - clk_enable only ensures that parents are enabled
 * rate - rate is adjustable.
 *        clk->rate = (parent->rate * N * (K + 1) >> P) / (M + 1)
 * parent - fixed parent.  No clk_set_parent support
 */

struct clk_factors {
	struct clk_hw hw;
	void __iomem *reg;
	struct clk_factors_config *config;
	void (*get_factors) (u32 *rate, u32 parent, u8 *n, u8 *k, u8 *m, u8 *p);
	spinlock_t *lock;
};

#define to_clk_factors(_hw) container_of(_hw, struct clk_factors, hw)

#define SETMASK(len, pos)		(((-1U) >> (31-len))  << (pos))
#define CLRMASK(len, pos)		(~(SETMASK(len, pos)))
#define FACTOR_GET(bit, len, reg)	(((reg) & SETMASK(len, bit)) >> (bit))

#define FACTOR_SET(bit, len, reg, val) \
	(((reg) & CLRMASK(len, bit)) | (val << (bit)))

static unsigned long clk_factors_recalc_rate(struct clk_hw *hw,
					     unsigned long parent_rate)
{
	u8 n = 1, k = 0, p = 0, m = 0;
	u32 reg;
	unsigned long rate;
	struct clk_factors *factors = to_clk_factors(hw);
	struct clk_factors_config *config = factors->config;

	/* Fetch the register value */
	reg = readl(factors->reg);

	/* Get each individual factor if applicable */
	if (config->nwidth != SUNXI_FACTORS_NOT_APPLICABLE)
		n = FACTOR_GET(config->nshift, config->nwidth, reg);
	if (config->kwidth != SUNXI_FACTORS_NOT_APPLICABLE)
		k = FACTOR_GET(config->kshift, config->kwidth, reg);
	if (config->mwidth != SUNXI_FACTORS_NOT_APPLICABLE)
		m = FACTOR_GET(config->mshift, config->mwidth, reg);
	if (config->pwidth != SUNXI_FACTORS_NOT_APPLICABLE)
		p = FACTOR_GET(config->pshift, config->pwidth, reg);

	/* Calculate the rate */
	rate = (parent_rate * n * (k + 1) >> p) / (m + 1);

	return rate;
}

static long clk_factors_round_rate(struct clk_hw *hw, unsigned long rate,
				   unsigned long *parent_rate)
{
	struct clk_factors *factors = to_clk_factors(hw);
	factors->get_factors((u32 *)&rate, (u32)*parent_rate,
			     NULL, NULL, NULL, NULL);

	return rate;
}

static int clk_factors_set_rate(struct clk_hw *hw, unsigned long rate,
				unsigned long parent_rate)
{
	u8 n, k, m, p;
	u32 reg;
	struct clk_factors *factors = to_clk_factors(hw);
	struct clk_factors_config *config = factors->config;
	unsigned long flags = 0;

	factors->get_factors((u32 *)&rate, (u32)parent_rate, &n, &k, &m, &p);

	if (factors->lock)
		spin_lock_irqsave(factors->lock, flags);

	/* Fetch the register value */
	reg = readl(factors->reg);

	/* Set up the new factors - macros do not do anything if width is 0 */
	reg = FACTOR_SET(config->nshift, config->nwidth, reg, n);
	reg = FACTOR_SET(config->kshift, config->kwidth, reg, k);
	reg = FACTOR_SET(config->mshift, config->mwidth, reg, m);
	reg = FACTOR_SET(config->pshift, config->pwidth, reg, p);

	/* Apply them now */
	writel(reg, factors->reg);

	/* delay 500us so pll stabilizes */
	__delay((rate >> 20) * 500 / 2);

	if (factors->lock)
		spin_unlock_irqrestore(factors->lock, flags);

	return 0;
}

static const struct clk_ops clk_factors_ops = {
	.recalc_rate = clk_factors_recalc_rate,
	.round_rate = clk_factors_round_rate,
	.set_rate = clk_factors_set_rate,
};

/**
 * clk_register_factors - register a factors clock with
 * the clock framework
 * @dev: device registering this clock
 * @name: name of this clock
 * @parent_name: name of clock's parent
 * @flags: framework-specific flags
 * @reg: register address to adjust factors
 * @config: shift and width of factors n, k, m and p
 * @get_factors: function to calculate the factors for a given frequency
 * @lock: shared register lock for this clock
 */
struct clk *clk_register_factors(struct device *dev, const char *name,
				 const char *parent_name,
				 unsigned long flags, void __iomem *reg,
				 struct clk_factors_config *config,
				 void (*get_factors)(u32 *rate, u32 parent,
						     u8 *n, u8 *k, u8 *m, u8 *p),
				 spinlock_t *lock)
{
	struct clk_factors *factors;
	struct clk *clk;
	struct clk_init_data init;

	/* allocate the factors */
	factors = kzalloc(sizeof(struct clk_factors), GFP_KERNEL);
	if (!factors) {
		pr_err("%s: could not allocate factors clk\n", __func__);
		return ERR_PTR(-ENOMEM);
	}

	init.name = name;
	init.ops = &clk_factors_ops;
	init.flags = flags;
	init.parent_names = (parent_name ? &parent_name : NULL);
	init.num_parents = (parent_name ? 1 : 0);

	/* struct clk_factors assignments */
	factors->reg = reg;
	factors->config = config;
	factors->lock = lock;
	factors->hw.init = &init;
	factors->get_factors = get_factors;

	/* register the clock */
	clk = clk_register(dev, &factors->hw);

	if (IS_ERR(clk))
		kfree(factors);

	return clk;
}
+27 −0
Original line number Diff line number Diff line
#ifndef __MACH_SUNXI_CLK_FACTORS_H
#define __MACH_SUNXI_CLK_FACTORS_H

#include <linux/clk-provider.h>
#include <linux/clkdev.h>

#define SUNXI_FACTORS_NOT_APPLICABLE	(0)

struct clk_factors_config {
	u8 nshift;
	u8 nwidth;
	u8 kshift;
	u8 kwidth;
	u8 mshift;
	u8 mwidth;
	u8 pshift;
	u8 pwidth;
};

struct clk *clk_register_factors(struct device *dev, const char *name,
				 const char *parent_name,
				 unsigned long flags, void __iomem *reg,
				 struct clk_factors_config *config,
				 void (*get_factors) (u32 *rate, u32 parent_rate,
						      u8 *n, u8 *k, u8 *m, u8 *p),
				 spinlock_t *lock);
#endif
Loading