summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2013-11-12 16:11:47 +0900
committerLinus Torvalds <torvalds@linux-foundation.org>2013-11-12 16:11:47 +0900
commit2b684c073f32b557661eba191ce0a584020367e2 (patch)
tree3d3694ce794236fd28e12ee27689c4c4e2720e98
parentc2d33069915d1f9b3b1dcc2199af11d4e072b037 (diff)
parent9ed9c07d9b7d42fc7042247c2be283731186464f (diff)
Merge tag 'clk-for-linus-3.13' of git://git.linaro.org/people/mturquette/linux
Pull clock framework changes from Mike Turquette: "The clock changes for 3.13 are an even mix of framework improvements & bug fixes along with updates to existing clock drivers and the additional of new clock drivers" * tag 'clk-for-linus-3.13' of git://git.linaro.org/people/mturquette/linux: clk: new driver for efm32 SoC clk: of: helper for determining number of parent clocks clk/zynq: Fix possible memory leak clk: keystone: Build Keystone clock drivers clk: keystone: Add gate control clock driver clk: keystone: add Keystone PLL clock driver Documentation: Add documentation for APM X-Gene clock binding clk: arm64: Add DTS clock entry for APM X-Gene Storm SoC clk: Add APM X-Gene SoC clock driver clk: wm831x: get rid of the implementation of remove function clk: Correct lookup logic in clk_fetch_parent_index() clk: Use kcalloc() to allocate arrays clk: Add error handling to clk_fetch_parent_index()
-rw-r--r--Documentation/devicetree/bindings/clock/efm32-clock.txt11
-rw-r--r--Documentation/devicetree/bindings/clock/keystone-gate.txt29
-rw-r--r--Documentation/devicetree/bindings/clock/keystone-pll.txt84
-rw-r--r--Documentation/devicetree/bindings/clock/xgene.txt111
-rw-r--r--arch/arm64/boot/dts/apm-storm.dtsi75
-rw-r--r--drivers/clk/Kconfig14
-rw-r--r--drivers/clk/Makefile3
-rw-r--r--drivers/clk/clk-efm32gg.c81
-rw-r--r--drivers/clk/clk-wm831x.c6
-rw-r--r--drivers/clk/clk-xgene.c521
-rw-r--r--drivers/clk/clk.c56
-rw-r--r--drivers/clk/keystone/Makefile1
-rw-r--r--drivers/clk/keystone/gate.c264
-rw-r--r--drivers/clk/keystone/pll.c305
-rw-r--r--drivers/clk/zynq/clkc.c16
-rw-r--r--include/dt-bindings/clock/efm32-cmu.h42
-rw-r--r--include/linux/clk-provider.h1
17 files changed, 1591 insertions, 29 deletions
diff --git a/Documentation/devicetree/bindings/clock/efm32-clock.txt b/Documentation/devicetree/bindings/clock/efm32-clock.txt
new file mode 100644
index 000000000000..263d293f6a10
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/efm32-clock.txt
@@ -0,0 +1,11 @@
+* Clock bindings for Energy Micro efm32 Giant Gecko's Clock Management Unit
+
+Required properties:
+- compatible: Should be "efm32gg,cmu"
+- reg: Base address and length of the register set
+- interrupts: Interrupt used by the CMU
+- #clock-cells: Should be <1>
+
+The clock consumer should specify the desired clock by having the clock ID in
+its "clocks" phandle cell. The header efm32-clk.h contains a list of available
+IDs.
diff --git a/Documentation/devicetree/bindings/clock/keystone-gate.txt b/Documentation/devicetree/bindings/clock/keystone-gate.txt
new file mode 100644
index 000000000000..c5aa187026e3
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/keystone-gate.txt
@@ -0,0 +1,29 @@
+Status: Unstable - ABI compatibility may be broken in the future
+
+Binding for Keystone gate control driver which uses PSC controller IP.
+
+This binding uses the common clock binding[1].
+
+[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
+
+Required properties:
+- compatible : shall be "ti,keystone,psc-clock".
+- #clock-cells : from common clock binding; shall be set to 0.
+- clocks : parent clock phandle
+- reg : psc control and domain address address space
+- reg-names : psc control and domain registers
+- domain-id : psc domain id needed to check the transition state register
+
+Optional properties:
+- clock-output-names : From common clock binding to override the
+ default output clock name
+Example:
+ clkusb: clkusb {
+ #clock-cells = <0>;
+ compatible = "ti,keystone,psc-clock";
+ clocks = <&chipclk16>;
+ clock-output-names = "usb";
+ reg = <0x02350008 0xb00>, <0x02350000 0x400>;
+ reg-names = "control", "domain";
+ domain-id = <0>;
+ };
diff --git a/Documentation/devicetree/bindings/clock/keystone-pll.txt b/Documentation/devicetree/bindings/clock/keystone-pll.txt
new file mode 100644
index 000000000000..12bd72605a31
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/keystone-pll.txt
@@ -0,0 +1,84 @@
+Status: Unstable - ABI compatibility may be broken in the future
+
+Binding for keystone PLLs. The main PLL IP typically has a multiplier,
+a divider and a post divider. The additional PLL IPs like ARMPLL, DDRPLL
+and PAPLL are controlled by the memory mapped register where as the Main
+PLL is controlled by a PLL controller registers along with memory mapped
+registers.
+
+This binding uses the common clock binding[1].
+
+[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
+
+Required properties:
+- #clock-cells : from common clock binding; shall be set to 0.
+- compatible : shall be "ti,keystone,main-pll-clock" or "ti,keystone,pll-clock"
+- clocks : parent clock phandle
+- reg - pll control0 and pll multipler registers
+- reg-names : control and multiplier. The multiplier is applicable only for
+ main pll clock
+- fixed-postdiv : fixed post divider value
+
+Example:
+ mainpllclk: mainpllclk@2310110 {
+ #clock-cells = <0>;
+ compatible = "ti,keystone,main-pll-clock";
+ clocks = <&refclkmain>;
+ reg = <0x02620350 4>, <0x02310110 4>;
+ reg-names = "control", "multiplier";
+ fixed-postdiv = <2>;
+ };
+
+ papllclk: papllclk@2620358 {
+ #clock-cells = <0>;
+ compatible = "ti,keystone,pll-clock";
+ clocks = <&refclkmain>;
+ clock-output-names = "pa-pll-clk";
+ reg = <0x02620358 4>;
+ reg-names = "control";
+ fixed-postdiv = <6>;
+ };
+
+Required properties:
+- #clock-cells : from common clock binding; shall be set to 0.
+- compatible : shall be "ti,keystone,pll-mux-clock"
+- clocks : link phandles of parent clocks
+- reg - pll mux register
+- bit-shift : number of bits to shift the bit-mask
+- bit-mask : arbitrary bitmask for programming the mux
+
+Optional properties:
+- clock-output-names : From common clock binding.
+
+Example:
+ mainmuxclk: mainmuxclk@2310108 {
+ #clock-cells = <0>;
+ compatible = "ti,keystone,pll-mux-clock";
+ clocks = <&mainpllclk>, <&refclkmain>;
+ reg = <0x02310108 4>;
+ bit-shift = <23>;
+ bit-mask = <1>;
+ clock-output-names = "mainmuxclk";
+ };
+
+Required properties:
+- #clock-cells : from common clock binding; shall be set to 0.
+- compatible : shall be "ti,keystone,pll-divider-clock"
+- clocks : parent clock phandle
+- reg - pll mux register
+- bit-shift : number of bits to shift the bit-mask
+- bit-mask : arbitrary bitmask for programming the divider
+
+Optional properties:
+- clock-output-names : From common clock binding.
+
+Example:
+ gemtraceclk: gemtraceclk@2310120 {
+ #clock-cells = <0>;
+ compatible = "ti,keystone,pll-divider-clock";
+ clocks = <&mainmuxclk>;
+ reg = <0x02310120 4>;
+ bit-shift = <0>;
+ bit-mask = <8>;
+ clock-output-names = "gemtraceclk";
+ };
diff --git a/Documentation/devicetree/bindings/clock/xgene.txt b/Documentation/devicetree/bindings/clock/xgene.txt
new file mode 100644
index 000000000000..1c4ef773feea
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/xgene.txt
@@ -0,0 +1,111 @@
+Device Tree Clock bindings for APM X-Gene
+
+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:
+ "apm,xgene-socpll-clock" - for a X-Gene SoC PLL clock
+ "apm,xgene-pcppll-clock" - for a X-Gene PCP PLL clock
+ "apm,xgene-device-clock" - for a X-Gene device clock
+
+Required properties for SoC or PCP PLL clocks:
+- reg : shall be the physical PLL register address for the pll clock.
+- clocks : shall be the input parent clock phandle for the clock. This should
+ be the reference clock.
+- #clock-cells : shall be set to 1.
+- clock-output-names : shall be the name of the PLL referenced by derive
+ clock.
+Optional properties for PLL clocks:
+- clock-names : shall be the name of the PLL. If missing, use the device name.
+
+Required properties for device clocks:
+- reg : shall be a list of address and length pairs describing the CSR
+ reset and/or the divider. Either may be omitted, but at least
+ one must be present.
+ - reg-names : shall be a string list describing the reg resource. This
+ may include "csr-reg" and/or "div-reg". If this property
+ is not present, the reg property is assumed to describe
+ only "csr-reg".
+- clocks : shall be the input parent clock phandle for the clock.
+- #clock-cells : shall be set to 1.
+- clock-output-names : shall be the name of the device referenced.
+Optional properties for device clocks:
+- clock-names : shall be the name of the device clock. If missing, use the
+ device name.
+- csr-offset : Offset to the CSR reset register from the reset address base.
+ Default is 0.
+- csr-mask : CSR reset mask bit. Default is 0xF.
+- enable-offset : Offset to the enable register from the reset address base.
+ Default is 0x8.
+- enable-mask : CSR enable mask bit. Default is 0xF.
+- divider-offset : Offset to the divider CSR register from the divider base.
+ Default is 0x0.
+- divider-width : Width of the divider register. Default is 0.
+- divider-shift : Bit shift of the divider register. Default is 0.
+
+For example:
+
+ pcppll: pcppll@17000100 {
+ compatible = "apm,xgene-pcppll-clock";
+ #clock-cells = <1>;
+ clocks = <&refclk 0>;
+ clock-names = "pcppll";
+ reg = <0x0 0x17000100 0x0 0x1000>;
+ clock-output-names = "pcppll";
+ type = <0>;
+ };
+
+ socpll: socpll@17000120 {
+ compatible = "apm,xgene-socpll-clock";
+ #clock-cells = <1>;
+ clocks = <&refclk 0>;
+ clock-names = "socpll";
+ reg = <0x0 0x17000120 0x0 0x1000>;
+ clock-output-names = "socpll";
+ type = <1>;
+ };
+
+ qmlclk: qmlclk {
+ compatible = "apm,xgene-device-clock";
+ #clock-cells = <1>;
+ clocks = <&socplldiv2 0>;
+ clock-names = "qmlclk";
+ reg = <0x0 0x1703C000 0x0 0x1000>;
+ reg-name = "csr-reg";
+ clock-output-names = "qmlclk";
+ };
+
+ ethclk: ethclk {
+ compatible = "apm,xgene-device-clock";
+ #clock-cells = <1>;
+ clocks = <&socplldiv2 0>;
+ clock-names = "ethclk";
+ reg = <0x0 0x17000000 0x0 0x1000>;
+ reg-names = "div-reg";
+ divider-offset = <0x238>;
+ divider-width = <0x9>;
+ divider-shift = <0x0>;
+ clock-output-names = "ethclk";
+ };
+
+ apbclk: apbclk {
+ compatible = "apm,xgene-device-clock";
+ #clock-cells = <1>;
+ clocks = <&ahbclk 0>;
+ clock-names = "apbclk";
+ reg = <0x0 0x1F2AC000 0x0 0x1000
+ 0x0 0x1F2AC000 0x0 0x1000>;
+ reg-names = "csr-reg", "div-reg";
+ csr-offset = <0x0>;
+ csr-mask = <0x200>;
+ enable-offset = <0x8>;
+ enable-mask = <0x200>;
+ divider-offset = <0x10>;
+ divider-width = <0x2>;
+ divider-shift = <0x0>;
+ flags = <0x8>;
+ clock-output-names = "apbclk";
+ };
+
diff --git a/arch/arm64/boot/dts/apm-storm.dtsi b/arch/arm64/boot/dts/apm-storm.dtsi
index bfdc57834929..d37d7369e260 100644
--- a/arch/arm64/boot/dts/apm-storm.dtsi
+++ b/arch/arm64/boot/dts/apm-storm.dtsi
@@ -103,6 +103,81 @@
#size-cells = <2>;
ranges;
+ clocks {
+ #address-cells = <2>;
+ #size-cells = <2>;
+ ranges;
+ refclk: refclk {
+ compatible = "fixed-clock";
+ #clock-cells = <1>;
+ clock-frequency = <100000000>;
+ clock-output-names = "refclk";
+ };
+
+ pcppll: pcppll@17000100 {
+ compatible = "apm,xgene-pcppll-clock";
+ #clock-cells = <1>;
+ clocks = <&refclk 0>;
+ clock-names = "pcppll";
+ reg = <0x0 0x17000100 0x0 0x1000>;
+ clock-output-names = "pcppll";
+ type = <0>;
+ };
+
+ socpll: socpll@17000120 {
+ compatible = "apm,xgene-socpll-clock";
+ #clock-cells = <1>;
+ clocks = <&refclk 0>;
+ clock-names = "socpll";
+ reg = <0x0 0x17000120 0x0 0x1000>;
+ clock-output-names = "socpll";
+ type = <1>;
+ };
+
+ socplldiv2: socplldiv2 {
+ compatible = "fixed-factor-clock";
+ #clock-cells = <1>;
+ clocks = <&socpll 0>;
+ clock-names = "socplldiv2";
+ clock-mult = <1>;
+ clock-div = <2>;
+ clock-output-names = "socplldiv2";
+ };
+
+ qmlclk: qmlclk {
+ compatible = "apm,xgene-device-clock";
+ #clock-cells = <1>;
+ clocks = <&socplldiv2 0>;
+ clock-names = "qmlclk";
+ reg = <0x0 0x1703C000 0x0 0x1000>;
+ reg-names = "csr-reg";
+ clock-output-names = "qmlclk";
+ };
+
+ ethclk: ethclk {
+ compatible = "apm,xgene-device-clock";
+ #clock-cells = <1>;
+ clocks = <&socplldiv2 0>;
+ clock-names = "ethclk";
+ reg = <0x0 0x17000000 0x0 0x1000>;
+ reg-names = "div-reg";
+ divider-offset = <0x238>;
+ divider-width = <0x9>;
+ divider-shift = <0x0>;
+ clock-output-names = "ethclk";
+ };
+
+ eth8clk: eth8clk {
+ compatible = "apm,xgene-device-clock";
+ #clock-cells = <1>;
+ clocks = <&ethclk 0>;
+ clock-names = "eth8clk";
+ reg = <0x0 0x1702C000 0x0 0x1000>;
+ reg-names = "csr-reg";
+ clock-output-names = "eth8clk";
+ };
+ };
+
serial0: serial@1c020000 {
device_type = "serial";
compatible = "ns16550";
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 279407a36391..5c51115081b3 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -93,6 +93,20 @@ config CLK_PPC_CORENET
This adds the clock driver support for Freescale PowerPC corenet
platforms using common clock framework.
+config COMMON_CLK_XGENE
+ bool "Clock driver for APM XGene SoC"
+ default y
+ depends on ARM64
+ ---help---
+ Sypport for the APM X-Gene SoC reference, PLL, and device clocks.
+
+config COMMON_CLK_KEYSTONE
+ tristate "Clock drivers for Keystone based SOCs"
+ depends on ARCH_KEYSTONE && OF
+ ---help---
+ Supports clock drivers for Keystone based SOCs. These SOCs have local
+ a power sleep control module that gate the clock to the IPs and PLLs.
+
endmenu
source "drivers/clk/mvebu/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 7b111062ccba..7a10bc9a23e7 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_COMMON_CLK) += clk-composite.o
# SoCs specific
obj-$(CONFIG_ARCH_BCM2835) += clk-bcm2835.o
+obj-$(CONFIG_ARCH_EFM32) += clk-efm32gg.o
obj-$(CONFIG_ARCH_NOMADIK) += clk-nomadik.o
obj-$(CONFIG_ARCH_HIGHBANK) += clk-highbank.o
obj-$(CONFIG_ARCH_NSPIRE) += clk-nspire.o
@@ -32,6 +33,8 @@ obj-$(CONFIG_ARCH_VT8500) += clk-vt8500.o
obj-$(CONFIG_ARCH_ZYNQ) += zynq/
obj-$(CONFIG_ARCH_TEGRA) += tegra/
obj-$(CONFIG_PLAT_SAMSUNG) += samsung/
+obj-$(CONFIG_COMMON_CLK_XGENE) += clk-xgene.o
+obj-$(CONFIG_COMMON_CLK_KEYSTONE) += keystone/
obj-$(CONFIG_X86) += x86/
diff --git a/drivers/clk/clk-efm32gg.c b/drivers/clk/clk-efm32gg.c
new file mode 100644
index 000000000000..bac2ddf49d02
--- /dev/null
+++ b/drivers/clk/clk-efm32gg.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2013 Pengutronix
+ * Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>
+ *
+ * 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.
+ */
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/clk-provider.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+#include <dt-bindings/clock/efm32-cmu.h>
+
+#define CMU_HFPERCLKEN0 0x44
+
+static struct clk *clk[37];
+static struct clk_onecell_data clk_data = {
+ .clks = clk,
+ .clk_num = ARRAY_SIZE(clk),
+};
+
+static int __init efm32gg_cmu_init(struct device_node *np)
+{
+ int i;
+ void __iomem *base;
+
+ for (i = 0; i < ARRAY_SIZE(clk); ++i)
+ clk[i] = ERR_PTR(-ENOENT);
+
+ base = of_iomap(np, 0);
+ if (!base) {
+ pr_warn("Failed to map address range for efm32gg,cmu node\n");
+ return -EADDRNOTAVAIL;
+ }
+
+ clk[clk_HFXO] = clk_register_fixed_rate(NULL, "HFXO", NULL,
+ CLK_IS_ROOT, 48000000);
+
+ clk[clk_HFPERCLKUSART0] = clk_register_gate(NULL, "HFPERCLK.USART0",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 0, 0, NULL);
+ clk[clk_HFPERCLKUSART1] = clk_register_gate(NULL, "HFPERCLK.USART1",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 1, 0, NULL);
+ clk[clk_HFPERCLKUSART2] = clk_register_gate(NULL, "HFPERCLK.USART2",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 2, 0, NULL);
+ clk[clk_HFPERCLKUART0] = clk_register_gate(NULL, "HFPERCLK.UART0",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 3, 0, NULL);
+ clk[clk_HFPERCLKUART1] = clk_register_gate(NULL, "HFPERCLK.UART1",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 4, 0, NULL);
+ clk[clk_HFPERCLKTIMER0] = clk_register_gate(NULL, "HFPERCLK.TIMER0",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 5, 0, NULL);
+ clk[clk_HFPERCLKTIMER1] = clk_register_gate(NULL, "HFPERCLK.TIMER1",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 6, 0, NULL);
+ clk[clk_HFPERCLKTIMER2] = clk_register_gate(NULL, "HFPERCLK.TIMER2",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 7, 0, NULL);
+ clk[clk_HFPERCLKTIMER3] = clk_register_gate(NULL, "HFPERCLK.TIMER3",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 8, 0, NULL);
+ clk[clk_HFPERCLKACMP0] = clk_register_gate(NULL, "HFPERCLK.ACMP0",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 9, 0, NULL);
+ clk[clk_HFPERCLKACMP1] = clk_register_gate(NULL, "HFPERCLK.ACMP1",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 10, 0, NULL);
+ clk[clk_HFPERCLKI2C0] = clk_register_gate(NULL, "HFPERCLK.I2C0",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 11, 0, NULL);
+ clk[clk_HFPERCLKI2C1] = clk_register_gate(NULL, "HFPERCLK.I2C1",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 12, 0, NULL);
+ clk[clk_HFPERCLKGPIO] = clk_register_gate(NULL, "HFPERCLK.GPIO",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 13, 0, NULL);
+ clk[clk_HFPERCLKVCMP] = clk_register_gate(NULL, "HFPERCLK.VCMP",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 14, 0, NULL);
+ clk[clk_HFPERCLKPRS] = clk_register_gate(NULL, "HFPERCLK.PRS",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 15, 0, NULL);
+ clk[clk_HFPERCLKADC0] = clk_register_gate(NULL, "HFPERCLK.ADC0",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 16, 0, NULL);
+ clk[clk_HFPERCLKDAC0] = clk_register_gate(NULL, "HFPERCLK.DAC0",
+ "HFXO", 0, base + CMU_HFPERCLKEN0, 17, 0, NULL);
+
+ return of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data);
+}
+CLK_OF_DECLARE(efm32ggcmu, "efm32gg,cmu", efm32gg_cmu_init);
diff --git a/drivers/clk/clk-wm831x.c b/drivers/clk/clk-wm831x.c
index 805b4c344006..b131041c8f48 100644
--- a/drivers/clk/clk-wm831x.c
+++ b/drivers/clk/clk-wm831x.c
@@ -391,14 +391,8 @@ static int wm831x_clk_probe(struct platform_device *pdev)
return 0;
}
-static int wm831x_clk_remove(struct platform_device *pdev)
-{
- return 0;
-}
-
static struct platform_driver wm831x_clk_driver = {
.probe = wm831x_clk_probe,
- .remove = wm831x_clk_remove,
.driver = {
.name = "wm831x-clk",
.owner = THIS_MODULE,
diff --git a/drivers/clk/clk-xgene.c b/drivers/clk/clk-xgene.c
new file mode 100644
index 000000000000..dd8a62d8f11f
--- /dev/null
+++ b/drivers/clk/clk-xgene.c
@@ -0,0 +1,521 @@
+/*
+ * clk-xgene.c - AppliedMicro X-Gene Clock Interface
+ *
+ * Copyright (c) 2013, Applied Micro Circuits Corporation
+ * Author: Loc Ho <lho@apm.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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ */
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/of_address.h>
+#include <asm/setup.h>
+
+/* Register SCU_PCPPLL bit fields */
+#define N_DIV_RD(src) (((src) & 0x000001ff))
+
+/* Register SCU_SOCPLL bit fields */
+#define CLKR_RD(src) (((src) & 0x07000000)>>24)
+#define CLKOD_RD(src) (((src) & 0x00300000)>>20)
+#define REGSPEC_RESET_F1_MASK 0x00010000
+#define CLKF_RD(src) (((src) & 0x000001ff))
+
+#define XGENE_CLK_DRIVER_VER "0.1"
+
+static DEFINE_SPINLOCK(clk_lock);
+
+static inline u32 xgene_clk_read(void *csr)
+{
+ return readl_relaxed(csr);
+}
+
+static inline void xgene_clk_write(u32 data, void *csr)
+{
+ return writel_relaxed(data, csr);
+}
+
+/* PLL Clock */
+enum xgene_pll_type {
+ PLL_TYPE_PCP = 0,
+ PLL_TYPE_SOC = 1,
+};
+
+struct xgene_clk_pll {
+ struct clk_hw hw;
+ const char *name;
+ void __iomem *reg;
+ spinlock_t *lock;
+ u32 pll_offset;
+ enum xgene_pll_type type;
+};
+
+#define to_xgene_clk_pll(_hw) container_of(_hw, struct xgene_clk_pll, hw)
+
+static int xgene_clk_pll_is_enabled(struct clk_hw *hw)
+{
+ struct xgene_clk_pll *pllclk = to_xgene_clk_pll(hw);
+ u32 data;
+
+ data = xgene_clk_read(pllclk->reg + pllclk->pll_offset);
+ pr_debug("%s pll %s\n", pllclk->name,
+ data & REGSPEC_RESET_F1_MASK ? "disabled" : "enabled");
+
+ return data & REGSPEC_RESET_F1_MASK ? 0 : 1;
+}
+
+static unsigned long xgene_clk_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct xgene_clk_pll *pllclk = to_xgene_clk_pll(hw);
+ unsigned long fref;
+ unsigned long fvco;
+ u32 pll;
+ u32 nref;
+ u32 nout;
+ u32 nfb;
+
+ pll = xgene_clk_read(pllclk->reg + pllclk->pll_offset);
+
+ if (pllclk->type == PLL_TYPE_PCP) {
+ /*
+ * PLL VCO = Reference clock * NF
+ * PCP PLL = PLL_VCO / 2
+ */
+ nout = 2;
+ fvco = parent_rate * (N_DIV_RD(pll) + 4);
+ } else {
+ /*
+ * Fref = Reference Clock / NREF;
+ * Fvco = Fref * NFB;
+ * Fout = Fvco / NOUT;
+ */
+ nref = CLKR_RD(pll) + 1;
+ nout = CLKOD_RD(pll) + 1;
+ nfb = CLKF_RD(pll);
+ fref = parent_rate / nref;
+ fvco = fref * nfb;
+ }
+ pr_debug("%s pll recalc rate %ld parent %ld\n", pllclk->name,
+ fvco / nout, parent_rate);
+
+ return fvco / nout;
+}
+
+const struct clk_ops xgene_clk_pll_ops = {
+ .is_enabled = xgene_clk_pll_is_enabled,
+ .recalc_rate = xgene_clk_pll_recalc_rate,
+};
+
+static struct clk *xgene_register_clk_pll(struct device *dev,
+ const char *name, const char *parent_name,
+ unsigned long flags, void __iomem *reg, u32 pll_offset,
+ u32 type, spinlock_t *lock)
+{
+ struct xgene_clk_pll *apmclk;
+ struct clk *clk;
+ struct clk_init_data init;
+
+ /* allocate the APM clock structure */
+ apmclk = kzalloc(sizeof(*apmclk), GFP_KERNEL);
+ if (!apmclk) {
+ pr_err("%s: could not allocate APM clk\n", __func__);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ init.name = name;
+ init.ops = &xgene_clk_pll_ops;
+ init.flags = flags;
+ init.parent_names = parent_name ? &parent_name : NULL;
+ init.num_parents = parent_name ? 1 : 0;
+
+ apmclk->name = name;
+ apmclk->reg = reg;
+ apmclk->lock = lock;
+ apmclk->pll_offset = pll_offset;
+ apmclk->type = type;
+ apmclk->hw.init = &init;
+
+ /* Register the clock */
+ clk = clk_register(dev, &apmclk->hw);
+ if (IS_ERR(clk)) {
+ pr_err("%s: could not register clk %s\n", __func__, name);
+ kfree(apmclk);
+ return NULL;
+ }
+ return clk;
+}
+
+static void xgene_pllclk_init(struct device_node *np, enum xgene_pll_type pll_type)
+{
+ const char *clk_name = np->full_name;
+ struct clk *clk;
+ void *reg;
+
+ reg = of_iomap(np, 0);
+ if (reg == NULL) {
+ pr_err("Unable to map CSR register for %s\n", np->full_name);
+ return;
+ }
+ of_property_read_string(np, "clock-output-names", &clk_name);
+ clk = xgene_register_clk_pll(NULL,
+ clk_name, of_clk_get_parent_name(np, 0),
+ CLK_IS_ROOT, reg, 0, pll_type, &clk_lock);
+ if (!IS_ERR(clk)) {
+ of_clk_add_provider(np, of_clk_src_simple_get, clk);
+ clk_register_clkdev(clk, clk_name, NULL);
+ pr_debug("Add %s clock PLL\n", clk_name);
+ }
+}
+
+static void xgene_socpllclk_init(struct device_node *np)
+{
+ xgene_pllclk_init(np, PLL_TYPE_SOC);
+}
+
+static void xgene_pcppllclk_init(struct device_node *np)
+{
+ xgene_pllclk_init(np, PLL_TYPE_PCP);
+}
+
+/* IP Clock */
+struct xgene_dev_parameters {
+ void __iomem *csr_reg; /* CSR for IP clock */
+ u32 reg_clk_offset; /* Offset to clock enable CSR */
+ u32 reg_clk_mask; /* Mask bit for clock enable */
+ u32 reg_csr_offset; /* Offset to CSR reset */
+ u32 reg_csr_mask; /* Mask bit for disable CSR reset */
+ void __iomem *divider_reg; /* CSR for divider */
+ u32 reg_divider_offset; /* Offset to divider register */
+ u32 reg_divider_shift; /* Bit shift to divider field */
+ u32 reg_divider_width; /* Width of the bit to divider field */
+};
+
+struct xgene_clk {
+ struct clk_hw hw;
+ const char *name;
+ spinlock_t *lock;
+ struct xgene_dev_parameters param;
+};
+
+#define to_xgene_clk(_hw) container_of(_hw, struct xgene_clk, hw)
+
+static int xgene_clk_enable(struct clk_hw *hw)
+{
+ struct xgene_clk *pclk = to_xgene_clk(hw);
+ unsigned long flags = 0;
+ u32 data;
+
+ if (pclk->lock)
+ spin_lock_irqsave(pclk->lock, flags);
+
+ if (pclk->param.csr_reg != NULL) {
+ pr_debug("%s clock enabled\n", pclk->name);
+ /* First enable the clock */
+ data = xgene_clk_read(pclk->param.csr_reg +
+ pclk->param.reg_clk_offset);
+ data |= pclk->param.reg_clk_mask;
+ xgene_clk_write(data, pclk->param.csr_reg +
+ pclk->param.reg_clk_offset);
+ pr_debug("%s clock PADDR base 0x%016LX clk offset 0x%08X mask 0x%08X value 0x%08X\n",
+ pclk->name, __pa(pclk->param.csr_reg),
+ pclk->param.reg_clk_offset, pclk->param.reg_clk_mask,
+ data);
+
+ /* Second enable the CSR */
+ data = xgene_clk_read(pclk->param.csr_reg +
+ pclk->param.reg_csr_offset);
+ data &= ~pclk->param.reg_csr_mask;
+ xgene_clk_write(data, pclk->param.csr_reg +
+ pclk->param.reg_csr_offset);
+ pr_debug("%s CSR RESET PADDR base 0x%016LX csr offset 0x%08X mask 0x%08X value 0x%08X\n",
+ pclk->name, __pa(pclk->param.csr_reg),
+ pclk->param.reg_csr_offset, pclk->param.reg_csr_mask,
+ data);
+ }
+
+ if (pclk->lock)
+ spin_unlock_irqrestore(pclk->lock, flags);
+
+ return 0;
+}
+
+static void xgene_clk_disable(struct clk_hw *hw)
+{
+ struct xgene_clk *pclk = to_xgene_clk(hw);
+ unsigned long flags = 0;
+ u32 data;
+
+ if (pclk->lock)
+ spin_lock_irqsave(pclk->lock, flags);
+
+ if (pclk->param.csr_reg != NULL) {
+ pr_debug("%s clock disabled\n", pclk->name);
+ /* First put the CSR in reset */
+ data = xgene_clk_read(pclk->param.csr_reg +
+ pclk->param.reg_csr_offset);
+ data |= pclk->param.reg_csr_mask;
+ xgene_clk_write(data, pclk->param.csr_reg +
+ pclk->param.reg_csr_offset);
+
+ /* Second disable the clock */
+ data = xgene_clk_read(pclk->param.csr_reg +
+ pclk->param.reg_clk_offset);
+ data &= ~pclk->param.reg_clk_mask;
+ xgene_clk_write(data, pclk->param.csr_reg +
+ pclk->param.reg_clk_offset);
+ }
+
+ if (pclk->lock)
+ spin_unlock_irqrestore(pclk->lock, flags);
+}
+
+static int xgene_clk_is_enabled(struct clk_hw *hw)
+{
+ struct xgene_clk *pclk = to_xgene_clk(hw);
+ u32 data = 0;
+
+ if (pclk->param.csr_reg != NULL) {
+ pr_debug("%s clock checking\n", pclk->name);
+ data = xgene_clk_read(pclk->param.csr_reg +
+ pclk->param.reg_clk_offset);
+ pr_debug("%s clock is %s\n", pclk->name,
+ data & pclk->param.reg_clk_mask ? "enabled" :
+ "disabled");
+ }
+
+ if (pclk->param.csr_reg == NULL)
+ return 1;
+ return data & pclk->param.reg_clk_mask ? 1 : 0;
+}
+
+static unsigned long xgene_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct xgene_clk *pclk = to_xgene_clk(hw);
+ u32 data;
+
+ if (pclk->param.divider_reg) {
+ data = xgene_clk_read(pclk->param.divider_reg +
+ pclk->param.reg_divider_offset);
+ data >>= pclk->param.reg_divider_shift;
+ data &= (1 << pclk->param.reg_divider_width) - 1;
+
+ pr_debug("%s clock recalc rate %ld parent %ld\n",
+ pclk->name, parent_rate / data, parent_rate);
+ return parent_rate / data;
+ } else {
+ pr_debug("%s clock recalc rate %ld parent %ld\n",
+ pclk->name, parent_rate, parent_rate);
+ return parent_rate;
+ }
+}
+
+static int xgene_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct xgene_clk *pclk = to_xgene_clk(hw);
+ unsigned long flags = 0;
+ u32 data;
+ u32 divider;
+ u32 divider_save;
+
+ if (pclk->lock)
+ spin_lock_irqsave(pclk->lock, flags);
+
+ if (pclk->param.divider_reg) {
+ /* Let's compute the divider */
+ if (rate > parent_rate)
+ rate = parent_rate;
+ divider_save = divider = parent_rate / rate; /* Rounded down */
+ divider &= (1 << pclk->param.reg_divider_width) - 1;
+ divider <<= pclk->param.reg_divider_shift;
+
+ /* Set new divider */
+ data = xgene_clk_read(pclk->param.divider_reg +
+ pclk->param.reg_divider_offset);
+ data &= ~((1 << pclk->param.reg_divider_width) - 1);
+ data |= divider;
+ xgene_clk_write(data, pclk->param.divider_reg +
+ pclk->param.reg_divider_offset);
+ pr_debug("%s clock set rate %ld\n", pclk->name,
+ parent_rate / divider_save);
+ } else {
+ divider_save = 1;
+ }
+
+ if (pclk->lock)
+ spin_unlock_irqrestore(pclk->lock, flags);
+
+ return parent_rate / divider_save;
+}
+
+static long xgene_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *prate)
+{
+ struct xgene_clk *pclk = to_xgene_clk(hw);
+ unsigned long parent_rate = *prate;
+ u32 divider;
+
+ if (pclk->param.divider_reg) {
+ /* Let's compute the divider */
+ if (rate > parent_rate)
+ rate = parent_rate;
+ divider = parent_rate / rate; /* Rounded down */
+ } else {
+ divider = 1;
+ }
+
+ return parent_rate / divider;
+}
+
+const struct clk_ops xgene_clk_ops = {
+ .enable = xgene_clk_enable,
+ .disable = xgene_clk_disable,
+ .is_enabled = xgene_clk_is_enabled,
+ .recalc_rate = xgene_clk_recalc_rate,
+ .set_rate = xgene_clk_set_rate,
+ .round_rate = xgene_clk_round_rate,
+};
+
+static struct clk *xgene_register_clk(struct device *dev,
+ const char *name, const char *parent_name,
+ struct xgene_dev_parameters *parameters, spinlock_t *lock)
+{
+ struct xgene_clk *apmclk;
+ struct clk *clk;
+ struct clk_init_data init;
+ int rc;
+
+ /* allocate the APM clock structure */
+ apmclk = kzalloc(sizeof(*apmclk), GFP_KERNEL);
+ if (!apmclk) {
+ pr_err("%s: could not allocate APM clk\n", __func__);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ init.name = name;
+ init.ops = &xgene_clk_ops;
+ init.flags = 0;
+ init.parent_names = parent_name ? &parent_name : NULL;
+ init.num_parents = parent_name ? 1 : 0;
+
+ apmclk->name = name;
+ apmclk->lock = lock;
+ apmclk->hw.init = &init;
+ apmclk->param = *parameters;
+
+ /* Register the clock */
+ clk = clk_register(dev, &apmclk->hw);
+ if (IS_ERR(clk)) {
+ pr_err("%s: could not register clk %s\n", __func__, name);
+ kfree(apmclk);
+ return clk;
+ }
+
+ /* Register the clock for lookup */
+ rc = clk_register_clkdev(clk, name, NULL);